Commit aed6385b authored by Nguyễn Thị Nguyệt Quế's avatar Nguyễn Thị Nguyệt Quế

Merge branch 'feat/challenge_7_filter_response_json' into 'develop'

feat(challenge_7): add filter, pagination and response Json

See merge request !7
parents fb9be9d0 11e8bbc6
import { Req, Res } from "#interface/IApi";
import { AuthService } from "#services/authService";
import { Application } from "express"
import { Resource } from "express-automatic-routes"
......@@ -29,14 +30,17 @@ export default (_express: Application) => {
* $ref: "#/components/schemas/LoginResponse"
*/
post: {
handler: async (req, res) => {
handler: async (req: Req, res: Res) => {
try {
const login = await authService.loginUser(req.body);
return res.status(200).json(login);
return res.sendOk({ data: login });
} catch (error) {
console.error('Error logging in user:', error);
return res.status(500).json({ error: (error as Error).message });
return res.sendError({
message: "Đăng nhập thất bại",
message_en: "Login failed",
status: 500
});
}
}
}
......
import { authenticate } from "#middlewares/authentication";
import { Req, Res } from "#interface/IApi";
import { authMiddleware } from "#middlewares/authentication";
import { AuthService } from "#services/authService";
import { Application } from "express";
import { Resource } from "express-automatic-routes";
......@@ -24,19 +25,21 @@ export default (_express: Application) => {
* $ref: "#/components/schemas/logoutResponse"
*/
post: {
middleware: [authenticate],
middleware: [authMiddleware],
handler: async (req, res) => {
handler: async (req: Req, res: Res) => {
try {
const userId = (req as any).user.id;
console.log('User ID to logout:', userId);
return res.status(200).json({
message: 'Đăng xuất thành công',
});
return res.sendOk({ data: null });
} catch (error) {
return res.status(500).json({ error: (error as Error).message });
return res.sendError({
message: "Đăng xuất thất bại",
message_en: "Logout failed",
status: 500
});
}
}
}
......
import { authenticate } from "#middlewares/authentication";
import { Req, Res } from "#interface/IApi";
import { authMiddleware } from "#middlewares/authentication";
import { AuthService } from "#services/authService";
import { Application } from "express";
import { Resource } from "express-automatic-routes";
......@@ -24,17 +25,28 @@ export default (_express: Application) => {
* $ref: "#/components/schemas/Profile"
*/
get: {
middleware: [authenticate],
middleware: [authMiddleware],
handler: async (req, res) => {
handler: async (req: Req, res: Res) => {
try {
const userId = (req as any).user.id;
const userId = req.user?.id;
const profile = await authService.profileUser(userId);
if (!userId) {
return res.sendError({
message: "Không tìm thấy thông tin người dùng",
message_en: "User information not found",
status: 401
});
}
return res.status(200).json(profile);
const profile = await authService.profileUser(userId);
return res.sendOk({ data: profile });
} catch (error) {
return res.status(500).json({ error: (error as Error).message });
return res.sendError({
message: "Lấy thông tin hồ sơ thất bại",
message_en: "Failed to fetch profile",
status: 500
});
}
}
}
......
import { Req, Res } from "#interface/IApi";
import { AuthService } from "#services/authService";
import { Application } from "express";
import { Resource } from "express-automatic-routes";
......@@ -29,23 +30,35 @@ export default (_express: Application) => {
* $ref: "#/components/schemas/Register"
*/
post: {
handler: async (req, res) => {
handler: async (req: Req, res: Res) => {
try {
const { name, email, password } = req.body;
if (!name || !email || !password) {
return res.status(400).json({ error: 'Vui lòng nhập đầy đủ thông tin' });
return res.sendError({
message: 'Vui lòng nhập đầy đủ thông tin',
message_en: 'Please provide all required fields',
status: 400
});
}
if (password.length < 6) {
return res.status(400).json({ error: 'Mật khẩu phải có ít nhất 6 ký tự' });
return res.sendError({
message: 'Mật khẩu phải có ít nhất 6 ký tự',
message_en: 'Password must be at least 6 characters long',
status: 400
});
}
const register = await authService.registerUser(req.body);
return res.status(201).json(register);
return res.sendOk({ data: register });
} catch (error) {
console.error('Error registering user:', error);
return res.status(500).json({ error: (error as Error).message });
return res.sendError({
message: 'Đăng ký người dùng thất bại',
message_en: 'User registration failed',
status: 500
});
}
}
}
......
import { authenticate } from "#middlewares/authentication";
import { Req, Res } from "#interface/IApi";
import { authMiddleware } from "#middlewares/authentication";
import { AuthService } from "#services/authService.js";
import { MailService } from "#services/mailService.js";
import { Application } from "express";
......@@ -28,12 +29,20 @@ export default (_express: Application) => {
* $ref: "#/components/schemas/sendOtpResponse"
*/
post: {
middleware: [authenticate],
middleware: [authMiddleware],
handler: async (req, res) => {
handler: async (req: Req, res: Res) => {
try {
const userId = (req as any).user.id;
const email = (req as any).user.email;
const userId = req.user?.id;
const email = req.user?.email;
if (!userId || !email) {
return res.sendError({
message: 'Thông tin người dùng không hợp lệ',
message_en: 'Invalid user information',
status: 400
});
}
// get otp
const otp = await authService.otpUser(userId);
......@@ -42,19 +51,23 @@ export default (_express: Application) => {
if (!mailResult || !mailResult.success) {
console.error('Gửi mail thất bại:', mailResult?.error);
return res.status(500).json({
email: email,
return res.sendError({
message: 'Không thể gửi email OTP. Vui lòng thử lại sau.',
error: mailResult?.error
message_en: 'Failed to send OTP email. Please try again later.',
status: 500
});
}
return res.status(200).json({
email: email,
return res.sendOk({
data: null,
message: 'OTP đã được gửi đến email của bạn'
});
} catch (error) {
return res.status(500).json({ error: (error as Error).message });
return res.sendError({
message: 'Đã xảy ra lỗi khi gửi OTP',
message_en: 'An error occurred while sending OTP',
status: 500
});
}
}
}
......
import { authenticate } from "#middlewares/authentication";
import { Req, Res } from "#interface/IApi";
import { authMiddleware } from "#middlewares/authentication";
import { AuthService } from "#services/authService.js";
import { Application } from "express";
import { Resource } from "express-automatic-routes";
......@@ -32,15 +33,28 @@ export default (_express: Application) => {
* $ref: "#/components/schemas/verifyOtpResponse"
*/
post: {
middleware: [authenticate],
middleware: [authMiddleware],
handler: async (req, res) => {
handler: async (req: Req, res: Res) => {
try {
const userId = (req as any).user.id;
const userId = req.user?.id;
if (!userId) {
return res.sendError({
message: 'Thông tin người dùng không hợp lệ',
message_en: 'Invalid user information',
status: 400
});
}
const verifyResult = await authService.verifyOtp(userId, req.body.otp);
res.status(201).json(verifyResult);
return res.sendOk({ data: verifyResult });
} catch (error) {
return res.status(500).json({ error: (error as Error).message });
return res.sendError({
message: 'Đã xảy ra lỗi khi xác minh OTP',
message_en: 'An error occurred while verifying OTP',
status: 500
});
}
}
}
......
import type { Application } from "express";
import type { Resource } from "express-automatic-routes";
import { ClassesProvider } from "#providers/ClassesProvider";
import { Req, Res } from "#interface/IApi";
import queryModifier from "#middlewares/request";
export default (_express: Application) => {
const classesProvider = new ClassesProvider();
......@@ -22,26 +24,23 @@ export default (_express: Application) => {
* content:
* application/json:
* schema:
* type: array
* items:
* $ref: "#/components/schemas/Class"
* $ref: "#/components/schemas/ClassListResponse"
*/
get: {
handler: async (req, res) => {
try {
const { filters, sort, page, pageSize } = req.query;
middleware: [queryModifier],
const classes = await classesProvider.getAllClasses({
filters: filters as string,
sort: sort as string,
page: page ? parseInt(page as string) : 1,
pageSize: pageSize ? parseInt(pageSize as string) : 10
});
handler: async (req: Req, res: Res) => {
try {
const classes = await classesProvider.getAllClasses(req.payload);
return res.json(classes);
return res.sendOk({ data: classes });
} catch (error) {
console.error('Error getting classes:', error);
return res.status(500).json({ error: (error as Error).message });
return res.sendError({
message: 'Đã xảy ra lỗi khi lấy danh sách lớp học',
message_en: 'An error occurred while fetching class list',
status: 500
});
}
}
},
......@@ -64,21 +63,25 @@ export default (_express: Application) => {
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Class"
* $ref: "#/components/schemas/ClassResponse"
* 400:
* description: Dữ liệu đầu vào không hợp lệ (Thiếu trường, sai định dạng)
* 500:
* description: Lỗi hệ thống phía Server
*/
post: {
handler: async (req, res) => {
handler: async (req: Req, res: Res) => {
try {
const newClass = await classesProvider.createClass(req.body);
return res.status(201).json(newClass);
return res.sendOk({ data: newClass });
} catch (error) {
console.error('Error creating class:', error);
return res.status(400).json({ error: (error as Error).message });
return res.sendError({
message: 'Đã xảy ra lỗi khi tạo lớp học mới',
message_en: 'An error occurred while creating the class',
status: 400
});
}
}
}
......
import type { Application } from "express";
import type { Resource } from "express-automatic-routes";
import { ClassesProvider } from "#providers/ClassesProvider.js";
import { Req, Res } from "#interface/IApi";
export default (_express: Application) => {
const classesProvider = new ClassesProvider();
......@@ -24,23 +25,29 @@ export default (_express: Application) => {
* content:
* application/json:
* schema:
* type: array
* items:
* $ref: "#/components/schemas/Class"
* $ref: "#/components/schemas/ClassResponse"
*/
get: {
handler: async (req, res) => {
handler: async (req: Req, res: Res) => {
const { id } = req.params;
if (!id || typeof id !== 'string') {
return res.status(400).json({ error: "ID không hợp lệ!" });
return res.sendError({
message: "ID không hợp lệ!",
message_en: "Invalid ID!",
status: 400
});
}
try {
const classData = await classesProvider.getClassById(id);
return res.json(classData);
return res.sendOk({ data: classData });
} catch (error) {
return res.status(404).json({ error: "Class not found" });
return res.sendError({
message: "Lớp học không tồn tại",
message_en: "Class not found",
status: 404
});
}
}
},
......@@ -70,23 +77,29 @@ export default (_express: Application) => {
* content:
* application/json:
* schema:
* type: array
* items:
* $ref: "#/components/schemas/Class"
* $ref: "#/components/schemas/ClassResponse"
*/
put: {
handler: async (req, res) => {
handler: async (req: Req, res: Res) => {
const { id } = req.params;
if (!id || typeof id !== 'string') {
return res.status(400).json({ error: "ID không hợp lệ!" });
return res.sendError({
message: "ID không hợp lệ!",
message_en: "Invalid ID!",
status: 400
});
}
try {
const classData = await classesProvider.updateClassById(id, req.body);
return res.json(classData);
return res.sendOk({ data: classData });
} catch (error) {
return res.status(404).json({ error: "Class not found" });
return res.sendError({
message: "Lớp học không tồn tại",
message_en: "Class not found",
status: 404
});
}
}
},
......@@ -110,18 +123,26 @@ export default (_express: Application) => {
* description: Xóa lớp học thành công
*/
delete: {
handler: async (req, res) => {
handler: async (req: Req, res: Res) => {
const { id } = req.params;
if (!id || typeof id !== 'string') {
return res.status(400).json({ error: "ID không hợp lệ!" });
return res.sendError({
message: "ID không hợp lệ!",
message_en: "Invalid ID!",
status: 400
});
}
try {
const classData = await classesProvider.deleteClassById(id);
return res.json(classData);
await classesProvider.deleteClassById(id);
return res.sendOk({ data: null });
} catch (error) {
return res.status(404).json({ error: "Class not found" });
return res.sendError({
message: "Lớp học không tồn tại",
message_en: "Class not found",
status: 404
});
}
}
}
......
import type { Application } from "express";
import type { Resource } from "express-automatic-routes";
import { CoursesProvider } from "#providers/CoursesProvider.js";
import { Req, Res } from "#interface/IApi";
export default (_express: Application) => {
const coursesProvider = new CoursesProvider();
......@@ -22,26 +23,21 @@ export default (_express: Application) => {
* content:
* application/json:
* schema:
* type: array
* items:
* $ref: "#/components/schemas/Course"
* $ref: "#/components/schemas/CourseListResponse"
*/
get: {
handler: async (req, res) => {
handler: async (req: Req, res: Res) => {
try {
const { filters, sort, page, pageSize } = req.query;
const courses = await coursesProvider.getAllCourses(req.payload);
const courses = await coursesProvider.getAllCourses({
filters: filters as string,
sort: sort as string,
page: page ? parseInt(page as string) : 1,
pageSize: pageSize ? parseInt(pageSize as string) : 10
});
return res.json(courses);
return res.sendOk({ data: courses });
} catch (error) {
console.error('Error getting courses:', error);
return res.status(500).json({ error: (error as Error).message });
return res.sendError({
message: "Lỗi khi lấy danh sách khóa học!",
message_en: "Error occurred while fetching courses!",
status: 500
});
}
}
},
......@@ -64,19 +60,21 @@ export default (_express: Application) => {
* content:
* application/json:
* schema:
* type: array
* items:
* $ref: "#/components/schemas/Course"
* $ref: "#/components/schemas/CourseResponse"
*/
post: {
handler: async (req, res) => {
handler: async (req: Req, res: Res) => {
try {
const newCourse = await coursesProvider.createCourse(req.body);
return res.status(201).json(newCourse);
return res.sendOk({ data: newCourse });
} catch (error) {
console.error('Error creating course:', error);
return res.status(400).json({ error: (error as Error).message });
return res.sendError({
message: "Lỗi khi tạo khóa học mới!",
message_en: "Error occurred while creating course!",
status: 400
});
}
}
}
......
import type { Application } from "express";
import type { Resource } from "express-automatic-routes";
import { CoursesProvider } from "#providers/CoursesProvider.js";
import { Req, Res } from "#interface/IApi";
export default (_express: Application) => {
const coursesProvider = new CoursesProvider();
......@@ -24,20 +25,22 @@ export default (_express: Application) => {
* content:
* application/json:
* schema:
* type: array
* items:
* $ref: "#/components/schemas/Course"
* $ref: "#/components/schemas/CourseResponse"
*/
get: {
handler: async (req, res) => {
handler: async (req: Req, res: Res) => {
const id = req.params.id;
if (!id || typeof id !== 'string') {
return res.status(400).json({ error: "ID không hợp lệ!" });
return res.sendError({
message: "ID không hợp lệ!",
message_en: "Invalid ID!",
status: 400
});
}
const course = await coursesProvider.getCourseById(id);
return res.json(course);
return res.sendOk({ data: course });
}
},
......@@ -66,23 +69,29 @@ export default (_express: Application) => {
* content:
* application/json:
* schema:
* type: array
* items:
* $ref: "#/components/schemas/Course"
* $ref: "#/components/schemas/CourseResponse"
*/
put: {
handler: async (req, res) => {
handler: async (req: Req, res: Res) => {
const { id } = req.params;
if (!id || typeof id !== 'string') {
return res.status(400).json({ error: "ID không hợp lệ!" });
return res.sendError({
message: "ID không hợp lệ!",
message_en: "Invalid ID!",
status: 400
});
}
try {
const updateCourse = await coursesProvider.updateCourse(id, req.body);
return res.json(updateCourse);
return res.sendOk({ data: updateCourse });
} catch (error) {
return res.status(500).json({ error: "Lỗi khi cập nhật khóa học!" });
return res.sendError({
message: "Lỗi khi cập nhật khóa học!",
message_en: "Error occurred while updating course!",
status: 500
});
}
}
},
......@@ -105,23 +114,29 @@ export default (_express: Application) => {
* content:
* application/json:
* schema:
* type: array
* items:
* $ref: "#/components/schemas/Course"
* $ref: "#/components/schemas/CourseResponse"
*/
delete: {
handler: async (req, res) => {
handler: async (req: Req, res: Res) => {
const { id } = req.params;
if (!id || typeof id !== 'string') {
return res.status(400).json({ error: "ID không hợp lệ!" });
return res.sendError({
message: "ID không hợp lệ!",
message_en: "Invalid ID!",
status: 400
});
}
try {
const deletedCourse = await coursesProvider.deleteCourse(id);
return res.json(deletedCourse);
return res.sendOk({ data: deletedCourse });
} catch (error) {
return res.status(500).json({ error: "Lỗi khi xóa khóa học!" });
return res.sendError({
message: "Lỗi khi xóa khóa học!",
message_en: "Error occurred while deleting course!",
status: 500
});
}
}
}
......
import type { Application } from "express";
import type { Resource } from "express-automatic-routes";
import { EnrollProvider } from "#providers/EnrollProvider.js";
import { Req, Res } from "#interface/IApi";
export default (_express: Application) => {
const enrollProvider = new EnrollProvider();
......@@ -29,14 +30,18 @@ export default (_express: Application) => {
* $ref: "#/components/schemas/AllStudentInClassOutput"
*/
post: {
handler: async (req, res) => {
handler: async (req: Req, res: Res) => {
try {
const students = await enrollProvider.getAllStudentInClass(req.body.class_id);
return res.status(200).json(students);
return res.sendOk({ data: students });
} catch (error) {
console.error('Error fetching students in class:', error);
return res.status(400).json({ error: (error as Error).message });
return res.sendError({
message: "Lỗi khi lấy danh sách học sinh trong lớp!",
message_en: "Error occurred while fetching students in class!",
status: 400
});
}
}
}
......
......@@ -2,7 +2,8 @@ import type { Application } from "express";
import type { Resource } from "express-automatic-routes";
import { EnrollProvider } from "#providers/EnrollProvider.js";
import { authorize } from "#middlewares/authorization";
import { login } from "#middlewares/authentication";
import { authMiddleware } from "#middlewares/authentication";
import { Req, Res } from "#interface/IApi";
export default (_express: Application) => {
const enrollProvider = new EnrollProvider();
......@@ -33,16 +34,29 @@ export default (_express: Application) => {
* $ref: "#/components/schemas/Enrollment"
*/
post: {
middleware: [login, authorize("student")],
handler: async (req, res) => {
middleware: [authMiddleware, authorize("student")],
handler: async (req: Req, res: Res) => {
try {
const userId = (req as any).user.id;
const userId = req.user?.id;
if (!userId) {
return res.sendError({
message: "Thông tin người dùng không hợp lệ!",
message_en: "Invalid user information!",
status: 400
});
}
const enroll = await enrollProvider.enroll(userId, req.body.class_id);
return res.status(201).json(enroll);
return res.sendOk({ data: enroll });
} catch (error) {
console.error('Error creating enrollment:', error);
return res.status(400).json({ error: (error as Error).message });
return res.sendError({
message: "Lỗi khi đăng ký khóa học!",
message_en: "Error occurred while enrolling in course!",
status: 400
});
}
}
}
......
......@@ -2,6 +2,7 @@ import type { Application } from "express";
import type { Resource } from "express-automatic-routes";
import { EnrollProvider } from "#providers/EnrollProvider.js";
import { authorize } from "#middlewares/authorization";
import { Req, Res } from "#interface/IApi";
export default (_express: Application) => {
const enrollProvider = new EnrollProvider();
......@@ -30,14 +31,18 @@ export default (_express: Application) => {
* $ref: "#/components/schemas/Unenrollment"
*/
post: {
handler: async (req, res) => {
handler: async (req: Req, res: Res) => {
try {
const enroll = await enrollProvider.unEnroll(req.body.userId, req.body.classId);
return res.status(201).json(enroll);
return res.sendOk({ data: enroll });
} catch (error) {
console.error('Error unenrolling:', error);
return res.status(400).json({ error: (error as Error).message });
return res.sendError({
message: "Lỗi khi hủy đăng ký khóa học!",
message_en: "Error occurred while unenrolling from course!",
status: 400
});
}
}
}
......
This diff is collapsed.
......@@ -4,6 +4,7 @@ import _autoroutes from 'express-automatic-routes';
import swaggerUi from 'swagger-ui-express';
import dotenv from 'dotenv';
import swaggerFile from '#/docs/swagger/swagger-output.json';
import response from '#middlewares/response';
dotenv.config();
......@@ -12,6 +13,8 @@ const port = 3000
app.use(express.json());
app.use(response as express.RequestHandler);
_autoroutes(app, {
dir: resolve(__dirname, './controllers'),
log: true
......
import { IncomingHttpHeaders } from "http";
import { Request, Response } from "express";
import type { WhereOptions } from "sequelize";
export interface JWTPayload {
id: string;
email: string;
username?: string;
roles?: string[];
role_id?: string;
permissions?: string[];
roleIds?: string[];
departmentIds?: string[];
type?: string;
iat?: number;
exp?: number;
}
export type OkParams = {
data: unknown;
message?: string;
message_en?: string;
status?: number;
};
export type ErrorParams = {
message: string;
message_en: string;
status: number;
error?: Error;
};
export type ResponseDTOParams = {
data?: unknown;
message?: string;
message_en?: string;
violations?: ViolationDTO[];
};
export interface Req extends Request {
user?: JWTPayload;
headers: IncomingHttpHeaders;
payload: payload;
}
export interface payload {
page: number;
pageSize: number;
sortBy: string;
sortOrder: string;
filters: WhereOptions;
}
export interface Res extends Response {
sendOk: (params: OkParams) => void;
sendError: (params: ErrorParams) => void;
}
export const ResponseDTO = ({ data, message, message_en, violations }: ResponseDTOParams) => ({
message: !message ? null : message,
message_en: !message_en ? null : message_en,
responseData: !data ? null : data,
status: violations === undefined || violations === null || violations.length == 0 ? "success" : "fail",
timeStamp: new Date().toISOString().replace(/T/, " ").replace(/\..+/, ""),
violations: !violations ? null : violations,
});
export class ViolationDTO {
private code: number;
private message: string;
private action: unknown;
constructor(code: number, message: string, action: unknown = null) {
this.code = code;
this.message = message;
this.action = action;
}
}
\ No newline at end of file
import jwt from 'jsonwebtoken';
import { Response, NextFunction } from 'express';
import { NextFunction } from 'express';
import { JWTPayload, Req, Res } from '#interface/IApi';
//demo
const JWT_SECRET = process.env.JWT_SECRET || '';
export const authenticate = (req: any, res: Response, next: NextFunction) => {
export const authMiddleware = (req: Req, res: Res, next: NextFunction) => {
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return res.status(401).json({ error: 'Không tìm thấy token' });
return res.sendError({
message: 'Authorization header không hợp lệ',
message_en: 'Invalid authorization header',
status: 401
});
}
const token = authHeader.split(' ')[1];
try {
const decoded = jwt.verify(token, JWT_SECRET) as any;
if (!token) {
return res.sendError({
message: 'Không tìm thấy token',
message_en: 'Token not found',
status: 401
});
}
try {
const decoded = jwt.verify(token, JWT_SECRET) as JWTPayload;
req.user = decoded;
next();
return next();
} catch (error) {
return res.status(403).json({ error: 'Token không hợp lệ hoặc đã hết hạn' });
return res.sendError({
message: 'Token không hợp lệ hoặc đã hết hạn',
message_en: 'Invalid or expired token',
status: 403
});
}
};
\ No newline at end of file
import jwt from 'jsonwebtoken';
import { Request, Response, NextFunction } from 'express';
import { NextFunction } from 'express';
import { models } from '#models/sequelize-config.js';
import { Req, Res } from '#interface/IApi';
export const authorize = (...allowedRoles: string[]) => {
return async (req: any, res: Response, next: NextFunction) => {
return async (req: Req, res: Res, next: NextFunction) => {
const roleId = req.user?.role_id || [];
const role = await models.roles.findOne({
......@@ -13,9 +13,13 @@ export const authorize = (...allowedRoles: string[]) => {
const hasPermission = allowedRoles.includes(role?.name || '');
if (!hasPermission) {
return res.status(403).json({ error: 'Bạn không có quyền truy cập tài nguyên này' });
return res.sendError({
message: 'Bạn không có quyền truy cập tài nguyên này',
message_en: 'You do not have permission to access this resource',
status: 403
});
}
next();
return next();
};
};
\ No newline at end of file
import { Req, Res } from "#interface/IApi";
import { NextFunction } from "express";
import { Op } from "sequelize";
export default function queryModifier(req: Req, _res: Res, next: NextFunction) {
const pageSize = parseInt(req.query.pageSize?.toString() || "10")
const page = parseInt(req.query.page?.toString() || "1")
const sortBy = req.query.sortBy?.toString() || 'created_at'
const sortOrder = req.query.sortOrder?.toString() || 'desc'
const filters = req.query.filters?.toString() || ""
const conditionCheckedChild: any[] = [];
if (filters) {
// Tách các điều kiện bằng dấu phẩy ","
const arrFilters = filters.split(",");
arrFilters.forEach((element) => {
// Mỗi phần tử sẽ có dạng: "status|=|published" hoặc "title|like|NodeJS"
const parts = element.split("|");
// Phải đủ 3 thành phần: [Tên cột, Toán tử, Giá trị]
if (parts.length === 3) {
const [field, operator, value] = parts as [string, string, string];
let condition: any = {};
// Dịch toán tử từ URL sang toán tử Sequelize tương ứng
switch (operator.toLowerCase()) {
case "=":
condition[field] = { [Op.eq]: value };
break;
case "like":
condition[field] = { [Op.iLike]: `%${value}%` };
break;
case ">":
condition[field] = { [Op.gt]: value };
break;
case "<":
condition[field] = { [Op.lt]: value };
break;
case ">=":
condition[field] = { [Op.gte]: value };
break;
case "<=":
condition[field] = { [Op.lte]: value };
break;
default:
condition[field] = { [Op.eq]: value };
}
conditionCheckedChild.push(condition);
}
});
}
// Kết hợp tất cả điều kiện với nhau bằng Op.and
const finalFilters = conditionCheckedChild.length > 0
? { [Op.and]: conditionCheckedChild }
: {};
req.payload = {
page,
pageSize,
sortBy,
sortOrder,
filters: finalFilters
};
next();
}
\ No newline at end of file
import { ErrorParams, OkParams, Req, Res, ResponseDTO, ViolationDTO } from "#interface/IApi";
import { NextFunction } from "express";
export default function (_req: Req, res: Res, next: NextFunction) {
res.sendOk = function ({
data,
message = 'Thành công',
message_en = 'Success',
status
}: OkParams) {
res.status(status ?? 200).json(
ResponseDTO({
data,
message,
message_en,
violations: []
})
);
};
res.sendError = function ({
message = 'Đã xảy ra lỗi',
message_en = 'An error occurred',
status = 500,
}: ErrorParams) {
res.status(status).json(
ResponseDTO({
message,
message_en,
violations: [new ViolationDTO(status, message, '')]
})
);
};
next();
}
\ No newline at end of file
import { payload } from '#interface/IApi';
import { models } from '#models/sequelize-config.js';
interface GetAllClassesParams {
filters?: string;
sort?: string;
page?: number;
pageSize?: number;
}
interface CreateClassInput {
name: string;
description?: string;
......@@ -14,18 +8,20 @@ interface CreateClassInput {
}
export class ClassesProvider {
async getAllClasses(params: GetAllClassesParams) {
const { filters, sort, page = 1, pageSize = 10 } = params;
const offset = (page - 1) * pageSize;
return await models.classes.findAndCountAll({
// where
order: sort ? [['created_at', sort]] : [['created_at', 'DESC']],
limit: pageSize,
offset: offset
async getAllClasses(params: payload) {
const { count, rows } = await models.classes.findAndCountAll({
where: params.filters,
order: params.sortBy ? [[params.sortBy, params.sortOrder]] : [['created_at', 'DESC']],
limit: params.pageSize,
offset: (params.page - 1) * params.pageSize,
raw: true
});
return {
count,
page: params.page,
pageSize: params.pageSize,
rows
};
}
async createClass(data: CreateClassInput) {
......
import { payload } from '#interface/IApi';
import { models } from '#models/sequelize-config.js';
interface GetAllCoursesParams {
filters?: string;
sort?: string;
page?: number;
pageSize?: number;
}
interface CreateCourseInput {
name: string;
description: string;
......@@ -14,17 +8,19 @@ interface CreateCourseInput {
}
export class CoursesProvider {
async getAllCourses(params: GetAllCoursesParams) {
const { filters, sort, page = 1, pageSize = 10 } = params;
const offset = (page - 1) * pageSize;
return await models.courses.findAndCountAll({
// where
order: sort ? [['created_at', sort]] : [['created_at', 'DESC']],
limit: pageSize,
offset: offset
async getAllCourses(params: payload) {
const { count, rows } = await models.courses.findAndCountAll({
where: params.filters,
order: params.sortBy ? [[params.sortBy, params.sortOrder]] : [['created_at', 'DESC']],
limit: params.pageSize,
offset: (params.page - 1) * params.pageSize
});
return {
count,
page: params.page,
pageSize: params.pageSize,
rows
};
}
async getCourseById(courseId: string) {
......
import { models } from "#models/sequelize-config.js";
export class EnrollProvider {
async enroll(userId: string, classId: string) {
const checkEnrollment = await models.enrollments.findOne({
......
......@@ -88,7 +88,7 @@ export class AuthService {
}
}
async profileUser(userId: number) {
async profileUser(userId: string) {
const user = await models.users.findByPk(userId);
if (!user) {
......
......@@ -2,35 +2,86 @@ const authProfileSchemas = {
Profile: {
type: 'object',
example: {
id: '123',
name: 'Pham Quang Bao',
email: 'phamquangbao@example.com',
phone: '+1234567890',
address: '123 Main St, Anytown, USA',
message: "string",
message_en: "string",
responseData: {
id: "string",
name: "string",
email: "string",
phone: "string",
address: "string",
status: "string",
},
status: "success | fail",
timeStamp: "2024-02-26 03:12:45",
violation: [
{}
]
},
properties: {
message: {
type: 'string',
description: 'Thông điệp trả về'
},
message_en: {
type: 'string',
description: 'Thông điệp trả về (bằng tiếng Anh)'
},
responseData: {
type: 'object',
description: 'Dữ liệu phản hồi chứa thông tin hồ sơ người dùng',
properties: {
id: {
type: 'uuid',
format: 'uuid',
example: '123',
type: 'string',
description: 'ID của người dùng'
},
name: {
type: 'string',
example: 'Pham Quang Bao',
description: 'Tên của người dùng'
},
email: {
type: 'string',
format: 'email',
example: 'phamquangbao@example.com',
description: 'Email của người dùng'
},
phone: {
type: 'string',
example: '+1234567890',
description: 'Số điện thoại của người dùng'
},
address: {
type: 'string',
example: '123 Main St, Anytown, USA',
description: 'Địa chỉ của người dùng'
},
status: {
type: 'string',
description: 'Trạng thái của người dùng'
}
}
},
status: {
type: 'string',
description: 'Trạng thái của phản hồi (success hoặc fail)'
},
timeStamp: {
type: 'string',
description: 'Thời gian phản hồi được tạo ra'
},
violation: {
type: 'array',
description: 'Danh sách các vi phạm (nếu có)',
items: {
type: 'object',
properties: {
field: {
type: 'string',
description: 'Tên trường vi phạm'
},
message: {
type: 'string',
description: 'Thông điệp lỗi liên quan đến trường vi phạm'
}
}
}
}
},
},
};
......
const classSchemas = {
// get schema
Class: {
type: 'object',
example: {
id: '3fa85f64-5717-4562-b3fc-2c963f66afa6',
name: 'Lớp 12A1',
description: 'Lớp chuyên toán',
course_id: '7c1a4d9c-3a2b-4c58-9df4-8e2c2d9a3c10',
status: 'active',
created_at: '2026-05-16T08:00:00.000Z',
created_by: '2b4f6f8e-19f6-4c5d-93c2-4d7a7c3d1e11',
updated_at: '2026-05-16T09:30:00.000Z',
updated_by: '2b4f6f8e-19f6-4c5d-93c2-4d7a7c3d1e11',
course_id: '7c1a4d9c-3a2b-4c58-9df4-8e2c2d9a3c10',
status: 'active',
},
properties: {
id: {
type: 'string',
format: 'uuid',
example: '3fa85f64-5717-4562-b3fc-2c963f66afa6',
id: { type: 'string', format: 'uuid' },
name: { type: 'string' },
description: { type: 'string', nullable: true },
course_id: { type: 'string', format: 'uuid', nullable: true },
status: { type: 'string', nullable: true },
created_at: { type: 'string', format: 'date-time', nullable: true },
created_by: { type: 'string', format: 'uuid', nullable: true },
updated_at: { type: 'string', format: 'date-time', nullable: true },
updated_by: { type: 'string', format: 'uuid', nullable: true },
},
name: {
type: 'string',
example: 'Lớp 12A1',
},
description: {
// get schema
ClassResponse: {
type: 'object',
properties: {
message: {
type: 'string',
nullable: true,
example: 'Lớp chuyên toán',
},
course_id: {
message_en: {
type: 'string',
format: 'uuid',
nullable: true,
example: '7c1a4d9c-3a2b-4c58-9df4-8e2c2d9a3c10',
},
responseData: {
$ref: '#/components/schemas/Class',
},
status: {
type: 'string',
nullable: true,
example: 'active',
example: 'success',
},
created_at: {
timeStamp: {
type: 'string',
format: 'date-time',
example: '2024-02-26 03:12:45',
},
violations: {
type: 'array',
items: {
type: 'object',
properties: {
code: {
type: 'number',
},
message: {
type: 'string',
},
action: {
nullable: true,
},
created_by: {
},
},
},
},
},
ClassListResponse: {
type: 'object',
properties: {
message: {
type: 'string',
format: 'uuid',
nullable: true,
example: '2b4f6f8e-19f6-4c5d-93c2-4d7a7c3d1e11',
},
updated_at: {
message_en: {
type: 'string',
format: 'date-time',
nullable: true,
},
updated_by: {
responseData: {
type: 'object',
properties: {
count: {
type: 'integer',
},
page: {
type: 'integer',
},
pageSize: {
type: 'integer',
},
rows: {
type: 'array',
items: {
$ref: '#/components/schemas/Class',
},
},
},
},
status: {
type: 'string',
format: 'uuid',
example: 'success',
},
timeStamp: {
type: 'string',
example: '2024-02-26 03:12:45',
},
violations: {
type: 'array',
items: {
type: 'object',
properties: {
code: {
type: 'number',
},
message: {
type: 'string',
},
action: {
nullable: true,
example: '2b4f6f8e-19f6-4c5d-93c2-4d7a7c3d1e11',
},
},
},
},
},
},
......
......@@ -5,41 +5,117 @@ const courseSchemas = {
id: '3fa85f64-5717-4562-b3fc-2c963f66afa6',
name: 'Khóa học 12A1',
description: 'Khóa học chuyên toán',
status: 'active',
created_at: '2026-05-16T08:00:00.000Z',
created_by: '2b4f6f8e-19f6-4c5d-93c2-4d7a7c3d1e11',
status: 'active',
},
properties: {
id: {
type: 'string',
format: 'uuid',
example: '3fa85f64-5717-4562-b3fc-2c963f66afa6',
id: { type: 'string', format: 'uuid' },
name: { type: 'string' },
description: { type: 'string', nullable: true },
status: { type: 'string', nullable: true },
created_at: { type: 'string', format: 'date-time', nullable: true },
created_by: { type: 'string', format: 'uuid', nullable: true },
},
name: {
},
CourseResponse: {
type: 'object',
properties: {
message: {
type: 'string',
example: 'Khóa học 12A1',
nullable: true,
},
description: {
message_en: {
type: 'string',
nullable: true,
example: 'Khóa học chuyên toán',
},
responseData: {
$ref: '#/components/schemas/Course',
},
status: {
type: 'string',
example: 'success',
},
timeStamp: {
type: 'string',
example: '2024-02-26 03:12:45',
},
violations: {
type: 'array',
items: {
type: 'object',
properties: {
code: {
type: 'number',
},
message: {
type: 'string',
},
action: {
nullable: true,
example: 'active',
},
created_at: {
},
},
},
},
},
CourseListResponse: {
type: 'object',
properties: {
message: {
type: 'string',
format: 'date-time',
nullable: true,
},
created_by: {
message_en: {
type: 'string',
format: 'uuid',
nullable: true,
example: '2b4f6f8e-19f6-4c5d-93c2-4d7a7c3d1e11',
}
},
responseData: {
type: 'object',
properties: {
count: {
type: 'integer',
},
page: {
type: 'integer',
},
pageSize: {
type: 'integer',
},
rows: {
type: 'array',
items: {
$ref: '#/components/schemas/Course',
},
},
},
},
status: {
type: 'string',
example: 'success',
},
timeStamp: {
type: 'string',
example: '2024-02-26 03:12:45',
},
violations: {
type: 'array',
items: {
type: 'object',
properties: {
code: {
type: 'number',
},
message: {
type: 'string',
},
action: {
nullable: true,
},
},
},
},
},
},
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment