
Plataforma comunitária que liga quem tem telhas (oferta) a quem precisa (procura) após a tempestade Kristin em Leiria e Marinha Grande, Portugal.
Abrir a app16 de fevereiro de 2025
O SOS Telha é uma rede de apoio à reconstrução desenvolvida pro-bono pela KORVO em resposta à devastação causada pela Tempestade Kristin no distrito de Leiria e Marinha Grande, Portugal.
Os utilizadores publicam Oferta (tenho telhas) ou Procura (preciso de telhas), com opção de mão de obra (preciso de ajuda para colocar telhas). Tudo é mostrado num mapa interativo para que as pessoas vejam o que está perto e evitem deslocações desnecessárias.
Mapa e lista do SOS Telha
Vista mobile de criar pedido — formulário
Parte 2 — seleção de localização estilo Uber
Residentes e empresas na área de Leiria/Marinha Grande afetados pela Tempestade Kristin que tenham telhas sobradas para dar ou precisem de telhas ou mão de obra para reconstruir.
Após uma calamidade, fazer corresponder oferta e procura localmente acelera a reconstrução e reduz desperdício. O mapa e os filtros facilitam encontrar ou oferecer ajuda perto de si.
Os posts de telha são carregados através de uma server action do Next.js: "use server" faz correr a função no servidor, consultamos o Supabase pelos registos mais recentes e mapeamo-los para o tipo TilePost. Um ficheiro que mostra o fluxo completo da base de dados à UI.
// app/actions/tile-posts.ts
"use server";
import { createClient } from "@/lib/supabase/server";
import { rowToTilePost } from "@/lib/tile-posts";
import type { TilePost } from "@/lib/types";
export async function getTilePosts(): Promise<TilePost[]> {
const supabase = await createClient();
const { data, error } = await supabase
.from("tile_posts")
.select(
"id, tile_type, quantity, tile_type_other, tile_items, lat, lng, " +
"post_type, contact, status, logistics, created_at, description, needs_mao_de_obra"
)
.order("created_at", { ascending: false });
if (error) {
console.error("getTilePosts error:", error);
return [];
}
return (data ?? []).map(rowToTilePost);
}
Não quisemos obrigar ninguém a criar conta para publicar ou editar. Por isso usamos um sistema de creator token em cookie: sem login, mas sabemos quais posts pertencem a qual browser.
Quando alguém publica um post, geramos um segredo aleatório (ex. um UUID), guardamo-lo na linha na base de dados e guardamos o mapeamento id do post → token num cookie httpOnly. Só o servidor o consegue ler, por isso o browser não pode ser enganado para o enviar para outro site. Sem emails nem passwords—apenas “estes post IDs foram criados neste browser.”
Quando voltam para marcar um post como concluído, atualizar quantidade ou apagar, o servidor lê o cookie, procura o token desse post e compara com creator_token na base de dados. Se coincidir, permitimos a atualização (usando o client service-role, porque o RLS bloqueia updates anónimos). Assim as pessoas podem publicar e gerir os seus anúncios sem conta; só o browser que criou o post pode alterá-lo ou apagá-lo.
Guardar o token ao criar:
// Ao criar: um token secreto por post, guardado na DB e num cookie httpOnly
const token = crypto.randomUUID();
await supabase.from("tile_posts").insert({
// ...tile_type, quantity, lat, lng, etc.
creator_token: token,
}).select("id").single();
const existing = await getCreatorTokensCookie();
await setCreatorTokensCookie({
...existing,
[id]: token, // postId → token para este browser poder editar este post depois
});
Verificar antes de permitir editar ou apagar:
// Antes de qualquer update/delete: token no cookie para este post === creator_token na DB
const cookieTokens = await getCreatorTokensCookie();
const token = cookieTokens[postId];
if (!token) {
return { success: false, error: "Só o autor pode marcar como concluído." };
}
const supabase = await createClient();
const { data: post } = await supabase
.from("tile_posts")
.select("creator_token")
.eq("id", postId)
.single();
if (!post || post.creator_token !== token) {
return { success: false, error: "Só o autor pode marcar como concluído." };
}
// Verificado: mesmo browser que criou o post. Usar client admin para mutar (RLS bloqueia updates anónimos).
const admin = createAdminClient();
await admin.from("tile_posts").update({ status: "claimed" }).eq("id", postId);
Desenvolvido pela KORVO como projeto de paixão para ligar comunidades e apoiar a reconstrução após a Tempestade Kristin.