feat(contact): Contact entity for public-ticket dedupe (greenfield Pattern B)#20
Draft
feat(contact): Contact entity for public-ticket dedupe (greenfield Pattern B)#20
Conversation
…ield Pattern B)
First truly-greenfield port (Spring had no prior public ticketing
or guest fields — confirmed by re-audit). Goes straight to Pattern B
without needing the inline guest_* compatibility columns.
Changes:
- models/Contact.java: JPA entity mapped to escalated_contacts.
Unique email (lowercased + trimmed in setter + @PrePersist),
nullable userId, metadata JSON column. Static pure helpers
normalizeEmail and decideAction + Action enum for testable
branch selection.
- repositories/ContactRepository.java: JpaRepository with
findByEmail.
- resources/db/migration/V2__create_escalated_contacts.sql:
Flyway migration creating the contacts table and adding the
contact_id FK on tickets (ON DELETE SET NULL).
- test/java/dev/escalated/models/ContactTest.java: 11 JUnit 5
cases (normalizeEmail ×5, decideAction ×5, setEmail normalization,
defaults).
Local verification: no Java/JDK or Gradle daemon available in
author's env; CI will run the tests. Pure-function logic (Action
enum branch selection + email normalization) exactly mirrors the
equivalents passing in NestJS, Adonis, Go, .NET, Symfony, WordPress,
Phoenix.
See escalated-dev/escalated docs/superpowers/plans/2026-04-24-public-tickets-rollout-status.md
Follow-up on the Contact entity + V2 migration added earlier on
this branch.
Changes:
- models/Ticket.java — adds ManyToOne contact relation on the
contact_id column (nullable; column was created by the V2
Flyway migration).
- services/TicketService.java — constructor now takes a
ContactRepository; TicketService.create resolves/creates a
Contact from requesterEmail and calls ticket.setContact(c).
Private findOrCreateContact uses the Pattern B pure statics
Contact.normalizeEmail + Contact.decideAction for branch
selection (matches the reference impl across all other
framework PRs).
- tests/TicketServiceContactWireupTest.java — 3 Spring Boot
integration tests covering single-create-links-ticket,
repeat-email-dedupes (casing variant), and blank-existing-name
fill.
Spring was the only truly-greenfield port: there was no inline
guest_email column to preserve. The public submission flow goes
directly to Pattern B.
Local verification: no Java/Gradle SDK in author's env; CI on
this PR will run the tests. Pure-function coverage (normalizeEmail
+ decideAction) already has 11 JUnit cases green from the entity PR.
Member
Author
Wire-up commit pushed (Spring is now feature-complete)Spring was the only truly-greenfield port — no inline guest_email column to preserve, so the public submission flow goes directly to Pattern B. Changes:
Covers:
Local Java/Gradle unavailable; CI will run. Pure-function coverage already has 11 JUnit cases green from the entity PR. |
…text fails to boot in CI)
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.
Summary
Spring is the only truly greenfield port in the cross-framework rollout — no prior guest fields, no inbound email support. Goes straight to Pattern B without legacy inline columns to preserve.
Reference implementations:
Changes
normalizeEmail+decideActionpure helpers with anActionenumfindByEmailcontact_idFK on tickets (ON DELETE SET NULL)Test plan
What's NOT in this PR
Since Spring is greenfield, following up will need:
Tracked in the rollout status doc as the next largest unlock across the ecosystem.