Source

controllers/auth.controller.js

import { env } from "@config/env.js";
import { AuthService } from "@services/auth.service.js";
import { logger } from "../config/logger.js";
import { CacheService } from "../utils/cache.utils.js";
// import type { IAuthRequest, ILoginRequest, IChangePasswordRequest, IAvatarUploadRequest } from '../types/auth/index.js';
/**
 * Authentication controller handling login, profile, and session endpoints.
 * @category Controllers
 */
export class AuthController {
    /**
     * Handle login and issue access/refresh tokens.
     * @param {IGenericBodyResponse<ILoginDto>} req Express request with login credentials.
     * @param {Response} res Express response instance.
     * @returns {Promise<void>} Resolves when the response has been sent.
     */
    static async login(req, res) {
        try {
            const credentials = req.body;
            const authService = new AuthService();
            const result = await authService.login(credentials);
            // Set refresh token as HTTP-only cookie
            res.cookie("refreshToken", result.refreshToken, {
                httpOnly: true,
                secure: env.NODE_ENV === "production", // Use secure cookies in production
                sameSite: "strict",
                maxAge: 7 * 24 * 60 * 60 * 1000, // 7 days
                path: "/",
            });
            // Only send access token in response body
            res.status(200).json({
                success: true,
                message: "Login successful",
                data: {
                    accessToken: result.accessToken,
                },
            });
        }
        catch (error) {
            logger.error({
                error,
                message: "Login controller error",
            });
            const message = error instanceof Error ? error.message : "Login failed";
            res.status(401).json({
                success: false,
                message,
            });
        }
    }
    /**
     * Change the authenticated user's password.
     * @param {IChangePasswordPayload} req Express request containing the user context and payload.
     * @param {Response} res Express response instance.
     * @returns {Promise<void>} Resolves when the response has been sent.
     */
    static async changePassword(req, res) {
        try {
            const userId = req.user?.userId;
            const authService = new AuthService();
            if (!userId) {
                res.status(401).json({
                    success: false,
                    message: "User not found",
                });
                return;
            }
            const passwordData = req.body;
            await authService.changePassword(userId, passwordData);
            res.status(200).json({
                success: true,
                message: "Password changed successfully",
            });
        }
        catch (error) {
            logger.error({
                error,
                message: "Change password controller error",
            });
            const message = error instanceof Error ? error.message : "Password change failed";
            const statusCode = message.includes("not found")
                ? 404
                : message.includes("incorrect")
                    ? 400
                    : 500;
            res.status(statusCode).json({
                success: false,
                message,
            });
        }
    }
    /**
     * Retrieve the currently authenticated user's profile.
     * @param {IChangePasswordPayload} req Express request with authenticated user.
     * @param {Response} res Express response instance.
     * @returns {Promise<void>} Resolves when the response has been sent.
     */
    static async getCurrentUser(req, res) {
        try {
            const userId = req.user?.userId;
            const authService = new AuthService();
            if (!userId) {
                res.status(401).json({
                    success: false,
                    message: "Unauthorized",
                });
                return;
            }
            const user = await authService.getUser(userId);
            res.status(200).json({
                success: true,
                data: user,
            });
        }
        catch (error) {
            logger.error({
                error,
                message: "Get current user controller error",
            });
            const message = error instanceof Error ? error.message : "Failed to get user info";
            const statusCode = message.includes("not found") ? 404 : 500;
            res.status(statusCode).json({
                success: false,
                message,
            });
        }
    }
    /**
     * Update the authenticated user's avatar.
     * @param {IChangeAvatarPayload} req Express request with avatar payload.
     * @param {Response} res Express response instance.
     * @returns {Promise<void>} Resolves when the response has been sent.
     */
    static async changeAvatar(req, res) {
        try {
            const userId = req.user?.userId;
            const authService = new AuthService();
            if (!userId) {
                res.status(401).json({
                    success: false,
                    message: "Unauthorized",
                });
                return;
            }
            const { avatar } = req.body;
            if (!avatar) {
                res.status(400).json({
                    success: false,
                    message: "Avatar data is required",
                });
                return;
            }
            const avatarUrl = await authService.changeAvatar(userId, avatar);
            res.status(200).json({
                success: true,
                message: "Avatar changed successfully",
                data: { avatarUrl },
            });
        }
        catch (error) {
            logger.error({
                error,
                message: "Change avatar controller error",
            });
            const message = error instanceof Error ? error.message : "Avatar change failed";
            const statusCode = message.includes("not found")
                ? 404
                : message.includes("Invalid") || message.includes("exceeds")
                    ? 400
                    : 500;
            res.status(statusCode).json({
                success: false,
                message,
            });
        }
    }
    // async changeAvatar(req: IAuthRequest, res: Response): Promise<void> {
    //   try {
    //     const userId = req.user!.userId;
    //     const avatarData: IAvatarUploadRequest = req.body as IAvatarUploadRequest;
    //     const avatarUrl = await authService.changeAvatar(userId, avatarData);
    //     res.status(200).json({
    //       success: true,
    //       message: 'Avatar changed successfully',
    //       data: { avatarUrl }
    //     });
    //   } catch (error) {
    //     logger.error('Change avatar controller error:', error);
    //     const message = error instanceof Error ? error.message : 'Avatar change failed';
    //     const statusCode = message.includes('not found') ? 404 : 500;
    //     res.status(statusCode).json({
    //       success: false,
    //       message
    //     });
    //   }
    // }
    /**
     * Exchange a refresh token for a new access token.
     * @param {IRefreshTokenPayload} req Express request with cookie header.
     * @param {Response} res Express response instance.
     * @returns {Promise<void>} Resolves when the response has been sent.
     */
    static async refreshToken(req, res) {
        try {
            const refreshToken = req.cookies?.refreshToken;
            const authService = new AuthService();
            if (!refreshToken || typeof refreshToken !== "string") {
                res.status(400).json({
                    success: false,
                    message: "Refresh token is required",
                });
                return;
            }
            const result = await authService.refreshToken(refreshToken);
            res.status(200).json({
                success: true,
                data: result,
            });
        }
        catch (error) {
            logger.error({
                error,
                message: "Refresh token controller error",
            });
            const message = error instanceof Error ? error.message : "Token refresh failed";
            const statusCode = message.includes("not found") ? 404 : 401;
            res.status(statusCode).json({
                success: false,
                message,
            });
        }
    }
    /**
     * Clear refresh token cookies and invalidate the session.
     * @param {IAuthRequest} req Express request.
     * @param {Response} res Express response instance.
     * @returns {Promise<void>} Resolves when the response has been sent.
     */
    static async logout(req, res) {
        try {
            // Delete user from cache if user ID is available (from optional auth middleware)
            if (req.user?.userId) {
                await CacheService.deleteUser(req.user.userId);
            }
            // Clear the refresh token cookie
            res.clearCookie("refreshToken", {
                httpOnly: true,
                secure: env.NODE_ENV === "production",
                sameSite: "strict",
                path: "/",
            });
            res.status(200).json({
                success: true,
                message: "Logged out successfully",
            });
        }
        catch (error) {
            logger.error({
                error,
                message: "Logout controller error",
            });
            const message = error instanceof Error ? error.message : "Logout failed";
            res.status(500).json({
                success: false,
                message,
            });
        }
    }
    /**
     * Update the authenticated user's name.
     * @param {IUpdateNamePayload} req Express request containing new name.
     * @param {Response} res Express response instance.
     * @returns {Promise<void>} Resolves when the response has been sent.
     */
    static async updateOwnName(req, res) {
        try {
            const userId = req.user?.userId;
            const authService = new AuthService();
            if (!userId) {
                res.status(401).json({
                    success: false,
                    message: "Unauthorized",
                });
                return;
            }
            const { name } = req.body;
            await authService.updateOwnName(userId, name);
            res.status(200).json({
                success: true,
                message: "Name updated successfully",
            });
        }
        catch (error) {
            logger.error({
                error,
                message: "Update own name controller error",
            });
            const message = error instanceof Error ? error.message : "Failed to update name";
            const statusCode = message.includes("not found") ? 404 : 500;
            res.status(statusCode).json({
                success: false,
                message,
            });
        }
    }
}