This commit is contained in:
@@ -16,7 +16,8 @@ from sqlalchemy import func, select, text
|
||||
from sqlalchemy.exc import SQLAlchemyError
|
||||
from starlette.concurrency import run_in_threadpool
|
||||
from .config import settings
|
||||
from .db import Job, initialize, sessions
|
||||
from .expiry import expiration
|
||||
from .db import Job, initialize, sessions, active_jobs
|
||||
from .storage import storage
|
||||
from .fonts import fonts, stylesheet
|
||||
from .models.api import UploadResponse, RenderStatus, ErrorEnvelope
|
||||
@@ -119,7 +120,7 @@ def authorize(identifier, key, token, *, comparison=False, resource_kind=None):
|
||||
# Private existence and expiry are not disclosed to unauthenticated callers.
|
||||
if job.access == 'private' and not (authenticated(key) or (token and hmac.compare_digest(job.token_hash, digest(token)))):
|
||||
error('not_found', 'Schematic not found', 404)
|
||||
if job.expires_at <= time.time(): error('link_expired', 'This schematic link has expired', 410)
|
||||
if job.expires_at != -1 and job.expires_at <= time.time(): error('link_expired', 'This schematic link has expired', 410)
|
||||
return job
|
||||
|
||||
def public_status(job):
|
||||
@@ -136,22 +137,21 @@ def public_status(job):
|
||||
503: {'description': 'Queue or storage unavailable'}})
|
||||
async def upload(file: Annotated[UploadFile, File(description='Native binary OLE or ASCII .SchDoc')],
|
||||
access: Annotated[Literal['private', 'unlisted'], Form()] = 'private',
|
||||
expires_in: Annotated[int | None, Form(gt=0, description='Link lifetime in seconds, up to configured maximum')] = None,
|
||||
expires_in: Annotated[int | None, Form(ge=-1, description='-1 for no expiry, or positive seconds up to configured maximum')] = None,
|
||||
x_api_key: Annotated[str | None, Header()] = None):
|
||||
require_api(x_api_key)
|
||||
ttl = expires_in or settings().link_ttl
|
||||
if ttl > settings().link_ttl: error('invalid_expiry', 'expires_in exceeds the configured link lifetime', 422)
|
||||
now = time.time()
|
||||
expires_at = expiration(expires_in, now)
|
||||
name, body = await read_schematic(file)
|
||||
identifier = secrets.token_hex(24)
|
||||
token = secrets.token_urlsafe(32)
|
||||
now = time.time()
|
||||
job = Job(id=identifier, filename=name, status='queued', access=access, token_hash=digest(token),
|
||||
created_at=now, updated_at=now, expires_at=now+ttl, attempt=0, input_key=f'{identifier}/input.SchDoc')
|
||||
created_at=now, updated_at=now, expires_at=expires_at, attempt=0, input_key=f'{identifier}/input.SchDoc')
|
||||
def persist():
|
||||
with sessions().begin() as db:
|
||||
# Serialize admission checks across API replicas on PostgreSQL.
|
||||
if db.bind.dialect.name == 'postgresql': db.execute(text('SELECT pg_advisory_xact_lock(84721309)'))
|
||||
pending = db.scalar(select(func.count()).select_from(Job).where(Job.status.in_(['queued','processing']), Job.expires_at > now))
|
||||
pending = db.scalar(select(func.count()).select_from(Job).where(Job.status.in_(['queued','processing']), active_jobs(now)))
|
||||
if pending >= settings().max_pending: error('queue_full', 'Rendering queue is full; try again later', 503)
|
||||
try: storage().put(job.input_key, bytes(body))
|
||||
except Exception: error('storage_unavailable', 'Upload storage unavailable', 503)
|
||||
@@ -203,7 +203,7 @@ def index(): return FileResponse(FRONTEND / 'index.html')
|
||||
|
||||
@app.get('/assets/{name}', include_in_schema=False)
|
||||
def asset(name: str):
|
||||
if name not in {'viewer.js','viewer.css','upload.js','upload.css','camera.js','compare.js','compare.css','compare-upload.js','interactions.js','pcb.js','pcb.css','pcb-layers.js'}: error('not_found', 'Asset not found', 404)
|
||||
if name not in {'viewer.js','viewer.css','upload.js','upload.css','camera.js','compare.js','compare.css','compare-upload.js','interactions.js','pcb.js','pcb.css','pcb-layers.js','share.js','share.css'}: error('not_found', 'Asset not found', 404)
|
||||
return FileResponse(FRONTEND / name)
|
||||
|
||||
@app.get('/docs', include_in_schema=False)
|
||||
|
||||
مرجع در شماره جدید
مسدود کردن کاربر