Skip to content

fix(attendees): stop member/attendee email conflicts from failing sync jobs and ticket assignment - #663

Open
romanetar wants to merge 5 commits into
mainfrom
fix/attendee-member-email-conflict
Open

romanetar wants to merge 5 commits into
mainfrom
fix/attendee-member-email-conflict

Conversation

@romanetar

@romanetar romanetar commented Nov 2, 2026 •

Copy link
Copy Markdown
Collaborator

ref https://app.clickup.com/t/9014802374/86bch3bp3

Summary

When a member's email changes, a summit can end up with the member's attendee on the old email and a second attendee with no member that already has the member's current email. Three paths collided on the SummitAttendee unique keys:

  • UpdateAttendeeInfo failed on SummitAttendee_Email_SummitID and rolled back the update for all of the member's attendees.
  • MemberAssocSummitOrders failed on SummitAttendee_Member_Summit and never linked the attendee.
  • PUT /api/v1/summits/all/orders/{order_id}/tickets/{ticket_id}/attendee returned 500.

The merge code in updateAttendeesByMemberId already existed but could never commit: Doctrine runs the UPDATEs in the order it loaded the entities, so the member's attendee took the email while the member-less row still had it.

Changes

  • AttendeeService::updateAttendeesByMemberId: flush the rename of the member-less attendee before the member's attendee takes the email, so the merge commits. When the merge does not apply (email not verified, or the email belongs to another member) keep the attendee email, log a warning and still update first name, last name and company.
  • ISummitAttendeeRepository::getLinkableByMember: shared by the job's last step and MemberService::assocSummitOrders. It skips (and logs) summits where the member already has an attendee.
  • SummitOrderService::ownerAssignTicket: throws ValidationException (412) instead of failing with a 500 when the member that owns the email already has a different attendee in the summit.
  • New command summit:repair-attendee-member-email-pairs (see Deployment Notes).
  • The member-less attendee is renamed to <id>-deleted before the merge, so the temporary email always fits SummitAttendee.Email (varchar 100).
  • Regression tests: tests/AttendeeMemberEmailConflictTest.php (added to the integration-tests matrix in push.yml) and two cases in OAuth2SummitTicketsApiTest.

Only UpdateAttendeeInfo removes a pair. The merge moves the tickets and deletes the member-less attendee together with its extra question answers, presentation votes, notes, RSVP invitations and announcement email records.

Testing

  • The 4 service cases fail on main (SummitAttendee_Email_SummitID x3, SummitAttendee_Member_Summit x1) and pass with the fix.
  • The 2 HTTP cases return 500 on main and 412 with the fix.
  • Locally, OAuth2SummitTicketsApiTest errors in tearDown (Can not delete ticket type ... has sold tickets) for almost every test, including ones unrelated to this change, and it does the same on a clean main. The assertions of the new HTTP tests pass; the OAuth2Tests job needs to be checked in CI.

Deployment Notes

After deploy, repair the existing attendee/member email pairs with the new summit:repair-attendee-member-email-pairs command. Only UpdateAttendeeInfo merges a pair, and only when the member's email is verified.

What counts as a pair: an attendee linked to a member whose current email differs from the attendee's email, plus another attendee of the same summit with no member that already has the member's current email.

Command

php artisan summit:repair-attendee-member-email-pairs [--dispatch] [--limit=N] [--after-member-id=ID]
Option Behavior
none Read-only. Prints the total number of pairs grouped by summit and by the member's email verified flag, then one row per pair with the summit, member, attendee and member-less attendee ids (no emails). Nothing is dispatched.
--dispatch Enqueues one UpdateAttendeeInfo job per distinct member that has a pair with a verified email. Pairs with an unverified email are listed but skipped.
--limit=N With --dispatch, enqueues at most N members, in ascending member id order.
--after-member-id=ID With --dispatch, skips the members with an id lower than or equal to ID. The command prints the last member id it dispatched, so a run can resume after a member whose job keeps failing instead of selecting it again.

Steps

  1. Run the command without options and keep the output (the "before" count).
  2. Check a sample of the verified pairs: the merge deletes the member-less attendee together with its extra question answers, presentation votes, notes, RSVP invitations and announcement email records. Only its tickets move to the member's attendee.
  3. Run --dispatch --limit=5 and check that those merges succeeded. If a member's job keeps failing, check the worker logs and continue with --after-member-id=<last id printed>.
  4. Run --dispatch for the rest.
  5. Run the command without options again (the "after" count). It must list only pairs whose member email is not verified; those are merged when the IDP publishes the user update after the person verifies the new email.

Verification over 7 days

  • Loki returns no lines for {service_name="summit-api", k8s_cluster_name="prod-fn", container="summit-api-worker-php"} |~ "SummitAttendee_(Email_SummitID|Member_Summit)".
  • nginx logs no 500 for PUT /api/v1/summits/all/orders/{order_id}/tickets/{ticket_id}/attendee.

…c jobs and ticket assignment

When a member's email changes to the email of a member-less attendee of the
same summit, UpdateAttendeeInfo, MemberAssocSummitOrders and ticket
assignment collided on the SummitAttendee unique keys.

- updateAttendeesByMemberId: flush the rename of the member-less attendee
  before the member's attendee takes the email, so the merge commits. When
  the merge does not apply (email not verified, or the email belongs to
  another member) keep the attendee email, log a warning and still update
  the other fields.
- Share getLinkableByMember between the job and assocSummitOrders so a
  member-less attendee is not linked to a member that already has an
  attendee in that summit.
- ownerAssignTicket: throw ValidationException (412) instead of failing
  with a 500 in that state.
- Add summit:repair-attendee-member-email-pairs to list the existing pairs
  and dispatch UpdateAttendeeInfo for the ones with a verified email.
