Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
B
BACKEND CHALLENGES
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Phạm Quang Bảo
BACKEND CHALLENGES
Commits
f3749432
Commit
f3749432
authored
May 17, 2026
by
Phạm Quang Bảo
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat(challenge_2): add api course method get and refactor folder
parent
3be0edb2
Changes
16
Hide whitespace changes
Inline
Side-by-side
Showing
16 changed files
with
268 additions
and
48 deletions
+268
-48
package.json
code/package.json
+1
-1
index.ts
code/src/controllers/api/v1.0/classes/index.ts
+0
-2
index.ts
code/src/controllers/api/v1.0/courses/index.ts
+34
-8
swagger-output.json
code/src/docs/swagger/swagger-output.json
+79
-0
classes.ts
code/src/models/classes.ts
+10
-1
courses.ts
code/src/models/courses.ts
+6
-1
enrollments.ts
code/src/models/enrollments.ts
+6
-1
roles.ts
code/src/models/roles.ts
+6
-1
sequelize-config.ts
code/src/models/sequelize-config.ts
+17
-0
user_auth.ts
code/src/models/user_auth.ts
+6
-1
users.ts
code/src/models/users.ts
+6
-1
ClassesProvider.ts
code/src/providers/ClassesProvider.ts
+11
-9
CoursesProvider.ts
code/src/providers/CoursesProvider.ts
+17
-12
database-gen.ts
code/src/scripts/database-gen.ts
+20
-10
config.ts
code/src/templates/swagger/config.ts
+2
-0
schemas.ts
code/src/templates/swagger/courses/schemas.ts
+47
-0
No files found.
code/package.json
View file @
f3749432
...
@@ -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"
:
{
...
...
code/src/controllers/api/v1.0/classes/index.ts
View file @
f3749432
...
@@ -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
,
...
...
code/src/controllers/api/v1.0/courses/index.ts
View file @
f3749432
...
@@ -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
});
}
}
}
}
}
};
};
...
...
code/src/docs/swagger/swagger-output.json
View file @
f3749432
...
@@ -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"
:
[]
...
...
code/src/models/classes.ts
View file @
f3749432
...
@@ -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
:
tru
e
,
timestamps
:
fals
e
,
indexes
:
[
indexes
:
[
{
{
name
:
"classes_pkey"
,
name
:
"classes_pkey"
,
...
...
code/src/models/courses.ts
View file @
f3749432
...
@@ -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
:
tru
e
,
timestamps
:
fals
e
,
indexes
:
[
indexes
:
[
{
{
name
:
"courses_pkey"
,
name
:
"courses_pkey"
,
...
...
code/src/models/enrollments.ts
View file @
f3749432
...
@@ -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
:
tru
e
timestamps
:
fals
e
})
as
typeof
enrollments
;
})
as
typeof
enrollments
;
}
}
}
}
code/src/models/roles.ts
View file @
f3749432
...
@@ -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
:
tru
e
,
timestamps
:
fals
e
,
indexes
:
[
indexes
:
[
{
{
name
:
"roles_pkey"
,
name
:
"roles_pkey"
,
...
...
code/src/models/sequelize-config.ts
0 → 100644
View file @
f3749432
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
code/src/models/user_auth.ts
View file @
f3749432
...
@@ -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
:
tru
e
,
timestamps
:
fals
e
,
indexes
:
[
indexes
:
[
{
{
name
:
"user_auth_pkey"
,
name
:
"user_auth_pkey"
,
...
...
code/src/models/users.ts
View file @
f3749432
...
@@ -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
:
tru
e
,
timestamps
:
fals
e
,
indexes
:
[
indexes
:
[
{
{
name
:
"users_pkey"
,
name
:
"users_pkey"
,
...
...
code/src/providers/ClassesProvider.ts
View file @
f3749432
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
...
...
code/src/providers/CoursesProvider.ts
View file @
f3749432
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
)
{
...
...
code/src/scripts/database-gen.ts
View file @
f3749432
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'
,
},
},
});
});
cons
t
models
=
initModels
(
sequelize
);
cons
ole
.
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
code/src/templates/swagger/config.ts
View file @
f3749432
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
:
{
...
...
code/src/templates/swagger/courses/schemas.ts
0 → 100644
View file @
f3749432
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
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment