Skip to content

feat(panel-admin): UserResource com List/View/Edit e Role hierarchy - #455

Open
YuriSouzaDev wants to merge 6 commits into
he4rt:4.xfrom
YuriSouzaDev:feat/user-resource-panel-admin
Open

feat(panel-admin): UserResource com List/View/Edit e Role hierarchy#455
YuriSouzaDev wants to merge 6 commits into
he4rt:4.xfrom
YuriSouzaDev:feat/user-resource-panel-admin

Conversation

@YuriSouzaDev

Copy link
Copy Markdown
Contributor

Closes #424

Contexto

Não existia UserResource no painel admin. Staff/moderação precisava de uma tela única pra ver e editar um membro por inteiro — os dados estavam espalhados entre Character, ExternalIdentity, Profile, Address e ModerationCase.

Este PR entrega List, Edit e View para os dados editáveis pelo admin, mantendo como seções agregadas somente-leitura os dados que vêm de outros domínios (respeitando a fronteira presentation/core — sem duplicar lógica de escrita de outros módulos). Create ficou fora de escopo por decisão explícita no issue: contas só nascem via OAuth, e criação manual seria admitir débito técnico.

O que entra

Identity — hierarquia de roles (pré-requisito pro Policy do Resource)

  • Enum Role (Staff, Compliance, Recruiter, SquadCaptain, Member) com isStaff(), isCompliance(), canViewUsers().
  • Migration adicionando role e soft deletes ao users; o unique index de username virou parcial (WHERE deleted_at IS NULL) pra não travar reuso de username por conta soft-deletada (regressão pega no fluxo de merge de contas).
  • UserPolicy: viewAny/view liberam staff/compliance/recruiter/squad captain; update/delete restritos a staff; restore/forceDelete restritos a compliance — hard delete nunca é o padrão, sempre com a confirmação explícita que o Filament já exige.
  • Relações profile(), workExperiences(), profileSkills() no User (aceitável no core por poder ser reaproveitado por outros módulos, conforme discutido no issue).

Panel-admin — UserResource

  • List: colunas de username/name/email (buscáveis), role, senioridade, disponibilidade, cidade, nível, status computado (ativo/suspenso/banido/removido) e donator; paginação [25, 50, 100]; filtros de role, senioridade e disponibilidade.
  • Edit (staff-only): identidade (username/name/email/role/is_donator), perfil profissional (nickname, headline, about, senioridade, disponibilidade, pretensão salarial, redes sociais, preferências de remoto/relocação/contratação/deficiência) e endereço — tudo num único submit.
  • View (staff/compliance/recruiter/squad captain): mesmos dados em modo leitura, mais as seções agregadas:
    • Gamificação (nível/XP/reputação/badges/carteira via character()) — 100% somente-leitura, sem action de conceder badge (adiado pro issue de badges por evento, já combinado na thread).
    • Atividade (conexões, total de mensagens, horas de voice aproximadas, cargos do Discord via providers()).
    • Moderação (casos como autor/responsável, suspensão/banimento) — oculta pra quem não é staff/compliance.
  • RelationManagers de Skills e Experiências profissionais: create/edit/delete pra staff, somente leitura pra recruiter/squad captain.

Decisões registradas durante a implementação

  • Skills RelationManager opera sobre profileSkills() (registros ProfileSkill diretos) em vez de Profile::skills() (BelongsToMany) — mesmo resultado prático, menos retrabalho sobre o que já existia.
  • preferences do perfil é um cast custom (AsWorkPreferences), não array puro — o form usa os hooks nativos do Filament (mutateRelationshipDataBeforeFill/SaveUsing) pra achatar/reagrupar os campos, seguindo o mesmo padrão manual já usado em ProfilePage (portal).

Testes

  • UserPolicyTest: cobre a hierarquia de roles em todas as abilities.
  • UserResourceTest (18 testes): autorização por página/seção (List/Edit/View × staff/compliance/recruiter/squad captain/member), edição multi-seção com persistência de preferences/social_links/endereço, validação de username duplicado, estado vazio pra usuário sem Character, RelationManagers (create/edit/delete pra staff, ocultas pra recruiter), soft delete como ação padrão, hard delete restrito a compliance.
vendor/bin/pest app-modules/identity app-modules/panel-admin
# 114 passed

PHPStan (nível 6), Pint e Rector limpos nos arquivos tocados.

