From e9fda40b2b57d618a5f6b85b2bacaef2c5b4250d Mon Sep 17 00:00:00 2001 From: Alex Date: Sat, 14 Jan 2023 23:49:47 -0600 Subject: [PATCH] feat(web) Individual assets shared mechanism (#1317) * Create shared link modal for individual asset * Added API to create asset shared link * Added viewer for individual shared link * Added multiselection app bar * Refactor gallery viewer to its own component * Refactor * Refactor * Add and remove asset from shared link * Fixed test * Fixed notification card doesn't wrap * Add check asset access when created asset shared link * pr feedback --- mobile/openapi/.openapi-generator/FILES | 6 + mobile/openapi/README.md | Bin 13537 -> 13908 bytes mobile/openapi/doc/APIKeyApi.md | Bin 4893 -> 4903 bytes mobile/openapi/doc/AlbumApi.md | Bin 17790 -> 17814 bytes mobile/openapi/doc/AssetApi.md | Bin 29219 -> 32450 bytes mobile/openapi/doc/AuthenticationApi.md | Bin 5939 -> 5949 bytes .../openapi/doc/CreateAssetsShareLinkDto.md | Bin 0 -> 583 bytes mobile/openapi/doc/DeviceInfoApi.md | Bin 4786 -> 4788 bytes mobile/openapi/doc/JobApi.md | Bin 4228 -> 4234 bytes mobile/openapi/doc/OAuthApi.md | Bin 4859 -> 4869 bytes mobile/openapi/doc/ServerInfoApi.md | Bin 3623 -> 3631 bytes mobile/openapi/doc/ShareApi.md | Bin 4992 -> 5002 bytes mobile/openapi/doc/SystemConfigApi.md | Bin 5484 -> 5492 bytes mobile/openapi/doc/TagApi.md | Bin 4713 -> 4723 bytes .../doc/UpdateAssetsToSharedLinkDto.md | Bin 0 -> 452 bytes mobile/openapi/doc/UserApi.md | Bin 12504 -> 12524 bytes mobile/openapi/lib/api.dart | Bin 4647 -> 4747 bytes mobile/openapi/lib/api/album_api.dart | Bin 20343 -> 20291 bytes mobile/openapi/lib/api/api_key_api.dart | Bin 7813 -> 7827 bytes mobile/openapi/lib/api/asset_api.dart | Bin 35454 -> 38958 bytes .../openapi/lib/api/authentication_api.dart | Bin 8162 -> 8136 bytes mobile/openapi/lib/api/device_info_api.dart | Bin 5595 -> 5600 bytes mobile/openapi/lib/api/job_api.dart | Bin 5114 -> 5122 bytes mobile/openapi/lib/api/o_auth_api.dart | Bin 7582 -> 7565 bytes mobile/openapi/lib/api/server_info_api.dart | Bin 5980 -> 5933 bytes mobile/openapi/lib/api/share_api.dart | Bin 8156 -> 8164 bytes mobile/openapi/lib/api/system_config_api.dart | Bin 6323 -> 6265 bytes mobile/openapi/lib/api/tag_api.dart | Bin 7636 -> 7668 bytes mobile/openapi/lib/api/user_api.dart | Bin 16351 -> 16367 bytes mobile/openapi/lib/api_client.dart | Bin 16220 -> 16426 bytes .../model/create_assets_share_link_dto.dart | Bin 0 -> 5754 bytes .../update_assets_to_shared_link_dto.dart | Bin 0 -> 3671 bytes mobile/openapi/test/album_api_test.dart | Bin 2270 -> 2450 bytes mobile/openapi/test/api_key_api_test.dart | Bin 1075 -> 1150 bytes mobile/openapi/test/asset_api_test.dart | Bin 3934 -> 4564 bytes .../openapi/test/authentication_api_test.dart | Bin 1160 -> 1235 bytes .../create_assets_share_link_dto_test.dart | Bin 0 -> 943 bytes mobile/openapi/test/device_info_api_test.dart | Bin 1023 -> 1038 bytes mobile/openapi/test/job_api_test.dart | Bin 855 -> 900 bytes mobile/openapi/test/o_auth_api_test.dart | Bin 1085 -> 1160 bytes mobile/openapi/test/server_info_api_test.dart | Bin 949 -> 1009 bytes mobile/openapi/test/share_api_test.dart | Bin 1150 -> 1225 bytes .../openapi/test/system_config_api_test.dart | Bin 1003 -> 1063 bytes mobile/openapi/test/tag_api_test.dart | Bin 1029 -> 1104 bytes ...update_assets_to_shared_link_dto_test.dart | Bin 0 -> 637 bytes mobile/openapi/test/user_api_test.dart | Bin 1765 -> 1915 bytes .../src/api-v1/asset/asset.controller.ts | 22 ++ .../immich/src/api-v1/asset/asset.service.ts | 41 +++- .../dto/add-assets-to-shared-link.dto.ts | 6 + .../asset/dto/create-asset-shared-link.dto.ts | 31 +++ server/immich-openapi-specs.json | 115 +++++++++++ web/src/api/open-api/api.ts | 192 +++++++++++++++++- 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 +- .../components/album-page/album-viewer.svelte | 124 +---------- .../asset-viewer/asset-viewer.svelte | 2 +- .../individual-shared-viewer.svelte | 150 ++++++++++++++ .../create-shared-link-modal.svelte | 73 ++++--- .../gallery-viewer/gallery-viewer.svelte | 118 +++++++++++ .../notification/notification-card.svelte | 2 +- web/src/lib/utils/file-uploader.ts | 20 +- web/src/routes/photos/+page.svelte | 30 ++- web/src/routes/share/[key]/+page.server.ts | 7 +- web/src/routes/share/[key]/+page.svelte | 12 +- 66 files changed, 791 insertions(+), 168 deletions(-) create mode 100644 mobile/openapi/doc/CreateAssetsShareLinkDto.md create mode 100644 mobile/openapi/doc/UpdateAssetsToSharedLinkDto.md create mode 100644 mobile/openapi/lib/model/create_assets_share_link_dto.dart create mode 100644 mobile/openapi/lib/model/update_assets_to_shared_link_dto.dart create mode 100644 mobile/openapi/test/create_assets_share_link_dto_test.dart create mode 100644 mobile/openapi/test/update_assets_to_shared_link_dto_test.dart create mode 100644 server/apps/immich/src/api-v1/asset/dto/add-assets-to-shared-link.dto.ts create mode 100644 server/apps/immich/src/api-v1/asset/dto/create-asset-shared-link.dto.ts create mode 100644 web/src/lib/components/share-page/individual-shared-viewer.svelte create mode 100644 web/src/lib/components/shared-components/gallery-viewer/gallery-viewer.svelte diff --git a/mobile/openapi/.openapi-generator/FILES b/mobile/openapi/.openapi-generator/FILES index d68e7e9b31..62b938b3a9 100644 --- a/mobile/openapi/.openapi-generator/FILES +++ b/mobile/openapi/.openapi-generator/FILES @@ -31,6 +31,7 @@ doc/CheckExistingAssetsDto.md doc/CheckExistingAssetsResponseDto.md doc/CreateAlbumDto.md doc/CreateAlbumShareLinkDto.md +doc/CreateAssetsShareLinkDto.md doc/CreateProfileImageResponseDto.md doc/CreateTagDto.md doc/CreateUserDto.md @@ -86,6 +87,7 @@ doc/ThumbnailFormat.md doc/TimeGroupEnum.md doc/UpdateAlbumDto.md doc/UpdateAssetDto.md +doc/UpdateAssetsToSharedLinkDto.md doc/UpdateTagDto.md doc/UpdateUserDto.md doc/UpsertDeviceInfoDto.md @@ -140,6 +142,7 @@ lib/model/check_existing_assets_dto.dart lib/model/check_existing_assets_response_dto.dart lib/model/create_album_dto.dart lib/model/create_album_share_link_dto.dart +lib/model/create_assets_share_link_dto.dart lib/model/create_profile_image_response_dto.dart lib/model/create_tag_dto.dart lib/model/create_user_dto.dart @@ -188,6 +191,7 @@ 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 @@ -224,6 +228,7 @@ test/check_existing_assets_dto_test.dart test/check_existing_assets_response_dto_test.dart test/create_album_dto_test.dart test/create_album_share_link_dto_test.dart +test/create_assets_share_link_dto_test.dart test/create_profile_image_response_dto_test.dart test/create_tag_dto_test.dart test/create_user_dto_test.dart @@ -279,6 +284,7 @@ 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 5ff0099b03da439c8555dd09052f47bc2f36cde1..46a36d839b1a40aabc39072d49e67611fea848d5 100644 GIT binary patch delta 233 zcmaEuc_nAUEM`MJ!;N$Ia`Pk?r6!i7Iu;kFmK0BZC?h;si*G#h!UEt&{nLxpj*(^B`(LN;MT~6tuJg97CKv zw6qlT!G>ZO2cfF`<>?IMgR|YR8Rl_ delta 34 scmV+-0Nwx8Y~g8;ngcK{FtMGz3$xA)@dmRv6c`q>kSe+fvye2z0S&eec>n+a diff --git a/mobile/openapi/doc/APIKeyApi.md b/mobile/openapi/doc/APIKeyApi.md index c715cedd8fe1ea62189daae2e7165925aa74d9e3..4918e6161600992b91142e9630975aa453b5a7f2 100644 GIT binary patch delta 48 zcmbQMwp?vPEYsv9{-n)&nSz*sjPlJbY;!;&<(sE+UIQ^=HXr4g&$Ky-e>XD#ze*A= delta 48 zcmZ3kHdk#!EYoCRwuH_5nSz)%3$qC_19=UT^#x=$AL3lkw7Hn)CrG|@^J9J&W&nZp B57+Tz1$RKh^j=e zs$#IJ9H6Sn6PZQ0AzWP$e{(hCF;=k@xOtv=2oonWvPJ17Q=VYmUTVX|YW!sG*rseu^M zPuSOwSOAD7fL9vNdZgQcxk}}p{1ZhD?Y)%kb#kl#g+&#w4&qT|afvmfXlY6}M zCUc}DPBu{E*nD1E3?yp83YOaZN%ajcgtgQ3J4E^BZfgk^pxAZB$&zmRlOybTH*ayA zzyucAEa^4_#JbD4xyNe;Nd0xj%|HEbLS!fZ^f%mmBjg!7M8(XwG=u{-bELfI$iSZB zw6t)hkL3SgVSZ8bThds ui<`F?Svf4pL?N4p<{Vh6!Q~#HpFybrmoy1!CysIQ*Boa^^an%n8B>cU0MB;&*^Y%&QubE zh-{8g4+pW1^FtKqI&nmjOu`m{FJM!#hT%H3hy?2(!ZHMuKGmUHryN=L8> Kn>SSpFarRS4OFZE diff --git a/mobile/openapi/doc/AuthenticationApi.md b/mobile/openapi/doc/AuthenticationApi.md index ffcece086bf1a2a29ad17b96cd6943882a5858cf..8c9b0e2d486b8ad8209341c29d99e998588a6e53 100644 GIT binary patch delta 57 zcmdn2w^wh&5vIwT`BOHlF()zu8J3&(vFWgaMSOTNnKy6d7la8fXPhi9puYK}NEQxHC5EF-J248RnZ=g_kpK&g5ui-u!|4G)O3Ev%J7skRp(r1q%R1 CcoZlA diff --git a/mobile/openapi/doc/CreateAssetsShareLinkDto.md b/mobile/openapi/doc/CreateAssetsShareLinkDto.md new file mode 100644 index 0000000000000000000000000000000000000000..2ceffddee254ec23f347fe80e29eb37028e6068b GIT binary patch literal 583 zcma)3O-lnY5WVMD4D6wHVYhb`q1qk_Z7Ehy%R=L3+J66Vc& zZ@w}hhgLdWwxrOM4<04sI0WRd-b;^jZ4VPBA+ikSBo*);q2q#+%|Y%^Y&IJuJqz7B z@(hC>zwVt!$vqJUH;LLgt$a*+))>T6wg?~a^zPC(!O(c^Sd)#>k~lJoZ!d!1qN2DI zi#(TUn3)QB9?5w=*MV=>?6uw9bx=erg;X)hkMqA}0suq02S)$^ diff --git a/mobile/openapi/doc/JobApi.md b/mobile/openapi/doc/JobApi.md index 124e3d2149e7c0aff0f4b33b0ea26193724fa3a6..b2f2b4f7aee1b90d71a27aed0396a3ba2c351e58 100644 GIT binary patch delta 36 ncmZos>{8tDfpPOY#ts%Bag}kh9k0yhR1P*~FmJORuNyM}5|j;k delta 36 ncmeBDY*F0sfpPOE#x@opahY+mFNYE{m^Im#LuPXVuNE@^7@-Z6 diff --git a/mobile/openapi/doc/OAuthApi.md b/mobile/openapi/doc/OAuthApi.md index 836cd845567d725d6aadb1ca31b02016a4dbb80d..d1bcec668519d3707343f4dd16caf7bc2b3c6af1 100644 GIT binary patch delta 56 zcmeyZ+N!o8k7=?4Tl(Z2uK3MH%mU1t9oUXDgG4sxa5;b&(UaE;aBmjia|5Z1-n?GG GhY0`_^AVf? delta 64 zcmZow`>nbmk7@E2-pI-G*?2ZzWy)s;aW>Cq%KcPd=FQANM)YPg_U#~%=*@0in?a0ao9FUgV%nT5Aj}K^vzHM; delta 56 zcmeBDZ&2T`mTB^5&dAA8yegY@m^U*6InA3>*n62af9B)?@gg@z@ydc3KsDZ*Wd(DY E0BDgEasU7T diff --git a/mobile/openapi/doc/SystemConfigApi.md b/mobile/openapi/doc/SystemConfigApi.md index ae8d234ccf94f78164e59c03d9c2a7fc53d0de83..74cf32606c0dfb8bd8fab3785bc9e3c59cab957c 100644 GIT binary patch delta 42 pcmaE(^+jt#Bh%&y+#F1s+gUQ8tgjq%L9Ej(K;j;;zA4r diff --git a/mobile/openapi/doc/TagApi.md b/mobile/openapi/doc/TagApi.md index 2603525e168ca2b4217ffa59534b54ca84d9c3d0..7e3996c10a4336beddb573cb3924dd06013765db 100644 GIT binary patch delta 60 zcmaE<@>ykrIMd`s9FdbRaIp+a^$^2~GlX*B5HmmcjVcL9^ HZyqxMROAz& diff --git a/mobile/openapi/doc/UpdateAssetsToSharedLinkDto.md b/mobile/openapi/doc/UpdateAssetsToSharedLinkDto.md new file mode 100644 index 0000000000000000000000000000000000000000..3f7d70c7e767c9667de702c33b8f7b3da4c88531 GIT binary patch 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_ literal 0 HcmV?d00001 diff --git a/mobile/openapi/doc/UserApi.md b/mobile/openapi/doc/UserApi.md index 752fb7945e0d4b981375370b9e4bd95ad7b00a55..01b6bd1ad3d49479fead36cc04a6a35213a116ec 100644 GIT binary patch delta 102 zcmcbS_$G0~U*^rhe0LZ(UuO-2ve>!(K&&q;5D7(54iNVm%JwQnH3cNsSy(G_6^0PEK!kN^Mx delta 127 zcmaEpcq4JcU*^rm5?2@}>kF`LzR4Pf#N+1ngYY)%3&c(VYxh5!Hn diff --git a/mobile/openapi/lib/api/album_api.dart b/mobile/openapi/lib/api/album_api.dart index 614aad807abc6f0e4d6bad616d984d9bfc687fa9..f3e2b96d94c78cd137fd893eb5d65b44a4c253ef 100644 GIT binary patch delta 1157 zcmex9kMZz4#tkbN9k~<~^!4>2q=H|5Nvf4XNJeI{LT+kFMt+JyQEEwPQC_h^Nk*!I zM@UG3LUd4SaY24wacZm{MAPJj_P(1vnQk(%A^4L$nG811VAW=(j`5SLdGscq7vtJI zn{yVjHJht>Jg8%Z(&Sr0CO|7hCszyeZmt(_K(a<@^DQAW>RH1*xnI(H^La63pMb3A z-rO%)O&x0-ChL0&ZeA@Lh2#s682e^d1!ihR`D?{jNXGkamR4nFhNdH;Q`6=w4PPb} zVER+oyhV$Nnw}5Qi$HQ6*z=S5goQW%H@MA2l|-qu+1_LcGa}6^O|~~#xOu)s8FhTo zy!pM&PM8liFSOrG9bxAR&bY@{N6@jvWGkGW(Su^Fl#_L86>}1!QG8YsbsRM U4=7u4ZFcaSk8I6mRi7Yc0By2e2LJ#7 delta 1176 zcmX>+kMa9F#tkbN#R5``((;RPixm>{6g)yg0uA)O-X@QHYk86l4 z)Cc<2V2^mF)Dr2J&5p9GLDo2Iwp3td48t_u-4zx(2($H5^2_sb@)J{tv;-R4yqixc zzJkRE@8na8BAcaEKQnpYwKO@uG_OQAsZzJJIJHPO6Q7lvZ8dzEHc!<0&*Y2ONRq?o zqV7s?7=h#YmceZ%XME-pA1F$jB}|qugHw@&$->R87G=zmm?lFbnAF_0&)#|Sew&@( z@ZP-7z8Ig;g)Z{MCr?nu<(;g+$uaq@o6O|>HUgVJJI_Z%D^OzdTQ@gGe@veQIEFZT VkegEBiRhrmUAS$VKY9l-0{~~-mE8aU diff --git a/mobile/openapi/lib/api/api_key_api.dart b/mobile/openapi/lib/api/api_key_api.dart index 26223bf89140f2cdfb78616031db8a2aada4d48f..7cc9a3589c47a5fa51b04542eaa3de30d2e26e02 100644 GIT binary patch delta 442 zcmZp*oou^d6{91Uf`Y!jK7>^8%P&c_QV7Y&ELO-(Ey>7FQ7B3+DJ{w?Rw&6xRqzN2 z2~da*N-ZwP&nr%i)q`l7oXDXx`2>sED=0uL^Fk?4= z2@q4RYP)25DAi08Y1n*Z=?k delta 465 zcmbPi+iJUE6{A}~YEfE#QEst9VxEFWNJxN!dVqg$h=P7%L8fkYYNfhDenDzcVo7Fx z9#BP!LQ!f-X;EIWLPQ<%~#r7&r!-Wc`J+bcLAdp1gR_xcjXZp%Bwv=QqE{%=gW&3RSC9_4j75 z6d8ALjZKSeKoD3_xVMsCWAChTk3%A#q?v9A^y$t=I)RMLiLZ()VNuL2a1VKBFV!BR z4R2I0$mW@uCz2G@6W2#*U-xF5<6rhcwBI$`8RNgnZAmgAiF=n7ay%!n(x^B5$DK&e zNylg!rml8|I=-8(d&Ozv4CUw=Z}NhRP78DkgI8vc{C03K;K7t7LrL3k;=#aMl~gUm zmx2aN`75|Q;#bKCL>_6NT1Y|7eiq%MKPke^+OI*%qQ|deoWjrA9WtLmt)osVpmkW` z+^7YAINy=K0>i#r5=RHMum+3XlENGIH3<(kLyg>&`4=Y+Z_~9>gQaA<**1ssRN0#b7d$KJ cFSbdwt)$o3r#Ts{Y=iQQmF-!~JvXX;1A|rHzyJUM delta 1328 zcmb7^PiPZC6vpX{9vVa%suB{}jtQnsb-Rd%c+i88G%1NBwhJP`hRyD@U7PNN{bQ(A zJbF-&GKwBN={*MC(Mtn@_U6HpNH2oN-UStR6T#q;b#ouS-}m14zUg$)ucxT{Zfdbg z%{69jSwt~NQI@M@HkU8w4_#9ko&K}f(s{1ras7cS&w!avW%)8RCsC+uTa?=F9yISJ|1PqgCKtX5X!KUO7; zwT&iIG;vLDQfmz7yq>wlSv11SGsoalS{+PL&!n;JCwfy1cJmk5)ElN^ZjXc9lTN^o z^DhUeJ?UVo>PHO^XX{&3Y&hy>_)Mb3d6>NV5%E|t1(Uh=_+~IdWsYHsjcsv!7DS6@ zu*=!x-781oP3g|a{J&IU@zMG7LwOGFRn8**x-<=MuOGoVzvrO@EUK95hG zhjIbIWNN=ofqpwm z8Nr0MjtikQS>rZU?I1{~jVloGE>E9rO6)LR+~3))WVDg zTBo$xgWH2z#_t!j0lVIjKN-b}{epJP&|o1ta5h_unlMdXAjq;gNBjm8sk$dyiz;kh NDWw51;hc;JGXPlCe8&I) delta 482 zcmX?M|HyvBF2=xs)S|TfqTFJI#5@I$kdOcc^#K3i5C#3j(vl4Q#FX63Jl*2V^gP|t z0(FJ_g4CkKlFa-(pyCvTqSTVoqP${-l8jV^=%Cc%g8aPV)L6aA_8dx+&$DPvp3T9v zIglxgX|fxK^yc#{`pm(2?Mlu_%u7$zEl4abF3&GYA=0+Z_8d1sHiT}j;^t%a#A`!N zetKpek>-MZu}9Db>bOn>9sEm^P<~UuAN|YfxEYPG(AC nNoq)bb}BwcPu3Jw*xVtd!Nl%X3XG3bo5_jta+~kSykP$#`^ diff --git a/mobile/openapi/lib/api/device_info_api.dart b/mobile/openapi/lib/api/device_info_api.dart index 2aa2ff7c5e880a8ea30e45395485a0950a645406..4cde7c5e4ab1cd2f62e5c1bfaedd5c70e2f66f6e 100644 GIT binary patch delta 29 lcmcbu{XlzzCI93GjL4ICwYOLPmgB&@VpYi82O+LX9IhmhFYV%bg GZe{?IvLz`1 diff --git a/mobile/openapi/lib/api/job_api.dart b/mobile/openapi/lib/api/job_api.dart index b64a67c35a001d491398070c5c3d8fdd7b753c3c..2fafd44d31d2fc632cfed911f53c2c365ea9a674 100644 GIT binary patch delta 256 zcmeyR-lVZ%38Oxjf`Yz2lvMD`FG;mh2+7DSR>(~)$;eMpC`v6UEy^oaD9K1w@CXSB z*nEr8jEM!PLt!&7%ReSZ616Kt2c;GlX qO%7(a-E7PC6`R2Up-`8RXz*kopqU$ZAA<}xo4kS7ezU0HTP6Vi#al}N diff --git a/mobile/openapi/lib/api/o_auth_api.dart b/mobile/openapi/lib/api/o_auth_api.dart index b8778596a7148e5fd06f943e54dee66425c09eed..61165c9a2d70da05ac1ff94b1b81bf21c503e2ba 100644 GIT binary patch delta 417 zcmbPd-D|yJ1*0REf`Y!jK7>^8%P&c_QV7Y&ELO-(Ey>7FQ7B3+DJ{w?Rw&6xRqzN2 z2~da*N-ZwP&nr%i)q`l7e336@b0kv=6C0c#wON3biJ2-!YfN_F)|>3l#I<=ZM-q}1 z8k-%sJ*Z&C=8Jp>kc^F*d|OIr^Ebh#Owb@9I$$Qxmr&U3B&y290t`-t$tmK0NHt;e PdL4ICwYOLPmvwSI=eVJ02CSTzT-Ym$< z#OzJL{QSJM%yi;S*VtUcF$-k6;^Z0*z0F13Qp}$CZOF;YBi8ks&+;9Bxcsf)cP4jC zbKPCx0h61bl$n#NTa=oTS(KVwg2xAw+a(k>8;hzkO^z4;X^&|ra&VXC5peG2b_r9a K&4;BC=Uy^F25R#EutdN^pl98XHP?TCyT9j9;P?C|V;1LoM zF!=$G%4T6EF(wwEE``l%ET5RjGNO@P5o*MI&VM8uvH1ayHB`F<|7()913jUzIaNpo KYDAsLJthFB2vF1j delta 324 zcmZ3hcSmo-CPv$U)S|TfqTFJI#5@I$kdOccb$8bg1^wdGqO#N?-ORkSe07EVg4CkK zlFa-(prRCoqSTVoqP${-l8n^N4;ihPHY>5bXL2QMjy}-9VxS>-%$xj>QFC)9yA0Fj z9?suPV%W``Y{)Ath1*R)GgLO;;t^+JcPj-tG}UJELP4?3qWq_soUogR>{tDQ%)E5` Q4pi8zF9dX8kjQN&0NT@W*#H0l diff --git a/mobile/openapi/lib/api/share_api.dart b/mobile/openapi/lib/api/share_api.dart index 6695e6aa0afb613766a6144983b235adb94e672d..d574023ffb27d9a17a1f023a5601f6d99dfd0c8d 100644 GIT binary patch delta 414 zcmca(|HOX73PwjR1qFS5eF&-GmtT@0rvM!VR<{nmkW~jkL8$Wp~pYmo=PEjTn zpraHvt8<5tY{cfRe3lUHli!P}ZWa){$V3%S`fM&0@q~NwqKxz8URLqVg5po9VEkrB Osp&AIH(!+DW(EMCHh1p; delta 432 zcmaE2f5(2q3P!Jh)S|TfqTFJI#5@I$kdOcc^#I2ZXAcGa;*7+iRQ>AAlv;I#{DRb? z#FEVXJfNx+g`(7w(xSX#g_4X^h3KHv;)49V;?!8Z$%-P@lRa78C)Y`FZ7yY+%rsd; z#ALH4YZ<;@rP#F#d}75L2L zf!81se7jjuWGcvq){`f&N^BMrf6C;IX|#*0k86l4d12|ZxkJhuY{O=0*^^8F8`YJZ diff --git a/mobile/openapi/lib/api/system_config_api.dart b/mobile/openapi/lib/api/system_config_api.dart index d2d0ac5ba50fbb21cf9ea60093b245dc0f52e7a5..328a1fc3b7159f20c285eb3beaa8fe65ec69617b 100644 GIT binary patch delta 291 zcmdmN_|ss+Hb#9e1qFS5D5>C=Uy^F25R#EutdN^pl98XHP?TCyT9j9;P?C|V;1LoM zFnJ%3@@5GpQ6?6kE``lnEH{|QG9s5<0cu1S=N*!b*u0M?5vpB=-<;WzL{BJ02c;Gl eqH(DP1{?@0-6P7^uIM7f7I7mELZ8(k<~vH70V GHzoi&xP2-B delta 439 zcmexjeZ_ji5=N(h)S|TfqTFJI#5@I$kdOcc^#K3i5C#2`#B_Cq{DRb?#FEVXJfLif zLQ!f-X;EIWLP5#0QhoiP>&Z{J-kV|?>+VX>$(xyYbeJ}q@@X);;nq)z zn}GiI+5Ae-7VNIg`65@CJTMIoa13$wAlbV>qpdgZ7e5Fx+GO&6aa(YZ@=HHu0ss}i BmJI*^ diff --git a/mobile/openapi/lib/api/user_api.dart b/mobile/openapi/lib/api/user_api.dart index f187652ac2437552472a680722b10fe1b4c9bcf3..51bd6180fb7e5e1ce9746025102c4412e85eef65 100644 GIT binary patch delta 973 zcmca#|Gs|1GDb%(1qFS5eF&-GmtT@@0VXTCRl!%38$~6qf?UHwUO(p|&+g)WgxN zG25J@b(LDyW&+u;S&~PG*$>ln7grzG5Lc)_szJW- zOsOT_qJYiU_(MPz*iVkr658A$beYKs(-L=AXh;!nu+rvzq8$*I=SjpdTjDWPKNnw^ zZ~iT9#x&W5M{;wZ+!3ZoeCD7;qdqx-u}m=q92lF!RIV@u;Ik_;FD;)eD|V}gL#&9? zdd}p5&x+*y(!3H9LVWT*QMS$NbQgg`9n7gS5Mma`v;v&+C-<6&s}YyfH(xYf4R$j) hjodQ3iY<)x&uD0T9Bb@j;?l-M?RD1Q>*gD45f2TP?) eHc*!43oS@VEJ=k)OkSz3JNbYK+vX~hM_d3jeImI4 delta 18 acmZ40z<8%_Lx=L_H_D#`HhY*p;sO9yn+U-G diff --git a/mobile/openapi/lib/model/create_assets_share_link_dto.dart b/mobile/openapi/lib/model/create_assets_share_link_dto.dart new file mode 100644 index 0000000000000000000000000000000000000000..12fc7fb64590f815efb93e682fbbefc79e925abc GIT binary patch literal 5754 zcmeHL?Qat|82{eC;%QZpi*}^IrztEc6)h7C+KHA)Q&dIHozom#?lRjUi%S3Z{T};# z=QKGwKA=exl^}`zyubNpA06!-9nr~$tBc3Ko}Hh)Ke;+PrB|=tp2c)}LFX4&^zP#H z^{YSkLB^CXQlZS?S?}qyJ^q>96s3~mX{pB3ESG&+7E3MDvCQRM7AE$;v-zS_W+#Wp zS7NQli=`PW`PVX2U@ycR|1X5Xr;$oSxYE1Xld{l86lR>IP?$|KsVuwRsVvV5smFO) z+^NnzxP*3#%pkjuAO@#)l* zbh}4%gh0HPW~mBIf(p5!Q5*d=W!Bt8_Q2Ri6|qzzGs|=j+NF7&Y8sGrl}6Wbvn&5z zaKKJXuqN`nTzy#NrAUL-RO&=!3zL-vuikax8`0ZLn_n)C%8HvIc~y1?PGO&2`#qOJ zODal({9eH(S}4RwnKkKIxy)0ViO-TIQWhX89v90z7n5A>(`8m95^hM+3z;A{iD3Q2 zrBVq`OQ~5j;*#XcR3d<>oQh>`=(EU|lA@)CaaPDdwbMVSE$GHHD_8QfRIw$wk%d&q zR9cDG{Ya$9QOG31l7%#SJPjUOTZ-XZr@9*iCv^cN-iRiQP| z51baBxM1tHIEkUZ1giv=%av zS_`%2qYb9c$e?v8WEn zn9?W26MOmC@W5|;37(_8x_-6b^qDaZXVojOR4(@20s&Zb*99aYe z$T$T-hq2`d)p#AdzOplImFWsrR8YJRXe6fqw-zj_WrDG)$2@SyfXOCH#z54wGUc)1 z7Fwt0v9-k(dR6$&Mqh+nA`YWr1j8`DPQZ6TKUk7pB5I)7^ikwMC{JjF@T|~F#c)X& z3#sN}!I0MoKxVt(uLtx=t~Gk_e_NCtHtu-+HJ9Q zgfo%Mq6o$}kz%rnmB&ZV`wJFJ7*pqcbtz)6>7aEK9%B3YfKS7DrY&UVN}@NH7w5eW z;>?=dU!ZTqOum)ASr?9)3OiR=9kJQc#}^H+xoRGxmeMcYMct+ds{J+}qgs%kpnX5g z`~rFw5#U=*P0PxUDdO;M&R$s0gx#<m|o%H;4e~E zS4^r?Q+B%UCdTZ+wA&KEXlPio2b7_JU_@;RaK~mNBA{(VR2jLmo>npUGuIBZYB1Q# zVOuhW$CM*_uTW$gBZA``)2+pIH>zCmO%TT%?p<2OcfRu7giuoRMGt3YtCrEVs}vqK zEZV0Rkj&=3mua;B*?eJwuHtr&Di!65Tb5b9Zcc*Rw8H}G+qCz%g{*gQB&g$N@@<{V zEZn)(!?n@P$AHttoX-s=+{i%1Wl9cv2K5!!UETC|{2vtE5#IA;U0)urSbPQe0~}`G%qEgSrH1|7Zo-}MO;uT>xdC>X Tqz`ZHplhx?_U-Zc9*X`SkB3QU literal 0 HcmV?d00001 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 new file mode 100644 index 0000000000000000000000000000000000000000..fb8a4c780daaf526c556a89a6ef9e9068e5442df GIT binary patch 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 literal 0 HcmV?d00001 diff --git a/mobile/openapi/test/album_api_test.dart b/mobile/openapi/test/album_api_test.dart index f120d01bdf0a032183a86dc384c2980bdad65cc0..8d22634b42a8c16e4149fcaed28e2f31bc6173f2 100644 GIT binary patch delta 183 zcmca7I7xWJAw~`bE(IXa*PpzbaRY+$j!j{53sVQGz)oiF$p*~X2*D}L2N4`UmQn;~ r56ff($Afhef&(%+mz87kZ`MSFU?|%H1m_*wE(B*G`xFF6k7EM>6k;o# delta 91 zcmbOvd{1!0A;!rU8COo;!OAl^j*WNnBBl;FJBYb#@^$82lNYe$O_pWt0g87+<;o^M VW}69Dw}gGxWM7V6IG>4g0svjpA@l$M diff --git a/mobile/openapi/test/api_key_api_test.dart b/mobile/openapi/test/api_key_api_test.dart index 588a6f4c20ef3b9dbd1e8c374093bd0041631e5a..df8272065cba8a7a1c9d3fae2d1494222e82e2fd 100644 GIT binary patch delta 72 tcmdnY@sDG}QAQ30E(IXa*PpzPaW;Yz#FUBPY+))xaFm%V5S%H@^8tPp5Pkpv delta 34 ncmeyzv6*ATQO3zHn0O~YWNe>Y&XfdX=TA;!E(Fq2lUV8i2RIHe diff --git a/mobile/openapi/test/asset_api_test.dart b/mobile/openapi/test/asset_api_test.dart index c7f356dac3fd576b0f52f4c4c1594886e519b60e..930893b3949c4cb4ab956a1ef34fb0a03ac82ba5 100644 GIT binary patch delta 451 zcmca7cSU)_Pi97iNlcOUCnxamOn%Cl1`*iD$}u^L3u+0-#;;uM zaHX3&xD(-KOHAhAg;+TmsA+RAuN_1x1rZ1+;Z$0X0uLw8JcJ^RP$Y?ve1rrju#hz& U%K^hnN&;CD*+fK;UE=Ej0PsneLI3~& delta 82 zcmcbjd{1t}Pv*&2S!PdGVUwObi?td^_X4r_=Id;?87Cj)*g3hA^Ty-?uE~>+@k&hA m;!c}9pL^b9AD+pZ-|^%!PA+7ZpWMk;wwaILmvOSA;A8-HR3GR7 diff --git a/mobile/openapi/test/authentication_api_test.dart b/mobile/openapi/test/authentication_api_test.dart index f855d323904c9d2748bff38fe98d39229f8815c3..45f283f8a979f68ba4bc18c6d038aed7ca158848 100644 GIT binary patch delta 72 tcmeC+yv(`bE+dBmmjV#z>rcMMI0?Z?WLk*e{ANOuX=5%x$h>Es2>^{+5vu?I delta 27 jcmcc2*}=KtF5~1kjNOylm}X7ZXYQVSk~wp-Bg-TJrB@3+ diff --git a/mobile/openapi/test/create_assets_share_link_dto_test.dart b/mobile/openapi/test/create_assets_share_link_dto_test.dart new file mode 100644 index 0000000000000000000000000000000000000000..813e6bced8434a24ab801bca096307465561b5de GIT binary patch literal 943 zcmb7>K~LL25QXpl71L7^DaF94f`S^=p^B7Zsf0sSAv3kd&Wg?Mc6SVo5dXci8&V-P zDCOX_$9iwRd9!(*XjF}!Ihj2 zy}X%xkmf$@w%67KM#GhA1gKg?KH0-gm{x{DrLE~i#!EEShr_B~q7PZNnGpsK>pSe| z#5ywQR4&g!Og%XLN5T|1kH=OJp|K7iD}m3RkZBSEakvr)&6T+;*k<=uHLyZuh9h{) z0L0ZQgBnx{QxlxMY95YGGqH5u_HHX z5A@r`o1XAq%3tN1Fh8x;fLhy!Yo{$XdoeGxZvs8%X(-fQIab!}MZM7eA4)o=6+UHu E0P^ZA$N&HU literal 0 HcmV?d00001 diff --git a/mobile/openapi/test/device_info_api_test.dart b/mobile/openapi/test/device_info_api_test.dart index f7271b8dcae409de148d2d011ef17caf5acfb69b..5bfbb4c328eccb1a1499eebe65652a526f611642 100644 GIT binary patch delta 17 Ycmey*-p8>al$lXs@_Z(}$)(I)05*pOy#N3J delta 11 ScmeC<_|Lu}lzDOr^C|!usRT&? diff --git a/mobile/openapi/test/job_api_test.dart b/mobile/openapi/test/job_api_test.dart index ece52521202ec4f80be92488370a5873e4f138c7..5ff6fd7745350b4b4205f98b6ea2d147ba0cdbac 100644 GIT binary patch delta 46 jcmcc4*22DF4rdXw*oEL&Fd=bvGtB`244w-d delta 19 bcmZo+zs|N{598$1jLnl{nVKiRXPOECQCkQ` diff --git a/mobile/openapi/test/o_auth_api_test.dart b/mobile/openapi/test/o_auth_api_test.dart index bc8b5f3810ebbe2979b606ee146f8ee7006d030f..db5e6c0a21b04a31461228df0e872f1a66b1d39c 100644 GIT binary patch delta 76 ycmdnX(ZRXl5F>{ImjV#z>rdXzI0eD+U|NLWoMo1u%*3375J+dvMR3kC7XSdO(-A)a delta 31 mcmeC++{>}y5aZ;FjJ=a{nPyKGXKtCiia8BPOH4Lr2?PMd-3t@| diff --git a/mobile/openapi/test/server_info_api_test.dart b/mobile/openapi/test/server_info_api_test.dart index b662587eef233d76fa5c2111d3eba13297441e24..e2222244eb5ba7779e26a06085902eac28c74b44 100644 GIT binary patch delta 59 ocmdnW{*isdMMe$I9ANn0Kmu%+yDRo delta 23 fcmey!zLkB$MaIbw7^^2|F*Q&A!&Ef6mbnA~fK3WA diff --git a/mobile/openapi/test/share_api_test.dart b/mobile/openapi/test/share_api_test.dart index fcc988cdffc713148f678e6ec636f1b37eb9052d..3fb8f8955d51e19bf9d691746fac9cca652f69fc 100644 GIT binary patch delta 80 zcmeyzaguYxAw~`bE(IXa*Pr~JNp=RH#!GDlw#Et@+Ic%$v!N#0Q+POApigX diff --git a/mobile/openapi/test/system_config_api_test.dart b/mobile/openapi/test/system_config_api_test.dart index 6cb7aa79d1d26c710984e02ec9a34c1f138337a2..28a3b0ed26455c4c93558188ea622a27b765da58 100644 GIT binary patch delta 67 tcmaFOzMNyjbw&;aE(IXa*PncWu>_f;G&!Cr3|T;G@(HGXgg^lEYyee#5d8oE delta 35 ncmZ3^@tS?Zb;ilh7;}MGdU7pO7?2j8{FSL6NJ~s!&0GTj7Pb!q diff --git a/mobile/openapi/test/tag_api_test.dart b/mobile/openapi/test/tag_api_test.dart index a504aedfaebd7c8cc0b551e384c8ce577e33f43c..ffb6c70a492846aa9455386ff1e856da929d43f0 100644 GIT binary patch delta 80 zcmZqWxWKVt4rZB6mYaN;F#{oB!jy*KOlB%Ya2S~@kU4UbCoq=+0Lgw3 A(f|Me delta 31 mcmcb>(aN!5594GPX7S077&9hEGR03m&Xfn_7fwFH+yel;)C=kW 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 new file mode 100644 index 0000000000000000000000000000000000000000..9b0fb4d1da40e03db79e016737ed4d2b399a77c1 GIT binary patch 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 literal 0 HcmV?d00001 diff --git a/mobile/openapi/test/user_api_test.dart b/mobile/openapi/test/user_api_test.dart index 5163a150041fad11c8b5f8bd5efe16a8038cc4fa..d24f3659062ddbe80ebe5f9600bf003b9a2e96ff 100644 GIT binary patch delta 157 zcmaFL`R~aNfsnf$W{UX9?vHP delta 67 zcmey(_mp?Te#Xh?80SwGWtNycg{cTg*Gyi(+z94-u*6Q@!cq%nN3f<%KFit$W>>ML JfoXR35&%g97Ki`< 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 a2f9a7f7f1..4f2d24a7c8 100644 --- a/server/apps/immich/src/api-v1/asset/asset.controller.ts +++ b/server/apps/immich/src/api-v1/asset/asset.controller.ts @@ -14,6 +14,7 @@ import { Header, Put, UploadedFiles, + Patch, } from '@nestjs/common'; import { Authenticated } from '../../decorators/authenticated.decorator'; import { AssetService } from './asset.service'; @@ -50,6 +51,9 @@ import { IMMICH_CONTENT_LENGTH_HINT, } from '../../constants/download.constant'; import { DownloadFilesDto } from './dto/download-files.dto'; +import { CreateAssetsShareLinkDto } from './dto/create-asset-shared-link.dto'; +import { SharedLinkResponseDto } from '../share/response-dto/shared-link-response.dto'; +import { UpdateAssetsToSharedLinkDto } from './dto/add-assets-to-shared-link.dto'; @ApiBearerAuth() @ApiTags('Asset') @@ -321,4 +325,22 @@ export class AssetController { ): Promise { return await this.assetService.checkExistingAssets(authUser, checkExistingAssetsDto); } + + @Authenticated() + @Post('/shared-link') + async createAssetsSharedLink( + @GetAuthUser() authUser: AuthUserDto, + @Body(ValidationPipe) dto: CreateAssetsShareLinkDto, + ): Promise { + return await this.assetService.createAssetsSharedLink(authUser, dto); + } + + @Authenticated({ isShared: true }) + @Patch('/shared-link') + async updateAssetsInSharedLink( + @GetAuthUser() authUser: AuthUserDto, + @Body(ValidationPipe) dto: UpdateAssetsToSharedLinkDto, + ): Promise { + return await this.assetService.updateAssetsInSharedLink(authUser, dto); + } } 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 aeae60da9d..6f2138ea59 100644 --- a/server/apps/immich/src/api-v1/asset/asset.service.ts +++ b/server/apps/immich/src/api-v1/asset/asset.service.ts @@ -13,7 +13,7 @@ import { InjectRepository } from '@nestjs/typeorm'; import { createHash, randomUUID } from 'node:crypto'; import { QueryFailedError, Repository } from 'typeorm'; import { AuthUserDto } from '../../decorators/auth-user.decorator'; -import { AssetEntity, AssetType } from '@app/infra'; +import { AssetEntity, AssetType, SharedLinkType } from '@app/infra'; import { constants, createReadStream, ReadStream, stat } from 'fs'; import { ServeFileDto } from './dto/serve-file.dto'; import { Response as Res } from 'express'; @@ -59,6 +59,9 @@ import { StorageService } from '@app/storage'; import { ShareCore } from '../share/share.core'; import { ISharedLinkRepository } from '../share/shared-link.repository'; import { DownloadFilesDto } from './dto/download-files.dto'; +import { CreateAssetsShareLinkDto } from './dto/create-asset-shared-link.dto'; +import { mapSharedLinkToResponseDto, SharedLinkResponseDto } from '../share/response-dto/shared-link-response.dto'; +import { UpdateAssetsToSharedLinkDto } from './dto/add-assets-to-shared-link.dto'; const fileInfo = promisify(stat); @@ -699,6 +702,42 @@ export class AssetService { throw new ForbiddenException(); } } + + async createAssetsSharedLink(authUser: AuthUserDto, dto: CreateAssetsShareLinkDto): Promise { + 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 sharedLink = await this.shareCore.createSharedLink(authUser.id, { + sharedType: SharedLinkType.INDIVIDUAL, + expiredAt: dto.expiredAt, + allowUpload: dto.allowUpload, + assets: assets, + description: dto.description, + }); + + return mapSharedLinkToResponseDto(sharedLink); + } + + async updateAssetsInSharedLink( + authUser: AuthUserDto, + dto: UpdateAssetsToSharedLinkDto, + ): 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.updateAssetsInSharedLink(authUser.sharedLinkId, assets); + return mapSharedLinkToResponseDto(updatedLink); + } } async function processETag(path: string, res: Res, headers: Record): Promise { 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 new file mode 100644 index 0000000000..2eb451d3d3 --- /dev/null +++ b/server/apps/immich/src/api-v1/asset/dto/add-assets-to-shared-link.dto.ts @@ -0,0 +1,6 @@ +import { IsNotEmpty } from 'class-validator'; + +export class UpdateAssetsToSharedLinkDto { + @IsNotEmpty() + assetIds!: string[]; +} diff --git a/server/apps/immich/src/api-v1/asset/dto/create-asset-shared-link.dto.ts b/server/apps/immich/src/api-v1/asset/dto/create-asset-shared-link.dto.ts new file mode 100644 index 0000000000..7a2f72be97 --- /dev/null +++ b/server/apps/immich/src/api-v1/asset/dto/create-asset-shared-link.dto.ts @@ -0,0 +1,31 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsArray, IsBoolean, IsNotEmpty, IsOptional, IsString } from 'class-validator'; + +export class CreateAssetsShareLinkDto { + @IsArray() + @IsString({ each: true }) + @IsNotEmpty({ each: true }) + @ApiProperty({ + isArray: true, + type: String, + title: 'Array asset IDs to be shared', + example: [ + 'bf973405-3f2a-48d2-a687-2ed4167164be', + 'dd41870b-5d00-46d2-924e-1d8489a0aa0f', + 'fad77c3f-deef-4e7e-9608-14c1aa4e559a', + ], + }) + assetIds!: string[]; + + @IsString() + @IsOptional() + expiredAt?: string; + + @IsBoolean() + @IsOptional() + allowUpload?: boolean; + + @IsString() + @IsOptional() + description?: string; +} diff --git a/server/immich-openapi-specs.json b/server/immich-openapi-specs.json index cf77df1ca4..82f2a501df 100644 --- a/server/immich-openapi-specs.json +++ b/server/immich-openapi-specs.json @@ -1258,6 +1258,78 @@ ] } }, + "/asset/shared-link": { + "post": { + "operationId": "createAssetsSharedLink", + "description": "", + "parameters": [], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateAssetsShareLinkDto" + } + } + } + }, + "responses": { + "201": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SharedLinkResponseDto" + } + } + } + } + }, + "tags": [ + "Asset" + ], + "security": [ + { + "bearer": [] + } + ] + }, + "patch": { + "operationId": "updateAssetsInSharedLink", + "description": "", + "parameters": [], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateAssetsToSharedLinkDto" + } + } + } + }, + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SharedLinkResponseDto" + } + } + } + } + }, + "tags": [ + "Asset" + ], + "security": [ + { + "bearer": [] + } + ] + } + }, "/share": { "get": { "operationId": "getAllSharedLinks", @@ -3548,6 +3620,35 @@ "existingIds" ] }, + "CreateAssetsShareLinkDto": { + "type": "object", + "properties": { + "assetIds": { + "title": "Array asset IDs to be shared", + "example": [ + "bf973405-3f2a-48d2-a687-2ed4167164be", + "dd41870b-5d00-46d2-924e-1d8489a0aa0f", + "fad77c3f-deef-4e7e-9608-14c1aa4e559a" + ], + "type": "array", + "items": { + "type": "string" + } + }, + "expiredAt": { + "type": "string" + }, + "allowUpload": { + "type": "boolean" + }, + "description": { + "type": "string" + } + }, + "required": [ + "assetIds" + ] + }, "SharedLinkType": { "type": "string", "enum": [ @@ -3654,6 +3755,20 @@ "allowUpload" ] }, + "UpdateAssetsToSharedLinkDto": { + "type": "object", + "properties": { + "assetIds": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "assetIds" + ] + }, "EditSharedLinkDto": { "type": "object", "properties": { diff --git a/web/src/api/open-api/api.ts b/web/src/api/open-api/api.ts index 4627973a43..5af6cb396f 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.40.0 + * The version of the OpenAPI document: 1.41.1 * * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). @@ -702,6 +702,37 @@ export interface CreateAlbumShareLinkDto { */ 'description'?: string; } +/** + * + * @export + * @interface CreateAssetsShareLinkDto + */ +export interface CreateAssetsShareLinkDto { + /** + * + * @type {Array} + * @memberof CreateAssetsShareLinkDto + */ + 'assetIds': Array; + /** + * + * @type {string} + * @memberof CreateAssetsShareLinkDto + */ + 'expiredAt'?: string; + /** + * + * @type {boolean} + * @memberof CreateAssetsShareLinkDto + */ + 'allowUpload'?: boolean; + /** + * + * @type {string} + * @memberof CreateAssetsShareLinkDto + */ + 'description'?: string; +} /** * * @export @@ -2029,6 +2060,19 @@ export interface UpdateAssetDto { */ 'isFavorite'?: boolean; } +/** + * + * @export + * @interface UpdateAssetsToSharedLinkDto + */ +export interface UpdateAssetsToSharedLinkDto { + /** + * + * @type {Array} + * @memberof UpdateAssetsToSharedLinkDto + */ + 'assetIds': Array; +} /** * * @export @@ -3599,6 +3643,45 @@ export const AssetApiAxiosParamCreator = function (configuration?: Configuration options: localVarRequestOptions, }; }, + /** + * + * @param {CreateAssetsShareLinkDto} createAssetsShareLinkDto + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + createAssetsSharedLink: async (createAssetsShareLinkDto: CreateAssetsShareLinkDto, options: AxiosRequestConfig = {}): Promise => { + // verify required parameter 'createAssetsShareLinkDto' is not null or undefined + assertParamExists('createAssetsSharedLink', 'createAssetsShareLinkDto', createAssetsShareLinkDto) + 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: 'POST', ...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(createAssetsShareLinkDto, localVarRequestOptions, configuration) + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, /** * * @param {DeleteAssetDto} deleteAssetDto @@ -4255,6 +4338,45 @@ 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 {any} assetData @@ -4329,6 +4451,16 @@ export const AssetApiFp = function(configuration?: Configuration) { const localVarAxiosArgs = await localVarAxiosParamCreator.checkExistingAssets(checkExistingAssetsDto, options); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); }, + /** + * + * @param {CreateAssetsShareLinkDto} createAssetsShareLinkDto + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async createAssetsSharedLink(createAssetsShareLinkDto: CreateAssetsShareLinkDto, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.createAssetsSharedLink(createAssetsShareLinkDto, options); + return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); + }, /** * * @param {DeleteAssetDto} deleteAssetDto @@ -4501,6 +4633,16 @@ 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 {any} assetData @@ -4539,6 +4681,15 @@ export const AssetApiFactory = function (configuration?: Configuration, basePath checkExistingAssets(checkExistingAssetsDto: CheckExistingAssetsDto, options?: any): AxiosPromise { return localVarFp.checkExistingAssets(checkExistingAssetsDto, options).then((request) => request(axios, basePath)); }, + /** + * + * @param {CreateAssetsShareLinkDto} createAssetsShareLinkDto + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + createAssetsSharedLink(createAssetsShareLinkDto: CreateAssetsShareLinkDto, options?: any): AxiosPromise { + return localVarFp.createAssetsSharedLink(createAssetsShareLinkDto, options).then((request) => request(axios, basePath)); + }, /** * * @param {DeleteAssetDto} deleteAssetDto @@ -4694,6 +4845,15 @@ 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 {any} assetData @@ -4735,6 +4895,17 @@ export class AssetApi extends BaseAPI { return AssetApiFp(this.configuration).checkExistingAssets(checkExistingAssetsDto, options).then((request) => request(this.axios, this.basePath)); } + /** + * + * @param {CreateAssetsShareLinkDto} createAssetsShareLinkDto + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof AssetApi + */ + public createAssetsSharedLink(createAssetsShareLinkDto: CreateAssetsShareLinkDto, options?: AxiosRequestConfig) { + return AssetApiFp(this.configuration).createAssetsSharedLink(createAssetsShareLinkDto, options).then((request) => request(this.axios, this.basePath)); + } + /** * * @param {DeleteAssetDto} deleteAssetDto @@ -4924,6 +5095,17 @@ 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 {any} assetData @@ -5300,6 +5482,7 @@ export const DeviceInfoApiAxiosParamCreator = function (configuration?: Configur * @deprecated * @param {UpsertDeviceInfoDto} upsertDeviceInfoDto * @param {*} [options] Override http request option. + * @deprecated * @throws {RequiredError} */ createDeviceInfo: async (upsertDeviceInfoDto: UpsertDeviceInfoDto, options: AxiosRequestConfig = {}): Promise => { @@ -5339,6 +5522,7 @@ export const DeviceInfoApiAxiosParamCreator = function (configuration?: Configur * @deprecated * @param {UpsertDeviceInfoDto} upsertDeviceInfoDto * @param {*} [options] Override http request option. + * @deprecated * @throws {RequiredError} */ updateDeviceInfo: async (upsertDeviceInfoDto: UpsertDeviceInfoDto, options: AxiosRequestConfig = {}): Promise => { @@ -5427,6 +5611,7 @@ export const DeviceInfoApiFp = function(configuration?: Configuration) { * @deprecated * @param {UpsertDeviceInfoDto} upsertDeviceInfoDto * @param {*} [options] Override http request option. + * @deprecated * @throws {RequiredError} */ async createDeviceInfo(upsertDeviceInfoDto: UpsertDeviceInfoDto, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { @@ -5437,6 +5622,7 @@ export const DeviceInfoApiFp = function(configuration?: Configuration) { * @deprecated * @param {UpsertDeviceInfoDto} upsertDeviceInfoDto * @param {*} [options] Override http request option. + * @deprecated * @throws {RequiredError} */ async updateDeviceInfo(upsertDeviceInfoDto: UpsertDeviceInfoDto, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { @@ -5467,6 +5653,7 @@ export const DeviceInfoApiFactory = function (configuration?: Configuration, bas * @deprecated * @param {UpsertDeviceInfoDto} upsertDeviceInfoDto * @param {*} [options] Override http request option. + * @deprecated * @throws {RequiredError} */ createDeviceInfo(upsertDeviceInfoDto: UpsertDeviceInfoDto, options?: any): AxiosPromise { @@ -5476,6 +5663,7 @@ export const DeviceInfoApiFactory = function (configuration?: Configuration, bas * @deprecated * @param {UpsertDeviceInfoDto} upsertDeviceInfoDto * @param {*} [options] Override http request option. + * @deprecated * @throws {RequiredError} */ updateDeviceInfo(upsertDeviceInfoDto: UpsertDeviceInfoDto, options?: any): AxiosPromise { @@ -5504,6 +5692,7 @@ export class DeviceInfoApi extends BaseAPI { * @deprecated * @param {UpsertDeviceInfoDto} upsertDeviceInfoDto * @param {*} [options] Override http request option. + * @deprecated * @throws {RequiredError} * @memberof DeviceInfoApi */ @@ -5515,6 +5704,7 @@ export class DeviceInfoApi extends BaseAPI { * @deprecated * @param {UpsertDeviceInfoDto} upsertDeviceInfoDto * @param {*} [options] Override http request option. + * @deprecated * @throws {RequiredError} * @memberof DeviceInfoApi */ diff --git a/web/src/api/open-api/base.ts b/web/src/api/open-api/base.ts index 4d4ced1519..2d8f7a0a43 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.40.0 + * The version of the OpenAPI document: 1.41.1 * * * 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 d51fd5cd23..acd18acb28 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.40.0 + * The version of the OpenAPI document: 1.41.1 * * * 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 14b5a1c7a9..65556da1f2 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.40.0 + * The version of the OpenAPI document: 1.41.1 * * * 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 0ce2e4a1fd..ea27b5ef1e 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.40.0 + * The version of the OpenAPI document: 1.41.1 * * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/web/src/lib/components/album-page/album-viewer.svelte b/web/src/lib/components/album-page/album-viewer.svelte index 2b78aa6f13..501d68d5a7 100644 --- a/web/src/lib/components/album-page/album-viewer.svelte +++ b/web/src/lib/components/album-page/album-viewer.svelte @@ -1,13 +1,11 @@ + +
+ {#if isMultiSelectionMode} + + +

+ Selected {selectedAssets.size} +

+
+ + downloadAssets(false)} + logo={CloudDownloadOutline} + /> + {#if isOwned} + + {/if} + +
+ {:else} + goto('/photos')} + backIcon={ArrowLeft} + showBackButton={false} + > + + + immich logo +

+ IMMICH +

+
+
+ + + {#if sharedLink?.allowUpload} + + {/if} + + downloadAssets(true)} + logo={FolderDownloadOutline} + /> + +
+ {/if} +
+ +
+
diff --git a/web/src/lib/components/shared-components/create-share-link-modal/create-shared-link-modal.svelte b/web/src/lib/components/shared-components/create-share-link-modal/create-shared-link-modal.svelte index c98d652447..5b3eabb6dc 100644 --- a/web/src/lib/components/shared-components/create-share-link-modal/create-shared-link-modal.svelte +++ b/web/src/lib/components/shared-components/create-share-link-modal/create-shared-link-modal.svelte @@ -2,7 +2,13 @@ import { createEventDispatcher, onMount } from 'svelte'; import BaseModal from '../base-modal.svelte'; import Link from 'svelte-material-icons/Link.svelte'; - import { AlbumResponseDto, api, SharedLinkResponseDto, SharedLinkType } from '@api'; + import { + AlbumResponseDto, + api, + AssetResponseDto, + SharedLinkResponseDto, + SharedLinkType + } from '@api'; import { notificationController, NotificationType } from '../notification/notification'; import { ImmichDropDownOption } from '../dropdown-button.svelte'; import SettingSwitch from '$lib/components/admin-page/settings/setting-switch.svelte'; @@ -10,9 +16,11 @@ import SettingInputField, { SettingInputFieldType } from '$lib/components/admin-page/settings/setting-input-field.svelte'; + import { handleError } from '$lib/utils/handle-error'; export let shareType: SharedLinkType; - export let album: AlbumResponseDto | undefined; + export let sharedAssets: AssetResponseDto[] = []; + export let album: AlbumResponseDto | undefined = undefined; export let editingLink: SharedLinkResponseDto | undefined = undefined; let isShowSharedLink = false; @@ -37,32 +45,36 @@ } }); - const createAlbumSharedLink = async () => { - if (album) { - try { - const expirationTime = getExpirationTimeInMillisecond(); - const currentTime = new Date().getTime(); - const expirationDate = expirationTime - ? new Date(currentTime + expirationTime).toISOString() - : undefined; + const handleCreateSharedLink = async () => { + const expirationTime = getExpirationTimeInMillisecond(); + const currentTime = new Date().getTime(); + const expirationDate = expirationTime + ? new Date(currentTime + expirationTime).toISOString() + : undefined; + try { + if (shareType === SharedLinkType.Album && album) { const { data } = await api.albumApi.createAlbumSharedLink({ albumId: album.id, expiredAt: expirationDate, allowUpload: isAllowUpload, description: description }); - buildSharedLink(data); - isShowSharedLink = true; - } catch (e) { - console.error('[createAlbumSharedLink] Error: ', e); - notificationController.show({ - type: NotificationType.Error, - message: 'Failed to create shared link' + } else { + const { data } = await api.assetApi.createAssetsSharedLink({ + assetIds: sharedAssets.map((a) => a.id), + expiredAt: expirationDate, + allowUpload: isAllowUpload, + description: description }); + buildSharedLink(data); } + } catch (e) { + handleError(e, 'Failed to create shared link'); } + + isShowSharedLink = true; }; const buildSharedLink = (createdLink: SharedLinkResponseDto) => { @@ -76,8 +88,11 @@ message: 'Copied to clipboard!', type: NotificationType.Info }); - } catch (error) { - console.error('Error', error); + } catch (e) { + handleError( + e, + 'Cannot copy to clipboard, make sure you are accessing the page through https' + ); } }; @@ -127,11 +142,7 @@ dispatch('close'); } catch (e) { - console.error('[handleEditLink]', e); - notificationController.show({ - type: NotificationType.Error, - message: 'Failed to edit shared link' - }); + handleError(e, 'Failed to edit shared link'); } } }; @@ -162,6 +173,18 @@ {/if} {/if} + {#if shareType == SharedLinkType.Individual} + {#if !editingLink} +
Let anyone with the link see the selected photo(s)
+ {:else} +
+ Individual shared | {editingLink.description} +
+ {/if} + {/if} +

LINK OPTIONS

@@ -215,7 +238,7 @@ {:else}
-

+

{@html notificationInfo.message}

diff --git a/web/src/lib/utils/file-uploader.ts b/web/src/lib/utils/file-uploader.ts index 671534f257..5c6949c224 100644 --- a/web/src/lib/utils/file-uploader.ts +++ b/web/src/lib/utils/file-uploader.ts @@ -12,7 +12,7 @@ import { addAssetsToAlbum } from '$lib/utils/asset-utils'; export const openFileUploadDialog = ( albumId: string | undefined = undefined, sharedKey: string | undefined = undefined, - callback?: () => void + onDone?: (id: string) => void ) => { try { const fileSelector = document.createElement('input'); @@ -28,8 +28,7 @@ export const openFileUploadDialog = ( } const files = Array.from(target.files); - await fileUploadHandler(files, albumId, sharedKey); - callback && callback(); + await fileUploadHandler(files, albumId, sharedKey, onDone); }; fileSelector.click(); @@ -41,7 +40,8 @@ export const openFileUploadDialog = ( export const fileUploadHandler = async ( files: File[], albumId: string | undefined = undefined, - sharedKey: string | undefined = undefined + sharedKey: string | undefined = undefined, + onDone?: (id: string) => void ) => { if (files.length > 50) { notificationController.show({ @@ -54,13 +54,13 @@ export const fileUploadHandler = async ( return; } - console.log('fileUploadHandler'); + const acceptedFile = files.filter( (e) => e.type.split('/')[0] === 'video' || e.type.split('/')[0] === 'image' ); for (const asset of acceptedFile) { - await fileUploader(asset, albumId, sharedKey); + await fileUploader(asset, albumId, sharedKey, onDone); } }; @@ -68,7 +68,8 @@ export const fileUploadHandler = async ( async function fileUploader( asset: File, albumId: string | undefined = undefined, - sharedKey: string | undefined = undefined + sharedKey: string | undefined = undefined, + onDone?: (id: string) => void ) { const assetType = asset.type.split('/')[0].toUpperCase(); const temp = asset.name.split('.'); @@ -135,6 +136,7 @@ async function fileUploader( if (albumId && dataId) { addAssetsToAlbum(albumId, [dataId]); } + onDone && dataId && onDone(dataId); return; } } @@ -154,10 +156,9 @@ async function fileUploader( request.upload.onload = () => { setTimeout(() => { uploadAssetsStore.removeUploadAsset(deviceAssetId); - + const res: AssetFileUploadResponseDto = JSON.parse(request.response || '{}'); if (albumId) { try { - const res: AssetFileUploadResponseDto = JSON.parse(request.response || '{}'); if (res.id) { addAssetsToAlbum(albumId, [res.id], sharedKey); } @@ -165,6 +166,7 @@ async function fileUploader( console.error('ERROR parsing data JSON in upload onload'); } } + onDone && onDone(res.id); }, 1000); }; diff --git a/web/src/routes/photos/+page.svelte b/web/src/routes/photos/+page.svelte index dccba87cf1..43208f38ea 100644 --- a/web/src/routes/photos/+page.svelte +++ b/web/src/routes/photos/+page.svelte @@ -6,9 +6,8 @@ import MenuOption from '$lib/components/shared-components/context-menu/menu-option.svelte'; import AlbumSelectionModal from '$lib/components/shared-components/album-selection-modal.svelte'; import { goto } from '$app/navigation'; - import type { PageData } from './$types'; - + import ShareVariantOutline from 'svelte-material-icons/ShareVariantOutline.svelte'; import { openFileUploadDialog } from '$lib/utils/file-uploader'; import { assetInteractionStore, @@ -21,16 +20,17 @@ import CircleIconButton from '$lib/components/shared-components/circle-icon-button.svelte'; import DeleteOutline from 'svelte-material-icons/DeleteOutline.svelte'; import Plus from 'svelte-material-icons/Plus.svelte'; - import { AlbumResponseDto, api } from '@api'; + import { AlbumResponseDto, api, SharedLinkType } from '@api'; import { notificationController, NotificationType } from '$lib/components/shared-components/notification/notification'; import { assetStore } from '$lib/stores/assets.store'; import { addAssetsToAlbum, bulkDownload } from '$lib/utils/asset-utils'; + import CreateSharedLinkModal from '$lib/components/shared-components/create-share-link-modal/create-shared-link-modal.svelte'; export let data: PageData; - + let isShowCreateSharedLinkModal = false; const deleteSelectedAssetHandler = async () => { try { if ( @@ -114,6 +114,15 @@ assetInteractionStore.clearMultiselect(); }); }; + + const handleCreateSharedLink = async () => { + isShowCreateSharedLinkModal = true; + }; + + const handleCloseSharedLinkModal = () => { + assetInteractionStore.clearMultiselect(); + isShowCreateSharedLinkModal = false; + };
@@ -129,6 +138,11 @@

+ (isShowAlbumPicker = false)} /> {/if} + + {#if isShowCreateSharedLinkModal} + + {/if}
{ +export const load: PageServerLoad = async ({ params, parent }) => { + const { user } = await parent(); + const { key } = params; try { @@ -22,7 +24,8 @@ export const load: PageServerLoad = async ({ params }) => { imageUrl: assetId ? getThumbnailUrl(assetId, ThumbnailFormat.Webp, sharedLink.key) : 'feature-panel.png' - } + }, + user }; } catch (e) { throw error(404, { diff --git a/web/src/routes/share/[key]/+page.svelte b/web/src/routes/share/[key]/+page.svelte index d436e61b90..554216efff 100644 --- a/web/src/routes/share/[key]/+page.svelte +++ b/web/src/routes/share/[key]/+page.svelte @@ -1,6 +1,7 @@ -{#if album} +{#if sharedLink.type == SharedLinkType.Album && album}
{/if} + +{#if sharedLink.type == SharedLinkType.Individual} +
+ +
+{/if}