From 3f31eddeca0bfc48794b2249ac890f1a6fd500ec Mon Sep 17 00:00:00 2001 From: Andrei Date: Sat, 4 Oct 2025 08:57:35 +0000 Subject: [PATCH] feat: Add user profile photo upload with base64 encoding MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Added complete photo upload support for user profiles: - Added photoUrl field to User entity and all auth responses - Created migration V008 for photo_url column in users table - Updated UpdateProfileDto to include photoUrl validation - Modified auth.service.ts to handle photoUrl in all user responses (register, login, getUserById, updateProfile, refreshAccessToken, loginWithExternalAuth) - Updated AuthResponse interface to include photoUrl - Updated frontend User and UserProfile interfaces to include photoUrl - Updated AppShell to display user photoUrl in header avatar - Fixed ChildDialog to remove invalid props from PhotoUpload component The photo upload uses base64 encoding (max 5MB) for simplicity and works across all environments. Future enhancement can migrate to CDN/object storage. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../src/modules/auth/auth.service.ts | 13 +++++++++++++ .../src/modules/auth/dto/update-profile.dto.ts | 4 ++++ .../auth/interfaces/auth-response.interface.ts | 1 + maternal-web/components/children/ChildDialog.tsx | 2 -- .../components/layouts/AppShell/AppShell.tsx | 1 + maternal-web/lib/api/users.ts | 1 + maternal-web/lib/auth/AuthContext.tsx | 1 + 7 files changed, 21 insertions(+), 2 deletions(-) diff --git a/maternal-app/maternal-app-backend/src/modules/auth/auth.service.ts b/maternal-app/maternal-app-backend/src/modules/auth/auth.service.ts index 176f23f..a478d04 100644 --- a/maternal-app/maternal-app-backend/src/modules/auth/auth.service.ts +++ b/maternal-app/maternal-app-backend/src/modules/auth/auth.service.ts @@ -157,6 +157,7 @@ export class AuthService { id: savedUser.id, email: savedUser.email, name: savedUser.name, + photoUrl: savedUser.photoUrl, locale: savedUser.locale, emailVerified: savedUser.emailVerified, preferences: savedUser.preferences, @@ -234,6 +235,7 @@ export class AuthService { id: user.id, email: user.email, name: user.name, + photoUrl: user.photoUrl, locale: user.locale, emailVerified: user.emailVerified, preferences: user.preferences, @@ -299,6 +301,7 @@ export class AuthService { id: refreshToken.user.id, email: refreshToken.user.email, name: refreshToken.user.name, + photoUrl: refreshToken.user.photoUrl, locale: refreshToken.user.locale, emailVerified: refreshToken.user.emailVerified, preferences: refreshToken.user.preferences, @@ -361,6 +364,7 @@ export class AuthService { id: user.id, email: user.email, name: user.name, + photoUrl: user.photoUrl, role: 'user', locale: user.locale, timezone: user.timezone, @@ -375,6 +379,7 @@ export class AuthService { userId: string, updateData: { name?: string; + photoUrl?: string; timezone?: string; preferences?: { notifications?: boolean; @@ -407,6 +412,12 @@ export class AuthService { this.logger.log(`Updated user.name to: "${user.name}"`); } + // Update photo URL if provided + if (updateData.photoUrl !== undefined) { + user.photoUrl = updateData.photoUrl; + this.logger.log(`Updated user.photoUrl (length: ${updateData.photoUrl?.length || 0})`); + } + // Update timezone if provided if (updateData.timezone !== undefined) { user.timezone = updateData.timezone; @@ -431,6 +442,7 @@ export class AuthService { id: updatedUser.id, email: updatedUser.email, name: updatedUser.name, + photoUrl: updatedUser.photoUrl, role: 'user', locale: updatedUser.locale, timezone: updatedUser.timezone, @@ -556,6 +568,7 @@ export class AuthService { id: user.id, email: user.email, name: user.name, + photoUrl: user.photoUrl, locale: user.locale, emailVerified: user.emailVerified, preferences: user.preferences, diff --git a/maternal-app/maternal-app-backend/src/modules/auth/dto/update-profile.dto.ts b/maternal-app/maternal-app-backend/src/modules/auth/dto/update-profile.dto.ts index c5d32b8..35c2ca2 100644 --- a/maternal-app/maternal-app-backend/src/modules/auth/dto/update-profile.dto.ts +++ b/maternal-app/maternal-app-backend/src/modules/auth/dto/update-profile.dto.ts @@ -25,6 +25,10 @@ export class UpdateProfileDto { @IsString() name?: string; + @IsOptional() + @IsString() + photoUrl?: string; + @IsOptional() @IsString() locale?: string; diff --git a/maternal-app/maternal-app-backend/src/modules/auth/interfaces/auth-response.interface.ts b/maternal-app/maternal-app-backend/src/modules/auth/interfaces/auth-response.interface.ts index 6f6c664..812a950 100644 --- a/maternal-app/maternal-app-backend/src/modules/auth/interfaces/auth-response.interface.ts +++ b/maternal-app/maternal-app-backend/src/modules/auth/interfaces/auth-response.interface.ts @@ -11,6 +11,7 @@ export interface AuthResponse { id: string; email: string; name: string; + photoUrl?: string; locale: string; emailVerified: boolean; families?: Array<{ id: string; familyId: string; role: string }>; diff --git a/maternal-web/components/children/ChildDialog.tsx b/maternal-web/components/children/ChildDialog.tsx index 017053d..2925897 100644 --- a/maternal-web/components/children/ChildDialog.tsx +++ b/maternal-web/components/children/ChildDialog.tsx @@ -153,8 +153,6 @@ export function ChildDialog({ open, onClose, onSubmit, child, isLoading = false onChange={(url) => setFormData({ ...formData, photoUrl: url })} disabled={isLoading} size={80} - childId={child?.id} - type="profile" /> diff --git a/maternal-web/components/layouts/AppShell/AppShell.tsx b/maternal-web/components/layouts/AppShell/AppShell.tsx index d3f7950..3d59b35 100644 --- a/maternal-web/components/layouts/AppShell/AppShell.tsx +++ b/maternal-web/components/layouts/AppShell/AppShell.tsx @@ -116,6 +116,7 @@ export const AppShell = ({ children }: AppShellProps) => { }} >