Skip to content

feat(contact): Contact entity for public-ticket dedupe (greenfield Pattern B)#20

Draft
mpge wants to merge 4 commits intomainfrom
feat/public-tickets-contact-convergence
Draft

feat(contact): Contact entity for public-ticket dedupe (greenfield Pattern B)#20
mpge wants to merge 4 commits intomainfrom
feat/public-tickets-contact-convergence

Conversation

@mpge
Copy link
Copy Markdown
Member

@mpge mpge commented Apr 24, 2026

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

  • Contact JPA entity — unique email (lowercased + trimmed), nullable userId, metadata JSON; static normalizeEmail + decideAction pure helpers with an Action enum
  • ContactRepository — JpaRepository with findByEmail
  • V2__create_escalated_contacts.sql — Flyway migration: creates contacts table + adds nullable contact_id FK on tickets (ON DELETE SET NULL)
  • ContactTest — 11 JUnit 5 cases (normalizeEmail ×5, decideAction ×5, setEmail normalization, defaults)

Test plan

  • CI runs 11 new JUnit 5 cases (local Java/Gradle unavailable)
  • Pure-function logic matches 9 other frameworks' passing equivalents

What's NOT in this PR

Since Spring is greenfield, following up will need:

  • Guest submission controller (`POST /escalated/widget/tickets`)
  • Inbound email webhook + router
  • Workflow executor wiring (Spring has Workflow but no runner)

Tracked in the rollout status doc as the next largest unlock across the ecosystem.

mpge added 2 commits April 24, 2026 01:59
…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.
@mpge
Copy link
Copy Markdown
Member Author

mpge commented Apr 24, 2026

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:

  • models/Ticket.java — ManyToOne contact relation on contact_id
  • services/TicketService.java — constructor takes ContactRepository; create() resolves/creates a Contact from requesterEmail via findOrCreateContact (uses Pattern B Contact.normalizeEmail + Contact.decideAction pure statics)
  • tests/TicketServiceContactWireupTest.java — 3 Spring Boot integration tests

Covers:

  • Single create links the ticket to a new Contact
  • Repeat email (casing variant) dedupes onto the same Contact
  • Existing contact with blank name gets filled

Local Java/Gradle unavailable; CI will run. Pure-function coverage already has 11 JUnit cases green from the entity PR.

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