Skip to content

Commit 31a3ed1

Browse files
committed
feat(master-asset): add compress image on upload
1 parent 55091da commit 31a3ed1

16 files changed

+227
-26
lines changed

.env.example

+3
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,6 @@ AWS_ACCESS_KEY_ID=
2424
AWS_SECRET_ACCESS_KEY=
2525
AWS_REGION=
2626
AWS_BUCKET=
27+
28+
#Generator File
29+
GENERATOR_FILE_URL=

package-lock.json

+48
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
},
4242
"dependencies": {
4343
"@aws-sdk/client-s3": "^3.310.0",
44+
"axios": "^1.7.4",
4445
"body-parser": "^1.20.1",
4546
"compression": "^1.7.4",
4647
"cors": "^2.8.5",

src/config/config.interface.ts

+3
Original file line numberDiff line numberDiff line change
@@ -36,4 +36,7 @@ export interface Config {
3636
bucket: string
3737
region: string
3838
}
39+
generator_file: {
40+
url: string
41+
}
3942
}

src/config/config.schema.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ export default Joi.object({
1111
FILE_URI: Joi.string().uri().optional(),
1212
FILE_TYPE: Joi.string()
1313
.optional()
14-
.default('image/jpg,image/png,image/jpeg,image/svg+xml'),
15-
FILE_MAX: Joi.number().optional().default(10),
14+
.default('image/jpg,image/png,image/jpeg,image/svg+xml,image/webp'),
15+
FILE_MAX: Joi.number().optional().default(50),
1616
DB_HOST: Joi.string().required(),
1717
DB_PORT: Joi.number().required(),
1818
DB_USERNAME: Joi.string().required(),
@@ -25,4 +25,5 @@ export default Joi.object({
2525
AWS_REGION: Joi.string().optional(),
2626
JWT_ACCESS_SECRET: Joi.string().required(),
2727
JWT_ALGORITHM: Joi.string().default('HS256'),
28+
GENERATOR_FILE_URL: Joi.string().uri().optional(),
2829
})

src/config/config.ts

+3
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,9 @@ const config: Config = {
4444
bucket: env.AWS_BUCKET,
4545
region: env.AWS_REGION,
4646
},
47+
generator_file: {
48+
url: env.GENERATOR_FILE_URL,
49+
},
4750
}
4851

4952
export default config

src/external/fileGenerator.ts

+67
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import axios from 'axios'
2+
import { Config } from '../config/config.interface'
3+
import Logger from '../pkg/logger'
4+
import S3 from './s3'
5+
import error from '../pkg/error'
6+
7+
class FileGenerator {
8+
constructor(
9+
private config: Config,
10+
private s3: S3,
11+
private logger: Logger
12+
) {}
13+
14+
public async ImageCompression(
15+
uri: string,
16+
quality: number,
17+
convertTo: string,
18+
path: string
19+
) {
20+
try {
21+
const { url, size } = await this.send('convert-image', {
22+
url: uri,
23+
quality,
24+
convertTo,
25+
})
26+
27+
const { data, headers } = await axios.get(url, {
28+
responseType: 'arraybuffer',
29+
})
30+
31+
const contentType = headers['content-type'] || ''
32+
33+
await this.s3.Upload(data, path, contentType)
34+
this.logger.Info('process compress image success', {
35+
category: 'compress-image',
36+
})
37+
return size
38+
} catch (error: any) {
39+
this.logger.Error(
40+
'process compress image failed: ' + error.message,
41+
{ category: 'compress-image' }
42+
)
43+
throw error
44+
}
45+
}
46+
47+
private async send(path: string, body: object) {
48+
try {
49+
const { data } = await axios.post(
50+
this.config.generator_file.url + '/' + path,
51+
body
52+
)
53+
54+
return data.data
55+
} catch (err: any) {
56+
const message = err.message
57+
58+
this.logger.Error(message, {
59+
category: FileGenerator.name,
60+
})
61+
62+
throw new error(err.status, message)
63+
}
64+
}
65+
}
66+
67+
export default FileGenerator

src/helpers/file.ts

+1-5
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,3 @@
1-
import path from 'path'
2-
31
export const CustomPathFile = (newPath: string, file: any) => {
4-
const ext = path.extname(file.filename)
5-
if (!ext) file.filename = file.filename + path.extname(file.originalname)
6-
return `${newPath}/${file.filename}`
2+
return `${newPath}/${file.originalname}`
73
}

src/helpers/regex.ts

+1
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@ export const RegexSanitize = /^[ a-zA-Z0-9_,.()'"&\?\-/]+$/
44
export const RegexObjectID = /^[0-9a-fA-F]{24}$/
55
export const RegexContentTypeImage = /^image\//
66
export const RegexExtensionImage = /.png|.jpg|.jpeg|.svg|.webp/i
7+
export const RegexContentTypeImageNotCompressed = /image\/svg\+xml|image\/webp/

src/helpers/requestParams.ts

-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ export const GetRequestParams = (query: Record<string, any>): RequestParams => {
1818
sort_order = 'asc'
1919
}
2020

21-
2221
return {
2322
...query,
2423
page,

src/modules/images/delivery/http/handler.ts

+3
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ class Handler {
2323
category: req.body.category,
2424
tags: req.body.tags,
2525
file: req.file || {},
26+
compression: req.body.compression,
27+
quality: req.body.quality,
28+
convertTo: req.body.convertTo,
2629
})
2730
}
2831

src/modules/images/entity/interface.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
export interface Store {
22
file: File
3-
caption: string
43
title: string
5-
description: string
64
category: string
7-
tags: string[]
5+
compression: boolean
6+
quality: number
7+
convertTo: string
88
}
99

1010
export interface File {

src/modules/images/entity/schema.ts

+11-6
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,19 @@ const file = Joi.object({
1313
})
1414

1515
export const Store = Joi.object({
16-
caption: Joi.string().regex(RegexSanitize).optional(),
1716
category: Joi.string().alphanum().required(),
18-
tags: Joi.array()
19-
.items(Joi.string().alphanum())
20-
.optional()
21-
.default([])
22-
.unique((a, b) => a == b, { ignoreUndefined: true }),
2317
title: Joi.string().regex(RegexSanitize).optional().default(null),
2418
description: Joi.string().regex(RegexSanitize).optional().default(null),
19+
compression: Joi.boolean().optional().default(false),
20+
quality: Joi.number().min(1).max(100).when('compression', {
21+
is: true,
22+
then: Joi.required(),
23+
otherwise: Joi.optional(),
24+
}),
25+
convertTo: Joi.string().valid('jpeg', 'webp').when('compression', {
26+
is: true,
27+
then: Joi.required(),
28+
otherwise: Joi.optional(),
29+
}),
2530
file,
2631
})

src/modules/images/images.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import Handler from './delivery/http/handler'
55
import Repository from './repository/mongo/repository'
66
import { Config } from '../../config/config.interface'
77
import S3 from '../../external/s3'
8+
import FileGenerator from '../../external/fileGenerator'
89

910
class Images {
1011
constructor(
@@ -13,8 +14,9 @@ class Images {
1314
private config: Config
1415
) {
1516
const s3 = new S3(config)
17+
const fileGenerator = new FileGenerator(config, s3, logger)
1618
const repository = new Repository(logger)
17-
const usecase = new Usecase(logger, repository, s3)
19+
const usecase = new Usecase(logger, repository, s3, fileGenerator)
1820
this.loadHttp(usecase)
1921
}
2022

src/modules/images/repository/mongo/repository.ts

+10
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,16 @@ class Repository {
5353

5454
return schemaNew.save()
5555
}
56+
57+
public async UpdateSize(id: string, size: number) {
58+
return imageSchema.findByIdAndUpdate(id, {
59+
'file.size': size,
60+
})
61+
}
62+
63+
public async FindByPath(path: string) {
64+
return imageSchema.findOne({ 'file.path': path }).exec()
65+
}
5666
}
5767

5868
export default Repository

0 commit comments

Comments
 (0)