Added minio and storage module
This commit is contained in:
parent
0ca35e85ef
commit
1059dd9fa8
15
.env.example
15
.env.example
|
|
@ -1,9 +1,18 @@
|
|||
# App config
|
||||
PORT=3000
|
||||
NODE_ENV=development
|
||||
|
||||
# Database config
|
||||
POSTGRES_USER=postgres
|
||||
POSTGRES_PASSWORD=postgres
|
||||
POSTGRES_DB=pandektes
|
||||
POSTGRES_PORT=5432
|
||||
|
||||
DATABASE_URL="postgresql://postgres:postgres@localhost:5432/pandektes?schema=public"
|
||||
|
||||
PORT=3000
|
||||
NODE_ENV=development
|
||||
# Storage Config (MinIO / S3 / R2)
|
||||
STORAGE_ENDPOINT=http://localhost:9000
|
||||
STORAGE_ACCESS_KEY=minioadmin
|
||||
STORAGE_SECRET_KEY=minioadmin
|
||||
STORAGE_BUCKET=cases
|
||||
STORAGE_REGION=us-east-1
|
||||
STORAGE_FORCE_PATH_STYLE=true
|
||||
|
|
|
|||
|
|
@ -12,5 +12,19 @@ services:
|
|||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data
|
||||
|
||||
minio:
|
||||
image: minio/minio
|
||||
container_name: pandektes-minio
|
||||
ports:
|
||||
- "9000:9000"
|
||||
- "9001:9001"
|
||||
environment:
|
||||
MINIO_ROOT_USER: ${MINIO_ROOT_USER:-minioadmin}
|
||||
MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD:-minioadmin}
|
||||
command: server /data --console-address ":9001"
|
||||
volumes:
|
||||
- minio_data:/data
|
||||
|
||||
volumes:
|
||||
postgres_data:
|
||||
minio_data:
|
||||
1653
package-lock.json
generated
1653
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -23,6 +23,8 @@
|
|||
"dependencies": {
|
||||
"@apollo/server": "^5.4.0",
|
||||
"@as-integrations/express5": "^1.1.2",
|
||||
"@aws-sdk/client-s3": "^3.1000.0",
|
||||
"@aws-sdk/s3-request-presigner": "^3.1000.0",
|
||||
"@nestjs/apollo": "^13.2.4",
|
||||
"@nestjs/common": "^11.1.14",
|
||||
"@nestjs/config": "^4.0.3",
|
||||
|
|
|
|||
9
src/common/storage/storage.module.ts
Normal file
9
src/common/storage/storage.module.ts
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
import { Global, Module } from '@nestjs/common';
|
||||
import { StorageService } from './storage.service';
|
||||
|
||||
@Global()
|
||||
@Module({
|
||||
providers: [StorageService],
|
||||
exports: [StorageService],
|
||||
})
|
||||
export class StorageModule {}
|
||||
73
src/common/storage/storage.service.ts
Normal file
73
src/common/storage/storage.service.ts
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
import { Injectable, OnModuleInit, Logger } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import {
|
||||
S3Client,
|
||||
PutObjectCommand,
|
||||
HeadBucketCommand,
|
||||
CreateBucketCommand,
|
||||
GetObjectCommand,
|
||||
} from '@aws-sdk/client-s3';
|
||||
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
|
||||
|
||||
@Injectable()
|
||||
export class StorageService implements OnModuleInit {
|
||||
private client: S3Client;
|
||||
private bucket: string;
|
||||
private readonly logger = new Logger(StorageService.name);
|
||||
|
||||
constructor(private configService: ConfigService) {
|
||||
// Decided to use standard S3 patterns so it can swap out easily
|
||||
// for other storage providers if need be.
|
||||
this.client = new S3Client({
|
||||
region: this.configService.get<string>('STORAGE_REGION'),
|
||||
endpoint: this.configService.get<string>('STORAGE_ENDPOINT'),
|
||||
credentials: {
|
||||
accessKeyId: this.configService.get<string>('STORAGE_ACCESS_KEY')!,
|
||||
secretAccessKey: this.configService.get<string>('STORAGE_SECRET_KEY')!,
|
||||
},
|
||||
forcePathStyle: this.configService.get<string>('STORAGE_FORCE_PATH_STYLE') === 'true',
|
||||
});
|
||||
this.bucket = this.configService.get<string>('STORAGE_BUCKET')!;
|
||||
}
|
||||
|
||||
async onModuleInit() {
|
||||
await this.ensureBucketExists();
|
||||
}
|
||||
|
||||
private async ensureBucketExists() {
|
||||
try {
|
||||
await this.client.send(new HeadBucketCommand({ Bucket: this.bucket }));
|
||||
} catch (error) {
|
||||
if (error.name === 'NotFound' || error.$metadata?.httpStatusCode === 404) {
|
||||
this.logger.log(`Bucket ${this.bucket} not found. Creating it...`);
|
||||
await this.client.send(new CreateBucketCommand({ Bucket: this.bucket }));
|
||||
} else {
|
||||
this.logger.error('Error checking/creating bucket', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async upload(buffer: Buffer, filename: string, mimetype: string): Promise<string> {
|
||||
const key = `${Date.now()}-${filename}`;
|
||||
|
||||
await this.client.send(
|
||||
new PutObjectCommand({
|
||||
Bucket: this.bucket,
|
||||
Key: key,
|
||||
Body: buffer,
|
||||
ContentType: mimetype,
|
||||
}),
|
||||
);
|
||||
|
||||
return key;
|
||||
}
|
||||
|
||||
async getPresignedUrl(key: string): Promise<string> {
|
||||
const command = new GetObjectCommand({
|
||||
Bucket: this.bucket,
|
||||
Key: key,
|
||||
});
|
||||
// Expire in 1 hour
|
||||
return getSignedUrl(this.client, command, { expiresIn: 3600 });
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue