@wrcb/cb-common 1.0.382 → 1.0.384
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 +2 -0
- package/build/index.js +2 -0
- package/build/middlewares/uploadMedia.d.ts +14 -0
- package/build/middlewares/uploadMedia.js +60 -0
- package/build/server.d.ts +7 -0
- package/build/server.js +7 -0
- package/build/services/TenantDataService.d.ts +2 -0
- package/build/services/TenantDataService.js +36 -3
- package/build/storage/factory.d.ts +4 -0
- package/build/storage/factory.js +32 -0
- package/build/storage/interfaces/MediaConfiguration.d.ts +23 -0
- package/build/storage/interfaces/MediaConfiguration.js +2 -0
- package/build/storage/providers/minio.d.ts +19 -0
- package/build/storage/providers/minio.js +134 -0
- package/build/storage/providers/spaces.d.ts +17 -0
- package/build/storage/providers/spaces.js +128 -0
- package/build/storage/services/storageService.d.ts +11 -0
- package/build/storage/services/storageService.js +156 -0
- package/build/storage/types/index.d.ts +37 -0
- package/build/storage/types/index.js +2 -0
- package/package.json +8 -1
package/build/index.d.ts
CHANGED
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,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,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
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wrcb/cb-common",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.384",
|
|
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
|
}
|