From 9d6e8bd52d430d8335c0db07a9b260fb17962c49 Mon Sep 17 00:00:00 2001 From: CM Consultor Date: Sun, 9 Aug 2026 04:19:44 +0000 Subject: [PATCH] fix: correct Streamlit progress values and API docs behind /v1 prefix --- api.py | 4 ++ app.py | 24 +++---- docker-compose.yaml | 166 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 182 insertions(+), 12 deletions(-) create mode 100644 docker-compose.yaml diff --git a/api.py b/api.py index 78d593f..e657216 100644 --- a/api.py +++ b/api.py @@ -13,6 +13,7 @@ Uso: """ import io +import os import zipfile from typing import List @@ -31,6 +32,9 @@ app = FastAPI( title=API_TITLE, version=API_VERSION, description=API_DESCRIPTION, + # A API é publicada externamente atrás de /v1 pelo Traefik. + # root_path corrige as URLs do Swagger/ReDoc/OpenAPI nesse cenário. + root_path=os.getenv("API_ROOT_PATH", ""), docs_url="/docs", redoc_url="/redoc", ) diff --git a/app.py b/app.py index 0467341..650118a 100644 --- a/app.py +++ b/app.py @@ -146,18 +146,18 @@ try: results = [] if operation == "📦 Comprimir PDF": - progress.progress(10, "Comprimindo...") + progress.progress(0.1, "Comprimindo...") for i, (fname, fobj) in enumerate(files_data.items()): result_data = engine.compress(fobj["data"], preset=extra_opts["preset"]) orig_mb = fobj["info"]["file_size_mb"] new_mb = round(len(result_data) / (1024 * 1024), 2) reduction = round((1 - (new_mb / orig_mb)) * 100, 1) if orig_mb > 0 else 0 results.append((fname.replace(".pdf", f"_comprimido.pdf"), result_data)) - progress.progress((i + 1) / len(files_data) * 100, + progress.progress((i + 1) / len(files_data), f"{fname}: {orig_mb}MB → {new_mb}MB ({reduction}% redução)") elif operation == "📄 PDF/A → PDF comum": - progress.progress(10, "Convertendo PDF/A...") + progress.progress(0.1, "Convertendo PDF/A...") for i, (fname, fobj) in enumerate(files_data.items()): if fobj["info"].get("is_pdfa"): result_data = engine.pdfa_to_regular(fobj["data"]) @@ -165,10 +165,10 @@ try: else: st.warning(f"{fname} não é PDF/A. Mantido original.") results.append((fname, fobj["data"])) - progress.progress((i + 1) / len(files_data) * 100) + progress.progress((i + 1) / len(files_data)) elif operation == "📋 Comprimir (mantendo PDF/A)": - progress.progress(10, "Comprimindo PDF/A...") + progress.progress(0.1, "Comprimindo PDF/A...") q = QUALITY_PRESETS[extra_opts["preset"]]["quality"] for i, (fname, fobj) in enumerate(files_data.items()): if fobj["info"].get("is_pdfa"): @@ -179,14 +179,14 @@ try: orig_mb = fobj["info"]["file_size_mb"] new_mb = round(len(result_data) / (1024 * 1024), 2) results.append((fname.replace(".pdf", f"_otimizado.pdf"), result_data)) - progress.progress((i + 1) / len(files_data) * 100) + progress.progress((i + 1) / len(files_data)) elif operation == "🔗 Merge (juntar PDFs)": - progress.progress(30, "Mesclando PDFs...") + progress.progress(0.3, "Mesclando PDFs...") merge_input = [(fobj["data"], fname) for fname, fobj in files_data.items()] result_data = engine.merge(merge_input) results.append(("merged.pdf", result_data)) - progress.progress(100, "Merge concluído!") + progress.progress(1.0, "Merge concluído!") elif operation == "✂️ Split (extrair páginas)": fname = list(files_data.keys())[0] @@ -210,11 +210,11 @@ try: idx = int(part) - 1 ranges.append((idx, idx)) - progress.progress(30, "Extraindo páginas...") + progress.progress(0.3, "Extraindo páginas...") splitted = engine.split(fobj["data"], ranges, fname.replace(".pdf", "")) for nome, data in splitted: results.append((nome, data)) - progress.progress(100, "Split concluído!") + progress.progress(1.0, "Split concluído!") elif operation == "🔄 Rotacionar páginas": fname = list(files_data.keys())[0] @@ -230,10 +230,10 @@ try: pages_list = [p for p in pages_list if 0 <= p < total] rotations = [(p, angle) for p in pages_list] - progress.progress(50, "Rotacionando...") + progress.progress(0.5, "Rotacionando...") result_data = engine.rotate(fobj["data"], rotations) results.append((fname.replace(".pdf", f"_rotacionado.pdf"), result_data)) - progress.progress(100) + progress.progress(1.0) elif operation == "📝 Extrair texto": for fname, fobj in files_data.items(): diff --git a/docker-compose.yaml b/docker-compose.yaml new file mode 100644 index 0000000..f1bfb93 --- /dev/null +++ b/docker-compose.yaml @@ -0,0 +1,166 @@ +x-logging: &default-logging + driver: json-file + options: + max-size: "1m" + max-file: "1" + tag: "{{.Name}}" + +x-common-env: &common-env + TZ: "${TZ:-America/Sao_Paulo}" + +services: + api: + image: ${PDF_TOOLKIT_API_IMAGE} + environment: + <<: *common-env + API_PORT: "${API_PORT:-8555}" + # Prefixo externo usado pelo Traefik (PathPrefix /v1 + strip). + API_ROOT_PATH: "${API_ROOT_PATH:-/v1}" + PDFTOOLKIT_API_KEY: "${PDFTOOLKIT_API_KEY:-}" + cap_drop: + - ALL + cap_add: + - CHOWN + - SETGID + - SETUID + - DAC_OVERRIDE + volumes: + - pdf-toolkit_uploads:/tmp/uploads + networks: + - network_front + healthcheck: + test: ["CMD-SHELL", "wget -q --spider http://127.0.0.1:${API_PORT:-8555}/health || exit 1"] + interval: 30s + timeout: 5s + retries: 3 + start_period: 10s + logging: *default-logging + deploy: + replicas: 1 + placement: + constraints: + - node.hostname == ${NODE_HOSTNAME} + restart_policy: + condition: on-failure + delay: 10s + max_attempts: 5 + window: 120s + update_config: + parallelism: 1 + delay: 10s + order: stop-first + failure_action: rollback + monitor: 90s + rollback_config: + parallelism: 1 + order: stop-first + monitor: 90s + resources: + reservations: + cpus: "0.25" + memory: 256M + limits: + cpus: "1.00" + memory: 1024M + labels: + - loki_enable=true + - traefik.enable=true + - traefik.docker.network=network_front + - traefik.http.routers.${APP_NAME}-api.rule=Host(`${APP_NAME}.${DOMAIN_NAME}`) && PathPrefix(`/v1`) + - traefik.http.routers.${APP_NAME}-api.priority=100 + - traefik.http.routers.${APP_NAME}-api.entrypoints=web,websecure + - traefik.http.routers.${APP_NAME}-api.tls=true + - traefik.http.routers.${APP_NAME}-api.tls.certresolver=${CERT_RESOLVER} + - traefik.http.routers.${APP_NAME}-api.service=${APP_NAME}-api + - traefik.http.services.${APP_NAME}-api.loadbalancer.server.port=${API_PORT:-8555} + - traefik.http.services.${APP_NAME}-api.loadbalancer.passhostheader=true + - traefik.http.middlewares.${APP_NAME}-api-strip.stripprefix.prefixes=/v1 + - traefik.http.middlewares.${APP_NAME}-api-compress.compress=true + - traefik.http.middlewares.${APP_NAME}-api-security.headers.contenttypenosniff=true + - traefik.http.middlewares.${APP_NAME}-api-security.headers.referrerpolicy=strict-origin-when-cross-origin + - traefik.http.middlewares.${APP_NAME}-api-security.headers.stsseconds=31536000 + - traefik.http.middlewares.${APP_NAME}-api-security.headers.stsincludesubdomains=true + - traefik.http.middlewares.${APP_NAME}-api-security.headers.stspreload=true + - traefik.http.middlewares.${APP_NAME}-api-security.headers.customresponseheaders.Server= + - traefik.http.routers.${APP_NAME}-api.middlewares=${APP_NAME}-api-strip,${APP_NAME}-api-compress,${APP_NAME}-api-security + + web: + image: ${PDF_TOOLKIT_WEB_IMAGE} + environment: + <<: *common-env + WEB_PORT: "${WEB_PORT:-8550}" + API_BASE_URL: "http://api:${API_PORT:-8555}" + cap_drop: + - ALL + cap_add: + - CHOWN + - SETGID + - SETUID + - DAC_OVERRIDE + volumes: + - pdf-toolkit_uploads:/tmp/uploads + networks: + - network_front + healthcheck: + test: ["CMD-SHELL", "wget -q --spider http://127.0.0.1:${WEB_PORT:-8550}/ || exit 1"] + interval: 30s + timeout: 5s + retries: 3 + start_period: 10s + logging: *default-logging + deploy: + replicas: 1 + placement: + constraints: + - node.hostname == ${NODE_HOSTNAME} + restart_policy: + condition: on-failure + delay: 10s + max_attempts: 5 + window: 120s + update_config: + parallelism: 1 + delay: 10s + order: stop-first + failure_action: rollback + monitor: 90s + rollback_config: + parallelism: 1 + order: stop-first + monitor: 90s + resources: + reservations: + cpus: "0.25" + memory: 256M + limits: + cpus: "1.00" + memory: 1024M + labels: + - loki_enable=true + - traefik.enable=true + - traefik.docker.network=network_front + - traefik.http.routers.${APP_NAME}.rule=Host(`${APP_NAME}.${DOMAIN_NAME}`) + - traefik.http.routers.${APP_NAME}.entrypoints=web,websecure + - traefik.http.routers.${APP_NAME}.tls=true + - traefik.http.routers.${APP_NAME}.tls.certresolver=${CERT_RESOLVER} + - traefik.http.routers.${APP_NAME}.service=${APP_NAME} + - traefik.http.services.${APP_NAME}.loadbalancer.server.port=${WEB_PORT:-8550} + - traefik.http.services.${APP_NAME}.loadbalancer.passhostheader=true + - traefik.http.middlewares.${APP_NAME}-compress.compress=true + - traefik.http.middlewares.${APP_NAME}-security.headers.contenttypenosniff=true + - traefik.http.middlewares.${APP_NAME}-security.headers.referrerpolicy=strict-origin-when-cross-origin + - traefik.http.middlewares.${APP_NAME}-security.headers.stsseconds=31536000 + - traefik.http.middlewares.${APP_NAME}-security.headers.stsincludesubdomains=true + - traefik.http.middlewares.${APP_NAME}-security.headers.stspreload=true + - traefik.http.middlewares.${APP_NAME}-security.headers.customresponseheaders.Server= + - traefik.http.routers.${APP_NAME}.middlewares=${APP_NAME}-compress,${APP_NAME}-security + +networks: + network_front: + external: true + name: network_front + +volumes: + pdf-toolkit_uploads: + external: true + name: pdf-toolkit_uploads \ No newline at end of file