Protótipo web · PWA responsivo

MeAjuda — documentação

Marketplace que conecta profissionais autônomos da construção e manutenção a ajudantes por diária. Publicar vaga → candidatar → aceitar → conversar → avaliar.

Next.js 15 · App Router Supabase · Postgres + Auth + Realtime TypeScript Tailwind · Poppins Multi-tenant · RLS
Fase: protótipo Idioma do produto: Português (BR) Projeto Supabase: meajudaai-mvp

01 Visão geral

O que é o produto e para quem.

O MeAjuda Aí resolve um encontro do dia a dia da obra: o profissional que precisa de um par de mãos amanhã e o ajudante que procura diária na região. A entrega desta fase é uma aplicação web responsiva (PWA) — abre no navegador do celular e do desktop, sem instalação.

Cada profissional é dono de uma empresa (workspace) com sua equipe; o marketplace de vagas cruza essas empresas com os ajudantes. Tudo é isolado por empresa via Row-Level Security no banco.

🧰 Profissional

Publica vagas de diária, avalia candidatos, seleciona, conversa e avalia. É admin de uma empresa.

👷 Ajudante

Busca vagas na região, candidata-se, combina pelo chat, executa e avalia o profissional.

🛡️ Administrador

Modera denúncias, gerencia usuários e o banner da home. É sysadmin da plataforma.

Há ainda o papel funcionário: subordinado a um profissional, com acesso liberado módulo a módulo pelo dono da empresa.

02 Arquitetura

Next.js no servidor, Supabase como backend gerenciado.

O frontend e a lógica de escrita vivem no App Router do Next.js (Server Components + Server Actions). O Supabase fornece Postgres, autenticação, Realtime e Storage. A regra de acesso mora no banco, em políticas RLS — o código de aplicação não é a última linha de defesa.

Next.js · App Router

Server Componentsleitura sob RLS
Server Actionsescrita + Zod
middleware.tssessão + guarda de rotas
Client Componentschat, mapa, Realtime

Supabase

PostgreSQL + RLS19 tabelas
Authe-mail/senha + JWT
Realtimemensagens, notificações
Storagebucket «avatares»

Decisões que moldam o código

  • Multi-tenant por RLS. Toda vaga, conversa e permissão pertence a um workspace; funções SECURITY DEFINER checam participação sem recursão de política.
  • Três clientes Supabase. server (sessão, respeita RLS), browser (Realtime) e admin (service-role, ignora RLS — só no servidor, após os guards).
  • Funciona sem JavaScript. Formulários usam <form action={serverAction}>; o POST nativo roda a ação no servidor e devolve estado. Progressive enhancement pensando em 4G de obra.
  • PII e coordenada exata isoladas. RLS esconde linha, não coluna — então dado sensível vive em tabela à parte (profiles_pii, vaga_local).

03 Stack & estrutura de pastas

Onde cada coisa mora.

CamadaTecnologia
Frontend / rotasNext.js 15 (App Router) · React 19 · TypeScript · Tailwind (Poppins)
Backend / bancoSupabase · PostgreSQL com RLS
AutenticaçãoSupabase Auth (e-mail/senha)
Tempo realSupabase Realtime (mensagens e notificações in-app)
MapasLeaflet + OpenStreetMap · geocoding via Nominatim
ValidaçãoZod (schemas em lib/validation.ts)
TestesVitest (unidade + isolamento RLS opt-in)
app/            Rotas: (auth) login/cadastro · (app) fluxo · (legal) termos/privacidade
components/     UI, nav, cards, chat, mapa, avaliação, notificações
lib/
  supabase/     clientes server / browser / admin + tipos gerados
  auth/         guards de usuário, papel, workspace, módulos
  actions/      server actions (escrita, com Zod + service-role após guard)
  *.ts          utilitários BR: format, categorias, cidades, validação, período…
supabase/migrations/  0001–0019 (schema, RLS, seeds, comentários)
tests/         Vitest (unidade + rls.test.ts opt-in)
docs/          esta documentação + apresentação

04 Modelo de dados

19 tabelas no schema public, todas com RLS ativa. Cada tabela e coluna tem COMMENT no banco (migration 0019) — visível no Dashboard e no \d+.

Identidade & acesso

Perfil público separado da PII (RLS esconde linha, não coluna).
TabelaColunas-chavePapel
profilesuser_id, nome, foto_url, cidade, tipo_base, nota_media, verificado, status, bioPerfil público 1:1 com auth.users. nota_media é mantida por trigger.
profiles_piiuser_id, telefone, emailPII isolada; lê só o dono ou o sysadmin. RLS estrita

Empresa (multi-tenant)

