Skip to content

Commit

Permalink
Merge pull request #287 from internxt/feat/workspaces-main
Browse files Browse the repository at this point in the history
[_] feat/workspaces (WIP)
  • Loading branch information
sg-gs authored Jul 12, 2024
2 parents e36a2c1 + e85a282 commit 217d334
Show file tree
Hide file tree
Showing 67 changed files with 13,586 additions and 90 deletions.
6 changes: 5 additions & 1 deletion .env.template
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
CRYPTO_SECRET=6KYQBP847D4ATSFA
CRYPTO_SECRET2=8Q8VMUE3BJZV87GT
GATEWAY_SECRET=LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlCT3dJQkFBSkJBTDlDTVRlZGEramdIcGJuTmtlSm51TlpnYzg5TGFvMGNQNkl6dlJrYTJ0MUVKbnh5ZTA1CndSWGZLMXFpbTFOMGU3cGhkd0RkRWYvNGJ1eFc5V2g1UWxzQ0F3RUFBUUpCQUpnRXljLzF2VDdGWFNyK3JpTWcKWFAxQ09LNTdaeCtCUFVyamZQTytHYSszWk1MRHhqaG44dGZmV1E4VUpKemJ5VkQ0Q0JqTmNra2xRN3phQ29BNwo1WWtDSVFEd0h2MXhVRkFVUkI2b3QwL0JMMWNxek5SNU80dFBMT0NjL2gyK0o4Y09WUUloQU12b0FrMm5IQWhSClpRNmhNZGFTdWtPVTE3MTYvRGxnNWNiSXNWYXh0bDN2QWlBTUdTT2YzL0lJODEyd0ZueFlPWEJrNGFrYTZwc2MKUkNDVkNHQ3JRZ25QZVFJZ2NTU2E2cFc0YzFFZTN5Qkl0RVNVZ0YxOTNKRDZsYWdUdDlxeXRHVkZ5UmNDSVFDYgp6dE85ampXcERmYTlnWTV2dVB4MFgyUkcxbjJQb0ZYVjVXT29RanNqbnc9PQotLS0tLUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQo=
DRIVE_GATEWAY_PUBLIC_SECRET=LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUlJQklqQU5CZ2txaGtpRzl3MEJBUUVGQUFPQ0FROEFNSUlCQ2dLQ0FRRUE0bmVlL0Y5OC8wTjJhbFNKQ3JnMAp5bzJRRysydzR5SGk3ZXVDT3JYYUhENzFmN0NrWExWdHlxWTFRRWNFVnFuQm5QUnVpR1EvSklzWkJKVXhoQTlOCmdwdUpIbVY0aytnMEorRGcxeS9wd3k4L0lNM0FhTnNtWWp4c0NZQUZBSWhqUDZNZlBsVTNTYWdyekVRNklFU3MKeHBzT1JhRXd3WUZIWm42TU50b0FGbktMb3VlMVprUVpSSlVwNGZSSlMvcGNrdVNSNjZFcjFLMjhYckpieGE5egpCNG9SbFJMb0ExQ2cvTFN6ZEFQc2lVMzlSOWtlamxBKzFOMkxLVWFrNVJ3OWN5TkM0N3lHS3ZicSt2TTNYaUFmCk43Wk1teTdkY09aeXcyZW9idFFUVzVtTmR1WElWOHhWeWUzMkpyY3BuYWVqbDlUMG5TWGJEWE9OV2d6N0lWcmoKNFFJREFRQUIKLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0t
HOST_DRIVE_WEB=http://localhost:3000
JWT_SECRET=38FTANE5LY90NHYZ
MAGIC_IV=d139cb9a2cd17092e79e1861cf9d7023
Expand Down Expand Up @@ -41,8 +42,11 @@ STRIPE_SK_TEST=sk_test_y
GATEWAY_USER=user
GATEWAY_PASS=gatewaypass


