From b5d75e20167b92de12cc50a816da214779cb0807 Mon Sep 17 00:00:00 2001 From: Jason Rasmussen Date: Mon, 14 Nov 2022 23:39:32 -0500 Subject: [PATCH] feat(server,web): system config for admin (#959) * feat: add admin config module for user configured config, uses it for ffmpeg * feat: add api endpoint to retrieve admin config settings and values * feat: add settings panel to admin page on web (wip) * feat: add api endpoint to update the admin config * chore: re-generate openapi spec after rebase * refactor: move from admin config to system config naming * chore: move away from UseGuards to new @Authenticated decorator * style: dark mode styling for lists and fix conflicting colors * wip: 2 column design, no edit button * refactor: system config * chore: generate open api * chore: rm broken test * chore: cleanup types * refactor: config module names Co-authored-by: Zack Pollard Co-authored-by: Zack Pollard --- mobile/openapi/.openapi-generator/FILES | 8 + mobile/openapi/README.md | Bin 10427 -> 10803 bytes mobile/openapi/doc/AdminConfigResponseDto.md | Bin 0 -> 425 bytes mobile/openapi/doc/ConfigApi.md | Bin 0 -> 2856 bytes mobile/openapi/doc/SystemConfigApi.md | Bin 0 -> 2826 bytes mobile/openapi/doc/SystemConfigEntity.md | Bin 0 -> 447 bytes mobile/openapi/doc/SystemConfigKey.md | Bin 0 -> 381 bytes mobile/openapi/doc/SystemConfigResponseDto.md | Bin 0 -> 495 bytes .../openapi/doc/SystemConfigResponseItem.md | Bin 0 -> 541 bytes mobile/openapi/lib/api.dart | Bin 3736 -> 3901 bytes mobile/openapi/lib/api/config_api.dart | Bin 0 -> 3396 bytes mobile/openapi/lib/api/system_config_api.dart | Bin 0 -> 3372 bytes mobile/openapi/lib/api_client.dart | Bin 14486 -> 14781 bytes mobile/openapi/lib/api_helper.dart | Bin 3844 -> 3952 bytes .../lib/model/admin_config_response_dto.dart | Bin 0 -> 3474 bytes .../lib/model/system_config_entity.dart | Bin 0 -> 6857 bytes .../openapi/lib/model/system_config_key.dart | Bin 0 -> 3248 bytes .../lib/model/system_config_response_dto.dart | Bin 0 -> 3535 bytes .../model/system_config_response_item.dart | Bin 0 -> 4185 bytes .../openapi/test/admin_config_api_test.dart | Bin 0 -> 596 bytes .../test/admin_config_response_dto_test.dart | Bin 0 -> 586 bytes mobile/openapi/test/config_api_test.dart | Bin 0 -> 724 bytes .../openapi/test/system_config_api_test.dart | Bin 0 -> 724 bytes .../test/system_config_entity_test.dart | Bin 0 -> 663 bytes .../openapi/test/system_config_key_test.dart | Bin 0 -> 427 bytes .../test/system_config_response_dto_test.dart | Bin 0 -> 589 bytes .../system_config_response_item_test.dart | Bin 0 -> 774 bytes server/README.md | 7 +- .../system-config/dto/update-system-config.ts | 20 ++ .../system-config-response.dto.ts | 20 ++ .../system-config/system-config.controller.ts | 24 ++ .../system-config/system-config.module.ts | 14 ++ .../system-config/system-config.service.ts | 20 ++ server/apps/immich/src/app.module.ts | 3 + .../microservices/src/microservices.module.ts | 5 +- .../processors/video-transcode.processor.ts | 12 +- server/immich-openapi-specs.json | 2 +- .../src/entities/system-config.entity.ts | 27 +++ .../1665540663419-CreateSystemConfigTable.ts | 15 ++ .../immich-config/src/immich-config.module.ts | 11 + .../src/immich-config.service.ts | 97 ++++++++ server/libs/immich-config/src/index.ts | 2 + server/libs/immich-config/tsconfig.lib.json | 9 + server/nest-cli.json | 11 +- server/package.json | 6 +- server/tsconfig.json | 34 +-- web/src/api/api.ts | 3 + web/src/api/open-api/api.ts | 228 ++++++++++++++++++ web/src/app.css | 2 +- .../admin-page/settings/settings-panel.svelte | 97 ++++++++ web/src/routes/admin/+page.server.ts | 6 +- web/src/routes/admin/+page.svelte | 14 +- 52 files changed, 659 insertions(+), 38 deletions(-) create mode 100644 mobile/openapi/doc/AdminConfigResponseDto.md create mode 100644 mobile/openapi/doc/ConfigApi.md create mode 100644 mobile/openapi/doc/SystemConfigApi.md create mode 100644 mobile/openapi/doc/SystemConfigEntity.md create mode 100644 mobile/openapi/doc/SystemConfigKey.md create mode 100644 mobile/openapi/doc/SystemConfigResponseDto.md create mode 100644 mobile/openapi/doc/SystemConfigResponseItem.md create mode 100644 mobile/openapi/lib/api/config_api.dart create mode 100644 mobile/openapi/lib/api/system_config_api.dart create mode 100644 mobile/openapi/lib/model/admin_config_response_dto.dart create mode 100644 mobile/openapi/lib/model/system_config_entity.dart create mode 100644 mobile/openapi/lib/model/system_config_key.dart create mode 100644 mobile/openapi/lib/model/system_config_response_dto.dart create mode 100644 mobile/openapi/lib/model/system_config_response_item.dart create mode 100644 mobile/openapi/test/admin_config_api_test.dart create mode 100644 mobile/openapi/test/admin_config_response_dto_test.dart create mode 100644 mobile/openapi/test/config_api_test.dart create mode 100644 mobile/openapi/test/system_config_api_test.dart create mode 100644 mobile/openapi/test/system_config_entity_test.dart create mode 100644 mobile/openapi/test/system_config_key_test.dart create mode 100644 mobile/openapi/test/system_config_response_dto_test.dart create mode 100644 mobile/openapi/test/system_config_response_item_test.dart create mode 100644 server/apps/immich/src/api-v1/system-config/dto/update-system-config.ts create mode 100644 server/apps/immich/src/api-v1/system-config/response-dto/system-config-response.dto.ts create mode 100644 server/apps/immich/src/api-v1/system-config/system-config.controller.ts create mode 100644 server/apps/immich/src/api-v1/system-config/system-config.module.ts create mode 100644 server/apps/immich/src/api-v1/system-config/system-config.service.ts create mode 100644 server/libs/database/src/entities/system-config.entity.ts create mode 100644 server/libs/database/src/migrations/1665540663419-CreateSystemConfigTable.ts create mode 100644 server/libs/immich-config/src/immich-config.module.ts create mode 100644 server/libs/immich-config/src/immich-config.service.ts create mode 100644 server/libs/immich-config/src/index.ts create mode 100644 server/libs/immich-config/tsconfig.lib.json create mode 100644 web/src/lib/components/admin-page/settings/settings-panel.svelte diff --git a/mobile/openapi/.openapi-generator/FILES b/mobile/openapi/.openapi-generator/FILES index 8cf63e845..3409c3eb1 100644 --- a/mobile/openapi/.openapi-generator/FILES +++ b/mobile/openapi/.openapi-generator/FILES @@ -59,6 +59,10 @@ doc/ServerStatsResponseDto.md doc/ServerVersionReponseDto.md doc/SignUpDto.md doc/SmartInfoResponseDto.md +doc/SystemConfigApi.md +doc/SystemConfigKey.md +doc/SystemConfigResponseDto.md +doc/SystemConfigResponseItem.md doc/ThumbnailFormat.md doc/TimeGroupEnum.md doc/UpdateAlbumDto.md @@ -79,6 +83,7 @@ lib/api/device_info_api.dart lib/api/job_api.dart lib/api/o_auth_api.dart lib/api/server_info_api.dart +lib/api/system_config_api.dart lib/api/user_api.dart lib/api_client.dart lib/api_exception.dart @@ -138,6 +143,9 @@ lib/model/server_stats_response_dto.dart lib/model/server_version_reponse_dto.dart lib/model/sign_up_dto.dart lib/model/smart_info_response_dto.dart +lib/model/system_config_key.dart +lib/model/system_config_response_dto.dart +lib/model/system_config_response_item.dart lib/model/thumbnail_format.dart lib/model/time_group_enum.dart lib/model/update_album_dto.dart diff --git a/mobile/openapi/README.md b/mobile/openapi/README.md index dd6f5105358b03d1c0c7e557e3280f351805fef3..9eba59a176802db0523b884962515a2eb30228a2 100644 GIT binary patch delta 357 zcmdlTxH)8lwM2bzWpPPru5*4~T4uUqL8g{MjY71RR(fg)L`X|3RwE@pSzjMbonCH= zGEh-6Sf3_Pla`jdYlxPXf_^dBKwXFoP=ZSfO)J!j(t?!4l2i}E)<6^@+Y}IrY18C| z5|W$!C8YSQkgfAht;FJfpeWG&np_IH3em`_CmX5?b0dYvWJ6gIb7TcUsl^5PdBv%o Qz#zk7F0%CI1hr@;07gN5+yDRo delta 17 YcmdlSvO92twZvw2Nj<*J57mR106z={H2?qr diff --git a/mobile/openapi/doc/AdminConfigResponseDto.md b/mobile/openapi/doc/AdminConfigResponseDto.md new file mode 100644 index 0000000000000000000000000000000000000000..d617d40dd408e335ab621e7a9808d533ceec1bba GIT binary patch literal 425 zcma)&O-lnY5Qgvh6$5*y8_4!vPpjL5U_U6{mcqu(w2d~IkcOfIVYe+SGXSdsjf9B?;DD)Ezx>Q=2gbK4juwy`}d0s7xL9)~h* zC<_uVi*}?a8Q*ii?>%vkjAbpYD&l8i^rgBQTO~xVM=y=6C@h)AhtJ8%@{PP=aejVY zNf(4#S>porH|IMWGoxrG0{V|4RETk1)A9VdC+V0R*D|PgOu^_inUx#t{4CbeQ63WPg`9sE>fYv|8sA=u<03U;%8GTa{&9@w&E?^|nG z|JCexem0&=rbkC-i`k3mL4WY*#{a|Q|F1@uF+Tcdsmdj)quHTJm@NcUMD!-YwP{>y z_i2I{_eC`u&_^nYg06pY0{6Q_9^z7ISrf+pOlcpaE}0&0I)U*J_^EYnN z3s_exmbg1CQ8TJoG$v$81xMF}`lCV_Bi#<5KYd?vyI?3GYoO{ncJyXbhP!th?9c=L zTgP5pr|pi@cPO1)abV}gVyQf_uB<@-R2lZ9O(4x&k<@FD$Oc-Z!JTtxoTjk0EaIAV zlj%%;T;F>{SZ1t23?q)z&CJ4Dnyu5UC`R+?cyE6i_dXqNG;R^jl4TWB5H!SEq67hh zJ6s^LJl~-MWPt%!nyUne(`rK$No(uPfRA2#kd-(&c>=PG92bl|J$UmsI~3ha zPS-D>eP~euhAr~&ew=&nF$ZKUYiU&(KPMX>cs(`xTHQ>n5@Il*Ka8v?te7UpPsz&a zxx8Uw8;c<7g}bh7TfCh)L7X>Efv;>6jchGN>0!!B8Hdxe$jO zR;H#m^klg_2IB9QeZcDh`^qUBl+mK-KiKQP7Wd!9S)Si;NV(+sd3GzTH&o1oY<#+bPM*@XwL`W=MI&P z1fBs3GJ7T0w)w0YjK{Q`AIvEw(4qz%&5s!6a3ouB@6MF)ZFITEsffvmVB|#4%pfaMj5pGQD zI=hb(#JIOdvk|?fvMlNL3n%b?m&ik0D=iyBn_npHgVYt%uRHYyiCF{&x1ra9@T4P- zJtzjae>M`G${5c?uti})mcUrLCGZc1GDf;Rz;O1e;%<;YA#0$TCRViHd5EOgzjLrh zKk;8W*5WozaqPZh>ExONJ1>?i<%xA|4FaIruqSN-Y37=w-ast2&@wgdfwYP<##TdjeVoTliEUcy8I?u{-yqHZ64rg(`)8h8Tjeai=^L1pa@8@I6C61tB$RQ3qzIvAo s!Yjs~{pZ~_aBAl^A~0T&J=`ba%_5PO=Wn9h`ER6#M<&=tRQ07!twk zT)#eh&a-a@W;Yp~oHmJ@i7XCctsTM#JdZJ7OB~u{BiZ0ttphQkE)Ewjlv00bA>|QA zDVdXUOUe3f60EuPuKN>Cx{>nnBZ*$pR*j7LT_TQo_<(-E&R2-6heFgLT-qa literal 0 HcmV?d00001 diff --git a/mobile/openapi/doc/SystemConfigKey.md b/mobile/openapi/doc/SystemConfigKey.md new file mode 100644 index 0000000000000000000000000000000000000000..b142ab55954b6a2a00bf5c1d66995948a74454cf GIT binary patch literal 381 zcma)1v2H>!4BY({mfipi2|G-*FjNJE3L8Rt(Tkx`NgO5a@T7ixlUJnDF0B{q?DL(k zL5>tmblS6}tKRon$Uky8yUR2608N8yHWuZTaG+o`-J7`(v~6om5^z3p5**#;vzy?w zidMpCUa5o9E|p~xM;W%p6Mo>KkF_l+cgaO@917a96o$I1GQ2}d{e=OJ$A|JK3ge~* z!>h}3I*qlSzN{9@%}RUQSnl3E1u@lEojIu?VV>ak^42AFh6@nbv7@Xd>G3b^a3T-LfZXpm8E1g+lnOHp(_Ty(~g>_?N^TkMd z`owz~kVmJzEe0~!7f^6>^4a!(teD?ku3(X(*wdAJfD5~N(}92JzH|I+7U;Ex_YsBRVnpX zEu?HGFH%NnCFRBjzWd4KI@-RMvY!7oEC!QDM#32E^r>fHf=CYxRb2~FRSz+d)ZC^h zp^vBPdR`QZ!)jS>R}qjIYEJ_?*2$sjXjD^{A&1ZH<$nFo-I%OGXFOTQUl1P$za7to F5buoDo{0be literal 0 HcmV?d00001 diff --git a/mobile/openapi/doc/SystemConfigResponseItem.md b/mobile/openapi/doc/SystemConfigResponseItem.md new file mode 100644 index 0000000000000000000000000000000000000000..03b753cb6b11a46d418cb2b70962cacd075ab05b GIT binary patch literal 541 zcma)3%WA_g5WMRv1U@7-*txf-w8IOoqi3g|-s9;y45Z{cKI6+5-eYNv?Cx)voj1MDrq)(`Aej$`3+TQnbO*2^HX&FU0Vr_=sSGBuVH2R zMD=mBd>f1BoeEf0^P*TB*2~p)O<{?&b+5soh;`(R_4!zQNcqq9a=-cKE`37|Mmnnu N{<63P-h`)&v2S`Xr!)Wn literal 0 HcmV?d00001 diff --git a/mobile/openapi/lib/api.dart b/mobile/openapi/lib/api.dart index 3415c62afd79a807134010b86aa3c073cd9472d2..72d4c4fd0f7c2d9df7ae00e4c3c0e958b9c3ddbd 100644 GIT binary patch delta 107 zcmbOsyH{>Q2QzPRWpPPrZhUfnURq}Q@ke^qa8lMR?6QpDFW$vR)0CF2BY5)KL delta 17 ZcmdlhH$!$q2lM9D%-$TED|n7F0RTSI27Ukl diff --git a/mobile/openapi/lib/api/config_api.dart b/mobile/openapi/lib/api/config_api.dart new file mode 100644 index 0000000000000000000000000000000000000000..632133d2d83ced15f7d00e7dd9e51ae47632d2e5 GIT binary patch literal 3396 zcmeHI-*4MC5PtVxaqUBG51QQtdl=j%nVUFmfGzeC2gMKsDr22&b~34wr@C&D|Gh_2 zlI6zkLx%w!&*@ICm!pf(<#0MWg|qWNMiHEj;bJ_6KgXx% zXMaE9WA5bdi7<3{(EH_}<(6`;w81#lCQfCB11MBsF^Mt5B`S)lwOr=f&`t}Ht;E{K zc|ozkcZD=;uEdg`xiI`sXe>K7cDMOVD@#IAEE5hWQ;Ei#+nvEOsnEumR&zc^3!&oQ zp2o98$e`ZaSop7fhI)dAVP0Ag3SLd86KKC2O`*^Ab7FnF zwuH;JOWp{QTJf<23{qi~1zI57T%yexEWGV;yt9J8;PYzl2qWoN? zy3++=*J|cV2dLXVfZSlpK>8lU2{Ke6xa9~{O}U^2Ltxrpr0NUX<;_FEJ(`Iu_!-~5 z#Tnz9YfCS5vMwzHWbXxpBEsC;)_ZC8s%an)7@ zLbYzzRqbukuXdVA_M{yxb6UeOs3Oals#{mra=L^b& zR+*B3ZXi}d($_f``XN*3RCYZcbSmtLP7sa`!O`=|@AU3Oa>?3(mbxWLfjMvV%1zYh z!whpb61yBlWh#!n?AOuj#hTF8XS*H*2ZQS+bN}@MdAPW%OHBwC&3iQUpeYS^>;hIi zK71%7>_p|@>8}y2`20dJO9;j0r8MMgmr@2r_H$Ust)XC-ls)emFv6 zTODx4F@G*J1qwZR!legUX?Ew)R(F&>2{W>3P7tON`&u`92{N8iWbz{po#J!?Pxdz_ zg7zLxwj-xnu;*(amq#ZwwoJ$kL{0kLsyn&sxKW@p&ga;U~BWLU$V|#0(6# zVb`5|pw~s72*PH0Taq1Ic~2gQ??}FR{2h({A1dA!d7jCczf${etyb-C)ph30_owUs unvhKbZr=zG3EJ6D$~_mvLmyitcQlGhecqC(9~Z@si(>m0|L-n}`_^Z@pJ)mI literal 0 HcmV?d00001 diff --git a/mobile/openapi/lib/api/system_config_api.dart b/mobile/openapi/lib/api/system_config_api.dart new file mode 100644 index 0000000000000000000000000000000000000000..e228c7199f5bd5e31493e0718cc4e7b2cb24a85c GIT binary patch literal 3372 zcmeHI-*4MC5PtVxaqUBG531b-dnnu{nVUFmfG+kD2gMKsDr22&b~34wr@C&D|9wYN zlI6_aLx%w!&*@IE>(RyNayT8G!rA%TQ3R)BxEN32xAE!u z*&mPim^=AvA`BfK^nN~Qxuu*dZ7@!?iBp;3018!DOk&J%iHf3XEtk1Aw9`UlE3vk5 zUQlfCV<8QjE3xEfE)4$@8q3a&-EBV8%92nN%Y*~URHE_bc4x3mDztH?)tryfLa6xp zvv{@;#xn&#+I>Fyu=MLcyEqbOPfNzLT_gLzJJZ zRCl@{>{`uy=>T=}2ap>~8BX7cI6($1gthFuswNk-;NngDL6W|tUEVwh+^Lz!f}io- z2b?joxwiC5C+pHOfc3FJvz|D z@jo8*j(yp{PvXW;*E#wON{24GYbliQZ+h92>XZHxhm-S;{|?|FipqD_&vvzF6jyCk zAXMvSUDe(u{c5L~WKY`BGN&~hgDSF&NxJpb4_vV8I!x|e6(v!cvrv)dl=N|fI1k7Y znq*4Sxgl5yN&9#fdLUEiR2DrRaVqSIP7sa`!BO(c@AU3Oa>=TJmbxV|ff;V}%FWN{ z^9*x00J|JTWfG3D?AKB1)tb=OXS=>|SGwyT^Znfdd6c-5OHBwC%_lVVSSby6>;hIi zGJGf`?BwL&*)I{S`20dJ%t{jvy$cAE-0*qNL-J!ua*57lWh5~zc~9C1-aE-1aCbNy zz2^YzCX~q2|M*(r=bQ%v6F{DFww8*u^l|0-j7co0Mgmr@2r_H$Ust)XC-iKvejq|( zTODx4F=s9`1=>7$!lefhX?Ew)R(F&>33IV&PLQDz`&uP?2{N8iWbz9Royv3qL-sc( zg7zLBwj-xhTk70kX&{$JCp5N9$PGbF^xYaex&C&I^&ac{Y{2n(Cp_UNxC!ER8z{sK z4EJEyo%@?Q7kMHG8CVwiErAYByblk-cO=$4@{U0N56y0iJkR9JU!DCAR;%{6syH*| x`&03MO~pNP$NJx-W@kSg_gn`}>n&nC>O`d)Z;8^6YvISWuzhF$ch|yw>n~3_UG4w? literal 0 HcmV?d00001 diff --git a/mobile/openapi/lib/api_client.dart b/mobile/openapi/lib/api_client.dart index 5e30d68e1d03327d97f3a3c85919368310855bec..e1e6fc55265919826ae358e624175230840a39e8 100644 GIT binary patch delta 138 zcmbPMxVL!293B4P%HopLT<83}w9Itx)XK??iXs?d{_^@f$m%A4l$U3N2uxnDX()j# d7nE9Dke^qa>IpOzWEgHqMp=c;b98=j0|1-vHCq4x delta 12 Ucmdm6JgsoU9G%UFb$@XK04s|ISO5S3 diff --git a/mobile/openapi/lib/api_helper.dart b/mobile/openapi/lib/api_helper.dart index 7db37768c4fd14966fc090f0a27280abc29c9387..94e878289ad6d335b0529e41bbc810b01c8e232a 100644 GIT binary patch delta 58 zcmZpX`yjVLmP0qVvbZEQ*Ev5gEi>IawNg`|no9u)ic(8Ti}DoEWH;YqS7QeNTJ;o; delta 12 Tcmew$*CMw;mSb}#$8$CSAl3x8 diff --git a/mobile/openapi/lib/model/admin_config_response_dto.dart b/mobile/openapi/lib/model/admin_config_response_dto.dart new file mode 100644 index 0000000000000000000000000000000000000000..240ac1ce43e16f7519e4a81bdd4adedeb07fed39 GIT binary patch literal 3474 zcmds3ZExE)5dQ98aVd&e0Tg-7ry;4`28}Z`ZDSx!2MmTG&=O^_l}U}HY8a{i`|e0d zRvNim_hrBgB$CK`zvnq}G#ZRXaQW$ecKX}&c6xVtKfQwMx9_JRT+QHib`KwBS8uQX zI6^U!e4R3F$7j)tvjKgIl~kI?nbL743VsfitPD?MUhp-SHf+Adx>VZsYOrF%wk9qs z8*Bb&B{Zr_w#NS@)A+Pp8#Jy>zj~siu}s=nr07s&f@`O4dYu(QauXLyu29V8OvbzY+pTKwmzElY-0oxLl=ia5z@kn zTgaKo->8(MbEn@ThX5ma!i`~iOAW^!=G+M5!~&mT)TsOHySaX}TF4bxpXSGfu7q_-eS}Rt zF{r@BM@Yl4nsoqQ>AKKPBubdEPf8wmC=gdL{HZjR=13N1@gIjZE2-|5A>rIY%cn1V zYlwZd;;phAUYt9bx`qV7A#y9{$rqjiPBw~Nq{41t$+rqCzvP5DspKgY^Luk=WDfI6 z2=DmBN{X^XQ$ixI3(gD%his0ts>^2%=mC29)b-c|<3691NO+V=g z$xL&SU|3^7KJ`X}uJK#51_8;N#a(c+2SB~wpI^|L@kBf)aQsemnPvCelE3uBaS~7w zLujdcazyi%esv@}_gVX*=vxBURCq5Cr~N@pUO|Do*I3v`r0_l;+`wLGwPDKwM zL-XdWxXD^K1_w+#KMvH6K+)TsCSYF$nk$|8f52FC9CRCjG5oL(u3=-}r$YmeCY`@; zokM#yijkGLNS7^tW@5+oJ;hbMZMquKm*A$!>F2xa%ta0honHM-m$7Jbujq!x*t5Q2 za?{-A`KZ;X+OiCj$4?p^u2#xlfi}YCxJ2xtt6P6ZPQ5Vd)+Z&N@2EsKS+a=`>p=>k z>k!o@q(%b0m+;6__>Rdl!eiviB9zW}Ak!xk+ zqSW^|<`z=N-+UD(3u)}p5yUxBW};jvEmwJjf3J^@l0sT5ZZ_7b<@cq|^7|>oxQ+O9 zB*ZC9@J_kPXe*@9YAt5FC+8yfDkdrd$?pD;I0;c-UbsbWqYr8`>RJ9Mi%QYP!wGcj zE8(t~e>envUPTVUe7Sb-hFH;45Jlh6$ z1UIb`Wvw(Df(sC^P&T*z*boAvn)XEeix<79c6rAnH_qhxUbvD!480WwyIz}t`{V_F zjad813(t8z@Em`Yt8baXNTeIUIZw{O^Cw$saAb#Pxt4{9|G=7W68xt~PM6=727=gbiQ}QG)^aRei*TUO0 z*qF7e4Ge{bUN=H1RI1m7VABW7`y){zt=sXzkfV%@3Du|CRz@K{rNb{JmU1Pazmte> zztDs*JY*@@1L0Nal=yn=&v9gGvPwDCwT3lv(c7ri9h+6ai6}`Xw21 zo82p7hkGRy82D>}+tmuJa*b3{C~2V}_Kt%Z$YRR4VS_$Wt=9KvQWq*rWwIDj*tC|S zV}X>+71j3zi9a|JgLffo`vCu^1T{Z9G)I=kJh)VG|A&xS-b5@z5}4O+TzhX=1l(30-mgn9^^ zUvfhBP60bR7X^0S*2>i(#Ns~r(r|CewY)2kc||4*t40qsGB|>avQ^=H_S)q?o3lMB z42dZLfOJ6-5cECDEdpb@T)7V3C=iB$dATMq)6rTg8H+dBx04>Lx8{cO9Y43(Ab_gN zlwyId63H~>oYA3V3se3i`H$=!s9Z@G8`kC;i~5u2;`45B2GUTIWqN!<80hs#tt{W3 zGEb*u^f0QcN7wj}{iN-^lP2t&ed&<$AUAzu(e!6A;uee4@p0*ems&q!)of}{M)WOL zw!Y8jdDOFtyze{ydB(Fa){F^#N@=o4ptriNm56Ji6%FEA0OYL*${xY8@Pn znpkb4cJvQ`>r~vLr64T{M|EYUno7L|I*w(9tve;qI9cdV0VTPzwHWfD zux`5n5_m_kpD>!>1_iaMWRyy$$`l)F960U6K)(0!LYHcJrwmEp+!z{ofAX;IzC9Xf zn?@Z2P`er8l<$$445oeRHfE4z%ayv1ag@89M-03hsdOI1gHQLcanfK!`W?nDX+%m6 zWyo|fd8tGb4$8gEFRC>EkC;m$e1`cAMoV(+fp!RLTc#qF15PN3QQ%1QeXA1Xh`JW< zJB%%w;eskqE$2NGdVt`ZK=jB-fdQ4;o&F{eoy|Z5beoKWiao`jx1G~nZLQi}4d$RF zbWg21d&Tdcfq>n;RMcPI8mTSH^irAONdy1@1WoJHO5Rf)>f8U9o5)(sKh#SG@&Ai0 zCDOIekF^&uG^54l*D7Bx5~&3cPi~!KiboUx+B#7bB~QcvwI|`_!V94Jf5zyT*H0Pf zUYrznGWkHKpI{a}S5dZr=nG6H=`~>U8R(^ls<^q|`T69zVzVU{wTCmwGc{@-StuK9 z?BU+1^Jeq!Nn}5+bWH+#;{%Y|%UL~%`Rp^F(cw9FjeA*B<^mk}x449dkqFE66~;k4 zKca)cbnBmTs5QS5H~@g}^fcZHWI{}aXlO9j7`4PhRZ2OUEJEk;=525Sk3-hws6dr}&+3F5-sS2UCR2pJCE>kk_7A$-@&utxS~ zq>aT3f)4k9wTveQ<45mI6a_bE;tJ(Zg7%k#t@Qc$|3yOIwKpcVh|k-E&!fxVq}<=8 literal 0 HcmV?d00001 diff --git a/mobile/openapi/lib/model/system_config_key.dart b/mobile/openapi/lib/model/system_config_key.dart new file mode 100644 index 0000000000000000000000000000000000000000..1154864bfaa09d39851d4fbbb422af7c5f5d24db GIT binary patch literal 3248 zcmb7GZExE)5dQ98aRG+L0X%u@ry-r|292|2>yn~z1`LKFFw)89Dv=sV#W0He_uY|_ z?8r*I1&A$+_kPbk$MJYD9>c}`{Pz1_W;e4h7xURATwQ;fjo|VYZf@uB$L;0y)n6wh z#*!aWVcg_x^yAwB|CB4G4aS)^aVB#-he}lz(-?CsP`Od_TNb4@u2)0k8?m)1n(nenFj2s91bFxvX6ivRBY6bq3gJjv@ zs=(p_4cpD65;@#C(@J_XAM_&{LqU*8edu&}7@oo8eK6`p+vDG?R^3A?vy(|3ePS}0 z^yFYpv#cm_6_dazr2+6vtu$!Sy{;K#dX$|oD|CNSCib11d7@*&62CJR!9<}Vm%%S8v69$?Q( z?jd6-ke@rE?-%5a&}g!ee+YFC&)rh*zui@9|81Qxzgqe{>L5B!r;r(4czC5-8gl|v zKs*DnBRk)gI5$FBqL%{A@GPPxQX_|ZGg5ouf*s^Hg`8erb^ZNK*dD>R2d#5h8NCr) z+zDhNw>Ww_^P}Ai6T!908LtP%7nCKJPi8P=i$L?}+xF%S@b5_48|`LA>9&nYGO7&> z!Po2C9L?cf0+^eF{fB?(%ak&!L)0po-8m4_x<4G6xdpm1 zq+8fC2cB<0e4Is=TB}-#R>z@YSU_qNWZ7Ra@bXPJYVUV_@B(AS4 z?+h}mb23(zyL$w`;1S{|JD`}>g&#Iu=XN&)*S3>5jGNi=8}k+m!U1Jiy1XRoa2oAy z3j3Ur2#fG+)t}c*?FJXodI?0yl=ED#Ui2@jhVz%Sd-nT_mb&Zgh|T>4f5hf)86L6O zEcqk$>NUDEmSQGo1)Wphn?G*dN!!@{zOaO2G{c2iReS^YC?B5QtFMYM9&U^QnnVX- zV9((@212+UH9lTh47^}CK4Shb+jj)o939bqmyA0kN7Gi5Kf}2Hg3wW!?uhpt@0x!B D8nr~h literal 0 HcmV?d00001 diff --git a/mobile/openapi/lib/model/system_config_response_dto.dart b/mobile/openapi/lib/model/system_config_response_dto.dart new file mode 100644 index 0000000000000000000000000000000000000000..23020930a9dea595cf2f455e7ca227c750572fbc GIT binary patch literal 3535 zcmds4U2hsW6n*Dc+*DPVNMzXNsY(iM)sR)wHb_bKVTI7h37mV7f6*7wfR7iTSgRda2u%x1=BGgZiQTI;ow(@Yj}DYfrZ?`pX+);DU1Vk5RL zTdjR&xsAfvqpza!-6-rB&6-Lh?<`+U| zFMrAMg|M#Gf;&%UMrI|oSg90$7p+!a2%3gPGkT`xx6-ZPR9<>R4=tiDaJZFz zZM7poT5f1`KzvLp1e?ngdIy9Own^j{%B4sS4yi{WOB#(k*n2!dSVSL{^RJq0yho&c z)_`h|d}F>!Yk?26CdL$;p%va(>h+T0Ytu^LCwsLM~%+WoSXK@(kU& zFjFp1TsXw>*CCVuVhL&tu|gD4B_Tj&8{(?gi6Yh|)IlW81vf${^}a?)WCO#B_y^jp ztwc`=k1t6}vEl~t0{s{ks>wrNmWnsVOL}o0WJ(a3g>6syLgruOlrZ`@b~ROg zOB1;@fPNyGcG~DuM)R-c&e$CC%9-+b0!p=7!IaXVKp}-gfY>`K!@&HJ)+alDi&ac#hriGmdnroa4-xM|6ER zyh+=DnH|MX&E1HV=%^R?^lx3Eqj{1t6H6B8@7?hjtQ?n}15V0n@i95sQ=r^GkYTd3 z6C3lqk+wWm(*i^H3fTwhGOV?3J~`s)%%3u{$M9PE=4^INU3SO85sq=xe@#!ERei!v zJM<9d_}rR}m=qYCG!uoBoyV&Du}!RW>%xbb)O>#RN8^YWHym5$DsxnHd`$eFDu)&r zELXn9ET()?dN3QNEsIvJ8GyJMYu9T;6B(vVqibj;NTXKFee;*}sat01b5g08PUDEE zX-2?#aB>h&;4ihj;p~zMxO1w01uJ3TmbIx5{sX+}^Bmc+p$Xazy z)N6q#eprf7vtBIKp*hF{Fy7P7X?LZ{(5qPFOIkclQM7$ov1+$fVN-rn(p5?QM1h07 zH1MHTuD>ZXU>ilnFiU!I7F7LR)wg;+sWw!1y@u@Z$^#{UW#WBjqx=F(++uv^J5ZC; zC=KQE48_q8ExrS@QP8Y`EzGdSiy3!y;0#MN^5&m5Hhk9^^<$6!KVEUwcKFq})!DW4 zzOg(10pYrIk4pwe$=s^W>TtKf(T_XGa*eHtB;2PQ;^$5i@r2t`DqOO5${%ph?@Qi) R2otWlNQVCkE?O90e*$!5eaQd- literal 0 HcmV?d00001 diff --git a/mobile/openapi/lib/model/system_config_response_item.dart b/mobile/openapi/lib/model/system_config_response_item.dart new file mode 100644 index 0000000000000000000000000000000000000000..f52ea6eaf0f43f276c6c4a58b5795345a78c4613 GIT binary patch literal 4185 zcmds4TW{Mo6n^)wI4Oc!!Bl0<(~!<-i`E^|OAMr0fx$2YMy6x7vZ#`D4I|Be-*-sK zrenL=`Z8bvVoN;t+jr>WGgmv23Pc9@cR7C zUk6}g&KF~0T<;`%deY%nbyI00^NBY3M3wTC=GENFaV|?ala))`cQvcEaXTeMxe!a6 z*K?N}`E{-g#49nwXDtlAoirB8jomF?=*l`#xm=B5P)(FHUfk|9R;o(cywueV*xXc9 z`ES4G#Z(yE>A;*LJt1AoO4KUD|Fce~D2283acP~Lz0%c0-CRmr!>ByR3Eg&xj**B< z>E@=gB&d=L8t&sCk@D^qGK1hgu7tG`wdo)?RW{?Cq&v>P$mOo%Z=#&<tFRoxI<3GY$F@8ptTCg$_%F|*FDs=aCq1b_CXu9lEcLZL3xKD(a zo`&!HB=58*`ltKk*nQ6x9|UIOXFfQ>CwDwYKLrLPsxbwOw4|I3iM2E_de-P1dM|+= z6EJicXog7VD5Yv%VzzjY7Rq{9iF&Ozass6DAZg|WrqAX)mpL%gsUiowPW703FC6TJ zr03ojU#X^Y!N<02a4Yd1@}$DC_M9jbw5g|O$qHfJR%MfbLkxTfpkLX!kw8A-@F8g? zYC;rF06+b~3LH^d%V63FUT7!j>8a=O){rX9Sl3Z8mBkl1CM+xVU5%An(zRS_$RYVVSxbrUr7eK960MLhSX+#sKL zr41%m%UU+wb(U}~OwU(9?|Yd&Scgt?&54a`v1`(BHIyw!<9qbn6%OjnNBnU1E39 zM3h#hcWsY`SvJz*0^!klpOxV#6q;dyRdXw{U)a0E@0qd~)c&k?QUCK`CXETb;NfD{ z);1R{5JP|UHmVoLxGp0Ci8^kLQB9M_s(C^=tW1o@0m&lCkaI?O_F|&~jg-?IUec!S zl-eh+#czhDJOIZP_U)|_xAV>#wJwKDFe*k%a<95}YsTe(A3v;yFIz8EYad`UQiq=a zr?k4Q=fD&k^8G(tYEZXzWpQejZEZ7tQ_{9k{Y;I$!p!$XRIvwYjl>Oupa;F?;ExCj(t!)CZqJXm7iVZ4#4I8M6Ow(khHOAChpB z@7!~8qbP_Xh+p#T@-f|{+c-}XSgoJZ2_zY8vK*eXWW9R5aF{3WORnv5Hocw&zNA`6 zjjWQIRn*`dI?)*{88+CWu#?}KS|znTtKiKi-y5bn%QU`sq@7&w*6oUGcUsiOHP`06 zypY0JE-a(c8B#^4d$~Dl){sEM8Yyar*&P?`=86?Nu1ydO86i}>c;hwBr9#0KMQ%;~ zufxS3#G zyilIYk_=@f(e`wA)~t~V9dER%3Fdb~@w*#d?1XiU9Wz2?MDZbNoN9wg7#Y(WhpnU8 zKZr-?<4{h3U>arzbP`yUtyB+MRkGfoGn5n)ui2NDCA31S!4Q5J0C6g*L<6#4r9hZp zpbmyJMughx&J3bpbO9KR;7`1l&)(j#@IbesC?o)VNK(*!1Ad2KEG=yP9=1o=_NXD; XUKM@JK%=O6mSnxeA%vl>{}1dJh`hnE literal 0 HcmV?d00001 diff --git a/mobile/openapi/test/config_api_test.dart b/mobile/openapi/test/config_api_test.dart new file mode 100644 index 0000000000000000000000000000000000000000..ff71ce99bfae6bdef18874cd459f8e71b77a4824 GIT binary patch literal 724 zcma)(K~IA)7>4isiq8{UGKcPDHfLOfnI$@si3d+j2Oq2oZEcH&nEiKKa8c(>cA%v2 zyx;p!g1`#`m^~)(>20)(R%P8&^RZHU6xnkgISw6d$D%H9nsZHFs; zIGzfw4dsTg%oehop=#s$U|GpHYElYbSj=oFCl_ZV-B6`HuX6-n()5KEI2IB)l`OE= z(0#Q(`GXkLCUDCRaEB6rd!oS}3fu^uvtlNh_a;~d6=l5FhYt_HVmafq1lIbe2-9!6 z*PnQnDU_&X&p$BEAHdffH)&p*TH)nGqYYMFiz*=O@KQ4QTBVC-**82prIqfq5 literal 0 HcmV?d00001 diff --git a/mobile/openapi/test/system_config_api_test.dart b/mobile/openapi/test/system_config_api_test.dart new file mode 100644 index 0000000000000000000000000000000000000000..db9e8c00236ba54a22581accaccb3fd0ed391b48 GIT binary patch literal 724 zcma)&O;5rw7{~AX6pyDtf=oFX5hHQKkSI%V9y~Q=dp4?DTiZ^v7{0sh29YQv9NMPM z@Bh*#2)rPG=}QuyKSYaYIZdJvX7k5r0AUP^IDw})oX=j*Eau3&j4Cr34KGKYyRw1{ zg(Me>?}GiBwRR(6+E+0&rX)?Des z@>FnbC^v*yJH47E08z=O4B92e9>YuCv0$Wi?bcJhchX_Uk2Giq>~8?W9lHLwy{bELvPeqsw(X5;+;w3+P! literal 0 HcmV?d00001 diff --git a/mobile/openapi/test/system_config_entity_test.dart b/mobile/openapi/test/system_config_entity_test.dart new file mode 100644 index 0000000000000000000000000000000000000000..c0c79c4e63952f382fecd8978175e8a9260fa41a GIT binary patch literal 663 zcma)&&riZI6vyxTE1oAHL8hDxghb+oAyJmdJb0>gdmELloo$C%4F9{YMNTf!!@9oZ z^ZoG_hCvv@@;OZ|@8eDUv`pg&R_lj&3{e7`B!$N$TCZL%2y^6Z&b3`kC)d-!jiQuF zBP*0ShDi}jt7g(zbxXS?8_L=H6PYh(zE z9@%g-4+t)`YMbG}1@u6JG5nZ%+pX0?maxZ;l;B&DRQU_o9YUjNLv1^FCFb>{=BAUG mFXl8yM`j{$uj?j+uo6uUh=m+*ALvCodm!488%%E!FD) literal 0 HcmV?d00001 diff --git a/mobile/openapi/test/system_config_key_test.dart b/mobile/openapi/test/system_config_key_test.dart new file mode 100644 index 0000000000000000000000000000000000000000..47271c7a781037f0ba7c85625bea09e604d6eaa8 GIT binary patch literal 427 zcmZvYK}*9h7>4ip6`!YWP#4`vHUx+5WT-1>J$MSC{n`cFBz{SiBKz+q$}ZYNzVO2H z)551?-Cw9*b$nIk$o6dLobz{&EbnNfR@Z)XGt{v!bu7z>NsCB Gc4XfmDv=EU literal 0 HcmV?d00001 diff --git a/mobile/openapi/test/system_config_response_dto_test.dart b/mobile/openapi/test/system_config_response_dto_test.dart new file mode 100644 index 0000000000000000000000000000000000000000..9a9d90e3ac7ea8a966cc9db5ca02c54e0689ab1b GIT binary patch literal 589 zcmZ`#Ur)j?5P#pNIG+XyGUdrgNF;6;5@iX_2cJsaZlk)jwe2*-@ZG%@c@iFay-R-o z?vf-*5}3af`Q<~l%GUEDOJT8m%tnyru*wT~%G2fI^@3m?c~^1cW|Q&tB=VzZq%z2A zWmqj*yoFx&7AuA=b|~HGIEzjz<4!Vo`^|Tj>E1DepS>_dm%O8@xuM^ou_SKnX?m%o zbzC|oDl!zcKojWpq**H@TGlGr5X^14WH(o=+;U^1=#UX&&C3to;H}aqxfTh%vEMqL z{)0HQF7f3E@TPvYKqrCcopspVD_M(XjaE}sOr46pl&GNNLJo%T69EvZ7LvCh!Y(Dk z>>PJ6oJPcGjOz6u_EzVR@d*CpgF*iGj->-S7lk1O2sueYg$CRfK^xlHxE*W`u+2e( Y+np>!P*0<( z$fYvKGG$mM3cQ6%Ru)$b3oKE(_F)#KR>mDAc=5q&%XH1)ZEb9p|NCc z>~TC*(mF036DtZ7nLrb8do-*N5-lr~%t_|fT(X-hmae(6QMB&}4b9UH&+%4ilw6CL zo{rzT(fcKFXkF}&gTROS*#cb&%xmkgyjL<4`3$Y5syLxP(N>8Slw8PW3qB(NqB0?Q z0U|7uBJ@wf%~mfWBsZ$k&4v#*VzfQwLjKROgEa)!*!?XxLyEeF4TS B_Q3!E literal 0 HcmV?d00001 diff --git a/server/README.md b/server/README.md index 3120cf65a..c889a5239 100644 --- a/server/README.md +++ b/server/README.md @@ -1 +1,6 @@ -# Immich Server- NestJs +## How to run migration + +1. Attached to the container shell +2. Run `npm run typeorm -- migration:generate ./libs/database/src/ -d libs/database/src/config/database.config.ts` +3. Check if the migration file makes sense +4. Move the migration file to folder `server/libs/database/src/migrations` in your code editor. \ No newline at end of file diff --git a/server/apps/immich/src/api-v1/system-config/dto/update-system-config.ts b/server/apps/immich/src/api-v1/system-config/dto/update-system-config.ts new file mode 100644 index 000000000..e762d6d5e --- /dev/null +++ b/server/apps/immich/src/api-v1/system-config/dto/update-system-config.ts @@ -0,0 +1,20 @@ +import { SystemConfigKey, SystemConfigValue } from '@app/database/entities/system-config.entity'; +import { ApiProperty } from '@nestjs/swagger'; +import { IsEnum, IsNotEmpty, ValidateNested } from 'class-validator'; + +export class UpdateSystemConfigDto { + @IsNotEmpty() + @ValidateNested({ each: true }) + config!: SystemConfigItem[]; +} + +export class SystemConfigItem { + @IsNotEmpty() + @IsEnum(SystemConfigKey) + @ApiProperty({ + enum: SystemConfigKey, + enumName: 'SystemConfigKey', + }) + key!: SystemConfigKey; + value!: SystemConfigValue; +} diff --git a/server/apps/immich/src/api-v1/system-config/response-dto/system-config-response.dto.ts b/server/apps/immich/src/api-v1/system-config/response-dto/system-config-response.dto.ts new file mode 100644 index 000000000..6dfd3ae1c --- /dev/null +++ b/server/apps/immich/src/api-v1/system-config/response-dto/system-config-response.dto.ts @@ -0,0 +1,20 @@ +import { SystemConfigKey, SystemConfigValue } from '@app/database/entities/system-config.entity'; +import { ApiProperty } from '@nestjs/swagger'; + +export class SystemConfigResponseDto { + config!: SystemConfigResponseItem[]; +} + +export class SystemConfigResponseItem { + @ApiProperty({ type: 'string' }) + name!: string; + + @ApiProperty({ enumName: 'SystemConfigKey', enum: SystemConfigKey }) + key!: SystemConfigKey; + + @ApiProperty({ type: 'string' }) + value!: SystemConfigValue; + + @ApiProperty({ type: 'string' }) + defaultValue!: SystemConfigValue; +} diff --git a/server/apps/immich/src/api-v1/system-config/system-config.controller.ts b/server/apps/immich/src/api-v1/system-config/system-config.controller.ts new file mode 100644 index 000000000..48e8002fa --- /dev/null +++ b/server/apps/immich/src/api-v1/system-config/system-config.controller.ts @@ -0,0 +1,24 @@ +import { Body, Controller, Get, Put, ValidationPipe } from '@nestjs/common'; +import { ApiBearerAuth, ApiTags } from '@nestjs/swagger'; +import { Authenticated } from '../../decorators/authenticated.decorator'; +import { UpdateSystemConfigDto } from './dto/update-system-config'; +import { SystemConfigResponseDto } from './response-dto/system-config-response.dto'; +import { SystemConfigService } from './system-config.service'; + +@ApiTags('System Config') +@ApiBearerAuth() +@Authenticated({ admin: true }) +@Controller('system-config') +export class SystemConfigController { + constructor(private readonly systemConfigService: SystemConfigService) {} + + @Get() + getConfig(): Promise { + return this.systemConfigService.getConfig(); + } + + @Put() + async updateConfig(@Body(ValidationPipe) dto: UpdateSystemConfigDto): Promise { + return this.systemConfigService.updateConfig(dto); + } +} diff --git a/server/apps/immich/src/api-v1/system-config/system-config.module.ts b/server/apps/immich/src/api-v1/system-config/system-config.module.ts new file mode 100644 index 000000000..ac5440403 --- /dev/null +++ b/server/apps/immich/src/api-v1/system-config/system-config.module.ts @@ -0,0 +1,14 @@ +import { SystemConfigEntity } from '@app/database/entities/system-config.entity'; +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { ImmichConfigModule } from 'libs/immich-config/src'; +import { ImmichJwtModule } from '../../modules/immich-jwt/immich-jwt.module'; +import { SystemConfigController } from './system-config.controller'; +import { SystemConfigService } from './system-config.service'; + +@Module({ + imports: [ImmichJwtModule, ImmichConfigModule, TypeOrmModule.forFeature([SystemConfigEntity])], + controllers: [SystemConfigController], + providers: [SystemConfigService], +}) +export class SystemConfigModule {} diff --git a/server/apps/immich/src/api-v1/system-config/system-config.service.ts b/server/apps/immich/src/api-v1/system-config/system-config.service.ts new file mode 100644 index 000000000..d79f3f5ab --- /dev/null +++ b/server/apps/immich/src/api-v1/system-config/system-config.service.ts @@ -0,0 +1,20 @@ +import { Injectable } from '@nestjs/common'; +import { ImmichConfigService } from 'libs/immich-config/src'; +import { UpdateSystemConfigDto } from './dto/update-system-config'; +import { SystemConfigResponseDto } from './response-dto/system-config-response.dto'; + +@Injectable() +export class SystemConfigService { + constructor(private immichConfigService: ImmichConfigService) {} + + async getConfig(): Promise { + const config = await this.immichConfigService.getSystemConfig(); + return { config }; + } + + async updateConfig(dto: UpdateSystemConfigDto): Promise { + await this.immichConfigService.updateSystemConfig(dto.config); + const config = await this.immichConfigService.getSystemConfig(); + return { config }; + } +} diff --git a/server/apps/immich/src/app.module.ts b/server/apps/immich/src/app.module.ts index bdb6b8d78..487847bbc 100644 --- a/server/apps/immich/src/app.module.ts +++ b/server/apps/immich/src/app.module.ts @@ -16,6 +16,7 @@ import { ScheduleModule } from '@nestjs/schedule'; import { ScheduleTasksModule } from './modules/schedule-tasks/schedule-tasks.module'; import { DatabaseModule } from '@app/database'; import { JobModule } from './api-v1/job/job.module'; +import { SystemConfigModule } from './api-v1/system-config/system-config.module'; import { OAuthModule } from './api-v1/oauth/oauth.module'; @Module({ @@ -60,6 +61,8 @@ import { OAuthModule } from './api-v1/oauth/oauth.module'; ScheduleTasksModule, JobModule, + + SystemConfigModule, ], controllers: [AppController], providers: [], diff --git a/server/apps/microservices/src/microservices.module.ts b/server/apps/microservices/src/microservices.module.ts index 0353bb08a..4f94f4a09 100644 --- a/server/apps/microservices/src/microservices.module.ts +++ b/server/apps/microservices/src/microservices.module.ts @@ -7,8 +7,9 @@ import { UserEntity } from '@app/database/entities/user.entity'; import { QueueNameEnum } from '@app/job/constants/queue-name.constant'; import { BullModule } from '@nestjs/bull'; import { Module } from '@nestjs/common'; -import { ConfigModule, ConfigService } from '@nestjs/config'; +import { ConfigModule } from '@nestjs/config'; import { TypeOrmModule } from '@nestjs/typeorm'; +import { ImmichConfigModule } from 'libs/immich-config/src'; import { CommunicationModule } from '../../immich/src/api-v1/communication/communication.module'; import { MicroservicesService } from './microservices.service'; import { AssetUploadedProcessor } from './processors/asset-uploaded.processor'; @@ -22,6 +23,7 @@ import { VideoTranscodeProcessor } from './processors/video-transcode.processor' imports: [ ConfigModule.forRoot(immichAppConfig), DatabaseModule, + ImmichConfigModule, TypeOrmModule.forFeature([UserEntity, ExifEntity, AssetEntity, SmartInfoEntity]), BullModule.forRootAsync({ useFactory: async () => ({ @@ -96,7 +98,6 @@ import { VideoTranscodeProcessor } from './processors/video-transcode.processor' VideoTranscodeProcessor, GenerateChecksumProcessor, MachineLearningProcessor, - ConfigService, ], exports: [], }) diff --git a/server/apps/microservices/src/processors/video-transcode.processor.ts b/server/apps/microservices/src/processors/video-transcode.processor.ts index 2e04a75e0..89dc263ed 100644 --- a/server/apps/microservices/src/processors/video-transcode.processor.ts +++ b/server/apps/microservices/src/processors/video-transcode.processor.ts @@ -9,6 +9,7 @@ import { InjectRepository } from '@nestjs/typeorm'; import { Job } from 'bull'; import ffmpeg from 'fluent-ffmpeg'; import { existsSync, mkdirSync } from 'fs'; +import { ImmichConfigService } from 'libs/immich-config/src'; import { Repository } from 'typeorm'; @Processor(QueueNameEnum.VIDEO_CONVERSION) @@ -16,6 +17,7 @@ export class VideoTranscodeProcessor { constructor( @InjectRepository(AssetEntity) private assetRepository: Repository, + private immichConfigService: ImmichConfigService, ) {} @Process({ name: mp4ConversionProcessorName, concurrency: 1 }) @@ -40,9 +42,17 @@ export class VideoTranscodeProcessor { } async runFFMPEGPipeLine(asset: AssetEntity, savedEncodedPath: string): Promise { + const config = await this.immichConfigService.getSystemConfigMap(); + return new Promise((resolve, reject) => { ffmpeg(asset.originalPath) - .outputOptions(['-crf 23', '-preset ultrafast', '-vcodec libx264', '-acodec mp3', '-vf scale=1280:-2']) + .outputOptions([ + `-crf ${config.ffmpeg_crf}`, + `-preset ${config.ffmpeg_preset}`, + `-vcodec ${config.ffmpeg_target_video_codec}`, + `-acodec ${config.ffmpeg_target_audio_codec}`, + `-vf scale=${config.ffmpeg_target_scaling}`, + ]) .output(savedEncodedPath) .on('start', () => { Logger.log('Start Converting Video', 'mp4Conversion'); diff --git a/server/immich-openapi-specs.json b/server/immich-openapi-specs.json index e7586d7c7..061ef913d 100644 --- a/server/immich-openapi-specs.json +++ b/server/immich-openapi-specs.json @@ -1 +1 @@ -{"openapi":"3.0.0","paths":{"/user":{"get":{"operationId":"getAllUsers","parameters":[{"name":"isAll","required":true,"in":"query","schema":{"type":"boolean"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/UserResponseDto"}}}}}},"tags":["User"],"security":[{"bearer":[]}]},"post":{"operationId":"createUser","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateUserDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserResponseDto"}}}}},"tags":["User"],"security":[{"bearer":[]}]},"put":{"operationId":"updateUser","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateUserDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserResponseDto"}}}}},"tags":["User"],"security":[{"bearer":[]}]}},"/user/info/{userId}":{"get":{"operationId":"getUserById","parameters":[{"name":"userId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserResponseDto"}}}}},"tags":["User"]}},"/user/me":{"get":{"operationId":"getMyUserInfo","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserResponseDto"}}}}},"tags":["User"],"security":[{"bearer":[]}]}},"/user/count":{"get":{"operationId":"getUserCount","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserCountResponseDto"}}}}},"tags":["User"]}},"/user/{userId}":{"delete":{"operationId":"deleteUser","parameters":[{"name":"userId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserResponseDto"}}}}},"tags":["User"],"security":[{"bearer":[]}]}},"/user/{userId}/restore":{"post":{"operationId":"restoreUser","parameters":[{"name":"userId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserResponseDto"}}}}},"tags":["User"],"security":[{"bearer":[]}]}},"/user/profile-image":{"post":{"operationId":"createProfileImage","parameters":[],"requestBody":{"required":true,"description":"A new avatar for the user","content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/CreateProfileImageDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateProfileImageResponseDto"}}}}},"tags":["User"],"security":[{"bearer":[]}]}},"/user/profile-image/{userId}":{"get":{"operationId":"getProfileImage","parameters":[{"name":"userId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["User"]}},"/asset/upload":{"post":{"operationId":"uploadFile","parameters":[],"requestBody":{"required":true,"description":"Asset Upload Information","content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/AssetFileUploadDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AssetFileUploadResponseDto"}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset/download":{"get":{"operationId":"downloadFile","parameters":[{"name":"aid","required":true,"in":"query","schema":{"title":"Device Asset ID","type":"string"}},{"name":"did","required":true,"in":"query","schema":{"title":"Device ID","type":"string"}},{"name":"isThumb","required":false,"in":"query","schema":{"title":"Is serve thumbnail (resize) file","type":"boolean"}},{"name":"isWeb","required":false,"in":"query","schema":{"title":"Is request made from web","type":"boolean"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset/file":{"get":{"operationId":"serveFile","parameters":[{"name":"aid","required":true,"in":"query","schema":{"title":"Device Asset ID","type":"string"}},{"name":"did","required":true,"in":"query","schema":{"title":"Device ID","type":"string"}},{"name":"isThumb","required":false,"in":"query","schema":{"title":"Is serve thumbnail (resize) file","type":"boolean"}},{"name":"isWeb","required":false,"in":"query","schema":{"title":"Is request made from web","type":"boolean"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset/thumbnail/{assetId}":{"get":{"operationId":"getAssetThumbnail","parameters":[{"name":"assetId","required":true,"in":"path","schema":{"type":"string"}},{"name":"format","required":false,"in":"query","schema":{"$ref":"#/components/schemas/ThumbnailFormat"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset/curated-objects":{"get":{"operationId":"getCuratedObjects","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/CuratedObjectsResponseDto"}}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset/curated-locations":{"get":{"operationId":"getCuratedLocations","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/CuratedLocationsResponseDto"}}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset/search-terms":{"get":{"operationId":"getAssetSearchTerms","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"type":"string"}}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset/search":{"post":{"operationId":"searchAsset","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SearchAssetDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/AssetResponseDto"}}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset/count-by-time-bucket":{"post":{"operationId":"getAssetCountByTimeBucket","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetAssetCountByTimeBucketDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AssetCountByTimeBucketResponseDto"}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset/count-by-user-id":{"get":{"operationId":"getAssetCountByUserId","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AssetCountByUserIdResponseDto"}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset":{"get":{"operationId":"getAllAssets","summary":"","description":"Get all AssetEntity belong to the user","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/AssetResponseDto"}}}}}},"tags":["Asset"],"security":[{"bearer":[]}]},"delete":{"operationId":"deleteAsset","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteAssetDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/DeleteAssetResponseDto"}}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset/time-bucket":{"post":{"operationId":"getAssetByTimeBucket","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetAssetByTimeBucketDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/AssetResponseDto"}}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset/{deviceId}":{"get":{"operationId":"getUserAssetsByDeviceId","summary":"","description":"Get all asset of a device that are in the database, ID only.","parameters":[{"name":"deviceId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"type":"string"}}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset/assetById/{assetId}":{"get":{"operationId":"getAssetById","summary":"","description":"Get a single asset's information","parameters":[{"name":"assetId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AssetResponseDto"}}}}},"tags":["Asset"],"security":[{"bearer":[]}]},"put":{"operationId":"updateAssetById","summary":"","description":"Update an asset","parameters":[{"name":"assetId","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateAssetDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AssetResponseDto"}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset/check":{"post":{"operationId":"checkDuplicateAsset","summary":"","description":"Check duplicated asset before uploading - for Web upload used","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CheckDuplicateAssetDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CheckDuplicateAssetResponseDto"}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset/exist":{"post":{"operationId":"checkExistingAssets","summary":"","description":"Checks if multiple assets exist on the server and returns all existing - used by background backup","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CheckExistingAssetsDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CheckExistingAssetsResponseDto"}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/auth/login":{"post":{"operationId":"login","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoginCredentialDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoginResponseDto"}}}}},"tags":["Authentication"]}},"/auth/admin-sign-up":{"post":{"operationId":"adminSignUp","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SignUpDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminSignupResponseDto"}}}},"400":{"description":"The server already has an admin"}},"tags":["Authentication"]}},"/auth/validateToken":{"post":{"operationId":"validateAccessToken","parameters":[],"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidateAccessTokenResponseDto"}}}}},"tags":["Authentication"],"security":[{"bearer":[]}]}},"/auth/logout":{"post":{"operationId":"logout","parameters":[],"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LogoutResponseDto"}}}}},"tags":["Authentication"]}},"/oauth/config":{"post":{"operationId":"generateConfig","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OAuthConfigDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OAuthConfigResponseDto"}}}}},"tags":["OAuth"]}},"/oauth/callback":{"post":{"operationId":"callback","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OAuthCallbackDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoginResponseDto"}}}}},"tags":["OAuth"]}},"/device-info":{"post":{"operationId":"createDeviceInfo","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateDeviceInfoDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeviceInfoResponseDto"}}}}},"tags":["Device Info"],"security":[{"bearer":[]}]},"patch":{"operationId":"updateDeviceInfo","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateDeviceInfoDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeviceInfoResponseDto"}}}}},"tags":["Device Info"],"security":[{"bearer":[]}]}},"/server-info":{"get":{"operationId":"getServerInfo","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ServerInfoResponseDto"}}}}},"tags":["Server Info"]}},"/server-info/ping":{"get":{"operationId":"pingServer","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ServerPingResponse"}}}}},"tags":["Server Info"]}},"/server-info/version":{"get":{"operationId":"getServerVersion","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ServerVersionReponseDto"}}}}},"tags":["Server Info"]}},"/server-info/stats":{"get":{"operationId":"getStats","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ServerStatsResponseDto"}}}}},"tags":["Server Info"]}},"/album/count-by-user-id":{"get":{"operationId":"getAlbumCountByUserId","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AlbumCountResponseDto"}}}}},"tags":["Album"],"security":[{"bearer":[]}]}},"/album":{"post":{"operationId":"createAlbum","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateAlbumDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AlbumResponseDto"}}}}},"tags":["Album"],"security":[{"bearer":[]}]},"get":{"operationId":"getAllAlbums","parameters":[{"name":"shared","required":false,"in":"query","schema":{"type":"boolean"}},{"name":"assetId","required":false,"in":"query","description":"Only returns albums that contain the asset\nIgnores the shared parameter\nundefined: get all albums","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/AlbumResponseDto"}}}}}},"tags":["Album"],"security":[{"bearer":[]}]}},"/album/{albumId}/users":{"put":{"operationId":"addUsersToAlbum","parameters":[{"name":"albumId","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddUsersDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AlbumResponseDto"}}}}},"tags":["Album"],"security":[{"bearer":[]}]}},"/album/{albumId}/assets":{"put":{"operationId":"addAssetsToAlbum","parameters":[{"name":"albumId","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddAssetsDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddAssetsResponseDto"}}}}},"tags":["Album"],"security":[{"bearer":[]}]},"delete":{"operationId":"removeAssetFromAlbum","parameters":[{"name":"albumId","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RemoveAssetsDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AlbumResponseDto"}}}}},"tags":["Album"],"security":[{"bearer":[]}]}},"/album/{albumId}":{"get":{"operationId":"getAlbumInfo","parameters":[{"name":"albumId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AlbumResponseDto"}}}}},"tags":["Album"],"security":[{"bearer":[]}]},"delete":{"operationId":"deleteAlbum","parameters":[{"name":"albumId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["Album"],"security":[{"bearer":[]}]},"patch":{"operationId":"updateAlbumInfo","parameters":[{"name":"albumId","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateAlbumDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AlbumResponseDto"}}}}},"tags":["Album"],"security":[{"bearer":[]}]}},"/album/{albumId}/user/{userId}":{"delete":{"operationId":"removeUserFromAlbum","parameters":[{"name":"albumId","required":true,"in":"path","schema":{"type":"string"}},{"name":"userId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["Album"],"security":[{"bearer":[]}]}},"/album/{albumId}/download":{"get":{"operationId":"downloadArchive","parameters":[{"name":"albumId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["Album"],"security":[{"bearer":[]}]}},"/jobs":{"get":{"operationId":"getAllJobsStatus","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AllJobStatusResponseDto"}}}}},"tags":["Job"],"security":[{"bearer":[]}]}},"/jobs/{jobId}":{"get":{"operationId":"getJobStatus","parameters":[{"name":"jobId","required":true,"in":"path","schema":{"$ref":"#/components/schemas/JobId"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobStatusResponseDto"}}}}},"tags":["Job"],"security":[{"bearer":[]}]},"put":{"operationId":"sendJobCommand","parameters":[{"name":"jobId","required":true,"in":"path","schema":{"$ref":"#/components/schemas/JobId"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobCommandDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"number"}}}}},"tags":["Job"],"security":[{"bearer":[]}]}}},"info":{"title":"Immich","description":"Immich API","version":"1.17.0","contact":{}},"tags":[],"servers":[{"url":"/api"}],"components":{"securitySchemes":{"bearer":{"scheme":"Bearer","bearerFormat":"JWT","type":"http","name":"JWT","description":"Enter JWT token","in":"header"}},"schemas":{"UserResponseDto":{"type":"object","properties":{"id":{"type":"string"},"email":{"type":"string"},"firstName":{"type":"string"},"lastName":{"type":"string"},"createdAt":{"type":"string"},"profileImagePath":{"type":"string"},"shouldChangePassword":{"type":"boolean"},"isAdmin":{"type":"boolean"},"deletedAt":{"format":"date-time","type":"string","nullable":true}},"required":["id","email","firstName","lastName","createdAt","profileImagePath","shouldChangePassword","isAdmin","deletedAt"]},"CreateUserDto":{"type":"object","properties":{"email":{"type":"string","example":"testuser@email.com"},"password":{"type":"string","example":"password"},"firstName":{"type":"string","example":"John"},"lastName":{"type":"string","example":"Doe"}},"required":["email","password","firstName","lastName"]},"UserCountResponseDto":{"type":"object","properties":{"userCount":{"type":"integer"}},"required":["userCount"]},"UpdateUserDto":{"type":"object","properties":{"id":{"type":"string"},"password":{"type":"string"},"firstName":{"type":"string"},"lastName":{"type":"string"},"isAdmin":{"type":"boolean"},"shouldChangePassword":{"type":"boolean"},"profileImagePath":{"type":"string"}},"required":["id"]},"CreateProfileImageDto":{"type":"object","properties":{"file":{"type":"string","format":"binary"}},"required":["file"]},"CreateProfileImageResponseDto":{"type":"object","properties":{"userId":{"type":"string"},"profileImagePath":{"type":"string"}},"required":["userId","profileImagePath"]},"AssetFileUploadDto":{"type":"object","properties":{"assetData":{"type":"string","format":"binary"}},"required":["assetData"]},"AssetFileUploadResponseDto":{"type":"object","properties":{"id":{"type":"string"}},"required":["id"]},"ThumbnailFormat":{"type":"string","enum":["JPEG","WEBP"]},"CuratedObjectsResponseDto":{"type":"object","properties":{"id":{"type":"string"},"object":{"type":"string"},"resizePath":{"type":"string"},"deviceAssetId":{"type":"string"},"deviceId":{"type":"string"}},"required":["id","object","resizePath","deviceAssetId","deviceId"]},"CuratedLocationsResponseDto":{"type":"object","properties":{"id":{"type":"string"},"city":{"type":"string"},"resizePath":{"type":"string"},"deviceAssetId":{"type":"string"},"deviceId":{"type":"string"}},"required":["id","city","resizePath","deviceAssetId","deviceId"]},"SearchAssetDto":{"type":"object","properties":{"searchTerm":{"type":"string"}},"required":["searchTerm"]},"AssetTypeEnum":{"type":"string","enum":["IMAGE","VIDEO","AUDIO","OTHER"]},"ExifResponseDto":{"type":"object","properties":{"id":{"type":"integer","nullable":true,"default":null,"format":"int64"},"fileSizeInByte":{"type":"integer","nullable":true,"default":null,"format":"int64"},"make":{"type":"string","nullable":true,"default":null},"model":{"type":"string","nullable":true,"default":null},"imageName":{"type":"string","nullable":true,"default":null},"exifImageWidth":{"type":"number","nullable":true,"default":null},"exifImageHeight":{"type":"number","nullable":true,"default":null},"orientation":{"type":"string","nullable":true,"default":null},"dateTimeOriginal":{"format":"date-time","type":"string","nullable":true,"default":null},"modifyDate":{"format":"date-time","type":"string","nullable":true,"default":null},"lensModel":{"type":"string","nullable":true,"default":null},"fNumber":{"type":"number","nullable":true,"default":null},"focalLength":{"type":"number","nullable":true,"default":null},"iso":{"type":"number","nullable":true,"default":null},"exposureTime":{"type":"number","nullable":true,"default":null},"latitude":{"type":"number","nullable":true,"default":null},"longitude":{"type":"number","nullable":true,"default":null},"city":{"type":"string","nullable":true,"default":null},"state":{"type":"string","nullable":true,"default":null},"country":{"type":"string","nullable":true,"default":null}}},"SmartInfoResponseDto":{"type":"object","properties":{"id":{"type":"string"},"tags":{"nullable":true,"type":"array","items":{"type":"string"}},"objects":{"nullable":true,"type":"array","items":{"type":"string"}}}},"AssetResponseDto":{"type":"object","properties":{"type":{"$ref":"#/components/schemas/AssetTypeEnum"},"id":{"type":"string"},"deviceAssetId":{"type":"string"},"ownerId":{"type":"string"},"deviceId":{"type":"string"},"originalPath":{"type":"string"},"resizePath":{"type":"string","nullable":true},"createdAt":{"type":"string"},"modifiedAt":{"type":"string"},"isFavorite":{"type":"boolean"},"mimeType":{"type":"string","nullable":true},"duration":{"type":"string"},"webpPath":{"type":"string","nullable":true},"encodedVideoPath":{"type":"string","nullable":true},"exifInfo":{"$ref":"#/components/schemas/ExifResponseDto"},"smartInfo":{"$ref":"#/components/schemas/SmartInfoResponseDto"}},"required":["type","id","deviceAssetId","ownerId","deviceId","originalPath","resizePath","createdAt","modifiedAt","isFavorite","mimeType","duration","webpPath","encodedVideoPath"]},"TimeGroupEnum":{"type":"string","enum":["day","month"]},"GetAssetCountByTimeBucketDto":{"type":"object","properties":{"timeGroup":{"$ref":"#/components/schemas/TimeGroupEnum"}},"required":["timeGroup"]},"AssetCountByTimeBucket":{"type":"object","properties":{"timeBucket":{"type":"string"},"count":{"type":"integer"}},"required":["timeBucket","count"]},"AssetCountByTimeBucketResponseDto":{"type":"object","properties":{"totalCount":{"type":"integer"},"buckets":{"type":"array","items":{"$ref":"#/components/schemas/AssetCountByTimeBucket"}}},"required":["totalCount","buckets"]},"AssetCountByUserIdResponseDto":{"type":"object","properties":{"photos":{"type":"integer"},"videos":{"type":"integer"}},"required":["photos","videos"]},"GetAssetByTimeBucketDto":{"type":"object","properties":{"timeBucket":{"title":"Array of date time buckets","example":["2015-06-01T00:00:00.000Z","2016-02-01T00:00:00.000Z","2016-03-01T00:00:00.000Z"],"type":"array","items":{"type":"string"}}},"required":["timeBucket"]},"UpdateAssetDto":{"type":"object","properties":{"isFavorite":{"type":"boolean"}},"required":["isFavorite"]},"DeleteAssetDto":{"type":"object","properties":{"ids":{"title":"Array of asset IDs to delete","example":["bf973405-3f2a-48d2-a687-2ed4167164be","dd41870b-5d00-46d2-924e-1d8489a0aa0f","fad77c3f-deef-4e7e-9608-14c1aa4e559a"],"type":"array","items":{"type":"string"}}},"required":["ids"]},"DeleteAssetStatus":{"type":"string","enum":["SUCCESS","FAILED"]},"DeleteAssetResponseDto":{"type":"object","properties":{"status":{"$ref":"#/components/schemas/DeleteAssetStatus"},"id":{"type":"string"}},"required":["status","id"]},"CheckDuplicateAssetDto":{"type":"object","properties":{"deviceAssetId":{"type":"string"},"deviceId":{"type":"string"}},"required":["deviceAssetId","deviceId"]},"CheckDuplicateAssetResponseDto":{"type":"object","properties":{"isExist":{"type":"boolean"},"id":{"type":"string"}},"required":["isExist"]},"CheckExistingAssetsDto":{"type":"object","properties":{"deviceAssetIds":{"type":"array","items":{"type":"string"}},"deviceId":{"type":"string"}},"required":["deviceAssetIds","deviceId"]},"CheckExistingAssetsResponseDto":{"type":"object","properties":{"existingIds":{"type":"array","items":{"type":"string"}}},"required":["existingIds"]},"LoginCredentialDto":{"type":"object","properties":{"email":{"type":"string","example":"testuser@email.com"},"password":{"type":"string","example":"password"}},"required":["email","password"]},"LoginResponseDto":{"type":"object","properties":{"accessToken":{"type":"string","readOnly":true},"userId":{"type":"string","readOnly":true},"userEmail":{"type":"string","readOnly":true},"firstName":{"type":"string","readOnly":true},"lastName":{"type":"string","readOnly":true},"profileImagePath":{"type":"string","readOnly":true},"isAdmin":{"type":"boolean","readOnly":true},"shouldChangePassword":{"type":"boolean","readOnly":true}},"required":["accessToken","userId","userEmail","firstName","lastName","profileImagePath","isAdmin","shouldChangePassword"]},"SignUpDto":{"type":"object","properties":{"email":{"type":"string","example":"testuser@email.com"},"password":{"type":"string","example":"password"},"firstName":{"type":"string","example":"Admin"},"lastName":{"type":"string","example":"Doe"}},"required":["email","password","firstName","lastName"]},"AdminSignupResponseDto":{"type":"object","properties":{"id":{"type":"string"},"email":{"type":"string"},"firstName":{"type":"string"},"lastName":{"type":"string"},"createdAt":{"type":"string"}},"required":["id","email","firstName","lastName","createdAt"]},"ValidateAccessTokenResponseDto":{"type":"object","properties":{"authStatus":{"type":"boolean"}},"required":["authStatus"]},"LogoutResponseDto":{"type":"object","properties":{"successful":{"type":"boolean","readOnly":true},"redirectUri":{"type":"string","readOnly":true}},"required":["successful","redirectUri"]},"OAuthConfigDto":{"type":"object","properties":{"redirectUri":{"type":"string"}},"required":["redirectUri"]},"OAuthConfigResponseDto":{"type":"object","properties":{"enabled":{"type":"boolean","readOnly":true},"url":{"type":"string","readOnly":true},"buttonText":{"type":"string","readOnly":true}},"required":["enabled"]},"OAuthCallbackDto":{"type":"object","properties":{"url":{"type":"string"}},"required":["url"]},"DeviceTypeEnum":{"type":"string","enum":["IOS","ANDROID","WEB"]},"CreateDeviceInfoDto":{"type":"object","properties":{"deviceType":{"$ref":"#/components/schemas/DeviceTypeEnum"},"deviceId":{"type":"string"},"isAutoBackup":{"type":"boolean"}},"required":["deviceType","deviceId"]},"DeviceInfoResponseDto":{"type":"object","properties":{"id":{"type":"integer"},"deviceType":{"$ref":"#/components/schemas/DeviceTypeEnum"},"userId":{"type":"string"},"deviceId":{"type":"string"},"createdAt":{"type":"string"},"isAutoBackup":{"type":"boolean"}},"required":["id","deviceType","userId","deviceId","createdAt","isAutoBackup"]},"UpdateDeviceInfoDto":{"type":"object","properties":{"deviceType":{"$ref":"#/components/schemas/DeviceTypeEnum"},"deviceId":{"type":"string"},"isAutoBackup":{"type":"boolean"}},"required":["deviceType","deviceId"]},"ServerInfoResponseDto":{"type":"object","properties":{"diskSizeRaw":{"type":"integer","format":"int64"},"diskUseRaw":{"type":"integer","format":"int64"},"diskAvailableRaw":{"type":"integer","format":"int64"},"diskUsagePercentage":{"type":"number","format":"float"},"diskSize":{"type":"string"},"diskUse":{"type":"string"},"diskAvailable":{"type":"string"}},"required":["diskSizeRaw","diskUseRaw","diskAvailableRaw","diskUsagePercentage","diskSize","diskUse","diskAvailable"]},"ServerPingResponse":{"type":"object","properties":{"res":{"type":"string","readOnly":true,"example":"pong"}},"required":["res"]},"ServerVersionReponseDto":{"type":"object","properties":{"major":{"type":"integer"},"minor":{"type":"integer"},"patch":{"type":"integer"},"build":{"type":"integer"}},"required":["major","minor","patch","build"]},"UsageByUserDto":{"type":"object","properties":{"userId":{"type":"string"},"objects":{"type":"integer"},"videos":{"type":"integer"},"photos":{"type":"integer"},"usageRaw":{"type":"integer","format":"int64"},"usage":{"type":"string"}},"required":["userId","objects","videos","photos","usageRaw","usage"]},"ServerStatsResponseDto":{"type":"object","properties":{"photos":{"type":"integer"},"videos":{"type":"integer"},"objects":{"type":"integer"},"usageRaw":{"type":"integer","format":"int64"},"usage":{"type":"string"},"usageByUser":{"title":"Array of usage for each user","example":[{"photos":1,"videos":1,"objects":1,"diskUsageRaw":1}],"type":"array","items":{"$ref":"#/components/schemas/UsageByUserDto"}}},"required":["photos","videos","objects","usageRaw","usage","usageByUser"]},"AlbumCountResponseDto":{"type":"object","properties":{"owned":{"type":"integer"},"shared":{"type":"integer"},"sharing":{"type":"integer"}},"required":["owned","shared","sharing"]},"CreateAlbumDto":{"type":"object","properties":{"albumName":{"type":"string"},"sharedWithUserIds":{"type":"array","items":{"type":"string"}},"assetIds":{"type":"array","items":{"type":"string"}}},"required":["albumName"]},"AlbumResponseDto":{"type":"object","properties":{"assetCount":{"type":"integer"},"id":{"type":"string"},"ownerId":{"type":"string"},"albumName":{"type":"string"},"createdAt":{"type":"string"},"albumThumbnailAssetId":{"type":"string","nullable":true},"shared":{"type":"boolean"},"sharedUsers":{"type":"array","items":{"$ref":"#/components/schemas/UserResponseDto"}},"assets":{"type":"array","items":{"$ref":"#/components/schemas/AssetResponseDto"}}},"required":["assetCount","id","ownerId","albumName","createdAt","albumThumbnailAssetId","shared","sharedUsers","assets"]},"AddUsersDto":{"type":"object","properties":{"sharedUserIds":{"type":"array","items":{"type":"string"}}},"required":["sharedUserIds"]},"AddAssetsDto":{"type":"object","properties":{"assetIds":{"type":"array","items":{"type":"string"}}},"required":["assetIds"]},"AddAssetsResponseDto":{"type":"object","properties":{"successfullyAdded":{"type":"integer"},"alreadyInAlbum":{"type":"array","items":{"type":"string"}},"album":{"$ref":"#/components/schemas/AlbumResponseDto"}},"required":["successfullyAdded","alreadyInAlbum"]},"RemoveAssetsDto":{"type":"object","properties":{"assetIds":{"type":"array","items":{"type":"string"}}},"required":["assetIds"]},"UpdateAlbumDto":{"type":"object","properties":{"albumName":{"type":"string"},"albumThumbnailAssetId":{"type":"string"}}},"JobCounts":{"type":"object","properties":{"active":{"type":"integer"},"completed":{"type":"integer"},"failed":{"type":"integer"},"delayed":{"type":"integer"},"waiting":{"type":"integer"}},"required":["active","completed","failed","delayed","waiting"]},"AllJobStatusResponseDto":{"type":"object","properties":{"thumbnailGenerationQueueCount":{"$ref":"#/components/schemas/JobCounts"},"metadataExtractionQueueCount":{"$ref":"#/components/schemas/JobCounts"},"videoConversionQueueCount":{"$ref":"#/components/schemas/JobCounts"},"machineLearningQueueCount":{"$ref":"#/components/schemas/JobCounts"},"isThumbnailGenerationActive":{"type":"boolean"},"isMetadataExtractionActive":{"type":"boolean"},"isVideoConversionActive":{"type":"boolean"},"isMachineLearningActive":{"type":"boolean"}},"required":["thumbnailGenerationQueueCount","metadataExtractionQueueCount","videoConversionQueueCount","machineLearningQueueCount","isThumbnailGenerationActive","isMetadataExtractionActive","isVideoConversionActive","isMachineLearningActive"]},"JobId":{"type":"string","enum":["thumbnail-generation","metadata-extraction","video-conversion","machine-learning"]},"JobStatusResponseDto":{"type":"object","properties":{"isActive":{"type":"boolean"},"queueCount":{"type":"object"}},"required":["isActive","queueCount"]},"JobCommand":{"type":"string","enum":["start","stop"]},"JobCommandDto":{"type":"object","properties":{"command":{"$ref":"#/components/schemas/JobCommand"}},"required":["command"]}}}} \ No newline at end of file +{"openapi":"3.0.0","paths":{"/user":{"get":{"operationId":"getAllUsers","parameters":[{"name":"isAll","required":true,"in":"query","schema":{"type":"boolean"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/UserResponseDto"}}}}}},"tags":["User"],"security":[{"bearer":[]}]},"post":{"operationId":"createUser","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateUserDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserResponseDto"}}}}},"tags":["User"],"security":[{"bearer":[]}]},"put":{"operationId":"updateUser","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateUserDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserResponseDto"}}}}},"tags":["User"],"security":[{"bearer":[]}]}},"/user/info/{userId}":{"get":{"operationId":"getUserById","parameters":[{"name":"userId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserResponseDto"}}}}},"tags":["User"]}},"/user/me":{"get":{"operationId":"getMyUserInfo","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserResponseDto"}}}}},"tags":["User"],"security":[{"bearer":[]}]}},"/user/count":{"get":{"operationId":"getUserCount","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserCountResponseDto"}}}}},"tags":["User"]}},"/user/{userId}":{"delete":{"operationId":"deleteUser","parameters":[{"name":"userId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserResponseDto"}}}}},"tags":["User"],"security":[{"bearer":[]}]}},"/user/{userId}/restore":{"post":{"operationId":"restoreUser","parameters":[{"name":"userId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserResponseDto"}}}}},"tags":["User"],"security":[{"bearer":[]}]}},"/user/profile-image":{"post":{"operationId":"createProfileImage","parameters":[],"requestBody":{"required":true,"description":"A new avatar for the user","content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/CreateProfileImageDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateProfileImageResponseDto"}}}}},"tags":["User"],"security":[{"bearer":[]}]}},"/user/profile-image/{userId}":{"get":{"operationId":"getProfileImage","parameters":[{"name":"userId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["User"]}},"/asset/upload":{"post":{"operationId":"uploadFile","parameters":[],"requestBody":{"required":true,"description":"Asset Upload Information","content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/AssetFileUploadDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AssetFileUploadResponseDto"}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset/download":{"get":{"operationId":"downloadFile","parameters":[{"name":"aid","required":true,"in":"query","schema":{"title":"Device Asset ID","type":"string"}},{"name":"did","required":true,"in":"query","schema":{"title":"Device ID","type":"string"}},{"name":"isThumb","required":false,"in":"query","schema":{"title":"Is serve thumbnail (resize) file","type":"boolean"}},{"name":"isWeb","required":false,"in":"query","schema":{"title":"Is request made from web","type":"boolean"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset/file":{"get":{"operationId":"serveFile","parameters":[{"name":"aid","required":true,"in":"query","schema":{"title":"Device Asset ID","type":"string"}},{"name":"did","required":true,"in":"query","schema":{"title":"Device ID","type":"string"}},{"name":"isThumb","required":false,"in":"query","schema":{"title":"Is serve thumbnail (resize) file","type":"boolean"}},{"name":"isWeb","required":false,"in":"query","schema":{"title":"Is request made from web","type":"boolean"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset/thumbnail/{assetId}":{"get":{"operationId":"getAssetThumbnail","parameters":[{"name":"assetId","required":true,"in":"path","schema":{"type":"string"}},{"name":"format","required":false,"in":"query","schema":{"$ref":"#/components/schemas/ThumbnailFormat"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset/curated-objects":{"get":{"operationId":"getCuratedObjects","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/CuratedObjectsResponseDto"}}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset/curated-locations":{"get":{"operationId":"getCuratedLocations","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/CuratedLocationsResponseDto"}}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset/search-terms":{"get":{"operationId":"getAssetSearchTerms","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"type":"string"}}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset/search":{"post":{"operationId":"searchAsset","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SearchAssetDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/AssetResponseDto"}}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset/count-by-time-bucket":{"post":{"operationId":"getAssetCountByTimeBucket","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetAssetCountByTimeBucketDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AssetCountByTimeBucketResponseDto"}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset/count-by-user-id":{"get":{"operationId":"getAssetCountByUserId","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AssetCountByUserIdResponseDto"}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset":{"get":{"operationId":"getAllAssets","summary":"","description":"Get all AssetEntity belong to the user","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/AssetResponseDto"}}}}}},"tags":["Asset"],"security":[{"bearer":[]}]},"delete":{"operationId":"deleteAsset","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteAssetDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/DeleteAssetResponseDto"}}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset/time-bucket":{"post":{"operationId":"getAssetByTimeBucket","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetAssetByTimeBucketDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/AssetResponseDto"}}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset/{deviceId}":{"get":{"operationId":"getUserAssetsByDeviceId","summary":"","description":"Get all asset of a device that are in the database, ID only.","parameters":[{"name":"deviceId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"type":"string"}}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset/assetById/{assetId}":{"get":{"operationId":"getAssetById","summary":"","description":"Get a single asset's information","parameters":[{"name":"assetId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AssetResponseDto"}}}}},"tags":["Asset"],"security":[{"bearer":[]}]},"put":{"operationId":"updateAssetById","summary":"","description":"Update an asset","parameters":[{"name":"assetId","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateAssetDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AssetResponseDto"}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset/check":{"post":{"operationId":"checkDuplicateAsset","summary":"","description":"Check duplicated asset before uploading - for Web upload used","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CheckDuplicateAssetDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CheckDuplicateAssetResponseDto"}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/asset/exist":{"post":{"operationId":"checkExistingAssets","summary":"","description":"Checks if multiple assets exist on the server and returns all existing - used by background backup","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CheckExistingAssetsDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CheckExistingAssetsResponseDto"}}}}},"tags":["Asset"],"security":[{"bearer":[]}]}},"/auth/login":{"post":{"operationId":"login","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoginCredentialDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoginResponseDto"}}}}},"tags":["Authentication"]}},"/auth/admin-sign-up":{"post":{"operationId":"adminSignUp","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SignUpDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminSignupResponseDto"}}}},"400":{"description":"The server already has an admin"}},"tags":["Authentication"]}},"/auth/validateToken":{"post":{"operationId":"validateAccessToken","parameters":[],"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidateAccessTokenResponseDto"}}}}},"tags":["Authentication"],"security":[{"bearer":[]}]}},"/auth/logout":{"post":{"operationId":"logout","parameters":[],"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LogoutResponseDto"}}}}},"tags":["Authentication"]}},"/oauth/config":{"post":{"operationId":"generateConfig","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OAuthConfigDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OAuthConfigResponseDto"}}}}},"tags":["OAuth"]}},"/oauth/callback":{"post":{"operationId":"callback","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OAuthCallbackDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoginResponseDto"}}}}},"tags":["OAuth"]}},"/device-info":{"post":{"operationId":"createDeviceInfo","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateDeviceInfoDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeviceInfoResponseDto"}}}}},"tags":["Device Info"],"security":[{"bearer":[]}]},"patch":{"operationId":"updateDeviceInfo","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateDeviceInfoDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeviceInfoResponseDto"}}}}},"tags":["Device Info"],"security":[{"bearer":[]}]}},"/server-info":{"get":{"operationId":"getServerInfo","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ServerInfoResponseDto"}}}}},"tags":["Server Info"]}},"/server-info/ping":{"get":{"operationId":"pingServer","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ServerPingResponse"}}}}},"tags":["Server Info"]}},"/server-info/version":{"get":{"operationId":"getServerVersion","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ServerVersionReponseDto"}}}}},"tags":["Server Info"]}},"/server-info/stats":{"get":{"operationId":"getStats","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ServerStatsResponseDto"}}}}},"tags":["Server Info"]}},"/album/count-by-user-id":{"get":{"operationId":"getAlbumCountByUserId","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AlbumCountResponseDto"}}}}},"tags":["Album"],"security":[{"bearer":[]}]}},"/album":{"post":{"operationId":"createAlbum","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateAlbumDto"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AlbumResponseDto"}}}}},"tags":["Album"],"security":[{"bearer":[]}]},"get":{"operationId":"getAllAlbums","parameters":[{"name":"shared","required":false,"in":"query","schema":{"type":"boolean"}},{"name":"assetId","required":false,"in":"query","description":"Only returns albums that contain the asset\nIgnores the shared parameter\nundefined: get all albums","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/AlbumResponseDto"}}}}}},"tags":["Album"],"security":[{"bearer":[]}]}},"/album/{albumId}/users":{"put":{"operationId":"addUsersToAlbum","parameters":[{"name":"albumId","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddUsersDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AlbumResponseDto"}}}}},"tags":["Album"],"security":[{"bearer":[]}]}},"/album/{albumId}/assets":{"put":{"operationId":"addAssetsToAlbum","parameters":[{"name":"albumId","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddAssetsDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddAssetsResponseDto"}}}}},"tags":["Album"],"security":[{"bearer":[]}]},"delete":{"operationId":"removeAssetFromAlbum","parameters":[{"name":"albumId","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RemoveAssetsDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AlbumResponseDto"}}}}},"tags":["Album"],"security":[{"bearer":[]}]}},"/album/{albumId}":{"get":{"operationId":"getAlbumInfo","parameters":[{"name":"albumId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AlbumResponseDto"}}}}},"tags":["Album"],"security":[{"bearer":[]}]},"delete":{"operationId":"deleteAlbum","parameters":[{"name":"albumId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["Album"],"security":[{"bearer":[]}]},"patch":{"operationId":"updateAlbumInfo","parameters":[{"name":"albumId","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateAlbumDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AlbumResponseDto"}}}}},"tags":["Album"],"security":[{"bearer":[]}]}},"/album/{albumId}/user/{userId}":{"delete":{"operationId":"removeUserFromAlbum","parameters":[{"name":"albumId","required":true,"in":"path","schema":{"type":"string"}},{"name":"userId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["Album"],"security":[{"bearer":[]}]}},"/album/{albumId}/download":{"get":{"operationId":"downloadArchive","parameters":[{"name":"albumId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"object"}}}}},"tags":["Album"],"security":[{"bearer":[]}]}},"/jobs":{"get":{"operationId":"getAllJobsStatus","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AllJobStatusResponseDto"}}}}},"tags":["Job"],"security":[{"bearer":[]}]}},"/jobs/{jobId}":{"get":{"operationId":"getJobStatus","parameters":[{"name":"jobId","required":true,"in":"path","schema":{"$ref":"#/components/schemas/JobId"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobStatusResponseDto"}}}}},"tags":["Job"],"security":[{"bearer":[]}]},"put":{"operationId":"sendJobCommand","parameters":[{"name":"jobId","required":true,"in":"path","schema":{"$ref":"#/components/schemas/JobId"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobCommandDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"number"}}}}},"tags":["Job"],"security":[{"bearer":[]}]}},"/system-config":{"get":{"operationId":"getConfig","parameters":[],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SystemConfigResponseDto"}}}}},"tags":["System Config"],"security":[{"bearer":[]}]},"put":{"operationId":"updateConfig","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateSystemConfigDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SystemConfigResponseDto"}}}}},"tags":["System Config"],"security":[{"bearer":[]}]}}},"info":{"title":"Immich","description":"Immich API","version":"1.17.0","contact":{}},"tags":[],"servers":[{"url":"/api"}],"components":{"securitySchemes":{"bearer":{"scheme":"Bearer","bearerFormat":"JWT","type":"http","name":"JWT","description":"Enter JWT token","in":"header"}},"schemas":{"UserResponseDto":{"type":"object","properties":{"id":{"type":"string"},"email":{"type":"string"},"firstName":{"type":"string"},"lastName":{"type":"string"},"createdAt":{"type":"string"},"profileImagePath":{"type":"string"},"shouldChangePassword":{"type":"boolean"},"isAdmin":{"type":"boolean"},"deletedAt":{"format":"date-time","type":"string","nullable":true}},"required":["id","email","firstName","lastName","createdAt","profileImagePath","shouldChangePassword","isAdmin","deletedAt"]},"CreateUserDto":{"type":"object","properties":{"email":{"type":"string","example":"testuser@email.com"},"password":{"type":"string","example":"password"},"firstName":{"type":"string","example":"John"},"lastName":{"type":"string","example":"Doe"}},"required":["email","password","firstName","lastName"]},"UserCountResponseDto":{"type":"object","properties":{"userCount":{"type":"integer"}},"required":["userCount"]},"UpdateUserDto":{"type":"object","properties":{"id":{"type":"string"},"password":{"type":"string"},"firstName":{"type":"string"},"lastName":{"type":"string"},"isAdmin":{"type":"boolean"},"shouldChangePassword":{"type":"boolean"},"profileImagePath":{"type":"string"}},"required":["id"]},"CreateProfileImageDto":{"type":"object","properties":{"file":{"type":"string","format":"binary"}},"required":["file"]},"CreateProfileImageResponseDto":{"type":"object","properties":{"userId":{"type":"string"},"profileImagePath":{"type":"string"}},"required":["userId","profileImagePath"]},"AssetFileUploadDto":{"type":"object","properties":{"assetData":{"type":"string","format":"binary"}},"required":["assetData"]},"AssetFileUploadResponseDto":{"type":"object","properties":{"id":{"type":"string"}},"required":["id"]},"ThumbnailFormat":{"type":"string","enum":["JPEG","WEBP"]},"CuratedObjectsResponseDto":{"type":"object","properties":{"id":{"type":"string"},"object":{"type":"string"},"resizePath":{"type":"string"},"deviceAssetId":{"type":"string"},"deviceId":{"type":"string"}},"required":["id","object","resizePath","deviceAssetId","deviceId"]},"CuratedLocationsResponseDto":{"type":"object","properties":{"id":{"type":"string"},"city":{"type":"string"},"resizePath":{"type":"string"},"deviceAssetId":{"type":"string"},"deviceId":{"type":"string"}},"required":["id","city","resizePath","deviceAssetId","deviceId"]},"SearchAssetDto":{"type":"object","properties":{"searchTerm":{"type":"string"}},"required":["searchTerm"]},"AssetTypeEnum":{"type":"string","enum":["IMAGE","VIDEO","AUDIO","OTHER"]},"ExifResponseDto":{"type":"object","properties":{"id":{"type":"integer","nullable":true,"default":null,"format":"int64"},"fileSizeInByte":{"type":"integer","nullable":true,"default":null,"format":"int64"},"make":{"type":"string","nullable":true,"default":null},"model":{"type":"string","nullable":true,"default":null},"imageName":{"type":"string","nullable":true,"default":null},"exifImageWidth":{"type":"number","nullable":true,"default":null},"exifImageHeight":{"type":"number","nullable":true,"default":null},"orientation":{"type":"string","nullable":true,"default":null},"dateTimeOriginal":{"format":"date-time","type":"string","nullable":true,"default":null},"modifyDate":{"format":"date-time","type":"string","nullable":true,"default":null},"lensModel":{"type":"string","nullable":true,"default":null},"fNumber":{"type":"number","nullable":true,"default":null},"focalLength":{"type":"number","nullable":true,"default":null},"iso":{"type":"number","nullable":true,"default":null},"exposureTime":{"type":"number","nullable":true,"default":null},"latitude":{"type":"number","nullable":true,"default":null},"longitude":{"type":"number","nullable":true,"default":null},"city":{"type":"string","nullable":true,"default":null},"state":{"type":"string","nullable":true,"default":null},"country":{"type":"string","nullable":true,"default":null}}},"SmartInfoResponseDto":{"type":"object","properties":{"id":{"type":"string"},"tags":{"nullable":true,"type":"array","items":{"type":"string"}},"objects":{"nullable":true,"type":"array","items":{"type":"string"}}}},"AssetResponseDto":{"type":"object","properties":{"type":{"$ref":"#/components/schemas/AssetTypeEnum"},"id":{"type":"string"},"deviceAssetId":{"type":"string"},"ownerId":{"type":"string"},"deviceId":{"type":"string"},"originalPath":{"type":"string"},"resizePath":{"type":"string","nullable":true},"createdAt":{"type":"string"},"modifiedAt":{"type":"string"},"isFavorite":{"type":"boolean"},"mimeType":{"type":"string","nullable":true},"duration":{"type":"string"},"webpPath":{"type":"string","nullable":true},"encodedVideoPath":{"type":"string","nullable":true},"exifInfo":{"$ref":"#/components/schemas/ExifResponseDto"},"smartInfo":{"$ref":"#/components/schemas/SmartInfoResponseDto"}},"required":["type","id","deviceAssetId","ownerId","deviceId","originalPath","resizePath","createdAt","modifiedAt","isFavorite","mimeType","duration","webpPath","encodedVideoPath"]},"TimeGroupEnum":{"type":"string","enum":["day","month"]},"GetAssetCountByTimeBucketDto":{"type":"object","properties":{"timeGroup":{"$ref":"#/components/schemas/TimeGroupEnum"}},"required":["timeGroup"]},"AssetCountByTimeBucket":{"type":"object","properties":{"timeBucket":{"type":"string"},"count":{"type":"integer"}},"required":["timeBucket","count"]},"AssetCountByTimeBucketResponseDto":{"type":"object","properties":{"totalCount":{"type":"integer"},"buckets":{"type":"array","items":{"$ref":"#/components/schemas/AssetCountByTimeBucket"}}},"required":["totalCount","buckets"]},"AssetCountByUserIdResponseDto":{"type":"object","properties":{"photos":{"type":"integer"},"videos":{"type":"integer"}},"required":["photos","videos"]},"GetAssetByTimeBucketDto":{"type":"object","properties":{"timeBucket":{"title":"Array of date time buckets","example":["2015-06-01T00:00:00.000Z","2016-02-01T00:00:00.000Z","2016-03-01T00:00:00.000Z"],"type":"array","items":{"type":"string"}}},"required":["timeBucket"]},"UpdateAssetDto":{"type":"object","properties":{"isFavorite":{"type":"boolean"}},"required":["isFavorite"]},"DeleteAssetDto":{"type":"object","properties":{"ids":{"title":"Array of asset IDs to delete","example":["bf973405-3f2a-48d2-a687-2ed4167164be","dd41870b-5d00-46d2-924e-1d8489a0aa0f","fad77c3f-deef-4e7e-9608-14c1aa4e559a"],"type":"array","items":{"type":"string"}}},"required":["ids"]},"DeleteAssetStatus":{"type":"string","enum":["SUCCESS","FAILED"]},"DeleteAssetResponseDto":{"type":"object","properties":{"status":{"$ref":"#/components/schemas/DeleteAssetStatus"},"id":{"type":"string"}},"required":["status","id"]},"CheckDuplicateAssetDto":{"type":"object","properties":{"deviceAssetId":{"type":"string"},"deviceId":{"type":"string"}},"required":["deviceAssetId","deviceId"]},"CheckDuplicateAssetResponseDto":{"type":"object","properties":{"isExist":{"type":"boolean"},"id":{"type":"string"}},"required":["isExist"]},"CheckExistingAssetsDto":{"type":"object","properties":{"deviceAssetIds":{"type":"array","items":{"type":"string"}},"deviceId":{"type":"string"}},"required":["deviceAssetIds","deviceId"]},"CheckExistingAssetsResponseDto":{"type":"object","properties":{"existingIds":{"type":"array","items":{"type":"string"}}},"required":["existingIds"]},"LoginCredentialDto":{"type":"object","properties":{"email":{"type":"string","example":"testuser@email.com"},"password":{"type":"string","example":"password"}},"required":["email","password"]},"LoginResponseDto":{"type":"object","properties":{"accessToken":{"type":"string","readOnly":true},"userId":{"type":"string","readOnly":true},"userEmail":{"type":"string","readOnly":true},"firstName":{"type":"string","readOnly":true},"lastName":{"type":"string","readOnly":true},"profileImagePath":{"type":"string","readOnly":true},"isAdmin":{"type":"boolean","readOnly":true},"shouldChangePassword":{"type":"boolean","readOnly":true}},"required":["accessToken","userId","userEmail","firstName","lastName","profileImagePath","isAdmin","shouldChangePassword"]},"SignUpDto":{"type":"object","properties":{"email":{"type":"string","example":"testuser@email.com"},"password":{"type":"string","example":"password"},"firstName":{"type":"string","example":"Admin"},"lastName":{"type":"string","example":"Doe"}},"required":["email","password","firstName","lastName"]},"AdminSignupResponseDto":{"type":"object","properties":{"id":{"type":"string"},"email":{"type":"string"},"firstName":{"type":"string"},"lastName":{"type":"string"},"createdAt":{"type":"string"}},"required":["id","email","firstName","lastName","createdAt"]},"ValidateAccessTokenResponseDto":{"type":"object","properties":{"authStatus":{"type":"boolean"}},"required":["authStatus"]},"LogoutResponseDto":{"type":"object","properties":{"successful":{"type":"boolean","readOnly":true},"redirectUri":{"type":"string","readOnly":true}},"required":["successful","redirectUri"]},"OAuthConfigDto":{"type":"object","properties":{"redirectUri":{"type":"string"}},"required":["redirectUri"]},"OAuthConfigResponseDto":{"type":"object","properties":{"enabled":{"type":"boolean","readOnly":true},"url":{"type":"string","readOnly":true},"buttonText":{"type":"string","readOnly":true}},"required":["enabled"]},"OAuthCallbackDto":{"type":"object","properties":{"url":{"type":"string"}},"required":["url"]},"DeviceTypeEnum":{"type":"string","enum":["IOS","ANDROID","WEB"]},"CreateDeviceInfoDto":{"type":"object","properties":{"deviceType":{"$ref":"#/components/schemas/DeviceTypeEnum"},"deviceId":{"type":"string"},"isAutoBackup":{"type":"boolean"}},"required":["deviceType","deviceId"]},"DeviceInfoResponseDto":{"type":"object","properties":{"id":{"type":"integer"},"deviceType":{"$ref":"#/components/schemas/DeviceTypeEnum"},"userId":{"type":"string"},"deviceId":{"type":"string"},"createdAt":{"type":"string"},"isAutoBackup":{"type":"boolean"}},"required":["id","deviceType","userId","deviceId","createdAt","isAutoBackup"]},"UpdateDeviceInfoDto":{"type":"object","properties":{"deviceType":{"$ref":"#/components/schemas/DeviceTypeEnum"},"deviceId":{"type":"string"},"isAutoBackup":{"type":"boolean"}},"required":["deviceType","deviceId"]},"ServerInfoResponseDto":{"type":"object","properties":{"diskSizeRaw":{"type":"integer","format":"int64"},"diskUseRaw":{"type":"integer","format":"int64"},"diskAvailableRaw":{"type":"integer","format":"int64"},"diskUsagePercentage":{"type":"number","format":"float"},"diskSize":{"type":"string"},"diskUse":{"type":"string"},"diskAvailable":{"type":"string"}},"required":["diskSizeRaw","diskUseRaw","diskAvailableRaw","diskUsagePercentage","diskSize","diskUse","diskAvailable"]},"ServerPingResponse":{"type":"object","properties":{"res":{"type":"string","readOnly":true,"example":"pong"}},"required":["res"]},"ServerVersionReponseDto":{"type":"object","properties":{"major":{"type":"integer"},"minor":{"type":"integer"},"patch":{"type":"integer"},"build":{"type":"integer"}},"required":["major","minor","patch","build"]},"UsageByUserDto":{"type":"object","properties":{"userId":{"type":"string"},"objects":{"type":"integer"},"videos":{"type":"integer"},"photos":{"type":"integer"},"usageRaw":{"type":"integer","format":"int64"},"usage":{"type":"string"}},"required":["userId","objects","videos","photos","usageRaw","usage"]},"ServerStatsResponseDto":{"type":"object","properties":{"photos":{"type":"integer"},"videos":{"type":"integer"},"objects":{"type":"integer"},"usageRaw":{"type":"integer","format":"int64"},"usage":{"type":"string"},"usageByUser":{"title":"Array of usage for each user","example":[{"photos":1,"videos":1,"objects":1,"diskUsageRaw":1}],"type":"array","items":{"$ref":"#/components/schemas/UsageByUserDto"}}},"required":["photos","videos","objects","usageRaw","usage","usageByUser"]},"AlbumCountResponseDto":{"type":"object","properties":{"owned":{"type":"integer"},"shared":{"type":"integer"},"sharing":{"type":"integer"}},"required":["owned","shared","sharing"]},"CreateAlbumDto":{"type":"object","properties":{"albumName":{"type":"string"},"sharedWithUserIds":{"type":"array","items":{"type":"string"}},"assetIds":{"type":"array","items":{"type":"string"}}},"required":["albumName"]},"AlbumResponseDto":{"type":"object","properties":{"assetCount":{"type":"integer"},"id":{"type":"string"},"ownerId":{"type":"string"},"albumName":{"type":"string"},"createdAt":{"type":"string"},"albumThumbnailAssetId":{"type":"string","nullable":true},"shared":{"type":"boolean"},"sharedUsers":{"type":"array","items":{"$ref":"#/components/schemas/UserResponseDto"}},"assets":{"type":"array","items":{"$ref":"#/components/schemas/AssetResponseDto"}}},"required":["assetCount","id","ownerId","albumName","createdAt","albumThumbnailAssetId","shared","sharedUsers","assets"]},"AddUsersDto":{"type":"object","properties":{"sharedUserIds":{"type":"array","items":{"type":"string"}}},"required":["sharedUserIds"]},"AddAssetsDto":{"type":"object","properties":{"assetIds":{"type":"array","items":{"type":"string"}}},"required":["assetIds"]},"AddAssetsResponseDto":{"type":"object","properties":{"successfullyAdded":{"type":"integer"},"alreadyInAlbum":{"type":"array","items":{"type":"string"}},"album":{"$ref":"#/components/schemas/AlbumResponseDto"}},"required":["successfullyAdded","alreadyInAlbum"]},"RemoveAssetsDto":{"type":"object","properties":{"assetIds":{"type":"array","items":{"type":"string"}}},"required":["assetIds"]},"UpdateAlbumDto":{"type":"object","properties":{"albumName":{"type":"string"},"albumThumbnailAssetId":{"type":"string"}}},"JobCounts":{"type":"object","properties":{"active":{"type":"integer"},"completed":{"type":"integer"},"failed":{"type":"integer"},"delayed":{"type":"integer"},"waiting":{"type":"integer"}},"required":["active","completed","failed","delayed","waiting"]},"AllJobStatusResponseDto":{"type":"object","properties":{"thumbnailGenerationQueueCount":{"$ref":"#/components/schemas/JobCounts"},"metadataExtractionQueueCount":{"$ref":"#/components/schemas/JobCounts"},"videoConversionQueueCount":{"$ref":"#/components/schemas/JobCounts"},"machineLearningQueueCount":{"$ref":"#/components/schemas/JobCounts"},"isThumbnailGenerationActive":{"type":"boolean"},"isMetadataExtractionActive":{"type":"boolean"},"isVideoConversionActive":{"type":"boolean"},"isMachineLearningActive":{"type":"boolean"}},"required":["thumbnailGenerationQueueCount","metadataExtractionQueueCount","videoConversionQueueCount","machineLearningQueueCount","isThumbnailGenerationActive","isMetadataExtractionActive","isVideoConversionActive","isMachineLearningActive"]},"JobId":{"type":"string","enum":["thumbnail-generation","metadata-extraction","video-conversion","machine-learning"]},"JobStatusResponseDto":{"type":"object","properties":{"isActive":{"type":"boolean"},"queueCount":{"type":"object"}},"required":["isActive","queueCount"]},"JobCommand":{"type":"string","enum":["start","stop"]},"JobCommandDto":{"type":"object","properties":{"command":{"$ref":"#/components/schemas/JobCommand"}},"required":["command"]},"SystemConfigKey":{"type":"string","enum":["ffmpeg_crf","ffmpeg_preset","ffmpeg_target_video_codec","ffmpeg_target_audio_codec","ffmpeg_target_scaling"]},"SystemConfigResponseItem":{"type":"object","properties":{"name":{"type":"string"},"key":{"$ref":"#/components/schemas/SystemConfigKey"},"value":{"type":"string"},"defaultValue":{"type":"string"}},"required":["name","key","value","defaultValue"]},"SystemConfigResponseDto":{"type":"object","properties":{"config":{"type":"array","items":{"$ref":"#/components/schemas/SystemConfigResponseItem"}}},"required":["config"]},"UpdateSystemConfigDto":{"type":"object","properties":{}}}}} \ No newline at end of file diff --git a/server/libs/database/src/entities/system-config.entity.ts b/server/libs/database/src/entities/system-config.entity.ts new file mode 100644 index 000000000..32503dd2b --- /dev/null +++ b/server/libs/database/src/entities/system-config.entity.ts @@ -0,0 +1,27 @@ +import { Column, Entity, PrimaryColumn } from 'typeorm'; + +@Entity('system_config') +export class SystemConfigEntity { + @PrimaryColumn() + key!: SystemConfigKey; + + @Column({ type: 'varchar', nullable: true }) + value!: SystemConfigValue; +} + +export type SystemConfig = SystemConfigEntity[]; + +export enum SystemConfigKey { + FFMPEG_CRF = 'ffmpeg_crf', + FFMPEG_PRESET = 'ffmpeg_preset', + FFMPEG_TARGET_VIDEO_CODEC = 'ffmpeg_target_video_codec', + FFMPEG_TARGET_AUDIO_CODEC = 'ffmpeg_target_audio_codec', + FFMPEG_TARGET_SCALING = 'ffmpeg_target_scaling', +} + +export type SystemConfigValue = string | null; + +export interface SystemConfigItem { + key: SystemConfigKey; + value: SystemConfigValue; +} diff --git a/server/libs/database/src/migrations/1665540663419-CreateSystemConfigTable.ts b/server/libs/database/src/migrations/1665540663419-CreateSystemConfigTable.ts new file mode 100644 index 000000000..40dd87c64 --- /dev/null +++ b/server/libs/database/src/migrations/1665540663419-CreateSystemConfigTable.ts @@ -0,0 +1,15 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class CreateSystemConfigTable1665540663419 implements MigrationInterface { + name = 'CreateSystemConfigTable1665540663419'; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `CREATE TABLE "system_config" ("key" character varying NOT NULL, "value" character varying, CONSTRAINT "PK_aab69295b445016f56731f4d535" PRIMARY KEY ("key"))`, + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`DROP TABLE "system_config"`); + } +} diff --git a/server/libs/immich-config/src/immich-config.module.ts b/server/libs/immich-config/src/immich-config.module.ts new file mode 100644 index 000000000..14e689783 --- /dev/null +++ b/server/libs/immich-config/src/immich-config.module.ts @@ -0,0 +1,11 @@ +import { SystemConfigEntity } from '@app/database/entities/system-config.entity'; +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { ImmichConfigService } from './immich-config.service'; + +@Module({ + imports: [TypeOrmModule.forFeature([SystemConfigEntity])], + providers: [ImmichConfigService], + exports: [ImmichConfigService], +}) +export class ImmichConfigModule {} diff --git a/server/libs/immich-config/src/immich-config.service.ts b/server/libs/immich-config/src/immich-config.service.ts new file mode 100644 index 000000000..a50086fc9 --- /dev/null +++ b/server/libs/immich-config/src/immich-config.service.ts @@ -0,0 +1,97 @@ +import { SystemConfigEntity, SystemConfigKey, SystemConfigValue } from '@app/database/entities/system-config.entity'; +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { In, Repository } from 'typeorm'; + +type SystemConfigMap = Record; + +const configDefaults: Record = { + [SystemConfigKey.FFMPEG_CRF]: { + name: 'FFmpeg Constant Rate Factor (-crf)', + value: '23', + }, + [SystemConfigKey.FFMPEG_PRESET]: { + name: 'FFmpeg preset (-preset)', + value: 'ultrafast', + }, + [SystemConfigKey.FFMPEG_TARGET_VIDEO_CODEC]: { + name: 'FFmpeg target video codec (-vcodec)', + value: 'libx264', + }, + [SystemConfigKey.FFMPEG_TARGET_AUDIO_CODEC]: { + name: 'FFmpeg target audio codec (-acodec)', + value: 'mp3', + }, + [SystemConfigKey.FFMPEG_TARGET_SCALING]: { + name: 'FFmpeg target scaling (-vf scale=)', + value: '1280:-2', + }, +}; + +@Injectable() +export class ImmichConfigService { + constructor( + @InjectRepository(SystemConfigEntity) + private systemConfigRepository: Repository, + ) {} + + public async getSystemConfig() { + const items = this._getDefaults(); + + // override default values + const overrides = await this.systemConfigRepository.find(); + for (const override of overrides) { + const item = items.find((_item) => _item.key === override.key); + if (item) { + item.value = override.value; + } + } + + return items; + } + + public async getSystemConfigMap(): Promise { + const items = await this.getSystemConfig(); + const map: Partial = {}; + + for (const { key, value } of items) { + map[key] = value; + } + + return map as SystemConfigMap; + } + + public async updateSystemConfig(items: SystemConfigEntity[]): Promise { + const deletes: SystemConfigEntity[] = []; + const updates: SystemConfigEntity[] = []; + + for (const item of items) { + if (item.value === null || item.value === this._getDefaultValue(item.key)) { + deletes.push(item); + continue; + } + + updates.push(item); + } + + if (updates.length > 0) { + await this.systemConfigRepository.save(updates); + } + + if (deletes.length > 0) { + await this.systemConfigRepository.delete({ key: In(deletes.map((item) => item.key)) }); + } + } + + private _getDefaults() { + return Object.values(SystemConfigKey).map((key) => ({ + key, + defaultValue: configDefaults[key].value, + ...configDefaults[key], + })); + } + + private _getDefaultValue(key: SystemConfigKey) { + return this._getDefaults().find((item) => item.key === key)?.value || null; + } +} diff --git a/server/libs/immich-config/src/index.ts b/server/libs/immich-config/src/index.ts new file mode 100644 index 000000000..e0a5aa3ae --- /dev/null +++ b/server/libs/immich-config/src/index.ts @@ -0,0 +1,2 @@ +export * from './immich-config.module'; +export * from './immich-config.service'; diff --git a/server/libs/immich-config/tsconfig.lib.json b/server/libs/immich-config/tsconfig.lib.json new file mode 100644 index 000000000..43ec129ea --- /dev/null +++ b/server/libs/immich-config/tsconfig.lib.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "declaration": true, + "outDir": "../../dist/libs/immich-config" + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist", "test", "**/*spec.ts"] +} diff --git a/server/nest-cli.json b/server/nest-cli.json index 782df618e..99f2ed151 100644 --- a/server/nest-cli.json +++ b/server/nest-cli.json @@ -70,6 +70,15 @@ "compilerOptions": { "tsConfigPath": "libs/job/tsconfig.lib.json" } + }, + "system-config": { + "type": "library", + "root": "libs/system-config", + "entryFile": "index", + "sourceRoot": "libs/system-config/src", + "compilerOptions": { + "tsConfigPath": "libs/system-config/tsconfig.lib.json" + } } } -} +} \ No newline at end of file diff --git a/server/package.json b/server/package.json index ea0398f0c..82024a298 100644 --- a/server/package.json +++ b/server/package.json @@ -13,6 +13,7 @@ "build": "nest build immich && nest build microservices && nest build cli", "format": "prettier --write \"apps/**/*.ts\" \"libs/**/*.ts\"", "start": "nest start", + "nest": "nest", "start:dev": "nest start --watch", "start:debug": "nest start --debug 0.0.0.0:9230 --watch", "start:prod": "node dist/main", @@ -139,7 +140,8 @@ "@app/database/config/(.*)": "/libs/database/src/config/$1", "@app/database/config": "/libs/database/src/config", "@app/common": "/libs/common/src", - "^@app/job(|/.*)$": "/libs/job/src/$1" + "^@app/job(|/.*)$": "/libs/job/src/$1", + "^@app/system-config(|/.*)$": "/libs/system-config/src/$1" } } -} +} \ No newline at end of file diff --git a/server/tsconfig.json b/server/tsconfig.json index ced2fc7da..8bd2202b7 100644 --- a/server/tsconfig.json +++ b/server/tsconfig.json @@ -16,29 +16,15 @@ "esModuleInterop": true, "baseUrl": "./", "paths": { - "@app/common": [ - "libs/common/src" - ], - "@app/common/*": [ - "libs/common/src/*" - ], - "@app/database": [ - "libs/database/src" - ], - "@app/database/*": [ - "libs/database/src/*" - ], - "@app/job": [ - "libs/job/src" - ], - "@app/job/*": [ - "libs/job/src/*" - ] + "@app/common": ["libs/common/src"], + "@app/common/*": ["libs/common/src/*"], + "@app/database": ["libs/database/src"], + "@app/database/*": ["libs/database/src/*"], + "@app/job": ["libs/job/src"], + "@app/job/*": ["libs/job/src/*"], + "@app/system-config": ["libs/immich-config/src"], + "@app/system-config/*": ["libs/immich-config/src/*"] } }, - "exclude": [ - "dist", - "node_modules", - "upload" - ] -} \ No newline at end of file + "exclude": ["dist", "node_modules", "upload"] +} diff --git a/web/src/api/api.ts b/web/src/api/api.ts index e98ba36da..355171c4f 100644 --- a/web/src/api/api.ts +++ b/web/src/api/api.ts @@ -8,6 +8,7 @@ import { JobApi, OAuthApi, ServerInfoApi, + SystemConfigApi, UserApi } from './open-api'; @@ -20,6 +21,7 @@ class ImmichApi { public deviceInfoApi: DeviceInfoApi; public serverInfoApi: ServerInfoApi; public jobApi: JobApi; + public systemConfigApi: SystemConfigApi; private config = new Configuration({ basePath: '/api' }); @@ -32,6 +34,7 @@ class ImmichApi { this.deviceInfoApi = new DeviceInfoApi(this.config); this.serverInfoApi = new ServerInfoApi(this.config); this.jobApi = new JobApi(this.config); + this.systemConfigApi = new SystemConfigApi(this.config); } public setAccessToken(accessToken: string) { diff --git a/web/src/api/open-api/api.ts b/web/src/api/open-api/api.ts index 209618f46..1e60b47a9 100644 --- a/web/src/api/open-api/api.ts +++ b/web/src/api/open-api/api.ts @@ -1407,6 +1407,67 @@ export interface SmartInfoResponseDto { * @enum {string} */ +export const SystemConfigKey = { + Crf: 'ffmpeg_crf', + Preset: 'ffmpeg_preset', + TargetVideoCodec: 'ffmpeg_target_video_codec', + TargetAudioCodec: 'ffmpeg_target_audio_codec', + TargetScaling: 'ffmpeg_target_scaling' +} as const; + +export type SystemConfigKey = typeof SystemConfigKey[keyof typeof SystemConfigKey]; + + +/** + * + * @export + * @interface SystemConfigResponseDto + */ +export interface SystemConfigResponseDto { + /** + * + * @type {Array} + * @memberof SystemConfigResponseDto + */ + 'config': Array; +} +/** + * + * @export + * @interface SystemConfigResponseItem + */ +export interface SystemConfigResponseItem { + /** + * + * @type {string} + * @memberof SystemConfigResponseItem + */ + 'name': string; + /** + * + * @type {SystemConfigKey} + * @memberof SystemConfigResponseItem + */ + 'key': SystemConfigKey; + /** + * + * @type {string} + * @memberof SystemConfigResponseItem + */ + 'value': string; + /** + * + * @type {string} + * @memberof SystemConfigResponseItem + */ + 'defaultValue': string; +} +/** + * + * @export + * @enum {string} + */ + export const ThumbnailFormat = { Jpeg: 'JPEG', Webp: 'WEBP' @@ -4946,6 +5007,173 @@ export class ServerInfoApi extends BaseAPI { } +/** + * SystemConfigApi - axios parameter creator + * @export + */ +export const SystemConfigApiAxiosParamCreator = function (configuration?: Configuration) { + return { + /** + * + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + getConfig: async (options: AxiosRequestConfig = {}): Promise => { + const localVarPath = `/system-config`; + // 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 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 {object} body + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + updateConfig: async (body: object, options: AxiosRequestConfig = {}): Promise => { + // verify required parameter 'body' is not null or undefined + assertParamExists('updateConfig', 'body', body) + const localVarPath = `/system-config`; + // 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 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(body, localVarRequestOptions, configuration) + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, + } +}; + +/** + * SystemConfigApi - functional programming interface + * @export + */ +export const SystemConfigApiFp = function(configuration?: Configuration) { + const localVarAxiosParamCreator = SystemConfigApiAxiosParamCreator(configuration) + return { + /** + * + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async getConfig(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.getConfig(options); + return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); + }, + /** + * + * @param {object} body + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async updateConfig(body: object, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.updateConfig(body, options); + return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); + }, + } +}; + +/** + * SystemConfigApi - factory interface + * @export + */ +export const SystemConfigApiFactory = function (configuration?: Configuration, basePath?: string, axios?: AxiosInstance) { + const localVarFp = SystemConfigApiFp(configuration) + return { + /** + * + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + getConfig(options?: any): AxiosPromise { + return localVarFp.getConfig(options).then((request) => request(axios, basePath)); + }, + /** + * + * @param {object} body + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + updateConfig(body: object, options?: any): AxiosPromise { + return localVarFp.updateConfig(body, options).then((request) => request(axios, basePath)); + }, + }; +}; + +/** + * SystemConfigApi - object-oriented interface + * @export + * @class SystemConfigApi + * @extends {BaseAPI} + */ +export class SystemConfigApi extends BaseAPI { + /** + * + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof SystemConfigApi + */ + public getConfig(options?: AxiosRequestConfig) { + return SystemConfigApiFp(this.configuration).getConfig(options).then((request) => request(this.axios, this.basePath)); + } + + /** + * + * @param {object} body + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof SystemConfigApi + */ + public updateConfig(body: object, options?: AxiosRequestConfig) { + return SystemConfigApiFp(this.configuration).updateConfig(body, options).then((request) => request(this.axios, this.basePath)); + } +} + + /** * UserApi - axios parameter creator * @export diff --git a/web/src/app.css b/web/src/app.css index 57a11126a..8d7682551 100644 --- a/web/src/app.css +++ b/web/src/app.css @@ -59,7 +59,7 @@ input:focus-visible { @layer utilities { .immich-form-input { - @apply bg-slate-100 p-2 rounded-md dark:text-immich-dark-bg focus:border-immich-primary text-sm; + @apply bg-slate-100 p-2 rounded-md dark:text-immich-dark-bg focus:border-immich-primary text-sm dark:bg-gray-600 dark:text-immich-dark-fg; } .immich-form-label { diff --git a/web/src/lib/components/admin-page/settings/settings-panel.svelte b/web/src/lib/components/admin-page/settings/settings-panel.svelte new file mode 100644 index 000000000..95b51ee4a --- /dev/null +++ b/web/src/lib/components/admin-page/settings/settings-panel.svelte @@ -0,0 +1,97 @@ + + +
+ + + + + + + + + {#each items as item, i} + + + + + {/each} + +
SettingValue
+ {item.name} + + +
+ +
+ +
+
diff --git a/web/src/routes/admin/+page.server.ts b/web/src/routes/admin/+page.server.ts index d03b1f0e6..01c8609ff 100644 --- a/web/src/routes/admin/+page.server.ts +++ b/web/src/routes/admin/+page.server.ts @@ -12,8 +12,6 @@ export const load: PageServerLoad = async ({ parent }) => { } const { data: allUsers } = await serverApi.userApi.getAllUsers(false); - return { - user: user, - allUsers: allUsers - }; + + return { user, allUsers }; }; diff --git a/web/src/routes/admin/+page.svelte b/web/src/routes/admin/+page.svelte index d56602cbf..42388f686 100644 --- a/web/src/routes/admin/+page.svelte +++ b/web/src/routes/admin/+page.svelte @@ -4,6 +4,7 @@ import { AdminSideBarSelection } from '$lib/models/admin-sidebar-selection'; import SideBarButton from '$lib/components/shared-components/side-bar/side-bar-button.svelte'; import AccountMultipleOutline from 'svelte-material-icons/AccountMultipleOutline.svelte'; + import Sync from 'svelte-material-icons/Sync.svelte'; import Cog from 'svelte-material-icons/Cog.svelte'; import Server from 'svelte-material-icons/Server.svelte'; import NavigationBar from '$lib/components/shared-components/navigation-bar.svelte'; @@ -16,6 +17,7 @@ import type { PageData } from './$types'; import { api, ServerStatsResponseDto, UserResponseDto } from '@api'; import JobsPanel from '$lib/components/admin-page/jobs/jobs-panel.svelte'; + import SettingsPanel from '$lib/components/admin-page/settings/settings-panel.svelte'; import ServerStatsPanel from '$lib/components/admin-page/server-stats/server-stats-panel.svelte'; import RestoreDialoge from '$lib/components/admin-page/restore-dialoge.svelte'; @@ -190,11 +192,18 @@ /> + {/if} + {#if selectedAction === AdminSideBarSelection.SETTINGS} + + {/if} {#if selectedAction === AdminSideBarSelection.STATS && serverStat} {/if}