TabelaColunas-chavePapel
workspacesid, owner_id, nome, cidadeEmpresa/equipe — a unidade de isolamento multi-tenant.
workspace_members(workspace_id, user_id), roleVínculo usuário↔empresa (owner | membro).
user_modules(user_id, workspace_id, module), allowedLiga/desliga módulos e capacidades por funcionário.
invitetoken, workspace_id, role, statusConvite por link + aprovação. Só o service-role manipula.

Marketplace

TabelaColunas-chavePapel
vagasid, workspace_id, titulo, categoria, cidade, valor_diaria, status, local_aprox_*Vaga de diária. Coração do marketplace.
vaga_localvaga_id, lat, lngCoordenada exata; lê só equipe dona ou ajudante aceito. RLS estrita
candidaturas(vaga_id, ajudante_id), statusCandidatura de um ajudante. Insert só em vaga aberta.
avaliacoesvaga_id, avaliador_id, avaliado_id, nota1–5 estrelas entre partes da diária. Alimenta a média por trigger.
demanda_servico(user_id, categoria, cidade)Demanda reprimida em busca sem resultado.
categorias_servicoslug, nome, ordemCatálogo de categorias (seed).

Comunicação

TabelaColunas-chavePapel
conversasid, workspace_id, tipo, ajudante_idCanal da equipe · DM interna · DM externa (com ajudante).
conversa_membros(conversa_id, user_id), lido_ateMembros + ponteiro de leitura (não-lidas).
mensagensid, conversa_id, remetente_id, conteudoMensagens do chat. Realtime
notificacoesid, user_id, tipo, titulo, linkNotificações in-app. Realtime

Agenda · moderação · admin

TabelaColunas-chavePapel
bloqueio_agenda(ajudante_id, data)Dias de indisponibilidade do ajudante (privado por dono).
denunciasid, denunciante_id, alvo_tipo, alvo_id, motivo, statusModeração; alvo polimórfico. Só sysadmin resolve.
home_bannerid=1, texto, ativoBanner único da home (singleton), editável pelo sysadmin.
🔗

Convenção: toda coluna *_id referencia auth.users(id) salvo indicação; created_at é timestamptz default now(). Chaves primárias em amarelo nas migrations.

05 Segurança & RLS

A regra de acesso é do banco. O código só orquestra.

Todas as tabelas têm Row-Level Security ativa. O papel do usuário viaja no JWT (app_metadata.app_role), injetado pelo auth hook custom_access_token_hook a partir de profiles.tipo_base. As políticas leem esse papel via current_app_role().

Para evitar recursão entre políticas (uma política de vagas que consulta candidaturas, e vice-versa), a checagem de participação fica em funções SECURITY DEFINER — elas não disparam RLS por dentro:

Helpers de RLS (todas comentadas no banco).
FunçãoResponde
current_app_role()Papel do usuário logado, lido do JWT.
is_workspace_member(ws)Pertence à empresa?
can_manage_vaga(vaga)É da equipe dona da vaga?
is_candidato(vaga)Tem candidatura nesta vaga?
is_parte_vaga(vaga, user)É candidato OU equipe da vaga? (gate de mensagem/avaliação)
vaga_aberta(vaga)Vaga existe e está aberta? (gate de candidatura)
is_ajudante_aceito(vaga, user)É o ajudante aceito? (libera coordenada exata)
is_conversa_membro(conversa)Participa da conversa? (núcleo da RLS do chat)
has_capability(user, ws, cap)Tem a capacidade liberada na empresa?

🪪 PII separada

profiles_pii guarda telefone/e-mail; lê só o dono ou o sysadmin.

📍 Local exato protegido

Mapa público usa coordenada arredondada (~1 km). O ponto exato (vaga_local) só aparece para a equipe e o ajudante aceito.

👁 Contas demo read-only

Toda escrita passa por requireWriter(); contas de demonstração são bloqueadas para não alterarem dados compartilhados.

🕵️ Denúncia anônima

O denunciante fica gravado, mas só a moderação o vê — jamais exposto ao denunciado.

06 Papéis & permissões (RBAC)

Quatro papéis globais + módulos e capacidades por funcionário.

PapelÉ…Pode
sysadminDono da plataformaModera denúncias, gerencia usuários e banner; enxerga tudo (god-mode das policies).
adminProfissional / dono de empresaPublica vagas, gere equipe, convida, liga/desliga módulos dos funcionários.
funcionarioSubordinado ao adminAcessa apenas os módulos/capacidades liberados pelo dono da empresa.
ajudanteMão de obraCandidata-se, conversa, avalia, gerencia a própria agenda.

Módulos e capacidades (tabela user_modules)

Módulos de painelvagas, equipe, financeiro, relatorios, mapa. Ausência de linha = default do papel.
Capacidades de açãopublicar_vagas, chat_ajudantes. Ausência de linha = OFF (menor privilégio).

