Skip to content

feat(profile): implement user profile system#60

Closed
bernardev254 wants to merge 45 commits intoStellar-Rent:mainfrom
bernardev254:feat/Implement-User-Profile-System
Closed

feat(profile): implement user profile system#60
bernardev254 wants to merge 45 commits intoStellar-Rent:mainfrom
bernardev254:feat/Implement-User-Profile-System

Conversation

@bernardev254
Copy link
Contributor

@bernardev254 bernardev254 commented Jun 1, 2025

StellarRent Logo

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 ensures type safety and improves error handling when interacting with the Supabase backend.

🔗 Related Issues

Closes #44 .

🔄 Changes Made

  1. Added profile.controller.ts with routes for
  • getProfile
  • updateProfile
  • deleteAccount
  • uploadAvatar
  1. Implemented profile.service.ts functions for:
  • Fetching user profiles from Supabase
  • Updating profiles with strict types (UserProfile)
  • Deleting accounts
  • Uploading avatar images to Supabase storage
  1. Integrated Supabase public URL retrieval after avatar upload
  2. Fixed type issues flagged by Biome and enforced interface usage for profile data

🖼️ Current Output

Provide visual evidence of the changes:

  • For small changes: Screenshots.
  • For large changes: Video or Loom link.

🧪 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

  • Unit tests added/modified
  • Integration tests performed
  • Manual tests executed
  • All tests pass in CI/CD

⚠️ Potential Risks

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:

  • 🔹 Performance optimization
  • 🔹 Increased test coverage
  • 🔹 Potential user experience enhancements

💬 Comments

Any additional context, questions, or considerations for reviewers.

Summary by CodeRabbit

  • New Features

    • Added user profile management endpoints: view, update, delete profiles, and upload avatars.
    • Introduced file upload support for user avatars.
    • Implemented database initialization to create required tables at server startup.
  • Enhancements

    • Authentication now uses Supabase's built-in methods for registration and login.
    • Expanded user profiles with address, preferences, social links, verification status, and last active timestamp.
  • Bug Fixes

    • Improved error handling and consistent response patterns for authentication and profile operations.
  • Chores

    • Updated environment and configuration files, including adding new environment variables and dependencies.
    • Refactored and reorganized routes and controllers for improved scalability.
    • Reformatted ESLint configuration for consistency.
    • Added TypeScript declarations to enhance typing for request user and file properties.
    • Simplified validation function signatures for login and registration inputs.

Benjtalkshow and others added 30 commits May 30, 2025 00:04
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jun 1, 2025

Warning

Rate limit exceeded

@bernardev254 has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 8 minutes and 34 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between 6ed4a36 and 8161d42.

📒 Files selected for processing (1)
  • apps/backend/src/types/auth.types.ts (1 hunks)

"""

Walkthrough

This update introduces a comprehensive user profile system on the backend. It adds new routes, controllers, services, middleware, and database schema for managing user profiles, including profile retrieval, update, deletion, and avatar upload. The authentication flow is refactored to use Supabase for user management, and type definitions are expanded to support the extended profile structure.

Changes

File(s) Change Summary
.env.example, .eslintrc.json, package.json Added SUPABASE_DB_URL, reformatted ESLint config, added multer dependency.
src/config/supabase.ts, src/types/auth.types.ts, src/types/userProfile.ts Refactored/expanded Supabase schema and types for detailed user profiles. Added UserProfileUpdate interface.
src/controllers/auth.controller.ts, src/controllers/profile.controller.ts, src/controllers/authController.ts Replaced old auth controller with new auth and profile controllers for registration, login, and profile management.
src/db/init.ts Added database initialization for new profiles table.
src/index.ts, src/routes/index.ts, src/routes/auth.ts, src/routes/profile.routes.ts Refactored routing: added profile routes, centralized router, updated route imports/exports.
src/middleware/auth.middleware.ts, src/middleware/multer.ts Refactored authentication to use Supabase, added multer middleware for file uploads.
src/services/auth.service.ts, src/services/profile.service.ts, src/services/a.ts Refactored auth service to use Supabase, added profile service for CRUD and avatar upload, added updateUserProfile helper.
src/types/express/index.d.ts Extended Express types for user and file properties on request.
src/validators/auth.validator.ts Simplified function signatures for validators.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant API
    participant AuthService
    participant ProfileService
    participant Supabase

    Client->>API: POST /auth/register
    API->>AuthService: registerUser(data)
    AuthService->>Supabase: signUp(email, password)
    Supabase-->>AuthService: userId
    AuthService->>Supabase: upsert profile (userId, profile fields)
    Supabase-->>AuthService: profile
    AuthService-->>API: token, user
    API-->>Client: 201 Created (token, user)

    Client->>API: GET /profile
    API->>ProfileService: getUserProfile(userId)
    ProfileService->>Supabase: fetch profile by userId
    Supabase-->>ProfileService: profile
    ProfileService-->>API: profile
    API-->>Client: 200 OK (profile)

    Client->>API: PATCH /profile
    API->>ProfileService: updateUserProfile(userId, updates)
    ProfileService->>Supabase: update profile
    Supabase-->>ProfileService: updated profile
    ProfileService-->>API: updated profile
    API-->>Client: 200 OK (updated profile)

    Client->>API: POST /profile/avatar
    API->>ProfileService: uploadUserAvatar(userId, file)
    ProfileService->>Supabase: upload file to storage
    Supabase-->>ProfileService: public URL
    ProfileService->>Supabase: update profile avatar_url
    Supabase-->>ProfileService: updated profile
    ProfileService-->>API: avatar_url
    API-->>Client: 200 OK (avatar_url)
Loading

Assessment against linked issues

Objective Addressed Explanation
Profile management endpoints: GET/PATCH/DELETE/POST avatar for /profile (#44)
Profile fields extension: support for extended fields (address, preferences, social_links, verification_status, last_active) (#44)
Security enhancements: email/phone verification, 2FA, session management, activity logging (#44) Only basic profile and authentication flows implemented; advanced security not present.
Advanced features: profile completion, verification badges, login history, device mgmt, notification prefs (#44) No implementation of these advanced features in this PR.
Integration points: property listings, reviews, booking, payments, saved properties (#44) No integration with these modules in this PR.

Assessment against linked issues: Out-of-scope changes

Code Change (file_path) Explanation
Addition of SUPABASE_DB_URL to .env.example This is a supporting change for Supabase integration, but not explicitly required by #44.
ESLint config reformat (.eslintrc.json) Purely stylistic/formatting change; not related to user profile objectives.

Possibly related PRs

  • Users integration #14: Refactored backend user profile schema and authentication to use Supabase, which is directly related to the current PR's shift to Supabase for user and profile management.

Poem

In the backend fields where user profiles grow,
A rabbit hops where Supabase streams flow.
Avatars upload, preferences bloom,
Address and socials find plenty of room.
Patch, fetch, or delete—profiles now thrive,
With new routes and types, the system’s alive!
🐇✨
"""

