diff --git a/server/immich-openapi-specs.json b/server/immich-openapi-specs.json index c3e69c481c..0b398cd1b2 100644 --- a/server/immich-openapi-specs.json +++ b/server/immich-openapi-specs.json @@ -402,6 +402,13 @@ }, "responses": { "201": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AlbumEntity" + } + } + }, "description": "" } }, @@ -4820,6 +4827,85 @@ ], "type": "object" }, + "AlbumEntity": { + "properties": { + "albumName": { + "type": "string" + }, + "albumThumbnailAsset": { + "allOf": [ + { + "$ref": "#/components/schemas/AssetEntity" + } + ], + "nullable": true + }, + "albumThumbnailAssetId": { + "nullable": true, + "type": "string" + }, + "assets": { + "items": { + "$ref": "#/components/schemas/AssetEntity" + }, + "type": "array" + }, + "createdAt": { + "format": "date-time", + "type": "string" + }, + "description": { + "type": "string" + }, + "id": { + "type": "string" + }, + "owner": { + "$ref": "#/components/schemas/UserEntity" + }, + "ownerId": { + "type": "string" + }, + "rules": { + "items": { + "$ref": "#/components/schemas/RuleEntity" + }, + "type": "array" + }, + "sharedLinks": { + "items": { + "$ref": "#/components/schemas/SharedLinkEntity" + }, + "type": "array" + }, + "sharedUsers": { + "items": { + "$ref": "#/components/schemas/UserEntity" + }, + "type": "array" + }, + "updatedAt": { + "format": "date-time", + "type": "string" + } + }, + "required": [ + "id", + "owner", + "ownerId", + "albumName", + "description", + "createdAt", + "updatedAt", + "albumThumbnailAsset", + "albumThumbnailAssetId", + "sharedUsers", + "assets", + "sharedLinks", + "rules" + ], + "type": "object" + }, "AlbumResponseDto": { "properties": { "albumName": { @@ -5015,6 +5101,223 @@ ], "type": "object" }, + "AssetEntity": { + "properties": { + "albums": { + "items": { + "$ref": "#/components/schemas/AlbumEntity" + }, + "type": "array" + }, + "checksum": { + "type": "object" + }, + "createdAt": { + "format": "date-time", + "type": "string" + }, + "deviceAssetId": { + "type": "string" + }, + "deviceId": { + "type": "string" + }, + "duration": { + "nullable": true, + "type": "string" + }, + "encodedVideoPath": { + "nullable": true, + "type": "string" + }, + "exifInfo": { + "$ref": "#/components/schemas/ExifEntity" + }, + "faces": { + "items": { + "$ref": "#/components/schemas/AssetFaceEntity" + }, + "type": "array" + }, + "fileCreatedAt": { + "format": "date-time", + "type": "string" + }, + "fileModifiedAt": { + "format": "date-time", + "type": "string" + }, + "id": { + "type": "string" + }, + "isArchived": { + "type": "boolean" + }, + "isFavorite": { + "type": "boolean" + }, + "isReadOnly": { + "type": "boolean" + }, + "isVisible": { + "type": "boolean" + }, + "livePhotoVideo": { + "allOf": [ + { + "$ref": "#/components/schemas/AssetEntity" + } + ], + "nullable": true + }, + "livePhotoVideoId": { + "nullable": true, + "type": "string" + }, + "originalFileName": { + "type": "string" + }, + "originalPath": { + "type": "string" + }, + "owner": { + "$ref": "#/components/schemas/UserEntity" + }, + "ownerId": { + "type": "string" + }, + "resizePath": { + "nullable": true, + "type": "string" + }, + "sharedLinks": { + "items": { + "$ref": "#/components/schemas/SharedLinkEntity" + }, + "type": "array" + }, + "sidecarPath": { + "nullable": true, + "type": "string" + }, + "smartInfo": { + "$ref": "#/components/schemas/SmartInfoEntity" + }, + "tags": { + "items": { + "$ref": "#/components/schemas/TagEntity" + }, + "type": "array" + }, + "thumbhash": { + "nullable": true, + "type": "object" + }, + "type": { + "enum": [ + "IMAGE", + "VIDEO", + "AUDIO", + "OTHER" + ], + "type": "string" + }, + "updatedAt": { + "format": "date-time", + "type": "string" + }, + "webpPath": { + "nullable": true, + "type": "string" + } + }, + "required": [ + "id", + "deviceAssetId", + "owner", + "ownerId", + "deviceId", + "type", + "originalPath", + "resizePath", + "webpPath", + "thumbhash", + "encodedVideoPath", + "createdAt", + "updatedAt", + "fileCreatedAt", + "fileModifiedAt", + "isFavorite", + "isArchived", + "isReadOnly", + "checksum", + "duration", + "isVisible", + "livePhotoVideo", + "livePhotoVideoId", + "originalFileName", + "sidecarPath", + "tags", + "sharedLinks", + "faces" + ], + "type": "object" + }, + "AssetFaceEntity": { + "properties": { + "asset": { + "$ref": "#/components/schemas/AssetEntity" + }, + "assetId": { + "type": "string" + }, + "boundingBoxX1": { + "type": "number" + }, + "boundingBoxX2": { + "type": "number" + }, + "boundingBoxY1": { + "type": "number" + }, + "boundingBoxY2": { + "type": "number" + }, + "embedding": { + "items": { + "type": "number" + }, + "nullable": true, + "type": "array" + }, + "imageHeight": { + "type": "number" + }, + "imageWidth": { + "type": "number" + }, + "person": { + "$ref": "#/components/schemas/PersonEntity" + }, + "personId": { + "type": "string" + } + }, + "required": [ + "assetId", + "personId", + "embedding", + "imageWidth", + "imageHeight", + "boundingBoxX1", + "boundingBoxY1", + "boundingBoxX2", + "boundingBoxY2", + "asset", + "person" + ], + "type": "object" + }, "AssetFileUploadResponseDto": { "properties": { "duplicate": { @@ -5657,6 +5960,142 @@ ], "type": "object" }, + "ExifEntity": { + "properties": { + "asset": { + "$ref": "#/components/schemas/AssetEntity" + }, + "assetId": { + "type": "string" + }, + "city": { + "nullable": true, + "type": "string" + }, + "country": { + "nullable": true, + "type": "string" + }, + "dateTimeOriginal": { + "format": "date-time", + "nullable": true, + "type": "string" + }, + "description": { + "description": "General info", + "type": "string" + }, + "exifImageHeight": { + "nullable": true, + "type": "number" + }, + "exifImageWidth": { + "nullable": true, + "type": "number" + }, + "exifTextSearchableColumn": { + "type": "string" + }, + "exposureTime": { + "nullable": true, + "type": "string" + }, + "fNumber": { + "nullable": true, + "type": "number" + }, + "fileSizeInByte": { + "nullable": true, + "type": "number" + }, + "focalLength": { + "nullable": true, + "type": "number" + }, + "fps": { + "description": "Video info", + "nullable": true, + "type": "number" + }, + "iso": { + "nullable": true, + "type": "number" + }, + "latitude": { + "nullable": true, + "type": "number" + }, + "lensModel": { + "nullable": true, + "type": "string" + }, + "livePhotoCID": { + "nullable": true, + "type": "string" + }, + "longitude": { + "nullable": true, + "type": "number" + }, + "make": { + "description": "Image info", + "nullable": true, + "type": "string" + }, + "model": { + "nullable": true, + "type": "string" + }, + "modifyDate": { + "format": "date-time", + "nullable": true, + "type": "string" + }, + "orientation": { + "nullable": true, + "type": "string" + }, + "projectionType": { + "nullable": true, + "type": "string" + }, + "state": { + "nullable": true, + "type": "string" + }, + "timeZone": { + "nullable": true, + "type": "string" + } + }, + "required": [ + "assetId", + "description", + "exifImageWidth", + "exifImageHeight", + "fileSizeInByte", + "orientation", + "dateTimeOriginal", + "modifyDate", + "timeZone", + "latitude", + "longitude", + "projectionType", + "city", + "livePhotoCID", + "state", + "country", + "make", + "model", + "lensModel", + "fNumber", + "focalLength", + "iso", + "exposureTime", + "exifTextSearchableColumn" + ], + "type": "object" + }, "ExifResponseDto": { "properties": { "city": { @@ -6154,6 +6593,54 @@ ], "type": "object" }, + "PersonEntity": { + "properties": { + "createdAt": { + "format": "date-time", + "type": "string" + }, + "faces": { + "items": { + "$ref": "#/components/schemas/AssetFaceEntity" + }, + "type": "array" + }, + "id": { + "type": "string" + }, + "isHidden": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "owner": { + "$ref": "#/components/schemas/UserEntity" + }, + "ownerId": { + "type": "string" + }, + "thumbnailPath": { + "type": "string" + }, + "updatedAt": { + "format": "date-time", + "type": "string" + } + }, + "required": [ + "id", + "createdAt", + "updatedAt", + "ownerId", + "owner", + "name", + "thumbnailPath", + "faces", + "isHidden" + ], + "type": "object" + }, "PersonResponseDto": { "properties": { "id": { @@ -6209,6 +6696,46 @@ ], "type": "object" }, + "RuleEntity": { + "properties": { + "album": { + "$ref": "#/components/schemas/AlbumEntity" + }, + "albumId": { + "type": "string" + }, + "id": { + "type": "string" + }, + "key": { + "enum": [ + "personId", + "exifInfo.city", + "asset.fileCreatedAt" + ], + "type": "string" + }, + "ownerId": { + "type": "string" + }, + "user": { + "$ref": "#/components/schemas/UserEntity" + }, + "value": { + "type": "string" + } + }, + "required": [ + "id", + "key", + "value", + "ownerId", + "user", + "albumId", + "album" + ], + "type": "object" + }, "RuleKey": { "enum": [ "personId", @@ -6600,6 +7127,80 @@ }, "type": "object" }, + "SharedLinkEntity": { + "properties": { + "album": { + "$ref": "#/components/schemas/AlbumEntity" + }, + "albumId": { + "nullable": true, + "type": "string" + }, + "allowDownload": { + "type": "boolean" + }, + "allowUpload": { + "type": "boolean" + }, + "assets": { + "items": { + "$ref": "#/components/schemas/AssetEntity" + }, + "type": "array" + }, + "createdAt": { + "format": "date-time", + "type": "string" + }, + "description": { + "nullable": true, + "type": "string" + }, + "expiresAt": { + "format": "date-time", + "nullable": true, + "type": "string" + }, + "id": { + "type": "string" + }, + "key": { + "type": "object" + }, + "showExif": { + "type": "boolean" + }, + "type": { + "enum": [ + "ALBUM", + "INDIVIDUAL" + ], + "type": "string" + }, + "user": { + "$ref": "#/components/schemas/UserEntity" + }, + "userId": { + "type": "string" + } + }, + "required": [ + "id", + "description", + "userId", + "user", + "key", + "type", + "createdAt", + "expiresAt", + "allowUpload", + "allowDownload", + "showExif", + "assets", + "albumId" + ], + "type": "object" + }, "SharedLinkResponseDto": { "properties": { "album": { @@ -6695,6 +7296,44 @@ ], "type": "object" }, + "SmartInfoEntity": { + "properties": { + "asset": { + "$ref": "#/components/schemas/AssetEntity" + }, + "assetId": { + "type": "string" + }, + "clipEmbedding": { + "items": { + "type": "number" + }, + "nullable": true, + "type": "array" + }, + "objects": { + "items": { + "type": "string" + }, + "nullable": true, + "type": "array" + }, + "tags": { + "items": { + "type": "string" + }, + "nullable": true, + "type": "array" + } + }, + "required": [ + "assetId", + "tags", + "objects", + "clipEmbedding" + ], + "type": "object" + }, "SmartInfoResponseDto": { "properties": { "objects": { @@ -6987,6 +7626,50 @@ ], "type": "object" }, + "TagEntity": { + "properties": { + "assets": { + "items": { + "$ref": "#/components/schemas/AssetEntity" + }, + "type": "array" + }, + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "renameTagId": { + "nullable": true, + "type": "string" + }, + "type": { + "enum": [ + "OBJECT", + "FACE", + "CUSTOM" + ], + "type": "string" + }, + "user": { + "$ref": "#/components/schemas/UserEntity" + }, + "userId": { + "type": "string" + } + }, + "required": [ + "id", + "type", + "name", + "user", + "userId", + "renameTagId", + "assets" + ], + "type": "object" + }, "TagResponseDto": { "properties": { "id": { @@ -7205,6 +7888,92 @@ ], "type": "object" }, + "UserEntity": { + "properties": { + "assets": { + "items": { + "$ref": "#/components/schemas/AssetEntity" + }, + "type": "array" + }, + "createdAt": { + "format": "date-time", + "type": "string" + }, + "deletedAt": { + "format": "date-time", + "nullable": true, + "type": "string" + }, + "email": { + "type": "string" + }, + "externalPath": { + "nullable": true, + "type": "string" + }, + "firstName": { + "type": "string" + }, + "id": { + "type": "string" + }, + "isAdmin": { + "type": "boolean" + }, + "lastName": { + "type": "string" + }, + "memoriesEnabled": { + "type": "boolean" + }, + "oauthId": { + "type": "string" + }, + "password": { + "type": "string" + }, + "profileImagePath": { + "type": "string" + }, + "shouldChangePassword": { + "type": "boolean" + }, + "storageLabel": { + "nullable": true, + "type": "string" + }, + "tags": { + "items": { + "$ref": "#/components/schemas/TagEntity" + }, + "type": "array" + }, + "updatedAt": { + "format": "date-time", + "type": "string" + } + }, + "required": [ + "id", + "firstName", + "lastName", + "isAdmin", + "email", + "storageLabel", + "externalPath", + "oauthId", + "profileImagePath", + "shouldChangePassword", + "createdAt", + "deletedAt", + "updatedAt", + "memoriesEnabled", + "tags", + "assets" + ], + "type": "object" + }, "UserResponseDto": { "properties": { "createdAt": { diff --git a/server/src/domain/album/album.service.ts b/server/src/domain/album/album.service.ts index 39b54acff5..d28a957a73 100644 --- a/server/src/domain/album/album.service.ts +++ b/server/src/domain/album/album.service.ts @@ -285,10 +285,10 @@ export class AlbumService { return album; } - async addRule(authUser: AuthUserDto, id: string, dto: CreateRuleDto) { - await this.access.requirePermission(authUser, Permission.ALBUM_READ, id); + async addRule(authUser: AuthUserDto, albumId: string, dto: CreateRuleDto) { + await this.access.requirePermission(authUser, Permission.ALBUM_READ, albumId); - const album = await this.findOrFail(id); + const album = await this.findOrFail(albumId); const user = await this.userRepository.get(authUser.id); if (!user) { @@ -305,8 +305,18 @@ export class AlbumService { await this.ruleRepository.create(rule); - return await this.findOrFail(id); + return await this.findOrFail(albumId); } - async removeRule(authUser: AuthUserDto, ruleId: string) {} + async removeRule(authUser: AuthUserDto, albumId: string, ruleId: string) { + await this.access.requirePermission(authUser, Permission.ALBUM_READ, albumId); + + const album = await this.findOrFail(albumId); + const rule = album.rules.find((rule) => rule.id === ruleId); + if (!rule) { + throw new BadRequestException('Rule not found'); + } + + await this.ruleRepository.delete(rule); + } } diff --git a/server/src/domain/album/rule.repository.ts b/server/src/domain/album/rule.repository.ts index 7908f83a57..6ac6278f6c 100644 --- a/server/src/domain/album/rule.repository.ts +++ b/server/src/domain/album/rule.repository.ts @@ -4,5 +4,5 @@ export const IRuleRepository = 'IRuleRepository'; export interface IRuleRepository { create(rule: RuleEntity): Promise; - delete(rule: RuleEntity): Promise; + delete(rule: RuleEntity): Promise; } diff --git a/server/src/immich/controllers/album.controller.ts b/server/src/immich/controllers/album.controller.ts index f39da91ee1..824f5f3d9e 100644 --- a/server/src/immich/controllers/album.controller.ts +++ b/server/src/immich/controllers/album.controller.ts @@ -99,6 +99,6 @@ export class AlbumController { @Param() { id }: UUIDParamDto, @Param('ruleId', new ParseMeUUIDPipe({ version: '4' })) ruleId: string, ) { - throw new Error('Not implemented'); + return this.service.removeRule(authUser, id, ruleId); } } diff --git a/server/src/infra/repositories/rule.repository.ts b/server/src/infra/repositories/rule.repository.ts index 49d360efd9..82f2ad9627 100644 --- a/server/src/infra/repositories/rule.repository.ts +++ b/server/src/infra/repositories/rule.repository.ts @@ -4,13 +4,13 @@ import { Repository } from 'typeorm'; import { RuleEntity } from '../entities'; export class RuleRepository implements IRuleRepository { - constructor(@InjectRepository(RuleEntity) private assetRepository: Repository) {} + constructor(@InjectRepository(RuleEntity) private repository: Repository) {} create(rule: RuleEntity): Promise { - return this.assetRepository.save(rule); + return this.repository.save(rule); } - delete(rule: RuleEntity): Promise { - throw new Error('Method not implemented.'); + delete(rule: RuleEntity): Promise { + return this.repository.remove(rule); } }