Files
dtf-system/app/app.py
Cauê Faleiros 352590e63a
All checks were successful
Build and deploy / Validate source (push) Successful in 1m25s
Build and deploy / Integration suite on a real stack (push) Successful in 3m16s
Build and deploy / Secret scan and release gate (push) Successful in 13s
Build and deploy / Publish images (push) Successful in 1m29s
feat: Kanban shows each order's artwork, filters quotes and searches orders and quotes
The customer's browser sends the small picture it already makes of each file
(at most 300 KB, WebP/JPEG/PNG read from the bytes, own uploads only); it goes
when the file's bytes go. Board cards, quote rows, the order panel and the
quote detail show it, with the layout drawing as a second view and as the
fallback for files without a picture.

Cotações gets a customer filter (e-mail, CNPJ, WhatsApp) and product, layout
and resolution-warning chips, with the page bar always shown. The board adds
Entrega, Retirada and stalled-order chips. The top search now covers orders in
any stage and unpaid quotes; CNPJ and phone match by digits only when nothing
but digits and punctuation was typed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 11:46:33 -03:00

50 lines
2.1 KiB
Python

"""The DTF Portal/API service: assembly only.
Configuration and shared helpers are in local.runtime; every route lives in a
router under local.api. This module creates the application, applies the
cross-cutting middleware, and includes them.
"""
from contextlib import asynccontextmanager
from fastapi import FastAPI
from fastapi.responses import JSONResponse
from starlette.middleware.trustedhost import TrustedHostMiddleware
from .core import db
from .core.auth import audit, client_ip
from .runtime import ALLOWED_HOSTS, ALLOWED_ORIGINS, storage
from .api import artwork, customer, health, operator, orders, payments, quotes, uploads
@asynccontextmanager
async def lifespan(app):
with db.connect() as c:
c.execute('SELECT 1 FROM dtf_local.operator_sessions LIMIT 1')
storage.health()
yield
app = FastAPI(title='DTF Portal/API', lifespan=lifespan, docs_url=None, redoc_url=None)
app.add_middleware(TrustedHostMiddleware, allowed_hosts=ALLOWED_HOSTS)
@app.middleware('http')
async def safe_headers(request, call_next):
if request.method not in ('GET','HEAD','OPTIONS'):
origin = request.headers.get('origin')
if request.headers.get('sec-fetch-site') == 'cross-site' or (origin and origin not in ALLOWED_ORIGINS):
audit('cross_origin_rejected', ip=client_ip(request))
return JSONResponse({'detail':'Cross-origin request rejected'}, status_code=403)
response = await call_next(request)
if response.status_code in (401,403,429) or response.status_code>=500:
audit('http_security_event', method=request.method, status=response.status_code, ip=client_ip(request))
# Nothing is cached unless a route says so (the Kanban's artwork pictures).
response.headers.setdefault('Cache-Control', 'no-store')
response.headers['X-Content-Type-Options'] = 'nosniff'
response.headers['Referrer-Policy'] = 'no-referrer'
return response
# Order is not significant: no two routers declare the same path.
for module in (health, uploads, quotes, orders, payments, operator, customer, artwork):
app.include_router(module.router)