feat(panel-admin): UserResource com List/View/Edit e Role hierarchy - #455
feat(panel-admin): UserResource com List/View/Edit e Role hierarchy#455YuriSouzaDev wants to merge 6 commits into
Conversation
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.
|
Warning Review limit reached
Next review available in: 28 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the 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 configurationConfiguration used: Repository YAML (base), Central YAML (inherited) Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (5)
📝 WalkthroughWalkthroughAdds 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: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 7
🧹 Nitpick comments (8)
app-modules/panel-admin/src/Filament/Resources/Users/UserResource.php (1)
77-80: 🔒 Security & Privacy | 🔵 Trivial | 💤 Low valueGlobal search over
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 valueLoop 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 winMissing coverage for the
nameuniqueness rule flagged inUserForm.php.No test exercises editing a user whose
namecollides 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 winTest asserts less than its name claims.
Only
createis checked;edit/deletevisibility is untested. Seed aWorkExperience/ProfileSkilland 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 valueDuplicated inline staff check.
auth()->user()?->role->isStaff() ?? falseis 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 valueMagic 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 winStaff authorization is inlined in three places instead of going through
UserPolicy.auth()->user()?->role->isStaff() ?? falseis 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
📒 Files selected for processing (21)
app-modules/identity/database/factories/UserFactory.phpapp-modules/identity/database/migrations/2026_07_26_120000_add_role_and_soft_deletes_to_users_table.phpapp-modules/identity/lang/en/enums.phpapp-modules/identity/lang/pt_BR/enums.phpapp-modules/identity/src/IdentityServiceProvider.phpapp-modules/identity/src/User/Enums/Role.phpapp-modules/identity/src/User/Models/User.phpapp-modules/identity/src/User/Policies/UserPolicy.phpapp-modules/identity/tests/Unit/User/UserPolicyTest.phpapp-modules/panel-admin/src/Filament/Resources/Users/Pages/EditUser.phpapp-modules/panel-admin/src/Filament/Resources/Users/Pages/ListUsers.phpapp-modules/panel-admin/src/Filament/Resources/Users/Pages/ViewUser.phpapp-modules/panel-admin/src/Filament/Resources/Users/RelationManagers/ProfileSkillsRelationManager.phpapp-modules/panel-admin/src/Filament/Resources/Users/RelationManagers/WorkExperiencesRelationManager.phpapp-modules/panel-admin/src/Filament/Resources/Users/Schemas/UserForm.phpapp-modules/panel-admin/src/Filament/Resources/Users/Schemas/UserInfolist.phpapp-modules/panel-admin/src/Filament/Resources/Users/Tables/UsersTable.phpapp-modules/panel-admin/src/Filament/Resources/Users/UserResource.phpapp-modules/panel-admin/src/PanelAdminServiceProvider.phpapp-modules/panel-admin/tests/Feature/Users/UserResourceTest.phpapp/Providers/AuthServiceProvider.php
…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).
|
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 |
There was a problem hiding this comment.
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
📒 Files selected for processing (11)
app-modules/identity/database/factories/UserFactory.phpapp-modules/identity/database/migrations/2026_07_27_000000_promote_configured_admins_to_staff_role.phpapp-modules/identity/src/User/Models/User.phpapp-modules/identity/src/User/Observers/UserObserver.phpapp-modules/identity/tests/Unit/User/UserPolicyTest.phpapp-modules/panel-admin/src/Filament/Resources/Users/RelationManagers/ProfileSkillsRelationManager.phpapp-modules/panel-admin/src/Filament/Resources/Users/RelationManagers/WorkExperiencesRelationManager.phpapp-modules/panel-admin/src/Filament/Resources/Users/Schemas/UserForm.phpapp-modules/panel-admin/src/Filament/Resources/Users/Schemas/UserInfolist.phpapp-modules/panel-admin/tests/Feature/Users/UserResourceTest.phpdatabase/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.
There was a problem hiding this comment.
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
📒 Files selected for processing (2)
app-modules/identity/database/migrations/2026_07_26_120000_add_role_and_soft_deletes_to_users_table.phpapp-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.
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. |
Closes #424
Contexto
Não existia
UserResourceno painel admin. Staff/moderação precisava de uma tela única pra ver e editar um membro por inteiro — os dados estavam espalhados entreCharacter,ExternalIdentity,Profile,AddresseModerationCase.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)
Role(Staff,Compliance,Recruiter,SquadCaptain,Member) comisStaff(),isCompliance(),canViewUsers().rolee soft deletes aousers; o unique index deusernamevirou 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/viewliberam staff/compliance/recruiter/squad captain;update/deleterestritos a staff;restore/forceDeleterestritos a compliance — hard delete nunca é o padrão, sempre com a confirmação explícita que o Filament já exige.profile(),workExperiences(),profileSkills()noUser(aceitável no core por poder ser reaproveitado por outros módulos, conforme discutido no issue).Panel-admin —
UserResource[25, 50, 100]; filtros de role, senioridade e disponibilidade.character()) — 100% somente-leitura, sem action de conceder badge (adiado pro issue de badges por evento, já combinado na thread).providers()).Decisões registradas durante a implementação
profileSkills()(registrosProfileSkilldiretos) em vez deProfile::skills()(BelongsToMany) — mesmo resultado prático, menos retrabalho sobre o que já existia.preferencesdo 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 emProfilePage(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 depreferences/social_links/endereço, validação de username duplicado, estado vazio pra usuário semCharacter, RelationManagers (create/edit/delete pra staff, ocultas pra recruiter), soft delete como ação padrão, hard delete restrito a compliance.PHPStan (nível 6), Pint e Rector limpos nos arquivos tocados.
Como testar manualmente
make dev, logar em/admincom uma contastaffoucompliance.compliance, a exclusão física (hard delete, com confirmação).