Como testar manualmente

  1. make dev, logar em /admin com uma conta staff ou compliance.
  2. Acessar Admin ▸ Usuários — conferir busca, filtros e paginação da listagem.
  3. Abrir um usuário (View) — conferir as abas Identidade/Perfil/Endereço/Gamificação/Atividade/Moderação.
  4. Editar um usuário — alterar campos de perfil (incluindo preferências e redes sociais) e endereço num único submit, salvar e conferir persistência.
  5. Testar a ação de excluir (soft delete por padrão) e, com uma conta compliance, a exclusão física (hard delete, com confirmação).

Adiciona uma tela única no /admin para staff visualizar e editar um
membro por inteiro, agregando dados hoje espalhados entre Character,
ExternalIdentity, Profile, Address e ModerationCase — mantendo a
fronteira presentation/core (seções agregadas são lidas via
relacionamento, sem duplicar lógica de escrita de outros domínios).

Identity:
- Enum Role (Staff, Compliance, Recruiter, SquadCaptain, Member) com
  hierarquia isStaff()/isCompliance()/canViewUsers().
- SoftDeletes no User + migration adicionando `role` e `deleted_at`;
  unique index de `username` vira parcial (WHERE deleted_at IS NULL)
  para não travar reuso de username por conta soft-deletada.
- UserPolicy: viewAny/view liberam staff/compliance/recruiter/squad
  captain; update/delete restritos a staff; restore/forceDelete
  restritos a compliance (hard delete nunca é o padrão).
- Relações profile()/workExperiences()/profileSkills() no User.

Panel-admin (UserResource, sem Create — contas só nascem via OAuth):
- List: colunas de senioridade/disponibilidade/cidade/nível/status
  computado (ativo/suspenso/banido/removido), paginação [25,50,100],
  filtros de role/senioridade/disponibilidade/trashed.
- Edit: identidade (username/name/email/role/is_donator), perfil
  profissional via Section::relationship('profile') com hooks pra
  achatar/reagrupar o cast custom de preferences, e endereço via
  Section::relationship('address').
- View: mesmos dados em modo leitura, mais Gamificação/Atividade/
  Moderação agregadas por relacionamento; seção de Moderação oculta
  para quem não é staff.
- RelationManagers de Skills e Experiências (create/edit/delete
  staff-only; somente leitura para recruiter/squad captain).

Testes: UserPolicyTest cobrindo a hierarquia de roles; UserResource-
Test cobrindo autorização por página/seção, edição multi-seção com
persistência de preferences/social_links/endereço, relation managers,
soft delete padrão e hard delete restrito a compliance.
@YuriSouzaDev
YuriSouzaDev requested a review from a team July 26, 2026 20:32
@coderabbitai

coderabbitai Bot commented Jul 26, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@YuriSouzaDev, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 28 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Repository YAML (base), Central YAML (inherited)

Review profile: CHILL

Plan: Pro Plus

Run ID: 101aadd2-778a-4e34-9d32-47e1191bcc09

📥 Commits

Reviewing files that changed from the base of the PR and between 91d9c7b and 310e496.

📒 Files selected for processing (5)
  • app-modules/identity/database/migrations/2026_07_26_120000_add_role_and_soft_deletes_to_users_table.php
  • app-modules/identity/database/migrations/2026_07_27_000000_promote_configured_admins_to_staff_role.php
  • app-modules/identity/src/User/Models/User.php
  • app-modules/identity/src/User/Observers/UserObserver.php
  • tests/Feature/AddressTest.php
📝 Walkthrough

Walkthrough

Adds role-based authorization, localized role labels, soft deletes, user-profile relationships, and configured-admin role promotion. Introduces a Filament Users resource with list, view, edit, forms, tables, infolists, relation managers, and navigation registration. Adds tests covering permissions, profile editing, related records, moderation visibility, and deletion behavior.

Suggested reviewers: clintonrocha98

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 30.16% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed Title is concise and matches the main change: the admin UserResource plus role hierarchy support.
Description check ✅ Passed Description is directly about the UserResource and role/access work in this PR.
Linked Issues check ✅ Passed Implements the requested UserResource, role hierarchy, policies, soft deletes, aggregates, relations, and tests.
Out of Scope Changes check ✅ Passed No clearly unrelated code changes are introduced beyond the requested resource, access, and supporting identity changes.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

