mirror of
https://github.com/EvolutionAPI/evolution-api.git
synced 2025-12-21 21:02:21 -06:00
refactor: integrations folder structure
This commit is contained in:
15
src/api/integrations/storage/s3/controllers/s3.controller.ts
Normal file
15
src/api/integrations/storage/s3/controllers/s3.controller.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { InstanceDto } from '@api/dto/instance.dto';
|
||||
import { MediaDto } from '@api/integrations/storage/s3/dto/media.dto';
|
||||
import { S3Service } from '@api/integrations/storage/s3/services/s3.service';
|
||||
|
||||
export class S3Controller {
|
||||
constructor(private readonly s3Service: S3Service) {}
|
||||
|
||||
public async getMedia(instance: InstanceDto, data: MediaDto) {
|
||||
return this.s3Service.getMedia(instance, data);
|
||||
}
|
||||
|
||||
public async getMediaUrl(instance: InstanceDto, data: MediaDto) {
|
||||
return this.s3Service.getMediaUrl(instance, data);
|
||||
}
|
||||
}
|
||||
6
src/api/integrations/storage/s3/dto/media.dto.ts
Normal file
6
src/api/integrations/storage/s3/dto/media.dto.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
export class MediaDto {
|
||||
id?: string;
|
||||
type?: string;
|
||||
messageId?: number;
|
||||
expiry?: number;
|
||||
}
|
||||
108
src/api/integrations/storage/s3/libs/minio.server.ts
Normal file
108
src/api/integrations/storage/s3/libs/minio.server.ts
Normal file
@@ -0,0 +1,108 @@
|
||||
import { ConfigService, S3 } from '@config/env.config';
|
||||
import { Logger } from '@config/logger.config';
|
||||
import { BadRequestException } from '@exceptions';
|
||||
import * as MinIo from 'minio';
|
||||
import { join } from 'path';
|
||||
import { Readable, Transform } from 'stream';
|
||||
|
||||
const logger = new Logger('S3 Service');
|
||||
|
||||
const BUCKET = new ConfigService().get<S3>('S3');
|
||||
|
||||
interface Metadata extends MinIo.ItemBucketMetadata {
|
||||
'Content-Type': string;
|
||||
}
|
||||
|
||||
const minioClient = (() => {
|
||||
if (BUCKET?.ENABLE) {
|
||||
return new MinIo.Client({
|
||||
endPoint: BUCKET.ENDPOINT,
|
||||
port: BUCKET.PORT,
|
||||
useSSL: BUCKET.USE_SSL,
|
||||
accessKey: BUCKET.ACCESS_KEY,
|
||||
secretKey: BUCKET.SECRET_KEY,
|
||||
region: BUCKET.REGION,
|
||||
});
|
||||
}
|
||||
})();
|
||||
|
||||
const bucketName = process.env.S3_BUCKET;
|
||||
|
||||
const bucketExists = async () => {
|
||||
if (minioClient) {
|
||||
try {
|
||||
const list = await minioClient.listBuckets();
|
||||
return list.find((bucket) => bucket.name === bucketName);
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const setBucketPolicy = async () => {
|
||||
if (minioClient) {
|
||||
const policy = {
|
||||
Version: '2012-10-17',
|
||||
Statement: [
|
||||
{
|
||||
Effect: 'Allow',
|
||||
Principal: '*',
|
||||
Action: ['s3:GetObject'],
|
||||
Resource: [`arn:aws:s3:::${bucketName}/*`],
|
||||
},
|
||||
],
|
||||
};
|
||||
await minioClient.setBucketPolicy(bucketName, JSON.stringify(policy));
|
||||
}
|
||||
};
|
||||
|
||||
const createBucket = async () => {
|
||||
if (minioClient) {
|
||||
try {
|
||||
const exists = await bucketExists();
|
||||
if (!exists) {
|
||||
await minioClient.makeBucket(bucketName);
|
||||
}
|
||||
|
||||
await setBucketPolicy();
|
||||
|
||||
logger.info(`S3 Bucket ${bucketName} - ON`);
|
||||
return true;
|
||||
} catch (error) {
|
||||
logger.error('S3 ERROR:');
|
||||
logger.error(error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
createBucket();
|
||||
|
||||
const uploadFile = async (fileName: string, file: Buffer | Transform | Readable, size: number, metadata: Metadata) => {
|
||||
if (minioClient) {
|
||||
const objectName = join('evolution-api', fileName);
|
||||
try {
|
||||
metadata['custom-header-application'] = 'evolution-api';
|
||||
return await minioClient.putObject(bucketName, objectName, file, size, metadata);
|
||||
} catch (error) {
|
||||
logger.error(error);
|
||||
return error;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const getObjectUrl = async (fileName: string, expiry?: number) => {
|
||||
if (minioClient) {
|
||||
try {
|
||||
const objectName = join('evolution-api', fileName);
|
||||
if (expiry) {
|
||||
return await minioClient.presignedGetObject(bucketName, objectName, expiry);
|
||||
}
|
||||
return await minioClient.presignedGetObject(bucketName, objectName);
|
||||
} catch (error) {
|
||||
throw new BadRequestException(error?.message);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export { BUCKET, getObjectUrl, uploadFile };
|
||||
35
src/api/integrations/storage/s3/routes/s3.router.ts
Normal file
35
src/api/integrations/storage/s3/routes/s3.router.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import { RouterBroker } from '@api/abstract/abstract.router';
|
||||
import { MediaDto } from '@api/integrations/storage/s3/dto/media.dto';
|
||||
import { s3Schema, s3UrlSchema } from '@api/integrations/storage/s3/validate/s3.schema';
|
||||
import { HttpStatus } from '@api/routes/index.router';
|
||||
import { s3Controller } from '@api/server.module';
|
||||
import { RequestHandler, Router } from 'express';
|
||||
|
||||
export class S3Router extends RouterBroker {
|
||||
constructor(...guards: RequestHandler[]) {
|
||||
super();
|
||||
this.router
|
||||
.post(this.routerPath('getMedia'), ...guards, async (req, res) => {
|
||||
const response = await this.dataValidate<MediaDto>({
|
||||
request: req,
|
||||
schema: s3Schema,
|
||||
ClassRef: MediaDto,
|
||||
execute: (instance, data) => s3Controller.getMedia(instance, data),
|
||||
});
|
||||
|
||||
res.status(HttpStatus.CREATED).json(response);
|
||||
})
|
||||
.post(this.routerPath('getMediaUrl'), ...guards, async (req, res) => {
|
||||
const response = await this.dataValidate<MediaDto>({
|
||||
request: req,
|
||||
schema: s3UrlSchema,
|
||||
ClassRef: MediaDto,
|
||||
execute: (instance, data) => s3Controller.getMediaUrl(instance, data),
|
||||
});
|
||||
|
||||
res.status(HttpStatus.OK).json(response);
|
||||
});
|
||||
}
|
||||
|
||||
public readonly router: Router = Router();
|
||||
}
|
||||
50
src/api/integrations/storage/s3/services/s3.service.ts
Normal file
50
src/api/integrations/storage/s3/services/s3.service.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import { InstanceDto } from '@api/dto/instance.dto';
|
||||
import { MediaDto } from '@api/integrations/storage/s3/dto/media.dto';
|
||||
import { getObjectUrl } from '@api/integrations/storage/s3/libs/minio.server';
|
||||
import { PrismaRepository } from '@api/repository/repository.service';
|
||||
import { Logger } from '@config/logger.config';
|
||||
import { BadRequestException } from '@exceptions';
|
||||
|
||||
export class S3Service {
|
||||
constructor(private readonly prismaRepository: PrismaRepository) {}
|
||||
|
||||
private readonly logger = new Logger('S3Service');
|
||||
|
||||
public async getMedia(instance: InstanceDto, query?: MediaDto) {
|
||||
try {
|
||||
const where: any = {
|
||||
instanceId: instance.instanceId,
|
||||
...query,
|
||||
};
|
||||
|
||||
const media = await this.prismaRepository.media.findMany({
|
||||
where,
|
||||
select: {
|
||||
id: true,
|
||||
fileName: true,
|
||||
type: true,
|
||||
mimetype: true,
|
||||
createdAt: true,
|
||||
Message: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!media || media.length === 0) {
|
||||
throw 'Media not found';
|
||||
}
|
||||
|
||||
return media;
|
||||
} catch (error) {
|
||||
throw new BadRequestException(error);
|
||||
}
|
||||
}
|
||||
|
||||
public async getMediaUrl(instance: InstanceDto, data: MediaDto) {
|
||||
const media = (await this.getMedia(instance, { id: data.id }))[0];
|
||||
const mediaUrl = await getObjectUrl(media.fileName, data.expiry);
|
||||
return {
|
||||
mediaUrl,
|
||||
...media,
|
||||
};
|
||||
}
|
||||
}
|
||||
43
src/api/integrations/storage/s3/validate/s3.schema.ts
Normal file
43
src/api/integrations/storage/s3/validate/s3.schema.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import { JSONSchema7 } from 'json-schema';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
const isNotEmpty = (...propertyNames: string[]): JSONSchema7 => {
|
||||
const properties = {};
|
||||
propertyNames.forEach(
|
||||
(property) =>
|
||||
(properties[property] = {
|
||||
minLength: 1,
|
||||
description: `The "${property}" cannot be empty`,
|
||||
}),
|
||||
);
|
||||
return {
|
||||
if: {
|
||||
propertyNames: {
|
||||
enum: [...propertyNames],
|
||||
},
|
||||
},
|
||||
then: { properties },
|
||||
};
|
||||
};
|
||||
|
||||
export const s3Schema: JSONSchema7 = {
|
||||
$id: v4(),
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'string' },
|
||||
type: { type: 'string' },
|
||||
messageId: { type: 'integer' },
|
||||
},
|
||||
...isNotEmpty('id', 'type', 'messageId'),
|
||||
};
|
||||
|
||||
export const s3UrlSchema: JSONSchema7 = {
|
||||
$id: v4(),
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'string', pattern: '\\d+', minLength: 1 },
|
||||
expiry: { type: 'string', pattern: '\\d+', minLength: 1 },
|
||||
},
|
||||
...isNotEmpty('id'),
|
||||
required: ['id'],
|
||||
};
|
||||
Reference in New Issue
Block a user