@wrcb/cb-common 1.0.383 → 1.0.385

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/build/index.d.ts CHANGED
@@ -18,3 +18,5 @@ export * from './types/userCategory';
18
18
  export * from './types/productTypes';
19
19
  export * from './events/subjects';
20
20
  export * from './services/TenantDataService';
21
+ export * from './storage/types';
22
+ export * from './storage/interfaces/MediaConfiguration';
package/build/index.js CHANGED
@@ -34,3 +34,5 @@ __exportStar(require("./types/userCategory"), exports);
34
34
  __exportStar(require("./types/productTypes"), exports);
35
35
  __exportStar(require("./events/subjects"), exports);
36
36
  __exportStar(require("./services/TenantDataService"), exports);
37
+ __exportStar(require("./storage/types"), exports);
38
+ __exportStar(require("./storage/interfaces/MediaConfiguration"), exports);
@@ -0,0 +1,14 @@
1
+ import { Request, Response, NextFunction } from 'express';
2
+ import { UploadResult } from '../storage/types';
3
+ declare global {
4
+ namespace Express {
5
+ interface Request {
6
+ uploadResults?: UploadResult[];
7
+ }
8
+ }
9
+ }
10
+ export declare const uploadMiddleware: {
11
+ single: (fieldName: string) => import("express").RequestHandler<import("express-serve-static-core").ParamsDictionary, any, any, import("qs").ParsedQs, Record<string, any>>;
12
+ multiple: (fieldName: string, maxCount?: number) => import("express").RequestHandler<import("express-serve-static-core").ParamsDictionary, any, any, import("qs").ParsedQs, Record<string, any>>;
13
+ processUpload: (req: Request, res: Response, next: NextFunction) => Promise<void>;
14
+ };
@@ -0,0 +1,60 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ var __importDefault = (this && this.__importDefault) || function (mod) {
12
+ return (mod && mod.__esModule) ? mod : { "default": mod };
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.uploadMiddleware = void 0;
16
+ const multer_1 = __importDefault(require("multer"));
17
+ const factory_1 = require("../storage/factory");
18
+ const tenant_1 = require("../types/tenant");
19
+ const storageService_1 = require("../storage/services/storageService");
20
+ const storage = multer_1.default.memoryStorage();
21
+ const upload = (0, multer_1.default)({ storage });
22
+ exports.uploadMiddleware = {
23
+ single: (fieldName) => upload.single(fieldName),
24
+ multiple: (fieldName, maxCount) => upload.array(fieldName, maxCount),
25
+ processUpload: (req, res, next) => __awaiter(void 0, void 0, void 0, function* () {
26
+ try {
27
+ const storageService = new storageService_1.StorageService(factory_1.StorageFactory.create());
28
+ const tenant = req.headers['x-tenant'] || tenant_1.Tenant.PoliticaBet;
29
+ const files = req.files || (req.file ? [req.file] : []);
30
+ if (!files.length) {
31
+ return next();
32
+ }
33
+ const uploadPromises = files.map((file) => __awaiter(void 0, void 0, void 0, function* () {
34
+ var _a;
35
+ const isVideo = file.mimetype.startsWith('video/');
36
+ // Solução 1: Declarar o tipo explicitamente
37
+ const type = isVideo ? 'video' : 'photo';
38
+ const options = {
39
+ tenant,
40
+ userId: ((_a = req.currentUser) === null || _a === void 0 ? void 0 : _a.id) || undefined,
41
+ targetId: req.body.targetId || undefined,
42
+ type,
43
+ generateThumbnail: true
44
+ };
45
+ if (isVideo) {
46
+ return storageService.uploadVideo(file.buffer, file.originalname, options);
47
+ }
48
+ else {
49
+ return storageService.uploadImage(file.buffer, file.originalname, options);
50
+ }
51
+ }));
52
+ const results = yield Promise.all(uploadPromises);
53
+ req.uploadResults = results;
54
+ next();
55
+ }
56
+ catch (error) {
57
+ next(error);
58
+ }
59
+ })
60
+ };
package/build/server.d.ts CHANGED
@@ -21,6 +21,7 @@ export * from './middlewares/requireVerifiedWhatsapp';
21
21
  export * from './middlewares/requireProvidedAddress';
22
22
  export * from './middlewares/requireProvidedPersonalData';
23
23
  export * from './middlewares/standardResponse';
24
+ export * from './middlewares/uploadMedia';
24
25
  export * from './events/baseListener';
25
26
  export * from './events/basePublisher';
26
27
  export * from './events/callStartedEvent';
@@ -44,3 +45,9 @@ export * from './events/linkAndCodeToVerifyEmailCreatedEvent';
44
45
  export * from './events/linkAndcodeToVerifyWhatsappCreatedEvent';
45
46
  export * from './services/RedisService';
46
47
  export * from './services/TenantDataService';
48
+ export * from './storage/services/storageService';
49
+ export * from './storage/factory';
50
+ export * from './storage/types';
51
+ export * from './storage/providers/minio';
52
+ export * from './storage/providers/spaces';
53
+ export * from './storage/interfaces/MediaConfiguration';
package/build/server.js CHANGED
@@ -37,6 +37,7 @@ __exportStar(require("./middlewares/requireVerifiedWhatsapp"), exports);
37
37
  __exportStar(require("./middlewares/requireProvidedAddress"), exports);
38
38
  __exportStar(require("./middlewares/requireProvidedPersonalData"), exports);
39
39
  __exportStar(require("./middlewares/standardResponse"), exports);
40
+ __exportStar(require("./middlewares/uploadMedia"), exports);
40
41
  __exportStar(require("./events/baseListener"), exports);
41
42
  __exportStar(require("./events/basePublisher"), exports);
42
43
  __exportStar(require("./events/callStartedEvent"), exports);
@@ -60,3 +61,9 @@ __exportStar(require("./events/linkAndCodeToVerifyEmailCreatedEvent"), exports);
60
61
  __exportStar(require("./events/linkAndcodeToVerifyWhatsappCreatedEvent"), exports);
61
62
  __exportStar(require("./services/RedisService"), exports);
62
63
  __exportStar(require("./services/TenantDataService"), exports);
64
+ __exportStar(require("./storage/services/storageService"), exports);
65
+ __exportStar(require("./storage/factory"), exports);
66
+ __exportStar(require("./storage/types"), exports);
67
+ __exportStar(require("./storage/providers/minio"), exports);
68
+ __exportStar(require("./storage/providers/spaces"), exports);
69
+ __exportStar(require("./storage/interfaces/MediaConfiguration"), exports);
@@ -3,6 +3,7 @@ import { Tenant } from '../types/tenant';
3
3
  import { UserCategory } from '../types/userCategory';
4
4
  import { UserTags } from '../types/userTags';
5
5
  import { ProductTypes } from '../types/productTypes';
6
+ import { MediaConfiguration } from '../storage/interfaces/MediaConfiguration';
6
7
  export interface CategoryConfiguration {
7
8
  allowedTags: UserTags[];
8
9
  allowedProducts: ProductTypes[];
@@ -28,6 +29,7 @@ export interface TenantData {
28
29
  AUTH_GOOGLE_SECRET: string;
29
30
  GOOGLE_ANALYTICS_MEASUREMENT_ID: string;
30
31
  GOOGLE_VERIFICATION: string;
32
+ MEDIA_CONFIG: MediaConfiguration;
31
33
  }
32
34
  export declare class TenantDataService {
33
35
  private static tenantDataMap;
@@ -86,7 +86,18 @@ TenantDataService.tenantDataMap = {
86
86
  AUTH_GOOGLE_ID: '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',
87
87
  AUTH_GOOGLE_SECRET: '',
88
88
  GOOGLE_ANALYTICS_MEASUREMENT_ID: '',
89
- GOOGLE_VERIFICATION: 'MxMj1CaliRbJ6G7cp0eIYXug0QF6hyugQ1P74T6sCzc'
89
+ GOOGLE_VERIFICATION: 'MxMj1CaliRbJ6G7cp0eIYXug0QF6hyugQ1P74T6sCzc',
90
+ MEDIA_CONFIG: {
91
+ maxPhotoSizeMB: 3,
92
+ maxVideoSizeMB: 50,
93
+ allowedImageFormats: ['jpg', 'jpeg', 'png', 'webp'],
94
+ allowedVideoFormats: ['mp4', 'webm', 'mov'],
95
+ imageQualities: {
96
+ thumbnail: { width: 150, height: 150, quality: 80 },
97
+ medium: { width: 800, height: 800, quality: 85 },
98
+ large: { width: 1920, height: 1920, quality: 90 }
99
+ }
100
+ }
90
101
  },
91
102
  [tenant_1.Tenant.TourismApp]: {
92
103
  TENANT: tenant_1.Tenant.TourismApp,
@@ -151,7 +162,18 @@ TenantDataService.tenantDataMap = {
151
162
  AUTH_GOOGLE_ID: '',
152
163
  AUTH_GOOGLE_SECRET: '',
153
164
  GOOGLE_ANALYTICS_MEASUREMENT_ID: '',
154
- GOOGLE_VERIFICATION: ''
165
+ GOOGLE_VERIFICATION: '',
166
+ MEDIA_CONFIG: {
167
+ maxPhotoSizeMB: 3,
168
+ maxVideoSizeMB: 50,
169
+ allowedImageFormats: ['jpg', 'jpeg', 'png', 'webp'],
170
+ allowedVideoFormats: ['mp4', 'webm', 'mov'],
171
+ imageQualities: {
172
+ thumbnail: { width: 150, height: 150, quality: 80 },
173
+ medium: { width: 800, height: 800, quality: 85 },
174
+ large: { width: 1920, height: 1920, quality: 90 }
175
+ }
176
+ }
155
177
  },
156
178
  [tenant_1.Tenant.PrivateShow]: {
157
179
  TENANT: tenant_1.Tenant.PrivateShow,
@@ -189,6 +211,17 @@ TenantDataService.tenantDataMap = {
189
211
  AUTH_GOOGLE_ID: '',
190
212
  AUTH_GOOGLE_SECRET: '',
191
213
  GOOGLE_ANALYTICS_MEASUREMENT_ID: '',
192
- GOOGLE_VERIFICATION: ''
214
+ GOOGLE_VERIFICATION: '',
215
+ MEDIA_CONFIG: {
216
+ maxPhotoSizeMB: 5,
217
+ maxVideoSizeMB: 100,
218
+ allowedImageFormats: ['jpg', 'jpeg', 'png', 'webp'],
219
+ allowedVideoFormats: ['mp4', 'webm', 'mov'],
220
+ imageQualities: {
221
+ thumbnail: { width: 150, height: 150, quality: 80 },
222
+ medium: { width: 800, height: 800, quality: 85 },
223
+ large: { width: 1920, height: 1920, quality: 90 }
224
+ }
225
+ }
193
226
  }
194
227
  };
@@ -0,0 +1,4 @@
1
+ import { StorageProvider } from './types';
2
+ export declare class StorageFactory {
3
+ static create(): StorageProvider;
4
+ }
@@ -0,0 +1,32 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.StorageFactory = void 0;
4
+ const minio_1 = require("./providers/minio");
5
+ const spaces_1 = require("./providers/spaces");
6
+ const server_1 = require("../server");
7
+ class StorageFactory {
8
+ static create() {
9
+ const provider = process.env.STORAGE_PROVIDER || 'minio';
10
+ switch (provider) {
11
+ case 'minio':
12
+ return new minio_1.MinioProvider({
13
+ endPoint: process.env.MINIO_ENDPOINT || 'localhost',
14
+ port: parseInt(process.env.MINIO_PORT || '9000'),
15
+ useSSL: process.env.MINIO_USE_SSL === 'true',
16
+ accessKey: process.env.MINIO_ACCESS_KEY || 'minioadmin',
17
+ secretKey: process.env.MINIO_SECRET_KEY || 'minioadmin',
18
+ bucket: process.env.MINIO_BUCKET || 'media-dev'
19
+ });
20
+ case 'spaces':
21
+ return new spaces_1.SpacesProvider({
22
+ endpoint: process.env.DO_SPACES_ENDPOINT || 'nyc3.digitaloceanspaces.com',
23
+ accessKeyId: process.env.DO_SPACES_KEY,
24
+ secretAccessKey: process.env.DO_SPACES_SECRET,
25
+ bucket: process.env.DO_SPACES_BUCKET
26
+ });
27
+ default:
28
+ throw new server_1.BadRequestError('UnknownStorageProvider');
29
+ }
30
+ }
31
+ }
32
+ exports.StorageFactory = StorageFactory;
@@ -0,0 +1,23 @@
1
+ export interface MediaConfiguration {
2
+ maxPhotoSizeMB: number;
3
+ maxVideoSizeMB: number;
4
+ allowedImageFormats: string[];
5
+ allowedVideoFormats: string[];
6
+ imageQualities: {
7
+ thumbnail: {
8
+ width: number;
9
+ height: number;
10
+ quality: number;
11
+ };
12
+ medium: {
13
+ width: number;
14
+ height: number;
15
+ quality: number;
16
+ };
17
+ large: {
18
+ width: number;
19
+ height: number;
20
+ quality: number;
21
+ };
22
+ };
23
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,19 @@
1
+ import { StorageProvider, UploadResult, MediaMetadata, ListFilters } from '../types';
2
+ export declare class MinioProvider implements StorageProvider {
3
+ private client;
4
+ private bucket;
5
+ constructor(config: {
6
+ endPoint: string;
7
+ port: number;
8
+ useSSL: boolean;
9
+ accessKey: string;
10
+ secretKey: string;
11
+ bucket: string;
12
+ });
13
+ private ensureBucket;
14
+ upload(file: Buffer, path: string): Promise<UploadResult>;
15
+ delete(id: string): Promise<boolean>;
16
+ get(id: string): Promise<MediaMetadata | null>;
17
+ list(filters: ListFilters): Promise<MediaMetadata[]>;
18
+ private buildPrefix;
19
+ }
@@ -0,0 +1,134 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
25
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
26
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
27
+ return new (P || (P = Promise))(function (resolve, reject) {
28
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
29
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
30
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
31
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
32
+ });
33
+ };
34
+ Object.defineProperty(exports, "__esModule", { value: true });
35
+ exports.MinioProvider = void 0;
36
+ // src/storage/providers/minio.ts
37
+ const Minio = __importStar(require("minio"));
38
+ class MinioProvider {
39
+ constructor(config) {
40
+ this.client = new Minio.Client({
41
+ endPoint: config.endPoint,
42
+ port: config.port,
43
+ useSSL: config.useSSL,
44
+ accessKey: config.accessKey,
45
+ secretKey: config.secretKey
46
+ });
47
+ this.bucket = config.bucket;
48
+ this.ensureBucket();
49
+ }
50
+ ensureBucket() {
51
+ return __awaiter(this, void 0, void 0, function* () {
52
+ const exists = yield this.client.bucketExists(this.bucket);
53
+ if (!exists) {
54
+ yield this.client.makeBucket(this.bucket, 'us-east-1');
55
+ }
56
+ });
57
+ }
58
+ upload(file, path) {
59
+ return __awaiter(this, void 0, void 0, function* () {
60
+ const id = path;
61
+ yield this.client.putObject(this.bucket, path, file, file.length);
62
+ const url = `${process.env.STORAGE_PUBLIC_URL}/${this.bucket}/${path}`;
63
+ return {
64
+ id,
65
+ metadata: {
66
+ url,
67
+ size: file.length,
68
+ type: 'image/jpeg', // Você pode detectar isso melhor
69
+ uploadedAt: new Date()
70
+ }
71
+ };
72
+ });
73
+ }
74
+ delete(id) {
75
+ return __awaiter(this, void 0, void 0, function* () {
76
+ try {
77
+ yield this.client.removeObject(this.bucket, id);
78
+ return true;
79
+ }
80
+ catch (_a) {
81
+ return false;
82
+ }
83
+ });
84
+ }
85
+ get(id) {
86
+ return __awaiter(this, void 0, void 0, function* () {
87
+ try {
88
+ const stat = yield this.client.statObject(this.bucket, id);
89
+ const url = `${process.env.STORAGE_PUBLIC_URL}/${this.bucket}/${id}`;
90
+ return {
91
+ url,
92
+ size: stat.size,
93
+ type: stat.metaData['content-type'] || 'application/octet-stream',
94
+ uploadedAt: stat.lastModified
95
+ };
96
+ }
97
+ catch (_a) {
98
+ return null;
99
+ }
100
+ });
101
+ }
102
+ list(filters) {
103
+ return __awaiter(this, void 0, void 0, function* () {
104
+ const prefix = this.buildPrefix(filters);
105
+ const stream = this.client.listObjectsV2(this.bucket, prefix, true);
106
+ const results = [];
107
+ return new Promise((resolve, reject) => {
108
+ stream.on('data', obj => {
109
+ if (obj.name) {
110
+ results.push({
111
+ url: `${process.env.STORAGE_PUBLIC_URL}/${this.bucket}/${obj.name}`,
112
+ size: obj.size,
113
+ type: 'application/octet-stream',
114
+ uploadedAt: obj.lastModified
115
+ });
116
+ }
117
+ });
118
+ stream.on('error', reject);
119
+ stream.on('end', () => resolve(results));
120
+ });
121
+ });
122
+ }
123
+ buildPrefix(filters) {
124
+ const parts = [filters.tenant];
125
+ if (filters.userId)
126
+ parts.push('users', filters.userId);
127
+ if (filters.targetId)
128
+ parts.push('targets', filters.targetId);
129
+ if (filters.type)
130
+ parts.push(filters.type);
131
+ return parts.join('/');
132
+ }
133
+ }
134
+ exports.MinioProvider = MinioProvider;
@@ -0,0 +1,17 @@
1
+ import { StorageProvider, UploadResult, MediaMetadata, ListFilters } from '../types';
2
+ export declare class SpacesProvider implements StorageProvider {
3
+ private s3;
4
+ private bucket;
5
+ constructor(config: {
6
+ endpoint: string;
7
+ accessKeyId: string;
8
+ secretAccessKey: string;
9
+ bucket: string;
10
+ });
11
+ upload(file: Buffer, path: string): Promise<UploadResult>;
12
+ delete(id: string): Promise<boolean>;
13
+ get(id: string): Promise<MediaMetadata | null>;
14
+ list(filters: ListFilters): Promise<MediaMetadata[]>;
15
+ private detectContentType;
16
+ private buildPrefix;
17
+ }
@@ -0,0 +1,128 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ var __importDefault = (this && this.__importDefault) || function (mod) {
12
+ return (mod && mod.__esModule) ? mod : { "default": mod };
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.SpacesProvider = void 0;
16
+ // src/storage/providers/minio.ts
17
+ // DIGITAL OCEAN SPACES
18
+ const aws_sdk_1 = __importDefault(require("aws-sdk"));
19
+ class SpacesProvider {
20
+ constructor(config) {
21
+ this.s3 = new aws_sdk_1.default.S3({
22
+ endpoint: new aws_sdk_1.default.Endpoint(config.endpoint),
23
+ accessKeyId: config.accessKeyId,
24
+ secretAccessKey: config.secretAccessKey
25
+ });
26
+ this.bucket = config.bucket;
27
+ }
28
+ upload(file, path) {
29
+ return __awaiter(this, void 0, void 0, function* () {
30
+ const params = {
31
+ Bucket: this.bucket,
32
+ Key: path,
33
+ Body: file,
34
+ ACL: 'public-read',
35
+ ContentType: this.detectContentType(path)
36
+ };
37
+ const result = yield this.s3.upload(params).promise();
38
+ return {
39
+ id: path,
40
+ metadata: {
41
+ url: result.Location,
42
+ size: file.length,
43
+ type: params.ContentType,
44
+ uploadedAt: new Date()
45
+ }
46
+ };
47
+ });
48
+ }
49
+ delete(id) {
50
+ return __awaiter(this, void 0, void 0, function* () {
51
+ try {
52
+ yield this.s3
53
+ .deleteObject({
54
+ Bucket: this.bucket,
55
+ Key: id
56
+ })
57
+ .promise();
58
+ return true;
59
+ }
60
+ catch (_a) {
61
+ return false;
62
+ }
63
+ });
64
+ }
65
+ get(id) {
66
+ return __awaiter(this, void 0, void 0, function* () {
67
+ try {
68
+ const head = yield this.s3
69
+ .headObject({
70
+ Bucket: this.bucket,
71
+ Key: id
72
+ })
73
+ .promise();
74
+ return {
75
+ url: `https://${this.bucket}.${this.s3.endpoint.hostname}/${id}`,
76
+ size: head.ContentLength || 0,
77
+ type: head.ContentType || 'application/octet-stream',
78
+ uploadedAt: head.LastModified || new Date()
79
+ };
80
+ }
81
+ catch (_a) {
82
+ return null;
83
+ }
84
+ });
85
+ }
86
+ list(filters) {
87
+ return __awaiter(this, void 0, void 0, function* () {
88
+ const prefix = this.buildPrefix(filters);
89
+ const params = {
90
+ Bucket: this.bucket,
91
+ Prefix: prefix,
92
+ MaxKeys: filters.limit || 1000
93
+ };
94
+ const result = yield this.s3.listObjectsV2(params).promise();
95
+ return (result.Contents || []).map(obj => ({
96
+ url: `https://${this.bucket}.${this.s3.endpoint.hostname}/${obj.Key}`,
97
+ size: obj.Size || 0,
98
+ type: 'application/octet-stream',
99
+ uploadedAt: obj.LastModified || new Date()
100
+ }));
101
+ });
102
+ }
103
+ detectContentType(path) {
104
+ var _a;
105
+ const ext = (_a = path.split('.').pop()) === null || _a === void 0 ? void 0 : _a.toLowerCase();
106
+ const types = {
107
+ jpg: 'image/jpeg',
108
+ jpeg: 'image/jpeg',
109
+ png: 'image/png',
110
+ webp: 'image/webp',
111
+ mp4: 'video/mp4',
112
+ webm: 'video/webm',
113
+ mov: 'video/quicktime'
114
+ };
115
+ return types[ext || ''] || 'application/octet-stream';
116
+ }
117
+ buildPrefix(filters) {
118
+ const parts = [filters.tenant];
119
+ if (filters.userId)
120
+ parts.push('users', filters.userId);
121
+ if (filters.targetId)
122
+ parts.push('targets', filters.targetId);
123
+ if (filters.type)
124
+ parts.push(filters.type);
125
+ return parts.join('/');
126
+ }
127
+ }
128
+ exports.SpacesProvider = SpacesProvider;
@@ -0,0 +1,11 @@
1
+ import { StorageProvider, UploadOptions, UploadResult } from '../types';
2
+ export declare class StorageService {
3
+ private provider;
4
+ constructor(provider: StorageProvider);
5
+ uploadImage(file: Buffer, filename: string, options: UploadOptions): Promise<UploadResult>;
6
+ uploadVideo(file: Buffer, filename: string, options: UploadOptions): Promise<UploadResult>;
7
+ private processImage;
8
+ private generatePath;
9
+ private getVideoMetadata;
10
+ private generateVideoThumbnail;
11
+ }
@@ -0,0 +1,156 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ var __importDefault = (this && this.__importDefault) || function (mod) {
12
+ return (mod && mod.__esModule) ? mod : { "default": mod };
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.StorageService = void 0;
16
+ // src/storage/services/storageService.ts
17
+ const sharp_1 = __importDefault(require("sharp"));
18
+ const fluent_ffmpeg_1 = __importDefault(require("fluent-ffmpeg"));
19
+ const fs_1 = require("fs"); // Importar promises do fs
20
+ const uuid_1 = require("uuid");
21
+ const TenantDataService_1 = require("../../services/TenantDataService");
22
+ const badRequestError_1 = require("src/errors/badRequestError");
23
+ class StorageService {
24
+ constructor(provider) {
25
+ this.provider = provider;
26
+ }
27
+ uploadImage(file, filename, options) {
28
+ return __awaiter(this, void 0, void 0, function* () {
29
+ const tenantData = TenantDataService_1.TenantDataService.getTenantData(options.tenant);
30
+ const config = tenantData.MEDIA_CONFIG;
31
+ // Validar tamanho
32
+ const sizeMB = file.length / (1024 * 1024);
33
+ if (sizeMB > config.maxPhotoSizeMB) {
34
+ throw new badRequestError_1.BadRequestError('ImageSizeExceeds');
35
+ }
36
+ // Validar formato
37
+ const metadata = yield (0, sharp_1.default)(file).metadata();
38
+ const format = metadata.format;
39
+ if (!format || !config.allowedImageFormats.includes(format)) {
40
+ throw new badRequestError_1.BadRequestError('InvalidImageFormat');
41
+ }
42
+ // Processar imagem - criar versões otimizadas
43
+ const versions = yield this.processImage(file, config);
44
+ // Upload de cada versão
45
+ const uploadPromises = Object.entries(versions).map(([size, buffer]) => {
46
+ const path = this.generatePath(options, filename, size);
47
+ return this.provider.upload(buffer, path, options);
48
+ });
49
+ const results = yield Promise.all(uploadPromises);
50
+ // Retornar a versão large como principal, com thumbnail
51
+ const mainResult = results.find(r => r.id.includes('large'));
52
+ const thumbResult = results.find(r => r.id.includes('thumbnail'));
53
+ return Object.assign(Object.assign({}, mainResult), { metadata: Object.assign(Object.assign({}, mainResult.metadata), { thumbnailUrl: thumbResult === null || thumbResult === void 0 ? void 0 : thumbResult.metadata.url }) });
54
+ });
55
+ }
56
+ uploadVideo(file, filename, options) {
57
+ return __awaiter(this, void 0, void 0, function* () {
58
+ const tenantData = TenantDataService_1.TenantDataService.getTenantData(options.tenant);
59
+ const config = tenantData.MEDIA_CONFIG;
60
+ // Validar tamanho
61
+ const sizeMB = file.length / (1024 * 1024);
62
+ if (sizeMB > config.maxVideoSizeMB) {
63
+ throw new badRequestError_1.BadRequestError('VideoSizeExceeds');
64
+ }
65
+ // Salvar temporariamente para processar com ffmpeg
66
+ const tempPath = `/tmp/${(0, uuid_1.v4)()}-${filename}`;
67
+ yield fs_1.promises.writeFile(tempPath, file);
68
+ try {
69
+ // Obter metadados do vídeo
70
+ const metadata = yield this.getVideoMetadata(tempPath);
71
+ // Gerar thumbnail se solicitado
72
+ let thumbnailUrl;
73
+ if (options.generateThumbnail) {
74
+ const thumbnail = yield this.generateVideoThumbnail(tempPath);
75
+ const thumbPath = this.generatePath(options, filename, 'thumbnail');
76
+ const thumbResult = yield this.provider.upload(thumbnail, thumbPath, options);
77
+ thumbnailUrl = thumbResult.metadata.url;
78
+ }
79
+ // Upload do vídeo
80
+ const videoPath = this.generatePath(options, filename, 'original');
81
+ const result = yield this.provider.upload(file, videoPath, options);
82
+ return Object.assign(Object.assign({}, result), { metadata: Object.assign(Object.assign(Object.assign({}, result.metadata), metadata), { thumbnailUrl }) });
83
+ }
84
+ finally {
85
+ // Limpar arquivo temporário
86
+ yield fs_1.promises.unlink(tempPath).catch(() => { });
87
+ }
88
+ });
89
+ }
90
+ processImage(file, config) {
91
+ return __awaiter(this, void 0, void 0, function* () {
92
+ const versions = {};
93
+ for (const [size, settings] of Object.entries(config.imageQualities)) {
94
+ versions[size] = yield (0, sharp_1.default)(file)
95
+ .resize(settings.width, settings.height, {
96
+ fit: 'inside',
97
+ withoutEnlargement: true
98
+ })
99
+ .jpeg({ quality: settings.quality })
100
+ .toBuffer();
101
+ }
102
+ return versions;
103
+ });
104
+ }
105
+ generatePath(options, filename, variant) {
106
+ const parts = [options.tenant]; // Declarar como array de strings
107
+ if (options.userId) {
108
+ parts.push('users', options.userId);
109
+ }
110
+ else if (options.targetId) {
111
+ parts.push('targets', options.targetId);
112
+ }
113
+ parts.push(options.type);
114
+ parts.push(variant);
115
+ parts.push(`${(0, uuid_1.v4)()}-${filename}`);
116
+ return parts.join('/');
117
+ }
118
+ getVideoMetadata(path) {
119
+ return __awaiter(this, void 0, void 0, function* () {
120
+ return new Promise((resolve, reject) => {
121
+ fluent_ffmpeg_1.default.ffprobe(path, (err, metadata) => {
122
+ if (err)
123
+ reject(err);
124
+ else {
125
+ const video = metadata.streams.find(s => s.codec_type === 'video');
126
+ resolve({
127
+ duration: metadata.format.duration,
128
+ width: video === null || video === void 0 ? void 0 : video.width,
129
+ height: video === null || video === void 0 ? void 0 : video.height
130
+ });
131
+ }
132
+ });
133
+ });
134
+ });
135
+ }
136
+ generateVideoThumbnail(videoPath) {
137
+ return __awaiter(this, void 0, void 0, function* () {
138
+ const outputPath = `/tmp/thumb-${(0, uuid_1.v4)()}.jpg`;
139
+ return new Promise((resolve, reject) => {
140
+ (0, fluent_ffmpeg_1.default)(videoPath)
141
+ .screenshots({
142
+ timestamps: ['50%'],
143
+ filename: outputPath,
144
+ size: '320x240'
145
+ })
146
+ .on('end', () => __awaiter(this, void 0, void 0, function* () {
147
+ const buffer = yield fs_1.promises.readFile(outputPath);
148
+ yield fs_1.promises.unlink(outputPath).catch(() => { });
149
+ resolve(buffer);
150
+ }))
151
+ .on('error', reject);
152
+ });
153
+ });
154
+ }
155
+ }
156
+ exports.StorageService = StorageService;
@@ -0,0 +1,37 @@
1
+ import { Tenant } from '../../types/tenant';
2
+ export type MediaType = 'photo' | 'video';
3
+ export interface UploadOptions {
4
+ tenant: Tenant;
5
+ userId?: string;
6
+ targetId?: string;
7
+ type: MediaType;
8
+ generateThumbnail?: boolean;
9
+ }
10
+ export interface MediaMetadata {
11
+ url: string;
12
+ thumbnailUrl?: string;
13
+ size: number;
14
+ type: string;
15
+ width?: number;
16
+ height?: number;
17
+ duration?: number;
18
+ uploadedAt: Date;
19
+ }
20
+ export interface UploadResult {
21
+ id: string;
22
+ metadata: MediaMetadata;
23
+ }
24
+ export interface StorageProvider {
25
+ upload(file: Buffer, filename: string, options: UploadOptions): Promise<UploadResult>;
26
+ delete(id: string): Promise<boolean>;
27
+ get(id: string): Promise<MediaMetadata | null>;
28
+ list(filters: ListFilters): Promise<MediaMetadata[]>;
29
+ }
30
+ export interface ListFilters {
31
+ tenant: Tenant;
32
+ userId?: string;
33
+ targetId?: string;
34
+ type?: MediaType;
35
+ limit?: number;
36
+ offset?: number;
37
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wrcb/cb-common",
3
- "version": "1.0.383",
3
+ "version": "1.0.385",
4
4
  "description": "Common resources between services",
5
5
  "main": "./build/index.js",
6
6
  "types": "./build/index.d.ts",
@@ -28,6 +28,8 @@
28
28
  "license": "ISC",
29
29
  "devDependencies": {
30
30
  "@types/express-rate-limit": "^5.1.3",
31
+ "@types/fluent-ffmpeg": "^2.1.27",
32
+ "@types/multer": "^2.0.0",
31
33
  "@typescript-eslint/eslint-plugin": "^8.29.1",
32
34
  "@typescript-eslint/parser": "^8.29.1",
33
35
  "eslint": "^9.24.0",
@@ -42,15 +44,20 @@
42
44
  "@types/express": "^4.17.21",
43
45
  "@types/jsonwebtoken": "^9.0.6",
44
46
  "@types/supertest": "^6.0.2",
47
+ "aws-sdk": "^2.1692.0",
45
48
  "cookie-session": "^2.1.0",
46
49
  "express": "^4.21.0",
47
50
  "express-rate-limit": "^7.5.0",
48
51
  "express-validator": "^7.1.0",
52
+ "fluent-ffmpeg": "^2.1.3",
49
53
  "ioredis": "^5.6.1",
50
54
  "jsonwebtoken": "^9.0.2",
55
+ "minio": "^8.0.5",
51
56
  "mongoose": "^8.6.3",
57
+ "multer": "^2.0.2",
52
58
  "node-nats-streaming": "^0.3.2",
53
59
  "rate-limit-redis": "^4.2.0",
60
+ "sharp": "^0.34.3",
54
61
  "uuid": "^11.1.0"
55
62
  }
56
63
  }