🧹 Nitpick comments (8)
app-modules/panel-admin/src/Filament/Resources/Users/UserResource.php (1)

77-80: 🔒 Security & Privacy | 🔵 Trivial | 💤 Low value

Global search over email exposes PII to every panel role.

Recruiters/squad captains get read access to this resource; global search results render matched attributes. Consider limiting to username/name.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app-modules/panel-admin/src/Filament/Resources/Users/UserResource.php` around
lines 77 - 80, Update UserResource::getGloballySearchableAttributes() to remove
email from the globally searchable attributes, retaining only username and name
so global search does not expose email values to panel roles.
app-modules/panel-admin/src/Filament/Resources/Users/Tables/UsersTable.php (1)

113-116: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

operator: named argument is being used to pass a value.

It works only because Eloquent re-shuffles a non-string operator into the value slot. Pass it positionally as the value.

Proposed fix
-                        true: static fn (Builder $query): Builder => $query->whereRelation('profile', 'available_for_proposals', operator: true),
-                        false: static fn (Builder $query): Builder => $query->whereRelation('profile', 'available_for_proposals', operator: false),
+                        true: static fn (Builder $query): Builder => $query->whereRelation('profile', 'available_for_proposals', '=', true),
+                        false: static fn (Builder $query): Builder => $query->whereRelation('profile', 'available_for_proposals', '=', false),
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app-modules/panel-admin/src/Filament/Resources/Users/Tables/UsersTable.php`
around lines 113 - 116, Update the true and false callbacks in the queries
configuration to pass the boolean available_for_proposals values positionally to
whereRelation instead of using the operator named argument; preserve the
existing profile relationship and filtering behavior.
app-modules/panel-admin/tests/Feature/Users/UserResourceTest.php (3)

27-39: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Loop hides which role failed.

Use a Pest dataset so failures name the role.

Proposed fix
-test('staff, recruiter e squad captain listam os usuários', function (): void {
+test('staff, recruiter e squad captain listam os usuários', function (string $state): void {
     $member = User::factory()->create();
 
-    foreach (['staff', 'recruiter', 'squadCaptain'] as $state) {
-        $viewer = User::factory()->{$state}()->create();
+    $viewer = User::factory()->{$state}()->create();
 
-        $this->actingAs($viewer);
+    $this->actingAs($viewer);
 
-        livewire(ListUsers::class)
-            ->loadTable()
-            ->assertCanSeeTableRecords([$viewer, $member]);
-    }
-});
+    livewire(ListUsers::class)
+        ->loadTable()
+        ->assertCanSeeTableRecords([$viewer, $member]);
+})->with(['staff', 'recruiter', 'squadCaptain']);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app-modules/panel-admin/tests/Feature/Users/UserResourceTest.php` around
lines 27 - 39, Replace the foreach in the “staff, recruiter e squad captain
listam os usuários” test with a Pest dataset that supplies each role to a
separate test case, preserving the existing viewer creation, authentication, and
table assertions so failures identify the specific role.

90-128: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Missing coverage for the name uniqueness rule flagged in UserForm.php.

No test exercises editing a user whose name collides with another — add one, or drop the rule.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app-modules/panel-admin/tests/Feature/Users/UserResourceTest.php` around
lines 90 - 128, Extend the user edit feature tests around EditUser to cover the
UserForm name uniqueness rule: create two users, edit one while assigning the
other’s name, submit the form, and assert the validation error is present and
the update is not accepted. Keep the existing successful multi-field submission
test unchanged.

279-298: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Test asserts less than its name claims.

Only create is checked; edit/delete visibility is untested. Seed a WorkExperience/ProfileSkill and assert those actions are hidden too.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app-modules/panel-admin/tests/Feature/Users/UserResourceTest.php` around
lines 279 - 298, Expand the test “recrutador não vê ações de
criar/editar/excluir nas relation managers” by creating representative
WorkExperience and ProfileSkill records for the target, then assert the create,
edit, and delete table actions are hidden in both WorkExperiencesRelationManager
and ProfileSkillsRelationManager. Keep the existing authentication and page
setup unchanged.
app-modules/panel-admin/src/Filament/Resources/Users/Schemas/UserInfolist.php (2)

49-49: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Duplicated inline staff check.

auth()->user()?->role->isStaff() ?? false is repeated here and in both relation managers. Extract a single helper/gate.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@app-modules/panel-admin/src/Filament/Resources/Users/Schemas/UserInfolist.php`
at line 49, Extract the repeated `auth()->user()?->role->isStaff() ?? false`
check into a shared helper or gate, then update the visibility callback in the
UserInfolist and both relation managers to reuse it. Preserve the existing
boolean behavior for authenticated staff and all other users.

