velody/backend/src/modules/artwork/artwork.service.ts
2026-05-30 09:43:14 +02:00

82 lines
1.9 KiB
TypeScript

import {
ForbiddenException,
Injectable,
NotFoundException,
} from '@nestjs/common';
import { stat } from 'node:fs/promises';
import { PrismaService } from '../../infrastructure/database/prisma.service';
import { LocalFilesystemStorageService } from '../storage/storage.service';
export interface ArtworkDownload {
filePath: string;
contentLength: number;
mimeType: string;
}
@Injectable()
export class ArtworkService {
constructor(
private readonly prismaService: PrismaService,
private readonly storageService: LocalFilesystemStorageService,
) {}
async getOwnedArtworkDownload(
artworkId: string,
deviceId: string,
): Promise<ArtworkDownload> {
const device = await this.prismaService.device.findUnique({
where: { id: deviceId },
select: {
userId: true,
},
});
if (!device) {
throw new NotFoundException('Device not found');
}
const artwork = await this.prismaService.artworkAsset.findUnique({
where: { id: artworkId },
select: {
storageKey: true,
mimeType: true,
track: {
select: {
userId: true,
},
},
},
});
if (!artwork || !artwork.track) {
throw new NotFoundException('Artwork not found');
}
if (artwork.track.userId !== device.userId) {
throw new ForbiddenException('Artwork does not belong to this device user.');
}
const filePath = this.storageService.resolve(artwork.storageKey);
try {
const fileStats = await stat(filePath);
if (!fileStats.isFile()) {
throw new NotFoundException('Artwork file not found');
}
return {
filePath,
contentLength: fileStats.size,
mimeType: artwork.mimeType,
};
} catch (error) {
if (error instanceof NotFoundException) {
throw error;
}
throw new NotFoundException('Artwork file not found');
}
}
}