sotre profile avatars in R2..
This commit is contained in:
@@ -0,0 +1,127 @@
|
||||
import { DeleteObjectCommand, PutObjectCommand, S3Client } from '@aws-sdk/client-s3';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
|
||||
type UploadPublicFileInput = {
|
||||
key: string;
|
||||
body: Buffer;
|
||||
contentType?: string | null;
|
||||
fileName?: string | null;
|
||||
};
|
||||
|
||||
@Injectable()
|
||||
export class R2PublicFileService {
|
||||
private client?: S3Client;
|
||||
|
||||
constructor(private readonly configService: ConfigService) {}
|
||||
|
||||
isConfigured() {
|
||||
return Boolean(
|
||||
this.bucket
|
||||
&& this.publicBaseUrl
|
||||
&& this.accessKeyId
|
||||
&& this.secretAccessKey,
|
||||
);
|
||||
}
|
||||
|
||||
createLmsCourseFileKey(input: {
|
||||
courseId: string;
|
||||
week: number;
|
||||
kind: 'REFERENCE' | 'EXAMPLE_CODE';
|
||||
fileId: string;
|
||||
extension?: string;
|
||||
}) {
|
||||
const section = input.kind === 'EXAMPLE_CODE' ? 'example-codes' : 'references';
|
||||
return [
|
||||
this.publicPrefix,
|
||||
'lms',
|
||||
'courses',
|
||||
input.courseId,
|
||||
'weeks',
|
||||
String(input.week),
|
||||
section,
|
||||
`${input.fileId}${input.extension ?? ''}`,
|
||||
].filter(Boolean).join('/');
|
||||
}
|
||||
|
||||
async uploadPublicFile(input: UploadPublicFileInput) {
|
||||
if (!this.isConfigured()) return null;
|
||||
|
||||
await this.getClient().send(new PutObjectCommand({
|
||||
Bucket: this.bucket,
|
||||
Key: input.key,
|
||||
Body: input.body,
|
||||
ContentLength: input.body.length,
|
||||
ContentType: input.contentType ?? 'application/octet-stream',
|
||||
ContentDisposition: input.fileName ? contentDispositionForFile(input.fileName) : undefined,
|
||||
}));
|
||||
|
||||
return this.publicUrl(input.key);
|
||||
}
|
||||
|
||||
async deletePublicFile(key: string) {
|
||||
if (!this.isConfigured() || !this.isPublicKey(key)) return;
|
||||
|
||||
await this.getClient().send(new DeleteObjectCommand({
|
||||
Bucket: this.bucket,
|
||||
Key: key,
|
||||
}));
|
||||
}
|
||||
|
||||
publicUrl(key: string) {
|
||||
if (!this.publicBaseUrl || !this.isPublicKey(key)) return null;
|
||||
return `${this.publicBaseUrl.replace(/\/$/, '')}/${encodeObjectKey(key)}`;
|
||||
}
|
||||
|
||||
isPublicKey(value: string) {
|
||||
return value.startsWith(`${this.publicPrefix}/`);
|
||||
}
|
||||
|
||||
private getClient() {
|
||||
if (this.client) return this.client;
|
||||
|
||||
const endpoint = this.configService.get<string>('R2_ENDPOINT')
|
||||
|| `https://${this.configService.get<string>('R2_ACCOUNT_ID')}.r2.cloudflarestorage.com`;
|
||||
|
||||
this.client = new S3Client({
|
||||
region: this.configService.get<string>('R2_REGION', 'auto'),
|
||||
endpoint,
|
||||
credentials: {
|
||||
accessKeyId: this.accessKeyId,
|
||||
secretAccessKey: this.secretAccessKey,
|
||||
},
|
||||
forcePathStyle: this.configService.get<string>('R2_FORCE_PATH_STYLE', 'true') !== 'false',
|
||||
});
|
||||
|
||||
return this.client;
|
||||
}
|
||||
|
||||
private get bucket() {
|
||||
return this.configService.get<string>('R2_BUCKET')?.trim() ?? '';
|
||||
}
|
||||
|
||||
private get publicBaseUrl() {
|
||||
return this.configService.get<string>('R2_PUBLIC_BASE_URL')?.trim() ?? '';
|
||||
}
|
||||
|
||||
private get accessKeyId() {
|
||||
return this.configService.get<string>('R2_ACCESS_KEY_ID')?.trim() ?? '';
|
||||
}
|
||||
|
||||
private get secretAccessKey() {
|
||||
return this.configService.get<string>('R2_SECRET_ACCESS_KEY')?.trim() ?? '';
|
||||
}
|
||||
|
||||
private get publicPrefix() {
|
||||
return this.configService.get<string>('R2_PUBLIC_FILE_PREFIX', 'studio-files/public').replace(/^\/+|\/+$/g, '');
|
||||
}
|
||||
}
|
||||
|
||||
function encodeObjectKey(key: string) {
|
||||
return key.split('/').map(encodeURIComponent).join('/');
|
||||
}
|
||||
|
||||
function contentDispositionForFile(fileName: string) {
|
||||
const fallbackName = fileName.replace(/[\\\"\r\n]/g, '_');
|
||||
return `attachment; filename="${fallbackName}"; filename*=UTF-8''${encodeURIComponent(fileName)}`;
|
||||
}
|
||||
Reference in New Issue
Block a user