Skip to content

feat(panel-admin): full CRUD User resource com informação agregada de perfil, gamificação, atividade e moderação #424

Description

@stherzada

Context

Não existe UserResource no painel admin. Staff/moderação precisa de uma tela única pra ver e editar um membro por inteiro — hoje os dados de identidade, perfil profissional, gamificação, atividade e moderação estão espalhados entre Character, ExternalIdentity, Profile, Address e ModerationCase.

Esta issue entrega List, Edit e View para os dados que fazem sentido serem editados pelo admin, mantendo como seções agregadas somente-leitura os dados que vêm de outros domínios — respeitando a fronteira presentation/core. Create fica fora de escopo (o fluxo de conta é via OAuth, que deve ser o caminho único e universal de ingresso; criação manual fere esse gate e, como fallback, seria admitir débito técnico).

Onde

app-modules/panel-admin/src/Filament/Resources/Users/

(seguir o padrão em pastas de ExternalIdentities/:
UserResource.php + Schemas/UserForm.php + Schemas/UserInfolist.php + Tables/UsersTable.php + Pages/{List,Edit,View}User.php + RelationManagers/).
Sem CreateUser.php (Create fora de escopo).

Escopo — editável

  • Identidade (users): username, name, email, is_donator.
  • Perfil (user_profiles): nickname, headline, about, birthdate, seniority_level, years_experience, available_for_proposals, start_availability, expected_salary_min/max, social_links, preferences (remoto/relocação/tipo de contrato/deficiência).
  • Skills: RelationManager sobre skills() (pivot proficiency + years_experience).
  • Experiências: RelationManager sobre workExperiences().
  • Endereço: país/estado/cidade (address()).

Motivação: correção de dado errado informado pelo membro (sobretudo campos únicos/imutáveis), sempre com análise por trás — alternativa auditável ao acesso direto ao banco.

Escopo — agregado / somente leitura

  • Gamificação (Character): level, XP, reputação, wallet, badges. Somente leitura — ajuste manual de wallet/XP fere a economia do ecossistema e dá permissibilidade demais ao admin (valores derivados).
  • Atividade (ExternalIdentity/DiscordMember→DiscordRole): conexões (Discord/GitHub/Twitch/devto), contagem de mensagens, horas de voice, roles do Discord.
  • Moderação (ModerationCase): casos como autor/assignee, suspended_until/banned_at.

Ações

  • Soft delete (default): ação de exclusão padrão faz soft delete, impedindo recadastro com os mesmos acessos (não impede criar outra conta). Alinha com a moderação existente: "Ban" ≈ soft delete; "Suspend" ≠ delete (restringe ações por X período).
  • Hard delete (restrito): exclusão física existe por compliance/lei (inegociável), mas nunca é o default — ação distinta, gated por permissão específica de compliance, com confirmação explícita.

Detalhes de implementação

  • Model do resource = User::class.
  • Delete: habilitar SoftDeletes no fluxo do Resource; ação padrão faz soft delete, hard delete é ação separada gated por permissão de compliance.
  • Autorização: definir Policy (ou Filament Shield) para o UserResource. Edit e Delete (soft) → apenas staff; hard delete → permissão de compliance específica (subconjunto ainda mais restrito); View → pode ser mais amplo (ex.: recrutadores, captains de squad), com a seção de Moderação e demais dados sensíveis ocultos para esses papéis. Campos sensíveis (email, is_donator) só editáveis por staff. "Staff" sozinho não basta como critério da tela inteira — a diferença de permissíveis por seção precisa ser explícita.
  • Tabela (index): username, name, seniority_level, available_for_proposals, cidade, level; ->searchable() em username/name/email; ->paginated([25,50,100]); SelectFilter de seniority; TernaryFilter de available_for_proposals.
  • Registrar em PanelAdminServiceProvider::register() e adicionar ...UserResource::getNavigationItems() em defaultNavigation().
  • Seções agregadas: ler via relacionamento (character(), providers(), caso por author/assignee) — sem duplicar lógica de escrita desses domínios dentro do panel-admin.

Critérios de aceite (Gherkin)

  • Listagem: dado que sou staff, quando acesso Admin ▸ Usuários, então vejo tabela buscável por name/username/email com level e status (ativo/suspenso/banido/donator).
  • Editar: dado que abro um usuário, quando altero campos de identidade/perfil/endereço e salvo, então os dados persistem corretamente (inclusive preferences e social_links).
  • Visualizar agregado: dado que estou na tela do usuário, então vejo Gamificação/Atividade/Moderação preenchidas via relacionamento, e nenhum campo dessas seções é editável por aqui.
  • Edge case — sem Character/Profile: dado um usuário sem Character ou sem user_profiles, quando abro a tela, então as seções correspondentes mostram estado vazio em vez de quebrar (usar Profile::ensureExists). Obs.: as 3 formas de acesso atuais (OAuth) já criam ao menos o user_profile, então esse estado é majoritariamente defensivo.
  • Soft delete é o default: dado que sou staff, quando excluo pela ação padrão, então o usuário é soft-deleted e não consegue se recadastrar com os mesmos acessos.
  • Hard delete é restrito: dado que não tenho a permissão de compliance, então a ação não aparece; dado que tenho, quando a executo, então preciso confirmar explicitamente antes da exclusão física.
  • Autorização: dado um usuário sem policy adequada, então não consegue editar (staff-only) nem — quando não autorizado — ver o Resource; papéis de View sem permissão de moderação não veem a seção de Moderação.
  • Resource aparece na sidebar do /admin.

Cobertura de teste

  • Save do profile (seguir os testes existentes do módulo profile).
  • RelationManager de Skills: attach/detach e edição do pivot (proficiency, years_experience).
  • RelationManager de Experiências: create/edit/delete.
  • Estado vazio das seções agregadas (Gamificação/Atividade/Moderação) para usuário sem Character/Profile — garantir que a tela não quebra.
  • Soft delete: exclusão padrão marca deleted_at e bloqueia recadastro com os mesmos acessos.
  • Hard delete: indisponível sem permissão de compliance; disponível e efetivo (com confirmação) com a permissão.
  • Concessão de badge: bloqueada sem evento atrelado; concede via action do domínio quando há evento.
  • Autorização: usuário sem policy adequada não deve conseguir editar (ou nem ver) o Resource; papel de View sem permissão de moderação não enxerga a seção de Moderação.

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions