Skip to content

Commit

Permalink
feat(file-uploader): added base for s3 storage (#214)
Browse files Browse the repository at this point in the history
* feat(file-uploader): added base for s3 storage

* refactor: added todo for eslint rules

* fix: try to fix yarn.lock
  • Loading branch information
slaveeks authored Mar 4, 2024
1 parent f68f4ad commit 5683e06
Show file tree
Hide file tree
Showing 7 changed files with 399 additions and 13 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
"@fastify/swagger-ui": "^1.9.3",
"@testcontainers/postgresql": "^10.2.1",
"arg": "^5.0.2",
"aws-sdk": "^2.1569.0",
"fastify": "^4.17.0",
"http-status-codes": "^2.2.0",
"jsonwebtoken": "^9.0.0",
Expand Down
2 changes: 1 addition & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { initORM, init as initRepositories } from '@repository/index.js';
const start = async (): Promise<void> => {
try {
const orm = await initORM(config.database);
const repositories = await initRepositories(orm);
const repositories = await initRepositories(orm, config.s3);
const domainServices = initDomainServices(repositories, config);
const api = new API(config.httpApi);

Expand Down
17 changes: 17 additions & 0 deletions src/infrastructure/config/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,18 @@ const OpenAIConfig = z.object({
token: z.string(),
});

/**
* S3 storage configuration
*/
const S3StorageConfig = z.object({
accessKeyId: z.string(),
secretAccessKey: z.string(),
region: z.optional(z.string()),
endpoint: z.optional(z.string()),
});

export type S3StorageConfig = z.infer<typeof S3StorageConfig>;

export type DatabaseConfig = z.infer<typeof DatabaseConfig>;

/**
Expand Down Expand Up @@ -107,6 +119,7 @@ const AppConfig = z.object({
database: DatabaseConfig,
auth: AuthConfig,
openai: OpenAIConfig,
s3: S3StorageConfig,
});

export type AppConfig = z.infer<typeof AppConfig>;
Expand Down Expand Up @@ -148,6 +161,10 @@ const defaultConfig: AppConfig = {
database: {
dsn: 'postgres://user:pass@postgres/codex-notes',
},
s3: {
accessKeyId: '',
secretAccessKey: '',
},
openai: {
token: '',
},
Expand Down
11 changes: 9 additions & 2 deletions src/repository/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { DatabaseConfig } from '@infrastructure/config/index.js';
import type { DatabaseConfig, S3StorageConfig } from '@infrastructure/config/index.js';
import NoteStorage from './storage/note.storage.js';
import NoteSettingsStorage from './storage/noteSettings.storage.js';
import NoteRelationshipStorage from './storage/noteRelations.storage.js';
Expand All @@ -17,6 +17,7 @@ import EditorToolsRepository from '@repository/editorTools.repository.js';
import TeamRepository from '@repository/team.repository.js';
import TeamStorage from '@repository/storage/team.storage.js';
import NoteRelationsRepository from '@repository/noteRelations.repository.js';
import { S3Storage } from './storage/s3/index.js';

/**
* Interface for initiated repositories
Expand Down Expand Up @@ -79,8 +80,9 @@ export async function initORM(databaseConfig: DatabaseConfig): Promise<Orm> {
* Initiate repositories
*
* @param orm - ORM instance
* @param s3Config - S3 storage config
*/
export async function init(orm: Orm): Promise<Repositories> {
export async function init(orm: Orm, s3Config: S3StorageConfig): Promise<Repositories> {
/**
* Create storage instances
*/
Expand All @@ -90,6 +92,11 @@ export async function init(orm: Orm): Promise<Repositories> {
const noteSettingsStorage = new NoteSettingsStorage(orm);
const noteRelationshipStorage = new NoteRelationshipStorage(orm);
const teamStorage = new TeamStorage(orm);
/**
* @todo remove ignoring of eslint rule after implementing file uploader repository
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars, no-unused-vars
const s3Storage = new S3Storage(s3Config.accessKeyId, s3Config.secretAccessKey, s3Config.region, s3Config.endpoint);

/**
* Create associations between note and note settings
Expand Down
82 changes: 82 additions & 0 deletions src/repository/storage/s3/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import S3 from 'aws-sdk/clients/s3.js';
import type { Buffer } from 'buffer';

/**
* Class to handle S3 bucket operations
*/
export class S3Storage {
/**
* S3 instance
*/
private readonly s3: S3;

/**
* Constructor for S3Bucket
*
* @param accessKeyId - AWS access key
* @param secretAccessKey - AWS secret access key
* @param region - AWS region
* @param endpoint - AWS endpoint (in case of localstack or other S3 compatible services)
*/
constructor(accessKeyId: string, secretAccessKey: string, region?: string, endpoint?: string) {
this.s3 = new S3({
endpoint,
region,
s3ForcePathStyle: true,
credentials: {
accessKeyId,
secretAccessKey,
},
});
}

/**
* Method to upload a file to S3
*
* @param bucket - S3 bucket name
* @param key - Key to store the file in S3
* @param file - file data to upload
*/
public async uploadFile(bucket: string, key: string, file: Buffer): Promise<string | null> {
/**
* Create an upload manager to upload the file to S3
*/
const uploadManager = this.s3.upload({
Bucket: bucket,
Key: key,
Body: file,
});

/**
* Wait for the upload to complete and return the URL of the uploaded file
*/
try {
const response = await uploadManager.promise();

return response.Location;
} catch (error) {
return null;
}
}

/**
* Method to get a file from S3
*
* @param bucket - S3 bucket name
* @param key - Key of the file in S3
*/
public async getFile(bucket: string, key: string): Promise<Buffer | null> {
const getObjectManager = this.s3.getObject({
Bucket: bucket,
Key: key,
});

try {
const response = await getObjectManager.promise();

return response.Body as Buffer;
} catch (error) {
return null;
}
}
}
2 changes: 1 addition & 1 deletion src/tests/utils/setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ beforeAll(async () => {
.start();

const orm = await initORM({ dsn: postgresContainer.getConnectionUri() });
const repositories = await initRepositories(orm);
const repositories = await initRepositories(orm, config.s3);
const domainServices = initDomainServices(repositories, config);
const api = new API(config.httpApi);

Expand Down
Loading

0 comments on commit 5683e06

Please sign in to comment.