Compare commits

...

290 Commits

Author SHA1 Message Date
Alex The Bot
96f29cefeb Version v1.107.2 2024-07-03 03:18:19 +00:00
Alex
6f950ea45d fix(mobile): incorrect translation string (#10794) 2024-07-02 22:13:22 -05:00
Alex
99c45bd4d2 fix(web): slow people page load (#10793) 2024-07-02 22:13:11 -05:00
Weblate (bot)
312030f275 chore(web): update translations (#10753)
Translate-URL: https://hosted.weblate.org/projects/immich/immich/ar/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/ca/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/da/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/en_devel/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/es/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/fa/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/fi/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/it/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/ja/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/ko/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/nl/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/pl/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/pt/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/sl/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/ta/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/th/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/vi/
Translation: Immich/immich

Co-authored-by: Alex van den Hoogen <alex3305@gmail.com>
Co-authored-by: Andrej Kralj <andrej.kralj@gmail.com>
Co-authored-by: Aurora <arci@anche.no>
Co-authored-by: Bartłomiej Ruk <bartek04041993@gmail.com>
Co-authored-by: Eryk Michalak <gnu.ewm@protonmail.com>
Co-authored-by: Heine Olsen <olsen10051988@gmail.com>
Co-authored-by: Henrik Lievonen <henrik.lievonen@hotmail.com>
Co-authored-by: Jordi Masip <jordi@masip.cat>
Co-authored-by: Jordy H <jordy@hoebergen.net>
Co-authored-by: Junghyuk Kwon <kwon@junghy.uk>
Co-authored-by: Justin Ruiter <weblate24@justinruiter.nl>
Co-authored-by: Maciek S <maslanypotwor1@gmail.com>
Co-authored-by: Majid <abtin.php@gmail.com>
Co-authored-by: Manar Aldroubi <droubi@gmail.com>
Co-authored-by: MiguelNdeCarvalho <geral@miguelndecarvalho.pt>
Co-authored-by: Nicolò <nicveronese@gmail.com>
Co-authored-by: Ryan Gleeson <gleeson.ryanj@gmail.com>
Co-authored-by: Vincenzo Nunziata <vinciosdev@gmail.com>
Co-authored-by: Ziemowit Zabawa <ziemek.zabawa@outlook.com>
Co-authored-by: dvbthien <dvbthien@dvbthien.onmicrosoft.com>
Co-authored-by: gallegonovato <fran-carro@hotmail.es>
Co-authored-by: nazo6 <git@nazo6.dev>
Co-authored-by: polar <polar8143@users.noreply.hosted.weblate.org>
Co-authored-by: wariw <wariwpl@gmail.com>
2024-07-02 22:03:20 -05:00
Alex
bed9ccadbc chore(mobile): post release pump (#10775) 2024-07-02 16:41:40 -05:00
renovate[bot]
d55499eba0 chore(deps): update typescript-projects (#10763)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-02 17:28:24 -04:00
Daniel Dietzler
910b75c6cc docs: fix typo in translations link (#10783) 2024-07-02 16:40:51 -04:00
Jason Rasmussen
6a11464d60 fix(server): do not allow merging a person into themselves (#10776) 2024-07-02 19:56:05 +00:00
Alex The Bot
aa29f5d69c Version v1.107.1 2024-07-02 19:04:29 +00:00
Alex
1ee10ee2d6 feat(mobile): Revert render assets on device by default (#10470) (#10774)
Revert "feat(mobile): render assets on device by default (#10470)"

This reverts commit 32da9d90e4.
2024-07-02 19:01:54 +00:00
Alex
f23401d911 fix(mobile): map crashes on Android (#10773)
Revert "fix(mobile): upgrade maplibre_gl package to fix issue with crash in ios7.4 above simulator (#10182)"

This reverts commit 99c6fdbc1c.
2024-07-02 13:43:52 -05:00
Alex
14d94df1b8 chore(mobile): post release pump (#10759)
* chore(mobile): post release pump

* remove cache report file
2024-07-02 11:20:52 -05:00
Alex The Bot
b47ec2f88f Version v1.107.0 2024-07-02 14:13:10 +00:00
Michel Heusschen
b5c8ca075c fix(web): scroll jank on memories page (#10752) 2024-07-02 11:59:11 +01:00
Harshith Goka
7bfa642fa3 feat(web): add keyboard shortcuts to duplicates utility (#10736)
SHIFT + K: Select keep all
SHIFT + T: Select trash all
SHIFT + C: Confirm selection
2024-07-02 11:32:53 +01:00
Weblate (bot)
9a83038728 chore(web): update translations (#10742)
Co-authored-by: Bezruchenko Simon <worcposj44@gmail.com>
Co-authored-by: Junghyuk Kwon <kwon@junghy.uk>
Co-authored-by: Luca De Falco <deffo89@gmail.com>
Co-authored-by: MSDNicrosoft <wang3311835119@hotmail.com>
Co-authored-by: Marcin Czop <marcin@czop.ru>
Co-authored-by: Mario <17320863+myanesp@users.noreply.github.com>
Co-authored-by: Patrick Bellasi <derkling@matbug.net>
Co-authored-by: Ryan Gleeson <gleeson.ryanj@gmail.com>
Co-authored-by: Shawn <xiaxinx@gmail.com>
Co-authored-by: Sitram <adrian.martis@gmail.com>
Co-authored-by: Suryo Wibowo <nutzlichsein+github@gmail.com>
Co-authored-by: boman <boman.d@gmail.com>
Co-authored-by: cevirici <cevirici13@users.noreply.hosted.weblate.org>
Co-authored-by: polar <polar8143@users.noreply.hosted.weblate.org>
Co-authored-by: traptegies <lars.reuss@gmx.de>
2024-07-02 10:22:54 +00:00
renovate[bot]
a1629f0793 chore(deps): update docker/build-push-action action to v6.2.0 (#10745)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-02 11:11:15 +01:00
renovate[bot]
d4cba57102 fix(deps): update typescript-projects (#10744)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-02 11:10:56 +01:00
renovate[bot]
2934676594 chore(deps): update node (#10741)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-02 11:09:15 +01:00
dependabot[bot]
ebea793534 chore(deps): bump docker/build-push-action from 6.1.0 to 6.2.0 (#10655)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-02 11:08:34 +01:00
renovate[bot]
eeae77422f chore(deps): update terraform cloudflare to v4.36.0 (#10718)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-02 11:08:16 +01:00
Weblate (bot)
850424e960 chore(web): update translations (#10729)
Translate-URL: https://hosted.weblate.org/projects/immich/immich/cs/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/fa/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/fi/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/fr/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/it/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/nl/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/pl/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/ru/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/th/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/tr/
Translation: Immich/immich

Co-authored-by: Alessandro Saglia <github.eatery9779@bear-d.me>
Co-authored-by: Denis Pacquier <denis.pacquier@gmail.com>
Co-authored-by: Eero Jääskeläinen <eero.jaaskelainen@gmail.com>
Co-authored-by: Kirill Zhukov <siper13@gmail.com>
Co-authored-by: Majid <abtin.php@gmail.com>
Co-authored-by: Michel Heusschen <59014050+michelheusschen@users.noreply.github.com>
Co-authored-by: Przemek <skweresp@gmail.com>
Co-authored-by: Ryan Gleeson <gleeson.ryanj@gmail.com>
Co-authored-by: cevirici <cevirici13@users.noreply.hosted.weblate.org>
Co-authored-by: waclaw66 <waclaw66@seznam.cz>
Co-authored-by: Вячеслав Лукьяненко <madeinchuguev@gmail.com>
2024-07-01 19:16:31 -04:00
Alex
58298bd038 docs: blog post July 2024 update (#10737) 2024-07-01 15:57:25 -05:00
Alex
e46af5c26b fix(web): activity status padding regression (#10734) 2024-07-01 15:06:46 -04:00
Zack Pollard
3b37b70626 feat(server): user and server license endpoints (#10682)
* feat: user license endpoints

* feat: server license endpoints

* chore: pr feedback

* chore: add more test cases

* chore: add prod license public keys

* chore: open-api generation
2024-07-01 17:43:16 +00:00
Alex
4193b0dede fix(web): suppress album upload notification (#10717)
* fix(web): suppress album upload notification

* restore translation strings
2024-07-01 13:19:57 -04:00
Michel Heusschen
ac51cad075 feat(web): html tags inside plural and select messages (#10696)
* feat(web): html tags inside plural and select messages

* add component docs
2024-07-01 11:54:13 -05:00
Alex
b54dd4e135 docs: Translations update (#10730)
chore(mobile): translation update
2024-07-01 09:44:15 -05:00
pyorot
f5164b42e0 fix(web): remove black bezels + better integrate ActivityStatus (#10667)
* remove black bezels + better integrate activity status

* remove justify-self-end + mr-4 → mr-3 (closer to desired spacing)

* clean up

* clean up some more

---------

Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2024-07-01 14:05:49 +00:00
Weblate (bot)
783088afbe chore(web): update translations (#10605)
Translate-URL: https://hosted.weblate.org/projects/immich/immich/ar/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/bg/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/ca/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/cs/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/de/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/en_devel/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/es/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/fa/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/fi/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/fr/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/he/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/id/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/it/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/ko/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/lv/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/nb_NO/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/nl/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/pt_BR/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/ro/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/ru/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/sr_Latn/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/sv/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/ta/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/th/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/uk/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/vi/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/zh_Hant/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/zh_SIMPLIFIED/
Translation: Immich/immich

Co-authored-by: Askolds Zusans <askolds.zusans@gmail.com>
Co-authored-by: AxGD <guillermeaxel@yahoo.fr>
Co-authored-by: Bezruchenko Simon <worcposj44@gmail.com>
Co-authored-by: CanbiZ <mickey.leskowitz@gmail.com>
Co-authored-by: Daniel <danielwichers@gmail.com>
Co-authored-by: Denis Pacquier <denis.pacquier@gmail.com>
Co-authored-by: Eero Jääskeläinen <eero.jaaskelainen@gmail.com>
Co-authored-by: Fredrik Ekdahl <fekdahl@gmail.com>
Co-authored-by: Ivan Naboichshikov <inaboichshikov@gmail.com>
Co-authored-by: JBP <weblate@1peer1boom.nl>
Co-authored-by: Jan <jan.widmer.ch@gmail.com>
Co-authored-by: Joachim Klahr <joachim@klahr.se>
Co-authored-by: Junghyuk Kwon <kwon@junghy.uk>
Co-authored-by: Kirill Zhukov <siper13@gmail.com>
Co-authored-by: Linerly <linerly@proton.me>
Co-authored-by: Londoneye02 <jcdelcaz@gmail.com>
Co-authored-by: Majid <abtin.php@gmail.com>
Co-authored-by: Manar Aldroubi <droubi@gmail.com>
Co-authored-by: Matteo <matteo.visintini@gmail.com>
Co-authored-by: Michel Heusschen <59014050+michelheusschen@users.noreply.github.com>
Co-authored-by: Mihai Mura <mihai.mura.dev@gmail.com>
Co-authored-by: PPNplus <ppnplus@protonmail.com>
Co-authored-by: Patrick Williamson <patrickwill@me.com>
Co-authored-by: Pavel Shamshin <odan@selaz.org>
Co-authored-by: PolarisYHNL <polarisyhnl@yeah.net>
Co-authored-by: Shawn <xiaxinx@gmail.com>
Co-authored-by: Son Do <son.do@merctrans.vn>
Co-authored-by: Stan P <g97d6liib@mozmail.com>
Co-authored-by: Ultragian <giancarlo.brasil@gmail.com>
Co-authored-by: Unimpeded Lemur <yg7lh0fz3@mozmail.com>
Co-authored-by: Vegard Skullerud <vegard@skullerud.net>
Co-authored-by: Victor Sueiro <kiwicaja@gmail.com>
Co-authored-by: Vincenzo Nunziata <vinciosdev@gmail.com>
Co-authored-by: YFrendo <yann.frendo@live.fr>
Co-authored-by: Yi Kuo <kuokuoyiyi@gmail.com>
Co-authored-by: Yusuf Mohammed <yousufinternet@gmail.com>
Co-authored-by: alien75 <thomas@imolesi.it>
Co-authored-by: carcawey <dacarva@gmail.com>
Co-authored-by: dvbthien <dvbthien@dvbthien.onmicrosoft.com>
Co-authored-by: dweissmueller <2868emerald@navalcadets.com>
Co-authored-by: eav5jhl0 <eav5jhl0@users.noreply.hosted.weblate.org>
Co-authored-by: guillezcurra <guillezcurra@gmail.com>
Co-authored-by: nhy42 <paul.zanolin@gmail.com>
Co-authored-by: polar <polar8143@users.noreply.hosted.weblate.org>
Co-authored-by: pyorot <FMasic@hotmail.co.uk>
Co-authored-by: waclaw66 <waclaw66@seznam.cz>
Co-authored-by: Вячеслав Лукьяненко <madeinchuguev@gmail.com>
2024-07-01 08:54:33 -05:00
indam
744dfb675b docs: sync Chinese README with the official English version (#10724) 2024-07-01 09:27:35 +01:00
daniel bogachevsky
1d282851e2 fix(web): Sort timezones in assets settings by offset (#10697)
* fixed timezones on web are sorted alphabetically

* swaped order of operations in order to use DataTime.offset property for sorting

* optimization
2024-06-30 22:41:47 -05:00
Dawid Rejowski
d00d33d8a5 chore(doc): small punctuation fix backup-and-restore.md (#10704)
Small punctuation fix
2024-06-30 21:59:18 -05:00
Michel Heusschen
560dbd3c65 fix(web): shared link card (#10702) 2024-06-30 17:34:52 -05:00
Michel Heusschen
c58148af35 feat(web): add more translations (#10700)
* feat(web): add more translations

* formatting
2024-06-30 17:29:10 -05:00
bo0tzz
e54c18367b chore: Lower default duplicate detection distance (#10703) 2024-06-30 11:36:02 -04:00
Alex
8b6d27f1bc fix(server): show partners assets on timeline without permission (#10705)
* fix(server): show partners assets on timeline without permission

* save all

* correct fix
2024-06-29 22:45:59 -05:00
Alex
887acb9d9f chore(mobile): Revert "remove exclude album mechanism for backup (#10552)" (#10686)
Revert "chore(mobile): remove exclude album mechanism for backup (#10552)"

This reverts commit 5f47cf604a.
2024-06-29 11:30:18 -05:00
Michel Heusschen
8f553ddb39 fix(web): i18n race condition in load function (#10693) 2024-06-29 11:29:56 -05:00
Jason Rasmussen
24c1855899 fix: album remove asset bug (#10687)
* fix: album remove asset bug

* trigger GH Action

---------

Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2024-06-29 00:17:58 -04:00
Ben McCann
6ebae3c84f chore(deps): upgrade @testing-library/svelte (#10690) 2024-06-28 21:31:27 -04:00
martin
e0bb9add91 feat(web): use websocket to update the feature photo (#10683)
feat: use ws to update the feature photo
2024-06-28 14:40:18 -05:00
Pascal Sommer
821570f2fb feat(web): show favorite icon in duplicate asset (#10688)
* show favorite icon in duplicate asset

* remove isSharedLink check

* swap places of favorite icon and view button
2024-06-28 14:37:12 -05:00
Zack Pollard
a2364a12cf refactor: move /server-info endpoints to /server (#10677) 2024-06-28 17:08:19 +01:00
renovate[bot]
e361640e39 chore(deps): update grafana/grafana docker tag to v11.1.0 (#10679)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-28 08:49:51 -04:00
martin
37b5d92110 fix(web): new feature photo (#9443)
* fix: new feature photo

* fix: use updatedAt
2024-06-27 20:16:26 -04:00
Matthew Momjian
325aa1d392 fix(docs): restart DB backup container (#10671)
Update backup-and-restore.md
2024-06-27 20:14:55 -04:00
Jason Rasmussen
72bf9439b0 refactor(server): event emits (#10648)
* refactor(server): event emits

* refactor: change default priority to 0
2024-06-27 15:54:20 -04:00
Jason Rasmussen
7e99394c70 fix(server): live photo relation (#10637)
* fix(server): live photo relation

* handle deletion and unit test

* lint

* chore: clean up and e2e tests

* fix test

* sql

---------

Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2024-06-27 14:41:49 -05:00
Miguel Domingues
8ff9c37d79 fix(web): match storage_template_migration_job with storage_template_migration (#10662) 2024-06-27 19:33:28 +00:00
Zhenzhen Zhao
0b4153e256 chore(trans): add zh-CN translations for custom proxy headers (#10660)
chore: add zh-CN translations for proxy headers

Signed-off-by: TripleZ <me@triplez.cn>
2024-06-27 13:38:51 -05:00
Michel Heusschen
12b9f3ad91 fix(server): about info version (#10659) 2024-06-27 12:36:25 -05:00
Jason Rasmussen
9fc9465cec feat(web): link router (#10644)
feat: link router
2024-06-27 09:09:28 -04:00
Alex
d8175d8da8 fix(mobile): asset state remain in gallery view after being deleted (#10603)
* fix(mobile): asset doesn't get removed from state renderList

* fix delete last assets

* refactor
2024-06-26 23:15:26 -05:00
Matej Kramny
922430da36 feat(mobile): add additional request headers (#10588)
* add additional request headers

* improve interface

* move headers under advanced settings

* refactor

* refactor

---------

Co-authored-by: Alex <alex.tran1502@gmail.com>
2024-06-26 19:31:55 +00:00
Feng Kaiyu
a3c3619811 fix(cli): fix broken --album on Windows. (#10626)
Extract folder names via system function to avoid the difference between / and \ on Windows.
2024-06-26 11:12:30 -05:00
Pedro Ribeiro
7f5a3e5adb docs: Add new community guide to access Immich with a custom domain (#10638)
add new community guide to access immich with a custom domain

Co-authored-by: ppr88 <ppr88@local>
2024-06-26 10:55:27 -05:00
Jason Rasmussen
63041674c2 fix(server): user delete with stacked assets (#10642) 2024-06-26 09:29:52 -04:00
Jason Rasmussen
8a445cac07 chore: build metadata (#10612)
feat: build metadata
2024-06-26 08:25:09 -04:00
renovate[bot]
15c1cd6449 chore(deps): update dependency @types/node to ^20.14.7 (#10635)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-25 21:48:48 +00:00
renovate[bot]
8198259de8 chore(deps): update dependency typescript to v5.5.2 (#10633)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-25 16:28:11 -04:00
Jason Rasmussen
6decf33226 chore: better auto labels (#10632) 2024-06-25 22:02:24 +02:00
renovate[bot]
df0064c83b chore(deps): pin node.js to 0ccc08f (#10628)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-25 17:22:16 +01:00
Zack Pollard
c754f2504b chore: bump node docker versions (#10629) 2024-06-25 12:19:51 -04:00
renovate[bot]
0891658668 chore(deps): update base-image to v20240625 (major) (#10620)
chore(deps): update base-image to v20240625

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-25 12:18:38 -04:00
renovate[bot]
5b909eeaf0 chore(deps): update mambaorg/micromamba:bookworm-slim docker digest to 333f759 (#10631) 2024-06-25 16:18:27 +00:00
Zack Pollard
0484a4e252 chore: add renovate makefile command for testing renovate changes (#10630) 2024-06-25 12:18:02 -04:00
renovate[bot]
bf83fdee49 chore(deps): update terraform cloudflare to v4.35.0 (#10420)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-25 17:04:15 +01:00
renovate[bot]
9eafbb0524 fix(deps): update machine-learning (#10610) 2024-06-25 12:03:27 -04:00
Mert
6356c28f64 refactor(ml): model sessions (#10559) 2024-06-25 12:00:24 -04:00
Zack Pollard
6538ad8de7 chore: update docker node alpine versions to 3.20 (#10621) 2024-06-25 11:04:02 -04:00
renovate[bot]
9f9e42a96a chore(deps): update dependency prettier-plugin-svelte to v3.2.5 (#10623)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-25 11:03:29 -04:00
renovate[bot]
905d6c1508 chore(deps): update dependency @types/node to ^20.14.6 (#10627)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-25 11:00:49 -04:00
Zack Pollard
91af793b52 ci: group docker node and npm node updates together (#10625)
also should fix @types/node not being correct versions
2024-06-25 15:49:50 +01:00
Zack Pollard
5912fcc393 ci: use .nvmrc for node-setup node-version in github actions (#10619)
* chore: add node version pinning with .nvmrc and volta for the typescript sdk

* ci: add missing setup-node actions and use .nvmrc for setup-node node-version
2024-06-25 14:01:15 +01:00
renovate[bot]
b5b0c6fe8b chore(deps): pin dependencies (#10618)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-25 11:22:38 +00:00
Jason Rasmussen
330648ff44 chore(deps): use full semver docker tag for node images (#10613)
* chore: use full semver docker tag for node images

* Update server/Dockerfile

Co-authored-by: bo0tzz <git@bo0tzz.me>

---------

Co-authored-by: bo0tzz <git@bo0tzz.me>
2024-06-25 11:12:27 +00:00
Junghyuk Kwon
54d1dc56a2 chore(docs): update Korean README (#10462)
* chore: update Korean README

* chore: correction

* chore(docs): update README_ko_KR.md

* chore: correction
2024-06-25 11:38:11 +01:00
renovate[bot]
d8e6b17ef9 fix(deps): update typescript-projects (#10616)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-25 11:14:15 +01:00
renovate[bot]
d7a33c8ec2 fix(deps): update typescript-projects (#10611)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-24 23:00:09 -04:00
Jason Rasmussen
0012369c67 chore: merge weblate (#10604) 2024-06-24 15:19:12 -04:00
waclaw66
cb3ac4ff9f chore(web): translation finetuning (#10601)
fixes
2024-06-24 15:05:45 -04:00
Weblate (bot)
4988df3fcb chore(web): update translations (#10593)
* chore(web): update translations

Co-authored-by: Denis Pacquier <denis.pacquier@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Immich <immich@futo.org>
Co-authored-by: Junghyuk Kwon <kwon@junghy.uk>
Co-authored-by: Linerly <linerly@proton.me>
Co-authored-by: Michał Kulik <michal.kulik91@gmail.com>
Co-authored-by: Michel Heusschen <59014050+michelheusschen@users.noreply.github.com>
Co-authored-by: Stan P <g97d6liib@mozmail.com>
Co-authored-by: polar <polar8143@users.noreply.hosted.weblate.org>
Co-authored-by: pyorot <FMasic@hotmail.co.uk>
Co-authored-by: waclaw66 <waclaw66@seznam.cz>
Translate-URL: https://hosted.weblate.org/projects/immich/immich/cs/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/en_devel/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/fr/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/id/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/ko/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/nl/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/pl/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/sr/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/ta/
Translation: Immich/immich

* chore: split serbian

---------

Co-authored-by: Denis Pacquier <denis.pacquier@gmail.com>
Co-authored-by: Immich <immich@futo.org>
Co-authored-by: Junghyuk Kwon <kwon@junghy.uk>
Co-authored-by: Linerly <linerly@proton.me>
Co-authored-by: Michał Kulik <michal.kulik91@gmail.com>
Co-authored-by: Michel Heusschen <59014050+michelheusschen@users.noreply.github.com>
Co-authored-by: Stan P <g97d6liib@mozmail.com>
Co-authored-by: polar <polar8143@users.noreply.hosted.weblate.org>
Co-authored-by: pyorot <FMasic@hotmail.co.uk>
Co-authored-by: waclaw66 <waclaw66@seznam.cz>
Co-authored-by: Jason Rasmussen <jrasm91@gmail.com>
2024-06-24 15:04:53 -04:00
Jason Rasmussen
fc6c9a19d9 feat: more languages (#10595)
chore: more languages
2024-06-24 14:15:08 -04:00
Jason Rasmussen
13cc1f0aa6 docs: add private photos to roadmap (#10599)
docs: add locked photos to roadmap
2024-06-24 14:01:44 -04:00
Jason Rasmussen
ba72802888 chore: use immich.app email for security reports (#10594)
chore: use  immich.app email for security reports
2024-06-24 07:25:48 -07:00
RanKKI
04f0e29df6 fix(mobile): inconsistent thumbnail's label (#10589)
* fix(mobile): inconsistent thumbnail with label

* fix: limit person's name width
2024-06-24 07:24:57 -07:00
Jason Rasmussen
c83de5213f docs: add info about translations and weblate (#10591)
docs: update
2024-06-24 07:23:09 -07:00
waclaw66
dd2c7400a6 chore(web): another missing translations (#10274)
* chore(web): another missing translations

* unused removed

* more keys

* lint fix

* test fixed

* dynamic translation fix

* fixes

* people search translation

* params fixed

* keep filter setting fix

* lint fix

* $t fixes

* Update web/src/lib/i18n/en.json

Co-authored-by: Michel Heusschen <59014050+michelheusschen@users.noreply.github.com>

* another missing

* activity translation

* link sharing translations

* expiration dropdown fix - didn't work localized

* notification title

* device logout

* search results

* reset to default

* unsaved change

* select from computer

* selected

* select-2

* select-3

* unmerge

* pluralize, force icu message

* Update web/src/lib/components/asset-viewer/asset-viewer.svelte

Co-authored-by: Michel Heusschen <59014050+michelheusschen@users.noreply.github.com>

* review fixes

* remove user

* plural fixes

* ffmpeg settings

* fixes

* error title

* plural fixes

* onboarding

* change password

* more more

* console log fix

* another

* api key desc

* map marker

* format fix

* key fix

* asset-utils

* utils

* misc

---------

Co-authored-by: Michel Heusschen <59014050+michelheusschen@users.noreply.github.com>
2024-06-24 09:50:01 -04:00
Weblate (bot)
df9e074304 chore(web): update translations (#10304)
Translate-URL: https://hosted.weblate.org/projects/immich/immich/ar/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/ca/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/cs/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/da/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/de/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/en_devel/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/es/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/fi/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/fr/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/he/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/hr/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/hu/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/id/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/it/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/ja/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/ko/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/nb_NO/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/nl/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/pl/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/pt/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/ru/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/sk/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/sl/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/sr/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/th/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/uk/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/vi/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/zh_SIMPLIFIED/
Translation: Immich/immich

Co-authored-by: 2001 Y <yoshiki.tamura2001@gmail.com>
Co-authored-by: Abdullah <abdullahsalameh66@gmail.com>
Co-authored-by: Ahmad Malek <maichael.gt@gmail.com>
Co-authored-by: AlexMa2011 <alexma2011@outlook.com>
Co-authored-by: Alexis <alexisl61@outlook.fr>
Co-authored-by: Andrej Kralj <andrej.kralj@gmail.com>
Co-authored-by: AxGD <guillermeaxel@yahoo.fr>
Co-authored-by: Clément Roblot <clement.roblot@martobre.fr>
Co-authored-by: Coooolfan <coolfan1024@outlook.com>
Co-authored-by: Daddie0 <33762262+GoByeBye@users.noreply.github.com>
Co-authored-by: Dean Cvjetanović <forteee@gmail.com>
Co-authored-by: Denis Pacquier <denis.pacquier@gmail.com>
Co-authored-by: Denis Rebaud <denis@rebaud.fr>
Co-authored-by: Eero Jääskeläinen <eero.jaaskelainen@gmail.com>
Co-authored-by: Fanfouer <fanfouer@outlook.com>
Co-authored-by: Gustavo Ceolin <gustavogiulceolin@hotmail.com>
Co-authored-by: Hadrián Montes <hadrianmontes@gmail.com>
Co-authored-by: Héctor Martínez Juste <hectorzin@hotmail.com>
Co-authored-by: IM Ben <beniiorga@gmail.com>
Co-authored-by: Ignacy Kajdan <ignacy.kajdan@gmail.com>
Co-authored-by: Immich <immich@futo.org>
Co-authored-by: J1mooo <programingstafi@gmail.com>
Co-authored-by: JBP <weblate@1peer1boom.nl>
Co-authored-by: Jan <jan.widmer.ch@gmail.com>
Co-authored-by: JinYoung Park <norahc1999@gmail.com>
Co-authored-by: Jordi Masip <jordi@masip.cat>
Co-authored-by: Joseph <josephlegrand33+hosted.weblate.org@gmail.com>
Co-authored-by: Julien Deveaux <julien.deveaux@hotmail.com>
Co-authored-by: Julius969 <juliusdjorup@proton.me>
Co-authored-by: Junghyuk Kwon <kwon@junghy.uk>
Co-authored-by: Kentai Radiquum <kentai.waah@gmail.com>
Co-authored-by: Kovács Gergely <kgerg@duck.com>
Co-authored-by: Kyle Park <mysky3056@gmail.com>
Co-authored-by: Lauritz Tieste <lauritz6000000@gmail.com>
Co-authored-by: Leo Bottaro <github@leobottaro.com>
Co-authored-by: Leo Bottaro <weblate@leobottaro.com>
Co-authored-by: Linerly <linerly@proton.me>
Co-authored-by: Linx <johnsmith_2003@hotmail.com>
Co-authored-by: Logge <hyper.xjo@gmail.com>
Co-authored-by: Maks s <smaks2313@gmail.com>
Co-authored-by: Matjaž T <matjaz@moj-svet.si>
Co-authored-by: Max <Maxime.morasse@hotmail.fr>
Co-authored-by: Maximilian Waidelich <44324946+maxwai@users.noreply.github.com>
Co-authored-by: Michał Kulik <michal.kulik91@gmail.com>
Co-authored-by: Michel Heusschen <59014050+michelheusschen@users.noreply.github.com>
Co-authored-by: Miki M <medolino2009@gmail.com>
Co-authored-by: Napat Srichan <napatsrichan2001@gmail.com>
Co-authored-by: Nick Götti <nick.goetti@outlook.com>
Co-authored-by: Patrick <patrickwill@me.com>
Co-authored-by: Pavel Shamshin <odan@selaz.org>
Co-authored-by: Peter Suba <peter.suba@gmail.com>
Co-authored-by: Petri Hämäläinen <petri.hamalainen@mailbox.org>
Co-authored-by: Pheggas <petko252@gmail.com>
Co-authored-by: PolarisYHNL <polarisyhnl@yeah.net>
Co-authored-by: Polly Julien <julien.polly@laposte.net>
Co-authored-by: Ptsa Daniel <ptsa1987@gmail.com>
Co-authored-by: Roukanken <kuko0411@gmail.com>
Co-authored-by: Ryan Gleeson <gleeson.ryanj@gmail.com>
Co-authored-by: Shawn <xiaxinx@gmail.com>
Co-authored-by: Simmer Lajos <weblate.linguini033@passinbox.com>
Co-authored-by: Sleeper CH <sleeperch@gmail.com>
Co-authored-by: Sophie <mail@sopht.li>
Co-authored-by: Stan P <g97d6liib@mozmail.com>
Co-authored-by: Swayerka <admin@crozet.cc>
Co-authored-by: Tyoda <tyoda@pm.me>
Co-authored-by: ZtereoHYPE <57519662+ZtereoHYPE@users.noreply.github.com>
Co-authored-by: aln <imyapear@gmail.com>
Co-authored-by: carcawey <dacarva@gmail.com>
Co-authored-by: clementdelestre <clementdelestre@gmail.com>
Co-authored-by: dvbthien <dvbthien@dvbthien.onmicrosoft.com>
Co-authored-by: eav5jhl0 <eav5jhl0@users.noreply.hosted.weblate.org>
Co-authored-by: gallegonovato <fran-carro@hotmail.es>
Co-authored-by: grgergo <gergo_g@proton.me>
Co-authored-by: guillezcurra <guillezcurra@gmail.com>
Co-authored-by: ingria <codefuhrer@gmail.com>
Co-authored-by: mxm199 <mxm199@bk.ru>
Co-authored-by: myurar1a <sirometroid1235@outlook.jp>
Co-authored-by: opl- <jakub.trzy@op.pl>
Co-authored-by: pyorot <FMasic@hotmail.co.uk>
Co-authored-by: waclaw66 <waclaw66@seznam.cz>
Co-authored-by: wariw <wariwpl@gmail.com>
Co-authored-by: weiwhy <why1573920133@hotmail.com>
Co-authored-by: Àlex Garcia <alexgarciavila@gmail.com>
Co-authored-by: Алексей Меринов <merinov@gmail.com>
Co-authored-by: Вячеслав Лукьяненко <madeinchuguev@gmail.com>
Co-authored-by: Кирилл Москатов <kirillmoskatov@gmail.com>
2024-06-24 12:38:50 +00:00
Alex
5f47cf604a chore(mobile): remove exclude album mechanism for backup (#10552)
* chore(mobile): remove exclude album selection mechanism

* code generator

* code generator
2024-06-22 15:31:27 -07:00
dependabot[bot]
8e2f6f1f41 chore(deps): bump docker/build-push-action from 6.0.1 to 6.1.0 (#10522)
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 6.0.1 to 6.1.0.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/v6.0.1...v6.1.0)

---
updated-dependencies:
- dependency-name: docker/build-push-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-22 11:10:47 -07:00
Alex
32da9d90e4 feat(mobile): render assets on device by default (#10470)
* feat(mobile): render asset on device by default

* remove unused service
2024-06-22 09:13:05 -07:00
Michel Heusschen
6164640575 fix(web): FormatMessage development keys (#10536) 2024-06-22 09:08:56 -07:00
Feng Kaiyu
4cb165304b fix(cli): handle patterns correctly on Windows (#10430)
Modify the handling of patterns in the `crawl` function to correctly
convert the current path to a pattern when it contains backslash on
Windows, in according to fast-glob's docs.
2024-06-21 17:09:02 -07:00
renovate[bot]
1200265425 chore(deps): update docker.io/redis:6.2-alpine docker digest to 328fe6a (#10515)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-21 16:46:39 -04:00
renovate[bot]
0a3aafd439 chore(deps): update redis:6.2-alpine docker digest to 328fe6a (#10516)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-21 16:46:24 -04:00
Michel Heusschen
aaf7c0b6db fix(web): missing translations (#10504) 2024-06-21 13:09:10 -07:00
Michel Heusschen
b3252ffdac feat(web): translations containing html (#10491)
* feat(web): translations containing html

* add tests and more translations

* more translations

* rename FormatTags --> FormatMessage

* update version_announcement_message
2024-06-21 13:08:36 -07:00
Michel Heusschen
1129020159 fix(web): six digit year input (#10517) 2024-06-21 13:05:17 -07:00
erathmus
61a5d67674 feat(web): Adds sort order from album to shared album. (#10528) 2024-06-21 08:14:30 -07:00
Mert
42f3b50422 fix(server): /places entries sometimes not ordered alphabetically (#10514) 2024-06-20 23:48:19 -04:00
Daniel Dietzler
5e9a7b17d9 fix(server): allow library id to be null in metadata search (#10512)
* fix: allow library id to be null in metadata search

* chore: open api
2024-06-20 16:02:05 -07:00
Ben
0fda67543d chore(web): context menu improvements (#10475)
- ability to add custom hover colors
- migrate activity menu to ButtonContextMenu component
- onClick callbacks rather than events for menu options
- remove slots
- configurable menu option colors
- improve menu option layout
2024-06-20 14:15:36 -07:00
Michel Heusschen
5cde52eec9 feat(web): duplicate ui tweaks (#10506) 2024-06-20 14:14:34 -07:00
Matthew Momjian
eff839251c fix(deployment): Postgres healthcheck, add username to pg_isready (#10221) 2024-06-20 14:17:57 -04:00
Mert
a42af06889 fix(ml): limit load retries (#10494) 2024-06-20 14:13:18 -04:00
Mert
79a8ab71ef fix(server): reindex after changing to a model with a different dimension size (#10496)
reindex after truncating
2024-06-19 17:25:02 -04:00
Mert
1191978d50 fix(server): library refresh not checking trashed assets (#10495)
* set `withDeleted`

* update sql
2024-06-19 20:42:55 +00:00
renovate[bot]
7ea0278b32 chore(deps): update dependency eslint-plugin-unicorn to v54 (#10486)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-19 12:56:35 -04:00
renovate[bot]
4ef033aa55 chore(deps): update base-image to v20240618 (major) (#10457)
chore(deps): update base-image to v20240618

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-19 12:53:02 -04:00
dependabot[bot]
660afa9fad chore(deps): bump docker/build-push-action from 6.0.0 to 6.0.1 (#10483)
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 6.0.0 to 6.0.1.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/v6.0.0...v6.0.1)

---
updated-dependencies:
- dependency-name: docker/build-push-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-19 12:26:52 -04:00
dependabot[bot]
104048ecd5 chore(deps): bump ws and engine.io-client in /e2e (#10488)
Bumps [ws](https://github.com/websockets/ws) and [engine.io-client](https://github.com/socketio/engine.io-client). These dependencies needed to be updated together.

Updates `ws` from 8.11.0 to 8.17.1
- [Release notes](https://github.com/websockets/ws/releases)
- [Commits](https://github.com/websockets/ws/compare/8.11.0...8.17.1)

Updates `engine.io-client` from 6.5.3 to 6.5.4
- [Release notes](https://github.com/socketio/engine.io-client/releases)
- [Changelog](https://github.com/socketio/engine.io-client/blob/main/CHANGELOG.md)
- [Commits](https://github.com/socketio/engine.io-client/compare/6.5.3...6.5.4)

---
updated-dependencies:
- dependency-name: ws
  dependency-type: indirect
- dependency-name: engine.io-client
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-19 16:25:55 +00:00
dependabot[bot]
bec77f926e chore(deps): bump braces from 3.0.2 to 3.0.3 in /server (#10487)
Bumps [braces](https://github.com/micromatch/braces) from 3.0.2 to 3.0.3.
- [Changelog](https://github.com/micromatch/braces/blob/master/CHANGELOG.md)
- [Commits](https://github.com/micromatch/braces/compare/3.0.2...3.0.3)

---
updated-dependencies:
- dependency-name: braces
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-19 16:23:21 +00:00
renovate[bot]
ba57a1144d chore(deps): update prom/prometheus docker digest to 075b1ba (#10484)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-19 12:20:58 -04:00
Mert
b3f9641edf feat(web): bulk deduplicate (#10448)
* bulk deduplicate

* notification for keeping all duplicates

* fix notification

* remove unused text

* pr feedback

* wording

* formatting
2024-06-19 12:11:59 -04:00
Mert
86cbc6e125 chore(ml): support python 3.12 (#10481) 2024-06-19 10:51:10 -04:00
Mert
968553a50e fix(server): video thumbnail generation failing with single i-frame (#10477) 2024-06-19 10:50:25 -04:00
Mert
5813dc02d1 fix(server): let thumbnail generation fail on error (#10479) 2024-06-19 10:50:09 -04:00
Mert
58b17a866b feat(web): display original heif images for safari (#10478) 2024-06-19 10:49:59 -04:00
renovate[bot]
c58b0ac66a chore(deps): update typescript-projects (#10445)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-19 07:39:25 -04:00
waclaw66
517a83cfa9 fix(web): comment send button (#10453) 2024-06-18 16:29:46 -07:00
renovate[bot]
7daa761eed chore(deps): update mambaorg/micromamba:bookworm-slim docker digest to b17c9b1 (#10465)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-18 19:05:26 -04:00
Mert
e58131492d fix(server): consider all I-frames for video thumbnails (#10471)
nointra instead of nokey
2024-06-18 19:02:33 -04:00
renovate[bot]
b21572cb32 chore(deps): update machine-learning (#10446)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-18 00:45:29 -04:00
renovate[bot]
8332efcd04 chore(deps): update dependency exiftool-vendored to v27 (#10447)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-17 21:03:23 -07:00
Ben
b71aa4473b feat(web): keyboard accessible context menus (#10017)
* feat(web,a11y): context menu keyboard navigation

* wip: all context menus visible

* wip: more migrations to the ButtonContextMenu, usability improvements

* wip: migrate Administration, PeopleCard

* wip: refocus the button on click, docs

* fix: more intuitive RightClickContextMenu

- configurable title
- focus management: tab keys, clicks, closing the menu
- automatically closing when an option is selected

* fix: refining the little details

- adjust the aria attributes
- intuitive escape key propagation
- extract context into its own file

* fix: dropdown options not clickable in a <Portal>

* wip: small fixes

- export selectedColor to prevent unexpected styling
- better context function naming

* chore: revert changes to list navigation, to reduce scope of the PR

* fix: remove topBorder prop

* feat: automatically select the first option on enter or space keypress

* fix: use Svelte store instead to handle selecting menu options

- better prop naming for ButtonContextMenu

* feat: hovering the mouse can change the active element

* fix: remove Portal, more predictable open/close behavior

* feat: make selected item visible using a scroll

- also: minor cleanup of the context-menu-navigation Svelte action

* feat: maintain context menu position on resize

* fix: use the whole padding class as better tailwind convention

* fix: options not announcing with screen reader for ButtonContextMenu

* fix: screen reader announcing right click context menu options

* fix: handle focus out scenario

---------

Co-authored-by: Alex <alex.tran1502@gmail.com>
2024-06-17 20:52:38 -07:00
Muhideen Mujeeb Adeoye
99c6fdbc1c fix(mobile): upgrade maplibre_gl package to fix issue with crash in ios7.4 above simulator (#10182)
* fix(mobile): upgrade maplibre_gl package to fix issue with crash in ios7.4 above simulator

* chore: switch from deprecated widget and controller name to new name in latest sdk

* remove todo

---------

Co-authored-by: Alex <alex.tran1502@gmail.com>
2024-06-17 22:27:54 +00:00
aviv926
c1a5ed3526 fix(web): Update prompt (#10237)
* update

* update

* update

* npm run format:fix
2024-06-17 15:24:04 -07:00
dependabot[bot]
9000ce4283 chore(deps): bump docker/build-push-action from 5.4.0 to 6.0.0 (#10433)
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 5.4.0 to 6.0.0.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/v5.4.0...v6.0.0)

---
updated-dependencies:
- dependency-name: docker/build-push-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-17 13:11:55 -07:00
Alex
e8994d9ffd fix(web): confirm button is disabled if two dialogs are shown subsequently (#10440) 2024-06-17 11:44:25 -07:00
Stephen Smith
1b67ea2d91 chore(server): update exiftool and migrate off deprecated method signatures (#10367)
* chore(server): update exiftool and migrate off deprecated method signatures

* chore(server): update exiftool-vendored to 27.0.0

* chore(server): switch away from deprecated exiftool method signatures
- options now includes read/writeArgs making the deprecated signatures with
  args array redundant
- switch read call from file,args,options to file,options
- switch write call from file,tags,args to file,tags,options

* chore(server): move largefilesupport flags into exiftool constructor
- options now includes read/writeArgs making it available to be set globally in
  constructor
- switches back to instantiating an instance of exiftool

* chore(server): consolidate exiftool config into constructor along with writeArgs

* chore(server): move exiftool instantiation into MetadataRepository constructor
2024-06-17 10:11:11 -07:00
François-Guillaume Lemesre
38e26fd67c chore: update Unraid Docker-Compose documentation to reflect missing healthcheck start_interval parameter from Docker Engine v24.0.9. (#10406)
* Update Unraid Docker-Compose documentation to reflect missing healthcheck start_interval parameter from Docker Engine v24.0.9.

Unraid v6.12.10 uses Docker Engine v24.0.9, which does not support setting a start_interval parameter, used by the database container. Added info to the documentation to bypass this while retaining the initial health check interval.

* Fixed Markdown formatting.

* Removed info box formatting issue.

Moved the information about Unraid's Docker Engine version to section 4 of the installation instructions, instead of trying to use an info box that broke the formatting.

* fix format

---------

Co-authored-by: Alex <alex.tran1502@gmail.com>
2024-06-17 17:08:31 +00:00
RanKKI
29e4666dfa fix(mobile): asset description is not shown on the sheet when opened for the first time (#10377)
* fix: invalidate asset's description when asset details changed

* refactor(exif-sheet): use description from exif instead

* refactor(asset-description): remove asset_description.provider

* fix(asset-description): set is empty based on exifInfo.description

* chore: rename service to provider
2024-06-17 10:01:02 -07:00
RanKKI
7ce87abc95 fix(mobile): my location button on maps not visible due to bottom padding (#10384)
fix(maps): my location button not visible due to bottom padding
2024-06-17 08:48:58 -07:00
RanKKI
eb987c14c1 fix(mobile): search page (#10385)
* refactor(search): hide people/places if empty

* refactor(search): remove unused stack

* refactor(search): fix dropdown menu's width

* feat(search): show camera make/model vertically on mobile devices

* fix: lint errors
2024-06-17 08:47:04 -07:00
Michel Heusschen
a6e767e46d fix(web): selecting shared link expiration (#10437) 2024-06-17 08:31:11 -07:00
Michel Heusschen
8e373cee8d fix(server): include archived assets in forced thumbnail generation (#10409) 2024-06-16 16:16:02 -04:00
Mert
6b1b5054f8 feat(server): separate face search relation (#10371)
* wip

* various fixes

* new migration

* fix test

* add face search entity, update sql

* update e2e

* set storage to external
2024-06-16 19:25:27 +00:00
RanKKI
0fe152b1ef fix(mobile): translation for title (#10324)
* fix(memory): translation for title

* chore: update memoery translation for dutch

* refactor(translation): avoid incompatibility with i18n website

* fix: lint errors
2024-06-16 15:54:15 +00:00
Mert
e77e87b936 fix(server): orientation handling for person thumbnails (#10382)
fix orientation handling
2024-06-16 08:45:58 -07:00
Michel Heusschen
0b08af7082 fix(web): update avatar color immediately (#10393) 2024-06-16 08:38:32 -07:00
Michel Heusschen
010eb1e0d6 fix(server): include trashed assets in forced thumbnail generation (#10389)
* fix(server): include trashed assets in forced thumbnail generation

* deleted -> trashed
2024-06-16 08:37:51 -07:00
Michel Heusschen
83a851b556 fix(web): play video muted when blocked by browser (#10383) 2024-06-16 08:37:25 -07:00
RanKKI
1cd51cc2de fix(app-bar): remove safe area of the app bar in photos page (#10340)
fix(app-bar): remove safe area of appbar in photos
2024-06-15 13:47:12 -07:00
Michel Heusschen
f3c15c7df8 feat(web): full screen view for duplicates (#10346)
* feat(web): full screen view for duplicates

* styling: make button visibility better

---------

Co-authored-by: Alex <alex.tran1502@gmail.com>
2024-06-15 20:45:20 +00:00
Michel Heusschen
6a5435764e fix(web): allow sending test email when using config file (#10351)
fix(web): send test email when using config file
2024-06-15 12:14:28 -07:00
Michel Heusschen
dfad4f0ff4 fix(web): prevent new uploads from temporarily showing in trash (#10348) 2024-06-15 13:44:18 -04:00
Snowknight26
aea1c46bea feat(web): add cover images to individual shares (#9988)
* feat(web): add cover images to individual shares

* Update wording in share modal

* Use translation function

* Add and use new translations

* Fix formatting

* Update with suggestions

* Update test language

* Update test and language file per suggestions

* Fix formatting

* Remove unused translation
2024-06-14 19:16:48 -04:00
Jason Rasmussen
78f600ebce refactor(server): partner ids (#10321) 2024-06-14 18:29:32 -04:00
Daniel Dietzler
c896fe393f refactor(web): byte unit utils (#10332)
refactor byte unit utils
2024-06-14 17:27:46 +00:00
renovate[bot]
b4b654b53f fix(deps): update dependency exiftool-vendored to v26.2.0 (#10102)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-14 13:27:12 -04:00
Daniel Dietzler
dddc06c3b2 feat: user preferences for archive download size (#10296)
* feat: user preferences for archive download size

* chore: open api

* chore: clean up

---------

Co-authored-by: Jason Rasmussen <jrasm91@gmail.com>
2024-06-14 11:27:12 -04:00
Matthew Momjian
596412cb8f docs: brief instructions to recover from corruption (#10319)
* brief instructions for corruption

* Update FAQ.mdx
2024-06-14 07:59:33 -05:00
renovate[bot]
e3a314b649 chore(deps): update node.js to eb17a08 (#10098)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-14 02:34:08 -05:00
renovate[bot]
2bdb4bca9e chore(deps): update dependency flutter to v3.22.2 (#10158)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-14 02:33:27 -05:00
Ben
211451d234 chore(web): standardize settings labels (#10303)
* chore(web): standardize settings labels

- spelling out "max" and "min" in full
- accordions use title case
- labels for settings all use sentence case
- remove the "Enable"/"Enabled"/"ENABLED" titles for toggles, in favor
  of just using the description
- change any gray labels to be immich blue, to match the look and feel
  of the other settings

* chore: update user settings toggle, remove unused "enable" strings
2024-06-14 02:32:41 -05:00
Ronald Cantillo
e1731fe316 fix(doc): literal translation & missing parts (#10295) 2024-06-14 02:31:11 -05:00
renovate[bot]
ee186a40c2 fix(deps): update typescript-projects (#10105)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-13 20:46:26 -04:00
Weblate (bot)
32a0688028 chore(web): update translations (#10285)
Translate-URL: https://hosted.weblate.org/projects/immich/immich/cs/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/da/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/de/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/en_devel/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/es/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/fi/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/fr/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/hu/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/it/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/ko/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/lv/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/nl/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/pl/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/pt/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/ru/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/sr/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/th/
Translation: Immich/immich

Co-authored-by: AxGD <guillermeaxel@yahoo.fr>
Co-authored-by: David Anes <david.anes@gmail.com>
Co-authored-by: Eero Jääskeläinen <eero.jaaskelainen@gmail.com>
Co-authored-by: Gustavo Ceolin <gustavogiulceolin@hotmail.com>
Co-authored-by: IM Ben <beniiorga@gmail.com>
Co-authored-by: Immich <immich@futo.org>
Co-authored-by: Jordy H <jordy@hoebergen.net>
Co-authored-by: Julius969 <juliusdjorup@proton.me>
Co-authored-by: Kyle Park <mysky3056@gmail.com>
Co-authored-by: Macgyver <macgyver@users.noreply.hosted.weblate.org>
Co-authored-by: Maks s <smaks2313@gmail.com>
Co-authored-by: Meliox <silent.ftp@gmail.com>
Co-authored-by: Michel Heusschen <59014050+michelheusschen@users.noreply.github.com>
Co-authored-by: Miki M <medolino2009@gmail.com>
Co-authored-by: Napat Srichan <napatsrichan2001@gmail.com>
Co-authored-by: RJS <skudru.rinalds@gmail.com>
Co-authored-by: Samoht11 <thomasa24@gmail.com>
Co-authored-by: Sleeper CH <sleeperch@gmail.com>
Co-authored-by: Sophie <mail@sopht.li>
Co-authored-by: Thomas <thomas.ceccato.02@gmail.com>
Co-authored-by: carcawey <dacarva@gmail.com>
Co-authored-by: grgergo <gergo_g@proton.me>
Co-authored-by: kyu seok Park <tofinders@gmail.com>
Co-authored-by: mxm199 <mxm199@bk.ru>
Co-authored-by: waclaw66 <waclaw66@seznam.cz>
Co-authored-by: Владислав Потаенко <vipotaenko02@gmail.com>
Co-authored-by: Вячеслав Лукьяненко <madeinchuguev@gmail.com>
2024-06-14 00:35:49 +00:00
Daniel Dietzler
e5ed7d4af1 chore: update discord links (#10301)
update discord links
2024-06-13 20:27:01 -04:00
William Brockhus
30627fe91e chore: fix typo in jobs-workers.md (#10302) 2024-06-13 19:06:58 -04:00
Jason Rasmussen
77bd162872 fix(server): headers already send (#10289) 2024-06-13 13:30:34 -05:00
Jason Rasmussen
c6ab047167 fix(server): oauth linking error message (#10287) 2024-06-13 11:42:07 -04:00
Alex The Bot
8c2195c820 Version v1.106.4 2024-06-13 15:12:51 +00:00
Zack Pollard
5e99f651ec feat(web): add chinese (traditional), bislama and croatian to our supported languages (#10283)
* feat(web): add chinese (traditional), bislama and croatian to our supported languages

* test: remove language tag tests as it doesn't really test the correctness of tags
2024-06-13 15:00:55 +00:00
Weblate (bot)
0de15121f2 chore(web): update translations (#10224)
Translate-URL: https://hosted.weblate.org/projects/immich/immich/ca/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/cs/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/de/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/en_devel/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/es/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/fi/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/fr/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/he/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/hu/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/it/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/ja/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/ko/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/nb_NO/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/nl/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/pl/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/ru/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/sk/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/sl/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/sr/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/sv/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/th/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/vi/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/zh_Hant/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/zh_SIMPLIFIED/
Translation: Immich/immich

Co-authored-by: Adrian <adrian.hundseth@gmail.com>
Co-authored-by: Andrej Kralj <andrej.kralj@gmail.com>
Co-authored-by: Ari <ayhavlin@gmail.com>
Co-authored-by: AxGD <guillermeaxel@yahoo.fr>
Co-authored-by: Beniamin Iorga <beniiorga@gmail.com>
Co-authored-by: BoBBer446 <eXestend@gmx.de>
Co-authored-by: Daddie0 <33762262+GoByeBye@users.noreply.github.com>
Co-authored-by: David Anes <david.anes@gmail.com>
Co-authored-by: Eero Jääskeläinen <eero.jaaskelainen@gmail.com>
Co-authored-by: Erik Mizenak <erikmizenak@gmail.com>
Co-authored-by: Eryk Michalak <gnu.ewm@protonmail.com>
Co-authored-by: Fanfouer <fanfouer@outlook.com>
Co-authored-by: Fjuro <fjuro@alius.cz>
Co-authored-by: J1mooo <programingstafi@gmail.com>
Co-authored-by: Jan <jan.widmer.ch@gmail.com>
Co-authored-by: Jordi Masip <jordi@masip.cat>
Co-authored-by: Kihoon Kim <kihoon.kim.dev@gmail.com>
Co-authored-by: Kyle Park <mysky3056@gmail.com>
Co-authored-by: Londoneye02 <jcdelcaz@gmail.com>
Co-authored-by: Michel Heusschen <59014050+michelheusschen@users.noreply.github.com>
Co-authored-by: Miki M <medolino2009@gmail.com>
Co-authored-by: Nega Duck <negaduck420@gmail.com>
Co-authored-by: Pavel Shamshin <odan@selaz.org>
Co-authored-by: Peter Suba <peter.suba@gmail.com>
Co-authored-by: Pheggas <petko252@gmail.com>
Co-authored-by: PolarisYHNL <polarisyhnl@yeah.net>
Co-authored-by: Pontus Österlindh <Pompe90@users.noreply.hosted.weblate.org>
Co-authored-by: Ptsa Daniel <ptsa1987@gmail.com>
Co-authored-by: Ryan Gleeson <gleeson.ryanj@gmail.com>
Co-authored-by: Shawn <xiaxinx@gmail.com>
Co-authored-by: SisyphusMD <guardian.note2892@fastmail.com>
Co-authored-by: ZHYang <i526842@gmail.com>
Co-authored-by: ZOKOB <remyfrichet@gmail.com>
Co-authored-by: Zack Pollard <zack@futo.org>
Co-authored-by: ZtereoHYPE <me@ztereohype.dev>
Co-authored-by: buck5060 <buck5060@gmail.com>
Co-authored-by: carcawey <dacarva@gmail.com>
Co-authored-by: dvbthien <dvbthien@dvbthien.onmicrosoft.com>
Co-authored-by: eav5jhl0 <eav5jhl0@users.noreply.hosted.weblate.org>
Co-authored-by: grgergo <gergo_g@proton.me>
Co-authored-by: guillezcurra <guillezcurra@gmail.com>
Co-authored-by: kyu seok Park <tofinders@gmail.com>
Co-authored-by: mxm199 <mxm199@bk.ru>
Co-authored-by: opl- <jakub.trzy@op.pl>
Co-authored-by: pyorot <FMasic@hotmail.co.uk>
Co-authored-by: waclaw66 <waclaw66@seznam.cz>
Co-authored-by: yuuaHP <identity@yuua.dev>
Co-authored-by: Владислав Потаенко <vipotaenko02@gmail.com>
Co-authored-by: Вячеслав Лукьяненко <madeinchuguev@gmail.com>
Co-authored-by: 변준서 <four2mis@gmail.com>
2024-06-13 15:50:05 +01:00
Michel Heusschen
212ba35aef chore(web): translations in page load functions (#10260) 2024-06-13 09:23:52 -05:00
Richard Salame
827ec1b63a chore(doc): update quick-start.mdx (#10276)
Update quick-start.mdx

The following changes are made:

- Changed the headings to capital case.
- Changed a few sentences to sound more clear.
- Removed '?' from the heading as per the standards.
2024-06-13 09:23:02 -05:00
Alex
e2a2c86a31 chore(server): optional originalMimeType in asset response payload (#10272)
* chore(server): optional originalMimeType in asset response payload

* lint

* Update web/src/lib/utils/asset-utils.ts

Co-authored-by: Jason Rasmussen <jrasm91@gmail.com>

* fix permission of shared link

* test

* test

* test

* test server

---------

Co-authored-by: Jason Rasmussen <jrasm91@gmail.com>
2024-06-13 09:21:47 -05:00
bo0tzz
df31eb1214 chore(docs): Delete unsupported SQL shenanigans (#10278) 2024-06-13 13:58:44 +00:00
Zack Pollard
0d6a4975a3 chore(web): remove unnecessary input.select for lang selector (#10273)
chore: remove unnecessary input.select for lang selector
2024-06-13 12:44:06 +00:00
Zack Pollard
7de2665344 fix(web): more language selector nits (#10271)
* fix: always sort development lang to bottom of list

* fix: clear search query in languages when box is clicked
2024-06-13 12:37:15 +01:00
bo0tzz
058ca28d88 feat(web): Language settings list UX nits (#10261)
* feat(web): Sort language settings list

before: https://bo0.tz/u/xMLnEW.png
after: https://bo0.tz/u/lGLn9h.png

* feat(web): Select combobox text when focused
2024-06-13 06:01:18 -05:00
Min Idzelis
b9593361a4 chore: additional makefile targets (#10243) 2024-06-13 05:58:44 -05:00
Michel Heusschen
a54e01ef2f fix: load original image for gifs (#10252) 2024-06-13 05:57:46 -05:00
Mert
fb641c74be fix(server): use preview image when generating person thumbnail from video (#10240) 2024-06-12 22:16:26 -04:00
Alex
c642150b85 chore(mobile): post release task (#10228) 2024-06-12 14:17:58 -05:00
Alex The Bot
a8a7d29891 Version v1.106.3 2024-06-12 18:26:10 +00:00
Alex
67e98ed313 fix(mobile): video player not updating state (#10220)
* fix(mobile): video player not updating state

* unused code
2024-06-12 12:43:01 -05:00
renovate[bot]
47ef48e3c2 chore(deps): update base-image to v20240611 (major) (#10118)
chore(deps): update base-image to v20240611

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-12 12:51:46 -04:00
waclaw66
376feadb76 fix(web): missing svelte translations (#10199)
* fix(web): missing svelte translations

* fixes

* format fix

* translation keys fix

* "merge" key fix

* Update web/src/lib/components/shared-components/side-bar/more-information-albums.svelte

Co-authored-by: Michel Heusschen <59014050+michelheusschen@users.noreply.github.com>

* Update web/src/lib/i18n/en.json

Co-authored-by: Michel Heusschen <59014050+michelheusschen@users.noreply.github.com>

* suggestion fix

* trash pluralization

* video+photo count fix

* format fix

* unused removal

* translation key fix

* duplicate key removal

* format fix

---------

Co-authored-by: Michel Heusschen <59014050+michelheusschen@users.noreply.github.com>
2024-06-12 17:37:46 +01:00
Jason Rasmussen
3d82005797 fix: no floats (replace with doubles) (#10218)
* fix: no floats (replace with doubles)

* Update server/src/utils/misc.ts

Co-authored-by: Zack Pollard <zackpollard@ymail.com>

---------

Co-authored-by: Zack Pollard <zackpollard@ymail.com>
2024-06-12 17:36:24 +01:00
Weblate (bot)
10aa00af21 chore(web): update translations (#10216)
Translate-URL: https://hosted.weblate.org/projects/immich/immich/cs/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/es/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/nl/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/pl/
Translation: Immich/immich

Co-authored-by: Mario <17320863+myanesp@users.noreply.github.com>
Co-authored-by: Michel Heusschen <59014050+michelheusschen@users.noreply.github.com>
Co-authored-by: opl- <jakub.trzy@op.pl>
Co-authored-by: waclaw66 <waclaw66@seznam.cz>
2024-06-12 17:35:04 +01:00
Zack Pollard
1f8bdcdce7 chore: renovate shouldn't update mobile native dependencies (#10217) 2024-06-12 17:00:54 +01:00
Jason Rasmussen
98ebfc22f8 chore: translations from mobile (#10214) 2024-06-12 15:47:51 +01:00
Weblate (bot)
032b99fe93 chore(web): update translations (#10203)
Translate-URL: https://hosted.weblate.org/projects/immich/immich/cs/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/de/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/en_devel/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/es/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/fi/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/fr/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/he/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/hu/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/it/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/nl/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/pl/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/ro/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/ru/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/uk/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/zh_SIMPLIFIED/
Translation: Immich/immich

Co-authored-by: Alexandr Zhytnyk <oper.kh@gmail.com>
Co-authored-by: Amadeous <am4d3ous@users.noreply.hosted.weblate.org>
Co-authored-by: Beniamin Iorga <beniiorga@gmail.com>
Co-authored-by: Denis Pacquier <denis.pacquier@gmail.com>
Co-authored-by: Eero Jääskeläinen <eero.jaaskelainen@gmail.com>
Co-authored-by: Fanfouer <fanfouer@outlook.com>
Co-authored-by: Jan <jan.widmer.ch@gmail.com>
Co-authored-by: Kentai Radiquum <kentai.waah@gmail.com>
Co-authored-by: Kim <shnukoms@users.noreply.hosted.weblate.org>
Co-authored-by: Maximilian Waidelich <44324946+maxwai@users.noreply.github.com>
Co-authored-by: Maximilian Waidelich <maximilian.waidelich@gmail.com>
Co-authored-by: Michel Heusschen <59014050+michelheusschen@users.noreply.github.com>
Co-authored-by: PolarisYHNL <polarisyhnl@yeah.net>
Co-authored-by: Shawn <xiaxinx@gmail.com>
Co-authored-by: Thomas <thomas.ceccato.02@gmail.com>
Co-authored-by: Yves ANDOLFATTO <register@yves.aleeas.com>
Co-authored-by: ZtereoHYPE <me@ztereohype.dev>
Co-authored-by: carcawey <dacarva@gmail.com>
Co-authored-by: clementdelestre <clementdelestre@gmail.com>
Co-authored-by: eav5jhl0 <eav5jhl0@users.noreply.hosted.weblate.org>
Co-authored-by: mgabor <mgabor@users.noreply.hosted.weblate.org>
Co-authored-by: opl- <jakub.trzy@op.pl>
Co-authored-by: waclaw66 <waclaw66@seznam.cz>
Co-authored-by: wariw <wariwpl@gmail.com>
Co-authored-by: Владислав Потаенко <vipotaenko02@gmail.com>
Co-authored-by: Вячеслав Лукьяненко <madeinchuguev@gmail.com>
2024-06-12 15:38:18 +01:00
Zack Pollard
07156135c2 fix(server): double counting cores when processor name includes the word "processor" (#10211) 2024-06-12 13:49:20 +00:00
Michel Heusschen
9dbf5db72e fix(server): checkExistingAssets (#10192)
Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2024-06-12 08:48:44 -05:00
Daniel Heppner
52170423be feat(web): select all duplicates (#10189)
* feat(web): select all duplicates

Allows users to select or deselect all duplicate photos when removing duplicates

* styling

* chore(web): add more translations to duplicates page

* color

---------

Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
Co-authored-by: Zack Pollard <zackpollard@ymail.com>
2024-06-12 13:01:55 +00:00
Zack Pollard
ae095baad3 fix(server): only run healthchecks when api worker is running on immich-server (#10204)
fix: only run healthchecks when api worker is running on immich-server
2024-06-12 12:44:30 +01:00
Michel Heusschen
f99f289f74 fix(web): small translation issues + remove unused (#10200)
* fix(web): small translation issues + remove unused

* more unused keys

* formatting

* fix(web): incorrectly used translations

* fix and remove unused translations

---------

Co-authored-by: Zack Pollard <zackpollard@ymail.com>
2024-06-12 12:13:10 +01:00
Alex
476eea44df chore(web): remove thumbnail usage for places card (#10142)
* chore(web): remove thumbnail usage for places

* remove href attribute from Thumbnail

* linting
2024-06-12 11:12:58 +00:00
Jason Rasmussen
e84657192c refactor: config caching (#10168) 2024-06-12 11:07:35 +00:00
Mert
5dda5d93f5 chore(docs): remove microservices from hwa docs (#10188)
remove microservices from hwa docs
2024-06-12 11:57:40 +01:00
Michel Heusschen
6260caf649 fix(web): multi file upload in albums (#10190) 2024-06-12 11:57:11 +01:00
Michel Heusschen
9e5c52b7b7 chore(web): more translations for user settings and admin pages (#10161)
* chore(web): more translations for user settings and admin pages

* JobSettings translations

* feedback

* missed one

* feedback
2024-06-12 11:54:40 +01:00
Weblate (bot)
0e1311e3d3 chore(web): update translations (#10152)
Translate-URL: https://hosted.weblate.org/projects/immich/immich/ca/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/cs/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/de/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/es/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/fi/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/fr/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/he/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/hu/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/it/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/ja/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/ko/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/nb_NO/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/nl/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/pl/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/ru/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/sk/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/sl/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/sv/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/vi/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/zh_Hant/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/zh_SIMPLIFIED/
Translation: Immich/immich

Co-authored-by: 94tiger <94tiger@naver.com>
Co-authored-by: Adrian <adrian.hundseth@gmail.com>
Co-authored-by: Andrej Kralj <andrej.kralj@gmail.com>
Co-authored-by: AngelaDMerkel <personal@caduffy.com>
Co-authored-by: Anton <ajp_anton@hotmail.com>
Co-authored-by: Beniamin Iorga <beniiorga@gmail.com>
Co-authored-by: CanbiZ <mickey.leskowitz@gmail.com>
Co-authored-by: Eero Jääskeläinen <eero.jaaskelainen@gmail.com>
Co-authored-by: Eryk Michalak <gnu.ewm@protonmail.com>
Co-authored-by: Flowake <weblate.cx6on@passmail.net>
Co-authored-by: Immich <immich@futo.org>
Co-authored-by: Jakub <jakubula.jm@gmail.com>
Co-authored-by: Jan <account@thebraker.net>
Co-authored-by: Jan <jan.widmer.ch@gmail.com>
Co-authored-by: Jason Dean Lessenich <jasonlessenich@gmail.com>
Co-authored-by: Joachim Klahr <joachim@klahr.se>
Co-authored-by: Joseph <josephlegrand33+hosted.weblate.org@gmail.com>
Co-authored-by: Julien Deveaux <julien.deveaux@hotmail.com>
Co-authored-by: Kentai Radiquum <kentai.waah@gmail.com>
Co-authored-by: Kim <shnukoms@users.noreply.hosted.weblate.org>
Co-authored-by: Kyle Park <mysky3056@gmail.com>
Co-authored-by: League2EB <info@league2eb.me>
Co-authored-by: Londoneye02 <jcdelcaz@gmail.com>
Co-authored-by: Luca Kröger <l.kroeger01@gmail.com>
Co-authored-by: Manic87 <nicolas@familie-mach.net>
Co-authored-by: Marcos Besteiro López (MarcosBL) <marcosbl@gmail.com>
Co-authored-by: MeisterEder286 <walbrun.johann@gmail.com>
Co-authored-by: Michel Heusschen <59014050+michelheusschen@users.noreply.github.com>
Co-authored-by: Miko-Matias Grönvall <matias.gronvall@gmail.com>
Co-authored-by: MozPri <primoz.arh@gmail.com>
Co-authored-by: Nathan <bonnemainsnathan@gmail.com>
Co-authored-by: Ole Morten Didriksen <code@oledid.com>
Co-authored-by: Pavel Shamshin <odan@selaz.org>
Co-authored-by: Peter Suba <peter.suba@gmail.com>
Co-authored-by: Pheggas <petko252@gmail.com>
Co-authored-by: PolarisYHNL <polarisyhnl@yeah.net>
Co-authored-by: Ptsa Daniel <ptsa1987@gmail.com>
Co-authored-by: RWDai <869759838@qq.com>
Co-authored-by: Shawn <xiaxinx@gmail.com>
Co-authored-by: Simmer Lajos <weblate.linguini033@passinbox.com>
Co-authored-by: SisyphusMD <guardian.note2892@fastmail.com>
Co-authored-by: Smiehoo <github@pocz.net>
Co-authored-by: Thomas <thomas.ceccato.02@gmail.com>
Co-authored-by: Tomas Babej <web+weblate@tbabej.com>
Co-authored-by: Tomek <tjomek@gmail.com>
Co-authored-by: VB <Victor2B@protonmail.com>
Co-authored-by: Vojtěch Bargl <bargl.vojtech@gmail.com>
Co-authored-by: YFrendo <yann.frendo@live.fr>
Co-authored-by: Yves ANDOLFATTO <register@yves.aleeas.com>
Co-authored-by: ZtereoHYPE <me@ztereohype.dev>
Co-authored-by: biglate <bigtech+weblate@aleeas.com>
Co-authored-by: carcawey <dacarva@gmail.com>
Co-authored-by: clementdelestre <clementdelestre@gmail.com>
Co-authored-by: eav5jhl0 <eav5jhl0@users.noreply.hosted.weblate.org>
Co-authored-by: ferrets <ferrets@live.cn>
Co-authored-by: frauhottelmann <frauhottelmann@gmail.com>
Co-authored-by: gilo <giantlolli@proton.me>
Co-authored-by: grgergo <gergo_g@proton.me>
Co-authored-by: guillezcurra <guillezcurra@gmail.com>
Co-authored-by: ingria <codefuhrer@gmail.com>
Co-authored-by: jie65535 <jie65535@qq.com>
Co-authored-by: myurar1a <sirometroid1235@outlook.jp>
Co-authored-by: sephrat <florian.dupret@gmail.com>
Co-authored-by: waclaw66 <waclaw66@seznam.cz>
Co-authored-by: Вячеслав Лукьяненко <madeinchuguev@gmail.com>
Co-authored-by: Кирилл Москатов <kirillmoskatov@gmail.com>
2024-06-12 11:52:33 +01:00
Stephen Smith
216cca4383 fix(server): exiftool largefilesupport only set for the first call (#10167)
* Revert "feat(server): enable exiftool largefilesupport (#9894)"

This reverts commit afa10ebcb2.

* feat(server): enable exiftool largefilesupport by passing options to read
2024-06-12 05:43:38 -05:00
Mert
cdc98de848 fix(server): increase pixel limit for thumbnail generation (#10181)
disable input limit
2024-06-11 22:11:03 -04:00
Mert
126cbeabe8 feat(server): add av1 support for vaapi (#10180)
add av1
2024-06-12 00:24:06 +00:00
Mert
2e0c6f6fff fix: postgres health check reporting any db without checksums as unhealthy (#10178)
handle disabled checksumming
2024-06-12 00:18:24 +00:00
Alex The Bot
81790ab166 Version v1.106.2 2024-06-11 19:09:13 +00:00
Alejandro Armas
69b948f3d0 fix(mobile): Motion Photos stopping music (#10151)
Add videoPlayer opt to prevent motionPhotos pausing music
2024-06-11 11:14:49 -05:00
Weblate (bot)
4b2ed28b1a chore: update translations (#10141)
chore(web): update translations

Translate-URL: https://hosted.weblate.org/projects/immich/immich/ca/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/de/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/es/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/fr/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/ja/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/nl/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/pl/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/ru/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/zh_SIMPLIFIED/
Translation: Immich/immich

Co-authored-by: Alex van den Hoogen <alex3305@gmail.com>
Co-authored-by: CanbiZ <mickey.leskowitz@gmail.com>
Co-authored-by: Eryk Michalak <gnu.ewm@protonmail.com>
Co-authored-by: Jordi Masip <jordi@masip.cat>
Co-authored-by: Joseph <josephlegrand33+hosted.weblate.org@gmail.com>
Co-authored-by: Kentai Radiquum <kentai.waah@gmail.com>
Co-authored-by: Manic87 <nicolas@familie-mach.net>
Co-authored-by: Marcos Besteiro López (MarcosBL) <marcosbl@gmail.com>
Co-authored-by: Mario <shopping.uncate@aleeas.com>
Co-authored-by: Michał Kulik <michal.kulik91@gmail.com>
Co-authored-by: Michel Heusschen <59014050+michelheusschen@users.noreply.github.com>
Co-authored-by: PolairsYHNL-Immich <polarisyhnl@gmail.com>
Co-authored-by: PolarisYHNL <polarisyhnl@yeah.net>
Co-authored-by: Shawn <xiaxinx@gmail.com>
Co-authored-by: Sophie <mail@sopht.li>
Co-authored-by: Zack Pollard <zack@futo.org>
Co-authored-by: upsetdog <upsetdog@proton.me>
Co-authored-by: Łukasz Kierepka <lukasz_kierepka@hotmail.com>
2024-06-11 17:06:53 +01:00
Michel Heusschen
b8e6ae65b1 fix(web): backwards asset navigation in GalleryViewer (#10132)
* fix(web): backwards asset navigation in GalleryViewer

* fix ctrl/cmd click

---------

Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2024-06-11 15:27:18 +00:00
renovate[bot]
36bdbf93a7 fix(deps): update machine-learning (#10099)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-11 11:24:10 -04:00
Alex
3eee6c4dcf fix(web): cannot view image when metadata sharing is turned off for public sharing (#10145)
* fix(web): cannot view image when metadata sharing is turned off for public sharing

* Update web/src/lib/utils/asset-utils.ts

Co-authored-by: Jason Rasmussen <jrasm91@gmail.com>

---------

Co-authored-by: Jason Rasmussen <jrasm91@gmail.com>
2024-06-11 15:23:48 +00:00
Michel Heusschen
3a3676bc82 fix(server): cache-control header missing from / requests (#10131)
* fix(server): cache-control header missing from / requests

* disable extension fallback
2024-06-11 10:18:52 -05:00
Zack Pollard
34fc572276 chore: update translations (#10140)
* chore:  (Chinese (Simplified) (zh_SIMPLIFIED))

Currently translated at 81.0% (631 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/zh_SIMPLIFIED/

* chore:  (Chinese (Simplified) (zh_SIMPLIFIED))

Currently translated at 81.0% (631 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/zh_SIMPLIFIED/

---------

Co-authored-by: PolairsYHNL-Immich <polarisyhnl@gmail.com>
Co-authored-by: Zack Pollard <zack@futo.org>
2024-06-11 15:28:03 +01:00
Zack Pollard
ef17c257ef chore: update translations (#10138)
* chore:  (German)

Currently translated at 8.9% (70 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/de/

* chore:  (German)

Currently translated at 8.9% (70 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/de/

* chore:  (Spanish)

Currently translated at 4.2% (33 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/es/

* chore:  (French)

Currently translated at 2.4% (19 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/fr/

* chore:  (French)

Currently translated at 2.4% (19 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/fr/

* chore:  (Russian)

Currently translated at 0.5% (4 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/ru/

* chore:  (Chinese (Simplified) (zh_SIMPLIFIED))

Currently translated at 75.6% (589 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/zh_SIMPLIFIED/

* chore:  (Chinese (Simplified) (zh_SIMPLIFIED))

Currently translated at 75.6% (589 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/zh_SIMPLIFIED/

* chore:  (German)

Currently translated at 34.2% (267 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/de/

* chore:  (German)

Currently translated at 34.2% (267 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/de/

* chore:  (German)

Currently translated at 34.2% (267 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/de/

* chore:  (German)

Currently translated at 34.2% (267 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/de/

* chore:  (French)

Currently translated at 4.1% (32 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/fr/

* chore:  (French)

Currently translated at 4.1% (32 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/fr/

* chore:  (French)

Currently translated at 4.1% (32 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/fr/

* chore:  (Italian)

Currently translated at 3.9% (31 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/it/

* chore:  (Italian)

Currently translated at 3.9% (31 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/it/

* chore:  (Czech)

Currently translated at 2.6% (21 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/cs/

* chore:  (Chinese (Simplified) (zh_SIMPLIFIED))

Currently translated at 75.7% (590 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/zh_SIMPLIFIED/

* chore:  (Chinese (Simplified) (zh_SIMPLIFIED))

Currently translated at 75.7% (590 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/zh_SIMPLIFIED/

* chore:  (German)

Currently translated at 34.2% (267 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/de/

* chore:  (German)

Currently translated at 34.2% (267 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/de/

* chore:  (Russian)

Currently translated at 0.7% (6 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/ru/

* chore:  (German)

Currently translated at 34.2% (267 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/de/

* chore:  (Dutch)

Currently translated at 5.9% (46 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/nl/

* chore:  (Italian)

Currently translated at 4.4% (35 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/it/

* chore:  (Polish)

Currently translated at 0.6% (5 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/pl/

* chore:  (Russian)

Currently translated at 1.9% (15 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/ru/

* chore:  (Russian)

Currently translated at 1.9% (15 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/ru/

* chore:  (Chinese (Simplified) (zh_SIMPLIFIED))

Currently translated at 76.5% (596 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/zh_SIMPLIFIED/

* chore:  (Chinese (Simplified) (zh_SIMPLIFIED))

Currently translated at 76.5% (596 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/zh_SIMPLIFIED/

* chore:  (Chinese (Simplified) (zh_SIMPLIFIED))

Currently translated at 76.5% (596 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/zh_SIMPLIFIED/

* chore:  (Chinese (Simplified) (zh_SIMPLIFIED))

Currently translated at 76.5% (596 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/zh_SIMPLIFIED/

* chore:  (Chinese (Simplified) (zh_SIMPLIFIED))

Currently translated at 76.5% (596 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/zh_SIMPLIFIED/

* chore:  (Dutch)

Currently translated at 6.0% (47 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/nl/

* chore:  (Chinese (Simplified) (zh_SIMPLIFIED))

Currently translated at 76.5% (596 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/zh_SIMPLIFIED/

* chore:  (Chinese (Simplified) (zh_SIMPLIFIED))

Currently translated at 76.5% (596 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/zh_SIMPLIFIED/

* chore:  (Russian)

Currently translated at 2.0% (16 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/ru/

* chore:  (Chinese (Simplified) (zh_SIMPLIFIED))

Currently translated at 80.2% (625 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/zh_SIMPLIFIED/

* chore:  (Chinese (Simplified) (zh_SIMPLIFIED))

Currently translated at 80.2% (625 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/zh_SIMPLIFIED/

* chore:  (Vietnamese)

Currently translated at 0.5% (4 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/vi/

---------

Co-authored-by: CanbiZ <mickey.leskowitz@gmail.com>
Co-authored-by: Luca Kröger <l.kroeger01@gmail.com>
Co-authored-by: Héctor Martínez Juste <hectorzin@hotmail.com>
Co-authored-by: Nathan <bonnemainsnathan@gmail.com>
Co-authored-by: Fanfouer <fanfouer@outlook.com>
Co-authored-by: Вячеслав Лукьяненко <madeinchuguev@gmail.com>
Co-authored-by: PolarisYHNL <polarisyhnl@yeah.net>
Co-authored-by: Shawn <xiaxinx@gmail.com>
Co-authored-by: Sophie <mail@sopht.li>
Co-authored-by: Stefan Gries <stefan@gries.nrw>
Co-authored-by: Bouchet Mateo <mateo.bouchet+hosted.weblate.org@mhaz42.fr>
Co-authored-by: Alessandro Saglia <webslate.eskimo0977@bear-d.me>
Co-authored-by: ZtereoHYPE <me@ztereohype.dev>
Co-authored-by: waclaw66 <waclaw66@seznam.cz>
Co-authored-by: jie65535 <jie65535@qq.com>
Co-authored-by: Michel Heusschen <59014050+michelheusschen@users.noreply.github.com>
Co-authored-by: Eryk Michalak <gnu.ewm@protonmail.com>
Co-authored-by: Pavel Shamshin <odan@selaz.org>
Co-authored-by: Quan <weiyideai520@hotmail.com>
Co-authored-by: PolairsYHNL-Immich <polarisyhnl@gmail.com>
Co-authored-by: Zack Pollard <zack@futo.org>
2024-06-11 15:16:23 +01:00
Weblate (bot)
4c69cb89d7 chore: update translations (#10125)
* chore:  (German)

Currently translated at 8.9% (70 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/de/

* chore:  (German)

Currently translated at 8.9% (70 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/de/

* chore:  (Spanish)

Currently translated at 4.2% (33 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/es/

* chore:  (French)

Currently translated at 2.4% (19 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/fr/

* chore:  (French)

Currently translated at 2.4% (19 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/fr/

* chore:  (Russian)

Currently translated at 0.5% (4 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/ru/

* chore:  (Chinese (Simplified) (zh_SIMPLIFIED))

Currently translated at 75.6% (589 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/zh_SIMPLIFIED/

* chore:  (Chinese (Simplified) (zh_SIMPLIFIED))

Currently translated at 75.6% (589 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/zh_SIMPLIFIED/

* chore:  (German)

Currently translated at 34.2% (267 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/de/

* chore:  (German)

Currently translated at 34.2% (267 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/de/

* chore:  (German)

Currently translated at 34.2% (267 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/de/

* chore:  (German)

Currently translated at 34.2% (267 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/de/

* chore:  (French)

Currently translated at 4.1% (32 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/fr/

* chore:  (French)

Currently translated at 4.1% (32 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/fr/

* chore:  (French)

Currently translated at 4.1% (32 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/fr/

* chore:  (Italian)

Currently translated at 3.9% (31 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/it/

* chore:  (Italian)

Currently translated at 3.9% (31 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/it/

* chore:  (Czech)

Currently translated at 2.6% (21 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/cs/

* chore:  (Chinese (Simplified) (zh_SIMPLIFIED))

Currently translated at 75.7% (590 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/zh_SIMPLIFIED/

* chore:  (Chinese (Simplified) (zh_SIMPLIFIED))

Currently translated at 75.7% (590 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/zh_SIMPLIFIED/

* chore:  (German)

Currently translated at 34.2% (267 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/de/

* chore:  (German)

Currently translated at 34.2% (267 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/de/

* chore:  (Russian)

Currently translated at 0.7% (6 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/ru/

* chore:  (German)

Currently translated at 34.2% (267 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/de/

* chore:  (Dutch)

Currently translated at 5.9% (46 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/nl/

* chore:  (Italian)

Currently translated at 4.4% (35 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/it/

* chore:  (Polish)

Currently translated at 0.6% (5 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/pl/

* chore:  (Russian)

Currently translated at 1.9% (15 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/ru/

* chore:  (Russian)

Currently translated at 1.9% (15 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/ru/

* chore:  (Chinese (Simplified) (zh_SIMPLIFIED))

Currently translated at 76.5% (596 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/zh_SIMPLIFIED/

* chore:  (Chinese (Simplified) (zh_SIMPLIFIED))

Currently translated at 76.5% (596 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/zh_SIMPLIFIED/

* chore:  (Chinese (Simplified) (zh_SIMPLIFIED))

Currently translated at 76.5% (596 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/zh_SIMPLIFIED/

* chore:  (Chinese (Simplified) (zh_SIMPLIFIED))

Currently translated at 76.5% (596 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/zh_SIMPLIFIED/

* chore:  (Chinese (Simplified) (zh_SIMPLIFIED))

Currently translated at 76.5% (596 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/zh_SIMPLIFIED/

* chore:  (Dutch)

Currently translated at 6.0% (47 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/nl/

* chore:  (Chinese (Simplified) (zh_SIMPLIFIED))

Currently translated at 76.5% (596 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/zh_SIMPLIFIED/

* chore:  (Chinese (Simplified) (zh_SIMPLIFIED))

Currently translated at 76.5% (596 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/zh_SIMPLIFIED/

---------

Co-authored-by: CanbiZ <mickey.leskowitz@gmail.com>
Co-authored-by: Luca Kröger <l.kroeger01@gmail.com>
Co-authored-by: Héctor Martínez Juste <hectorzin@hotmail.com>
Co-authored-by: Nathan <bonnemainsnathan@gmail.com>
Co-authored-by: Fanfouer <fanfouer@outlook.com>
Co-authored-by: Вячеслав Лукьяненко <madeinchuguev@gmail.com>
Co-authored-by: PolarisYHNL <polarisyhnl@yeah.net>
Co-authored-by: Shawn <xiaxinx@gmail.com>
Co-authored-by: Sophie <mail@sopht.li>
Co-authored-by: Stefan Gries <stefan@gries.nrw>
Co-authored-by: Bouchet Mateo <mateo.bouchet+hosted.weblate.org@mhaz42.fr>
Co-authored-by: Alessandro Saglia <webslate.eskimo0977@bear-d.me>
Co-authored-by: ZtereoHYPE <me@ztereohype.dev>
Co-authored-by: waclaw66 <waclaw66@seznam.cz>
Co-authored-by: jie65535 <jie65535@qq.com>
Co-authored-by: Michel Heusschen <59014050+michelheusschen@users.noreply.github.com>
Co-authored-by: Eryk Michalak <gnu.ewm@protonmail.com>
Co-authored-by: Pavel Shamshin <odan@selaz.org>
Co-authored-by: Quan <weiyideai520@hotmail.com>
Co-authored-by: PolairsYHNL-Immich <polarisyhnl@gmail.com>
2024-06-11 13:42:57 +00:00
Jason Rasmussen
735455508c feat(cli): auto-release (#10127) 2024-06-11 08:33:36 -05:00
Alex
eba166a2f1 fix(web): cannot click on explore place (#10121) 2024-06-11 08:32:39 -05:00
Jason Rasmussen
8cf8a2cb35 chore(cli): prepare release (#10124) 2024-06-11 12:16:49 +00:00
Zack Pollard
1767ed2192 chore(web): enable prettier json key sorting recursively (#10120) 2024-06-11 12:52:20 +01:00
Zack Pollard
3c15dae341 docs: fix archive script labels and change to variable to nextVersion (#10119) 2024-06-11 12:37:20 +01:00
Zack Pollard
8568c2e8b9 docs: add archived docs back to v1.100.0 (#10116)
chore: add archived docs back to v1.100.0
2024-06-11 12:36:59 +01:00
Alex
d558ea819a fix(web): cannot perform duplication actions as normal user (#10115)
* fix(web): cannot perform duplication actions as normal user

* use immich dialog
2024-06-11 11:30:42 +00:00
Alex
60701d131e chore(mobile): post release pump (#10114) 2024-06-11 06:26:52 -05:00
Alex
04808f8b5c fix(mobile): warning message not resetting when changing server URL (#10112) 2024-06-11 10:48:49 +00:00
Zack Pollard
8a866297f7 docs: fix archive version url to include v prefix (#10111)
* docs: fix archive version url to include v prefix

* docs: fix archived versions to add missing v to 1.106.1
2024-06-11 05:43:39 -05:00
Alex The Bot
b5991c908e Version v1.106.1 2024-06-11 09:39:23 +00:00
Jason Rasmussen
321c3ccfc6 docs: version switcher (#10091)
* docs: version switcher

* chore: pump script

* chore: fix linting on bash script

* chore: remove 1.106.0 from archived versions

* chore: change version archive script to take next server version not current version

---------

Co-authored-by: Zack Pollard <zackpollard@ymail.com>
2024-06-11 10:33:45 +01:00
Weblate (bot)
05874bd84e chore: update translations (#10096)
* chore:  (German)

Currently translated at 5.9% (46 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/de/

* chore:  (Italian)

Currently translated at 3.5% (28 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/it/

* chore:  (German)

Currently translated at 8.7% (68 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/de/

* chore:  (Chinese (Simplified) (zh_SIMPLIFIED))

Currently translated at 74.9% (584 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/zh_SIMPLIFIED/

* chore:  (Dutch)

Currently translated at 5.7% (45 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/nl/

* chore:  (English (Developer))

Currently translated at 100.0% (779 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/en_devel/

---------

Co-authored-by: Martin Bosner <martin@bosner.de>
Co-authored-by: Alessandro Saglia <webslate.eskimo0977@bear-d.me>
Co-authored-by: Sophie <mail@sopht.li>
Co-authored-by: jie65535 <jie65535@qq.com>
Co-authored-by: Zack Pollard <zack@futo.org>
2024-06-11 10:09:58 +01:00
Michel Heusschen
79705dc58d fix(web): language selector for chinese and norwegian (#10107)
* fix(web): language selector for chinese and norwegian

* add unit test

* formatter

* undo name change
2024-06-11 09:07:42 +00:00
aviv926
71a132b0b8 docs: Update the system settings page (#10094)
* updating

* npm run format

* fix \ > /
2024-06-11 03:52:29 -05:00
Alex
d14f23497c fix(server): album update disable no effect (#10090) 2024-06-11 03:51:58 -05:00
Jason Rasmussen
a916df56ee fix: roadmap docs (#10095) 2024-06-10 22:11:50 +00:00
aviv926
73dcb9b452 docs: add detect duplicate assets to roadmap (#10093)
* updating

* uncomment
2024-06-10 21:49:10 +00:00
Alex The Bot
f32c02bd25 Version v1.106.0 2024-06-10 17:50:00 +00:00
Zack Pollard
b16c9405d8 docs: otel metrics port worker split (#10085) 2024-06-10 12:44:10 -05:00
Alex
46df165ef2 feat(mobile): compatibility message warning (#10065)
* feat(mobile): compatibility message warning

* refactor and better signature
2024-06-10 12:43:54 -05:00
Zack Pollard
19e35d8d3f chore(server): remove unused imagemin type dependency (#10084) 2024-06-10 17:08:25 +00:00
Alex
c4c070569f fix(web): mouse-wheel scrolling on detail panel is disabled (#10080) 2024-06-10 12:05:52 -05:00
Jason Rasmussen
7651f70c88 fix(server): asset delete logic (#10077)
* fix(server): asset delete logic

* test: e2e
2024-06-10 13:04:34 -04:00
Alex
4698c39855 chore: remove pr labeler requirement (#10081) 2024-06-10 12:59:19 -04:00
Zack Pollard
2f2aecfb47 fix(server): otel not working due to port conflicts after combining containers (#10078)
fix: otel not working due to port conflicts after combining containers

Fixes #9759
2024-06-10 16:01:04 +00:00
dependabot[bot]
20efd82461 chore(deps): bump docker/build-push-action from 5.3.0 to 5.4.0 (#10069)
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 5.3.0 to 5.4.0.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/v5.3.0...v5.4.0)

---
updated-dependencies:
- dependency-name: docker/build-push-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-10 14:52:50 +01:00
Zack Pollard
22a0b4d900 chore(web): order json files alphabetically (#10076) 2024-06-10 09:37:21 -04:00
Weblate (bot)
2f25a8a437 chore: update translations (#10075)
chore:  (Vietnamese)

Currently translated at 0.3% (3 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/vi/

Co-authored-by: Zack Pollard <zack@futo.org>
2024-06-10 14:16:21 +01:00
Weblate (bot)
7a0bc0ea87 chore: update translations (#10074)
chore:  (Vietnamese)

Currently translated at 0.2% (2 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/vi/

Co-authored-by: Zack Pollard <zack@futo.org>
2024-06-10 14:09:12 +01:00
Weblate (bot)
a564c80193 chore: update translations (#10073)
chore:  (Vietnamese)

Currently translated at 0.1% (1 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/vi/

Co-authored-by: Zack Pollard <zack@futo.org>
2024-06-10 14:06:20 +01:00
Weblate (bot)
f4671617d1 chore: update translations (#10072)
chore:  (Vietnamese)

Currently translated at 0.1% (1 of 779 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/vi/

Co-authored-by: Zack Pollard <zack@futo.org>
2024-06-10 14:03:48 +01:00
Zack Pollard
d331da0ced chore(web): fix weblate conflicts (#10071)
* chore:  (Chinese (Simplified) (zh_SIMPLIFIED))

Currently translated at 29.2% (228 of 780 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/zh_SIMPLIFIED/

* chore:  (Chinese (Simplified) (zh_SIMPLIFIED))

Currently translated at 29.2% (228 of 780 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/zh_SIMPLIFIED/

* chore:  (Chinese (Simplified) (zh_SIMPLIFIED))

Currently translated at 29.2% (228 of 780 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/zh_SIMPLIFIED/

* chore:  (Dutch)

Currently translated at 5.8% (46 of 780 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/nl/

* chore:  (Chinese (Simplified) (zh_SIMPLIFIED))

Currently translated at 51.2% (400 of 780 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/zh_SIMPLIFIED/

* chore:  (Chinese (Simplified) (zh_SIMPLIFIED))

Currently translated at 51.2% (400 of 780 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/zh_SIMPLIFIED/

* chore:  (Chinese (Simplified) (zh_SIMPLIFIED))

Currently translated at 51.2% (400 of 780 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/zh_SIMPLIFIED/

* chore:  (German)

Currently translated at 5.7% (45 of 780 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/de/

* chore:  (German)

Currently translated at 5.7% (45 of 780 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/de/

* chore:  (Hungarian)

Currently translated at 0.1% (1 of 780 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/hu/

* chore:  (Chinese (Simplified) (zh_SIMPLIFIED))

Currently translated at 55.3% (432 of 780 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/zh_SIMPLIFIED/

* chore:  (Chinese (Simplified) (zh_SIMPLIFIED))

Currently translated at 55.3% (432 of 780 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/zh_SIMPLIFIED/

* chore:  (German)

Currently translated at 5.7% (45 of 780 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/de/

* chore:  (Dutch)

Currently translated at 5.8% (46 of 780 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/nl/

* chore:  (Spanish)

Currently translated at 0.1% (1 of 780 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/es/

* chore:  (Arabic)

Currently translated at 0.0% (0 of 780 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/ar/

* chore:  (Catalan)

Currently translated at 0.0% (0 of 780 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/ca/

* chore:  (Danish)

Currently translated at 0.0% (0 of 780 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/da/

* chore:  (Finnish)

Currently translated at 0.0% (0 of 780 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/fi/

* chore:  (French)

Currently translated at 0.0% (0 of 780 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/fr/

* chore:  (Hebrew)

Currently translated at 0.0% (0 of 780 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/he/

* chore:  (Hindi)

Currently translated at 0.0% (0 of 780 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/hi/

* chore:  (Hungarian)

Currently translated at 0.1% (1 of 780 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/hu/

* chore:  (Italian)

Currently translated at 0.0% (0 of 780 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/it/

* chore:  (Japanese)

Currently translated at 0.0% (0 of 780 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/ja/

* chore:  (Korean)

Currently translated at 0.0% (0 of 780 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/ko/

* chore:  (Lithuanian)

Currently translated at 0.0% (0 of 780 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/lt/

* chore:  (Latvian)

Currently translated at 0.0% (0 of 780 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/lv/

* chore:  (Mongolian)

Currently translated at 0.0% (0 of 780 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/mn/

* chore:  (Norwegian Bokmål)

Currently translated at 0.0% (0 of 780 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/nb_NO/

* chore:  (Polish)

Currently translated at 0.0% (0 of 780 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/pl/

* chore:  (Portuguese)

Currently translated at 0.0% (0 of 780 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/pt/

* chore:  (Romanian)

Currently translated at 0.0% (0 of 780 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/ro/

* chore:  (Russian)

Currently translated at 0.0% (0 of 780 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/ru/

* chore:  (Slovak)

Currently translated at 0.0% (0 of 780 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/sk/

* chore:  (Slovenian)

Currently translated at 0.0% (0 of 780 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/sl/

* chore:  (Serbian)

Currently translated at 0.0% (0 of 780 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/sr/

* chore:  (Swedish)

Currently translated at 0.0% (0 of 780 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/sv/

* chore:  (Thai)

Currently translated at 0.0% (0 of 780 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/th/

* chore:  (Ukrainian)

Currently translated at 0.0% (0 of 780 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/uk/

* chore:  (Vietnamese)

Currently translated at 0.0% (0 of 780 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/vi/

* chore:  (Czech)

Currently translated at 0.0% (0 of 780 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/cs/

* chore:  (Chinese (Simplified) (zh_SIMPLIFIED))

Currently translated at 55.3% (432 of 780 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/zh_SIMPLIFIED/

* chore(web): enable prettier for json files in web

---------

Co-authored-by: PolarisYHNL <polarisyhnl@yeah.net>
Co-authored-by: LLL <326867814@qq.com>
Co-authored-by: jie65535 <jie65535@qq.com>
Co-authored-by: bo0tzz <git@bo0tzz.me>
Co-authored-by: CanbiZ <mickey.leskowitz@gmail.com>
Co-authored-by: Manic87 <nicolas@familie-mach.net>
Co-authored-by: Peter Suba <peter.suba@gmail.com>
Co-authored-by: Anonymous <noreply@weblate.org>
2024-06-10 13:59:54 +01:00
aviv926
84da9abcbc docs: Add Email Notifications info (#9930)
* Add Email Notifications info

* remove spaces

* Add ` ` to smtp link

* Small fixes

* PR feedback

* npm run format:fix

* PR feedback

* update docs

* formatting

---------

Co-authored-by: Alex <alex.tran1502@gmail.com>
2024-06-09 20:07:08 +00:00
Robert Schäfer
48eede59b9 refactor: dedicated icon for permanently delete (#10052)
Motivation
----------
It's a follow up to #10028. I think it would be better user experience if one can tell by the icon what the delete button is about to do.

I hope I caught all the occurences where one can permanently delete assets.

How to test
-----------
1. Visit e.g. `/trash`
2. If you select some assets, the delete button in the top right corner
   looks different.
2024-06-09 14:25:27 -05:00
Fynn Petersen-Frey
972c66d467 fix(server): proper asset sync (#10019)
* fix(server,mobile): proper asset sync

* fix CI issues

* only use id instead of createdAt+id

* remove createdAt index

* fix typo

* cleanup createdAt usage

---------

Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2024-06-09 14:19:28 -05:00
Robert Schäfer
69795a3763 refactor: remove dead code from Makefile (#10061)
Motivation
----------
I guess these make targets should have been deleted in 57136e48fb.

How to test
-----------
1. Nothing really, this removes dead code
2024-06-09 19:18:41 +00:00
Robert Schäfer
9c337223e6 ci: automatically apply PR labels (#10064)
Motivation
----------
For me as a new contributor it is frustrating to submit a PR and it will always fail. Even worse: I have to wait for another contributor with more power to assign the label for me.

This will improve developer experience, as some of the labels can be assigned automatically based on changed files.

How to test
-----------
1. Merge this PR
2. Submit a couple of PRs with changes in the respective directories
3. Labels should be automatically applied
4. "Enforce PR labels" github workflow will re-run when "Pull Request Labeler" completes
2024-06-09 14:18:02 -05:00
George Shao
4d862525bc feat(web): allow ctrl-click / cmd-click on photos (#9954)
* feat(web): allow ctrl-click / cmd-click on photos

* fix: photo opening when deselected bug

* fix: consistent naming

* remove redundant code

* fix: disabled picture is clickable in "add to album" grid

* remove unnecessary code

* cleanup

* fix file permissions

* fix: album selection bug

* fix: stack slideshow bug & search gallery viewer bug

* cleanup

* fix dark mode stack slideshow bug

* cleanup

---------

Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2024-06-08 15:33:23 -05:00
Robert Schäfer
d8d64ecc45 fix: prevent trashing of trashed assets (#10028)
* fix: prevent trashing of trashed assets

Motivation
----------
This will improve user experience by hiding a pointless action.

You can not trash a trashed asset again. It won't get any trashier than it already is.

How to test
-----------
1. Visit route `/trash`
2. Click on an asset
3. Press "Delete" on your keyboard
4. Nothing happens
5. Try to find the trash button in the top right
6. You can't find it

* refactor: follow @michelheusschen's review

See:
https://github.com/immich-app/immich/pull/10028#pullrequestreview-2105296755

* refactor:  follow @michelheusschen's 2nd review

See: https://github.com/immich-app/immich/pull/10028#discussion_r1632057833
2024-06-08 15:03:39 -05:00
Alex
e1e7de4d4c chore(mobile): translation update (#10053) 2024-06-08 19:51:36 +00:00
Mert
df412d60c5 fix(server): min face detection score not being passed correctly (#10050)
fix min score not being passed correctly
2024-06-08 13:55:19 -04:00
Alex
dbd9782763 fix(mobile): viewAsset URL string (#10044) 2024-06-08 10:50:14 +00:00
klahr
2519922dd3 fix(doc):language links updates (#10035)
* Added Swedish translation of README.

* Updated all READMEs.
2024-06-08 09:59:30 +00:00
Michel Heusschen
78b10bbcc6 fix(web): drag and drop with non english language (#10040) 2024-06-08 04:57:46 -05:00
Michel Heusschen
4ec47b4186 fix(web): storage migration description (#10041) 2024-06-08 04:57:18 -05:00
Alejandro Armas
3c5ba77e86 fix(server): server stats showing wrong quota usage (#10036)
* Add filter to exclude external libraries from the user quota usage

* Add filter to exclude external libraries from the user quota usage

* fix sql syntax
2024-06-08 04:56:11 -05:00
Mert
62f8bd80f4 fix(server): add fallback for video thumbnail generation (#10034)
they called me a madman
2024-06-08 04:55:05 -05:00
Weblate (bot)
69193598cb chore: update translations (#10038)
* chore:  (Chinese (Simplified) (zh_SIMPLIFIED))

Currently translated at 29.2% (228 of 780 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/zh_SIMPLIFIED/

* chore:  (Chinese (Simplified) (zh_SIMPLIFIED))

Currently translated at 29.2% (228 of 780 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/zh_SIMPLIFIED/

* chore:  (Chinese (Simplified) (zh_SIMPLIFIED))

Currently translated at 29.2% (228 of 780 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/zh_SIMPLIFIED/

* chore:  (Dutch)

Currently translated at 5.8% (46 of 780 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/nl/

* chore:  (Chinese (Simplified) (zh_SIMPLIFIED))

Currently translated at 51.2% (400 of 780 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/zh_SIMPLIFIED/

* chore:  (Chinese (Simplified) (zh_SIMPLIFIED))

Currently translated at 51.2% (400 of 780 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/zh_SIMPLIFIED/

* chore:  (Chinese (Simplified) (zh_SIMPLIFIED))

Currently translated at 51.2% (400 of 780 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/zh_SIMPLIFIED/

---------

Co-authored-by: PolarisYHNL <polarisyhnl@yeah.net>
Co-authored-by: LLL <326867814@qq.com>
Co-authored-by: jie65535 <jie65535@qq.com>
Co-authored-by: bo0tzz <git@bo0tzz.me>
2024-06-08 04:54:23 -05:00
Immich
f92513be7e chore: remove translations (Spanish (es_ES@new)) 2024-06-07 15:00:16 -05:00
Immich
b5e0a1cec8 chore: add translations (Spanish (es_ES@new)) 2024-06-07 15:00:04 -05:00
Daniel Dietzler
2ffde69caf fix: weblate (#10029)
* chore: add translations  (Spanish (es_ES@new))

* chore: remove translations (Spanish (es_ES@new))

---------

Co-authored-by: Immich <immich@futo.org>
2024-06-07 14:52:40 -05:00
Alex
9291c782d5 fix(web): weblate merge conflict (#10027)
* chore: add translations  (German)

* chore: add translations  (Dutch)

* chore: add translations  (Spanish)

* chore: add translations  (Spanish (es_ES@new))

* chore:  (Spanish)

Currently translated at 0.1% (1 of 769 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/es/

* chore: remove translations (Spanish (es_ES@new))

* chore: add translations  (Arabic)

* chore: add translations  (Catalan)

* chore: add translations  (Danish)

* chore: add translations  (Finnish)

* chore: add translations  (French)

* chore: add translations  (Hebrew)

* chore: add translations  (Hindi)

* chore: add translations  (Hungarian)

* chore: add translations  (Italian)

* chore: add translations  (Japanese)

* chore: add translations  (Korean)

* chore: add translations  (Lithuanian)

* chore: add translations  (Latvian)

* chore: add translations  (Mongolian)

* chore: add translations  (Norwegian Bokmål)

* chore: add translations  (Polish)

* chore: add translations  (Portuguese)

* chore: add translations  (Romanian)

* chore: add translations  (Russian)

* chore: add translations  (Slovak)

* chore: add translations  (Slovenian)

* chore: add translations  (Serbian)

* chore: add translations  (Swedish)

* chore: add translations  (Thai)

* chore: add translations  (Ukrainian)

* chore: add translations  (Vietnamese)

* chore: add translations  (Czech)

* chore: add translations  (Chinese (Simplified) (zh_SIMPLIFIED))

* chore:  (German)

Currently translated at 1.3% (10 of 769 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/de/

* chore:  (Chinese (Simplified) (zh_SIMPLIFIED))

Currently translated at 25.7% (198 of 769 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/zh_SIMPLIFIED/

* chore:  (Chinese (Simplified) (zh_SIMPLIFIED))

Currently translated at 25.7% (198 of 769 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/zh_SIMPLIFIED/

---------

Co-authored-by: Immich <immich@futo.org>
Co-authored-by: Daniel Dietzler <mail@ddietzler.dev>
Co-authored-by: Manic87 <nicolas@familie-mach.net>
Co-authored-by: PolarisYHNL <polarisyhnl@yeah.net>
Co-authored-by: LLL <326867814@qq.com>
2024-06-07 14:50:23 -05:00
Alex
b595881084 fix(web): translation (#10026) 2024-06-07 13:54:22 -05:00
Min Idzelis
4b49d3a85d feat: photo-viewer; use <img> instead of blob urls, simplify/refactor, avoid window.events (#9883)
* Photoviewer

* make copyImage/zoomToggle optional

* Add e2e test

* lint

* Accept bo0tzz suggestion

Co-authored-by: bo0tzz <git@bo0tzz.me>

* Bad merge and review comments

* unused import

---------

Co-authored-by: bo0tzz <git@bo0tzz.me>
Co-authored-by: Alex <alex.tran1502@gmail.com>
2024-06-07 13:22:46 -05:00
Michel Heusschen
def5f59242 fix(web): language setting (#10024) 2024-06-07 11:35:05 -05:00
Alex
9ac2ac2fcb feat(web): send test email button (#10011)
* feat(web): test email button

* openapi

* UI button

* Show notification

* pr feedback

* remove jobs

* send email directly from repository and add feedback

* avoid sending many emails

* linter

* pr feedback

* lint

* lint

* lint
2024-06-07 11:34:09 -05:00
Michel Heusschen
d5f3d98dfc chore(web): use development lang for tests (#10025) 2024-06-07 16:12:39 +01:00
Zack Pollard
3e118793de chore(web): missing notification settings translations (#10022)
* chore: missing notification settings translations

* chore: admin library tasks description translation
2024-06-07 10:01:41 -05:00
Michel Heusschen
c8f2d994c6 fix(web): translations (#10021) 2024-06-07 12:23:13 +01:00
Mert
f2148ddf03 fix(server): video thumbnail colors when using webp (#10018)
use gbrpf32le
2024-06-07 02:43:10 -04:00
Mert
2b1b43a7e4 feat(ml): composable ml (#9973)
* modularize model classes

* various fixes

* expose port

* change response

* round coordinates

* simplify preload

* update server

* simplify interface

simplify

* update tests

* composable endpoint

* cleanup

fixes

remove unnecessary interface

support text input, cleanup

* ew camelcase

* update server

server fixes

fix typing

* ml fixes

update locustfile

fixes

* cleaner response

* better repo response

* update tests

formatting and typing

rename

* undo compose change

* linting

fix type

actually fix typing

* stricter typing

fix detection-only response

no need for defaultdict

* update spec file

update api

linting

* update e2e

* unnecessary dimension

* remove commented code

* remove duplicate code

* remove unused imports

* add batch dim
2024-06-07 03:09:47 +00:00
Snowknight26
7a46f80ddc feat(web): add archive shortcut to grid (#9499)
* feat(web): add archive shortcut to grid

* Fix error

* Don't unnecessarily pass parameter

* Use an existing function to close the menu

* Deduplicate type

---------

Co-authored-by: Alex <alex.tran1502@gmail.com>
Co-authored-by: Jason Rasmussen <jrasm91@gmail.com>
2024-06-06 19:23:49 -04:00
Weblate (bot)
c6c480c882 chore: update translations (#10003)
* chore: add translations  (German)

* chore: add translations  (Dutch)

* chore: add translations  (Spanish)

* chore: add translations  (Spanish (es_ES@new))

* chore:  (Spanish)

Currently translated at 0.1% (1 of 769 strings)

Translation: Immich/immich
Translate-URL: https://hosted.weblate.org/projects/immich/immich/es/

* chore: remove translations (Spanish (es_ES@new))

* chore: add translations  (Arabic)

* chore: add translations  (Catalan)

* chore: add translations  (Danish)

* chore: add translations  (Finnish)

* chore: add translations  (French)

* chore: add translations  (Hebrew)

* chore: add translations  (Hindi)

* chore: add translations  (Hungarian)

* chore: add translations  (Italian)

* chore: add translations  (Japanese)

* chore: add translations  (Korean)

* chore: add translations  (Lithuanian)

* chore: add translations  (Latvian)

* chore: add translations  (Mongolian)

* chore: add translations  (Norwegian Bokmål)

* chore: add translations  (Polish)

* chore: add translations  (Portuguese)

* chore: add translations  (Romanian)

* chore: add translations  (Russian)

* chore: add translations  (Slovak)

* chore: add translations  (Slovenian)

* chore: add translations  (Serbian)

* chore: add translations  (Swedish)

* chore: add translations  (Thai)

* chore: add translations  (Ukrainian)

* chore: add translations  (Vietnamese)

* chore: add translations  (Czech)

* chore: add translations  (Chinese (Simplified) (zh_SIMPLIFIED))

* chore: add base languages

---------

Co-authored-by: Immich <immich@futo.org>
Co-authored-by: Daniel Dietzler <mail@ddietzler.dev>
Co-authored-by: Jason Rasmussen <jrasm91@gmail.com>
2024-06-06 17:20:41 +00:00
Jason Rasmussen
4ad97ccded fix(server): closed connections (#10013) 2024-06-06 09:09:42 -05:00
renovate[bot]
ca12f3b15f chore(deps): update grafana/grafana:11.0.0-ubuntu docker digest to dcd3ae7 (#9770)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-06 13:15:39 +01:00
renovate[bot]
86eb2525d7 chore(deps): update redis:6.2-alpine docker digest to d6c2911 (#9843)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-06 13:13:39 +01:00
renovate[bot]
079864dfbe chore(deps): update docker.io/redis:6.2-alpine docker digest to d6c2911 (#9842)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-06 13:13:15 +01:00
Michel Heusschen
051e6cfc0b fix(web): clear locale setting (#10008) 2024-06-06 07:47:22 -04:00
Zack Pollard
8f42766979 feat: seperate sub-process for api worker (#10000) 2024-06-06 11:56:57 +01:00
renovate[bot]
7e2a03a8d9 chore(deps): update base-image to v20240604 (major) (#10004)
chore(deps): update base-image to v20240604

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-05 19:20:36 -04:00
Daniel Dietzler
1947316b0b refactor: one locales file for all english translations (#10006)
one locales file for all english translations
2024-06-05 18:57:44 -04:00
Jason Rasmussen
0f976edf96 feat(server): log http exceptions (#9996) 2024-06-05 17:07:47 -04:00
Min Idzelis
ce985ef8f8 fix: AssetInterceptor "can't set headers after they are sent" (#9987)
* fix: AssetInterceptor "can't set headers after they are sent"

* chore: remove long comment

---------

Co-authored-by: Jason Rasmussen <jrasm91@gmail.com>
2024-06-05 09:30:19 -04:00
Jason Rasmussen
cf223dc98c fix(web): show duplicate message (#9992) 2024-06-05 09:29:52 -04:00
Snowknight26
97ffddee7c feat(web): add an empty placeholder to the explore page (#9990)
* feat(web): add an empty placeholder to the explore page

* Change the message wording per suggestion

* fix: test

---------

Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2024-06-05 12:19:57 +00:00
Zack Pollard
abf6fc25f7 chore: change default thumbnail concurrency and auto-detect container core count (#9981)
* feat: automatically detect amount of CPU cores and allow overriding with CPU_CORES env var

* chore: change default thumbnail concurrency to 3
2024-06-05 11:45:53 +01:00
Jan108
b2761b12d1 feat(web): Option to assign people to unassigned faces (#9773)
* added unassigned faces to people edit

* svelte fix

* fix format

* Captialized unassigned person name, removed person id from alttext, fixed problem with multiple faces per person

* Added faces to the getAssetInfo API endpoint

* Updated openApi clients

* Readded the photoeditor dependency

* fixed lint/format

* fixed photoViewer type

* changes getAssetInfo.faces to only include unassigned faces

* fix: bad merge

* title

* logic

---------

Co-authored-by: Jan108 <dasJan108@gmail.com>
Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2024-06-05 07:26:00 +00:00
722 changed files with 61013 additions and 10270 deletions

View File

@@ -4,6 +4,7 @@
design/
docker/
!docker/scripts
docs/
e2e/
fastlane/

View File

@@ -1,11 +1,11 @@
blank_issues_enabled: false
contact_links:
- name: I have a question or need support
url: https://discord.gg/D8JsnBEuKb
url: https://discord.immich.app
about: We use GitHub for tracking bugs, please check out our Discord channel for freaky fast support.
- name: Feature Request
url: https://github.com/immich-app/immich/discussions/new?category=feature-request
about: Please use our GitHub Discussion for making feature requests.
- name: I'm unsure where to go
url: https://discord.gg/D8JsnBEuKb
url: https://discord.immich.app
about: If you are unsure where to go, then joining our Discord is recommended; Just ask!

35
.github/labeler.yml vendored Normal file
View File

@@ -0,0 +1,35 @@
cli:
- changed-files:
- any-glob-to-any-file:
- cli/src/**
documentation:
- changed-files:
- any-glob-to-any-file:
- docs/blob/**
- docs/docs/**
- docs/src/**
- docs/static/**
🖥web:
- changed-files:
- any-glob-to-any-file:
- web/src/**
- web/static/**
📱mobile:
- changed-files:
- any-glob-to-any-file:
- mobile/lib/**
- mobile/test/**
🗄server:
- changed-files:
- any-glob-to-any-file:
- server/src/**
- server/test/**
🧠machine-learning:
- changed-files:
- any-glob-to-any-file:
- machine-learning/app/**

View File

@@ -1,16 +1,17 @@
name: CLI Build
on:
workflow_dispatch:
push:
branches: [main]
paths:
- "cli/**"
- ".github/workflows/cli.yml"
- 'cli/**'
- '.github/workflows/cli.yml'
pull_request:
branches: [main]
paths:
- "cli/**"
- ".github/workflows/cli.yml"
- 'cli/**'
- '.github/workflows/cli.yml'
release:
types: [published]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
@@ -32,8 +33,8 @@ jobs:
# Setup .npmrc file to publish to npm
- uses: actions/setup-node@v4
with:
node-version: "20.x"
registry-url: "https://registry.npmjs.org"
node-version-file: './cli/.nvmrc'
registry-url: 'https://registry.npmjs.org'
- name: Prepare SDK
run: npm ci --prefix ../open-api/typescript-sdk/
- name: Build SDK
@@ -41,7 +42,7 @@ jobs:
- run: npm ci
- run: npm run build
- run: npm publish
if: ${{ github.event_name == 'workflow_dispatch' }}
if: ${{ github.event_name == 'release' }}
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
@@ -83,15 +84,15 @@ jobs:
images: |
name=ghcr.io/${{ github.repository_owner }}/immich-cli
tags: |
type=raw,value=${{ steps.package-version.outputs.version }},enable=${{ github.event_name == 'workflow_dispatch' }}
type=raw,value=latest,enable=${{ github.event_name == 'workflow_dispatch' }}
type=raw,value=${{ steps.package-version.outputs.version }},enable=${{ github.event_name == 'release' }}
type=raw,value=latest,enable=${{ github.event_name == 'release' }}
- name: Build and push image
uses: docker/build-push-action@v5.3.0
uses: docker/build-push-action@v6.2.0
with:
file: cli/Dockerfile
platforms: linux/amd64,linux/arm64
push: ${{ github.event_name == 'workflow_dispatch' }}
push: ${{ github.event_name == 'release' }}
cache-from: type=gha
cache-to: type=gha,mode=max
tags: ${{ steps.metadata.outputs.tags }}

View File

@@ -115,7 +115,7 @@ jobs:
fi
- name: Build and push image
uses: docker/build-push-action@v5.3.0
uses: docker/build-push-action@v6.2.0
with:
context: ${{ matrix.context }}
file: ${{ matrix.file }}
@@ -124,7 +124,11 @@ jobs:
push: ${{ !github.event.pull_request.head.repo.fork }}
cache-from: type=registry,ref=ghcr.io/${{ github.repository_owner }}/immich-build-cache:${{matrix.image}}
cache-to: ${{ steps.cache-target.outputs.cache-to }}
build-args: |
DEVICE=${{ matrix.device }}
tags: ${{ steps.metadata.outputs.tags }}
labels: ${{ steps.metadata.outputs.labels }}
build-args: |
DEVICE=${{ matrix.device }}
BUILD_ID=${{ github.run_id }}
BUILD_IMAGE=${{ github.event_name == 'release' && github.ref_name || steps.metadata.outputs.tags }}
BUILD_SOURCE_REF=${{ github.ref_name }}
BUILD_SOURCE_COMMIT=${{ github.sha }}

View File

@@ -26,6 +26,11 @@ jobs:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version-file: './docs/.nvmrc'
- name: Run npm install
run: npm ci

12
.github/workflows/pr-labeler.yml vendored Normal file
View File

@@ -0,0 +1,12 @@
name: "Pull Request Labeler"
on:
- pull_request_target
jobs:
labeler:
permissions:
contents: read
pull-requests: write
runs-on: ubuntu-latest
steps:
- uses: actions/labeler@v5

View File

@@ -1,13 +0,0 @@
name: Enforce PR labels
on:
pull_request:
types: [labeled, unlabeled, opened, edited, synchronize]
jobs:
enforce-label:
name: Enforce label
runs-on: ubuntu-latest
steps:
- if: toJson(github.event.pull_request.labels) == '[]'
run: exit 1

View File

@@ -19,7 +19,7 @@ jobs:
# Setup .npmrc file to publish to npm
- uses: actions/setup-node@v4
with:
node-version: '20.x'
node-version-file: './open-api/typescript-sdk/.nvmrc'
registry-url: 'https://registry.npmjs.org'
- name: Install deps
run: npm ci

View File

@@ -21,6 +21,11 @@ jobs:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version-file: './server/.nvmrc'
- name: Run npm install
run: npm ci
@@ -54,7 +59,7 @@ jobs:
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 20
node-version-file: './cli/.nvmrc'
- name: Setup typescript-sdk
run: npm ci && npm run build
@@ -79,6 +84,38 @@ jobs:
run: npm run test:cov
if: ${{ !cancelled() }}
cli-unit-tests-win:
name: CLI (Windows)
runs-on: windows-latest
defaults:
run:
working-directory: ./cli
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version-file: './cli/.nvmrc'
- name: Setup typescript-sdk
run: npm ci && npm run build
working-directory: ./open-api/typescript-sdk
- name: Install deps
run: npm ci
# Skip linter & formatter in Windows test.
- name: Run tsc
run: npm run check
if: ${{ !cancelled() }}
- name: Run unit tests & coverage
run: npm run test:cov
if: ${{ !cancelled() }}
web-unit-tests:
name: Web
runs-on: ubuntu-latest
@@ -90,6 +127,11 @@ jobs:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version-file: './web/.nvmrc'
- name: Run setup typescript-sdk
run: npm ci && npm run build
working-directory: ./open-api/typescript-sdk
@@ -133,7 +175,7 @@ jobs:
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 20
node-version-file: './e2e/.nvmrc'
- name: Run setup typescript-sdk
run: npm ci && npm run build
@@ -241,6 +283,11 @@ jobs:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version-file: './server/.nvmrc'
- name: Install server dependencies
run: npm --prefix=server ci
@@ -291,6 +338,11 @@ jobs:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version-file: './server/.nvmrc'
- name: Install server dependencies
run: npm ci

View File

@@ -10,12 +10,6 @@ dev-update:
dev-scale:
docker compose -f ./docker/docker-compose.dev.yml up --build -V --scale immich-server=3 --remove-orphans
stage:
docker compose -f ./docker/docker-compose.staging.yml up --build -V --remove-orphans
pull-stage:
docker compose -f ./docker/docker-compose.staging.yml pull
.PHONY: e2e
e2e:
docker compose -f ./e2e/docker-compose.yml up --build -V --remove-orphans
@@ -41,3 +35,51 @@ sql:
attach-server:
docker exec -it docker_immich-server_1 sh
renovate:
LOG_LEVEL=debug npx renovate --platform=local --repository-cache=reset
MODULES = e2e server web cli sdk
audit-%:
npm --prefix $(subst sdk,open-api/typescript-sdk,$*) audit fix
install-%:
npm --prefix $(subst sdk,open-api/typescript-sdk,$*) i
build-cli: build-sdk
build-web: build-sdk
build-%: install-%
npm --prefix $(subst sdk,open-api/typescript-sdk,$*) run | grep 'build' >/dev/null \
&& npm --prefix $(subst sdk,open-api/typescript-sdk,$*) run build || true
format-%:
npm --prefix $(subst sdk,open-api/typescript-sdk,$*) run | grep 'format:fix' >/dev/null \
&& npm --prefix $(subst sdk,open-api/typescript-sdk,$*) run format:fix || true
lint-%:
npm --prefix $* run lint:fix
check-%:
npm --prefix $* run check
check-web:
npm --prefix web run check:typescript
npm --prefix web run check:svelte
test-%:
npm --prefix $* run test
test-e2e:
docker compose -f ./e2e/docker-compose.yml build
npm --prefix e2e run test
npm --prefix e2e run test:web
build-all: $(foreach M,$(MODULES),build-$M) ;
install-all: $(foreach M,$(MODULES),install-$M) ;
check-all: $(foreach M,$(MODULES),check-$M) ;
lint-all: $(foreach M,$(MODULES),lint-$M) ;
format-all: $(foreach M,$(MODULES),format-$M) ;
audit-all: $(foreach M,$(MODULES),audit-$M) ;
hygiene-all: lint-all format-all check-all sql audit-all;
test-all: $(foreach M,$(MODULES),test-$M) ;
clean:
find . -name "node_modules" -type d -prune -exec rm -rf '{}' +
find . -name "dist" -type d -prune -exec rm -rf '{}' +
find . -name "build" -type d -prune -exec rm -rf '{}' +
find . -name "svelte-kit" -type d -prune -exec rm -rf '{}' +
docker compose -f ./docker/docker-compose.dev.yml rm -v -f || true
docker compose -f ./e2e/docker-compose.yml rm -v -f || true

View File

@@ -1,7 +1,7 @@
<p align="center">
<br/>
<a href="https://opensource.org/license/agpl-v3"><img src="https://img.shields.io/badge/License-AGPL_v3-blue.svg?color=3F51B5&style=for-the-badge&label=License&logoColor=000000&labelColor=ececec" alt="License: AGPLv3"></a>
<a href="https://discord.gg/D8JsnBEuKb">
<a href="https://discord.immich.app">
<img src="https://img.shields.io/discord/979116623879368755.svg?label=Discord&logo=Discord&style=for-the-badge&logoColor=000000&labelColor=ececec" alt="Discord"/>
</a>
<br/>
@@ -19,19 +19,21 @@
<br/>
<p align="center">
<a href="readme_i18n/README_ca_ES.md">Català</a>
<a href="readme_i18n/README_es_ES.md">Español</a>
<a href="readme_i18n/README_fr_FR.md">Français</a>
<a href="readme_i18n/README_it_IT.md">Italiano</a>
<a href="readme_i18n/README_ja_JP.md">日本語</a>
<a href="readme_i18n/README_ko_KR.md">한국어</a>
<a href="readme_i18n/README_de_DE.md">Deutsch</a>
<a href="readme_i18n/README_nl_NL.md">Nederlands</a>
<a href="readme_i18n/README_tr_TR.md">Türkçe</a>
<a href="readme_i18n/README_zh_CN.md">中文</a>
<a href="readme_i18n/README_ru_RU.md">Русский</a>
<a href="readme_i18n/README_pt_BR.md">Português Brasileiro</a>
<a href="readme_i18n/README_ar_JO.md">العربية</a>
<a href="readme_i18n/README_ca_ES.md">Català</a>
<a href="readme_i18n/README_es_ES.md">Español</a>
<a href="readme_i18n/README_fr_FR.md">Français</a>
<a href="readme_i18n/README_it_IT.md">Italiano</a>
<a href="readme_i18n/README_ja_JP.md">日本語</a>
<a href="readme_i18n/README_ko_KR.md">한국어</a>
<a href="readme_i18n/README_de_DE.md">Deutsch</a>
<a href="readme_i18n/README_nl_NL.md">Nederlands</a>
<a href="readme_i18n/README_tr_TR.md">Türkçe</a>
<a href="readme_i18n/README_zh_CN.md">中文</a>
<a href="readme_i18n/README_ru_RU.md">Русский</a>
<a href="readme_i18n/README_pt_BR.md">Português Brasileiro</a>
<a href="readme_i18n/README_sv_SE.md">Svenska</a>
<a href="readme_i18n/README_ar_JO.md">العربية</a>
</p>
## Disclaimer
@@ -41,45 +43,36 @@
- ⚠️ **Do not use the app as the only way to store your photos and videos.**
- ⚠️ Always follow [3-2-1](https://www.backblaze.com/blog/the-3-2-1-backup-strategy/) backup plan for your precious photos and videos!
## Content
> [!NOTE]
> You can find the main documentation, including installation guides, at https://immich.app/.
- [Official Documentation](https://immich.app/docs)
- [Roadmap](https://github.com/orgs/immich-app/projects/1)
## Links
- [Documentation](https://immich.app/docs)
- [About](https://immich.app/docs/overview/introduction)
- [Installation](https://immich.app/docs/install/requirements)
- [Roadmap](https://immich.app/roadmap)
- [Demo](#demo)
- [Features](#features)
- [Introduction](https://immich.app/docs/overview/introduction)
- [Installation](https://immich.app/docs/install/requirements)
- [Contribution Guidelines](https://immich.app/docs/overview/support-the-project)
## Documentation
You can find the main documentation, including installation guides, at https://immich.app/.
- [Translations](https://immich.app/docs/developer/translations)
- [Contributing](https://immich.app/docs/overview/support-the-project)
## Demo
You can access the web demo at https://demo.immich.app
Access the demo [here](https://demo.immich.app). The demo is running on a Free-tier Oracle VM in Amsterdam with a 2.4Ghz quad-core ARM64 CPU and 24GB RAM.
For the mobile app, you can use `https://demo.immich.app/api` for the `Server Endpoint URL`
```bash title="Demo Credential"
The credential
email: demo@immich.app
password: demo
```
### Login credentials
```
Spec: Free-tier Oracle VM - Amsterdam - 2.4Ghz quad-core ARM64 CPU, 24GB RAM
```
## Activities
![Activities](https://repobeats.axiom.co/api/embed/9e86d9dc3ddd137161f2f6d2e758d7863b1789cb.svg "Repobeats analytics image")
| Email | Password |
| --------------- | -------- |
| demo@immich.app | demo |
## Features
| Features | Mobile | Web |
| :--------------------------------------------- | -------- | ----- |
| :------------------------------------------- | ------ | --- |
| Upload and view videos and photos | Yes | Yes |
| Auto backup when the app is opened | Yes | N/A |
| Prevent duplication of assets | Yes | Yes |
@@ -109,13 +102,19 @@ Spec: Free-tier Oracle VM - Amsterdam - 2.4Ghz quad-core ARM64 CPU, 24GB RAM
| Read-only gallery | Yes | Yes |
| Stacked Photos | Yes | Yes |
## Contributors
## Translations
<a href="https://github.com/alextran1502/immich/graphs/contributors">
<img src="https://contrib.rocks/image?repo=immich-app/immich" width="100%"/>
Read more about translations [here](https://immich.app/docs/developer/translations).
<a href="https://hosted.weblate.org/engage/immich/">
<img src="https://hosted.weblate.org/widget/immich/immich/multi-auto.svg" alt="Translation status" />
</a>
## Star History
## Repository activity
![Activities](https://repobeats.axiom.co/api/embed/9e86d9dc3ddd137161f2f6d2e758d7863b1789cb.svg "Repobeats analytics image")
## Star history
<a href="https://star-history.com/#immich-app/immich&Date">
<picture>
@@ -124,3 +123,9 @@ Spec: Free-tier Oracle VM - Amsterdam - 2.4Ghz quad-core ARM64 CPU, 24GB RAM
<img alt="Star History Chart" src="https://api.star-history.com/svg?repos=immich-app/immich&type=Date" width="100%" />
</picture>
</a>
## Contributors
<a href="https://github.com/alextran1502/immich/graphs/contributors">
<img src="https://contrib.rocks/image?repo=immich-app/immich" width="100%"/>
</a>

View File

@@ -2,4 +2,4 @@
## Reporting a Vulnerability
Please report security issues to `alex.tran1502@gmail.com`
Please report security issues to `security@immich.app`

View File

@@ -1 +1 @@
20.14
20.15

View File

@@ -1,4 +1,4 @@
FROM node:20-alpine3.19@sha256:696ae41fb5880949a15ade7879a2deae93b3f0723f757bdb5b8a9e4a744ce27f as core
FROM node:20.15.0-alpine3.20@sha256:df01469346db2bf1cfc1f7261aeab86b2960efa840fe2bd46d83ff339f463665 as core
WORKDIR /usr/src/open-api/typescript-sdk
COPY open-api/typescript-sdk/package*.json open-api/typescript-sdk/tsconfig*.json ./
@@ -16,4 +16,4 @@ RUN npm run build
WORKDIR /import
ENTRYPOINT ["node", "/usr/src/app/dist"]
ENTRYPOINT ["node", "/usr/src/app/dist"]

385
cli/package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "@immich/cli",
"version": "2.2.0",
"version": "2.2.7",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@immich/cli",
"version": "2.2.0",
"version": "2.2.7",
"license": "GNU Affero General Public License version 3",
"dependencies": {
"fast-glob": "^3.3.2",
@@ -21,7 +21,7 @@
"@types/cli-progress": "^3.11.0",
"@types/lodash-es": "^4.17.12",
"@types/mock-fs": "^4.13.1",
"@types/node": "^20.3.1",
"@types/node": "^20.14.9",
"@typescript-eslint/eslint-plugin": "^7.0.0",
"@typescript-eslint/parser": "^7.0.0",
"@vitest/coverage-v8": "^1.2.2",
@@ -31,7 +31,7 @@
"eslint": "^8.56.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-prettier": "^5.1.3",
"eslint-plugin-unicorn": "^53.0.0",
"eslint-plugin-unicorn": "^54.0.0",
"mock-fs": "^5.2.0",
"prettier": "^3.2.5",
"prettier-plugin-organize-imports": "^3.2.4",
@@ -47,14 +47,14 @@
},
"../open-api/typescript-sdk": {
"name": "@immich/sdk",
"version": "1.105.1",
"version": "1.107.2",
"dev": true,
"license": "GNU Affero General Public License version 3",
"dependencies": {
"@oazapfts/runtime": "^1.0.2"
},
"devDependencies": {
"@types/node": "^20.12.13",
"@types/node": "^20.14.9",
"typescript": "^5.3.3"
}
},
@@ -300,13 +300,14 @@
"dev": true
},
"node_modules/@esbuild/aix-ppc64": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz",
"integrity": "sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==",
"version": "0.21.5",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz",
"integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==",
"cpu": [
"ppc64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"aix"
@@ -316,13 +317,14 @@
}
},
"node_modules/@esbuild/android-arm": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.20.2.tgz",
"integrity": "sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==",
"version": "0.21.5",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz",
"integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==",
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
@@ -332,13 +334,14 @@
}
},
"node_modules/@esbuild/android-arm64": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.20.2.tgz",
"integrity": "sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==",
"version": "0.21.5",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz",
"integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
@@ -348,13 +351,14 @@
}
},
"node_modules/@esbuild/android-x64": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.20.2.tgz",
"integrity": "sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==",
"version": "0.21.5",
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz",
"integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
@@ -364,13 +368,14 @@
}
},
"node_modules/@esbuild/darwin-arm64": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.20.2.tgz",
"integrity": "sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==",
"version": "0.21.5",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz",
"integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
@@ -380,13 +385,14 @@
}
},
"node_modules/@esbuild/darwin-x64": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.20.2.tgz",
"integrity": "sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==",
"version": "0.21.5",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz",
"integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
@@ -396,13 +402,14 @@
}
},
"node_modules/@esbuild/freebsd-arm64": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.2.tgz",
"integrity": "sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==",
"version": "0.21.5",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz",
"integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"freebsd"
@@ -412,13 +419,14 @@
}
},
"node_modules/@esbuild/freebsd-x64": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.20.2.tgz",
"integrity": "sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==",
"version": "0.21.5",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz",
"integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"freebsd"
@@ -428,13 +436,14 @@
}
},
"node_modules/@esbuild/linux-arm": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.20.2.tgz",
"integrity": "sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==",
"version": "0.21.5",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz",
"integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==",
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
@@ -444,13 +453,14 @@
}
},
"node_modules/@esbuild/linux-arm64": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.20.2.tgz",
"integrity": "sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==",
"version": "0.21.5",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz",
"integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
@@ -460,13 +470,14 @@
}
},
"node_modules/@esbuild/linux-ia32": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.20.2.tgz",
"integrity": "sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==",
"version": "0.21.5",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz",
"integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==",
"cpu": [
"ia32"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
@@ -476,13 +487,14 @@
}
},
"node_modules/@esbuild/linux-loong64": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.20.2.tgz",
"integrity": "sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==",
"version": "0.21.5",
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz",
"integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==",
"cpu": [
"loong64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
@@ -492,13 +504,14 @@
}
},
"node_modules/@esbuild/linux-mips64el": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.20.2.tgz",
"integrity": "sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==",
"version": "0.21.5",
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz",
"integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==",
"cpu": [
"mips64el"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
@@ -508,13 +521,14 @@
}
},
"node_modules/@esbuild/linux-ppc64": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.20.2.tgz",
"integrity": "sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==",
"version": "0.21.5",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz",
"integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==",
"cpu": [
"ppc64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
@@ -524,13 +538,14 @@
}
},
"node_modules/@esbuild/linux-riscv64": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.20.2.tgz",
"integrity": "sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==",
"version": "0.21.5",
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz",
"integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==",
"cpu": [
"riscv64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
@@ -540,13 +555,14 @@
}
},
"node_modules/@esbuild/linux-s390x": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.20.2.tgz",
"integrity": "sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==",
"version": "0.21.5",
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz",
"integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==",
"cpu": [
"s390x"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
@@ -556,13 +572,14 @@
}
},
"node_modules/@esbuild/linux-x64": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.20.2.tgz",
"integrity": "sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==",
"version": "0.21.5",
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz",
"integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
@@ -572,13 +589,14 @@
}
},
"node_modules/@esbuild/netbsd-x64": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.20.2.tgz",
"integrity": "sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==",
"version": "0.21.5",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz",
"integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"netbsd"
@@ -588,13 +606,14 @@
}
},
"node_modules/@esbuild/openbsd-x64": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.20.2.tgz",
"integrity": "sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==",
"version": "0.21.5",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz",
"integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"openbsd"
@@ -604,13 +623,14 @@
}
},
"node_modules/@esbuild/sunos-x64": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.20.2.tgz",
"integrity": "sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==",
"version": "0.21.5",
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz",
"integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"sunos"
@@ -620,13 +640,14 @@
}
},
"node_modules/@esbuild/win32-arm64": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.20.2.tgz",
"integrity": "sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==",
"version": "0.21.5",
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz",
"integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
@@ -636,13 +657,14 @@
}
},
"node_modules/@esbuild/win32-ia32": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.20.2.tgz",
"integrity": "sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==",
"version": "0.21.5",
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz",
"integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==",
"cpu": [
"ia32"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
@@ -652,13 +674,14 @@
}
},
"node_modules/@esbuild/win32-x64": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.20.2.tgz",
"integrity": "sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==",
"version": "0.21.5",
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz",
"integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
@@ -1138,9 +1161,9 @@
}
},
"node_modules/@types/node": {
"version": "20.12.13",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.13.tgz",
"integrity": "sha512-gBGeanV41c1L171rR7wjbMiEpEI/l5XFQdLLfhr/REwpgDy/4U8y89+i8kRiLzDyZdOkXh+cRaTetUnCYutoXA==",
"version": "20.14.9",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.9.tgz",
"integrity": "sha512-06OCtnTXtWOZBJlRApleWndH4JsRVs1pDCc8dLSQp+7PpUpX3ePdHyeNSFTeSe7FtKyQkrlPvHwJOW3SLd8Oyg==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -1154,17 +1177,17 @@
"dev": true
},
"node_modules/@typescript-eslint/eslint-plugin": {
"version": "7.11.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.11.0.tgz",
"integrity": "sha512-P+qEahbgeHW4JQ/87FuItjBj8O3MYv5gELDzr8QaQ7fsll1gSMTYb6j87MYyxwf3DtD7uGFB9ShwgmCJB5KmaQ==",
"version": "7.14.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.14.1.tgz",
"integrity": "sha512-aAJd6bIf2vvQRjUG3ZkNXkmBpN+J7Wd0mfQiiVCJMu9Z5GcZZdcc0j8XwN/BM97Fl7e3SkTXODSk4VehUv7CGw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/regexpp": "^4.10.0",
"@typescript-eslint/scope-manager": "7.11.0",
"@typescript-eslint/type-utils": "7.11.0",
"@typescript-eslint/utils": "7.11.0",
"@typescript-eslint/visitor-keys": "7.11.0",
"@typescript-eslint/scope-manager": "7.14.1",
"@typescript-eslint/type-utils": "7.14.1",
"@typescript-eslint/utils": "7.14.1",
"@typescript-eslint/visitor-keys": "7.14.1",
"graphemer": "^1.4.0",
"ignore": "^5.3.1",
"natural-compare": "^1.4.0",
@@ -1188,16 +1211,16 @@
}
},
"node_modules/@typescript-eslint/parser": {
"version": "7.11.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.11.0.tgz",
"integrity": "sha512-yimw99teuaXVWsBcPO1Ais02kwJ1jmNA1KxE7ng0aT7ndr1pT1wqj0OJnsYVGKKlc4QJai86l/025L6z8CljOg==",
"version": "7.14.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.14.1.tgz",
"integrity": "sha512-8lKUOebNLcR0D7RvlcloOacTOWzOqemWEWkKSVpMZVF/XVcwjPR+3MD08QzbW9TCGJ+DwIc6zUSGZ9vd8cO1IA==",
"dev": true,
"license": "BSD-2-Clause",
"dependencies": {
"@typescript-eslint/scope-manager": "7.11.0",
"@typescript-eslint/types": "7.11.0",
"@typescript-eslint/typescript-estree": "7.11.0",
"@typescript-eslint/visitor-keys": "7.11.0",
"@typescript-eslint/scope-manager": "7.14.1",
"@typescript-eslint/types": "7.14.1",
"@typescript-eslint/typescript-estree": "7.14.1",
"@typescript-eslint/visitor-keys": "7.14.1",
"debug": "^4.3.4"
},
"engines": {
@@ -1217,14 +1240,14 @@
}
},
"node_modules/@typescript-eslint/scope-manager": {
"version": "7.11.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.11.0.tgz",
"integrity": "sha512-27tGdVEiutD4POirLZX4YzT180vevUURJl4wJGmm6TrQoiYwuxTIY98PBp6L2oN+JQxzE0URvYlzJaBHIekXAw==",
"version": "7.14.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.14.1.tgz",
"integrity": "sha512-gPrFSsoYcsffYXTOZ+hT7fyJr95rdVe4kGVX1ps/dJ+DfmlnjFN/GcMxXcVkeHDKqsq6uAcVaQaIi3cFffmAbA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/types": "7.11.0",
"@typescript-eslint/visitor-keys": "7.11.0"
"@typescript-eslint/types": "7.14.1",
"@typescript-eslint/visitor-keys": "7.14.1"
},
"engines": {
"node": "^18.18.0 || >=20.0.0"
@@ -1235,14 +1258,14 @@
}
},
"node_modules/@typescript-eslint/type-utils": {
"version": "7.11.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.11.0.tgz",
"integrity": "sha512-WmppUEgYy+y1NTseNMJ6mCFxt03/7jTOy08bcg7bxJJdsM4nuhnchyBbE8vryveaJUf62noH7LodPSo5Z0WUCg==",
"version": "7.14.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.14.1.tgz",
"integrity": "sha512-/MzmgNd3nnbDbOi3LfasXWWe292+iuo+umJ0bCCMCPc1jLO/z2BQmWUUUXvXLbrQey/JgzdF/OV+I5bzEGwJkQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/typescript-estree": "7.11.0",
"@typescript-eslint/utils": "7.11.0",
"@typescript-eslint/typescript-estree": "7.14.1",
"@typescript-eslint/utils": "7.14.1",
"debug": "^4.3.4",
"ts-api-utils": "^1.3.0"
},
@@ -1263,9 +1286,9 @@
}
},
"node_modules/@typescript-eslint/types": {
"version": "7.11.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.11.0.tgz",
"integrity": "sha512-MPEsDRZTyCiXkD4vd3zywDCifi7tatc4K37KqTprCvaXptP7Xlpdw0NR2hRJTetG5TxbWDB79Ys4kLmHliEo/w==",
"version": "7.14.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.14.1.tgz",
"integrity": "sha512-mL7zNEOQybo5R3AavY+Am7KLv8BorIv7HCYS5rKoNZKQD9tsfGUpO4KdAn3sSUvTiS4PQkr2+K0KJbxj8H9NDg==",
"dev": true,
"license": "MIT",
"engines": {
@@ -1277,14 +1300,14 @@
}
},
"node_modules/@typescript-eslint/typescript-estree": {
"version": "7.11.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.11.0.tgz",
"integrity": "sha512-cxkhZ2C/iyi3/6U9EPc5y+a6csqHItndvN/CzbNXTNrsC3/ASoYQZEt9uMaEp+xFNjasqQyszp5TumAVKKvJeQ==",
"version": "7.14.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.14.1.tgz",
"integrity": "sha512-k5d0VuxViE2ulIO6FbxxSZaxqDVUyMbXcidC8rHvii0I56XZPv8cq+EhMns+d/EVIL41sMXqRbK3D10Oza1bbA==",
"dev": true,
"license": "BSD-2-Clause",
"dependencies": {
"@typescript-eslint/types": "7.11.0",
"@typescript-eslint/visitor-keys": "7.11.0",
"@typescript-eslint/types": "7.14.1",
"@typescript-eslint/visitor-keys": "7.14.1",
"debug": "^4.3.4",
"globby": "^11.1.0",
"is-glob": "^4.0.3",
@@ -1306,16 +1329,16 @@
}
},
"node_modules/@typescript-eslint/utils": {
"version": "7.11.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.11.0.tgz",
"integrity": "sha512-xlAWwPleNRHwF37AhrZurOxA1wyXowW4PqVXZVUNCLjB48CqdPJoJWkrpH2nij9Q3Lb7rtWindtoXwxjxlKKCA==",
"version": "7.14.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.14.1.tgz",
"integrity": "sha512-CMmVVELns3nak3cpJhZosDkm63n+DwBlDX8g0k4QUa9BMnF+lH2lr3d130M1Zt1xxmB3LLk3NV7KQCq86ZBBhQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/eslint-utils": "^4.4.0",
"@typescript-eslint/scope-manager": "7.11.0",
"@typescript-eslint/types": "7.11.0",
"@typescript-eslint/typescript-estree": "7.11.0"
"@typescript-eslint/scope-manager": "7.14.1",
"@typescript-eslint/types": "7.14.1",
"@typescript-eslint/typescript-estree": "7.14.1"
},
"engines": {
"node": "^18.18.0 || >=20.0.0"
@@ -1329,13 +1352,13 @@
}
},
"node_modules/@typescript-eslint/visitor-keys": {
"version": "7.11.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.11.0.tgz",
"integrity": "sha512-7syYk4MzjxTEk0g/w3iqtgxnFQspDJfn6QKD36xMuuhTzjcxY7F8EmBLnALjVyaOF1/bVocu3bS/2/F7rXrveQ==",
"version": "7.14.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.14.1.tgz",
"integrity": "sha512-Crb+F75U1JAEtBeQGxSKwI60hZmmzaqA3z9sYsVm8X7W5cwLEm5bRe0/uXS6+MR/y8CVpKSR/ontIAIEPFcEkA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/types": "7.11.0",
"@typescript-eslint/types": "7.14.1",
"eslint-visitor-keys": "^3.4.3"
},
"engines": {
@@ -1476,10 +1499,11 @@
}
},
"node_modules/acorn": {
"version": "8.11.3",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz",
"integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==",
"version": "8.12.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.0.tgz",
"integrity": "sha512-RTvkC4w+KNXrM39/lWCUaG0IbRkWdCv7W/IOW9oU6SawyxulvkQy5HQPVTKxEjczcUvapcrw3cFx/60VN/NRNw==",
"dev": true,
"license": "MIT",
"bin": {
"acorn": "bin/acorn"
},
@@ -1934,11 +1958,12 @@
}
},
"node_modules/esbuild": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.2.tgz",
"integrity": "sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==",
"version": "0.21.5",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz",
"integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"bin": {
"esbuild": "bin/esbuild"
},
@@ -1946,29 +1971,29 @@
"node": ">=12"
},
"optionalDependencies": {
"@esbuild/aix-ppc64": "0.20.2",
"@esbuild/android-arm": "0.20.2",
"@esbuild/android-arm64": "0.20.2",
"@esbuild/android-x64": "0.20.2",
"@esbuild/darwin-arm64": "0.20.2",
"@esbuild/darwin-x64": "0.20.2",
"@esbuild/freebsd-arm64": "0.20.2",
"@esbuild/freebsd-x64": "0.20.2",
"@esbuild/linux-arm": "0.20.2",
"@esbuild/linux-arm64": "0.20.2",
"@esbuild/linux-ia32": "0.20.2",
"@esbuild/linux-loong64": "0.20.2",
"@esbuild/linux-mips64el": "0.20.2",
"@esbuild/linux-ppc64": "0.20.2",
"@esbuild/linux-riscv64": "0.20.2",
"@esbuild/linux-s390x": "0.20.2",
"@esbuild/linux-x64": "0.20.2",
"@esbuild/netbsd-x64": "0.20.2",
"@esbuild/openbsd-x64": "0.20.2",
"@esbuild/sunos-x64": "0.20.2",
"@esbuild/win32-arm64": "0.20.2",
"@esbuild/win32-ia32": "0.20.2",
"@esbuild/win32-x64": "0.20.2"
"@esbuild/aix-ppc64": "0.21.5",
"@esbuild/android-arm": "0.21.5",
"@esbuild/android-arm64": "0.21.5",
"@esbuild/android-x64": "0.21.5",
"@esbuild/darwin-arm64": "0.21.5",
"@esbuild/darwin-x64": "0.21.5",
"@esbuild/freebsd-arm64": "0.21.5",
"@esbuild/freebsd-x64": "0.21.5",
"@esbuild/linux-arm": "0.21.5",
"@esbuild/linux-arm64": "0.21.5",
"@esbuild/linux-ia32": "0.21.5",
"@esbuild/linux-loong64": "0.21.5",
"@esbuild/linux-mips64el": "0.21.5",
"@esbuild/linux-ppc64": "0.21.5",
"@esbuild/linux-riscv64": "0.21.5",
"@esbuild/linux-s390x": "0.21.5",
"@esbuild/linux-x64": "0.21.5",
"@esbuild/netbsd-x64": "0.21.5",
"@esbuild/openbsd-x64": "0.21.5",
"@esbuild/sunos-x64": "0.21.5",
"@esbuild/win32-arm64": "0.21.5",
"@esbuild/win32-ia32": "0.21.5",
"@esbuild/win32-x64": "0.21.5"
}
},
"node_modules/escalade": {
@@ -2090,10 +2115,11 @@
}
},
"node_modules/eslint-plugin-unicorn": {
"version": "53.0.0",
"resolved": "https://registry.npmjs.org/eslint-plugin-unicorn/-/eslint-plugin-unicorn-53.0.0.tgz",
"integrity": "sha512-kuTcNo9IwwUCfyHGwQFOK/HjJAYzbODHN3wP0PgqbW+jbXqpNWxNVpVhj2tO9SixBwuAdmal8rVcWKBxwFnGuw==",
"version": "54.0.0",
"resolved": "https://registry.npmjs.org/eslint-plugin-unicorn/-/eslint-plugin-unicorn-54.0.0.tgz",
"integrity": "sha512-XxYLRiYtAWiAjPv6z4JREby1TAE2byBC7wlh0V4vWDCpccOSU1KovWV//jqPXF6bq3WKxqX9rdjoRQ1EhdmNdQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-validator-identifier": "^7.24.5",
"@eslint-community/eslint-utils": "^4.4.0",
@@ -2123,10 +2149,11 @@
}
},
"node_modules/eslint-plugin-unicorn/node_modules/@eslint/eslintrc": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.0.2.tgz",
"integrity": "sha512-wV19ZEGEMAC1eHgrS7UQPqsdEiCIbTKTasEfcXAigzoXICcqZSjBZEHlZwNVvKg6UBCjSlos84XiLqsRJnIcIg==",
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.1.0.tgz",
"integrity": "sha512-4Bfj15dVJdoy3RfZmmo86RK1Fwzn6SstsvK9JS+BaVKqC6QQQQyXekNaC+g+LKNgkQ+2VhGAzm6hO40AhMR3zQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"ajv": "^6.12.4",
"debug": "^4.3.2",
@@ -2150,6 +2177,7 @@
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"dev": true,
"license": "MIT",
"dependencies": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
@@ -2160,6 +2188,7 @@
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.0.0.tgz",
"integrity": "sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw==",
"dev": true,
"license": "Apache-2.0",
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
@@ -2168,12 +2197,13 @@
}
},
"node_modules/eslint-plugin-unicorn/node_modules/espree": {
"version": "10.0.1",
"resolved": "https://registry.npmjs.org/espree/-/espree-10.0.1.tgz",
"integrity": "sha512-MWkrWZbJsL2UwnjxTX3gG8FneachS/Mwg7tdGXce011sJd5b0JG54vat5KHnfSBODZ3Wvzd2WnjxyzsRoVv+ww==",
"version": "10.1.0",
"resolved": "https://registry.npmjs.org/espree/-/espree-10.1.0.tgz",
"integrity": "sha512-M1M6CpiE6ffoigIOWYO9UDP8TMUw9kqb21tf+08IgDYjCsOvCuDt4jQcZmoYxx+w7zlKw9/N0KXfto+I8/FrXA==",
"dev": true,
"license": "BSD-2-Clause",
"dependencies": {
"acorn": "^8.11.3",
"acorn": "^8.12.0",
"acorn-jsx": "^5.3.2",
"eslint-visitor-keys": "^4.0.0"
},
@@ -2189,6 +2219,7 @@
"resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz",
"integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=18"
},
@@ -2201,6 +2232,7 @@
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
"dev": true,
"license": "ISC",
"dependencies": {
"brace-expansion": "^1.1.7"
},
@@ -3048,9 +3080,9 @@
}
},
"node_modules/minimatch": {
"version": "9.0.4",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz",
"integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==",
"version": "9.0.5",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
"integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
"dev": true,
"license": "ISC",
"dependencies": {
@@ -3411,10 +3443,11 @@
}
},
"node_modules/prettier": {
"version": "3.2.5",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz",
"integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==",
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.2.tgz",
"integrity": "sha512-rAVeHYMcv8ATV5d508CFdn+8/pHPpXeIid1DdrPwXnaAdH7cqjVbpJaT5eq4yRAFU/lsbwYwSF/n5iNrdJHPQA==",
"dev": true,
"license": "MIT",
"bin": {
"prettier": "bin/prettier.cjs"
},
@@ -4207,10 +4240,11 @@
}
},
"node_modules/typescript": {
"version": "5.4.5",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz",
"integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==",
"version": "5.5.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.2.tgz",
"integrity": "sha512-NcRtPEOsPFFWjobJEtfihkLCZCXZt/os3zf8nTxjVH3RvTSxjrCamJpbExGvYOF+tFHc3pA65qpdwPbzjohhew==",
"dev": true,
"license": "Apache-2.0",
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
@@ -4281,13 +4315,13 @@
}
},
"node_modules/vite": {
"version": "5.2.12",
"resolved": "https://registry.npmjs.org/vite/-/vite-5.2.12.tgz",
"integrity": "sha512-/gC8GxzxMK5ntBwb48pR32GGhENnjtY30G4A0jemunsBkiEZFw60s8InGpN8gkhHEkjnRK1aSAxeQgwvFhUHAA==",
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/vite/-/vite-5.3.2.tgz",
"integrity": "sha512-6lA7OBHBlXUxiJxbO5aAY2fsHHzDr1q7DvXYnyZycRs2Dz+dXBWuhpWHvmljTRTpQC2uvGmUFFkSHF2vGo90MA==",
"dev": true,
"license": "MIT",
"dependencies": {
"esbuild": "^0.20.1",
"esbuild": "^0.21.3",
"postcss": "^8.4.38",
"rollup": "^4.13.0"
},
@@ -4480,10 +4514,11 @@
"dev": true
},
"node_modules/yaml": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.4.2.tgz",
"integrity": "sha512-B3VqDZ+JAg1nZpaEmWtTXUlBneoGx6CPM9b0TENK6aoSu5t73dItudwdgmi6tHlIZZId4dZ9skcAQ2UbcyAeVA==",
"version": "2.4.5",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.4.5.tgz",
"integrity": "sha512-aBx2bnqDzVOyNKfsysjA2ms5ZlnjSAW2eG3/L5G/CSujfjLJTJsEw1bGw8kCf04KodQWk1pxlGnZ56CRxiawmg==",
"dev": true,
"license": "ISC",
"bin": {
"yaml": "bin.mjs"
},

View File

@@ -1,6 +1,6 @@
{
"name": "@immich/cli",
"version": "2.2.0",
"version": "2.2.7",
"description": "Command Line Interface (CLI) for Immich",
"type": "module",
"exports": "./dist/index.js",
@@ -18,7 +18,7 @@
"@types/cli-progress": "^3.11.0",
"@types/lodash-es": "^4.17.12",
"@types/mock-fs": "^4.13.1",
"@types/node": "^20.3.1",
"@types/node": "^20.14.9",
"@typescript-eslint/eslint-plugin": "^7.0.0",
"@typescript-eslint/parser": "^7.0.0",
"@vitest/coverage-v8": "^1.2.2",
@@ -28,7 +28,7 @@
"eslint": "^8.56.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-prettier": "^5.1.3",
"eslint-plugin-unicorn": "^53.0.0",
"eslint-plugin-unicorn": "^54.0.0",
"mock-fs": "^5.2.0",
"prettier": "^3.2.5",
"prettier-plugin-organize-imports": "^3.2.4",
@@ -62,6 +62,6 @@
"lodash-es": "^4.17.21"
},
"volta": {
"node": "20.14.0"
"node": "20.15.0"
}
}

View File

@@ -0,0 +1,19 @@
import { platform } from 'node:os';
import { UploadOptionsDto, getAlbumName } from 'src/commands/asset';
import { describe, expect, it } from 'vitest';
describe('Unit function tests', () => {
it('should return a non-undefined value', () => {
if (platform() === 'win32') {
// This is meaningless for Unix systems.
expect(getAlbumName(String.raw`D:\test\Filename.txt`, {} as UploadOptionsDto)).toBe('test');
}
expect(getAlbumName('D:/parentfolder/test/Filename.txt', {} as UploadOptionsDto)).toBe('test');
});
it('has higher priority to return `albumName` in `options`', () => {
expect(getAlbumName('/parentfolder/test/Filename.txt', { albumName: 'example' } as UploadOptionsDto)).toBe(
'example',
);
});
});

View File

@@ -15,7 +15,6 @@ import { Presets, SingleBar } from 'cli-progress';
import { chunk } from 'lodash-es';
import { Stats, createReadStream } from 'node:fs';
import { stat, unlink } from 'node:fs/promises';
import os from 'node:os';
import path, { basename } from 'node:path';
import { BaseOptions, authenticate, crawl, sha1 } from 'src/utils';
@@ -25,7 +24,7 @@ const s = (count: number) => (count === 1 ? '' : 's');
type AssetBulkUploadCheckResults = Array<AssetBulkUploadCheckResult & { id: string }>;
type Asset = { id: string; filepath: string };
interface UploadOptionsDto {
export interface UploadOptionsDto {
recursive?: boolean;
ignore?: string;
dryRun?: boolean;
@@ -346,7 +345,9 @@ const updateAlbums = async (assets: Asset[], options: UploadOptionsDto) => {
}
};
const getAlbumName = (filepath: string, options: UploadOptionsDto) => {
const folderName = os.platform() === 'win32' ? filepath.split('\\').at(-2) : filepath.split('/').at(-2);
return options.albumName ?? folderName;
// `filepath` valid format:
// - Windows: `D:\\test\\Filename.txt` or `D:/test/Filename.txt`
// - Unix: `/test/Filename.txt`
export const getAlbumName = (filepath: string, options: UploadOptionsDto) => {
return options.albumName ?? path.basename(path.dirname(filepath));
};

View File

@@ -1,4 +1,5 @@
import mockfs from 'mock-fs';
import { readFileSync } from 'node:fs';
import { CrawlOptions, crawl } from 'src/utils';
interface Test {
@@ -9,6 +10,10 @@ interface Test {
const cwd = process.cwd();
const readContent = (path: string) => {
return readFileSync(path).toString();
};
const extensions = [
'.jpg',
'.jpeg',
@@ -256,7 +261,8 @@ const tests: Test[] = [
{
test: 'should support ignoring absolute paths',
options: {
pathsToCrawl: ['/'],
// Currently, fast-glob has some caveat when dealing with `/`.
pathsToCrawl: ['/*s'],
recursive: true,
exclusionPattern: '/images/**',
},
@@ -276,14 +282,16 @@ describe('crawl', () => {
describe('crawl', () => {
for (const { test, options, files } of tests) {
it(test, async () => {
mockfs(Object.fromEntries(Object.keys(files).map((file) => [file, ''])));
// The file contents is the same as the path.
mockfs(Object.fromEntries(Object.keys(files).map((file) => [file, file])));
const actual = await crawl({ ...options, extensions });
const expected = Object.entries(files)
.filter((entry) => entry[1])
.map(([file]) => file);
expect(actual.sort()).toEqual(expected.sort());
// Compare file's content instead of path since a file can be represent in multiple ways.
expect(actual.map((path) => readContent(path)).sort()).toEqual(expected.sort());
});
}
});

View File

@@ -1,8 +1,9 @@
import { getMyUser, init, isHttpError } from '@immich/sdk';
import { glob } from 'fast-glob';
import { convertPathToPattern, glob } from 'fast-glob';
import { createHash } from 'node:crypto';
import { createReadStream } from 'node:fs';
import { readFile, stat, writeFile } from 'node:fs/promises';
import { platform } from 'node:os';
import { join, resolve } from 'node:path';
import yaml from 'yaml';
@@ -106,6 +107,11 @@ export interface CrawlOptions {
exclusionPattern?: string;
extensions: string[];
}
const convertPathToPatternOnWin = (path: string) => {
return platform() === 'win32' ? convertPathToPattern(path) : path;
};
export const crawl = async (options: CrawlOptions): Promise<string[]> => {
const { extensions: extensionsWithPeriod, recursive, pathsToCrawl, exclusionPattern, includeHidden } = options;
const extensions = extensionsWithPeriod.map((extension) => extension.replace('.', ''));
@@ -124,11 +130,11 @@ export const crawl = async (options: CrawlOptions): Promise<string[]> => {
if (stats.isFile() || stats.isSymbolicLink()) {
crawledFiles.push(absolutePath);
} else {
patterns.push(absolutePath);
patterns.push(convertPathToPatternOnWin(absolutePath));
}
} catch (error: any) {
if (error.code === 'ENOENT') {
patterns.push(currentPath);
patterns.push(convertPathToPatternOnWin(currentPath));
} else {
throw error;
}

View File

@@ -2,6 +2,7 @@ import { defineConfig } from 'vite';
import tsconfigPaths from 'vite-tsconfig-paths';
export default defineConfig({
resolve: { alias: { src: '/src' } },
build: {
rollupOptions: {
input: 'src/index.ts',

View File

@@ -2,37 +2,37 @@
# Manual edits may be lost in future updates.
provider "registry.opentofu.org/cloudflare/cloudflare" {
version = "4.34.0"
constraints = "4.34.0"
version = "4.36.0"
constraints = "4.36.0"
hashes = [
"h1:+W0+Xe1AUh7yvHjDbgR9T7CY1UbBC3Y6U7Eo+ucLnJM=",
"h1:2+1lKObDDdFZRluvROF3RKtXD66CFT3PfnHOvR6CmfA=",
"h1:7vluN2wmw8D9nI11YwTgoGv3hGDXlkt8xqQ4L/JABeQ=",
"h1:B0Urm8ZKTJ8cXzSCtEpJ+o+LsD8MXaD6LU59qVbh50Q=",
"h1:FpGLCm5oF12FaRti3E4iQJlkVbdCC7toyGVuH8og7KY=",
"h1:FunTmrCMDy+rom7YskY0WiL5/Y164zFrrD9xnBxU5NY=",
"h1:GrxZhEb+5HzmHF/BvZBdGKBJy6Wyjme0+ABVDz/63to=",
"h1:J36dda2K42/oTfHuZ4jKkW5+nI6BTWFRUvo60P17NJg=",
"h1:Kq0Wyn+j6zoQeghMYixbnfnyP9ZSIEJbOCzMbaCiAQQ=",
"h1:TKxunXCiS/z105sN/kBNFwU6tIKD67JKJ3ZKjwzoCuI=",
"h1:TR0URKFQxsRO5/v7bKm5hkD/CTTjsG7aVGllL/Mf25c=",
"h1:V+3Qs0Reb6r+8p4XjE5ZFDWYrOIN0x5SwORz4wvHOJ4=",
"h1:mZB3Ui7V/lPQMQK53eBOjIHcrul74252dT06Kgn3J+s=",
"h1:wJwZrIXxoki8omXLJ7XA7B1KaSrtcLMJp090fRtFRAc=",
"zh:02aa46743c1585ada8faa7db23af68ea614053a506f88f05d1090ff5e0e68076",
"zh:1e1a545e83e6457a0e15357b23139bc288fb4fbd5e9a5ddfedc95a6a0216b08c",
"zh:29eef2621e0b1501f620e615bf73b1b90d5417d745e38af63634bc03250faf87",
"zh:3c20989d7e1e141882e6091384bf85fdc83f70f3d29e3e047c493a07de992095",
"zh:3d39619379ba29c7ffb15196f0ea72a04c84cfcdf4b39ac42ac4cf4c19f3eae2",
"zh:805f4a2774e9279c590b8214aabe6df9dcc22bb995df2530513f2f78c647ce75",
"h1:00/Y+l17VV4RquGSfwDnYsGYzyf2ZmdQwUgeIzXC7eg=",
"h1:489GpKItA/VRIUA5S4+F8MsnurGVciRvUFyIV81MJTU=",
"h1:7cnczyKGj3+gvaJ0r5JIVWLXPbQfkHYejac76MJx+I8=",
"h1:8rmr1PjJc14Xmor2eEvo5/WBojylt1eYdx6VbSU3Ulo=",
"h1:HjgphNjtgny5tkcUAQoGgBdcuQ+0IyhL8yLsiBqWAP0=",
"h1:LH3umxdBnJcAyeVoBLVn+PC0F0CzN6v9UN6lb6CqQPE=",
"h1:Xx6WUD/zB8fM9SjkFx06Fgx2K7aGJIVvsJS2pwqALEM=",
"h1:YizL5YN9zQ8YkSR6V/G201YrCVdnkF9EUIK4lpROWiA=",
"h1:aPcXVGjYcCJdqvWSzc/dEjwj05LnbWZje8IanygVjcI=",
"h1:eKCvfashdCqfDcFGXE2gq+XxAURD5SzuaQ9Brs3zLos=",
"h1:gpKcBYkBcfn/uF1A8W7MD/OysMZW7EU4QVYvPEEnxGc=",
"h1:kCkcxZZnkKAnMz9scUQHb19d9/l9FPOHovAyrvtA618=",
"h1:t8mXXnICTeKqoD29uvyLFHVWMfMzTUrJuHje8lpI0zU=",
"h1:zjzavjIdLDGRYsWd3v0HJz6ul12Cewj9RW/cqAQ4DxI=",
"zh:02665712b3893307596b3caab99cf1f2502d5caca18e22d4b37bb535e628e102",
"zh:1514b0d3ef62934484ac471113ee68cddec0c21e56b4f710922741fe9b6e6fdf",
"zh:1fab4dfcecbcea13267b42e5ff05ba0692aa2dcb247b8e633fea0daf49feb156",
"zh:24d8367295fe1f1b2be37802aecb96edf32f743364663ffe781d1bb92438395d",
"zh:34e84e7940c99dcf65663cfd25afac22bf5c8a5ff2cd21900c67180d3a072be9",
"zh:3d71d63204a329acf1d1de8638f2c725243cb94cf444d2d7acde54b3d1ac1696",
"zh:57831ba88e779a762bcfa224ba9eac8bc22ef9cd70cd541d848b351e0ba6a75c",
"zh:6407560f2e548afcb4852c91efc664627a9ee565c31a9c81fc9ea1806fca0567",
"zh:738ddbc664d75f4859aa09444a27809bc398795a8ea8f5be8531040690287712",
"zh:841ca2b2d78b6f8d33ec3435bc090c5e04a3a7d85c80df11227a7ea00d36f6b1",
"zh:890df766e9b839623b1f0437355032a3c006226a6c200cd911e15ee1a9014e9f",
"zh:8af716f8655a57aa986861a8a7fa1d724594a284bd77c870eaea4db5f8b9732d",
"zh:a3d13c93b4e6ee6004782debaa9a17f990f2fe8ec8ba545c232818bb6064aba9",
"zh:bfa136acf82d3719473c0064446cc16d1b0303d98b06f55f503b7abeebceadb1",
"zh:ca6cf9254ae5436f2efbc01a0e3f7e4aa3c08b45182037b3eb3eb9539b2f7aec",
"zh:cba32d5de02674004e0a5955bd5222016d9991ca0553d4bd3bea517cd9def6ab",
"zh:d22c8cd527c6d0e84567f57be5911792e2fcd5969e3bba3747489f18bb16705b",
"zh:e4eeede9b3e72cdadd6cc252d4cbcf41baee6ecfd12bacd927e2dcbe733ab210",
"zh:facdaa787a69f86203cd3cc6922baea0b4a18bd9c36b0a8162e2e88ef6c90655",
"zh:8b3d3d63354032ab9b2403c50728e9aa4e83c7367eaad2d18794221addeafc0f",
"zh:9e293443fe3127e488f540229983c1b9688268185f87567bb3d18e794697acd2",
"zh:b3a22439156e46461213db183e2e89569cd2e8d7cbcfc4b9f90469090e105807",
"zh:f430feb5d51891e84028459e57039045dea4f1f5fcf671161d8ac2d8f28763f3",
]
}

View File

@@ -5,7 +5,7 @@ terraform {
required_providers {
cloudflare = {
source = "cloudflare/cloudflare"
version = "4.34.0"
version = "4.36.0"
}
}
}

View File

@@ -2,37 +2,37 @@
# Manual edits may be lost in future updates.
provider "registry.opentofu.org/cloudflare/cloudflare" {
version = "4.34.0"
constraints = "4.34.0"
version = "4.36.0"
constraints = "4.36.0"
hashes = [
"h1:+W0+Xe1AUh7yvHjDbgR9T7CY1UbBC3Y6U7Eo+ucLnJM=",
"h1:2+1lKObDDdFZRluvROF3RKtXD66CFT3PfnHOvR6CmfA=",
"h1:7vluN2wmw8D9nI11YwTgoGv3hGDXlkt8xqQ4L/JABeQ=",
"h1:B0Urm8ZKTJ8cXzSCtEpJ+o+LsD8MXaD6LU59qVbh50Q=",
"h1:FpGLCm5oF12FaRti3E4iQJlkVbdCC7toyGVuH8og7KY=",
"h1:FunTmrCMDy+rom7YskY0WiL5/Y164zFrrD9xnBxU5NY=",
"h1:GrxZhEb+5HzmHF/BvZBdGKBJy6Wyjme0+ABVDz/63to=",
"h1:J36dda2K42/oTfHuZ4jKkW5+nI6BTWFRUvo60P17NJg=",
"h1:Kq0Wyn+j6zoQeghMYixbnfnyP9ZSIEJbOCzMbaCiAQQ=",
"h1:TKxunXCiS/z105sN/kBNFwU6tIKD67JKJ3ZKjwzoCuI=",
"h1:TR0URKFQxsRO5/v7bKm5hkD/CTTjsG7aVGllL/Mf25c=",
"h1:V+3Qs0Reb6r+8p4XjE5ZFDWYrOIN0x5SwORz4wvHOJ4=",
"h1:mZB3Ui7V/lPQMQK53eBOjIHcrul74252dT06Kgn3J+s=",
"h1:wJwZrIXxoki8omXLJ7XA7B1KaSrtcLMJp090fRtFRAc=",
"zh:02aa46743c1585ada8faa7db23af68ea614053a506f88f05d1090ff5e0e68076",
"zh:1e1a545e83e6457a0e15357b23139bc288fb4fbd5e9a5ddfedc95a6a0216b08c",
"zh:29eef2621e0b1501f620e615bf73b1b90d5417d745e38af63634bc03250faf87",
"zh:3c20989d7e1e141882e6091384bf85fdc83f70f3d29e3e047c493a07de992095",
"zh:3d39619379ba29c7ffb15196f0ea72a04c84cfcdf4b39ac42ac4cf4c19f3eae2",
"zh:805f4a2774e9279c590b8214aabe6df9dcc22bb995df2530513f2f78c647ce75",
"h1:00/Y+l17VV4RquGSfwDnYsGYzyf2ZmdQwUgeIzXC7eg=",
"h1:489GpKItA/VRIUA5S4+F8MsnurGVciRvUFyIV81MJTU=",
"h1:7cnczyKGj3+gvaJ0r5JIVWLXPbQfkHYejac76MJx+I8=",
"h1:8rmr1PjJc14Xmor2eEvo5/WBojylt1eYdx6VbSU3Ulo=",
"h1:HjgphNjtgny5tkcUAQoGgBdcuQ+0IyhL8yLsiBqWAP0=",
"h1:LH3umxdBnJcAyeVoBLVn+PC0F0CzN6v9UN6lb6CqQPE=",
"h1:Xx6WUD/zB8fM9SjkFx06Fgx2K7aGJIVvsJS2pwqALEM=",
"h1:YizL5YN9zQ8YkSR6V/G201YrCVdnkF9EUIK4lpROWiA=",
"h1:aPcXVGjYcCJdqvWSzc/dEjwj05LnbWZje8IanygVjcI=",
"h1:eKCvfashdCqfDcFGXE2gq+XxAURD5SzuaQ9Brs3zLos=",
"h1:gpKcBYkBcfn/uF1A8W7MD/OysMZW7EU4QVYvPEEnxGc=",
"h1:kCkcxZZnkKAnMz9scUQHb19d9/l9FPOHovAyrvtA618=",
"h1:t8mXXnICTeKqoD29uvyLFHVWMfMzTUrJuHje8lpI0zU=",
"h1:zjzavjIdLDGRYsWd3v0HJz6ul12Cewj9RW/cqAQ4DxI=",
"zh:02665712b3893307596b3caab99cf1f2502d5caca18e22d4b37bb535e628e102",
"zh:1514b0d3ef62934484ac471113ee68cddec0c21e56b4f710922741fe9b6e6fdf",
"zh:1fab4dfcecbcea13267b42e5ff05ba0692aa2dcb247b8e633fea0daf49feb156",
"zh:24d8367295fe1f1b2be37802aecb96edf32f743364663ffe781d1bb92438395d",
"zh:34e84e7940c99dcf65663cfd25afac22bf5c8a5ff2cd21900c67180d3a072be9",
"zh:3d71d63204a329acf1d1de8638f2c725243cb94cf444d2d7acde54b3d1ac1696",
"zh:57831ba88e779a762bcfa224ba9eac8bc22ef9cd70cd541d848b351e0ba6a75c",
"zh:6407560f2e548afcb4852c91efc664627a9ee565c31a9c81fc9ea1806fca0567",
"zh:738ddbc664d75f4859aa09444a27809bc398795a8ea8f5be8531040690287712",
"zh:841ca2b2d78b6f8d33ec3435bc090c5e04a3a7d85c80df11227a7ea00d36f6b1",
"zh:890df766e9b839623b1f0437355032a3c006226a6c200cd911e15ee1a9014e9f",
"zh:8af716f8655a57aa986861a8a7fa1d724594a284bd77c870eaea4db5f8b9732d",
"zh:a3d13c93b4e6ee6004782debaa9a17f990f2fe8ec8ba545c232818bb6064aba9",
"zh:bfa136acf82d3719473c0064446cc16d1b0303d98b06f55f503b7abeebceadb1",
"zh:ca6cf9254ae5436f2efbc01a0e3f7e4aa3c08b45182037b3eb3eb9539b2f7aec",
"zh:cba32d5de02674004e0a5955bd5222016d9991ca0553d4bd3bea517cd9def6ab",
"zh:d22c8cd527c6d0e84567f57be5911792e2fcd5969e3bba3747489f18bb16705b",
"zh:e4eeede9b3e72cdadd6cc252d4cbcf41baee6ecfd12bacd927e2dcbe733ab210",
"zh:facdaa787a69f86203cd3cc6922baea0b4a18bd9c36b0a8162e2e88ef6c90655",
"zh:8b3d3d63354032ab9b2403c50728e9aa4e83c7367eaad2d18794221addeafc0f",
"zh:9e293443fe3127e488f540229983c1b9688268185f87567bb3d18e794697acd2",
"zh:b3a22439156e46461213db183e2e89569cd2e8d7cbcfc4b9f90469090e105807",
"zh:f430feb5d51891e84028459e57039045dea4f1f5fcf671161d8ac2d8f28763f3",
]
}

View File

@@ -5,7 +5,7 @@ terraform {
required_providers {
cloudflare = {
source = "cloudflare/cloudflare"
version = "4.34.0"
version = "4.36.0"
}
}
}

View File

@@ -26,6 +26,16 @@ services:
- /etc/localtime:/etc/localtime:ro
env_file:
- .env
environment:
IMMICH_REPOSITORY: immich-app/immich
IMMICH_REPOSITORY_URL: https://github.com/immich-app/immich
IMMICH_SOURCE_REF: local
IMMICH_SOURCE_COMMIT: af2efbdbbddc27cd06142f22253ccbbbbeec1f55
IMMICH_SOURCE_URL: https://github.com/immich-app/immich/commit/af2efbdbbddc27cd06142f22253ccbbbbeec1f55
IMMICH_BUILD: '9654404849'
IMMICH_BUILD_URL: https://github.com/immich-app/immich/actions/runs/9654404849
IMMICH_BUILD_IMAGE: development
IMMICH_BUILD_IMAGE_URL: https://github.com/immich-app/immich/pkgs/container/immich-server
ulimits:
nofile:
soft: 1048576
@@ -84,7 +94,7 @@ services:
redis:
container_name: immich_redis
image: redis:6.2-alpine@sha256:e31ca60b18f7e9b78b573d156702471d4eda038803c0b8e6f01559f350031e93
image: redis:6.2-alpine@sha256:328fe6a5822256d065debb36617a8169dbfbd77b797c525288e465f56c1d392b
healthcheck:
test: redis-cli ping || exit 1
@@ -103,11 +113,26 @@ services:
ports:
- 5432:5432
healthcheck:
test: pg_isready --dbname='${DB_DATABASE_NAME}' || exit 1; Chksum="$$(psql --dbname='${DB_DATABASE_NAME}' --username='${DB_USERNAME}' --tuples-only --no-align --command='SELECT SUM(checksum_failures) FROM pg_stat_database')"; echo "checksum failure count is $$Chksum"; [ "$$Chksum" = '0' ] || exit 1
test: pg_isready --dbname='${DB_DATABASE_NAME}' --username='${DB_USERNAME}' || exit 1; Chksum="$$(psql --dbname='${DB_DATABASE_NAME}' --username='${DB_USERNAME}' --tuples-only --no-align --command='SELECT COALESCE(SUM(checksum_failures), 0) FROM pg_stat_database')"; echo "checksum failure count is $$Chksum"; [ "$$Chksum" = '0' ] || exit 1
interval: 5m
start_interval: 30s
start_period: 5m
command: ["postgres", "-c" ,"shared_preload_libraries=vectors.so", "-c", 'search_path="$$user", public, vectors', "-c", "logging_collector=on", "-c", "max_wal_size=2GB", "-c", "shared_buffers=512MB", "-c", "wal_compression=on"]
command:
[
'postgres',
'-c',
'shared_preload_libraries=vectors.so',
'-c',
'search_path="$$user", public, vectors',
'-c',
'logging_collector=on',
'-c',
'max_wal_size=2GB',
'-c',
'shared_buffers=512MB',
'-c',
'wal_compression=on',
]
# set IMMICH_METRICS=true in .env to enable metrics
# immich-prometheus:

View File

@@ -41,7 +41,7 @@ services:
redis:
container_name: immich_redis
image: redis:6.2-alpine@sha256:e31ca60b18f7e9b78b573d156702471d4eda038803c0b8e6f01559f350031e93
image: redis:6.2-alpine@sha256:328fe6a5822256d065debb36617a8169dbfbd77b797c525288e465f56c1d392b
healthcheck:
test: redis-cli ping || exit 1
restart: always
@@ -61,7 +61,7 @@ services:
ports:
- 5432:5432
healthcheck:
test: pg_isready --dbname='${DB_DATABASE_NAME}' || exit 1; Chksum="$$(psql --dbname='${DB_DATABASE_NAME}' --username='${DB_USERNAME}' --tuples-only --no-align --command='SELECT SUM(checksum_failures) FROM pg_stat_database')"; echo "checksum failure count is $$Chksum"; [ "$$Chksum" = '0' ] || exit 1
test: pg_isready --dbname='${DB_DATABASE_NAME}' --username='${DB_USERNAME}' || exit 1; Chksum="$$(psql --dbname='${DB_DATABASE_NAME}' --username='${DB_USERNAME}' --tuples-only --no-align --command='SELECT COALESCE(SUM(checksum_failures), 0) FROM pg_stat_database')"; echo "checksum failure count is $$Chksum"; [ "$$Chksum" = '0' ] || exit 1
interval: 5m
start_interval: 30s
start_period: 5m
@@ -73,7 +73,7 @@ services:
container_name: immich_prometheus
ports:
- 9090:9090
image: prom/prometheus@sha256:5c435642ca4d8427ca26f4901c11114023004709037880cd7860d5b7176aa731
image: prom/prometheus@sha256:075b1ba2c4ebb04bc3a6ab86c06ec8d8099f8fda1c96ef6d104d9bb1def1d8bc
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml
- prometheus-data:/prometheus
@@ -85,7 +85,7 @@ services:
command: ['./run.sh', '-disable-reporting']
ports:
- 3000:3000
image: grafana/grafana:11.0.0-ubuntu@sha256:02e99d1ee0b52dc9d3000c7b5314e7a07e0dfd69cc49bb3f8ce323491ed3406b
image: grafana/grafana:11.1.0-ubuntu@sha256:c7fc29ec783d5e7fc1bdfaad6f92345a345cffbc5d21c388ca228175006fc107
volumes:
- grafana-data:/var/lib/grafana

View File

@@ -43,7 +43,7 @@ services:
redis:
container_name: immich_redis
image: docker.io/redis:6.2-alpine@sha256:e31ca60b18f7e9b78b573d156702471d4eda038803c0b8e6f01559f350031e93
image: docker.io/redis:6.2-alpine@sha256:328fe6a5822256d065debb36617a8169dbfbd77b797c525288e465f56c1d392b
healthcheck:
test: redis-cli ping || exit 1
restart: always
@@ -59,7 +59,7 @@ services:
volumes:
- ${DB_DATA_LOCATION}:/var/lib/postgresql/data
healthcheck:
test: pg_isready --dbname='${DB_DATABASE_NAME}' || exit 1; Chksum="$$(psql --dbname='${DB_DATABASE_NAME}' --username='${DB_USERNAME}' --tuples-only --no-align --command='SELECT SUM(checksum_failures) FROM pg_stat_database')"; echo "checksum failure count is $$Chksum"; [ "$$Chksum" = '0' ] || exit 1
test: pg_isready --dbname='${DB_DATABASE_NAME}' --username='${DB_USERNAME}' || exit 1; Chksum="$$(psql --dbname='${DB_DATABASE_NAME}' --username='${DB_USERNAME}' --tuples-only --no-align --command='SELECT COALESCE(SUM(checksum_failures), 0) FROM pg_stat_database')"; echo "checksum failure count is $$Chksum"; [ "$$Chksum" = '0' ] || exit 1
interval: 5m
start_interval: 30s
start_period: 5m

View File

@@ -3,10 +3,10 @@ global:
evaluation_interval: 15s
scrape_configs:
- job_name: immich_server
- job_name: immich_api
static_configs:
- targets: ['immich-server:8081']
- job_name: immich_microservices
static_configs:
- targets: ['immich-microservices:8081']
- targets: ['immich-server:8082']

49
docker/scripts/get-cpus.sh Executable file
View File

@@ -0,0 +1,49 @@
#!/bin/sh
set -eu
LOG_LEVEL="${IMMICH_LOG_LEVEL:='info'}"
logDebug() {
if [ "$LOG_LEVEL" = "debug" ] || [ "$LOG_LEVEL" = "verbose" ]; then
echo "DEBUG: $1" >&2
fi
}
if [ -f /sys/fs/cgroup/cgroup.controllers ]; then
logDebug "cgroup v2 detected."
if [ -f /sys/fs/cgroup/cpu.max ]; then
read -r quota period </sys/fs/cgroup/cpu.max
if [ "$quota" = "max" ]; then
logDebug "No CPU limits set."
unset quota period
fi
else
logDebug "/sys/fs/cgroup/cpu.max not found."
fi
else
logDebug "cgroup v1 detected."
if [ -f /sys/fs/cgroup/cpu/cpu.cfs_quota_us ] && [ -f /sys/fs/cgroup/cpu/cpu.cfs_period_us ]; then
quota=$(cat /sys/fs/cgroup/cpu/cpu.cfs_quota_us)
period=$(cat /sys/fs/cgroup/cpu/cpu.cfs_period_us)
if [ "$quota" = "-1" ]; then
logDebug "No CPU limits set."
unset quota period
fi
else
logDebug "/sys/fs/cgroup/cpu/cpu.cfs_quota_us or /sys/fs/cgroup/cpu/cpu.cfs_period_us not found."
fi
fi
if [ -n "${quota:-}" ] && [ -n "${period:-}" ]; then
cpus=$((quota / period))
if [ "$cpus" -eq 0 ]; then
cpus=1
fi
else
cpus=$(grep -c ^processor /proc/cpuinfo)
fi
echo "$cpus"

View File

@@ -1 +1 @@
20.14
20.15

View File

@@ -94,7 +94,7 @@ Thank you, and I am asking for your support for the project. I hope to be a full
- Bitcoin: 3QVAb9dCHutquVejeNXitPqZX26Yg5kxb7
- Give a project a star - the contributors love gazing at the stars and seeing their creations shining in the sky.
Join our friendly [Discord](https://discord.gg/D8JsnBEuKb) to talk and discuss Immich, tech, or anything
Join our friendly [Discord](https://discord.immich.app) to talk and discuss Immich, tech, or anything
Cheer!

View File

@@ -142,7 +142,7 @@ Thank you, and I am asking for your support for the project. I hope to be a full
- Bitcoin: 3QVAb9dCHutquVejeNXitPqZX26Yg5kxb7
- Give a project a star - the contributors love gazing at the stars and seeing their creations shining in the sky.
Join our friendly [Discord](https://discord.gg/D8JsnBEuKb) to talk and discuss Immich, tech, or anything
Join our friendly [Discord](https://discord.immich.app) to talk and discuss Immich, tech, or anything
Cheer!

View File

@@ -0,0 +1,77 @@
---
title: Immich Update - July 2024
authors: [alextran]
tags: [update, v1.106.0]
---
Hello everybody! Alex from Immich here and I am back with another development progress update for the project.
Summer has returned once again, and the night sky is filled with stars, thank you for **38_000 shining stars** you have sent to our [GitHub repo](https://github.com/immich-app/immich)! Since the last announcement several core contributors have started full time. Everything is going great with development, PRs get merged with _brrrrrrr_ rate, conversation exchange between team members is on a new high, we met and are working with the great engineers at FUTO. The spirit is high and we have a lot of things brewing that we think you will like.
Let's go over some of the updates we had since the last post.
### Container consolidation
Reduced the number of total containers from 5 to 4 by making the microservices thread get spawned directly in the server container. Woohoo, remember when Immich had 7 containers?
### Email notifications
![smtp](https://github.com/immich-app/immich/assets/27055614/949cba85-d3f1-4cd3-b246-a6f5fb5d3ae8)
We added email notifications to the app with SMTP settings that you can configure for the following events
- A new account is created for you.
- You are added to a shared album.
- New media is added to an album.
### Versioned docs
You can now jump back into the past or take a peek at the unreleased version of the documentation by selecting the version on the website.
![version-doc](https://github.com/immich-app/immich/assets/27055614/6d22898a-5093-41ad-b416-4573d7ce6e03)
### Similarity deduplication
With more machine learning and CLIP magic, we now have similarity deduplication built into the application where it will search for closely similar images and let you decide what to do with them; i.e keep or trash.
![similarity-deduplication](https://github.com/immich-app/immich/assets/27055614/3cac8478-fbf7-47ea-acb6-0146901dc67e)
### Permanent URL for asset on the web
The detail view for an asset now has a permanent URL so you can easily share them with your loved ones.
### Web app translations
We now have a public Weblate project which the community can use to translate the webapp to their native languages. We are planning to port the mobile app translation to this platform as well. If you would like to contribute, you can take a look [here](https://hosted.weblate.org/projects/immich/immich/). We're already close to 50% translations -- we really appreciate everyone contributing to that!
![web-translation](https://github.com/immich-app/immich/assets/27055614/363df2ed-656c-4584-bd82-0708a693c5bc)
### Read-only/Editor mode on shared album
As the owner of the album, you can choose if the shared user can edit the album or to only view the content of the album without any modification.
![read-only-album](https://github.com/immich-app/immich/assets/27055614/c6f66375-b869-495a-9a86-3e87b316d109)
### Better video thumbnails
Immich now tries to find a descriptive video thumbnail instead of simply using the first frame. No more black images for thumbnails!
### Public Roadmap
We now have a [public roadmap](https://immich.app/roadmap), giving you a high-level overview of things the team is working on. The first goal of this roadmap is to bring Immich to a stable release, which is expected sometime later this year. Some of the highlights include
- Auto stacking - Auto stacking of burst photos
- Basic editor - Basic photo editing capabilities
- Workflows - Automate tasks with workflows
- Fine grained access controls - Granular access controls for users and api keys
- Better background backups - Rework background backups to be more reliable
- Private/locked photos - Private assets with extra protections
Beyond the items in the roadmap, we have _many many_ more ideas for Immich. The team and I hope that you are enjoying the application, find it helpful in your life and we have nothing but the intention of building out great software for you all!
Have an amazing Summer or Winter for those in the southern hemisphere! :D
Until next time,
Cheers!
Alex

View File

@@ -133,40 +133,6 @@ For example, say you have existing transcodes with the policy "Videos higher tha
No. Our design principle is that the original assets should always be untouched.
### How can I move all data (photos, persons, albums, libraries) from one user to another?
This is not officially supported but can be accomplished with some database updates. You can do this on the command line (in the PostgreSQL container using the `psql` command), or you can add, for example, an [Adminer](https://www.adminer.org/) container to the `docker-compose.yml` file so that you can use a web interface.
<details>
<summary>Steps</summary>
1. **MAKE A BACKUP** - See [backup and restore](/docs/administration/backup-and-restore.md).
2. Find the ID of both the 'source' and the 'destination' user (it's the id column in the `users` table)
3. Four tables need to be updated:
```sql
BEGIN;
-- reassign albums
UPDATE albums SET "ownerId" = '<destinationId>' WHERE "ownerId" = '<sourceId>';
-- reassign people
UPDATE person SET "ownerId" = '<destinationId>' WHERE "ownerId" = '<sourceId>';
-- reassign assets
UPDATE assets SET "ownerId" = '<destinationId>' WHERE "ownerId" = '<sourceId>'
AND CHECKSUM NOT IN (SELECT CHECKSUM FROM assets WHERE "ownerId" = '<destinationId>');
-- reassign external libraries
UPDATE libraries SET "ownerId" = '<destinationId>' WHERE "ownerId" = '<sourceId>';
COMMIT;
```
4. There might be left-over assets in the 'source' user's library if they are skipped by the last query because of duplicate checksums. These are probably duplicates anyway, and can probably be removed.
</details>
---
## Albums
@@ -442,4 +408,11 @@ docker exec -it immich_postgres psql --dbname=immich --username=<DB_USERNAME> --
</details>
If corruption is detected, you should immediately make a backup before performing any other work in the database.
To do so, you may need to set the `zero_damaged_pages=on` flag for the database server to allow `pg_dumpall` to succeed.
After taking a backup, the recommended next step is to restore the database from a healthy backup before corruption was detected.
The damaged database dump can be used to manually recover any changes made since the last backup, if needed.
The causes of possible corruption are many, but can include unexpected poweroffs or unmounts, use of a network share for Postgres data, or a poor storage medium such an SD card or failing HDD/SSD.
[huggingface]: https://huggingface.co/immich-app

View File

@@ -76,6 +76,7 @@ services:
backup:
container_name: immich_db_dumper
image: prodrigestivill/postgres-backup-local:14
restart: always
env_file:
- .env
environment:
@@ -191,6 +192,6 @@ When you turn off the storage template engine, it will leave the assets in `UPLO
</Tabs>
:::danger
Do not touch the files inside these folders under any circumstances except taking a backup, changing or removing an asset can cause untracked and missing files.
Do not touch the files inside these folders under any circumstances except taking a backup. Changing or removing an asset can cause untracked and missing files.
You can think of it as App-Which-Must-Not-Be-Named, the only access to viewing, changing and deleting assets is only through the mobile or browser interface.
:::

View File

@@ -0,0 +1,23 @@
# Email Notifications
Immich supports the option to send notifications via Email for the following events:
- Creating a new user
- Notifying a user when they get added to a shared album
- Informing other users about the addition of new assets to a shared album
## SMTP settings
You can access the settings panel from the web at `Administration -> Settings -> Notification settings`
Under Email, enter the following details to connect with SMTP servers.
You can use the following [guide](/docs/guides/smtp-gmail) to use Gmail's SMTP server.
<img src={require('./img/email-settings.png').default} width="80%" title="SMTP settings" />
## User's notifications settings
Users can manage their email notification settings from their account settings page on the web. They can choose to turn email notifications on or off for the following events:
<img src={require('./img/user-notifications-settings.png').default} width="80%" title="User notification settings" />

Binary file not shown.

After

Width:  |  Height:  |  Size: 218 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 109 KiB

View File

@@ -27,7 +27,7 @@ Copy the entire `immich-server` block as a new service and make the following ch
+ container_name: immich_microservices
```
Once you have two copies of the immich-server service, make the following chnages to each one. This will allow one container to only serve the web UI and API, and the other one to handle all other tasks.
Once you have two copies of the immich-server service, make the following changes to each one. This will allow one container to only serve the web UI and API, and the other one to handle all other tasks.
```diff
services:

View File

@@ -10,6 +10,59 @@ Viewing and modifying the system settings is restricted to the Administrator.
You can always return to the default settings by clicking the `Reset to default` button.
:::
## Authentication Settings
Manage password, OAuth, and other authentication settings
### OAuth Authentication
Immich supports OAuth Authentication. Read more about this feature and its configuration [here](/docs/administration/oauth).
### Password Authentication
The administrator can choose to disable login with username and password for the entire instance. This means that **no one**, including the system administrator, will be able to log using this method. If [OAuth Authentication](/docs/administration/oauth) is also disabled, no users will be able to login using **any** method. Changing this setting does not affect existing sessions, just new login attempts.
:::tip
You can always use the [Server CLI](/docs/administration/server-commands) to re-enable password login.
:::
## Image Settings (thumbnails and previews)
- Thumbnails - Used in the main timeline.
- Previews - Used in the asset viewer.
By default Immich creates 3 thumbnails for each asset,
Blurred (thumbhash) , Small - thumbnails (webp) , and Large - previews (jpeg/webp), using these settings you can change the quality for the thumbnails and previews files that are created.
**Thumbnail format**
Allows you to choose the type of format you want for the Thumbnail images, Webp produces smaller files than jpeg, but is slower to encode.
:::tip
You can read in detail about the advantages and disadvantages of using webp over jpeg on [Adobe's website](https://www.adobe.com/creativecloud/file-types/image/raster/webp-file.html)
:::
**Thumbnail resolution**
Used when viewing groups of photos (main timeline, album view, etc.). Higher resolutions can preserve more detail but take longer to encode, have larger file sizes, and can reduce app responsiveness.
**Preview format**
Allows you to choose the type of format you want for the Preview images, Webp produces smaller files than jpeg, but is slower to encode.
**Preview resolution**
Used when viewing a single photo and for machine learning. Higher resolutions can preserve more detail but take longer to encode, have larger file sizes, and can reduce app responsiveness.
**Quality**
Image quality from 1-100. Higher is better for quality but produces larger files, this option affects the Preview and Thumbnail images.
**Prefer wide gamut**
Use Display P3 for thumbnails. This better preserves the vibrance of images with wide colorspaces, but images may appear differently on old devices with an old browser version. sRGB images are kept as sRGB to avoid color shifts.
**Prefer embedded preview**
Use embedded previews in RAW photos as the input to image processing when available. This can produce more accurate colors for some images, but the quality of the preview is camera-dependent and the image may have more compression artifacts.
:::tip
The default resolution for Large thumbnails can be lowered from 1440p (default) to 1080p or 720p to save storage space.
:::
## Job Settings
Using these settings, you can determine the amount of work that will run concurrently for each task in microservices. Some tasks can be set to higher values on computers with powerful hardware and storage with good I/O capabilities.
@@ -19,6 +72,11 @@ this advice improves throughput, not latency, for example, it will make Smart Se
It is important to remember that jobs like Smart Search, Face Detection, Facial Recognition, and Transcode Videos require a **lot** of processing power and therefore do not exaggerate the amount of jobs because you're probably thoroughly overloading the server.
:::danger IMPORTANT
If you increase the concurrency from the defaults we set, especially for thumbnail generation, make sure you do not increase them past the amount of CPU cores you have available.
Doing so can impact API responsiveness with no gain in thumbnail generation speed.
:::
:::info Facial Recognition Concurrency
The Facial Recognition Concurrency value cannot be changed because
[DBSCAN](https://www.youtube.com/watch?v=RDZUdRSDOok) is traditionally sequential, but there are parallel implementations of it out there. Our implementation isn't parallel.
@@ -87,17 +145,9 @@ The map can be adjusted via [OpenMapTiles](https://openmaptiles.org/styles/) for
Immich supports [Reverse Geocoding](/docs/features/reverse-geocoding) using data from the [GeoNames](https://www.geonames.org/) geographical database.
## OAuth Authentication
## Notification Settings
Immich supports OAuth Authentication. Read more about this feature and its configuration [here](/docs/administration/oauth).
## Password Authentication
The administrator can choose to disable login with username and password for the entire instance. This means that **no one**, including the system administrator, will be able to log using this method. If [OAuth Authentication](/docs/administration/oauth) is also disabled, no users will be able to login using **any** method. Changing this setting does not affect existing sessions, just new login attempts.
:::tip
You can always use the [Server CLI](/docs/administration/server-commands) to re-enable password login.
:::
SMTP server setup, for user creation notifications, new albums, etc. More information can be found [here](/docs/administration/email-notification)
## Server Settings
@@ -125,27 +175,6 @@ p {
}
```
## Thumbnail Settings
By default Immich creates 3 thumbnails for each asset,
Blurred (thumbhash) , Small (webp) , and Large (jpeg), using these settings you can change the quality for the thumbnail files that are created.
**Small thumbnail resolution**
Used when viewing groups of photos (main timeline, album view, etc.). Higher resolutions can preserve more detail but take longer to encode, have larger file sizes, and can reduce app responsiveness.
**Large thumbnail resolution**
Used when viewing a single photo and for machine learning. Higher resolutions can preserve more detail but take longer to encode, have larger file sizes, and can reduce app responsiveness.
**Quality**
Thumbnail quality from 1-100. Higher is better for quality but produces larger files.
**Prefer wide gamut**
Use display p3 for thumbnails. This better preserves the vibrance of images with wide color spaces, but images may appear differently on old devices with an old browser version. Srgb images are kept as srgb to avoid color shifts.
:::tip
The default resolution for Large thumbnails can be lowered from 1440p (default) to 1080p or 720p to save storage space.
:::
## Trash Settings
In the system administrator's option to set a trash for deleted files, these files will remain in the trash until the deletion date 30 days (default) or as defined by the system administrator.

View File

@@ -13,6 +13,20 @@ Immich supports multiple users, each with their own library.
<UserCreate />
## Send new user email notification
:::note
This option is only available if an SMTP server has been configured in the administrator settings.
:::
Admin can send a welcome email if the Email option is set, you can learn here how to set up the SMTP server in Immich.
<img
src={require('./img/send-user-email-notification.webp').default}
width="40%"
title="Send user email notification"
/>
## Set Storage Quota For User
Admin can specify the storage quota for the user as the instance's admin; once the limit is reached, the user won't be able to upload to the instance anymore.

View File

@@ -0,0 +1,21 @@
# Translations
:::tip
You can request a new language [here](https://hosted.weblate.org/new-lang/immich/immich/).
:::
## Weblate
[Weblate](https://weblate.org/) is a "libre software web-based continuous localization system". Immich localization efforts are managed on their [hosted platform](https://hosted.weblate.org/projects/immich/immich/).
## International message format
Plurals, numbers, dates and other locale specific message formats can be handled by using the [ICU message format](https://unicode-org.github.io/icu/userguide/format_parse/messages/). Internally, this is handled by the [intl-messageformat](https://www.npmjs.com/package/intl-messageformat) library. Their [documentation](https://formatjs.io/docs/intl-messageformat/) includes common, editable examples via a "live editor" feature, which can be useful to test and debug message formats.
## Progress
Immich currently supports the following languages:
<a href="https://hosted.weblate.org/engage/immich/">
<img src="https://hosted.weblate.org/widget/immich/immich/multi-auto.svg" alt="Translation status" />
</a>

View File

@@ -1,7 +1,7 @@
# Troubleshooting
:::tip
A great option to get assistance with troubleshooting is to join our [Discord](https://discord.gg/D8JsnBEuKb) server, where we have a dedicated channel for `#contributing`.
A great option to get assistance with troubleshooting is to join our [Discord](https://discord.immich.app) server, where we have a dedicated channel for `#contributing`.
:::
## Known Issues

View File

@@ -60,17 +60,17 @@ For RKMPP to work:
#### Basic Setup
1. If you do not already have it, download the latest [`hwaccel.transcoding.yml`][hw-file] file and ensure it's in the same folder as the `docker-compose.yml`.
2. In the `docker-compose.yml` under `immich-microservices`, uncomment the `extends` section and change `cpu` to the appropriate backend.
2. In the `docker-compose.yml` under `immich-server`, uncomment the `extends` section and change `cpu` to the appropriate backend.
- For VAAPI on WSL2, be sure to use `vaapi-wsl` rather than `vaapi`
3. Redeploy the `immich-microservices` container with these updated settings.
3. Redeploy the `immich-server` container with these updated settings.
4. In the Admin page under `Video transcoding settings`, change the hardware acceleration setting to the appropriate option and save.
5. (Optional) If using a compatible backend, you may enable hardware decoding for optimal performance.
#### Single Compose File
Some platforms, including Unraid and Portainer, do not support multiple Compose files as of writing. As an alternative, you can "inline" the relevant contents of the [`hwaccel.transcoding.yml`][hw-file] file into the `immich-microservices` service directly.
Some platforms, including Unraid and Portainer, do not support multiple Compose files as of writing. As an alternative, you can "inline" the relevant contents of the [`hwaccel.transcoding.yml`][hw-file] file into the `immich-server` service directly.
For example, the `qsv` section in this file is:
@@ -79,21 +79,22 @@ devices:
- /dev/dri:/dev/dri
```
You can add this to the `immich-microservices` service instead of extending from `hwaccel.transcoding.yml`:
You can add this to the `immich-server` service instead of extending from `hwaccel.transcoding.yml`:
```yaml
immich-microservices:
container_name: immich_microservices
immich-server:
container_name: immich_server
image: ghcr.io/immich-app/immich-server:${IMMICH_VERSION:-release}
# Note the lack of an `extends` section
devices:
- /dev/dri:/dev/dri
command: ['start.sh', 'microservices']
volumes:
- ${UPLOAD_LOCATION}:/usr/src/app/upload
- /etc/localtime:/etc/localtime:ro
env_file:
- .env
ports:
- 2283:3001
depends_on:
- redis
- database

Binary file not shown.

After

Width:  |  Height:  |  Size: 218 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

View File

@@ -0,0 +1,20 @@
# SMTP settings using Gmail
This guide walks you through how to get the information you need to set up your Immich instance to send emails using Gmail's SMTP server.
## Create an app password
From your Google account settings
- Add [2-Step Verification](https://support.google.com/accounts/answer/185839) to your Google account (Required)
- [Create an app password](https://myaccount.google.com/apppasswords).
At the end of creating your app passwords, a password will be displayed; save it, it will be used for the password field when setting up the SMTP server in Immich.
<img src={require('./img/google-app-password.webp').default} title="Authorised redirect URIs" />
## Entering the SMTP credential in Immich
Entering your credential in Immich's email notification settings at `Administration -> Settings -> Notification Settings`
<img src={require('./img/email-settings.png').default} width="80%" title="SMTP settings" />

View File

@@ -38,16 +38,19 @@ Regardless of filesystem, it is not recommended to use a network share for your
## General
| Variable | Description | Default | Containers | Workers |
| :------------------------------ | :---------------------------------------------- | :----------------------: | :----------------------- | :----------------- |
| `TZ` | Timezone | | server | microservices |
| `IMMICH_ENV` | Environment (production, development) | `production` | server, machine learning | api, microservices |
| `IMMICH_LOG_LEVEL` | Log Level (verbose, debug, log, warn, error) | `log` | server, machine learning | api, microservices |
| `IMMICH_MEDIA_LOCATION` | Media Location | `./upload`<sup>\*1</sup> | server | api, microservices |
| `IMMICH_CONFIG_FILE` | Path to config file | | server | api, microservices |
| `IMMICH_WEB_ROOT` | Path of root index.html | `/usr/src/app/www` | server | api |
| `IMMICH_REVERSE_GEOCODING_ROOT` | Path of reverse geocoding dump directory | `/usr/src/resources` | server | microservices |
| `NO_COLOR` | Set to `true` to disable color-coded log output | `false` | server, machine learning | |
| Variable | Description | Default | Containers | Workers |
| :---------------------------------- | :---------------------------------------------- | :--------------------------: | :----------------------- | :----------------- |
| `TZ` | Timezone | | server | microservices |
| `IMMICH_ENV` | Environment (production, development) | `production` | server, machine learning | api, microservices |
| `IMMICH_LOG_LEVEL` | Log Level (verbose, debug, log, warn, error) | `log` | server, machine learning | api, microservices |
| `IMMICH_MEDIA_LOCATION` | Media Location | `./upload`<sup>\*1</sup> | server | api, microservices |
| `IMMICH_CONFIG_FILE` | Path to config file | | server | api, microservices |
| `IMMICH_WEB_ROOT` | Path of root index.html | `/usr/src/app/www` | server | api |
| `IMMICH_REVERSE_GEOCODING_ROOT` | Path of reverse geocoding dump directory | `/usr/src/resources` | server | microservices |
| `NO_COLOR` | Set to `true` to disable color-coded log output | `false` | server, machine learning | |
| `CPU_CORES` | Amount of cores available to the immich server | auto-detected cpu core count | server | |
| `IMMICH_API_METRICS_PORT` | Port for the OTEL metrics | `8081` | server | api |
| `IMMICH_MICROSERVICES_METRICS_PORT` | Port for the OTEL metrics | `8082` | server | microservices |
\*1: With the default `WORKDIR` of `/usr/src/app`, this path will resolve to `/usr/src/app/upload`.
It only need to be set if the Immich deployment method is changing.

View File

@@ -27,7 +27,7 @@ For more information about setting up the community image see [here](https://git
:::info
- Guide was written using Unraid v6.12.10
- Guide was written using Unraid v6.12.10.
- Requires you to have installed the plugin: [Docker Compose Manager](https://forums.unraid.net/topic/114415-plugin-docker-compose-manager/)
- An Unraid share created for your images
- There has been a [report](https://forums.unraid.net/topic/130006-errortraps-traps-node27707-trap-invalid-opcode-ip14fcfc8d03c0-sp7fff32889dd8-more/#comment-1189395) of this not working if your Unraid server doesn't support AVX _(e.g. using a T610)_
@@ -46,7 +46,8 @@ alt="Select Plugins > Compose.Manager > Add New Stack > Label it Immich"
/>
3. Select the cog ⚙️ next to Immich then click "**Edit Stack**"
4. Click "**Compose File**" and then paste the entire contents of the [Immich Docker Compose](https://github.com/immich-app/immich/releases/latest/download/docker-compose.yml) file into the Unraid editor. Remove any text that may be in the text area by default.
4. Click "**Compose File**" and then paste the entire contents of the [Immich Docker Compose](https://github.com/immich-app/immich/releases/latest/download/docker-compose.yml) file into the Unraid editor. Remove any text that may be in the text area by default. Note that Unraid v6.12.10 uses version 24.0.9 of the Docker Engine, which does not support healthcheck `start_interval` as defined in the `database` service of the Docker compose file (version 25 or higher is needed). This parameter defines an initial waiting period before starting health checks, to give the container time to start up. Commenting out the `start_interval` and `start_period` parameters will allow the containers to start up normally. The only downside to this is that the database container will not receive an initial health check until `interval` time has passed.
<details >
<summary>Using an existing Postgres container? Click me! Otherwise proceed to step 5.</summary>
<ul>
@@ -70,6 +71,7 @@ alt="Select Plugins > Compose.Manager > Add New Stack > Label it Immich"
/>
</ul>
</details>
5. Click "**Save Changes**", you will be promoted to edit stack UI labels, just leave this blank and click "**Ok**"
6. Select the cog ⚙️ next to Immich, click "**Edit Stack**", then click "**Env File**"
7. Paste the entire contents of the [Immich example.env](https://github.com/immich-app/immich/releases/latest/download/example.env) file into the Unraid editor, then **before saving** edit the following:

View File

@@ -13,4 +13,4 @@ Running into an issue or have a question? Try the following:
[github-issues]: https://github.com/immich-app/immich/issues
[github-releases]: https://github.com/immich-app/immich/releases
[discord-link]: https://discord.com/invite/D8JsnBEuKb
[discord-link]: https://discord.immich.app

View File

@@ -5,21 +5,21 @@ sidebar_position: 3
# Quick Start
Here is a quick, no-choices path to install Immich and take it for a test drive.
Once you've tried it, perhaps you'll use one of the many other ways
Once you've tried it, you might use one of the many other ways
to install and use it.
## Requirements
Check the [requirements page](/docs/install/requirements) to get started.
## Install and launch via Docker Compose
## Install and Launch via Docker Compose
Follow the [Docker Compose (Recommended)](/docs/install/docker-compose) instructions
to install the server.
- Where random passwords are required, `pwgen` is a handy utility.
- `UPLOAD_LOCATION` should be set to some new directory on the server
with free space.
with enough free space.
- You may ignore "Step 4 - Upgrading".
## Try the Web UI
@@ -48,26 +48,26 @@ import MobileAppLogin from '/docs/partials/_mobile-app-login.md';
In the mobile app, you should see the photo you uploaded from the web UI.
### Transfer Photos from your Mobile Device
### Transfer Photos from Your Mobile Device
import MobileAppBackup from '/docs/partials/_mobile-app-backup.md';
<MobileAppBackup />
Depending on how many photos are on your mobile device, this backup may
The backup time differs depending on how many photos are on your mobile device. Large uploads may
take quite a while.
You can select the Jobs tab to see Immich processing your photos.
You can select the **Jobs** tab to see Immich processing your photos.
<img src={require('/docs/guides/img/jobs-tab.png').default} title="Jobs tab" />
## Set up your backups
## Set up Your Backups
You may want to back up the content of your Immich instance
along with other parts of your server; be sure to read about
[database backup](/docs/administration/backup-and-restore).
## Where to go from here?
## Where to Go From Here
You may decide you'd like to install the server a different way;
the Install category on the left menu provides many options.

View File

@@ -4,11 +4,17 @@ sidebar_position: 5
# Support The Project
## Contributing
## Report issues
1. Testing - Using Immich and reporting bugs is a great way to help support the project. Found a bug? [Open an issue on GitHub][github-issue].
1. Translations - The Immich mobile app has been translated into [17 languages][github-langs] so far! To contribute with translations, email me at alex.tran1502@gmail.com or send me a message on discord.
1. Development - If you are a programmer or developer, take a look at Immich's [technology stack](/docs/developer/architecture.mdx) and consider fixing bugs or building new features. The team and I are always looking for new contributors. For information about how to contribute as a developer, see the [Developer](/docs/developer/architecture.mdx) section.
By far the easiest way to help make Immich better it to use it and report issues and bugs. Found a bug? [Open an issue on GitHub][github-issue].
## Translations
Support the project by localizing on [Weblate](https://hosted.weblate.org/projects/immich/immich/). For more information, see the [Translations](/docs/developer/translations) section.
## Development
If you are a programmer or developer, take a look at Immich's [technology stack](/docs/developer/architecture.mdx) and consider fixing bugs or building new features. The team and I are always looking for new contributors. For information about how to contribute as a developer, see the [Developer](/docs/developer/architecture.mdx) section.
[github-issue]: https://github.com/immich-app/immich/issues/new/choose
[github-langs]: https://github.com/immich-app/immich/tree/main/mobile/assets/i18n

View File

@@ -94,6 +94,10 @@ const config = {
srcDark: 'img/immich-logo-inline-dark.png',
},
items: [
{
type: 'custom-versionSwitcher',
position: 'right',
},
{
to: '/docs/overview/introduction',
position: 'right',
@@ -120,7 +124,7 @@ const config = {
position: 'right',
},
{
href: 'https://discord.gg/D8JsnBEuKb',
href: 'https://discord.immich.app',
label: 'Discord',
position: 'right',
},
@@ -147,7 +151,7 @@ const config = {
items: [
{
label: 'Discord',
href: 'https://discord.com/invite/D8JsnBEuKb',
href: 'https://discord.immich.app',
},
{
label: 'Reddit',

363
docs/package-lock.json generated
View File

@@ -2155,9 +2155,10 @@
}
},
"node_modules/@docusaurus/core": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/@docusaurus/core/-/core-3.3.2.tgz",
"integrity": "sha512-PzKMydKI3IU1LmeZQDi+ut5RSuilbXnA8QdowGeJEgU8EJjmx3rBHNT1LxQxOVqNEwpWi/csLwd9bn7rUjggPA==",
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/@docusaurus/core/-/core-3.4.0.tgz",
"integrity": "sha512-g+0wwmN2UJsBqy2fQRQ6fhXruoEa62JDeEa5d8IdTJlMoaDaEDfHh7WjwGRn4opuTQWpjAwP/fbcgyHKlE+64w==",
"license": "MIT",
"dependencies": {
"@babel/core": "^7.23.3",
"@babel/generator": "^7.23.3",
@@ -2169,12 +2170,12 @@
"@babel/runtime": "^7.22.6",
"@babel/runtime-corejs3": "^7.22.6",
"@babel/traverse": "^7.22.8",
"@docusaurus/cssnano-preset": "3.3.2",
"@docusaurus/logger": "3.3.2",
"@docusaurus/mdx-loader": "3.3.2",
"@docusaurus/utils": "3.3.2",
"@docusaurus/utils-common": "3.3.2",
"@docusaurus/utils-validation": "3.3.2",
"@docusaurus/cssnano-preset": "3.4.0",
"@docusaurus/logger": "3.4.0",
"@docusaurus/mdx-loader": "3.4.0",
"@docusaurus/utils": "3.4.0",
"@docusaurus/utils-common": "3.4.0",
"@docusaurus/utils-validation": "3.4.0",
"autoprefixer": "^10.4.14",
"babel-loader": "^9.1.3",
"babel-plugin-dynamic-import-node": "^2.3.3",
@@ -2240,9 +2241,10 @@
}
},
"node_modules/@docusaurus/cssnano-preset": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/@docusaurus/cssnano-preset/-/cssnano-preset-3.3.2.tgz",
"integrity": "sha512-+5+epLk/Rp4vFML4zmyTATNc3Is+buMAL6dNjrMWahdJCJlMWMPd/8YfU+2PA57t8mlSbhLJ7vAZVy54cd1vRQ==",
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/@docusaurus/cssnano-preset/-/cssnano-preset-3.4.0.tgz",
"integrity": "sha512-qwLFSz6v/pZHy/UP32IrprmH5ORce86BGtN0eBtG75PpzQJAzp9gefspox+s8IEOr0oZKuQ/nhzZ3xwyc3jYJQ==",
"license": "MIT",
"dependencies": {
"cssnano-preset-advanced": "^6.1.2",
"postcss": "^8.4.38",
@@ -2254,9 +2256,10 @@
}
},
"node_modules/@docusaurus/logger": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/@docusaurus/logger/-/logger-3.3.2.tgz",
"integrity": "sha512-Ldu38GJ4P8g4guN7d7pyCOJ7qQugG7RVyaxrK8OnxuTlaImvQw33aDRwaX2eNmX8YK6v+//Z502F4sOZbHHCHQ==",
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/@docusaurus/logger/-/logger-3.4.0.tgz",
"integrity": "sha512-bZwkX+9SJ8lB9kVRkXw+xvHYSMGG4bpYHKGXeXFvyVc79NMeeBSGgzd4TQLHH+DYeOJoCdl8flrFJVxlZ0wo/Q==",
"license": "MIT",
"dependencies": {
"chalk": "^4.1.2",
"tslib": "^2.6.0"
@@ -2266,13 +2269,14 @@
}
},
"node_modules/@docusaurus/mdx-loader": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/@docusaurus/mdx-loader/-/mdx-loader-3.3.2.tgz",
"integrity": "sha512-AFRxj/aOk3/mfYDPxE3wTbrjeayVRvNSZP7mgMuUlrb2UlPRbSVAFX1k2RbgAJrnTSwMgb92m2BhJgYRfptN3g==",
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/@docusaurus/mdx-loader/-/mdx-loader-3.4.0.tgz",
"integrity": "sha512-kSSbrrk4nTjf4d+wtBA9H+FGauf2gCax89kV8SUSJu3qaTdSIKdWERlngsiHaCFgZ7laTJ8a67UFf+xlFPtuTw==",
"license": "MIT",
"dependencies": {
"@docusaurus/logger": "3.3.2",
"@docusaurus/utils": "3.3.2",
"@docusaurus/utils-validation": "3.3.2",
"@docusaurus/logger": "3.4.0",
"@docusaurus/utils": "3.4.0",
"@docusaurus/utils-validation": "3.4.0",
"@mdx-js/mdx": "^3.0.0",
"@slorber/remark-comment": "^1.0.0",
"escape-html": "^1.0.3",
@@ -2304,11 +2308,12 @@
}
},
"node_modules/@docusaurus/module-type-aliases": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/@docusaurus/module-type-aliases/-/module-type-aliases-3.3.2.tgz",
"integrity": "sha512-b/XB0TBJah5yKb4LYuJT4buFvL0MGAb0+vJDrJtlYMguRtsEBkf2nWl5xP7h4Dlw6ol0hsHrCYzJ50kNIOEclw==",
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/@docusaurus/module-type-aliases/-/module-type-aliases-3.4.0.tgz",
"integrity": "sha512-A1AyS8WF5Bkjnb8s+guTDuYmUiwJzNrtchebBHpc0gz0PyHJNMaybUlSrmJjHVcGrya0LKI4YcR3lBDQfXRYLw==",
"license": "MIT",
"dependencies": {
"@docusaurus/types": "3.3.2",
"@docusaurus/types": "3.4.0",
"@types/history": "^4.7.11",
"@types/react": "*",
"@types/react-router-config": "*",
@@ -2322,17 +2327,18 @@
}
},
"node_modules/@docusaurus/plugin-content-blog": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-blog/-/plugin-content-blog-3.3.2.tgz",
"integrity": "sha512-fJU+dmqp231LnwDJv+BHVWft8pcUS2xVPZdeYH6/ibH1s2wQ/sLcmUrGWyIv/Gq9Ptj8XWjRPMghlxghuPPoxg==",
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-blog/-/plugin-content-blog-3.4.0.tgz",
"integrity": "sha512-vv6ZAj78ibR5Jh7XBUT4ndIjmlAxkijM3Sx5MAAzC1gyv0vupDQNhzuFg1USQmQVj3P5I6bquk12etPV3LJ+Xw==",
"license": "MIT",
"dependencies": {
"@docusaurus/core": "3.3.2",
"@docusaurus/logger": "3.3.2",
"@docusaurus/mdx-loader": "3.3.2",
"@docusaurus/types": "3.3.2",
"@docusaurus/utils": "3.3.2",
"@docusaurus/utils-common": "3.3.2",
"@docusaurus/utils-validation": "3.3.2",
"@docusaurus/core": "3.4.0",
"@docusaurus/logger": "3.4.0",
"@docusaurus/mdx-loader": "3.4.0",
"@docusaurus/types": "3.4.0",
"@docusaurus/utils": "3.4.0",
"@docusaurus/utils-common": "3.4.0",
"@docusaurus/utils-validation": "3.4.0",
"cheerio": "^1.0.0-rc.12",
"feed": "^4.2.2",
"fs-extra": "^11.1.1",
@@ -2353,18 +2359,19 @@
}
},
"node_modules/@docusaurus/plugin-content-docs": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-docs/-/plugin-content-docs-3.3.2.tgz",
"integrity": "sha512-Dm1ri2VlGATTN3VGk1ZRqdRXWa1UlFubjaEL6JaxaK7IIFqN/Esjpl+Xw10R33loHcRww/H76VdEeYayaL76eg==",
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-docs/-/plugin-content-docs-3.4.0.tgz",
"integrity": "sha512-HkUCZffhBo7ocYheD9oZvMcDloRnGhBMOZRyVcAQRFmZPmNqSyISlXA1tQCIxW+r478fty97XXAGjNYzBjpCsg==",
"license": "MIT",
"dependencies": {
"@docusaurus/core": "3.3.2",
"@docusaurus/logger": "3.3.2",
"@docusaurus/mdx-loader": "3.3.2",
"@docusaurus/module-type-aliases": "3.3.2",
"@docusaurus/types": "3.3.2",
"@docusaurus/utils": "3.3.2",
"@docusaurus/utils-common": "3.3.2",
"@docusaurus/utils-validation": "3.3.2",
"@docusaurus/core": "3.4.0",
"@docusaurus/logger": "3.4.0",
"@docusaurus/mdx-loader": "3.4.0",
"@docusaurus/module-type-aliases": "3.4.0",
"@docusaurus/types": "3.4.0",
"@docusaurus/utils": "3.4.0",
"@docusaurus/utils-common": "3.4.0",
"@docusaurus/utils-validation": "3.4.0",
"@types/react-router-config": "^5.0.7",
"combine-promises": "^1.1.0",
"fs-extra": "^11.1.1",
@@ -2383,15 +2390,16 @@
}
},
"node_modules/@docusaurus/plugin-content-pages": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-pages/-/plugin-content-pages-3.3.2.tgz",
"integrity": "sha512-EKc9fQn5H2+OcGER8x1aR+7URtAGWySUgULfqE/M14+rIisdrBstuEZ4lUPDRrSIexOVClML82h2fDS+GSb8Ew==",
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-pages/-/plugin-content-pages-3.4.0.tgz",
"integrity": "sha512-h2+VN/0JjpR8fIkDEAoadNjfR3oLzB+v1qSXbIAKjQ46JAHx3X22n9nqS+BWSQnTnp1AjkjSvZyJMekmcwxzxg==",
"license": "MIT",
"dependencies": {
"@docusaurus/core": "3.3.2",
"@docusaurus/mdx-loader": "3.3.2",
"@docusaurus/types": "3.3.2",
"@docusaurus/utils": "3.3.2",
"@docusaurus/utils-validation": "3.3.2",
"@docusaurus/core": "3.4.0",
"@docusaurus/mdx-loader": "3.4.0",
"@docusaurus/types": "3.4.0",
"@docusaurus/utils": "3.4.0",
"@docusaurus/utils-validation": "3.4.0",
"fs-extra": "^11.1.1",
"tslib": "^2.6.0",
"webpack": "^5.88.1"
@@ -2405,13 +2413,14 @@
}
},
"node_modules/@docusaurus/plugin-debug": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/@docusaurus/plugin-debug/-/plugin-debug-3.3.2.tgz",
"integrity": "sha512-oBIBmwtaB+YS0XlmZ3gCO+cMbsGvIYuAKkAopoCh0arVjtlyPbejzPrHuCoRHB9G7abjNZw7zoONOR8+8LM5+Q==",
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/@docusaurus/plugin-debug/-/plugin-debug-3.4.0.tgz",
"integrity": "sha512-uV7FDUNXGyDSD3PwUaf5YijX91T5/H9SX4ErEcshzwgzWwBtK37nUWPU3ZLJfeTavX3fycTOqk9TglpOLaWkCg==",
"license": "MIT",
"dependencies": {
"@docusaurus/core": "3.3.2",
"@docusaurus/types": "3.3.2",
"@docusaurus/utils": "3.3.2",
"@docusaurus/core": "3.4.0",
"@docusaurus/types": "3.4.0",
"@docusaurus/utils": "3.4.0",
"fs-extra": "^11.1.1",
"react-json-view-lite": "^1.2.0",
"tslib": "^2.6.0"
@@ -2425,13 +2434,14 @@
}
},
"node_modules/@docusaurus/plugin-google-analytics": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-analytics/-/plugin-google-analytics-3.3.2.tgz",
"integrity": "sha512-jXhrEIhYPSClMBK6/IA8qf1/FBoxqGXZvg7EuBax9HaK9+kL3L0TJIlatd8jQJOMtds8mKw806TOCc3rtEad1A==",
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-analytics/-/plugin-google-analytics-3.4.0.tgz",
"integrity": "sha512-mCArluxEGi3cmYHqsgpGGt3IyLCrFBxPsxNZ56Mpur0xSlInnIHoeLDH7FvVVcPJRPSQ9/MfRqLsainRw+BojA==",
"license": "MIT",
"dependencies": {
"@docusaurus/core": "3.3.2",
"@docusaurus/types": "3.3.2",
"@docusaurus/utils-validation": "3.3.2",
"@docusaurus/core": "3.4.0",
"@docusaurus/types": "3.4.0",
"@docusaurus/utils-validation": "3.4.0",
"tslib": "^2.6.0"
},
"engines": {
@@ -2443,13 +2453,14 @@
}
},
"node_modules/@docusaurus/plugin-google-gtag": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-gtag/-/plugin-google-gtag-3.3.2.tgz",
"integrity": "sha512-vcrKOHGbIDjVnNMrfbNpRQR1x6Jvcrb48kVzpBAOsKbj9rXZm/idjVAXRaewwobHdOrJkfWS/UJoxzK8wyLRBQ==",
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-gtag/-/plugin-google-gtag-3.4.0.tgz",
"integrity": "sha512-Dsgg6PLAqzZw5wZ4QjUYc8Z2KqJqXxHxq3vIoyoBWiLEEfigIs7wHR+oiWUQy3Zk9MIk6JTYj7tMoQU0Jm3nqA==",
"license": "MIT",
"dependencies": {
"@docusaurus/core": "3.3.2",
"@docusaurus/types": "3.3.2",
"@docusaurus/utils-validation": "3.3.2",
"@docusaurus/core": "3.4.0",
"@docusaurus/types": "3.4.0",
"@docusaurus/utils-validation": "3.4.0",
"@types/gtag.js": "^0.0.12",
"tslib": "^2.6.0"
},
@@ -2462,13 +2473,14 @@
}
},
"node_modules/@docusaurus/plugin-google-tag-manager": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-tag-manager/-/plugin-google-tag-manager-3.3.2.tgz",
"integrity": "sha512-ldkR58Fdeks0vC+HQ+L+bGFSJsotQsipXD+iKXQFvkOfmPIV6QbHRd7IIcm5b6UtwOiK33PylNS++gjyLUmaGw==",
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-tag-manager/-/plugin-google-tag-manager-3.4.0.tgz",
"integrity": "sha512-O9tX1BTwxIhgXpOLpFDueYA9DWk69WCbDRrjYoMQtFHSkTyE7RhNgyjSPREUWJb9i+YUg3OrsvrBYRl64FCPCQ==",
"license": "MIT",
"dependencies": {
"@docusaurus/core": "3.3.2",
"@docusaurus/types": "3.3.2",
"@docusaurus/utils-validation": "3.3.2",
"@docusaurus/core": "3.4.0",
"@docusaurus/types": "3.4.0",
"@docusaurus/utils-validation": "3.4.0",
"tslib": "^2.6.0"
},
"engines": {
@@ -2480,16 +2492,17 @@
}
},
"node_modules/@docusaurus/plugin-sitemap": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/@docusaurus/plugin-sitemap/-/plugin-sitemap-3.3.2.tgz",
"integrity": "sha512-/ZI1+bwZBhAgC30inBsHe3qY9LOZS+79fRGkNdTcGHRMcdAp6Vw2pCd1gzlxd/xU+HXsNP6cLmTOrggmRp3Ujg==",
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/@docusaurus/plugin-sitemap/-/plugin-sitemap-3.4.0.tgz",
"integrity": "sha512-+0VDvx9SmNrFNgwPoeoCha+tRoAjopwT0+pYO1xAbyLcewXSemq+eLxEa46Q1/aoOaJQ0qqHELuQM7iS2gp33Q==",
"license": "MIT",
"dependencies": {
"@docusaurus/core": "3.3.2",
"@docusaurus/logger": "3.3.2",
"@docusaurus/types": "3.3.2",
"@docusaurus/utils": "3.3.2",
"@docusaurus/utils-common": "3.3.2",
"@docusaurus/utils-validation": "3.3.2",
"@docusaurus/core": "3.4.0",
"@docusaurus/logger": "3.4.0",
"@docusaurus/types": "3.4.0",
"@docusaurus/utils": "3.4.0",
"@docusaurus/utils-common": "3.4.0",
"@docusaurus/utils-validation": "3.4.0",
"fs-extra": "^11.1.1",
"sitemap": "^7.1.1",
"tslib": "^2.6.0"
@@ -2503,23 +2516,24 @@
}
},
"node_modules/@docusaurus/preset-classic": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/@docusaurus/preset-classic/-/preset-classic-3.3.2.tgz",
"integrity": "sha512-1SDS7YIUN1Pg3BmD6TOTjhB7RSBHJRpgIRKx9TpxqyDrJ92sqtZhomDc6UYoMMLQNF2wHFZZVGFjxJhw2VpL+Q==",
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/@docusaurus/preset-classic/-/preset-classic-3.4.0.tgz",
"integrity": "sha512-Ohj6KB7siKqZaQhNJVMBBUzT3Nnp6eTKqO+FXO3qu/n1hJl3YLwVKTWBg28LF7MWrKu46UuYavwMRxud0VyqHg==",
"license": "MIT",
"dependencies": {
"@docusaurus/core": "3.3.2",
"@docusaurus/plugin-content-blog": "3.3.2",
"@docusaurus/plugin-content-docs": "3.3.2",
"@docusaurus/plugin-content-pages": "3.3.2",
"@docusaurus/plugin-debug": "3.3.2",
"@docusaurus/plugin-google-analytics": "3.3.2",
"@docusaurus/plugin-google-gtag": "3.3.2",
"@docusaurus/plugin-google-tag-manager": "3.3.2",
"@docusaurus/plugin-sitemap": "3.3.2",
"@docusaurus/theme-classic": "3.3.2",
"@docusaurus/theme-common": "3.3.2",
"@docusaurus/theme-search-algolia": "3.3.2",
"@docusaurus/types": "3.3.2"
"@docusaurus/core": "3.4.0",
"@docusaurus/plugin-content-blog": "3.4.0",
"@docusaurus/plugin-content-docs": "3.4.0",
"@docusaurus/plugin-content-pages": "3.4.0",
"@docusaurus/plugin-debug": "3.4.0",
"@docusaurus/plugin-google-analytics": "3.4.0",
"@docusaurus/plugin-google-gtag": "3.4.0",
"@docusaurus/plugin-google-tag-manager": "3.4.0",
"@docusaurus/plugin-sitemap": "3.4.0",
"@docusaurus/theme-classic": "3.4.0",
"@docusaurus/theme-common": "3.4.0",
"@docusaurus/theme-search-algolia": "3.4.0",
"@docusaurus/types": "3.4.0"
},
"engines": {
"node": ">=18.0"
@@ -2530,22 +2544,23 @@
}
},
"node_modules/@docusaurus/theme-classic": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/@docusaurus/theme-classic/-/theme-classic-3.3.2.tgz",
"integrity": "sha512-gepHFcsluIkPb4Im9ukkiO4lXrai671wzS3cKQkY9BXQgdVwsdPf/KS0Vs4Xlb0F10fTz+T3gNjkxNEgSN9M0A==",
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/@docusaurus/theme-classic/-/theme-classic-3.4.0.tgz",
"integrity": "sha512-0IPtmxsBYv2adr1GnZRdMkEQt1YW6tpzrUPj02YxNpvJ5+ju4E13J5tB4nfdaen/tfR1hmpSPlTFPvTf4kwy8Q==",
"license": "MIT",
"dependencies": {
"@docusaurus/core": "3.3.2",
"@docusaurus/mdx-loader": "3.3.2",
"@docusaurus/module-type-aliases": "3.3.2",
"@docusaurus/plugin-content-blog": "3.3.2",
"@docusaurus/plugin-content-docs": "3.3.2",
"@docusaurus/plugin-content-pages": "3.3.2",
"@docusaurus/theme-common": "3.3.2",
"@docusaurus/theme-translations": "3.3.2",
"@docusaurus/types": "3.3.2",
"@docusaurus/utils": "3.3.2",
"@docusaurus/utils-common": "3.3.2",
"@docusaurus/utils-validation": "3.3.2",
"@docusaurus/core": "3.4.0",
"@docusaurus/mdx-loader": "3.4.0",
"@docusaurus/module-type-aliases": "3.4.0",
"@docusaurus/plugin-content-blog": "3.4.0",
"@docusaurus/plugin-content-docs": "3.4.0",
"@docusaurus/plugin-content-pages": "3.4.0",
"@docusaurus/theme-common": "3.4.0",
"@docusaurus/theme-translations": "3.4.0",
"@docusaurus/types": "3.4.0",
"@docusaurus/utils": "3.4.0",
"@docusaurus/utils-common": "3.4.0",
"@docusaurus/utils-validation": "3.4.0",
"@mdx-js/react": "^3.0.0",
"clsx": "^2.0.0",
"copy-text-to-clipboard": "^3.2.0",
@@ -2569,17 +2584,18 @@
}
},
"node_modules/@docusaurus/theme-common": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/@docusaurus/theme-common/-/theme-common-3.3.2.tgz",
"integrity": "sha512-kXqSaL/sQqo4uAMQ4fHnvRZrH45Xz2OdJ3ABXDS7YVGPSDTBC8cLebFrRR4YF9EowUHto1UC/EIklJZQMG/usA==",
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/@docusaurus/theme-common/-/theme-common-3.4.0.tgz",
"integrity": "sha512-0A27alXuv7ZdCg28oPE8nH/Iz73/IUejVaCazqu9elS4ypjiLhK3KfzdSQBnL/g7YfHSlymZKdiOHEo8fJ0qMA==",
"license": "MIT",
"dependencies": {
"@docusaurus/mdx-loader": "3.3.2",
"@docusaurus/module-type-aliases": "3.3.2",
"@docusaurus/plugin-content-blog": "3.3.2",
"@docusaurus/plugin-content-docs": "3.3.2",
"@docusaurus/plugin-content-pages": "3.3.2",
"@docusaurus/utils": "3.3.2",
"@docusaurus/utils-common": "3.3.2",
"@docusaurus/mdx-loader": "3.4.0",
"@docusaurus/module-type-aliases": "3.4.0",
"@docusaurus/plugin-content-blog": "3.4.0",
"@docusaurus/plugin-content-docs": "3.4.0",
"@docusaurus/plugin-content-pages": "3.4.0",
"@docusaurus/utils": "3.4.0",
"@docusaurus/utils-common": "3.4.0",
"@types/history": "^4.7.11",
"@types/react": "*",
"@types/react-router-config": "*",
@@ -2598,18 +2614,19 @@
}
},
"node_modules/@docusaurus/theme-search-algolia": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/@docusaurus/theme-search-algolia/-/theme-search-algolia-3.3.2.tgz",
"integrity": "sha512-qLkfCl29VNBnF1MWiL9IyOQaHxUvicZp69hISyq/xMsNvFKHFOaOfk9xezYod2Q9xx3xxUh9t/QPigIei2tX4w==",
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/@docusaurus/theme-search-algolia/-/theme-search-algolia-3.4.0.tgz",
"integrity": "sha512-aiHFx7OCw4Wck1z6IoShVdUWIjntC8FHCw9c5dR8r3q4Ynh+zkS8y2eFFunN/DL6RXPzpnvKCg3vhLQYJDmT9Q==",
"license": "MIT",
"dependencies": {
"@docsearch/react": "^3.5.2",
"@docusaurus/core": "3.3.2",
"@docusaurus/logger": "3.3.2",
"@docusaurus/plugin-content-docs": "3.3.2",
"@docusaurus/theme-common": "3.3.2",
"@docusaurus/theme-translations": "3.3.2",
"@docusaurus/utils": "3.3.2",
"@docusaurus/utils-validation": "3.3.2",
"@docusaurus/core": "3.4.0",
"@docusaurus/logger": "3.4.0",
"@docusaurus/plugin-content-docs": "3.4.0",
"@docusaurus/theme-common": "3.4.0",
"@docusaurus/theme-translations": "3.4.0",
"@docusaurus/utils": "3.4.0",
"@docusaurus/utils-validation": "3.4.0",
"algoliasearch": "^4.18.0",
"algoliasearch-helper": "^3.13.3",
"clsx": "^2.0.0",
@@ -2628,9 +2645,10 @@
}
},
"node_modules/@docusaurus/theme-translations": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/@docusaurus/theme-translations/-/theme-translations-3.3.2.tgz",
"integrity": "sha512-bPuiUG7Z8sNpGuTdGnmKl/oIPeTwKr0AXLGu9KaP6+UFfRZiyWbWE87ti97RrevB2ffojEdvchNujparR3jEZQ==",
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/@docusaurus/theme-translations/-/theme-translations-3.4.0.tgz",
"integrity": "sha512-zSxCSpmQCCdQU5Q4CnX/ID8CSUUI3fvmq4hU/GNP/XoAWtXo9SAVnM3TzpU8Gb//H3WCsT8mJcTfyOk3d9ftNg==",
"license": "MIT",
"dependencies": {
"fs-extra": "^11.1.1",
"tslib": "^2.6.0"
@@ -2640,9 +2658,10 @@
}
},
"node_modules/@docusaurus/types": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/@docusaurus/types/-/types-3.3.2.tgz",
"integrity": "sha512-5p201S7AZhliRxTU7uMKtSsoC8mgPA9bs9b5NQg1IRdRxJfflursXNVsgc3PcMqiUTul/v1s3k3rXXFlRE890w==",
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/@docusaurus/types/-/types-3.4.0.tgz",
"integrity": "sha512-4jcDO8kXi5Cf9TcyikB/yKmz14f2RZ2qTRerbHAsS+5InE9ZgSLBNLsewtFTcTOXSVcbU3FoGOzcNWAmU1TR0A==",
"license": "MIT",
"dependencies": {
"@mdx-js/mdx": "^3.0.0",
"@types/history": "^4.7.11",
@@ -2660,12 +2679,13 @@
}
},
"node_modules/@docusaurus/utils": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/@docusaurus/utils/-/utils-3.3.2.tgz",
"integrity": "sha512-f4YMnBVymtkSxONv4Y8js3Gez9IgHX+Lcg6YRMOjVbq8sgCcdYK1lf6SObAuz5qB/mxiSK7tW0M9aaiIaUSUJg==",
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/@docusaurus/utils/-/utils-3.4.0.tgz",
"integrity": "sha512-fRwnu3L3nnWaXOgs88BVBmG1yGjcQqZNHG+vInhEa2Sz2oQB+ZjbEMO5Rh9ePFpZ0YDiDUhpaVjwmS+AU2F14g==",
"license": "MIT",
"dependencies": {
"@docusaurus/logger": "3.3.2",
"@docusaurus/utils-common": "3.3.2",
"@docusaurus/logger": "3.4.0",
"@docusaurus/utils-common": "3.4.0",
"@svgr/webpack": "^8.1.0",
"escape-string-regexp": "^4.0.0",
"file-loader": "^6.2.0",
@@ -2682,6 +2702,7 @@
"shelljs": "^0.8.5",
"tslib": "^2.6.0",
"url-loader": "^4.1.1",
"utility-types": "^3.10.0",
"webpack": "^5.88.1"
},
"engines": {
@@ -2697,9 +2718,10 @@
}
},
"node_modules/@docusaurus/utils-common": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/@docusaurus/utils-common/-/utils-common-3.3.2.tgz",
"integrity": "sha512-QWFTLEkPYsejJsLStgtmetMFIA3pM8EPexcZ4WZ7b++gO5jGVH7zsipREnCHzk6+eDgeaXfkR6UPaTt86bp8Og==",
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/@docusaurus/utils-common/-/utils-common-3.4.0.tgz",
"integrity": "sha512-NVx54Wr4rCEKsjOH5QEVvxIqVvm+9kh7q8aYTU5WzUU9/Hctd6aTrcZ3G0Id4zYJ+AeaG5K5qHA4CY5Kcm2iyQ==",
"license": "MIT",
"dependencies": {
"tslib": "^2.6.0"
},
@@ -2716,15 +2738,18 @@
}
},
"node_modules/@docusaurus/utils-validation": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/@docusaurus/utils-validation/-/utils-validation-3.3.2.tgz",
"integrity": "sha512-itDgFs5+cbW9REuC7NdXals4V6++KifgVMzoGOOOSIifBQw+8ULhy86u5e1lnptVL0sv8oAjq2alO7I40GR7pA==",
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/@docusaurus/utils-validation/-/utils-validation-3.4.0.tgz",
"integrity": "sha512-hYQ9fM+AXYVTWxJOT1EuNaRnrR2WGpRdLDQG07O8UOpsvCPWUVOeo26Rbm0JWY2sGLfzAb+tvJ62yF+8F+TV0g==",
"license": "MIT",
"dependencies": {
"@docusaurus/logger": "3.3.2",
"@docusaurus/utils": "3.3.2",
"@docusaurus/utils-common": "3.3.2",
"@docusaurus/logger": "3.4.0",
"@docusaurus/utils": "3.4.0",
"@docusaurus/utils-common": "3.4.0",
"fs-extra": "^11.2.0",
"joi": "^17.9.2",
"js-yaml": "^4.1.0",
"lodash": "^4.17.21",
"tslib": "^2.6.0"
},
"engines": {
@@ -13573,10 +13598,11 @@
}
},
"node_modules/prettier": {
"version": "3.2.5",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz",
"integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==",
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.2.tgz",
"integrity": "sha512-rAVeHYMcv8ATV5d508CFdn+8/pHPpXeIid1DdrPwXnaAdH7cqjVbpJaT5eq4yRAFU/lsbwYwSF/n5iNrdJHPQA==",
"dev": true,
"license": "MIT",
"bin": {
"prettier": "bin/prettier.cjs"
},
@@ -15986,9 +16012,10 @@
}
},
"node_modules/tailwindcss": {
"version": "3.4.3",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.3.tgz",
"integrity": "sha512-U7sxQk/n397Bmx4JHbJx/iSOOv5G+II3f1kpLpY2QeUv5DcPdcTsYLlusZfq1NthHS1c1cZoyFmmkex1rzke0A==",
"version": "3.4.4",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.4.tgz",
"integrity": "sha512-ZoyXOdJjISB7/BcLTR6SEsLgKtDStYyYZVLsUtWChO4Ps20CBad7lfJKVDiejocV4ME1hLmyY0WJE3hSDcmQ2A==",
"license": "MIT",
"dependencies": {
"@alloc/quick-lru": "^5.2.0",
"arg": "^5.0.2",
@@ -16025,6 +16052,7 @@
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
"integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
"license": "ISC",
"dependencies": {
"is-glob": "^4.0.3"
},
@@ -16346,9 +16374,10 @@
}
},
"node_modules/typescript": {
"version": "5.4.5",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz",
"integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==",
"version": "5.5.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.2.tgz",
"integrity": "sha512-NcRtPEOsPFFWjobJEtfihkLCZCXZt/os3zf8nTxjVH3RvTSxjrCamJpbExGvYOF+tFHc3pA65qpdwPbzjohhew==",
"license": "Apache-2.0",
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"

View File

@@ -56,6 +56,6 @@
"node": ">=20"
},
"volta": {
"node": "20.14.0"
"node": "20.15.0"
}
}

View File

@@ -38,6 +38,11 @@ const guides: CommunityGuidesProps[] = [
description: 'Import your Google Photos files into Immich and add your albums',
url: 'https://github.com/immich-app/immich/discussions/1340',
},
{
title: 'Access Immich with custom domain',
description: 'Access your local Immich installation over the internet using your own domain',
url: 'https://github.com/ppr88/immich-guides/blob/main/open-immich-custom-domain.md',
},
];
function CommunityGuide({ title, description, url }: CommunityGuidesProps): JSX.Element {

View File

@@ -0,0 +1,59 @@
import '@docusaurus/theme-classic/lib/theme/Unlisted/index';
import { useWindowSize } from '@docusaurus/theme-common';
import DropdownNavbarItem from '@theme/NavbarItem/DropdownNavbarItem';
import React, { useEffect, useState } from 'react';
export default function VersionSwitcher(): JSX.Element {
const [versions, setVersions] = useState([]);
const [label, setLabel] = useState('Versions');
const windowSize = useWindowSize();
useEffect(() => {
async function getVersions() {
try {
let baseUrl = 'https://immich.app';
if (window.location.origin === 'http://localhost:3005') {
baseUrl = window.location.origin;
}
const response = await fetch(`${baseUrl}/archived-versions.json`);
const archiveVersions = await response.json();
const allVersions = [
{ label: 'Next', url: 'https://main.preview.immich.app' },
{ label: 'Latest', url: 'https://immich.app' },
...archiveVersions,
];
setVersions(allVersions);
const activeVersion = allVersions.find((version) => new URL(version.url).origin === window.location.origin);
if (activeVersion) {
setLabel(activeVersion.label);
}
} catch (error) {
console.error('Failed to fetch versions', error);
}
}
if (versions.length === 0) {
getVersions();
}
}, []);
return (
versions.length > 0 && (
<DropdownNavbarItem
className="navbar__item"
label={label}
mobile={windowSize === 'mobile'}
items={versions.map(({ label, url }) => ({
label,
to: url,
target: '_self',
}))}
/>
)
);
}

View File

@@ -36,7 +36,7 @@ function HomepageHeader() {
<Link
className="flex place-items-center place-content-center py-3 px-8 border bg-immich-dark-primary dark:bg-immich-primary rounded-full hover:no-underline text-immich-primary dark:text-immich-dark-bg font-bold uppercase"
to="https://discord.gg/D8JsnBEuKb"
to="https://discord.immich.app"
>
Discord
</Link>

View File

@@ -14,6 +14,7 @@ import {
mdiCheckboxMarked,
mdiCloudUploadOutline,
mdiCollage,
mdiContentDuplicate,
mdiDevices,
mdiEmailOutline,
mdiExpansionCard,
@@ -28,12 +29,14 @@ import {
mdiForum,
mdiHandshakeOutline,
mdiHeart,
mdiHistory,
mdiImage,
mdiImageAlbum,
mdiImageEdit,
mdiImageMultipleOutline,
mdiImageSearch,
mdiKeyboardSettingsOutline,
mdiLockOutline,
mdiMagnify,
mdiMagnifyScan,
mdiMap,
@@ -69,6 +72,7 @@ import React from 'react';
import { Item, Timeline } from '../components/timeline';
const releases = {
'v1.106.1': new Date(2024, 5, 11),
'v1.104.0': new Date(2024, 4, 13),
'v1.103.0': new Date(2024, 3, 29),
'v1.102.0': new Date(2024, 3, 15),
@@ -157,6 +161,14 @@ const withRelease = ({
};
const roadmap: Item[] = [
{
done: false,
icon: mdiLockOutline,
iconColor: 'sandybrown',
title: 'Private/locked photos',
description: 'Private assets with extra protections',
getDateLabel: () => 'Planned for 2024',
},
{
done: false,
icon: mdiRocketLaunch,
@@ -197,14 +209,6 @@ const roadmap: Item[] = [
description: 'Granular access controls for users and api keys',
getDateLabel: () => 'Planned for 2024',
},
{
done: false,
icon: mdiWeb,
iconColor: 'royalblue',
title: 'Web translations',
description: 'Translate the web application to multiple languages',
getDateLabel: () => 'Planned for 2024',
},
{
done: false,
icon: mdiCameraBurst,
@@ -216,13 +220,32 @@ const roadmap: Item[] = [
];
const milestones: Item[] = [
// withRelease({
// icon: mdiVectorCombine,
// title: 'Container consolidation',
// description:
// 'The microservices container can be run as a worker within the server image, allowing us to remove it from the default stack.',
// release: 'v1.106.0',
// }),
withRelease({
icon: mdiHistory,
title: 'Versioned documentation',
description: 'View documentation as it was at the time of past releases',
release: 'v1.106.1',
}),
withRelease({
icon: mdiWeb,
iconColor: 'royalblue',
title: 'Web translations',
description: 'Translate the web application to multiple languages',
release: 'v1.106.1',
}),
withRelease({
icon: mdiContentDuplicate,
title: 'Similar image detection',
description: 'Detect duplicate assets that arent exactly identical',
release: 'v1.106.1',
}),
withRelease({
icon: mdiVectorCombine,
title: 'Container consolidation',
description:
'The microservices container can be run as a worker within the server image, allowing us to remove it from the default stack.',
release: 'v1.106.1',
}),
withRelease({
icon: mdiPencil,
iconColor: 'saddlebrown',

View File

@@ -0,0 +1,7 @@
import ComponentTypes from '@theme-original/NavbarItem/ComponentTypes';
import VersionSwitcher from '@site/src/components/version-switcher';
export default {
...ComponentTypes,
'custom-versionSwitcher': VersionSwitcher,
};

74
docs/static/archived-versions.json vendored Normal file
View File

@@ -0,0 +1,74 @@
[
{
"label": "v1.107.2",
"url": "https://v1.107.2.archive.immich.app"
},
{
"label": "v1.107.1",
"url": "https://v1.107.1.archive.immich.app"
},
{
"label": "v1.107.0",
"url": "https://v1.107.0.archive.immich.app"
},
{
"label": "v1.106.4",
"url": "https://v1.106.4.archive.immich.app"
},
{
"label": "v1.106.3",
"url": "https://v1.106.3.archive.immich.app"
},
{
"label": "v1.106.2",
"url": "https://v1.106.2.archive.immich.app"
},
{
"label": "v1.106.1",
"url": "https://v1.106.1.archive.immich.app"
},
{
"label": "v1.105.1",
"url": "https://v1.105.1.archive.immich.app/"
},
{
"label": "v1.105.0",
"url": "https://v1.105.0.archive.immich.app/"
},
{
"label": "v1.104.0",
"url": "https://v1.104.0.archive.immich.app/"
},
{
"label": "v1.103.1",
"url": "https://v1.103.1.archive.immich.app/"
},
{
"label": "v1.103.0",
"url": "https://v1.103.0.archive.immich.app/"
},
{
"label": "v1.102.3",
"url": "https://v1.102.3.archive.immich.app/"
},
{
"label": "v1.102.2",
"url": "https://v1.102.2.archive.immich.app/"
},
{
"label": "v1.102.1",
"url": "https://v1.102.1.archive.immich.app/"
},
{
"label": "v1.102.0",
"url": "https://v1.102.0.archive.immich.app/"
},
{
"label": "v1.101.0",
"url": "https://v1.101.0.archive.immich.app/"
},
{
"label": "v1.100.0",
"url": "https://v1.100.0.archive.immich.app/"
}
]

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 70 KiB

View File

@@ -1 +1 @@
20.14
20.15

View File

@@ -10,6 +10,11 @@ services:
build:
context: ../
dockerfile: server/Dockerfile
args:
- BUILD_ID=1234567890
- BUILD_IMAGE=e2e
- BUILD_SOURCE_REF=e2e
- BUILD_SOURCE_COMMIT=e2eeeeeeeeeeeeeeeeee
environment:
- DB_HOSTNAME=database
- DB_USERNAME=postgres
@@ -17,6 +22,7 @@ services:
- DB_DATABASE_NAME=immich
- IMMICH_MACHINE_LEARNING_ENABLED=false
- IMMICH_METRICS=true
- IMMICH_ENV=testing
volumes:
- upload:/usr/src/app/upload
- ./test-assets:/test-assets
@@ -27,7 +33,7 @@ services:
- 2283:3001
redis:
image: redis:6.2-alpine@sha256:e31ca60b18f7e9b78b573d156702471d4eda038803c0b8e6f01559f350031e93
image: redis:6.2-alpine@sha256:328fe6a5822256d065debb36617a8169dbfbd77b797c525288e465f56c1d392b
database:
image: tensorchord/pgvecto-rs:pg14-v0.2.0@sha256:90724186f0a3517cf6914295b5ab410db9ce23190a2d9d0b9dd6463e3fa298f0

222
e2e/package-lock.json generated
View File

@@ -1,19 +1,19 @@
{
"name": "immich-e2e",
"version": "1.105.1",
"version": "1.107.2",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "immich-e2e",
"version": "1.105.1",
"version": "1.107.2",
"license": "GNU Affero General Public License version 3",
"devDependencies": {
"@immich/cli": "file:../cli",
"@immich/sdk": "file:../open-api/typescript-sdk",
"@playwright/test": "^1.44.1",
"@types/luxon": "^3.4.2",
"@types/node": "^20.11.17",
"@types/node": "^20.14.9",
"@types/pg": "^8.11.0",
"@types/pngjs": "^6.0.4",
"@types/supertest": "^6.0.2",
@@ -23,8 +23,8 @@
"eslint": "^8.57.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-prettier": "^5.1.3",
"eslint-plugin-unicorn": "^53.0.0",
"exiftool-vendored": "^26.0.0",
"eslint-plugin-unicorn": "^54.0.0",
"exiftool-vendored": "^27.0.0",
"luxon": "^3.4.4",
"pg": "^8.11.3",
"pngjs": "^7.0.0",
@@ -39,7 +39,7 @@
},
"../cli": {
"name": "@immich/cli",
"version": "2.2.0",
"version": "2.2.7",
"dev": true,
"license": "GNU Affero General Public License version 3",
"dependencies": {
@@ -55,7 +55,7 @@
"@types/cli-progress": "^3.11.0",
"@types/lodash-es": "^4.17.12",
"@types/mock-fs": "^4.13.1",
"@types/node": "^20.3.1",
"@types/node": "^20.14.9",
"@typescript-eslint/eslint-plugin": "^7.0.0",
"@typescript-eslint/parser": "^7.0.0",
"@vitest/coverage-v8": "^1.2.2",
@@ -65,7 +65,7 @@
"eslint": "^8.56.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-prettier": "^5.1.3",
"eslint-plugin-unicorn": "^53.0.0",
"eslint-plugin-unicorn": "^54.0.0",
"mock-fs": "^5.2.0",
"prettier": "^3.2.5",
"prettier-plugin-organize-imports": "^3.2.4",
@@ -81,14 +81,14 @@
},
"../open-api/typescript-sdk": {
"name": "@immich/sdk",
"version": "1.105.1",
"version": "1.107.2",
"dev": true,
"license": "GNU Affero General Public License version 3",
"dependencies": {
"@oazapfts/runtime": "^1.0.2"
},
"devDependencies": {
"@types/node": "^20.12.13",
"@types/node": "^20.14.9",
"typescript": "^5.3.3"
}
},
@@ -971,18 +971,19 @@
}
},
"node_modules/@playwright/test": {
"version": "1.44.1",
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.44.1.tgz",
"integrity": "sha512-1hZ4TNvD5z9VuhNJ/walIjvMVvYkZKf71axoF/uiAqpntQJXpG64dlXhoDXE3OczPuTuvjf/M5KWFg5VAVUS3Q==",
"version": "1.45.0",
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.45.0.tgz",
"integrity": "sha512-TVYsfMlGAaxeUllNkywbwek67Ncf8FRGn8ZlRdO291OL3NjG9oMbfVhyP82HQF0CZLMrYsvesqoUekxdWuF9Qw==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"playwright": "1.44.1"
"playwright": "1.45.0"
},
"bin": {
"playwright": "cli.js"
},
"engines": {
"node": ">=16"
"node": ">=18"
}
},
"node_modules/@rollup/rollup-android-arm-eabi": {
@@ -1230,9 +1231,9 @@
"dev": true
},
"node_modules/@types/node": {
"version": "20.12.13",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.13.tgz",
"integrity": "sha512-gBGeanV41c1L171rR7wjbMiEpEI/l5XFQdLLfhr/REwpgDy/4U8y89+i8kRiLzDyZdOkXh+cRaTetUnCYutoXA==",
"version": "20.14.9",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.9.tgz",
"integrity": "sha512-06OCtnTXtWOZBJlRApleWndH4JsRVs1pDCc8dLSQp+7PpUpX3ePdHyeNSFTeSe7FtKyQkrlPvHwJOW3SLd8Oyg==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -1344,17 +1345,17 @@
}
},
"node_modules/@typescript-eslint/eslint-plugin": {
"version": "7.11.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.11.0.tgz",
"integrity": "sha512-P+qEahbgeHW4JQ/87FuItjBj8O3MYv5gELDzr8QaQ7fsll1gSMTYb6j87MYyxwf3DtD7uGFB9ShwgmCJB5KmaQ==",
"version": "7.14.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.14.1.tgz",
"integrity": "sha512-aAJd6bIf2vvQRjUG3ZkNXkmBpN+J7Wd0mfQiiVCJMu9Z5GcZZdcc0j8XwN/BM97Fl7e3SkTXODSk4VehUv7CGw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/regexpp": "^4.10.0",
"@typescript-eslint/scope-manager": "7.11.0",
"@typescript-eslint/type-utils": "7.11.0",
"@typescript-eslint/utils": "7.11.0",
"@typescript-eslint/visitor-keys": "7.11.0",
"@typescript-eslint/scope-manager": "7.14.1",
"@typescript-eslint/type-utils": "7.14.1",
"@typescript-eslint/utils": "7.14.1",
"@typescript-eslint/visitor-keys": "7.14.1",
"graphemer": "^1.4.0",
"ignore": "^5.3.1",
"natural-compare": "^1.4.0",
@@ -1378,16 +1379,16 @@
}
},
"node_modules/@typescript-eslint/parser": {
"version": "7.11.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.11.0.tgz",
"integrity": "sha512-yimw99teuaXVWsBcPO1Ais02kwJ1jmNA1KxE7ng0aT7ndr1pT1wqj0OJnsYVGKKlc4QJai86l/025L6z8CljOg==",
"version": "7.14.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.14.1.tgz",
"integrity": "sha512-8lKUOebNLcR0D7RvlcloOacTOWzOqemWEWkKSVpMZVF/XVcwjPR+3MD08QzbW9TCGJ+DwIc6zUSGZ9vd8cO1IA==",
"dev": true,
"license": "BSD-2-Clause",
"dependencies": {
"@typescript-eslint/scope-manager": "7.11.0",
"@typescript-eslint/types": "7.11.0",
"@typescript-eslint/typescript-estree": "7.11.0",
"@typescript-eslint/visitor-keys": "7.11.0",
"@typescript-eslint/scope-manager": "7.14.1",
"@typescript-eslint/types": "7.14.1",
"@typescript-eslint/typescript-estree": "7.14.1",
"@typescript-eslint/visitor-keys": "7.14.1",
"debug": "^4.3.4"
},
"engines": {
@@ -1407,14 +1408,14 @@
}
},
"node_modules/@typescript-eslint/scope-manager": {
"version": "7.11.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.11.0.tgz",
"integrity": "sha512-27tGdVEiutD4POirLZX4YzT180vevUURJl4wJGmm6TrQoiYwuxTIY98PBp6L2oN+JQxzE0URvYlzJaBHIekXAw==",
"version": "7.14.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.14.1.tgz",
"integrity": "sha512-gPrFSsoYcsffYXTOZ+hT7fyJr95rdVe4kGVX1ps/dJ+DfmlnjFN/GcMxXcVkeHDKqsq6uAcVaQaIi3cFffmAbA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/types": "7.11.0",
"@typescript-eslint/visitor-keys": "7.11.0"
"@typescript-eslint/types": "7.14.1",
"@typescript-eslint/visitor-keys": "7.14.1"
},
"engines": {
"node": "^18.18.0 || >=20.0.0"
@@ -1425,14 +1426,14 @@
}
},
"node_modules/@typescript-eslint/type-utils": {
"version": "7.11.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.11.0.tgz",
"integrity": "sha512-WmppUEgYy+y1NTseNMJ6mCFxt03/7jTOy08bcg7bxJJdsM4nuhnchyBbE8vryveaJUf62noH7LodPSo5Z0WUCg==",
"version": "7.14.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.14.1.tgz",
"integrity": "sha512-/MzmgNd3nnbDbOi3LfasXWWe292+iuo+umJ0bCCMCPc1jLO/z2BQmWUUUXvXLbrQey/JgzdF/OV+I5bzEGwJkQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/typescript-estree": "7.11.0",
"@typescript-eslint/utils": "7.11.0",
"@typescript-eslint/typescript-estree": "7.14.1",
"@typescript-eslint/utils": "7.14.1",
"debug": "^4.3.4",
"ts-api-utils": "^1.3.0"
},
@@ -1453,9 +1454,9 @@
}
},
"node_modules/@typescript-eslint/types": {
"version": "7.11.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.11.0.tgz",
"integrity": "sha512-MPEsDRZTyCiXkD4vd3zywDCifi7tatc4K37KqTprCvaXptP7Xlpdw0NR2hRJTetG5TxbWDB79Ys4kLmHliEo/w==",
"version": "7.14.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.14.1.tgz",
"integrity": "sha512-mL7zNEOQybo5R3AavY+Am7KLv8BorIv7HCYS5rKoNZKQD9tsfGUpO4KdAn3sSUvTiS4PQkr2+K0KJbxj8H9NDg==",
"dev": true,
"license": "MIT",
"engines": {
@@ -1467,14 +1468,14 @@
}
},
"node_modules/@typescript-eslint/typescript-estree": {
"version": "7.11.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.11.0.tgz",
"integrity": "sha512-cxkhZ2C/iyi3/6U9EPc5y+a6csqHItndvN/CzbNXTNrsC3/ASoYQZEt9uMaEp+xFNjasqQyszp5TumAVKKvJeQ==",
"version": "7.14.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.14.1.tgz",
"integrity": "sha512-k5d0VuxViE2ulIO6FbxxSZaxqDVUyMbXcidC8rHvii0I56XZPv8cq+EhMns+d/EVIL41sMXqRbK3D10Oza1bbA==",
"dev": true,
"license": "BSD-2-Clause",
"dependencies": {
"@typescript-eslint/types": "7.11.0",
"@typescript-eslint/visitor-keys": "7.11.0",
"@typescript-eslint/types": "7.14.1",
"@typescript-eslint/visitor-keys": "7.14.1",
"debug": "^4.3.4",
"globby": "^11.1.0",
"is-glob": "^4.0.3",
@@ -1506,9 +1507,9 @@
}
},
"node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": {
"version": "9.0.4",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz",
"integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==",
"version": "9.0.5",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
"integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
"dev": true,
"license": "ISC",
"dependencies": {
@@ -1522,16 +1523,16 @@
}
},
"node_modules/@typescript-eslint/utils": {
"version": "7.11.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.11.0.tgz",
"integrity": "sha512-xlAWwPleNRHwF37AhrZurOxA1wyXowW4PqVXZVUNCLjB48CqdPJoJWkrpH2nij9Q3Lb7rtWindtoXwxjxlKKCA==",
"version": "7.14.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.14.1.tgz",
"integrity": "sha512-CMmVVELns3nak3cpJhZosDkm63n+DwBlDX8g0k4QUa9BMnF+lH2lr3d130M1Zt1xxmB3LLk3NV7KQCq86ZBBhQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/eslint-utils": "^4.4.0",
"@typescript-eslint/scope-manager": "7.11.0",
"@typescript-eslint/types": "7.11.0",
"@typescript-eslint/typescript-estree": "7.11.0"
"@typescript-eslint/scope-manager": "7.14.1",
"@typescript-eslint/types": "7.14.1",
"@typescript-eslint/typescript-estree": "7.14.1"
},
"engines": {
"node": "^18.18.0 || >=20.0.0"
@@ -1545,13 +1546,13 @@
}
},
"node_modules/@typescript-eslint/visitor-keys": {
"version": "7.11.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.11.0.tgz",
"integrity": "sha512-7syYk4MzjxTEk0g/w3iqtgxnFQspDJfn6QKD36xMuuhTzjcxY7F8EmBLnALjVyaOF1/bVocu3bS/2/F7rXrveQ==",
"version": "7.14.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.14.1.tgz",
"integrity": "sha512-Crb+F75U1JAEtBeQGxSKwI60hZmmzaqA3z9sYsVm8X7W5cwLEm5bRe0/uXS6+MR/y8CVpKSR/ontIAIEPFcEkA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/types": "7.11.0",
"@typescript-eslint/types": "7.14.1",
"eslint-visitor-keys": "^3.4.3"
},
"engines": {
@@ -1671,10 +1672,11 @@
"dev": true
},
"node_modules/acorn": {
"version": "8.11.3",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz",
"integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==",
"version": "8.12.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.0.tgz",
"integrity": "sha512-RTvkC4w+KNXrM39/lWCUaG0IbRkWdCv7W/IOW9oU6SawyxulvkQy5HQPVTKxEjczcUvapcrw3cFx/60VN/NRNw==",
"dev": true,
"license": "MIT",
"bin": {
"acorn": "bin/acorn"
},
@@ -2276,15 +2278,15 @@
"dev": true
},
"node_modules/engine.io-client": {
"version": "6.5.3",
"resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.5.3.tgz",
"integrity": "sha512-9Z0qLB0NIisTRt1DZ/8U2k12RJn8yls/nXMZLn+/N8hANT3TcYjKFKcwbw5zFQiN4NTde3TSY9zb79e1ij6j9Q==",
"version": "6.5.4",
"resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.5.4.tgz",
"integrity": "sha512-GeZeeRjpD2qf49cZQ0Wvh/8NJNfeXkXXcoGh+F77oEAgo9gUHwT1fCRxSNU+YEEaysOJTnsFHmM5oAcPy4ntvQ==",
"dev": true,
"dependencies": {
"@socket.io/component-emitter": "~3.1.0",
"debug": "~4.3.1",
"engine.io-parser": "~5.2.1",
"ws": "~8.11.0",
"ws": "~8.17.1",
"xmlhttprequest-ssl": "~2.0.0"
}
},
@@ -2484,10 +2486,11 @@
}
},
"node_modules/eslint-plugin-unicorn": {
"version": "53.0.0",
"resolved": "https://registry.npmjs.org/eslint-plugin-unicorn/-/eslint-plugin-unicorn-53.0.0.tgz",
"integrity": "sha512-kuTcNo9IwwUCfyHGwQFOK/HjJAYzbODHN3wP0PgqbW+jbXqpNWxNVpVhj2tO9SixBwuAdmal8rVcWKBxwFnGuw==",
"version": "54.0.0",
"resolved": "https://registry.npmjs.org/eslint-plugin-unicorn/-/eslint-plugin-unicorn-54.0.0.tgz",
"integrity": "sha512-XxYLRiYtAWiAjPv6z4JREby1TAE2byBC7wlh0V4vWDCpccOSU1KovWV//jqPXF6bq3WKxqX9rdjoRQ1EhdmNdQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-validator-identifier": "^7.24.5",
"@eslint-community/eslint-utils": "^4.4.0",
@@ -2517,10 +2520,11 @@
}
},
"node_modules/eslint-plugin-unicorn/node_modules/@eslint/eslintrc": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.0.2.tgz",
"integrity": "sha512-wV19ZEGEMAC1eHgrS7UQPqsdEiCIbTKTasEfcXAigzoXICcqZSjBZEHlZwNVvKg6UBCjSlos84XiLqsRJnIcIg==",
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.1.0.tgz",
"integrity": "sha512-4Bfj15dVJdoy3RfZmmo86RK1Fwzn6SstsvK9JS+BaVKqC6QQQQyXekNaC+g+LKNgkQ+2VhGAzm6hO40AhMR3zQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"ajv": "^6.12.4",
"debug": "^4.3.2",
@@ -2544,6 +2548,7 @@
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.0.0.tgz",
"integrity": "sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw==",
"dev": true,
"license": "Apache-2.0",
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
@@ -2552,12 +2557,13 @@
}
},
"node_modules/eslint-plugin-unicorn/node_modules/espree": {
"version": "10.0.1",
"resolved": "https://registry.npmjs.org/espree/-/espree-10.0.1.tgz",
"integrity": "sha512-MWkrWZbJsL2UwnjxTX3gG8FneachS/Mwg7tdGXce011sJd5b0JG54vat5KHnfSBODZ3Wvzd2WnjxyzsRoVv+ww==",
"version": "10.1.0",
"resolved": "https://registry.npmjs.org/espree/-/espree-10.1.0.tgz",
"integrity": "sha512-M1M6CpiE6ffoigIOWYO9UDP8TMUw9kqb21tf+08IgDYjCsOvCuDt4jQcZmoYxx+w7zlKw9/N0KXfto+I8/FrXA==",
"dev": true,
"license": "BSD-2-Clause",
"dependencies": {
"acorn": "^8.11.3",
"acorn": "^8.12.0",
"acorn-jsx": "^5.3.2",
"eslint-visitor-keys": "^4.0.0"
},
@@ -2573,6 +2579,7 @@
"resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz",
"integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=18"
},
@@ -2700,9 +2707,9 @@
}
},
"node_modules/exiftool-vendored": {
"version": "26.1.0",
"resolved": "https://registry.npmjs.org/exiftool-vendored/-/exiftool-vendored-26.1.0.tgz",
"integrity": "sha512-Bhy2Ia86Agt3+PbJJhWeVMqJNXl74XJ0Oygef5F5uCL13fTxlmF8dECHiChyx8bBc3sxIw+2Q3ehWunJh3bs6w==",
"version": "27.0.0",
"resolved": "https://registry.npmjs.org/exiftool-vendored/-/exiftool-vendored-27.0.0.tgz",
"integrity": "sha512-/jHX8Jjadj0YJzpqnuBo1Yy2ln2hnRbBIc+3jcVOLQ6qhHEKsLRlfJ145Ghn7k/EcnfpDzVX3V8AUCTC8juTow==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -4127,10 +4134,11 @@
}
},
"node_modules/pg": {
"version": "8.11.5",
"resolved": "https://registry.npmjs.org/pg/-/pg-8.11.5.tgz",
"integrity": "sha512-jqgNHSKL5cbDjFlHyYsCXmQDrfIX/3RsNwYqpd4N0Kt8niLuNoRNH+aazv6cOd43gPh9Y4DjQCtb+X0MH0Hvnw==",
"version": "8.12.0",
"resolved": "https://registry.npmjs.org/pg/-/pg-8.12.0.tgz",
"integrity": "sha512-A+LHUSnwnxrnL/tZ+OLfqR1SxLN3c/pgDztZ47Rpbsd4jUytsTtwQo/TLPRzPJMp/1pbhYVhH9cuSZLAajNfjQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"pg-connection-string": "^2.6.4",
"pg-pool": "^3.6.2",
@@ -4255,33 +4263,35 @@
}
},
"node_modules/playwright": {
"version": "1.44.1",
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.44.1.tgz",
"integrity": "sha512-qr/0UJ5CFAtloI3avF95Y0L1xQo6r3LQArLIg/z/PoGJ6xa+EwzrwO5lpNr/09STxdHuUoP2mvuELJS+hLdtgg==",
"version": "1.45.0",
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.45.0.tgz",
"integrity": "sha512-4z3ac3plDfYzGB6r0Q3LF8POPR20Z8D0aXcxbJvmfMgSSq1hkcgvFRXJk9rUq5H/MJ0Ktal869hhOdI/zUTeLA==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"playwright-core": "1.44.1"
"playwright-core": "1.45.0"
},
"bin": {
"playwright": "cli.js"
},
"engines": {
"node": ">=16"
"node": ">=18"
},
"optionalDependencies": {
"fsevents": "2.3.2"
}
},
"node_modules/playwright-core": {
"version": "1.44.1",
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.44.1.tgz",
"integrity": "sha512-wh0JWtYTrhv1+OSsLPgFzGzt67Y7BE/ZS3jEqgGBlp2ppp1ZDj8c+9IARNW4dwf1poq5MgHreEM2KV/GuR4cFA==",
"version": "1.45.0",
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.45.0.tgz",
"integrity": "sha512-lZmHlFQ0VYSpAs43dRq1/nJ9G/6SiTI7VPqidld9TDefL9tX87bTKExWZZUF5PeRyqtXqd8fQi2qmfIedkwsNQ==",
"dev": true,
"license": "Apache-2.0",
"bin": {
"playwright-core": "cli.js"
},
"engines": {
"node": ">=16"
"node": ">=18"
}
},
"node_modules/pluralize": {
@@ -4385,10 +4395,11 @@
}
},
"node_modules/prettier": {
"version": "3.2.5",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz",
"integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==",
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.2.tgz",
"integrity": "sha512-rAVeHYMcv8ATV5d508CFdn+8/pHPpXeIid1DdrPwXnaAdH7cqjVbpJaT5eq4yRAFU/lsbwYwSF/n5iNrdJHPQA==",
"dev": true,
"license": "MIT",
"bin": {
"prettier": "bin/prettier.cjs"
},
@@ -5260,10 +5271,11 @@
}
},
"node_modules/typescript": {
"version": "5.4.5",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz",
"integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==",
"version": "5.5.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.2.tgz",
"integrity": "sha512-NcRtPEOsPFFWjobJEtfihkLCZCXZt/os3zf8nTxjVH3RvTSxjrCamJpbExGvYOF+tFHc3pA65qpdwPbzjohhew==",
"dev": true,
"license": "Apache-2.0",
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
@@ -5572,16 +5584,16 @@
"dev": true
},
"node_modules/ws": {
"version": "8.11.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz",
"integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==",
"version": "8.17.1",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz",
"integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==",
"dev": true,
"engines": {
"node": ">=10.0.0"
},
"peerDependencies": {
"bufferutil": "^4.0.1",
"utf-8-validate": "^5.0.2"
"utf-8-validate": ">=5.0.2"
},
"peerDependenciesMeta": {
"bufferutil": {

View File

@@ -1,6 +1,6 @@
{
"name": "immich-e2e",
"version": "1.105.1",
"version": "1.107.2",
"description": "",
"main": "index.js",
"type": "module",
@@ -23,7 +23,7 @@
"@immich/sdk": "file:../open-api/typescript-sdk",
"@playwright/test": "^1.44.1",
"@types/luxon": "^3.4.2",
"@types/node": "^20.11.17",
"@types/node": "^20.14.9",
"@types/pg": "^8.11.0",
"@types/pngjs": "^6.0.4",
"@types/supertest": "^6.0.2",
@@ -33,8 +33,8 @@
"eslint": "^8.57.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-prettier": "^5.1.3",
"eslint-plugin-unicorn": "^53.0.0",
"exiftool-vendored": "^26.0.0",
"eslint-plugin-unicorn": "^54.0.0",
"exiftool-vendored": "^27.0.0",
"luxon": "^3.4.4",
"pg": "^8.11.3",
"pngjs": "^7.0.0",
@@ -47,6 +47,6 @@
"vitest": "^1.3.0"
},
"volta": {
"node": "20.14.0"
"node": "20.15.0"
}
}

View File

@@ -88,7 +88,7 @@ describe('/albums', () => {
});
await addAssetsToAlbum(
{ id: user2Albums[0].id, bulkIdsDto: { ids: [user1Asset1.id] } },
{ id: user2Albums[0].id, bulkIdsDto: { ids: [user1Asset1.id, user1Asset2.id] } },
{ headers: asBearerAuth(user1.accessToken) },
);
@@ -261,7 +261,7 @@ describe('/albums', () => {
.get(`/albums?assetId=${user1Asset2.id}`)
.set('Authorization', `Bearer ${user1.accessToken}`);
expect(status).toBe(200);
expect(body).toHaveLength(1);
expect(body).toHaveLength(2);
});
it('should return the album collection filtered by assetId and ignores shared=true', async () => {
@@ -509,7 +509,17 @@ describe('/albums', () => {
expect(body).toEqual(errorDto.unauthorized);
});
it('should not be able to remove foreign asset from own album', async () => {
it('should require authorization', async () => {
const { status, body } = await request(app)
.delete(`/albums/${user1Albums[1].id}/assets`)
.set('Authorization', `Bearer ${user2.accessToken}`)
.send({ ids: [user1Asset1.id] });
expect(status).toBe(400);
expect(body).toEqual(errorDto.noPermission);
});
it('should be able to remove foreign asset from owned album', async () => {
const { status, body } = await request(app)
.delete(`/albums/${user2Albums[0].id}/assets`)
.set('Authorization', `Bearer ${user2.accessToken}`)
@@ -519,8 +529,7 @@ describe('/albums', () => {
expect(body).toEqual([
expect.objectContaining({
id: user1Asset1.id,
success: false,
error: 'no_permission',
success: true,
}),
]);
});
@@ -555,10 +564,10 @@ describe('/albums', () => {
const { status, body } = await request(app)
.delete(`/albums/${user2Albums[0].id}/assets`)
.set('Authorization', `Bearer ${user1.accessToken}`)
.send({ ids: [user1Asset1.id] });
.send({ ids: [user1Asset2.id] });
expect(status).toBe(200);
expect(body).toEqual([expect.objectContaining({ id: user1Asset1.id, success: true })]);
expect(body).toEqual([expect.objectContaining({ id: user1Asset2.id, success: true })]);
});
it('should not be able to remove assets from album as a viewer', async () => {

View File

@@ -588,6 +588,58 @@ describe('/asset', () => {
const after = await utils.getAssetInfo(admin.accessToken, assetId);
expect(after.isTrashed).toBe(true);
});
it('should clean up live photos', async () => {
const { id: motionId } = await utils.createAsset(admin.accessToken, {
assetData: { filename: 'test.mp4', bytes: makeRandomImage() },
});
const { id: photoId } = await utils.createAsset(admin.accessToken, { livePhotoVideoId: motionId });
await utils.waitForWebsocketEvent({ event: 'assetUpload', id: photoId });
await utils.waitForWebsocketEvent({ event: 'assetHidden', id: motionId });
const asset = await utils.getAssetInfo(admin.accessToken, photoId);
expect(asset.livePhotoVideoId).toBe(motionId);
const { status } = await request(app)
.delete('/assets')
.send({ ids: [photoId], force: true })
.set('Authorization', `Bearer ${admin.accessToken}`);
expect(status).toBe(204);
await utils.waitForWebsocketEvent({ event: 'assetDelete', id: photoId });
await utils.waitForWebsocketEvent({ event: 'assetDelete', id: motionId });
});
it('should not delete a shared motion asset', async () => {
const { id: motionId } = await utils.createAsset(admin.accessToken, {
assetData: { filename: 'test.mp4', bytes: makeRandomImage() },
});
const { id: asset1 } = await utils.createAsset(admin.accessToken, { livePhotoVideoId: motionId });
const { id: asset2 } = await utils.createAsset(admin.accessToken, { livePhotoVideoId: motionId });
await utils.waitForWebsocketEvent({ event: 'assetUpload', id: asset1 });
await utils.waitForWebsocketEvent({ event: 'assetUpload', id: asset2 });
await utils.waitForWebsocketEvent({ event: 'assetHidden', id: motionId });
const asset = await utils.getAssetInfo(admin.accessToken, asset1);
expect(asset.livePhotoVideoId).toBe(motionId);
const { status } = await request(app)
.delete('/assets')
.send({ ids: [asset1], force: true })
.set('Authorization', `Bearer ${admin.accessToken}`);
expect(status).toBe(204);
await utils.waitForWebsocketEvent({ event: 'assetDelete', id: asset1 });
await utils.waitForQueueFinish(admin.accessToken, 'backgroundTask');
await expect(utils.getAssetInfo(admin.accessToken, motionId)).resolves.toMatchObject({ id: motionId });
await expect(utils.getAssetInfo(admin.accessToken, asset2)).resolves.toMatchObject({
id: asset2,
livePhotoVideoId: motionId,
});
});
});
describe('GET /assets/:id/thumbnail', () => {
@@ -1148,4 +1200,29 @@ describe('/asset', () => {
expect(video.checksum).toStrictEqual(checksum);
});
});
describe('POST /assets/exist', () => {
it('ignores invalid deviceAssetIds', async () => {
const response = await utils.checkExistingAssets(user1.accessToken, {
deviceId: 'test-assets-exist',
deviceAssetIds: ['invalid', 'INVALID'],
});
expect(response.existingIds).toHaveLength(0);
});
it('returns the IDs of existing assets', async () => {
await utils.createAsset(user1.accessToken, {
deviceId: 'test-assets-exist',
deviceAssetId: 'test-asset-0',
});
const response = await utils.checkExistingAssets(user1.accessToken, {
deviceId: 'test-assets-exist',
deviceAssetIds: ['test-asset-0'],
});
expect(response.existingIds).toEqual(['test-asset-0']);
});
});
});

View File

@@ -1,4 +1,11 @@
import { LibraryResponseDto, LoginResponseDto, ScanLibraryDto, getAllLibraries, scanLibrary } from '@immich/sdk';
import {
LibraryResponseDto,
LoginResponseDto,
ScanLibraryDto,
getAllLibraries,
removeOfflineFiles,
scanLibrary,
} from '@immich/sdk';
import { cpSync, existsSync } from 'node:fs';
import { Socket } from 'socket.io-client';
import { userDto, uuidDto } from 'src/fixtures';
@@ -384,6 +391,51 @@ describe('/libraries', () => {
);
});
it('should not try to delete offline files', async () => {
utils.createImageFile(`${testAssetDir}/temp/offline1/assetA.png`);
const library = await utils.createLibrary(admin.accessToken, {
ownerId: admin.userId,
importPaths: [`${testAssetDirInternal}/temp/offline1`],
});
await scan(admin.accessToken, library.id);
await utils.waitForQueueFinish(admin.accessToken, 'library');
const { assets: initialAssets } = await utils.metadataSearch(admin.accessToken, { libraryId: library.id });
expect(initialAssets).toEqual({
count: 1,
total: 1,
facets: [],
items: [expect.objectContaining({ originalFileName: 'assetA.png' })],
nextPage: null,
});
utils.removeImageFile(`${testAssetDir}/temp/offline1/assetA.png`);
await scan(admin.accessToken, library.id);
await utils.waitForQueueFinish(admin.accessToken, 'library');
const { assets: offlineAssets } = await utils.metadataSearch(admin.accessToken, {
libraryId: library.id,
isOffline: true,
});
expect(offlineAssets).toEqual({
count: 1,
total: 1,
facets: [],
items: [expect.objectContaining({ originalFileName: 'assetA.png' })],
nextPage: null,
});
utils.createImageFile(`${testAssetDir}/temp/offline1/assetA.png`);
await removeOfflineFiles({ id: library.id }, { headers: asBearerAuth(admin.accessToken) });
await utils.waitForQueueFinish(admin.accessToken, 'library');
await utils.waitForWebsocketEvent({ event: 'assetDelete', total: 1 });
expect(existsSync(`${testAssetDir}/temp/offline1/assetA.png`)).toBe(true);
});
it('should scan new files', async () => {
const library = await utils.createLibrary(admin.accessToken, {
ownerId: admin.userId,
@@ -507,10 +559,10 @@ describe('/libraries', () => {
it('should remove offline files', async () => {
const library = await utils.createLibrary(admin.accessToken, {
ownerId: admin.userId,
importPaths: [`${testAssetDirInternal}/temp`],
importPaths: [`${testAssetDirInternal}/temp/offline2`],
});
utils.createImageFile(`${testAssetDir}/temp/directoryA/assetB.png`);
utils.createImageFile(`${testAssetDir}/temp/offline2/assetA.png`);
await scan(admin.accessToken, library.id);
await utils.waitForQueueFinish(admin.accessToken, 'library');
@@ -518,9 +570,9 @@ describe('/libraries', () => {
const { assets: initialAssets } = await utils.metadataSearch(admin.accessToken, {
libraryId: library.id,
});
expect(initialAssets.count).toBe(3);
expect(initialAssets.count).toBe(1);
utils.removeImageFile(`${testAssetDir}/temp/directoryA/assetB.png`);
utils.removeImageFile(`${testAssetDir}/temp/offline2/assetA.png`);
await scan(admin.accessToken, library.id);
await utils.waitForQueueFinish(admin.accessToken, 'library');
@@ -541,7 +593,7 @@ describe('/libraries', () => {
const { assets } = await utils.metadataSearch(admin.accessToken, { libraryId: library.id });
expect(assets.count).toBe(2);
expect(assets.count).toBe(0);
});
it('should not remove online files', async () => {

View File

@@ -230,4 +230,21 @@ describe('/people', () => {
expect(body).toMatchObject({ birthDate: null });
});
});
describe('POST /people/:id/merge', () => {
it('should require authentication', async () => {
const { status, body } = await request(app).post(`/people/${uuidDto.notFound}/merge`);
expect(status).toBe(401);
expect(body).toEqual(errorDto.unauthorized);
});
it('should not supporting merging a person into themselves', async () => {
const { status, body } = await request(app)
.post(`/people/${visiblePerson.id}/merge`)
.set('Authorization', `Bearer ${admin.accessToken}`)
.send({ ids: [visiblePerson.id] });
expect(status).toBe(400);
expect(body).toEqual(errorDto.badRequest('Cannot merge a person into themselves'));
});
});
});

View File

@@ -339,6 +339,13 @@ describe('/search', () => {
should: 'should search by model',
deferred: () => ({ dto: { model: 'Canon EOS 7D' }, assets: [assetDenali] }),
},
{
should: 'should allow searching the upload library (libraryId: null)',
deferred: () => ({
dto: { libraryId: null, size: 1 },
assets: [assetLast],
}),
},
];
for (const { should, deferred } of searchTests) {

View File

@@ -15,6 +15,40 @@ describe('/server-info', () => {
nonAdmin = await utils.userSetup(admin.accessToken, createUserDto.user1);
});
describe('GET /server-info/about', () => {
it('should require authentication', async () => {
const { status, body } = await request(app).get('/server-info/about');
expect(status).toBe(401);
expect(body).toEqual(errorDto.unauthorized);
});
it('should return about information', async () => {
const { status, body } = await request(app)
.get('/server-info/about')
.set('Authorization', `Bearer ${admin.accessToken}`);
expect(status).toBe(200);
expect(body).toEqual({
version: expect.any(String),
versionUrl: expect.any(String),
repository: 'immich-app/immich',
repositoryUrl: 'https://github.com/immich-app/immich',
build: '1234567890',
buildUrl: 'https://github.com/immich-app/immich/actions/runs/1234567890',
buildImage: 'e2e',
buildImageUrl: 'https://github.com/immich-app/immich/pkgs/container/immich-server',
sourceRef: 'e2e',
sourceCommit: 'e2eeeeeeeeeeeeeeeeee',
sourceUrl: 'https://github.com/immich-app/immich/commit/e2eeeeeeeeeeeeeeeeee',
nodejs: expect.any(String),
ffmpeg: expect.any(String),
imagemagick: expect.any(String),
libvips: expect.any(String),
exiftool: expect.any(String),
licensed: false,
});
});
});
describe('GET /server-info/storage', () => {
it('should require authentication', async () => {
const { status, body } = await request(app).get('/server-info/storage');

View File

@@ -0,0 +1,307 @@
import { LoginResponseDto } 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';
const serverLicense = {
licenseKey: 'IMSV-6ECZ-91TE-WZRM-Q7AQ-MBN4-UW48-2CPT-71X9',
activationKey:
'4kJUNUWMq13J14zqPFm1NodRcI6MV6DeOGvQNIgrM8Sc9nv669wyEVvFw1Nz4Kb1W7zLWblOtXEQzpRRqC4r4fKjewJxfbpeo9sEsqAVIfl4Ero-Vp1Dg21-sVdDGZEAy2oeTCXAyCT5d1JqrqR6N1qTAm4xOx9ujXQRFYhjRG8uwudw7_Q49pF18Tj5OEv9qCqElxztoNck4i6O_azsmsoOQrLIENIWPh3EynBN3ESpYERdCgXO8MlWeuG14_V1HbNjnJPZDuvYg__YfMzoOEtfm1sCqEaJ2Ww-BaX7yGfuCL4XsuZlCQQNHjfscy_WywVfIZPKCiW8QR74i0cSzQ',
};
describe('/server', () => {
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('GET /server/about', () => {
it('should require authentication', async () => {
const { status, body } = await request(app).get('/server/about');
expect(status).toBe(401);
expect(body).toEqual(errorDto.unauthorized);
});
it('should return about information', async () => {
const { status, body } = await request(app)
.get('/server/about')
.set('Authorization', `Bearer ${admin.accessToken}`);
expect(status).toBe(200);
expect(body).toEqual({
version: expect.any(String),
versionUrl: expect.any(String),
repository: 'immich-app/immich',
repositoryUrl: 'https://github.com/immich-app/immich',
build: '1234567890',
buildUrl: 'https://github.com/immich-app/immich/actions/runs/1234567890',
buildImage: 'e2e',
buildImageUrl: 'https://github.com/immich-app/immich/pkgs/container/immich-server',
sourceRef: 'e2e',
sourceCommit: 'e2eeeeeeeeeeeeeeeeee',
sourceUrl: 'https://github.com/immich-app/immich/commit/e2eeeeeeeeeeeeeeeeee',
nodejs: expect.any(String),
ffmpeg: expect.any(String),
imagemagick: expect.any(String),
libvips: expect.any(String),
exiftool: expect.any(String),
licensed: false,
});
});
});
describe('GET /server/storage', () => {
it('should require authentication', async () => {
const { status, body } = await request(app).get('/server/storage');
expect(status).toBe(401);
expect(body).toEqual(errorDto.unauthorized);
});
it('should return the disk information', async () => {
const { status, body } = await request(app)
.get('/server/storage')
.set('Authorization', `Bearer ${admin.accessToken}`);
expect(status).toBe(200);
expect(body).toEqual({
diskAvailable: expect.any(String),
diskAvailableRaw: expect.any(Number),
diskSize: expect.any(String),
diskSizeRaw: expect.any(Number),
diskUsagePercentage: expect.any(Number),
diskUse: expect.any(String),
diskUseRaw: expect.any(Number),
});
});
});
describe('GET /server/ping', () => {
it('should respond with pong', async () => {
const { status, body } = await request(app).get('/server/ping');
expect(status).toBe(200);
expect(body).toEqual({ res: 'pong' });
});
});
describe('GET /server/version', () => {
it('should respond with the server version', async () => {
const { status, body } = await request(app).get('/server/version');
expect(status).toBe(200);
expect(body).toEqual({
major: expect.any(Number),
minor: expect.any(Number),
patch: expect.any(Number),
});
});
});
describe('GET /server/features', () => {
it('should respond with the server features', async () => {
const { status, body } = await request(app).get('/server/features');
expect(status).toBe(200);
expect(body).toEqual({
smartSearch: false,
configFile: false,
duplicateDetection: false,
facialRecognition: false,
map: true,
reverseGeocoding: true,
oauth: false,
oauthAutoLaunch: false,
passwordLogin: true,
search: true,
sidecar: true,
trash: true,
email: false,
});
});
});
describe('GET /server/config', () => {
it('should respond with the server configuration', async () => {
const { status, body } = await request(app).get('/server/config');
expect(status).toBe(200);
expect(body).toEqual({
loginPageMessage: '',
oauthButtonText: 'Login with OAuth',
trashDays: 30,
userDeleteDelay: 7,
isInitialized: true,
externalDomain: '',
isOnboarded: false,
});
});
});
describe('GET /server/statistics', () => {
it('should require authentication', async () => {
const { status, body } = await request(app).get('/server/statistics');
expect(status).toBe(401);
expect(body).toEqual(errorDto.unauthorized);
});
it('should only work for admins', async () => {
const { status, body } = await request(app)
.get('/server/statistics')
.set('Authorization', `Bearer ${nonAdmin.accessToken}`);
expect(status).toBe(403);
expect(body).toEqual(errorDto.forbidden);
});
it('should return the server stats', async () => {
const { status, body } = await request(app)
.get('/server/statistics')
.set('Authorization', `Bearer ${admin.accessToken}`);
expect(status).toBe(200);
expect(body).toEqual({
photos: 0,
usage: 0,
usageByUser: [
{
quotaSizeInBytes: null,
photos: 0,
usage: 0,
userName: 'Immich Admin',
userId: admin.userId,
videos: 0,
},
{
quotaSizeInBytes: null,
photos: 0,
usage: 0,
userName: 'User 1',
userId: nonAdmin.userId,
videos: 0,
},
],
videos: 0,
});
});
});
describe('GET /server/media-types', () => {
it('should return accepted media types', async () => {
const { status, body } = await request(app).get('/server/media-types');
expect(status).toBe(200);
expect(body).toEqual({
sidecar: ['.xmp'],
image: expect.any(Array),
video: expect.any(Array),
});
});
});
describe('GET /server/theme', () => {
it('should respond with the server theme', async () => {
const { status, body } = await request(app).get('/server/theme');
expect(status).toBe(200);
expect(body).toEqual({
customCss: '',
});
});
});
describe('GET /server/license', () => {
it('should require authentication', async () => {
const { status, body } = await request(app).get('/server/license');
expect(status).toBe(401);
expect(body).toEqual(errorDto.unauthorized);
});
it('should only work for admins', async () => {
const { status, body } = await request(app)
.get('/server/license')
.set('Authorization', `Bearer ${nonAdmin.accessToken}`);
expect(status).toBe(403);
expect(body).toEqual(errorDto.forbidden);
});
it('should return the server license', async () => {
await request(app).put('/server/license').set('Authorization', `Bearer ${admin.accessToken}`).send(serverLicense);
const { status, body } = await request(app)
.get('/server/license')
.set('Authorization', `Bearer ${admin.accessToken}`);
expect(status).toBe(200);
expect(body).toEqual({
...serverLicense,
activatedAt: expect.any(String),
});
});
});
describe('DELETE /server/license', () => {
it('should require authentication', async () => {
const { status, body } = await request(app).delete('/server/license');
expect(status).toBe(401);
expect(body).toEqual(errorDto.unauthorized);
});
it('should only work for admins', async () => {
const { status, body } = await request(app)
.delete('/server/license')
.set('Authorization', `Bearer ${nonAdmin.accessToken}`);
expect(status).toBe(403);
expect(body).toEqual(errorDto.forbidden);
});
it('should delete the server license', async () => {
await request(app)
.delete('/server/license')
.set('Authorization', `Bearer ${admin.accessToken}`)
.send(serverLicense);
const { status } = await request(app).get('/server/license').set('Authorization', `Bearer ${admin.accessToken}`);
expect(status).toBe(200);
});
});
describe('PUT /server/license', () => {
it('should require authentication', async () => {
const { status, body } = await request(app).put('/server/license');
expect(status).toBe(401);
expect(body).toEqual(errorDto.unauthorized);
});
it('should only work for admins', async () => {
const { status, body } = await request(app)
.put('/server/license')
.set('Authorization', `Bearer ${nonAdmin.accessToken}`);
expect(status).toBe(403);
expect(body).toEqual(errorDto.forbidden);
});
it('should set the server license', async () => {
const { status, body } = await request(app)
.put('/server/license')
.set('Authorization', `Bearer ${admin.accessToken}`)
.send(serverLicense);
expect(status).toBe(200);
expect(body).toEqual({ ...serverLicense, activatedAt: expect.any(String) });
const { body: licenseBody } = await request(app)
.get('/server/license')
.set('Authorization', `Bearer ${admin.accessToken}`);
expect(licenseBody).toEqual({ ...serverLicense, activatedAt: expect.any(String) });
});
it('should reject license not starting with IMSV-', async () => {
const { status, body } = await request(app)
.put('/server/license')
.set('Authorization', `Bearer ${admin.accessToken}`)
.send({ licenseKey: 'IMCL-ABCD-ABCD-ABCD-ABCD-ABCD-ABCD-ABCD-ABCD', activationKey: 'activationKey' });
expect(status).toBe(400);
expect(body.message).toBe('Invalid license key');
});
it('should reject license with invalid activation key', async () => {
const { status, body } = await request(app)
.put('/server/license')
.set('Authorization', `Bearer ${admin.accessToken}`)
.send({ licenseKey: serverLicense.licenseKey, activationKey: `invalid${serverLicense.activationKey}` });
expect(status).toBe(400);
expect(body.message).toBe('Invalid license key');
});
});
});

View File

@@ -5,6 +5,7 @@ import {
getUserAdmin,
getUserPreferencesAdmin,
login,
updateAssets,
} from '@immich/sdk';
import { Socket } from 'socket.io-client';
import { createUserDto, uuidDto } from 'src/fixtures';
@@ -20,18 +21,16 @@ describe('/admin/users', () => {
let nonAdmin: LoginResponseDto;
let deletedUser: LoginResponseDto;
let userToDelete: LoginResponseDto;
let userToHardDelete: LoginResponseDto;
beforeAll(async () => {
await utils.resetDatabase();
admin = await utils.adminSetup({ onboarding: false });
[websocket, nonAdmin, deletedUser, userToDelete, userToHardDelete] = await Promise.all([
[websocket, nonAdmin, deletedUser, userToDelete] = await Promise.all([
utils.connectWebsocket(admin.accessToken),
utils.userSetup(admin.accessToken, createUserDto.user1),
utils.userSetup(admin.accessToken, createUserDto.user2),
utils.userSetup(admin.accessToken, createUserDto.user3),
utils.userSetup(admin.accessToken, createUserDto.user4),
]);
await deleteUserAdmin(
@@ -64,13 +63,12 @@ describe('/admin/users', () => {
.get(`/admin/users`)
.set('Authorization', `Bearer ${admin.accessToken}`);
expect(status).toBe(200);
expect(body).toHaveLength(4);
expect(body).toHaveLength(3);
expect(body).toEqual(
expect.arrayContaining([
expect.objectContaining({ email: admin.userEmail }),
expect.objectContaining({ email: nonAdmin.userEmail }),
expect.objectContaining({ email: userToDelete.userEmail }),
expect.objectContaining({ email: userToHardDelete.userEmail }),
]),
);
});
@@ -81,13 +79,12 @@ describe('/admin/users', () => {
.set('Authorization', `Bearer ${admin.accessToken}`);
expect(status).toBe(200);
expect(body).toHaveLength(5);
expect(body).toHaveLength(4);
expect(body).toEqual(
expect.arrayContaining([
expect.objectContaining({ email: admin.userEmail }),
expect.objectContaining({ email: nonAdmin.userEmail }),
expect.objectContaining({ email: userToDelete.userEmail }),
expect.objectContaining({ email: userToHardDelete.userEmail }),
expect.objectContaining({ email: deletedUser.userEmail }),
]),
);
@@ -250,18 +247,23 @@ describe('/admin/users', () => {
.set('Authorization', `Bearer ${admin.accessToken}`);
expect(status).toBe(200);
expect(body).toEqual({
avatar: { color: 'orange' },
memories: { enabled: false },
emailNotifications: { enabled: true, albumInvite: true, albumUpdate: true },
});
expect(body).toMatchObject({ avatar: { color: 'orange' } });
const after = await getUserPreferencesAdmin({ id: admin.userId }, { headers: asBearerAuth(admin.accessToken) });
expect(after).toEqual({
avatar: { color: 'orange' },
memories: { enabled: false },
emailNotifications: { enabled: true, albumInvite: true, albumUpdate: true },
});
expect(after).toMatchObject({ avatar: { color: 'orange' } });
});
it('should update download archive size', async () => {
const { status, body } = await request(app)
.put(`/admin/users/${admin.userId}/preferences`)
.send({ download: { archiveSize: 1_234_567 } })
.set('Authorization', `Bearer ${admin.accessToken}`);
expect(status).toBe(200);
expect(body).toMatchObject({ download: { archiveSize: 1_234_567 } });
const after = await getUserPreferencesAdmin({ id: admin.userId }, { headers: asBearerAuth(admin.accessToken) });
expect(after).toMatchObject({ download: { archiveSize: 1_234_567 } });
});
});
@@ -294,19 +296,49 @@ describe('/admin/users', () => {
});
it('should hard delete a user', async () => {
const user = await utils.userSetup(admin.accessToken, createUserDto.create('hard-delete-1'));
const { status, body } = await request(app)
.delete(`/admin/users/${userToHardDelete.userId}`)
.delete(`/admin/users/${user.userId}`)
.send({ force: true })
.set('Authorization', `Bearer ${admin.accessToken}`);
expect(status).toBe(200);
expect(body).toMatchObject({
id: userToHardDelete.userId,
id: user.userId,
updatedAt: expect.any(String),
deletedAt: expect.any(String),
});
await utils.waitForWebsocketEvent({ event: 'userDelete', id: userToHardDelete.userId, timeout: 5000 });
await utils.waitForWebsocketEvent({ event: 'userDelete', id: user.userId, timeout: 5000 });
});
it('should hard delete a user with stacked assets', async () => {
const user = await utils.userSetup(admin.accessToken, createUserDto.create('hard-delete-1'));
const [asset1, asset2] = await Promise.all([
utils.createAsset(user.accessToken),
utils.createAsset(user.accessToken),
]);
await updateAssets(
{ assetBulkUpdateDto: { stackParentId: asset1.id, ids: [asset2.id] } },
{ headers: asBearerAuth(user.accessToken) },
);
const { status, body } = await request(app)
.delete(`/admin/users/${user.userId}`)
.send({ force: true })
.set('Authorization', `Bearer ${admin.accessToken}`);
expect(status).toBe(200);
expect(body).toMatchObject({
id: user.userId,
updatedAt: expect.any(String),
deletedAt: expect.any(String),
});
await utils.waitForWebsocketEvent({ event: 'userDelete', id: user.userId, timeout: 5000 });
});
});

View File

@@ -5,6 +5,12 @@ import { app, asBearerAuth, utils } from 'src/utils';
import request from 'supertest';
import { beforeAll, describe, expect, it } from 'vitest';
const userLicense = {
licenseKey: 'IMCL-FF69-TUK1-RWZU-V9Q8-QGQS-S5GC-X4R2-UFK4',
activationKey:
'KuX8KsktrBSiXpQMAH0zLgA5SpijXVr_PDkzLdWUlAogCTMBZ0I3KCHXK0eE9EEd7harxup8_EHMeqAWeHo5VQzol6LGECpFv585U9asXD4Zc-UXt3mhJr2uhazqipBIBwJA2YhmUCDy8hiyiGsukDQNu9Rg9C77UeoKuZBWVjWUBWG0mc1iRqfvF0faVM20w53czAzlhaMxzVGc3Oimbd7xi_CAMSujF_2y8QpA3X2fOVkQkzdcH9lV0COejl7IyH27zQQ9HrlrXv3Lai5Hw67kNkaSjmunVBxC5PS0TpKoc9SfBJMaAGWnaDbjhjYUrm-8nIDQnoeEAidDXVAdPw',
};
describe('/users', () => {
let admin: LoginResponseDto;
let deletedUser: LoginResponseDto;
@@ -72,6 +78,24 @@ describe('/users', () => {
quotaUsageInBytes: 0,
});
});
it('should get my user with license info', async () => {
const { status: licenseStatus } = await request(app)
.put(`/users/me/license`)
.send(userLicense)
.set('Authorization', `Bearer ${nonAdmin.accessToken}`);
expect(licenseStatus).toBe(200);
const { status, body } = await request(app)
.get(`/users/me`)
.set('Authorization', `Bearer ${nonAdmin.accessToken}`);
expect(status).toBe(200);
expect(body).toMatchObject({
id: nonAdmin.userId,
email: nonAdmin.userEmail,
quotaUsageInBytes: 0,
license: userLicense,
});
});
});
describe('PUT /users/me', () => {
@@ -173,6 +197,45 @@ describe('/users', () => {
const after = await getMyPreferences({ headers: asBearerAuth(admin.accessToken) });
expect(after).toMatchObject({ memories: { enabled: false } });
});
it('should update avatar color', async () => {
const { status, body } = await request(app)
.put(`/users/me/preferences`)
.send({ avatar: { color: 'blue' } })
.set('Authorization', `Bearer ${admin.accessToken}`);
expect(status).toBe(200);
expect(body).toMatchObject({ avatar: { color: 'blue' } });
const after = await getMyPreferences({ headers: asBearerAuth(admin.accessToken) });
expect(after).toMatchObject({ avatar: { color: 'blue' } });
});
it('should require an integer for download archive size', async () => {
const { status, body } = await request(app)
.put(`/users/me/preferences`)
.send({ download: { archiveSize: 1_234_567.89 } })
.set('Authorization', `Bearer ${admin.accessToken}`);
expect(status).toBe(400);
expect(body).toEqual(errorDto.badRequest(['download.archiveSize must be an integer number']));
});
it('should update download archive size', async () => {
const before = await getMyPreferences({ headers: asBearerAuth(admin.accessToken) });
expect(before).toMatchObject({ download: { archiveSize: 4 * 2 ** 30 } });
const { status, body } = await request(app)
.put(`/users/me/preferences`)
.send({ download: { archiveSize: 1_234_567 } })
.set('Authorization', `Bearer ${admin.accessToken}`);
expect(status).toBe(200);
expect(body).toMatchObject({ download: { archiveSize: 1_234_567 } });
const after = await getMyPreferences({ headers: asBearerAuth(admin.accessToken) });
expect(after).toMatchObject({ download: { archiveSize: 1_234_567 } });
});
});
describe('GET /users/:id', () => {
@@ -197,4 +260,81 @@ describe('/users', () => {
});
});
});
describe('GET /server/license', () => {
it('should require authentication', async () => {
const { status, body } = await request(app).get('/users/me/license');
expect(status).toBe(401);
expect(body).toEqual(errorDto.unauthorized);
});
it('should return the user license', async () => {
await request(app)
.put('/users/me/license')
.set('Authorization', `Bearer ${nonAdmin.accessToken}`)
.send(userLicense);
const { status, body } = await request(app)
.get('/users/me/license')
.set('Authorization', `Bearer ${nonAdmin.accessToken}`);
expect(status).toBe(200);
expect(body).toEqual({
...userLicense,
activatedAt: expect.any(String),
});
});
});
describe('PUT /users/me/license', () => {
it('should require authentication', async () => {
const { status } = await request(app).put(`/users/me/license`);
expect(status).toEqual(401);
});
it('should set the user license', async () => {
const { status, body } = await request(app)
.put(`/users/me/license`)
.send(userLicense)
.set('Authorization', `Bearer ${nonAdmin.accessToken}`);
expect(status).toBe(200);
expect(body).toMatchObject({ ...userLicense, activatedAt: expect.any(String) });
expect(status).toBe(200);
expect(body).toEqual({ ...userLicense, activatedAt: expect.any(String) });
const { body: licenseBody } = await request(app)
.get('/users/me/license')
.set('Authorization', `Bearer ${nonAdmin.accessToken}`);
expect(licenseBody).toEqual({ ...userLicense, activatedAt: expect.any(String) });
});
it('should reject license not starting with IMCL-', async () => {
const { status, body } = await request(app)
.put('/users/me/license')
.set('Authorization', `Bearer ${nonAdmin.accessToken}`)
.send({ licenseKey: 'IMSV-ABCD-ABCD-ABCD-ABCD-ABCD-ABCD-ABCD-ABCD', activationKey: 'activationKey' });
expect(status).toBe(400);
expect(body.message).toBe('Invalid license key');
});
it('should reject license with invalid activation key', async () => {
const { status, body } = await request(app)
.put('/users/me/license')
.set('Authorization', `Bearer ${nonAdmin.accessToken}`)
.send({ licenseKey: userLicense.licenseKey, activationKey: `invalid${userLicense.activationKey}` });
expect(status).toBe(400);
expect(body.message).toBe('Invalid license key');
});
});
describe('DELETE /users/me/license', () => {
it('should require authentication', async () => {
const { status } = await request(app).put(`/users/me/license`);
expect(status).toEqual(401);
});
it('should delete the user license', async () => {
const { status } = await request(app)
.delete(`/users/me/license`)
.set('Authorization', `Bearer ${nonAdmin.accessToken}`);
expect(status).toBe(200);
});
});
});

View File

@@ -5,51 +5,61 @@ export const errorDto = {
error: 'Unauthorized',
statusCode: 401,
message: 'Authentication required',
correlationId: expect.any(String),
},
forbidden: {
error: 'Forbidden',
statusCode: 403,
message: expect.any(String),
correlationId: expect.any(String),
},
wrongPassword: {
error: 'Bad Request',
statusCode: 400,
message: 'Wrong password',
correlationId: expect.any(String),
},
invalidToken: {
error: 'Unauthorized',
statusCode: 401,
message: 'Invalid user token',
correlationId: expect.any(String),
},
invalidShareKey: {
error: 'Unauthorized',
statusCode: 401,
message: 'Invalid share key',
correlationId: expect.any(String),
},
invalidSharePassword: {
error: 'Unauthorized',
statusCode: 401,
message: 'Invalid password',
correlationId: expect.any(String),
},
badRequest: (message: any = null) => ({
error: 'Bad Request',
statusCode: 400,
message: message ?? expect.anything(),
correlationId: expect.any(String),
}),
noPermission: {
error: 'Bad Request',
statusCode: 400,
message: expect.stringContaining('Not found or no'),
correlationId: expect.any(String),
},
incorrectLogin: {
error: 'Unauthorized',
statusCode: 401,
message: 'Incorrect email or password',
correlationId: expect.any(String),
},
alreadyHasAdmin: {
error: 'Bad Request',
statusCode: 400,
message: 'The server already has an admin',
correlationId: expect.any(String),
},
};
@@ -71,6 +81,7 @@ export const signupResponseDto = {
quotaUsageInBytes: 0,
quotaSizeInBytes: null,
status: 'active',
license: null,
},
};

View File

@@ -3,6 +3,7 @@ import {
AssetMediaCreateDto,
AssetMediaResponseDto,
AssetResponseDto,
CheckExistingAssetsDto,
CreateAlbumDto,
CreateLibraryDto,
MetadataSearchDto,
@@ -10,6 +11,7 @@ import {
SharedLinkCreateDto,
UserAdminCreateDto,
ValidateLibraryDto,
checkExistingAssets,
createAlbum,
createApiKey,
createLibrary,
@@ -45,7 +47,7 @@ import { makeRandomImage } from 'src/generators';
import request from 'supertest';
type CommandResponse = { stdout: string; stderr: string; exitCode: number | null };
type EventType = 'assetUpload' | 'assetUpdate' | 'assetDelete' | 'userDelete';
type EventType = 'assetUpload' | 'assetUpdate' | 'assetDelete' | 'userDelete' | 'assetHidden';
type WaitOptions = { event: EventType; id?: string; total?: number; timeout?: number };
type AdminSetupOptions = { onboarding?: boolean };
type AssetData = { bytes?: Buffer; filename: string };
@@ -90,6 +92,7 @@ const executeCommand = (command: string, args: string[]) => {
let client: pg.Client | null = null;
const events: Record<EventType, Set<string>> = {
assetHidden: new Set<string>(),
assetUpload: new Set<string>(),
assetUpdate: new Set<string>(),
assetDelete: new Set<string>(),
@@ -201,6 +204,7 @@ export const utils = {
.on('connect', () => resolve(websocket))
.on('on_upload_success', (data: AssetResponseDto) => onEvent({ event: 'assetUpload', id: data.id }))
.on('on_asset_update', (data: AssetResponseDto) => onEvent({ event: 'assetUpdate', id: data.id }))
.on('on_asset_hidden', (assetId: string) => onEvent({ event: 'assetHidden', id: assetId }))
.on('on_asset_delete', (assetId: string) => onEvent({ event: 'assetDelete', id: assetId }))
.on('on_user_delete', (userId: string) => onEvent({ event: 'userDelete', id: userId }))
.connect();
@@ -323,6 +327,40 @@ export const utils = {
return body as AssetMediaResponseDto;
},
replaceAsset: async (
accessToken: string,
assetId: string,
dto?: Partial<Omit<AssetMediaCreateDto, 'assetData'>> & { assetData?: AssetData },
) => {
const _dto = {
deviceAssetId: 'test-1',
deviceId: 'test',
fileCreatedAt: new Date().toISOString(),
fileModifiedAt: new Date().toISOString(),
...dto,
};
const assetData = dto?.assetData?.bytes || makeRandomImage();
const filename = dto?.assetData?.filename || 'example.png';
if (dto?.assetData?.bytes) {
console.log(`Uploading ${filename}`);
}
const builder = request(app)
.put(`/assets/${assetId}/original`)
.attach('assetData', assetData, filename)
.set('Authorization', `Bearer ${accessToken}`);
for (const [key, value] of Object.entries(_dto)) {
void builder.field(key, String(value));
}
const { body } = await builder;
return body as AssetMediaResponseDto;
},
createImageFile: (path: string) => {
if (!existsSync(dirname(path))) {
mkdirSync(dirname(path), { recursive: true });
@@ -340,6 +378,9 @@ export const utils = {
getAssetInfo: (accessToken: string, id: string) => getAssetInfo({ id }, { headers: asBearerAuth(accessToken) }),
checkExistingAssets: (accessToken: string, checkExistingAssetsDto: CheckExistingAssetsDto) =>
checkExistingAssets({ checkExistingAssetsDto }, { headers: asBearerAuth(accessToken) }),
metadataSearch: async (accessToken: string, dto: MetadataSearchDto) => {
return searchMetadata({ metadataSearchDto: dto }, { headers: asBearerAuth(accessToken) });
},
@@ -359,14 +400,7 @@ export const utils = {
return;
}
const vector = Array.from({ length: 512 }, Math.random);
const embedding = `[${vector.join(',')}]`;
await client.query('INSERT INTO asset_faces ("assetId", "personId", "embedding") VALUES ($1, $2, $3)', [
assetId,
personId,
embedding,
]);
await client.query('INSERT INTO asset_faces ("assetId", "personId") VALUES ($1, $2)', [assetId, personId]);
},
setPersonThumbnail: async (personId: string) => {

View File

@@ -0,0 +1,57 @@
import { AssetMediaResponseDto, LoginResponseDto } from '@immich/sdk';
import { Page, expect, test } from '@playwright/test';
import { utils } from 'src/utils';
function imageLocator(page: Page) {
return page.getByAltText('Image taken on').locator('visible=true');
}
test.describe('Photo Viewer', () => {
let admin: LoginResponseDto;
let asset: AssetMediaResponseDto;
test.beforeAll(async () => {
utils.initSdk();
await utils.resetDatabase();
admin = await utils.adminSetup();
asset = await utils.createAsset(admin.accessToken);
});
test.beforeEach(async ({ context, page }) => {
// before each test, login as user
await utils.setAuthCookies(context, admin.accessToken);
await page.goto('/photos');
await page.waitForLoadState('networkidle');
});
test('initially shows a loading spinner', async ({ page }) => {
await page.route(`/api/assets/${asset.id}/thumbnail**`, async (route) => {
// slow down the request for thumbnail, so spiner has chance to show up
await new Promise((f) => setTimeout(f, 2000));
await route.continue();
});
await page.goto(`/photos/${asset.id}`);
await page.waitForLoadState('load');
// this is the spinner
await page.waitForSelector('svg[role=status]');
await expect(page.getByRole('status')).toBeVisible();
});
test('loads high resolution photo when zoomed', async ({ page }) => {
await page.goto(`/photos/${asset.id}`);
await expect.poll(async () => await imageLocator(page).getAttribute('src')).toContain('thumbnail');
const box = await imageLocator(page).boundingBox();
expect(box).toBeTruthy;
const { x, y, width, height } = box!;
await page.mouse.move(x + width / 2, y + height / 2);
await page.mouse.wheel(0, -1);
await expect.poll(async () => await imageLocator(page).getAttribute('src')).toContain('original');
});
test('reloads photo when checksum changes', async ({ page }) => {
await page.goto(`/photos/${asset.id}`);
await expect.poll(async () => await imageLocator(page).getAttribute('src')).toContain('thumbnail');
const initialSrc = await imageLocator(page).getAttribute('src');
await utils.replaceAsset(admin.accessToken, asset.id);
await expect.poll(async () => await imageLocator(page).getAttribute('src')).not.toBe(initialSrc);
});
});

View File

@@ -1,6 +1,6 @@
ARG DEVICE=cpu
FROM python:3.11-bookworm@sha256:96de1ea4821d73fd2c1853d1fdc3cf794ccfe2fae4c3f08579e846de51760a61 as builder-cpu
FROM python:3.11-bookworm@sha256:7bec1574675e7fd9e3a540a03cd7d6811c59ca261bd300cd665369d8f435298a as builder-cpu
FROM openvino/ubuntu22_runtime:2023.3.0@sha256:176646df619032ea6c10faf842867119c393e7497b7f88b5e307e932a0fd5aa8 as builder-openvino
USER root
@@ -36,7 +36,7 @@ RUN python3 -m venv /opt/venv
COPY poetry.lock pyproject.toml ./
RUN poetry install --sync --no-interaction --no-ansi --no-root --with ${DEVICE} --without dev
FROM python:3.11-slim-bookworm@sha256:fc39d2e68b554c3f0a5cb8a776280c0b3d73b4c04b83dbade835e2a171ca27ef as prod-cpu
FROM python:3.11-slim-bookworm@sha256:17ec9dc2367aa748559d0212f34665ec4df801129de32db705ea34654b5bc77a as prod-cpu
FROM openvino/ubuntu22_runtime:2023.3.0@sha256:176646df619032ea6c10faf842867119c393e7497b7f88b5e307e932a0fd5aa8 as prod-openvino
USER root

View File

@@ -52,8 +52,6 @@ class Ann(metaclass=_Singleton):
def __init__(self, log_level: int = 3, tuning_level: int = 1, tuning_file: str | None = None) -> None:
if not is_available:
raise RuntimeError("libann is not available!")
if tuning_file and not exists(tuning_file):
raise ValueError("tuning_file must point to an existing (possibly empty) file!")
if tuning_level == 0 and tuning_file is None:
raise ValueError("tuning_level == 0 reads existing tuning information and requires a tuning_file")
if tuning_level < 0 or tuning_level > 3:
@@ -67,6 +65,12 @@ class Ann(metaclass=_Singleton):
self.input_shapes: dict[int, tuple[tuple[int], ...]] = {}
self.ann: int | None = None
self.new()
if self.tuning_file is not None:
# make sure tuning file exists (without clearing contents)
# once filled, the tuning file reduces the cost/time of the first
# inference after model load by 10s of seconds
open(self.tuning_file, "a").close()
def new(self) -> None:
if self.ann is None:
@@ -95,17 +99,19 @@ class Ann(metaclass=_Singleton):
model_path: str,
fast_math: bool = True,
fp16: bool = False,
save_cached_network: bool = False,
cached_network_path: str | None = None,
) -> int:
if not model_path.endswith((".armnn", ".tflite", ".onnx")):
raise ValueError("model_path must be a file with extension .armnn, .tflite or .onnx")
if not exists(model_path):
raise ValueError("model_path must point to an existing file!")
save_cached_network = False
if cached_network_path is not None and not exists(cached_network_path):
raise ValueError("cached_network_path must point to an existing (possibly empty) file!")
if save_cached_network and cached_network_path is None:
raise ValueError("save_cached_network is True, cached_network_path must be specified!")
save_cached_network = True
# create empty model cache file
open(cached_network_path, "a").close()
net_id: int = libann.load(
self.ann,
model_path.encode(),

View File

@@ -12,8 +12,6 @@ from rich.logging import RichHandler
from uvicorn import Server
from uvicorn.workers import UvicornWorker
from .schemas import ModelType
class PreloadModelData(BaseModel):
clip: str | None
@@ -21,7 +19,7 @@ class PreloadModelData(BaseModel):
class Settings(BaseSettings):
cache_folder: str = "/cache"
cache_folder: Path = Path("/cache")
model_ttl: int = 300
model_ttl_poll_s: int = 10
host: str = "0.0.0.0"
@@ -55,14 +53,6 @@ def clean_name(model_name: str) -> str:
return model_name.split("/")[-1].translate(_clean_name)
def get_cache_dir(model_name: str, model_type: ModelType) -> Path:
return Path(settings.cache_folder) / model_type.value / clean_name(model_name)
def get_hf_model_name(model_name: str) -> str:
return f"immich-app/{clean_name(model_name)}"
LOG_LEVELS: dict[str, int] = {
"critical": logging.ERROR,
"error": logging.ERROR,

View File

@@ -8,6 +8,8 @@ from fastapi.testclient import TestClient
from numpy.typing import NDArray
from PIL import Image
from app.config import log
from .main import app
@@ -96,12 +98,77 @@ def clip_tokenizer_cfg() -> dict[str, Any]:
@pytest.fixture(scope="function")
def providers(request: pytest.FixtureRequest) -> Iterator[dict[str, Any]]:
def providers(request: pytest.FixtureRequest) -> Iterator[mock.Mock]:
marker = request.node.get_closest_marker("providers")
if marker is None:
raise ValueError("Missing marker 'providers'")
providers = marker.args[0]
with mock.patch("app.models.base.ort.get_available_providers") as mocked:
with mock.patch("app.sessions.ort.ort.get_available_providers") as mocked:
mocked.return_value = providers
yield providers
@pytest.fixture(scope="function")
def ort_pybind() -> Iterator[mock.Mock]:
with mock.patch("app.sessions.ort.ort.capi._pybind_state") as mocked:
yield mocked
@pytest.fixture(scope="function")
def ov_device_ids(request: pytest.FixtureRequest, ort_pybind: mock.Mock) -> Iterator[mock.Mock]:
marker = request.node.get_closest_marker("ov_device_ids")
if marker is None:
raise ValueError("Missing marker 'ov_device_ids'")
ort_pybind.get_available_openvino_device_ids.return_value = marker.args[0]
return ort_pybind
@pytest.fixture(scope="function")
def ort_session() -> Iterator[mock.Mock]:
with mock.patch("app.sessions.ort.ort.InferenceSession") as mocked:
yield mocked
@pytest.fixture(scope="function")
def ann_session() -> Iterator[mock.Mock]:
with mock.patch("app.sessions.ann.Ann") as mocked:
yield mocked
@pytest.fixture(scope="function")
def rmtree() -> Iterator[mock.Mock]:
with mock.patch("app.models.base.rmtree", autospec=True) as mocked:
mocked.avoids_symlink_attacks = True
yield mocked
@pytest.fixture(scope="function")
def path() -> Iterator[mock.Mock]:
path = mock.MagicMock()
path.exists.return_value = True
path.is_dir.return_value = True
path.is_file.return_value = True
path.with_suffix.return_value = path
path.return_value = path
with mock.patch("app.models.base.Path", return_value=path) as mocked:
yield mocked
@pytest.fixture(scope="function")
def info() -> Iterator[mock.Mock]:
with mock.patch.object(log, "info") as mocked:
yield mocked
@pytest.fixture(scope="function")
def warning() -> Iterator[mock.Mock]:
with mock.patch.object(log, "warning") as mocked:
yield mocked
@pytest.fixture(scope="function")
def snapshot_download() -> Iterator[mock.Mock]:
with mock.patch("app.models.base.snapshot_download") as mocked:
yield mocked

View File

@@ -6,22 +6,34 @@ import threading
import time
from concurrent.futures import ThreadPoolExecutor
from contextlib import asynccontextmanager
from functools import partial
from typing import Any, AsyncGenerator, Callable, Iterator
from zipfile import BadZipFile
import orjson
from fastapi import Depends, FastAPI, Form, HTTPException, UploadFile
from fastapi import Depends, FastAPI, File, Form, HTTPException
from fastapi.responses import ORJSONResponse
from onnxruntime.capi.onnxruntime_pybind11_state import InvalidProtobuf, NoSuchFile
from PIL.Image import Image
from pydantic import ValidationError
from starlette.formparsers import MultiPartParser
from app.models import get_model_deps
from app.models.base import InferenceModel
from app.models.transforms import decode_pil
from .config import PreloadModelData, log, settings
from .models.cache import ModelCache
from .schemas import (
InferenceEntries,
InferenceEntry,
InferenceResponse,
MessageResponse,
ModelIdentity,
ModelTask,
ModelType,
PipelineRequest,
T,
TextResponse,
)
@@ -63,12 +75,21 @@ async def lifespan(_: FastAPI) -> AsyncGenerator[None, None]:
gc.collect()
async def preload_models(preload_models: PreloadModelData) -> None:
log.info(f"Preloading models: {preload_models}")
if preload_models.clip is not None:
await load(await model_cache.get(preload_models.clip, ModelType.CLIP))
if preload_models.facial_recognition is not None:
await load(await model_cache.get(preload_models.facial_recognition, ModelType.FACIAL_RECOGNITION))
async def preload_models(preload: PreloadModelData) -> None:
log.info(f"Preloading models: {preload}")
if preload.clip is not None:
model = await model_cache.get(preload.clip, ModelType.TEXTUAL, ModelTask.SEARCH)
await load(model)
model = await model_cache.get(preload.clip, ModelType.VISUAL, ModelTask.SEARCH)
await load(model)
if preload.facial_recognition is not None:
model = await model_cache.get(preload.facial_recognition, ModelType.DETECTION, ModelTask.FACIAL_RECOGNITION)
await load(model)
model = await model_cache.get(preload.facial_recognition, ModelType.RECOGNITION, ModelTask.FACIAL_RECOGNITION)
await load(model)
def update_state() -> Iterator[None]:
@@ -81,6 +102,27 @@ def update_state() -> Iterator[None]:
active_requests -= 1
def get_entries(entries: str = Form()) -> InferenceEntries:
try:
request: PipelineRequest = orjson.loads(entries)
without_deps: list[InferenceEntry] = []
with_deps: list[InferenceEntry] = []
for task, types in request.items():
for type, entry in types.items():
parsed: InferenceEntry = {
"name": entry["modelName"],
"task": task,
"type": type,
"options": entry.get("options", {}),
}
dep = get_model_deps(parsed["name"], type, task)
(with_deps if dep else without_deps).append(parsed)
return without_deps, with_deps
except (orjson.JSONDecodeError, ValidationError, KeyError, AttributeError) as e:
log.error(f"Invalid request format: {e}")
raise HTTPException(422, "Invalid request format.")
app = FastAPI(lifespan=lifespan)
@@ -96,56 +138,72 @@ def ping() -> str:
@app.post("/predict", dependencies=[Depends(update_state)])
async def predict(
model_name: str = Form(alias="modelName"),
model_type: ModelType = Form(alias="modelType"),
options: str = Form(default="{}"),
entries: InferenceEntries = Depends(get_entries),
image: bytes | None = File(default=None),
text: str | None = Form(default=None),
image: UploadFile | None = None,
) -> Any:
if image is not None:
inputs: str | bytes = await image.read()
inputs: Image | str = await run(lambda: decode_pil(image))
elif text is not None:
inputs = text
else:
raise HTTPException(400, "Either image or text must be provided")
try:
kwargs = orjson.loads(options)
except orjson.JSONDecodeError:
raise HTTPException(400, f"Invalid options JSON: {options}")
model = await load(await model_cache.get(model_name, model_type, ttl=settings.model_ttl, **kwargs))
model.configure(**kwargs)
outputs = await run(model.predict, inputs)
return ORJSONResponse(outputs)
response = await run_inference(inputs, entries)
return ORJSONResponse(response)
async def run(func: Callable[..., Any], inputs: Any) -> Any:
async def run_inference(payload: Image | str, entries: InferenceEntries) -> InferenceResponse:
outputs: dict[ModelIdentity, Any] = {}
response: InferenceResponse = {}
async def _run_inference(entry: InferenceEntry) -> None:
model = await model_cache.get(entry["name"], entry["type"], entry["task"], ttl=settings.model_ttl)
inputs = [payload]
for dep in model.depends:
try:
inputs.append(outputs[dep])
except KeyError:
message = f"Task {entry['task']} of type {entry['type']} depends on output of {dep}"
raise HTTPException(400, message)
model = await load(model)
output = await run(model.predict, *inputs, **entry["options"])
outputs[model.identity] = output
response[entry["task"]] = output
without_deps, with_deps = entries
await asyncio.gather(*[_run_inference(entry) for entry in without_deps])
if with_deps:
await asyncio.gather(*[_run_inference(entry) for entry in with_deps])
if isinstance(payload, Image):
response["imageHeight"], response["imageWidth"] = payload.height, payload.width
return response
async def run(func: Callable[..., T], *args: Any, **kwargs: Any) -> T:
if thread_pool is None:
return func(inputs)
return await asyncio.get_running_loop().run_in_executor(thread_pool, func, inputs)
return func(*args, **kwargs)
partial_func = partial(func, *args, **kwargs)
return await asyncio.get_running_loop().run_in_executor(thread_pool, partial_func)
async def load(model: InferenceModel) -> InferenceModel:
if model.loaded:
return model
def _load(model: InferenceModel) -> None:
def _load(model: InferenceModel) -> InferenceModel:
if model.load_attempts > 1:
raise HTTPException(500, f"Failed to load model '{model.model_name}'")
with lock:
model.load()
return model
try:
await run(_load, model)
return model
return await run(_load, model)
except (OSError, InvalidProtobuf, BadZipFile, NoSuchFile):
log.warning(
(
f"Failed to load {model.model_type.replace('_', ' ')} model '{model.model_name}'."
"Clearing cache and retrying."
)
)
log.warning(f"Failed to load {model.model_type.replace('_', ' ')} model '{model.model_name}'. Clearing cache.")
model.clear_cache()
await run(_load, model)
return model
return await run(_load, model)
async def idle_shutdown_task() -> None:

View File

@@ -1,24 +1,40 @@
from typing import Any
from app.schemas import ModelType
from app.models.base import InferenceModel
from app.models.clip.textual import MClipTextualEncoder, OpenClipTextualEncoder
from app.models.clip.visual import OpenClipVisualEncoder
from app.schemas import ModelSource, ModelTask, ModelType
from .base import InferenceModel
from .clip import MCLIPEncoder, OpenCLIPEncoder
from .constants import is_insightface, is_mclip, is_openclip
from .facial_recognition import FaceRecognizer
from .constants import get_model_source
from .facial_recognition.detection import FaceDetector
from .facial_recognition.recognition import FaceRecognizer
def from_model_type(model_type: ModelType, model_name: str, **model_kwargs: Any) -> InferenceModel:
match model_type:
case ModelType.CLIP:
if is_openclip(model_name):
return OpenCLIPEncoder(model_name, **model_kwargs)
elif is_mclip(model_name):
return MCLIPEncoder(model_name, **model_kwargs)
case ModelType.FACIAL_RECOGNITION:
if is_insightface(model_name):
return FaceRecognizer(model_name, **model_kwargs)
def get_model_class(model_name: str, model_type: ModelType, model_task: ModelTask) -> type[InferenceModel]:
source = get_model_source(model_name)
match source, model_type, model_task:
case ModelSource.OPENCLIP | ModelSource.MCLIP, ModelType.VISUAL, ModelTask.SEARCH:
return OpenClipVisualEncoder
case ModelSource.OPENCLIP, ModelType.TEXTUAL, ModelTask.SEARCH:
return OpenClipTextualEncoder
case ModelSource.MCLIP, ModelType.TEXTUAL, ModelTask.SEARCH:
return MClipTextualEncoder
case ModelSource.INSIGHTFACE, ModelType.DETECTION, ModelTask.FACIAL_RECOGNITION:
return FaceDetector
case ModelSource.INSIGHTFACE, ModelType.RECOGNITION, ModelTask.FACIAL_RECOGNITION:
return FaceRecognizer
case _:
raise ValueError(f"Unknown model type {model_type}")
raise ValueError(f"Unknown model combination: {source}, {model_type}, {model_task}")
raise ValueError(f"Unknown {model_type} model {model_name}")
def from_model_type(model_name: str, model_type: ModelType, model_task: ModelTask, **kwargs: Any) -> InferenceModel:
return get_model_class(model_name, model_type, model_task)(model_name, **kwargs)
def get_model_deps(model_name: str, model_type: ModelType, model_task: ModelTask) -> list[tuple[ModelType, ModelTask]]:
return get_model_class(model_name, model_type, model_task).depends

View File

@@ -3,39 +3,37 @@ from __future__ import annotations
from abc import ABC, abstractmethod
from pathlib import Path
from shutil import rmtree
from typing import Any
from typing import Any, ClassVar
import onnxruntime as ort
from huggingface_hub import snapshot_download
import ann.ann
from app.models.constants import SUPPORTED_PROVIDERS
from app.sessions.ort import OrtSession
from ..config import get_cache_dir, get_hf_model_name, log, settings
from ..schemas import ModelRuntime, ModelType
from .ann import AnnSession
from ..config import clean_name, log, settings
from ..schemas import ModelFormat, ModelIdentity, ModelSession, ModelTask, ModelType
from ..sessions.ann import AnnSession
class InferenceModel(ABC):
_model_type: ModelType
depends: ClassVar[list[ModelIdentity]]
identity: ClassVar[ModelIdentity]
def __init__(
self,
model_name: str,
cache_dir: Path | str | None = None,
providers: list[str] | None = None,
provider_options: list[dict[str, Any]] | None = None,
sess_options: ort.SessionOptions | None = None,
preferred_runtime: ModelRuntime | None = None,
preferred_format: ModelFormat | None = None,
session: ModelSession | None = None,
**model_kwargs: Any,
) -> None:
self.loaded = False
self.model_name = model_name
self.cache_dir = Path(cache_dir) if cache_dir is not None else self.cache_dir_default
self.providers = providers if providers is not None else self.providers_default
self.provider_options = provider_options if provider_options is not None else self.provider_options_default
self.sess_options = sess_options if sess_options is not None else self.sess_options_default
self.preferred_runtime = preferred_runtime if preferred_runtime is not None else self.preferred_runtime_default
self.loaded = session is not None
self.load_attempts = 0
self.model_name = clean_name(model_name)
self.cache_dir = Path(cache_dir) if cache_dir is not None else self._cache_dir_default
self.model_format = preferred_format if preferred_format is not None else self._model_format_default
if session is not None:
self.session = session
def download(self) -> None:
if not self.cached:
@@ -47,35 +45,38 @@ class InferenceModel(ABC):
def load(self) -> None:
if self.loaded:
return
self.load_attempts += 1
self.download()
log.info(f"Loading {self.model_type.replace('-', ' ')} model '{self.model_name}' to memory")
self._load()
attempt = f"Attempt #{self.load_attempts + 1} to load" if self.load_attempts else "Loading"
log.info(f"{attempt} {self.model_type.replace('-', ' ')} model '{self.model_name}' to memory")
self.session = self._load()
self.loaded = True
def predict(self, inputs: Any, **model_kwargs: Any) -> Any:
def predict(self, *inputs: Any, **model_kwargs: Any) -> Any:
self.load()
if model_kwargs:
self.configure(**model_kwargs)
return self._predict(inputs)
return self._predict(*inputs, **model_kwargs)
@abstractmethod
def _predict(self, inputs: Any) -> Any: ...
def _predict(self, *inputs: Any, **model_kwargs: Any) -> Any: ...
def configure(self, **model_kwargs: Any) -> None:
def configure(self, **kwargs: Any) -> None:
pass
def _download(self) -> None:
ignore_patterns = [] if self.preferred_runtime == ModelRuntime.ARMNN else ["*.armnn"]
ignore_patterns = [] if self.model_format == ModelFormat.ARMNN else ["*.armnn"]
snapshot_download(
get_hf_model_name(self.model_name),
f"immich-app/{clean_name(self.model_name)}",
cache_dir=self.cache_dir,
local_dir=self.cache_dir,
local_dir_use_symlinks=False,
ignore_patterns=ignore_patterns,
)
@abstractmethod
def _load(self) -> None: ...
def _load(self) -> ModelSession:
return self._make_session(self.model_path)
def clear_cache(self) -> None:
if not self.cache_dir.exists():
@@ -99,34 +100,31 @@ class InferenceModel(ABC):
self.cache_dir.unlink()
self.cache_dir.mkdir(parents=True, exist_ok=True)
def _make_session(self, model_path: Path) -> AnnSession | ort.InferenceSession:
if not model_path.is_file():
onnx_path = model_path.with_suffix(".onnx")
if not onnx_path.is_file():
raise ValueError(f"Model path '{model_path}' does not exist")
log.warning(
f"Could not find model path '{model_path}'. " f"Falling back to ONNX model path '{onnx_path}' instead.",
)
model_path = onnx_path
def _make_session(self, model_path: Path) -> ModelSession:
match model_path.suffix:
case ".armnn":
session = AnnSession(model_path)
session: ModelSession = AnnSession(model_path)
case ".onnx":
session = ort.InferenceSession(
model_path.as_posix(),
sess_options=self.sess_options,
providers=self.providers,
provider_options=self.provider_options,
)
session = OrtSession(model_path)
case _:
raise ValueError(f"Unsupported model file type: {model_path.suffix}")
return session
@property
def model_dir(self) -> Path:
return self.cache_dir / self.model_type.value
@property
def model_path(self) -> Path:
return self.model_dir / f"model.{self.model_format}"
@property
def model_task(self) -> ModelTask:
return self.identity[1]
@property
def model_type(self) -> ModelType:
return self._model_type
return self.identity[0]
@property
def cache_dir(self) -> Path:
@@ -137,103 +135,26 @@ class InferenceModel(ABC):
self._cache_dir = cache_dir
@property
def cache_dir_default(self) -> Path:
return get_cache_dir(self.model_name, self.model_type)
def _cache_dir_default(self) -> Path:
return settings.cache_folder / self.model_task.value / self.model_name
@property
def cached(self) -> bool:
return self.cache_dir.is_dir() and any(self.cache_dir.iterdir())
return self.model_path.is_file()
@property
def providers(self) -> list[str]:
return self._providers
def model_format(self) -> ModelFormat:
return self._preferred_format
@providers.setter
def providers(self, providers: list[str]) -> None:
log.info(
(f"Setting '{self.model_name}' execution providers to {providers}, " "in descending order of preference"),
)
self._providers = providers
@model_format.setter
def model_format(self, preferred_format: ModelFormat) -> None:
log.debug(f"Setting preferred format to {preferred_format}")
self._preferred_format = preferred_format
@property
def providers_default(self) -> list[str]:
available_providers = set(ort.get_available_providers())
log.debug(f"Available ORT providers: {available_providers}")
if (openvino := "OpenVINOExecutionProvider") in available_providers:
device_ids: list[str] = ort.capi._pybind_state.get_available_openvino_device_ids()
log.debug(f"Available OpenVINO devices: {device_ids}")
gpu_devices = [device_id for device_id in device_ids if device_id.startswith("GPU")]
if not gpu_devices:
log.warning("No GPU device found in OpenVINO. Falling back to CPU.")
available_providers.remove(openvino)
return [provider for provider in SUPPORTED_PROVIDERS if provider in available_providers]
@property
def provider_options(self) -> list[dict[str, Any]]:
return self._provider_options
@provider_options.setter
def provider_options(self, provider_options: list[dict[str, Any]]) -> None:
log.debug(f"Setting execution provider options to {provider_options}")
self._provider_options = provider_options
@property
def provider_options_default(self) -> list[dict[str, Any]]:
options = []
for provider in self.providers:
match provider:
case "CPUExecutionProvider" | "CUDAExecutionProvider":
option = {"arena_extend_strategy": "kSameAsRequested"}
case "OpenVINOExecutionProvider":
option = {"device_type": "GPU_FP32", "cache_dir": (self.cache_dir / "openvino").as_posix()}
case _:
option = {}
options.append(option)
return options
@property
def sess_options(self) -> ort.SessionOptions:
return self._sess_options
@sess_options.setter
def sess_options(self, sess_options: ort.SessionOptions) -> None:
log.debug(f"Setting execution_mode to {sess_options.execution_mode.name}")
log.debug(f"Setting inter_op_num_threads to {sess_options.inter_op_num_threads}")
log.debug(f"Setting intra_op_num_threads to {sess_options.intra_op_num_threads}")
self._sess_options = sess_options
@property
def sess_options_default(self) -> ort.SessionOptions:
sess_options = ort.SessionOptions()
sess_options.enable_cpu_mem_arena = False
# avoid thread contention between models
if settings.model_inter_op_threads > 0:
sess_options.inter_op_num_threads = settings.model_inter_op_threads
# these defaults work well for CPU, but bottleneck GPU
elif settings.model_inter_op_threads == 0 and self.providers == ["CPUExecutionProvider"]:
sess_options.inter_op_num_threads = 1
if settings.model_intra_op_threads > 0:
sess_options.intra_op_num_threads = settings.model_intra_op_threads
elif settings.model_intra_op_threads == 0 and self.providers == ["CPUExecutionProvider"]:
sess_options.intra_op_num_threads = 2
if sess_options.inter_op_num_threads > 1:
sess_options.execution_mode = ort.ExecutionMode.ORT_PARALLEL
return sess_options
@property
def preferred_runtime(self) -> ModelRuntime:
return self._preferred_runtime
@preferred_runtime.setter
def preferred_runtime(self, preferred_runtime: ModelRuntime) -> None:
log.debug(f"Setting preferred runtime to {preferred_runtime}")
self._preferred_runtime = preferred_runtime
@property
def preferred_runtime_default(self) -> ModelRuntime:
return ModelRuntime.ARMNN if ann.ann.is_available and settings.ann else ModelRuntime.ONNX
def _model_format_default(self) -> ModelFormat:
prefer_ann = ann.ann.is_available and settings.ann
ann_exists = (self.model_dir / "model.armnn").is_file()
if prefer_ann and not ann_exists:
log.warning(f"ARM NN is available, but '{self.model_name}' does not support ARM NN. Falling back to ONNX.")
return ModelFormat.ARMNN if prefer_ann and ann_exists else ModelFormat.ONNX

View File

@@ -5,9 +5,9 @@ from aiocache.lock import OptimisticLock
from aiocache.plugins import TimingPlugin
from app.models import from_model_type
from app.models.base import InferenceModel
from ..schemas import ModelType, has_profiling
from .base import InferenceModel
from ..schemas import ModelTask, ModelType, has_profiling
class ModelCache:
@@ -31,28 +31,21 @@ class ModelCache:
if profiling:
plugins.append(TimingPlugin())
self.revalidate_enable = revalidate
self.should_revalidate = revalidate
self.cache = SimpleMemoryCache(timeout=timeout, plugins=plugins, namespace=None)
async def get(self, model_name: str, model_type: ModelType, **model_kwargs: Any) -> InferenceModel:
"""
Args:
model_name: Name of model in the model hub used for the task.
model_type: Model type or task, which determines which model zoo is used.
Returns:
model: The requested model.
"""
key = f"{model_name}{model_type.value}{model_kwargs.get('mode', '')}"
async def get(
self, model_name: str, model_type: ModelType, model_task: ModelTask, **model_kwargs: Any
) -> InferenceModel:
key = f"{model_name}{model_type}{model_task}"
async with OptimisticLock(self.cache, key) as lock:
model: InferenceModel | None = await self.cache.get(key)
if model is None:
model = from_model_type(model_type, model_name, **model_kwargs)
model = from_model_type(model_name, model_type, model_task, **model_kwargs)
await lock.cas(model, ttl=model_kwargs.get("ttl", None))
elif self.revalidate_enable:
elif self.should_revalidate:
await self.revalidate(key, model_kwargs.get("ttl", None))
return model

View File

@@ -1,189 +0,0 @@
import json
from abc import abstractmethod
from functools import cached_property
from io import BytesIO
from pathlib import Path
from typing import Any, Literal
import numpy as np
from numpy.typing import NDArray
from PIL import Image
from tokenizers import Encoding, Tokenizer
from app.config import clean_name, log
from app.models.transforms import crop, get_pil_resampling, normalize, resize, to_numpy
from app.schemas import ModelType
from .base import InferenceModel
class BaseCLIPEncoder(InferenceModel):
_model_type = ModelType.CLIP
def __init__(
self,
model_name: str,
cache_dir: Path | str | None = None,
mode: Literal["text", "vision"] | None = None,
**model_kwargs: Any,
) -> None:
self.mode = mode
super().__init__(model_name, cache_dir, **model_kwargs)
def _load(self) -> None:
if self.mode == "text" or self.mode is None:
log.debug(f"Loading clip text model '{self.model_name}'")
self.text_model = self._make_session(self.textual_path)
log.debug(f"Loaded clip text model '{self.model_name}'")
if self.mode == "vision" or self.mode is None:
log.debug(f"Loading clip vision model '{self.model_name}'")
self.vision_model = self._make_session(self.visual_path)
log.debug(f"Loaded clip vision model '{self.model_name}'")
def _predict(self, image_or_text: Image.Image | str) -> NDArray[np.float32]:
if isinstance(image_or_text, bytes):
image_or_text = Image.open(BytesIO(image_or_text))
match image_or_text:
case Image.Image():
if self.mode == "text":
raise TypeError("Cannot encode image as text-only model")
outputs: NDArray[np.float32] = self.vision_model.run(None, self.transform(image_or_text))[0][0]
case str():
if self.mode == "vision":
raise TypeError("Cannot encode text as vision-only model")
outputs = self.text_model.run(None, self.tokenize(image_or_text))[0][0]
case _:
raise TypeError(f"Expected Image or str, but got: {type(image_or_text)}")
return outputs
@abstractmethod
def tokenize(self, text: str) -> dict[str, NDArray[np.int32]]:
pass
@abstractmethod
def transform(self, image: Image.Image) -> dict[str, NDArray[np.float32]]:
pass
@property
def textual_dir(self) -> Path:
return self.cache_dir / "textual"
@property
def visual_dir(self) -> Path:
return self.cache_dir / "visual"
@property
def model_cfg_path(self) -> Path:
return self.cache_dir / "config.json"
@property
def textual_path(self) -> Path:
return self.textual_dir / f"model.{self.preferred_runtime}"
@property
def visual_path(self) -> Path:
return self.visual_dir / f"model.{self.preferred_runtime}"
@property
def tokenizer_file_path(self) -> Path:
return self.textual_dir / "tokenizer.json"
@property
def tokenizer_cfg_path(self) -> Path:
return self.textual_dir / "tokenizer_config.json"
@property
def preprocess_cfg_path(self) -> Path:
return self.visual_dir / "preprocess_cfg.json"
@property
def cached(self) -> bool:
return self.textual_path.is_file() and self.visual_path.is_file()
@cached_property
def model_cfg(self) -> dict[str, Any]:
log.debug(f"Loading model config for CLIP model '{self.model_name}'")
model_cfg: dict[str, Any] = json.load(self.model_cfg_path.open())
log.debug(f"Loaded model config for CLIP model '{self.model_name}'")
return model_cfg
@cached_property
def tokenizer_file(self) -> dict[str, Any]:
log.debug(f"Loading tokenizer file for CLIP model '{self.model_name}'")
tokenizer_file: dict[str, Any] = json.load(self.tokenizer_file_path.open())
log.debug(f"Loaded tokenizer file for CLIP model '{self.model_name}'")
return tokenizer_file
@cached_property
def tokenizer_cfg(self) -> dict[str, Any]:
log.debug(f"Loading tokenizer config for CLIP model '{self.model_name}'")
tokenizer_cfg: dict[str, Any] = json.load(self.tokenizer_cfg_path.open())
log.debug(f"Loaded tokenizer config for CLIP model '{self.model_name}'")
return tokenizer_cfg
@cached_property
def preprocess_cfg(self) -> dict[str, Any]:
log.debug(f"Loading visual preprocessing config for CLIP model '{self.model_name}'")
preprocess_cfg: dict[str, Any] = json.load(self.preprocess_cfg_path.open())
log.debug(f"Loaded visual preprocessing config for CLIP model '{self.model_name}'")
return preprocess_cfg
class OpenCLIPEncoder(BaseCLIPEncoder):
def __init__(
self,
model_name: str,
cache_dir: Path | str | None = None,
mode: Literal["text", "vision"] | None = None,
**model_kwargs: Any,
) -> None:
super().__init__(clean_name(model_name), cache_dir, mode, **model_kwargs)
def _load(self) -> None:
super()._load()
self._load_tokenizer()
size: list[int] | int = self.preprocess_cfg["size"]
self.size = size[0] if isinstance(size, list) else size
self.resampling = get_pil_resampling(self.preprocess_cfg["interpolation"])
self.mean = np.array(self.preprocess_cfg["mean"], dtype=np.float32)
self.std = np.array(self.preprocess_cfg["std"], dtype=np.float32)
def _load_tokenizer(self) -> Tokenizer:
log.debug(f"Loading tokenizer for CLIP model '{self.model_name}'")
text_cfg: dict[str, Any] = self.model_cfg["text_cfg"]
context_length: int = text_cfg.get("context_length", 77)
pad_token: str = self.tokenizer_cfg["pad_token"]
self.tokenizer: Tokenizer = Tokenizer.from_file(self.tokenizer_file_path.as_posix())
pad_id: int = self.tokenizer.token_to_id(pad_token)
self.tokenizer.enable_padding(length=context_length, pad_token=pad_token, pad_id=pad_id)
self.tokenizer.enable_truncation(max_length=context_length)
log.debug(f"Loaded tokenizer for CLIP model '{self.model_name}'")
def tokenize(self, text: str) -> dict[str, NDArray[np.int32]]:
tokens: Encoding = self.tokenizer.encode(text)
return {"text": np.array([tokens.ids], dtype=np.int32)}
def transform(self, image: Image.Image) -> dict[str, NDArray[np.float32]]:
image = resize(image, self.size)
image = crop(image, self.size)
image_np = to_numpy(image)
image_np = normalize(image_np, self.mean, self.std)
return {"image": np.expand_dims(image_np.transpose(2, 0, 1), 0)}
class MCLIPEncoder(OpenCLIPEncoder):
def tokenize(self, text: str) -> dict[str, NDArray[np.int32]]:
tokens: Encoding = self.tokenizer.encode(text)
return {
"input_ids": np.array([tokens.ids], dtype=np.int32),
"attention_mask": np.array([tokens.attention_mask], dtype=np.int32),
}

View File

@@ -0,0 +1,98 @@
import json
from abc import abstractmethod
from functools import cached_property
from pathlib import Path
from typing import Any
import numpy as np
from numpy.typing import NDArray
from tokenizers import Encoding, Tokenizer
from app.config import log
from app.models.base import InferenceModel
from app.schemas import ModelSession, ModelTask, ModelType
class BaseCLIPTextualEncoder(InferenceModel):
depends = []
identity = (ModelType.TEXTUAL, ModelTask.SEARCH)
def _predict(self, inputs: str, **kwargs: Any) -> NDArray[np.float32]:
res: NDArray[np.float32] = self.session.run(None, self.tokenize(inputs))[0][0]
return res
def _load(self) -> ModelSession:
log.debug(f"Loading tokenizer for CLIP model '{self.model_name}'")
self.tokenizer = self._load_tokenizer()
log.debug(f"Loaded tokenizer for CLIP model '{self.model_name}'")
return super()._load()
@abstractmethod
def _load_tokenizer(self) -> Tokenizer:
pass
@abstractmethod
def tokenize(self, text: str) -> dict[str, NDArray[np.int32]]:
pass
@property
def model_cfg_path(self) -> Path:
return self.cache_dir / "config.json"
@property
def tokenizer_file_path(self) -> Path:
return self.model_dir / "tokenizer.json"
@property
def tokenizer_cfg_path(self) -> Path:
return self.model_dir / "tokenizer_config.json"
@cached_property
def model_cfg(self) -> dict[str, Any]:
log.debug(f"Loading model config for CLIP model '{self.model_name}'")
model_cfg: dict[str, Any] = json.load(self.model_cfg_path.open())
log.debug(f"Loaded model config for CLIP model '{self.model_name}'")
return model_cfg
@cached_property
def tokenizer_file(self) -> dict[str, Any]:
log.debug(f"Loading tokenizer file for CLIP model '{self.model_name}'")
tokenizer_file: dict[str, Any] = json.load(self.tokenizer_file_path.open())
log.debug(f"Loaded tokenizer file for CLIP model '{self.model_name}'")
return tokenizer_file
@cached_property
def tokenizer_cfg(self) -> dict[str, Any]:
log.debug(f"Loading tokenizer config for CLIP model '{self.model_name}'")
tokenizer_cfg: dict[str, Any] = json.load(self.tokenizer_cfg_path.open())
log.debug(f"Loaded tokenizer config for CLIP model '{self.model_name}'")
return tokenizer_cfg
class OpenClipTextualEncoder(BaseCLIPTextualEncoder):
def _load_tokenizer(self) -> Tokenizer:
text_cfg: dict[str, Any] = self.model_cfg["text_cfg"]
context_length: int = text_cfg.get("context_length", 77)
pad_token: str = self.tokenizer_cfg["pad_token"]
tokenizer: Tokenizer = Tokenizer.from_file(self.tokenizer_file_path.as_posix())
pad_id: int = tokenizer.token_to_id(pad_token)
tokenizer.enable_padding(length=context_length, pad_token=pad_token, pad_id=pad_id)
tokenizer.enable_truncation(max_length=context_length)
return tokenizer
def tokenize(self, text: str) -> dict[str, NDArray[np.int32]]:
tokens: Encoding = self.tokenizer.encode(text)
return {"text": np.array([tokens.ids], dtype=np.int32)}
class MClipTextualEncoder(OpenClipTextualEncoder):
def tokenize(self, text: str) -> dict[str, NDArray[np.int32]]:
tokens: Encoding = self.tokenizer.encode(text)
return {
"input_ids": np.array([tokens.ids], dtype=np.int32),
"attention_mask": np.array([tokens.attention_mask], dtype=np.int32),
}

View File

@@ -0,0 +1,69 @@
import json
from abc import abstractmethod
from functools import cached_property
from pathlib import Path
from typing import Any
import numpy as np
from numpy.typing import NDArray
from PIL import Image
from app.config import log
from app.models.base import InferenceModel
from app.models.transforms import crop_pil, decode_pil, get_pil_resampling, normalize, resize_pil, to_numpy
from app.schemas import ModelSession, ModelTask, ModelType
class BaseCLIPVisualEncoder(InferenceModel):
depends = []
identity = (ModelType.VISUAL, ModelTask.SEARCH)
def _predict(self, inputs: Image.Image | bytes, **kwargs: Any) -> NDArray[np.float32]:
image = decode_pil(inputs)
res: NDArray[np.float32] = self.session.run(None, self.transform(image))[0][0]
return res
@abstractmethod
def transform(self, image: Image.Image) -> dict[str, NDArray[np.float32]]:
pass
@property
def model_cfg_path(self) -> Path:
return self.cache_dir / "config.json"
@property
def preprocess_cfg_path(self) -> Path:
return self.model_dir / "preprocess_cfg.json"
@cached_property
def model_cfg(self) -> dict[str, Any]:
log.debug(f"Loading model config for CLIP model '{self.model_name}'")
model_cfg: dict[str, Any] = json.load(self.model_cfg_path.open())
log.debug(f"Loaded model config for CLIP model '{self.model_name}'")
return model_cfg
@cached_property
def preprocess_cfg(self) -> dict[str, Any]:
log.debug(f"Loading visual preprocessing config for CLIP model '{self.model_name}'")
preprocess_cfg: dict[str, Any] = json.load(self.preprocess_cfg_path.open())
log.debug(f"Loaded visual preprocessing config for CLIP model '{self.model_name}'")
return preprocess_cfg
class OpenClipVisualEncoder(BaseCLIPVisualEncoder):
def _load(self) -> ModelSession:
size: list[int] | int = self.preprocess_cfg["size"]
self.size = size[0] if isinstance(size, list) else size
self.resampling = get_pil_resampling(self.preprocess_cfg["interpolation"])
self.mean = np.array(self.preprocess_cfg["mean"], dtype=np.float32)
self.std = np.array(self.preprocess_cfg["std"], dtype=np.float32)
return super()._load()
def transform(self, image: Image.Image) -> dict[str, NDArray[np.float32]]:
image = resize_pil(image, self.size)
image = crop_pil(image, self.size)
image_np = to_numpy(image)
image_np = normalize(image_np, self.mean, self.std)
return {"image": np.expand_dims(image_np.transpose(2, 0, 1), 0)}

View File

@@ -1,4 +1,5 @@
from app.config import clean_name
from app.schemas import ModelSource
_OPENCLIP_MODELS = {
"RN50__openai",
@@ -54,13 +55,16 @@ _INSIGHTFACE_MODELS = {
SUPPORTED_PROVIDERS = ["CUDAExecutionProvider", "OpenVINOExecutionProvider", "CPUExecutionProvider"]
def is_openclip(model_name: str) -> bool:
return clean_name(model_name) in _OPENCLIP_MODELS
def get_model_source(model_name: str) -> ModelSource | None:
cleaned_name = clean_name(model_name)
if cleaned_name in _INSIGHTFACE_MODELS:
return ModelSource.INSIGHTFACE
def is_mclip(model_name: str) -> bool:
return clean_name(model_name) in _MCLIP_MODELS
if cleaned_name in _MCLIP_MODELS:
return ModelSource.MCLIP
if cleaned_name in _OPENCLIP_MODELS:
return ModelSource.OPENCLIP
def is_insightface(model_name: str) -> bool:
return clean_name(model_name) in _INSIGHTFACE_MODELS
return None

View File

@@ -1,90 +0,0 @@
from pathlib import Path
from typing import Any
import cv2
import numpy as np
from insightface.model_zoo import ArcFaceONNX, RetinaFace
from insightface.utils.face_align import norm_crop
from numpy.typing import NDArray
from app.config import clean_name
from app.schemas import Face, ModelType, is_ndarray
from .base import InferenceModel
class FaceRecognizer(InferenceModel):
_model_type = ModelType.FACIAL_RECOGNITION
def __init__(
self,
model_name: str,
min_score: float = 0.7,
cache_dir: Path | str | None = None,
**model_kwargs: Any,
) -> None:
self.min_score = model_kwargs.pop("minScore", min_score)
super().__init__(clean_name(model_name), cache_dir, **model_kwargs)
def _load(self) -> None:
self.det_model = RetinaFace(session=self._make_session(self.det_file))
self.rec_model = ArcFaceONNX(
self.rec_file.with_suffix(".onnx").as_posix(),
session=self._make_session(self.rec_file),
)
self.det_model.prepare(
ctx_id=0,
det_thresh=self.min_score,
input_size=(640, 640),
)
self.rec_model.prepare(ctx_id=0)
def _predict(self, image: NDArray[np.uint8] | bytes) -> list[Face]:
if isinstance(image, bytes):
decoded_image = cv2.imdecode(np.frombuffer(image, np.uint8), cv2.IMREAD_COLOR)
else:
decoded_image = image
assert is_ndarray(decoded_image, np.uint8)
bboxes, kpss = self.det_model.detect(decoded_image)
if bboxes.size == 0:
return []
assert is_ndarray(kpss, np.float32)
scores = bboxes[:, 4].tolist()
bboxes = bboxes[:, :4].round().tolist()
results = []
height, width, _ = decoded_image.shape
for (x1, y1, x2, y2), score, kps in zip(bboxes, scores, kpss):
cropped_img = norm_crop(decoded_image, kps)
embedding: NDArray[np.float32] = self.rec_model.get_feat(cropped_img)[0]
face: Face = {
"imageWidth": width,
"imageHeight": height,
"boundingBox": {
"x1": x1,
"y1": y1,
"x2": x2,
"y2": y2,
},
"score": score,
"embedding": embedding,
}
results.append(face)
return results
@property
def cached(self) -> bool:
return self.det_file.is_file() and self.rec_file.is_file()
@property
def det_file(self) -> Path:
return self.cache_dir / "detection" / f"model.{self.preferred_runtime}"
@property
def rec_file(self) -> Path:
return self.cache_dir / "recognition" / f"model.{self.preferred_runtime}"
def configure(self, **model_kwargs: Any) -> None:
self.det_model.det_thresh = model_kwargs.pop("minScore", self.det_model.det_thresh)

View File

@@ -0,0 +1,48 @@
from pathlib import Path
from typing import Any
import numpy as np
from insightface.model_zoo import RetinaFace
from numpy.typing import NDArray
from app.models.base import InferenceModel
from app.models.transforms import decode_cv2
from app.schemas import FaceDetectionOutput, ModelSession, ModelTask, ModelType
class FaceDetector(InferenceModel):
depends = []
identity = (ModelType.DETECTION, ModelTask.FACIAL_RECOGNITION)
def __init__(
self,
model_name: str,
min_score: float = 0.7,
cache_dir: Path | str | None = None,
**model_kwargs: Any,
) -> None:
self.min_score = model_kwargs.pop("minScore", min_score)
super().__init__(model_name, cache_dir, **model_kwargs)
def _load(self) -> ModelSession:
session = self._make_session(self.model_path)
self.model = RetinaFace(session=session)
self.model.prepare(ctx_id=0, det_thresh=self.min_score, input_size=(640, 640))
return session
def _predict(self, inputs: NDArray[np.uint8] | bytes, **kwargs: Any) -> FaceDetectionOutput:
inputs = decode_cv2(inputs)
bboxes, landmarks = self._detect(inputs)
return {
"boxes": bboxes[:, :4].round(),
"scores": bboxes[:, 4],
"landmarks": landmarks,
}
def _detect(self, inputs: NDArray[np.uint8] | bytes) -> tuple[NDArray[np.float32], NDArray[np.float32]]:
return self.model.detect(inputs) # type: ignore
def configure(self, **kwargs: Any) -> None:
self.model.det_thresh = kwargs.pop("minScore", self.model.det_thresh)

View File

@@ -0,0 +1,86 @@
from pathlib import Path
from typing import Any
import numpy as np
import onnx
from insightface.model_zoo import ArcFaceONNX
from insightface.utils.face_align import norm_crop
from numpy.typing import NDArray
from onnx.tools.update_model_dims import update_inputs_outputs_dims
from PIL import Image
from app.config import clean_name, log
from app.models.base import InferenceModel
from app.models.transforms import decode_cv2
from app.schemas import FaceDetectionOutput, FacialRecognitionOutput, ModelFormat, ModelSession, ModelTask, ModelType
from app.sessions import has_batch_axis
class FaceRecognizer(InferenceModel):
depends = [(ModelType.DETECTION, ModelTask.FACIAL_RECOGNITION)]
identity = (ModelType.RECOGNITION, ModelTask.FACIAL_RECOGNITION)
def __init__(
self,
model_name: str,
min_score: float = 0.7,
cache_dir: Path | str | None = None,
**model_kwargs: Any,
) -> None:
super().__init__(clean_name(model_name), cache_dir, **model_kwargs)
self.min_score = model_kwargs.pop("minScore", min_score)
self.batch = self.model_format == ModelFormat.ONNX
def _load(self) -> ModelSession:
session = self._make_session(self.model_path)
if self.model_format == ModelFormat.ONNX and not has_batch_axis(session):
self._add_batch_axis(self.model_path)
session = self._make_session(self.model_path)
self.model = ArcFaceONNX(
self.model_path.with_suffix(".onnx").as_posix(),
session=session,
)
return session
def _predict(
self, inputs: NDArray[np.uint8] | bytes | Image.Image, faces: FaceDetectionOutput, **kwargs: Any
) -> FacialRecognitionOutput:
if faces["boxes"].shape[0] == 0:
return []
inputs = decode_cv2(inputs)
cropped_faces = self._crop(inputs, faces)
embeddings = self._predict_batch(cropped_faces) if self.batch else self._predict_single(cropped_faces)
return self.postprocess(faces, embeddings)
def _predict_batch(self, cropped_faces: list[NDArray[np.uint8]]) -> NDArray[np.float32]:
embeddings: NDArray[np.float32] = self.model.get_feat(cropped_faces)
return embeddings
def _predict_single(self, cropped_faces: list[NDArray[np.uint8]]) -> NDArray[np.float32]:
embeddings: list[NDArray[np.float32]] = []
for face in cropped_faces:
embeddings.append(self.model.get_feat(face))
return np.concatenate(embeddings, axis=0)
def postprocess(self, faces: FaceDetectionOutput, embeddings: NDArray[np.float32]) -> FacialRecognitionOutput:
return [
{
"boundingBox": {"x1": x1, "y1": y1, "x2": x2, "y2": y2},
"embedding": embedding,
"score": score,
}
for (x1, y1, x2, y2), embedding, score in zip(faces["boxes"], embeddings, faces["scores"])
]
def _crop(self, image: NDArray[np.uint8], faces: FaceDetectionOutput) -> list[NDArray[np.uint8]]:
return [norm_crop(image, landmark) for landmark in faces["landmarks"]]
def _add_batch_axis(self, model_path: Path) -> None:
log.debug(f"Adding batch axis to model {model_path}")
proto = onnx.load(model_path)
static_input_dims = [shape.dim_value for shape in proto.graph.input[0].type.tensor_type.shape.dim[1:]]
static_output_dims = [shape.dim_value for shape in proto.graph.output[0].type.tensor_type.shape.dim[1:]]
input_dims = {proto.graph.input[0].name: ["batch"] + static_input_dims}
output_dims = {proto.graph.output[0].name: ["batch"] + static_output_dims}
updated_proto = update_inputs_outputs_dims(proto, input_dims, output_dims)
onnx.save(updated_proto, model_path)

View File

@@ -1,3 +1,7 @@
from io import BytesIO
from typing import IO
import cv2
import numpy as np
from numpy.typing import NDArray
from PIL import Image
@@ -5,7 +9,7 @@ from PIL import Image
_PIL_RESAMPLING_METHODS = {resampling.name.lower(): resampling for resampling in Image.Resampling}
def resize(img: Image.Image, size: int) -> Image.Image:
def resize_pil(img: Image.Image, size: int) -> Image.Image:
if img.width < img.height:
return img.resize((size, int((img.height / img.width) * size)), resample=Image.Resampling.BICUBIC)
else:
@@ -13,7 +17,7 @@ def resize(img: Image.Image, size: int) -> Image.Image:
# https://stackoverflow.com/a/60883103
def crop(img: Image.Image, size: int) -> Image.Image:
def crop_pil(img: Image.Image, size: int) -> Image.Image:
left = int((img.size[0] / 2) - (size / 2))
upper = int((img.size[1] / 2) - (size / 2))
right = left + size
@@ -23,14 +27,36 @@ def crop(img: Image.Image, size: int) -> Image.Image:
def to_numpy(img: Image.Image) -> NDArray[np.float32]:
return np.asarray(img.convert("RGB")).astype(np.float32) / 255.0
return np.asarray(img if img.mode == "RGB" else img.convert("RGB"), dtype=np.float32) / 255.0
def normalize(
img: NDArray[np.float32], mean: float | NDArray[np.float32], std: float | NDArray[np.float32]
) -> NDArray[np.float32]:
return (img - mean) / std
return np.divide(img - mean, std, dtype=np.float32)
def get_pil_resampling(resample: str) -> Image.Resampling:
return _PIL_RESAMPLING_METHODS[resample.lower()]
def pil_to_cv2(image: Image.Image) -> NDArray[np.uint8]:
return cv2.cvtColor(np.array(image), cv2.COLOR_RGB2BGR) # type: ignore
def decode_pil(image_bytes: bytes | IO[bytes] | Image.Image) -> Image.Image:
if isinstance(image_bytes, Image.Image):
return image_bytes
image = Image.open(BytesIO(image_bytes) if isinstance(image_bytes, bytes) else image_bytes)
image.load() # type: ignore
if not image.mode == "RGB":
image = image.convert("RGB")
return image
def decode_cv2(image_bytes: NDArray[np.uint8] | bytes | Image.Image) -> NDArray[np.uint8]:
if isinstance(image_bytes, bytes):
image_bytes = decode_pil(image_bytes) # pillow is much faster than cv2
if isinstance(image_bytes, Image.Image):
return pil_to_cv2(image_bytes)
return image_bytes

View File

@@ -1,5 +1,5 @@
from enum import Enum
from typing import Any, Protocol, TypedDict, TypeGuard
from typing import Any, Literal, Protocol, TypedDict, TypeGuard, TypeVar
import numpy as np
import numpy.typing as npt
@@ -28,31 +28,99 @@ class BoundingBox(TypedDict):
y2: int
class ModelType(StrEnum):
CLIP = "clip"
class ModelTask(StrEnum):
FACIAL_RECOGNITION = "facial-recognition"
SEARCH = "clip"
class ModelRuntime(StrEnum):
ONNX = "onnx"
class ModelType(StrEnum):
DETECTION = "detection"
RECOGNITION = "recognition"
TEXTUAL = "textual"
VISUAL = "visual"
class ModelFormat(StrEnum):
ARMNN = "armnn"
ONNX = "onnx"
class ModelSource(StrEnum):
INSIGHTFACE = "insightface"
MCLIP = "mclip"
OPENCLIP = "openclip"
ModelIdentity = tuple[ModelType, ModelTask]
class SessionNode(Protocol):
@property
def name(self) -> str | None: ...
@property
def shape(self) -> tuple[int, ...]: ...
class ModelSession(Protocol):
def run(
self,
output_names: list[str] | None,
input_feed: dict[str, npt.NDArray[np.float32]] | dict[str, npt.NDArray[np.int32]],
run_options: Any = None,
) -> list[npt.NDArray[np.float32]]: ...
def get_inputs(self) -> list[SessionNode]: ...
def get_outputs(self) -> list[SessionNode]: ...
class HasProfiling(Protocol):
profiling: dict[str, float]
class Face(TypedDict):
class FaceDetectionOutput(TypedDict):
boxes: npt.NDArray[np.float32]
scores: npt.NDArray[np.float32]
landmarks: npt.NDArray[np.float32]
class DetectedFace(TypedDict):
boundingBox: BoundingBox
embedding: npt.NDArray[np.float32]
imageWidth: int
imageHeight: int
score: float
FacialRecognitionOutput = list[DetectedFace]
class PipelineEntry(TypedDict):
modelName: str
options: dict[str, Any]
PipelineRequest = dict[ModelTask, dict[ModelType, PipelineEntry]]
class InferenceEntry(TypedDict):
name: str
task: ModelTask
type: ModelType
options: dict[str, Any]
InferenceEntries = tuple[list[InferenceEntry], list[InferenceEntry]]
InferenceResponse = dict[ModelTask | Literal["imageHeight"] | Literal["imageWidth"], Any]
def has_profiling(obj: Any) -> TypeGuard[HasProfiling]:
return hasattr(obj, "profiling") and isinstance(obj.profiling, dict)
def is_ndarray(obj: Any, dtype: "type[np._DTypeScalar_co]") -> "TypeGuard[npt.NDArray[np._DTypeScalar_co]]":
return isinstance(obj, np.ndarray) and obj.dtype == dtype
T = TypeVar("T")

View File

@@ -0,0 +1,5 @@
from app.schemas import ModelSession
def has_batch_axis(session: ModelSession) -> bool:
return not isinstance(session.get_inputs()[0].shape[0], int) or session.get_inputs()[0].shape[0] < 0

Some files were not shown because too many files have changed in this diff Show More