From 656dc0840686ded138871d63cd969824bf552fec Mon Sep 17 00:00:00 2001 From: Jason Rasmussen Date: Wed, 31 May 2023 21:51:28 -0400 Subject: [PATCH] refactor(server): tags (#2589) * refactor: tags * chore: open api * chore: unused import * feat: add/remove/get tag assets * chore: open api * chore: finish tag tests for add/remove assets --- mobile/openapi/.openapi-generator/FILES | 6 + mobile/openapi/README.md | Bin 17241 -> 17625 bytes mobile/openapi/doc/AssetIdsDto.md | Bin 0 -> 436 bytes mobile/openapi/doc/AssetIdsResponseDto.md | Bin 0 -> 485 bytes mobile/openapi/doc/TagApi.md | Bin 9926 -> 16303 bytes mobile/openapi/doc/TagResponseDto.md | Bin 561 -> 515 bytes mobile/openapi/doc/UpdateTagDto.md | Bin 463 -> 417 bytes mobile/openapi/lib/api.dart | Bin 5421 -> 5496 bytes mobile/openapi/lib/api/tag_api.dart | Bin 7636 -> 12972 bytes mobile/openapi/lib/api_client.dart | Bin 17414 -> 17578 bytes mobile/openapi/lib/model/asset_ids_dto.dart | Bin 0 -> 3318 bytes .../lib/model/asset_ids_response_dto.dart | Bin 0 -> 6828 bytes .../openapi/lib/model/tag_response_dto.dart | Bin 4206 -> 3830 bytes mobile/openapi/lib/model/update_tag_dto.dart | Bin 4339 -> 3614 bytes mobile/openapi/test/asset_ids_dto_test.dart | Bin 0 -> 589 bytes .../test/asset_ids_response_dto_test.dart | Bin 0 -> 771 bytes mobile/openapi/test/tag_api_test.dart | Bin 1029 -> 1500 bytes .../openapi/test/tag_response_dto_test.dart | Bin 949 -> 842 bytes mobile/openapi/test/update_tag_dto_test.dart | Bin 659 -> 552 bytes .../src/api-v1/asset/asset-repository.ts | 8 +- .../immich/src/api-v1/asset/asset.module.ts | 2 - .../src/api-v1/tag/dto/update-tag.dto.ts | 11 - .../immich/src/api-v1/tag/tag.controller.ts | 49 -- .../apps/immich/src/api-v1/tag/tag.module.ts | 18 - .../immich/src/api-v1/tag/tag.repository.ts | 61 -- .../immich/src/api-v1/tag/tag.service.spec.ts | 94 --- .../apps/immich/src/api-v1/tag/tag.service.ts | 52 -- server/apps/immich/src/app.module.ts | 5 +- server/apps/immich/src/controllers/index.ts | 1 + .../immich/src/controllers/tag.controller.ts | 75 ++ server/immich-openapi-specs.json | 650 +++++++++++------- .../domain/src/asset/dto/asset-ids.dto.ts | 6 + server/libs/domain/src/asset/dto/index.ts | 2 + .../domain/src/asset/dto/map-marker.dto.ts | 2 +- server/libs/domain/src/asset/index.ts | 1 + .../response-dto/asset-ids-response.dto.ts | 11 + .../domain/src/asset/response-dto/index.ts | 3 +- server/libs/domain/src/domain.module.ts | 2 + server/libs/domain/src/tag/index.ts | 5 +- .../libs/domain/src/tag/response-dto/index.ts | 1 - .../{response-dto => }/tag-response.dto.ts | 7 - .../domain/src/tag/tag.dto.ts} | 8 +- server/libs/domain/src/tag/tag.repository.ts | 16 + .../libs/domain/src/tag/tag.service.spec.ts | 178 +++++ server/libs/domain/src/tag/tag.service.ts | 104 +++ server/libs/domain/test/fixtures.ts | 30 +- server/libs/domain/test/index.ts | 1 + .../libs/domain/test/tag.repository.mock.ts | 16 + server/libs/infra/src/entities/index.ts | 2 + server/libs/infra/src/entities/tag.entity.ts | 2 +- server/libs/infra/src/infra.module.ts | 3 + server/libs/infra/src/repositories/index.ts | 1 + .../infra/src/repositories/tag.repository.ts | 123 ++++ web/src/api/open-api/api.ts | 637 ++++++++++++----- 54 files changed, 1491 insertions(+), 702 deletions(-) create mode 100644 mobile/openapi/doc/AssetIdsDto.md create mode 100644 mobile/openapi/doc/AssetIdsResponseDto.md create mode 100644 mobile/openapi/lib/model/asset_ids_dto.dart create mode 100644 mobile/openapi/lib/model/asset_ids_response_dto.dart create mode 100644 mobile/openapi/test/asset_ids_dto_test.dart create mode 100644 mobile/openapi/test/asset_ids_response_dto_test.dart delete mode 100644 server/apps/immich/src/api-v1/tag/dto/update-tag.dto.ts delete mode 100644 server/apps/immich/src/api-v1/tag/tag.controller.ts delete mode 100644 server/apps/immich/src/api-v1/tag/tag.module.ts delete mode 100644 server/apps/immich/src/api-v1/tag/tag.repository.ts delete mode 100644 server/apps/immich/src/api-v1/tag/tag.service.spec.ts delete mode 100644 server/apps/immich/src/api-v1/tag/tag.service.ts create mode 100644 server/apps/immich/src/controllers/tag.controller.ts create mode 100644 server/libs/domain/src/asset/dto/asset-ids.dto.ts create mode 100644 server/libs/domain/src/asset/dto/index.ts create mode 100644 server/libs/domain/src/asset/response-dto/asset-ids-response.dto.ts delete mode 100644 server/libs/domain/src/tag/response-dto/index.ts rename server/libs/domain/src/tag/{response-dto => }/tag-response.dto.ts (83%) rename server/{apps/immich/src/api-v1/tag/dto/create-tag.dto.ts => libs/domain/src/tag/tag.dto.ts} (64%) create mode 100644 server/libs/domain/src/tag/tag.repository.ts create mode 100644 server/libs/domain/src/tag/tag.service.spec.ts create mode 100644 server/libs/domain/src/tag/tag.service.ts create mode 100644 server/libs/domain/test/tag.repository.mock.ts create mode 100644 server/libs/infra/src/repositories/tag.repository.ts diff --git a/mobile/openapi/.openapi-generator/FILES b/mobile/openapi/.openapi-generator/FILES index eafeb6851..9335f934b 100644 --- a/mobile/openapi/.openapi-generator/FILES +++ b/mobile/openapi/.openapi-generator/FILES @@ -25,6 +25,8 @@ doc/AssetCountByTimeBucket.md doc/AssetCountByTimeBucketResponseDto.md doc/AssetCountByUserIdResponseDto.md doc/AssetFileUploadResponseDto.md +doc/AssetIdsDto.md +doc/AssetIdsResponseDto.md doc/AssetResponseDto.md doc/AssetTypeEnum.md doc/AuthDeviceResponseDto.md @@ -154,6 +156,8 @@ lib/model/asset_count_by_time_bucket.dart lib/model/asset_count_by_time_bucket_response_dto.dart lib/model/asset_count_by_user_id_response_dto.dart lib/model/asset_file_upload_response_dto.dart +lib/model/asset_ids_dto.dart +lib/model/asset_ids_response_dto.dart lib/model/asset_response_dto.dart lib/model/asset_type_enum.dart lib/model/auth_device_response_dto.dart @@ -252,6 +256,8 @@ test/asset_count_by_time_bucket_response_dto_test.dart test/asset_count_by_time_bucket_test.dart test/asset_count_by_user_id_response_dto_test.dart test/asset_file_upload_response_dto_test.dart +test/asset_ids_dto_test.dart +test/asset_ids_response_dto_test.dart test/asset_response_dto_test.dart test/asset_type_enum_test.dart test/auth_device_response_dto_test.dart diff --git a/mobile/openapi/README.md b/mobile/openapi/README.md index c0b8511265b1c0ecaa9503d6272571f063979e4b..99d6f2da260bf3393eff1adc23aefb69722fe066 100644 GIT binary patch delta 386 zcmccF#(1-naf6d4b4X(PWI-)q&g7!h#FEq!ATL@IA)1n!lL`@?tEt76o?7CVlLORI z4AKJPCg$V-wG>Z&p{c|JiJD;UZ7ycEZr9FQh<5IYgb{-vn|X8Y%*!o~A}>^WNMlMA%jfMO!71t~!DH&4}8=GZJ^ Ha#R2SC^#tr diff --git a/mobile/openapi/doc/AssetIdsDto.md b/mobile/openapi/doc/AssetIdsDto.md new file mode 100644 index 0000000000000000000000000000000000000000..002c3ee5e63b9fb04c5b2e0a37668fae6d191098 GIT binary patch literal 436 zcma)2!D_=W488j+1UYmyIK6jckdYn=Z7JJsArKR*ooQkjTRjZ+@w2nQx?MJ3jGo?m zl3oGi(P?kXkqi!H6(jQ2#F|NnqJSM~1N??CwZZHDqR%M0t}{BYu%oBIu$b}nY_`gV znXtIe)XC`>QZul{C=Pl=_<+}UpT8PoAFO9fE>#EO#85Xcfj5;>f7L=tJzpVZB1^fm zksn?-Sa*J$poBICPx?AAFhNf)GBj-~MAJN_6EnEadBBjQZF^UitLIHs?>FUW3Pb&M nM8`U>tWFfv6i!^ix4JrR|EWz;DRjn@b^OEPYv8ZpOCiK3B&df* literal 0 HcmV?d00001 diff --git a/mobile/openapi/doc/AssetIdsResponseDto.md b/mobile/openapi/doc/AssetIdsResponseDto.md new file mode 100644 index 0000000000000000000000000000000000000000..4aaebf73a79f556f052852841b8bc5f4985cac80 GIT binary patch literal 485 zcma)2!D_=W488j+1UYmyIK6MDjPy`gOX+qCfq1djnI)F7)x%&PKRXR98-vXk^Yosc zq*nlWblTf;B!fd)#fZE!@r2Qni&!%WQ55h%+5o>IOfvAgf753aUDp{MSlH21U|7xd z+u3xJjk92Nm!*@_F(fju#V8JXMEHQGcb~r+V;`(%OD>@UaZ;$oOT`6J>Mty$)YogI zOj;=)Iaqg|K*EWRm%hgsXZo3FSdbV3gQ^_LMH>wKGfp`fM|;~KuYtK;sUy(v?L&Umtpzgb)d{usU%LVN;~6_o7& literal 0 HcmV?d00001 diff --git a/mobile/openapi/doc/TagApi.md b/mobile/openapi/doc/TagApi.md index 3dd11aab6b1f1cf403b4a766abe79d2c5f80ba8d..7c5b3d9a1ce89a25f8452d582ccbf358eb0eaac7 100644 GIT binary patch delta 1396 zcmcgr&ui2`6eeldb+>li!!FjC#?h=aJ8FY?P-tD)sz^XZWyOQq63q|-v0Y3Ai!J*f zl**hu=-pG5-1SmC3Eoxk4-mY06nrz;O(xmZlk}9lU*G%Q_vU@PzB}0YqV^mQ;|C^u zx_y7)v1>efLY#<1j`!lHJ|1;RKzNiQ>Da&{Q8x&nHq2vy-w6W12-|2AVSM#U4`XD3 z1I+3@j3EK4x?xD7@lr^0c%)ctJ@?%$D~U0nlPS{jhV70~0Gx*#zDu_y&4d3*-3qO4 z-Wl;Mwt`Mi*Zu;+0|_foW%jVZkd^ga(6i@w^sUsG0tmV%%}+7=hoqL_Yl7nOZyhW2 zTxoUWpr4Gpm1Gqxb)|`@UnB9e@&UO44vecElvxeJv!HqxB9F z^8|J_?U%HJVvy?Et-K={b^8QJHyNDmnRkqr=3Yvt613z203jG(U@|1aqD2~PHb`b6 zMnW`)6ynz0JE;?8FDEyz?S?|54odT&(P_k=1Ej=<|^W7|X^pwJ;zEolgs-lpGcvt~}_!c1aK z{H^?2TF%|TKjjcI|BoP+n%!Rxaj3|&&~ZOCsbmNKRaeMhM^7D#QT}tv5ATHeepQ)E+azA_G`=E&ULz<4V{g77=*+?amC%?xQO6G^1JtIl zd79W4MkSCe*u|I*fl2}GN@tlIqa(uw70}2`ftv-?3N&luB)j924+uz4UdMR`=EdFS WQk!p>pJ$o8$_Aul&gONtj7$LC=#BRP diff --git a/mobile/openapi/doc/TagResponseDto.md b/mobile/openapi/doc/TagResponseDto.md index 31805c2a8141b1e69c1302efa94ca06552616451..366e95033747de5e7e42dc98168ed1de813157b1 100644 GIT binary patch delta 35 rcmdnU(#$g9=ETp&f|)5=S_(A^T3W#+MVWc&T3VB3867A0FfIiE<}nN* delta 66 zcmZo>*~l{C=42j5sfjm@C#x}9=xAvbrRF8(riLV@d!}e77PFY diff --git a/mobile/openapi/doc/UpdateTagDto.md b/mobile/openapi/doc/UpdateTagDto.md index ad4e551be6bb567a0a664555ba9266003fd12d2b..42aae30b5c2b6cdef21f8deca92311ca09744d6d 100644 GIT binary patch delta 10 RcmX@lypVaqpUGN`O92@b1ZDsL delta 23 fcmZ3;e4cs2A6_l3qSU;^+|-c7bkCHDw~qk;ZN3TD diff --git a/mobile/openapi/lib/api.dart b/mobile/openapi/lib/api.dart index 833628d62e01cbd45031d5c7b4c50fb9c7176c90..20f225513faebc362cd127e4c35515b24e0c77c8 100644 GIT binary patch delta 35 pcmZ3h^+RjJb#~^=l;X*O{1TJ@v#~K{rc4&(R01(KUuO^F1pxI745R=6 delta 12 TcmeyNwN`7xb@t6l9AUfwCK&|C diff --git a/mobile/openapi/lib/api/tag_api.dart b/mobile/openapi/lib/api/tag_api.dart index 3591cf11cb0f1f767ca8a62909db8d081311d7ce..e420792ca4ebcdcb3699d85e6262b3abd4bb7305 100644 GIT binary patch delta 960 zcmca&y(V>o2NQEhV)|wersZJ9WRZEH1GDO9rJD7v$#^r@EBn+u7SGplE|S4Am@A?j@T&M7h0GKk>@E?P)>MM44$myf@F*!M@XSlg*8pjOxoLBP^doR^bHKa<3ULI7O)z}& zHFg2Ge;`Q!e@Llt<-si`JE%TS{=<0zZ-hv?;|r)#gd2zmDlKJ%a*D#rT6`ZUpMku+ USwP|`Bols>3Iu09H|ahm0EHw|m;e9( delta 188 zcmZ3Jdc}H!2h(P6rsYhN!3%H#%VMYzyL8J*4d71Wgh1^W{X delta 14 VcmZ40$=KGxxZ$zF=5|G8WdJTx1(*N; diff --git a/mobile/openapi/lib/model/asset_ids_dto.dart b/mobile/openapi/lib/model/asset_ids_dto.dart new file mode 100644 index 0000000000000000000000000000000000000000..d7646284753cc13d147de3388716bc05d295a25e GIT binary patch literal 3318 zcmbVOZEqqs5dO}un4C^wkv2;&pDI$giyBhpUV@ZMKU4^<%&r07EjG2ihft3H-Whuh zrf@ASQo`aL&&%^XGj_Mz>UQDw_vg{oZ^Oso)9v%{4(=a54pX=r!Qs^eJIVHRq)aR=wj_p+}2teU?BM##*xVc zgiEU68QMlK650!37s6!d*13c}xEf$QN%8HrgFyj43S)oyY_*W{0R&U`=AHV+wZ>}E zYN}L4`D$jBhJHU8O~3F0tFQ~MJJ271N2Op@LP68;=KFX51mdu^%S)G_e;K9N|8WjN zT9|VS3uYF-s*+<==VPZ15{2XiH-_mAk%~MmxDm#gC84-1IPd_(WqKUID*uqU%GwdG zS8qy2A|2%rk+6XN<_n#=lm^Q&h-|6NY_t>8_ z>qun{+wwRBjjs@_X#}fl9u~JrXHRwWKB^?y?WII%;Py^*9oG4PrvjT+Gz0gaw~!Ea zhb@Xp2jfmGzCqH-tfDcS%(YsRvmVS?Wq9YnY42b%dYLfT z8N=nuMuMYGNE)wdO=_mCAsp4|UwW$h@jS>#U9JWMF5-JX2O3qy-lz*G`HYpNpBB+h zk0s4Y3VeWUv+Ibe0G0FNW^4m{Jn>_cXpEc$KV<3arC}4D0KWUygfRw+stc6|R6153k#3JmkyajG(jzm1e(ULBs zyYY6dS3iT!=eU9WFiI4u4GUS=P_ literal 0 HcmV?d00001 diff --git a/mobile/openapi/lib/model/asset_ids_response_dto.dart b/mobile/openapi/lib/model/asset_ids_response_dto.dart new file mode 100644 index 0000000000000000000000000000000000000000..6f9befea71d871c1c3d0afb631730eb376fe8e8a GIT binary patch literal 6828 zcmd5>ZExE~68`RAF)oaX#wm20PY2~>*ElP8ZX3HuoC1X)2y1d>v7wf_-DM0R^?$!- z_JyP@TCochcLrih?JTtR7IXO5vq4SS7*RTFHxthE`znNUn+smIPL%O)8tLqzj zcYSgB_P0ZjG35`r(02Sf{r>dG`54<%nuk8=22#DOXb2VfOzBEi4}gYg~neiwSjPL`q{He87q{X7daFbi$ZG0ZhDoKg_34o zR_YF9b}7{S@88X{rO@W!0O~AN3#!&qiFJ|U-_nj=sOyD+YVdW9e0N?+RA@OoQDTJftv<{#z*19F#2VD6iga=b>kyv$!y@1 zy%Xzyc-ceBH>kTJJ0)BFXetF|J}no85+(hPM>M7#>Msv(eB>O(R1bmygBhs1iTsEx zSk;n+2cE20kjtOcyX|mH?6znTMP2S~E5d#C0vujX7&wRxhjm|Q*r*#j~}kD(g9q|O8iep)s&eiRLV=#0K>R>F)W4Xt6f#xSEABT zC|1`pK72@y+t7#iJts>V8EiPZ+lZ6mSFVxk#m#0dLq?q@?7ShhXmWh$yB*sQkYb0I zn^Vgf0jA680Akcv8G>JN^1a7PJ8jZoq+BNX7z1q!6c2d7lx z&D*FYR-!-f7uR6#bagLoOLQe#2*B9zAtsnFL@iBST1MkO`O}$?3YRlW0#(|xr8aB1tR`IaOv>ph6woZba&<#cm7S6d4J2m%Mo;5ImtZKZvm4r#k zKrL5=@O`dL12488K~+X^0gj$Nv{|jRpUY;}y`(0ZR-gR#8-* zzcX{7n+Zk8aE|W`yD_pJd8{(YRk1(kxl@Om38#Y>>#5I_S-h%1Be#NXMfQoQ_sdJ4 z0n3k4)1mZ2@Up#mSxWOo*t|LG+1dCRp#4ZWFx&&l*Wvyt&Vaz){mH(XbdGeFOYBiU zT!#4|rq?UT;%g*5TM=@O9#AXfKR*f?(p{Wz$D8)* zKpp{LxqbT-$m0^YfRGIX7v~}Pm0x5b)vsnUPwC&)y~I_?kgr@Gsdnqte%(R#S1jvr1U2VAOb<-UNP zg(xI3R-6cN{%vB5uE~*Yc2`~%T*2s(Y^g)GV>yxNT;J8a^tl8} z1|QWYh4dmf#rdoNBKlex(Gk0S`{KyY)4hw=)@24K(FaV6rL;iyy%75dhPMCLBaAQm Ovg#54S&N{4to|?epv>F= literal 0 HcmV?d00001 diff --git a/mobile/openapi/lib/model/tag_response_dto.dart b/mobile/openapi/lib/model/tag_response_dto.dart index 42c386df34bccad14b8594d836b54ba8c5187485..8c4dd45edeb91e18ac84f4ebcc15928f3a59fb7d 100644 GIT binary patch delta 176 zcmaE-@J)6@598!Tj1rT37{w;SZ%}dNp4M|M*Owr*|P^i_k<^oc| zB}JKe=?a-ClkYJqPUdBj7KDh{E5OxHR%9xjypc(K@-!yt$tRe!)fLp#fI5L7za%5I z2-#o-TU#W3ADM#rGE?+25{omO^HWka6|^VYG0RNOV^-5s&<2~J0k_061*A8xG$%*F zUco@Y3So?AisIxhW(T3n6kC;@VeP)`126{j!C_?PiRH%mdNFl2@KQFpS9d3+gih8VqEk+1hgO%4Rq~;W- z!u9Iw>k<61rIgBPNa+pkR=g8w$KzL#DK@MeJWXm--zvZ~b3JgYd PP^2j+sDnLzkxvx>>4uv= diff --git a/mobile/openapi/lib/model/update_tag_dto.dart b/mobile/openapi/lib/model/update_tag_dto.dart index 85c1761a6b52ba14444f6f810d6eac53be8fa44c..325131480f4cc9c0443c53a00a01a92a2dbcb53d 100644 GIT binary patch delta 38 ucmeyYI8SCnE92(bjHQf|TUjb5tFabOzQ}4fS(EMH<}&tL=FMi@dszV_0}dYm delta 321 zcmbOy^I362E2EHtLPRNa7%6d8Iiy3ib*H3RWmOZZUgG=^%^Psvv2c ztjuyyBr{Dx1KqWg8(Acgb*aZH*kX8T@){O-WY26q&oYVGQUO_?l|pV}L0Do=X{vu( gNM%8)O>jw3W?s6TMpkit9?)ItNRHVY&T*U-0Hefhg#Z8m diff --git a/mobile/openapi/test/asset_ids_dto_test.dart b/mobile/openapi/test/asset_ids_dto_test.dart new file mode 100644 index 0000000000000000000000000000000000000000..840f6f5ccadbb12a03643d24d25ac3b678b396b3 GIT binary patch literal 589 zcmZuu-AltT5P$DqaXxK>x$2%oojB+w1GS*@K}52ny>`JasmXOR#Q)t(bRf8gd>r|G z-6c(vG==HYGC#hV-OnDT%UK5V#qDeeSq}I467KSBF@HW1SU_Hv=y)<7osN@G>e^UG zs@7MunVc7K=MU2h zYdoW2rAsl?l_nSI{-9Z-4SCgAQwxmOXw=z>D%R+HlKf&W)^u)LGT7==)+lt{ zyZ8rT@|=ck4+y5A_8=pHsrST-(r0Xwt<@!LP@Db$J`(@|R@z_#I_573CV%n$!9^md z+S#t{_k!f#*WM6*Wunln_I&xsPMi7)@YfXjB~{op3|nkE`IczGR&F2s}~Y~uxZ lP0%{&*zpcldx_PL5Bi-cV(37W;LAMAqsUiCDYtljCErrh!5shq literal 0 HcmV?d00001 diff --git a/mobile/openapi/test/asset_ids_response_dto_test.dart b/mobile/openapi/test/asset_ids_response_dto_test.dart new file mode 100644 index 0000000000000000000000000000000000000000..df6a8642d83a155f6c2bfd62fd25c980d78f3c02 GIT binary patch literal 771 zcmb7=O;5r=5Qgvl72^p^kSZr5A(5nDNR%du2T$wT4rOE6U1zo$V*Kw;7dY8OJ#@R% zJnzReO_MZ*>EkLtyO}L#_tVuZgZbiiHh?ULWxj&DJX_44P6TVo>rzvgjD{DZq#aFV z9idt~QfpJ=6*RVCEEU$cMH>dk*=)U|(5cY%hu*RBO;ChyjUka+y_M5zl6OF2F(>Yp z7tV6fHYig{pjjKFDCf?w+E`>&J6j1BHrlGob5(3KagyviLeJ~sRabcBJX-BdD$lUp zx;OrT1muvm#}Q~l+nGUr2~3$WEK0sd_EHnG;F5P|O4w>+`={`k0El31tggYtJqmYOeAuj~-w9F7e}wb2V^N6v+H*z=8W$z+nd@a`bCJr2b|vUU84F=i5cK`qY delta 105 zcmcb^-O91yCgbEgjOLTyGa0fgq@?DgmZVM&WKsw6%_Tz;(}Plr3-a@dQ(a2(?d%lN qGV@X#b8<8$`!kEPLphV>S)IUa|2&XRML1iY)njrNtJCE5thxZo=OgX_ diff --git a/mobile/openapi/test/tag_response_dto_test.dart b/mobile/openapi/test/tag_response_dto_test.dart index 41b581efbd1c8cae9b0143260a6831cd2f73444f..705a21ecd0864f31476f7580d9f8fcee067369c8 100644 GIT binary patch delta 23 fcmdnWeu{0wImXHAOqr9fG6qfVWZFJ?H8U3ganA`1 delta 56 zcmX@bwv~OuImXEbOp%k%F=kGlz_elVeI`E6qSU;^+|-c7^vNHYl)2zM&&knD>XRLq Fxd3q|6mS3l diff --git a/mobile/openapi/test/update_tag_dto_test.dart b/mobile/openapi/test/update_tag_dto_test.dart index 4f4e9498bcc133f49b87618333456102d05611b8..7c67e55d740bf111e9239f18c58ded87a7b417af 100644 GIT binary patch delta 18 ZcmbQtx`Jhc91|CpfTh?006O&5tIM` diff --git a/server/apps/immich/src/api-v1/asset/asset-repository.ts b/server/apps/immich/src/api-v1/asset/asset-repository.ts index 982e5c9f5..de7a9df9e 100644 --- a/server/apps/immich/src/api-v1/asset/asset-repository.ts +++ b/server/apps/immich/src/api-v1/asset/asset-repository.ts @@ -1,7 +1,7 @@ import { SearchPropertiesDto } from './dto/search-properties.dto'; import { CuratedLocationsResponseDto } from './response-dto/curated-locations-response.dto'; import { AssetEntity, AssetType, ExifEntity } from '@app/infra/entities'; -import { Inject, Injectable } from '@nestjs/common'; +import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm/repository/Repository'; import { CuratedObjectsResponseDto } from './response-dto/curated-objects-response.dto'; @@ -12,7 +12,6 @@ import { AssetCountByUserIdResponseDto } from './response-dto/asset-count-by-use import { CheckExistingAssetsDto } from './dto/check-existing-assets.dto'; import { In } from 'typeorm/find-options/operator/In'; import { UpdateAssetDto } from './dto/update-asset.dto'; -import { ITagRepository } from '../tag/tag.repository'; import { IsNull, Not } from 'typeorm'; import { AssetSearchDto } from './dto/asset-search.dto'; @@ -52,10 +51,7 @@ export const IAssetRepository = 'IAssetRepository'; @Injectable() export class AssetRepository implements IAssetRepository { constructor( - @InjectRepository(AssetEntity) - private assetRepository: Repository, - - @Inject(ITagRepository) private _tagRepository: ITagRepository, + @InjectRepository(AssetEntity) private assetRepository: Repository, @InjectRepository(ExifEntity) private exifRepository: Repository, ) {} diff --git a/server/apps/immich/src/api-v1/asset/asset.module.ts b/server/apps/immich/src/api-v1/asset/asset.module.ts index ebadeca75..6ec9c603a 100644 --- a/server/apps/immich/src/api-v1/asset/asset.module.ts +++ b/server/apps/immich/src/api-v1/asset/asset.module.ts @@ -5,7 +5,6 @@ import { TypeOrmModule } from '@nestjs/typeorm'; import { AssetEntity, ExifEntity } from '@app/infra/entities'; import { AssetRepository, IAssetRepository } from './asset-repository'; import { DownloadModule } from '../../modules/download/download.module'; -import { TagModule } from '../tag/tag.module'; import { AlbumModule } from '../album/album.module'; const ASSET_REPOSITORY_PROVIDER = { @@ -18,7 +17,6 @@ const ASSET_REPOSITORY_PROVIDER = { // TypeOrmModule.forFeature([AssetEntity, ExifEntity]), DownloadModule, - TagModule, AlbumModule, ], controllers: [AssetController], diff --git a/server/apps/immich/src/api-v1/tag/dto/update-tag.dto.ts b/server/apps/immich/src/api-v1/tag/dto/update-tag.dto.ts deleted file mode 100644 index 64632f1f6..000000000 --- a/server/apps/immich/src/api-v1/tag/dto/update-tag.dto.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { IsOptional, IsString } from 'class-validator'; - -export class UpdateTagDto { - @IsString() - @IsOptional() - name?: string; - - @IsString() - @IsOptional() - renameTagId?: string; -} diff --git a/server/apps/immich/src/api-v1/tag/tag.controller.ts b/server/apps/immich/src/api-v1/tag/tag.controller.ts deleted file mode 100644 index df12513df..000000000 --- a/server/apps/immich/src/api-v1/tag/tag.controller.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { Controller, Get, Post, Body, Patch, Param, Delete, ValidationPipe } from '@nestjs/common'; -import { TagService } from './tag.service'; -import { CreateTagDto } from './dto/create-tag.dto'; -import { UpdateTagDto } from './dto/update-tag.dto'; -import { Authenticated } from '../../decorators/authenticated.decorator'; -import { ApiTags } from '@nestjs/swagger'; -import { AuthUserDto, GetAuthUser } from '../../decorators/auth-user.decorator'; -import { mapTag, TagResponseDto } from '@app/domain'; -import { UUIDParamDto } from '../../controllers/dto/uuid-param.dto'; - -@ApiTags('Tag') -@Controller('tag') -@Authenticated() -export class TagController { - constructor(private readonly tagService: TagService) {} - - @Post() - create( - @GetAuthUser() authUser: AuthUserDto, - @Body(ValidationPipe) createTagDto: CreateTagDto, - ): Promise { - return this.tagService.create(authUser, createTagDto); - } - - @Get() - findAll(@GetAuthUser() authUser: AuthUserDto): Promise { - return this.tagService.findAll(authUser); - } - - @Get(':id') - async findOne(@GetAuthUser() authUser: AuthUserDto, @Param() { id }: UUIDParamDto): Promise { - const tag = await this.tagService.findOne(authUser, id); - return mapTag(tag); - } - - @Patch(':id') - update( - @GetAuthUser() authUser: AuthUserDto, - @Param() { id }: UUIDParamDto, - @Body(ValidationPipe) updateTagDto: UpdateTagDto, - ): Promise { - return this.tagService.update(authUser, id, updateTagDto); - } - - @Delete(':id') - delete(@GetAuthUser() authUser: AuthUserDto, @Param() { id }: UUIDParamDto): Promise { - return this.tagService.remove(authUser, id); - } -} diff --git a/server/apps/immich/src/api-v1/tag/tag.module.ts b/server/apps/immich/src/api-v1/tag/tag.module.ts deleted file mode 100644 index 38b7924a6..000000000 --- a/server/apps/immich/src/api-v1/tag/tag.module.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { Module } from '@nestjs/common'; -import { TagService } from './tag.service'; -import { TagController } from './tag.controller'; -import { TagEntity } from '@app/infra/entities'; -import { TypeOrmModule } from '@nestjs/typeorm'; -import { TagRepository, ITagRepository } from './tag.repository'; - -const TAG_REPOSITORY_PROVIDER = { - provide: ITagRepository, - useClass: TagRepository, -}; -@Module({ - imports: [TypeOrmModule.forFeature([TagEntity])], - controllers: [TagController], - providers: [TagService, TAG_REPOSITORY_PROVIDER], - exports: [TAG_REPOSITORY_PROVIDER], -}) -export class TagModule {} diff --git a/server/apps/immich/src/api-v1/tag/tag.repository.ts b/server/apps/immich/src/api-v1/tag/tag.repository.ts deleted file mode 100644 index ec32d8b74..000000000 --- a/server/apps/immich/src/api-v1/tag/tag.repository.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { TagEntity, TagType } from '@app/infra/entities'; -import { Injectable } from '@nestjs/common'; -import { InjectRepository } from '@nestjs/typeorm'; -import { In, Repository } from 'typeorm'; -import { UpdateTagDto } from './dto/update-tag.dto'; - -export interface ITagRepository { - create(userId: string, tagType: TagType, tagName: string): Promise; - getByIds(userId: string, tagIds: string[]): Promise; - getById(tagId: string, userId: string): Promise; - getByUserId(userId: string): Promise; - update(tag: TagEntity, updateTagDto: UpdateTagDto): Promise; - remove(tag: TagEntity): Promise; -} - -export const ITagRepository = 'ITagRepository'; - -@Injectable() -export class TagRepository implements ITagRepository { - constructor( - @InjectRepository(TagEntity) - private tagRepository: Repository, - ) {} - - async create(userId: string, tagType: TagType, tagName: string): Promise { - const tag = new TagEntity(); - tag.name = tagName; - tag.type = tagType; - tag.userId = userId; - - return this.tagRepository.save(tag); - } - - async getById(tagId: string, userId: string): Promise { - return await this.tagRepository.findOne({ where: { id: tagId, userId }, relations: ['user'] }); - } - - async getByIds(userId: string, tagIds: string[]): Promise { - return await this.tagRepository.find({ - where: { id: In(tagIds), userId }, - relations: { - user: true, - }, - }); - } - - async getByUserId(userId: string): Promise { - return await this.tagRepository.find({ where: { userId } }); - } - - async update(tag: TagEntity, updateTagDto: UpdateTagDto): Promise { - tag.name = updateTagDto.name ?? tag.name; - tag.renameTagId = updateTagDto.renameTagId ?? tag.renameTagId; - - return this.tagRepository.save(tag); - } - - async remove(tag: TagEntity): Promise { - return await this.tagRepository.remove(tag); - } -} diff --git a/server/apps/immich/src/api-v1/tag/tag.service.spec.ts b/server/apps/immich/src/api-v1/tag/tag.service.spec.ts deleted file mode 100644 index 4609bb781..000000000 --- a/server/apps/immich/src/api-v1/tag/tag.service.spec.ts +++ /dev/null @@ -1,94 +0,0 @@ -import { TagEntity, TagType, UserEntity } from '@app/infra/entities'; -import { AuthUserDto } from '../../decorators/auth-user.decorator'; -import { ITagRepository } from './tag.repository'; -import { TagService } from './tag.service'; - -describe('TagService', () => { - let sut: TagService; - let tagRepositoryMock: jest.Mocked; - - const user1AuthUser: AuthUserDto = Object.freeze({ - id: '1111', - email: 'testuser@email.com', - isAdmin: false, - }); - - const user1: UserEntity = Object.freeze({ - id: '1111', - firstName: 'Alex', - lastName: 'Tran', - isAdmin: true, - email: 'testuser@email.com', - profileImagePath: '', - shouldChangePassword: true, - createdAt: new Date('2022-12-02T19:29:23.603Z'), - deletedAt: null, - updatedAt: new Date('2022-12-02T19:29:23.603Z'), - tags: [], - assets: [], - oauthId: 'oauth-id-1', - storageLabel: null, - }); - - // const user2: UserEntity = Object.freeze({ - // id: '2222', - // firstName: 'Alex', - // lastName: 'Tran', - // isAdmin: true, - // email: 'testuser2@email.com', - // profileImagePath: '', - // shouldChangePassword: true, - // createdAt: '2022-12-02T19:29:23.603Z', - // deletedAt: undefined, - // tags: [], - // oauthId: 'oauth-id-2', - // }); - - const user1Tag1: TagEntity = Object.freeze({ - name: 'user 1 tag 1', - type: TagType.CUSTOM, - userId: user1.id, - user: user1, - renameTagId: '', - id: 'user1-tag-1-id', - assets: [], - }); - - // const user1Tag2: TagEntity = Object.freeze({ - // name: 'user 1 tag 2', - // type: TagType.CUSTOM, - // userId: user1.id, - // user: user1, - // renameTagId: '', - // id: 'user1-tag-2-id', - // assets: [], - // }); - - beforeAll(() => { - tagRepositoryMock = { - create: jest.fn(), - getByIds: jest.fn(), - getById: jest.fn(), - getByUserId: jest.fn(), - remove: jest.fn(), - update: jest.fn(), - }; - - sut = new TagService(tagRepositoryMock); - }); - - it('creates tag', async () => { - const createTagDto = { - name: 'user 1 tag 1', - type: TagType.CUSTOM, - }; - - tagRepositoryMock.create.mockResolvedValue(user1Tag1); - - const result = await sut.create(user1AuthUser, createTagDto); - - expect(result.userId).toEqual(user1AuthUser.id); - expect(result.name).toEqual(createTagDto.name); - expect(result.type).toEqual(createTagDto.type); - }); -}); diff --git a/server/apps/immich/src/api-v1/tag/tag.service.ts b/server/apps/immich/src/api-v1/tag/tag.service.ts deleted file mode 100644 index 71998661a..000000000 --- a/server/apps/immich/src/api-v1/tag/tag.service.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { TagEntity } from '@app/infra/entities'; -import { BadRequestException, Inject, Injectable, Logger } from '@nestjs/common'; -import { AuthUserDto } from '../../decorators/auth-user.decorator'; -import { CreateTagDto } from './dto/create-tag.dto'; -import { UpdateTagDto } from './dto/update-tag.dto'; -import { ITagRepository } from './tag.repository'; -import { mapTag, TagResponseDto } from '@app/domain'; - -@Injectable() -export class TagService { - readonly logger = new Logger(TagService.name); - - constructor(@Inject(ITagRepository) private _tagRepository: ITagRepository) {} - - async create(authUser: AuthUserDto, createTagDto: CreateTagDto) { - try { - const newTag = await this._tagRepository.create(authUser.id, createTagDto.type, createTagDto.name); - return mapTag(newTag); - } catch (e: any) { - this.logger.error(e, e.stack); - throw new BadRequestException(`Failed to create tag: ${e.detail}`); - } - } - - async findAll(authUser: AuthUserDto) { - const tags = await this._tagRepository.getByUserId(authUser.id); - return tags.map(mapTag); - } - - async findOne(authUser: AuthUserDto, id: string): Promise { - const tag = await this._tagRepository.getById(id, authUser.id); - - if (!tag) { - throw new BadRequestException('Tag not found'); - } - - return tag; - } - - async update(authUser: AuthUserDto, id: string, updateTagDto: UpdateTagDto): Promise { - const tag = await this.findOne(authUser, id); - - await this._tagRepository.update(tag, updateTagDto); - - return mapTag(tag); - } - - async remove(authUser: AuthUserDto, id: string): Promise { - const tag = await this.findOne(authUser, id); - await this._tagRepository.remove(tag); - } -} diff --git a/server/apps/immich/src/app.module.ts b/server/apps/immich/src/app.module.ts index a9168975b..51f5e3207 100644 --- a/server/apps/immich/src/app.module.ts +++ b/server/apps/immich/src/app.module.ts @@ -3,7 +3,6 @@ import { AssetModule } from './api-v1/asset/asset.module'; import { AlbumModule } from './api-v1/album/album.module'; import { AppController } from './app.controller'; import { ScheduleModule } from '@nestjs/schedule'; -import { TagModule } from './api-v1/tag/tag.module'; import { DomainModule, SearchService } from '@app/domain'; import { InfraModule } from '@app/infra'; import { @@ -20,6 +19,7 @@ import { SharedLinkController, SystemConfigController, UserController, + TagController, } from './controllers'; import { APP_GUARD } from '@nestjs/core'; import { AuthGuard } from './middlewares/auth.guard'; @@ -27,11 +27,11 @@ import { AppCronJobs } from './app.cron-jobs'; @Module({ imports: [ + // DomainModule.register({ imports: [InfraModule] }), AssetModule, AlbumModule, ScheduleModule.forRoot(), - TagModule, ], controllers: [ AppController, @@ -46,6 +46,7 @@ import { AppCronJobs } from './app.cron-jobs'; ServerInfoController, SharedLinkController, SystemConfigController, + TagController, UserController, PersonController, ], diff --git a/server/apps/immich/src/controllers/index.ts b/server/apps/immich/src/controllers/index.ts index d86db2beb..5917f0f79 100644 --- a/server/apps/immich/src/controllers/index.ts +++ b/server/apps/immich/src/controllers/index.ts @@ -10,4 +10,5 @@ export * from './search.controller'; export * from './server-info.controller'; export * from './shared-link.controller'; export * from './system-config.controller'; +export * from './tag.controller'; export * from './user.controller'; diff --git a/server/apps/immich/src/controllers/tag.controller.ts b/server/apps/immich/src/controllers/tag.controller.ts new file mode 100644 index 000000000..45b5233e2 --- /dev/null +++ b/server/apps/immich/src/controllers/tag.controller.ts @@ -0,0 +1,75 @@ +import { + AssetIdsDto, + AssetIdsResponseDto, + AssetResponseDto, + CreateTagDto, + TagResponseDto, + TagService, + UpdateTagDto, +} from '@app/domain'; +import { Body, Controller, Delete, Get, Param, Patch, Post, Put } from '@nestjs/common'; +import { ApiTags } from '@nestjs/swagger'; +import { AuthUserDto, GetAuthUser } from '../decorators/auth-user.decorator'; +import { Authenticated } from '../decorators/authenticated.decorator'; +import { UseValidation } from '../decorators/use-validation.decorator'; +import { UUIDParamDto } from './dto/uuid-param.dto'; + +@ApiTags('Tag') +@Controller('tag') +@Authenticated() +@UseValidation() +export class TagController { + constructor(private service: TagService) {} + + @Post() + createTag(@GetAuthUser() authUser: AuthUserDto, @Body() dto: CreateTagDto): Promise { + return this.service.create(authUser, dto); + } + + @Get() + getAllTags(@GetAuthUser() authUser: AuthUserDto): Promise { + return this.service.getAll(authUser); + } + + @Get(':id') + getTagById(@GetAuthUser() authUser: AuthUserDto, @Param() { id }: UUIDParamDto): Promise { + return this.service.getById(authUser, id); + } + + @Patch(':id') + updateTag( + @GetAuthUser() authUser: AuthUserDto, + @Param() { id }: UUIDParamDto, + @Body() dto: UpdateTagDto, + ): Promise { + return this.service.update(authUser, id, dto); + } + + @Delete(':id') + deleteTag(@GetAuthUser() authUser: AuthUserDto, @Param() { id }: UUIDParamDto): Promise { + return this.service.remove(authUser, id); + } + + @Get(':id/assets') + getTagAssets(@GetAuthUser() authUser: AuthUserDto, @Param() { id }: UUIDParamDto): Promise { + return this.service.getAssets(authUser, id); + } + + @Put(':id/assets') + tagAssets( + @GetAuthUser() authUser: AuthUserDto, + @Param() { id }: UUIDParamDto, + @Body() dto: AssetIdsDto, + ): Promise { + return this.service.addAssets(authUser, id, dto); + } + + @Delete(':id/assets') + untagAssets( + @GetAuthUser() authUser: AuthUserDto, + @Body() dto: AssetIdsDto, + @Param() { id }: UUIDParamDto, + ): Promise { + return this.service.removeAssets(authUser, id, dto); + } +} diff --git a/server/immich-openapi-specs.json b/server/immich-openapi-specs.json index 45a6dcde3..a09c0e493 100644 --- a/server/immich-openapi-specs.json +++ b/server/immich-openapi-specs.json @@ -1785,6 +1785,357 @@ ] } }, + "/tag": { + "post": { + "operationId": "createTag", + "parameters": [], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateTagDto" + } + } + } + }, + "responses": { + "201": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TagResponseDto" + } + } + } + } + }, + "tags": [ + "Tag" + ], + "security": [ + { + "bearer": [] + }, + { + "cookie": [] + }, + { + "api_key": [] + } + ] + }, + "get": { + "operationId": "getAllTags", + "parameters": [], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TagResponseDto" + } + } + } + } + } + }, + "tags": [ + "Tag" + ], + "security": [ + { + "bearer": [] + }, + { + "cookie": [] + }, + { + "api_key": [] + } + ] + } + }, + "/tag/{id}": { + "get": { + "operationId": "getTagById", + "parameters": [ + { + "name": "id", + "required": true, + "in": "path", + "schema": { + "format": "uuid", + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TagResponseDto" + } + } + } + } + }, + "tags": [ + "Tag" + ], + "security": [ + { + "bearer": [] + }, + { + "cookie": [] + }, + { + "api_key": [] + } + ] + }, + "patch": { + "operationId": "updateTag", + "parameters": [ + { + "name": "id", + "required": true, + "in": "path", + "schema": { + "format": "uuid", + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateTagDto" + } + } + } + }, + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TagResponseDto" + } + } + } + } + }, + "tags": [ + "Tag" + ], + "security": [ + { + "bearer": [] + }, + { + "cookie": [] + }, + { + "api_key": [] + } + ] + }, + "delete": { + "operationId": "deleteTag", + "parameters": [ + { + "name": "id", + "required": true, + "in": "path", + "schema": { + "format": "uuid", + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "" + } + }, + "tags": [ + "Tag" + ], + "security": [ + { + "bearer": [] + }, + { + "cookie": [] + }, + { + "api_key": [] + } + ] + } + }, + "/tag/{id}/assets": { + "get": { + "operationId": "getTagAssets", + "parameters": [ + { + "name": "id", + "required": true, + "in": "path", + "schema": { + "format": "uuid", + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AssetResponseDto" + } + } + } + } + } + }, + "tags": [ + "Tag" + ], + "security": [ + { + "bearer": [] + }, + { + "cookie": [] + }, + { + "api_key": [] + } + ] + }, + "put": { + "operationId": "tagAssets", + "parameters": [ + { + "name": "id", + "required": true, + "in": "path", + "schema": { + "format": "uuid", + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AssetIdsDto" + } + } + } + }, + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AssetIdsResponseDto" + } + } + } + } + } + }, + "tags": [ + "Tag" + ], + "security": [ + { + "bearer": [] + }, + { + "cookie": [] + }, + { + "api_key": [] + } + ] + }, + "delete": { + "operationId": "untagAssets", + "parameters": [ + { + "name": "id", + "required": true, + "in": "path", + "schema": { + "format": "uuid", + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AssetIdsDto" + } + } + } + }, + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AssetIdsResponseDto" + } + } + } + } + } + }, + "tags": [ + "Tag" + ], + "security": [ + { + "bearer": [] + }, + { + "cookie": [] + }, + { + "api_key": [] + } + ] + } + }, "/user": { "get": { "operationId": "getAllUsers", @@ -3598,206 +3949,6 @@ ] } }, - "/tag": { - "post": { - "operationId": "create", - "parameters": [], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/CreateTagDto" - } - } - } - }, - "responses": { - "201": { - "description": "", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/TagResponseDto" - } - } - } - } - }, - "tags": [ - "Tag" - ], - "security": [ - { - "bearer": [] - }, - { - "cookie": [] - }, - { - "api_key": [] - } - ] - }, - "get": { - "operationId": "findAll", - "parameters": [], - "responses": { - "200": { - "description": "", - "content": { - "application/json": { - "schema": { - "type": "array", - "items": { - "$ref": "#/components/schemas/TagResponseDto" - } - } - } - } - } - }, - "tags": [ - "Tag" - ], - "security": [ - { - "bearer": [] - }, - { - "cookie": [] - }, - { - "api_key": [] - } - ] - } - }, - "/tag/{id}": { - "get": { - "operationId": "findOne", - "parameters": [ - { - "name": "id", - "required": true, - "in": "path", - "schema": { - "format": "uuid", - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/TagResponseDto" - } - } - } - } - }, - "tags": [ - "Tag" - ], - "security": [ - { - "bearer": [] - }, - { - "cookie": [] - }, - { - "api_key": [] - } - ] - }, - "patch": { - "operationId": "update", - "parameters": [ - { - "name": "id", - "required": true, - "in": "path", - "schema": { - "format": "uuid", - "type": "string" - } - } - ], - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/UpdateTagDto" - } - } - } - }, - "responses": { - "200": { - "description": "", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/TagResponseDto" - } - } - } - } - }, - "tags": [ - "Tag" - ], - "security": [ - { - "bearer": [] - }, - { - "cookie": [] - }, - { - "api_key": [] - } - ] - }, - "delete": { - "operationId": "delete", - "parameters": [ - { - "name": "id", - "required": true, - "in": "path", - "schema": { - "format": "uuid", - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "" - } - }, - "tags": [ - "Tag" - ], - "security": [ - { - "bearer": [] - }, - { - "cookie": [] - }, - { - "api_key": [] - } - ] - } - }, "/album/count-by-user-id": { "get": { "operationId": "getAlbumCountByUserId", @@ -4384,26 +4535,22 @@ "TagResponseDto": { "type": "object", "properties": { - "id": { - "type": "string" - }, "type": { "$ref": "#/components/schemas/TagTypeEnum" }, + "id": { + "type": "string" + }, "name": { "type": "string" }, "userId": { "type": "string" - }, - "renameTagId": { - "type": "string", - "nullable": true } }, "required": [ - "id", "type", + "id", "name", "userId" ] @@ -5665,6 +5812,67 @@ "presetOptions" ] }, + "CreateTagDto": { + "type": "object", + "properties": { + "type": { + "$ref": "#/components/schemas/TagTypeEnum" + }, + "name": { + "type": "string" + } + }, + "required": [ + "type", + "name" + ] + }, + "UpdateTagDto": { + "type": "object", + "properties": { + "name": { + "type": "string" + } + } + }, + "AssetIdsDto": { + "type": "object", + "properties": { + "assetIds": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + } + } + }, + "required": [ + "assetIds" + ] + }, + "AssetIdsResponseDto": { + "type": "object", + "properties": { + "assetId": { + "type": "string" + }, + "success": { + "type": "boolean" + }, + "error": { + "enum": [ + "duplicate", + "no_permission", + "not_found" + ], + "type": "string" + } + }, + "required": [ + "assetId", + "success" + ] + }, "CreateUserDto": { "type": "object", "properties": { @@ -6318,32 +6526,6 @@ "assetIds" ] }, - "CreateTagDto": { - "type": "object", - "properties": { - "type": { - "$ref": "#/components/schemas/TagTypeEnum" - }, - "name": { - "type": "string" - } - }, - "required": [ - "type", - "name" - ] - }, - "UpdateTagDto": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "renameTagId": { - "type": "string" - } - } - }, "AlbumCountResponseDto": { "type": "object", "properties": { diff --git a/server/libs/domain/src/asset/dto/asset-ids.dto.ts b/server/libs/domain/src/asset/dto/asset-ids.dto.ts new file mode 100644 index 000000000..8c9b60b3d --- /dev/null +++ b/server/libs/domain/src/asset/dto/asset-ids.dto.ts @@ -0,0 +1,6 @@ +import { ValidateUUID } from '../../../../../apps/immich/src/decorators/validate-uuid.decorator'; + +export class AssetIdsDto { + @ValidateUUID({ each: true }) + assetIds!: string[]; +} diff --git a/server/libs/domain/src/asset/dto/index.ts b/server/libs/domain/src/asset/dto/index.ts new file mode 100644 index 000000000..900b25d28 --- /dev/null +++ b/server/libs/domain/src/asset/dto/index.ts @@ -0,0 +1,2 @@ +export * from './asset-ids.dto'; +export * from './map-marker.dto'; diff --git a/server/libs/domain/src/asset/dto/map-marker.dto.ts b/server/libs/domain/src/asset/dto/map-marker.dto.ts index 5398b9547..af39a4980 100644 --- a/server/libs/domain/src/asset/dto/map-marker.dto.ts +++ b/server/libs/domain/src/asset/dto/map-marker.dto.ts @@ -1,7 +1,7 @@ import { ApiProperty } from '@nestjs/swagger'; -import { toBoolean } from 'apps/immich/src/utils/transform.util'; import { Transform, Type } from 'class-transformer'; import { IsBoolean, IsDate, IsOptional } from 'class-validator'; +import { toBoolean } from '../../../../../apps/immich/src/utils/transform.util'; export class MapMarkerDto { @ApiProperty() diff --git a/server/libs/domain/src/asset/index.ts b/server/libs/domain/src/asset/index.ts index aa429787d..833df4c56 100644 --- a/server/libs/domain/src/asset/index.ts +++ b/server/libs/domain/src/asset/index.ts @@ -1,3 +1,4 @@ export * from './asset.repository'; export * from './asset.service'; +export * from './dto'; export * from './response-dto'; diff --git a/server/libs/domain/src/asset/response-dto/asset-ids-response.dto.ts b/server/libs/domain/src/asset/response-dto/asset-ids-response.dto.ts new file mode 100644 index 000000000..928bed24d --- /dev/null +++ b/server/libs/domain/src/asset/response-dto/asset-ids-response.dto.ts @@ -0,0 +1,11 @@ +export enum AssetIdErrorReason { + DUPLICATE = 'duplicate', + NO_PERMISSION = 'no_permission', + NOT_FOUND = 'not_found', +} + +export class AssetIdsResponseDto { + assetId!: string; + success!: boolean; + error?: AssetIdErrorReason; +} diff --git a/server/libs/domain/src/asset/response-dto/index.ts b/server/libs/domain/src/asset/response-dto/index.ts index 7e17e324f..b82249d2b 100644 --- a/server/libs/domain/src/asset/response-dto/index.ts +++ b/server/libs/domain/src/asset/response-dto/index.ts @@ -1,4 +1,5 @@ +export * from './asset-ids-response.dto'; export * from './asset-response.dto'; export * from './exif-response.dto'; -export * from './smart-info-response.dto'; export * from './map-marker-response.dto'; +export * from './smart-info-response.dto'; diff --git a/server/libs/domain/src/domain.module.ts b/server/libs/domain/src/domain.module.ts index 9aea15b44..ec2443cd7 100644 --- a/server/libs/domain/src/domain.module.ts +++ b/server/libs/domain/src/domain.module.ts @@ -17,6 +17,7 @@ import { SmartInfoService } from './smart-info'; import { StorageService } from './storage'; import { StorageTemplateService } from './storage-template'; import { INITIAL_SYSTEM_CONFIG, SystemConfigService } from './system-config'; +import { TagService } from './tag'; import { UserService } from './user'; const providers: Provider[] = [ @@ -38,6 +39,7 @@ const providers: Provider[] = [ StorageService, StorageTemplateService, SystemConfigService, + TagService, UserService, { provide: INITIAL_SYSTEM_CONFIG, diff --git a/server/libs/domain/src/tag/index.ts b/server/libs/domain/src/tag/index.ts index 0b415ca92..0ea02172e 100644 --- a/server/libs/domain/src/tag/index.ts +++ b/server/libs/domain/src/tag/index.ts @@ -1 +1,4 @@ -export * from './response-dto'; +export * from './tag-response.dto'; +export * from './tag.dto'; +export * from './tag.repository'; +export * from './tag.service'; diff --git a/server/libs/domain/src/tag/response-dto/index.ts b/server/libs/domain/src/tag/response-dto/index.ts deleted file mode 100644 index b08b1f61c..000000000 --- a/server/libs/domain/src/tag/response-dto/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './tag-response.dto'; diff --git a/server/libs/domain/src/tag/response-dto/tag-response.dto.ts b/server/libs/domain/src/tag/tag-response.dto.ts similarity index 83% rename from server/libs/domain/src/tag/response-dto/tag-response.dto.ts rename to server/libs/domain/src/tag/tag-response.dto.ts index b679a378c..a533b15c9 100644 --- a/server/libs/domain/src/tag/response-dto/tag-response.dto.ts +++ b/server/libs/domain/src/tag/tag-response.dto.ts @@ -2,17 +2,11 @@ import { TagEntity, TagType } from '@app/infra/entities'; import { ApiProperty } from '@nestjs/swagger'; export class TagResponseDto { - @ApiProperty() id!: string; - @ApiProperty({ enumName: 'TagTypeEnum', enum: TagType }) type!: string; - name!: string; - userId!: string; - - renameTagId?: string | null; } export function mapTag(entity: TagEntity): TagResponseDto { @@ -21,6 +15,5 @@ export function mapTag(entity: TagEntity): TagResponseDto { type: entity.type, name: entity.name, userId: entity.userId, - renameTagId: entity.renameTagId, }; } diff --git a/server/apps/immich/src/api-v1/tag/dto/create-tag.dto.ts b/server/libs/domain/src/tag/tag.dto.ts similarity index 64% rename from server/apps/immich/src/api-v1/tag/dto/create-tag.dto.ts rename to server/libs/domain/src/tag/tag.dto.ts index 69dd946eb..4cce47530 100644 --- a/server/apps/immich/src/api-v1/tag/dto/create-tag.dto.ts +++ b/server/libs/domain/src/tag/tag.dto.ts @@ -1,6 +1,6 @@ import { TagType } from '@app/infra/entities'; import { ApiProperty } from '@nestjs/swagger'; -import { IsEnum, IsNotEmpty, IsString } from 'class-validator'; +import { IsEnum, IsNotEmpty, IsOptional, IsString } from 'class-validator'; export class CreateTagDto { @IsString() @@ -12,3 +12,9 @@ export class CreateTagDto { @ApiProperty({ enumName: 'TagTypeEnum', enum: TagType }) type!: TagType; } + +export class UpdateTagDto { + @IsString() + @IsOptional() + name?: string; +} diff --git a/server/libs/domain/src/tag/tag.repository.ts b/server/libs/domain/src/tag/tag.repository.ts new file mode 100644 index 000000000..4e6f583b4 --- /dev/null +++ b/server/libs/domain/src/tag/tag.repository.ts @@ -0,0 +1,16 @@ +import { AssetEntity, TagEntity } from '@app/infra/entities'; + +export const ITagRepository = 'ITagRepository'; + +export interface ITagRepository { + getById(userId: string, tagId: string): Promise; + getAll(userId: string): Promise; + create(tag: Partial): Promise; + update(tag: Partial): Promise; + remove(tag: TagEntity): Promise; + hasName(userId: string, name: string): Promise; + hasAsset(userId: string, tagId: string, assetId: string): Promise; + getAssets(userId: string, tagId: string): Promise; + addAssets(userId: string, tagId: string, assetIds: string[]): Promise; + removeAssets(userId: string, tagId: string, assetIds: string[]): Promise; +} diff --git a/server/libs/domain/src/tag/tag.service.spec.ts b/server/libs/domain/src/tag/tag.service.spec.ts new file mode 100644 index 000000000..62c145080 --- /dev/null +++ b/server/libs/domain/src/tag/tag.service.spec.ts @@ -0,0 +1,178 @@ +import { TagType } from '@app/infra/entities'; +import { BadRequestException } from '@nestjs/common'; +import { when } from 'jest-when'; +import { assetEntityStub, authStub, newTagRepositoryMock, tagResponseStub, tagStub } from '../../test'; +import { AssetIdErrorReason } from '../asset'; +import { ITagRepository } from './tag.repository'; +import { TagService } from './tag.service'; + +describe(TagService.name, () => { + let sut: TagService; + let tagMock: jest.Mocked; + + beforeEach(() => { + tagMock = newTagRepositoryMock(); + sut = new TagService(tagMock); + }); + + it('should work', () => { + expect(sut).toBeDefined(); + }); + + describe('getAll', () => { + it('should return all tags for a user', async () => { + tagMock.getAll.mockResolvedValue([tagStub.tag1]); + await expect(sut.getAll(authStub.admin)).resolves.toEqual([tagResponseStub.tag1]); + expect(tagMock.getAll).toHaveBeenCalledWith(authStub.admin.id); + }); + }); + + describe('getById', () => { + it('should throw an error for an invalid id', async () => { + tagMock.getById.mockResolvedValue(null); + await expect(sut.getById(authStub.admin, 'tag-1')).rejects.toBeInstanceOf(BadRequestException); + expect(tagMock.getById).toHaveBeenCalledWith(authStub.admin.id, 'tag-1'); + }); + + it('should return a tag for a user', async () => { + tagMock.getById.mockResolvedValue(tagStub.tag1); + await expect(sut.getById(authStub.admin, 'tag-1')).resolves.toEqual(tagResponseStub.tag1); + expect(tagMock.getById).toHaveBeenCalledWith(authStub.admin.id, 'tag-1'); + }); + }); + + describe('create', () => { + it('should throw an error for a duplicate tag', async () => { + tagMock.hasName.mockResolvedValue(true); + await expect(sut.create(authStub.admin, { name: 'tag-1', type: TagType.CUSTOM })).rejects.toBeInstanceOf( + BadRequestException, + ); + expect(tagMock.hasName).toHaveBeenCalledWith(authStub.admin.id, 'tag-1'); + expect(tagMock.create).not.toHaveBeenCalled(); + }); + + it('should create a new tag', async () => { + tagMock.create.mockResolvedValue(tagStub.tag1); + await expect(sut.create(authStub.admin, { name: 'tag-1', type: TagType.CUSTOM })).resolves.toEqual( + tagResponseStub.tag1, + ); + expect(tagMock.create).toHaveBeenCalledWith({ + userId: authStub.admin.id, + name: 'tag-1', + type: TagType.CUSTOM, + }); + }); + }); + + describe('update', () => { + it('should throw an error for an invalid id', async () => { + tagMock.getById.mockResolvedValue(null); + await expect(sut.update(authStub.admin, 'tag-1', { name: 'tag-2' })).rejects.toBeInstanceOf(BadRequestException); + expect(tagMock.getById).toHaveBeenCalledWith(authStub.admin.id, 'tag-1'); + expect(tagMock.remove).not.toHaveBeenCalled(); + }); + + it('should update a tag', async () => { + tagMock.getById.mockResolvedValue(tagStub.tag1); + tagMock.update.mockResolvedValue(tagStub.tag1); + await expect(sut.update(authStub.admin, 'tag-1', { name: 'tag-2' })).resolves.toEqual(tagResponseStub.tag1); + expect(tagMock.getById).toHaveBeenCalledWith(authStub.admin.id, 'tag-1'); + expect(tagMock.update).toHaveBeenCalledWith({ id: 'tag-1', name: 'tag-2' }); + }); + }); + + describe('remove', () => { + it('should throw an error for an invalid id', async () => { + tagMock.getById.mockResolvedValue(null); + await expect(sut.remove(authStub.admin, 'tag-1')).rejects.toBeInstanceOf(BadRequestException); + expect(tagMock.getById).toHaveBeenCalledWith(authStub.admin.id, 'tag-1'); + expect(tagMock.remove).not.toHaveBeenCalled(); + }); + + it('should remove a tag', async () => { + tagMock.getById.mockResolvedValue(tagStub.tag1); + await sut.remove(authStub.admin, 'tag-1'); + expect(tagMock.getById).toHaveBeenCalledWith(authStub.admin.id, 'tag-1'); + expect(tagMock.remove).toHaveBeenCalledWith(tagStub.tag1); + }); + }); + + describe('getAssets', () => { + it('should throw an error for an invalid id', async () => { + tagMock.getById.mockResolvedValue(null); + await expect(sut.remove(authStub.admin, 'tag-1')).rejects.toBeInstanceOf(BadRequestException); + expect(tagMock.getById).toHaveBeenCalledWith(authStub.admin.id, 'tag-1'); + expect(tagMock.remove).not.toHaveBeenCalled(); + }); + + it('should get the assets for a tag', async () => { + tagMock.getById.mockResolvedValue(tagStub.tag1); + tagMock.getAssets.mockResolvedValue([assetEntityStub.image]); + await sut.getAssets(authStub.admin, 'tag-1'); + expect(tagMock.getById).toHaveBeenCalledWith(authStub.admin.id, 'tag-1'); + expect(tagMock.getAssets).toHaveBeenCalledWith(authStub.admin.id, 'tag-1'); + }); + }); + + describe('addAssets', () => { + it('should throw an error for an invalid id', async () => { + tagMock.getById.mockResolvedValue(null); + await expect(sut.addAssets(authStub.admin, 'tag-1', { assetIds: ['asset-1'] })).rejects.toBeInstanceOf( + BadRequestException, + ); + expect(tagMock.getById).toHaveBeenCalledWith(authStub.admin.id, 'tag-1'); + expect(tagMock.addAssets).not.toHaveBeenCalled(); + }); + + it('should reject duplicate asset ids and accept new ones', async () => { + tagMock.getById.mockResolvedValue(tagStub.tag1); + + when(tagMock.hasAsset).calledWith(authStub.admin.id, 'tag-1', 'asset-1').mockResolvedValue(true); + when(tagMock.hasAsset).calledWith(authStub.admin.id, 'tag-1', 'asset-2').mockResolvedValue(false); + + await expect( + sut.addAssets(authStub.admin, 'tag-1', { + assetIds: ['asset-1', 'asset-2'], + }), + ).resolves.toEqual([ + { assetId: 'asset-1', success: false, error: AssetIdErrorReason.DUPLICATE }, + { assetId: 'asset-2', success: true }, + ]); + + expect(tagMock.getById).toHaveBeenCalledWith(authStub.admin.id, 'tag-1'); + expect(tagMock.hasAsset).toHaveBeenCalledTimes(2); + expect(tagMock.addAssets).toHaveBeenCalledWith(authStub.admin.id, 'tag-1', ['asset-2']); + }); + }); + + describe('removeAssets', () => { + it('should throw an error for an invalid id', async () => { + tagMock.getById.mockResolvedValue(null); + await expect(sut.removeAssets(authStub.admin, 'tag-1', { assetIds: ['asset-1'] })).rejects.toBeInstanceOf( + BadRequestException, + ); + expect(tagMock.getById).toHaveBeenCalledWith(authStub.admin.id, 'tag-1'); + expect(tagMock.removeAssets).not.toHaveBeenCalled(); + }); + + it('should accept accept ids that are tagged and reject the rest', async () => { + tagMock.getById.mockResolvedValue(tagStub.tag1); + + when(tagMock.hasAsset).calledWith(authStub.admin.id, 'tag-1', 'asset-1').mockResolvedValue(true); + when(tagMock.hasAsset).calledWith(authStub.admin.id, 'tag-1', 'asset-2').mockResolvedValue(false); + + await expect( + sut.removeAssets(authStub.admin, 'tag-1', { + assetIds: ['asset-1', 'asset-2'], + }), + ).resolves.toEqual([ + { assetId: 'asset-1', success: true }, + { assetId: 'asset-2', success: false, error: AssetIdErrorReason.NOT_FOUND }, + ]); + + expect(tagMock.getById).toHaveBeenCalledWith(authStub.admin.id, 'tag-1'); + expect(tagMock.hasAsset).toHaveBeenCalledTimes(2); + expect(tagMock.removeAssets).toHaveBeenCalledWith(authStub.admin.id, 'tag-1', ['asset-1']); + }); + }); +}); diff --git a/server/libs/domain/src/tag/tag.service.ts b/server/libs/domain/src/tag/tag.service.ts new file mode 100644 index 000000000..25075bd4e --- /dev/null +++ b/server/libs/domain/src/tag/tag.service.ts @@ -0,0 +1,104 @@ +import { BadRequestException, Inject, Injectable } from '@nestjs/common'; +import { AssetIdErrorReason, AssetIdsDto, AssetIdsResponseDto, AssetResponseDto, mapAsset } from '../asset'; +import { AuthUserDto } from '../auth'; +import { mapTag, TagResponseDto } from './tag-response.dto'; +import { CreateTagDto, UpdateTagDto } from './tag.dto'; +import { ITagRepository } from './tag.repository'; + +@Injectable() +export class TagService { + constructor(@Inject(ITagRepository) private repository: ITagRepository) {} + + getAll(authUser: AuthUserDto) { + return this.repository.getAll(authUser.id).then((tags) => tags.map(mapTag)); + } + + async getById(authUser: AuthUserDto, id: string): Promise { + const tag = await this.findOrFail(authUser, id); + return mapTag(tag); + } + + async create(authUser: AuthUserDto, dto: CreateTagDto) { + const duplicate = await this.repository.hasName(authUser.id, dto.name); + if (duplicate) { + throw new BadRequestException(`A tag with that name already exists`); + } + + const tag = await this.repository.create({ + userId: authUser.id, + name: dto.name, + type: dto.type, + }); + + return mapTag(tag); + } + + async update(authUser: AuthUserDto, id: string, dto: UpdateTagDto): Promise { + await this.findOrFail(authUser, id); + const tag = await this.repository.update({ id, name: dto.name }); + return mapTag(tag); + } + + async remove(authUser: AuthUserDto, id: string): Promise { + const tag = await this.findOrFail(authUser, id); + await this.repository.remove(tag); + } + + async getAssets(authUser: AuthUserDto, id: string): Promise { + await this.findOrFail(authUser, id); + const assets = await this.repository.getAssets(authUser.id, id); + return assets.map(mapAsset); + } + + async addAssets(authUser: AuthUserDto, id: string, dto: AssetIdsDto): Promise { + await this.findOrFail(authUser, id); + + const results: AssetIdsResponseDto[] = []; + for (const assetId of dto.assetIds) { + const hasAsset = await this.repository.hasAsset(authUser.id, id, assetId); + if (hasAsset) { + results.push({ assetId, success: false, error: AssetIdErrorReason.DUPLICATE }); + } else { + results.push({ assetId, success: true }); + } + } + + await this.repository.addAssets( + authUser.id, + id, + results.filter((result) => result.success).map((result) => result.assetId), + ); + + return results; + } + + async removeAssets(authUser: AuthUserDto, id: string, dto: AssetIdsDto): Promise { + await this.findOrFail(authUser, id); + + const results: AssetIdsResponseDto[] = []; + for (const assetId of dto.assetIds) { + const hasAsset = await this.repository.hasAsset(authUser.id, id, assetId); + if (!hasAsset) { + results.push({ assetId, success: false, error: AssetIdErrorReason.NOT_FOUND }); + } else { + results.push({ assetId, success: true }); + } + } + + await this.repository.removeAssets( + authUser.id, + id, + results.filter((result) => result.success).map((result) => result.assetId), + ); + + return results; + } + + private async findOrFail(authUser: AuthUserDto, id: string) { + const tag = await this.repository.getById(authUser.id, id); + if (!tag) { + throw new BadRequestException('Tag not found'); + } + return tag; + } +} diff --git a/server/libs/domain/test/fixtures.ts b/server/libs/domain/test/fixtures.ts index 3d00e7f24..50cf697cb 100644 --- a/server/libs/domain/test/fixtures.ts +++ b/server/libs/domain/test/fixtures.ts @@ -2,17 +2,19 @@ import { AlbumEntity, APIKeyEntity, AssetEntity, + AssetFaceEntity, AssetType, - PersonEntity, + ExifEntity, PartnerEntity, + PersonEntity, SharedLinkEntity, SharedLinkType, SystemConfig, + TagEntity, + TagType, TranscodePreset, UserEntity, UserTokenEntity, - AssetFaceEntity, - ExifEntity, } from '@app/infra/entities'; import { AlbumResponseDto, @@ -23,6 +25,7 @@ import { mapUser, SearchResult, SharedLinkResponseDto, + TagResponseDto, VideoFormat, VideoInfo, VideoStreamInfo, @@ -988,3 +991,24 @@ export const faceStub = { embedding: [1, 2, 3, 4], }), }; + +export const tagStub = { + tag1: Object.freeze({ + id: 'tag-1', + name: 'Tag1', + type: TagType.CUSTOM, + userId: userEntityStub.admin.id, + user: userEntityStub.admin, + renameTagId: null, + assets: [], + }), +}; + +export const tagResponseStub = { + tag1: Object.freeze({ + id: 'tag-1', + name: 'Tag1', + type: 'CUSTOM', + userId: 'admin_id', + }), +}; diff --git a/server/libs/domain/test/index.ts b/server/libs/domain/test/index.ts index 546b7ced5..f984d366c 100644 --- a/server/libs/domain/test/index.ts +++ b/server/libs/domain/test/index.ts @@ -15,6 +15,7 @@ export * from './shared-link.repository.mock'; export * from './smart-info.repository.mock'; export * from './storage.repository.mock'; export * from './system-config.repository.mock'; +export * from './tag.repository.mock'; export * from './user-token.repository.mock'; export * from './user.repository.mock'; diff --git a/server/libs/domain/test/tag.repository.mock.ts b/server/libs/domain/test/tag.repository.mock.ts new file mode 100644 index 000000000..d42d97e28 --- /dev/null +++ b/server/libs/domain/test/tag.repository.mock.ts @@ -0,0 +1,16 @@ +import { ITagRepository } from '../src'; + +export const newTagRepositoryMock = (): jest.Mocked => { + return { + getAll: jest.fn(), + getById: jest.fn(), + create: jest.fn(), + update: jest.fn(), + remove: jest.fn(), + hasAsset: jest.fn(), + hasName: jest.fn(), + getAssets: jest.fn(), + addAssets: jest.fn(), + removeAssets: jest.fn(), + }; +}; diff --git a/server/libs/infra/src/entities/index.ts b/server/libs/infra/src/entities/index.ts index f166b5927..6864a3f73 100644 --- a/server/libs/infra/src/entities/index.ts +++ b/server/libs/infra/src/entities/index.ts @@ -7,6 +7,7 @@ import { PersonEntity } from './person.entity'; import { SharedLinkEntity } from './shared-link.entity'; import { SmartInfoEntity } from './smart-info.entity'; import { SystemConfigEntity } from './system-config.entity'; +import { TagEntity } from './tag.entity'; import { UserTokenEntity } from './user-token.entity'; import { UserEntity } from './user.entity'; @@ -34,6 +35,7 @@ export const databaseEntities = [ SharedLinkEntity, SmartInfoEntity, SystemConfigEntity, + TagEntity, UserEntity, UserTokenEntity, ]; diff --git a/server/libs/infra/src/entities/tag.entity.ts b/server/libs/infra/src/entities/tag.entity.ts index cad4d4c97..a364529db 100644 --- a/server/libs/infra/src/entities/tag.entity.ts +++ b/server/libs/infra/src/entities/tag.entity.ts @@ -21,7 +21,7 @@ export class TagEntity { userId!: string; @Column({ type: 'uuid', comment: 'The new renamed tagId', nullable: true }) - renameTagId!: string; + renameTagId!: string | null; @ManyToMany(() => AssetEntity, (asset) => asset.tags) assets!: AssetEntity[]; diff --git a/server/libs/infra/src/infra.module.ts b/server/libs/infra/src/infra.module.ts index 34cd5f7c8..70b462db1 100644 --- a/server/libs/infra/src/infra.module.ts +++ b/server/libs/infra/src/infra.module.ts @@ -17,6 +17,7 @@ import { ISmartInfoRepository, IStorageRepository, ISystemConfigRepository, + ITagRepository, IUserRepository, IUserTokenRepository, } from '@app/domain'; @@ -45,6 +46,7 @@ import { SharedLinkRepository, SmartInfoRepository, SystemConfigRepository, + TagRepository, TypesenseRepository, UserRepository, UserTokenRepository, @@ -68,6 +70,7 @@ const providers: Provider[] = [ { provide: ISmartInfoRepository, useClass: SmartInfoRepository }, { provide: IStorageRepository, useClass: FilesystemProvider }, { provide: ISystemConfigRepository, useClass: SystemConfigRepository }, + { provide: ITagRepository, useClass: TagRepository }, { provide: IUserRepository, useClass: UserRepository }, { provide: IUserTokenRepository, useClass: UserTokenRepository }, ]; diff --git a/server/libs/infra/src/repositories/index.ts b/server/libs/infra/src/repositories/index.ts index 75347863e..bb49e336e 100644 --- a/server/libs/infra/src/repositories/index.ts +++ b/server/libs/infra/src/repositories/index.ts @@ -14,6 +14,7 @@ export * from './person.repository'; export * from './shared-link.repository'; export * from './smart-info.repository'; export * from './system-config.repository'; +export * from './tag.repository'; export * from './typesense.repository'; export * from './user-token.repository'; export * from './user.repository'; diff --git a/server/libs/infra/src/repositories/tag.repository.ts b/server/libs/infra/src/repositories/tag.repository.ts new file mode 100644 index 000000000..8713f0650 --- /dev/null +++ b/server/libs/infra/src/repositories/tag.repository.ts @@ -0,0 +1,123 @@ +import { ITagRepository } from '@app/domain'; +import { AssetEntity, TagEntity } from '@app/infra/entities'; +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; + +@Injectable() +export class TagRepository implements ITagRepository { + constructor( + @InjectRepository(AssetEntity) private assetRepository: Repository, + @InjectRepository(TagEntity) private repository: Repository, + ) {} + + getById(userId: string, id: string): Promise { + return this.repository.findOne({ + where: { + id, + userId, + }, + relations: { + user: true, + }, + }); + } + + getAll(userId: string): Promise { + return this.repository.find({ where: { userId } }); + } + + create(tag: Partial): Promise { + return this.save(tag); + } + + update(tag: Partial): Promise { + return this.save(tag); + } + + async remove(tag: TagEntity): Promise { + await this.repository.remove(tag); + } + + async getAssets(userId: string, tagId: string): Promise { + return this.assetRepository.find({ + where: { + tags: { + userId, + id: tagId, + }, + }, + relations: { + exifInfo: true, + tags: true, + faces: { + person: true, + }, + }, + order: { + createdAt: 'ASC', + }, + }); + } + + async addAssets(userId: string, id: string, assetIds: string[]): Promise { + for (const assetId of assetIds) { + const asset = await this.assetRepository.findOneOrFail({ + where: { + ownerId: userId, + id: assetId, + }, + relations: { + tags: true, + }, + }); + asset.tags.push({ id } as TagEntity); + await this.assetRepository.save(asset); + } + } + + async removeAssets(userId: string, id: string, assetIds: string[]): Promise { + for (const assetId of assetIds) { + const asset = await this.assetRepository.findOneOrFail({ + where: { + ownerId: userId, + id: assetId, + }, + relations: { + tags: true, + }, + }); + asset.tags = asset.tags.filter((tag) => tag.id !== id); + await this.assetRepository.save(asset); + } + } + + hasAsset(userId: string, tagId: string, assetId: string): Promise { + return this.repository.exist({ + where: { + id: tagId, + userId, + assets: { + id: assetId, + }, + }, + relations: { + assets: true, + }, + }); + } + + hasName(userId: string, name: string): Promise { + return this.repository.exist({ + where: { + name, + userId, + }, + }); + } + + private async save(tag: Partial): Promise { + const { id } = await this.repository.save(tag); + return this.repository.findOneOrFail({ where: { id }, relations: { user: true } }); + } +} diff --git a/web/src/api/open-api/api.ts b/web/src/api/open-api/api.ts index e8afa9343..5fbe6bd5d 100644 --- a/web/src/api/open-api/api.ts +++ b/web/src/api/open-api/api.ts @@ -536,6 +536,53 @@ export interface AssetFileUploadResponseDto { */ 'duplicate': boolean; } +/** + * + * @export + * @interface AssetIdsDto + */ +export interface AssetIdsDto { + /** + * + * @type {Array} + * @memberof AssetIdsDto + */ + 'assetIds': Array; +} +/** + * + * @export + * @interface AssetIdsResponseDto + */ +export interface AssetIdsResponseDto { + /** + * + * @type {string} + * @memberof AssetIdsResponseDto + */ + 'assetId': string; + /** + * + * @type {boolean} + * @memberof AssetIdsResponseDto + */ + 'success': boolean; + /** + * + * @type {string} + * @memberof AssetIdsResponseDto + */ + 'error'?: AssetIdsResponseDtoErrorEnum; +} + +export const AssetIdsResponseDtoErrorEnum = { + Duplicate: 'duplicate', + NoPermission: 'no_permission', + NotFound: 'not_found' +} as const; + +export type AssetIdsResponseDtoErrorEnum = typeof AssetIdsResponseDtoErrorEnum[keyof typeof AssetIdsResponseDtoErrorEnum]; + /** * * @export @@ -2420,18 +2467,18 @@ export interface SystemConfigTemplateStorageOptionDto { * @interface TagResponseDto */ export interface TagResponseDto { - /** - * - * @type {string} - * @memberof TagResponseDto - */ - 'id': string; /** * * @type {TagTypeEnum} * @memberof TagResponseDto */ 'type': TagTypeEnum; + /** + * + * @type {string} + * @memberof TagResponseDto + */ + 'id': string; /** * * @type {string} @@ -2444,12 +2491,6 @@ export interface TagResponseDto { * @memberof TagResponseDto */ 'userId': string; - /** - * - * @type {string} - * @memberof TagResponseDto - */ - 'renameTagId'?: string | null; } @@ -2558,12 +2599,6 @@ export interface UpdateTagDto { * @memberof UpdateTagDto */ 'name'?: string; - /** - * - * @type {string} - * @memberof UpdateTagDto - */ - 'renameTagId'?: string; } /** * @@ -10647,57 +10682,15 @@ export class SystemConfigApi extends BaseAPI { */ export const TagApiAxiosParamCreator = function (configuration?: Configuration) { return { - /** - * - * @param {string} id - * @param {*} [options] Override http request option. - * @throws {RequiredError} - */ - _delete: async (id: string, options: AxiosRequestConfig = {}): Promise => { - // verify required parameter 'id' is not null or undefined - assertParamExists('_delete', 'id', id) - const localVarPath = `/tag/{id}` - .replace(`{${"id"}}`, encodeURIComponent(String(id))); - // 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: 'DELETE', ...baseOptions, ...options}; - const localVarHeaderParameter = {} as any; - const localVarQueryParameter = {} as any; - - // authentication cookie required - - // authentication api_key required - await setApiKeyToObject(localVarHeaderParameter, "x-api-key", configuration) - - // authentication bearer required - // http bearer authentication required - await setBearerAuthToObject(localVarHeaderParameter, configuration) - - - - setSearchParams(localVarUrlObj, localVarQueryParameter); - let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; - localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; - - return { - url: toPathString(localVarUrlObj), - options: localVarRequestOptions, - }; - }, /** * * @param {CreateTagDto} createTagDto * @param {*} [options] Override http request option. * @throws {RequiredError} */ - create: async (createTagDto: CreateTagDto, options: AxiosRequestConfig = {}): Promise => { + createTag: async (createTagDto: CreateTagDto, options: AxiosRequestConfig = {}): Promise => { // verify required parameter 'createTagDto' is not null or undefined - assertParamExists('create', 'createTagDto', createTagDto) + assertParamExists('createTag', 'createTagDto', createTagDto) const localVarPath = `/tag`; // use dummy base URL string because the URL constructor only accepts absolute URLs. const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); @@ -10735,10 +10728,52 @@ export const TagApiAxiosParamCreator = function (configuration?: Configuration) }, /** * + * @param {string} id * @param {*} [options] Override http request option. * @throws {RequiredError} */ - findAll: async (options: AxiosRequestConfig = {}): Promise => { + deleteTag: async (id: string, options: AxiosRequestConfig = {}): Promise => { + // verify required parameter 'id' is not null or undefined + assertParamExists('deleteTag', 'id', id) + const localVarPath = `/tag/{id}` + .replace(`{${"id"}}`, encodeURIComponent(String(id))); + // 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: 'DELETE', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + // authentication cookie required + + // authentication api_key required + await setApiKeyToObject(localVarHeaderParameter, "x-api-key", configuration) + + // authentication bearer required + // http bearer authentication required + await setBearerAuthToObject(localVarHeaderParameter, configuration) + + + + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, + /** + * + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + getAllTags: async (options: AxiosRequestConfig = {}): Promise => { const localVarPath = `/tag`; // use dummy base URL string because the URL constructor only accepts absolute URLs. const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); @@ -10777,9 +10812,51 @@ export const TagApiAxiosParamCreator = function (configuration?: Configuration) * @param {*} [options] Override http request option. * @throws {RequiredError} */ - findOne: async (id: string, options: AxiosRequestConfig = {}): Promise => { + getTagAssets: async (id: string, options: AxiosRequestConfig = {}): Promise => { // verify required parameter 'id' is not null or undefined - assertParamExists('findOne', 'id', id) + assertParamExists('getTagAssets', 'id', id) + const localVarPath = `/tag/{id}/assets` + .replace(`{${"id"}}`, encodeURIComponent(String(id))); + // 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: 'GET', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + // authentication cookie required + + // authentication api_key required + await setApiKeyToObject(localVarHeaderParameter, "x-api-key", configuration) + + // authentication bearer required + // http bearer authentication required + await setBearerAuthToObject(localVarHeaderParameter, configuration) + + + + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, + /** + * + * @param {string} id + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + getTagById: async (id: string, options: AxiosRequestConfig = {}): Promise => { + // verify required parameter 'id' is not null or undefined + assertParamExists('getTagById', 'id', id) const localVarPath = `/tag/{id}` .replace(`{${"id"}}`, encodeURIComponent(String(id))); // use dummy base URL string because the URL constructor only accepts absolute URLs. @@ -10813,6 +10890,102 @@ export const TagApiAxiosParamCreator = function (configuration?: Configuration) options: localVarRequestOptions, }; }, + /** + * + * @param {string} id + * @param {AssetIdsDto} assetIdsDto + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + tagAssets: async (id: string, assetIdsDto: AssetIdsDto, options: AxiosRequestConfig = {}): Promise => { + // verify required parameter 'id' is not null or undefined + assertParamExists('tagAssets', 'id', id) + // verify required parameter 'assetIdsDto' is not null or undefined + assertParamExists('tagAssets', 'assetIdsDto', assetIdsDto) + const localVarPath = `/tag/{id}/assets` + .replace(`{${"id"}}`, encodeURIComponent(String(id))); + // 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: 'PUT', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + // authentication cookie required + + // authentication api_key required + await setApiKeyToObject(localVarHeaderParameter, "x-api-key", configuration) + + // 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(assetIdsDto, localVarRequestOptions, configuration) + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, + /** + * + * @param {string} id + * @param {AssetIdsDto} assetIdsDto + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + untagAssets: async (id: string, assetIdsDto: AssetIdsDto, options: AxiosRequestConfig = {}): Promise => { + // verify required parameter 'id' is not null or undefined + assertParamExists('untagAssets', 'id', id) + // verify required parameter 'assetIdsDto' is not null or undefined + assertParamExists('untagAssets', 'assetIdsDto', assetIdsDto) + const localVarPath = `/tag/{id}/assets` + .replace(`{${"id"}}`, encodeURIComponent(String(id))); + // 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: 'DELETE', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + // authentication cookie required + + // authentication api_key required + await setApiKeyToObject(localVarHeaderParameter, "x-api-key", configuration) + + // 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(assetIdsDto, localVarRequestOptions, configuration) + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, /** * * @param {string} id @@ -10820,11 +10993,11 @@ export const TagApiAxiosParamCreator = function (configuration?: Configuration) * @param {*} [options] Override http request option. * @throws {RequiredError} */ - update: async (id: string, updateTagDto: UpdateTagDto, options: AxiosRequestConfig = {}): Promise => { + updateTag: async (id: string, updateTagDto: UpdateTagDto, options: AxiosRequestConfig = {}): Promise => { // verify required parameter 'id' is not null or undefined - assertParamExists('update', 'id', id) + assertParamExists('updateTag', 'id', id) // verify required parameter 'updateTagDto' is not null or undefined - assertParamExists('update', 'updateTagDto', updateTagDto) + assertParamExists('updateTag', 'updateTagDto', updateTagDto) const localVarPath = `/tag/{id}` .replace(`{${"id"}}`, encodeURIComponent(String(id))); // use dummy base URL string because the URL constructor only accepts absolute URLs. @@ -10871,33 +11044,14 @@ export const TagApiAxiosParamCreator = function (configuration?: Configuration) export const TagApiFp = function(configuration?: Configuration) { const localVarAxiosParamCreator = TagApiAxiosParamCreator(configuration) return { - /** - * - * @param {string} id - * @param {*} [options] Override http request option. - * @throws {RequiredError} - */ - async _delete(id: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { - const localVarAxiosArgs = await localVarAxiosParamCreator._delete(id, options); - return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); - }, /** * * @param {CreateTagDto} createTagDto * @param {*} [options] Override http request option. * @throws {RequiredError} */ - async create(createTagDto: CreateTagDto, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { - const localVarAxiosArgs = await localVarAxiosParamCreator.create(createTagDto, options); - return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); - }, - /** - * - * @param {*} [options] Override http request option. - * @throws {RequiredError} - */ - async findAll(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise>> { - const localVarAxiosArgs = await localVarAxiosParamCreator.findAll(options); + async createTag(createTagDto: CreateTagDto, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.createTag(createTagDto, options); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); }, /** @@ -10906,8 +11060,59 @@ export const TagApiFp = function(configuration?: Configuration) { * @param {*} [options] Override http request option. * @throws {RequiredError} */ - async findOne(id: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { - const localVarAxiosArgs = await localVarAxiosParamCreator.findOne(id, options); + async deleteTag(id: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.deleteTag(id, options); + return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); + }, + /** + * + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async getAllTags(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise>> { + const localVarAxiosArgs = await localVarAxiosParamCreator.getAllTags(options); + return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); + }, + /** + * + * @param {string} id + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async getTagAssets(id: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise>> { + const localVarAxiosArgs = await localVarAxiosParamCreator.getTagAssets(id, options); + return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); + }, + /** + * + * @param {string} id + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async getTagById(id: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.getTagById(id, options); + return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); + }, + /** + * + * @param {string} id + * @param {AssetIdsDto} assetIdsDto + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async tagAssets(id: string, assetIdsDto: AssetIdsDto, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise>> { + const localVarAxiosArgs = await localVarAxiosParamCreator.tagAssets(id, assetIdsDto, options); + return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); + }, + /** + * + * @param {string} id + * @param {AssetIdsDto} assetIdsDto + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async untagAssets(id: string, assetIdsDto: AssetIdsDto, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise>> { + const localVarAxiosArgs = await localVarAxiosParamCreator.untagAssets(id, assetIdsDto, options); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); }, /** @@ -10917,8 +11122,8 @@ export const TagApiFp = function(configuration?: Configuration) { * @param {*} [options] Override http request option. * @throws {RequiredError} */ - async update(id: string, updateTagDto: UpdateTagDto, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { - const localVarAxiosArgs = await localVarAxiosParamCreator.update(id, updateTagDto, options); + async updateTag(id: string, updateTagDto: UpdateTagDto, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.updateTag(id, updateTagDto, options); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); }, } @@ -10931,31 +11136,14 @@ export const TagApiFp = function(configuration?: Configuration) { export const TagApiFactory = function (configuration?: Configuration, basePath?: string, axios?: AxiosInstance) { const localVarFp = TagApiFp(configuration) return { - /** - * - * @param {string} id - * @param {*} [options] Override http request option. - * @throws {RequiredError} - */ - _delete(id: string, options?: any): AxiosPromise { - return localVarFp._delete(id, options).then((request) => request(axios, basePath)); - }, /** * * @param {CreateTagDto} createTagDto * @param {*} [options] Override http request option. * @throws {RequiredError} */ - create(createTagDto: CreateTagDto, options?: any): AxiosPromise { - return localVarFp.create(createTagDto, options).then((request) => request(axios, basePath)); - }, - /** - * - * @param {*} [options] Override http request option. - * @throws {RequiredError} - */ - findAll(options?: any): AxiosPromise> { - return localVarFp.findAll(options).then((request) => request(axios, basePath)); + createTag(createTagDto: CreateTagDto, options?: any): AxiosPromise { + return localVarFp.createTag(createTagDto, options).then((request) => request(axios, basePath)); }, /** * @@ -10963,8 +11151,54 @@ export const TagApiFactory = function (configuration?: Configuration, basePath?: * @param {*} [options] Override http request option. * @throws {RequiredError} */ - findOne(id: string, options?: any): AxiosPromise { - return localVarFp.findOne(id, options).then((request) => request(axios, basePath)); + deleteTag(id: string, options?: any): AxiosPromise { + return localVarFp.deleteTag(id, options).then((request) => request(axios, basePath)); + }, + /** + * + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + getAllTags(options?: any): AxiosPromise> { + return localVarFp.getAllTags(options).then((request) => request(axios, basePath)); + }, + /** + * + * @param {string} id + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + getTagAssets(id: string, options?: any): AxiosPromise> { + return localVarFp.getTagAssets(id, options).then((request) => request(axios, basePath)); + }, + /** + * + * @param {string} id + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + getTagById(id: string, options?: any): AxiosPromise { + return localVarFp.getTagById(id, options).then((request) => request(axios, basePath)); + }, + /** + * + * @param {string} id + * @param {AssetIdsDto} assetIdsDto + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + tagAssets(id: string, assetIdsDto: AssetIdsDto, options?: any): AxiosPromise> { + return localVarFp.tagAssets(id, assetIdsDto, options).then((request) => request(axios, basePath)); + }, + /** + * + * @param {string} id + * @param {AssetIdsDto} assetIdsDto + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + untagAssets(id: string, assetIdsDto: AssetIdsDto, options?: any): AxiosPromise> { + return localVarFp.untagAssets(id, assetIdsDto, options).then((request) => request(axios, basePath)); }, /** * @@ -10973,71 +11207,127 @@ export const TagApiFactory = function (configuration?: Configuration, basePath?: * @param {*} [options] Override http request option. * @throws {RequiredError} */ - update(id: string, updateTagDto: UpdateTagDto, options?: any): AxiosPromise { - return localVarFp.update(id, updateTagDto, options).then((request) => request(axios, basePath)); + updateTag(id: string, updateTagDto: UpdateTagDto, options?: any): AxiosPromise { + return localVarFp.updateTag(id, updateTagDto, options).then((request) => request(axios, basePath)); }, }; }; /** - * Request parameters for _delete operation in TagApi. + * Request parameters for createTag operation in TagApi. * @export - * @interface TagApiDeleteRequest + * @interface TagApiCreateTagRequest */ -export interface TagApiDeleteRequest { - /** - * - * @type {string} - * @memberof TagApiDelete - */ - readonly id: string -} - -/** - * Request parameters for create operation in TagApi. - * @export - * @interface TagApiCreateRequest - */ -export interface TagApiCreateRequest { +export interface TagApiCreateTagRequest { /** * * @type {CreateTagDto} - * @memberof TagApiCreate + * @memberof TagApiCreateTag */ readonly createTagDto: CreateTagDto } /** - * Request parameters for findOne operation in TagApi. + * Request parameters for deleteTag operation in TagApi. * @export - * @interface TagApiFindOneRequest + * @interface TagApiDeleteTagRequest */ -export interface TagApiFindOneRequest { +export interface TagApiDeleteTagRequest { /** * * @type {string} - * @memberof TagApiFindOne + * @memberof TagApiDeleteTag */ readonly id: string } /** - * Request parameters for update operation in TagApi. + * Request parameters for getTagAssets operation in TagApi. * @export - * @interface TagApiUpdateRequest + * @interface TagApiGetTagAssetsRequest */ -export interface TagApiUpdateRequest { +export interface TagApiGetTagAssetsRequest { /** * * @type {string} - * @memberof TagApiUpdate + * @memberof TagApiGetTagAssets + */ + readonly id: string +} + +/** + * Request parameters for getTagById operation in TagApi. + * @export + * @interface TagApiGetTagByIdRequest + */ +export interface TagApiGetTagByIdRequest { + /** + * + * @type {string} + * @memberof TagApiGetTagById + */ + readonly id: string +} + +/** + * Request parameters for tagAssets operation in TagApi. + * @export + * @interface TagApiTagAssetsRequest + */ +export interface TagApiTagAssetsRequest { + /** + * + * @type {string} + * @memberof TagApiTagAssets + */ + readonly id: string + + /** + * + * @type {AssetIdsDto} + * @memberof TagApiTagAssets + */ + readonly assetIdsDto: AssetIdsDto +} + +/** + * Request parameters for untagAssets operation in TagApi. + * @export + * @interface TagApiUntagAssetsRequest + */ +export interface TagApiUntagAssetsRequest { + /** + * + * @type {string} + * @memberof TagApiUntagAssets + */ + readonly id: string + + /** + * + * @type {AssetIdsDto} + * @memberof TagApiUntagAssets + */ + readonly assetIdsDto: AssetIdsDto +} + +/** + * Request parameters for updateTag operation in TagApi. + * @export + * @interface TagApiUpdateTagRequest + */ +export interface TagApiUpdateTagRequest { + /** + * + * @type {string} + * @memberof TagApiUpdateTag */ readonly id: string /** * * @type {UpdateTagDto} - * @memberof TagApiUpdate + * @memberof TagApiUpdateTag */ readonly updateTagDto: UpdateTagDto } @@ -11051,24 +11341,24 @@ export interface TagApiUpdateRequest { export class TagApi extends BaseAPI { /** * - * @param {TagApiDeleteRequest} requestParameters Request parameters. + * @param {TagApiCreateTagRequest} requestParameters Request parameters. * @param {*} [options] Override http request option. * @throws {RequiredError} * @memberof TagApi */ - public _delete(requestParameters: TagApiDeleteRequest, options?: AxiosRequestConfig) { - return TagApiFp(this.configuration)._delete(requestParameters.id, options).then((request) => request(this.axios, this.basePath)); + public createTag(requestParameters: TagApiCreateTagRequest, options?: AxiosRequestConfig) { + return TagApiFp(this.configuration).createTag(requestParameters.createTagDto, options).then((request) => request(this.axios, this.basePath)); } /** * - * @param {TagApiCreateRequest} requestParameters Request parameters. + * @param {TagApiDeleteTagRequest} requestParameters Request parameters. * @param {*} [options] Override http request option. * @throws {RequiredError} * @memberof TagApi */ - public create(requestParameters: TagApiCreateRequest, options?: AxiosRequestConfig) { - return TagApiFp(this.configuration).create(requestParameters.createTagDto, options).then((request) => request(this.axios, this.basePath)); + public deleteTag(requestParameters: TagApiDeleteTagRequest, options?: AxiosRequestConfig) { + return TagApiFp(this.configuration).deleteTag(requestParameters.id, options).then((request) => request(this.axios, this.basePath)); } /** @@ -11077,30 +11367,63 @@ export class TagApi extends BaseAPI { * @throws {RequiredError} * @memberof TagApi */ - public findAll(options?: AxiosRequestConfig) { - return TagApiFp(this.configuration).findAll(options).then((request) => request(this.axios, this.basePath)); + public getAllTags(options?: AxiosRequestConfig) { + return TagApiFp(this.configuration).getAllTags(options).then((request) => request(this.axios, this.basePath)); } /** * - * @param {TagApiFindOneRequest} requestParameters Request parameters. + * @param {TagApiGetTagAssetsRequest} requestParameters Request parameters. * @param {*} [options] Override http request option. * @throws {RequiredError} * @memberof TagApi */ - public findOne(requestParameters: TagApiFindOneRequest, options?: AxiosRequestConfig) { - return TagApiFp(this.configuration).findOne(requestParameters.id, options).then((request) => request(this.axios, this.basePath)); + public getTagAssets(requestParameters: TagApiGetTagAssetsRequest, options?: AxiosRequestConfig) { + return TagApiFp(this.configuration).getTagAssets(requestParameters.id, options).then((request) => request(this.axios, this.basePath)); } /** * - * @param {TagApiUpdateRequest} requestParameters Request parameters. + * @param {TagApiGetTagByIdRequest} requestParameters Request parameters. * @param {*} [options] Override http request option. * @throws {RequiredError} * @memberof TagApi */ - public update(requestParameters: TagApiUpdateRequest, options?: AxiosRequestConfig) { - return TagApiFp(this.configuration).update(requestParameters.id, requestParameters.updateTagDto, options).then((request) => request(this.axios, this.basePath)); + public getTagById(requestParameters: TagApiGetTagByIdRequest, options?: AxiosRequestConfig) { + return TagApiFp(this.configuration).getTagById(requestParameters.id, options).then((request) => request(this.axios, this.basePath)); + } + + /** + * + * @param {TagApiTagAssetsRequest} requestParameters Request parameters. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof TagApi + */ + public tagAssets(requestParameters: TagApiTagAssetsRequest, options?: AxiosRequestConfig) { + return TagApiFp(this.configuration).tagAssets(requestParameters.id, requestParameters.assetIdsDto, options).then((request) => request(this.axios, this.basePath)); + } + + /** + * + * @param {TagApiUntagAssetsRequest} requestParameters Request parameters. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof TagApi + */ + public untagAssets(requestParameters: TagApiUntagAssetsRequest, options?: AxiosRequestConfig) { + return TagApiFp(this.configuration).untagAssets(requestParameters.id, requestParameters.assetIdsDto, options).then((request) => request(this.axios, this.basePath)); + } + + /** + * + * @param {TagApiUpdateTagRequest} requestParameters Request parameters. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof TagApi + */ + public updateTag(requestParameters: TagApiUpdateTagRequest, options?: AxiosRequestConfig) { + return TagApiFp(this.configuration).updateTag(requestParameters.id, requestParameters.updateTagDto, options).then((request) => request(this.axios, this.basePath)); } }