Move token delete and storage purge off UploadService; enforce expiry and max views on GET without purging until the scheduled cleanup job runs.
65 lines
2.2 KiB
TypeScript
65 lines
2.2 KiB
TypeScript
import { Injectable, NotFoundException } from '@nestjs/common';
|
|
import { InjectDataSource, InjectRepository } from '@nestjs/typeorm';
|
|
import { Cron, CronExpression } from '@nestjs/schedule';
|
|
import { DataSource, Repository } from 'typeorm';
|
|
import { hashDeleteToken } from '../../../utils/hashDeleteToken';
|
|
import { isErrorEnoent } from '../../../utils/isErrorEnoent';
|
|
import { StorageService } from '../../storage/services/StorageService';
|
|
import { Upload } from '../entities/Upload';
|
|
|
|
@Injectable()
|
|
export class UploadCleanupService {
|
|
constructor(
|
|
@InjectDataSource()
|
|
private readonly dataSource: DataSource,
|
|
private readonly storageService: StorageService,
|
|
@InjectRepository(Upload)
|
|
private readonly uploadRepository: Repository<Upload>
|
|
) {}
|
|
|
|
async deleteUpload(id: string, deleteToken: string): Promise<void> {
|
|
await this.dataSource.transaction(async manager => {
|
|
const uploadRepository = manager.getRepository(Upload);
|
|
|
|
const upload = await uploadRepository.findOne({
|
|
where: { id, deleteTokenHash: hashDeleteToken(deleteToken) }
|
|
});
|
|
|
|
if (!upload) {
|
|
throw new NotFoundException();
|
|
}
|
|
|
|
await this.purgeUpload(upload, uploadRepository);
|
|
});
|
|
}
|
|
|
|
@Cron(CronExpression.EVERY_MINUTE)
|
|
async purgeUploads(): Promise<void> {
|
|
const purgeBatchSize = 100;
|
|
const now = new Date();
|
|
|
|
const uploads = await this.uploadRepository
|
|
.createQueryBuilder('upload')
|
|
.where('upload.expiresAt IS NOT NULL AND upload.expiresAt < :now', { now })
|
|
.orWhere('upload.maxViews IS NOT NULL AND upload.viewCount >= upload.maxViews')
|
|
.take(purgeBatchSize)
|
|
.getMany();
|
|
|
|
for (const upload of uploads) {
|
|
await this.purgeUpload(upload, this.uploadRepository);
|
|
}
|
|
}
|
|
|
|
async purgeUpload(upload: Upload, uploadRepository: Repository<Upload>): Promise<void> {
|
|
try {
|
|
await this.storageService.unlink(upload.storagePath);
|
|
} catch (error: unknown) {
|
|
if (!isErrorEnoent(error)) {
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
await uploadRepository.remove(upload);
|
|
}
|
|
}
|