From 094a41ac9a552c123f1ffe7d8efc0f9c1790fcc5 Mon Sep 17 00:00:00 2001 From: Jason Rasmussen Date: Wed, 30 Apr 2025 11:17:23 -0400 Subject: [PATCH] chore: remove audit file report (#17994) --- e2e/src/api/specs/audit.e2e-spec.ts | 43 --- mobile/openapi/README.md | Bin 35036 -> 34394 bytes mobile/openapi/lib/api.dart | Bin 12737 -> 12441 bytes mobile/openapi/lib/api/file_reports_api.dart | Bin 4747 -> 0 bytes mobile/openapi/lib/api_client.dart | Bin 32269 -> 31663 bytes mobile/openapi/lib/api_helper.dart | Bin 6956 -> 6756 bytes .../openapi/lib/model/file_checksum_dto.dart | Bin 2973 -> 0 bytes .../lib/model/file_checksum_response_dto.dart | Bin 3236 -> 0 bytes mobile/openapi/lib/model/file_report_dto.dart | Bin 3177 -> 0 bytes .../lib/model/file_report_fix_dto.dart | Bin 2866 -> 0 bytes .../lib/model/file_report_item_dto.dart | Bin 4339 -> 0 bytes .../openapi/lib/model/path_entity_type.dart | Bin 2753 -> 0 bytes mobile/openapi/lib/model/path_type.dart | Bin 3219 -> 0 bytes open-api/immich-openapi-specs.json | 232 ------------ open-api/typescript-sdk/src/fetch-client.ts | 65 ---- .../src/controllers/file-report.controller.ts | 29 -- server/src/controllers/index.ts | 2 - server/src/dtos/audit.dto.ts | 73 ---- server/src/services/audit.service.spec.ts | 148 +------- server/src/services/audit.service.ts | 203 +--------- web/src/routes/admin/repair/+page.svelte | 358 ------------------ web/src/routes/admin/repair/+page.ts | 18 - 22 files changed, 4 insertions(+), 1167 deletions(-) delete mode 100644 e2e/src/api/specs/audit.e2e-spec.ts delete mode 100644 mobile/openapi/lib/api/file_reports_api.dart delete mode 100644 mobile/openapi/lib/model/file_checksum_dto.dart delete mode 100644 mobile/openapi/lib/model/file_checksum_response_dto.dart delete mode 100644 mobile/openapi/lib/model/file_report_dto.dart delete mode 100644 mobile/openapi/lib/model/file_report_fix_dto.dart delete mode 100644 mobile/openapi/lib/model/file_report_item_dto.dart delete mode 100644 mobile/openapi/lib/model/path_entity_type.dart delete mode 100644 mobile/openapi/lib/model/path_type.dart delete mode 100644 server/src/controllers/file-report.controller.ts delete mode 100644 server/src/dtos/audit.dto.ts delete mode 100644 web/src/routes/admin/repair/+page.svelte delete mode 100644 web/src/routes/admin/repair/+page.ts diff --git a/e2e/src/api/specs/audit.e2e-spec.ts b/e2e/src/api/specs/audit.e2e-spec.ts deleted file mode 100644 index c6a2adbb0..000000000 --- a/e2e/src/api/specs/audit.e2e-spec.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { deleteAssets, getAuditFiles, updateAsset, type LoginResponseDto } from '@immich/sdk'; -import { asBearerAuth, utils } from 'src/utils'; -import { beforeAll, describe, expect, it } from 'vitest'; - -describe('/audits', () => { - let admin: LoginResponseDto; - - beforeAll(async () => { - await utils.resetDatabase(); - await utils.resetFilesystem(); - - admin = await utils.adminSetup(); - }); - - // TODO: Enable these tests again once #7436 is resolved as these were flaky - describe.skip('GET :/file-report', () => { - it('excludes assets without issues from report', async () => { - const [trashedAsset, archivedAsset] = await Promise.all([ - utils.createAsset(admin.accessToken), - utils.createAsset(admin.accessToken), - utils.createAsset(admin.accessToken), - ]); - - await Promise.all([ - deleteAssets({ assetBulkDeleteDto: { ids: [trashedAsset.id] } }, { headers: asBearerAuth(admin.accessToken) }), - updateAsset( - { - id: archivedAsset.id, - updateAssetDto: { isArchived: true }, - }, - { headers: asBearerAuth(admin.accessToken) }, - ), - ]); - - const body = await getAuditFiles({ - headers: asBearerAuth(admin.accessToken), - }); - - expect(body.orphans).toHaveLength(0); - expect(body.extras).toHaveLength(0); - }); - }); -}); diff --git a/mobile/openapi/README.md b/mobile/openapi/README.md index d46945f6409aa8623e45ed43c580030c4e2b238d..a4fbbf371a7cfea4c960194900f5f1b5f4aecfe8 100644 GIT binary patch delta 24 gcmcaJk?B?s(}qBq&Hb{LE}IV|MR9CS%Px}z0EU?g9{>OV delta 617 zcmccB!*pjN(}qBqdbiA+)S%RY{GyU#$AU~Pg&KuuEv>Z73dhov%o31fv6fb>MoNCN zzCNl_z1$RKpt?kmx-^hFO`u*atpNYv5G^eQ{UV5&`amT>aV{-X%}{&NQ|n8JvM4>Z z1l=NcS7dwe*#Y*3b4F@%c5!Jgns=<>UI+OHXbs3K$uPCpy#$lq>?336Vh&UfGux#k zA1SDiM1f{&aw+I4M1w*BB0f1FM1%*yEzO;LFh~R`Hc-ui#1K#n#UzNRTV@3|#SmdE z>OD(RbFpavinD+uHa8@sa5yEFWVq&)WR_HhR2HN{eHDNr2sauejHU|2-Fz#fK^6eS Cx7lU@ diff --git a/mobile/openapi/lib/api.dart b/mobile/openapi/lib/api.dart index ba64363c9728f5e5099bcb7b2c6d3f32b787a370..78fa31691de75a7df9aeec318b62c0314d8b2293 100644 GIT binary patch delta 30 ocmV+(0O9|^W0_&FT?Dg#1g{OV^AeW^lkOP~ldBsQv+fzw7t(eOF#rGn delta 167 zcmbQ4crbZGEDLv9W=?8+QEEYcQOV>$X7SCjEbDo>Kw`-msma;JlMiZ1@q*Pr_@%j% z7YZt|K{QQ1uO`EfWI%jcX2oPj4haEdvCNXxT#(>oeIbs`!a@_+xDrb;;#2cVGD|8a hPmq^nDFE_8jLE!`?32$(a7=C&5CO3_pOH8#4gg0TJIw$9 diff --git a/mobile/openapi/lib/api/file_reports_api.dart b/mobile/openapi/lib/api/file_reports_api.dart deleted file mode 100644 index 73b3feaedb5dd55312ad9640867e3340e8a0ba18..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4747 zcmeHLQEwVK5Ps)ZO#4va#6hpRhbm3T(FEE^y}%_@)hUXovpXByF80>;2DmE!{l@n0 zvI~Lcdaaa7eF$Lh%y?!z^L?}FbZVUr^xlq#zrO09_Ro6b{xJ+r-t=2=JcQHX82%g{ zpA7zfL}FI*kB}*I@T~pY^O{}qYata*BB>@3k8uxDk!lPl7~>3uY2|A^OQbS)YOr|D zmU@z;W}@&@$`z>#HlxpkDf%|3G#OX=ZuLM4ZJ00<9@2q4;;5XuzVon{3)GWXifa-x zQzjEYPDJrGp%7j(Vd|ULiZ95pKGwT)wrPjqnMKu zhd`?S=VcT2Rx3Ct{XRT|5F?hxrck8g03t3}48`6`!#(X#(nqK$;WG_P!0YjN1ohGI zd<-4s{p&=0QMXYjX1Ek26@o&ODxtwl5iZV9CzLh3Y}0YhV3FuOzmx6%0vD^e2YhjC z1$(>6A{VAy6p|qttK$N6oE^( zbv3KEJxCNrbc62lCn3@u`Cv79kf)F?VZ_XolCJ8N+_*&EWzMCZsv}^rH#EHZfC1fS zBDHxb!zI~T4?i>JZ|zfxYB{pm)Fit9SF!9~T)rr|HboYqS_z&T#l>|C{CD^BZCTjf z^-fV|*rfvK(@+JDmkBx#GJ_X&mLxF`TtRd`XesLCkF7SyN@G=KZ7y!)N9>JG8tysR zM>dhIZ0V}4p07$F=UaKU@^v}dd~X$0zEgI+&2@j4m?a#7NaL7#sPfdIf!Ax=Ox@Z@ zzEdIJNFJ^g@qaE{UmPlHJBtEy#?6k-STC}c3lo4xw%dI7wh8u3s*hxd&^-X#Rp)M} zeZ|5vLQ>SGnRO0O<10D0sM22qm{?r6%TO!x%(fKSwI%;@X;2rk&CHS8O4c{<=v99O zNB4)K4d}Q=vjsZ7IWW|}9u$5I1o$8QPPh5++C3|l6^9Tx9d@BoGL34PY-z)aN`;^v zV%Is|dN)Plb%O4hGzc2i4XT3Ug@h~1RdeE667n!%2b*V4e{aE@_+$k8{%l;xT3l{DHX*!lGf{?k#BqLNYllerU4I0RJ{i;9%V+0`L49_rSXos$3~xPY)*Lj6Eg0Gr_%a6 z%y&VapRdRBY{)D5`C}mb&l#^=i1)AMH(ZyV z72=6X9YtO|$nozd5q&Ens#b^{x{FtY?X2S&r67`wrFPI*&XLwWQP$3?uiL>1tVq+b mm1V4Uz;^UWqaY{dEAf%9OaZz1sdh$Q3W2FX>)?_ z7nOia;DmE0FAP!Ph4V_>GAlsxXd*x>_z-G5OH#qkKokCxr2kUl_u;AmF_DH1A9v0HEs;IsgCw diff --git a/mobile/openapi/lib/model/file_checksum_dto.dart b/mobile/openapi/lib/model/file_checksum_dto.dart deleted file mode 100644 index 7dc9ccdf2f93ef6bfc7ae827c069918b98ccd07a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2973 zcmbVOZExE)5dQ98aV>^K22^?5r^2b-7L7BcYh$3zJ`{!_FcNLCl|_}LY8a{i`|ggC zEGyoUEkF`UJl@OmJa^=1G#HKG?O(U^7k|udW}n{P&Mx8V`ok=Q%Q@W4Z{g$o^7`t_ zGc+U1H>ogg@_Y2#%K^Qrb)k)nSK7oYmCG?yMP+3g%Uo_`;lk!!ZAxw2$sHo!ik*$i z%Ed;0uav>#)IIqC~Xaa$qs=Y9VJe-n~!(2cq?svGFMmF@b9wN#ZW2mOK2shJZBDV_Y1phuy@c z!7y@~`Z`z}y(MpBSYc1e;gQ$z(Kw)JH6nX9t4kpkTj|tX@XYAdIE5S|UZF0INTIw# zxaaZFWP@7ebi#duwMW`WTjh?F-}k*(9=IZpL=`0gXJ?FKr0k7$vrXxCjZ4HD2Nkw9 zafmSBxMb7AdP6WXi&1+)Bhon6g1c*E2&98vo=sTeU5}&yDk2hT>Q&X*>2aklBtJ(e z*Sj9j)c}pPyuCT@h}(2afPzDlYob%#jw-?0^Wa~-_Vt> drruxan$3UG-nOZy_l!p8p1>EsivdoOzX9o)!0P}2 diff --git a/mobile/openapi/lib/model/file_checksum_response_dto.dart b/mobile/openapi/lib/model/file_checksum_response_dto.dart deleted file mode 100644 index 7b963c8bd539edaf73d579106598ac421e5ac902..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3236 zcmbVOU2oG!6n)RHxJ8J#TH~<$gxXC2(FDOlBbDq*D2lA9J&p$)k9THF6qWqluv(qY=FMdoh3hZhAevd9#>a!R-CVX$V(yxSlWI)BNiF?8_ON zk>%@@DKq{f`u*jAeu{M|6^}Ei;!G6$0_w8XJdJt5H@q~V|1CC^ROX}yE4FN>O0GFc%?uH! z;Wk#=3?^wygd+=IBw6_9dj*Tqz?vJ#na+j7!3ZB*uM`%aj!04*I6>l`X|$-f-Yb^aRw^TCtRNKw#?( z;YknqPjDn@ps^2UN^^pp_(*_C=#XinMG4d99<6~JZnqt1-}Dt9*o0)~iNAmitNtO3 z=hLA;##++hxeXT%O+;;Ngk2jZEx5{gBSsS(8}4^>b)D8z75n=#M0C$ zs^jVKrpcr@N9Z?RMpAcy2krv=lZ|30x}m1Kh z&jnWTQ!1?8*iqFtHn^jjtI7qmw~81)?0A2ndy4xa9MOuWYUhcK=$GKyH$~XG#vVs~NI5shbcUH`tiFtEiu6{1sh2QZkQ`U$c%o zOXERxVSC5S_s|m&eJzk3k%jGCA0|9(m3Lx2V8)JMMzF|vtBv+DiZaxAfndmejk3Ar zfFE3qX=zdzeE2-SrmJlu3HudpZ?b=pZ@UC0cNUey)x>>vhFNo)9op;c#nnp}ySQe0+t8C7un@15BT zFSc46HDFs>&h?x#!<|k?(#X>izZd49+h;E@L=b!R6{2KCR9!&i{Od zW@Py;XUfcfPJVhlqOW3CNyXExROwcfdZ?Via>8&;WA0H)ae6*rBlG%%?60oDVf z4G3FU#uKz2Jc`I4z!X9!{BNU}hB??iV7-aEXHwNz;r)kq6RZm0qtND;&qj%Aw*VKV zJMYq67{14n7vO_gn|JaXR|)~cI5$$3)UskmDwxls)y)^4ArPkEY69~GIAAkYMpSVe zo_zoAK7qIo&&!v#hcxHBUVd#@DanJxWnjk* z6igSt%A8~Awv1M#B+iOwTx+I|BreoT!L`uVEOHno!F>WhxO1Pdd+-24#{wAXK13F^ zamNATzjo2*FWmpOjrj{7elxaVKe5`A8bf}>F%a1T1Aox6!mv&^TT!tRzTgqc=7II? z-5XmsSc|n9yNu%v8ut`;&&XHNS@{EJrmW6wKsvZ1{XJd(z^WYF0?;6ghHtEFxQ4Bg zdmm_Rk8otRXfURMS|bvh7^NICA%$j6ljd<9t3?s*V}RRiW6q)cccEK2M{ry$hn06{B4*)7R<*!m)@ zE$TI(jT!$FY-=}nWc=G;;hyrBlo_=5i4 zBNW;wi8bmG@(7g((b_rw#gDYHt-sS@$6#P`z$d!4^3Ne8xHs~ u)tHua&)~!7)g@h9J?ZXl8k~P8-*%7}w-$?V&2TGy9Ckc?#S01Uk$(VrhylR> diff --git a/mobile/openapi/lib/model/file_report_fix_dto.dart b/mobile/openapi/lib/model/file_report_fix_dto.dart deleted file mode 100644 index d46cdeb4b784b045d9e7c1db87859fa1ddb5eb13..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2866 zcmbVOZExE)5dQ98aV>^K0aSV0ry{An7K=TkYhxhIJ`9E-FcNLElSP%J>KUp3`|ggS zEz9nbEr4T7y4UA!hVHCsl1a2qy@Nsf|Gyd}h znvvz(To^a_Ir-^zk1o}s)JCRrZPK|a-l{D;bPbMo=DQ#Nla)D-UDa!Qq zk7>3P#`b!c&VinTu4E}HmEh;9*UJiFEsU|bJ4ro^)!)}n!($Jij{w|BSDVs;fKsku z+O?a3;_@st&=@NyFII?db_C@)++f}L3!9qG%3cLJO=MwASfIAJ5bV`{?YlYbm^~mN@ zT4mWR;$D;BeH{OjGpbyG)BZ=qjh^p8^R!Jl%-{$9nw>LtA4T=XJH?Q1P2Y)Yz1 zak`w!QXIsk&WR0w<4DZEa19Jw1Ea4kU|3fRBXWt#jkv%VUUunuiM-N4VCCM1q=>bL z5C8{6aF`?$I2~OABd-t&)%ocMw55h>gUD}SW@8JjS*MwiOAnmdqLw#e@(?( zzHCwv&O3G+v^q2FosmKHr+oD6fpLfwwpB}Au?cCcmodM zw5RmNfQ8r&NhDDliO6$Le9O~AOS3|84uEdFoTj#-0M#XgUmS&JtYM)&>)pzSuoBgd z?lu>pR1XVU5!^v>M`6M?ppFCVnD<{%XS;!C<$HR@J<%Mum#{k-Q#_pT45qWzFUkW3 z@vhkf4imak(uQsbP2bVeTymgO87Ms<7F~A(gl*#` ztuaPP`_pW#+!Alm{Lq=*W#N@KL$78-;zmbvdU>H}D5VpMAQc^t5$l2;oz%v2=MQ*w-P+T_z519Aj$QR| GnfwFSBBEsg diff --git a/mobile/openapi/lib/model/file_report_item_dto.dart b/mobile/openapi/lib/model/file_report_item_dto.dart deleted file mode 100644 index 1ef08c2b48511f15abaa6fd6156a71a88489571a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4339 zcmbVPQE%Hg41UkAV2fc%abCK+ry;v!8`k#F-P#OmufQQF4AoW=ZI0!WaP$Wh2`$S5IhrPo?`0&@|^wl57=f__^Tpl06$?50g2#%(3KD~sq>Cx%Q z-+QP=n(tF#-1xWQuWx$vQ!R3BWIWdr-)dE8<2o%wwiX*3 z7p04h{HIg~&2zEBzlAXPzmvwIb7Q;B6P;Tpau=%!PL{&hUJti(py!|qnTtXV@!#8CFUf?paDvHwm6XG&ldB`A;jRa8h!yxMU1@R) z0&=;ASxfO66bG2dAzHMS2jmC9EtMT6OPPGPB>Lb&D6 z)~-ZWvf=#!rprnBzwjlK!b-@sLtrm4LMRLt$G8pHr7p7+mf}{zjg&bm<*)KG%fwA4 z_uxY1iNwGq*g_@j(}aSn9RBl_p}9h0LW9PNtQ6t4o6+BpN-hO6y=QvJ7rS@*NO7jGKHTW>v`GuzCa# zLHZFXg={h)($uSU1b}frk|1w@_PC5Cqv}WY8FRv} zTBFZ;mE{54rXP|qs4RT1u|@{N!QF4P&WPSvC~aUo9!zh($plG{NSOl|Prx6dL}UXB zJ_;*8e)uoIgjy7amoL#bc#WFG=)TT!S0m&}8uj;%4=?2eYaRZzqZM)|KlOJ@gDVkK zy33++2Mg(t6?XZN2s55=LUn(mwwB98S}V+k+7br1lvY_*8w4yA z#|Q9>w+LxJwnE9a*FuurE1cVQsRe$;GN=8=fbs6P(42*A#xKHGRKqlW5h4#ViZn>+ zJF1}=zXbc2mwB@;YBpRCR?MBg3fDi;}iLlKOjrG0n* zmaD4fz^vU;aL?lr+yJ|nnSMyDe~tcjTCewX4>H5X=Ql?dblMTpl{#-%5)jeW_?Oo1sGV@S3WrA(W#?-)KobgU2 zFS)9$R-(R#(FY%Z zgRH?wRv9M3x)wJFex{PMs_L!H2D}w%1X_+@C5kI5-*iq)sJu=FbOIu1@b|zEd@>Fk zd(t>4_7C_Q}x6o;4e> zS5Vz^6{{MDqsh#!i1Jw>JgwqSAVc{{NHH+R7y6Fp<-+JS-DisO-6R$E(6^f>Y!c&gMboTd(P!L8jaFz&@rO?%UdS zTXzh-st-|QOXpT&q-CGT5HCHR^bJd2U1}AixKr!O%4y1L5Q;T|@Wo!5Kr|bEEA$Cb zzY~s-zb_1GQN_dHX!~gng;06i`KN_vy|D-8vD1vbZQHEE`y|FE0=|Uk#pAC8J0POe zYyyl!`lX}|=}<^RmbzJaSb3QbsQ$<2Ca4SBZB80 zSKPJ9oX7V1Km2(nHJ(GWY9=&V!ILb)#CCCRfiWbQ>f$xX6xFA7dv2qeVI7Aa_i7O1 z5H9hhN6v`@^}7}6X7P3<5NUdV-AJ157PqCdAmG!*^qd|>6#;nU*T#R6ZGFHecU+HZ c^qi)BKe(EssFKtZrt$lVCMlcXMG-sEzoF-YKmY&$ diff --git a/mobile/openapi/lib/model/path_entity_type.dart b/mobile/openapi/lib/model/path_entity_type.dart deleted file mode 100644 index fdcdae4f1b5f8fd8f1180fdd6cf19f40548b023e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2753 zcmai0U2oeq6n*!vxB-U70X%uzQ<2V11H{=fv`Mi#4})O{v_#olWl|%l7)Fu*zH>=Q zi4~@_u}Bvs`jas z|JBMuU5gz)D`D~NrFAfF-LQIQwDUsyRON7}3MFk&cZ1GKrKL+tqc;%qTcOjpzogk# zST`EMohMU}siYQ_O7MO+8jZ42I7gp_-_Bv*9~Os7(zg-O6#3so=Sp}P7)Dbz>hLhnRbQe~V|>r%$d94Q^^wdB^U zmdBk89XSd#6xqS+@5hqwm+(at@|%QX3_G4V9zLx?NR7c`0~b_SvkSG&4?sAo8QaZW zI2L7$Ev(QEgSC@(e3s-gGr1f;!$;$TGw(-AC4Ri_JNp;)pU}6pF(qxR*$b}Gj0#aY zIe9z_m~LTYrK?NsaNqP2333@!L1Q*Z0AeWqn>WPIiE_8b&v%tSv=(90SQJdGL6x#3jWTmgIZneDO zq{eNT&bxvI|Bs_bX1^(QJ;H3D7Lh7IE1oi;34r6Ktq5loh$53by?qFhH_d2Hf>oix z7qQ_hYN3(~FK8{YuPoiSkPNa^4@IHDg~+_IhacgMA(?hDUtG)vS*zy&Bo-%fh~Zw7 zZAd4UYJ5Bf#7PH|;zK0IZsf1RL^#myvaL}-!)nRGD=w4hrO7mjw#84nK_|c$W`7Ip ztQg4N)}V1zpQ(G;D*CF8r?sTmpqyNtWzx9#Te=1Qt+PX1%x;cgaw%&XwTWe;^pTX z!mh;bMAcEHXA3XRA(F+oVcTKet=wVrA;Uu-&?7Zn=FP0?i zg2;zVEsajI&0bTA*DtifLVHbzFP}7WSqQN6If~Q1IKh)H{pX3Wgy)virQOv0rz}8W ze0s0HY5*brjfnlj$V611(@$KkxRF~Q-(gK=Uycv77zf}BI$KH)PV7-tj|PCqh8Vfk QgKM*=aq+}U@0f=F2guixCjbBd diff --git a/mobile/openapi/lib/model/path_type.dart b/mobile/openapi/lib/model/path_type.dart deleted file mode 100644 index 55453ed1e8e510a150bab4a7943a6528b5b63dce..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3219 zcmai0VQ(8Z4E^q3!2(0$0G_?=Q<2V110>lpbV;!~0|vu5$ak`>^(=XkE{0d+zwc2x zot?XD7a+DJ@sSkyNNO?}OeS>oID7d1mz(>WKdxps*K~XL;buhF4|M-9qu(E{?{5Ek z1u>R#kZH%LAiDP@Z4zUh4z^$U{I};v;ppVjg?AEmz74ZA?7ziXK#MU z@{O==Fn~EvW<_Qvwb-dK{@)G;gS-^Z(MRDov%^l(mjTfPH@}N!mGF`TX}Kqt}xM%6eQ`i*kqaDEO`QSl7mN*B_W2c_2(PT4J%jW)J& zyOfp{u9Oxfee$-E3~Jt+CuuFLL!>_RCjJOXY%93`*Ikf~T zP{onbkt=g1K3i#r=VK%WX&pwHA3a7d@L5~Y))=krkbsp713Rq1hhR655+ICg49Lsmto z){v#wuZh=V4G+6X&8$b7CCb)dOT0|!i`Q@(S!%EPcme+hb7_O^hOr=s7r_zT?VDUs;_{-tcvXDKr;Nkb?c^!7~qp2{jo(>p&D@_e7`l zlnYJZB&TRW_^m<*MQxzD9a&1=)IDny^a+g?8WX#qM213mK}(T;X6Yk#+-lY>hJ+M? zgx=W0*IC3A%NpMi)krpRJUznZ>BaiBfl1{$wE-VvSycpf0$h|^kBpJtLJJhPpy?7HG7cc_Bl*`xlfQ3LV87^#as5exc)zGJ?`_eZkvjRC-eg#AN@ kkG1~^q%NgLdvAJgtKXpv>+a6{ZcX==?B%iDzU3+MA76(b@c;k- diff --git a/open-api/immich-openapi-specs.json b/open-api/immich-openapi-specs.json index 826af5a2e..adad1308c 100644 --- a/open-api/immich-openapi-specs.json +++ b/open-api/immich-openapi-specs.json @@ -4651,118 +4651,6 @@ ] } }, - "/reports": { - "get": { - "operationId": "getAuditFiles", - "parameters": [], - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/FileReportDto" - } - } - }, - "description": "" - } - }, - "security": [ - { - "bearer": [] - }, - { - "cookie": [] - }, - { - "api_key": [] - } - ], - "tags": [ - "File Reports" - ] - } - }, - "/reports/checksum": { - "post": { - "operationId": "getFileChecksums", - "parameters": [], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/FileChecksumDto" - } - } - }, - "required": true - }, - "responses": { - "201": { - "content": { - "application/json": { - "schema": { - "items": { - "$ref": "#/components/schemas/FileChecksumResponseDto" - }, - "type": "array" - } - } - }, - "description": "" - } - }, - "security": [ - { - "bearer": [] - }, - { - "cookie": [] - }, - { - "api_key": [] - } - ], - "tags": [ - "File Reports" - ] - } - }, - "/reports/fix": { - "post": { - "operationId": "fixAuditFiles", - "parameters": [], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/FileReportFixDto" - } - } - }, - "required": true - }, - "responses": { - "201": { - "description": "" - } - }, - "security": [ - { - "bearer": [] - }, - { - "cookie": [] - }, - { - "api_key": [] - } - ], - "tags": [ - "File Reports" - ] - } - }, "/search/cities": { "get": { "operationId": "getAssetsByCity", @@ -9749,105 +9637,6 @@ ], "type": "object" }, - "FileChecksumDto": { - "properties": { - "filenames": { - "items": { - "type": "string" - }, - "type": "array" - } - }, - "required": [ - "filenames" - ], - "type": "object" - }, - "FileChecksumResponseDto": { - "properties": { - "checksum": { - "type": "string" - }, - "filename": { - "type": "string" - } - }, - "required": [ - "checksum", - "filename" - ], - "type": "object" - }, - "FileReportDto": { - "properties": { - "extras": { - "items": { - "type": "string" - }, - "type": "array" - }, - "orphans": { - "items": { - "$ref": "#/components/schemas/FileReportItemDto" - }, - "type": "array" - } - }, - "required": [ - "extras", - "orphans" - ], - "type": "object" - }, - "FileReportFixDto": { - "properties": { - "items": { - "items": { - "$ref": "#/components/schemas/FileReportItemDto" - }, - "type": "array" - } - }, - "required": [ - "items" - ], - "type": "object" - }, - "FileReportItemDto": { - "properties": { - "checksum": { - "type": "string" - }, - "entityId": { - "format": "uuid", - "type": "string" - }, - "entityType": { - "allOf": [ - { - "$ref": "#/components/schemas/PathEntityType" - } - ] - }, - "pathType": { - "allOf": [ - { - "$ref": "#/components/schemas/PathType" - } - ] - }, - "pathValue": { - "type": "string" - } - }, - "required": [ - "entityId", - "entityType", - "pathType", - "pathValue" - ], - "type": "object" - }, "FoldersResponse": { "properties": { "enabled": { @@ -10889,27 +10678,6 @@ ], "type": "object" }, - "PathEntityType": { - "enum": [ - "asset", - "person", - "user" - ], - "type": "string" - }, - "PathType": { - "enum": [ - "original", - "fullsize", - "preview", - "thumbnail", - "encoded_video", - "sidecar", - "face", - "profile" - ], - "type": "string" - }, "PeopleResponse": { "properties": { "enabled": { diff --git a/open-api/typescript-sdk/src/fetch-client.ts b/open-api/typescript-sdk/src/fetch-client.ts index 743eeadf0..2684d2558 100644 --- a/open-api/typescript-sdk/src/fetch-client.ts +++ b/open-api/typescript-sdk/src/fetch-client.ts @@ -800,27 +800,6 @@ export type AssetFaceUpdateDto = { export type PersonStatisticsResponseDto = { assets: number; }; -export type FileReportItemDto = { - checksum?: string; - entityId: string; - entityType: PathEntityType; - pathType: PathType; - pathValue: string; -}; -export type FileReportDto = { - extras: string[]; - orphans: FileReportItemDto[]; -}; -export type FileChecksumDto = { - filenames: string[]; -}; -export type FileChecksumResponseDto = { - checksum: string; - filename: string; -}; -export type FileReportFixDto = { - items: FileReportItemDto[]; -}; export type SearchExploreItem = { data: AssetResponseDto; value: string; @@ -2663,35 +2642,6 @@ export function getPersonThumbnail({ id }: { ...opts })); } -export function getAuditFiles(opts?: Oazapfts.RequestOpts) { - return oazapfts.ok(oazapfts.fetchJson<{ - status: 200; - data: FileReportDto; - }>("/reports", { - ...opts - })); -} -export function getFileChecksums({ fileChecksumDto }: { - fileChecksumDto: FileChecksumDto; -}, opts?: Oazapfts.RequestOpts) { - return oazapfts.ok(oazapfts.fetchJson<{ - status: 201; - data: FileChecksumResponseDto[]; - }>("/reports/checksum", oazapfts.json({ - ...opts, - method: "POST", - body: fileChecksumDto - }))); -} -export function fixAuditFiles({ fileReportFixDto }: { - fileReportFixDto: FileReportFixDto; -}, opts?: Oazapfts.RequestOpts) { - return oazapfts.ok(oazapfts.fetchText("/reports/fix", oazapfts.json({ - ...opts, - method: "POST", - body: fileReportFixDto - }))); -} export function getAssetsByCity(opts?: Oazapfts.RequestOpts) { return oazapfts.ok(oazapfts.fetchJson<{ status: 200; @@ -3751,21 +3701,6 @@ export enum PartnerDirection { SharedBy = "shared-by", SharedWith = "shared-with" } -export enum PathEntityType { - Asset = "asset", - Person = "person", - User = "user" -} -export enum PathType { - Original = "original", - Fullsize = "fullsize", - Preview = "preview", - Thumbnail = "thumbnail", - EncodedVideo = "encoded_video", - Sidecar = "sidecar", - Face = "face", - Profile = "profile" -} export enum SearchSuggestionType { Country = "country", State = "state", diff --git a/server/src/controllers/file-report.controller.ts b/server/src/controllers/file-report.controller.ts deleted file mode 100644 index a51a94a50..000000000 --- a/server/src/controllers/file-report.controller.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { Body, Controller, Get, Post } from '@nestjs/common'; -import { ApiTags } from '@nestjs/swagger'; -import { FileChecksumDto, FileChecksumResponseDto, FileReportDto, FileReportFixDto } from 'src/dtos/audit.dto'; -import { Authenticated } from 'src/middleware/auth.guard'; -import { AuditService } from 'src/services/audit.service'; - -@ApiTags('File Reports') -@Controller('reports') -export class ReportController { - constructor(private service: AuditService) {} - - @Get() - @Authenticated({ admin: true }) - getAuditFiles(): Promise { - return this.service.getFileReport(); - } - - @Post('checksum') - @Authenticated({ admin: true }) - getFileChecksums(@Body() dto: FileChecksumDto): Promise { - return this.service.getChecksums(dto); - } - - @Post('fix') - @Authenticated({ admin: true }) - fixAuditFiles(@Body() dto: FileReportFixDto): Promise { - return this.service.fixItems(dto.items); - } -} diff --git a/server/src/controllers/index.ts b/server/src/controllers/index.ts index e36793b3d..9c39e580b 100644 --- a/server/src/controllers/index.ts +++ b/server/src/controllers/index.ts @@ -8,7 +8,6 @@ import { AuthController } from 'src/controllers/auth.controller'; import { DownloadController } from 'src/controllers/download.controller'; import { DuplicateController } from 'src/controllers/duplicate.controller'; import { FaceController } from 'src/controllers/face.controller'; -import { ReportController } from 'src/controllers/file-report.controller'; import { JobController } from 'src/controllers/job.controller'; import { LibraryController } from 'src/controllers/library.controller'; import { MapController } from 'src/controllers/map.controller'; @@ -53,7 +52,6 @@ export const controllers = [ OAuthController, PartnerController, PersonController, - ReportController, SearchController, ServerController, SessionController, diff --git a/server/src/dtos/audit.dto.ts b/server/src/dtos/audit.dto.ts deleted file mode 100644 index 434da46eb..000000000 --- a/server/src/dtos/audit.dto.ts +++ /dev/null @@ -1,73 +0,0 @@ -import { ApiProperty } from '@nestjs/swagger'; -import { Type } from 'class-transformer'; -import { IsArray, IsEnum, IsString, IsUUID, ValidateNested } from 'class-validator'; -import { AssetPathType, EntityType, PathType, PersonPathType, UserPathType } from 'src/enum'; -import { Optional, ValidateDate, ValidateUUID } from 'src/validation'; - -const PathEnum = Object.values({ ...AssetPathType, ...PersonPathType, ...UserPathType }); - -export class AuditDeletesDto { - @ValidateDate() - after!: Date; - - @ApiProperty({ enum: EntityType, enumName: 'EntityType' }) - @IsEnum(EntityType) - entityType!: EntityType; - - @Optional() - @IsUUID('4') - @ApiProperty({ format: 'uuid' }) - userId?: string; -} - -export enum PathEntityType { - ASSET = 'asset', - PERSON = 'person', - USER = 'user', -} - -export class AuditDeletesResponseDto { - needsFullSync!: boolean; - ids!: string[]; -} - -export class FileReportDto { - orphans!: FileReportItemDto[]; - extras!: string[]; -} - -export class FileChecksumDto { - @IsString({ each: true }) - filenames!: string[]; -} - -export class FileChecksumResponseDto { - filename!: string; - checksum!: string; -} - -export class FileReportFixDto { - @IsArray() - @ValidateNested({ each: true }) - @Type(() => FileReportItemDto) - items!: FileReportItemDto[]; -} - -// used both as request and response dto -export class FileReportItemDto { - @ValidateUUID() - entityId!: string; - - @ApiProperty({ enumName: 'PathEntityType', enum: PathEntityType }) - @IsEnum(PathEntityType) - entityType!: PathEntityType; - - @ApiProperty({ enumName: 'PathType', enum: PathEnum }) - @IsEnum(PathEnum) - pathType!: PathType; - - @IsString() - pathValue!: string; - - checksum?: string; -} diff --git a/server/src/services/audit.service.spec.ts b/server/src/services/audit.service.spec.ts index 6ef139f50..381b2ec7e 100644 --- a/server/src/services/audit.service.spec.ts +++ b/server/src/services/audit.service.spec.ts @@ -1,6 +1,4 @@ -import { BadRequestException } from '@nestjs/common'; -import { FileReportItemDto } from 'src/dtos/audit.dto'; -import { AssetFileType, AssetPathType, JobStatus, PersonPathType, UserPathType } from 'src/enum'; +import { JobStatus } from 'src/enum'; import { AuditService } from 'src/services/audit.service'; import { newTestService, ServiceMocks } from 'test/utils'; @@ -25,148 +23,4 @@ describe(AuditService.name, () => { expect(mocks.audit.removeBefore).toHaveBeenCalledWith(expect.any(Date)); }); }); - - describe('getChecksums', () => { - it('should fail if the file is not in the immich path', async () => { - await expect(sut.getChecksums({ filenames: ['foo/bar'] })).rejects.toBeInstanceOf(BadRequestException); - - expect(mocks.crypto.hashFile).not.toHaveBeenCalled(); - }); - - it('should get checksum for valid file', async () => { - await expect(sut.getChecksums({ filenames: ['./upload/my-file.jpg'] })).resolves.toEqual([ - { filename: './upload/my-file.jpg', checksum: expect.any(String) }, - ]); - - expect(mocks.crypto.hashFile).toHaveBeenCalledWith('./upload/my-file.jpg'); - }); - }); - - describe('fixItems', () => { - it('should fail if the file is not in the immich path', async () => { - await expect( - sut.fixItems([ - { entityId: 'my-id', pathType: AssetPathType.ORIGINAL, pathValue: 'foo/bar' } as FileReportItemDto, - ]), - ).rejects.toBeInstanceOf(BadRequestException); - - expect(mocks.asset.update).not.toHaveBeenCalled(); - expect(mocks.asset.upsertFile).not.toHaveBeenCalled(); - expect(mocks.person.update).not.toHaveBeenCalled(); - expect(mocks.user.update).not.toHaveBeenCalled(); - }); - - it('should update encoded video path', async () => { - await sut.fixItems([ - { - entityId: 'my-id', - pathType: AssetPathType.ENCODED_VIDEO, - pathValue: './upload/my-video.mp4', - } as FileReportItemDto, - ]); - - expect(mocks.asset.update).toHaveBeenCalledWith({ id: 'my-id', encodedVideoPath: './upload/my-video.mp4' }); - expect(mocks.asset.upsertFile).not.toHaveBeenCalled(); - expect(mocks.person.update).not.toHaveBeenCalled(); - expect(mocks.user.update).not.toHaveBeenCalled(); - }); - - it('should update preview path', async () => { - await sut.fixItems([ - { - entityId: 'my-id', - pathType: AssetPathType.PREVIEW, - pathValue: './upload/my-preview.png', - } as FileReportItemDto, - ]); - - expect(mocks.asset.upsertFile).toHaveBeenCalledWith({ - assetId: 'my-id', - type: AssetFileType.PREVIEW, - path: './upload/my-preview.png', - }); - expect(mocks.asset.update).not.toHaveBeenCalled(); - expect(mocks.person.update).not.toHaveBeenCalled(); - expect(mocks.user.update).not.toHaveBeenCalled(); - }); - - it('should update thumbnail path', async () => { - await sut.fixItems([ - { - entityId: 'my-id', - pathType: AssetPathType.THUMBNAIL, - pathValue: './upload/my-thumbnail.webp', - } as FileReportItemDto, - ]); - - expect(mocks.asset.upsertFile).toHaveBeenCalledWith({ - assetId: 'my-id', - type: AssetFileType.THUMBNAIL, - path: './upload/my-thumbnail.webp', - }); - expect(mocks.asset.update).not.toHaveBeenCalled(); - expect(mocks.person.update).not.toHaveBeenCalled(); - expect(mocks.user.update).not.toHaveBeenCalled(); - }); - - it('should update original path', async () => { - await sut.fixItems([ - { - entityId: 'my-id', - pathType: AssetPathType.ORIGINAL, - pathValue: './upload/my-original.png', - } as FileReportItemDto, - ]); - - expect(mocks.asset.update).toHaveBeenCalledWith({ id: 'my-id', originalPath: './upload/my-original.png' }); - expect(mocks.asset.upsertFile).not.toHaveBeenCalled(); - expect(mocks.person.update).not.toHaveBeenCalled(); - expect(mocks.user.update).not.toHaveBeenCalled(); - }); - - it('should update sidecar path', async () => { - await sut.fixItems([ - { - entityId: 'my-id', - pathType: AssetPathType.SIDECAR, - pathValue: './upload/my-sidecar.xmp', - } as FileReportItemDto, - ]); - - expect(mocks.asset.update).toHaveBeenCalledWith({ id: 'my-id', sidecarPath: './upload/my-sidecar.xmp' }); - expect(mocks.asset.upsertFile).not.toHaveBeenCalled(); - expect(mocks.person.update).not.toHaveBeenCalled(); - expect(mocks.user.update).not.toHaveBeenCalled(); - }); - - it('should update face path', async () => { - await sut.fixItems([ - { - entityId: 'my-id', - pathType: PersonPathType.FACE, - pathValue: './upload/my-face.jpg', - } as FileReportItemDto, - ]); - - expect(mocks.person.update).toHaveBeenCalledWith({ id: 'my-id', thumbnailPath: './upload/my-face.jpg' }); - expect(mocks.asset.update).not.toHaveBeenCalled(); - expect(mocks.asset.upsertFile).not.toHaveBeenCalled(); - expect(mocks.user.update).not.toHaveBeenCalled(); - }); - - it('should update profile path', async () => { - await sut.fixItems([ - { - entityId: 'my-id', - pathType: UserPathType.PROFILE, - pathValue: './upload/my-profile-pic.jpg', - } as FileReportItemDto, - ]); - - expect(mocks.user.update).toHaveBeenCalledWith('my-id', { profileImagePath: './upload/my-profile-pic.jpg' }); - expect(mocks.asset.update).not.toHaveBeenCalled(); - expect(mocks.asset.upsertFile).not.toHaveBeenCalled(); - expect(mocks.person.update).not.toHaveBeenCalled(); - }); - }); }); diff --git a/server/src/services/audit.service.ts b/server/src/services/audit.service.ts index a049a9c64..7c9a070dd 100644 --- a/server/src/services/audit.service.ts +++ b/server/src/services/audit.service.ts @@ -1,23 +1,9 @@ -import { BadRequestException, Injectable } from '@nestjs/common'; +import { Injectable } from '@nestjs/common'; import { DateTime } from 'luxon'; -import { resolve } from 'node:path'; -import { AUDIT_LOG_MAX_DURATION, JOBS_ASSET_PAGINATION_SIZE } from 'src/constants'; -import { StorageCore } from 'src/cores/storage.core'; +import { AUDIT_LOG_MAX_DURATION } from 'src/constants'; import { OnJob } from 'src/decorators'; -import { FileChecksumDto, FileChecksumResponseDto, FileReportItemDto, PathEntityType } from 'src/dtos/audit.dto'; -import { - AssetFileType, - AssetPathType, - JobName, - JobStatus, - PersonPathType, - QueueName, - StorageFolder, - UserPathType, -} from 'src/enum'; +import { JobName, JobStatus, QueueName } from 'src/enum'; import { BaseService } from 'src/services/base.service'; -import { getAssetFiles } from 'src/utils/asset.util'; -import { usePagination } from 'src/utils/pagination'; @Injectable() export class AuditService extends BaseService { @@ -26,187 +12,4 @@ export class AuditService extends BaseService { await this.auditRepository.removeBefore(DateTime.now().minus(AUDIT_LOG_MAX_DURATION).toJSDate()); return JobStatus.SUCCESS; } - - async getChecksums(dto: FileChecksumDto) { - const results: FileChecksumResponseDto[] = []; - for (const filename of dto.filenames) { - if (!StorageCore.isImmichPath(filename)) { - throw new BadRequestException( - `Could not get the checksum of ${filename} because the file isn't accessible by Immich`, - ); - } - - const checksum = await this.cryptoRepository.hashFile(filename); - results.push({ filename, checksum: checksum.toString('base64') }); - } - return results; - } - - async fixItems(items: FileReportItemDto[]) { - for (const { entityId: id, pathType, pathValue } of items) { - if (!StorageCore.isImmichPath(pathValue)) { - throw new BadRequestException( - `Could not fix item ${id} with path ${pathValue} because the file isn't accessible by Immich`, - ); - } - - switch (pathType) { - case AssetPathType.ENCODED_VIDEO: { - await this.assetRepository.update({ id, encodedVideoPath: pathValue }); - break; - } - - case AssetPathType.PREVIEW: { - await this.assetRepository.upsertFile({ assetId: id, type: AssetFileType.PREVIEW, path: pathValue }); - break; - } - - case AssetPathType.THUMBNAIL: { - await this.assetRepository.upsertFile({ assetId: id, type: AssetFileType.THUMBNAIL, path: pathValue }); - break; - } - - case AssetPathType.ORIGINAL: { - await this.assetRepository.update({ id, originalPath: pathValue }); - break; - } - - case AssetPathType.SIDECAR: { - await this.assetRepository.update({ id, sidecarPath: pathValue }); - break; - } - - case PersonPathType.FACE: { - await this.personRepository.update({ id, thumbnailPath: pathValue }); - break; - } - - case UserPathType.PROFILE: { - await this.userRepository.update(id, { profileImagePath: pathValue }); - break; - } - } - } - } - - private fullPath(filename: string) { - return resolve(filename); - } - - async getFileReport() { - const hasFile = (items: Set, filename: string) => items.has(filename) || items.has(this.fullPath(filename)); - const crawl = async (folder: StorageFolder) => - new Set( - await this.storageRepository.crawl({ - includeHidden: true, - pathsToCrawl: [StorageCore.getBaseFolder(folder)], - }), - ); - - const uploadFiles = await crawl(StorageFolder.UPLOAD); - const libraryFiles = await crawl(StorageFolder.LIBRARY); - const thumbFiles = await crawl(StorageFolder.THUMBNAILS); - const videoFiles = await crawl(StorageFolder.ENCODED_VIDEO); - const profileFiles = await crawl(StorageFolder.PROFILE); - const allFiles = new Set(); - for (const list of [libraryFiles, thumbFiles, videoFiles, profileFiles, uploadFiles]) { - for (const item of list) { - allFiles.add(item); - } - } - - const track = (filename: string | null | undefined) => { - if (!filename) { - return; - } - allFiles.delete(filename); - allFiles.delete(this.fullPath(filename)); - }; - - this.logger.log( - `Found ${libraryFiles.size} original files, ${thumbFiles.size} thumbnails, ${videoFiles.size} encoded videos, ${profileFiles.size} profile files`, - ); - const pagination = usePagination(JOBS_ASSET_PAGINATION_SIZE, (options) => - this.assetRepository.getAll(options, { withDeleted: true, withArchived: true }), - ); - - let assetCount = 0; - - const orphans: FileReportItemDto[] = []; - for await (const assets of pagination) { - assetCount += assets.length; - for (const { id, files, originalPath, encodedVideoPath, isExternal, checksum } of assets) { - const { fullsizeFile, previewFile, thumbnailFile } = getAssetFiles(files); - for (const file of [ - originalPath, - fullsizeFile?.path, - previewFile?.path, - encodedVideoPath, - thumbnailFile?.path, - ]) { - track(file); - } - - const entity = { entityId: id, entityType: PathEntityType.ASSET, checksum: checksum.toString('base64') }; - if ( - originalPath && - !hasFile(libraryFiles, originalPath) && - !hasFile(uploadFiles, originalPath) && - // Android motion assets - !hasFile(videoFiles, originalPath) && - // ignore external library assets - !isExternal - ) { - orphans.push({ ...entity, pathType: AssetPathType.ORIGINAL, pathValue: originalPath }); - } - if (previewFile && !hasFile(thumbFiles, previewFile.path)) { - orphans.push({ ...entity, pathType: AssetPathType.PREVIEW, pathValue: previewFile.path }); - } - if (thumbnailFile && !hasFile(thumbFiles, thumbnailFile.path)) { - orphans.push({ ...entity, pathType: AssetPathType.THUMBNAIL, pathValue: thumbnailFile.path }); - } - if (encodedVideoPath && !hasFile(videoFiles, encodedVideoPath)) { - orphans.push({ ...entity, pathType: AssetPathType.THUMBNAIL, pathValue: encodedVideoPath }); - } - } - } - - const users = await this.userRepository.getList(); - for (const { id, profileImagePath } of users) { - track(profileImagePath); - - const entity = { entityId: id, entityType: PathEntityType.USER }; - if (profileImagePath && !hasFile(profileFiles, profileImagePath)) { - orphans.push({ ...entity, pathType: UserPathType.PROFILE, pathValue: profileImagePath }); - } - } - - let peopleCount = 0; - for await (const { id, thumbnailPath } of this.personRepository.getAll()) { - track(thumbnailPath); - const entity = { entityId: id, entityType: PathEntityType.PERSON }; - if (thumbnailPath && !hasFile(thumbFiles, thumbnailPath)) { - orphans.push({ ...entity, pathType: PersonPathType.FACE, pathValue: thumbnailPath }); - } - - if (peopleCount === JOBS_ASSET_PAGINATION_SIZE) { - this.logger.log(`Found ${assetCount} assets, ${users.length} users, ${peopleCount} people`); - peopleCount = 0; - } - } - - this.logger.log(`Found ${assetCount} assets, ${users.length} users, ${peopleCount} people`); - - const extras: string[] = []; - for (const file of allFiles) { - extras.push(file); - } - - // send as absolute paths - for (const orphan of orphans) { - orphan.pathValue = this.fullPath(orphan.pathValue); - } - - return { orphans, extras }; - } } diff --git a/web/src/routes/admin/repair/+page.svelte b/web/src/routes/admin/repair/+page.svelte deleted file mode 100644 index 2d8ceca4d..000000000 --- a/web/src/routes/admin/repair/+page.svelte +++ /dev/null @@ -1,358 +0,0 @@ - - - - {#snippet buttons()} - - - - - - - {/snippet} -
-
- {#if matches.length + extras.length + orphans.length === 0} -
- -
- {:else} -
- - - - - - - - {#each matches as match (match.extra.filename)} - handleSplit(match)} - > - - - - {/each} - -
-
-

- {$t('matches').toUpperCase()} - {matches.length > 0 ? `(${matches.length.toLocaleString($locale)})` : ''} -

-

{$t('admin.these_files_matched_by_checksum')}

-
-
- {match.orphan.pathValue} => - {match.extra.filename} - - ({match.orphan.entityType}/{match.orphan.pathType}) -
- - - - - - - - - {#each orphans as orphan, index (index)} - - - - - - {/each} - -
-
-

- {$t('admin.offline_paths').toUpperCase()} - {orphans.length > 0 ? `(${orphans.length.toLocaleString($locale)})` : ''} -

-

- {$t('admin.offline_paths_description')} -

-
-
copyToClipboard(orphan.pathValue)}> - {}} /> - - {orphan.pathValue} - - ({orphan.entityType}) -
- - - - - - - - - {#each extras as extra (extra.filename)} - handleCheckOne(extra.filename)} - title={extra.filename} - > - - - - {/each} - -
-
-

- {$t('admin.untracked_files').toUpperCase()} - {extras.length > 0 ? `(${extras.length.toLocaleString($locale)})` : ''} -

-

- {$t('admin.untracked_files_description')} -

-
-
copyToClipboard(extra.filename)}> - {}} /> - - {extra.filename} - - {#if extra.checksum} - [sha1:{extra.checksum}] - {/if} - -
-
- {/if} -
-
-
diff --git a/web/src/routes/admin/repair/+page.ts b/web/src/routes/admin/repair/+page.ts deleted file mode 100644 index 9e52abb57..000000000 --- a/web/src/routes/admin/repair/+page.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { authenticate } from '$lib/utils/auth'; -import { getFormatter } from '$lib/utils/i18n'; -import { getAuditFiles } from '@immich/sdk'; -import type { PageLoad } from './$types'; - -export const load = (async () => { - await authenticate({ admin: true }); - const { orphans, extras } = await getAuditFiles(); - const $t = await getFormatter(); - - return { - orphans, - extras, - meta: { - title: $t('repair'), - }, - }; -}) satisfies PageLoad;