WORKSPACES_USER_INVITATION_EMAIL_ID=d-de1ed6df4a9947129c0bf592c808b58d
WORKSPACES_GUEST_USER_INVITATION_EMAIL_ID=d-41b4608fc94a41bca65aab7ed6ccad15
PAYMENTS_API_URL=http://payments-api:8003

JITSI_SECRET=jitsi-secret
JITSI_APP_ID=jitsi-app-id
JITSI_API_KEY=jitsi-api-key
JITSI_API_KEY=jitsi-api-key
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@
"geoip-lite": "^1.4.8",
"helmet": "^7.1.0",
"mariadb": "^3.2.2",
"multer-s3": "^3.0.1",
"nest-winston": "^1.9.4",
"node-device-detector": "2.0.17",
"openpgp": "^5.11.0",
Expand Down Expand Up @@ -98,12 +99,14 @@
"@types/hapi__joi": "^17.1.13",
"@types/jest": "29.5.8",
"@types/jsonwebtoken": "9.0.2",
"@types/multer": "^1.4.11",
"@types/node": "^20.9.0",
"@types/sequelize": "^4.28.18",
"@types/supertest": "^2.0.16",
"@types/uuid": "^9.0.7",
"@typescript-eslint/eslint-plugin": "^6.11.0",
"@typescript-eslint/parser": "^6.11.0",
"aws-sdk-client-mock": "^4.0.0",
"chance": "^1.1.11",
"eslint": "^8.53.0",
"eslint-config-prettier": "^9.0.0",
Expand Down
4 changes: 4 additions & 0 deletions src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ import { FuzzySearchModule } from './modules/fuzzy-search/fuzzy-search.module';
import { SharingModule } from './modules/sharing/sharing.module';
import { AppSumoModule } from './modules/app-sumo/app-sumo.module';
import { PlanModule } from './modules/plan/plan.module';
import { WorkspacesModule } from './modules/workspaces/workspaces.module';
import { GatewayModule } from './modules/gateway/gateway.module';

