Commit f3749432 authored by Phạm Quang Bảo's avatar Phạm Quang Bảo

feat(challenge_2): add api course method get and refactor folder

parent 3be0edb2
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
"scripts": { "scripts": {
"dev": "tsx watch ./src/index.ts", "dev": "tsx watch ./src/index.ts",
"test": "echo \"Error: no test specified\" && exit 1", "test": "echo \"Error: no test specified\" && exit 1",
"gen:db": "sequelize-auto -h localhost -d challenge_db -u postgres -x 123456 -p 2550 --dialect postgres --lang ts -o ./src/models --caseProp s --useDefine", "gen:db": "tsx src/scripts/database-gen.ts",
"gen:swagger": "tsx src/scripts/swagger-gen.ts" "gen:swagger": "tsx src/scripts/swagger-gen.ts"
}, },
"imports": { "imports": {
......
...@@ -29,10 +29,8 @@ export default (_express: Application) => { ...@@ -29,10 +29,8 @@ export default (_express: Application) => {
get: { get: {
handler: async (req, res) => { handler: async (req, res) => {
try { try {
// 1. Lấy các tham số từ Query String (được Swagger gửi lên)
const { filters, sort, page, pageSize } = req.query; const { filters, sort, page, pageSize } = req.query;
// 2. Truyền các tham số này vào hàm xử lý
const classes = await classesProvider.getAllClasses({ const classes = await classesProvider.getAllClasses({
filters: filters as string, filters: filters as string,
sort: sort as string, sort: sort as string,
......
...@@ -6,17 +6,43 @@ export default (_express: Application) => { ...@@ -6,17 +6,43 @@ export default (_express: Application) => {
const coursesProvider = new CoursesProvider(); const coursesProvider = new CoursesProvider();
return <Resource>{ return <Resource>{
/**
* @openapi
* /api/v1.0/courses:
* get:
* tags: [Courses]
* parameters:
* - $ref: '#/components/parameters/filters'
* - $ref: '#/components/parameters/sort'
* - $ref: '#/components/parameters/page'
* - $ref: '#/components/parameters/pageSize'
* responses:
* 200:
* description: Trả về danh sách khóa học thành công
* content:
* application/json:
* schema:
* type: array
* items:
* $ref: "#/components/schemas/Course"
*/
get: { get: {
handler: async (req, res) => { handler: async (req, res) => {
// #swagger.tags = ['Courses'] try {
// #swagger.summary = 'Lấy danh sách khóa học' const { filters, sort, page, pageSize } = req.query;
/* #swagger.responses[200] = {
description: 'Lấy dữ liệu thành công',
schema: { $ref: '#/definitions/Course' }
} */
const courses = await coursesProvider.getAllCourses(); const courses = await coursesProvider.getAllCourses({
return res.json(courses); 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);
} catch (error) {
console.error('Error getting courses:', error);
return res.status(500).json({ error: (error as Error).message });
}
} }
} }
}; };
......
...@@ -68,6 +68,49 @@ ...@@ -68,6 +68,49 @@
"example": "2b4f6f8e-19f6-4c5d-93c2-4d7a7c3d1e11" "example": "2b4f6f8e-19f6-4c5d-93c2-4d7a7c3d1e11"
} }
} }
},
"Course": {
"type": "object",
"example": {
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"name": "Khóa học 12A1",
"description": "Khóa học chuyên toán",
"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"
},
"name": {
"type": "string",
"example": "Khóa học 12A1"
},
"description": {
"type": "string",
"nullable": true,
"example": "Khóa học chuyên toán"
},
"status": {
"type": "string",
"nullable": true,
"example": "active"
},
"created_at": {
"type": "string",
"format": "date-time",
"nullable": true
},
"created_by": {
"type": "string",
"format": "uuid",
"nullable": true,
"example": "2b4f6f8e-19f6-4c5d-93c2-4d7a7c3d1e11"
}
}
} }
}, },
"parameters": { "parameters": {
...@@ -141,6 +184,42 @@ ...@@ -141,6 +184,42 @@
} }
} }
} }
},
"/api/v1.0/courses": {
"get": {
"tags": [
"Courses"
],
"parameters": [
{
"$ref": "#/components/parameters/filters"
},
{
"$ref": "#/components/parameters/sort"
},
{
"$ref": "#/components/parameters/page"
},
{
"$ref": "#/components/parameters/pageSize"
}
],
"responses": {
"200": {
"description": "Trả về danh sách khóa học thành công",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Course"
}
}
}
}
}
}
}
} }
}, },
"tags": [] "tags": []
......
...@@ -65,10 +65,19 @@ export class classes extends Model<classesAttributes, classesCreationAttributes> ...@@ -65,10 +65,19 @@ export class classes extends Model<classesAttributes, classesCreationAttributes>
type: DataTypes.TEXT, type: DataTypes.TEXT,
allowNull: true allowNull: true
}, },
created_at: {
type: DataTypes.DATE,
allowNull: true,
defaultValue: Sequelize.Sequelize.fn('now')
},
created_by: { created_by: {
type: DataTypes.UUID, type: DataTypes.UUID,
allowNull: true allowNull: true
}, },
updated_at: {
type: DataTypes.DATE,
allowNull: true
},
updated_by: { updated_by: {
type: DataTypes.UUID, type: DataTypes.UUID,
allowNull: true allowNull: true
...@@ -88,7 +97,7 @@ export class classes extends Model<classesAttributes, classesCreationAttributes> ...@@ -88,7 +97,7 @@ export class classes extends Model<classesAttributes, classesCreationAttributes>
}, { }, {
tableName: 'classes', tableName: 'classes',
schema: 'public', schema: 'public',
timestamps: true, timestamps: false,
indexes: [ indexes: [
{ {
name: "classes_pkey", name: "classes_pkey",
......
...@@ -53,6 +53,11 @@ export class courses extends Model<coursesAttributes, coursesCreationAttributes> ...@@ -53,6 +53,11 @@ export class courses extends Model<coursesAttributes, coursesCreationAttributes>
type: DataTypes.TEXT, type: DataTypes.TEXT,
allowNull: true allowNull: true
}, },
created_at: {
type: DataTypes.DATE,
allowNull: true,
defaultValue: Sequelize.Sequelize.fn('now')
},
created_by: { created_by: {
type: DataTypes.UUID, type: DataTypes.UUID,
allowNull: true allowNull: true
...@@ -64,7 +69,7 @@ export class courses extends Model<coursesAttributes, coursesCreationAttributes> ...@@ -64,7 +69,7 @@ export class courses extends Model<coursesAttributes, coursesCreationAttributes>
}, { }, {
tableName: 'courses', tableName: 'courses',
schema: 'public', schema: 'public',
timestamps: true, timestamps: false,
indexes: [ indexes: [
{ {
name: "courses_pkey", name: "courses_pkey",
......
...@@ -58,6 +58,11 @@ export class enrollments extends Model<enrollmentsAttributes, enrollmentsCreatio ...@@ -58,6 +58,11 @@ export class enrollments extends Model<enrollmentsAttributes, enrollmentsCreatio
key: 'id' key: 'id'
} }
}, },
created_at: {
type: DataTypes.DATE,
allowNull: true,
defaultValue: Sequelize.Sequelize.fn('now')
},
status: { status: {
type: DataTypes.STRING(50), type: DataTypes.STRING(50),
allowNull: true allowNull: true
...@@ -65,7 +70,7 @@ export class enrollments extends Model<enrollmentsAttributes, enrollmentsCreatio ...@@ -65,7 +70,7 @@ export class enrollments extends Model<enrollmentsAttributes, enrollmentsCreatio
}, { }, {
tableName: 'enrollments', tableName: 'enrollments',
schema: 'public', schema: 'public',
timestamps: true timestamps: false
}) as typeof enrollments; }) as typeof enrollments;
} }
} }
...@@ -51,6 +51,11 @@ export class roles extends Model<rolesAttributes, rolesCreationAttributes> imple ...@@ -51,6 +51,11 @@ export class roles extends Model<rolesAttributes, rolesCreationAttributes> imple
type: DataTypes.TEXT, type: DataTypes.TEXT,
allowNull: true allowNull: true
}, },
created_at: {
type: DataTypes.DATE,
allowNull: true,
defaultValue: Sequelize.Sequelize.fn('now')
},
created_by: { created_by: {
type: DataTypes.UUID, type: DataTypes.UUID,
allowNull: true allowNull: true
...@@ -58,7 +63,7 @@ export class roles extends Model<rolesAttributes, rolesCreationAttributes> imple ...@@ -58,7 +63,7 @@ export class roles extends Model<rolesAttributes, rolesCreationAttributes> imple
}, { }, {
tableName: 'roles', tableName: 'roles',
schema: 'public', schema: 'public',
timestamps: true, timestamps: false,
indexes: [ indexes: [
{ {
name: "roles_pkey", name: "roles_pkey",
......
import { Sequelize } from 'sequelize';
import { initModels } from '#/models/init-models.js';
const sequelize = new Sequelize('challenge_db', 'postgres', '123456', {
host: 'localhost',
port: 2550,
dialect: 'postgres',
logging: false,
define: {
underscored: true,
timestamps: false,
},
});
const models = initModels(sequelize);
export { sequelize, models };
\ No newline at end of file
...@@ -45,11 +45,16 @@ export class user_auth extends Model<user_authAttributes, user_authCreationAttri ...@@ -45,11 +45,16 @@ export class user_auth extends Model<user_authAttributes, user_authCreationAttri
password_hash: { password_hash: {
type: DataTypes.TEXT, type: DataTypes.TEXT,
allowNull: true allowNull: true
},
created_at: {
type: DataTypes.DATE,
allowNull: true,
defaultValue: Sequelize.Sequelize.fn('now')
} }
}, { }, {
tableName: 'user_auth', tableName: 'user_auth',
schema: 'public', schema: 'public',
timestamps: true, timestamps: false,
indexes: [ indexes: [
{ {
name: "user_auth_pkey", name: "user_auth_pkey",
......
...@@ -88,6 +88,11 @@ export class users extends Model<usersAttributes, usersCreationAttributes> imple ...@@ -88,6 +88,11 @@ export class users extends Model<usersAttributes, usersCreationAttributes> imple
type: DataTypes.TEXT, type: DataTypes.TEXT,
allowNull: true allowNull: true
}, },
created_at: {
type: DataTypes.DATE,
allowNull: true,
defaultValue: Sequelize.Sequelize.fn('now')
},
role_id: { role_id: {
type: DataTypes.UUID, type: DataTypes.UUID,
allowNull: true, allowNull: true,
...@@ -99,7 +104,7 @@ export class users extends Model<usersAttributes, usersCreationAttributes> imple ...@@ -99,7 +104,7 @@ export class users extends Model<usersAttributes, usersCreationAttributes> imple
}, { }, {
tableName: 'users', tableName: 'users',
schema: 'public', schema: 'public',
timestamps: true, timestamps: false,
indexes: [ indexes: [
{ {
name: "users_pkey", name: "users_pkey",
......
import { models } from '#scripts/database-gen.js'; import { models } from '#models/sequelize-config.js';
import { Op } from 'sequelize';
interface GetAllClassesParams {
filters?: string;
sort?: string;
page?: number;
pageSize?: number;
}
export class ClassesProvider { export class ClassesProvider {
async getAllClasses(params: { filters?: string, sort?: string, page?: number, pageSize?: number }) {
async getAllClasses(params: GetAllClassesParams) {
const { filters, sort, page = 1, pageSize = 10 } = params; const { filters, sort, page = 1, pageSize = 10 } = params;
const offset = (page - 1) * pageSize; const offset = (page - 1) * pageSize;
const where: any = {};
if (filters) {
where.name = { [Op.like]: `%${filters}%` };
}
return await models.classes.findAndCountAll({ return await models.classes.findAndCountAll({
where, // where
order: sort ? [['created_at', sort]] : [['created_at', 'DESC']], order: sort ? [['created_at', sort]] : [['created_at', 'DESC']],
limit: pageSize, limit: pageSize,
offset: offset offset: offset
......
import { models } from '#scripts/database-gen.js'; import { models } from '#models/sequelize-config.js';
interface GetAllCoursesParams {
filters?: string;
sort?: string;
page?: number;
pageSize?: number;
}
export class CoursesProvider { export class CoursesProvider {
async getAllCourses() { async getAllCourses(params: GetAllCoursesParams) {
try { const { filters, sort, page = 1, pageSize = 10 } = params;
const data = await models.courses.findAll({
attributes: ['id', 'name'],
order: [['id', 'DESC']]
});
return data; const offset = (page - 1) * pageSize;
} catch (error) {
console.error("Lỗi khi lấy danh sách courses:", error); return await models.courses.findAndCountAll({
throw error; // where
} order: sort ? [['created_at', sort]] : [['created_at', 'DESC']],
limit: pageSize,
offset: offset
});
} }
async getCourseById(courseId: number) { async getCourseById(courseId: number) {
......
import { Sequelize } from 'sequelize'; import SequelizeAuto from 'sequelize-auto';
import { initModels } from '#/models/init-models.js';
const sequelize = new Sequelize('challenge_db', 'postgres', '123456', { const auto = new SequelizeAuto('challenge_db', 'postgres', '123456', {
host: 'localhost', host: 'localhost',
port: 2550, port: 2550,
dialect: 'postgres', dialect: 'postgres',
logging: false, directory: './src/models',
define: { lang: 'ts',
underscored: true, // @ts-ignore
caseProp: 's',
singularize: false,
useDefine: true,
additional: {
timestamps: false, timestamps: false,
createdAt: 'created_at',
updatedAt: 'updated_at',
}, },
}); });
const models = initModels(sequelize); console.log('🔄 Đang tiến hành quét Database và sinh Model...');
export { sequelize, models }; auto.run()
\ No newline at end of file .then((data) => {
const tableCount = Object.keys(data.tables).length;
console.log(`✅ Thành công! Đã sinh ra cấu trúc cho ${tableCount} bảng.`);
})
.catch((err) => {
console.error('❌ Có lỗi xảy ra trong quá trình gen model:', err);
});
\ No newline at end of file
import path from 'node:path'; import path from 'node:path';
import classSchemas from './classes/schemas.js'; import classSchemas from './classes/schemas.js';
import courseSchemas from './courses/schemas.js';
import type { Options } from 'swagger-jsdoc'; import type { Options } from 'swagger-jsdoc';
...@@ -13,6 +14,7 @@ const swaggerOptions: Options = { ...@@ -13,6 +14,7 @@ const swaggerOptions: Options = {
components: { components: {
schemas: { schemas: {
...classSchemas, ...classSchemas,
...courseSchemas,
}, },
parameters: { parameters: {
filters: { filters: {
......
const courseSchemas = {
Course: {
type: 'object',
example: {
id: '3fa85f64-5717-4562-b3fc-2c963f66afa6',
name: 'Khóa học 12A1',
description: 'Khóa học chuyên toán',
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',
},
name: {
type: 'string',
example: 'Khóa học 12A1',
},
description: {
type: 'string',
nullable: true,
example: 'Khóa học chuyên toán',
},
status: {
type: 'string',
nullable: true,
example: 'active',
},
created_at: {
type: 'string',
format: 'date-time',
nullable: true,
},
created_by: {
type: 'string',
format: 'uuid',
nullable: true,
example: '2b4f6f8e-19f6-4c5d-93c2-4d7a7c3d1e11',
}
},
},
};
export default courseSchemas;
\ No newline at end of file
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