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
4cd74144
Commit
4cd74144
authored
Jun 04, 2026
by
Phạm Quang Bảo
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
fix: save file in local and add api upload management
parent
cfeeda3b
Changes
13
Show whitespace changes
Inline
Side-by-side
Showing
13 changed files
with
837 additions
and
86 deletions
+837
-86
cloudinary.config.ts
backend/src/config/cloudinary.config.ts
+0
-10
multer.config.ts
backend/src/config/multer.config.ts
+27
-3
index.ts
backend/src/controllers/api/v1.0/courses/import/index.ts
+3
-3
index.ts
backend/src/controllers/api/v1.0/upload/index.ts
+53
-7
{id}.ts
backend/src/controllers/api/v1.0/upload/{id}.ts
+146
-0
swagger-output.json
backend/src/docs/swagger/swagger-output.json
+264
-19
index.ts
backend/src/index.ts
+2
-1
init-models.ts
backend/src/models/init-models.ts
+7
-0
upload.ts
backend/src/models/upload.ts
+97
-0
UploadProvider.ts
backend/src/providers/UploadProvider.ts
+64
-0
uploadService.ts
backend/src/services/uploadService.ts
+0
-26
schemas.ts
backend/src/templates/swagger/upload/schemas.ts
+174
-17
image-1780547195335-108126692.png
backend/uploads/image-1780547195335-108126692.png
+0
-0
No files found.
backend/src/config/cloudinary.config.ts
deleted
100644 → 0
View file @
cfeeda3b
import
{
v2
as
cloudinary
}
from
"cloudinary"
;
import
"dotenv/config"
;
cloudinary
.
config
({
cloud_name
:
process
.
env
.
CLOUD_NAME
!
as
string
,
api_key
:
process
.
env
.
API_KEY
!
as
string
,
api_secret
:
process
.
env
.
API_SECRET
!
as
string
,
});
export
default
cloudinary
;
\ No newline at end of file
backend/src/config/multer.config.ts
View file @
4cd74144
import
multer
from
"multer"
;
import
path
from
"node:path"
;
// save in memory
const
memoryStorage
=
multer
.
memoryStorage
();
const
upload
=
multer
({
const
upload
Memory
=
multer
({
storage
:
memoryStorage
,
limits
:
{
fileSize
:
2
*
1024
*
1024
,
// 2
MB
fileSize
:
5
*
1024
*
1024
,
// 5
MB
},
});
export
default
upload
;
\ No newline at end of file
// save in disk
const
diskStorage
=
multer
.
diskStorage
({
destination
:
function
(
req
,
file
,
cb
)
{
cb
(
null
,
'uploads/'
);
},
filename
:
function
(
req
,
file
,
cb
)
{
const
uniqueSuffix
=
Date
.
now
()
+
'-'
+
Math
.
round
(
Math
.
random
()
*
1
E9
);
const
ext
=
path
.
extname
(
file
.
originalname
);
cb
(
null
,
file
.
fieldname
+
'-'
+
uniqueSuffix
+
ext
);
}
});
const
uploadDisk
=
multer
({
storage
:
diskStorage
,
limits
:
{
fileSize
:
5
*
1024
*
1024
,
// 5 MB
},
});
export
{
uploadMemory
,
uploadDisk
};
\ No newline at end of file
backend/src/controllers/api/v1.0/courses/import/index.ts
View file @
4cd74144
import
{
Application
}
from
"express"
;
import
{
Readable
}
from
'stream'
;
import
{
Resource
}
from
"express-automatic-routes"
;
import
upload
from
"#config/multer.config"
;
import
{
uploadMemory
}
from
"#config/multer.config"
;
import
csv
from
'csv-parser'
;
import
{
Req
,
Res
}
from
"#interfaces/IApi"
;
import
{
CoursesProvider
}
from
"#providers/CoursesProvider"
;
...
...
@@ -29,7 +29,7 @@ export default (_express: Application) => {
* file:
* type: string
* format: binary
* description: File CSV (Giới hạn dưới
2
MB)
* description: File CSV (Giới hạn dưới
5
MB)
* responses:
* 201:
* description: Tạo khóa học mới thành công
...
...
@@ -39,7 +39,7 @@ export default (_express: Application) => {
* $ref: "#/components/schemas/CourseResponse"
*/
post
:
{
middleware
:
[
upload
.
single
(
'file'
)],
middleware
:
[
upload
Memory
.
single
(
'file'
)],
handler
:
async
(
req
:
Req
,
res
:
Res
)
=>
{
try
{
...
...
backend/src/controllers/api/v1.0/upload/index.ts
View file @
4cd74144
...
...
@@ -3,13 +3,53 @@ import { Application } from "express";
import
{
Resource
}
from
"express-automatic-routes"
;
import
queryModifier
from
"#middlewares/request"
;
import
{
authMiddleware
}
from
"#middlewares/authentication"
;
import
upload
from
"#config/multer.config"
;
import
{
Upload
Service
}
from
"#services/uploadService
"
;
import
{
uploadDisk
}
from
"#config/multer.config"
;
import
{
Upload
Provider
}
from
"#providers/UploadProvider
"
;
export
default
(
_express
:
Application
)
=>
{
const
upload
Service
=
new
UploadService
();
const
upload
Provider
=
new
UploadProvider
();
return
<
Resource
>
{
/**
* @openapi
* /api/v1.0/upload:
* get:
* tags: [Upload]
* security:
* - bearerAuth: []
* description: Lấy danh sách file ảnh.
* responses:
* 200:
* description: Lấy danh sách file ảnh thành công
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/getResponse"
*/
get
:
{
middleware
:
[
authMiddleware
,
queryModifier
,
uploadDisk
.
single
(
"image"
)],
handler
:
async
(
req
:
Req
,
res
:
Res
)
=>
{
try
{
const
image
=
await
uploadProvider
.
getImage
();
res
.
sendOk
({
data
:
image
,
message
:
"Lấy danh sách ảnh thành công"
,
message_en
:
"Images retrieved successfully"
,
status
:
200
});
}
catch
(
error
)
{
console
.
error
(
"Error retrieving images:"
,
error
);
res
.
sendError
({
message
:
"Lấy danh sách ảnh thất bại"
,
message_en
:
"Failed to retrieve images"
,
status
:
500
});
}
}
},
/**
* @openapi
* /api/v1.0/upload:
...
...
@@ -37,10 +77,10 @@ export default (_express: Application) => {
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/
Upload
Response"
* $ref: "#/components/schemas/
post
Response"
*/
post
:
{
middleware
:
[
authMiddleware
,
queryModifier
,
upload
.
single
(
"image"
)],
middleware
:
[
authMiddleware
,
queryModifier
,
upload
Disk
.
single
(
"image"
)],
handler
:
async
(
req
:
Req
,
res
:
Res
)
=>
{
try
{
...
...
@@ -60,10 +100,16 @@ export default (_express: Application) => {
});
}
const
result
=
await
uploadService
.
uploadImage
(
req
.
file
.
buffer
);
const
image
=
await
uploadProvider
.
uploadImage
(
req
);
res
.
sendOk
({
data
:
{
width
:
result
?.
width
,
height
:
result
?.
height
,
bytes
:
result
?.
bytes
,
url
:
result
?.
url
},
data
:
{
id
:
image
.
id
,
url
:
image
.
url
,
name
:
image
.
name
,
size
:
image
.
size
,
type
:
image
.
type
},
message
:
"Upload ảnh thành công"
,
message_en
:
"Image uploaded successfully"
,
status
:
200
...
...
backend/src/controllers/api/v1.0/upload/{id}.ts
0 → 100644
View file @
4cd74144
import
{
Req
,
Res
}
from
"#interfaces/IApi"
;
import
{
authMiddleware
}
from
"#middlewares/authentication"
;
import
queryModifier
from
"#middlewares/request"
;
import
{
UploadProvider
}
from
"#providers/UploadProvider"
;
import
{
Application
}
from
"express"
;
import
{
Resource
}
from
"express-automatic-routes"
;
export
default
(
_express
:
Application
)
=>
{
const
uploadProvider
=
new
UploadProvider
();
return
<
Resource
>
{
/**
* @openapi
* /api/v1.0/upload/{id}:
* get:
* tags: [Upload]
* security:
* - bearerAuth: []
* description: Lấy thông tin file ảnh theo ID.
* parameters:
* - name: id
* in: path
* required: true
* schema:
* type: string
* responses:
* 200:
* description: Lấy thông tin file ảnh thành công
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/getIdResponse"
*/
get
:
{
middleware
:
[
authMiddleware
,
queryModifier
],
handler
:
async
(
req
:
Req
,
res
:
Res
)
=>
{
try
{
if
(
!
req
.
params
.
id
)
{
return
res
.
sendError
({
message
:
"ID ảnh không được để trống"
,
message_en
:
"Image ID cannot be empty"
,
status
:
400
});
}
if
(
typeof
req
.
params
.
id
!==
"string"
)
{
return
res
.
sendError
({
message
:
"ID ảnh không hợp lệ"
,
message_en
:
"Image ID is invalid"
,
status
:
400
});
}
const
image
=
await
uploadProvider
.
getImageById
(
req
.
params
.
id
);
res
.
sendOk
({
data
:
{
id
:
image
.
id
,
url
:
image
.
url
,
name
:
image
.
name
,
size
:
image
.
size
,
type
:
image
.
type
},
message
:
"Lấy thông tin ảnh thành công"
,
message_en
:
"Image information retrieved successfully"
,
status
:
200
});
}
catch
(
error
)
{
console
.
error
(
"Error retrieving image by ID:"
,
error
);
res
.
sendError
({
message
:
"Lấy thông tin ảnh thất bại"
,
message_en
:
"Failed to retrieve image information"
,
status
:
500
});
}
}
},
/**
* @openapi
* /api/v1.0/upload/{id}:
* delete:
* tags: [Upload]
* security:
* - bearerAuth: []
* parameters:
* - name: id
* in: path
* required: true
* schema:
* type: string
* description: Xóa file ảnh theo ID.
* responses:
* 200:
* description: Xóa file ảnh thành công
* content:
* application/json:
* schema:
* $ref: "#/components/schemas/deleteResponse"
*/
delete
:
{
middleware
:
[
authMiddleware
,
queryModifier
],
handler
:
async
(
req
:
Req
,
res
:
Res
)
=>
{
try
{
if
(
!
req
.
params
.
id
)
{
return
res
.
sendError
({
message
:
"ID ảnh không được để trống"
,
message_en
:
"Image ID cannot be empty"
,
status
:
400
});
}
if
(
typeof
req
.
params
.
id
!==
"string"
)
{
return
res
.
sendError
({
message
:
"ID ảnh không hợp lệ"
,
message_en
:
"Image ID is invalid"
,
status
:
400
});
}
const
image
=
await
uploadProvider
.
deleteImageById
(
req
.
params
.
id
);
res
.
sendOk
({
data
:
{
id
:
image
.
id
,
url
:
image
.
url
,
name
:
image
.
name
,
size
:
image
.
size
,
type
:
image
.
type
},
message
:
"Xóa ảnh thành công"
,
message_en
:
"Image deleted successfully"
,
status
:
200
});
}
catch
(
error
)
{
console
.
error
(
"Error deleting image by ID:"
,
error
);
res
.
sendError
({
message
:
"Xóa ảnh thất bại"
,
message_en
:
"Failed to delete image"
,
status
:
500
});
}
}
}
};
}
\ No newline at end of file
backend/src/docs/swagger/swagger-output.json
View file @
4cd74144
...
...
@@ -1010,7 +1010,20 @@
}
}
},
"UploadResponse"
:
{
"postInput"
:
{
"type"
:
"object"
,
"example"
:
{
"file"
:
"image.jpg"
},
"properties"
:
{
"file"
:
{
"type"
:
"string"
,
"format"
:
"binary"
,
"example"
:
"image.jpg"
}
}
},
"getResponse"
:
{
"type"
:
"object"
,
"properties"
:
{
"message"
:
{
...
...
@@ -1021,24 +1034,129 @@
"type"
:
"string"
,
"nullable"
:
true
},
"responseData"
:
{
"data"
:
{
"type"
:
"array"
,
"items"
:
{
"type"
:
"object"
,
"properties"
:
{
"width"
:
{
"type"
:
"number"
,
"example"
:
800
"id"
:
{
"type"
:
"string"
,
"example"
:
"123e4567-e89b-12d3-a456-426614174000"
},
"url"
:
{
"type"
:
"string"
,
"example"
:
"uploads/image-1780543350715-29021218.png"
},
"height"
:
{
"name"
:
{
"type"
:
"string"
,
"example"
:
"image-1780543350715-29021218.png"
},
"size"
:
{
"type"
:
"number"
,
"example"
:
600
"example"
:
150000
},
"type"
:
{
"type"
:
"string"
,
"example"
:
"image/jpeg"
}
}
}
},
"status"
:
{
"type"
:
"string"
,
"example"
:
"success"
},
"timeStamp"
:
{
"type"
:
"string"
,
"example"
:
"2024-02-26 03:12:45"
},
"violations"
:
{
"type"
:
"array"
}
}
},
"getIdResponse"
:
{
"type"
:
"object"
,
"properties"
:
{
"message"
:
{
"type"
:
"string"
,
"nullable"
:
true
},
"message_en"
:
{
"type"
:
"string"
,
"nullable"
:
true
},
"data"
:
{
"type"
:
"object"
,
"properties"
:
{
"id"
:
{
"type"
:
"string"
,
"example"
:
"123e4567-e89b-12d3-a456-426614174000"
},
"url"
:
{
"type"
:
"string"
,
"example"
:
"uploads/image-1780543350715-29021218.png"
},
"bytes"
:
{
"name"
:
{
"type"
:
"string"
,
"example"
:
"image-1780543350715-29021218.png"
},
"size"
:
{
"type"
:
"number"
,
"example"
:
150000
},
"type"
:
{
"type"
:
"string"
,
"example"
:
"image/jpeg"
}
}
},
"status"
:
{
"type"
:
"string"
,
"example"
:
"success"
},
"timeStamp"
:
{
"type"
:
"string"
,
"example"
:
"2024-02-26 03:12:45"
},
"violations"
:
{
"type"
:
"array"
}
}
},
"postResponse"
:
{
"type"
:
"object"
,
"properties"
:
{
"message"
:
{
"type"
:
"string"
,
"nullable"
:
true
},
"message_en"
:
{
"type"
:
"string"
,
"nullable"
:
true
},
"responseData"
:
{
"type"
:
"object"
,
"properties"
:
{
"id"
:
{
"type"
:
"string"
,
"example"
:
"123e4567-e89b-12d3-a456-426614174000"
},
"url"
:
{
"type"
:
"string"
,
"example"
:
"https://res.cloudinary.com/demo/image/upload/v1312461204/sample.jpg"
"example"
:
"uploads/image-1780543350715-29021218.png"
},
"name"
:
{
"type"
:
"string"
,
"example"
:
"image-1780543350715-29021218.png"
},
"size"
:
{
"type"
:
"number"
,
"example"
:
15078
},
"type"
:
{
"type"
:
"string"
,
"example"
:
"image/png"
}
}
},
...
...
@@ -1055,16 +1173,52 @@
}
}
},
"
UploadInput
"
:
{
"
deleteResponse
"
:
{
"type"
:
"object"
,
"example"
:
{
"file"
:
"image.jpg"
"properties"
:
{
"message"
:
{
"type"
:
"string"
,
"nullable"
:
true
},
"message_en"
:
{
"type"
:
"string"
,
"nullable"
:
true
},
"data"
:
{
"type"
:
"object"
,
"properties"
:
{
"file
"
:
{
"id
"
:
{
"type"
:
"string"
,
"format"
:
"binary"
,
"example"
:
"image.jpg"
"example"
:
"123e4567-e89b-12d3-a456-426614174000"
},
"url"
:
{
"type"
:
"string"
,
"example"
:
"uploads/image-1780543350715-29021218.png"
},
"name"
:
{
"type"
:
"string"
,
"example"
:
"image-1780543350715-29021218.png"
},
"size"
:
{
"type"
:
"number"
,
"example"
:
150000
},
"type"
:
{
"type"
:
"string"
,
"example"
:
"image/jpeg"
}
}
},
"status"
:
{
"type"
:
"string"
,
"example"
:
"success"
},
"timeStamp"
:
{
"type"
:
"string"
,
"example"
:
"2024-02-26 03:12:45"
},
"violations"
:
{
"type"
:
"array"
}
}
}
...
...
@@ -1704,7 +1858,7 @@
"file"
:
{
"type"
:
"string"
,
"format"
:
"binary"
,
"description"
:
"File CSV (Giới hạn dưới
2
MB)"
"description"
:
"File CSV (Giới hạn dưới
5
MB)"
}
}
}
...
...
@@ -1966,7 +2120,98 @@
}
}
},
"/api/v1.0/upload/{id}"
:
{
"get"
:
{
"tags"
:
[
"Upload"
],
"security"
:
[
{
"bearerAuth"
:
[]
}
],
"description"
:
"Lấy thông tin file ảnh theo ID."
,
"parameters"
:
[
{
"name"
:
"id"
,
"in"
:
"path"
,
"required"
:
true
,
"schema"
:
{
"type"
:
"string"
}
}
],
"responses"
:
{
"200"
:
{
"description"
:
"Lấy thông tin file ảnh thành công"
,
"content"
:
{
"application/json"
:
{
"schema"
:
{
"$ref"
:
"#/components/schemas/getIdResponse"
}
}
}
}
}
},
"delete"
:
{
"tags"
:
[
"Upload"
],
"security"
:
[
{
"bearerAuth"
:
[]
}
],
"parameters"
:
[
{
"name"
:
"id"
,
"in"
:
"path"
,
"required"
:
true
,
"schema"
:
{
"type"
:
"string"
}
}
],
"description"
:
"Xóa file ảnh theo ID."
,
"responses"
:
{
"200"
:
{
"description"
:
"Xóa file ảnh thành công"
,
"content"
:
{
"application/json"
:
{
"schema"
:
{
"$ref"
:
"#/components/schemas/deleteResponse"
}
}
}
}
}
}
},
"/api/v1.0/upload"
:
{
"get"
:
{
"tags"
:
[
"Upload"
],
"security"
:
[
{
"bearerAuth"
:
[]
}
],
"description"
:
"Lấy danh sách file ảnh."
,
"responses"
:
{
"200"
:
{
"description"
:
"Lấy danh sách file ảnh thành công"
,
"content"
:
{
"application/json"
:
{
"schema"
:
{
"$ref"
:
"#/components/schemas/getResponse"
}
}
}
}
}
},
"post"
:
{
"tags"
:
[
"Upload"
...
...
@@ -2003,7 +2248,7 @@
"content"
:
{
"application/json"
:
{
"schema"
:
{
"$ref"
:
"#/components/schemas/
Upload
Response"
"$ref"
:
"#/components/schemas/
post
Response"
}
}
}
...
...
backend/src/index.ts
View file @
4cd74144
import
express
from
'express'
;
import
{
resolve
}
from
'path'
;
import
path
,
{
resolve
}
from
'path'
;
import
_autoroutes
from
'express-automatic-routes'
;
import
swaggerUi
from
'swagger-ui-express'
;
import
response
from
'#middlewares/response'
;
...
...
@@ -21,6 +21,7 @@ mountRoutes(app, {
emailCronJob
.
start
();
app
.
use
(
'/uploads'
,
express
.
static
(
path
.
join
(
process
.
cwd
(),
'uploads'
)));
app
.
use
(
'/swagger/index'
,
swaggerUi
.
serve
,
swaggerUi
.
setup
(
swaggerFile
));
app
.
listen
(
port
,
()
=>
{
...
...
backend/src/models/init-models.ts
View file @
4cd74144
...
...
@@ -9,6 +9,8 @@ import { refresh_token as _refresh_token } from "./refresh_token";
import
type
{
refresh_tokenAttributes
,
refresh_tokenCreationAttributes
}
from
"./refresh_token"
;
import
{
roles
as
_roles
}
from
"./roles"
;
import
type
{
rolesAttributes
,
rolesCreationAttributes
}
from
"./roles"
;
import
{
upload
as
_upload
}
from
"./upload"
;
import
type
{
uploadAttributes
,
uploadCreationAttributes
}
from
"./upload"
;
import
{
user_auth
as
_user_auth
}
from
"./user_auth"
;
import
type
{
user_authAttributes
,
user_authCreationAttributes
}
from
"./user_auth"
;
import
{
users
as
_users
}
from
"./users"
;
...
...
@@ -20,6 +22,7 @@ export {
_enrollments
as
enrollments
,
_refresh_token
as
refresh_token
,
_roles
as
roles
,
_upload
as
upload
,
_user_auth
as
user_auth
,
_users
as
users
,
};
...
...
@@ -35,6 +38,8 @@ export type {
refresh_tokenCreationAttributes
,
rolesAttributes
,
rolesCreationAttributes
,
uploadAttributes
,
uploadCreationAttributes
,
user_authAttributes
,
user_authCreationAttributes
,
usersAttributes
,
...
...
@@ -47,6 +52,7 @@ export function initModels(sequelize: Sequelize) {
const
enrollments
=
_enrollments
.
initModel
(
sequelize
);
const
refresh_token
=
_refresh_token
.
initModel
(
sequelize
);
const
roles
=
_roles
.
initModel
(
sequelize
);
const
upload
=
_upload
.
initModel
(
sequelize
);
const
user_auth
=
_user_auth
.
initModel
(
sequelize
);
const
users
=
_users
.
initModel
(
sequelize
);
...
...
@@ -69,6 +75,7 @@ export function initModels(sequelize: Sequelize) {
enrollments
:
enrollments
,
refresh_token
:
refresh_token
,
roles
:
roles
,
upload
:
upload
,
user_auth
:
user_auth
,
users
:
users
,
};
...
...
backend/src/models/upload.ts
0 → 100644
View file @
4cd74144
import
*
as
Sequelize
from
'sequelize'
;
import
{
DataTypes
,
Model
,
Optional
}
from
'sequelize'
;
export
interface
uploadAttributes
{
url
:
string
;
name
?:
string
;
size
?:
number
;
type
?:
string
;
created_at
:
Date
;
created_by
?:
string
;
updated_at
?:
Date
;
updated_by
?:
string
;
id
:
string
;
}
export
type
uploadPk
=
"id"
;
export
type
uploadId
=
upload
[
uploadPk
];
export
type
uploadOptionalAttributes
=
"name"
|
"size"
|
"type"
|
"created_at"
|
"created_by"
|
"updated_at"
|
"updated_by"
|
"id"
;
export
type
uploadCreationAttributes
=
Optional
<
uploadAttributes
,
uploadOptionalAttributes
>
;
export
class
upload
extends
Model
<
uploadAttributes
,
uploadCreationAttributes
>
implements
uploadAttributes
{
url
!
:
string
;
name
?:
string
;
size
?:
number
;
type
?:
string
;
created_at
!
:
Date
;
created_by
?:
string
;
updated_at
?:
Date
;
updated_by
?:
string
;
id
!
:
string
;
static
initModel
(
sequelize
:
Sequelize
.
Sequelize
):
typeof
upload
{
return
sequelize
.
define
(
'upload'
,
{
url
:
{
type
:
DataTypes
.
TEXT
,
allowNull
:
false
,
unique
:
"upload_url_unique"
},
name
:
{
type
:
DataTypes
.
TEXT
,
allowNull
:
true
},
size
:
{
type
:
DataTypes
.
INTEGER
,
allowNull
:
true
},
type
:
{
type
:
DataTypes
.
TEXT
,
allowNull
:
true
},
created_at
:
{
type
:
DataTypes
.
DATE
,
allowNull
:
false
,
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
},
id
:
{
type
:
DataTypes
.
UUID
,
allowNull
:
false
,
defaultValue
:
DataTypes
.
UUIDV4
,
primaryKey
:
true
}
},
{
tableName
:
'upload'
,
schema
:
'public'
,
timestamps
:
false
,
indexes
:
[
{
name
:
"upload_pkey"
,
unique
:
true
,
fields
:
[
{
name
:
"id"
},
]
},
{
name
:
"upload_url_unique"
,
unique
:
true
,
fields
:
[
{
name
:
"url"
},
]
},
]
})
as
typeof
upload
;
}
}
backend/src/providers/UploadProvider.ts
0 → 100644
View file @
4cd74144
import
{
Req
}
from
"#interfaces/IApi"
;
import
{
models
}
from
"#models/sequelize-config"
;
import
path
from
"path/win32"
;
import
fs
from
'fs'
;
export
class
UploadProvider
{
async
getImage
()
{
const
images
=
await
models
.
upload
.
findAll
({
attributes
:
[
"id"
,
"url"
,
"name"
,
"size"
,
"type"
],
order
:
[[
"created_at"
,
"DESC"
]]
});
return
images
;
}
async
getImageById
(
id
:
string
)
{
const
image
=
await
models
.
upload
.
findOne
({
where
:
{
id
},
attributes
:
[
"id"
,
"url"
,
"name"
,
"size"
,
"type"
]
});
if
(
!
image
)
{
throw
new
Error
(
"Image not found!"
);
}
return
image
;
}
async
uploadImage
(
req
:
Req
)
{
if
(
!
req
.
file
)
{
throw
new
Error
(
"No file uploaded!"
);
}
if
(
!
req
.
file
.
mimetype
.
startsWith
(
"image/"
))
{
throw
new
Error
(
"Only image uploads are allowed!"
);
}
const
image
=
await
models
.
upload
.
create
({
url
:
req
.
file
.
path
.
replace
(
/
\\
/g
,
'/'
),
name
:
req
.
file
.
filename
,
size
:
req
.
file
.
size
,
type
:
req
.
file
.
mimetype
});
return
image
;
}
async
deleteImageById
(
id
:
string
)
{
const
image
=
await
models
.
upload
.
findOne
({
where
:
{
id
}
});
if
(
!
image
)
{
throw
new
Error
(
"Image not found!"
);
}
const
filePath
=
path
.
join
(
process
.
cwd
(),
image
.
url
);
if
(
fs
.
existsSync
(
filePath
))
{
fs
.
unlinkSync
(
filePath
);
await
image
.
destroy
();
}
else
{
console
.
warn
(
`File tồn tại trong DB nhưng không tìm thấy trên ổ cứng:
${
filePath
}
`
);
}
return
image
;
}
}
\ No newline at end of file
backend/src/services/uploadService.ts
deleted
100644 → 0
View file @
cfeeda3b
import
{
Readable
}
from
"stream"
;
import
cloudinary
from
"#config/cloudinary.config"
;
type
UploadResult
=
{
width
?:
number
;
height
?:
number
;
bytes
?:
number
;
url
?:
string
;
[
key
:
string
]:
any
;
};
export
class
UploadService
{
async
uploadImage
(
buffer
:
Buffer
):
Promise
<
UploadResult
|
undefined
>
{
return
new
Promise
((
resolve
,
reject
)
=>
{
const
stream
=
cloudinary
.
uploader
.
upload_stream
(
{
folder
:
"images"
},
(
error
,
result
)
=>
{
if
(
error
)
return
reject
(
error
);
resolve
(
result
as
UploadResult
);
}
);
Readable
.
from
(
buffer
).
pipe
(
stream
);
});
}
}
\ No newline at end of file
backend/src/templates/swagger/upload/schemas.ts
View file @
4cd74144
const
uploadSchemas
=
{
UploadResponse
:
{
postInput
:
{
type
:
'object'
,
example
:
{
file
:
'image.jpg'
,
},
properties
:
{
file
:
{
type
:
'string'
,
format
:
'binary'
,
example
:
'image.jpg'
,
},
},
},
getResponse
:
{
type
:
'object'
,
properties
:
{
message
:
{
...
...
@@ -10,25 +24,132 @@ const uploadSchemas = {
type
:
'string'
,
nullable
:
true
,
},
responseData
:
{
data
:
{
type
:
'array'
,
items
:
{
type
:
'object'
,
properties
:
{
width
:
{
type
:
'number
'
,
example
:
800
,
id
:
{
type
:
'string
'
,
example
:
'123e4567-e89b-12d3-a456-426614174000'
,
},
height
:
{
url
:
{
type
:
'string'
,
example
:
'uploads/image-1780543350715-29021218.png'
,
},
name
:
{
type
:
'string'
,
example
:
'image-1780543350715-29021218.png'
,
},
size
:
{
type
:
'number'
,
example
:
600
,
example
:
150000
,
},
type
:
{
type
:
'string'
,
example
:
'image/jpeg'
,
},
},
},
},
status
:
{
type
:
'string'
,
example
:
'success'
,
},
timeStamp
:
{
type
:
'string'
,
example
:
'2024-02-26 03:12:45'
,
},
violations
:
{
type
:
'array'
,
},
},
},
getIdResponse
:
{
type
:
'object'
,
properties
:
{
message
:
{
type
:
'string'
,
nullable
:
true
,
},
message_en
:
{
type
:
'string'
,
nullable
:
true
,
},
data
:
{
type
:
'object'
,
properties
:
{
id
:
{
type
:
'string'
,
example
:
'123e4567-e89b-12d3-a456-426614174000'
,
},
bytes
:
{
url
:
{
type
:
'string'
,
example
:
'uploads/image-1780543350715-29021218.png'
,
},
name
:
{
type
:
'string'
,
example
:
'image-1780543350715-29021218.png'
,
},
size
:
{
type
:
'number'
,
example
:
150000
,
},
type
:
{
type
:
'string'
,
example
:
'image/jpeg'
,
},
},
},
status
:
{
type
:
'string'
,
example
:
'success'
,
},
timeStamp
:
{
type
:
'string'
,
example
:
'2024-02-26 03:12:45'
,
},
violations
:
{
type
:
'array'
,
},
},
},
postResponse
:
{
type
:
'object'
,
properties
:
{
message
:
{
type
:
'string'
,
nullable
:
true
,
},
message_en
:
{
type
:
'string'
,
nullable
:
true
,
},
responseData
:
{
type
:
'object'
,
properties
:
{
id
:
{
type
:
'string'
,
example
:
'123e4567-e89b-12d3-a456-426614174000'
,
},
url
:
{
type
:
'string'
,
example
:
'
https://res.cloudinary.com/demo/image/upload/v1312461204/sample.jpg'
,
example
:
'
uploads/image-1780543350715-29021218.png'
},
name
:
{
type
:
'string'
,
example
:
'image-1780543350715-29021218.png'
},
size
:
{
type
:
'number'
,
example
:
15078
},
type
:
{
type
:
'string'
,
example
:
'image/png'
}
},
},
status
:
{
...
...
@@ -45,16 +166,52 @@ const uploadSchemas = {
},
},
UploadInput
:
{
deleteResponse
:
{
type
:
'object'
,
example
:
{
file
:
'image.jpg'
,
properties
:
{
message
:
{
type
:
'string'
,
nullable
:
true
,
},
message_en
:
{
type
:
'string'
,
nullable
:
true
,
},
data
:
{
type
:
'object'
,
properties
:
{
file
:
{
id
:
{
type
:
'string'
,
format
:
'binary'
,
example
:
'image.jpg'
,
example
:
'123e4567-e89b-12d3-a456-426614174000'
,
},
url
:
{
type
:
'string'
,
example
:
'uploads/image-1780543350715-29021218.png'
,
},
name
:
{
type
:
'string'
,
example
:
'image-1780543350715-29021218.png'
,
},
size
:
{
type
:
'number'
,
example
:
150000
,
},
type
:
{
type
:
'string'
,
example
:
'image/jpeg'
,
},
},
},
status
:
{
type
:
'string'
,
example
:
'success'
,
},
timeStamp
:
{
type
:
'string'
,
example
:
'2024-02-26 03:12:45'
,
},
violations
:
{
type
:
'array'
,
},
},
},
...
...
backend/uploads/image-1780547195335-108126692.png
0 → 100644
View file @
4cd74144
14.7 KB
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