import { Controller, Get, Head, InternalServerErrorException, Logger, NotFoundException, Query, Res } from '@nestjs/common'; import type { FastifyReply } from 'fastify'; import { ImageMetaResponse, ImageRequestParams } from 'picsur-shared/dist/dto/api/image.dto'; import { HasFailed } from 'picsur-shared/dist/types'; import { UsersService } from '../../collections/user-db/user-db.service'; import { ImageFullIdParam } from '../../decorators/image-id/image-full-id.decorator'; import { ImageIdParam } from '../../decorators/image-id/image-id.decorator'; import { RequiredPermissions } from '../../decorators/permissions.decorator'; import { Returns } from '../../decorators/returns.decorator'; import { ImageManagerService } from '../../managers/image/image.service'; import type { ImageFullId } from '../../models/constants/image-full-id.const'; import { Permission } from '../../models/constants/permissions.const'; import { EUserBackend2EUser } from '../../models/transformers/user.transformer'; // This is the only controller with CORS enabled @Controller('i') @RequiredPermissions(Permission.ImageView) export class ImageController { private readonly logger = new Logger('ImageController'); constructor( private readonly imagesService: ImageManagerService, private readonly userService: UsersService, ) {} @Head(':id') async headImage( @Res({ passthrough: true }) res: FastifyReply, @ImageFullIdParam() fullid: ImageFullId, ) { if (fullid.type === 'original') { const fullmime = await this.imagesService.getOriginalMime(fullid.id); if (HasFailed(fullmime)) { this.logger.warn(fullmime.getReason()); throw new NotFoundException('Could not find image'); } res.type(fullmime.mime); return; } res.type(fullid.mime); } @Get(':id') async getImage( // Usually passthrough is for manually sending the response, // But we need it here to set the mime type @Res({ passthrough: true }) res: FastifyReply, @ImageFullIdParam() fullid: ImageFullId, @Query() params: ImageRequestParams, ): Promise { if (fullid.type === 'original') { const image = await this.imagesService.getOriginal(fullid.id); if (HasFailed(image)) { this.logger.warn(image.getReason()); throw new NotFoundException('Could not find image'); } res.type(image.mime); return image.data; } const image = await this.imagesService.getConverted( fullid.id, fullid.mime, params, ); if (HasFailed(image)) { this.logger.warn(image.getReason()); throw new NotFoundException('Failed to get image'); } res.type(image.mime); return image.data; } @Get('meta/:id') @Returns(ImageMetaResponse) async getImageMeta(@ImageIdParam() id: string): Promise { const image = await this.imagesService.findOne(id); if (HasFailed(image)) { this.logger.warn(image.getReason()); throw new NotFoundException('Could not find image'); } const [fileMimes, imageUser] = await Promise.all([ this.imagesService.getFileMimes(id), this.userService.findOne(image.user_id), ]); if (HasFailed(fileMimes)) { this.logger.warn(fileMimes.getReason()); throw new InternalServerErrorException('Could not get image mime'); } if (HasFailed(imageUser)) { this.logger.warn(imageUser.getReason()); throw new InternalServerErrorException('Could not get image user'); } return { image, user: EUserBackend2EUser(imageUser), fileMimes }; } }