366-369: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Magic string 'joined' for voice state.

Use the state enum instead of a literal.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@app-modules/panel-admin/src/Filament/Resources/Users/Schemas/UserInfolist.php`
around lines 366 - 369, Update the Voice query in UserInfolist to replace the
literal 'joined' state with the corresponding Voice state enum value, reusing
the existing enum symbol and leaving the query behavior unchanged.
app-modules/panel-admin/src/Filament/Resources/Users/RelationManagers/WorkExperiencesRelationManager.php (1)

103-106: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Staff authorization is inlined in three places instead of going through UserPolicy. auth()->user()?->role->isStaff() ?? false is copy-pasted, so a role-hierarchy change must be applied in every copy and never gets policy coverage.

  • app-modules/panel-admin/src/Filament/Resources/Users/RelationManagers/WorkExperiencesRelationManager.php#L103-L106: replace the method body with a Gate/policy ability check (e.g. Gate::allows('update', $this->getOwnerRecord())).
  • app-modules/panel-admin/src/Filament/Resources/Users/RelationManagers/ProfileSkillsRelationManager.php#L84-L87: same replacement.
  • app-modules/panel-admin/src/Filament/Resources/Users/Schemas/UserInfolist.php#L49-L49: gate the Moderação tab on the same shared ability instead of the inline role check.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@app-modules/panel-admin/src/Filament/Resources/Users/RelationManagers/WorkExperiencesRelationManager.php`
