Enhance application metadata and user interface with bilingual support
- Added bilingual comments and metadata in `layout.tsx`, including application title and description in both English and Vietnamese. - Updated `page.tsx` to include bilingual loading messages, welcome prompts, and login instructions for improved user experience. - Enhanced `login/page.tsx` with bilingual labels and messages for form fields and error handling. - Improved `auth.api.ts` with detailed comments on authentication processes, including registration, login, logout, and token management. - Updated `auth.store.ts` to include bilingual comments for state management and authentication methods. - Enhanced API response types in `api.types.ts` with bilingual documentation for better clarity. - Improved user management routes and services in `user.controller.ts` and `user.service.ts` with bilingual comments for better maintainability.
This commit is contained in:
@@ -1,17 +1,29 @@
|
||||
import type { Metadata } from 'next';
|
||||
import './globals.css';
|
||||
|
||||
/**
|
||||
* EN: Metadata for the application
|
||||
* VI: Metadata cho ứng dụng
|
||||
*/
|
||||
export const metadata: Metadata = {
|
||||
title: 'GoodGo Platform',
|
||||
description: 'Enterprise microservices platform',
|
||||
description: 'Enterprise microservices platform / Nền tảng microservices doanh nghiệp',
|
||||
};
|
||||
|
||||
/**
|
||||
* EN: Root layout component for the entire application
|
||||
* VI: Component layout gốc cho toàn bộ ứng dụng
|
||||
*
|
||||
* @param children - Child components to render / Components con để render
|
||||
*/
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
// EN: Root HTML structure with English language
|
||||
// VI: Cấu trúc HTML gốc với ngôn ngữ tiếng Anh
|
||||
<html lang="en">
|
||||
<body>{children}</body>
|
||||
</html>
|
||||
|
||||
@@ -4,31 +4,57 @@ import { useState } from 'react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useAuthStore } from '@/stores/auth.store';
|
||||
|
||||
/**
|
||||
* EN: Login page component for user authentication
|
||||
* VI: Component trang đăng nhập để xác thực người dùng
|
||||
*/
|
||||
export default function LoginPage() {
|
||||
// EN: Next.js router for navigation
|
||||
// VI: Next.js router để điều hướng
|
||||
const router = useRouter();
|
||||
|
||||
// EN: Auth store hooks for login functionality
|
||||
// VI: Auth store hooks cho chức năng đăng nhập
|
||||
const { login, isLoading } = useAuthStore();
|
||||
|
||||
// EN: Form state management
|
||||
// VI: Quản lý trạng thái form
|
||||
const [email, setEmail] = useState('');
|
||||
const [password, setPassword] = useState('');
|
||||
const [error, setError] = useState('');
|
||||
|
||||
/**
|
||||
* EN: Handle form submission for login
|
||||
* VI: Xử lý submit form để đăng nhập
|
||||
*/
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
setError('');
|
||||
try {
|
||||
// EN: Attempt login through auth store
|
||||
// VI: Thử đăng nhập thông qua auth store
|
||||
await login(email, password);
|
||||
// EN: Redirect to home page on successful login
|
||||
// VI: Chuyển hướng về trang chủ khi đăng nhập thành công
|
||||
router.push('/');
|
||||
} catch (err: any) {
|
||||
setError(err.message || 'Login failed');
|
||||
setError(err.message || 'Login failed / Đăng nhập thất bại');
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
// EN: Centered login form layout
|
||||
// VI: Layout form đăng nhập được căn giữa
|
||||
<div className="min-h-screen flex items-center justify-center">
|
||||
<form onSubmit={handleSubmit} className="bg-white p-8 rounded shadow-md w-96">
|
||||
<h2 className="text-2xl font-bold mb-4">Login</h2>
|
||||
<h2 className="text-2xl font-bold mb-4">Login / Đăng nhập</h2>
|
||||
|
||||
{/* EN: Error message display / VI: Hiển thị thông báo lỗi */}
|
||||
{error && <div className="text-red-500 mb-4">{error}</div>}
|
||||
|
||||
{/* EN: Email input field / VI: Trường nhập email */}
|
||||
<div className="mb-4">
|
||||
<label className="block mb-2">Email</label>
|
||||
<label className="block mb-2">Email / Email</label>
|
||||
<input
|
||||
type="email"
|
||||
value={email}
|
||||
@@ -37,8 +63,10 @@ export default function LoginPage() {
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* EN: Password input field / VI: Trường nhập mật khẩu */}
|
||||
<div className="mb-4">
|
||||
<label className="block mb-2">Password</label>
|
||||
<label className="block mb-2">Password / Mật khẩu</label>
|
||||
<input
|
||||
type="password"
|
||||
value={password}
|
||||
@@ -47,12 +75,14 @@ export default function LoginPage() {
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* EN: Submit button with loading state / VI: Nút submit với trạng thái loading */}
|
||||
<button
|
||||
type="submit"
|
||||
disabled={isLoading}
|
||||
className="w-full bg-blue-500 text-white p-2 rounded hover:bg-blue-600 disabled:opacity-50"
|
||||
>
|
||||
{isLoading ? 'Logging in...' : 'Login'}
|
||||
{isLoading ? 'Logging in... / Đang đăng nhập...' : 'Login / Đăng nhập'}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -3,30 +3,46 @@
|
||||
import { useAuthStore } from '@/stores/auth.store';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
/**
|
||||
* EN: Home page component - main application entry point
|
||||
* VI: Component trang chủ - điểm vào chính của ứng dụng
|
||||
*/
|
||||
export default function Home() {
|
||||
// EN: Get authentication state from store
|
||||
// VI: Lấy trạng thái xác thực từ store
|
||||
const { user, isAuthenticated, isLoading, fetchUser } = useAuthStore();
|
||||
|
||||
// EN: Fetch user data on component mount if not authenticated
|
||||
// VI: Fetch dữ liệu user khi component mount nếu chưa xác thực
|
||||
useEffect(() => {
|
||||
if (!isAuthenticated && !isLoading) {
|
||||
fetchUser();
|
||||
}
|
||||
}, [isAuthenticated, isLoading, fetchUser]);
|
||||
|
||||
// EN: Show loading state while checking authentication
|
||||
// VI: Hiển thị trạng thái loading trong khi kiểm tra xác thực
|
||||
if (isLoading) {
|
||||
return <div className="p-8">Loading...</div>;
|
||||
return <div className="p-8">Loading... / Đang tải...</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
// EN: Main content area with responsive padding
|
||||
// VI: Khu vực nội dung chính với padding responsive
|
||||
<main className="min-h-screen p-8">
|
||||
<h1 className="text-4xl font-bold mb-4">GoodGo Platform</h1>
|
||||
<h1 className="text-4xl font-bold mb-4">GoodGo Platform / Nền tảng GoodGo</h1>
|
||||
|
||||
{/* EN: Conditional rendering based on authentication status / VI: Render có điều kiện dựa trên trạng thái xác thực */}
|
||||
{isAuthenticated && user ? (
|
||||
// EN: Authenticated user welcome message / VI: Thông báo chào mừng người dùng đã xác thực
|
||||
<div>
|
||||
<p>Welcome, {user.email}!</p>
|
||||
<p>Role: {user.role}</p>
|
||||
<p>Welcome, {user.email}! / Chào mừng, {user.email}!</p>
|
||||
<p>Role: {user.role} / Vai trò: {user.role}</p>
|
||||
</div>
|
||||
) : (
|
||||
// EN: Login prompt for unauthenticated users / VI: Nhắc đăng nhập cho người dùng chưa xác thực
|
||||
<div>
|
||||
<p>Please log in to continue.</p>
|
||||
<p>Please log in to continue. / Vui lòng đăng nhập để tiếp tục.</p>
|
||||
</div>
|
||||
)}
|
||||
</main>
|
||||
|
||||
@@ -1,13 +1,27 @@
|
||||
import { apiClient } from './client';
|
||||
import { LoginDto, RegisterDto, AuthResponse, ApiResponse, UserResponse } from '@goodgo/types';
|
||||
|
||||
/**
|
||||
* EN: Authentication API service for frontend
|
||||
* VI: Service API xác thực cho frontend
|
||||
*/
|
||||
export const authApi = {
|
||||
/**
|
||||
* EN: Register new user account
|
||||
* VI: Đăng ký tài khoản người dùng mới
|
||||
*/
|
||||
register: async (data: RegisterDto): Promise<ApiResponse<AuthResponse>> => {
|
||||
return apiClient.post('/auth/register', data);
|
||||
},
|
||||
|
||||
/**
|
||||
* EN: Login user and store tokens
|
||||
* VI: Đăng nhập người dùng và lưu tokens
|
||||
*/
|
||||
login: async (data: LoginDto): Promise<ApiResponse<AuthResponse>> => {
|
||||
const response = await apiClient.post('/auth/login', data);
|
||||
// EN: Store tokens in client and localStorage on successful login
|
||||
// VI: Lưu tokens trong client và localStorage khi đăng nhập thành công
|
||||
if (response.success && response.data) {
|
||||
apiClient.setAuthToken(response.data.accessToken);
|
||||
if (typeof window !== 'undefined') {
|
||||
@@ -17,9 +31,17 @@ export const authApi = {
|
||||
return response;
|
||||
},
|
||||
|
||||
/**
|
||||
* EN: Logout user and clear tokens
|
||||
* VI: Đăng xuất người dùng và xóa tokens
|
||||
*/
|
||||
logout: async (): Promise<ApiResponse> => {
|
||||
// EN: Get refresh token from localStorage for logout request
|
||||
// VI: Lấy refresh token từ localStorage cho request logout
|
||||
const refreshToken = typeof window !== 'undefined' ? localStorage.getItem('refreshToken') : null;
|
||||
const response = await apiClient.post('/auth/logout', { refreshToken });
|
||||
// EN: Clear tokens from client and localStorage
|
||||
// VI: Xóa tokens khỏi client và localStorage
|
||||
apiClient.removeAuthToken();
|
||||
if (typeof window !== 'undefined') {
|
||||
localStorage.removeItem('refreshToken');
|
||||
@@ -27,18 +49,32 @@ export const authApi = {
|
||||
return response;
|
||||
},
|
||||
|
||||
/**
|
||||
* EN: Refresh access token using refresh token
|
||||
* VI: Làm mới access token sử dụng refresh token
|
||||
*/
|
||||
refreshToken: async (refreshToken: string): Promise<ApiResponse<{ accessToken: string }>> => {
|
||||
const response = await apiClient.post('/auth/refresh', { refreshToken });
|
||||
// EN: Update access token in client on successful refresh
|
||||
// VI: Cập nhật access token trong client khi refresh thành công
|
||||
if (response.success && response.data) {
|
||||
apiClient.setAuthToken(response.data.accessToken);
|
||||
}
|
||||
return response;
|
||||
},
|
||||
|
||||
/**
|
||||
* EN: Get current authenticated user profile
|
||||
* VI: Lấy hồ sơ người dùng đã xác thực hiện tại
|
||||
*/
|
||||
getMe: async (): Promise<ApiResponse<UserResponse>> => {
|
||||
return apiClient.get('/users/me');
|
||||
},
|
||||
|
||||
/**
|
||||
* EN: Change user password
|
||||
* VI: Thay đổi mật khẩu người dùng
|
||||
*/
|
||||
changePassword: async (currentPassword: string, newPassword: string): Promise<ApiResponse> => {
|
||||
return apiClient.put('/auth/password', { currentPassword, newPassword });
|
||||
},
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
import { createHttpClient } from '@goodgo/http-client';
|
||||
|
||||
// EN: Get API base URL from environment or use default
|
||||
// VI: Lấy API base URL từ environment hoặc sử dụng mặc định
|
||||
const API_URL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost/api/v1';
|
||||
|
||||
/**
|
||||
* EN: HTTP client instance configured for API calls
|
||||
* VI: Instance HTTP client đã cấu hình cho API calls
|
||||
*/
|
||||
export const apiClient = createHttpClient({
|
||||
baseURL: API_URL,
|
||||
timeout: 30000,
|
||||
timeout: 30000, // EN: 30 second timeout / VI: Timeout 30 giây
|
||||
});
|
||||
|
||||
@@ -3,16 +3,31 @@ import { persist } from 'zustand/middleware';
|
||||
import { UserResponse } from '@goodgo/types';
|
||||
import { authApi } from '../services/api/auth.api';
|
||||
|
||||
/**
|
||||
* EN: Authentication state interface for Zustand store
|
||||
* VI: Interface trạng thái xác thực cho Zustand store
|
||||
*/
|
||||
interface AuthState {
|
||||
/** EN: Current authenticated user / VI: Người dùng đã xác thực hiện tại */
|
||||
user: UserResponse | null;
|
||||
/** EN: Authentication status / VI: Trạng thái xác thực */
|
||||
isAuthenticated: boolean;
|
||||
/** EN: Loading state for async operations / VI: Trạng thái loading cho async operations */
|
||||
isLoading: boolean;
|
||||
/** EN: Login method / VI: Method đăng nhập */
|
||||
login: (email: string, password: string) => Promise<void>;
|
||||
/** EN: Register method / VI: Method đăng ký */
|
||||
register: (email: string, password: string, confirmPassword: string) => Promise<void>;
|
||||
/** EN: Logout method / VI: Method đăng xuất */
|
||||
logout: () => Promise<void>;
|
||||
/** EN: Fetch current user method / VI: Method lấy thông tin user hiện tại */
|
||||
fetchUser: () => Promise<void>;
|
||||
}
|
||||
|
||||
/**
|
||||
* EN: Zustand store for authentication state management with persistence
|
||||
* VI: Zustand store để quản lý trạng thái xác thực với persistence
|
||||
*/
|
||||
export const useAuthStore = create<AuthState>()(
|
||||
persist(
|
||||
(set) => ({
|
||||
@@ -20,6 +35,10 @@ export const useAuthStore = create<AuthState>()(
|
||||
isAuthenticated: false,
|
||||
isLoading: false,
|
||||
|
||||
/**
|
||||
* EN: Login user and update store state
|
||||
* VI: Đăng nhập người dùng và cập nhật trạng thái store
|
||||
*/
|
||||
login: async (email: string, password: string) => {
|
||||
set({ isLoading: true });
|
||||
try {
|
||||
@@ -31,7 +50,7 @@ export const useAuthStore = create<AuthState>()(
|
||||
isLoading: false,
|
||||
});
|
||||
} else {
|
||||
throw new Error(response.error?.message || 'Login failed');
|
||||
throw new Error(response.error?.message || 'Login failed / Đăng nhập thất bại');
|
||||
}
|
||||
} catch (error) {
|
||||
set({ isLoading: false });
|
||||
@@ -39,6 +58,10 @@ export const useAuthStore = create<AuthState>()(
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* EN: Register new user and update store state
|
||||
* VI: Đăng ký người dùng mới và cập nhật trạng thái store
|
||||
*/
|
||||
register: async (email: string, password: string, confirmPassword: string) => {
|
||||
set({ isLoading: true });
|
||||
try {
|
||||
@@ -50,7 +73,7 @@ export const useAuthStore = create<AuthState>()(
|
||||
isLoading: false,
|
||||
});
|
||||
} else {
|
||||
throw new Error(response.error?.message || 'Registration failed');
|
||||
throw new Error(response.error?.message || 'Registration failed / Đăng ký thất bại');
|
||||
}
|
||||
} catch (error) {
|
||||
set({ isLoading: false });
|
||||
@@ -58,6 +81,10 @@ export const useAuthStore = create<AuthState>()(
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* EN: Logout user and clear store state
|
||||
* VI: Đăng xuất người dùng và xóa trạng thái store
|
||||
*/
|
||||
logout: async () => {
|
||||
try {
|
||||
await authApi.logout();
|
||||
@@ -69,6 +96,10 @@ export const useAuthStore = create<AuthState>()(
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* EN: Fetch current user profile from API
|
||||
* VI: Lấy hồ sơ người dùng hiện tại từ API
|
||||
*/
|
||||
fetchUser: async () => {
|
||||
set({ isLoading: true });
|
||||
try {
|
||||
@@ -87,6 +118,8 @@ export const useAuthStore = create<AuthState>()(
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
// EN: Clear user state on fetch failure
|
||||
// VI: Xóa trạng thái user khi fetch thất bại
|
||||
set({
|
||||
user: null,
|
||||
isAuthenticated: false,
|
||||
@@ -96,7 +129,11 @@ export const useAuthStore = create<AuthState>()(
|
||||
},
|
||||
}),
|
||||
{
|
||||
// EN: Persist auth state to localStorage
|
||||
// VI: Persist trạng thái auth vào localStorage
|
||||
name: 'auth-storage',
|
||||
// EN: Only persist user and isAuthenticated, exclude isLoading
|
||||
// VI: Chỉ persist user và isAuthenticated, loại trừ isLoading
|
||||
partialize: (state) => ({ user: state.user, isAuthenticated: state.isAuthenticated }),
|
||||
}
|
||||
)
|
||||
|
||||
@@ -1,29 +1,62 @@
|
||||
/**
|
||||
* EN: Standardized API response interface
|
||||
* VI: Interface phản hồi API chuẩn hóa
|
||||
*/
|
||||
export interface ApiResponse<T = any> {
|
||||
/** EN: Operation success status / VI: Trạng thái thành công của thao tác */
|
||||
success: boolean;
|
||||
/** EN: Response data payload / VI: Payload dữ liệu phản hồi */
|
||||
data?: T;
|
||||
/** EN: Success message / VI: Thông báo thành công */
|
||||
message?: string;
|
||||
/** EN: Error details (if failed) / VI: Chi tiết lỗi (nếu thất bại) */
|
||||
error?: ApiError;
|
||||
/** EN: Response timestamp / VI: Timestamp phản hồi */
|
||||
timestamp: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* EN: API error structure
|
||||
* VI: Cấu trúc lỗi API
|
||||
*/
|
||||
export interface ApiError {
|
||||
/** EN: Error code identifier / VI: Mã định danh lỗi */
|
||||
code: string;
|
||||
/** EN: Human-readable error message / VI: Thông báo lỗi dễ đọc */
|
||||
message: string;
|
||||
/** EN: Additional error details / VI: Chi tiết lỗi bổ sung */
|
||||
details?: Record<string, any>;
|
||||
}
|
||||
|
||||
/**
|
||||
* EN: Paginated API response interface
|
||||
* VI: Interface phản hồi API có phân trang
|
||||
*/
|
||||
export interface PaginatedResponse<T> extends ApiResponse<T[]> {
|
||||
/** EN: Pagination metadata / VI: Metadata phân trang */
|
||||
pagination: {
|
||||
/** EN: Current page number / VI: Số trang hiện tại */
|
||||
page: number;
|
||||
/** EN: Items per page / VI: Số items mỗi trang */
|
||||
limit: number;
|
||||
/** EN: Total number of items / VI: Tổng số items */
|
||||
total: number;
|
||||
/** EN: Total number of pages / VI: Tổng số trang */
|
||||
totalPages: number;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* EN: Query parameters for pagination and sorting
|
||||
* VI: Tham số query cho phân trang và sắp xếp
|
||||
*/
|
||||
export interface PaginationQuery {
|
||||
/** EN: Page number (1-based) / VI: Số trang (bắt đầu từ 1) */
|
||||
page?: number;
|
||||
/** EN: Items per page / VI: Số items mỗi trang */
|
||||
limit?: number;
|
||||
/** EN: Field to sort by / VI: Trường để sắp xếp */
|
||||
sortBy?: string;
|
||||
/** EN: Sort order / VI: Thứ tự sắp xếp */
|
||||
sortOrder?: 'asc' | 'desc';
|
||||
}
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
// EN: Re-export all user-related types
|
||||
// VI: Re-export tất cả types liên quan đến user
|
||||
export * from './user.types';
|
||||
// EN: Re-export all authentication-related types
|
||||
// VI: Re-export tất cả types liên quan đến authentication
|
||||
export * from './auth.types';
|
||||
// EN: Re-export all API-related types
|
||||
// VI: Re-export tất cả types liên quan đến API
|
||||
export * from './api.types';
|
||||
|
||||
@@ -2,13 +2,26 @@ import { Router } from 'express';
|
||||
import { AuthController } from './auth.controller';
|
||||
import { authenticate } from '../../middlewares/auth.middleware';
|
||||
|
||||
/**
|
||||
* EN: Create and configure authentication routes
|
||||
* VI: Tạo và cấu hình routes xác thực
|
||||
*
|
||||
* @returns Configured Express router for auth endpoints / Router Express đã cấu hình cho auth endpoints
|
||||
*/
|
||||
export const createAuthRouter = (): Router => {
|
||||
// EN: Create router instance
|
||||
// VI: Tạo instance router
|
||||
const router = Router();
|
||||
const authController = new AuthController();
|
||||
|
||||
// EN: Public authentication routes (no auth required)
|
||||
// VI: Routes xác thực công khai (không yêu cầu auth)
|
||||
router.post('/register', authController.register);
|
||||
router.post('/login', authController.login);
|
||||
router.post('/refresh', authController.refresh);
|
||||
|
||||
// EN: Protected routes (authentication required)
|
||||
// VI: Routes được bảo vệ (yêu cầu authentication)
|
||||
router.post('/logout', authenticate, authController.logout);
|
||||
router.put('/password', authenticate, authController.changePassword);
|
||||
|
||||
|
||||
@@ -4,13 +4,29 @@ import { updateUserDtoSchema } from './user.dto';
|
||||
import { AuthRequest } from '../../middlewares/auth.middleware';
|
||||
import { ApiResponse, PaginatedResponse, UserResponse } from '@goodgo/types';
|
||||
|
||||
/**
|
||||
* EN: User management controller handling user-related HTTP requests
|
||||
* VI: Controller quản lý người dùng xử lý các yêu cầu HTTP liên quan đến người dùng
|
||||
*/
|
||||
export class UserController {
|
||||
/** EN: User service instance / VI: Instance user service */
|
||||
private userService: UserService;
|
||||
|
||||
/**
|
||||
* EN: Initialize user controller with service dependency
|
||||
* VI: Khởi tạo user controller với dependency service
|
||||
*/
|
||||
constructor() {
|
||||
this.userService = new UserService();
|
||||
}
|
||||
|
||||
/**
|
||||
* EN: Get current authenticated user profile
|
||||
* VI: Lấy hồ sơ người dùng đã xác thực hiện tại
|
||||
*
|
||||
* @param req - Authenticated request with user info / Request đã xác thực với thông tin người dùng
|
||||
* @param res - Express response object / Đối tượng response của Express
|
||||
*/
|
||||
getMe = async (req: AuthRequest, res: Response): Promise<void> => {
|
||||
try {
|
||||
const user = await this.userService.getCurrentUser(req.user!.userId);
|
||||
@@ -34,8 +50,17 @@ export class UserController {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* EN: Get paginated list of users (admin endpoint)
|
||||
* VI: Lấy danh sách người dùng có phân trang (endpoint admin)
|
||||
*
|
||||
* @param req - Express request with query parameters / Request Express với query parameters
|
||||
* @param res - Express response object / Đối tượng response của Express
|
||||
*/
|
||||
getUsers = async (req: Request, res: Response): Promise<void> => {
|
||||
try {
|
||||
// EN: Parse pagination parameters from query
|
||||
// VI: Parse tham số phân trang từ query
|
||||
const page = parseInt(req.query.page as string) || 1;
|
||||
const limit = parseInt(req.query.limit as string) || 20;
|
||||
|
||||
@@ -54,13 +79,20 @@ export class UserController {
|
||||
success: false,
|
||||
error: {
|
||||
code: 'USER_002',
|
||||
message: error.message || 'Failed to fetch users',
|
||||
message: error.message || 'Failed to fetch users / Lấy danh sách người dùng thất bại',
|
||||
},
|
||||
timestamp: new Date().toISOString(),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* EN: Get user by ID (admin endpoint)
|
||||
* VI: Lấy người dùng theo ID (endpoint admin)
|
||||
*
|
||||
* @param req - Express request with user ID in params / Request Express với user ID trong params
|
||||
* @param res - Express response object / Đối tượng response của Express
|
||||
*/
|
||||
getUserById = async (req: Request, res: Response): Promise<void> => {
|
||||
try {
|
||||
const user = await this.userService.getUserById(req.params.id);
|
||||
@@ -84,15 +116,24 @@ export class UserController {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* EN: Update user information (admin endpoint)
|
||||
* VI: Cập nhật thông tin người dùng (endpoint admin)
|
||||
*
|
||||
* @param req - Express request with user ID in params and update data in body / Request Express với user ID trong params và dữ liệu cập nhật trong body
|
||||
* @param res - Express response object / Đối tượng response của Express
|
||||
*/
|
||||
updateUser = async (req: Request, res: Response): Promise<void> => {
|
||||
try {
|
||||
// EN: Validate update data using Zod schema
|
||||
// VI: Xác thực dữ liệu cập nhật sử dụng Zod schema
|
||||
const data = updateUserDtoSchema.parse(req.body);
|
||||
const user = await this.userService.updateUser(req.params.id, data);
|
||||
|
||||
const response: ApiResponse<UserResponse> = {
|
||||
success: true,
|
||||
data: user,
|
||||
message: 'User updated successfully',
|
||||
message: 'User updated successfully / Người dùng đã cập nhật thành công',
|
||||
timestamp: new Date().toISOString(),
|
||||
};
|
||||
|
||||
@@ -102,20 +143,29 @@ export class UserController {
|
||||
success: false,
|
||||
error: {
|
||||
code: 'USER_004',
|
||||
message: error.message || 'Failed to update user',
|
||||
message: error.message || 'Failed to update user / Cập nhật người dùng thất bại',
|
||||
},
|
||||
timestamp: new Date().toISOString(),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* EN: Delete user (admin endpoint)
|
||||
* VI: Xóa người dùng (endpoint admin)
|
||||
*
|
||||
* @param req - Express request with user ID in params / Request Express với user ID trong params
|
||||
* @param res - Express response object / Đối tượng response của Express
|
||||
*/
|
||||
deleteUser = async (req: Request, res: Response): Promise<void> => {
|
||||
try {
|
||||
// EN: Delete user through service
|
||||
// VI: Xóa người dùng thông qua service
|
||||
await this.userService.deleteUser(req.params.id);
|
||||
|
||||
const response: ApiResponse = {
|
||||
success: true,
|
||||
message: 'User deleted successfully',
|
||||
message: 'User deleted successfully / Người dùng đã xóa thành công',
|
||||
timestamp: new Date().toISOString(),
|
||||
};
|
||||
|
||||
@@ -125,7 +175,7 @@ export class UserController {
|
||||
success: false,
|
||||
error: {
|
||||
code: 'USER_005',
|
||||
message: error.message || 'Failed to delete user',
|
||||
message: error.message || 'Failed to delete user / Xóa người dùng thất bại',
|
||||
},
|
||||
timestamp: new Date().toISOString(),
|
||||
});
|
||||
|
||||
@@ -1,9 +1,17 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
/**
|
||||
* EN: Zod schema for user update validation
|
||||
* VI: Schema Zod để xác thực cập nhật người dùng
|
||||
*/
|
||||
export const updateUserDtoSchema = z.object({
|
||||
/** EN: Optional email update / VI: Cập nhật email tùy chọn */
|
||||
email: z.string().email().optional(),
|
||||
/** EN: Optional role update / VI: Cập nhật vai trò tùy chọn */
|
||||
role: z.enum(['USER', 'ADMIN', 'SUPER_ADMIN']).optional(),
|
||||
/** EN: Optional active status update / VI: Cập nhật trạng thái hoạt động tùy chọn */
|
||||
isActive: z.boolean().optional(),
|
||||
});
|
||||
|
||||
/** EN: TypeScript type inferred from update user schema / VI: Type TypeScript suy ra từ schema cập nhật người dùng */
|
||||
export type UpdateUserDto = z.infer<typeof updateUserDtoSchema>;
|
||||
|
||||
@@ -2,11 +2,24 @@ import { Router } from 'express';
|
||||
import { UserController } from './user.controller';
|
||||
import { authenticate, authorize } from '../../middlewares/auth.middleware';
|
||||
|
||||
/**
|
||||
* EN: Create and configure user management routes
|
||||
* VI: Tạo và cấu hình routes quản lý người dùng
|
||||
*
|
||||
* @returns Configured Express router for user endpoints / Router Express đã cấu hình cho user endpoints
|
||||
*/
|
||||
export const createUserRouter = (): Router => {
|
||||
// EN: Create router instance
|
||||
// VI: Tạo instance router
|
||||
const router = Router();
|
||||
const userController = new UserController();
|
||||
|
||||
// EN: Public route for authenticated user profile
|
||||
// VI: Route công khai cho hồ sơ người dùng đã xác thực
|
||||
router.get('/me', authenticate, userController.getMe);
|
||||
|
||||
// EN: Admin-only routes for user management
|
||||
// VI: Routes chỉ dành cho admin để quản lý người dùng
|
||||
router.get('/', authenticate, authorize('ADMIN', 'SUPER_ADMIN'), userController.getUsers);
|
||||
router.get('/:id', authenticate, authorize('ADMIN', 'SUPER_ADMIN'), userController.getUserById);
|
||||
router.put('/:id', authenticate, authorize('ADMIN', 'SUPER_ADMIN'), userController.updateUser);
|
||||
|
||||
@@ -3,7 +3,19 @@ import { logger } from '@goodgo/logger';
|
||||
import { UpdateUserDto } from './user.dto';
|
||||
import { UserResponse, Role as TypeRole } from '@goodgo/types';
|
||||
|
||||
/**
|
||||
* EN: User management service handling CRUD operations
|
||||
* VI: Service quản lý người dùng xử lý các thao tác CRUD
|
||||
*/
|
||||
export class UserService {
|
||||
/**
|
||||
* EN: Get current authenticated user information
|
||||
* VI: Lấy thông tin người dùng đã xác thực hiện tại
|
||||
*
|
||||
* @param userId - Unique user identifier / Mã định danh duy nhất người dùng
|
||||
* @returns User response data / Dữ liệu phản hồi người dùng
|
||||
* @throws Error if user not found / Lỗi nếu không tìm thấy người dùng
|
||||
*/
|
||||
async getCurrentUser(userId: string): Promise<UserResponse> {
|
||||
const user = await prisma.user.findUnique({
|
||||
where: { id: userId },
|
||||
@@ -23,13 +35,23 @@ export class UserService {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* EN: Get user by ID (admin function)
|
||||
* VI: Lấy người dùng theo ID (hàm admin)
|
||||
*
|
||||
* @param id - User identifier / Mã định danh người dùng
|
||||
* @returns User response data / Dữ liệu phản hồi người dùng
|
||||
* @throws Error if user not found / Lỗi nếu không tìm thấy người dùng
|
||||
*/
|
||||
async getUserById(id: string): Promise<UserResponse> {
|
||||
// EN: Find user in database
|
||||
// VI: Tìm người dùng trong database
|
||||
const user = await prisma.user.findUnique({
|
||||
where: { id },
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
throw new Error('User not found');
|
||||
throw new Error('User not found / Không tìm thấy người dùng');
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -42,6 +64,14 @@ export class UserService {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* EN: Get paginated list of users (admin function)
|
||||
* VI: Lấy danh sách người dùng có phân trang (hàm admin)
|
||||
*
|
||||
* @param page - Page number (default: 1) / Số trang (mặc định: 1)
|
||||
* @param limit - Items per page (default: 20) / Số items mỗi trang (mặc định: 20)
|
||||
* @returns Paginated user list / Danh sách người dùng có phân trang
|
||||
*/
|
||||
async getUsers(page: number = 1, limit: number = 20) {
|
||||
const skip = (page - 1) * limit;
|
||||
|
||||
@@ -72,7 +102,17 @@ export class UserService {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* EN: Update user information (admin function)
|
||||
* VI: Cập nhật thông tin người dùng (hàm admin)
|
||||
*
|
||||
* @param id - User identifier / Mã định danh người dùng
|
||||
* @param data - Update data / Dữ liệu cập nhật
|
||||
* @returns Updated user response / Phản hồi người dùng đã cập nhật
|
||||
*/
|
||||
async updateUser(id: string, data: UpdateUserDto): Promise<UserResponse> {
|
||||
// EN: Update user in database
|
||||
// VI: Cập nhật người dùng trong database
|
||||
const user = await prisma.user.update({
|
||||
where: { id },
|
||||
data: {
|
||||
@@ -82,7 +122,7 @@ export class UserService {
|
||||
},
|
||||
});
|
||||
|
||||
logger.info('User updated', { userId: id });
|
||||
logger.info('User updated / Người dùng đã cập nhật', { userId: id });
|
||||
|
||||
return {
|
||||
id: user.id,
|
||||
@@ -94,11 +134,19 @@ export class UserService {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* EN: Delete user (admin function)
|
||||
* VI: Xóa người dùng (hàm admin)
|
||||
*
|
||||
* @param id - User identifier to delete / Mã định danh người dùng cần xóa
|
||||
*/
|
||||
async deleteUser(id: string): Promise<void> {
|
||||
// EN: Delete user from database
|
||||
// VI: Xóa người dùng khỏi database
|
||||
await prisma.user.delete({
|
||||
where: { id },
|
||||
});
|
||||
|
||||
logger.info('User deleted', { userId: id });
|
||||
logger.info('User deleted / Người dùng đã xóa', { userId: id });
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user