refactor(server): album user route params (#29503)

This commit is contained in:
Timon
2026-07-15 20:15:41 +02:00
committed by GitHub
parent 5d29f3edc1
commit 4c2d2284aa
5 changed files with 45 additions and 19 deletions

View File

@@ -681,8 +681,10 @@ class AlbumsApi {
/// Parameters: /// Parameters:
/// ///
/// * [String] id (required): /// * [String] id (required):
/// Album ID
/// ///
/// * [String] userId (required): /// * [String] userId (required):
/// Album user ID, or \"me\" to reference the current user.
Future<Response> removeUserFromAlbumWithHttpInfo(String id, String userId, { Future<void>? abortTrigger, }) async { Future<Response> removeUserFromAlbumWithHttpInfo(String id, String userId, { Future<void>? abortTrigger, }) async {
// ignore: prefer_const_declarations // ignore: prefer_const_declarations
final apiPath = r'/albums/{id}/user/{userId}' final apiPath = r'/albums/{id}/user/{userId}'
@@ -718,8 +720,10 @@ class AlbumsApi {
/// Parameters: /// Parameters:
/// ///
/// * [String] id (required): /// * [String] id (required):
/// Album ID
/// ///
/// * [String] userId (required): /// * [String] userId (required):
/// Album user ID, or \"me\" to reference the current user.
Future<void> removeUserFromAlbum(String id, String userId, { Future<void>? abortTrigger, }) async { Future<void> removeUserFromAlbum(String id, String userId, { Future<void>? abortTrigger, }) async {
final response = await removeUserFromAlbumWithHttpInfo(id, userId, abortTrigger: abortTrigger,); final response = await removeUserFromAlbumWithHttpInfo(id, userId, abortTrigger: abortTrigger,);
if (response.statusCode >= HttpStatus.badRequest) { if (response.statusCode >= HttpStatus.badRequest) {
@@ -798,8 +802,10 @@ class AlbumsApi {
/// Parameters: /// Parameters:
/// ///
/// * [String] id (required): /// * [String] id (required):
/// Album ID
/// ///
/// * [String] userId (required): /// * [String] userId (required):
/// Album user ID, or \"me\" to reference the current user.
/// ///
/// * [UpdateAlbumUserDto] updateAlbumUserDto (required): /// * [UpdateAlbumUserDto] updateAlbumUserDto (required):
Future<Response> updateAlbumUserWithHttpInfo(String id, String userId, UpdateAlbumUserDto updateAlbumUserDto, { Future<void>? abortTrigger, }) async { Future<Response> updateAlbumUserWithHttpInfo(String id, String userId, UpdateAlbumUserDto updateAlbumUserDto, { Future<void>? abortTrigger, }) async {
@@ -837,8 +843,10 @@ class AlbumsApi {
/// Parameters: /// Parameters:
/// ///
/// * [String] id (required): /// * [String] id (required):
/// Album ID
/// ///
/// * [String] userId (required): /// * [String] userId (required):
/// Album user ID, or \"me\" to reference the current user.
/// ///
/// * [UpdateAlbumUserDto] updateAlbumUserDto (required): /// * [UpdateAlbumUserDto] updateAlbumUserDto (required):
Future<void> updateAlbumUser(String id, String userId, UpdateAlbumUserDto updateAlbumUserDto, { Future<void>? abortTrigger, }) async { Future<void> updateAlbumUser(String id, String userId, UpdateAlbumUserDto updateAlbumUserDto, { Future<void>? abortTrigger, }) async {

View File

@@ -2702,6 +2702,7 @@
"name": "id", "name": "id",
"required": true, "required": true,
"in": "path", "in": "path",
"description": "Album ID",
"schema": { "schema": {
"format": "uuid", "format": "uuid",
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
@@ -2712,6 +2713,14 @@
"name": "userId", "name": "userId",
"required": true, "required": true,
"in": "path", "in": "path",
"description": "Album user ID, or \"me\" to reference the current user.",
"x-immich-history": [
{
"version": "v3",
"state": "Updated",
"description": "\"me\" as a value is deprecated"
}
],
"schema": { "schema": {
"type": "string" "type": "string"
} }
@@ -2762,6 +2771,7 @@
"name": "id", "name": "id",
"required": true, "required": true,
"in": "path", "in": "path",
"description": "Album ID",
"schema": { "schema": {
"format": "uuid", "format": "uuid",
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
@@ -2772,6 +2782,14 @@
"name": "userId", "name": "userId",
"required": true, "required": true,
"in": "path", "in": "path",
"description": "Album user ID, or \"me\" to reference the current user.",
"x-immich-history": [
{
"version": "v3",
"state": "Updated",
"description": "\"me\" as a value is deprecated"
}
],
"schema": { "schema": {
"type": "string" "type": "string"
} }

View File

@@ -7,6 +7,7 @@ import {
AlbumsAddAssetsDto, AlbumsAddAssetsDto,
AlbumsAddAssetsResponseDto, AlbumsAddAssetsResponseDto,
AlbumStatisticsResponseDto, AlbumStatisticsResponseDto,
AlbumUserParamDto,
CreateAlbumDto, CreateAlbumDto,
GetAlbumsDto, GetAlbumsDto,
UpdateAlbumDto, UpdateAlbumDto,
@@ -18,7 +19,7 @@ import { MapMarkerResponseDto } from 'src/dtos/map.dto';
import { ApiTag, Permission } from 'src/enum'; import { ApiTag, Permission } from 'src/enum';
import { Auth, Authenticated } from 'src/middleware/auth.guard'; import { Auth, Authenticated } from 'src/middleware/auth.guard';
import { AlbumService } from 'src/services/album.service'; import { AlbumService } from 'src/services/album.service';
import { ParseMeUUIDPipe, UUIDParamDto } from 'src/validation'; import { UUIDParamDto } from 'src/validation';
@ApiTags(ApiTag.Albums) @ApiTags(ApiTag.Albums)
@Controller('albums') @Controller('albums')
@@ -175,8 +176,7 @@ export class AlbumController {
}) })
updateAlbumUser( updateAlbumUser(
@Auth() auth: AuthDto, @Auth() auth: AuthDto,
@Param() { id }: UUIDParamDto, @Param() { id, userId }: AlbumUserParamDto,
@Param('userId', new ParseMeUUIDPipe({ version: '4' })) userId: string,
@Body() dto: UpdateAlbumUserDto, @Body() dto: UpdateAlbumUserDto,
): Promise<void> { ): Promise<void> {
return this.service.updateUser(auth, id, userId, dto); return this.service.updateUser(auth, id, userId, dto);
@@ -190,11 +190,7 @@ export class AlbumController {
description: 'Remove a user from an album. Use an ID of "me" to leave a shared album.', description: 'Remove a user from an album. Use an ID of "me" to leave a shared album.',
history: new HistoryBuilder().added('v1').beta('v1').stable('v2'), history: new HistoryBuilder().added('v1').beta('v1').stable('v2'),
}) })
removeUserFromAlbum( removeUserFromAlbum(@Auth() auth: AuthDto, @Param() { id, userId }: AlbumUserParamDto): Promise<void> {
@Auth() auth: AuthDto,
@Param() { id }: UUIDParamDto,
@Param('userId', new ParseMeUUIDPipe({ version: '4' })) userId: string,
): Promise<void> {
return this.service.removeUser(auth, id, userId); return this.service.removeUser(auth, id, userId);
} }
} }

View File

@@ -1,6 +1,7 @@
import { ShallowDehydrateObject } from 'kysely'; import { ShallowDehydrateObject } from 'kysely';
import { createZodDto } from 'nestjs-zod'; import { createZodDto } from 'nestjs-zod';
import { AlbumUser, AuthSharedLink } from 'src/database'; import { AlbumUser, AuthSharedLink } from 'src/database';
import { HistoryBuilder } from 'src/decorators';
import { BulkIdErrorReasonSchema } from 'src/dtos/asset-ids.response.dto'; import { BulkIdErrorReasonSchema } from 'src/dtos/asset-ids.response.dto';
import { MapAsset } from 'src/dtos/asset-response.dto'; import { MapAsset } from 'src/dtos/asset-response.dto';
import { UserResponseSchema, mapUser } from 'src/dtos/user.dto'; import { UserResponseSchema, mapUser } from 'src/dtos/user.dto';
@@ -140,6 +141,19 @@ export const AlbumResponseSchema = z
}) })
.meta({ id: 'AlbumResponseDto' }); .meta({ id: 'AlbumResponseDto' });
const AlbumUserParamSchema = z.object({
id: z.uuidv4().describe('Album ID'),
// TODO: disallow 'me' as a shortcut in v4 and type userId as uuidv4
userId: z
.string()
.refine((value) => value === 'me' || z.uuidv4().safeParse(value).success, {
error: 'Must be a UUID v4 or "me"',
})
.describe('Album user ID, or "me" to reference the current user.')
.meta(new HistoryBuilder().updated('v3', '"me" as a value is deprecated').getExtensions()),
});
export class AlbumUserParamDto extends createZodDto(AlbumUserParamSchema) {}
export class AddUsersDto extends createZodDto(AddUsersSchema) {} export class AddUsersDto extends createZodDto(AddUsersSchema) {}
export class AlbumUserCreateDto extends createZodDto(AlbumUserCreateSchema) {} export class AlbumUserCreateDto extends createZodDto(AlbumUserCreateSchema) {}
export class CreateAlbumDto extends createZodDto(CreateAlbumSchema) {} export class CreateAlbumDto extends createZodDto(CreateAlbumSchema) {}

View File

@@ -1,4 +1,4 @@
import { ArgumentMetadata, FileValidator, Injectable, ParseUUIDPipe } from '@nestjs/common'; import { FileValidator, Injectable } from '@nestjs/common';
import { DateTime } from 'luxon'; import { DateTime } from 'luxon';
import { createZodDto } from 'nestjs-zod'; import { createZodDto } from 'nestjs-zod';
import sanitize from 'sanitize-filename'; import sanitize from 'sanitize-filename';
@@ -75,16 +75,6 @@ export function IsNotSiblingOf<
); );
} }
@Injectable()
export class ParseMeUUIDPipe extends ParseUUIDPipe {
async transform(value: string, metadata: ArgumentMetadata) {
if (value == 'me') {
return value;
}
return super.transform(value, metadata);
}
}
@Injectable() @Injectable()
export class FileNotEmptyValidator extends FileValidator { export class FileNotEmptyValidator extends FileValidator {
constructor(private requiredFields: string[]) { constructor(private requiredFields: string[]) {