around lines 103 - 106, Replace the inline staff-role checks with the shared
UserPolicy ability check: update isEditableByCurrentUser() in
app-modules/panel-admin/src/Filament/Resources/Users/RelationManagers/WorkExperiencesRelationManager.php:103-106
and ProfileSkillsRelationManager.php:84-87 to use Gate::allows('update',
$this->getOwnerRecord()), and update the Moderação tab condition in
UserInfolist.php:49 to use the same ability. Ensure the required Gate import is
present in each affected file.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@app-modules/identity/database/migrations/2026_07_26_120000_add_role_and_soft_deletes_to_users_table.php`:
- Around line 32-34: Update the rollback logic in the users-table migration
before applying unique('username') to reconcile soft-deleted duplicate
usernames, such as by renaming or otherwise separating deleted duplicates; then
restore the global username constraint and remove the role and deleted_at
columns.

In `@app-modules/identity/src/User/Models/User.php`:
- Line 61: Update the User model’s deletion behavior around SoftDeletes and
UserObserver::deleted() so address deletion occurs only when the user is
force-deleted, not when soft-deleted. Preserve address data through soft-delete
and restore flows while retaining the existing cascade for permanent deletion.

In `@app-modules/identity/tests/Unit/User/UserPolicyTest.php`:
- Around line 47-61: Extend the UserPolicyTest mutation coverage by creating
recruiter and squad-captain users and asserting UserPolicy::update, delete,
restore, and forceDelete all return false for each read-only role. Preserve the
existing member and compliance assertions.

In
`@app-modules/panel-admin/src/Filament/Resources/Users/RelationManagers/ProfileSkillsRelationManager.php`:
- Around line 31-36: Add scoped uniqueness validation to the skill_id Select in
ProfileSkillsRelationManager, importing the required Unique rule and Profile
symbols. Configure the rule to constrain profile_id using the owner profile and
ignore the current record, so duplicate profile-skill assignments produce
Filament validation instead of a database exception.

In
`@app-modules/panel-admin/src/Filament/Resources/Users/RelationManagers/WorkExperiencesRelationManager.php`:
- Around line 46-56: Update the form schema in WorkExperiencesRelationManager so
is_currently_working_here is no longer live unless a dependent field is added,
and add validation ensuring end_date is not earlier than start_date and is
absent when the current-working checkbox is selected. Keep start_date required
and preserve the existing field labels.

In `@app-modules/panel-admin/src/Filament/Resources/Users/Schemas/UserForm.php`:
- Around line 58-61: Remove the unique validation from the name field in the
UserForm schema, specifically the TextInput::make('name') definition, while
preserving its label and required validation; keep uniqueness validation only on
the username field.
- Around line 81-95: Update the mutateRelationshipDataBeforeFillUsing callback
to verify that $data['preferences'] is an actual WorkPreferences instance before
accessing its properties. When the value is absent, null, or a plain array, use
a WorkPreferences default instance, and remove or correct the misleading `@var`
annotation while preserving the existing mapped fields.

---

Nitpick comments:
In
`@app-modules/panel-admin/src/Filament/Resources/Users/RelationManagers/WorkExperiencesRelationManager.php`:
- Around line 103-106: Replace the inline staff-role checks with the shared
UserPolicy ability check: update isEditableByCurrentUser() in
app-modules/panel-admin/src/Filament/Resources/Users/RelationManagers/WorkExperiencesRelationManager.php:103-106
and ProfileSkillsRelationManager.php:84-87 to use Gate::allows('update',
$this->getOwnerRecord()), and update the Moderação tab condition in
UserInfolist.php:49 to use the same ability. Ensure the required Gate import is
present in each affected file.

In
`@app-modules/panel-admin/src/Filament/Resources/Users/Schemas/UserInfolist.php`:
- Line 49: Extract the repeated `auth()->user()?->role->isStaff() ?? false`
check into a shared helper or gate, then update the visibility callback in the
UserInfolist and both relation managers to reuse it. Preserve the existing
boolean behavior for authenticated staff and all other users.
- Around line 366-369: Update the Voice query in UserInfolist to replace the
literal 'joined' state with the corresponding Voice state enum value, reusing
the existing enum symbol and leaving the query behavior unchanged.

In `@app-modules/panel-admin/src/Filament/Resources/Users/Tables/UsersTable.php`:
- Around line 113-116: Update the true and false callbacks in the queries
configuration to pass the boolean available_for_proposals values positionally to
whereRelation instead of using the operator named argument; preserve the
existing profile relationship and filtering behavior.

In `@app-modules/panel-admin/src/Filament/Resources/Users/UserResource.php`:
- Around line 77-80: Update UserResource::getGloballySearchableAttributes() to
remove email from the globally searchable attributes, retaining only username
and name so global search does not expose email values to panel roles.

In `@app-modules/panel-admin/tests/Feature/Users/UserResourceTest.php`:
- Around line 27-39: Replace the foreach in the “staff, recruiter e squad
captain listam os usuários” test with a Pest dataset that supplies each role to
a separate test case, preserving the existing viewer creation, authentication,
and table assertions so failures identify the specific role.
- Around line 90-128: Extend the user edit feature tests around EditUser to
cover the UserForm name uniqueness rule: create two users, edit one while
assigning the other’s name, submit the form, and assert the validation error is
present and the update is not accepted. Keep the existing successful multi-field
submission test unchanged.
- Around line 279-298: Expand the test “recrutador não vê ações de
criar/editar/excluir nas relation managers” by creating representative
WorkExperience and ProfileSkill records for the target, then assert the create,
edit, and delete table actions are hidden in both WorkExperiencesRelationManager
and ProfileSkillsRelationManager. Keep the existing authentication and page
setup unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Central YAML (inherited)

Review profile: CHILL

Plan: Pro Plus

Run ID: ac38e745-698b-4962-b0e6-489690ffa43e

📥 Commits

Reviewing files that changed from the base of the PR and between 5c8d0bf and 0f6baed.

📒 Files selected for processing (21)
  • app-modules/identity/database/factories/UserFactory.php
  • app-modules/identity/database/migrations/2026_07_26_120000_add_role_and_soft_deletes_to_users_table.php
  • app-modules/identity/lang/en/enums.php
  • app-modules/identity/lang/pt_BR/enums.php
  • app-modules/identity/src/IdentityServiceProvider.php
  • app-modules/identity/src/User/Enums/Role.php
  • app-modules/identity/src/User/Models/User.php
  • app-modules/identity/src/User/Policies/UserPolicy.php
  • app-modules/identity/tests/Unit/User/UserPolicyTest.php
  • app-modules/panel-admin/src/Filament/Resources/Users/Pages/EditUser.php
  • app-modules/panel-admin/src/Filament/Resources/Users/Pages/ListUsers.php
  • app-modules/panel-admin/src/Filament/Resources/Users/Pages/ViewUser.php
  • app-modules/panel-admin/src/Filament/Resources/Users/RelationManagers/ProfileSkillsRelationManager.php
  • app-modules/panel-admin/src/Filament/Resources/Users/RelationManagers/WorkExperiencesRelationManager.php
  • app-modules/panel-admin/src/Filament/Resources/Users/Schemas/UserForm.php
  • app-modules/panel-admin/src/Filament/Resources/Users/Schemas/UserInfolist.php
  • app-modules/panel-admin/src/Filament/Resources/Users/Tables/UsersTable.php
  • app-modules/panel-admin/src/Filament/Resources/Users/UserResource.php
  • app-modules/panel-admin/src/PanelAdminServiceProvider.php
  • app-modules/panel-admin/tests/Feature/Users/UserResourceTest.php
  • app/Providers/AuthServiceProvider.php

Comment thread app-modules/identity/src/User/Models/User.php
Comment thread app-modules/identity/tests/Unit/User/UserPolicyTest.php Outdated
Comment thread app-modules/panel-admin/src/Filament/Resources/Users/Schemas/UserForm.php Outdated
Comment thread app-modules/panel-admin/src/Filament/Resources/Users/Schemas/UserForm.php Outdated
…iza autorização

canAccessPanel() comparava com IDs de panel que nunca existiram, então
qualquer usuário autenticado (inclusive Member) entrava em /admin via
default => true. Agora exige isAdmin() ou role->canViewUsers().

Usernames configurados em HE4RT_ADMINS_USERNAMES são promovidos para
Role::Staff automaticamente na criação (UserObserver) e via migration
de backfill para quem já existia, para que a autorização de recursos
dependa só de role em vez de duas fontes de verdade divergentes.

RelationManagers e o Infolist de Users agora reusam UserPolicy::update()
via Gate em vez de duplicar auth()->user()?->role->isStaff() em cada
lugar, e corrige um edit quebrado deixado em
WorkExperiencesRelationManager (return UsePolicy::class).

Também adiciona validação de unicidade de skill por profile em
ProfileSkillsRelationManager (antes estourava exception crua do banco).
@YuriSouzaDev

Copy link
Copy Markdown
Contributor Author

Olá, @stherzada ,

A @BrunaDomingues levantou um bom ponto em uma conversa que tivemos, quanto ao acesso aos dados dos usuários.

Entendo que Staffs podem ver tudo, mas as demais roles, que dados elas podem ter acesso para não ferir nenhuma regra da LGPD? Cabe abrir uma issue para debater sobre isso, não? Já que não está incluso nessa task

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@app-modules/identity/database/migrations/2026_07_27_000000_promote_configured_admins_to_staff_role.php`:
- Line 18: Normalize configured admin usernames consistently: in
app-modules/identity/database/migrations/2026_07_27_000000_promote_configured_admins_to_staff_role.php
lines 18-18, trim each value before filtering and querying; in
app-modules/identity/src/User/Observers/UserObserver.php lines 44-47, trim each
configured username before strict membership checks and keep User::isAdmin()
aligned.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Central YAML (inherited)

