feat(profile): implement user profile system#68
feat(profile): implement user profile system#68bernardev254 wants to merge 2 commits intoStellar-Rent:mainfrom
Conversation
WalkthroughThis update introduces a comprehensive user profile system to the backend application. It adds new profile-related routes and controllers, expands the user profile schema, integrates Supabase authentication and storage, and implements middleware for authentication and file uploads. The database schema and service logic are refactored to support extended profile fields and avatar uploads. Changes
Sequence Diagram(s)sequenceDiagram
participant Client
participant API
participant AuthMiddleware
participant Supabase
participant ProfileService
participant Storage
Client->>API: PATCH /profile (with JWT)
API->>AuthMiddleware: Authenticate token
AuthMiddleware->>Supabase: getUser(token)
Supabase-->>AuthMiddleware: user object
AuthMiddleware-->>API: attach user to req
API->>ProfileService: updateUserProfile(userId, updates)
ProfileService->>Supabase: Update profiles table
Supabase-->>ProfileService: Updated profile
ProfileService-->>API: profile
API-->>Client: 200 OK + updated profile
Client->>API: POST /profile/avatar (with file)
API->>AuthMiddleware: Authenticate token
AuthMiddleware->>Supabase: getUser(token)
Supabase-->>AuthMiddleware: user object
AuthMiddleware-->>API: attach user to req
API->>ProfileService: uploadUserAvatar(userId, req)
ProfileService->>Storage: Upload file to Supabase storage
Storage-->>ProfileService: avatar_url
ProfileService->>Supabase: Update profiles.avatar_url
Supabase-->>ProfileService: Updated profile
ProfileService-->>API: avatar_url
API-->>Client: 200 OK + avatar_url
Assessment against linked issues
Assessment against linked issues: Out-of-scope changes
Possibly related PRs
Poem
📜 Recent review detailsConfiguration used: CodeRabbit UI 📒 Files selected for processing (4)
🚧 Files skipped from review as they are similar to previous changes (4)
✨ Finishing Touches
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Actionable comments posted: 19
🧹 Nitpick comments (7)
apps/backend/.eslintrc.json (1)
13-16: Avoid duplicateno-unused-varsrules
The base ESLint rule overlaps with the TS-specific rule, which can lead to redundant warnings. Consider disabling the core rule instead:{ "rules": { - "no-unused-vars": "warn", + "no-unused-vars": "off", "@typescript-eslint/no-unused-vars": "warn" } }apps/backend/src/types/userProfile.ts (1)
1-24: Consider adding validation constraints and addressing potential serialization issues.The interface structure is comprehensive, but there are a few areas for improvement:
- Empty updates: All fields being optional allows empty update objects
- Date serialization: The
Datetype may cause issues in JSON serialization/deserializationConsider these improvements:
export interface UserProfileUpdate { email?: string; name?: string; avatar_url?: string; phone?: string; address?: { street?: string; city?: string; country?: string; postal_code?: string; }; preferences?: { notifications?: boolean; newsletter?: boolean; language?: string; }; social_links?: { facebook?: string; twitter?: string; instagram?: string; }; verification_status?: 'unverified' | 'pending' | 'verified'; - last_active?: Date; + last_active?: string; // ISO string format for JSON compatibility } +// Helper type to ensure at least one field is provided +export type ValidUserProfileUpdate = UserProfileUpdate & { + [K in keyof UserProfileUpdate]: UserProfileUpdate[K] extends undefined ? never : UserProfileUpdate[K]; +}[keyof UserProfileUpdate] extends never ? never : UserProfileUpdate;apps/backend/src/db/init.ts (2)
12-24: Consider adding indexes for better query performance.The current schema lacks indexes on frequently queried columns like
verification_statusandlast_active, which could impact performance as the user base grows.Add indexes after table creation:
); `); + await client.query(` + CREATE INDEX IF NOT EXISTS idx_profiles_verification_status + ON public.profiles(verification_status); + + CREATE INDEX IF NOT EXISTS idx_profiles_last_active + ON public.profiles(last_active); + `);
26-31: Improve error logging and add connection retry logic.The current error handling logs errors but doesn't distinguish between different failure types. Consider adding more specific error handling and retry logic for transient connection issues.
Enhance error handling:
} catch (error) { - console.error('❌ Failed to initialize DB:', error); + console.error('❌ Failed to initialize DB:', { + message: error instanceof Error ? error.message : 'Unknown error', + code: error instanceof Error && 'code' in error ? error.code : undefined, + }); + throw error; // Re-throw to allow caller to handle initialization failure } finally { - await client.end(); + try { + await client.end(); + } catch (closeError) { + console.warn('Warning: Failed to close database connection:', closeError); + } }apps/backend/src/controllers/profile.controller.ts (1)
36-43: Improve error handling consistency in uploadAvatar.The function follows the same pattern as others but should provide more specific error messages for file upload issues.
Enhance error handling for file uploads:
export const uploadAvatar = async (req: Request, res: Response) => { if (!req.user) return res.status(401).json({ error: 'Usuario no autenticado' }); const result = await uploadUserAvatar(req.user.id, req); - if (result.error) return res.status(400).json({ error: result.error.message }); + if (result.error) { + const errorMessage = result.error.message; + if (errorMessage.includes('No file uploaded')) { + return res.status(400).json({ error: 'No se subió ningún archivo' }); + } + if (errorMessage.includes('Invalid file type')) { + return res.status(400).json({ error: 'Tipo de archivo no válido' }); + } + if (errorMessage.includes('File size exceeds')) { + return res.status(413).json({ error: 'Archivo demasiado grande' }); + } + return res.status(500).json({ error: 'Error al subir avatar' }); + } res.json({ avatar_url: result.avatar_url }); };apps/backend/src/types/auth.types.ts (1)
57-57: Consider adding JSDoc comment forlast_activefield.Since
last_activestores ISO date strings, adding documentation would improve clarity for other developers.- last_active: string; + /** ISO 8601 date string representing the user's last activity */ + last_active: string;apps/backend/src/config/supabase.ts (1)
30-30: Consider using timestamp type in the database schema.While storing dates as ISO strings works, using a proper
timestamportimestamptzcolumn type in the database would provide better performance for date-based queries and automatic timezone handling.If you need to keep the string type for compatibility, consider adding a database-level constraint to validate ISO format or creating an index on this column for better query performance.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (21)
apps/backend/.env.example(1 hunks)apps/backend/.eslintrc.json(1 hunks)apps/backend/package.json(1 hunks)apps/backend/src/config/supabase.ts(1 hunks)apps/backend/src/controllers/auth.controller.ts(1 hunks)apps/backend/src/controllers/authController.ts(0 hunks)apps/backend/src/controllers/profile.controller.ts(1 hunks)apps/backend/src/db/init.ts(1 hunks)apps/backend/src/index.ts(4 hunks)apps/backend/src/middleware/auth.middleware.ts(1 hunks)apps/backend/src/middleware/multer.ts(1 hunks)apps/backend/src/routes/auth.ts(1 hunks)apps/backend/src/routes/index.ts(1 hunks)apps/backend/src/routes/profile.routes.ts(1 hunks)apps/backend/src/services/a.ts(1 hunks)apps/backend/src/services/auth.service.ts(1 hunks)apps/backend/src/services/profile.service.ts(1 hunks)apps/backend/src/types/auth.types.ts(1 hunks)apps/backend/src/types/express/index.d.ts(1 hunks)apps/backend/src/types/userProfile.ts(1 hunks)apps/backend/src/validators/auth.validator.ts(2 hunks)
💤 Files with no reviewable changes (1)
- apps/backend/src/controllers/authController.ts
🧰 Additional context used
🧬 Code Graph Analysis (8)
apps/backend/src/routes/auth.ts (2)
apps/backend/src/validators/auth.validator.ts (2)
validateLogin(5-18)validateRegister(20-33)apps/backend/src/controllers/auth.controller.ts (2)
login(23-38)register(4-21)
apps/backend/src/routes/profile.routes.ts (3)
apps/backend/src/middleware/auth.middleware.ts (1)
authenticateToken(4-25)apps/backend/src/controllers/profile.controller.ts (4)
getProfile(9-16)updateProfile(18-25)deleteAccount(27-34)uploadAvatar(36-43)apps/backend/src/middleware/multer.ts (1)
upload(4-4)
apps/backend/src/types/express/index.d.ts (1)
apps/backend/src/types/auth.types.ts (1)
User(11-15)
apps/backend/src/index.ts (1)
apps/backend/src/db/init.ts (1)
initializeDatabase(4-32)
apps/backend/src/controllers/profile.controller.ts (1)
apps/backend/src/services/profile.service.ts (4)
getUserProfile(5-9)updateUserProfile(11-19)deleteUserAccount(21-24)uploadUserAvatar(26-50)
apps/backend/src/services/auth.service.ts (2)
apps/backend/src/config/supabase.ts (1)
supabase(49-61)apps/backend/src/types/auth.types.ts (1)
AuthResponse(66-69)
apps/backend/src/controllers/auth.controller.ts (1)
apps/backend/src/services/auth.service.ts (2)
registerUser(15-69)loginUser(11-13)
apps/backend/src/middleware/auth.middleware.ts (1)
apps/backend/src/config/supabase.ts (1)
supabase(49-61)
🔇 Additional comments (15)
apps/backend/.eslintrc.json (5)
2-5: Valid environment configuration
Enablingnodeandes2021environments aligns with the backend’s runtime and language target.
6-6: Extends are correct
Combiningeslint:recommendedwithplugin:@typescript-eslint/recommendedensures both core and TS-specific linting rules are applied.
7-7: Parser configuration is correct
Using@typescript-eslint/parseris required for parsing TypeScript syntax.
8-11: Parser options are consistent
SpecifyingecmaVersion: 12(ES2021) andsourceType: "module"matches the project’s module system and language features.
12-12: Plugin declaration is correct
Including the@typescript-eslintplugin is necessary for the TS ruleset.apps/backend/package.json (1)
22-22:Details
✅ Verification successful
Verify the multer package version for validity and security.
Please ensure that multer version 2.0.0 exists and check for any associated security vulnerabilities.
🏁 Script executed:
#!/bin/bash # Description: Verify multer package version and check for security advisories # Check latest version available on npm curl -s https://registry.npmjs.org/multer | jq '.["dist-tags"].latest' # Check if version 2.0.0 exists curl -s https://registry.npmjs.org/multer | jq '.versions | has("2.0.0")' # Check for security advisories gh api graphql -f query=' { securityVulnerabilities(first: 5, ecosystem: NPM, package: "multer") { nodes { advisory { summary severity publishedAt } vulnerableVersionRange firstPatchedVersion { identifier } } } }'Length of output: 991
Multer dependency version validated
- Version 2.0.0 exists and is the first patched release for the reported high-severity DoS vulnerabilities (both fixed in 2.0.0).
- The caret range (
^2.0.0) will pull in the patched 2.0.0 and any later 2.x (latest is 2.0.1).No further changes needed.
apps/backend/.env.example (1)
5-5: LGTM: Service role key addition is appropriate.The addition of SUPABASE_SERVICE_ROLE_KEY is correct for service-level operations with Supabase.
apps/backend/src/routes/auth.ts (2)
2-2: LGTM: Import path update aligns with new file structure.The updated import path correctly reflects the new controller file naming convention.
5-10: LGTM: Router refactoring improves code clarity.The router renaming from
routertoauthRouterprovides better semantic meaning and aligns with modern naming conventions.apps/backend/src/routes/index.ts (1)
1-11: LGTM! Clean and well-organized routing structure.The consolidated router approach with clear prefixes and helpful comments provides good organization for the API endpoints.
apps/backend/src/types/express/index.d.ts (2)
6-7: LGTM! Proper type extensions for middleware integration.The optional properties correctly support the authentication middleware (
user) and file upload middleware (file) without breaking existing code.
2-2:Details
❌ Incorrect review comment
Verify User type consistency across the application.
The import uses
Userfrom@supabase/supabase-js, but there's also a localUserinterface defined inapps/backend/src/types/auth.types.ts. Ensure there are no type conflicts and that the application consistently uses the intended User type.
🏁 Script executed:
#!/bin/bash # Description: Check for User type usage conflicts between local and Supabase types # Expected: Verify which User type is used where and if there are any conflicts # Search for local User interface definition echo "=== Local User interface definition ===" rg -A 5 "interface User" --type ts echo -e "\n=== User type imports from Supabase ===" rg "import.*User.*@supabase" --type ts echo -e "\n=== User type imports from local files ===" rg "import.*User.*\.\/" --type ts echo -e "\n=== User type usage in code ===" rg ": User[^a-zA-Z]" --type ts -A 2Length of output: 2261
I’ll check where (and if) the local
Userinterface is actually imported and used in the backend:
🏁 Script executed:
#!/bin/bash echo "=== Imports of local User from auth.types.ts ===" rg "import.*User.*auth.types" --type ts echo -e "\n=== Imports of User from local files (excluding Supabase) ===" rg "import.*User.*'\." --type ts | grep -v supabaseLength of output: 662
No action needed: backend exclusively uses Supabase User type
The backend’s
Express.Request.useris typed withUserfrom@supabase/supabase-js, and the localUserinterface in
apps/backend/src/types/auth.types.tsis never imported or used in the backend. There’s no conflicting usage—feel free to ignore the original conflict warning.Likely an incorrect or invalid review comment.
apps/backend/src/validators/auth.validator.ts (1)
5-5: LGTM! Improved code formatting.The simplified function signatures enhance readability while maintaining the same validation logic.
Also applies to: 20-20
apps/backend/src/routes/profile.routes.ts (1)
1-19: LGTM! Clean and well-structured profile routing implementation.The profile routes are well-organized with proper authentication middleware applied to all endpoints. The use of appropriate HTTP methods (GET for retrieval, PATCH for updates, DELETE for account deletion, and POST for avatar uploads) follows RESTful conventions. The avatar upload route correctly integrates the multer middleware for file handling.
apps/backend/src/middleware/auth.middleware.ts (1)
4-25: Excellent refactoring to use Supabase authentication!The migration from manual JWT verification to Supabase's built-in
auth.getUser()method is a significant improvement. This approach:
- Leverages Supabase's robust token validation
- Eliminates the need for manual JWT secret management
- Provides better integration with Supabase's user management system
- Simplifies the authentication flow
The implementation correctly handles token extraction, validation, and user attachment to the request object.
apps/backend/.env.example
Outdated
| CLOUDINARY_CLOUD_NAME=cloudinary_cloud_name | ||
| CLOUDINARY_API_KEY=cloudinary_api_key | ||
| CLOUDINARY_API_SECRET=cloudinary_api_secret | ||
| SUPABASE_DB_URL=postgresql No newline at end of file |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Fix the database URL example format.
The current value postgresql is not a valid PostgreSQL connection string format. This could mislead developers about the expected format.
-SUPABASE_DB_URL=postgresql
+SUPABASE_DB_URL=postgresql://username:password@hostname:port/database📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| SUPABASE_DB_URL=postgresql | |
| SUPABASE_DB_URL=postgresql://username:password@hostname:port/database |
🤖 Prompt for AI Agents
In apps/backend/.env.example at line 6, the SUPABASE_DB_URL value is set to
"postgresql", which is not a valid PostgreSQL connection string format. Replace
this placeholder with a properly formatted example connection string, such as
"postgresql://user:password@host:port/database", to clearly demonstrate the
expected format to developers.
| const storage = multer.memoryStorage(); // store in memory before upload to Supabase | ||
| export const upload = multer({ storage }); |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Add essential security and performance configurations.
The current multer configuration lacks important security and performance safeguards that could lead to memory exhaustion or malicious file uploads.
-const storage = multer.memoryStorage(); // store in memory before upload to Supabase
-export const upload = multer({ storage });
+const storage = multer.memoryStorage();
+
+export const upload = multer({
+ storage,
+ limits: {
+ fileSize: 5 * 1024 * 1024, // 5MB limit
+ files: 1
+ },
+ fileFilter: (req, file, cb) => {
+ // Only allow image files for avatars
+ if (file.mimetype.startsWith('image/')) {
+ cb(null, true);
+ } else {
+ cb(new Error('Only image files are allowed'));
+ }
+ }
+});This adds:
- File size limit (5MB) to prevent memory exhaustion
- Single file limit for avatar uploads
- Image file type validation for security
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const storage = multer.memoryStorage(); // store in memory before upload to Supabase | |
| export const upload = multer({ storage }); | |
| const storage = multer.memoryStorage(); | |
| export const upload = multer({ | |
| storage, | |
| limits: { | |
| fileSize: 5 * 1024 * 1024, // 5MB limit | |
| files: 1 | |
| }, | |
| fileFilter: (req, file, cb) => { | |
| // Only allow image files for avatars | |
| if (file.mimetype.startsWith('image/')) { | |
| cb(null, true); | |
| } else { | |
| cb(new Error('Only image files are allowed')); | |
| } | |
| } | |
| }); |
🤖 Prompt for AI Agents
In apps/backend/src/middleware/multer.ts around lines 3 to 4, the multer
configuration is missing critical security and performance settings. Update the
multer setup to include a file size limit of 5MB to prevent memory exhaustion,
restrict uploads to a single file for avatar uploads, and add validation to
allow only image file types for security. Implement these constraints within the
multer options to enhance safety and resource management.
| import type { UserProfileUpdate } from '../types/userProfile'; | ||
|
|
||
| export const updateUserProfile = async (userId: string, updates: UserProfileUpdate) => { | ||
| const { data, error } = await supabase | ||
| .from('profiles') | ||
| .update({ ...updates, last_active: new Date().toISOString() }) | ||
| .eq('user_id', userId) | ||
| .single(); | ||
|
|
||
| return error ? { error } : { profile: data }; | ||
| }; |
There was a problem hiding this comment.
Critical: Fix filename and missing import.
This file has two critical issues:
- Inappropriate filename: The file is named
a.tswhich should be properly named (e.g.,profile.service.ts) - Missing import: The
supabaseclient is used but not imported, which will cause a runtime error
Add the missing import at the top of the file:
+import { supabase } from '../config/supabase';
import type { UserProfileUpdate } from '../types/userProfile';Also rename this file to a more descriptive name like profile.service.ts or integrate this function into the existing profile service file.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| import type { UserProfileUpdate } from '../types/userProfile'; | |
| export const updateUserProfile = async (userId: string, updates: UserProfileUpdate) => { | |
| const { data, error } = await supabase | |
| .from('profiles') | |
| .update({ ...updates, last_active: new Date().toISOString() }) | |
| .eq('user_id', userId) | |
| .single(); | |
| return error ? { error } : { profile: data }; | |
| }; | |
| import { supabase } from '../config/supabase'; | |
| import type { UserProfileUpdate } from '../types/userProfile'; | |
| export const updateUserProfile = async (userId: string, updates: UserProfileUpdate) => { | |
| const { data, error } = await supabase | |
| .from('profiles') | |
| .update({ ...updates, last_active: new Date().toISOString() }) | |
| .eq('user_id', userId) | |
| .single(); | |
| return error ? { error } : { profile: data }; | |
| }; |
🤖 Prompt for AI Agents
In apps/backend/src/services/a.ts lines 1 to 11, rename the file from a.ts to a
more descriptive name like profile.service.ts to reflect its purpose. Also, add
the missing import statement for the supabase client at the top of the file to
avoid runtime errors when calling supabase methods.
apps/backend/src/index.ts
Outdated
| // Initialize DB and then start server | ||
| initializeDatabase().then(() => { | ||
| app.listen(PORT, () => { | ||
| console.log(`🚀 Server running on http://localhost:${PORT}`); | ||
| }); | ||
| }); |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Consider adding error handling for database initialization.
The database initialization should handle potential failures gracefully to prevent the server from starting with an uninitialized database.
Add error handling:
// Initialize DB and then start server
-initializeDatabase().then(() => {
- app.listen(PORT, () => {
- console.log(`🚀 Server running on http://localhost:${PORT}`);
- });
-});
+initializeDatabase()
+ .then(() => {
+ app.listen(PORT, () => {
+ console.log(`🚀 Server running on http://localhost:${PORT}`);
+ });
+ })
+ .catch((error) => {
+ console.error('Failed to initialize database:', error);
+ process.exit(1);
+ });📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| // Initialize DB and then start server | |
| initializeDatabase().then(() => { | |
| app.listen(PORT, () => { | |
| console.log(`🚀 Server running on http://localhost:${PORT}`); | |
| }); | |
| }); | |
| // Initialize DB and then start server | |
| initializeDatabase() | |
| .then(() => { | |
| app.listen(PORT, () => { | |
| console.log(`🚀 Server running on http://localhost:${PORT}`); | |
| }); | |
| }) | |
| .catch((error) => { | |
| console.error('Failed to initialize database:', error); | |
| process.exit(1); | |
| }); |
🤖 Prompt for AI Agents
In apps/backend/src/index.ts around lines 54 to 59, the database initialization
lacks error handling, which could lead to the server starting without a properly
initialized database. Modify the promise chain to include a catch block that
logs the error and prevents the server from starting if initialization fails.
This ensures the server only starts when the database is successfully
initialized and errors are properly reported.
| const router = Router(); | ||
|
|
||
| // Apply prefixes here | ||
| router.use('/auth', authRouter); | ||
| router.use('/profile', profileRouter); | ||
|
|
There was a problem hiding this comment.
🛠️ Refactor suggestion
Remove redundant router creation.
The new router instance and manual mounting of individual routers is redundant if you're already using the consolidated routes module at line 44.
If using the consolidated routes approach, remove this section:
-const router = Router();
-
-// Apply prefixes here
-router.use('/auth', authRouter);
-router.use('/profile', profileRouter);📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const router = Router(); | |
| // Apply prefixes here | |
| router.use('/auth', authRouter); | |
| router.use('/profile', profileRouter); |
🤖 Prompt for AI Agents
In apps/backend/src/index.ts around lines 27 to 32, remove the redundant
creation of a new router instance and the manual mounting of individual routers
like '/auth' and '/profile' since the consolidated routes module imported and
used at line 44 already handles these. Delete the lines that create the router
and call router.use for these routes to avoid duplication.
| export const deleteAccount = async (req: Request, res: Response) => { | ||
| if (!req.user) return res.status(401).json({ error: 'Usuario no autenticado' }); | ||
|
|
||
| const result = await deleteUserAccount(req.user.id); | ||
| if (result.error) return res.status(400).json({ error: result.error.message }); | ||
|
|
||
| res.json({ message: 'Cuenta eliminada' }); | ||
| }; |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Add confirmation requirement for account deletion.
Account deletion is a destructive operation that should require explicit confirmation to prevent accidental deletions.
Add confirmation requirement:
export const deleteAccount = async (req: Request, res: Response) => {
if (!req.user) return res.status(401).json({ error: 'Usuario no autenticado' });
+ const { confirmation } = req.body;
+ if (!confirmation || confirmation !== 'DELETE_MY_ACCOUNT') {
+ return res.status(400).json({
+ error: 'Se requiere confirmación explícita para eliminar la cuenta'
+ });
+ }
- const result = await deleteUserAccount(req.user.id);
+ const result = await deleteUserAccount(req.user.id, true);
- if (result.error) return res.status(400).json({ error: result.error.message });
+ if (result.error) {
+ return res.status(500).json({ error: 'Error al eliminar cuenta' });
+ }
res.json({ message: 'Cuenta eliminada' });
};Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In apps/backend/src/controllers/profile.controller.ts around lines 27 to 34, the
deleteAccount function currently deletes the user account immediately without
confirmation. Modify the function to require an explicit confirmation parameter
(e.g., a confirmation flag or token) in the request body or query. Check for
this confirmation before proceeding with deletion, and if it is missing or
false, respond with an error status and message asking the user to confirm the
deletion. This will prevent accidental account deletions.
| export const registerSchema = z.object({ | ||
| email: z.string().email(), | ||
| password: z.string().min(6), | ||
| name: z.string(), | ||
| avatar_url: z.string().url().optional(), | ||
| phone: z.string().optional(), | ||
| address: z.any().optional(), // ideally make this stricter | ||
| preferences: z.any().optional(), | ||
| social_links: z.any().optional(), | ||
| }); |
There was a problem hiding this comment.
Replace z.any() with proper type validation for nested objects.
Using z.any() for address, preferences, and social_links bypasses validation and type safety. These fields should have proper schema definitions that match the PublicProfile interface structure.
export const registerSchema = z.object({
email: z.string().email(),
password: z.string().min(6),
name: z.string(),
avatar_url: z.string().url().optional(),
phone: z.string().optional(),
- address: z.any().optional(), // ideally make this stricter
+ address: z.object({
+ street: z.string(),
+ city: z.string(),
+ country: z.string(),
+ postal_code: z.string(),
+ }).optional(),
- preferences: z.any().optional(),
+ preferences: z.object({
+ notifications: z.boolean(),
+ newsletter: z.boolean(),
+ language: z.string(),
+ }).optional(),
- social_links: z.any().optional(),
+ social_links: z.object({
+ facebook: z.string().url().optional(),
+ twitter: z.string().url().optional(),
+ instagram: z.string().url().optional(),
+ }).optional(),
});📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| export const registerSchema = z.object({ | |
| email: z.string().email(), | |
| password: z.string().min(6), | |
| name: z.string(), | |
| avatar_url: z.string().url().optional(), | |
| phone: z.string().optional(), | |
| address: z.any().optional(), // ideally make this stricter | |
| preferences: z.any().optional(), | |
| social_links: z.any().optional(), | |
| }); | |
| export const registerSchema = z.object({ | |
| email: z.string().email(), | |
| password: z.string().min(6), | |
| name: z.string(), | |
| avatar_url: z.string().url().optional(), | |
| phone: z.string().optional(), | |
| address: z.object({ | |
| street: z.string(), | |
| city: z.string(), | |
| country: z.string(), | |
| postal_code: z.string(), | |
| }).optional(), | |
| preferences: z.object({ | |
| notifications: z.boolean(), | |
| newsletter: z.boolean(), | |
| language: z.string(), | |
| }).optional(), | |
| social_links: z.object({ | |
| facebook: z.string().url().optional(), | |
| twitter: z.string().url().optional(), | |
| instagram: z.string().url().optional(), | |
| }).optional(), | |
| }); |
🤖 Prompt for AI Agents
In apps/backend/src/types/auth.types.ts between lines 22 and 31, replace the use
of z.any() for the address, preferences, and social_links fields with detailed
zod schema definitions that reflect the expected structure of these nested
objects according to the PublicProfile interface. Define proper nested object
schemas using z.object() with appropriate field validations to ensure type
safety and validation coverage.
| // export const loginUser = async (input: LoginInput): Promise<AuthResponse> => { | ||
| // const { data: user, error: userError } = await supabase | ||
| // .from('users') | ||
| // .select( | ||
| // ` | ||
| // id, | ||
| // email, | ||
| // password_hash, | ||
| // profiles ( | ||
| // name, | ||
| // avatar_url, | ||
| // phone, | ||
| // address, | ||
| // preferences, | ||
| // social_links, | ||
| // verification_status, | ||
| // last_active | ||
| // ) | ||
| // ` | ||
| // ) | ||
| // .eq('email', input.email) | ||
| // .single(); | ||
|
|
||
| // if (userError || !user) { | ||
| // throw new Error('Usuario no encontrado'); | ||
| // } | ||
|
|
||
| // const isPasswordValid = await bcrypt.compare( | ||
| // input.password, | ||
| // user.password_hash | ||
| // ); | ||
| // if (!isPasswordValid) { | ||
| // throw new Error('Contraseña incorrecta'); | ||
| // } | ||
|
|
||
| // if (!process.env.JWT_SECRET) { | ||
| // throw new Error('JWT_SECRET environment variable is required'); | ||
| // } | ||
|
|
||
| // const token = jwt.sign( | ||
| // { | ||
| // id: user.id, | ||
| // email: user.email, | ||
| // }, | ||
| // process.env.JWT_SECRET, | ||
| // { | ||
| // expiresIn: '1h', | ||
| // } | ||
| // ); | ||
|
|
||
| // const userResponse: AuthUser = { | ||
| // id: user.id, | ||
| // email: user.email, | ||
| // profile: user.profiles as PublicProfile, | ||
| // }; | ||
|
|
||
| // return { token, user: userResponse }; | ||
| // }; | ||
|
|
||
| // export const registerUser = async ( | ||
| // input: RegisterInput | ||
| // ): Promise<AuthResponse> => { | ||
| // const { data: existingUser } = await supabase | ||
| // .from('users') | ||
| // .select('email') | ||
| // .eq('email', input.email) | ||
| // .single(); | ||
|
|
||
| // if (existingUser) { | ||
| // throw new Error('El email ya está registrado'); | ||
| // } | ||
|
|
||
| // const hashedPassword = await bcrypt.hash(input.password, 10); | ||
|
|
||
| // const { data: user, error: insertError } = await supabase | ||
| // .from('users') | ||
| // .insert([ | ||
| // { | ||
| // email: input.email, | ||
| // password_hash: hashedPassword, | ||
| // name: input.name, | ||
| // }, | ||
| // ]) | ||
| // .select() | ||
| // .single(); | ||
|
|
||
| // if (insertError || !user) { | ||
| // throw new Error('Error al crear usuario'); | ||
| // } | ||
|
|
||
| // if (!process.env.JWT_SECRET) { | ||
| // throw new Error('JWT_SECRET environment variable is required'); | ||
| // } | ||
|
|
||
| // const token = jwt.sign( | ||
| // { | ||
| // id: user.id, | ||
| // email: user.email, | ||
| // }, | ||
| // process.env.JWT_SECRET, | ||
| // { | ||
| // expiresIn: '1h', | ||
| // } | ||
| // ); | ||
|
|
||
| // const userResponse: User = { | ||
| // id: user.id, | ||
| // email: user.email, | ||
| // name: user.name, | ||
| // }; | ||
|
|
||
| // return { token, user: userResponse }; | ||
| // }; |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Remove commented legacy code.
The large block of commented code should be removed to improve code maintainability. Version control preserves the history if this code needs to be referenced later.
🤖 Prompt for AI Agents
In apps/backend/src/services/auth.service.ts between lines 71 and 183, remove
the entire block of commented-out legacy code related to loginUser and
registerUser functions. This will clean up the file and improve maintainability
since the version control system already preserves the code history.
| export const loginUser = async ({ email, password }: { email: string; password: string }) => { | ||
| return await supabase.auth.signInWithPassword({ email, password }); | ||
| }; |
There was a problem hiding this comment.
Fix return type mismatch - loginUser should return AuthResponse.
The function currently returns Supabase's auth response directly, but based on the imported types and usage in controllers, it should return an AuthResponse object.
-export const loginUser = async ({ email, password }: { email: string; password: string }) => {
- return await supabase.auth.signInWithPassword({ email, password });
-};
+export const loginUser = async ({ email, password }: LoginInput): Promise<AuthResponse> => {
+ const { data, error } = await supabase.auth.signInWithPassword({ email, password });
+
+ if (error || !data.user || !data.session) {
+ console.error('Error al iniciar sesión:', error);
+ throw new Error('Error al iniciar sesión');
+ }
+
+ // Fetch user profile
+ const { data: profile, error: profileError } = await supabase
+ .from('profiles')
+ .select('*')
+ .eq('user_id', data.user.id)
+ .single();
+
+ if (profileError || !profile) {
+ console.error('Error al obtener perfil:', profileError);
+ throw new Error('Error al obtener perfil de usuario');
+ }
+
+ return {
+ token: data.session.access_token,
+ user: {
+ id: data.user.id,
+ email: data.user.email!,
+ profile: profile as PublicProfile,
+ },
+ };
+};🤖 Prompt for AI Agents
In apps/backend/src/services/auth.service.ts around lines 11 to 13, the
loginUser function currently returns the raw Supabase auth response, causing a
return type mismatch. Modify the function to explicitly return an AuthResponse
object by mapping or transforming the Supabase response to match the
AuthResponse type expected by the controllers and imported types.
| // Optional: you can create a JWT here if needed | ||
| return { | ||
| token: data.session?.access_token ?? '', | ||
| user: { | ||
| id: userId, | ||
| email, | ||
| profile: { | ||
| name, | ||
| avatar_url, | ||
| phone, | ||
| address, | ||
| preferences, | ||
| social_links, | ||
| verification_status: 'unverified', | ||
| last_active: new Date().toISOString(), | ||
| }, | ||
| }, | ||
| process.env.JWT_SECRET, | ||
| { | ||
| expiresIn: '1h', | ||
| } | ||
| ); | ||
|
|
||
| const userResponse: User = { | ||
| id: user.id, | ||
| email: user.email, | ||
| name: user.name, | ||
| }; |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Improve token handling and ensure data consistency.
The current implementation has two issues:
- Using an empty string as fallback for missing token could cause authentication failures
- Manually constructing the profile object instead of fetching from database may lead to inconsistencies
- // Optional: you can create a JWT here if needed
+ if (!data.session?.access_token) {
+ throw new Error('No se pudo obtener el token de autenticación');
+ }
+
+ // Fetch the created profile to ensure consistency
+ const { data: profile, error: profileError } = await supabase
+ .from('profiles')
+ .select('*')
+ .eq('user_id', userId)
+ .single();
+
+ if (profileError || !profile) {
+ throw new Error('Error al obtener el perfil creado');
+ }
+
return {
- token: data.session?.access_token ?? '',
+ token: data.session.access_token,
user: {
id: userId,
email,
- profile: {
- name,
- avatar_url,
- phone,
- address,
- preferences,
- social_links,
- verification_status: 'unverified',
- last_active: new Date().toISOString(),
- },
+ profile: profile as PublicProfile,
},
};📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| // Optional: you can create a JWT here if needed | |
| return { | |
| token: data.session?.access_token ?? '', | |
| user: { | |
| id: userId, | |
| email, | |
| profile: { | |
| name, | |
| avatar_url, | |
| phone, | |
| address, | |
| preferences, | |
| social_links, | |
| verification_status: 'unverified', | |
| last_active: new Date().toISOString(), | |
| }, | |
| }, | |
| process.env.JWT_SECRET, | |
| { | |
| expiresIn: '1h', | |
| } | |
| ); | |
| const userResponse: User = { | |
| id: user.id, | |
| email: user.email, | |
| name: user.name, | |
| }; | |
| if (!data.session?.access_token) { | |
| throw new Error('No se pudo obtener el token de autenticación'); | |
| } | |
| // Fetch the created profile to ensure consistency | |
| const { data: profile, error: profileError } = await supabase | |
| .from('profiles') | |
| .select('*') | |
| .eq('user_id', userId) | |
| .single(); | |
| if (profileError || !profile) { | |
| throw new Error('Error al obtener el perfil creado'); | |
| } | |
| return { | |
| token: data.session.access_token, | |
| user: { | |
| id: userId, | |
| email, | |
| profile: profile as PublicProfile, | |
| }, | |
| }; |
🤖 Prompt for AI Agents
In apps/backend/src/services/auth.service.ts around lines 51 to 68, avoid
returning an empty string as a fallback for the token to prevent authentication
failures; instead, handle the absence of the token explicitly, such as throwing
an error or returning null. Also, replace the manual construction of the user
profile object with a proper fetch from the database to ensure data consistency
and accuracy.
respp
left a comment
There was a problem hiding this comment.
Hi @bernardev254, thanks for your contribution
I wasn't able to run the database to verify that the endpoints are working correctly, since the file src/db/init.ts has some issues. Because of that, I kindly ask you to attach screenshots or example responses so I can see what the endpoints return.
At first glance, the code looks very well implemented. There are just a few things to address before I can merge:
Update setup.sql
To ensure other contributors can run the database properly, you'll need to create the profiles table and configure the storage buckets.
Update the README
If you made changes that affect the initial setup, please update the README.md located inside the /database directory.
Add missing tests
There are some missing tests to cover the functionality you added.
Once that's done, I'll review it again and merge the code.
Thanks again!
Also, make sure to address the changes suggested by Coderabbit to avoid technical debt. Thanks |
thanks for the update..i have realised that my branch has diverged too much after creating a new branch and cherry picked prevoius commits.now my local repo does not even contain the database folder. |
Hi @bernardev254, thanks for the quick update and for letting me know! That makes perfect sense. Starting fresh with a clean branch sounds like a good plan — it’ll help avoid any confusion from the diverged history and missing files. Once you open the new PR, feel free to tag me again and I’ll be happy to review it. Let me know if you need any help during the process. Thanks again for your contribution! |
|
closing this pr in preference of #72 |
Pull Request | StellarRent
📝 Summary
This PR implements the user profile management system, enabling users to view, update, and delete their profile, as well as upload a profile avatar. It enforces type safety and improves error handling for interactions with the Supabase backend.
🔗 Related Issues
Closes #44
🔄 Changes Made
Integrated Supabase public URL retrieval after avatar upload
Fixed type issues flagged by Biome and enforced interface usage for profile data
🖼️ Current Output
Provide visual evidence of the changes:
🧪 Testing
If applicable, describe the tests performed. Include screenshots, test outputs, or any resources that help reviewers understand how the changes were tested.
✅ Testing Checklist
List any possible issues that might arise with this change.
🚀 Next Steps & Improvements
This change lays a solid foundation for further optimizations. Some areas that could benefit from future improvements include:
💬 Comments
Any additional context, questions, or considerations for reviewers.
Summary by CodeRabbit
New Features
Enhancements
Bug Fixes
Chores