feat: connect Tiny through its v3 API with OAuth
All checks were successful
Build and deploy / Validate source (push) Successful in 9s
Build and deploy / Integration suite on a real stack (push) Successful in 2m49s
Build and deploy / Secret scan and release gate (push) Successful in 9s
Build and deploy / Publish images and notify Portainer (push) Has been skipped
All checks were successful
Build and deploy / Validate source (push) Successful in 9s
Build and deploy / Integration suite on a real stack (push) Successful in 2m49s
Build and deploy / Secret scan and release gate (push) Successful in 9s
Build and deploy / Publish images and notify Portainer (push) Has been skipped
Tiny v3 replaces the v2 token adapter. An operator connects Tiny once from the Kanban; the callback is authorised by a single-use state, because Tiny's cross-site redirect does not carry the SameSite=Strict operator cookie. Tokens are kept in provider_tokens, the refresh token rotates under a row lock, and the worker keeps the connection alive while order creation is off. Orders find or create the customer's contact by CNPJ, then POST /pedidos with product ids from TINY_PRODUCT_TEXTIL_FOLHA, _TEXTIL_AVULSA, _UV_FOLHA and _UV_AVULSA and numeroOrdemCompra DTF-<number>; a retry searches the customer's recent orders for that number first. The product settings avoid a _FILE suffix, which the secrets loader reads as a secret file path. Production passes the application credentials through but keeps TINY_ADAPTER fake: Tiny has no sandbox, so creating real orders waits for a supervised test. compose.providers.yaml gives the local API and worker an internet route for provider testing; the default local stack still has none. Verified with the full CI integration sequence locally, including the new tiny_oauth_test against the real database. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -21,6 +21,7 @@ if os.environ.get('TINY_ADAPTER') == 'tiny':
|
||||
adapters['tiny'] = TinyOrders()
|
||||
last_tick = 0.0
|
||||
last_cleanup = 0.0
|
||||
last_tiny_keepalive = 0.0
|
||||
storage = LocalS3Storage()
|
||||
scan_thread = None
|
||||
render_thread = None
|
||||
@@ -52,6 +53,27 @@ def tick():
|
||||
c.execute("UPDATE dtf_local.outbox SET attempts=attempts+1, last_error=%s, available_at=now() + %s * interval '1 second' WHERE id=%s", (str(exc),delay,job['id']))
|
||||
last_tick = time.monotonic()
|
||||
|
||||
def tiny_keepalive():
|
||||
"""Keep a connected Tiny authorised even while no orders are sent.
|
||||
|
||||
The refresh token expires unless it is used; access_token() refreshes (and
|
||||
rotates) only when the access token is about to expire, so asking every few
|
||||
minutes renews the connection roughly once per access-token lifetime.
|
||||
"""
|
||||
global last_tiny_keepalive
|
||||
if time.monotonic()-last_tiny_keepalive < 600:
|
||||
return
|
||||
last_tiny_keepalive = time.monotonic()
|
||||
from . import tiny
|
||||
if not tiny.configured():
|
||||
return
|
||||
try:
|
||||
tiny.TinyAuth().access_token()
|
||||
except tiny.TinyNotConnected:
|
||||
pass
|
||||
except Exception:
|
||||
logging.exception('Tiny connection refresh failed')
|
||||
|
||||
def loop():
|
||||
global last_cleanup
|
||||
while True:
|
||||
@@ -60,6 +82,7 @@ def loop():
|
||||
if time.monotonic()-last_cleanup > 60:
|
||||
cleanup()
|
||||
last_cleanup = time.monotonic()
|
||||
tiny_keepalive()
|
||||
except Exception:
|
||||
logging.exception('Local worker tick failed')
|
||||
time.sleep(1)
|
||||
|
||||
Reference in New Issue
Block a user