Skip to content

Commit 1a422c9

Browse files
authored
Merge pull request #115 from DigiChanges/feat/GC/NRS-29/optimize-files
feat: optimize multipart files
2 parents 6eec066 + 720c566 commit 1a422c9

24 files changed

+333
-15
lines changed

Dockerfile

+12-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,17 @@
11
FROM node:16-alpine as dev
22

3-
RUN apk add bash dumb-init
3+
RUN apk add bash dumb-init curl
4+
RUN curl -s https://raw.githubusercontent.com/Intervox/node-webp/latest/bin/install_webp | bash
5+
RUN apk add --no-cache --update libwebp-tools
6+
RUN apk add --no-cache --update libpng-dev libjpeg-turbo-dev giflib-dev tiff-dev autoconf automake make gcc g++ wget
7+
RUN wget --no-check-certificate https://storage.googleapis.com/downloads.webmproject.org/releases/webp/libwebp-1.0.0.tar.gz && \
8+
tar -xvzf libwebp-1.0.0.tar.gz && \
9+
cd libwebp-1.0.0 && \
10+
./configure && \
11+
make && \
12+
make install && \
13+
cd .. && \
14+
rm -rf libwebp-1.0.0 libwebp-1.0.0.tar.gz
415

516
WORKDIR /usr/app
617

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
"compression": "^1.7.4",
4545
"config": "^3.3.8",
4646
"cors": "^2.8.5",
47+
"cwebp": "^2.0.5",
4748
"dayjs": "^1.11.5",
4849
"dotenv": "^16.0.2",
4950
"envalid": "^7.3.1",

src/File/Domain/Entities/File.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ class File extends Base implements IFileDomain
2222
this.setName(data?.hasOriginalName ?? false);
2323
}
2424

25-
private setName(hasOriginalName: boolean)
25+
public setName(hasOriginalName: boolean)
2626
{
2727
this.name = this._id;
2828

src/File/Domain/Entities/IFileDomain.ts

+1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ interface IFileDomain extends IBaseDomain
1010
size: number;
1111
version: number;
1212
isPublic: boolean;
13+
setName(hasOriginalName: boolean): void;
1314
}
1415

1516
export default IFileDomain;
+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
interface IFileMultipart
2+
{
3+
fieldname: string;
4+
originalname: string;
5+
encoding: string;
6+
mimetype: string;
7+
destination: string;
8+
filename: string;
9+
path: string;
10+
size: number;
11+
}
12+
13+
export default IFileMultipart;

src/File/Domain/Payloads/FileBase64RepPayload.ts

+2-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
1-
import FileOptionsQueryPayload from './FileOptionsQueryPayload';
2-
import FileRepPayload from './FileRepPayload';
1+
import FilePayload from './FilePayload';
32

4-
interface FileBase64RepPayload extends FileRepPayload, FileOptionsQueryPayload
3+
interface FileBase64RepPayload extends FilePayload
54
{
65
base64: string,
76
}
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
import FileOptionsQueryPayload from './FileOptionsQueryPayload';
2-
import FileRepPayload from './FileRepPayload';
1+
import IFileMultipart from '../Entities/IFileMultipart';
2+
import FilePayload from './FilePayload';
33

4-
interface FileMultipartRepPayload extends FileRepPayload, FileOptionsQueryPayload
4+
interface FileMultipartRepPayload extends FilePayload
55
{
6-
file: any; // TODO: Add interface
6+
file: IFileMultipart;
77
}
88

99
export default FileMultipartRepPayload;

src/File/Domain/Payloads/FileOptionsQueryPayload.ts

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ interface FileOptionsQueryPayload
44
isOriginalName: boolean;
55
isPublic: boolean;
66
isOverwrite: boolean;
7+
isOptimize: boolean;
78
}
89

910
export default FileOptionsQueryPayload;
+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import FileRepPayload from './FileRepPayload';
2+
import FileOptionsQueryPayload from './FileOptionsQueryPayload';
3+
4+
interface FilePayload extends FileRepPayload, FileOptionsQueryPayload {}
5+
6+
export default FilePayload;

src/File/Domain/Payloads/FileRepPayload.ts

+1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ interface FileRepPayload
77
extension: string;
88
size: number;
99
isPublic: boolean;
10+
isImage: boolean;
1011
}
1112

1213
export default FileRepPayload;

src/File/Domain/Services/FileService.ts

