chore!: remove without assets (#27835)

* chore!: remove without assets

* fix: linting and e2e

---------

Co-authored-by: Daniel Dietzler <mail@ddietzler.dev>
This commit is contained in:
Jason Rasmussen
2026-04-15 18:34:22 -04:00
committed by GitHub
parent 4ffa26c969
commit a69eecf3bc
16 changed files with 214 additions and 301 deletions
+2 -7
View File
@@ -3,7 +3,6 @@ import { ApiTags } from '@nestjs/swagger';
import { Endpoint, HistoryBuilder } from 'src/decorators';
import {
AddUsersDto,
AlbumInfoDto,
AlbumResponseDto,
AlbumsAddAssetsDto,
AlbumsAddAssetsResponseDto,
@@ -66,12 +65,8 @@ export class AlbumController {
description: 'Retrieve information about a specific album by its ID.',
history: new HistoryBuilder().added('v1').beta('v1').stable('v2'),
})
getAlbumInfo(
@Auth() auth: AuthDto,
@Param() { id }: UUIDParamDto,
@Query() dto: AlbumInfoDto,
): Promise<AlbumResponseDto> {
return this.service.get(auth, id, dto);
getAlbumInfo(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<AlbumResponseDto> {
return this.service.get(auth, id);
}
@Patch(':id')
+2 -2
View File
@@ -10,13 +10,13 @@ describe('mapAlbum', () => {
.asset({ localDateTime: endDate }, (builder) => builder.exif())
.asset({ localDateTime: startDate }, (builder) => builder.exif())
.build();
const dto = mapAlbum(getForAlbum(album), false);
const dto = mapAlbum(getForAlbum(album));
expect(dto.startDate).toEqual(startDate.toISOString());
expect(dto.endDate).toEqual(endDate.toISOString());
});
it('should not set start and end dates for empty assets', () => {
const dto = mapAlbum(getForAlbum(AlbumFactory.create()), false);
const dto = mapAlbum(getForAlbum(AlbumFactory.create()));
expect(dto.startDate).toBeUndefined();
expect(dto.endDate).toBeUndefined();
});
+2 -19
View File
@@ -3,8 +3,7 @@ import _ from 'lodash';
import { createZodDto } from 'nestjs-zod';
import { AlbumUser, AuthSharedLink, User } from 'src/database';
import { BulkIdErrorReasonSchema } from 'src/dtos/asset-ids.response.dto';
import { AssetResponseSchema, MapAsset, mapAsset } from 'src/dtos/asset-response.dto';
import { AuthDto } from 'src/dtos/auth.dto';
import { MapAsset } from 'src/dtos/asset-response.dto';
import { UserResponseSchema, mapUser } from 'src/dtos/user.dto';
import { AlbumUserRole, AlbumUserRoleSchema, AssetOrder, AssetOrderSchema } from 'src/enum';
import { MaybeDehydrated } from 'src/types';
@@ -12,12 +11,6 @@ import { asDateString } from 'src/utils/date';
import { stringToBool } from 'src/validation';
import z from 'zod';
const AlbumInfoSchema = z
.object({
withoutAssets: stringToBool.optional().describe('Exclude assets from response'),
})
.meta({ id: 'AlbumInfoDto' });
const AlbumUserAddSchema = z
.object({
userId: z.uuidv4().describe('User ID'),
@@ -122,7 +115,6 @@ export const AlbumResponseSchema = z
shared: z.boolean().describe('Is shared album'),
albumUsers: z.array(AlbumUserResponseSchema),
hasSharedLink: z.boolean().describe('Has shared link'),
assets: z.array(AssetResponseSchema),
owner: UserResponseSchema,
assetCount: z.int().min(0).describe('Number of assets'),
// TODO: use `isoDatetimeToDate` when using `ZodSerializerDto` on the controllers.
@@ -141,7 +133,6 @@ export const AlbumResponseSchema = z
})
.meta({ id: 'AlbumResponseDto' });
export class AlbumInfoDto extends createZodDto(AlbumInfoSchema) {}
export class AddUsersDto extends createZodDto(AddUsersSchema) {}
export class AlbumUserCreateDto extends createZodDto(AlbumUserCreateSchema) {}
export class CreateAlbumDto extends createZodDto(CreateAlbumSchema) {}
@@ -170,11 +161,7 @@ export type MapAlbumDto = {
order: AssetOrder;
};
export const mapAlbum = (
entity: MaybeDehydrated<MapAlbumDto>,
withAssets: boolean,
auth?: AuthDto,
): AlbumResponseDto => {
export const mapAlbum = (entity: MaybeDehydrated<MapAlbumDto>): AlbumResponseDto => {
const albumUsers: AlbumUserResponseDto[] = [];
if (entity.albumUsers) {
@@ -215,12 +202,8 @@ export const mapAlbum = (
hasSharedLink,
startDate: asDateString(startDate),
endDate: asDateString(endDate),
assets: (withAssets ? assets : []).map((asset) => mapAsset(asset, { auth })),
assetCount: entity.assets?.length || 0,
isActivityEnabled: entity.isActivityEnabled,
order: entity.order,
};
};
export const mapAlbumWithAssets = (entity: MaybeDehydrated<MapAlbumDto>) => mapAlbum(entity, true);
export const mapAlbumWithoutAssets = (entity: MaybeDehydrated<MapAlbumDto>) => mapAlbum(entity, false);
+2 -2
View File
@@ -1,7 +1,7 @@
import { createZodDto } from 'nestjs-zod';
import { SharedLink } from 'src/database';
import { HistoryBuilder } from 'src/decorators';
import { AlbumResponseSchema, mapAlbumWithoutAssets } from 'src/dtos/album.dto';
import { AlbumResponseSchema, mapAlbum } from 'src/dtos/album.dto';
import { AssetResponseSchema, mapAsset } from 'src/dtos/asset-response.dto';
import { SharedLinkTypeSchema } from 'src/enum';
import { emptyStringToNull, isoDatetimeToDate } from 'src/validation';
@@ -96,7 +96,7 @@ export function mapSharedLink(sharedLink: SharedLink, options: { stripAssetMetad
createdAt: sharedLink.createdAt,
expiresAt: sharedLink.expiresAt,
assets: assets.map((asset) => mapAsset(asset, { stripMetadata: options.stripAssetMetadata })),
album: sharedLink.album ? mapAlbumWithoutAssets(sharedLink.album) : undefined,
album: sharedLink.album ? mapAlbum(sharedLink.album) : undefined,
allowUpload: sharedLink.allowUpload,
allowDownload: sharedLink.allowDownload,
showMetadata: sharedLink.showExif,
+7 -7
View File
@@ -563,9 +563,9 @@ describe(AlbumService.name, () => {
},
]);
await sut.get(AuthFactory.create(album.owner), album.id, {});
await sut.get(AuthFactory.create(album.owner), album.id);
expect(mocks.album.getById).toHaveBeenCalledWith(album.id, { withAssets: true });
expect(mocks.album.getById).toHaveBeenCalledWith(album.id, { withAssets: false });
expect(mocks.access.album.checkOwnerAccess).toHaveBeenCalledWith(album.owner.id, new Set([album.id]));
});
@@ -584,9 +584,9 @@ describe(AlbumService.name, () => {
]);
const auth = AuthFactory.from().sharedLink().build();
await sut.get(auth, album.id, {});
await sut.get(auth, album.id);
expect(mocks.album.getById).toHaveBeenCalledWith(album.id, { withAssets: true });
expect(mocks.album.getById).toHaveBeenCalledWith(album.id, { withAssets: false });
expect(mocks.access.album.checkSharedLinkAccess).toHaveBeenCalledWith(auth.sharedLink!.id, new Set([album.id]));
});
@@ -605,9 +605,9 @@ describe(AlbumService.name, () => {
},
]);
await sut.get(AuthFactory.create(user), album.id, {});
await sut.get(AuthFactory.create(user), album.id);
expect(mocks.album.getById).toHaveBeenCalledWith(album.id, { withAssets: true });
expect(mocks.album.getById).toHaveBeenCalledWith(album.id, { withAssets: false });
expect(mocks.access.album.checkSharedAlbumAccess).toHaveBeenCalledWith(
user.id,
new Set([album.id]),
@@ -617,7 +617,7 @@ describe(AlbumService.name, () => {
it('should throw an error for no access', async () => {
const auth = AuthFactory.create();
await expect(sut.get(auth, 'album-123', {})).rejects.toBeInstanceOf(BadRequestException);
await expect(sut.get(auth, 'album-123')).rejects.toBeInstanceOf(BadRequestException);
expect(mocks.access.album.checkOwnerAccess).toHaveBeenCalledWith(auth.user.id, new Set(['album-123']));
expect(mocks.access.album.checkSharedAlbumAccess).toHaveBeenCalledWith(
+7 -11
View File
@@ -1,7 +1,6 @@
import { BadRequestException, Injectable } from '@nestjs/common';
import {
AddUsersDto,
AlbumInfoDto,
AlbumResponseDto,
AlbumsAddAssetsDto,
AlbumsAddAssetsResponseDto,
@@ -10,8 +9,6 @@ import {
GetAlbumsDto,
mapAlbum,
MapAlbumDto,
mapAlbumWithAssets,
mapAlbumWithoutAssets,
UpdateAlbumDto,
UpdateAlbumUserDto,
} from 'src/dtos/album.dto';
@@ -64,7 +61,7 @@ export class AlbumService extends BaseService {
}
return albums.map((album) => ({
...mapAlbumWithoutAssets(album),
...mapAlbum(album),
sharedLinks: undefined,
startDate: asDateString(albumMetadata[album.id]?.startDate ?? undefined),
endDate: asDateString(albumMetadata[album.id]?.endDate ?? undefined),
@@ -74,11 +71,10 @@ export class AlbumService extends BaseService {
}));
}
async get(auth: AuthDto, id: string, dto: AlbumInfoDto): Promise<AlbumResponseDto> {
async get(auth: AuthDto, id: string): Promise<AlbumResponseDto> {
await this.requireAccess({ auth, permission: Permission.AlbumRead, ids: [id] });
await this.albumRepository.updateThumbnails();
const withAssets = dto.withoutAssets === undefined ? true : !dto.withoutAssets;
const album = await this.findOrFail(id, { withAssets });
const album = await this.findOrFail(id, { withAssets: false });
const [albumMetadataForIds] = await this.albumRepository.getMetadataForIds([album.id]);
const hasSharedUsers = album.albumUsers && album.albumUsers.length > 0;
@@ -86,7 +82,7 @@ export class AlbumService extends BaseService {
const isShared = hasSharedUsers || hasSharedLink;
return {
...mapAlbum(album, withAssets, auth),
...mapAlbum(album),
startDate: asDateString(albumMetadataForIds?.startDate ?? undefined),
endDate: asDateString(albumMetadataForIds?.endDate ?? undefined),
assetCount: albumMetadataForIds?.assetCount ?? 0,
@@ -144,7 +140,7 @@ export class AlbumService extends BaseService {
await this.eventRepository.emit('AlbumInvite', { id: album.id, userId });
}
return mapAlbumWithAssets(album);
return mapAlbum(album);
}
async update(auth: AuthDto, id: string, dto: UpdateAlbumDto): Promise<AlbumResponseDto> {
@@ -167,7 +163,7 @@ export class AlbumService extends BaseService {
order: dto.order,
});
return mapAlbumWithoutAssets({ ...updatedAlbum, assets: album.assets });
return mapAlbum({ ...updatedAlbum, assets: album.assets });
}
async delete(auth: AuthDto, id: string): Promise<void> {
@@ -305,7 +301,7 @@ export class AlbumService extends BaseService {
await this.eventRepository.emit('AlbumInvite', { id, userId });
}
return this.findOrFail(id, { withAssets: true }).then(mapAlbumWithoutAssets);
return this.findOrFail(id, { withAssets: true }).then(mapAlbum);
}
async removeUser(auth: AuthDto, id: string, userId: string | 'me'): Promise<void> {