@Module({
imports: [
Expand Down Expand Up @@ -114,6 +116,8 @@ import { PlanModule } from './modules/plan/plan.module';
SharingModule,
AppSumoModule,
PlanModule,
WorkspacesModule,
GatewayModule,
],
controllers: [],
providers: [],
Expand Down
14 changes: 14 additions & 0 deletions src/config/configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ export default () => ({
cryptoSecret2: process.env.CRYPTO_SECRET2,
jwt: process.env.JWT_SECRET,
gateway: process.env.GATEWAY_SECRET,
driveGateway: process.env.DRIVE_GATEWAY_PUBLIC_SECRET,
captcha: process.env.RECAPTCHA_V3,
jitsiSecret: process.env.JITSI_SECRET,
},
Expand Down Expand Up @@ -99,6 +100,10 @@ export default () => ({
process.env.SENDGRID_TEMPLATE_DRIVE_UPDATE_USER_EMAIL || '',
unblockAccountEmail:
process.env.SENDGRID_TEMPLATE_DRIVE_UNBLOCK_ACCOUNT || '',
invitationToWorkspaceUser:
process.env.WORKSPACES_USER_INVITATION_EMAIL_ID || '',
invitationToWorkspaceGuestUser:
process.env.WORKSPACES_GUEST_USER_INVITATION_EMAIL_ID || '',
},
},
newsletter: {
Expand All @@ -121,4 +126,13 @@ export default () => ({
appId: process.env.JITSI_APP_ID,
apiKey: process.env.JITSI_API_KEY,
},
avatar: {
accessKey: process.env.AVATAR_ACCESS_KEY || 'internxt',
secretKey: process.env.AVATAR_SECRET_KEY || 'internxt',
bucket: process.env.AVATAR_BUCKET || 'avatars',
region: process.env.AVATAR_REGION || 'us-east-1',
endpoint: process.env.AVATAR_ENDPOINT,
endpointForSignedUrls: process.env.AVATAR_ENDPOINT_REWRITE_FOR_SIGNED_URLS,
forcePathStyle: process.env.AVATAR_FORCE_PATH_STYLE || 'true',
},
});
71 changes: 71 additions & 0 deletions src/externals/avatar/avatar.service.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { ConfigModule, ConfigService } from '@nestjs/config';
import { Test, TestingModule } from '@nestjs/testing';
import { AvatarService } from './avatar.service';
import { S3Client, DeleteObjectCommand } from '@aws-sdk/client-s3';
import { mockClient } from 'aws-sdk-client-mock';
import configuration from '../../config/configuration';
import { v4 } from 'uuid';
import * as s3RequestPresigner from '@aws-sdk/s3-request-presigner';

jest.mock('@aws-sdk/s3-request-presigner');

describe('Avatar Service', () => {
let service: AvatarService;
let mockS3Client;

beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
imports: [
ConfigModule.forRoot({
envFilePath: [`.env.${process.env.NODE_ENV}`],
load: [configuration],
isGlobal: true,
}),
],
providers: [
{
provide: AvatarService,
useFactory: async (configService: ConfigService) => {
return new AvatarService(configService);
},
inject: [ConfigService],
},
],
}).compile();

service = module.get<AvatarService>(AvatarService);
mockS3Client = mockClient(S3Client);
});

describe('Get avatar download url', () => {
it('When avatar key is null then it should throw an error', async () => {
const avatarKey = null;
jest
.spyOn(s3RequestPresigner, 'getSignedUrl')
.mockRejectedValueOnce(new Error());
await expect(service.getDownloadUrl(avatarKey)).rejects.toThrow();
});
it('When avatar key is not null then it should return an url', async () => {
const avatarKey = v4();
const expectedUrl = `https://avatar.network.com/${avatarKey}`;
jest
.spyOn(s3RequestPresigner, 'getSignedUrl')
.mockResolvedValueOnce(expectedUrl);
const response = await service.getDownloadUrl(avatarKey);
expect(response).toBe(expectedUrl);
});
});

describe('Delete avatar', () => {
it('When deleting the avatar is successful, it should delete the avatar file and return true', async () => {
const avatarKey = v4();
await expect(service.deleteAvatar(avatarKey)).resolves.toBeTruthy();
});

it('When the avatar deletion fails, then the error is propagated', async () => {
const avatarKey = v4();
mockS3Client.on(DeleteObjectCommand).rejects();
await expect(service.deleteAvatar(avatarKey)).rejects.toThrow();
});
});
});
62 changes: 48 additions & 14 deletions src/externals/avatar/avatar.service.ts
Original file line number Diff line number Diff line change
@@ -1,38 +1,72 @@
import { Injectable } from '@nestjs/common';
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
import { GetObjectCommand, S3 } from '@aws-sdk/client-s3';

import {
GetObjectCommand,
S3Client,
DeleteObjectCommand,
} from '@aws-sdk/client-s3';
import { User } from '../../modules/user/user.domain';
import { Workspace } from '../../modules/workspaces/domains/workspaces.domain';
import { ConfigService } from '@nestjs/config';

