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
e657aea5
Commit
e657aea5
authored
May 19, 2026
by
Phạm Quang Bảo
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat(challenge_6): add api enrollment and fix auth
parent
59dd600b
Changes
13
Show whitespace changes
Inline
Side-by-side
Showing
13 changed files
with
527 additions
and
10 deletions
+527
-10
index.ts
code/src/controllers/api/v1.0/auth/logout/index.ts
+2
-2
index.ts
code/src/controllers/api/v1.0/auth/profile/index.ts
+2
-2
index.ts
code/src/controllers/api/v1.0/auth/send-otp/index.ts
+2
-2
index.ts
code/src/controllers/api/v1.0/auth/verify-otp/index.ts
+2
-2
index.ts
...ollers/api/v1.0/enrollments/all-student-in-class/index.ts
+44
-0
index.ts
code/src/controllers/api/v1.0/enrollments/enroll/index.ts
+50
-0
index.ts
code/src/controllers/api/v1.0/enrollments/unenroll/index.ts
+45
-0
swagger-output.json
code/src/docs/swagger/swagger-output.json
+195
-0
authentication.ts
code/src/middlewares/authentication.ts
+2
-2
authorization.ts
code/src/middlewares/authorization.ts
+21
-0
EnrollProvider.ts
code/src/providers/EnrollProvider.ts
+60
-0
config.ts
code/src/templates/swagger/config.ts
+2
-0
schema.ts
code/src/templates/swagger/enrollment/schema.ts
+100
-0
No files found.
code/src/controllers/api/v1.0/auth/logout/index.ts
View file @
e657aea5
import
{
authMiddleware
}
from
"#middlewares/authentication"
;
import
{
login
}
from
"#middlewares/authentication"
;
import
{
AuthService
}
from
"#services/authService"
;
import
{
Application
}
from
"express"
;
import
{
Resource
}
from
"express-automatic-routes"
;
...
...
@@ -24,7 +24,7 @@ export default (_express: Application) => {
* $ref: "#/components/schemas/logoutResponse"
*/
post
:
{
middleware
:
[
authMiddleware
],
middleware
:
[
login
],
handler
:
async
(
req
,
res
)
=>
{
try
{
...
...
code/src/controllers/api/v1.0/auth/profile/index.ts
View file @
e657aea5
import
{
authMiddleware
}
from
"#middlewares/authentication"
;
import
{
login
}
from
"#middlewares/authentication"
;
import
{
AuthService
}
from
"#services/authService"
;
import
{
Application
}
from
"express"
;
import
{
Resource
}
from
"express-automatic-routes"
;
...
...
@@ -24,7 +24,7 @@ export default (_express: Application) => {
* $ref: "#/components/schemas/Profile"
*/
get
:
{
middleware
:
[
authMiddleware
],
middleware
:
[
login
],
handler
:
async
(
req
,
res
)
=>
{
try
{
...
...
code/src/controllers/api/v1.0/auth/send-otp/index.ts
View file @
e657aea5
import
{
authMiddleware
}
from
"#middlewares/authentication"
;
import
{
login
}
from
"#middlewares/authentication"
;
import
{
AuthService
}
from
"#services/authService.js"
;
import
{
MailService
}
from
"#services/mailService.js"
;
import
{
Application
}
from
"express"
;
...
...
@@ -28,7 +28,7 @@ export default (_express: Application) => {
* $ref: "#/components/schemas/sendOtpResponse"
*/
post
:
{
middleware
:
[
authMiddleware
],
middleware
:
[
login
],
handler
:
async
(
req
,
res
)
=>
{
try
{
...
...
code/src/controllers/api/v1.0/auth/verify-otp/index.ts
View file @
e657aea5
import
{
authMiddleware
}
from
"#middlewares/authentication"
;
import
{
login
}
from
"#middlewares/authentication"
;
import
{
AuthService
}
from
"#services/authService.js"
;
import
{
Application
}
from
"express"
;
import
{
Resource
}
from
"express-automatic-routes"
;
...
...
@@ -32,7 +32,7 @@ export default (_express: Application) => {
* $ref: "#/components/schemas/verifyOtpResponse"
*/
post
:
{
middleware
:
[
authMiddleware
],
middleware
:
[
login
],
handler
:
async
(
req
,
res
)
=>
{
try
{
...
...
code/src/controllers/api/v1.0/enrollments/all-student-in-class/index.ts
0 → 100644
View file @
e657aea5
import
type
{
Application
}
from
"express"
;
import
type
{
Resource
}
from
"express-automatic-routes"
;
import
{
EnrollProvider
}
from
"#providers/EnrollProvider.js"
;
export
default
(
_express
:
Application
)
=>
{
const
enrollProvider
=
new
EnrollProvider
();
return
<
Resource
>
{
/**
* @openapi
* /api/v1.0/enrollments/all-student-in-class:
* post:
* tags: [enrollments]
* description: Xem tất cả học sinh có trong lớp học
* requestBody:
* required: true
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/AllStudentInClassInput"
* responses:
* 200:
* description: Trả về danh sách học sinh trong lớp học
* content:
* application/json:
* schema:
* type: array
* items:
* $ref: "#/components/schemas/AllStudentInClassOutput"
*/
post
:
{
handler
:
async
(
req
,
res
)
=>
{
try
{
const
students
=
await
enrollProvider
.
getAllStudentInClass
(
req
.
body
.
class_id
);
return
res
.
status
(
200
).
json
(
students
);
}
catch
(
error
)
{
console
.
error
(
'Error fetching students in class:'
,
error
);
return
res
.
status
(
400
).
json
({
error
:
(
error
as
Error
).
message
});
}
}
}
};
};
\ No newline at end of file
code/src/controllers/api/v1.0/enrollments/enroll/index.ts
0 → 100644
View file @
e657aea5
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"
;
export
default
(
_express
:
Application
)
=>
{
const
enrollProvider
=
new
EnrollProvider
();
return
<
Resource
>
{
/**
* @openapi
* /api/v1.0/enrollments/enroll:
* post:
* tags: [enrollments]
* security:
* - bearerAuth: []
* description: Tham gia khoá học
* requestBody:
* required: true
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/EnrollmentInput"
* responses:
* 201:
* description: Đăng ký khóa học thành công
* content:
* application/json:
* schema:
* type: array
* items:
* $ref: "#/components/schemas/Enrollment"
*/
post
:
{
middleware
:
[
login
,
authorize
(
"student"
)],
handler
:
async
(
req
,
res
)
=>
{
try
{
const
userId
=
(
req
as
any
).
user
.
id
;
const
enroll
=
await
enrollProvider
.
enroll
(
userId
,
req
.
body
.
class_id
);
return
res
.
status
(
201
).
json
(
enroll
);
}
catch
(
error
)
{
console
.
error
(
'Error creating enrollment:'
,
error
);
return
res
.
status
(
400
).
json
({
error
:
(
error
as
Error
).
message
});
}
}
}
};
};
\ No newline at end of file
code/src/controllers/api/v1.0/enrollments/unenroll/index.ts
0 → 100644
View file @
e657aea5
import
type
{
Application
}
from
"express"
;
import
type
{
Resource
}
from
"express-automatic-routes"
;
import
{
EnrollProvider
}
from
"#providers/EnrollProvider.js"
;
import
{
authorize
}
from
"#middlewares/authorization"
;
export
default
(
_express
:
Application
)
=>
{
const
enrollProvider
=
new
EnrollProvider
();
return
<
Resource
>
{
/**
* @openapi
* /api/v1.0/enrollments/unenroll:
* post:
* tags: [enrollments]
* description: Hủy đăng ký khóa học
* requestBody:
* required: true
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/UnenrollmentInput"
* responses:
* 201:
* description: Hủy đăng ký khóa học thành công
* content:
* application/json:
* schema:
* type: array
* items:
* $ref: "#/components/schemas/Unenrollment"
*/
post
:
{
handler
:
async
(
req
,
res
)
=>
{
try
{
const
enroll
=
await
enrollProvider
.
unEnroll
(
req
.
body
.
userId
,
req
.
body
.
classId
);
return
res
.
status
(
201
).
json
(
enroll
);
}
catch
(
error
)
{
console
.
error
(
'Error unenrolling:'
,
error
);
return
res
.
status
(
400
).
json
({
error
:
(
error
as
Error
).
message
});
}
}
}
};
};
\ No newline at end of file
code/src/docs/swagger/swagger-output.json
View file @
e657aea5
...
...
@@ -358,6 +358,97 @@
"type"
:
"string"
}
}
},
"EnrollmentInput"
:
{
"type"
:
"object"
,
"example"
:
{
"class_id"
:
"3fa85f64-5717-4562-b3fc-2c963f66afa6"
},
"properties"
:
{
"class_id"
:
{
"type"
:
"string"
,
"example"
:
"3fa85f64-5717-4562-b3fc-2c963f66afa6"
}
}
},
"UnenrollmentInput"
:
{
"type"
:
"object"
,
"example"
:
{
"userId"
:
"3fa85f64-5717-4562-b3fc-2c963f66afa6"
,
"classId"
:
"3fa85f64-5717-4562-b3fc-2c963f66afa6"
},
"properties"
:
{
"userId"
:
{
"type"
:
"string"
,
"example"
:
"3fa85f64-5717-4562-b3fc-2c963f66afa6"
},
"classId"
:
{
"type"
:
"string"
,
"example"
:
"3fa85f64-5717-4562-b3fc-2c963f66afa6"
}
}
},
"Unenrollment"
:
{
"type"
:
"object"
,
"example"
:
{
"message"
:
"Hủy đăng ký khóa học thành công"
}
},
"Enrollment"
:
{
"type"
:
"object"
,
"example"
:
{
"user_id"
:
"3fa85f64-5717-4562-b3fc-2c963f66afa6"
,
"class_id"
:
"3fa85f64-5717-4562-b3fc-2c963f66afa6"
,
"status"
:
"active"
},
"properties"
:
{
"user_id"
:
{
"type"
:
"string"
,
"example"
:
"3fa85f64-5717-4562-b3fc-2c963f66afa6"
},
"class_id"
:
{
"type"
:
"string"
,
"example"
:
"3fa85f64-5717-4562-b3fc-2c963f66afa6"
},
"status"
:
{
"type"
:
"string"
,
"example"
:
"active"
}
}
},
"AllStudentInClassInput"
:
{
"type"
:
"object"
,
"example"
:
{
"class_id"
:
"3fa85f64-5717-4562-b3fc-2c963f66afa6"
},
"properties"
:
{
"class_id"
:
{
"type"
:
"string"
,
"example"
:
"3fa85f64-5717-4562-b3fc-2c963f66afa6"
}
}
},
"AllStudentInClassOutput"
:
{
"type"
:
"object"
,
"example"
:
{
"id"
:
"3fa85f64-5717-4562-b3fc-2c963f66afa6"
,
"name"
:
"Nguyen Van A"
,
"email"
:
"nguyenvana@example.com"
},
"properties"
:
{
"id"
:
{
"type"
:
"string"
,
"example"
:
"3fa85f64-5717-4562-b3fc-2c963f66afa6"
},
"name"
:
{
"type"
:
"string"
,
"example"
:
"Nguyen Van A"
},
"email"
:
{
"type"
:
"string"
,
"example"
:
"nguyenvana@example.com"
}
}
}
},
"parameters"
:
{
...
...
@@ -913,6 +1004,110 @@
}
}
}
},
"/api/v1.0/enrollments/all-student-in-class"
:
{
"post"
:
{
"tags"
:
[
"enrollments"
],
"description"
:
"Xem tất cả học sinh có trong lớp học"
,
"requestBody"
:
{
"required"
:
true
,
"content"
:
{
"application/json"
:
{
"schema"
:
{
"$ref"
:
"#/components/schemas/AllStudentInClassInput"
}
}
}
},
"responses"
:
{
"200"
:
{
"description"
:
"Trả về danh sách học sinh trong lớp học"
,
"content"
:
{
"application/json"
:
{
"schema"
:
{
"type"
:
"array"
,
"items"
:
{
"$ref"
:
"#/components/schemas/AllStudentInClassOutput"
}
}
}
}
}
}
}
},
"/api/v1.0/enrollments/enroll"
:
{
"post"
:
{
"tags"
:
[
"enrollments"
],
"security"
:
[
{
"bearerAuth"
:
[]
}
],
"description"
:
"Tham gia khoá học"
,
"requestBody"
:
{
"required"
:
true
,
"content"
:
{
"application/json"
:
{
"schema"
:
{
"$ref"
:
"#/components/schemas/EnrollmentInput"
}
}
}
},
"responses"
:
{
"201"
:
{
"description"
:
"Đăng ký khóa học thành công"
,
"content"
:
{
"application/json"
:
{
"schema"
:
{
"type"
:
"array"
,
"items"
:
{
"$ref"
:
"#/components/schemas/Enrollment"
}
}
}
}
}
}
}
},
"/api/v1.0/enrollments/unenroll"
:
{
"post"
:
{
"tags"
:
[
"enrollments"
],
"description"
:
"Hủy đăng ký khóa học"
,
"requestBody"
:
{
"required"
:
true
,
"content"
:
{
"application/json"
:
{
"schema"
:
{
"$ref"
:
"#/components/schemas/UnenrollmentInput"
}
}
}
},
"responses"
:
{
"201"
:
{
"description"
:
"Hủy đăng ký khóa học thành công"
,
"content"
:
{
"application/json"
:
{
"schema"
:
{
"type"
:
"array"
,
"items"
:
{
"$ref"
:
"#/components/schemas/Unenrollment"
}
}
}
}
}
}
}
}
},
"tags"
:
[]
...
...
code/src/middlewares/authentication.ts
View file @
e657aea5
import
jwt
from
'jsonwebtoken'
;
import
{
Re
quest
,
Re
sponse
,
NextFunction
}
from
'express'
;
import
{
Response
,
NextFunction
}
from
'express'
;
//demo
const
JWT_SECRET
=
process
.
env
.
JWT_SECRET
||
''
;
export
const
authMiddleware
=
(
req
:
any
,
res
:
Response
,
next
:
NextFunction
)
=>
{
export
const
login
=
(
req
:
any
,
res
:
Response
,
next
:
NextFunction
)
=>
{
const
authHeader
=
req
.
headers
.
authorization
;
if
(
!
authHeader
||
!
authHeader
.
startsWith
(
'Bearer '
))
{
...
...
code/src/middlewares/authorization.ts
0 → 100644
View file @
e657aea5
import
jwt
from
'jsonwebtoken'
;
import
{
Request
,
Response
,
NextFunction
}
from
'express'
;
import
{
models
}
from
'#models/sequelize-config.js'
;
export
const
authorize
=
(...
allowedRoles
:
string
[])
=>
{
return
async
(
req
:
any
,
res
:
Response
,
next
:
NextFunction
)
=>
{
const
roleId
=
req
.
user
?.
role_id
||
[];
const
role
=
await
models
.
roles
.
findOne
({
where
:
{
id
:
roleId
}
});
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'
});
}
next
();
};
};
\ No newline at end of file
code/src/providers/EnrollProvider.ts
0 → 100644
View file @
e657aea5
import
{
models
}
from
"#models/sequelize-config.js"
;
export
class
EnrollProvider
{
async
enroll
(
userId
:
string
,
classId
:
string
)
{
const
checkEnrollment
=
await
models
.
enrollments
.
findOne
({
where
:
{
user_id
:
userId
,
class_id
:
classId
,
status
:
"active"
}
});
if
(
checkEnrollment
)
{
throw
new
Error
(
"User is already enrolled in this class"
);
}
else
{
const
enrollment
=
await
models
.
enrollments
.
create
(
{
user_id
:
userId
,
class_id
:
classId
,
status
:
"active"
}
);
return
enrollment
;
}
}
async
unEnroll
(
userId
:
string
,
classId
:
string
)
{
const
checkEnrollment
=
await
models
.
enrollments
.
findOne
({
where
:
{
user_id
:
userId
,
class_id
:
classId
,
status
:
"active"
}
});
if
(
!
checkEnrollment
)
{
throw
new
Error
(
"User is not enrolled in this class"
);
}
else
{
checkEnrollment
.
status
=
"inactive"
;
await
checkEnrollment
.
save
();
return
{
message
:
"Hủy đăng ký khóa học thành công"
};
}
}
async
getAllStudentInClass
(
classId
:
string
)
{
const
enrollments
=
await
models
.
enrollments
.
findAll
({
where
:
{
class_id
:
classId
,
status
:
"active"
},
include
:
[
{
model
:
models
.
users
,
as
:
"user"
,
attributes
:
[
"id"
,
"name"
,
"email"
]
}
]
});
const
users
=
enrollments
.
map
((
e
:
any
)
=>
e
.
user
)
.
filter
((
u
:
any
)
=>
u
!=
null
);
return
users
;
}
}
\ No newline at end of file
code/src/templates/swagger/config.ts
View file @
e657aea5
...
...
@@ -9,6 +9,7 @@ import authProfileSchemas from './authProfile/schema.js';
import
sendOTPSchemas
from
'./sendOTP/schema.js'
;
import
verifyOTPSchemas
from
'./verifyOTP/schema.js'
;
import
logoutSchemas
from
'./logout/schema.js'
;
import
enrollmentSchemas
from
'./enrollment/schema.js'
;
const
swaggerOptions
:
Options
=
{
definition
:
{
...
...
@@ -34,6 +35,7 @@ const swaggerOptions: Options = {
...
sendOTPSchemas
,
...
verifyOTPSchemas
,
...
logoutSchemas
,
...
enrollmentSchemas
,
},
parameters
:
{
filters
:
{
...
...
code/src/templates/swagger/enrollment/schema.ts
0 → 100644
View file @
e657aea5
const
enrollmentSchemas
=
{
EnrollmentInput
:
{
type
:
'object'
,
example
:
{
class_id
:
'3fa85f64-5717-4562-b3fc-2c963f66afa6'
,
},
properties
:
{
class_id
:
{
type
:
'string'
,
example
:
'3fa85f64-5717-4562-b3fc-2c963f66afa6'
,
},
},
},
UnenrollmentInput
:
{
type
:
'object'
,
example
:
{
userId
:
'3fa85f64-5717-4562-b3fc-2c963f66afa6'
,
classId
:
'3fa85f64-5717-4562-b3fc-2c963f66afa6'
,
},
properties
:
{
userId
:
{
type
:
'string'
,
example
:
'3fa85f64-5717-4562-b3fc-2c963f66afa6'
,
},
classId
:
{
type
:
'string'
,
example
:
'3fa85f64-5717-4562-b3fc-2c963f66afa6'
,
},
},
},
Unenrollment
:
{
type
:
'object'
,
example
:
{
message
:
'Hủy đăng ký khóa học thành công'
,
},
},
Enrollment
:
{
type
:
'object'
,
example
:
{
user_id
:
'3fa85f64-5717-4562-b3fc-2c963f66afa6'
,
class_id
:
'3fa85f64-5717-4562-b3fc-2c963f66afa6'
,
status
:
'active'
,
},
properties
:
{
user_id
:
{
type
:
'string'
,
example
:
'3fa85f64-5717-4562-b3fc-2c963f66afa6'
,
},
class_id
:
{
type
:
'string'
,
example
:
'3fa85f64-5717-4562-b3fc-2c963f66afa6'
,
},
status
:
{
type
:
'string'
,
example
:
'active'
,
},
},
},
AllStudentInClassInput
:
{
type
:
'object'
,
example
:
{
class_id
:
'3fa85f64-5717-4562-b3fc-2c963f66afa6'
,
},
properties
:
{
class_id
:
{
type
:
'string'
,
example
:
'3fa85f64-5717-4562-b3fc-2c963f66afa6'
,
},
},
},
AllStudentInClassOutput
:
{
type
:
'object'
,
example
:
{
id
:
'3fa85f64-5717-4562-b3fc-2c963f66afa6'
,
name
:
'Nguyen Van A'
,
email
:
'nguyenvana@example.com'
,
},
properties
:
{
id
:
{
type
:
'string'
,
example
:
'3fa85f64-5717-4562-b3fc-2c963f66afa6'
,
},
name
:
{
type
:
'string'
,
example
:
'Nguyen Van A'
,
},
email
:
{
type
:
'string'
,
example
:
'nguyenvana@example.com'
,
},
},
},
};
export
default
enrollmentSchemas
;
\ 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