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 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 | 12x 12x 12x 12x 12x 12x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 1x 1x 1x 2x 2x 2x 1x 2x 1x 2x 1x | import { Processor, WorkerHost } from '@nestjs/bullmq';
import { Job } from 'bullmq';
import { PrismaService } from '@app/modules/prisma/prisma.service';
import { S3Client, DeleteObjectCommand } from '@aws-sdk/client-s3';
import { ConfigService } from '@nestjs/config';
import { Injectable, Logger } from '@nestjs/common';
interface MediaGcPayload {}
@Processor('media-gc')
@Injectable()
export class MediaGcProcessor extends WorkerHost {
private readonly logger = new Logger(MediaGcProcessor.name);
private readonly r2: S3Client;
private readonly bucket: string;
private readonly retentionDays: number;
constructor(
private readonly prisma: PrismaService,
private readonly config: ConfigService,
) {
super();
this.bucket = this.config.get<string>('R2_BUCKET', 'media-bucket');
const endpoint = this.config.get<string>('R2_ENDPOINT');
const region = this.config.get<string>('R2_REGION', 'us-east-1');
this.r2 = new S3Client({
region,
credentials: {
accessKeyId: this.config.get<string>('R2_ACCESS_KEY_ID') || '',
secretAccessKey: this.config.get<string>('R2_SECRET_ACCESS_KEY') || '',
} as any,
endpoint: endpoint || undefined,
forcePathStyle: !!endpoint,
});
this.retentionDays = parseInt(this.config.get<string>('MEDIA_RETENTION_DAYS') || '30', 10);
}
async process(_job: Job<MediaGcPayload>): Promise<void> {
const threshold = new Date(Date.now() - this.retentionDays * 24 * 60 * 60 * 1000);
// @ts-ignore prisma generated
const expired = await this.prisma.client.media.findMany({
where: {
deletedAt: { not: null, lt: threshold },
},
take: 1000, // safety cap per run
});
if (expired.length === 0) {
this.logger.debug('No expired media to purge.');
return;
}
for (const m of expired) {
try {
await this.deleteKey(m.storageKey);
if (m.variants) {
Object.values(m.variants).forEach(async (key: any) => {
if (typeof key === 'string') await this.deleteKey(key);
});
}
} catch (err) {
this.logger.error(`Failed deleting S3 objects for media ${m.id}`, err as any);
}
}
// Hard delete rows
// @ts-ignore prisma generated
await this.prisma.client.media.deleteMany({
where: { id: { in: expired.map((m: any) => m.id) } },
});
this.logger.log(`Purged ${expired.length} media rows (older than ${this.retentionDays} days)`);
}
private async deleteKey(key: string) {
await this.r2.send(
new DeleteObjectCommand({
Bucket: this.bucket,
Key: key,
}),
);
}
} |