✨ Finishing Touches
  • 📝 Generate Docstrings

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.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need 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)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 19

🧹 Nitpick comments (6)
apps/backend/.env.example (1)

5-6: Important: Secure these sensitive environment variables.

The new Supabase environment variables are correctly added. However, ensure that:

  1. SUPABASE_SERVICE_ROLE_KEY has elevated privileges and should be kept strictly confidential
  2. SUPABASE_DB_URL should use proper authentication and connection pooling in production
  3. These values should never be committed to version control

Consider adding comments in the .env.example file to document the purpose of each variable.

 JWT_SECRET=xyz
+# Supabase service role key (admin privileges - keep secure!)
 SUPABASE_SERVICE_ROLE_KEY=supabase_service_role_key
+# Direct database connection URL
 SUPABASE_DB_URL=postgresql
apps/backend/src/index.ts (1)

55-59: Consider error handling for database initialization.

The database initialization is properly awaited before starting the server, which is good practice. However, consider adding error handling to gracefully handle initialization failures.

 // 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 start server:', error);
+    process.exit(1);
+  });
apps/backend/src/types/userProfile.ts (2)

23-23: Consider Date serialization implications.

The Date type for last_active may cause issues during JSON serialization/deserialization. Consider using ISO string representation or timestamp number for better API compatibility.

-  last_active?: Date;
+  last_active?: string; // ISO date string

Or add a comment to clarify expected format:

-  last_active?: Date;
+  last_active?: Date; // Will be serialized as ISO string in JSON

2-5: Consider adding validation constraints documentation.

While the interface provides type safety, consider documenting validation requirements for fields like email format, phone number format, and URL validation for social links.

