fix(lms): support direct R2 map uploads and preserve empty course groups
This commit is contained in:
@@ -0,0 +1,18 @@
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { R2PublicFileService } from './r2-public-file.service';
|
||||
|
||||
describe('R2 direct upload URL', () => {
|
||||
it('signs a PUT without a checksum for an empty body', async () => {
|
||||
const service = new R2PublicFileService(new ConfigService({
|
||||
R2_BUCKET: 'test', R2_ACCOUNT_ID: 'test',
|
||||
R2_PUBLIC_BASE_URL: 'https://files.example.com',
|
||||
R2_ACCESS_KEY_ID: 'test', R2_SECRET_ACCESS_KEY: 'test',
|
||||
}));
|
||||
const url = new URL((await service.createUploadUrl('studio-files/public/test.db'))!);
|
||||
expect(url.protocol).toBe('https:');
|
||||
expect(url.searchParams.get('X-Amz-Expires')).toBe('3600');
|
||||
expect(url.searchParams.has('X-Amz-Signature')).toBe(true);
|
||||
expect(url.searchParams.has('x-amz-checksum-crc32')).toBe(false);
|
||||
expect(await service.createUploadUrl('private/test.db')).toBeNull();
|
||||
});
|
||||
});
|
||||
@@ -1,4 +1,5 @@
|
||||
import { DeleteObjectCommand, PutObjectCommand, S3Client } from '@aws-sdk/client-s3';
|
||||
import { DeleteObjectCommand, HeadObjectCommand, PutObjectCommand, S3Client } from '@aws-sdk/client-s3';
|
||||
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
|
||||
@@ -86,6 +87,21 @@ export class R2PublicFileService {
|
||||
}));
|
||||
}
|
||||
|
||||
async createUploadUrl(key: string) {
|
||||
if (!this.isConfigured() || !this.isPublicKey(key)) return null;
|
||||
return getSignedUrl(this.getClient(), new PutObjectCommand({
|
||||
Bucket: this.bucket,
|
||||
Key: key,
|
||||
ContentType: 'application/octet-stream',
|
||||
}), { expiresIn: 3600 });
|
||||
}
|
||||
|
||||
async verifyUploadedFile(key: string) {
|
||||
if (!this.isConfigured() || !this.isPublicKey(key)) return null;
|
||||
const result = await this.getClient().send(new HeadObjectCommand({ Bucket: this.bucket, Key: key }));
|
||||
return (result.ContentLength ?? 0) > 0 ? this.publicUrl(key) : null;
|
||||
}
|
||||
|
||||
publicUrl(key: string) {
|
||||
if (!this.publicBaseUrl || !this.isPublicKey(key)) return null;
|
||||
return `${this.publicBaseUrl.replace(/\/$/, '')}/${encodeObjectKey(key)}`;
|
||||
@@ -109,6 +125,8 @@ export class R2PublicFileService {
|
||||
secretAccessKey: this.secretAccessKey,
|
||||
},
|
||||
forcePathStyle: this.configService.get<string>('R2_FORCE_PATH_STYLE', 'true') !== 'false',
|
||||
// Presigned PUT bodies are supplied later by the platform, not this client.
|
||||
requestChecksumCalculation: 'WHEN_REQUIRED',
|
||||
});
|
||||
|
||||
return this.client;
|
||||
|
||||
Reference in New Issue
Block a user