1
0
mirror of https://github.com/immich-app/immich.git synced 2024-12-27 10:58:13 +02:00

fix(server): download album error handling (#917)

This commit is contained in:
Jason Rasmussen 2022-11-03 10:12:02 -04:00 committed by GitHub
parent 32e79ce7b3
commit db0a55cd65
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 27 additions and 13 deletions

View File

@ -25,7 +25,7 @@ import { GetAlbumsDto } from './dto/get-albums.dto';
import { ApiBearerAuth, ApiTags } from '@nestjs/swagger'; import { ApiBearerAuth, ApiTags } from '@nestjs/swagger';
import { AlbumResponseDto } from './response-dto/album-response.dto'; import { AlbumResponseDto } from './response-dto/album-response.dto';
import { AlbumCountResponseDto } from './response-dto/album-count-response.dto'; import { AlbumCountResponseDto } from './response-dto/album-count-response.dto';
import {AddAssetsResponseDto} from "./response-dto/add-assets-response.dto"; import { AddAssetsResponseDto } from './response-dto/add-assets-response.dto';
import { Response as Res } from 'express'; import { Response as Res } from 'express';
// TODO might be worth creating a AlbumParamsDto that validates `albumId` instead of using the pipe. // TODO might be worth creating a AlbumParamsDto that validates `albumId` instead of using the pipe.
@ -60,7 +60,7 @@ export class AlbumController {
@GetAuthUser() authUser: AuthUserDto, @GetAuthUser() authUser: AuthUserDto,
@Body(ValidationPipe) addAssetsDto: AddAssetsDto, @Body(ValidationPipe) addAssetsDto: AddAssetsDto,
@Param('albumId', new ParseUUIDPipe({ version: '4' })) albumId: string, @Param('albumId', new ParseUUIDPipe({ version: '4' })) albumId: string,
) : Promise<AddAssetsResponseDto> { ): Promise<AddAssetsResponseDto> {
return this.albumService.addAssetsToAlbum(authUser, addAssetsDto, albumId); return this.albumService.addAssetsToAlbum(authUser, addAssetsDto, albumId);
} }
@ -121,6 +121,8 @@ export class AlbumController {
@Param('albumId', new ParseUUIDPipe({ version: '4' })) albumId: string, @Param('albumId', new ParseUUIDPipe({ version: '4' })) albumId: string,
@Response({ passthrough: true }) res: Res, @Response({ passthrough: true }) res: Res,
): Promise<any> { ): Promise<any> {
return this.albumService.downloadArchive(authUser, albumId, res); const { stream, filename } = await this.albumService.downloadArchive(authUser, albumId);
res.attachment(filename);
return stream;
} }
} }

View File

@ -6,6 +6,7 @@ import {
ForbiddenException, ForbiddenException,
Logger, Logger,
InternalServerErrorException, InternalServerErrorException,
StreamableFile,
} from '@nestjs/common'; } from '@nestjs/common';
import { AuthUserDto } from '../../decorators/auth-user.decorator'; import { AuthUserDto } from '../../decorators/auth-user.decorator';
import { CreateAlbumDto } from './dto/create-album.dto'; import { CreateAlbumDto } from './dto/create-album.dto';
@ -20,8 +21,8 @@ import { AlbumCountResponseDto } from './response-dto/album-count-response.dto';
import { ASSET_REPOSITORY, IAssetRepository } from '../asset/asset-repository'; import { ASSET_REPOSITORY, IAssetRepository } from '../asset/asset-repository';
import { AddAssetsResponseDto } from './response-dto/add-assets-response.dto'; import { AddAssetsResponseDto } from './response-dto/add-assets-response.dto';
import { AddAssetsDto } from './dto/add-assets.dto'; import { AddAssetsDto } from './dto/add-assets.dto';
import { Response as Res } from 'express';
import archiver from 'archiver'; import archiver from 'archiver';
import { extname } from 'path';
@Injectable() @Injectable()
export class AlbumService { export class AlbumService {
@ -149,17 +150,28 @@ export class AlbumService {
return this._albumRepository.getCountByUserId(authUser.id); return this._albumRepository.getCountByUserId(authUser.id);
} }
async downloadArchive(authUser: AuthUserDto, albumId: string, res: Res) { async downloadArchive(authUser: AuthUserDto, albumId: string) {
try {
const album = await this._getAlbum({ authUser, albumId, validateIsOwner: false }); const album = await this._getAlbum({ authUser, albumId, validateIsOwner: false });
if (!album.assets || album.assets.length === 0) {
throw new BadRequestException('Cannot download an empty album.');
}
try {
const archive = archiver('zip', { store: true }); const archive = archiver('zip', { store: true });
res.attachment(`${album.albumName}.zip`); const stream = new StreamableFile(archive);
archive.pipe(res);
album.assets?.forEach((a) => { for (const { assetInfo } of album.assets) {
const name = `${a.assetInfo.exifInfo?.imageName || a.assetInfo.id}.${a.assetInfo.originalPath.split('.')[1]}`; const { originalPath } = assetInfo;
archive.file(a.assetInfo.originalPath, { name }); const name = `${assetInfo.exifInfo?.imageName || assetInfo.id}${extname(originalPath)}`;
}); archive.file(originalPath, { name });
return archive.finalize(); }
archive.finalize();
return {
stream,
filename: `${album.albumName}.zip`,
};
} catch (e) { } catch (e) {
Logger.error(`Error downloading album ${e}`, 'downloadArchive'); Logger.error(`Error downloading album ${e}`, 'downloadArchive');
throw new InternalServerErrorException(`Failed to download album ${e}`, 'DownloadArchive'); throw new InternalServerErrorException(`Failed to download album ${e}`, 'DownloadArchive');