Template
Modelo do encurtador em FastAPI
Gerado a partir de deploy-templates/fastapi (dado.cloud ab2f4c0).
This commit is contained in:
@@ -7,6 +7,7 @@ import html
|
||||
import json
|
||||
import os
|
||||
import platform
|
||||
import re
|
||||
import time
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from pathlib import Path
|
||||
@@ -15,7 +16,7 @@ import fastapi
|
||||
from fastapi import FastAPI, Request
|
||||
from fastapi.responses import HTMLResponse, JSONResponse, RedirectResponse, Response
|
||||
|
||||
from links import FORMATO_CODIGO, ErroDeValidacao, criar_link, listar_links, registrar_clique
|
||||
from links import FORMATO_CODIGO, ErroDeValidacao, buscar_link, criar_link, host_do_link, listar_links, registrar_clique
|
||||
|
||||
# Variável de ambiente de exemplo: na Dado, ela se configura no painel do app.
|
||||
MENSAGEM = os.environ.get("MENSAGEM_BOAS_VINDAS") or "Defina a variável MENSAGEM_BOAS_VINDAS para trocar este texto."
|
||||
@@ -36,6 +37,19 @@ PAGINA_INICIAL = (
|
||||
.replace("{{MENSAGEM}}", html.escape(MENSAGEM))
|
||||
)
|
||||
PAGINA_NAO_ENCONTRADA = (PASTA_PAGINAS / "nao-encontrado.html").read_text(encoding="utf-8")
|
||||
MODELO_AVISO = (PASTA_PAGINAS / "aviso.html").read_text(encoding="utf-8")
|
||||
# A página de aviso não roda script nenhum: o navegador só aceita o CSS dela.
|
||||
CSP_AVISO = "default-src 'none'; style-src 'unsafe-inline'; img-src data:; frame-ancestors 'none'; base-uri 'none'; form-action 'none'"
|
||||
MARCADORES_AVISO = re.compile(r"\{\{(HOST|URL|CODIGO)\}\}")
|
||||
|
||||
|
||||
def pagina_aviso(link: dict) -> str:
|
||||
"""Página de aviso antes de sair: mostra para onde o link leva. Todo valor é
|
||||
escapado (o link foi digitado por alguém) e entra numa passada só, para um
|
||||
valor nunca ser lido como outro marcador."""
|
||||
valores = {"HOST": host_do_link(link["url"]), "URL": link["url"], "CODIGO": link["codigo"]}
|
||||
return MARCADORES_AVISO.sub(lambda m: html.escape(valores[m.group(1)]), MODELO_AVISO)
|
||||
|
||||
|
||||
# Freio simples contra abuso: no máximo 30 links novos a cada 10 minutos por IP.
|
||||
LIMITE_POR_IP = 30
|
||||
@@ -171,14 +185,36 @@ async def criar(request: Request):
|
||||
return JSONResponse(status_code=201, content=para_json(link, request))
|
||||
|
||||
|
||||
# O redirecionamento é feito pelo servidor: GET /<código> → 302 para o link.
|
||||
# Fica por último: as rotas fixas acima têm prioridade.
|
||||
# GET /<código> não redireciona direto: mostra antes uma página de aviso com o
|
||||
# destino. Sem ela, qualquer pessoa usaria o seu endereço para disfarçar um
|
||||
# link de golpe. A página não conta clique — quem só olha não foi ao destino.
|
||||
# Estas rotas ficam por último: as rotas fixas acima têm prioridade.
|
||||
@app.get("/{codigo}", include_in_schema=False)
|
||||
def aviso(codigo: str):
|
||||
link = buscar_link(codigo) if FORMATO_CODIGO.match(codigo) else None
|
||||
if link is None:
|
||||
return HTMLResponse(PAGINA_NAO_ENCONTRADA, status_code=404)
|
||||
return HTMLResponse(
|
||||
pagina_aviso(link),
|
||||
headers={
|
||||
"Cache-Control": "no-store",
|
||||
"Referrer-Policy": "no-referrer",
|
||||
"X-Robots-Tag": "noindex, nofollow",
|
||||
"Content-Security-Policy": CSP_AVISO,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
# O botão "Continuar" leva para GET /<código>/ir: aqui o servidor soma o
|
||||
# clique e redireciona (302) para o link.
|
||||
@app.get("/{codigo}/ir", include_in_schema=False)
|
||||
def redirecionar(codigo: str):
|
||||
link = registrar_clique(codigo) if FORMATO_CODIGO.match(codigo) else None
|
||||
if link is None:
|
||||
return HTMLResponse(PAGINA_NAO_ENCONTRADA, status_code=404)
|
||||
return RedirectResponse(link["url"], status_code=302, headers={"Cache-Control": "no-store"})
|
||||
return RedirectResponse(
|
||||
link["url"], status_code=302, headers={"Cache-Control": "no-store", "Referrer-Policy": "no-referrer"}
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
Reference in New Issue
Block a user