@Injectable()
export class AvatarService {
async getDownloadUrl(avatar: User['avatar']): Promise<string> {
const s3 = new S3({
endpoint: process.env.AVATAR_ENDPOINT,
region: process.env.AVATAR_REGION,
private configService: ConfigService;

constructor(configService: ConfigService) {
this.configService = configService;
}

AvatarS3Instance(): S3Client {
return new S3Client({
endpoint: this.configService.get('avatar.endpoint'),
region: this.configService.get('avatar.region'),
credentials: {
accessKeyId: process.env.AVATAR_ACCESS_KEY,
secretAccessKey: process.env.AVATAR_SECRET_KEY,
accessKeyId: this.configService.get('avatar.accessKey'),
secretAccessKey: this.configService.get('avatar.secretKey'),
},
forcePathStyle: process.env.AVATAR_FORCE_PATH_STYLE === 'true',
forcePathStyle:
this.configService.get('avatar.forcePathStyle') === 'true',
});
}

async getDownloadUrl(
avatarKey: User['avatar'] | Workspace['avatar'],
): Promise<string> {
const s3Client = this.AvatarS3Instance();
const bucket = this.configService.get('avatar.bucket');
const url = await getSignedUrl(
s3,
s3Client,
new GetObjectCommand({
Bucket: process.env.AVATAR_BUCKET,
Key: avatar,
Bucket: bucket,
Key: avatarKey,
}),
{
expiresIn: 24 * 3600,
},
);

const endpointRewrite = process.env.AVATAR_ENDPOINT_REWRITE_FOR_SIGNED_URLS;
const endpointRewrite = this.configService.get(
'avatar.endpointForSignedUrls',
);
if (endpointRewrite) {
return url.replace(process.env.AVATAR_ENDPOINT, endpointRewrite);
const avatarEndpoint = this.configService.get('avatar.endpoint');
return url.replace(avatarEndpoint, endpointRewrite);
} else {
return url;
}
}

async deleteAvatar(
avatarKey: User['avatar'] | Workspace['avatar'],
): Promise<boolean> {
const s3Client = this.AvatarS3Instance();
const bucket = this.configService.get('avatar.bucket');
const deleteObjectCommand = new DeleteObjectCommand({
Bucket: bucket,
Key: avatarKey,
});
await s3Client.send(deleteObjectCommand);
return true;
}
}
41 changes: 41 additions & 0 deletions src/externals/multer/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { S3Client, ObjectCannedACL } from '@aws-sdk/client-s3';
import { v4 } from 'uuid';
import multerS3 from 'multer-s3';
import { MulterOptions } from '@nestjs/platform-express/multer/interfaces/multer-options.interface';
import configuration from '../../config/configuration';
import { BadRequestException } from '@nestjs/common';

const { avatar: avatarConfig } = configuration();

export const avatarStorageS3Config: MulterOptions = {
storage: multerS3({
s3: new S3Client({
endpoint: avatarConfig.endpoint,
region: avatarConfig.region,
credentials: {
accessKeyId: avatarConfig.accessKey,
secretAccessKey: avatarConfig.secretKey,
},
forcePathStyle: avatarConfig.forcePathStyle === 'true',
}),
bucket: avatarConfig.bucket,
acl: ObjectCannedACL.public_read,
contentType: multerS3.AUTO_CONTENT_TYPE,
key: (req, file, cb) => {
const filenameKeyS3 = v4();
req.filenameKeyS3 = filenameKeyS3;
cb(null, filenameKeyS3);
},
}),
fileFilter: (req, file, cb) => {
if (file.mimetype.startsWith('image/')) {
cb(null, true);
} else {
cb(new BadRequestException('Only image file are allowed'), false);
}
},
limits: {
fileSize: 1024 * 1024 * 1,
files: 1,
},
};
3 changes: 2 additions & 1 deletion src/modules/auth/auth.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { JwtStrategy } from './jwt.strategy';
import { UserModule } from '../user/user.module';
import { UserUseCases } from '../user/user.usecase';
import { BasicStrategy } from './basic.strategy';
import { GatewayRS256JwtStrategy } from './gateway-rs256jwt.strategy';

@Module({
imports: [
Expand All @@ -25,7 +26,7 @@ import { BasicStrategy } from './basic.strategy';
},
}),
],
providers: [JwtStrategy, BasicStrategy],
providers: [JwtStrategy, BasicStrategy, GatewayRS256JwtStrategy],
controllers: [],
exports: [JwtStrategy, BasicStrategy, PassportModule],
})
Expand Down
Loading

0 comments on commit 217d334

Please sign in to comment.