Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 | 20x 20x 20x 20x 20x 20x 12x 12x 12x 12x 12x 1x 1x 1x | import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { S3Client, GetObjectCommand, PutObjectCommand } from '@aws-sdk/client-s3';
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
/**
* R2SignedUrlService – issues pre-signed URLs for objects stored in a *private* Cloudflare R2 bucket.
*
* Why a dedicated service instead of using MediaService directly?
* • isolates SigV4 logic & credentials (principle of least privilege – we can mount *read-only* creds here).
* • keeps a single responsibility: URL signing (extendable to AWS S3 later by swapping the endpoint).
*/
@Injectable()
export class R2SignedUrlService {
private readonly s3: S3Client;
private readonly bucket: string;
/** 7 days in seconds – R2 maximum TTL for signed URLs */
private static readonly MAX_TTL = 7 * 24 * 60 * 60;
constructor(private readonly config: ConfigService) {
this.bucket = this.config.get<string>('R2_BUCKET', 'media-bucket');
const endpoint = this.config.get<string>('R2_ENDPOINT', 'https://r2.cloudflarestorage.com');
const region = this.config.get<string>('R2_REGION', 'auto');
this.s3 = new S3Client({
region,
endpoint,
forcePathStyle: true, // required for R2
credentials: {
accessKeyId: this.config.get<string>('R2_ACCESS_KEY_ID') || '',
secretAccessKey: this.config.get<string>('R2_SECRET_ACCESS_KEY') || '',
},
});
}
/**
* Generate a *download* URL.
* @param key Object key (same as you pass to PutObjectCommand)
* @param ttl Time-to-live in **seconds** (capped at 7 days)
*/
async signGet(key: string, ttl = 60 * 60): Promise<string> {
const expiresIn = Math.min(ttl, R2SignedUrlService.MAX_TTL);
const command = new GetObjectCommand({ Bucket: this.bucket, Key: key });
return getSignedUrl(this.s3, command, { expiresIn });
}
/**
* Generate an *upload* URL (useful for browser or mobile direct-to-R2 uploads).
* @param key Destination object key
* @param ttl Time-to-live in **seconds** (capped at 7 days)
* @param contentType Optional content-type hint (iOS clients often require it)
*/
async signPut(key: string, ttl = 10 * 60, contentType?: string): Promise<string> {
const expiresIn = Math.min(ttl, R2SignedUrlService.MAX_TTL);
const command = new PutObjectCommand({
Bucket: this.bucket,
Key: key,
ContentType: contentType,
});
return getSignedUrl(this.s3, command, { expiresIn });
}
} |