From 171b6bb0a6571262420c0bd3789d66267bc097e1 Mon Sep 17 00:00:00 2001 From: Jason Rasmussen <jrasm91@gmail.com> Date: Fri, 19 Apr 2024 20:36:15 -0400 Subject: [PATCH] refactor: system metadata (#8923) refactor(server): system metadata --- e2e/src/api/specs/server-info.e2e-spec.ts | 17 +- e2e/src/api/specs/system-metadata.e2e-spec.ts | 76 +++++++++ e2e/src/utils.ts | 7 +- mobile/openapi/.openapi-generator/FILES | 9 ++ mobile/openapi/README.md | Bin 26365 -> 26826 bytes .../openapi/doc/AdminOnboardingUpdateDto.md | Bin 0 -> 423 bytes .../doc/ReverseGeocodingStateResponseDto.md | Bin 0 -> 474 bytes mobile/openapi/doc/ServerInfoApi.md | Bin 11069 -> 9228 bytes mobile/openapi/doc/SystemMetadataApi.md | Bin 0 -> 6367 bytes mobile/openapi/lib/api.dart | Bin 9114 -> 9254 bytes mobile/openapi/lib/api/server_info_api.dart | Bin 12548 -> 11619 bytes .../openapi/lib/api/system_metadata_api.dart | Bin 0 -> 4697 bytes mobile/openapi/lib/api_client.dart | Bin 25021 -> 25237 bytes .../model/admin_onboarding_update_dto.dart | Bin 0 -> 2989 bytes .../reverse_geocoding_state_response_dto.dart | Bin 0 -> 3748 bytes .../admin_onboarding_update_dto_test.dart | Bin 0 -> 600 bytes ...rse_geocoding_state_response_dto_test.dart | Bin 0 -> 745 bytes mobile/openapi/test/server_info_api_test.dart | Bin 1583 -> 1473 bytes .../test/system_metadata_api_test.dart | Bin 0 -> 936 bytes open-api/immich-openapi-specs.json | 150 +++++++++++++++--- open-api/typescript-sdk/src/fetch-client.ts | 38 ++++- server/src/controllers/index.ts | 2 + .../src/controllers/server-info.controller.ts | 9 +- .../controllers/system-metadata.controller.ts | 28 ++++ server/src/dtos/system-metadata.dto.ts | 15 ++ .../src/repositories/metadata.repository.ts | 2 +- server/src/services/index.ts | 2 + .../src/services/server-info.service.spec.ts | 8 - server/src/services/server-info.service.ts | 8 +- .../services/system-metadata.service.spec.ts | 31 ++++ .../src/services/system-metadata.service.ts | 29 ++++ web/src/routes/auth/onboarding/+page.svelte | 4 +- 32 files changed, 362 insertions(+), 73 deletions(-) create mode 100644 e2e/src/api/specs/system-metadata.e2e-spec.ts create mode 100644 mobile/openapi/doc/AdminOnboardingUpdateDto.md create mode 100644 mobile/openapi/doc/ReverseGeocodingStateResponseDto.md create mode 100644 mobile/openapi/doc/SystemMetadataApi.md create mode 100644 mobile/openapi/lib/api/system_metadata_api.dart create mode 100644 mobile/openapi/lib/model/admin_onboarding_update_dto.dart create mode 100644 mobile/openapi/lib/model/reverse_geocoding_state_response_dto.dart create mode 100644 mobile/openapi/test/admin_onboarding_update_dto_test.dart create mode 100644 mobile/openapi/test/reverse_geocoding_state_response_dto_test.dart create mode 100644 mobile/openapi/test/system_metadata_api_test.dart create mode 100644 server/src/controllers/system-metadata.controller.ts create mode 100644 server/src/dtos/system-metadata.dto.ts create mode 100644 server/src/services/system-metadata.service.spec.ts create mode 100644 server/src/services/system-metadata.service.ts diff --git a/e2e/src/api/specs/server-info.e2e-spec.ts b/e2e/src/api/specs/server-info.e2e-spec.ts index 5cfd6a8b98..690bfae744 100644 --- a/e2e/src/api/specs/server-info.e2e-spec.ts +++ b/e2e/src/api/specs/server-info.e2e-spec.ts @@ -1,4 +1,4 @@ -import { LoginResponseDto, getServerConfig } from '@immich/sdk'; +import { LoginResponseDto } from '@immich/sdk'; import { createUserDto } from 'src/fixtures'; import { errorDto } from 'src/responses'; import { app, utils } from 'src/utils'; @@ -162,19 +162,4 @@ describe('/server-info', () => { }); }); }); - - describe('POST /server-info/admin-onboarding', () => { - it('should set admin onboarding', async () => { - const config = await getServerConfig({}); - expect(config.isOnboarded).toBe(false); - - const { status } = await request(app) - .post('/server-info/admin-onboarding') - .set('Authorization', `Bearer ${admin.accessToken}`); - expect(status).toBe(204); - - const newConfig = await getServerConfig({}); - expect(newConfig.isOnboarded).toBe(true); - }); - }); }); diff --git a/e2e/src/api/specs/system-metadata.e2e-spec.ts b/e2e/src/api/specs/system-metadata.e2e-spec.ts new file mode 100644 index 0000000000..bd17bf2524 --- /dev/null +++ b/e2e/src/api/specs/system-metadata.e2e-spec.ts @@ -0,0 +1,76 @@ +import { LoginResponseDto, getServerConfig } from '@immich/sdk'; +import { createUserDto } from 'src/fixtures'; +import { errorDto } from 'src/responses'; +import { app, utils } from 'src/utils'; +import request from 'supertest'; +import { beforeAll, describe, expect, it } from 'vitest'; + +describe('/server-info', () => { + let admin: LoginResponseDto; + let nonAdmin: LoginResponseDto; + + beforeAll(async () => { + await utils.resetDatabase(); + admin = await utils.adminSetup({ onboarding: false }); + nonAdmin = await utils.userSetup(admin.accessToken, createUserDto.user1); + }); + + describe('POST /system-metadata/admin-onboarding', () => { + it('should require authentication', async () => { + const { status, body } = await request(app).post('/system-metadata/admin-onboarding').send({ isOnboarded: true }); + expect(status).toBe(401); + expect(body).toEqual(errorDto.unauthorized); + }); + + it('should only work for admins', async () => { + const { status, body } = await request(app) + .post('/system-metadata/admin-onboarding') + .set('Authorization', `Bearer ${nonAdmin.accessToken}`) + .send({ isOnboarded: true }); + expect(status).toBe(403); + expect(body).toEqual(errorDto.forbidden); + }); + + it('should set admin onboarding', async () => { + const config = await getServerConfig({}); + expect(config.isOnboarded).toBe(false); + + const { status } = await request(app) + .post('/system-metadata/admin-onboarding') + .set('Authorization', `Bearer ${admin.accessToken}`) + .send({ isOnboarded: true }); + expect(status).toBe(204); + + const newConfig = await getServerConfig({}); + expect(newConfig.isOnboarded).toBe(true); + }); + }); + + describe('GET /system-metadata/reverse-geocoding-state', () => { + it('should require authentication', async () => { + const { status, body } = await request(app).get('/system-metadata/reverse-geocoding-state'); + expect(status).toBe(401); + expect(body).toEqual(errorDto.unauthorized); + }); + + it('should only work for admins', async () => { + const { status, body } = await request(app) + .get('/system-metadata/reverse-geocoding-state') + .set('Authorization', `Bearer ${nonAdmin.accessToken}`); + expect(status).toBe(403); + expect(body).toEqual(errorDto.forbidden); + }); + + it('should get the reverse geocoding state', async () => { + const { status, body } = await request(app) + .get('/system-metadata/reverse-geocoding-state') + .set('Authorization', `Bearer ${admin.accessToken}`); + + expect(status).toBe(200); + expect(body).toEqual({ + lastUpdate: expect.any(String), + lastImportFileName: 'cities500.txt', + }); + }); + }); +}); diff --git a/e2e/src/utils.ts b/e2e/src/utils.ts index 0047502023..96994c7f0a 100644 --- a/e2e/src/utils.ts +++ b/e2e/src/utils.ts @@ -24,8 +24,8 @@ import { getConfigDefaults, login, searchMetadata, - setAdminOnboarding, signUpAdmin, + updateAdminOnboarding, updateConfig, validate, } from '@immich/sdk'; @@ -264,7 +264,10 @@ export const utils = { await signUpAdmin({ signUpDto: signupDto.admin }); const response = await login({ loginCredentialDto: loginDto.admin }); if (options.onboarding) { - await setAdminOnboarding({ headers: asBearerAuth(response.accessToken) }); + await updateAdminOnboarding( + { adminOnboardingUpdateDto: { isOnboarded: true } }, + { headers: asBearerAuth(response.accessToken) }, + ); } return response; }, diff --git a/mobile/openapi/.openapi-generator/FILES b/mobile/openapi/.openapi-generator/FILES index 42f1034dce..64229329aa 100644 --- a/mobile/openapi/.openapi-generator/FILES +++ b/mobile/openapi/.openapi-generator/FILES @@ -13,6 +13,7 @@ doc/ActivityCreateDto.md doc/ActivityResponseDto.md doc/ActivityStatisticsResponseDto.md doc/AddUsersDto.md +doc/AdminOnboardingUpdateDto.md doc/AlbumApi.md doc/AlbumCountResponseDto.md doc/AlbumResponseDto.md @@ -123,6 +124,7 @@ doc/QueueStatusDto.md doc/ReactionLevel.md doc/ReactionType.md doc/RecognitionConfig.md +doc/ReverseGeocodingStateResponseDto.md doc/ScanLibraryDto.md doc/SearchAlbumResponseDto.md doc/SearchApi.md @@ -174,6 +176,7 @@ doc/SystemConfigTemplateStorageOptionDto.md doc/SystemConfigThemeDto.md doc/SystemConfigTrashDto.md doc/SystemConfigUserDto.md +doc/SystemMetadataApi.md doc/TagApi.md doc/TagResponseDto.md doc/TagTypeEnum.md @@ -226,6 +229,7 @@ lib/api/sessions_api.dart lib/api/shared_link_api.dart lib/api/sync_api.dart lib/api/system_config_api.dart +lib/api/system_metadata_api.dart lib/api/tag_api.dart lib/api/timeline_api.dart lib/api/trash_api.dart @@ -242,6 +246,7 @@ lib/model/activity_create_dto.dart lib/model/activity_response_dto.dart lib/model/activity_statistics_response_dto.dart lib/model/add_users_dto.dart +lib/model/admin_onboarding_update_dto.dart lib/model/album_count_response_dto.dart lib/model/album_response_dto.dart lib/model/all_job_status_response_dto.dart @@ -343,6 +348,7 @@ lib/model/queue_status_dto.dart lib/model/reaction_level.dart lib/model/reaction_type.dart lib/model/recognition_config.dart +lib/model/reverse_geocoding_state_response_dto.dart lib/model/scan_library_dto.dart lib/model/search_album_response_dto.dart lib/model/search_asset_response_dto.dart @@ -419,6 +425,7 @@ test/activity_create_dto_test.dart test/activity_response_dto_test.dart test/activity_statistics_response_dto_test.dart test/add_users_dto_test.dart +test/admin_onboarding_update_dto_test.dart test/album_api_test.dart test/album_count_response_dto_test.dart test/album_response_dto_test.dart @@ -534,6 +541,7 @@ test/queue_status_dto_test.dart test/reaction_level_test.dart test/reaction_type_test.dart test/recognition_config_test.dart +test/reverse_geocoding_state_response_dto_test.dart test/scan_library_dto_test.dart test/search_album_response_dto_test.dart test/search_api_test.dart @@ -585,6 +593,7 @@ test/system_config_template_storage_option_dto_test.dart test/system_config_theme_dto_test.dart test/system_config_trash_dto_test.dart test/system_config_user_dto_test.dart +test/system_metadata_api_test.dart test/tag_api_test.dart test/tag_response_dto_test.dart test/tag_type_enum_test.dart diff --git a/mobile/openapi/README.md b/mobile/openapi/README.md index 3ebd65025b606cd030899cfffaad5099acb6832b..27d631e4fd03f971074a7eb7a3764485ec64ad20 100644 GIT binary patch delta 491 zcmex+mhsd@#tocCo4bsjvr7h77MG;v`lgm7rX-dmIu>MVDby%LYiXrVmNAl6LzmRc zO;Jt<sm;yI%g;;7Pb^Bw%uAnq!BV6?7ixq)SW*`u3p9^Q3#T=yB|)iWsYS)9?y337 z`5@DROA<>`wX|Y2Qu34a^)WO;olpePl@8OzS`5+#a)~qCB~UfGa7DUcMR*-lS^x}( z)X8#2l2X__50&;Zl9K~V>%uKV^Y-R!s~w!ipm6eshf^rnY?qRJsNWp1NpBW&nImL_ cHBf?5iwpAeic=9LU{yA`AyRbn<}fb-0JILcP5=M^ delta 76 zcmV-S0JHzf(E<I<0k8=&lZP%60dtcHIvbPAF%ScDWptBlJ02Kia&~2MEop9MZ!ckF iZE0>TZ*F35VRB@%|1t3gv#vX`3A3O@j}o(gSW6IS@g4^N diff --git a/mobile/openapi/doc/AdminOnboardingUpdateDto.md b/mobile/openapi/doc/AdminOnboardingUpdateDto.md new file mode 100644 index 0000000000000000000000000000000000000000..b25084301943eb5495f27b1e781338f4732efdd4 GIT binary patch literal 423 zcma)2!A`?4488j+EcGxJDcv1TMco03jS0bNn$)cEMnn@wsW~9=@g$`ZgA3&1Jp1|C zeid@0V4|x%TN>)ddKVtTkxjBLoS&mLLai`BRpC7wi}FG^aWR_y)tm{suCpcyI3GC) zF6R98th_4fg|N6O)JbWVaxsac5w^w?e&Ex4ETN^bPcDk%kkOVUGxWvF$qS_QUl^2f z<9rWUr7~ZsBl9qQGXmJ}Z}TB2jGJNs4sMjg;i|4zkL#x0tZQ#8%l(_DAf_y)Pn`5* e!MMcl?q+xQ&)#f^reGtxAU`C&2L2kp1b|PEh=TtB literal 0 HcmV?d00001 diff --git a/mobile/openapi/doc/ReverseGeocodingStateResponseDto.md b/mobile/openapi/doc/ReverseGeocodingStateResponseDto.md new file mode 100644 index 0000000000000000000000000000000000000000..87f8aa8ab7cfc8c3ee7b938638b9538caa64edfc GIT binary patch literal 474 zcma)2!Ab)$5WVLs2KG?9knLShm90{+rEK+73L7?K4en+_G75qp?_^!DilDiKH+gSf z-Yb9tdJ|kVkj1`g@D*diTcnPx^QS!<#s*U$pRi_9swm)&j0Ju|7&Q<~_iRq6+P1YO zvT{S9$S|Acmy^RHyJW)bDpSWzyC{#59VWFk1HwDpzlD6&n7Zf!JMx0I#KO>LM}}ue z>%Xun%ibhjk6*iSJ!ZSI6j@pBIZDzI2%eY^!AkPq8R~GjmdwBeo7@`oF7ferQB{j( ywX8R*YOsZ+?qxtP{YeJ>NKub%r8#`5m%H^pwbGqNZv#2c-&TAY{xF{_r9J=#W|SBJ literal 0 HcmV?d00001 diff --git a/mobile/openapi/doc/ServerInfoApi.md b/mobile/openapi/doc/ServerInfoApi.md index cb5cf0fd3ec98fc4cd06b4afb039859add422bee..e8121a8001e11a58ed44ca847a54b7d8ff49eff2 100644 GIT binary patch delta 12 TcmdlR*5k2Z5%XpqmMjGTBS{2` delta 261 zcmeD2*c-NC5wmEtmR50UiDOD`W}bguQhs7lN@iaA<OPhPmO$Y|kZ?X+SW}@!K}##Z zKR857OF_RlwWut$NH;StEngq3P#3Cl^K9lq1zlyJQcRPzw7BdPFoh>8O4x3;5&X!k zkXVozpP5%&l9-pAs)wmiV{(D0lMtr(<b`~(l4bdsDGC~?xdkPa3Pq{K1^Ic!sR~K? fDV3AsS;Z#jiLK=J%P&z#NlnYlOHG-~D6j(nRP|o4 diff --git a/mobile/openapi/doc/SystemMetadataApi.md b/mobile/openapi/doc/SystemMetadataApi.md new file mode 100644 index 0000000000000000000000000000000000000000..f8c2347afed6701d0a62ff44945123b26b3fe1f9 GIT binary patch literal 6367 zcmeHL?{C{S5dE&d;s6h@EL1YKkJ%Q$O}w<o8V9!Xp~!-Sk<K<3nbagYjo0LV-=nBF z{@FTf1GI$=1dho&-tl;RPfs<{87q;=$o}=Fh2il#HX;#5v@+>=wHp1YMM7r8)H;1j znTT)1kiDy`t3>3+ljBV1hU(S$UbS4)E@utuzgknzYo#fjb>9{wa;BV9VdO27(G*zI z!1EAwq!YTQH~s!8EdH5rVes>ii#V5=ky?4-{0DRMKf~qU-gyuV*|d_eR2_A#MV?4C z41x=PWt-7BslilOQ#~_nV8tLfYWIVHnnhWfa9q`<S=xqA#;~mdcKMd`f{$3o+D7ad zVfa&FbLTL8bSSLDCxcfW;mjp0eX1NL8BUM4-M6z|yY<sfZ|l9IQ&6mR_4f8@DVMV{ z!9yJWs=eRv@TsBg+IM1{rO)cLW|R7z!wwy2HITyzI?cB14Npl-&|SIw7j3829>rSU zNcNh|ga=}h8npUBDp{GxW&~UOE927mH_RRL)DG_Zb>~=b*z1B(`km_UJ7?WX#JN0f zzel$thlz^yc#I|Jnp6G3c6q^Ol2MlPK;Dth(J-RdjH{40XrS}QG+&V%kYY|a`SRRO zT`*+mBDvhQs}FW}iL(8VBr4BuvqH<sZB?QYk6a0t+vvJHLVM%~qG0+tqBDH;Opa?$ zeSO1fy9&NBCUX(0HggN3i0GSE9CJ~WtNB&49UQcKy~}>*xP4M@lr2}r|HZ}s1N~>) zoIZ?XJi;AQoC6ThhDa!~*;W}&^&~IXJ#r>Ge{}U5^pT<{qKB`%fz2VY4{soqNXed( zR6sNpGcMQ)TKm@2<Wu_S5kAf-=K}W_=JVd=ZC=EG>ukL;oRRUJ7ls}Pw;jdu0laQm zXHak7#axmPs>upgX=)F5C8=<K70dSMC;m&>6MAqYXkqPC<N_=Litzd)SrDrv)5wJq z%>^lKN&q0Crr_GjG;)a81&HK|%rs-qlFcSqwPdqt4paw(wTakPn?WMhbI^9-M^Ure zZXLdBTUK;~jaPJD-JPZRYSbFIo?V)EgH^-@W*@OgY!N&PK^#DA1HcUX*jo_n(Fy7R zY?ca0SQ^+{kQqBAvMjBzx%qd2rvmT%{576REX*36U0nFJ20aZ~21sRLFut&}M8^}0 z(Iv=-0O1gO2_H|Rm|x<ZPB`6ik+HR1@OH&q3U4do_;SPJ0$6kHOqYvHtAZ=`Tx+xA z&>e@q*i%Mz<7WiF<Io?ULpPwz3o_~2h%e2jYlF@KuI1Iw+3fhWdUE(C_;rI62Dz@C z@k<%}^s-<kRCl6uCrV#Rw(kh_M-io!d|ecrGYo+?-6YB&N(r)j)3klBHz#;)!#wKa z=$Sb7Rg~`9n4cqO?*y_JJFyB6UvsZ@2|uCtxOm?CU3e|L|Mj4!HsLqk;NH_0DCT>c n;~Eiut7QUQ7-wdRSJqCM(X~#d--E_p5z<RSdtQToryBb|U>aw- literal 0 HcmV?d00001 diff --git a/mobile/openapi/lib/api.dart b/mobile/openapi/lib/api.dart index 8520bab3056ca6340d4b5fabdb3bdfadb00de704..44bd35a6838a99692a4c97328b6ed3feef902bc6 100644 GIT binary patch delta 86 zcmbQ`zRY7oIV*p0WpPPrZhUTPNn%Q3N#f>2)^m*fDY=<>@%ed4`H4j-nR)4x4LB7x o>$7j<6E8|FOD!r+jZaU_PtFHP#TS<(mZVND<W=1KKy()`0IAR+M*si- delta 26 icmZ4HG0S~JIqT**tUDMduV-i7+`+zvZ*z{=E?xkWs|rs5 diff --git a/mobile/openapi/lib/api/server_info_api.dart b/mobile/openapi/lib/api/server_info_api.dart index 77840acd19247702f1fb83175465b4e491d35b9f..b67045add13f3825be1657f7bd05c6d52f845159 100644 GIT binary patch delta 10 RcmZokdK|SOQJ1Nf3ji7Q1M2_) delta 156 zcmaDH)snO!QFn4TkEDEne{hI`esOA1S!$7PW?ovpequ^)W}a?-UQ&KyQOaaJ4LhOY z)DlOKpg%+`GcSGeMD_#$6oo*+&3A<)8JU3wPHvE#C0>@FnPR7ap>=Wsi#j#~6%-b- J)N<8w0RXDpIMo0E diff --git a/mobile/openapi/lib/api/system_metadata_api.dart b/mobile/openapi/lib/api/system_metadata_api.dart new file mode 100644 index 0000000000000000000000000000000000000000..f3952fda8a565f4974b78534c216d7bf97c17544 GIT binary patch literal 4697 zcmeHKU2oeq6n)pPxa~u253=0_dl=lto|`ynfHn3K2gMKsDq~&S>||0UskmN||GrC7 zlI>Jpx@G8EAP@c!$@{@`&pjgB?V#O;{>6CMJRY15&idoQAsn6jItbx#2&cm_ycr&z z9R2=;mf4lR#7vp}z1EL=fqmpNAr(ebsiG-QumhRMG{zAoxIkgT;+Zc}sZ6B>OO|Y< zqck&-!Vei&WG>i(zEh^?*PznmT<L1_i4@u}VIm$=KtAQDoVl(HmT-YON~D<4GBal) z`uTY@nKPw>fWjG&Q;;bNmhu*TUIalfNto7fzS0I4Z_u!q8P-oZd<x+6(fTD#9VDDm z=>a+Lf3F(QSwF%4#{XUqVw|!pF{L2|1(<Td5-7*k7H+mp(2r0N><bM{!0YjN1hwPA z7~0w;+9cdurfn84xM)goEt!hBnAPn56f;~3vW!8Y$&}Dw<_MQ(s8hlduUeGCQARWP zvKZ^a3{Bs98<x)Aax;HzOnM@wvfcouS7PFT0Vv7QfmGp?;C7XYVkAI)d`&@$ou<sp z2^dwoe*+GIssIi%D(-|O-u3YMJx-_)snq6`j90m($M!>pYBjPH(-ipSZ{=h6^6JG# zW^-gQs`bKiqX@<j{BQTu?dD=zk-uH)96Ka{z76qYyh_n!kUO-|EdwAg&ARk0E^g!> z*dLwL-8_IrEzJMh^4E%ojm;H91qj9S=Dv!tb^67~W|A$g2aD9Kpa&vL65{mMtm{#4 z*RYt}+Fj%%y2G1`l)4BHjqFvi>JF29xl~R~+dWIh&An{&W#Ln(7p5%|3X|yr86$M} z!4mh}jkK;=d`8-X+B9<d0yTt^OWT2ij}uI7zf?I2^K2}y$**PEtCc}ru5CChEE=|? z)D>^%NS#N;T}XqVQQe>_yHH5DCKs@z#-c?&VJk{|&wmPGN$cl~kQdUBY}kOnjqO$E zxo=z-8@@nmvQ*p<Q)!Ir5Z+ng?oh!foOwq9$c^73>BrLxfge-q6^sE%6v&zjQsk$F z>m%YdGbIwRw3{$$<^0Pc7xILTAlmhaPi(6Kc5_6dQ)xVto;{<jd(GLb&XcX`2zlUJ zWnCPvS~>Qm&bDGCJUL7FpV+rrRQFWbU7QeVyQo<Ut)RiKX-_YQk1uVWlI@I={r^Cx z6;fd|HfJcQu7qpT79Srb*Js}8apBX_9lwTX@7m3yc7K1`T}h(iR8VSo<*F}M@=EJ% zqCV>Rqn>|>o>x+7gRZ%*-?!`m`aT+-|EC9W_ZBJdLLGYN(1`zrAK-G=8|n(!_G!I5 zb=f14ziBnBx6cmxnLD1{@w?THcjnffcZGB>({KBBU{97i0Q){J4mFmh37@#TUHiS3 zqV{jE5|+W=-d*A=^}&tgaTh9kp#D(a@3_lU2HR@5UCa#g>&4b7+g}_0zkAMYa2xyu Dpg1Ye literal 0 HcmV?d00001 diff --git a/mobile/openapi/lib/api_client.dart b/mobile/openapi/lib/api_client.dart index 0a0cd80088bbdd96df0fef2da6db126d89dea032..a92f1df7a7bed8c052cc85c84d8993ab98f23266 100644 GIT binary patch delta 108 zcmdmcm~rY+#tolk_)>B+^ZfIY@)L_vGV{_WD=NyN3nfZxZT>71uP7RnT9#T=oa&yM fpPUa;99)uEk~(>!gS-efnaLmQbvFmwWhnyy6;vu) delta 19 bcmbPwlyUE2#tolkHiyf`D{j`Z&sGKiS`i2B diff --git a/mobile/openapi/lib/model/admin_onboarding_update_dto.dart b/mobile/openapi/lib/model/admin_onboarding_update_dto.dart new file mode 100644 index 0000000000000000000000000000000000000000..50c4ae090ede8409ad0d2003b51e6b03e8eaf215 GIT binary patch literal 2989 zcmbVOZExE)5dQ98aRG)}0aSVGry;4m28%PaYh$2w2Mk6a&=O^_lSz%FY8a{i`|e0d zp;s#jY9O&i-5a0hxg#f&(PRQwpO=f5f6Q;^AFr14Yq)v)ejda10&W*e_^`Nsd-L}h znvvz3oM}6KmHhH*M2})4mF8)obXtgtUqB-p!}F9^e9NVcyJxYjm9{56ShZt&lh%z* zHUGB}8r>z^;{Tdy{I*;h46e;-_e@D+nY5|MF`*~~*UsIXOjZfWO<F0rK{H!2ng057 znw3nO(FoI7Pz9)(OIC{n|1L+PtYXH%RlXH+A=irOT*%GmI%k$&TLt$cfC&Qek=sT~ z0}LeJ!D`6<4umT=;|V$rSw$2Tpo0T!DNG_vo1f<~zCN5|aR6(jDhOV=#W(6J*BSxo z3~)=DS*2k*jTY-KJj0FJlIwGr&cFkgu_~fGarfriH~$Nx4;Bh9UbsqKu&_c*^k`K= zTG((4B{SvkD(6^-3)D-Blq7k^jbVCEN<eZ;ZiI1WQLy8ri{SwY_x{OhBnzzaKO(o? z@et~(An5d`;NfoUQ2oH_*FI1T`5q-IvKhvHXOzUqp0YwnR>2osf+>7weSf%g<%6?W z>2Z)`e1{(YC)%59r!D703kTx;hcpLF=HOe=fUpL>vZ~<*3az$n%#~Y0nHFd;COxlk z-*+*{rmDOkcEULI23*ye7uZm)7j=`N(o9MkOtCes$WrX$Qsu;*Ul<hBZ>|;xX$!Hh zvBMY3id==HN-V5f2RK6BnIsuA_B#$DFB9Ki)U$)NL|Cr>1T<a1meqd|Y>Q&KulZ|g zz*$6$$8b#O`5E;oeJ~|`9o+wxO~SKhvqj~-z=DV5IfDvc-yl^-@c7L4ZVqg;+Oc(o z-2?@z437^TJ{}#-O;cG?=3tW5ov)RErqF~lm4%(|1c*(3tL%JR+kF=svB$|0wJFpN zN1QW#c;B((nVSwGo{$jT?r<Y5Xaw8?>t|CZ#4t3?N!nJz^4x19@br*(&>s=ddRax= zi#q5op!?;bxL#}73HyH#Ka$5uj=GXPOlTDjh{15(#0crYIv!=i(KhvL4YdHykM``o zfc}_G@ub58oepk4W{;S~RlV=HP3TE*)9s2_@;iGz$1Y&v>;uN0;2MEjdB=bFo<^qg z_nM_59w8iE`-a2q<0FkXhnDhZ;!fBSm#EL^A~3|tOK*<e?F7YB0Nv<jL{YKJPbh{| zHr#eIawL<J+KJ(L{f#j~RXvgNN3``!**r&r?Q9HY$CC=f{T-7(#85&M_D}{wi~II| zX!N*OpBK;3l)#jMXeO{Md9RQDeuh#scy=?Szd`A&IpBe4FfFwUgLj`6w{+cWRpCy_ a{&M#x`Sy2(*}a41=wje*dzT{|MgIXv!PQ^@ literal 0 HcmV?d00001 diff --git a/mobile/openapi/lib/model/reverse_geocoding_state_response_dto.dart b/mobile/openapi/lib/model/reverse_geocoding_state_response_dto.dart new file mode 100644 index 0000000000000000000000000000000000000000..71e1d3ad99b5b3584620c8b322926fda965ddadd GIT binary patch literal 3748 zcmbtXU2oeq6n*!vxB-U7!Bn~H(~!(gizO-2HZhR60|vto7>SD6$)ZNmHH_5%efRQ8 zwp=NZw*^RIQ}_EE9#VrrZ!m!OUnb+%A1-e%@7_-?FW~C>^JM@RW4Ikp;AVVref8%N znvvzZL>M=G8~*aPM_<)ru8oXRZK70V@)VZ&(#j-~nOw-+1=V-8D70~{9wJ+bwT+6U zi;VnxsSLX3Vu61PVer3`#$s?|+udWGTPJcCsRR?MR7vCAZELbj<<dr(&L7dt%|#yl z`X-9!!q{F9(>c&7=tAbAP$B-F^?Gq8tc5%I4a<-pq>go>^2d88oV=5^!0htEX?W@Z z3~(TK(k)GHK|n57Fzxc2f%4_YGQ<E~)_uwXP>l)PT-gxobJu>nSBStZPAquF>PwMe z)%0|8f}H`}JA=KPLo-C<G5h-EcN%A5aNHQ-LF<eLW`xrQhQt2&;j4^sjBYN?2@FTT zu*4$kQ?)_$<okF21nPj{zIx?5@<Chr#k?&oK*NiBsH~sqNaYS5rGvS!^WSwMu?L@g zOLpRTE@Nq}Fl*u{(rzxTvfixUoFyg9mstkq@CHs>Dm@CTWGC?RF3PUlAH4ZN&8q<m z0}D>`hrUPRxQkxXX6?hDnrne?fs5eh{CRfl>$nlcZ=5!OWQ{vn#Urfbqt!Xq!3<7S zE;9IvOE83n_0#6ePeG;NNz(@T5zn*=2tL@<95>57o?(t>b_(a&fXu8cOGBJHVi6jp zW3$W>w3o_bll`)It-G9>?~i2|uROkXinE~&{=XCnM4kW(w*}=ad=uGHT1bsvl(DCN z2}LYLgJV*?DZ)PsnD2hSu9WrKR@*;?ktxcrS8lV6Q8{cxni8RqI~7N$`E#Ane&U5r zT|D<Sd4`pY<60|r5RrVPq&8S!wGOaHL~jztsHY7|_E|7leM#XvYwBb9{L`f5DJ(?s z2TgOFPS%Ai(~bJHBLZk;@d>Lsg`|K?go6T=oY@AzRw&BG|1DWlk;${Bocd>lgXq$X z5oBm~g_PJEKR5E^kB3L2SK=W<C52SrMhiC8{CWCD+GXZQ2s)EZXIwi0N+}3WDigKd z6F*e;R=dkZ;nr0S#28Peewj+I@WAz^yXO@Pm?6VXWJB5jzFTap93~BP$Fj!mr^no8 zD3XM^So35kDoFN#DZQMgHv82mPoetcqtsEW%WvmAiHEJ&Rny_Ip;cX{JHyrmyn+eq zdJ!dWH)Ir_+k&WP<&E<cwwJ>Q?`L=orRydi!F!0~s##Z<hxAQJTXjdUaEJdMdmlLT zel;XJIDmtkvg2QJP3MyLXHHWwFAxD;YqCzjk=**JX})u<l$+xc?MJ%CcA@l|-7re2 z?086{JKgUo`c8dsY6m*8uzQ=1C;*wZaw~wtbOB@3S$AOS|H8ckX>|xiOVzJwPA3Dg z&wWyKAr}(F+tJpD7;I%>ClJAwolLGhb-~m%QVn1-ms|d9-!hP@7B5f&X|Y6VJxIU< y++teVRsufWk8kPar93tKslI(h`9!|kE8ghIkEiXD#;2QqQl3}-J>{&2+V&4Yf7_Y> literal 0 HcmV?d00001 diff --git a/mobile/openapi/test/admin_onboarding_update_dto_test.dart b/mobile/openapi/test/admin_onboarding_update_dto_test.dart new file mode 100644 index 0000000000000000000000000000000000000000..09cc73e977c0748ac9d4babb2828f7e65a246e9d GIT binary patch literal 600 zcmZ`#&1=Fi6u<Yccso_W+0D~XgbA%f*es0huv3ifYYl6YEJ-(H_`mNZDh#HFybt(& zy(CGLB(QiZ^4q6uoxLuKEQRIjIU7Nm!#XeEB~Mq&_ZxzF<YUE+n@`5~lgO{4k;)*e zm0`7LaSFZcEmjO$>`=PVc^92l#$9Ca_M7i5)4gK`KYL+_E_p{)b3@&su_SKnWqPTk zbzC|oDl!zcKojWpqFE~>TGlGr5X|kkWDj?&+;L;0=$H{=&C3to;8bapT#JO>*k2vb z{y`jCm-uo9cvHVypf7<%)d`u)t>UH<vU$@LcbGaAeJN2v$AugW;U@whRxKoNL4;#U zg!xtSU^t72(iqk2LF~=0LgNvf41&d0DRQu<Kv<CqgtR21!T@fEpbcGa+#WW6u$!Y6 Yw|iNJ$ev5Fi#*MP&cVl`DfC113;U7BF#rGn literal 0 HcmV?d00001 diff --git a/mobile/openapi/test/reverse_geocoding_state_response_dto_test.dart b/mobile/openapi/test/reverse_geocoding_state_response_dto_test.dart new file mode 100644 index 0000000000000000000000000000000000000000..91fdfcfea4030dafc64cd9cb685a3d2efbaad4ad GIT binary patch literal 745 zcma)3&1=Fi6u<Yccsr@!?B;1G!i1^_n*~1(JH^mGYgkRP<kca=|9!7<UZ$Xjrg`xD ze2L>Ij$yc3rjL`+Y&0J(M+uClZ=*IODa_I(yr;=@{PDoB8u^+_M}uzXxf|7^DztGZ zHpYpKDzOKZt~}-fOWdKR_IXx2YaCr=$nskrys#AshhLR)OxJS9r<IQXgwC_L^VjK_ z(VnCxp>j4<8-*^={Z+G4Y4oBrx?mV>r4}zwBHK#mqv(_o8dheXvcR6PsHIggf1SE@ zqkjP*^c2^}8Bm+nvj<)Y%yG}*ae^i@xzfdgB;g#r<;<9niS|b2u#-wRTksPBFngo4 zEJ1}*8N%SENVC<Cm|8ef*=D0Qze#Dd;m<HwF352>D5a-qc&9Nx<&0O)vJmR9t_c^Q zErNBt8b=3MUsBd5ZRrm>3zcdKuC+{)G{_xKasL~yY|gv6%kTuf&xi$s;W7FJA!qh$ literal 0 HcmV?d00001 diff --git a/mobile/openapi/test/server_info_api_test.dart b/mobile/openapi/test/server_info_api_test.dart index 68cd1c348bcf9c43aacbf924cc90ca0391480ca8..dac465116eb42c185c525ec1eee5ca4a399c6078 100644 GIT binary patch delta 14 VcmZ3_bC7$(ZdSHhO>3@NE&wM_1aJTV delta 65 zcmX@ey`E>oZq~^T%v{0>#i=EZDY=<>{&`9HiA5=ydFhk?GD`?y5njlw&Q_~w%~i_< E01$~64gdfE diff --git a/mobile/openapi/test/system_metadata_api_test.dart b/mobile/openapi/test/system_metadata_api_test.dart new file mode 100644 index 0000000000000000000000000000000000000000..bc1ce6f6f33849d10723585ed976cf7f29255adc GIT binary patch literal 936 zcmb7>PfNo<5XJBL6yvECs!>nkKP;pbK@HS;@U)D{G%hB)>+VD;;&*p8RgoG+57{uu z@6Eh<o2E&c!uV#MZ=Fr9CbRK;lEL)+Vlsj(hpT)Jmw9$Rz1<KjlBXqlKHl5j-AiJr zD`P#Wh4pHoYdV0|w1G-RH8o^-^ik`^S<k%+te<filxtaedTg~9xxq$GhhE-HK1g#P z`sIZ+fzhzir3};yO}>+d-eI*i6jW_ZB^ckMQTscpxJMt7WaSYC4vPn@=)gKM=yWR2 zcHDY!_zhtSoW|n=h@qqqzXXs_;CdODnoD9Vk#X#F@?>=h4QeyogqH+B@P#&5gYNby z2#&w2hMR|pu$8y18xDSHKN!J#-`IKDa_i}6TsGR|=FXxowW)5Lc%h8#2~@;uKShmU zX$qMJ-5#rPJ>+Us2X~7^fh;r6v%C|(t~`J7F{3BBOFAQ4*l6UM#m9_-lPmo6(+mCL hs+a$_H)y+1*27q<Ka&IeA<`ex>+SwWlQGBF<PA}THTnPm literal 0 HcmV?d00001 diff --git a/open-api/immich-openapi-specs.json b/open-api/immich-openapi-specs.json index f49df7baea..4f666b303c 100644 --- a/open-api/immich-openapi-specs.json +++ b/open-api/immich-openapi-specs.json @@ -4908,31 +4908,6 @@ ] } }, - "/server-info/admin-onboarding": { - "post": { - "operationId": "setAdminOnboarding", - "parameters": [], - "responses": { - "204": { - "description": "" - } - }, - "security": [ - { - "bearer": [] - }, - { - "cookie": [] - }, - { - "api_key": [] - } - ], - "tags": [ - "Server Info" - ] - } - }, "/server-info/config": { "get": { "operationId": "getServerConfig", @@ -5885,6 +5860,103 @@ ] } }, + "/system-metadata/admin-onboarding": { + "get": { + "operationId": "getAdminOnboarding", + "parameters": [], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AdminOnboardingUpdateDto" + } + } + }, + "description": "" + } + }, + "security": [ + { + "bearer": [] + }, + { + "cookie": [] + }, + { + "api_key": [] + } + ], + "tags": [ + "System Metadata" + ] + }, + "post": { + "operationId": "updateAdminOnboarding", + "parameters": [], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AdminOnboardingUpdateDto" + } + } + }, + "required": true + }, + "responses": { + "204": { + "description": "" + } + }, + "security": [ + { + "bearer": [] + }, + { + "cookie": [] + }, + { + "api_key": [] + } + ], + "tags": [ + "System Metadata" + ] + } + }, + "/system-metadata/reverse-geocoding-state": { + "get": { + "operationId": "getReverseGeocodingState", + "parameters": [], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ReverseGeocodingStateResponseDto" + } + } + }, + "description": "" + } + }, + "security": [ + { + "bearer": [] + }, + { + "cookie": [] + }, + { + "api_key": [] + } + ], + "tags": [ + "System Metadata" + ] + } + }, "/tag": { "get": { "operationId": "getAllTags", @@ -7180,6 +7252,17 @@ ], "type": "object" }, + "AdminOnboardingUpdateDto": { + "properties": { + "isOnboarded": { + "type": "boolean" + } + }, + "required": [ + "isOnboarded" + ], + "type": "object" + }, "AlbumCountResponseDto": { "properties": { "notShared": { @@ -9618,6 +9701,23 @@ ], "type": "object" }, + "ReverseGeocodingStateResponseDto": { + "properties": { + "lastImportFileName": { + "nullable": true, + "type": "string" + }, + "lastUpdate": { + "nullable": true, + "type": "string" + } + }, + "required": [ + "lastImportFileName", + "lastUpdate" + ], + "type": "object" + }, "ScanLibraryDto": { "properties": { "refreshAllFiles": { diff --git a/open-api/typescript-sdk/src/fetch-client.ts b/open-api/typescript-sdk/src/fetch-client.ts index 9148b4d3b1..1bf219162d 100644 --- a/open-api/typescript-sdk/src/fetch-client.ts +++ b/open-api/typescript-sdk/src/fetch-client.ts @@ -998,6 +998,13 @@ export type SystemConfigTemplateStorageOptionDto = { weekOptions: string[]; yearOptions: string[]; }; +export type AdminOnboardingUpdateDto = { + isOnboarded: boolean; +}; +export type ReverseGeocodingStateResponseDto = { + lastImportFileName: string | null; + lastUpdate: string | null; +}; export type CreateTagDto = { name: string; "type": TagTypeEnum; @@ -2330,12 +2337,6 @@ export function getServerInfo(opts?: Oazapfts.RequestOpts) { ...opts })); } -export function setAdminOnboarding(opts?: Oazapfts.RequestOpts) { - return oazapfts.ok(oazapfts.fetchText("/server-info/admin-onboarding", { - ...opts, - method: "POST" - })); -} export function getServerConfig(opts?: Oazapfts.RequestOpts) { return oazapfts.ok(oazapfts.fetchJson<{ status: 200; @@ -2597,6 +2598,31 @@ export function getStorageTemplateOptions(opts?: Oazapfts.RequestOpts) { ...opts })); } +export function getAdminOnboarding(opts?: Oazapfts.RequestOpts) { + return oazapfts.ok(oazapfts.fetchJson<{ + status: 200; + data: AdminOnboardingUpdateDto; + }>("/system-metadata/admin-onboarding", { + ...opts + })); +} +export function updateAdminOnboarding({ adminOnboardingUpdateDto }: { + adminOnboardingUpdateDto: AdminOnboardingUpdateDto; +}, opts?: Oazapfts.RequestOpts) { + return oazapfts.ok(oazapfts.fetchText("/system-metadata/admin-onboarding", oazapfts.json({ + ...opts, + method: "POST", + body: adminOnboardingUpdateDto + }))); +} +export function getReverseGeocodingState(opts?: Oazapfts.RequestOpts) { + return oazapfts.ok(oazapfts.fetchJson<{ + status: 200; + data: ReverseGeocodingStateResponseDto; + }>("/system-metadata/reverse-geocoding-state", { + ...opts + })); +} export function getAllTags(opts?: Oazapfts.RequestOpts) { return oazapfts.ok(oazapfts.fetchJson<{ status: 200; diff --git a/server/src/controllers/index.ts b/server/src/controllers/index.ts index ad2f6e8de1..bd10c41a43 100644 --- a/server/src/controllers/index.ts +++ b/server/src/controllers/index.ts @@ -21,6 +21,7 @@ import { SessionController } from 'src/controllers/session.controller'; import { SharedLinkController } from 'src/controllers/shared-link.controller'; import { SyncController } from 'src/controllers/sync.controller'; import { SystemConfigController } from 'src/controllers/system-config.controller'; +import { SystemMetadataController } from 'src/controllers/system-metadata.controller'; import { TagController } from 'src/controllers/tag.controller'; import { TimelineController } from 'src/controllers/timeline.controller'; import { TrashController } from 'src/controllers/trash.controller'; @@ -51,6 +52,7 @@ export const controllers = [ SharedLinkController, SyncController, SystemConfigController, + SystemMetadataController, TagController, TimelineController, TrashController, diff --git a/server/src/controllers/server-info.controller.ts b/server/src/controllers/server-info.controller.ts index e32b0d191c..35e5e17594 100644 --- a/server/src/controllers/server-info.controller.ts +++ b/server/src/controllers/server-info.controller.ts @@ -1,4 +1,4 @@ -import { Controller, Get, HttpCode, HttpStatus, Post } from '@nestjs/common'; +import { Controller, Get } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; import { ServerConfigDto, @@ -65,11 +65,4 @@ export class ServerInfoController { getSupportedMediaTypes(): ServerMediaTypesResponseDto { return this.service.getSupportedMediaTypes(); } - - @AdminRoute() - @Post('admin-onboarding') - @HttpCode(HttpStatus.NO_CONTENT) - setAdminOnboarding(): Promise<void> { - return this.service.setAdminOnboarding(); - } } diff --git a/server/src/controllers/system-metadata.controller.ts b/server/src/controllers/system-metadata.controller.ts new file mode 100644 index 0000000000..7f186fec03 --- /dev/null +++ b/server/src/controllers/system-metadata.controller.ts @@ -0,0 +1,28 @@ +import { Body, Controller, Get, HttpCode, HttpStatus, Post } from '@nestjs/common'; +import { ApiTags } from '@nestjs/swagger'; +import { AdminOnboardingUpdateDto, ReverseGeocodingStateResponseDto } from 'src/dtos/system-metadata.dto'; +import { Authenticated } from 'src/middleware/auth.guard'; +import { SystemMetadataService } from 'src/services/system-metadata.service'; + +@ApiTags('System Metadata') +@Controller('system-metadata') +@Authenticated({ admin: true }) +export class SystemMetadataController { + constructor(private service: SystemMetadataService) {} + + @Get('admin-onboarding') + getAdminOnboarding(): Promise<AdminOnboardingUpdateDto> { + return this.service.getAdminOnboarding(); + } + + @Post('admin-onboarding') + @HttpCode(HttpStatus.NO_CONTENT) + updateAdminOnboarding(@Body() dto: AdminOnboardingUpdateDto): Promise<void> { + return this.service.updateAdminOnboarding(dto); + } + + @Get('reverse-geocoding-state') + getReverseGeocodingState(): Promise<ReverseGeocodingStateResponseDto> { + return this.service.getReverseGeocodingState(); + } +} diff --git a/server/src/dtos/system-metadata.dto.ts b/server/src/dtos/system-metadata.dto.ts new file mode 100644 index 0000000000..1c04435341 --- /dev/null +++ b/server/src/dtos/system-metadata.dto.ts @@ -0,0 +1,15 @@ +import { IsBoolean } from 'class-validator'; + +export class AdminOnboardingUpdateDto { + @IsBoolean() + isOnboarded!: boolean; +} + +export class AdminOnboardingResponseDto { + isOnboarded!: boolean; +} + +export class ReverseGeocodingStateResponseDto { + lastUpdate!: string | null; + lastImportFileName!: string | null; +} diff --git a/server/src/repositories/metadata.repository.ts b/server/src/repositories/metadata.repository.ts index 8eeb0064ac..e7d37407d9 100644 --- a/server/src/repositories/metadata.repository.ts +++ b/server/src/repositories/metadata.repository.ts @@ -36,8 +36,8 @@ export class MetadataRepository implements IMetadataRepository { this.logger.log('Initializing metadata repository'); const geodataDate = await readFile(geodataDatePath, 'utf8'); + // TODO move to metadata service init const geocodingMetadata = await this.systemMetadataRepository.get(SystemMetadataKey.REVERSE_GEOCODING_STATE); - if (geocodingMetadata?.lastUpdate === geodataDate) { return; } diff --git a/server/src/services/index.ts b/server/src/services/index.ts index db3d6083e9..2305708caa 100644 --- a/server/src/services/index.ts +++ b/server/src/services/index.ts @@ -25,6 +25,7 @@ import { StorageTemplateService } from 'src/services/storage-template.service'; import { StorageService } from 'src/services/storage.service'; import { SyncService } from 'src/services/sync.service'; import { SystemConfigService } from 'src/services/system-config.service'; +import { SystemMetadataService } from 'src/services/system-metadata.service'; import { TagService } from 'src/services/tag.service'; import { TimelineService } from 'src/services/timeline.service'; import { TrashService } from 'src/services/trash.service'; @@ -58,6 +59,7 @@ export const services = [ StorageTemplateService, SyncService, SystemConfigService, + SystemMetadataService, TagService, TimelineService, TrashService, diff --git a/server/src/services/server-info.service.spec.ts b/server/src/services/server-info.service.spec.ts index 836909b74f..115ab4b6a1 100644 --- a/server/src/services/server-info.service.spec.ts +++ b/server/src/services/server-info.service.spec.ts @@ -1,5 +1,4 @@ import { serverVersion } from 'src/constants'; -import { SystemMetadataKey } from 'src/entities/system-metadata.entity'; import { IEventRepository } from 'src/interfaces/event.interface'; import { ILoggerRepository } from 'src/interfaces/logger.interface'; import { IServerInfoRepository } from 'src/interfaces/server-info.interface'; @@ -207,13 +206,6 @@ describe(ServerInfoService.name, () => { }); }); - describe('setAdminOnboarding', () => { - it('should set admin onboarding to true', async () => { - await sut.setAdminOnboarding(); - expect(systemMetadataMock.set).toHaveBeenCalledWith(SystemMetadataKey.ADMIN_ONBOARDING, { isOnboarded: true }); - }); - }); - describe('getStats', () => { it('should total up usage by user', async () => { userMock.getUserStats.mockResolvedValue([ diff --git a/server/src/services/server-info.service.ts b/server/src/services/server-info.service.ts index bb092896bf..52bf8bd1d3 100644 --- a/server/src/services/server-info.service.ts +++ b/server/src/services/server-info.service.ts @@ -51,7 +51,9 @@ export class ServerInfoService { const featureFlags = await this.getFeatures(); if (featureFlags.configFile) { - await this.setAdminOnboarding(); + await this.systemMetadataRepository.set(SystemMetadataKey.ADMIN_ONBOARDING, { + isOnboarded: true, + }); } } @@ -105,10 +107,6 @@ export class ServerInfoService { }; } - setAdminOnboarding(): Promise<void> { - return this.systemMetadataRepository.set(SystemMetadataKey.ADMIN_ONBOARDING, { isOnboarded: true }); - } - async getStatistics(): Promise<ServerStatsResponseDto> { const userStats: UserStatsQueryResponse[] = await this.userRepository.getUserStats(); const serverStats = new ServerStatsResponseDto(); diff --git a/server/src/services/system-metadata.service.spec.ts b/server/src/services/system-metadata.service.spec.ts new file mode 100644 index 0000000000..9d11c1c72a --- /dev/null +++ b/server/src/services/system-metadata.service.spec.ts @@ -0,0 +1,31 @@ +import { SystemMetadataKey } from 'src/entities/system-metadata.entity'; +import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface'; +import { SystemMetadataService } from 'src/services/system-metadata.service'; +import { newSystemMetadataRepositoryMock } from 'test/repositories/system-metadata.repository.mock'; +import { Mocked } from 'vitest'; + +describe(SystemMetadataService.name, () => { + let sut: SystemMetadataService; + let metadataMock: Mocked<ISystemMetadataRepository>; + + beforeEach(() => { + metadataMock = newSystemMetadataRepositoryMock(); + sut = new SystemMetadataService(metadataMock); + }); + + it('should work', () => { + expect(sut).toBeDefined(); + }); + + describe('updateAdminOnboarding', () => { + it('should update isOnboarded to true', async () => { + await expect(sut.updateAdminOnboarding({ isOnboarded: true })).resolves.toBeUndefined(); + expect(metadataMock.set).toHaveBeenCalledWith(SystemMetadataKey.ADMIN_ONBOARDING, { isOnboarded: true }); + }); + + it('should update isOnboarded to false', async () => { + await expect(sut.updateAdminOnboarding({ isOnboarded: false })).resolves.toBeUndefined(); + expect(metadataMock.set).toHaveBeenCalledWith(SystemMetadataKey.ADMIN_ONBOARDING, { isOnboarded: false }); + }); + }); +}); diff --git a/server/src/services/system-metadata.service.ts b/server/src/services/system-metadata.service.ts new file mode 100644 index 0000000000..e8fddfc13c --- /dev/null +++ b/server/src/services/system-metadata.service.ts @@ -0,0 +1,29 @@ +import { Inject, Injectable } from '@nestjs/common'; +import { + AdminOnboardingResponseDto, + AdminOnboardingUpdateDto, + ReverseGeocodingStateResponseDto, +} from 'src/dtos/system-metadata.dto'; +import { SystemMetadataKey } from 'src/entities/system-metadata.entity'; +import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface'; + +@Injectable() +export class SystemMetadataService { + constructor(@Inject(ISystemMetadataRepository) private repository: ISystemMetadataRepository) {} + + async getAdminOnboarding(): Promise<AdminOnboardingResponseDto> { + const value = await this.repository.get(SystemMetadataKey.ADMIN_ONBOARDING); + return { isOnboarded: false, ...value }; + } + + async updateAdminOnboarding(dto: AdminOnboardingUpdateDto): Promise<void> { + await this.repository.set(SystemMetadataKey.ADMIN_ONBOARDING, { + isOnboarded: dto.isOnboarded, + }); + } + + async getReverseGeocodingState(): Promise<ReverseGeocodingStateResponseDto> { + const value = await this.repository.get(SystemMetadataKey.REVERSE_GEOCODING_STATE); + return { lastUpdate: null, lastImportFileName: null, ...value }; + } +} diff --git a/web/src/routes/auth/onboarding/+page.svelte b/web/src/routes/auth/onboarding/+page.svelte index 09139a7f7e..4647ad8bde 100644 --- a/web/src/routes/auth/onboarding/+page.svelte +++ b/web/src/routes/auth/onboarding/+page.svelte @@ -5,7 +5,7 @@ import OnboadingStorageTemplate from '$lib/components/onboarding-page/onboarding-storage-template.svelte'; import OnboardingTheme from '$lib/components/onboarding-page/onboarding-theme.svelte'; import { AppRoute, QueryParameter } from '$lib/constants'; - import { setAdminOnboarding } from '@immich/sdk'; + import { updateAdminOnboarding } from '@immich/sdk'; let index = 0; @@ -28,7 +28,7 @@ const handleDoneClicked = async () => { if (index >= onboardingSteps.length - 1) { - await setAdminOnboarding(); + await updateAdminOnboarding({ adminOnboardingUpdateDto: { isOnboarded: true } }); await goto(AppRoute.PHOTOS); } else { index++;