+76
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,17 @@ import IFileDTO from '../Models/IFileDTO';
1616
import { validate } from 'uuid';
1717
import { getRequestContext } from '../../../Shared/Presentation/Shared/RequestContext';
1818
import IFilesystem from '../../../Shared/Infrastructure/Filesystem/IFilesystem';
19+
// @ts-ignore
20+
import { CWebp } from 'cwebp';
21+
import IFileMultipart from '../Entities/IFileMultipart';
22+
import FileMultipartOptimizeDTO from '../../Presentation/Requests/FileMultipartOptimizeDTO';
23+
import FileBase64OptimizeDTO from '../../Presentation/Requests/FileBase64OptimizeDTO';
24+
import * as fs from 'fs';
25+
import FileUpdateMultipartPayload from '../Payloads/FileUpdateMultipartPayload';
26+
import FileUpdateMultipartOptimizeDTO from '../../Presentation/Requests/FileUpdateMultipartOptimizeDTO';
27+
import FileUpdateBase64Payload from '../Payloads/FileUpdateBase64Payload';
28+
import FileUpdateBase64OptimizeDTO from '../../Presentation/Requests/FileUpdateBase64OptimizeDTO';
29+
import FilePayload from '../Payloads/FilePayload';
1930

2031
class FileService
2132
{
@@ -59,6 +70,14 @@ class FileService
5970
return await this.repository.save(file);
6071
}
6172

73+
async update(file: IFileDomain, payload: FilePayload): Promise<IFileDomain>
74+
{
75+
file.originalName = payload.originalName;
76+
file.setName(payload.isOriginalName);
77+
78+
return await this.persist(file, payload);
79+
}
80+
6281
async uploadFileBase64(file: IFileDomain, payload: FileBase64RepPayload): Promise<any>
6382
{
6483
await this.fileSystem.uploadFileByBuffer(file, payload.base64);
@@ -130,6 +149,63 @@ class FileService
130149
void await this.fileSystem.removeObjects(file);
131150
return file;
132151
}
152+
153+
private async getFileMultipartOptimized(payload: FileMultipartRepPayload): Promise<IFileMultipart>
154+
{
155+
const encoder = CWebp(payload.file.path);
156+
const newPath = payload.file.path.replace(payload.extension, 'webp');
157+
await encoder.write(newPath);
158+
159+
return {
160+
fieldname: payload.file.fieldname,
161+
originalname: payload.file.originalname.replace(payload.extension, 'webp'),
162+
encoding: payload.file.encoding,
163+
mimetype: 'image/webp',
164+
destination: payload.file.destination,
165+
filename: payload.file.filename.replace(payload.extension, 'webp'),
166+
path: newPath,
167+
size: payload.size
168+
};
169+
}
170+
171+
private async getFileBase64Optimized(payload: FileBase64RepPayload): Promise<string>
172+
{
173+
const buffer = Buffer.from(payload.base64, 'base64');
174+
const encoder = CWebp(buffer);
175+
const newPath = '/tmp/converted.webp';
176+
await encoder.write(newPath);
177+
178+
const buff = fs.readFileSync(newPath);
179+
return buff.toString('base64');
180+
}
181+
182+
async optimizeMultipartToUpload(payload: FileMultipartRepPayload): Promise<FileMultipartRepPayload>
183+
{
184+
const file = await this.getFileMultipartOptimized(payload);
185+
186+
return new FileMultipartOptimizeDTO(payload, file);
187+
}
188+
189+
async optimizeMultipartToUpdate(payload: FileUpdateMultipartPayload): Promise<FileUpdateMultipartPayload>
190+
{
191+
const file = await this.getFileMultipartOptimized(payload);
192+
193+
return new FileUpdateMultipartOptimizeDTO(payload, file);
194+
}
195+
196+
async optimizeBase64ToUpload(payload: FileBase64RepPayload): Promise<FileBase64RepPayload>
197+
{
198+
const base64data = await this.getFileBase64Optimized(payload);
199+
200+
return new FileBase64OptimizeDTO(payload, base64data);
201+
}
202+
203+
async optimizeBase64ToUpdate(payload: FileUpdateBase64Payload): Promise<FileUpdateBase64Payload>
204+
{
205+
const base64data = await this.getFileBase64Optimized(payload);
206+
207+
return new FileUpdateBase64OptimizeDTO(payload, base64data);
208+
}
133209
}
134210

135211
export default FileService;

src/File/Domain/UseCases/UpdateFileBase64UseCase.ts

