Conversation
…/api/emails/generators/IntersectionNotificationSummaryEmailGenerator.java Co-authored-by: Matt Cook <mattheworion.cook@gmail.com>
…com/CDOT-CV/jpo-cvmanager into intersection-api-email-generation-1
There was a problem hiding this comment.
Pull request overview
Adds end-user email subscription management, including a public /unsubscribe webapp route that authenticates via a signed JWT embedded in email links, plus Intersection API endpoints/data model updates to fetch and persist subscription preferences.
Changes:
- Introduces a public
/unsubscribepage and a reusable subscription management form/UI in the webapp. - Adds Intersection API controllers/services/mappers to list/update subscription preferences (authenticated and unsubscribe-token flows).
- Updates email subscription persistence model (email type metadata such as description/required role) and related SQL scripts/tests.
Reviewed changes
Copilot reviewed 53 out of 54 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| webapp/src/store.tsx | Registers new RTK Query slices for subscription management/unsubscribe. |
| webapp/src/pages/Admin.tsx | Removes notification-related prefetch tied to old admin notification UI. |
| webapp/src/models/email-subscriptions.d.ts | Adds TS types for subscription payloads returned by Intersection API. |
| webapp/src/features/api/unsubscribeApiSlice.ts | RTK Query slice for unauthenticated unsubscribe-token subscription fetch/update. |
| webapp/src/features/api/subscriptionManagementApiSlice.ts | RTK Query slice for authenticated in-app subscription management. |
| webapp/src/features/adminNotificationTab/adminNotificationTabSlice.tsx | Removes legacy admin notification state management. |
| webapp/src/features/adminNotificationTab/adminNotificationTabSlice.test.ts | Removes tests for legacy admin notification slice. |
| webapp/src/features/adminNotificationTab/snapshots/AdminNotificationTab.test.tsx.snap | Removes snapshots for legacy admin notification UI. |
| webapp/src/features/adminNotificationTab/AdminNotificationTab.tsx | Removes legacy admin notification tab UI. |
| webapp/src/features/adminEditNotification/adminEditNotificationSlice.tsx | Removes legacy notification edit slice. |
| webapp/src/features/adminEditNotification/adminEditNotificationSlice.test.ts | Removes tests for legacy notification edit slice. |
| webapp/src/features/adminEditNotification/snapshots/AdminEditNotification.test.tsx.snap | Removes legacy notification edit snapshots. |
| webapp/src/features/adminEditNotification/AdminEditNotification.tsx | Removes legacy notification edit UI. |
| webapp/src/features/adminAddNotification/adminAddNotificationSlice.tsx | Removes legacy notification add slice. |
| webapp/src/features/adminAddNotification/adminAddNotificationSlice.test.ts | Removes tests for legacy notification add slice. |
| webapp/src/features/adminAddNotification/snapshots/AdminAddNotification.test.tsx.snap | Removes legacy notification add snapshots. |
| webapp/src/features/adminAddNotification/AdminAddNotification.tsx | Removes legacy notification add UI. |
| webapp/src/features/adminAddNotification/AdminAddNotification.test.tsx | Removes legacy notification add snapshot test. |
| webapp/src/components/SubscriptionManagement.tsx | New authenticated subscription settings page (replaces old settings page). |
| webapp/src/components/SubscriptionManagement.test.tsx | Updates snapshot test to cover new SubscriptionManagement component. |
| webapp/src/components/snapshots/SubscriptionManagement.test.tsx.snap | Adds snapshot baseline for SubscriptionManagement. |
| webapp/src/components/SubscriptionForm.tsx | Adds shared subscription preferences form (save/unsubscribe-all, per-frequency toggles). |
| webapp/src/components/SubscriptionForm.test.tsx | Adds snapshot test for SubscriptionForm. |
| webapp/src/components/snapshots/SubscriptionForm.test.tsx.snap | Adds snapshot baseline for SubscriptionForm. |
| webapp/src/Unsubscribe.tsx | Adds unauthenticated unsubscribe page driven by token query param. |
| webapp/src/Unsubscribe.test.tsx | Updates snapshot test target (currently mis-imports the icon). |
| webapp/src/snapshots/Unsubscribe.test.tsx.snap | Adds snapshot baseline for unsubscribe test. |
| webapp/src/Dashboard.tsx | Routes /dashboard/settings/* to SubscriptionManagement instead of legacy notifications UI. |
| webapp/src/App.tsx | Adds public /unsubscribe route outside Keycloak-protected routing. |
| services/intersection-api/api/src/main/java/us/dot/its/jpo/ode/api/services/EmailService.java | Implements subscription option retrieval and subscription update logic. |
| services/intersection-api/api/src/main/java/us/dot/its/jpo/ode/api/controllers/users/UnsubscribeController.java | Adds unsubscribe-token GET/POST endpoints for subscription management. |
| services/intersection-api/api/src/main/java/us/dot/its/jpo/ode/api/controllers/users/SubscriptionController.java | Adds authenticated GET/POST endpoints for subscription management. |
| services/intersection-api/api/src/main/java/us/dot/its/jpo/ode/api/keycloak/config/KeycloakSecurityConfig.java | Permits unauthenticated access to unsubscribe endpoints. |
| services/intersection-api/api/src/main/java/us/dot/its/jpo/ode/api/repositories/UserEmailNotificationRepository.java | Adds queries for subscriptions by user and a delete-by-type-and-user method. |
| services/intersection-api/api/src/main/java/us/dot/its/jpo/ode/api/repositories/EmailTypeRepository.java | Adds repository for email type metadata. |
| services/intersection-api/api/src/main/java/us/dot/its/jpo/ode/api/mappers/UserEmailNotificationMapper.java | Adds MapStruct mapper between entities and DTOs. |
| services/intersection-api/api/src/main/java/us/dot/its/jpo/ode/api/models/emails/UserEmailNotificationDto.java | Adds DTO used by new subscription endpoints. |
| services/intersection-api/api/src/main/java/us/dot/its/jpo/ode/api/models/emails/EmailSubscriptionGetResponse.java | Adds response wrapper for subscription GET endpoints. |
| services/intersection-api/api/src/main/java/us/dot/its/jpo/ode/api/models/emails/ManageSubscriptionsBody.java | Adds request body model (currently unused by new controllers). |
| services/intersection-api/api/src/main/java/us/dot/its/jpo/ode/api/models/postgres/tables/UserEmailNotification.java | Adds subscribed/frequency helper methods and entity equality config. |
| services/intersection-api/api/src/main/java/us/dot/its/jpo/ode/api/models/postgres/tables/EmailType.java | Adds required role relationship to email type metadata. |
| services/intersection-api/api/src/test/java/us/dot/its/jpo/ode/api/services/EmailServiceTest.java | Expands tests to cover subscription update scenarios. |
| services/intersection-api/api/src/test/java/us/dot/its/jpo/ode/api/controllers/users/UnsubscribeControllerTest.java | Adds controller tests for unsubscribe-token subscription management. |
| services/intersection-api/api/src/test/java/us/dot/its/jpo/ode/api/controllers/users/SubscriptionControllerTest.java | Adds controller tests for authenticated subscription management. |
| services/intersection-api/api/src/test/java/us/dot/its/jpo/ode/api/models/postgres/derived/EmailSubscriptionTest.java | Adds tests for subscription DTO helper methods. |
| services/intersection-api/api/src/test/java/us/dot/its/jpo/ode/api/emails/UnsubscribeTokenGeneratorTest.java | Adds unit tests for unsubscribe token generation/validation. |
| services/intersection-api/api/src/test/java/us/dot/its/jpo/ode/api/services/RsuCredentialManagementServiceTest.java | Formatting/import cleanup in existing tests. |
| services/intersection-api/api/src/test/resources/application-integration-test.yaml | Tweaks integration-test config (Mongo quoting, hikari pool sizing). |
| services/intersection-api/api/src/test/resources/application.properties | Adds default test datasource config (currently points to localhost postgres). |
| resources/sql_scripts/CVManager_CreateTables.sql | Extends schema for email type required role FK. |
| resources/sql_scripts/CVManager_SampleData.sql | Updates seed data for new email_type fields and inserts. |
| resources/sql_scripts/update_scripts/email_type_field_update.sql | Adds migration for email_type description column. |
| docker-compose-intersection.yml | Updates Intersection API emailer SMTP port setting. |
| .vscode/settings.json | Adds “unsubscription” to workspace dictionary. |
Comments suppressed due to low confidence (1)
webapp/src/Unsubscribe.test.tsx:16
- This test is rendering the MUI
Unsubscribeicon (@mui/icons-material) instead of the app's/unsubscribepage component (webapp/src/Unsubscribe.tsx). As written, the snapshot won’t exercise any of the unsubscribe page logic and will give a false sense of coverage. Import and render the localUnsubscribecomponent (default export from./Unsubscribe) instead.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 51 out of 52 changed files in this pull request and generated 6 comments.
Comments suppressed due to low confidence (1)
webapp/src/Unsubscribe.test.tsx:16
Unsubscribe.test.tsxis rendering the MUIUnsubscribeicon instead of the app's/unsubscribepage component. This makes the snapshot meaningless for the feature and would miss regressions in the actual unsubscribe flow. Import and render the localUnsubscribecomponent from./Unsubscribe(or the correct path) instead of@mui/icons-material.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 51 out of 52 changed files in this pull request and generated 5 comments.
Comments suppressed due to low confidence (1)
webapp/src/Unsubscribe.test.tsx:16
- The snapshot test is rendering MUI’s
Unsubscribeicon (@mui/icons-material) instead of the app’sUnsubscribepage component (./Unsubscribe). This makes the snapshot meaningless and won’t catch regressions in the unsubscribe flow. Import and render the page component (and provide any required router params/query string in the test).
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| ALTER TABLE public.email_type | ||
| ADD CONSTRAINT IF NOT EXISTS fk_role_id FOREIGN KEY (required_role) | ||
| REFERENCES public.roles (role_id) MATCH SIMPLE | ||
| ON UPDATE NO ACTION | ||
| ON DELETE NO ACTION; |
There was a problem hiding this comment.
PostgreSQL supports ADD COLUMN IF NOT EXISTS, but it does not support ALTER TABLE ... ADD CONSTRAINT IF NOT EXISTS ... in many commonly used Postgres versions. This migration may fail at runtime. Consider guarding constraint creation with a DO $$ ... IF NOT EXISTS (SELECT 1 FROM pg_constraint ...) THEN ... END IF; $$ block, or create the constraint unconditionally in a fresh migration where you can ensure it doesn’t already exist.
| ALTER TABLE public.email_type | |
| ADD CONSTRAINT IF NOT EXISTS fk_role_id FOREIGN KEY (required_role) | |
| REFERENCES public.roles (role_id) MATCH SIMPLE | |
| ON UPDATE NO ACTION | |
| ON DELETE NO ACTION; | |
| DO $$ | |
| BEGIN | |
| IF NOT EXISTS ( | |
| SELECT 1 | |
| FROM pg_constraint c | |
| JOIN pg_class t ON t.oid = c.conrelid | |
| JOIN pg_namespace n ON n.oid = t.relnamespace | |
| WHERE c.conname = 'fk_role_id' | |
| AND t.relname = 'email_type' | |
| AND n.nspname = 'public' | |
| ) THEN | |
| ALTER TABLE public.email_type | |
| ADD CONSTRAINT fk_role_id FOREIGN KEY (required_role) | |
| REFERENCES public.roles (role_id) MATCH SIMPLE | |
| ON UPDATE NO ACTION | |
| ON DELETE NO ACTION; | |
| END IF; | |
| END $$; |
|
I am wondering if it would be worth it to manage intersection subscriptions by organization, so a user who is an admin in one org and user in another could only subscribe to support requests in the first org? |
PR Details
Description
Emails generated by the Intersection API include an unsubscribe url. This PR adds the "unauthenticated" webapp page /unsubscribe, which authenticates the user using a signed JWT token generated by the intersection API and embedded in the unsubscribe url. This same page now also replaces the previous notification user settings page.
required_roleis a new field in the email_type object, denoting what user role is required to receive that type of notificationHow Has This Been Tested?
2026-04-21.13-21-11.mp4
This was tested using the following steps:
Types of changes
Checklist: