From b66024005968d60783d52c518c00a7a57923a353 Mon Sep 17 00:00:00 2001 From: Alex Date: Wed, 15 Feb 2023 15:21:22 -0600 Subject: [PATCH] fix(web/server) uploaded asset in shared link not loaded (#1766) * fix(web/server): Uploaded asset to shared link does not get added to the shared link/album * remove unused code * Add endpoints for each remove and add assets to shared link * Update api * Added deletion logic * Convert callback to async/await * Fix linter * Fix test * Fix server test * added test * Test coverage * modify DTO * Add notification * fix test --- mobile/openapi/.openapi-generator/FILES | 3 - mobile/openapi/README.md | Bin 13549 -> 13609 bytes mobile/openapi/doc/AssetApi.md | Bin 33632 -> 35041 bytes .../doc/UpdateAssetsToSharedLinkDto.md | Bin 452 -> 0 bytes mobile/openapi/lib/api.dart | Bin 4746 -> 4694 bytes mobile/openapi/lib/api/asset_api.dart | Bin 41770 -> 43328 bytes mobile/openapi/lib/api_client.dart | Bin 16374 -> 16268 bytes .../update_assets_to_shared_link_dto.dart | Bin 3671 -> 0 bytes mobile/openapi/test/asset_api_test.dart | Bin 4809 -> 4968 bytes ...update_assets_to_shared_link_dto_test.dart | Bin 637 -> 0 bytes .../src/api-v1/asset/asset.controller.ts | 20 +- .../src/api-v1/asset/asset.service.spec.ts | 25 +- .../immich/src/api-v1/asset/asset.service.ts | 27 ++- .../dto/add-assets-to-shared-link.dto.ts | 6 - server/immich-openapi-specs.json | 89 ++++--- server/libs/domain/src/share/share.core.ts | 15 +- server/package.json | 4 +- web/src/api/open-api/api.ts | 222 +++++++++++------- web/src/api/open-api/base.ts | 2 +- web/src/api/open-api/common.ts | 2 +- web/src/api/open-api/configuration.ts | 2 +- web/src/api/open-api/index.ts | 2 +- .../album-page/asset-selection.svelte | 14 +- .../individual-shared-viewer.svelte | 22 +- web/src/lib/utils/file-uploader.ts | 163 ++++++------- 25 files changed, 362 insertions(+), 256 deletions(-) delete mode 100644 mobile/openapi/doc/UpdateAssetsToSharedLinkDto.md delete mode 100644 mobile/openapi/lib/model/update_assets_to_shared_link_dto.dart delete mode 100644 mobile/openapi/test/update_assets_to_shared_link_dto_test.dart delete mode 100644 server/apps/immich/src/api-v1/asset/dto/add-assets-to-shared-link.dto.ts diff --git a/mobile/openapi/.openapi-generator/FILES b/mobile/openapi/.openapi-generator/FILES index 56a94fb33..dd3c2b3a6 100644 --- a/mobile/openapi/.openapi-generator/FILES +++ b/mobile/openapi/.openapi-generator/FILES @@ -86,7 +86,6 @@ doc/ThumbnailFormat.md doc/TimeGroupEnum.md doc/UpdateAlbumDto.md doc/UpdateAssetDto.md -doc/UpdateAssetsToSharedLinkDto.md doc/UpdateTagDto.md doc/UpdateUserDto.md doc/UpsertDeviceInfoDto.md @@ -189,7 +188,6 @@ lib/model/thumbnail_format.dart lib/model/time_group_enum.dart lib/model/update_album_dto.dart lib/model/update_asset_dto.dart -lib/model/update_assets_to_shared_link_dto.dart lib/model/update_tag_dto.dart lib/model/update_user_dto.dart lib/model/upsert_device_info_dto.dart @@ -281,7 +279,6 @@ test/thumbnail_format_test.dart test/time_group_enum_test.dart test/update_album_dto_test.dart test/update_asset_dto_test.dart -test/update_assets_to_shared_link_dto_test.dart test/update_tag_dto_test.dart test/update_user_dto_test.dart test/upsert_device_info_dto_test.dart diff --git a/mobile/openapi/README.md b/mobile/openapi/README.md index 6e2f5c856d024378f20bd2908ce59e7c71f2aae1..e95dc1908316458cc8956d684eee5ce8bb514b2f 100644 GIT binary patch delta 129 zcmaExxiV|QEM{{(qm6Ula&af7q&OBAr&) Ct1-X; delta 119 zcmZ3P^)_?DEM_x3!;N#_a&2bhQD)z~LRg4#a=Zv9bFpXMWJOuw$sc%lCfAB^GZ$y( zP2SI~HQA6?Wb+P@wQ?qo#l@*5#Uc5@8Hq)yDL$Eb*)Apdu^K7)$@==C1u2OoshBD@ J2OD2w1OVJBD&_zH diff --git a/mobile/openapi/doc/AssetApi.md b/mobile/openapi/doc/AssetApi.md index aa5335d17487e8cc8dd9f03f8be6a8702bf5335a..595a21d0c91c6daa7594ef4dea78dc35f35e0424 100644 GIT binary patch delta 578 zcmaFR#`JI^(}Zi>i76?L#l@*5#Uc5V*RxB3_=#YCN&e*P>{{IVKmmms1+HiDs{u0qjF86W%My)@pm5ln%<(Y_Ga!I|(n1d% zBzGW7LHzcJQ4dWJ=)WMO@C7P^dJ!TD@})Ym7ElD>uma>;Og93(Y=CS4lCRM$26-H6 SE6nekLz0^XHXo^+!wCSJkJ*m^ delta 409 zcmaDjk?BDj(}Zi2jf8|Z`!U{Qoji~I4o^yANvdOUacW7iXWqnzQj@(nxS5MH^Cq(~ zYE3TW@CPfl;rkf1`A}Z70E+6#wwbDir3J_a1ZN}`rKb2~=4ERjON8VjBsLc+JY@{Q zCF@d>uVAYXiexCVUZAXornQ2;K2{Zz-^v)7ARFwNhp-l>dw^z6USJ!*hpKV%DenF# oWFa(XXlW_bC`4;%;c=;!R;>>B)sfN}JcnH8O5?uAj#V05qkXL;wH) diff --git a/mobile/openapi/doc/UpdateAssetsToSharedLinkDto.md b/mobile/openapi/doc/UpdateAssetsToSharedLinkDto.md deleted file mode 100644 index 3f7d70c7e767c9667de702c33b8f7b3da4c88531..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 452 zcma)2J!``-5Z(1F4m_kW$l2RcaBzlz6GD?t#$aJx5)oS`q{~p~kFVqgns#Zmh7= zjy%KchCklS7bzeSW>1MaIqiIW^lUJQtr`(N;PCF!RblA8acs!OX-ga#^6o_NZj$n^ zS>(A&`>b`y^GMF~+5~>yvp4qiGC>h-G#=G;U|@otY+z{GR*0rq#uXE|N&UbOrEU9A z6pPnYS#4Ius54FdbwtZ5eOVsK%c-0&hi`ScTmMrVtCDE7BV+l8#aH03@VOA;6DJRi AqyPW_ diff --git a/mobile/openapi/lib/api.dart b/mobile/openapi/lib/api.dart index edec2eb9738d87ae170c873754be9541a4bb2904..2f426ca69922c6b8b4a0b7b8ef159152d5b38a6a 100644 GIT binary patch delta 12 TcmeBDy{58(RbaEFKp+zU9Q6af delta 30 lcmcbn(xtkARe&?GxHz?>IKCu*ax|C9WCa2C&8z}}OaP*X2~Ge2 diff --git a/mobile/openapi/lib/api/asset_api.dart b/mobile/openapi/lib/api/asset_api.dart index 72580bc4c9dcabb0afa540abc095c68752192b09..cf360d2418f50b49ff6f2e4c98e8976503ca4475 100644 GIT binary patch delta 552 zcmZ2=jOoB7rVT3?Cr8HdPVSWp6Ld^TaV#!QEh%;>$&Xb?Oi7s>$SXfNA&<)f$cD&- zB=B4FpAnOHbg(#ow=ovU!T8NujKQU$Ug()^{3btT# z`MG~hKGkywhmE&-CDh1`r25PqBst&V)H#8U8c#GCPZys!5C329+aA! zUzUpG=OVBG%-hZoE=1m~C_fj6-_eXwK-LBe0d{?;*%zl+a{@yF%7X?3UY7ua!w}g> kRF`N3p?U&lGB9L}aqB_}9%N;(5Zb$`v{-a=|HRpX06<;VV*mgE delta 362 zcmX?biD}g_rVT3?H*0c46i@!%Vlw%IIM?QaCR5SLyC!gJ6^G;pXCxM-ruby$WxJH* z$10Q-q$HN4Iu;kFmY_&Z{>UN;(#waez%y_1#cFYbP#k)I24XdDa)7khBpv(7=a{%B zFPE^J{A(gNFN$TGH`Z)pB;A3AldYz3+ZB7}A^fC4hTC)}&z!=|3^WGhIT2+4!a{5F YM(KA diff --git a/mobile/openapi/lib/api_client.dart b/mobile/openapi/lib/api_client.dart index 632c79f6c95e946e9fa3954a1ae7cbd9032abe47..8fe18d2f7964f8857577410d950279b1902a06e2 100644 GIT binary patch delta 12 TcmexX-&4O~mC0ri(??tYERqHH delta 50 zcmeCF|5m?Yl?jJqadB!%aY+7T0S!fo(1Mi2l2n*ba7JQLYKl*0UiRij6K-w*3HcLE diff --git a/mobile/openapi/lib/model/update_assets_to_shared_link_dto.dart b/mobile/openapi/lib/model/update_assets_to_shared_link_dto.dart deleted file mode 100644 index fb8a4c780daaf526c556a89a6ef9e9068e5442df..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3671 zcmds4QE%He5PtWsxD-XK0FJ!oX>cZ|!Qu>Q(-=t7hruueTB2h%GO3YN4I}k`-yJF1 z^6IKXUIr{cY>B!%-hKCdC%WJ7^!sr6`Eh*u$LMZ!fB87Ng6o^xQ4g-ha5sL0Pvfhb z>pzdsj4a>hOxxrves|WPTk#~7=ILDNbS?@$fJ#<|=P57vl1tkQ_hMNpZ4Y{|V#U@b zEi0R9{#PY5x=Xgi&ys2Uwp<$wuFYZhOi5#zw5iB(LopXzJ9l$%vqDI2(n856G_wmP z(_eo{vjx+p)4}a5s5z*TOIC^)|Ia&}tYF5#=Q3xOUmC-$c~lSR!1G%npRcTfmkvN5 zvAE~9($W9}$yYFGahZZ}NoPDp-xkA&VguNPFfpdV?M-eV0ar6jragSRIl&|WZiTVG zJy;Xf;!+p!zhkN<)%FL{XBLHG z;}F0D(k$EK0#^Bp$W_{H!F>{fCU3*lwCm9P#L61x9^`A}smO*H_=8ar!}@m3g=7VM z!6od~ch)bPbB~d;nCNcUp*uzMy;gfe?Re#^X<I$o%p zTILtpSbyQtagf#!`!oZ-m|5hKBsOAhVI5!(d1n%5Y%7di$0E-W(iYY*lBUuep=TC9 zI4oI760;0d>K4LWf@i)qB-z^WPFW7`2F|7ilGxd`kS%!j%yYoONV$t#*fq@fS|R*1 zjv@uAd{5PP(Zw&bJKLcSjIjuQW!(k5uf10!}u=lBGj~* z|7&bHAtyLW9Isxp9qOHCEwQp@gZzF=8joS~T1Zw`JS=XalN}Z6=Pi{++gL%gg99o? z9B3SGZE61thzRF>xnlujc+w>4Fgn?D9PCYd3RPFMab|VSgu-?>)CzO2gqC847f|5t z*Vw8(tx=vmcyaHFC#_a&R^U*EIV%kBZ32b7)HF9$VQI{@Z8VvByh*qeotQ$|!SS(! zFBS%+G+LIn!BCP;RPUlzM1OJ(+Y=D}q3?atHc(=sqpK0iX%J#JNNO53qmzuNxR~X+ zA2$t84?YcJN@TAUwU<>?VH5xyK)7;I+z_w()O@A1p-6M-V6#Hj9uJycjn__F#PBL= z_64(PZHwz);@kG4*-Aszp9}}EJ;bFb8C;}ep?`B@19vUPRlN>Pj_FQt6AF7}vt7*C z@uShYpEMoen|MbzpAtV?!n_E*4&Qs7N7t7J)h(q!vtAjlHx1z@wO@{q^*Z(KasEb=18(iycrM?q= z?>``5A0F{ALs=5n!kNIGVep87XPsq*k7gY3>}D{3QXlhg&>g+}PmSRG5f2o*)(>CY Q^xGX^-S3g}4i2}!0q2OMCjbBd diff --git a/mobile/openapi/test/asset_api_test.dart b/mobile/openapi/test/asset_api_test.dart index b96510494ca619f9d1ea1ead6a59de490a5fbe77..6c23680d27c985fa8d58b05286ba464ad8290e82 100644 GIT binary patch delta 168 zcmX@9`a*5PVaCawT%wctS;W;6Q&Jp@i&IOAL-K<&5{pt(d@}R0H5_50E+zR2KqZrB zGb?Z-DV;o##ccCo#;ZIaGYpGTbMwnmA;!2B<>w+96oe!NG7F*V{vh6NwH^Ma7JQL zYKl*0UbaRkl0-;8Lc*maUjbPYOm4Cis~jJ)glFDl2PU)0_qatTCkV1`78VL+1OQJ( BGM@kd diff --git a/mobile/openapi/test/update_assets_to_shared_link_dto_test.dart b/mobile/openapi/test/update_assets_to_shared_link_dto_test.dart deleted file mode 100644 index 9b0fb4d1da40e03db79e016737ed4d2b399a77c1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 637 zcmaKpK~Li_5QXpg6~n2mR0zvy1zSX=lvSlJk!U!q5Hh1=l3Jv;*PehX#D8b(($iKP zVtb_b=9@Rk^DNI{{!o>tzZZ9l`+2n};C8uMOra>@uB_lsSuAh=o`@_eZyNMGyEyxC zk;PJX#(Gk%^{Ul9UBY07K#iiFwq$sEsP)!a&qp2Dzu_(@H?Z>bZ_r-s2DfrL^l~%# zAkBR^Zm+EgjE0qN1gKk0KH0-jSg#EQ)mzhvj5lc1&vR98(1$GB&j=%j^$T`%X&o7K zI+yP(rXF4WCt(Vl$KxP~&{zkMmB52*kZB$Qaj5KCFx0HHc`4XtuU0p(MQz3tc+UXD z*lL44=#-}>n0@CTPp&d?b>0qcJc`Kg)T1eUVG?Mi1OHmH*QUD$jN>hv0ON*Q9D0UV z><9UkX|wS7G!YJ@R06W;0lXn_UN+=;2hWEV&wCDrovBl(Xp~@8S(K^CN1R$->Hf$* E0T>h1Hvj+t diff --git a/server/apps/immich/src/api-v1/asset/asset.controller.ts b/server/apps/immich/src/api-v1/asset/asset.controller.ts index aaafd74f4..4c4cdd0e9 100644 --- a/server/apps/immich/src/api-v1/asset/asset.controller.ts +++ b/server/apps/immich/src/api-v1/asset/asset.controller.ts @@ -1,3 +1,4 @@ +import { AddAssetsDto } from './../album/dto/add-assets.dto'; import { Controller, Post, @@ -52,10 +53,10 @@ import { import { DownloadFilesDto } from './dto/download-files.dto'; import { CreateAssetsShareLinkDto } from './dto/create-asset-shared-link.dto'; import { SharedLinkResponseDto } from '@app/domain'; -import { UpdateAssetsToSharedLinkDto } from './dto/add-assets-to-shared-link.dto'; import { AssetSearchDto } from './dto/asset-search.dto'; import { assetUploadOption, ImmichFile } from '../../config/asset-upload.config'; import FileNotEmptyValidator from '../validation/file-not-empty-validator'; +import { RemoveAssetsDto } from '../album/dto/remove-assets.dto'; function asStreamableFile({ stream, type, length }: ImmichReadStream) { return new StreamableFile(stream, { type, length }); @@ -330,11 +331,20 @@ export class AssetController { } @Authenticated({ isShared: true }) - @Patch('/shared-link') - async updateAssetsInSharedLink( + @Patch('/shared-link/add') + async addAssetsToSharedLink( @GetAuthUser() authUser: AuthUserDto, - @Body(ValidationPipe) dto: UpdateAssetsToSharedLinkDto, + @Body(ValidationPipe) dto: AddAssetsDto, ): Promise { - return await this.assetService.updateAssetsInSharedLink(authUser, dto); + return await this.assetService.addAssetsToSharedLink(authUser, dto); + } + + @Authenticated({ isShared: true }) + @Patch('/shared-link/remove') + async removeAssetsFromSharedLink( + @GetAuthUser() authUser: AuthUserDto, + @Body(ValidationPipe) dto: RemoveAssetsDto, + ): Promise { + return await this.assetService.removeAssetsFromSharedLink(authUser, dto); } } diff --git a/server/apps/immich/src/api-v1/asset/asset.service.spec.ts b/server/apps/immich/src/api-v1/asset/asset.service.spec.ts index 08c4ed1ac..6d9f1750a 100644 --- a/server/apps/immich/src/api-v1/asset/asset.service.spec.ts +++ b/server/apps/immich/src/api-v1/asset/asset.service.spec.ts @@ -198,14 +198,31 @@ describe('AssetService', () => { sharedLinkRepositoryMock.get.mockResolvedValue(null); sharedLinkRepositoryMock.hasAssetAccess.mockResolvedValue(true); - await expect(sut.updateAssetsInSharedLink(authDto, dto)).rejects.toBeInstanceOf(BadRequestException); + await expect(sut.addAssetsToSharedLink(authDto, dto)).rejects.toBeInstanceOf(BadRequestException); expect(assetRepositoryMock.getById).toHaveBeenCalledWith(asset1.id); expect(sharedLinkRepositoryMock.get).toHaveBeenCalledWith(authDto.id, authDto.sharedLinkId); - expect(sharedLinkRepositoryMock.hasAssetAccess).toHaveBeenCalledWith(authDto.sharedLinkId, asset1.id); expect(sharedLinkRepositoryMock.save).not.toHaveBeenCalled(); }); + it('should add assets to a shared link', async () => { + const asset1 = _getAsset_1(); + + const authDto = authStub.adminSharedLink; + const dto = { assetIds: [asset1.id] }; + + assetRepositoryMock.getById.mockResolvedValue(asset1); + sharedLinkRepositoryMock.get.mockResolvedValue(sharedLinkStub.valid); + sharedLinkRepositoryMock.hasAssetAccess.mockResolvedValue(true); + sharedLinkRepositoryMock.save.mockResolvedValue(sharedLinkStub.valid); + + await expect(sut.addAssetsToSharedLink(authDto, dto)).resolves.toEqual(sharedLinkResponseStub.valid); + + expect(assetRepositoryMock.getById).toHaveBeenCalledWith(asset1.id); + expect(sharedLinkRepositoryMock.get).toHaveBeenCalledWith(authDto.id, authDto.sharedLinkId); + expect(sharedLinkRepositoryMock.save).toHaveBeenCalled(); + }); + it('should remove assets from a shared link', async () => { const asset1 = _getAsset_1(); @@ -217,11 +234,11 @@ describe('AssetService', () => { sharedLinkRepositoryMock.hasAssetAccess.mockResolvedValue(true); sharedLinkRepositoryMock.save.mockResolvedValue(sharedLinkStub.valid); - await expect(sut.updateAssetsInSharedLink(authDto, dto)).resolves.toEqual(sharedLinkResponseStub.valid); + await expect(sut.removeAssetsFromSharedLink(authDto, dto)).resolves.toEqual(sharedLinkResponseStub.valid); expect(assetRepositoryMock.getById).toHaveBeenCalledWith(asset1.id); expect(sharedLinkRepositoryMock.get).toHaveBeenCalledWith(authDto.id, authDto.sharedLinkId); - expect(sharedLinkRepositoryMock.hasAssetAccess).toHaveBeenCalledWith(authDto.sharedLinkId, asset1.id); + expect(sharedLinkRepositoryMock.save).toHaveBeenCalled(); }); }); diff --git a/server/apps/immich/src/api-v1/asset/asset.service.ts b/server/apps/immich/src/api-v1/asset/asset.service.ts index 35c14e703..959571f38 100644 --- a/server/apps/immich/src/api-v1/asset/asset.service.ts +++ b/server/apps/immich/src/api-v1/asset/asset.service.ts @@ -58,8 +58,9 @@ import { ISharedLinkRepository } from '@app/domain'; import { DownloadFilesDto } from './dto/download-files.dto'; import { CreateAssetsShareLinkDto } from './dto/create-asset-shared-link.dto'; import { mapSharedLink, SharedLinkResponseDto } from '@app/domain'; -import { UpdateAssetsToSharedLinkDto } from './dto/add-assets-to-shared-link.dto'; import { AssetSearchDto } from './dto/asset-search.dto'; +import { AddAssetsDto } from '../album/dto/add-assets.dto'; +import { RemoveAssetsDto } from '../album/dto/remove-assets.dto'; const fileInfo = promisify(stat); @@ -606,23 +607,35 @@ export class AssetService { return mapSharedLink(sharedLink); } - async updateAssetsInSharedLink( - authUser: AuthUserDto, - dto: UpdateAssetsToSharedLinkDto, - ): Promise { + async addAssetsToSharedLink(authUser: AuthUserDto, dto: AddAssetsDto): Promise { if (!authUser.sharedLinkId) { throw new ForbiddenException(); } const assets = []; - await this.checkAssetsAccess(authUser, dto.assetIds); for (const assetId of dto.assetIds) { const asset = await this._assetRepository.getById(assetId); assets.push(asset); } - const updatedLink = await this.shareCore.updateAssets(authUser.id, authUser.sharedLinkId, assets); + const updatedLink = await this.shareCore.addAssets(authUser.id, authUser.sharedLinkId, assets); + return mapSharedLink(updatedLink); + } + + async removeAssetsFromSharedLink(authUser: AuthUserDto, dto: RemoveAssetsDto): Promise { + if (!authUser.sharedLinkId) { + throw new ForbiddenException(); + } + + const assets = []; + + for (const assetId of dto.assetIds) { + const asset = await this._assetRepository.getById(assetId); + assets.push(asset); + } + + const updatedLink = await this.shareCore.removeAssets(authUser.id, authUser.sharedLinkId, assets); return mapSharedLink(updatedLink); } diff --git a/server/apps/immich/src/api-v1/asset/dto/add-assets-to-shared-link.dto.ts b/server/apps/immich/src/api-v1/asset/dto/add-assets-to-shared-link.dto.ts deleted file mode 100644 index 2eb451d3d..000000000 --- a/server/apps/immich/src/api-v1/asset/dto/add-assets-to-shared-link.dto.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { IsNotEmpty } from 'class-validator'; - -export class UpdateAssetsToSharedLinkDto { - @IsNotEmpty() - assetIds!: string[]; -} diff --git a/server/immich-openapi-specs.json b/server/immich-openapi-specs.json index 1e28e5c0a..80d56f07a 100644 --- a/server/immich-openapi-specs.json +++ b/server/immich-openapi-specs.json @@ -1869,9 +1869,11 @@ "bearer": [] } ] - }, + } + }, + "/asset/shared-link/add": { "patch": { - "operationId": "updateAssetsInSharedLink", + "operationId": "addAssetsToSharedLink", "description": "", "parameters": [], "requestBody": { @@ -1879,7 +1881,44 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/UpdateAssetsToSharedLinkDto" + "$ref": "#/components/schemas/AddAssetsDto" + } + } + } + }, + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SharedLinkResponseDto" + } + } + } + } + }, + "tags": [ + "Asset" + ], + "security": [ + { + "bearer": [] + } + ] + } + }, + "/asset/shared-link/remove": { + "patch": { + "operationId": "removeAssetsFromSharedLink", + "description": "", + "parameters": [], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RemoveAssetsDto" } } } @@ -4171,7 +4210,21 @@ "assetIds" ] }, - "UpdateAssetsToSharedLinkDto": { + "AddAssetsDto": { + "type": "object", + "properties": { + "assetIds": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "assetIds" + ] + }, + "RemoveAssetsDto": { "type": "object", "properties": { "assetIds": { @@ -4267,20 +4320,6 @@ "sharedUserIds" ] }, - "AddAssetsDto": { - "type": "object", - "properties": { - "assetIds": { - "type": "array", - "items": { - "type": "string" - } - } - }, - "required": [ - "assetIds" - ] - }, "AddAssetsResponseDto": { "type": "object", "properties": { @@ -4302,20 +4341,6 @@ "alreadyInAlbum" ] }, - "RemoveAssetsDto": { - "type": "object", - "properties": { - "assetIds": { - "type": "array", - "items": { - "type": "string" - } - } - }, - "required": [ - "assetIds" - ] - }, "UpdateAlbumDto": { "type": "object", "properties": { diff --git a/server/libs/domain/src/share/share.core.ts b/server/libs/domain/src/share/share.core.ts index d244c8da9..0fa62cbb6 100644 --- a/server/libs/domain/src/share/share.core.ts +++ b/server/libs/domain/src/share/share.core.ts @@ -63,13 +63,24 @@ export class ShareCore { return this.repository.remove(link); } - async updateAssets(userId: string, id: string, assets: AssetEntity[]) { + async addAssets(userId: string, id: string, assets: AssetEntity[]) { const link = await this.get(userId, id); if (!link) { throw new BadRequestException('Shared link not found'); } - return this.repository.save({ ...link, assets }); + return this.repository.save({ ...link, assets: [...link.assets, ...assets] }); + } + + async removeAssets(userId: string, id: string, assets: AssetEntity[]) { + const link = await this.get(userId, id); + if (!link) { + throw new BadRequestException('Shared link not found'); + } + + const newAssets = link.assets.filter((asset) => assets.find((a) => a.id === asset.id)); + + return this.repository.save({ ...link, assets: newAssets }); } async hasAssetAccess(id: string, assetId: string): Promise { diff --git a/server/package.json b/server/package.json index 23bfe29b9..fee823cc9 100644 --- a/server/package.json +++ b/server/package.json @@ -140,9 +140,9 @@ }, "./libs/domain/": { "branches": 80, - "functions": 89, + "functions": 88, "lines": 95, - "statements": 95 + "statements": 94 } }, "testEnvironment": "node", diff --git a/web/src/api/open-api/api.ts b/web/src/api/open-api/api.ts index 5ceade1fc..a7cfed187 100644 --- a/web/src/api/open-api/api.ts +++ b/web/src/api/open-api/api.ts @@ -4,7 +4,7 @@ * Immich * Immich API * - * The version of the OpenAPI document: 1.46.1 + * The version of the OpenAPI document: 1.47.2 * * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). @@ -2083,19 +2083,6 @@ export interface UpdateAssetDto { */ 'isFavorite'?: boolean; } -/** - * - * @export - * @interface UpdateAssetsToSharedLinkDto - */ -export interface UpdateAssetsToSharedLinkDto { - /** - * - * @type {Array} - * @memberof UpdateAssetsToSharedLinkDto - */ - 'assetIds': Array; -} /** * * @export @@ -3588,6 +3575,45 @@ export class AlbumApi extends BaseAPI { */ export const AssetApiAxiosParamCreator = function (configuration?: Configuration) { return { + /** + * + * @param {AddAssetsDto} addAssetsDto + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + addAssetsToSharedLink: async (addAssetsDto: AddAssetsDto, options: AxiosRequestConfig = {}): Promise => { + // verify required parameter 'addAssetsDto' is not null or undefined + assertParamExists('addAssetsToSharedLink', 'addAssetsDto', addAssetsDto) + const localVarPath = `/asset/shared-link/add`; + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { method: 'PATCH', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + // authentication bearer required + // http bearer authentication required + await setBearerAuthToObject(localVarHeaderParameter, configuration) + + + + localVarHeaderParameter['Content-Type'] = 'application/json'; + + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + localVarRequestOptions.data = serializeDataIfNeeded(addAssetsDto, localVarRequestOptions, configuration) + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, /** * Check duplicated asset before uploading - for Web upload used * @param {CheckDuplicateAssetDto} checkDuplicateAssetDto @@ -4232,6 +4258,45 @@ export const AssetApiAxiosParamCreator = function (configuration?: Configuration options: localVarRequestOptions, }; }, + /** + * + * @param {RemoveAssetsDto} removeAssetsDto + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + removeAssetsFromSharedLink: async (removeAssetsDto: RemoveAssetsDto, options: AxiosRequestConfig = {}): Promise => { + // verify required parameter 'removeAssetsDto' is not null or undefined + assertParamExists('removeAssetsFromSharedLink', 'removeAssetsDto', removeAssetsDto) + const localVarPath = `/asset/shared-link/remove`; + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { method: 'PATCH', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + // authentication bearer required + // http bearer authentication required + await setBearerAuthToObject(localVarHeaderParameter, configuration) + + + + localVarHeaderParameter['Content-Type'] = 'application/json'; + + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + localVarRequestOptions.data = serializeDataIfNeeded(removeAssetsDto, localVarRequestOptions, configuration) + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, /** * * @param {SearchAssetDto} searchAssetDto @@ -4361,45 +4426,6 @@ export const AssetApiAxiosParamCreator = function (configuration?: Configuration options: localVarRequestOptions, }; }, - /** - * - * @param {UpdateAssetsToSharedLinkDto} updateAssetsToSharedLinkDto - * @param {*} [options] Override http request option. - * @throws {RequiredError} - */ - updateAssetsInSharedLink: async (updateAssetsToSharedLinkDto: UpdateAssetsToSharedLinkDto, options: AxiosRequestConfig = {}): Promise => { - // verify required parameter 'updateAssetsToSharedLinkDto' is not null or undefined - assertParamExists('updateAssetsInSharedLink', 'updateAssetsToSharedLinkDto', updateAssetsToSharedLinkDto) - const localVarPath = `/asset/shared-link`; - // use dummy base URL string because the URL constructor only accepts absolute URLs. - const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); - let baseOptions; - if (configuration) { - baseOptions = configuration.baseOptions; - } - - const localVarRequestOptions = { method: 'PATCH', ...baseOptions, ...options}; - const localVarHeaderParameter = {} as any; - const localVarQueryParameter = {} as any; - - // authentication bearer required - // http bearer authentication required - await setBearerAuthToObject(localVarHeaderParameter, configuration) - - - - localVarHeaderParameter['Content-Type'] = 'application/json'; - - setSearchParams(localVarUrlObj, localVarQueryParameter); - let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; - localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; - localVarRequestOptions.data = serializeDataIfNeeded(updateAssetsToSharedLinkDto, localVarRequestOptions, configuration) - - return { - url: toPathString(localVarUrlObj), - options: localVarRequestOptions, - }; - }, /** * * @param {AssetTypeEnum} assetType @@ -4518,6 +4544,16 @@ export const AssetApiAxiosParamCreator = function (configuration?: Configuration export const AssetApiFp = function(configuration?: Configuration) { const localVarAxiosParamCreator = AssetApiAxiosParamCreator(configuration) return { + /** + * + * @param {AddAssetsDto} addAssetsDto + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async addAssetsToSharedLink(addAssetsDto: AddAssetsDto, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.addAssetsToSharedLink(addAssetsDto, options); + return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); + }, /** * Check duplicated asset before uploading - for Web upload used * @param {CheckDuplicateAssetDto} checkDuplicateAssetDto @@ -4687,6 +4723,16 @@ export const AssetApiFp = function(configuration?: Configuration) { const localVarAxiosArgs = await localVarAxiosParamCreator.getUserAssetsByDeviceId(deviceId, options); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); }, + /** + * + * @param {RemoveAssetsDto} removeAssetsDto + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async removeAssetsFromSharedLink(removeAssetsDto: RemoveAssetsDto, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.removeAssetsFromSharedLink(removeAssetsDto, options); + return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); + }, /** * * @param {SearchAssetDto} searchAssetDto @@ -4720,16 +4766,6 @@ export const AssetApiFp = function(configuration?: Configuration) { const localVarAxiosArgs = await localVarAxiosParamCreator.updateAsset(assetId, updateAssetDto, options); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); }, - /** - * - * @param {UpdateAssetsToSharedLinkDto} updateAssetsToSharedLinkDto - * @param {*} [options] Override http request option. - * @throws {RequiredError} - */ - async updateAssetsInSharedLink(updateAssetsToSharedLinkDto: UpdateAssetsToSharedLinkDto, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { - const localVarAxiosArgs = await localVarAxiosParamCreator.updateAssetsInSharedLink(updateAssetsToSharedLinkDto, options); - return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); - }, /** * * @param {AssetTypeEnum} assetType @@ -4760,6 +4796,15 @@ export const AssetApiFp = function(configuration?: Configuration) { export const AssetApiFactory = function (configuration?: Configuration, basePath?: string, axios?: AxiosInstance) { const localVarFp = AssetApiFp(configuration) return { + /** + * + * @param {AddAssetsDto} addAssetsDto + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + addAssetsToSharedLink(addAssetsDto: AddAssetsDto, options?: any): AxiosPromise { + return localVarFp.addAssetsToSharedLink(addAssetsDto, options).then((request) => request(axios, basePath)); + }, /** * Check duplicated asset before uploading - for Web upload used * @param {CheckDuplicateAssetDto} checkDuplicateAssetDto @@ -4912,6 +4957,15 @@ export const AssetApiFactory = function (configuration?: Configuration, basePath getUserAssetsByDeviceId(deviceId: string, options?: any): AxiosPromise> { return localVarFp.getUserAssetsByDeviceId(deviceId, options).then((request) => request(axios, basePath)); }, + /** + * + * @param {RemoveAssetsDto} removeAssetsDto + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + removeAssetsFromSharedLink(removeAssetsDto: RemoveAssetsDto, options?: any): AxiosPromise { + return localVarFp.removeAssetsFromSharedLink(removeAssetsDto, options).then((request) => request(axios, basePath)); + }, /** * * @param {SearchAssetDto} searchAssetDto @@ -4942,15 +4996,6 @@ export const AssetApiFactory = function (configuration?: Configuration, basePath updateAsset(assetId: string, updateAssetDto: UpdateAssetDto, options?: any): AxiosPromise { return localVarFp.updateAsset(assetId, updateAssetDto, options).then((request) => request(axios, basePath)); }, - /** - * - * @param {UpdateAssetsToSharedLinkDto} updateAssetsToSharedLinkDto - * @param {*} [options] Override http request option. - * @throws {RequiredError} - */ - updateAssetsInSharedLink(updateAssetsToSharedLinkDto: UpdateAssetsToSharedLinkDto, options?: any): AxiosPromise { - return localVarFp.updateAssetsInSharedLink(updateAssetsToSharedLinkDto, options).then((request) => request(axios, basePath)); - }, /** * * @param {AssetTypeEnum} assetType @@ -4980,6 +5025,17 @@ export const AssetApiFactory = function (configuration?: Configuration, basePath * @extends {BaseAPI} */ export class AssetApi extends BaseAPI { + /** + * + * @param {AddAssetsDto} addAssetsDto + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof AssetApi + */ + public addAssetsToSharedLink(addAssetsDto: AddAssetsDto, options?: AxiosRequestConfig) { + return AssetApiFp(this.configuration).addAssetsToSharedLink(addAssetsDto, options).then((request) => request(this.axios, this.basePath)); + } + /** * Check duplicated asset before uploading - for Web upload used * @param {CheckDuplicateAssetDto} checkDuplicateAssetDto @@ -5166,6 +5222,17 @@ export class AssetApi extends BaseAPI { return AssetApiFp(this.configuration).getUserAssetsByDeviceId(deviceId, options).then((request) => request(this.axios, this.basePath)); } + /** + * + * @param {RemoveAssetsDto} removeAssetsDto + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof AssetApi + */ + public removeAssetsFromSharedLink(removeAssetsDto: RemoveAssetsDto, options?: AxiosRequestConfig) { + return AssetApiFp(this.configuration).removeAssetsFromSharedLink(removeAssetsDto, options).then((request) => request(this.axios, this.basePath)); + } + /** * * @param {SearchAssetDto} searchAssetDto @@ -5202,17 +5269,6 @@ export class AssetApi extends BaseAPI { return AssetApiFp(this.configuration).updateAsset(assetId, updateAssetDto, options).then((request) => request(this.axios, this.basePath)); } - /** - * - * @param {UpdateAssetsToSharedLinkDto} updateAssetsToSharedLinkDto - * @param {*} [options] Override http request option. - * @throws {RequiredError} - * @memberof AssetApi - */ - public updateAssetsInSharedLink(updateAssetsToSharedLinkDto: UpdateAssetsToSharedLinkDto, options?: AxiosRequestConfig) { - return AssetApiFp(this.configuration).updateAssetsInSharedLink(updateAssetsToSharedLinkDto, options).then((request) => request(this.axios, this.basePath)); - } - /** * * @param {AssetTypeEnum} assetType diff --git a/web/src/api/open-api/base.ts b/web/src/api/open-api/base.ts index 1ffb5e4ff..aa00dd944 100644 --- a/web/src/api/open-api/base.ts +++ b/web/src/api/open-api/base.ts @@ -4,7 +4,7 @@ * Immich * Immich API * - * The version of the OpenAPI document: 1.46.1 + * The version of the OpenAPI document: 1.47.2 * * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/web/src/api/open-api/common.ts b/web/src/api/open-api/common.ts index 4d7d11c83..8f295ff06 100644 --- a/web/src/api/open-api/common.ts +++ b/web/src/api/open-api/common.ts @@ -4,7 +4,7 @@ * Immich * Immich API * - * The version of the OpenAPI document: 1.46.1 + * The version of the OpenAPI document: 1.47.2 * * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/web/src/api/open-api/configuration.ts b/web/src/api/open-api/configuration.ts index e0596e4b1..af2967072 100644 --- a/web/src/api/open-api/configuration.ts +++ b/web/src/api/open-api/configuration.ts @@ -4,7 +4,7 @@ * Immich * Immich API * - * The version of the OpenAPI document: 1.46.1 + * The version of the OpenAPI document: 1.47.2 * * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/web/src/api/open-api/index.ts b/web/src/api/open-api/index.ts index 49f0b7a38..b7490d529 100644 --- a/web/src/api/open-api/index.ts +++ b/web/src/api/open-api/index.ts @@ -4,7 +4,7 @@ * Immich * Immich API * - * The version of the OpenAPI document: 1.46.1 + * The version of the OpenAPI document: 1.47.2 * * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/web/src/lib/components/album-page/asset-selection.svelte b/web/src/lib/components/album-page/asset-selection.svelte index 401d581c2..9682a8ec9 100644 --- a/web/src/lib/components/album-page/asset-selection.svelte +++ b/web/src/lib/components/album-page/asset-selection.svelte @@ -16,6 +16,7 @@ export let albumId: string; export let assetsInAlbum: AssetResponseDto[]; + const locale = navigator.language; onMount(() => { $assetsInAlbumStoreState = assetsInAlbum; @@ -28,8 +29,11 @@ assetInteractionStore.clearMultiselect(); }; - - const locale = navigator.language; + const handleSelectFromComputerClicked = async () => { + await openFileUploadDialog(albumId, ''); + assetInteractionStore.clearMultiselect(); + dispatch('go-back'); + };