MAI CALL - step 3

This commit is contained in:
2026-05-16 15:32:56 +01:00
parent e249a6f0b2
commit 8e57ccc7f0
12 changed files with 910 additions and 2 deletions
+24
View File
@@ -0,0 +1,24 @@
{
"name": "@repo/storage",
"version": "0.0.0",
"private": true,
"type": "module",
"main": "./src/index.ts",
"types": "./src/index.ts",
"exports": {
".": "./src/index.ts"
},
"scripts": {
"typecheck": "tsc --noEmit",
"clean": "rimraf .turbo node_modules"
},
"dependencies": {
"@aws-sdk/client-s3": "^3.787.0",
"@aws-sdk/s3-request-presigner": "^3.787.0"
},
"devDependencies": {
"@repo/config": "workspace:*",
"rimraf": "^6.0.1",
"typescript": "^5.7.2"
}
}
+21
View File
@@ -0,0 +1,21 @@
export type { ObjectStorage } from './interface.js';
export { MinioStorage } from './minio.js';
import { MinioStorage } from './minio.js';
export function makeStorage(): MinioStorage {
const endpoint = process.env['S3_ENDPOINT'];
const region = process.env['S3_REGION'] ?? 'us-east-1';
const bucket = process.env['S3_BUCKET'];
const accessKey = process.env['S3_ACCESS_KEY'];
const secretKey = process.env['S3_SECRET_KEY'];
const forcePathStyle = process.env['S3_FORCE_PATH_STYLE'] === 'true';
if (!endpoint || !bucket || !accessKey || !secretKey) {
throw new Error(
'Missing required env vars: S3_ENDPOINT, S3_BUCKET, S3_ACCESS_KEY, S3_SECRET_KEY',
);
}
return new MinioStorage({ endpoint, region, bucket, accessKey, secretKey, forcePathStyle });
}
+5
View File
@@ -0,0 +1,5 @@
export interface ObjectStorage {
signPut(key: string, contentType: string, byteSize: number, ttlSec: number): Promise<{ url: string; expiresAt: Date }>;
signGet(key: string, ttlSec: number): Promise<{ url: string; expiresAt: Date }>;
delete(key: string): Promise<void>;
}
+60
View File
@@ -0,0 +1,60 @@
import {
S3Client,
PutObjectCommand,
GetObjectCommand,
DeleteObjectCommand,
} from '@aws-sdk/client-s3';
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
import type { ObjectStorage } from './interface.js';
interface MinioStorageOptions {
endpoint: string;
region: string;
bucket: string;
accessKey: string;
secretKey: string;
forcePathStyle: boolean;
}
export class MinioStorage implements ObjectStorage {
private readonly client: S3Client;
private readonly bucket: string;
constructor(opts: MinioStorageOptions) {
this.bucket = opts.bucket;
this.client = new S3Client({
endpoint: opts.endpoint,
region: opts.region,
credentials: {
accessKeyId: opts.accessKey,
secretAccessKey: opts.secretKey,
},
forcePathStyle: opts.forcePathStyle,
});
}
async signPut(
key: string,
contentType: string,
_byteSize: number,
ttlSec: number,
): Promise<{ url: string; expiresAt: Date }> {
const cmd = new PutObjectCommand({
Bucket: this.bucket,
Key: key,
ContentType: contentType,
});
const url = await getSignedUrl(this.client, cmd, { expiresIn: ttlSec });
return { url, expiresAt: new Date(Date.now() + ttlSec * 1000) };
}
async signGet(key: string, ttlSec: number): Promise<{ url: string; expiresAt: Date }> {
const cmd = new GetObjectCommand({ Bucket: this.bucket, Key: key });
const url = await getSignedUrl(this.client, cmd, { expiresIn: ttlSec });
return { url, expiresAt: new Date(Date.now() + ttlSec * 1000) };
}
async delete(key: string): Promise<void> {
await this.client.send(new DeleteObjectCommand({ Bucket: this.bucket, Key: key }));
}
}
+6
View File
@@ -0,0 +1,6 @@
{
"extends": "@repo/config/tsconfig/library.json",
"compilerOptions": {
"tsBuildInfoFile": "node_modules/.cache/tsbuildinfo.json"
}
}