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
96ed263f
Commit
96ed263f
authored
May 18, 2026
by
Phạm Quang Bảo
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat(challenge_3): add register, login, get profile with json web token (JWT)
parent
d2554426
Changes
14
Show whitespace changes
Inline
Side-by-side
Showing
14 changed files
with
757 additions
and
0 deletions
+757
-0
package.json
code/package.json
+4
-0
pnpm-lock.yaml
code/pnpm-lock.yaml
+134
-0
index.ts
code/src/controllers/api/v1.0/auth/login/index.ts
+44
-0
index.ts
code/src/controllers/api/v1.0/auth/profile/index.ts
+42
-0
index.ts
code/src/controllers/api/v1.0/auth/register/index.ts
+53
-0
swagger-output.json
code/src/docs/swagger/swagger-output.json
+209
-0
authorization.ts
code/src/middlewares/authorization.ts
+24
-0
LoginProvider.ts
code/src/providers/LoginProvider.ts
+47
-0
ProfileProvider.ts
code/src/providers/ProfileProvider.ts
+14
-0
RegisterProvider.ts
code/src/providers/RegisterProvider.ts
+48
-0
schema.ts
code/src/templates/swagger/authProfile/schema.ts
+38
-0
config.ts
code/src/templates/swagger/config.ts
+13
-0
schemas.ts
code/src/templates/swagger/login/schemas.ts
+35
-0
schemas.ts
code/src/templates/swagger/register/schemas.ts
+52
-0
No files found.
code/package.json
View file @
96ed263f
...
@@ -19,8 +19,10 @@
...
@@ -19,8 +19,10 @@
"#docs/*"
:
"./src/docs/*"
"#docs/*"
:
"./src/docs/*"
},
},
"dependencies"
:
{
"dependencies"
:
{
"bcrypt"
:
"^6.0.0"
,
"express"
:
"^5.2.1"
,
"express"
:
"^5.2.1"
,
"express-automatic-routes"
:
"^1.1.0"
,
"express-automatic-routes"
:
"^1.1.0"
,
"jsonwebtoken"
:
"^9.0.3"
,
"module-alias"
:
"^2.3.4"
,
"module-alias"
:
"^2.3.4"
,
"pg"
:
"^8.20.0"
,
"pg"
:
"^8.20.0"
,
"pg-hstore"
:
"^2.3.4"
,
"pg-hstore"
:
"^2.3.4"
,
...
@@ -30,7 +32,9 @@
...
@@ -30,7 +32,9 @@
"swagger-ui-express"
:
"^5.0.1"
"swagger-ui-express"
:
"^5.0.1"
},
},
"devDependencies"
:
{
"devDependencies"
:
{
"@types/bcrypt"
:
"^6.0.0"
,
"@types/express"
:
"^5.0.6"
,
"@types/express"
:
"^5.0.6"
,
"@types/jsonwebtoken"
:
"^9.0.10"
,
"@types/node"
:
"^25.7.0"
,
"@types/node"
:
"^25.7.0"
,
"@types/swagger-jsdoc"
:
"^6.0.4"
,
"@types/swagger-jsdoc"
:
"^6.0.4"
,
"@types/swagger-ui-express"
:
"^4.1.8"
,
"@types/swagger-ui-express"
:
"^4.1.8"
,
...
...
code/pnpm-lock.yaml
View file @
96ed263f
...
@@ -8,12 +8,18 @@ importers:
...
@@ -8,12 +8,18 @@ importers:
.
:
.
:
dependencies
:
dependencies
:
bcrypt
:
specifier
:
^6.0.0
version
:
6.0.0
express
:
express
:
specifier
:
^5.2.1
specifier
:
^5.2.1
version
:
5.2.1
version
:
5.2.1
express-automatic-routes
:
express-automatic-routes
:
specifier
:
^1.1.0
specifier
:
^1.1.0
version
:
1.1.0
version
:
1.1.0
jsonwebtoken
:
specifier
:
^9.0.3
version
:
9.0.3
module-alias
:
module-alias
:
specifier
:
^2.3.4
specifier
:
^2.3.4
version
:
2.3.4
version
:
2.3.4
...
@@ -36,9 +42,15 @@ importers:
...
@@ -36,9 +42,15 @@ importers:
specifier
:
^5.0.1
specifier
:
^5.0.1
version
:
5.0.1(express@5.2.1)
version
:
5.0.1(express@5.2.1)
devDependencies
:
devDependencies
:
'
@types/bcrypt'
:
specifier
:
^6.0.0
version
:
6.0.0
'
@types/express'
:
'
@types/express'
:
specifier
:
^5.0.6
specifier
:
^5.0.6
version
:
5.0.6
version
:
5.0.6
'
@types/jsonwebtoken'
:
specifier
:
^9.0.10
version
:
9.0.10
'
@types/node'
:
'
@types/node'
:
specifier
:
^25.7.0
specifier
:
^25.7.0
version
:
25.7.0
version
:
25.7.0
...
@@ -263,6 +275,9 @@ packages:
...
@@ -263,6 +275,9 @@ packages:
'
@tsconfig/node16@1.0.4'
:
'
@tsconfig/node16@1.0.4'
:
resolution
:
{
integrity
:
sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==
}
resolution
:
{
integrity
:
sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==
}
'
@types/bcrypt@6.0.0'
:
resolution
:
{
integrity
:
sha512-/oJGukuH3D2+D+3H4JWLaAsJ/ji86dhRidzZ/Od7H/i8g+aCmvkeCc6Ni/f9uxGLSQVCRZkX2/lqEFG2BvWtlQ==
}
'
@types/body-parser@1.19.6'
:
'
@types/body-parser@1.19.6'
:
resolution
:
{
integrity
:
sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==
}
resolution
:
{
integrity
:
sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==
}
...
@@ -284,6 +299,9 @@ packages:
...
@@ -284,6 +299,9 @@ packages:
'
@types/json-schema@7.0.15'
:
'
@types/json-schema@7.0.15'
:
resolution
:
{
integrity
:
sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==
}
resolution
:
{
integrity
:
sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==
}
'
@types/jsonwebtoken@9.0.10'
:
resolution
:
{
integrity
:
sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA==
}
'
@types/ms@2.1.0'
:
'
@types/ms@2.1.0'
:
resolution
:
{
integrity
:
sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==
}
resolution
:
{
integrity
:
sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==
}
...
@@ -341,6 +359,10 @@ packages:
...
@@ -341,6 +359,10 @@ packages:
balanced-match@1.0.2
:
balanced-match@1.0.2
:
resolution
:
{
integrity
:
sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==
}
resolution
:
{
integrity
:
sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==
}
bcrypt@6.0.0
:
resolution
:
{
integrity
:
sha512-cU8v/EGSrnH+HnxV2z0J7/blxH8gq7Xh2JFT6Aroax7UohdmiJJlxApMxtKfuI7z68NvvVcmR78k2LbT6efhRg==
}
engines
:
{
node
:
'
>=
18'
}
body-parser@2.2.2
:
body-parser@2.2.2
:
resolution
:
{
integrity
:
sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==
}
resolution
:
{
integrity
:
sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==
}
engines
:
{
node
:
'
>=18'
}
engines
:
{
node
:
'
>=18'
}
...
@@ -348,6 +370,9 @@ packages:
...
@@ -348,6 +370,9 @@ packages:
brace-expansion@1.1.14
:
brace-expansion@1.1.14
:
resolution
:
{
integrity
:
sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==
}
resolution
:
{
integrity
:
sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==
}
buffer-equal-constant-time@1.0.1
:
resolution
:
{
integrity
:
sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==
}
bytes@3.1.2
:
bytes@3.1.2
:
resolution
:
{
integrity
:
sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==
}
resolution
:
{
integrity
:
sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==
}
engines
:
{
node
:
'
>=
0.8'
}
engines
:
{
node
:
'
>=
0.8'
}
...
@@ -432,6 +457,9 @@ packages:
...
@@ -432,6 +457,9 @@ packages:
resolution
:
{
integrity
:
sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==
}
resolution
:
{
integrity
:
sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==
}
engines
:
{
node
:
'
>=
0.4'
}
engines
:
{
node
:
'
>=
0.4'
}
ecdsa-sig-formatter@1.0.11
:
resolution
:
{
integrity
:
sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==
}
ee-first@1.1.1
:
ee-first@1.1.1
:
resolution
:
{
integrity
:
sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==
}
resolution
:
{
integrity
:
sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==
}
...
@@ -569,17 +597,48 @@ packages:
...
@@ -569,17 +597,48 @@ packages:
resolution
:
{
integrity
:
sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==
}
resolution
:
{
integrity
:
sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==
}
hasBin
:
true
hasBin
:
true
jsonwebtoken@9.0.3
:
resolution
:
{
integrity
:
sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==
}
engines
:
{
node
:
'
>=12'
,
npm
:
'
>=6'
}
jwa@2.0.1
:
resolution
:
{
integrity
:
sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==
}
jws@4.0.1
:
resolution
:
{
integrity
:
sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==
}
lodash.get@4.4.2
:
lodash.get@4.4.2
:
resolution
:
{
integrity
:
sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==
}
resolution
:
{
integrity
:
sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==
}
deprecated
:
This package is deprecated. Use the optional chaining (?.) operator instead.
deprecated
:
This package is deprecated. Use the optional chaining (?.) operator instead.
lodash.includes@4.3.0
:
resolution
:
{
integrity
:
sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==
}
lodash.isboolean@3.0.3
:
resolution
:
{
integrity
:
sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==
}
lodash.isequal@4.5.0
:
lodash.isequal@4.5.0
:
resolution
:
{
integrity
:
sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==
}
resolution
:
{
integrity
:
sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==
}
deprecated
:
This package is deprecated. Use require('node:util').isDeepStrictEqual instead.
deprecated
:
This package is deprecated. Use require('node:util').isDeepStrictEqual instead.
lodash.isinteger@4.0.4
:
resolution
:
{
integrity
:
sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==
}
lodash.isnumber@3.0.3
:
resolution
:
{
integrity
:
sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==
}
lodash.isplainobject@4.0.6
:
resolution
:
{
integrity
:
sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==
}
lodash.isstring@4.0.1
:
resolution
:
{
integrity
:
sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==
}
lodash.mergewith@4.6.2
:
lodash.mergewith@4.6.2
:
resolution
:
{
integrity
:
sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==
}
resolution
:
{
integrity
:
sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==
}
lodash.once@4.1.1
:
resolution
:
{
integrity
:
sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==
}
lodash@4.18.1
:
lodash@4.18.1
:
resolution
:
{
integrity
:
sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==
}
resolution
:
{
integrity
:
sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==
}
...
@@ -630,6 +689,14 @@ packages:
...
@@ -630,6 +689,14 @@ packages:
resolution
:
{
integrity
:
sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==
}
resolution
:
{
integrity
:
sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==
}
engines
:
{
node
:
'
>=
0.6'
}
engines
:
{
node
:
'
>=
0.6'
}
node-addon-api@8.7.0
:
resolution
:
{
integrity
:
sha512-9MdFxmkKaOYVTV+XVRG8ArDwwQ77XIgIPyKASB1k3JPq3M8fGQQQE3YpMOrKm6g//Ktx8ivZr8xo1Qmtqub+GA==
}
engines
:
{
node
:
^18 || ^20 || >= 21
}
node-gyp-build@4.8.4
:
resolution
:
{
integrity
:
sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==
}
hasBin
:
true
object-inspect@1.13.4
:
object-inspect@1.13.4
:
resolution
:
{
integrity
:
sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==
}
resolution
:
{
integrity
:
sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==
}
engines
:
{
node
:
'
>=
0.4'
}
engines
:
{
node
:
'
>=
0.4'
}
...
@@ -742,6 +809,9 @@ packages:
...
@@ -742,6 +809,9 @@ packages:
resolution
:
{
integrity
:
sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==
}
resolution
:
{
integrity
:
sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==
}
engines
:
{
node
:
'
>=
18'
}
engines
:
{
node
:
'
>=
18'
}
safe-buffer@5.2.1
:
resolution
:
{
integrity
:
sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
}
safer-buffer@2.1.2
:
safer-buffer@2.1.2
:
resolution
:
{
integrity
:
sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
}
resolution
:
{
integrity
:
sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
}
...
@@ -1081,6 +1151,10 @@ snapshots:
...
@@ -1081,6 +1151,10 @@ snapshots:
'
@tsconfig/node16@1.0.4'
:
{}
'
@tsconfig/node16@1.0.4'
:
{}
'
@types/bcrypt@6.0.0'
:
dependencies
:
'
@types/node'
:
25.7.0
'
@types/body-parser@1.19.6'
:
'
@types/body-parser@1.19.6'
:
dependencies
:
dependencies
:
'
@types/connect'
:
3.4.38
'
@types/connect'
:
3.4.38
...
@@ -1111,6 +1185,11 @@ snapshots:
...
@@ -1111,6 +1185,11 @@ snapshots:
'
@types/json-schema@7.0.15'
:
{}
'
@types/json-schema@7.0.15'
:
{}
'
@types/jsonwebtoken@9.0.10'
:
dependencies
:
'
@types/ms'
:
2.1.0
'
@types/node'
:
25.7.0
'
@types/ms@2.1.0'
:
{}
'
@types/ms@2.1.0'
:
{}
'
@types/node@25.7.0'
:
'
@types/node@25.7.0'
:
...
@@ -1162,6 +1241,11 @@ snapshots:
...
@@ -1162,6 +1241,11 @@ snapshots:
balanced-match@1.0.2
:
{}
balanced-match@1.0.2
:
{}
bcrypt@6.0.0
:
dependencies
:
node-addon-api
:
8.7.0
node-gyp-build
:
4.8.4
body-parser@2.2.2
:
body-parser@2.2.2
:
dependencies
:
dependencies
:
bytes
:
3.1.2
bytes
:
3.1.2
...
@@ -1181,6 +1265,8 @@ snapshots:
...
@@ -1181,6 +1265,8 @@ snapshots:
balanced-match
:
1.0.2
balanced-match
:
1.0.2
concat-map
:
0.0.1
concat-map
:
0.0.1
buffer-equal-constant-time@1.0.1
:
{}
bytes@3.1.2
:
{}
bytes@3.1.2
:
{}
call-bind-apply-helpers@1.0.2
:
call-bind-apply-helpers@1.0.2
:
...
@@ -1244,6 +1330,10 @@ snapshots:
...
@@ -1244,6 +1330,10 @@ snapshots:
es-errors
:
1.3.0
es-errors
:
1.3.0
gopd
:
1.2.0
gopd
:
1.2.0
ecdsa-sig-formatter@1.0.11
:
dependencies
:
safe-buffer
:
5.2.1
ee-first@1.1.1
:
{}
ee-first@1.1.1
:
{}
emoji-regex@8.0.0
:
{}
emoji-regex@8.0.0
:
{}
...
@@ -1424,12 +1514,50 @@ snapshots:
...
@@ -1424,12 +1514,50 @@ snapshots:
dependencies
:
dependencies
:
argparse
:
2.0.1
argparse
:
2.0.1
jsonwebtoken@9.0.3
:
dependencies
:
jws
:
4.0.1
lodash.includes
:
4.3.0
lodash.isboolean
:
3.0.3
lodash.isinteger
:
4.0.4
lodash.isnumber
:
3.0.3
lodash.isplainobject
:
4.0.6
lodash.isstring
:
4.0.1
lodash.once
:
4.1.1
ms
:
2.1.3
semver
:
7.8.0
jwa@2.0.1
:
dependencies
:
buffer-equal-constant-time
:
1.0.1
ecdsa-sig-formatter
:
1.0.11
safe-buffer
:
5.2.1
jws@4.0.1
:
dependencies
:
jwa
:
2.0.1
safe-buffer
:
5.2.1
lodash.get@4.4.2
:
{}
lodash.get@4.4.2
:
{}
lodash.includes@4.3.0
:
{}
lodash.isboolean@3.0.3
:
{}
lodash.isequal@4.5.0
:
{}
lodash.isequal@4.5.0
:
{}
lodash.isinteger@4.0.4
:
{}
lodash.isnumber@3.0.3
:
{}
lodash.isplainobject@4.0.6
:
{}
lodash.isstring@4.0.1
:
{}
lodash.mergewith@4.6.2
:
{}
lodash.mergewith@4.6.2
:
{}
lodash.once@4.1.1
:
{}
lodash@4.18.1
:
{}
lodash@4.18.1
:
{}
make-error@1.3.6
:
{}
make-error@1.3.6
:
{}
...
@@ -1464,6 +1592,10 @@ snapshots:
...
@@ -1464,6 +1592,10 @@ snapshots:
negotiator@1.0.0
:
{}
negotiator@1.0.0
:
{}
node-addon-api@8.7.0
:
{}
node-gyp-build@4.8.4
:
{}
object-inspect@1.13.4
:
{}
object-inspect@1.13.4
:
{}
on-finished@2.4.1
:
on-finished@2.4.1
:
...
@@ -1567,6 +1699,8 @@ snapshots:
...
@@ -1567,6 +1699,8 @@ snapshots:
transitivePeerDependencies
:
transitivePeerDependencies
:
-
supports-color
-
supports-color
safe-buffer@5.2.1
:
{}
safer-buffer@2.1.2
:
{}
safer-buffer@2.1.2
:
{}
semver@7.8.0
:
{}
semver@7.8.0
:
{}
...
...
code/src/controllers/api/v1.0/auth/login/index.ts
0 → 100644
View file @
96ed263f
import
{
LoginProvider
}
from
"#providers/LoginProvider.js"
;
import
{
Application
}
from
"express"
import
{
Resource
}
from
"express-automatic-routes"
export
default
(
_express
:
Application
)
=>
{
const
loginProvider
=
new
LoginProvider
();
return
<
Resource
>
{
/**
* @openapi
* /api/v1.0/auth/login:
* post:
* tags: [Auth]
* description: Đăng nhập người dùng
* requestBody:
* required: true
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/LoginInput"
* responses:
* 200:
* description: Đăng nhập thành công
* content:
* application/json:
* schema:
* type: array
* items:
* $ref: "#/components/schemas/LoginResponse"
*/
post
:
{
handler
:
async
(
req
,
res
)
=>
{
try
{
const
login
=
await
loginProvider
.
loginUser
(
req
.
body
);
return
res
.
status
(
200
).
json
(
login
);
}
catch
(
error
)
{
console
.
error
(
'Error logging in user:'
,
error
);
return
res
.
status
(
500
).
json
({
error
:
(
error
as
Error
).
message
});
}
}
}
}
}
\ No newline at end of file
code/src/controllers/api/v1.0/auth/profile/index.ts
0 → 100644
View file @
96ed263f
import
{
authMiddleware
}
from
"#middlewares/authorization"
;
import
{
ProfileProvider
}
from
"#providers/ProfileProvider.js"
;
import
{
Application
}
from
"express"
;
import
{
Resource
}
from
"express-automatic-routes"
;
export
default
(
_express
:
Application
)
=>
{
const
profileProvider
=
new
ProfileProvider
();
return
<
Resource
>
{
/**
* @openapi
* /api/v1.0/auth/profile:
* get:
* tags: [Auth]
* security:
* - bearerAuth: []
* description: Lấy thông tin hồ sơ người dùng
* responses:
* 200:
* description: Trả về thông tin hồ sơ người dùng
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/Profile"
*/
get
:
{
middleware
:
[
authMiddleware
],
handler
:
async
(
req
,
res
)
=>
{
try
{
const
userId
=
(
req
as
any
).
user
.
id
;
const
profile
=
await
profileProvider
.
profileProvider
(
userId
);
return
res
.
status
(
200
).
json
(
profile
);
}
catch
(
error
)
{
return
res
.
status
(
500
).
json
({
error
:
(
error
as
Error
).
message
});
}
}
}
}
}
\ No newline at end of file
code/src/controllers/api/v1.0/auth/register/index.ts
0 → 100644
View file @
96ed263f
import
{
RegisterProvider
}
from
"#providers/RegisterProvider.js"
;
import
{
Application
}
from
"express"
;
import
{
Resource
}
from
"express-automatic-routes"
;
export
default
(
_express
:
Application
)
=>
{
const
registerProvider
=
new
RegisterProvider
();
return
<
Resource
>
{
/**
* @openapi
* /api/v1.0/auth/register:
* post:
* tags: [Auth]
* description: Đăng ký người dùng mới
* requestBody:
* required: true
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/CreateUserInput"
* responses:
* 201:
* description: Tạo người dùng mới thành công
* content:
* application/json:
* schema:
* type: array
* items:
* $ref: "#/components/schemas/Register"
*/
post
:
{
handler
:
async
(
req
,
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'
});
}
if
(
password
.
length
<
6
)
{
return
res
.
status
(
400
).
json
({
error
:
'Mật khẩu phải có ít nhất 6 ký tự'
});
}
const
register
=
await
registerProvider
.
registerUser
(
req
.
body
);
return
res
.
status
(
201
).
json
(
register
);
}
catch
(
error
)
{
console
.
error
(
'Error registering user:'
,
error
);
return
res
.
status
(
500
).
json
({
error
:
(
error
as
Error
).
message
});
}
}
}
}
}
\ No newline at end of file
code/src/docs/swagger/swagger-output.json
View file @
96ed263f
...
@@ -5,6 +5,13 @@
...
@@ -5,6 +5,13 @@
"version"
:
"1.0.0"
"version"
:
"1.0.0"
},
},
"components"
:
{
"components"
:
{
"securitySchemes"
:
{
"bearerAuth"
:
{
"type"
:
"apiKey"
,
"in"
:
"header"
,
"name"
:
"Authorization"
}
},
"schemas"
:
{
"schemas"
:
{
"Class"
:
{
"Class"
:
{
"type"
:
"object"
,
"type"
:
"object"
,
...
@@ -190,6 +197,117 @@
...
@@ -190,6 +197,117 @@
"example"
:
"active"
"example"
:
"active"
}
}
}
}
},
"Register"
:
{
"type"
:
"object"
,
"example"
:
{
"id"
:
"123"
,
"name"
:
"Pham Quang Bao"
,
"email"
:
"phamquangbao@example.com"
},
"properties"
:
{
"id"
:
{
"type"
:
"uuid"
,
"format"
:
"uuid"
,
"example"
:
"123"
},
"name"
:
{
"type"
:
"string"
,
"example"
:
"Pham Quang Bao"
},
"email"
:
{
"type"
:
"string"
,
"format"
:
"email"
,
"example"
:
"phamquangbao@example.com"
}
}
},
"CreateUserInput"
:
{
"type"
:
"object"
,
"example"
:
{
"name"
:
"Pham Quang Bao"
,
"email"
:
"phamquangbao@example.com"
,
"password"
:
"123456"
},
"properties"
:
{
"name"
:
{
"type"
:
"string"
,
"example"
:
"Pham Quang Bao"
},
"email"
:
{
"type"
:
"string"
,
"format"
:
"email"
,
"example"
:
"phamquangbao@example.com"
},
"password"
:
{
"type"
:
"string"
,
"example"
:
"123456"
}
}
},
"LoginResponse"
:
{
"type"
:
"object"
,
"example"
:
{
"accessToken"
:
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjEyMyIsIm5hbWUiOiJQaGFtIFF1YW5nIEJhbyIsImVtYWlsIjoicGhhbXF1YW5nYmFvQGV4YW1wbGUuY29tIiwiaWF0IjoxNjg4ODg3MDYyfQ.abc123def456ghi789jkl012mno345pqr678stu901vwx234yz567"
},
"properties"
:
{
"accessToken"
:
{
"type"
:
"string"
,
"example"
:
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjEyMyIsIm5hbWUiOiJQaGFtIFF1YW5nIEJhbyIsImVtYWlsIjoicGhhbXF1YW5nYmFvQGV4YW1wbGUuY29tIiwiaWF0IjoxNjg4ODg3MDYyfQ.abc123def456ghi789jkl012mno345pqr678stu901vwx234yz567"
}
}
},
"LoginInput"
:
{
"type"
:
"object"
,
"example"
:
{
"email"
:
"phamquangbao@example.com"
,
"password"
:
"123456"
},
"properties"
:
{
"email"
:
{
"type"
:
"string"
,
"format"
:
"email"
,
"example"
:
"phamquangbao@example.com"
},
"password"
:
{
"type"
:
"string"
,
"example"
:
"123456"
}
}
},
"Profile"
:
{
"type"
:
"object"
,
"example"
:
{
"id"
:
"123"
,
"name"
:
"Pham Quang Bao"
,
"email"
:
"phamquangbao@example.com"
,
"phone"
:
"+1234567890"
,
"address"
:
"123 Main St, Anytown, USA"
},
"properties"
:
{
"id"
:
{
"type"
:
"uuid"
,
"format"
:
"uuid"
,
"example"
:
"123"
},
"name"
:
{
"type"
:
"string"
,
"example"
:
"Pham Quang Bao"
},
"email"
:
{
"type"
:
"string"
,
"format"
:
"email"
,
"example"
:
"phamquangbao@example.com"
},
"phone"
:
{
"type"
:
"string"
,
"example"
:
"+1234567890"
},
"address"
:
{
"type"
:
"string"
,
"example"
:
"123 Main St, Anytown, USA"
}
}
}
}
},
},
"parameters"
:
{
"parameters"
:
{
...
@@ -228,6 +346,97 @@
...
@@ -228,6 +346,97 @@
}
}
},
},
"paths"
:
{
"paths"
:
{
"/api/v1.0/auth/login"
:
{
"post"
:
{
"tags"
:
[
"Auth"
],
"description"
:
"Đăng nhập người dùng"
,
"requestBody"
:
{
"required"
:
true
,
"content"
:
{
"application/json"
:
{
"schema"
:
{
"$ref"
:
"#/components/schemas/LoginInput"
}
}
}
},
"responses"
:
{
"200"
:
{
"description"
:
"Đăng nhập thành công"
,
"content"
:
{
"application/json"
:
{
"schema"
:
{
"type"
:
"array"
,
"items"
:
{
"$ref"
:
"#/components/schemas/LoginResponse"
}
}
}
}
}
}
}
},
"/api/v1.0/auth/profile"
:
{
"get"
:
{
"tags"
:
[
"Auth"
],
"security"
:
[
{
"bearerAuth"
:
[]
}
],
"description"
:
"Lấy thông tin hồ sơ người dùng"
,
"responses"
:
{
"200"
:
{
"description"
:
"Trả về thông tin hồ sơ người dùng"
,
"content"
:
{
"application/json"
:
{
"schema"
:
{
"$ref"
:
"#/components/schemas/Profile"
}
}
}
}
}
}
},
"/api/v1.0/auth/register"
:
{
"post"
:
{
"tags"
:
[
"Auth"
],
"description"
:
"Đăng ký người dùng mới"
,
"requestBody"
:
{
"required"
:
true
,
"content"
:
{
"application/json"
:
{
"schema"
:
{
"$ref"
:
"#/components/schemas/CreateUserInput"
}
}
}
},
"responses"
:
{
"201"
:
{
"description"
:
"Tạo người dùng mới thành công"
,
"content"
:
{
"application/json"
:
{
"schema"
:
{
"type"
:
"array"
,
"items"
:
{
"$ref"
:
"#/components/schemas/Register"
}
}
}
}
}
}
}
},
"/api/v1.0/classes/{id}"
:
{
"/api/v1.0/classes/{id}"
:
{
"get"
:
{
"get"
:
{
"tags"
:
[
"tags"
:
[
...
...
code/src/middlewares/authorization.ts
0 → 100644
View file @
96ed263f
import
jwt
from
'jsonwebtoken'
;
import
{
Request
,
Response
,
NextFunction
}
from
'express'
;
//demo
const
JWT_SECRET
=
'1234567890'
;
export
const
authMiddleware
=
(
req
:
any
,
res
:
Response
,
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'
});
}
const
token
=
authHeader
.
split
(
' '
)[
1
];
try
{
const
decoded
=
jwt
.
verify
(
token
,
JWT_SECRET
)
as
any
;
req
.
user
=
decoded
;
next
();
}
catch
(
error
)
{
return
res
.
status
(
403
).
json
({
error
:
'Token không hợp lệ hoặc đã hết hạn'
});
}
};
\ No newline at end of file
code/src/providers/LoginProvider.ts
0 → 100644
View file @
96ed263f
import
{
models
}
from
"#models/sequelize-config.js"
;
import
bcrypt
from
'bcrypt'
;
import
jwt
from
'jsonwebtoken'
;
interface
LoginInput
{
email
:
string
;
password
:
string
;
}
export
class
LoginProvider
{
// demo
private
readonly
JWT_SECRET
=
'1234567890'
;
async
loginUser
(
input
:
LoginInput
)
{
const
user
=
await
models
.
users
.
findOne
({
where
:
{
email
:
input
.
email
},
});
if
(
!
user
)
{
throw
new
Error
(
'Email hoặc mật khẩu không đúng'
);
}
const
auth
=
await
models
.
user_auth
.
findOne
({
where
:
{
user_id
:
user
?.
id
}
});
const
passwordMatch
=
await
bcrypt
.
compare
(
input
.
password
,
auth
?.
password_hash
||
''
);
if
(
!
passwordMatch
)
{
throw
new
Error
(
'Email hoặc mật khẩu không đúng'
);
}
const
payload
=
{
id
:
user
.
id
,
email
:
user
.
email
,
role_id
:
user
.
role_id
};
const
token
=
jwt
.
sign
(
payload
,
this
.
JWT_SECRET
,
{
expiresIn
:
'1h'
,
});
return
{
accessToken
:
token
};
}
}
\ No newline at end of file
code/src/providers/ProfileProvider.ts
0 → 100644
View file @
96ed263f
import
{
models
}
from
"#models/sequelize-config.js"
;
export
class
ProfileProvider
{
async
profileProvider
(
userId
:
number
)
{
const
user
=
await
models
.
users
.
findByPk
(
userId
);
if
(
!
user
)
{
throw
new
Error
(
'Người dùng không tồn tại'
);
}
return
user
;
}
}
\ No newline at end of file
code/src/providers/RegisterProvider.ts
0 → 100644
View file @
96ed263f
import
{
models
,
sequelize
}
from
'#models/sequelize-config.js'
;
import
bcrypt
from
'bcrypt'
;
interface
RegisterInput
{
name
:
string
;
email
:
string
;
password
:
string
;
}
export
class
RegisterProvider
{
async
registerUser
(
input
:
RegisterInput
)
{
const
salt
=
await
bcrypt
.
genSalt
(
10
);
const
passwordHash
=
await
bcrypt
.
hash
(
input
.
password
,
salt
);
try
{
const
result
=
await
sequelize
.
transaction
(
async
(
t
)
=>
{
const
registeredUser
=
await
models
.
users
.
create
(
{
name
:
input
.
name
,
email
:
input
.
email
,
},
{
transaction
:
t
}
);
await
models
.
user_auth
.
create
(
{
user_id
:
registeredUser
.
id
,
password_hash
:
passwordHash
,
},
{
transaction
:
t
}
);
return
{
id
:
registeredUser
.
id
,
name
:
registeredUser
.
name
,
email
:
registeredUser
.
email
,
};
});
return
result
;
}
catch
(
error
)
{
console
.
error
(
'Error during user registration:'
,
error
);
throw
new
Error
(
'Failed to register user'
);
}
}
}
\ No newline at end of file
code/src/templates/swagger/authProfile/schema.ts
0 → 100644
View file @
96ed263f
const
authProfileSchemas
=
{
Profile
:
{
type
:
'object'
,
example
:
{
id
:
'123'
,
name
:
'Pham Quang Bao'
,
email
:
'phamquangbao@example.com'
,
phone
:
'+1234567890'
,
address
:
'123 Main St, Anytown, USA'
,
},
properties
:
{
id
:
{
type
:
'uuid'
,
format
:
'uuid'
,
example
:
'123'
,
},
name
:
{
type
:
'string'
,
example
:
'Pham Quang Bao'
,
},
email
:
{
type
:
'string'
,
format
:
'email'
,
example
:
'phamquangbao@example.com'
,
},
phone
:
{
type
:
'string'
,
example
:
'+1234567890'
,
},
address
:
{
type
:
'string'
,
example
:
'123 Main St, Anytown, USA'
,
},
},
},
};
export
default
authProfileSchemas
;
\ No newline at end of file
code/src/templates/swagger/config.ts
View file @
96ed263f
...
@@ -3,6 +3,9 @@ import classSchemas from './classes/schemas.js';
...
@@ -3,6 +3,9 @@ import classSchemas from './classes/schemas.js';
import
courseSchemas
from
'./courses/schemas.js'
;
import
courseSchemas
from
'./courses/schemas.js'
;
import
type
{
Options
}
from
'swagger-jsdoc'
;
import
type
{
Options
}
from
'swagger-jsdoc'
;
import
registerSchemas
from
'./register/schemas.js'
;
import
loginSchemas
from
'./login/schemas.js'
;
import
authProfileSchemas
from
'./authProfile/schema.js'
;
const
swaggerOptions
:
Options
=
{
const
swaggerOptions
:
Options
=
{
definition
:
{
definition
:
{
...
@@ -12,9 +15,19 @@ const swaggerOptions: Options = {
...
@@ -12,9 +15,19 @@ const swaggerOptions: Options = {
version
:
'1.0.0'
,
version
:
'1.0.0'
,
},
},
components
:
{
components
:
{
securitySchemes
:
{
bearerAuth
:
{
type
:
'apiKey'
,
in
:
'header'
,
name
:
'Authorization'
,
}
},
schemas
:
{
schemas
:
{
...
classSchemas
,
...
classSchemas
,
...
courseSchemas
,
...
courseSchemas
,
...
registerSchemas
,
...
loginSchemas
,
...
authProfileSchemas
,
},
},
parameters
:
{
parameters
:
{
filters
:
{
filters
:
{
...
...
code/src/templates/swagger/login/schemas.ts
0 → 100644
View file @
96ed263f
const
loginSchemas
=
{
LoginResponse
:
{
type
:
'object'
,
example
:
{
accessToken
:
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjEyMyIsIm5hbWUiOiJQaGFtIFF1YW5nIEJhbyIsImVtYWlsIjoicGhhbXF1YW5nYmFvQGV4YW1wbGUuY29tIiwiaWF0IjoxNjg4ODg3MDYyfQ.abc123def456ghi789jkl012mno345pqr678stu901vwx234yz567'
,
},
properties
:
{
accessToken
:
{
type
:
'string'
,
example
:
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjEyMyIsIm5hbWUiOiJQaGFtIFF1YW5nIEJhbyIsImVtYWlsIjoicGhhbXF1YW5nYmFvQGV4YW1wbGUuY29tIiwiaWF0IjoxNjg4ODg3MDYyfQ.abc123def456ghi789jkl012mno345pqr678stu901vwx234yz567'
,
},
},
},
LoginInput
:
{
type
:
'object'
,
example
:
{
email
:
'phamquangbao@example.com'
,
password
:
'123456'
,
},
properties
:
{
email
:
{
type
:
'string'
,
format
:
'email'
,
example
:
'phamquangbao@example.com'
,
},
password
:
{
type
:
'string'
,
example
:
'123456'
,
},
},
},
};
export
default
loginSchemas
;
\ No newline at end of file
code/src/templates/swagger/register/schemas.ts
0 → 100644
View file @
96ed263f
const
registerSchemas
=
{
Register
:
{
type
:
'object'
,
example
:
{
id
:
'123'
,
name
:
'Pham Quang Bao'
,
email
:
'phamquangbao@example.com'
,
},
properties
:
{
id
:
{
type
:
'uuid'
,
format
:
'uuid'
,
example
:
'123'
,
},
name
:
{
type
:
'string'
,
example
:
'Pham Quang Bao'
,
},
email
:
{
type
:
'string'
,
format
:
'email'
,
example
:
'phamquangbao@example.com'
,
},
},
},
CreateUserInput
:
{
type
:
'object'
,
example
:
{
name
:
'Pham Quang Bao'
,
email
:
'phamquangbao@example.com'
,
password
:
'123456'
,
},
properties
:
{
name
:
{
type
:
'string'
,
example
:
'Pham Quang Bao'
,
},
email
:
{
type
:
'string'
,
format
:
'email'
,
example
:
'phamquangbao@example.com'
,
},
password
:
{
type
:
'string'
,
example
:
'123456'
,
},
},
},
};
export
default
registerSchemas
;
\ 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