Skip to content

User Routes Documentation

This section provides a detailed breakdown of the authenticated user API routes, outlining their purpose, the flow of requests through middleware, controllers, and services, and the expected responses. All user routes require authentication with a USER or ADMIN role.

Global User Middleware

All routes defined in src/api/user.routes.ts are protected by the following middleware:

  • auth: Ensures the request has a valid JWT and authenticates the user.
  • authorize([Role.USER]): Checks if the authenticated user has at least the USER role.

This means that any request to these endpoints must include a valid JWT for an authenticated user.


1. Get User Profile

Endpoint: GET /v1/user/profile Description: Retrieves the profile information of the authenticated user.

Flow Map

  1. Initial Request:
  2. Method: GET
  3. Path: /v1/user/profile
  4. Client Sends: No request body or parameters. A valid JWT for an authenticated user in the Authorization header.

  5. Route Handler (src/api/user.routes.ts):

    router.get('/profile', userController.getProfile);
    

  6. The request first passes through the global auth and authorize([Role.USER]) middleware, ensuring the user is authenticated and authorized.
  7. If authorized, the request is forwarded to userController.getProfile.

  8. Controller (src/controllers/user.controller.ts):

    const getProfile = async (req: Request, res: Response, next: NextFunction) => {
      try {
        if (!req.user) {
          return res.status(401).json({ message: 'User not authenticated' });
        }
    
        const authenticatedUser = req.user;
        const user = await userService.getUserById(authenticatedUser.id);
        if (!user) {
          return res.status(404).json({ message: 'User not found' });
        }
        const {
          password: _userPassword,
          passwordHistory: _passwordHistory,
          ...userWithoutPassword
        } = user;
        res.status(200).json({ success: true, data: userWithoutPassword });
      } catch (error) {
        next(error);
      }
    };
    

  9. The controller verifies req.user is populated by the authentication middleware.
  10. It retrieves the full user object from the database using userService.getUserById() based on the authenticated user's ID.
  11. If the user is not found (which should ideally not happen after authentication), it returns a 404 Not Found error.
  12. It destructures the password and passwordHistory fields from the user object to prevent sending sensitive data in the response.
  13. Sends a 200 OK response with the user's profile data.
  14. Any errors are caught and passed to the Express error handling middleware.

  15. Service (src/services/user.service.ts):

    const getUserById = async (id: string): Promise<User | null> => {
      return prisma.user.findUnique({ where: { id } });
    };
    

  16. The service uses the Prisma client (prisma.user.findUnique()) to query the database for a user record by their ID.
  17. It returns the User object or null if not found.

  18. Response to Client:

  19. Status: 200 OK
  20. Body (JSON):
    {
      "success": true,
      "data": {
        "id": "uuid-of-user",
        "username": "johndoe",
        "email": "john.doe@example.com",
        "role": "USER",
        "googleId": null,
        "provider": "LOCAL",
        "createdAt": "2023-01-01T12:00:00.000Z",
        "updatedAt": "2023-01-01T12:00:00.000Z"
      }
    }
    
  21. Error Responses:
  22. 401 Unauthorized: If no valid JWT is provided or the token is expired/invalid.
  23. 403 Forbidden: If the authenticated user does not have the USER role.
  24. 404 Not Found: If the user is not found in the database (unlikely after authentication).
  25. 500 Internal Server Error: For unexpected server-side errors.

2. Update User Email

Endpoint: PUT /v1/user/profile/email Description: Updates the authenticated user's email address after verifying their current password.

Flow Map

  1. Initial Request:
  2. Method: PUT
  3. Path: /v1/user/profile/email
  4. Client Sends:
  5. A valid JWT for an authenticated user in the Authorization header.
  6. Request Body (JSON):

    {
      "email": "new.email@example.com",
      "password": "current_password123"
    }
    

  7. Route Handler (src/api/user.routes.ts):

    router.put('/profile/email', validate(userValidation.updateEmail), userController.updateEmail);
    

  8. The request first passes through the global auth and authorize([Role.USER]) middleware.
  9. Then, validate(userValidation.updateEmail) middleware validates the request body against the userValidation.updateEmail Zod schema, ensuring email and password are present and valid.
  10. If validation passes, the request is forwarded to userController.updateEmail.

  11. Controller (src/controllers/user.controller.ts):

    const updateEmail = async (req: Request, res: Response, next: NextFunction) => {
      try {
        if (!req.user) {
          return res.status(401).json({ message: 'User not authenticated' });
        }
        const { email, password } = req.body;
        const authenticatedUser = req.user;
        const updatedUser = await userService.updateUserEmail(authenticatedUser.id, email, password);
        if (!updatedUser) {
          return res.status(404).json({ message: 'User not found or password incorrect' });
        }
        const {
          password: _userPassword,
          passwordHistory: _passwordHistory,
          ...userWithoutPassword
        } = updatedUser;
        res.status(200).json({ success: true, data: userWithoutPassword });
      } catch (error) {
        next(error);
      }
    };
    

  12. The controller verifies req.user is populated.
  13. It extracts email and password from req.body.
  14. It calls userService.updateUserEmail() with the authenticated user's ID, the new email, and the current password.
  15. If the update is successful, it destructures the password and passwordHistory fields from the updated user object.
  16. Sends a 200 OK response with the updated user data.
  17. Any errors are caught and passed to the Express error handling middleware.

  18. Service (src/services/user.service.ts):

    const updateUserEmail = async (
      userId: string,
      newEmail: string,
      currentPassword: string,
    ): Promise<User | null> => {
      const user = await prisma.user.findUnique({ where: { id: userId } });
      if (!user || !user.password) {
        throw new Error('User not found or does not have a password set.');
      }
    
      const isPasswordMatch = await bcrypt.compare(currentPassword, user.password);
      if (!isPasswordMatch) {
        throw new Error('Incorrect password'); // This will be caught by ApiError middleware
      }
    
      return prisma.user.update({
        where: { id: userId },
        data: { email: newEmail },
      });
    };
    

  19. The service first retrieves the user by userId.
  20. It compares the currentPassword provided by the user with the stored hashed password using bcrypt.compare(). If they don't match, it throws an error.
  21. If the password is correct, it updates the user's email in the database using prisma.user.update().
  22. Returns the updated User object or null.

  23. Response to Client:

  24. Status: 200 OK
  25. Body (JSON):
    {
      "success": true,
      "data": {
        "id": "uuid-of-user",
        "username": "johndoe",
        "email": "new.email@example.com",
        "role": "USER",
        "googleId": null,
        "provider": "LOCAL",
        "createdAt": "2023-01-01T12:00:00.000Z",
        "updatedAt": "2023-01-01T12:00:00.000Z"
      }
    }
    
  26. Error Responses:
  27. 400 Bad Request: If validation fails.
  28. 401 Unauthorized: If no valid JWT is provided, or if the provided current password is incorrect.
  29. 403 Forbidden: If the authenticated user does not have the USER role.
  30. 404 Not Found: If the user is not found in the database.
  31. 500 Internal Server Error: For unexpected server-side errors.