Guards em lib/auth/: requireModule / guardModule (server action / página) e requireCapability. sysadmin e admin recebem tudo; funcionário depende das linhas explícitas.

07 Fluxos principais

Do anúncio à reputação, em cinco passos.

01PublicarProfissional anuncia a diária
02CandidatarAjudante se candidata
03AceitarContato + chat liberados
04ConversarCombinam pela DM externa
05AvaliarNota 1–5 dos dois lados

Ciclo de vida da vaga

O status da vaga guia a interface e dispara notificações a cada transição:

abertano ar para ajudantes próximos
em_andamentocandidato aceito; dia do serviço
finalizadaserviço entregue → libera avaliação
canceladaavisa quem se candidatou

A candidatura tem o seu próprio ciclo: aguardando → aceito | recusado | cancelado. Aceitar coloca a vaga em andamento e abre (ou reaproveita) a DM externa equipe↔ajudante, que persiste entre diárias.

08 Server Actions

A superfície de escrita, em lib/actions/. Cada uma valida com Zod, passa pelos guards e revalida os caminhos afetados.

DomínioAções
Autenticaçãocadastrar, entrar, recuperarSenha, definirSenha, trocarMeuPapel, logout
VagaspublicarVaga, editarVaga, mudarStatusVaga
Candidaturascandidatar, cancelarCandidatura, responderCandidatura
Avaliaçõesavaliar
Chat & leituraenviarMensagem, marcarNotificacoesVistas, marcarMensagensLidas
PerfilsalvarPerfil, removerFoto
EmpresasetActiveWorkspace, criarEmpresa, excluirEquipe, convidarMembro
ConvitescriarConvite, aceitarConvite, aprovarConvite, recusarConvite
EquipesetModuloFuncionario
AdmindefinirPapel, criarAdmin
ModeraçãocriarDenuncia, moderarDenuncia
OutrosregistrarDemanda, salvarBanner, alternarBloqueio, geocodeAddress
✍️

Padrão: ações ligadas a <form> recebem FormData e devolvem EstadoForm (erro + valores preservados, nunca senha). Ações de botão devolvem ActionResult ({ ok, erro? }).

09 Rotas & telas

App Router, agrupado por área. Cada arquivo de rota tem um doc-comment de topo.

GrupoRotas
Público/ landing + demo · /convite/[token]
(auth)/login · /cadastro · /recuperar-senha · /nova-senha
(app) comum/inicio · /vagas · /vagas/[id] · /mapa · /mensagens · /chat/[conversaId] · /notificacoes · /perfil/[id] · /perfil/editar
(app) profissional/publicar · /minhas-vagas · /minhas-vagas/[id]/candidatos · /equipe · /financeiro · /relatorios
(app) ajudante/minhas-diarias · /agenda · /avaliar/[vagaId]
(app) admin/admin/usuarios · /admin/denuncias · /admin/demanda
(legal)/termos · /privacidade
API/api/demo/enter · /auth/confirmar

10 Setup & execução

Do clone ao navegador.

Rodar localmente

npm install
npm run dev      # http://localhost:3000

O .env.local aponta para o projeto Supabase meajudaai-mvp. Para outro ambiente, veja .env.example (URL, anon key, service-role key, NEXT_PUBLIC_SITE_URL).

Scripts

ComandoFaz
npm run devServidor de desenvolvimento
npm run buildBuild de produção
npm run typechecktsc --noEmit
npm run lintESLint (next lint)
npm testVitest (unidade)
npm run test:integrationIsolamento RLS no Supabase real (opt-in)

Passo único no Supabase

Registre o auth hook para o papel entrar no JWT:
Dashboard → Authentication → Hooks → Custom Access Token → public.custom_access_token_hook.
O app funciona sem isso (lê o papel do banco como fallback), mas o hook é o caminho oficial.

🧱

As migrations 0001–0019 descrevem o schema, a RLS, os seeds e os comentários. No protótipo elas são aplicadas via conector Supabase MCP; a 0019 anexa os COMMENT ON de tabelas, colunas e funções.

11 Contas de demonstração

A landing (/) tem cards «Explore sem cadastro». O clique entra por /api/demo/enter?who=… com cookies session-only. Tudo somente leitura.

QuemPapelE-mail
João EletricistaProfissionaljoao.demo@meajudaai.app
Carlos SilvaAjudantecarlos.demo@meajudaai.app
Ana AssistenteFuncionárioana.demo@meajudaai.app
Rafael NunesModeradoradmin.demo@meajudaai.app

Os dados de exemplo (vagas, candidatura aceita, conversa, avaliações e denúncias) são semeados pelas migrations 0005 e 0007.