+7-1
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,14 @@ class UpdateFileBase64UseCase
99
async handle(payload: FileUpdateBase64Payload): Promise<any>
1010
{
1111
const { id } = payload;
12+
13+
if (payload.isOptimize && payload.isImage)
14+
{
15+
payload = await this.fileService.optimizeBase64ToUpdate(payload);
16+
}
17+
1218
let file: IFileDomain = await this.fileService.getOne(id);
13-
file = await this.fileService.persist(file, payload);
19+
file = await this.fileService.update(file, payload);
1420
return await this.fileService.uploadFileBase64(file, payload);
1521
}
1622
}

src/File/Domain/UseCases/UpdateFileMultipartUseCase.ts

+7-1
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,14 @@ class UpdateFileMultipartUseCase
99
async handle(payload: FileUpdateMultipartPayload): Promise<any>
1010
{
1111
const { id } = payload;
12+
13+
if (payload.isOptimize && payload.isImage)
14+
{
15+
payload = await this.fileService.optimizeMultipartToUpdate(payload);
16+
}
17+
1218
let file: IFileDomain = await this.fileService.getOne(id);
13-
file = await this.fileService.persist(file, payload);
19+
file = await this.fileService.update(file, payload);
1420
return await this.fileService.uploadFileMultipart(file, payload);
1521
}
1622
}

src/File/Domain/UseCases/UploadBase64UseCase.ts

+5
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,11 @@ class UploadBase64UseCase
99

1010
async handle(payload: FileBase64RepPayload): Promise<any>
1111
{
12+
if (payload.isOptimize && payload.isImage)
13+
{
14+
payload = await this.fileService.optimizeBase64ToUpload(payload);
15+
}
16+
1217
const build = {
1318
hasOriginalName: payload.isOriginalName,
1419
originalName: payload.originalName

src/File/Domain/UseCases/UploadMultipartUseCase.ts

+5
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,11 @@ class UploadMultipartUseCase
99

1010
async handle(payload: FileMultipartRepPayload): Promise<any>
1111
{
12+
if (payload.isOptimize && payload.isImage)
13+
{
14+
payload = await this.fileService.optimizeMultipartToUpload(payload);
15+
}
16+
1217
const build = {
1318
hasOriginalName: payload.isOriginalName,
1419
originalName: payload.originalName

src/File/Presentation/Handlers/FileExpressHandler.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ class FileExpressHandler
148148
{
149149
const body = {
150150
file: req.file,
151-
params: req.params,
151+
query: req.query,
152152
id: req.params.id
153153
};
154154

src/File/Presentation/Handlers/FileKoaHandler.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ FileKoaHandler.put('/:id', <any>FileKoaReqMulterMiddleware.single('file'), Autho
132132
{
133133
const body = {
134134
file: ctx.request.file,
135-
params: ctx.params,
135+
query: ctx.query,
136136
id: ctx.params.id
137137
};
138138

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import FileBase64RepPayload from '../../Domain/Payloads/FileBase64RepPayload';
2+
3+
class FileBase64OptimizeDTO implements FileBase64RepPayload
4+
{
5+
private readonly _req: FileBase64RepPayload;
6+
private readonly _base64: string;
7+
8+
constructor(fileRequest: FileBase64RepPayload, _base64: string)
9+
{
10+
this._req = fileRequest;
11+
this._base64 = _base64;
12+
}
13+
14+
get extension(): string
15+
{
16+
return 'webp';
17+
}
18+
19+
get base64(): string
20+
{
21+
return this._base64;
22+
}
23+
get isOptimize(): boolean
24+
{
25+
return this._req.isOptimize;
26+
}
27+
get isOriginalName(): boolean
28+
{
29+
return this._req.isOriginalName;
30+
}
31+
get isOverwrite(): boolean
32+
{
33+
return this._req.isOverwrite;
34+
}
35+
get isPublic(): boolean
36+
{
37+
return this._req.isPublic;
38+
}
39+
get mimeType(): string
40+
{
41+
return 'image/webp';
42+
}
43+
get originalName(): string
44+
{
45+
return this._req.originalName.replace(this._req.extension, 'webp');
46+
}
47+
get path(): string
48+
{
49+
return '/';
50+
}
51+
get size(): number
52+
{
53+
return this._req.size;
54+
}
55+
56+
get isImage(): boolean
57+
{
58+
return this._req.isImage;
59+
}
60+
}
61+
62+
export default FileBase64OptimizeDTO;

src/File/Presentation/Requests/FileBase64RepRequest.ts

+5
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,11 @@ class FileBase64RepRequest extends FileOptionsQueryRequest implements FileBase64
5353
{
5454
return this._base64;
5555
}
56+
57+
get isImage(): boolean
58+
{
59+
return this._mimeType.includes('image');
60+
}
5661
}
5762

5863
export default FileBase64RepRequest;

0 commit comments

Comments
 (0)