Skip to content

Commit 9433672

Browse files
authored
feat: migrate to aws-sdk v3 (#265)
* feat: migrate to aws-sdk v3 * feat: updated config * refactor: little changes
1 parent 157992d commit 9433672

File tree

6 files changed

+1393
-196
lines changed

6 files changed

+1393
-196
lines changed

app-config.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ s3:
3333
accessKeyId: 'secret'
3434
secretAccessKey: 'secret'
3535
endpoint: 'http://localhost:9000'
36+
region: 'my-region'
3637

3738
database:
3839
dsn: 'postgres://codex:postgres@postgres:5432/notes'

docker-compose.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,4 +31,5 @@ services:
3131
environment:
3232
MINIO_ROOT_USER: codex
3333
MINIO_ROOT_PASSWORD: minio12345
34+
MINIO_REGION: my-region
3435
command: server --console-address ":9001" /data

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
"vitest": "^1.4.0"
4040
},
4141
"dependencies": {
42+
"@aws-sdk/client-s3": "^3.609.0",
4243
"@codex-team/config-loader": "^1.0.0",
4344
"@fastify/cookie": "^8.3.0",
4445
"@fastify/cors": "^8.3.0",
@@ -48,7 +49,6 @@
4849
"@fastify/swagger-ui": "^1.9.3",
4950
"@testcontainers/postgresql": "^10.2.1",
5051
"arg": "^5.0.2",
51-
"aws-sdk": "^2.1569.0",
5252
"fastify": "^4.17.0",
5353
"http-status-codes": "^2.2.0",
5454
"jsonwebtoken": "^9.0.0",
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import type { Readable } from 'stream';
2+
import { Buffer } from 'buffer';
3+
4+
/**
5+
* Convert stream to buffer
6+
* @param stream - stream object
7+
* @returns buffer data made from stream
8+
*/
9+
export const streamToBuffer = (stream: Readable): Promise<Buffer> =>
10+
new Promise((resolve, reject) => {
11+
const chunks: Buffer[] = [];
12+
13+
stream.on('data', (chunk: Buffer) => chunks.push(chunk));
14+
stream.on('error', reject);
15+
stream.on('end', () => resolve(Buffer.concat(chunks)));
16+
});

src/repository/storage/s3/index.ts

Lines changed: 44 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import { getLogger } from '@infrastructure/logging/index.js';
2-
import S3 from 'aws-sdk/clients/s3.js';
3-
import type { Buffer } from 'buffer';
2+
import { S3Client, GetObjectCommand, PutObjectCommand, CreateBucketCommand } from '@aws-sdk/client-s3';
3+
import { Buffer } from 'buffer';
4+
import { Readable } from 'stream';
5+
import { streamToBuffer } from '@infrastructure/utils/streamToBuffer.js';
6+
import { isEmpty } from '@infrastructure/utils/empty.js';
47

58
const s3StorageLogger = getLogger('s3Storage');
69

@@ -11,7 +14,7 @@ export class S3Storage {
1114
/**
1215
* S3 instance
1316
*/
14-
private readonly s3: S3;
17+
private readonly s3: S3Client;
1518

1619
/**
1720
* Constructor for S3Bucket
@@ -21,10 +24,10 @@ export class S3Storage {
2124
* @param endpoint - AWS endpoint (in case of localstack or other S3 compatible services)
2225
*/
2326
constructor(accessKeyId: string, secretAccessKey: string, region?: string, endpoint?: string) {
24-
this.s3 = new S3({
27+
this.s3 = new S3Client({
2528
endpoint,
2629
region,
27-
s3ForcePathStyle: true,
30+
forcePathStyle: true,
2831
credentials: {
2932
accessKeyId,
3033
secretAccessKey,
@@ -39,27 +42,22 @@ export class S3Storage {
3942
* @param file - file data to upload
4043
*/
4144
public async uploadFile(bucket: string, key: string, file: Buffer): Promise<string | null> {
42-
/**
43-
* Create an upload manager to upload the file to S3
44-
*/
45-
const uploadManager = this.s3.upload({
46-
Bucket: bucket,
47-
Key: key,
48-
Body: file,
49-
});
50-
51-
/**
52-
* Wait for the upload to complete and return the URL of the uploaded file
53-
*/
5445
try {
55-
const response = await uploadManager.promise();
56-
57-
return response.Location;
46+
/**
47+
* Try to upload file data to s3
48+
*/
49+
await this.s3.send(new PutObjectCommand({
50+
Bucket: bucket,
51+
Key: key,
52+
Body: file,
53+
}))
5854
} catch (error) {
5955
s3StorageLogger.error(error);
6056

6157
return null;
6258
}
59+
60+
return key;
6361
}
6462

6563
/**
@@ -68,18 +66,24 @@ export class S3Storage {
6866
* @param key - Key of the file in S3
6967
*/
7068
public async getFile(bucket: string, key: string): Promise<Buffer | null> {
71-
const getObjectManager = this.s3.getObject({
72-
Bucket: bucket,
73-
Key: key,
74-
});
75-
7669
try {
77-
const response = await getObjectManager.promise();
70+
const { Body } = await this.s3.send(new GetObjectCommand({
71+
Bucket: bucket,
72+
Key: key,
73+
}));
7874

79-
return response.Body as Buffer;
80-
} catch (error) {
81-
s3StorageLogger.error(error);
75+
/**
76+
* Body must be readable to parse stream
77+
*/
78+
if (!(Body instanceof Readable)) {
79+
s3StorageLogger.error('Expected Body to be a Readable stream');
8280

81+
return null;
82+
}
83+
const fileContent = await streamToBuffer(Body);
84+
return fileContent;
85+
} catch (err) {
86+
s3StorageLogger.error(err);
8387
return null;
8488
}
8589
}
@@ -89,18 +93,20 @@ export class S3Storage {
8993
* @param name - bucket name
9094
*/
9195
public async createBucket(name: string): Promise<string | null> {
92-
const createBucketManager = this.s3.createBucket({
93-
Bucket: name,
94-
});
95-
9696
try {
97-
const response = await createBucketManager.promise();
97+
const { Location } = await this.s3.send(new CreateBucketCommand({
98+
Bucket: name,
99+
}));
98100

99-
return response.Location as string;
100-
} catch (error) {
101-
s3StorageLogger.error(error);
101+
if (isEmpty(Location)) {
102+
return null;
103+
}
102104

103-
return null;
105+
return Location;
106+
} catch (err) {
107+
s3StorageLogger.error(err);
108+
109+
return null;
104110
}
105111
}
106112
}

0 commit comments

Comments
 (0)