rl-core-api 0.15.6 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/core/bootstrap/bootstrapCore.util.js +1 -1
- package/dist/core/config/env.schema.d.ts +7 -0
- package/dist/core/config/env.schema.js +4 -0
- package/dist/core/images/imageCleanup.service.d.ts +9 -0
- package/dist/core/images/imageCleanup.service.js +50 -0
- package/dist/core/images/imageFetch.service.d.ts +11 -0
- package/dist/core/images/imageFetch.service.js +190 -0
- package/dist/core/images/imageProcessing.service.d.ts +9 -0
- package/dist/core/images/imageProcessing.service.js +74 -0
- package/dist/core/images/imageProfile.interface.d.ts +40 -0
- package/dist/core/images/imageProfile.interface.js +29 -0
- package/dist/core/images/imageProfile.registry.d.ts +14 -0
- package/dist/core/images/imageProfile.registry.js +61 -0
- package/dist/core/images/imageStorage.service.d.ts +28 -0
- package/dist/core/images/imageStorage.service.js +151 -0
- package/dist/core/images/images.controller.d.ts +16 -0
- package/dist/core/images/images.controller.js +142 -0
- package/dist/core/images/images.module.d.ts +2 -0
- package/dist/core/images/images.module.js +35 -0
- package/dist/core/images/images.processor.d.ts +18 -0
- package/dist/core/images/images.processor.js +75 -0
- package/dist/core/images/images.service.d.ts +34 -0
- package/dist/core/images/images.service.js +137 -0
- package/dist/core/images/imagesApi.module.d.ts +2 -0
- package/dist/core/images/imagesApi.module.js +25 -0
- package/dist/core/images/imagesWorker.module.d.ts +2 -0
- package/dist/core/images/imagesWorker.module.js +22 -0
- package/dist/core/images/responses/imageAccepted.response.d.ts +4 -0
- package/dist/core/images/responses/imageAccepted.response.js +28 -0
- package/dist/core/images/responses/imageProfile.response.d.ts +10 -0
- package/dist/core/images/responses/imageProfile.response.js +55 -0
- package/dist/core/images/responses/imageStatus.response.d.ts +7 -0
- package/dist/core/images/responses/imageStatus.response.js +36 -0
- package/dist/core/images/uploadImageFromUrl.command.d.ts +3 -0
- package/dist/core/images/uploadImageFromUrl.command.js +23 -0
- package/dist/core/queue/job.producer.d.ts +1 -1
- package/dist/core/queue/job.producer.js +7 -1
- package/dist/core/queue/queue.constants.d.ts +4 -1
- package/dist/core/queue/queue.constants.js +5 -2
- package/dist/features/users/domain/avatarImage.profile.d.ts +13 -0
- package/dist/features/users/domain/avatarImage.profile.js +30 -0
- package/dist/features/users/domain/index.d.ts +1 -1
- package/dist/features/users/domain/index.js +1 -1
- package/dist/features/users/presentation/users.controller.d.ts +6 -5
- package/dist/features/users/presentation/users.controller.js +28 -15
- package/dist/features/users/users.module.js +3 -1
- package/dist/index.d.ts +17 -1
- package/dist/index.js +43 -2
- package/dist/rlCore.module.js +5 -1
- package/package.json +1 -1
- package/dist/features/users/domain/avatar.service.d.ts +0 -5
- package/dist/features/users/domain/avatar.service.js +0 -61
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
var ImageStorageService_1;
|
|
12
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
+
exports.ImageStorageService = void 0;
|
|
14
|
+
const common_1 = require("@nestjs/common");
|
|
15
|
+
const crypto_1 = require("crypto");
|
|
16
|
+
const promises_1 = require("fs/promises");
|
|
17
|
+
const path_1 = require("path");
|
|
18
|
+
const env_service_1 = require("../config/env.service");
|
|
19
|
+
const imageProfile_interface_1 = require("./imageProfile.interface");
|
|
20
|
+
const SAFE_ID = /^[A-Za-z0-9_-]{1,64}$/;
|
|
21
|
+
const SAFE_FOLDER = /^[A-Za-z0-9_-]{1,64}$/;
|
|
22
|
+
const PENDING = "pending";
|
|
23
|
+
const PUBLIC_PATH = /^\/uploads\/(?<folder>[A-Za-z0-9_-]{1,64})\/(?:(?<pending>pending)\/)?(?<id>[A-Za-z0-9_-]{1,64})(?<ext>\.[a-z]{3,4})$/;
|
|
24
|
+
let ImageStorageService = ImageStorageService_1 = class ImageStorageService {
|
|
25
|
+
constructor(env) {
|
|
26
|
+
this.env = env;
|
|
27
|
+
this.logger = new common_1.Logger(ImageStorageService_1.name);
|
|
28
|
+
this.uploadsRoot = this.absolute(this.env.get("UPLOADS_DIR"));
|
|
29
|
+
this.incomingRoot = this.absolute(this.env.get("IMAGE_INCOMING_DIR"));
|
|
30
|
+
}
|
|
31
|
+
async saveIncoming(buffer) {
|
|
32
|
+
const id = (0, crypto_1.randomUUID)();
|
|
33
|
+
const path = this.incomingPathFor(id);
|
|
34
|
+
await (0, promises_1.mkdir)((0, path_1.dirname)(path), { recursive: true });
|
|
35
|
+
await (0, promises_1.writeFile)(path, buffer);
|
|
36
|
+
return id;
|
|
37
|
+
}
|
|
38
|
+
async readIncoming(id) {
|
|
39
|
+
return await (0, promises_1.readFile)(this.incomingPathFor(id));
|
|
40
|
+
}
|
|
41
|
+
async removeIncoming(id) {
|
|
42
|
+
await (0, promises_1.rm)(this.incomingPathFor(id), { force: true });
|
|
43
|
+
}
|
|
44
|
+
async savePending(profile, data) {
|
|
45
|
+
const id = (0, crypto_1.randomUUID)();
|
|
46
|
+
const path = this.pathFor(profile.folder, id, imageProfile_interface_1.EXTENSION_BY_FORMAT[profile.format], true);
|
|
47
|
+
await (0, promises_1.mkdir)((0, path_1.dirname)(path), { recursive: true });
|
|
48
|
+
await (0, promises_1.writeFile)(path, data);
|
|
49
|
+
return this.publicPathOf(profile.folder, id, imageProfile_interface_1.EXTENSION_BY_FORMAT[profile.format], true);
|
|
50
|
+
}
|
|
51
|
+
async promote(publicPath) {
|
|
52
|
+
const ref = this.parsePublicPath(publicPath);
|
|
53
|
+
if (!ref) {
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
if (!ref.pending) {
|
|
57
|
+
return publicPath;
|
|
58
|
+
}
|
|
59
|
+
const from = this.pathFor(ref.folder, ref.id, ref.extension, true);
|
|
60
|
+
const to = this.pathFor(ref.folder, ref.id, ref.extension, false);
|
|
61
|
+
await (0, promises_1.mkdir)((0, path_1.dirname)(to), { recursive: true });
|
|
62
|
+
await (0, promises_1.rename)(from, to);
|
|
63
|
+
return this.publicPathOf(ref.folder, ref.id, ref.extension, false);
|
|
64
|
+
}
|
|
65
|
+
async remove(publicPath) {
|
|
66
|
+
const ref = this.parsePublicPath(publicPath);
|
|
67
|
+
if (!ref) {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
await (0, promises_1.rm)(this.pathFor(ref.folder, ref.id, ref.extension, ref.pending), {
|
|
71
|
+
force: true,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
parsePublicPath(publicPath) {
|
|
75
|
+
const match = PUBLIC_PATH.exec(publicPath ?? "");
|
|
76
|
+
if (!match?.groups) {
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
return {
|
|
80
|
+
folder: match.groups.folder,
|
|
81
|
+
id: match.groups.id,
|
|
82
|
+
extension: match.groups.ext,
|
|
83
|
+
pending: match.groups.pending === PENDING,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
async removeExpired(pendingMaxAgeMs, incomingMaxAgeMs) {
|
|
87
|
+
const folders = await (0, promises_1.readdir)(this.uploadsRoot, { withFileTypes: true }).catch(() => []);
|
|
88
|
+
let removed = 0;
|
|
89
|
+
for (const folder of folders) {
|
|
90
|
+
if (!folder.isDirectory() || !SAFE_FOLDER.test(folder.name)) {
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
removed += await this.sweep((0, path_1.join)(this.uploadsRoot, folder.name, PENDING), pendingMaxAgeMs);
|
|
94
|
+
}
|
|
95
|
+
removed += await this.sweep(this.incomingRoot, incomingMaxAgeMs);
|
|
96
|
+
if (removed > 0) {
|
|
97
|
+
this.logger.log(`Faxina de imagens: ${removed} arquivo(s) removido(s)`);
|
|
98
|
+
}
|
|
99
|
+
return removed;
|
|
100
|
+
}
|
|
101
|
+
pathFor(folder, id, extension, pending) {
|
|
102
|
+
if (!SAFE_FOLDER.test(folder) || !SAFE_ID.test(id)) {
|
|
103
|
+
throw new Error("Identificador inválido para caminho de imagem");
|
|
104
|
+
}
|
|
105
|
+
const path = pending
|
|
106
|
+
? (0, path_1.resolve)(this.uploadsRoot, folder, PENDING, `${id}${extension}`)
|
|
107
|
+
: (0, path_1.resolve)(this.uploadsRoot, folder, `${id}${extension}`);
|
|
108
|
+
if (!path.startsWith((0, path_1.resolve)(this.uploadsRoot))) {
|
|
109
|
+
throw new Error("Caminho de imagem fora da pasta de uploads");
|
|
110
|
+
}
|
|
111
|
+
return path;
|
|
112
|
+
}
|
|
113
|
+
incomingPathFor(id) {
|
|
114
|
+
if (!SAFE_ID.test(id)) {
|
|
115
|
+
throw new Error("Identificador inválido para arquivo recebido");
|
|
116
|
+
}
|
|
117
|
+
const path = (0, path_1.resolve)(this.incomingRoot, id);
|
|
118
|
+
if (!path.startsWith((0, path_1.resolve)(this.incomingRoot))) {
|
|
119
|
+
throw new Error("Caminho fora da pasta de arquivos recebidos");
|
|
120
|
+
}
|
|
121
|
+
return path;
|
|
122
|
+
}
|
|
123
|
+
publicPathOf(folder, id, extension, pending) {
|
|
124
|
+
return pending
|
|
125
|
+
? `/uploads/${folder}/${PENDING}/${id}${extension}`
|
|
126
|
+
: `/uploads/${folder}/${id}${extension}`;
|
|
127
|
+
}
|
|
128
|
+
absolute(configured) {
|
|
129
|
+
return (0, path_1.isAbsolute)(configured) ? configured : (0, path_1.join)(process.cwd(), configured);
|
|
130
|
+
}
|
|
131
|
+
async sweep(directory, maxAgeMs) {
|
|
132
|
+
const cutoff = Date.now() - maxAgeMs;
|
|
133
|
+
const files = await (0, promises_1.readdir)(directory).catch(() => []);
|
|
134
|
+
let removed = 0;
|
|
135
|
+
for (const file of files) {
|
|
136
|
+
const path = (0, path_1.join)(directory, file);
|
|
137
|
+
const info = await (0, promises_1.stat)(path).catch(() => null);
|
|
138
|
+
if (!info?.isFile() || info.mtimeMs > cutoff) {
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
await (0, promises_1.rm)(path, { force: true });
|
|
142
|
+
removed += 1;
|
|
143
|
+
}
|
|
144
|
+
return removed;
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
exports.ImageStorageService = ImageStorageService;
|
|
148
|
+
exports.ImageStorageService = ImageStorageService = ImageStorageService_1 = __decorate([
|
|
149
|
+
(0, common_1.Injectable)(),
|
|
150
|
+
__metadata("design:paramtypes", [env_service_1.EnvService])
|
|
151
|
+
], ImageStorageService);
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { AuthenticatedUser } from "../auth/authenticatedUser.interface";
|
|
2
|
+
import { ImagesService } from "./images.service";
|
|
3
|
+
import { ImageAcceptedResponse } from "./responses/imageAccepted.response";
|
|
4
|
+
import { ImageProfileResponse } from "./responses/imageProfile.response";
|
|
5
|
+
import { ImageStatusResponse } from "./responses/imageStatus.response";
|
|
6
|
+
import { UploadImageFromUrlCommand } from "./uploadImageFromUrl.command";
|
|
7
|
+
export declare class ImagesController {
|
|
8
|
+
private readonly images;
|
|
9
|
+
constructor(images: ImagesService);
|
|
10
|
+
listProfiles(): ImageProfileResponse[];
|
|
11
|
+
getProfile(name: string): ImageProfileResponse;
|
|
12
|
+
upload(user: AuthenticatedUser, profile: string, file: Express.Multer.File): Promise<ImageAcceptedResponse>;
|
|
13
|
+
uploadFromUrl(user: AuthenticatedUser, profile: string, command: UploadImageFromUrlCommand): Promise<ImageAcceptedResponse>;
|
|
14
|
+
status(user: AuthenticatedUser, jobId: string): Promise<ImageStatusResponse>;
|
|
15
|
+
remove(user: AuthenticatedUser, profile: string, path: string): Promise<void>;
|
|
16
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
12
|
+
return function (target, key) { decorator(target, key, paramIndex); }
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.ImagesController = void 0;
|
|
16
|
+
const common_1 = require("@nestjs/common");
|
|
17
|
+
const platform_express_1 = require("@nestjs/platform-express");
|
|
18
|
+
const swagger_1 = require("@nestjs/swagger");
|
|
19
|
+
const multer_1 = require("multer");
|
|
20
|
+
const currentUser_decorator_1 = require("../auth/decorators/currentUser.decorator");
|
|
21
|
+
const permissions_guard_1 = require("../auth/guards/permissions.guard");
|
|
22
|
+
const images_service_1 = require("./images.service");
|
|
23
|
+
const imageAccepted_response_1 = require("./responses/imageAccepted.response");
|
|
24
|
+
const imageProfile_response_1 = require("./responses/imageProfile.response");
|
|
25
|
+
const imageStatus_response_1 = require("./responses/imageStatus.response");
|
|
26
|
+
const uploadImageFromUrl_command_1 = require("./uploadImageFromUrl.command");
|
|
27
|
+
const ROUTE_MAX_BYTES = 20 * 1024 * 1024;
|
|
28
|
+
const ACCEPTED_TYPES = ["image/jpeg", "image/png", "image/webp"];
|
|
29
|
+
let ImagesController = class ImagesController {
|
|
30
|
+
constructor(images) {
|
|
31
|
+
this.images = images;
|
|
32
|
+
}
|
|
33
|
+
listProfiles() {
|
|
34
|
+
return this.images.profiles().map(toProfileResponse);
|
|
35
|
+
}
|
|
36
|
+
getProfile(name) {
|
|
37
|
+
return toProfileResponse(this.images.profile(name));
|
|
38
|
+
}
|
|
39
|
+
async upload(user, profile, file) {
|
|
40
|
+
return await this.images.fromFile(user, profile, file);
|
|
41
|
+
}
|
|
42
|
+
async uploadFromUrl(user, profile, command) {
|
|
43
|
+
return await this.images.fromUrl(user, profile, command.url);
|
|
44
|
+
}
|
|
45
|
+
async status(user, jobId) {
|
|
46
|
+
const job = await this.images.status(jobId, user.id);
|
|
47
|
+
const summary = job.summary;
|
|
48
|
+
return {
|
|
49
|
+
state: job.state,
|
|
50
|
+
percent: job.progress?.percent ?? 0,
|
|
51
|
+
path: summary?.path ?? null,
|
|
52
|
+
errorMessage: job.error?.message ?? null,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
async remove(user, profile, path) {
|
|
56
|
+
await this.images.remove(user, profile, path);
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
exports.ImagesController = ImagesController;
|
|
60
|
+
__decorate([
|
|
61
|
+
(0, common_1.Get)("profiles"),
|
|
62
|
+
(0, swagger_1.ApiOkResponse)({ type: imageProfile_response_1.ImageProfileResponse, isArray: true }),
|
|
63
|
+
__metadata("design:type", Function),
|
|
64
|
+
__metadata("design:paramtypes", []),
|
|
65
|
+
__metadata("design:returntype", Array)
|
|
66
|
+
], ImagesController.prototype, "listProfiles", null);
|
|
67
|
+
__decorate([
|
|
68
|
+
(0, common_1.Get)("profiles/:name"),
|
|
69
|
+
(0, swagger_1.ApiParam)({ name: "name", example: "funkos" }),
|
|
70
|
+
(0, swagger_1.ApiOkResponse)({ type: imageProfile_response_1.ImageProfileResponse }),
|
|
71
|
+
__param(0, (0, common_1.Param)("name")),
|
|
72
|
+
__metadata("design:type", Function),
|
|
73
|
+
__metadata("design:paramtypes", [String]),
|
|
74
|
+
__metadata("design:returntype", imageProfile_response_1.ImageProfileResponse)
|
|
75
|
+
], ImagesController.prototype, "getProfile", null);
|
|
76
|
+
__decorate([
|
|
77
|
+
(0, common_1.Post)(":profile"),
|
|
78
|
+
(0, common_1.HttpCode)(common_1.HttpStatus.ACCEPTED),
|
|
79
|
+
(0, swagger_1.ApiParam)({ name: "profile", example: "funkos" }),
|
|
80
|
+
(0, swagger_1.ApiConsumes)("multipart/form-data"),
|
|
81
|
+
(0, swagger_1.ApiResponse)({ status: 202, type: imageAccepted_response_1.ImageAcceptedResponse }),
|
|
82
|
+
(0, common_1.UseInterceptors)((0, platform_express_1.FileInterceptor)("file", {
|
|
83
|
+
storage: (0, multer_1.memoryStorage)(),
|
|
84
|
+
limits: { fileSize: ROUTE_MAX_BYTES, files: 1 },
|
|
85
|
+
})),
|
|
86
|
+
__param(0, (0, currentUser_decorator_1.CurrentUser)()),
|
|
87
|
+
__param(1, (0, common_1.Param)("profile")),
|
|
88
|
+
__param(2, (0, common_1.UploadedFile)()),
|
|
89
|
+
__metadata("design:type", Function),
|
|
90
|
+
__metadata("design:paramtypes", [Object, String, Object]),
|
|
91
|
+
__metadata("design:returntype", Promise)
|
|
92
|
+
], ImagesController.prototype, "upload", null);
|
|
93
|
+
__decorate([
|
|
94
|
+
(0, common_1.Post)(":profile/from-url"),
|
|
95
|
+
(0, common_1.HttpCode)(common_1.HttpStatus.ACCEPTED),
|
|
96
|
+
(0, swagger_1.ApiParam)({ name: "profile", example: "funkos" }),
|
|
97
|
+
(0, swagger_1.ApiResponse)({ status: 202, type: imageAccepted_response_1.ImageAcceptedResponse }),
|
|
98
|
+
__param(0, (0, currentUser_decorator_1.CurrentUser)()),
|
|
99
|
+
__param(1, (0, common_1.Param)("profile")),
|
|
100
|
+
__param(2, (0, common_1.Body)()),
|
|
101
|
+
__metadata("design:type", Function),
|
|
102
|
+
__metadata("design:paramtypes", [Object, String, uploadImageFromUrl_command_1.UploadImageFromUrlCommand]),
|
|
103
|
+
__metadata("design:returntype", Promise)
|
|
104
|
+
], ImagesController.prototype, "uploadFromUrl", null);
|
|
105
|
+
__decorate([
|
|
106
|
+
(0, common_1.Get)(":jobId/status"),
|
|
107
|
+
(0, swagger_1.ApiParam)({ name: "jobId", example: "312" }),
|
|
108
|
+
(0, swagger_1.ApiOkResponse)({ type: imageStatus_response_1.ImageStatusResponse }),
|
|
109
|
+
__param(0, (0, currentUser_decorator_1.CurrentUser)()),
|
|
110
|
+
__param(1, (0, common_1.Param)("jobId")),
|
|
111
|
+
__metadata("design:type", Function),
|
|
112
|
+
__metadata("design:paramtypes", [Object, String]),
|
|
113
|
+
__metadata("design:returntype", Promise)
|
|
114
|
+
], ImagesController.prototype, "status", null);
|
|
115
|
+
__decorate([
|
|
116
|
+
(0, common_1.Delete)(":profile"),
|
|
117
|
+
(0, common_1.HttpCode)(common_1.HttpStatus.NO_CONTENT),
|
|
118
|
+
(0, swagger_1.ApiParam)({ name: "profile", example: "funkos" }),
|
|
119
|
+
(0, swagger_1.ApiResponse)({ status: 204, description: "Apagada, ou já não existia" }),
|
|
120
|
+
__param(0, (0, currentUser_decorator_1.CurrentUser)()),
|
|
121
|
+
__param(1, (0, common_1.Param)("profile")),
|
|
122
|
+
__param(2, (0, common_1.Query)("path")),
|
|
123
|
+
__metadata("design:type", Function),
|
|
124
|
+
__metadata("design:paramtypes", [Object, String, String]),
|
|
125
|
+
__metadata("design:returntype", Promise)
|
|
126
|
+
], ImagesController.prototype, "remove", null);
|
|
127
|
+
exports.ImagesController = ImagesController = __decorate([
|
|
128
|
+
(0, swagger_1.ApiTags)("Images"),
|
|
129
|
+
(0, swagger_1.ApiBearerAuth)(),
|
|
130
|
+
(0, common_1.UseGuards)(permissions_guard_1.PermissionsGuard),
|
|
131
|
+
(0, common_1.Controller)({ path: "images", version: "1" }),
|
|
132
|
+
__metadata("design:paramtypes", [images_service_1.ImagesService])
|
|
133
|
+
], ImagesController);
|
|
134
|
+
const toProfileResponse = (profile) => ({
|
|
135
|
+
name: profile.name,
|
|
136
|
+
width: profile.width,
|
|
137
|
+
height: profile.height ?? null,
|
|
138
|
+
fit: profile.fit,
|
|
139
|
+
format: profile.format,
|
|
140
|
+
maxBytes: profile.maxBytes,
|
|
141
|
+
accept: ACCEPTED_TYPES,
|
|
142
|
+
});
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.ImagesModule = void 0;
|
|
10
|
+
const common_1 = require("@nestjs/common");
|
|
11
|
+
const core_1 = require("@nestjs/core");
|
|
12
|
+
const imageFetch_service_1 = require("./imageFetch.service");
|
|
13
|
+
const imageProcessing_service_1 = require("./imageProcessing.service");
|
|
14
|
+
const imageProfile_registry_1 = require("./imageProfile.registry");
|
|
15
|
+
const imageStorage_service_1 = require("./imageStorage.service");
|
|
16
|
+
let ImagesModule = class ImagesModule {
|
|
17
|
+
};
|
|
18
|
+
exports.ImagesModule = ImagesModule;
|
|
19
|
+
exports.ImagesModule = ImagesModule = __decorate([
|
|
20
|
+
(0, common_1.Module)({
|
|
21
|
+
imports: [core_1.DiscoveryModule],
|
|
22
|
+
providers: [
|
|
23
|
+
imageProfile_registry_1.ImageProfileRegistry,
|
|
24
|
+
imageProcessing_service_1.ImageProcessingService,
|
|
25
|
+
imageFetch_service_1.ImageFetchService,
|
|
26
|
+
imageStorage_service_1.ImageStorageService,
|
|
27
|
+
],
|
|
28
|
+
exports: [
|
|
29
|
+
imageProfile_registry_1.ImageProfileRegistry,
|
|
30
|
+
imageProcessing_service_1.ImageProcessingService,
|
|
31
|
+
imageFetch_service_1.ImageFetchService,
|
|
32
|
+
imageStorage_service_1.ImageStorageService,
|
|
33
|
+
],
|
|
34
|
+
})
|
|
35
|
+
], ImagesModule);
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { Logger } from "@nestjs/common";
|
|
2
|
+
import { ImageFetchService } from "./imageFetch.service";
|
|
3
|
+
import { ImageProcessingService } from "./imageProcessing.service";
|
|
4
|
+
import { ImageJobPayload, ImageJobSummary } from "./imageProfile.interface";
|
|
5
|
+
import { ImageProfileRegistry } from "./imageProfile.registry";
|
|
6
|
+
import { ImageStorageService } from "./imageStorage.service";
|
|
7
|
+
import { JobContext } from "../queue/job.interface";
|
|
8
|
+
import { JobEventsService } from "../queue/jobEvents.service";
|
|
9
|
+
import { JobWorkerHost } from "../queue/jobWorkerHost";
|
|
10
|
+
export declare class ImagesProcessor extends JobWorkerHost<ImageJobPayload, ImageJobSummary> {
|
|
11
|
+
private readonly registry;
|
|
12
|
+
private readonly processing;
|
|
13
|
+
private readonly fetcher;
|
|
14
|
+
private readonly storage;
|
|
15
|
+
protected readonly logger: Logger;
|
|
16
|
+
constructor(jobEvents: JobEventsService, registry: ImageProfileRegistry, processing: ImageProcessingService, fetcher: ImageFetchService, storage: ImageStorageService);
|
|
17
|
+
handle(job: JobContext<ImageJobPayload>): Promise<ImageJobSummary>;
|
|
18
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
var ImagesProcessor_1;
|
|
12
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
+
exports.ImagesProcessor = void 0;
|
|
14
|
+
const bullmq_1 = require("@nestjs/bullmq");
|
|
15
|
+
const common_1 = require("@nestjs/common");
|
|
16
|
+
const bullmq_2 = require("bullmq");
|
|
17
|
+
const imageFetch_service_1 = require("./imageFetch.service");
|
|
18
|
+
const imageProcessing_service_1 = require("./imageProcessing.service");
|
|
19
|
+
const imageProfile_interface_1 = require("./imageProfile.interface");
|
|
20
|
+
const imageProfile_registry_1 = require("./imageProfile.registry");
|
|
21
|
+
const imageStorage_service_1 = require("./imageStorage.service");
|
|
22
|
+
const jobEvents_service_1 = require("../queue/jobEvents.service");
|
|
23
|
+
const jobWorkerHost_1 = require("../queue/jobWorkerHost");
|
|
24
|
+
const queue_constants_1 = require("../queue/queue.constants");
|
|
25
|
+
const STEPS = 2;
|
|
26
|
+
let ImagesProcessor = ImagesProcessor_1 = class ImagesProcessor extends jobWorkerHost_1.JobWorkerHost {
|
|
27
|
+
constructor(jobEvents, registry, processing, fetcher, storage) {
|
|
28
|
+
super(jobEvents);
|
|
29
|
+
this.registry = registry;
|
|
30
|
+
this.processing = processing;
|
|
31
|
+
this.fetcher = fetcher;
|
|
32
|
+
this.storage = storage;
|
|
33
|
+
this.logger = new common_1.Logger(ImagesProcessor_1.name);
|
|
34
|
+
}
|
|
35
|
+
async handle(job) {
|
|
36
|
+
const { payload } = job;
|
|
37
|
+
const profile = this.registry.find(payload.profile);
|
|
38
|
+
if (!profile) {
|
|
39
|
+
throw new bullmq_2.UnrecoverableError(`Perfil de imagem "${payload.profile}" não registrado neste consumer`);
|
|
40
|
+
}
|
|
41
|
+
const fromUrl = payload.kind === imageProfile_interface_1.ImageSourceKind.URL;
|
|
42
|
+
await job.report(1, STEPS, fromUrl ? "Baixando a imagem" : "Lendo o arquivo");
|
|
43
|
+
const input = fromUrl
|
|
44
|
+
? await this.fetcher.download(payload.source, profile.maxBytes)
|
|
45
|
+
: await this.storage.readIncoming(payload.source);
|
|
46
|
+
if (fromUrl) {
|
|
47
|
+
const detected = this.processing.detectType(input);
|
|
48
|
+
if (!detected) {
|
|
49
|
+
throw new common_1.BadRequestException("O endereço não devolveu uma imagem (use JPEG, PNG ou WebP)");
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
await job.report(2, STEPS, "Convertendo");
|
|
53
|
+
try {
|
|
54
|
+
return await this.processing.process(profile, input);
|
|
55
|
+
}
|
|
56
|
+
finally {
|
|
57
|
+
if (!fromUrl) {
|
|
58
|
+
await this.storage.removeIncoming(payload.source).catch(() => undefined);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
exports.ImagesProcessor = ImagesProcessor;
|
|
64
|
+
exports.ImagesProcessor = ImagesProcessor = ImagesProcessor_1 = __decorate([
|
|
65
|
+
(0, bullmq_1.Processor)(queue_constants_1.IMAGES_QUEUE, {
|
|
66
|
+
concurrency: queue_constants_1.IMAGE_CONCURRENCY,
|
|
67
|
+
lockDuration: queue_constants_1.IMAGE_LOCK_DURATION_MS,
|
|
68
|
+
autorun: false,
|
|
69
|
+
}),
|
|
70
|
+
__metadata("design:paramtypes", [jobEvents_service_1.JobEventsService,
|
|
71
|
+
imageProfile_registry_1.ImageProfileRegistry,
|
|
72
|
+
imageProcessing_service_1.ImageProcessingService,
|
|
73
|
+
imageFetch_service_1.ImageFetchService,
|
|
74
|
+
imageStorage_service_1.ImageStorageService])
|
|
75
|
+
], ImagesProcessor);
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { AuthenticatedUser } from "../auth/authenticatedUser.interface";
|
|
2
|
+
import { ImageFetchService } from "./imageFetch.service";
|
|
3
|
+
import { ImageProcessingService } from "./imageProcessing.service";
|
|
4
|
+
import { ImageJobSummary, ImageProfile } from "./imageProfile.interface";
|
|
5
|
+
import { ImageProfileRegistry } from "./imageProfile.registry";
|
|
6
|
+
import { ImageStorageService } from "./imageStorage.service";
|
|
7
|
+
import { JobStatus } from "../queue/job.interface";
|
|
8
|
+
import { JobProducer } from "../queue/job.producer";
|
|
9
|
+
export interface ImageAccepted {
|
|
10
|
+
jobId: string | null;
|
|
11
|
+
path: string | null;
|
|
12
|
+
}
|
|
13
|
+
export declare class ImagesService {
|
|
14
|
+
private readonly registry;
|
|
15
|
+
private readonly producer;
|
|
16
|
+
private readonly processing;
|
|
17
|
+
private readonly fetcher;
|
|
18
|
+
private readonly storage;
|
|
19
|
+
constructor(registry: ImageProfileRegistry, producer: JobProducer, processing: ImageProcessingService, fetcher: ImageFetchService, storage: ImageStorageService);
|
|
20
|
+
profiles(): ImageProfile[];
|
|
21
|
+
profile(name: string): ImageProfile;
|
|
22
|
+
fromFile(user: AuthenticatedUser, profileName: string, file?: Express.Multer.File): Promise<ImageAccepted>;
|
|
23
|
+
fromFileNow(user: AuthenticatedUser, profileName: string, file?: Express.Multer.File): Promise<string>;
|
|
24
|
+
fromUrl(user: AuthenticatedUser, profileName: string, url: string): Promise<ImageAccepted>;
|
|
25
|
+
status(jobId: string, ownerId: string): Promise<JobStatus<ImageJobSummary>>;
|
|
26
|
+
remove(user: AuthenticatedUser, profileName: string, path: string): Promise<void>;
|
|
27
|
+
discard(path: string | null | undefined): Promise<void>;
|
|
28
|
+
promote(path: string | null | undefined): Promise<string | null>;
|
|
29
|
+
private authorize;
|
|
30
|
+
private enqueue;
|
|
31
|
+
private processNow;
|
|
32
|
+
private tooLarge;
|
|
33
|
+
private labelOf;
|
|
34
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.ImagesService = void 0;
|
|
13
|
+
const common_1 = require("@nestjs/common");
|
|
14
|
+
const imageFetch_service_1 = require("./imageFetch.service");
|
|
15
|
+
const imageProcessing_service_1 = require("./imageProcessing.service");
|
|
16
|
+
const imageProfile_interface_1 = require("./imageProfile.interface");
|
|
17
|
+
const imageProfile_registry_1 = require("./imageProfile.registry");
|
|
18
|
+
const imageStorage_service_1 = require("./imageStorage.service");
|
|
19
|
+
const job_producer_1 = require("../queue/job.producer");
|
|
20
|
+
const queue_constants_1 = require("../queue/queue.constants");
|
|
21
|
+
let ImagesService = class ImagesService {
|
|
22
|
+
constructor(registry, producer, processing, fetcher, storage) {
|
|
23
|
+
this.registry = registry;
|
|
24
|
+
this.producer = producer;
|
|
25
|
+
this.processing = processing;
|
|
26
|
+
this.fetcher = fetcher;
|
|
27
|
+
this.storage = storage;
|
|
28
|
+
}
|
|
29
|
+
profiles() {
|
|
30
|
+
return this.registry.all();
|
|
31
|
+
}
|
|
32
|
+
profile(name) {
|
|
33
|
+
return this.registry.require(name);
|
|
34
|
+
}
|
|
35
|
+
async fromFile(user, profileName, file) {
|
|
36
|
+
const profile = this.authorize(user, profileName);
|
|
37
|
+
if (!file?.buffer?.length) {
|
|
38
|
+
throw new common_1.BadRequestException("Nenhum arquivo recebido");
|
|
39
|
+
}
|
|
40
|
+
if (file.size > profile.maxBytes) {
|
|
41
|
+
throw this.tooLarge(profile);
|
|
42
|
+
}
|
|
43
|
+
this.processing.assertSupported(file.buffer, file.mimetype);
|
|
44
|
+
if (!this.producer.isAvailable(queue_constants_1.IMAGES_QUEUE)) {
|
|
45
|
+
return await this.processNow(profile, file.buffer);
|
|
46
|
+
}
|
|
47
|
+
const source = await this.storage.saveIncoming(file.buffer);
|
|
48
|
+
return await this.enqueue(user.id, profile, {
|
|
49
|
+
profile: profile.name,
|
|
50
|
+
kind: imageProfile_interface_1.ImageSourceKind.FILE,
|
|
51
|
+
source,
|
|
52
|
+
label: file.originalname,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
async fromFileNow(user, profileName, file) {
|
|
56
|
+
const profile = this.authorize(user, profileName);
|
|
57
|
+
if (!file?.buffer?.length) {
|
|
58
|
+
throw new common_1.BadRequestException("Nenhum arquivo recebido");
|
|
59
|
+
}
|
|
60
|
+
if (file.size > profile.maxBytes) {
|
|
61
|
+
throw this.tooLarge(profile);
|
|
62
|
+
}
|
|
63
|
+
this.processing.assertSupported(file.buffer, file.mimetype);
|
|
64
|
+
const summary = await this.processing.process(profile, file.buffer);
|
|
65
|
+
const path = await this.storage.promote(summary.path);
|
|
66
|
+
return path ?? summary.path;
|
|
67
|
+
}
|
|
68
|
+
async fromUrl(user, profileName, url) {
|
|
69
|
+
const profile = this.authorize(user, profileName);
|
|
70
|
+
await this.fetcher.assertAllowed(url);
|
|
71
|
+
if (!this.producer.isAvailable(queue_constants_1.IMAGES_QUEUE)) {
|
|
72
|
+
const downloaded = await this.fetcher.download(url, profile.maxBytes);
|
|
73
|
+
return await this.processNow(profile, downloaded, true);
|
|
74
|
+
}
|
|
75
|
+
return await this.enqueue(user.id, profile, {
|
|
76
|
+
profile: profile.name,
|
|
77
|
+
kind: imageProfile_interface_1.ImageSourceKind.URL,
|
|
78
|
+
source: url,
|
|
79
|
+
label: this.labelOf(url),
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
async status(jobId, ownerId) {
|
|
83
|
+
return await this.producer.status(jobId, ownerId, queue_constants_1.IMAGES_QUEUE);
|
|
84
|
+
}
|
|
85
|
+
async remove(user, profileName, path) {
|
|
86
|
+
const profile = this.authorize(user, profileName);
|
|
87
|
+
const ref = this.storage.parsePublicPath(path);
|
|
88
|
+
if (!ref || ref.folder !== profile.folder) {
|
|
89
|
+
throw new common_1.BadRequestException("Caminho de imagem inválido");
|
|
90
|
+
}
|
|
91
|
+
await this.storage.remove(path);
|
|
92
|
+
}
|
|
93
|
+
async discard(path) {
|
|
94
|
+
await this.storage.remove(path);
|
|
95
|
+
}
|
|
96
|
+
async promote(path) {
|
|
97
|
+
return await this.storage.promote(path);
|
|
98
|
+
}
|
|
99
|
+
authorize(user, profileName) {
|
|
100
|
+
const profile = this.registry.require(profileName);
|
|
101
|
+
if (profile.permission && !user.permissions.includes(profile.permission)) {
|
|
102
|
+
throw new common_1.ForbiddenException("Você não tem permissão para enviar imagem aqui");
|
|
103
|
+
}
|
|
104
|
+
return profile;
|
|
105
|
+
}
|
|
106
|
+
async enqueue(ownerId, profile, payload) {
|
|
107
|
+
const ref = await this.producer.enqueue(profile.name, payload, {
|
|
108
|
+
ownerId,
|
|
109
|
+
queue: queue_constants_1.IMAGES_QUEUE,
|
|
110
|
+
});
|
|
111
|
+
return { jobId: ref.jobId, path: null };
|
|
112
|
+
}
|
|
113
|
+
async processNow(profile, buffer, detect = false) {
|
|
114
|
+
if (detect) {
|
|
115
|
+
this.processing.assertSupported(buffer, this.processing.detectType(buffer) ?? "");
|
|
116
|
+
}
|
|
117
|
+
const summary = await this.processing.process(profile, buffer);
|
|
118
|
+
return { jobId: null, path: summary.path };
|
|
119
|
+
}
|
|
120
|
+
tooLarge(profile) {
|
|
121
|
+
const mb = Math.round(profile.maxBytes / (1024 * 1024));
|
|
122
|
+
return new common_1.BadRequestException(`A imagem passa do limite de ${mb}MB`);
|
|
123
|
+
}
|
|
124
|
+
labelOf(url) {
|
|
125
|
+
const last = url.split("?")[0].split("/").filter(Boolean).pop();
|
|
126
|
+
return last?.slice(0, 60) ?? "imagem";
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
exports.ImagesService = ImagesService;
|
|
130
|
+
exports.ImagesService = ImagesService = __decorate([
|
|
131
|
+
(0, common_1.Injectable)(),
|
|
132
|
+
__metadata("design:paramtypes", [imageProfile_registry_1.ImageProfileRegistry,
|
|
133
|
+
job_producer_1.JobProducer,
|
|
134
|
+
imageProcessing_service_1.ImageProcessingService,
|
|
135
|
+
imageFetch_service_1.ImageFetchService,
|
|
136
|
+
imageStorage_service_1.ImageStorageService])
|
|
137
|
+
], ImagesService);
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.ImagesApiModule = void 0;
|
|
10
|
+
const common_1 = require("@nestjs/common");
|
|
11
|
+
const imageCleanup_service_1 = require("./imageCleanup.service");
|
|
12
|
+
const images_controller_1 = require("./images.controller");
|
|
13
|
+
const images_module_1 = require("./images.module");
|
|
14
|
+
const images_service_1 = require("./images.service");
|
|
15
|
+
let ImagesApiModule = class ImagesApiModule {
|
|
16
|
+
};
|
|
17
|
+
exports.ImagesApiModule = ImagesApiModule;
|
|
18
|
+
exports.ImagesApiModule = ImagesApiModule = __decorate([
|
|
19
|
+
(0, common_1.Module)({
|
|
20
|
+
imports: [images_module_1.ImagesModule],
|
|
21
|
+
controllers: [images_controller_1.ImagesController],
|
|
22
|
+
providers: [images_service_1.ImagesService, imageCleanup_service_1.ImageCleanupService],
|
|
23
|
+
exports: [images_service_1.ImagesService, images_module_1.ImagesModule],
|
|
24
|
+
})
|
|
25
|
+
], ImagesApiModule);
|