Template
Modelo do encurtador em FastAPI
Gerado a partir de deploy-templates/fastapi (dado.cloud 4c2a882).
This commit is contained in:
@@ -0,0 +1,78 @@
|
||||
"""Guarda dos links encurtados.
|
||||
|
||||
Os links ficam num dicionário na memória do processo: simples e sem banco, mas
|
||||
somem toda vez que o app reinicia ou é publicado de novo. Para guardar de vez,
|
||||
troque este arquivo por consultas a um Postgres.
|
||||
"""
|
||||
|
||||
import re
|
||||
import secrets
|
||||
from datetime import datetime, timezone
|
||||
from urllib.parse import urlsplit
|
||||
|
||||
ALFABETO = "abcdefghijklmnopqrstuvwxyz0123456789"
|
||||
TAMANHO_CODIGO = 6
|
||||
TAMANHO_MAXIMO_URL = 2048
|
||||
# Teto de links guardados: passou disso, o mais antigo sai (protege a memória).
|
||||
MAXIMO_DE_LINKS = 1000
|
||||
LINKS_NA_LISTA = 20
|
||||
# Caminhos do próprio app que nunca podem virar código.
|
||||
RESERVADOS = {"api", "docs"}
|
||||
|
||||
FORMATO_CODIGO = re.compile(r"^[a-z0-9]{6}$")
|
||||
|
||||
# Dicionário do Python mantém a ordem de inserção: o primeiro é o mais antigo.
|
||||
_links: dict[str, dict] = {}
|
||||
|
||||
|
||||
class ErroDeValidacao(ValueError):
|
||||
pass
|
||||
|
||||
|
||||
def _gerar_codigo() -> str:
|
||||
while True:
|
||||
codigo = "".join(secrets.choice(ALFABETO) for _ in range(TAMANHO_CODIGO))
|
||||
if codigo not in _links and codigo not in RESERVADOS:
|
||||
return codigo
|
||||
|
||||
|
||||
def _validar_url(valor) -> str:
|
||||
if not isinstance(valor, str) or not valor.strip():
|
||||
raise ErroDeValidacao('Envie o campo "url" com o link que você quer encurtar.')
|
||||
texto = valor.strip()
|
||||
if len(texto) > TAMANHO_MAXIMO_URL:
|
||||
raise ErroDeValidacao(f"Esse link é comprido demais (máximo de {TAMANHO_MAXIMO_URL} caracteres).")
|
||||
try:
|
||||
partes = urlsplit(texto)
|
||||
except ValueError:
|
||||
partes = None
|
||||
if not partes or partes.scheme not in ("http", "https") or not partes.hostname or any(c.isspace() for c in texto):
|
||||
raise ErroDeValidacao("Esse não parece um link válido. Use um endereço que comece com http:// ou https://.")
|
||||
return texto
|
||||
|
||||
|
||||
def criar_link(valor) -> dict:
|
||||
link = {
|
||||
"codigo": _gerar_codigo(),
|
||||
"url": _validar_url(valor),
|
||||
"cliques": 0,
|
||||
"criadoEm": datetime.now(timezone.utc).isoformat(timespec="milliseconds").replace("+00:00", "Z"),
|
||||
}
|
||||
_links[link["codigo"]] = link
|
||||
if len(_links) > MAXIMO_DE_LINKS:
|
||||
del _links[next(iter(_links))]
|
||||
return link
|
||||
|
||||
|
||||
def listar_links() -> list[dict]:
|
||||
"""Os últimos links criados, do mais novo para o mais antigo."""
|
||||
return list(reversed(list(_links.values())[-LINKS_NA_LISTA:]))
|
||||
|
||||
|
||||
def registrar_clique(codigo: str) -> dict | None:
|
||||
"""Soma um clique e devolve o link, ou None se o código não existe."""
|
||||
link = _links.get(codigo)
|
||||
if link is None:
|
||||
return None
|
||||
link["cliques"] += 1
|
||||
return link
|
||||
Reference in New Issue
Block a user