-
Notifications
You must be signed in to change notification settings - Fork 0
feat(profile): implement complete email management system #92
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
- Add comprehensive email management UI component (LFXV2-520) - Create backend API endpoints for email CRUD operations (LFXV2-521) - Implement shared TypeScript interfaces for type safety (LFXV2-523) - Fix email preferences form initialization timing (LFXV2-524) - Add email verification requirements for preferences and primary selection Features implemented: * Email address management with primary email selection via star icon * Email preferences configuration for meetings, notifications, billing * Add/delete email functionality with validation and confirmation * Responsive UI with proper error handling and user feedback * Backend API with proper validation and business rules * Database-ready with RLS policies (schema in LFXV2-522) * Email verification requirements for all notification preferences * Frontend and backend validation for verified emails only Security enhancements: * Only verified emails can be set as primary * Only verified emails appear in notification preference dropdowns * User-friendly messaging explaining verification requirements * Backend validation prevents unverified emails from being primary 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> Signed-off-by: Asitha de Silva <asithade@gmail.com>
- Add resend verification button for unverified emails with toast feedback - Implement email verification requirements for notification preferences - Only verified emails can be set as primary or used in preferences - Add user-friendly messaging explaining verification requirements - Backend validation prevents unverified emails from being primary - Frontend and backend validation for secure email management 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> Signed-off-by: Asitha de Silva <asithade@gmail.com>
WalkthroughImplements end-to-end email management and preferences. Adds frontend UI and logic (forms, signals, actions), exposes tooltip support in shared button, introduces UserService APIs, wires server routes/controllers to new SupabaseService methods, and adds shared interfaces for emails and preferences. No modifications to existing routes; additions are additive across layers. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor U as User
participant UI as ProfileEmailComponent
participant USvc as UserService (FE)
participant API as ProfileController
participant DB as SupabaseService
rect rgba(200,230,255,0.3)
note over UI: Initial load
U->>UI: Open Email Settings
UI->>USvc: getUserEmails()
USvc->>API: GET /api/profile/emails
API->>DB: getEmailManagementData(userId)
DB-->>API: { emails, preferences }
API-->>USvc: 200 { emails, preferences }
USvc-->>UI: Data
UI-->>U: Render list & preferences
end
rect rgba(220,255,220,0.3)
note over UI: Add new email
U->>UI: Submit add email
UI->>USvc: addEmail(email)
USvc->>API: POST /api/profile/emails { email }
API->>DB: addUserEmail(userId, email)
DB-->>API: new UserEmail
API-->>USvc: 201 UserEmail
USvc-->>UI: New email
UI->>USvc: getUserEmails() (refresh)
USvc->>API: GET /api/profile/emails
API->>DB: getEmailManagementData(userId)
DB-->>API: data
API-->>USvc: 200 data
USvc-->>UI: Data
UI-->>U: Updated list
end
rect rgba(255,245,200,0.4)
note over UI: Set primary (verified only)
U->>UI: Click "Set as primary"
UI->>USvc: setPrimaryEmail(emailId)
USvc->>API: PUT /api/profile/emails/:id/primary
API->>DB: setPrimaryEmail(userId, id)
DB-->>API: ok
API-->>USvc: 200 { message }
USvc-->>UI: ok
UI->>USvc: getUserEmails() (refresh)
USvc-->>UI: data
UI-->>U: Primary updated
end
rect rgba(255,220,220,0.4)
note over UI: Delete email (guarded)
U->>UI: Click "Delete"
UI->>U: Confirm dialog
U-->>UI: Confirm
UI->>USvc: deleteEmail(emailId)
USvc->>API: DELETE /api/profile/emails/:id
API->>DB: deleteUserEmail(id, userId)
DB-->>API: ok
API-->>USvc: 204
USvc-->>UI: ok
UI->>USvc: getUserEmails() (refresh)
USvc-->>UI: data
UI-->>U: List updated
end
rect rgba(230,230,255,0.4)
note over UI: Update preferences
U->>UI: Save preferences
UI->>USvc: updateEmailPreferences(payload)
USvc->>API: PUT /api/profile/email-preferences
API->>DB: updateEmailPreferences(userId, payload)
DB-->>API: updated prefs
API-->>USvc: 200 prefs
USvc-->>UI: prefs
UI-->>U: Saved toast
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~75 minutes Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing touches
🧪 Generate unit tests
Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR implements a comprehensive email management system for user profiles, allowing users to manage multiple email addresses and configure notification preferences. The implementation spans frontend UI components, backend API endpoints, database interactions, and shared type definitions.
- Complete email address management with primary email designation and verification status
- Email preferences configuration for different notification types (general, meeting, billing)
- Full-stack implementation with Angular frontend, Express backend, and Supabase integration
Reviewed Changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| packages/shared/src/interfaces/user-profile.interface.ts | Adds TypeScript interfaces for email management data structures |
| apps/lfx-one/src/server/services/supabase.service.ts | Implements Supabase service methods for email CRUD operations |
| apps/lfx-one/src/server/routes/profile.route.ts | Defines REST API routes for email management endpoints |
| apps/lfx-one/src/server/controllers/profile.controller.ts | Implements controller methods with validation and error handling |
| apps/lfx-one/src/app/shared/services/user.service.ts | Adds frontend service methods for email management API calls |
| apps/lfx-one/src/app/shared/components/button/button.component.ts | Adds tooltip support to button component |
| apps/lfx-one/src/app/shared/components/button/button.component.html | Updates button template with tooltip properties |
| apps/lfx-one/src/app/modules/profile/email/profile-email.component.ts | Complete Angular component implementation with reactive forms and state management |
| apps/lfx-one/src/app/modules/profile/email/profile-email.component.html | Full UI template for email management with accessibility features |
Comments suppressed due to low confidence (1)
apps/lfx-one/src/server/controllers/profile.controller.ts:1
- This comment appears to be floating without proper context. It should either be moved to line 886 as '// Update existing preferences' or removed if it's redundant with the code structure.
// Copyright The Linux Foundation and each contributor to LFX.
Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.
🚀 Deployment StatusYour branch has been deployed to: https://ui-pr-92.dev.v2.cluster.linuxfound.info Deployment Details:
The deployment will be automatically removed when this PR is closed. |
✅ E2E Tests PassedBrowser: chromium All E2E tests passed successfully. Test Configuration
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 7
🧹 Nitpick comments (18)
apps/lfx-one/src/app/shared/components/button/button.component.ts (1)
62-65: Name the public API independent of PrimeNG; keep “pTooltip” as an alias.Expose a neutral input name (tooltip) and alias it to pTooltip for template compatibility. Also, type tooltipPosition to allowed values to catch mistakes early.
Apply this diff:
- // Tooltip - public readonly pTooltip = input<string | undefined>(undefined); - public readonly tooltipPosition = input<string>('top'); + // Tooltip + // Keep external binding `[pTooltip]` but expose neutral API internally. + public readonly tooltip = input<string | undefined>(undefined, { alias: 'pTooltip' }); + type TooltipPosition = 'top' | 'right' | 'bottom' | 'left'; + public readonly tooltipPosition = input<TooltipPosition>('top');apps/lfx-one/src/app/shared/components/button/button.component.html (1)
21-22: Update bindings to match the neutral input name after aliasing.If you adopt the alias refactor, bind the internal signal
tooltip()here.- [pTooltip]="pTooltip()" + [pTooltip]="tooltip()" - [tooltipPosition]="tooltipPosition()" + [tooltipPosition]="tooltipPosition()"And similarly below:
- [pTooltip]="pTooltip()" + [pTooltip]="tooltip()" - [tooltipPosition]="tooltipPosition()" + [tooltipPosition]="tooltipPosition()"Also applies to: 52-53
apps/lfx-one/src/app/modules/profile/email/profile-email.component.html (3)
46-47: Replace nested ternaries in template expressions.Guidelines disallow nested ternaries; move this logic to helpers/computed in the component TS.
- [pTooltip]="email.isPrimary ? 'Primary email' : email.is_verified ? 'Set as primary email' : 'Verify email to set as primary'" + [pTooltip]="primaryTooltip(email)" ... - [attr.aria-label]="email.isPrimary ? 'Primary email' : email.is_verified ? 'Set as primary email' : 'Verify email to set as primary'" + [attr.aria-label]="primaryAriaLabel(email)"Add to profile-email.component.ts:
primaryTooltip(e: { isPrimary: boolean; is_verified: boolean }) { if (e.isPrimary) return 'Primary email'; return e.is_verified ? 'Set as primary email' : 'Verify email to set as primary'; } primaryAriaLabel(e: { isPrimary: boolean; is_verified: boolean }) { return this.primaryTooltip(e); }Also applies to: 55-55
40-56: Add aria-pressed to star toggle for better a11y.Conveys pressed state to assistive tech.
- [attr.aria-label]="primaryAriaLabel(email)"> + [attr.aria-label]="primaryAriaLabel(email)" + [attr.aria-pressed]="email.isPrimary">
255-258: Add data-testid to new components for consistency.Required by guidelines for new Angular components.
-<p-toast position="top-right" /> +<p-toast position="top-right" data-testid="profile-email-toast" /> -<p-confirmDialog /> +<p-confirmDialog data-testid="profile-email-confirm" />apps/lfx-one/src/server/services/supabase.service.ts (3)
721-727: Harden unique-email error handling.Rely on HTTP 409 from PostgREST; avoid brittle string matching.
- if (!response.ok) { - const errorData = await response.text(); - if (response.status === 409 || errorData.includes('unique')) { - throw new Error('Email address is already in use by another user'); - } - throw new Error(`Failed to add email: ${response.status} ${response.statusText}`); - } + if (!response.ok) { + if (response.status === 409) { + throw new Error('Email address is already in use by another user'); + } + const errorText = await response.text(); + throw new Error(`Failed to add email: ${response.status} ${response.statusText}: ${errorText}`); + }
736-763: Avoid TOCTOU on delete; guard at the delete query.Let the DB enforce “not primary” at delete time; still keep pre-checks for UX.
- const url = `${this.baseUrl}/user_emails?id=eq.${emailId}&user_id=eq.${userId}`; + const url = `${this.baseUrl}/user_emails?id=eq.${emailId}&user_id=eq.${userId}&is_primary=eq.false`;Confirm DB trigger exists to prevent last-email deletion; otherwise a concurrent delete could still remove the last email.
860-901: Validate preference email IDs belong to user and are verified before write.Prevents cross-user references/unverified selections if DB policies are relaxed.
- public async updateEmailPreferences(userId: string, preferences: UpdateEmailPreferencesRequest): Promise<EmailPreferences> { + public async updateEmailPreferences(userId: string, preferences: UpdateEmailPreferencesRequest): Promise<EmailPreferences> { + // Validate referenced emails (if provided) + const ids = [preferences.notification_email_id, preferences.meeting_email_id, preferences.billing_email_id].filter(Boolean) as string[]; + if (ids.length) { + const params = new URLSearchParams({ user_id: `eq.${userId}`, id: `in.(${ids.join(',')})`, select: 'id,is_verified' }); + const url = `${this.baseUrl}/user_emails?${params.toString()}`; + const r = await fetch(url, { method: 'GET', headers: this.getHeaders(), signal: AbortSignal.timeout(this.timeout) }); + if (!r.ok) throw new Error(`Failed to validate preference emails: ${r.status} ${r.statusText}`); + const rows: Array<{ id: string; is_verified: boolean }> = await r.json(); + const ok = new Map(rows.map((e) => [e.id, e.is_verified])); + for (const id of ids) { + if (!ok.has(id)) throw new Error('Invalid email selected for preferences'); + if (!ok.get(id)) throw new Error('Only verified emails can be used for preferences'); + } + }apps/lfx-one/src/app/shared/services/user.service.ts (1)
62-64: Rename getUserEmails to getEmailManagementData (or make the API return only emails)Method name implies a list of emails but the method returns EmailManagementData (emails + preferences) — rename or change the API to avoid confusion.
- Option A (recommended): rename the client method and update callers.
- public getUserEmails(): Observable<EmailManagementData> { - return this.http.get<EmailManagementData>('/api/profile/emails'); - } + public getEmailManagementData(): Observable<EmailManagementData> { + return this.http.get<EmailManagementData>('/api/profile/emails'); + }Update callers (e.g. apps/lfx-one/src/app/modules/profile/email/profile-email.component.ts).
- Option B: keep getUserEmails() name but make the route return only UserEmail[] (adjust server controller to call supabaseService.getUserEmails or change supabase.service.ts instead). Relevant server files: apps/lfx-one/src/server/controllers/profile.controller.ts and apps/lfx-one/src/server/services/supabase.service.ts.
apps/lfx-one/src/server/controllers/profile.controller.ts (4)
228-231: Rename localuserIdtouserto remove confusion and improve readability.Variable currently holds a user record, not an ID.
Apply this diff:
- const userId = await this.supabaseService.getUser(username); + const user = await this.supabaseService.getUser(username); - if (!userId) { + if (!user) { Logger.error(req, 'get_user_emails', startTime, new Error('User not found')); const validationError = ServiceValidationError.forField('user_id', 'User not found', { operation: 'get_user_emails', service: 'profile_controller', path: req.path, }); return next(validationError); } - const emailData = await this.supabaseService.getEmailManagementData(userId.id); + const emailData = await this.supabaseService.getEmailManagementData(user.id); Logger.success(req, 'get_user_emails', startTime, { - user_id: userId.id, + user_id: user.id, email_count: emailData.emails.length, has_preferences: !!emailData.preferences, });Also applies to: 242-246
369-379: ValidateemailIdformat (UUID) and fail fast.Prevents unnecessary DB calls and surfaces clearer errors for malformed IDs.
Apply this diff:
if (!emailId) { Logger.error(req, 'delete_user_email', startTime, new Error('Email ID is required')); const validationError = ServiceValidationError.forField('email_id', 'Email ID is required', { operation: 'delete_user_email', service: 'profile_controller', path: req.path, }); return next(validationError); } + + // Validate UUID format to fail fast + if (!uuidValidate(emailId)) { + const validationError = ServiceValidationError.forField('email_id', 'Invalid email ID format', { + operation: 'delete_user_email', + service: 'profile_controller', + path: req.path, + }); + return next(validationError); + }Add this import near the top of the file:
import { validate as uuidValidate } from 'uuid';
503-513: Fix error message inconsistency (“authentication required” vs “not found”).Log says “User not found” but the error message says “User authentication required”.
Apply this diff:
- const validationError = ServiceValidationError.forField('user_id', 'User authentication required', { + const validationError = ServiceValidationError.forField('user_id', 'User not found', { operation: 'get_email_preferences', service: 'profile_controller', path: req.path, });
566-586: Validate preference values’ types and narrowupdateDatato the request shape.Ensure values are string UUIDs or null; type
updateDatato avoid accidental fields.Apply this diff:
- const preferences: UpdateEmailPreferencesRequest = req.body; - const allowedFields = ['meeting_email_id', 'notification_email_id', 'billing_email_id']; - const updateData: any = {}; + const preferences: UpdateEmailPreferencesRequest = req.body; + const allowedFields: (keyof UpdateEmailPreferencesRequest)[] = [ + 'meeting_email_id', + 'notification_email_id', + 'billing_email_id', + ]; + const updateData: Partial<UpdateEmailPreferencesRequest> = {}; - for (const [key, value] of Object.entries(preferences)) { - if (allowedFields.includes(key)) { - updateData[key] = value; - } - } + for (const [key, value] of Object.entries(preferences)) { + if (allowedFields.includes(key as keyof UpdateEmailPreferencesRequest)) { + if (value === null || typeof value === 'string') { + updateData[key as keyof UpdateEmailPreferencesRequest] = value as any; + } else { + const validationError = ServiceValidationError.forField( + key, + 'Must be a string UUID or null', + { operation: 'update_email_preferences', service: 'profile_controller', path: req.path }, + ); + return next(validationError); + } + } + }apps/lfx-one/src/app/modules/profile/email/profile-email.component.ts (5)
246-257: Add a “None” option to preference dropdowns.Requirements call for a “None” choice; only verified emails are currently included.
Apply this diff:
- private initializeEmailOptions(): Signal<EmailOption[]> { + private initializeEmailOptions(): Signal<SelectItem[]> { return computed(() => { // Only include verified emails in notification preferences - const verifiedEmails = this.emails() + const verifiedEmails = this.emails() .filter((email) => email.is_verified) .map((email) => ({ label: email.email, value: email.id, })); - return verifiedEmails; + return [{ label: 'None', value: null }, ...verifiedEmails]; }); }
232-241: Set loading=true on each refresh emission (not just initial load).Otherwise subsequent refreshes won’t show a loading state.
Apply this diff:
- return toSignal( - this.refresh.pipe( - switchMap(() => + return toSignal( + this.refresh.pipe( + tap(() => this.loading.set(true)), + switchMap(() => this.userService.getUserEmails().pipe( tap((data) => { this.initializePreferencesForm(data.preferences); }), finalize(() => this.loading.set(false)) ) ) ), { initialValue: null } );
15-15: Prefer PrimeNG’s official option type instead of a localEmailOption.Aligns with coding guideline: reference PrimeNG component interfaces for related types.
Apply this diff:
-import { ConfirmationService, MessageService } from 'primeng/api'; +import { ConfirmationService, MessageService, SelectItem } from 'primeng/api';-interface EmailOption { - label: string; - value: string | null; -}- private initializeEmailOptions(): Signal<EmailOption[]> { + private initializeEmailOptions(): Signal<SelectItem[]> {Also applies to: 21-24, 246-247
49-50: Optional: AvoidBehaviorSubject<void>(undefined)for kick-off.Use an explicit trigger subject or a
merge(of(null), refresh)to avoidvoid/undefinedquirks.Example:
private refresh = new Subject<void>(); // ... return toSignal(merge(of(null), this.refresh).pipe(/* ... */), { initialValue: null });
95-118: Optional: Guard subscriptions withtakeUntilDestroyedfor safety.HTTP completes, but this hardens against edge cases (component destroyed mid-flight).
Example:
import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { DestroyRef, inject } from '@angular/core'; private readonly destroyRef = inject(DestroyRef); // ... this.userService.addEmail(email) .pipe(takeUntilDestroyed(this.destroyRef), finalize(() => this.addingEmail.set(false))) .subscribe(/* ... */);Also applies to: 135-153, 164-183, 205-226
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Disabled knowledge base sources:
- Jira integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (9)
apps/lfx-one/src/app/modules/profile/email/profile-email.component.html(1 hunks)apps/lfx-one/src/app/modules/profile/email/profile-email.component.ts(1 hunks)apps/lfx-one/src/app/shared/components/button/button.component.html(2 hunks)apps/lfx-one/src/app/shared/components/button/button.component.ts(2 hunks)apps/lfx-one/src/app/shared/services/user.service.ts(2 hunks)apps/lfx-one/src/server/controllers/profile.controller.ts(2 hunks)apps/lfx-one/src/server/routes/profile.route.ts(1 hunks)apps/lfx-one/src/server/services/supabase.service.ts(2 hunks)packages/shared/src/interfaces/user-profile.interface.ts(1 hunks)
🧰 Additional context used
📓 Path-based instructions (5)
packages/shared/src/interfaces/**/*.ts
📄 CodeRabbit inference engine (CLAUDE.md)
Place all TypeScript interfaces in the shared package at packages/shared/src/interfaces
Files:
packages/shared/src/interfaces/user-profile.interface.ts
**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.{ts,tsx}: Use TypeScript interfaces instead of union types for better maintainability
When defining PrimeNG-related types, reference the official PrimeNG component interfaces
Files:
packages/shared/src/interfaces/user-profile.interface.tsapps/lfx-one/src/app/shared/services/user.service.tsapps/lfx-one/src/server/routes/profile.route.tsapps/lfx-one/src/app/shared/components/button/button.component.tsapps/lfx-one/src/app/modules/profile/email/profile-email.component.tsapps/lfx-one/src/server/services/supabase.service.tsapps/lfx-one/src/server/controllers/profile.controller.ts
**/*.{ts,tsx,js,jsx,mjs,cjs,html,css,scss}
📄 CodeRabbit inference engine (CLAUDE.md)
Include required license headers on all source files
Files:
packages/shared/src/interfaces/user-profile.interface.tsapps/lfx-one/src/app/shared/components/button/button.component.htmlapps/lfx-one/src/app/shared/services/user.service.tsapps/lfx-one/src/app/modules/profile/email/profile-email.component.htmlapps/lfx-one/src/server/routes/profile.route.tsapps/lfx-one/src/app/shared/components/button/button.component.tsapps/lfx-one/src/app/modules/profile/email/profile-email.component.tsapps/lfx-one/src/server/services/supabase.service.tsapps/lfx-one/src/server/controllers/profile.controller.ts
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Do not nest ternary expressions
Files:
packages/shared/src/interfaces/user-profile.interface.tsapps/lfx-one/src/app/shared/services/user.service.tsapps/lfx-one/src/server/routes/profile.route.tsapps/lfx-one/src/app/shared/components/button/button.component.tsapps/lfx-one/src/app/modules/profile/email/profile-email.component.tsapps/lfx-one/src/server/services/supabase.service.tsapps/lfx-one/src/server/controllers/profile.controller.ts
apps/lfx-one/src/**/*.html
📄 CodeRabbit inference engine (CLAUDE.md)
apps/lfx-one/src/**/*.html: Always add data-testid attributes when creating new Angular components for reliable test targeting
Use data-testid naming convention [section]-[component]-[element]
Files:
apps/lfx-one/src/app/shared/components/button/button.component.htmlapps/lfx-one/src/app/modules/profile/email/profile-email.component.html
🧠 Learnings (1)
📚 Learning: 2025-09-16T03:32:46.518Z
Learnt from: CR
PR: linuxfoundation/lfx-v2-ui#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-16T03:32:46.518Z
Learning: All PrimeNG components are wrapped in LFX components to keep UI library independence
Applied to files:
apps/lfx-one/src/app/shared/components/button/button.component.ts
🧬 Code graph analysis (5)
apps/lfx-one/src/app/shared/services/user.service.ts (1)
packages/shared/src/interfaces/user-profile.interface.ts (5)
EmailManagementData(116-119)UserEmail(72-82)AddEmailRequest(100-102)EmailPreferences(87-95)UpdateEmailPreferencesRequest(107-111)
apps/lfx-one/src/app/shared/components/button/button.component.ts (1)
apps/lfx-one/src/app/modules/profile/email/profile-email.component.ts (1)
Component(26-268)
apps/lfx-one/src/app/modules/profile/email/profile-email.component.ts (1)
packages/shared/src/interfaces/user-profile.interface.ts (4)
UserEmail(72-82)UpdateEmailPreferencesRequest(107-111)EmailManagementData(116-119)EmailPreferences(87-95)
apps/lfx-one/src/server/services/supabase.service.ts (1)
packages/shared/src/interfaces/user-profile.interface.ts (4)
UserEmail(72-82)EmailPreferences(87-95)UpdateEmailPreferencesRequest(107-111)EmailManagementData(116-119)
apps/lfx-one/src/server/controllers/profile.controller.ts (1)
packages/shared/src/interfaces/user-profile.interface.ts (2)
AddEmailRequest(100-102)UpdateEmailPreferencesRequest(107-111)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: build-and-push
🔇 Additional comments (7)
apps/lfx-one/src/app/shared/components/button/button.component.ts (1)
9-15: Good addition: TooltipModule wired into the wrapper.Importing TooltipModule at the wrapper level keeps the tooltip directive encapsulated and consistent with our “wrap PrimeNG” approach.
packages/shared/src/interfaces/user-profile.interface.ts (1)
97-119: Interfaces look good; fields and nullability match usage.No blockers here.
Verify i18n/serialization never relies on Date objects; these are ISO strings everywhere, which is consistent with PostgREST.
apps/lfx-one/src/server/services/supabase.service.ts (1)
906-913: LGTM: efficient combined fetch.Parallelizing emails and preferences is right; keep it.
apps/lfx-one/src/app/shared/services/user.service.ts (1)
98-100: LGTM: preferences update endpoint contract.Shape matches shared interface; server can upsert.
apps/lfx-one/src/server/routes/profile.route.ts (2)
28-30: No change required — route already returns combined EmailManagementDataprofileController.getUserEmails calls this.supabaseService.getEmailManagementData and returns emailData (apps/lfx-one/src/server/controllers/profile.controller.ts — see the assignment to
emailData); leave router.get('/emails', profileController.getUserEmails) as-is.Likely an incorrect or invalid review comment.
40-45: Missing “resend verification” endpoint referenced by UI.Template calls resendVerification(email) but there’s no route. Add a POST route and controller handler.
// GET /api/profile/email-preferences - Get user email preferences router.get('/email-preferences', (req, res, next) => profileController.getEmailPreferences(req, res, next)); // PUT /api/profile/email-preferences - Update user email preferences router.put('/email-preferences', (req, res, next) => profileController.updateEmailPreferences(req, res, next)); + +// POST /api/profile/emails/:emailId/resend-verification - Resend verification email +router.post('/emails/:emailId/resend-verification', (req, res, next) => + profileController.resendVerificationEmail(req, res, next) +);If rate limiting is required (issue LFXV2-521), ensure this route is covered by your throttling middleware.
apps/lfx-one/src/server/controllers/profile.controller.ts (1)
467-475: Backend enforces verified-only primary email — confirmedsetPrimaryEmail in apps/lfx-one/src/server/services/supabase.service.ts validates email.is_verified and throws "Only verified emails can be set as primary" (see the check around lines ~792–795).
🧹 Deployment RemovedThe deployment for PR #92 has been removed. |
Summary
Complete implementation of email management functionality for user profiles.
Features Implemented
Email Address Management (LFXV2-520)
Email Preferences Configuration (LFXV2-520)
Email Verification System
Backend Implementation (LFXV2-521)
Database Schema (LFXV2-522)
Shared Types (LFXV2-523)
Bug Fixes (LFXV2-524)
Security Enhancements
Technical Implementation
Files Modified
Frontend
apps/lfx-one/src/app/modules/profile/email/profile-email.component.tsapps/lfx-one/src/app/modules/profile/email/profile-email.component.htmlapps/lfx-one/src/app/shared/services/user.service.tsBackend
apps/lfx-one/src/server/controllers/profile.controller.tsapps/lfx-one/src/server/routes/profile.route.tsapps/lfx-one/src/server/services/supabase.service.tsShared
packages/shared/src/interfaces/user-profile.interface.tsTest Plan
Related JIRA Tickets
Parent Epic
🤖 Generated with Claude Code