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_USER=postgres
|
||||||
POSTGRES_PASSWORD=postgres
|
POSTGRES_PASSWORD=postgres
|
||||||
POSTGRES_DB=pandektes
|
POSTGRES_DB=pandektes
|
||||||
POSTGRES_PORT=5432
|
POSTGRES_PORT=5432
|
||||||
|
|
||||||
DATABASE_URL="postgresql://postgres:postgres@localhost:5432/pandektes?schema=public"
|
DATABASE_URL="postgresql://postgres:postgres@localhost:5432/pandektes?schema=public"
|
||||||
|
|
||||||
PORT=3000
|
# Storage Config (MinIO / S3 / R2)
|
||||||
NODE_ENV=development
|
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:
|
volumes:
|
||||||
- postgres_data:/var/lib/postgresql/data
|
- 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:
|
volumes:
|
||||||
postgres_data:
|
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": {
|
"dependencies": {
|
||||||
"@apollo/server": "^5.4.0",
|
"@apollo/server": "^5.4.0",
|
||||||
"@as-integrations/express5": "^1.1.2",
|
"@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/apollo": "^13.2.4",
|
||||||
"@nestjs/common": "^11.1.14",
|
"@nestjs/common": "^11.1.14",
|
||||||
"@nestjs/config": "^4.0.3",
|
"@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