diff --git a/packages/api-gateway/src/models/file_config.dto.ts b/packages/api-gateway/src/models/file_config.dto.ts index 12de043a..508c232b 100644 --- a/packages/api-gateway/src/models/file_config.dto.ts +++ b/packages/api-gateway/src/models/file_config.dto.ts @@ -53,3 +53,15 @@ export class FileConfigResponse { this.buckets = fileConfig.buckets; } } + +export class SetupFileServiceResponse { + @ApiProperty({ + type: 'boolean', + description: 'Whether file services were successfully setup', + }) + success: boolean; + + constructor(res: FileConfigProto.SetupResponse) { + this.success = res.success; + } +} diff --git a/packages/api-gateway/src/modules/file_config/file_config.controller.ts b/packages/api-gateway/src/modules/file_config/file_config.controller.ts index b06872a8..54fbef6d 100644 --- a/packages/api-gateway/src/modules/file_config/file_config.controller.ts +++ b/packages/api-gateway/src/modules/file_config/file_config.controller.ts @@ -5,11 +5,13 @@ import { Inject, OnModuleInit, Param, + Post, } from '@nestjs/common'; import { ClientGrpc } from '@nestjs/microservices'; import { ApiBadRequestResponse, ApiBearerAuth, + ApiCreatedResponse, ApiNotFoundResponse, ApiOkResponse, ApiOperation, @@ -19,26 +21,39 @@ import { import { AuthCommonProto, FileConfigProto } from 'juno-proto'; import { lastValueFrom } from 'rxjs'; import { ApiKey } from 'src/decorators/api_key.decorator'; -import { FileConfigResponse } from 'src/models/file_config.dto'; +import { + FileConfigResponse, + SetupFileServiceResponse, +} from 'src/models/file_config.dto'; -const { FILE_SERVICE_CONFIG_DB_SERVICE_NAME } = FileConfigProto; +const { + FILE_SERVICE_CONFIG_DB_SERVICE_NAME, + FILE_SERVICE_CONFIG_SERVICE_NAME, +} = FileConfigProto; @ApiBearerAuth('api_key') @ApiTags('file_config') @Controller('file') export class FileConfigController implements OnModuleInit { + private fileConfigService: FileConfigProto.FileServiceConfigServiceClient; private fileConfigDBService: FileConfigProto.FileServiceConfigDbServiceClient; constructor( @Inject(FILE_SERVICE_CONFIG_DB_SERVICE_NAME) + private fileConfigDbClient: ClientGrpc, + @Inject(FILE_SERVICE_CONFIG_SERVICE_NAME) private fileConfigClient: ClientGrpc, ) {} onModuleInit() { this.fileConfigDBService = - this.fileConfigClient.getService( + this.fileConfigDbClient.getService( FILE_SERVICE_CONFIG_DB_SERVICE_NAME, ); + this.fileConfigService = + this.fileConfigClient.getService( + FILE_SERVICE_CONFIG_SERVICE_NAME, + ); } @Get('config/:projectId') @@ -75,4 +90,27 @@ export class FileConfigController implements OnModuleInit { return new FileConfigResponse(await lastValueFrom(config)); } + + @ApiOperation({ + summary: 'Sets up file services for the Project/Environment', + }) + @ApiCreatedResponse({ + description: 'File Services setup successfully', + type: SetupFileServiceResponse, + }) + @ApiUnauthorizedResponse({ description: 'Unauthorized' }) + @ApiBadRequestResponse({ description: 'Bad Request' }) + @Post('config/setup') + async setup( + @ApiKey() apiKey: AuthCommonProto.ApiKey, + ): Promise { + const setupResponse = await lastValueFrom( + this.fileConfigService.setup({ + projectId: apiKey.project.id, + environment: apiKey.environment, + }), + ); + + return new SetupFileServiceResponse(setupResponse); + } } diff --git a/packages/api-gateway/src/modules/file_config/file_config.module.ts b/packages/api-gateway/src/modules/file_config/file_config.module.ts index c8d1599b..08996bc0 100644 --- a/packages/api-gateway/src/modules/file_config/file_config.module.ts +++ b/packages/api-gateway/src/modules/file_config/file_config.module.ts @@ -15,7 +15,10 @@ import { API_KEY_SERVICE_NAME, JUNO_API_KEY_PACKAGE_NAME, } from 'juno-proto/dist/gen/api_key'; -import { FILE_SERVICE_CONFIG_DB_SERVICE_NAME } from 'juno-proto/dist/gen/file_config'; +import { + FILE_SERVICE_CONFIG_DB_SERVICE_NAME, + FILE_SERVICE_CONFIG_SERVICE_NAME, +} from 'juno-proto/dist/gen/file_config'; import { JWT_SERVICE_NAME, JUNO_JWT_PACKAGE_NAME, @@ -45,6 +48,15 @@ import { protoPath: FileConfigProtoFile, }, }, + { + name: FILE_SERVICE_CONFIG_SERVICE_NAME, + transport: Transport.GRPC, + options: { + url: process.env.FILE_SERVICE_ADDR, + package: FileConfigProto.JUNO_FILE_SERVICE_CONFIG_PACKAGE_NAME, + protoPath: FileConfigProtoFile, + }, + }, { name: JWT_SERVICE_NAME, transport: Transport.GRPC, diff --git a/packages/api-gateway/test/file_bucket.e2e-spec.ts b/packages/api-gateway/test/file_bucket.e2e-spec.ts index 0cbc847e..9ef9cbcc 100644 --- a/packages/api-gateway/test/file_bucket.e2e-spec.ts +++ b/packages/api-gateway/test/file_bucket.e2e-spec.ts @@ -143,7 +143,9 @@ describe('File Bucket Routes', () => { }, }; const client = new S3Client(metadata); - const command = new DeleteBucketCommand({ Bucket: uniqueBucketName }); + const command = new DeleteBucketCommand({ + Bucket: `${uniqueBucketName}-0-prod`, + }); await client.send(command); }); @@ -223,7 +225,7 @@ describe('File Bucket Routes', () => { }; const client = new S3Client(metadata); const command = new DeleteBucketCommand({ - Bucket: uniqueBucketName + '-duplicate', + Bucket: uniqueBucketName + '-duplicate-0-prod', }); await client.send(command); diff --git a/packages/api-gateway/test/file_config.e2e-spec.ts b/packages/api-gateway/test/file_config.e2e-spec.ts index 5060b97f..a2f8e4b7 100644 --- a/packages/api-gateway/test/file_config.e2e-spec.ts +++ b/packages/api-gateway/test/file_config.e2e-spec.ts @@ -1,16 +1,16 @@ -import { Test, TestingModule } from '@nestjs/testing'; +import * as GRPC from '@grpc/grpc-js'; +import * as ProtoLoader from '@grpc/proto-loader'; import { ClassSerializerInterceptor, INestApplication, ValidationPipe, } from '@nestjs/common'; -import { AppModule } from './../src/app.module'; import { Reflector } from '@nestjs/core'; -import * as request from 'supertest'; +import { Test, TestingModule } from '@nestjs/testing'; import { ResetProtoFile } from 'juno-proto'; -import * as GRPC from '@grpc/grpc-js'; -import * as ProtoLoader from '@grpc/proto-loader'; import { RpcExceptionFilter } from 'src/rpc_exception_filter'; +import * as request from 'supertest'; +import { AppModule } from './../src/app.module'; let app: INestApplication; const ADMIN_EMAIL = 'test-superadmin@test.com'; @@ -101,4 +101,17 @@ describe('File Config Routes', () => { .set('Authorization', 'Bearer ' + apiKey) .expect(404); }); + + it('Should setup file services for a valid project + api key', async () => { + return await request(app.getHttpServer()) + .post('/file/config/setup') + .set('Authorization', 'Bearer ' + apiKey) + .expect(201); + }); + + it('Should throw a 401 status error when setup file services without valid credentials', async () => { + return await request(app.getHttpServer()) + .post('/file/config/setup') + .expect(401); + }); }); diff --git a/packages/db-service/src/modules/file_config/file_config.controller.ts b/packages/db-service/src/modules/file_config/file_config.controller.ts index 18657e62..19e6b394 100644 --- a/packages/db-service/src/modules/file_config/file_config.controller.ts +++ b/packages/db-service/src/modules/file_config/file_config.controller.ts @@ -1,9 +1,9 @@ +import { status } from '@grpc/grpc-js'; import { Controller } from '@nestjs/common'; -import { FileServiceConfigService } from './file_config.service'; import { RpcException } from '@nestjs/microservices'; -import { status } from '@grpc/grpc-js'; -import { FileConfigProto } from 'juno-proto'; import { Prisma } from '@prisma/client'; +import { FileConfigProto } from 'juno-proto'; +import { FileServiceConfigService } from './file_config.service'; @Controller() @FileConfigProto.FileServiceConfigDbServiceControllerMethods() @@ -26,16 +26,15 @@ export class FileConfigController environment, }); if (existingConfig) { - throw new RpcException({ - code: status.ALREADY_EXISTS, - message: 'Config already exists', - }); + return { + id: existingConfig.id, + environment: existingConfig.environment, + files: [], + buckets: [], + }; } - } catch (error) { - throw new RpcException({ - code: status.ALREADY_EXISTS, - message: 'Config already exists', - }); + } catch { + // Intentionally ignoring getConfig error for now } const config = await this.fileServiceConfigService.createConfig(request); diff --git a/packages/db-service/test/file_config.e2e-spec.ts b/packages/db-service/test/file_config.e2e-spec.ts index 37b76897..e3025baa 100644 --- a/packages/db-service/test/file_config.e2e-spec.ts +++ b/packages/db-service/test/file_config.e2e-spec.ts @@ -1,15 +1,15 @@ -import { Test, TestingModule } from '@nestjs/testing'; -import { INestMicroservice } from '@nestjs/common'; -import { AppModule } from '../src/app.module'; -import * as ProtoLoader from '@grpc/proto-loader'; import * as GRPC from '@grpc/grpc-js'; +import * as ProtoLoader from '@grpc/proto-loader'; +import { INestMicroservice } from '@nestjs/common'; import { MicroserviceOptions, Transport } from '@nestjs/microservices'; +import { Test, TestingModule } from '@nestjs/testing'; import { FileConfigProto, FileConfigProtoFile, ResetProto, ResetProtoFile, } from 'juno-proto'; +import { AppModule } from '../src/app.module'; let app: INestMicroservice; let createdConfigId: number; @@ -135,8 +135,8 @@ describe('File Service Config Tests', () => { }); it('creates a duplicate file service config', async () => { - try { - await new Promise((resolve, reject) => { + const response: FileConfigProto.FileServiceConfig = await new Promise( + (resolve, reject) => { configClient.createConfig( { projectId: 0, @@ -152,10 +152,9 @@ describe('File Service Config Tests', () => { } }, ); - }); - } catch (err) { - expect(err).toBeDefined(); - } + }, + ); + expect(response).toHaveProperty('id'); }); it('deletes a config', async () => { diff --git a/packages/file-service/src/app.module.ts b/packages/file-service/src/app.module.ts index b36d60b1..b39abbfc 100644 --- a/packages/file-service/src/app.module.ts +++ b/packages/file-service/src/app.module.ts @@ -1,19 +1,21 @@ import { Module } from '@nestjs/common'; +import { SentryModule } from '@sentry/nestjs/setup'; import { AppController } from './app.controller'; import { AppService } from './app.service'; -import { SentryModule } from '@sentry/nestjs/setup'; -import { HealthModule } from './modules/health/health.module'; -import { FileUploadModule } from './modules/file_upload/file_upload.module'; -import { FileProviderModule } from './modules/file_provider/file_provider.module'; import { FileBucketModule } from './modules/file_bucket/file_bucket.module'; +import { FileConfigModule } from './modules/file_config/file_config.module'; +import { FileProviderModule } from './modules/file_provider/file_provider.module'; +import { FileTransferModule } from './modules/file_transfer/file_transfer.module'; +import { HealthModule } from './modules/health/health.module'; @Module({ imports: [ SentryModule.forRoot(), HealthModule, - FileUploadModule, + FileTransferModule, FileProviderModule, FileBucketModule, + FileConfigModule, ], controllers: [AppController], providers: [AppService], diff --git a/packages/file-service/src/modules/file_bucket/azure_handler.ts b/packages/file-service/src/modules/file_bucket/azure_handler.ts index 1a6f19ad..64aa1d88 100644 --- a/packages/file-service/src/modules/file_bucket/azure_handler.ts +++ b/packages/file-service/src/modules/file_bucket/azure_handler.ts @@ -46,7 +46,11 @@ export class AzureBucketHandler { ): Promise { try { const blobClient = await this.getBlobServiceClient(); - await blobClient.getContainerClient(request.name).create(); + await blobClient + .getContainerClient( + `${request.name}-${request.configId}-${request.configEnv}`, + ) + .create(); const dbBucket = await lastValueFrom( this.fileDBService.createBucket(request), @@ -72,7 +76,11 @@ export class AzureBucketHandler { }), ); const blobClient = await this.getBlobServiceClient(); - await blobClient.getContainerClient(request.name).delete(); + await blobClient + .getContainerClient( + `${request.name}-${request.configId}-${request.configEnv}`, + ) + .delete(); return bucket; } catch (error) { throw new RpcException({ diff --git a/packages/file-service/src/modules/file_bucket/s3_handler.ts b/packages/file-service/src/modules/file_bucket/s3_handler.ts index d72050a4..83a0ad8d 100644 --- a/packages/file-service/src/modules/file_bucket/s3_handler.ts +++ b/packages/file-service/src/modules/file_bucket/s3_handler.ts @@ -36,7 +36,7 @@ export class S3BucketHandler { try { const s3Client = await this.getS3Client('us-east-005'); const createBucketCommand = new CreateBucketCommand({ - Bucket: request.name, + Bucket: `${request.name}-${request.configId}-${request.configEnv}`, }); await s3Client.send(createBucketCommand); @@ -64,7 +64,7 @@ export class S3BucketHandler { try { const s3Client = await this.getS3Client('us-east-005'); const deleteBucketCommand = new DeleteBucketCommand({ - Bucket: request.name, + Bucket: `${request.name}-${request.configId}-${request.configEnv}`, }); await s3Client.send(deleteBucketCommand); const bucket = await lastValueFrom( diff --git a/packages/file-service/src/modules/file_config/file_config.controller.ts b/packages/file-service/src/modules/file_config/file_config.controller.ts new file mode 100644 index 00000000..a531263a --- /dev/null +++ b/packages/file-service/src/modules/file_config/file_config.controller.ts @@ -0,0 +1,17 @@ +import { Controller } from '@nestjs/common'; +import { FileConfigProto } from 'juno-proto'; +import { FileConfigService } from './file_config.service'; + +@Controller() +@FileConfigProto.FileServiceConfigServiceControllerMethods() +export class FileConfigController + implements FileConfigProto.FileServiceConfigServiceController +{ + constructor(private readonly fileConfigService: FileConfigService) {} + + async setup( + request: FileConfigProto.SetupRequest, + ): Promise { + return this.fileConfigService.setup(request); + } +} diff --git a/packages/file-service/src/modules/file_config/file_config.module.ts b/packages/file-service/src/modules/file_config/file_config.module.ts new file mode 100644 index 00000000..d46cac38 --- /dev/null +++ b/packages/file-service/src/modules/file_config/file_config.module.ts @@ -0,0 +1,29 @@ +import { Module } from '@nestjs/common'; +import { ConfigModule } from '@nestjs/config'; +import { ClientsModule, Transport } from '@nestjs/microservices'; +import { FileConfigProto, FileConfigProtoFile } from 'juno-proto'; +import { join } from 'path'; +import { FileConfigController } from './file_config.controller'; +import { FileConfigService } from './file_config.service'; + +@Module({ + imports: [ + ConfigModule.forRoot({ + envFilePath: join(__dirname, '../../../../../.env.local'), + }), + ClientsModule.register([ + { + name: FileConfigProto.FILE_SERVICE_CONFIG_DB_SERVICE_NAME, + transport: Transport.GRPC, + options: { + url: process.env.DB_SERVICE_ADDR, + package: FileConfigProto.JUNO_FILE_SERVICE_CONFIG_PACKAGE_NAME, + protoPath: FileConfigProtoFile, + }, + }, + ]), + ], + controllers: [FileConfigController], + providers: [FileConfigService], +}) +export class FileConfigModule {} diff --git a/packages/file-service/src/modules/file_config/file_config.service.ts b/packages/file-service/src/modules/file_config/file_config.service.ts new file mode 100644 index 00000000..490867b2 --- /dev/null +++ b/packages/file-service/src/modules/file_config/file_config.service.ts @@ -0,0 +1,46 @@ +import { Injectable, Inject, OnModuleInit } from '@nestjs/common'; +import { ClientGrpc, RpcException } from '@nestjs/microservices'; +import { FileConfigProto } from 'juno-proto'; +import { lastValueFrom } from 'rxjs'; +import { status } from '@grpc/grpc-js'; + +@Injectable() +export class FileConfigService implements OnModuleInit { + private fileConfigDbService: FileConfigProto.FileServiceConfigDbServiceClient; + constructor( + @Inject(FileConfigProto.FILE_SERVICE_CONFIG_DB_SERVICE_NAME) + private fileDBClient: ClientGrpc, + ) {} + + onModuleInit() { + this.fileConfigDbService = + this.fileDBClient.getService( + FileConfigProto.FILE_SERVICE_CONFIG_DB_SERVICE_NAME, + ); + } + + async setup( + request: FileConfigProto.SetupRequest, + ): Promise { + const config = await lastValueFrom( + this.fileConfigDbService.createConfig({ + projectId: request.projectId, + environment: request.environment, + buckets: [], + files: [], + }), + ); + + if (!config) { + throw new RpcException({ + code: status.INVALID_ARGUMENT, + message: 'Failed to create file service config', + }); + } + + return { + success: true, + config, + }; + } +} diff --git a/packages/file-service/src/modules/file_upload/azure_handler.ts b/packages/file-service/src/modules/file_transfer/azure_handler.ts similarity index 94% rename from packages/file-service/src/modules/file_upload/azure_handler.ts rename to packages/file-service/src/modules/file_transfer/azure_handler.ts index 25b82c8a..072cf91d 100644 --- a/packages/file-service/src/modules/file_upload/azure_handler.ts +++ b/packages/file-service/src/modules/file_transfer/azure_handler.ts @@ -59,7 +59,7 @@ export class AzureFileHandler { try { const blobSAS = generateBlobSASQueryParameters( { - containerName: request.bucketName, // Required + containerName: `${request.bucketName}-${request.configId}-${request.configEnv}`, // Required blobName: request.fileName, // Required permissions: BlobSASPermissions.from({ read: true, @@ -87,7 +87,7 @@ export class AzureFileHandler { try { const blobSAS = generateBlobSASQueryParameters( { - containerName: request.bucketName, // Required + containerName: `${request.bucketName}-${request.configId}-${request.configEnv}`, // Required blobName: request.fileName, // Required permissions: BlobSASPermissions.from({ write: true, diff --git a/packages/file-service/src/modules/file_upload/file_upload.controller.ts b/packages/file-service/src/modules/file_transfer/file_transfer.controller.ts similarity index 96% rename from packages/file-service/src/modules/file_upload/file_upload.controller.ts rename to packages/file-service/src/modules/file_transfer/file_transfer.controller.ts index b570dd32..c230b50a 100644 --- a/packages/file-service/src/modules/file_upload/file_upload.controller.ts +++ b/packages/file-service/src/modules/file_transfer/file_transfer.controller.ts @@ -1,19 +1,18 @@ +import { status } from '@grpc/grpc-js'; import { Controller, Inject } from '@nestjs/common'; +import { ClientGrpc, RpcException } from '@nestjs/microservices'; import { FileProto, FileProviderProto } from 'juno-proto'; import { FileServiceController } from 'juno-proto/dist/gen/file'; -import { ClientGrpc } from '@nestjs/microservices'; -import { RpcException } from '@nestjs/microservices'; -import { status } from '@grpc/grpc-js'; import { lastValueFrom } from 'rxjs'; -import { S3FileHandler } from './s3_handler'; import { AzureFileHandler } from './azure_handler'; +import { S3FileHandler } from './s3_handler'; const { FILE_DB_SERVICE_NAME } = FileProto; const { FILE_PROVIDER_DB_SERVICE_NAME } = FileProviderProto; @Controller() @FileProto.FileServiceControllerMethods() -export class FileUploadController implements FileServiceController { +export class FileTransferController implements FileServiceController { private fileDBService: FileProto.FileDbServiceClient; private fileProviderDbService: FileProviderProto.FileProviderDbServiceClient; diff --git a/packages/file-service/src/modules/file_upload/file_upload.module.ts b/packages/file-service/src/modules/file_transfer/file_transfer.module.ts similarity index 88% rename from packages/file-service/src/modules/file_upload/file_upload.module.ts rename to packages/file-service/src/modules/file_transfer/file_transfer.module.ts index 31200941..23b39b48 100644 --- a/packages/file-service/src/modules/file_upload/file_upload.module.ts +++ b/packages/file-service/src/modules/file_transfer/file_transfer.module.ts @@ -1,14 +1,14 @@ import { Module } from '@nestjs/common'; -import { FileUploadController } from './file_upload.controller'; -import { ClientsModule, Transport } from '@nestjs/microservices'; import { ConfigModule } from '@nestjs/config'; -import { join } from 'path'; +import { ClientsModule, Transport } from '@nestjs/microservices'; import { FileProto, FileProtoFile, FileProviderProto, FileProviderProtoFile, } from 'juno-proto'; +import { join } from 'path'; +import { FileTransferController } from './file_transfer.controller'; const { FILE_DB_SERVICE_NAME, JUNO_FILE_SERVICE_FILE_PACKAGE_NAME } = FileProto; const { FILE_PROVIDER_DB_SERVICE_NAME } = FileProviderProto; @@ -39,6 +39,6 @@ const { FILE_PROVIDER_DB_SERVICE_NAME } = FileProviderProto; }, ]), ], - controllers: [FileUploadController], + controllers: [FileTransferController], }) -export class FileUploadModule {} +export class FileTransferModule {} diff --git a/packages/file-service/src/modules/file_upload/s3_handler.ts b/packages/file-service/src/modules/file_transfer/s3_handler.ts similarity index 95% rename from packages/file-service/src/modules/file_upload/s3_handler.ts rename to packages/file-service/src/modules/file_transfer/s3_handler.ts index 21a34429..fa6d6a85 100644 --- a/packages/file-service/src/modules/file_upload/s3_handler.ts +++ b/packages/file-service/src/modules/file_transfer/s3_handler.ts @@ -61,7 +61,7 @@ export class S3FileHandler { try { const client = await this.getS3Client('us-east-005'); const getcommand = new GetObjectCommand({ - Bucket: request.bucketName, + Bucket: `${request.bucketName}-${request.configId}-${request.configEnv}`, Key: request.fileName, }); @@ -83,7 +83,7 @@ export class S3FileHandler { try { const s3Client = await this.getS3Client('us-east-005'); const command = new PutObjectCommand({ - Bucket: request.bucketName, + Bucket: `${request.bucketName}-${request.configId}-${request.configEnv}`, Key: request.fileName, }); url = await getSignedUrl(s3Client, command, { expiresIn: 3600 }); diff --git a/packages/file-service/test/file_bucket.e2e-spec.ts b/packages/file-service/test/file_bucket.e2e-spec.ts index 42e3fdaa..6ecdddbf 100644 --- a/packages/file-service/test/file_bucket.e2e-spec.ts +++ b/packages/file-service/test/file_bucket.e2e-spec.ts @@ -133,7 +133,9 @@ afterAll(async () => { }, }; const client = new S3Client(metadata); - const command = new DeleteBucketCommand({ Bucket: bucketName }); + const command = new DeleteBucketCommand({ + Bucket: `${bucketName}-${configId}-${configEnv}`, + }); await client.send(command); }); @@ -154,6 +156,25 @@ const accountKey = process.env.azureStorageAccountKey; describe('File Bucket Creation Tests', () => { it('Successfully creates a bucket - S3', async () => { + const metadata = { + endpoint: baseURL, + region: region, + credentials: { + accessKeyId: accessKeyId as string, + secretAccessKey: secretAccessKey as string, + }, + }; + + const client = new S3Client(metadata); + const command = new DeleteBucketCommand({ + Bucket: `successful-bucket-${configId}-${configEnv}`, + }); + // Delete in case it already exists + + try { + await client.send(command); + } catch {} + const createBucketPromise = new Promise((resolve, reject) => { bucketClient.registerBucket( { @@ -172,21 +193,20 @@ describe('File Bucket Creation Tests', () => { ); }); await createBucketPromise; - const metadata = { - endpoint: baseURL, - region: region, - credentials: { - accessKeyId: accessKeyId as string, - secretAccessKey: secretAccessKey as string, - }, - }; - const client = new S3Client(metadata); - const command = new DeleteBucketCommand({ Bucket: 'successful-bucket' }); await client.send(command); }); it('Successfully creates a bucket - Azure', async () => { + const sharedKeyCredential = new StorageSharedKeyCredential( + accountName ?? '', + accountKey ?? '', + ); + const client = new BlobServiceClient( + `https://${accountName}.blob.core.windows.net`, + sharedKeyCredential, + ); + const createBucketPromise = new Promise((resolve, reject) => { bucketClient.registerBucket( { @@ -205,16 +225,10 @@ describe('File Bucket Creation Tests', () => { ); }); await createBucketPromise; - const sharedKeyCredential = new StorageSharedKeyCredential( - accountName ?? '', - accountKey ?? '', - ); - const client = new BlobServiceClient( - `https://${accountName}.blob.core.windows.net`, - sharedKeyCredential, - ); - await client.getContainerClient('successful-bucket-azure').delete(); + await client + .getContainerClient(`successful-bucket-azure-${configId}-${configEnv}`) + .delete(); }); it('Fails to create a bucket with invalid provider', async () => { diff --git a/packages/file-service/test/file_config.e2e-spec.ts b/packages/file-service/test/file_config.e2e-spec.ts new file mode 100644 index 00000000..2e147aa2 --- /dev/null +++ b/packages/file-service/test/file_config.e2e-spec.ts @@ -0,0 +1,123 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { INestMicroservice } from '@nestjs/common'; +import { AppModule } from './../src/app.module'; +import * as ProtoLoader from '@grpc/proto-loader'; +import * as GRPC from '@grpc/grpc-js'; +import { MicroserviceOptions, Transport } from '@nestjs/microservices'; +import { + FileConfigProto, + FileConfigProtoFile, + ResetProtoFile, +} from 'juno-proto'; + +let app: INestMicroservice; + +jest.setTimeout(15000); + +const TEST_SERVICE_ADDR = 'file-service:50006'; + +async function initApp() { + const moduleFixture: TestingModule = await Test.createTestingModule({ + imports: [AppModule], + }).compile(); + + const app = moduleFixture.createNestMicroservice({ + transport: Transport.GRPC, + options: { + package: [FileConfigProto.JUNO_FILE_SERVICE_CONFIG_PACKAGE_NAME], + protoPath: [FileConfigProtoFile], + url: TEST_SERVICE_ADDR, + }, + }); + + await app.init(); + + await app.listen(); + return app; +} + +beforeAll(async () => { + app = await initApp(); + + const proto = ProtoLoader.loadSync([ResetProtoFile]) as any; + + const protoGRPC = GRPC.loadPackageDefinition(proto) as any; + + const resetClient = new protoGRPC.juno.reset_db.DatabaseReset( + process.env.DB_SERVICE_ADDR, + GRPC.credentials.createInsecure(), + ); + + await new Promise((resolve) => { + resetClient.resetDb({}, () => { + resolve(0); + }); + }); +}); + +afterAll(async () => { + await app.close(); +}); + +describe('File Config Tests', () => { + let fileConfigClient: any; + + beforeEach(async () => { + const proto = ProtoLoader.loadSync([FileConfigProtoFile]) as any; + + // TODO once a controller is implemented, create the client for that service + const protoGRPC = GRPC.loadPackageDefinition(proto) as any; + + // good to note that juno.file_service.provider is used because that is the proto package + // FileProviderFileService is the Grpc method name in gen/file_provider.ts + fileConfigClient = + new protoGRPC.juno.file_service.config.FileServiceConfigService( + TEST_SERVICE_ADDR, + GRPC.credentials.createInsecure(), + ); + }); + + it('Register a config with valid parameters', async () => { + try { + const registerRequest: FileConfigProto.SetupRequest = { + projectId: 0, + environment: 'test', + }; + const response = await new Promise((resolve) => { + fileConfigClient.setup(registerRequest, (err: any, resp: any) => { + expect(err).toBeNull(); + resolve(resp); + }); + }); + expect(response).toBeDefined(); + } catch (err) { + expect(err).toBeNull(); + } + }); + + it('Register a duplicate config with valid parameters', async () => { + try { + const registerRequest: FileConfigProto.SetupRequest = { + projectId: 0, + environment: 'test', + }; + const response = await new Promise((resolve) => { + fileConfigClient.setup(registerRequest, (err: any, resp: any) => { + expect(err).toBeNull(); + resolve(resp); + }); + }); + expect(response).toBeDefined(); + + const response2 = await new Promise((resolve) => { + fileConfigClient.setup(registerRequest, (err: any, resp: any) => { + expect(err).toBeNull(); + resolve(resp); + }); + }); + expect(response2).toBeDefined(); + } catch (err) { + expect(err).toBeNull(); + } + }); +}); diff --git a/packages/proto/definitions/file_config.proto b/packages/proto/definitions/file_config.proto index 1d481167..b595bdbd 100644 --- a/packages/proto/definitions/file_config.proto +++ b/packages/proto/definitions/file_config.proto @@ -14,6 +14,20 @@ service FileServiceConfigDbService { rpc updateConfig(UpdateFileServiceConfigRequest) returns (FileServiceConfig); } +service FileServiceConfigService { + rpc setup(SetupRequest) returns (SetupResponse); +} + +message SetupRequest { + int64 projectId = 1; + string environment = 2; +} + +message SetupResponse { + bool success = 1; + FileServiceConfig config = 2; +} + message FileServiceConfig { int64 id = 1; repeated bucket.Bucket buckets = 2; diff --git a/packages/proto/src/gen/file_config.ts b/packages/proto/src/gen/file_config.ts index ee016826..e168b70f 100644 --- a/packages/proto/src/gen/file_config.ts +++ b/packages/proto/src/gen/file_config.ts @@ -12,6 +12,16 @@ import { Bucket } from './file_bucket'; export const protobufPackage = 'juno.file_service.config'; +export interface SetupRequest { + projectId: number; + environment: string; +} + +export interface SetupResponse { + success: boolean; + config: FileServiceConfig | undefined; +} + export interface FileServiceConfig { id: number; buckets: Bucket[]; @@ -128,3 +138,44 @@ export function FileServiceConfigDbServiceControllerMethods() { } export const FILE_SERVICE_CONFIG_DB_SERVICE_NAME = 'FileServiceConfigDbService'; + +export interface FileServiceConfigServiceClient { + setup(request: SetupRequest): Observable; +} + +export interface FileServiceConfigServiceController { + setup( + request: SetupRequest, + ): Promise | Observable | SetupResponse; +} + +export function FileServiceConfigServiceControllerMethods() { + return function (constructor: Function) { + const grpcMethods: string[] = ['setup']; + for (const method of grpcMethods) { + const descriptor: any = Reflect.getOwnPropertyDescriptor( + constructor.prototype, + method, + ); + GrpcMethod('FileServiceConfigService', method)( + constructor.prototype[method], + method, + descriptor, + ); + } + const grpcStreamMethods: string[] = []; + for (const method of grpcStreamMethods) { + const descriptor: any = Reflect.getOwnPropertyDescriptor( + constructor.prototype, + method, + ); + GrpcStreamMethod('FileServiceConfigService', method)( + constructor.prototype[method], + method, + descriptor, + ); + } + }; +} + +export const FILE_SERVICE_CONFIG_SERVICE_NAME = 'FileServiceConfigService';