Modelo do encurtador em Express

Gerado a partir de deploy-templates/express (dado.cloud 4c2a882).
This commit is contained in:
dado.cloud
2026-10-21 12:27:02 +00:00
commit c9e3d2ac7f
8 changed files with 1460 additions and 0 deletions
+69
View File
@@ -0,0 +1,69 @@
import { randomInt } from 'node:crypto';
// Os links ficam num Map 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.
const ALFABETO = 'abcdefghijklmnopqrstuvwxyz0123456789';
const TAMANHO_CODIGO = 6;
const TAMANHO_MAXIMO_URL = 2048;
// Teto de links guardados: passou disso, o mais antigo sai (protege a memória).
const MAXIMO_DE_LINKS = 1000;
const LINKS_NA_LISTA = 20;
// Caminhos do próprio app que nunca podem virar código.
const RESERVADOS = new Set(['api']);
export const FORMATO_CODIGO = /^[a-z0-9]{6}$/;
const links = new Map();
export class ErroDeValidacao extends Error {}
function gerarCodigo() {
for (;;) {
let codigo = '';
for (let i = 0; i < TAMANHO_CODIGO; i += 1) codigo += ALFABETO[randomInt(ALFABETO.length)];
if (!links.has(codigo) && !RESERVADOS.has(codigo)) return codigo;
}
}
function validarUrl(valor) {
if (typeof valor !== 'string' || valor.trim() === '') {
throw new ErroDeValidacao('Envie o campo "url" com o link que você quer encurtar.');
}
const texto = valor.trim();
if (texto.length > TAMANHO_MAXIMO_URL) {
throw new ErroDeValidacao(`Esse link é comprido demais (máximo de ${TAMANHO_MAXIMO_URL} caracteres).`);
}
let url;
try {
url = new URL(texto);
} catch {
url = null;
}
if (!url || (url.protocol !== 'http:' && url.protocol !== 'https:')) {
throw new ErroDeValidacao('Esse não parece um link válido. Use um endereço que comece com http:// ou https://.');
}
return url.href;
}
export function criarLink(valor) {
const url = validarUrl(valor);
const link = { codigo: gerarCodigo(), url, cliques: 0, criadoEm: new Date().toISOString() };
links.set(link.codigo, link);
if (links.size > MAXIMO_DE_LINKS) links.delete(links.keys().next().value);
return link;
}
/** Os últimos links criados, do mais novo para o mais antigo. */
export function listarLinks() {
return [...links.values()].slice(-LINKS_NA_LISTA).reverse();
}
/** Soma um clique e devolve o link, ou null se o código não existe. */
export function registrarClique(codigo) {
const link = links.get(codigo);
if (!link) return null;
link.cliques += 1;
return link;
}