Review profile: CHILL

Plan: Pro Plus

Run ID: 9ca165e4-b465-4e61-af52-298f31bd9b4e

📥 Commits

Reviewing files that changed from the base of the PR and between 0f6baed and 87bada0.

📒 Files selected for processing (11)
  • app-modules/identity/database/factories/UserFactory.php
  • app-modules/identity/database/migrations/2026_07_27_000000_promote_configured_admins_to_staff_role.php
  • app-modules/identity/src/User/Models/User.php
  • app-modules/identity/src/User/Observers/UserObserver.php
  • app-modules/identity/tests/Unit/User/UserPolicyTest.php
  • app-modules/panel-admin/src/Filament/Resources/Users/RelationManagers/ProfileSkillsRelationManager.php
  • app-modules/panel-admin/src/Filament/Resources/Users/RelationManagers/WorkExperiencesRelationManager.php
  • app-modules/panel-admin/src/Filament/Resources/Users/Schemas/UserForm.php
  • app-modules/panel-admin/src/Filament/Resources/Users/Schemas/UserInfolist.php
  • app-modules/panel-admin/tests/Feature/Users/UserResourceTest.php
  • database/seeders/BaseSeeder.php
🚧 Files skipped from review as they are similar to previous changes (6)
  • app-modules/panel-admin/src/Filament/Resources/Users/RelationManagers/ProfileSkillsRelationManager.php
  • app-modules/identity/database/factories/UserFactory.php
  • app-modules/panel-admin/src/Filament/Resources/Users/Schemas/UserForm.php
  • app-modules/panel-admin/tests/Feature/Users/UserResourceTest.php
  • app-modules/panel-admin/src/Filament/Resources/Users/Schemas/UserInfolist.php
  • app-modules/identity/src/User/Models/User.php