- Add regression tests and run them in the integration-tests matrix.
@coderabbitai

coderabbitai Bot commented Nov 2, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Warning

Review limit reached

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Next included review available in 39 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

Learn how review limits work.

Review configuration:

Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 72630a57-c5c7-463b-aea7-94e13bfde8e4
Commits

Reviewing files that changed from the base of the PR and between 0a4b3ba and 9c08848.

Files selected for processing: 5
  • app/Console/Commands/RepairAttendeeMemberEmailPairsCommand.php
  • app/Repositories/Summit/DoctrineSummitAttendeeRepository.php
  • app/Services/Model/AttendeeService.php
  • app/Services/Model/Imp/SummitOrderService.php
  • tests/AttendeeMemberEmailConflictTest.php
Recent review info
Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 7d7fbe1d-5435-41d2-9fe7-713891133f37
Commits

Reviewing files that changed from the base of the PR and between 2981548 and 0a4b3ba.

Files selected for processing: 10
  • .github/workflows/push.yml
  • app/Console/Commands/RepairAttendeeMemberEmailPairsCommand.php
  • app/Console/Kernel.php
  • app/Models/Foundation/Summit/Repositories/ISummitAttendeeRepository.php
  • app/Repositories/Summit/DoctrineSummitAttendeeRepository.php
  • app/Services/Model/AttendeeService.php
  • app/Services/Model/Imp/MemberService.php
  • app/Services/Model/Imp/SummitOrderService.php
  • tests/AttendeeMemberEmailConflictTest.php
  • tests/oauth2/OAuth2SummitTicketsApiTest.php

Included review availability: This review used your included allowance. Your plan provides up to 1 included review per hour; 0 remain after this review.

Walkthrough

The change adds attendee matching safeguards, handles verified email conflicts by merging eligible attendees, rejects conflicting ticket assignments, and adds a command to report matching attendee pairs and optionally dispatch updates for verified members.

Changes: Attendee email conflicts

Layer / File(s) Summary
Select linkable attendees
app/Models/Foundation/Summit/Repositories/ISummitAttendeeRepository.php, app/Repositories/Summit/DoctrineSummitAttendeeRepository.php, app/Services/Model/Imp/MemberService.php, tests/AttendeeMemberEmailConflictTest.php
The repository selects matching memberless attendees only when the member has no attendee in that summit. assocSummitOrders uses this selection. Tests cover association behavior.
Reconcile attendee email conflicts
app/Services/Model/AttendeeService.php, tests/AttendeeMemberEmailConflictTest.php
For a verified member email, the service merges an unlinked attendee with a matching email, transfers tickets, and removes the duplicate attendee and its note. Tests cover verified and unverified emails and attendees owned by another member.
Reject conflicting ticket assignments
app/Services/Model/Imp/SummitOrderService.php, tests/oauth2/OAuth2SummitTicketsApiTest.php
Ticket assignment now rejects a different attendee already associated with the member in that summit. Tests check the response and confirm attendee and ticket data remain unchanged for verified and unverified emails.
Report and dispatch attendee-pair repairs
app/Console/Commands/RepairAttendeeMemberEmailPairsCommand.php, app/Console/Kernel.php, .github/workflows/push.yml, tests/AttendeeMemberEmailConflictTest.php
The registered command reports matching pairs. With --dispatch, it dispatches updates for distinct verified members and can apply a nonnegative --limit. Tests cover dry-run, dispatch, and unverified members; the workflow adds the test suite.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Change: Bug fix

Suggested reviewers: smarcet

Merge Risk: Moderate · previous review 0a4b3

Limited repair runs can leave later conflicts untouched, while long-email conflicts may remain unresolved. Address those failures and the repair report and logging concerns before merging.

Pre-merge checks: Passed 5
Check name Status Explanation
Description Check Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check Passed The title clearly summarizes the main change: preventing attendee/member email conflicts from breaking sync jobs and ticket assignment.
Docstring Coverage Passed Docstring coverage is 80.00% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff with cyclomatic complexity of at least 2. Documentati…
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
Full details: Docstring Coverage

Explanation

Docstring coverage is 80.00% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff with cyclomatic complexity of at least 2. Documentation-only edits and deletions within surviving functions are included; unchanged diff context and fully deleted functions are excluded. Analyzed 5 functions across 9 files. (1 skipped: 1 unsupported.)

Finishing Touches
Generate docstrings
  • Commit to this branch
  • Create a new PR
Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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

@github-actions

github-actions Bot commented Nov 2, 2026

Copy link
Copy Markdown

📘 OpenAPI / Swagger preview

➡️ https://OpenStackweb.github.io/summit-api/openapi/pr-663/

This page is automatically updated on each push to this PR.

@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.

  • Fix comments: Fix CodeRabbit comments on this PR

Comment thread app/Services/Model/AttendeeService.php Resolved
A limited run selected the same first members again if their job kept
failing, so later pairs were never dispatched. Order the members by id
and add --after-member-id, and print the last dispatched member id.
The dry run only showed counts. List the summit, member, attendee and
member-less attendee ids of each pair, without emails.
email + "-deleted" can exceed SummitAttendee.Email (varchar 100). Use a
value that is unique per attendee and always fits.
The ids already identify the conflict.
@github-actions

github-actions Bot commented Nov 2, 2026

Copy link
Copy Markdown

📘 OpenAPI / Swagger preview

➡️ https://OpenStackweb.github.io/summit-api/openapi/pr-663/

This page is automatically updated on each push to this PR.

@romanetar
romanetar requested a review from smarcet November 2, 2026 13:55

This branch has not been deployed

No deployments
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.

1 participant