Add JSDoc comments with validation requirements:

 export interface UserProfileUpdate {
+  /** Email address - must be valid email format */
   email?: string;
   name?: string;
+  /** Avatar URL - must be valid HTTPS URL */
   avatar_url?: string;
+  /** Phone number - international format recommended */
   phone?: string;
apps/backend/src/config/supabase.ts (1)

29-30: Consider making verification_status enum more explicit.

The verification status type is correctly defined, but consider extracting it as a separate type for reusability across the codebase.

+export type VerificationStatus = 'unverified' | 'pending' | 'verified';
+
export type Database = {
  public: {
    Tables: {
      profiles: {
        Row: {
          user_id: 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';
+         verification_status: VerificationStatus;
          last_active: string; // store as ISO string
        };
apps/backend/src/services/auth.service.ts (1)

71-183: Remove commented-out code.

Large blocks of commented code reduce readability. If this code needs to be referenced, it's available in version control history.

-// 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 };
-// };
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bac7f50 and f29216a.

📒 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 (7)
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/services/a.ts (2)
apps/backend/src/types/userProfile.ts (1)
  • UserProfileUpdate (1-24)
apps/backend/src/config/supabase.ts (1)
  • supabase (49-61)
apps/backend/src/middleware/auth.middleware.ts (1)
apps/backend/src/config/supabase.ts (1)
  • supabase (49-61)
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/controllers/auth.controller.ts (1)
apps/backend/src/services/auth.service.ts (2)
  • registerUser (15-69)
  • loginUser (11-13)
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)
🔇 Additional comments (13)
apps/backend/.eslintrc.json (1)

2-17: LGTM! Clean formatting improvements.

The ESLint configuration formatting has been standardized with consistent indentation and proper JSON structure. The rules and configuration remain unchanged and appropriate for a TypeScript project.

apps/backend/src/routes/auth.ts (2)

2-2: Good refactoring: Improved import path consistency.

The updated import path follows better file naming conventions with the .controller suffix.


5-10: Excellent naming improvement.

Renaming from generic router to descriptive authRouter improves code readability and makes the purpose clearer, especially when this router is imported and used elsewhere.

apps/backend/src/routes/index.ts (1)

1-11: LGTM! Clean routing structure with proper organization.

The central routing hub approach is well-implemented with clear path prefixes and helpful comments. This provides good separation of concerns between auth and profile functionality.

apps/backend/src/validators/auth.validator.ts (1)

5-5: Good formatting improvement for function signatures.

Converting to single-line function signatures improves readability while maintaining all the validation logic and error handling.

Also applies to: 20-20

apps/backend/src/types/express/index.d.ts (2)

7-7: Good addition for file upload support.

The file?: Express.Multer.File property correctly supports the avatar upload functionality mentioned in the PR objectives.


6-6:

Details

❌ Incorrect review comment

Clarify User type to avoid potential conflicts.

There's a potential type conflict. This file imports User from @supabase/supabase-js, but there's also a local User interface defined in apps/backend/src/types/auth.types.ts (lines 11-15). Ensure consistent usage throughout the codebase.

Consider either:

  1. Using the Supabase User type consistently across the application
  2. Or using the local User type and importing it from the local types

Let me verify which User type is being used in other files:


🏁 Script executed:

#!/bin/bash
# Search for User type imports and usage to identify potential conflicts
rg -A 2 -B 2 "import.*User.*from"
rg -A 2 -B 2 "user\?\:" --type ts

Length of output: 1510


No User type conflicts detected

I checked the codebase and confirmed that only the Supabase User type is ever imported (in apps/backend/src/types/express/index.d.ts). The local User interface you have in apps/backend/src/types/auth.types.ts isn’t imported or used anywhere else, so there’s no collision at compile time. You can safely ignore the original warning about conflicting User types—or, if the local type is no longer needed, consider renaming or removing it for clarity.

Likely an incorrect or invalid review comment.

apps/backend/src/middleware/auth.middleware.ts (1)

4-25: LGTM! Clean refactoring to Supabase authentication.

The migration from manual JWT verification to Supabase's getUser method is well-implemented. The middleware correctly:

  • Extracts the Bearer token
  • Validates the token with Supabase
  • Handles errors appropriately
  • Attaches the user to the request for downstream use

The async/await pattern and error handling are properly implemented.

apps/backend/src/routes/profile.routes.ts (1)

13-16: Well-structured profile routes with proper authentication.

The route definitions follow REST conventions and are properly secured:

  • GET for retrieval, PATCH for updates, DELETE for removal
  • All routes protected with authenticateToken middleware
  • Avatar upload correctly uses multer for file handling
apps/backend/src/types/userProfile.ts (1)

1-24: Well-designed interface with comprehensive profile fields.

The UserProfileUpdate interface provides a solid foundation for profile management:

  • Optional fields enable partial updates
  • Nested objects create logical groupings
  • String literal types for verification_status ensure type safety
  • Comprehensive coverage of profile data
apps/backend/src/db/init.ts (1)

12-24:

Details

❓ Verification inconclusive

Verify foreign key reference and add schema validation.

The table creation assumes auth.users exists. This could fail if Supabase auth schema isn't properly set up. Consider adding a check or handling this dependency.


🏁 Script executed:

#!/bin/bash
# Verify if auth.users table exists in Supabase schema and check for any existing profiles table structure
rg -A 10 "auth\.users" --type ts
ast-grep --pattern 'CREATE TABLE $_ auth.users'

Length of output: 808


Ensure auth.users table exists before creating profiles

Confirm that the auth.users table (usually provisioned by Supabase Auth) is available when this migration runs. If you depend on Supabase’s default auth schema, document this requirement or add a pre-migration check. Otherwise, consider a dedicated migration to create or validate auth.users before referencing it.

• File: apps/backend/src/db/init.ts
Lines: 12–24 (foreign key reference to auth.users(id))

    await client.query(`
      CREATE TABLE IF NOT EXISTS public.profiles (
        user_id uuid PRIMARY KEY REFERENCES auth.users(id) ON DELETE CASCADE,
        name TEXT NOT NULL,
        avatar_url TEXT,
        phone TEXT,
        address JSONB,
        preferences JSONB,
        social_links JSONB,
        verification_status TEXT CHECK (verification_status IN ('unverified', 'pending', 'verified')) DEFAULT 'unverified',
        last_active TIMESTAMPTZ DEFAULT now()
      );
    `);
apps/backend/src/config/supabase.ts (1)

7-36: Excellent schema design with comprehensive type safety.

The migration from users to profiles table with expanded structure is well-designed. The nested objects for address, preferences, and social_links provide good organization, and the TypeScript types properly handle Insert/Update operations.

apps/backend/src/types/auth.types.ts (1)

60-64: Good interface design for AuthUser.

The AuthUser interface properly combines user identification with profile data, providing a clean separation of concerns.

Comment on lines +1 to +4
import multer from 'multer';

const storage = multer.memoryStorage(); // store in memory before upload to Supabase
export const upload = multer({ storage });
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Critical: Add security configurations to prevent vulnerabilities.

The current multer configuration lacks essential security measures that could lead to serious vulnerabilities:

  1. No file size limits - Attackers could upload large files to exhaust server memory
  2. No file type restrictions - Any file type can be uploaded
  3. No upload limits - Multiple large uploads could crash the server

Apply this diff to add essential security configurations:

 import multer from 'multer';

 const storage = multer.memoryStorage(); // store in memory before upload to Supabase
-export const upload = multer({ storage });
+export const upload = multer({ 
+  storage,
+  limits: {
+    fileSize: 5 * 1024 * 1024, // 5MB limit
+    files: 1 // Only allow 1 file per request
+  },
+  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'));
+    }
+  }
+});
📝 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.

Suggested change
import multer from 'multer';
const storage = multer.memoryStorage(); // store in memory before upload to Supabase
export const upload = multer({ storage });
import multer from 'multer';
const storage = multer.memoryStorage(); // store in memory before upload to Supabase
export const upload = multer({
storage,
limits: {
fileSize: 5 * 1024 * 1024, // 5MB limit
files: 1 // Only allow 1 file per request
},
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 lines 1 to 4, the multer setup lacks
security configurations such as file size limits, file type restrictions, and
upload limits. To fix this, update the multer configuration to include a file
size limit (e.g., max file size in bytes), restrict accepted file types using a
fileFilter function that checks MIME types, and limit the number of files or
fields accepted per request. This will prevent large or malicious uploads from
exhausting server resources or causing crashes.

Comment on lines +1 to +11
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 };
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix critical import issue and improve file naming.

Several issues need to be addressed:

  1. Missing import: The supabase client is used but not imported, causing a compilation error.
  2. Poor file naming: a.ts is not descriptive. Consider renaming to profile.service.ts.
  3. Missing input validation: The userId parameter should be validated before use.

Add the missing import at the top of the file:

+import { supabase } from '../config/supabase';
 import type { UserProfileUpdate } from '../types/userProfile';

Consider adding input validation:

 export const updateUserProfile = async (userId: string, updates: UserProfileUpdate) => {
+  if (!userId || typeof userId !== 'string') {
+    return { error: { message: 'Invalid user ID provided' } };
+  }
+  
   const { data, error } = await supabase
📝 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.

Suggested change
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) => {
if (!userId || typeof userId !== 'string') {
return { error: { message: 'Invalid user ID provided' } };
}
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, the supabase client is used but
not imported, causing a compilation error; add the appropriate import statement
for supabase at the top of the file. Rename the file from a.ts to
profile.service.ts to improve clarity and maintainability. Additionally, add
input validation for the userId parameter at the start of the updateUserProfile
function to ensure it is a non-empty string before proceeding with the database
update.

Comment on lines +10 to +12
import routes from './routes';
import authRouter from './routes/auth';
import profileRouter from './routes/profile.routes';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix inconsistent router imports and usage.

The code imports both the consolidated routes from ./routes and individual routers (authRouter, profileRouter), but only uses the consolidated routes on line 44. This creates unused imports and suggests incomplete refactoring.

Remove the unused individual router imports:

 import routes from './routes';
-import authRouter from './routes/auth';
-import profileRouter from './routes/profile.routes';
📝 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.

Suggested change
import routes from './routes';
import authRouter from './routes/auth';
import profileRouter from './routes/profile.routes';
import routes from './routes';
🤖 Prompt for AI Agents
In apps/backend/src/index.ts around lines 10 to 12, the imports for authRouter
and profileRouter are unused because only the consolidated routes import is used
later. Remove the individual router imports for authRouter and profileRouter to
clean up unused code and complete the refactoring.

Comment on lines +27 to +31
const router = Router();

// Apply prefixes here
router.use('/auth', authRouter);
router.use('/profile', profileRouter);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Remove redundant router construction.

Lines 27-31 create a new router and mount individual routers, but this router is never used since line 44 uses the consolidated routes instead. This dead code should be removed.

Remove the unused router construction:

-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.

Suggested change
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 31, the code creates a new
router and mounts individual routers on it, but this router is never used later
since the consolidated `routes` router is used instead. Remove the entire block
that constructs and uses this unused router to eliminate dead code and avoid
confusion.

Comment on lines +18 to 49
// Step 1: Supabase Auth registration
const { data, error } = await supabase.auth.signUp({ email, password });

if (!process.env.JWT_SECRET) {
throw new Error('JWT_SECRET environment variable is required');
if (error || !data.user) {
console.error('Error al registrar usuario:', error);
throw new Error('Error al registrar usuario');
}

const token = jwt.sign(
{
id: user.id,
email: user.email,
},
process.env.JWT_SECRET,
{
expiresIn: '1h',
}
);
const userId = data.user.id;

const userResponse: User = {
id: user.id,
email: user.email,
name: user.name,
};

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([
// Step 2: Insert into profiles
const { error: profileError } = await supabase.from('profiles').upsert(
[
{
email: input.email,
password_hash: hashedPassword,
name: input.name,
user_id: userId,
name,
avatar_url,
phone,
address,
preferences,
social_links,
verification_status: 'unverified',
last_active: new Date().toISOString(),
},
])
.select()
.single();

if (insertError || !user) {
throw new Error('Error al crear usuario');
}
],
{ onConflict: 'user_id' }
);

if (!process.env.JWT_SECRET) {
throw new Error('JWT_SECRET environment variable is required');
if (profileError) {
console.error('Error al crear perfil de usuario:', profileError);
throw new Error('Error al crear perfil de usuario');
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

⚠️ Potential issue

Consider using a transaction for atomic user registration.

The current implementation creates a user in Supabase Auth and then separately creates a profile. If profile creation fails, you'll have an orphaned auth user without a profile.

Consider wrapping both operations in a transaction or implementing rollback logic:

 export const registerUser = async (input: RegisterInput): Promise<AuthResponse> => {
   const { email, password, name, avatar_url, phone, address, preferences, social_links } = input;
 
   // Step 1: Supabase Auth registration
   const { data, error } = await supabase.auth.signUp({ email, password });
 
   if (error || !data.user) {
     console.error('Error al registrar usuario:', error);
     throw new Error('Error al registrar usuario');
   }
 
   const userId = data.user.id;
 
   // Step 2: Insert into profiles
-  const { error: profileError } = await supabase.from('profiles').upsert(
+  const { error: profileError } = await supabase.from('profiles').insert(
     [
       {
         user_id: userId,
         name,
         avatar_url,
         phone,
         address,
         preferences,
         social_links,
         verification_status: 'unverified',
         last_active: new Date().toISOString(),
       },
     ],
-    { onConflict: 'user_id' }
   );
 
   if (profileError) {
     console.error('Error al crear perfil de usuario:', profileError);
+    // Consider deleting the auth user if profile creation fails
+    await supabase.auth.admin.deleteUser(userId);
     throw new Error('Error al crear perfil de usuario');
   }

Also, using insert instead of upsert ensures we don't accidentally overwrite existing profiles.

📝 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.

Suggested change
// Step 1: Supabase Auth registration
const { data, error } = await supabase.auth.signUp({ email, password });
if (!process.env.JWT_SECRET) {
throw new Error('JWT_SECRET environment variable is required');
if (error || !data.user) {
console.error('Error al registrar usuario:', error);
throw new Error('Error al registrar usuario');
}
const token = jwt.sign(
{
id: user.id,
email: user.email,
},
process.env.JWT_SECRET,
{
expiresIn: '1h',
}
);
const userId = data.user.id;
const userResponse: User = {
id: user.id,
email: user.email,
name: user.name,
};
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([
// Step 2: Insert into profiles
const { error: profileError } = await supabase.from('profiles').upsert(
[
{
email: input.email,
password_hash: hashedPassword,
name: input.name,
user_id: userId,
name,
avatar_url,
phone,
address,
preferences,
social_links,
verification_status: 'unverified',
last_active: new Date().toISOString(),
},
])
.select()
.single();
if (insertError || !user) {
throw new Error('Error al crear usuario');
}
],
{ onConflict: 'user_id' }
);
if (!process.env.JWT_SECRET) {
throw new Error('JWT_SECRET environment variable is required');
if (profileError) {
console.error('Error al crear perfil de usuario:', profileError);
throw new Error('Error al crear perfil de usuario');
}
// Step 1: Supabase Auth registration
const { data, error } = await supabase.auth.signUp({ email, password });
if (error || !data.user) {
console.error('Error al registrar usuario:', error);
throw new Error('Error al registrar usuario');
}
const userId = data.user.id;
// Step 2: Insert into profiles
const { error: profileError } = await supabase.from('profiles').insert(
[
{
user_id: userId,
name,
avatar_url,
phone,
address,
preferences,
social_links,
verification_status: 'unverified',
last_active: new Date().toISOString(),
},
]
);
if (profileError) {
console.error('Error al crear perfil de usuario:', profileError);
// Consider deleting the auth user if profile creation fails
await supabase.auth.admin.deleteUser(userId);
throw new Error('Error al crear perfil de usuario');
}
🤖 Prompt for AI Agents
In apps/backend/src/services/auth.service.ts between lines 18 and 49, the user
registration process creates a Supabase Auth user and then inserts or updates
the profile separately, risking orphaned users if profile creation fails. To fix
this, implement a transaction or rollback mechanism that ensures both user
creation and profile insertion succeed or both fail together. Replace the upsert
with an insert to avoid overwriting existing profiles, and handle errors to
rollback the auth user creation if profile insertion fails.

Comment on lines +18 to +25
export const updateProfile = async (req: Request, res: Response) => {
if (!req.user) return res.status(401).json({ error: 'Usuario no autenticado' });

const result = await updateUserProfile(req.user.id, req.body);
if (result.error) return res.status(400).json({ error: result.error.message });

res.json(result.profile);
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

⚠️ Potential issue

Add input validation for profile updates.

Passing req.body directly to the service without validation is a security risk. Users could potentially update fields they shouldn't have access to.

Apply this diff to add input validation:

 export const updateProfile = async (req: Request, res: Response) => {
   if (!req.user) return res.status(401).json({ error: 'Usuario no autenticado' });
 
-  const result = await updateUserProfile(req.user.id, req.body);
+  // Validate and sanitize input
+  const allowedFields = ['name', 'avatar_url', 'phone', 'address', 'preferences', 'social_links'];
+  const updates: any = {};
+  
+  for (const field of allowedFields) {
+    if (req.body[field] !== undefined) {
+      updates[field] = req.body[field];
+    }
+  }
+  
+  const result = await updateUserProfile(req.user.id, updates);
   if (result.error) return res.status(400).json({ error: result.error.message });
 
   res.json(result.profile);
 };

Consider also adding schema validation using a library like Joi or Zod for more robust validation.

📝 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.

Suggested change
export const updateProfile = async (req: Request, res: Response) => {
if (!req.user) return res.status(401).json({ error: 'Usuario no autenticado' });
const result = await updateUserProfile(req.user.id, req.body);
if (result.error) return res.status(400).json({ error: result.error.message });
res.json(result.profile);
};
export const updateProfile = async (req: Request, res: Response) => {
if (!req.user) return res.status(401).json({ error: 'Usuario no autenticado' });
// Validate and sanitize input
const allowedFields = ['name', 'avatar_url', 'phone', 'address', 'preferences', 'social_links'];
const updates: any = {};
for (const field of allowedFields) {
if (req.body[field] !== undefined) {
updates[field] = req.body[field];
}
}
const result = await updateUserProfile(req.user.id, updates);
if (result.error) return res.status(400).json({ error: result.error.message });
res.json(result.profile);
};
🤖 Prompt for AI Agents
In apps/backend/src/controllers/profile.controller.ts around lines 18 to 25, the
updateProfile function passes req.body directly to updateUserProfile without
validating the input, which is a security risk. To fix this, add input
validation before calling updateUserProfile by defining a whitelist of allowed
fields or using a validation schema with a library like Joi or Zod. Validate
req.body against this schema and return a 400 error response if validation
fails, ensuring only permitted fields are processed.

Comment on lines +27 to +34
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' });
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Consider deleting the auth user when deleting an account.

The current implementation only deletes the profile but not the associated Supabase auth user, which could lead to orphaned auth records.

Consider updating the service to also delete the auth user:

// In profile.service.ts
export const deleteUserAccount = async (userId: string) => {
  // Delete profile
  const { error: profileError } = await supabase.from('profiles').delete().eq('user_id', userId);
  if (profileError) return { error: profileError };
  
  // Delete auth user
  const { error: authError } = await supabase.auth.admin.deleteUser(userId);
  if (authError) return { error: authError };
  
  return { success: true };
};

🏁 Script executed:

#!/bin/bash
# Description: Check if there's a cascade delete or if auth users are handled separately

# Search for any auth user deletion logic
rg -A 5 "deleteUser|auth.*delete|delete.*auth" --type ts

# Check if there's a database trigger or RLS policy for cascade deletion
fd -e sql | xargs rg -l "CASCADE DELETE|ON DELETE CASCADE" 2>/dev/null || echo "No SQL files found"

Length of output: 1964


Ensure Supabase auth user is deleted when removing the profile

The current deleteUserAccount implementation only deletes the row from the profiles table, leaving behind an orphaned Supabase auth user.

Please update in apps/backend/src/services/profile.service.ts:

  • Augment deleteUserAccount to also call supabase.auth.admin.deleteUser(userId) using your service‐role key.
  • Handle any errors from the auth deletion before returning success.

Suggested change:

 export const deleteUserAccount = async (userId: string) => {
   // Delete profile
   const { error: profileError } = await supabase
     .from('profiles')
     .delete()
     .eq('user_id', userId);
   if (profileError) return { error: profileError };

+  // Delete corresponding Supabase auth user
+  const { error: authError } = await supabase.auth.admin.deleteUser(userId);
+  if (authError) return { error: authError };

   return { success: true };
 };
📝 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.

Suggested change
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' });
};
export const deleteUserAccount = async (userId: string) => {
// Delete profile
const { error: profileError } = await supabase
.from('profiles')
.delete()
.eq('user_id', userId);
if (profileError) return { error: profileError };
// Delete corresponding Supabase auth user
const { error: authError } = await supabase.auth.admin.deleteUser(userId);
if (authError) return { error: authError };
return { success: true };
};
🤖 Prompt for AI Agents
In apps/backend/src/services/profile.service.ts around the deleteUserAccount
function, the current code only deletes the user profile from the database but
does not remove the associated Supabase auth user, causing orphaned auth
records. Update deleteUserAccount to first delete the profile row, then call
supabase.auth.admin.deleteUser(userId) to delete the auth user using the
service-role key. Handle any errors from the auth deletion and return them
appropriately before returning success.

Comment on lines +5 to +9
export const getUserProfile = async (userId: string) => {
const { data, error } = await supabase.from('profiles').select('*').eq('user_id', userId);

return error ? { error } : { profile: data };
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Use explicit field selection instead of select('*').

Using select('*') could potentially expose sensitive or internal fields that shouldn't be returned to the client.

 export const getUserProfile = async (userId: string) => {
-  const { data, error } = await supabase.from('profiles').select('*').eq('user_id', userId);
+  const { data, error } = await supabase
+    .from('profiles')
+    .select('user_id, name, avatar_url, phone, address, preferences, social_links, verification_status, last_active, created_at')
+    .eq('user_id', userId);
 
   return error ? { error } : { profile: data };
 };

This ensures only intended fields are exposed to the client.

📝 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.

Suggested change
export const getUserProfile = async (userId: string) => {
const { data, error } = await supabase.from('profiles').select('*').eq('user_id', userId);
return error ? { error } : { profile: data };
};
export const getUserProfile = async (userId: string) => {
const { data, error } = await supabase
.from('profiles')
.select('user_id, name, avatar_url, phone, address, preferences, social_links, verification_status, last_active, created_at')
.eq('user_id', userId);
return error ? { error } : { profile: data };
};
🤖 Prompt for AI Agents
In apps/backend/src/services/profile.service.ts around lines 5 to 9, replace the
use of select('*') with an explicit list of fields to retrieve from the
'profiles' table. Identify and specify only the safe, necessary fields to expose
in the select method to avoid returning sensitive or internal data to the
client.

Comment on lines +26 to +50
export const uploadUserAvatar = async (userId: string, req: Request) => {
if (!req.file) {
return { error: new Error('No file uploaded') };
}

const file = req.file;
const filePath = `avatars/${userId}/${Date.now()}_${file.originalname}`;

const { error: uploadError } = await supabase.storage
.from('avatars')
.upload(filePath, file.buffer, {
contentType: file.mimetype,
});

if (uploadError) return { error: uploadError };

const { data } = supabase.storage.from('avatars').getPublicUrl(filePath);
const publicUrl = data?.publicUrl;

if (!publicUrl) return { error: new Error('Failed to generate public URL') };

await supabase.from('profiles').update({ avatar_url: publicUrl }).eq('user_id', userId);

return { avatar_url: publicUrl };
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

⚠️ Potential issue

Add file validation and improve error handling for avatar uploads.

The current implementation has several security concerns:

  1. No file type validation
  2. No file size limits
  3. file.originalname could contain unsafe characters
  4. Missing error handling for profile update

Apply these improvements:

 export const uploadUserAvatar = async (userId: string, req: Request) => {
   if (!req.file) {
     return { error: new Error('No file uploaded') };
   }
 
   const file = req.file;
+  
+  // Validate file type
+  const allowedMimeTypes = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];
+  if (!allowedMimeTypes.includes(file.mimetype)) {
+    return { error: new Error('Invalid file type. Only JPEG, PNG, GIF, and WebP are allowed') };
+  }
+  
+  // Validate file size (e.g., 5MB limit)
+  const maxSizeInBytes = 5 * 1024 * 1024;
+  if (file.size > maxSizeInBytes) {
+    return { error: new Error('File size exceeds 5MB limit') };
+  }
+  
+  // Sanitize filename
+  const sanitizedFilename = file.originalname.replace(/[^a-zA-Z0-9.-]/g, '_');
-  const filePath = `avatars/${userId}/${Date.now()}_${file.originalname}`;
+  const filePath = `avatars/${userId}/${Date.now()}_${sanitizedFilename}`;
 
   const { error: uploadError } = await supabase.storage
     .from('avatars')
     .upload(filePath, file.buffer, {
       contentType: file.mimetype,
     });
 
   if (uploadError) return { error: uploadError };
 
   const { data } = supabase.storage.from('avatars').getPublicUrl(filePath);
   const publicUrl = data?.publicUrl;
 
   if (!publicUrl) return { error: new Error('Failed to generate public URL') };
 
-  await supabase.from('profiles').update({ avatar_url: publicUrl }).eq('user_id', userId);
+  const { error: updateError } = await supabase
+    .from('profiles')
+    .update({ avatar_url: publicUrl })
+    .eq('user_id', userId);
+    
+  if (updateError) {
+    // Consider deleting the uploaded file if profile update fails
+    await supabase.storage.from('avatars').remove([filePath]);
+    return { error: new Error('Failed to update profile with new avatar') };
+  }
 
   return { avatar_url: publicUrl };
 };

Also ensure that file size limits are configured in your multer middleware.

📝 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.

Suggested change
export const uploadUserAvatar = async (userId: string, req: Request) => {
if (!req.file) {
return { error: new Error('No file uploaded') };
}
const file = req.file;
const filePath = `avatars/${userId}/${Date.now()}_${file.originalname}`;
const { error: uploadError } = await supabase.storage
.from('avatars')
.upload(filePath, file.buffer, {
contentType: file.mimetype,
});
if (uploadError) return { error: uploadError };
const { data } = supabase.storage.from('avatars').getPublicUrl(filePath);
const publicUrl = data?.publicUrl;
if (!publicUrl) return { error: new Error('Failed to generate public URL') };
await supabase.from('profiles').update({ avatar_url: publicUrl }).eq('user_id', userId);
return { avatar_url: publicUrl };
};
export const uploadUserAvatar = async (userId: string, req: Request) => {
if (!req.file) {
return { error: new Error('No file uploaded') };
}
const file = req.file;
// Validate file type
const allowedMimeTypes = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];
if (!allowedMimeTypes.includes(file.mimetype)) {
return { error: new Error('Invalid file type. Only JPEG, PNG, GIF, and WebP are allowed') };
}
// Validate file size (e.g., 5MB limit)
const maxSizeInBytes = 5 * 1024 * 1024;
if (file.size > maxSizeInBytes) {
return { error: new Error('File size exceeds 5MB limit') };
}
// Sanitize filename
const sanitizedFilename = file.originalname.replace(/[^a-zA-Z0-9.\-]/g, '_');
const filePath = `avatars/${userId}/${Date.now()}_${sanitizedFilename}`;
const { error: uploadError } = await supabase.storage
.from('avatars')
.upload(filePath, file.buffer, {
contentType: file.mimetype,
});
if (uploadError) {
return { error: uploadError };
}
const { data } = supabase.storage.from('avatars').getPublicUrl(filePath);
const publicUrl = data?.publicUrl;
if (!publicUrl) {
return { error: new Error('Failed to generate public URL') };
}
const { error: updateError } = await supabase
.from('profiles')
.update({ avatar_url: publicUrl })
.eq('user_id', userId);
if (updateError) {
// Consider deleting the uploaded file if profile update fails
await supabase.storage.from('avatars').remove([filePath]);
return { error: new Error('Failed to update profile with new avatar') };
}
return { avatar_url: publicUrl };
};
🤖 Prompt for AI Agents
In apps/backend/src/services/profile.service.ts between lines 26 and 50, add
validation to check the uploaded file's type against allowed image MIME types
and ensure the file size is within acceptable limits (note: configure size
limits in multer middleware). Sanitize file.originalname to remove unsafe
characters before using it in the file path. Add error handling for the profile
update operation by checking the result and returning an error if the update
fails. These changes will improve security and robustness of the avatar upload
process.

bernardev254 and others added 2 commits June 1, 2025 09:29
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (2)
apps/backend/src/index.ts (2)

11-13: ⚠️ Potential issue

Remove unused router imports.

These individual router imports are still unused as identified in previous reviews. The code only uses the consolidated routes import on line 46, making these imports redundant.

Apply this diff to remove the unused imports:

 import routes from './routes';
-import authRouter from './routes/auth';
-import profileRouter from './routes/profile.routes';

29-33: ⚠️ Potential issue

Remove redundant router construction.

This router construction is still unused as identified in previous reviews. The router is created and configured but never actually used in the application middleware stack.

Apply this diff to remove the unused router construction:

-const router = Router();
-
-// Apply prefixes here
-router.use('/auth', authRouter);
-router.use('/profile', profileRouter);
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f29216a and 50b7191.

📒 Files selected for processing (3)
  • apps/backend/package.json (1 hunks)
  • apps/backend/src/config/supabase.ts (1 hunks)
  • apps/backend/src/index.ts (4 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • apps/backend/package.json
  • apps/backend/src/config/supabase.ts
🧰 Additional context used
🧬 Code Graph Analysis (1)
apps/backend/src/index.ts (1)
apps/backend/src/db/init.ts (1)
  • initializeDatabase (4-32)
🔇 Additional comments (2)
apps/backend/src/index.ts (2)

5-5: Good addition for database initialization.

The import of initializeDatabase is properly used later in the file to ensure the database schema is ready before the server starts.


60-65: Excellent database initialization pattern.

The modification to initialize the database before starting the server is a good practice that ensures the schema is ready and prevents potential startup issues.

app.use(rateLimiter);

// Routes
app.use('/api', routes);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Verify the consolidated routes structure.

The application now uses consolidated routes under the /api prefix. Ensure that the ./routes/index.ts file properly exports and configures all necessary routes including auth and profile routes.


🏁 Script executed:

#!/bin/bash
# Description: Verify the routes structure and exports
# Expected: Find the routes index file and check its exports

echo "=== Checking routes index file ==="
cat apps/backend/src/routes/index.ts

echo -e "\n=== Checking for route files ==="
fd -t f -e ts . apps/backend/src/routes/

echo -e "\n=== Checking route imports in index ==="
rg "import.*from.*routes" apps/backend/src/routes/index.ts

Length of output: 918


Missing property routes in consolidated router

The apps/backend/src/routes/index.ts currently only mounts the auth and profile routers, but there’s also a property.route.ts under apps/backend/src/routes/ that isn’t exposed. Please update index.ts to import and mount it:

• File: apps/backend/src/routes/index.ts

  • Add:
    import propertyRouter from './property.route';
  • Add:
    router.use('/property', propertyRouter);

• Verify that apps/backend/src/routes/property.route.ts exports a default Express Router.

🤖 Prompt for AI Agents
In apps/backend/src/routes/index.ts around line 46, the consolidated router
currently imports and mounts only auth and profile routes but misses the
property routes. To fix this, import the property router from './property.route'
and mount it on the router with the path '/property'. Also, verify that
apps/backend/src/routes/property.route.ts exports a default Express Router to
ensure proper integration.

// Routes
app.use('/api', routes);

app.use('/auth', authRoutes);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix missing import for authRoutes.

The code references authRoutes on line 48, but this import is not visible in the current code. This will cause a runtime error.

Add the missing import or verify if this line should be removed:

+import authRoutes from './routes/auth.routes';

Or if this is redundant with the new consolidated routes, remove this line:

-app.use('/auth', authRoutes);
🤖 Prompt for AI Agents
In apps/backend/src/index.ts at line 48, the code uses authRoutes without
importing it, causing a runtime error. To fix this, either add the appropriate
import statement for authRoutes at the top of the file or, if authRoutes is no
longer needed due to route consolidation, remove the line app.use('/auth',
authRoutes) entirely.

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
@respp
Copy link
Contributor

respp commented Jun 3, 2025

Hi @bernardev25 thank you so much for your work on PR #60 and for implementing the profile management system in StellarRent, I value your effort, but I'm having trouble reviewing your specific changes because the PR includes many commits from other contributors like Benjtalkshow and Lynndabel, which makes it a bit confusing for me; would it be possible for you to resubmit this PR from a new branch that only contains your modifications related to the issue, so I can better focus on your contribution? If you need help creating the branch, I’d be happy to guide you, thanks in advance for your collaboration.

@bernardev254
Copy link
Contributor Author

I have created a new branch and submitted a new pr.check it out #68

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.

5 participants