UserObserver::deleted() disparava tanto em soft delete quanto em force
delete, então restaurar um usuário soft-deletado deixava o address
perdido para sempre. Move o cleanup para o evento forceDeleted, que só
dispara na exclusão permanente.

down() da migration de role/soft-deletes tentava recriar a constraint
unique('username') global sem antes tratar duplicatas entre linhas
ativas e soft-deletadas — que up() permite intencionalmente (reuso de
username em merge de conta). Isso quebraria o rollback com duplicate
key violation. Adiciona um UPDATE que renomeia as duplicatas perdedoras
com um sufixo neutro (_dup_<id8>, não "_deleted_", já que a linha
renomeada não é necessariamente a trashed) antes de restaurar a
constraint.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@app-modules/identity/database/migrations/2026_07_26_120000_add_role_and_soft_deletes_to_users_table.php`:
- Around line 38-52: Update the username-rewrite SQL in the migration’s up
method to allocate collision-free rollback usernames: account for all existing
usernames and ensure generated values cannot collide with each other, using a
deterministic suffix/allocation strategy before the unique index is restored.
Keep the existing duplicate-ranking behavior based on deleted_at and created_at
unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Central YAML (inherited)

Review profile: CHILL

Plan: Pro Plus

Run ID: c792bfbc-fc53-4cad-a2f1-6739c829e4d6

📥 Commits

Reviewing files that changed from the base of the PR and between 87bada0 and 91d9c7b.

📒 Files selected for processing (2)
  • app-modules/identity/database/migrations/2026_07_26_120000_add_role_and_soft_deletes_to_users_table.php
  • app-modules/identity/src/User/Observers/UserObserver.php
🚧 Files skipped from review as they are similar to previous changes (1)
  • app-modules/identity/src/User/Observers/UserObserver.php

O parse de HE4RT_ADMINS_USERNAMES fazia split por vírgula sem trim,
então "alice, bob" (com espaço) nunca batia contra in_array strict,
causando promoção/acesso inconsistentes. Centraliza o parse em
User::configuredAdminUsernames() (com trim + filtro de vazios) e faz
User::isAdmin(), UserObserver e a migration de backfill reusarem o
mesmo helper em vez de duplicar a lógica cada um do seu jeito.
O dedup do down() gerava um sufixo determinístico (_dup_<8 chars do id>)
sem checar contra os usernames já existentes na tabela. Se o candidato
coincidisse com um username não relacionado já cadastrado, a UPDATE
passava (sem constraint ativa no momento), mas o unique('username')
logo depois quebrava — e por ser determinístico, rodar de novo falhava
do mesmo jeito.

Move o dedup para PHP: monta o conjunto de todos os usernames já em
uso, e para cada duplicata perdedora incrementa um contador até achar
um candidato livre. Testado forçando uma colisão proposital via tinker
+ rollback real.
…lete

O teste antigo assumia que soft delete de User cascateava a exclusão do
address, que era exatamente o bug corrigido (UserObserver::deleted() ->
forceDeleted()). Divide em dois casos: soft delete preserva o address,
force delete apaga.

@stherzada stherzada left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Arrasou! Se puder pegar a #425 depois que está for mergeada, vai ajudar demais :)

Coloca no seu grupo e no central dos squads para o pessoal dar revisada.

@stherzada

Copy link
Copy Markdown
Contributor

Olá, @stherzada ,

A @BrunaDomingues levantou um bom ponto em uma conversa que tivemos, quanto ao acesso aos dados dos usuários.

Entendo que Staffs podem ver tudo, mas as demais roles, que dados elas podem ter acesso para não ferir nenhuma regra da LGPD? Cabe abrir uma issue para debater sobre isso, não? Já que não está incluso nessa task

Bom ponto, vou pensar em como abordar essa task e ainda essa semana já abro, vou deixar um tempo aberta pois acredito que vai ser um debate extenso, mas de verdade, ótima iniciativa.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

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

3 participants