Um projeto de portfólio para aprender desenvolvimento back-end, construindo, travando e resolvendo problemas reais. O backend e toda a lógica foram escritos à mão. O design, o HTML e os efeitos visuais foram gerados com ajuda de IA.
O to-be list é um gerenciador de tarefas simples de propósito. A ideia nunca foi a complexidade da regra de negócio — foi usar uma base simples para estudar a fundo cada camada de uma aplicação web real em produção: autenticação, modelagem de dados, migrações de schema, segurança contra ataques comuns e deploy em infraestrutura de verdade.
Cada decisão técnica relevante aqui — por que
Flask-WTF
em vez de CSRF manual, por que Alembic em vez de recriar tabelas, por que
migrar para PostgreSQL — foi tomada entendendo o porquê, não copiando de
um boilerplate.
Implementei proteção contra CSRF com
Flask-WTF, sem migrar todos os
formulários para classes FlaskForm —
decisão consciente para manter o escopo da tarefa isolado. A parte mais
interessante não foi a proteção em si, mas centralizar a injeção do
token num único script no template base, em vez de repetir em cada
formulário — inclusive os que vêm de partials via
{% include %}.
Validei testando manualmente: alterar o token no DevTools e confirmar
que a requisição cai com
400 Bad Request antes de chegar à
view.
Migrei o banco local para PostgreSQL gerenciado, com troca de driver
(psycopg v3), ajuste de tipos
incompatíveis entre os dois bancos, e remoção do
create_all() em favor do Alembic
como única fonte de verdade do schema. No caminho, diagnostiquei e
corrigi manualmente uma migration corrompida, gerada a partir de um
estado transitório do banco.
Resolvi um bug sutil no Dockerfile: a diferença entre
exec form e shell form do
CMD. A forma array padrão não passa
por uma shell, então a variável
$PORT (injetada dinamicamente pela
hospedagem) nunca era expandida — o servidor sempre subia na porta
errada. A correção combina a sintaxe recomendada com uma shell explícita
por dentro.
Como request.form.get() retorna
string vazia (não None) quando um
campo é deixado em branco — e por que isso quebraria uma constraint de
e-mail único no banco se não fosse tratado. Como
== None não funciona como esperado
numa query do SQLAlchemy, e por que
.isnot(None) é a forma correta.
Construído por Weder Borges
Ver código no GitHub →