Conversation
Abre o único fluxo que o módulo conduz: a candidatura. Entrar exige o gate APTO do onboarding, então quem não concluiu o onboarding Squads é barrado e direcionado para lá. O limite de uma candidatura pendente por squad é o índice parcial UNIQUE (squad_id, user_id) WHERE status = 'pending', não um SELECT prévio: duas candidaturas simultâneas leriam "nenhuma pendente" e inseririam as duas. O insert decide. Uma candidatura recusada sai do índice, então candidatar-se de novo depois continua valendo. A decisão do capitão (aprovar/recusar) fica na #361, e a exclusividade de um squad ativo por pessoa na #362.
5 tasks
hefeus
reviewed
Sep 8, 2026
Comment on lines
+52
to
+54
| } catch (UniqueConstraintViolationException) { | ||
| throw ApplicationAlreadyPending::for($squad, $applicant); | ||
| } |
Contributor
There was a problem hiding this comment.
Este catch não está genérico demais? Hoje só tem um campo unique nesta tabela, mas amanhã se tiver um outro unique irá mascará qualquer erro como somente no unique atual.
Contributor
Author
There was a problem hiding this comment.
Faz sentido, boa pegada. Hoje só existe um unique na tabela então não quebra nada agora, mas o catch pega qualquer UniqueConstraintViolationException sem checar qual índice foi. Vou trocar pra reconferir se existe mesmo uma candidatura pendente pra esse squad+usuário antes de mapear pra ApplicationAlreadyPending, e relançar a exceção original se não existir. Assim não fica preso ao nome físico do índice, se um dia surgir outro unique na tabela o catch não mascara ele.
Contributor
Author
Danilo-Sam
approved these changes
Sep 13, 2026
O catch pegava qualquer UniqueConstraintViolationException e assumia que era sempre candidatura pendente. Agora reconfere se existe mesmo uma candidatura pendente pra esse squad e usuário antes de mapear pra ApplicationAlreadyPending, e relança a exceção original se não existir. O insert precisou ir pra dentro de um DB::transaction porque, sem isolar numa savepoint, o insert que falha aborta a transação inteira no Postgres e a query de reconferência quebra junto.
sirelves
dismissed stale reviews from GabrielFVDev and BrunaDomingues
via
September 15, 2026 15:50
0f0c26d
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Materializa a candidatura a um squad, o único fluxo que o módulo conduz, gated no APTO do onboarding.
squad_applications:status(defaultpending),message,decided_by,decided_at,INDEX (squad_id, status)e o partialUNIQUE (squad_id, user_id) WHERE status = 'pending'SquadApplication(PHPDoc@property,#[Table],#[UseFactory], casts) +SquadApplicationFactory+ enumApplicationStatus(pending/approved/rejected)ApplyToSquad: lêOnboardingCompletionGate::isCompleted($user, OnboardingType::Squads)e barra quem não é APTO comNotAptForSquads, cuja mensagem já nomeia o onboarding exigido para a UI direcionarApplicationAlreadyPendingSobre a duplicata pendente
Não tem
SELECTantes do insert: duas candidaturas simultâneas leriam "nenhuma pendente" e inseririam as duas. Quem decide é o índice parcial, e a Action traduz a violação para a exception de domínio. Como candidatura recusada sai do índice, candidatar-se de novo depois continua valendo (tem teste pra isso).Fora de escopo (por dependência)
DecideApplication) é a feat(squads): DecideApplication (capitão) → membership + evento join #361, do @reag-dev, que sobe em cima desta branchApplyToSquadPlano de testes
ApplyToSquadTest.php: APTO abrependingcom mensagem, não-APTO barrado (sem onboarding, onboarding em progresso, só o Welcome concluído), duplicata pendente barrada, candidatura recusada não bloqueia nova, pendente em outro squad não bloqueia. 7/7 verdesapp-modules/squads: 47/47 verdesmake check(rector + pint + phpstan) limpoCloses #360