fix: correct Streamlit progress values and API docs behind /v1 prefix
This commit is contained in:
4
api.py
4
api.py
@@ -13,6 +13,7 @@ Uso:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import io
|
import io
|
||||||
|
import os
|
||||||
import zipfile
|
import zipfile
|
||||||
from typing import List
|
from typing import List
|
||||||
|
|
||||||
@@ -31,6 +32,9 @@ app = FastAPI(
|
|||||||
title=API_TITLE,
|
title=API_TITLE,
|
||||||
version=API_VERSION,
|
version=API_VERSION,
|
||||||
description=API_DESCRIPTION,
|
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",
|
docs_url="/docs",
|
||||||
redoc_url="/redoc",
|
redoc_url="/redoc",
|
||||||
)
|
)
|
||||||
|
|||||||
24
app.py
24
app.py
@@ -146,18 +146,18 @@ try:
|
|||||||
results = []
|
results = []
|
||||||
|
|
||||||
if operation == "📦 Comprimir PDF":
|
if operation == "📦 Comprimir PDF":
|
||||||
progress.progress(10, "Comprimindo...")
|
progress.progress(0.1, "Comprimindo...")
|
||||||
for i, (fname, fobj) in enumerate(files_data.items()):
|
for i, (fname, fobj) in enumerate(files_data.items()):
|
||||||
result_data = engine.compress(fobj["data"], preset=extra_opts["preset"])
|
result_data = engine.compress(fobj["data"], preset=extra_opts["preset"])
|
||||||
orig_mb = fobj["info"]["file_size_mb"]
|
orig_mb = fobj["info"]["file_size_mb"]
|
||||||
new_mb = round(len(result_data) / (1024 * 1024), 2)
|
new_mb = round(len(result_data) / (1024 * 1024), 2)
|
||||||
reduction = round((1 - (new_mb / orig_mb)) * 100, 1) if orig_mb > 0 else 0
|
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))
|
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)")
|
f"{fname}: {orig_mb}MB → {new_mb}MB ({reduction}% redução)")
|
||||||
|
|
||||||
elif operation == "📄 PDF/A → PDF comum":
|
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()):
|
for i, (fname, fobj) in enumerate(files_data.items()):
|
||||||
if fobj["info"].get("is_pdfa"):
|
if fobj["info"].get("is_pdfa"):
|
||||||
result_data = engine.pdfa_to_regular(fobj["data"])
|
result_data = engine.pdfa_to_regular(fobj["data"])
|
||||||
@@ -165,10 +165,10 @@ try:
|
|||||||
else:
|
else:
|
||||||
st.warning(f"{fname} não é PDF/A. Mantido original.")
|
st.warning(f"{fname} não é PDF/A. Mantido original.")
|
||||||
results.append((fname, fobj["data"]))
|
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)":
|
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"]
|
q = QUALITY_PRESETS[extra_opts["preset"]]["quality"]
|
||||||
for i, (fname, fobj) in enumerate(files_data.items()):
|
for i, (fname, fobj) in enumerate(files_data.items()):
|
||||||
if fobj["info"].get("is_pdfa"):
|
if fobj["info"].get("is_pdfa"):
|
||||||
@@ -179,14 +179,14 @@ try:
|
|||||||
orig_mb = fobj["info"]["file_size_mb"]
|
orig_mb = fobj["info"]["file_size_mb"]
|
||||||
new_mb = round(len(result_data) / (1024 * 1024), 2)
|
new_mb = round(len(result_data) / (1024 * 1024), 2)
|
||||||
results.append((fname.replace(".pdf", f"_otimizado.pdf"), result_data))
|
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)":
|
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()]
|
merge_input = [(fobj["data"], fname) for fname, fobj in files_data.items()]
|
||||||
result_data = engine.merge(merge_input)
|
result_data = engine.merge(merge_input)
|
||||||
results.append(("merged.pdf", result_data))
|
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)":
|
elif operation == "✂️ Split (extrair páginas)":
|
||||||
fname = list(files_data.keys())[0]
|
fname = list(files_data.keys())[0]
|
||||||
@@ -210,11 +210,11 @@ try:
|
|||||||
idx = int(part) - 1
|
idx = int(part) - 1
|
||||||
ranges.append((idx, idx))
|
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", ""))
|
splitted = engine.split(fobj["data"], ranges, fname.replace(".pdf", ""))
|
||||||
for nome, data in splitted:
|
for nome, data in splitted:
|
||||||
results.append((nome, data))
|
results.append((nome, data))
|
||||||
progress.progress(100, "Split concluído!")
|
progress.progress(1.0, "Split concluído!")
|
||||||
|
|
||||||
elif operation == "🔄 Rotacionar páginas":
|
elif operation == "🔄 Rotacionar páginas":
|
||||||
fname = list(files_data.keys())[0]
|
fname = list(files_data.keys())[0]
|
||||||
@@ -230,10 +230,10 @@ try:
|
|||||||
pages_list = [p for p in pages_list if 0 <= p < total]
|
pages_list = [p for p in pages_list if 0 <= p < total]
|
||||||
|
|
||||||
rotations = [(p, angle) for p in pages_list]
|
rotations = [(p, angle) for p in pages_list]
|
||||||
progress.progress(50, "Rotacionando...")
|
progress.progress(0.5, "Rotacionando...")
|
||||||
result_data = engine.rotate(fobj["data"], rotations)
|
result_data = engine.rotate(fobj["data"], rotations)
|
||||||
results.append((fname.replace(".pdf", f"_rotacionado.pdf"), result_data))
|
results.append((fname.replace(".pdf", f"_rotacionado.pdf"), result_data))
|
||||||
progress.progress(100)
|
progress.progress(1.0)
|
||||||
|
|
||||||
elif operation == "📝 Extrair texto":
|
elif operation == "📝 Extrair texto":
|
||||||
for fname, fobj in files_data.items():
|
for fname, fobj in files_data.items():
|
||||||
|
|||||||
166
docker-compose.yaml
Normal file
166
docker-compose.yaml
Normal file
@@ -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
|
||||||
Reference in New Issue
Block a user