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 @@
"scripts": {
"dev": "tsx watch ./src/index.ts",
"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"
},
"imports": {
......
......@@ -29,10 +29,8 @@ export default (_express: Application) => {
get: {
handler: async (req, res) => {
try {
// 1. Lấy các tham số từ Query String (được Swagger gửi lên)
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({
filters: filters as string,
sort: sort as string,
......
......@@ -6,17 +6,43 @@ export default (_express: Application) => {
const coursesProvider = new CoursesProvider();
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: {
handler: async (req, res) => {
// #swagger.tags = ['Courses']
// #swagger.summary = 'Lấy danh sách khóa học'
/* #swagger.responses[200] = {
description: 'Lấy dữ liệu thành công',
schema: { $ref: '#/definitions/Course' }
} */
try {
const { filters, sort, page, pageSize } = req.query;
const courses = await coursesProvider.getAllCourses();
return res.json(courses);
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);
} catch (error) {
console.error('Error getting courses:', error);
return res.status(500).json({ error: (error as Error).message });
}
}
}
};
......
......@@ -68,6 +68,49 @@
"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": {
......@@ -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": []
......
......@@ -65,10 +65,19 @@ export class classes extends Model<classesAttributes, classesCreationAttributes>
type: DataTypes.TEXT,
allowNull: true
},
created_at: {
type: DataTypes.DATE,
allowNull: true,
defaultValue: Sequelize.Sequelize.fn('now')
},
created_by: {
type: DataTypes.UUID,
allowNull: true
},
updated_at: {
type: DataTypes.DATE,
allowNull: true
},
updated_by: {
type: DataTypes.UUID,
allowNull: true
......@@ -88,7 +97,7 @@ export class classes extends Model<classesAttributes, classesCreationAttributes>
}, {
tableName: 'classes',
schema: 'public',
timestamps: true,
timestamps: false,
indexes: [
{
name: "classes_pkey",
......
......@@ -53,6 +53,11 @@ export class courses extends Model<coursesAttributes, coursesCreationAttributes>
type: DataTypes.TEXT,
allowNull: true
},
created_at: {
type: DataTypes.DATE,
allowNull: true,
defaultValue: Sequelize.Sequelize.fn('now')
},
created_by: {
type: DataTypes.UUID,
allowNull: true
......@@ -64,7 +69,7 @@ export class courses extends Model<coursesAttributes, coursesCreationAttributes>
}, {
tableName: 'courses',
schema: 'public',
timestamps: true,
timestamps: false,
indexes: [
{
name: "courses_pkey",
......
......@@ -58,6 +58,11 @@ export class enrollments extends Model<enrollmentsAttributes, enrollmentsCreatio
key: 'id'
}
},
created_at: {
type: DataTypes.DATE,
allowNull: true,
defaultValue: Sequelize.Sequelize.fn('now')
},
status: {
type: DataTypes.STRING(50),
allowNull: true
......@@ -65,7 +70,7 @@ export class enrollments extends Model<enrollmentsAttributes, enrollmentsCreatio
}, {
tableName: 'enrollments',
schema: 'public',
timestamps: true
timestamps: false
}) as typeof enrollments;
}
}
......@@ -51,6 +51,11 @@ export class roles extends Model<rolesAttributes, rolesCreationAttributes> imple
type: DataTypes.TEXT,
allowNull: true
},
created_at: {
type: DataTypes.DATE,
allowNull: true,
defaultValue: Sequelize.Sequelize.fn('now')
},
created_by: {
type: DataTypes.UUID,
allowNull: true
......@@ -58,7 +63,7 @@ export class roles extends Model<rolesAttributes, rolesCreationAttributes> imple
}, {
tableName: 'roles',
schema: 'public',
timestamps: true,
timestamps: false,
indexes: [
{
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
password_hash: {
type: DataTypes.TEXT,
allowNull: true
},
created_at: {
type: DataTypes.DATE,
allowNull: true,
defaultValue: Sequelize.Sequelize.fn('now')
}
}, {
tableName: 'user_auth',
schema: 'public',
timestamps: true,
timestamps: false,
indexes: [
{
name: "user_auth_pkey",
......
......@@ -88,6 +88,11 @@ export class users extends Model<usersAttributes, usersCreationAttributes> imple
type: DataTypes.TEXT,
allowNull: true
},
created_at: {
type: DataTypes.DATE,
allowNull: true,
defaultValue: Sequelize.Sequelize.fn('now')
},
role_id: {
type: DataTypes.UUID,
allowNull: true,
......@@ -99,7 +104,7 @@ export class users extends Model<usersAttributes, usersCreationAttributes> imple
}, {
tableName: 'users',
schema: 'public',
timestamps: true,
timestamps: false,
indexes: [
{
name: "users_pkey",
......
import { models } from '#scripts/database-gen.js';
import { Op } from 'sequelize';
import { models } from '#models/sequelize-config.js';
interface GetAllClassesParams {
filters?: string;
sort?: string;
page?: number;
pageSize?: number;
}
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 offset = (page - 1) * pageSize;
const where: any = {};
if (filters) {
where.name = { [Op.like]: `%${filters}%` };
}
return await models.classes.findAndCountAll({
where,
// where
order: sort ? [['created_at', sort]] : [['created_at', 'DESC']],
limit: pageSize,
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 {
async getAllCourses() {
try {
const data = await models.courses.findAll({
attributes: ['id', 'name'],
order: [['id', 'DESC']]
});
async getAllCourses(params: GetAllCoursesParams) {
const { filters, sort, page = 1, pageSize = 10 } = params;
return data;
} catch (error) {
console.error("Lỗi khi lấy danh sách courses:", error);
throw error;
}
const offset = (page - 1) * pageSize;
return await models.courses.findAndCountAll({
// where
order: sort ? [['created_at', sort]] : [['created_at', 'DESC']],
limit: pageSize,
offset: offset
});
}
async getCourseById(courseId: number) {
......
import { Sequelize } from 'sequelize';
import { initModels } from '#/models/init-models.js';
import SequelizeAuto from 'sequelize-auto';
const sequelize = new Sequelize('challenge_db', 'postgres', '123456', {
const auto = new SequelizeAuto('challenge_db', 'postgres', '123456', {
host: 'localhost',
port: 2550,
dialect: 'postgres',
logging: false,
define: {
underscored: true,
directory: './src/models',
lang: 'ts',
// @ts-ignore
caseProp: 's',
singularize: false,
useDefine: true,
additional: {
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 };
\ No newline at end of file
auto.run()
.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 classSchemas from './classes/schemas.js';
import courseSchemas from './courses/schemas.js';
import type { Options } from 'swagger-jsdoc';
......@@ -13,6 +14,7 @@ const swaggerOptions: Options = {
components: {
schemas: {
...classSchemas,
...courseSchemas,
},
parameters: {
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