Compare commits

..

429 Commits

Author SHA1 Message Date
Yaros
4bd2154cbc fix: admin user details responsive layout 2026-07-21 19:17:01 +02:00
Santo Shakil
d5adfb97dd fix(mobile): treat wired ethernet as unmetered on ios (#29351)
check the metered flags instead of the interface type, so ethernet counts like android already does.
2026-07-21 11:02:36 -05:00
Priyanshu
b70cb58bc8 fix(web): improve slideshow controls visibility on bright backgrounds (#29950)
Co-authored-by: priyanshuANDcoad <priyanshu23154050@akgec.ac.in>
2026-07-21 15:58:51 +00:00
Santo Shakil
943bfafb7a fix(mobile): show real error when an asset can't be added to an album (#29754)
* fix(mobile): show real error when an asset can't be added to an album

* fix(mobile): show added toast when an add-to-album partly succeeds

* docs(mobile): explain why a partial add-to-album shows added
2026-07-21 10:56:50 -05:00
Timon
ee4bd3f833 feat: add album asset event handling (#29008)
* feat: add album asset event handling

* fix medium tests

* patch medium test

* collapse in AlbumUpdate
2026-07-21 10:55:46 -05:00
Adam Gastineau
7a7303aceb fix(mobile): properly group download tasks for Live Photos (#29952)
* fix(mobile): properly group download tasks for Live Photos

* Remove completely unused onLivePhotoDownloadStatus

* Remove dead code
2026-07-21 10:45:15 -05:00
Adam Gastineau
0a7c067b20 chore(mobile): bump maplibre to 0.26.2 in SwiftPM lock (#30108) 2026-07-21 10:44:30 -05:00
Luis Nachtigall
c8de56c422 fix(mobile): prevent duplicate album creation during submission (#30003)
* fix: prevent duplicate album creation during submission

* Nit fix

---------

Co-authored-by: Adam Gastineau <adam@agg.im>
2026-07-21 15:31:23 +00:00
shenlong
0e4572c37b chore: pin photo_manager to 3.9.0 (#30091)
Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2026-07-21 10:06:27 -05:00
Santo Shakil
14a2776453 fix(mobile): add album picker to the partner view bottom sheet (#30099) 2026-07-21 10:05:34 -05:00
renovate[bot]
59bc81423c chore(deps): update github-actions (major) (#30095) 2026-07-21 16:37:38 +02:00
Roberto Carlos Martinez Arriaga
e6fff3b15e feat(web): add maintenance link to command palette (#30016) 2026-07-21 16:27:23 +02:00
renovate[bot]
755df23b22 chore(deps): update ghcr.io/devcontainers/features/docker-in-docker docker tag to v4 (#30094) 2026-07-21 16:05:54 +02:00
Jason Rasmussen
899f547053 fix(web): lens model search (#30088) 2026-07-21 13:54:28 +00:00
Santo Shakil
4938fd4c89 fix(mobile): back up files moved into a watched folder on android (#29872) 2026-07-21 19:09:50 +05:30
Santo Shakil
e81a79224c fix(mobile): stop double close animation when dismissing an asset (#29413) 2026-07-21 19:08:45 +05:30
shenlong
7b0f58b2c1 refactor: gracefully stop during engine startup (#29179)
Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2026-07-21 19:08:13 +05:30
renovate[bot]
ae8398ffe4 chore(deps): update docker.io/valkey/valkey:9 docker digest to 8e8d64b (#30085) 2026-07-21 15:36:01 +02:00
renovate[bot]
aa08dad1f5 chore(deps): update dependency eslint-plugin-unicorn to v72 (#30092)
Co-authored-by: Daniel Dietzler <mail@ddietzler.dev>
2026-07-21 15:33:46 +02:00
renovate[bot]
ce022233ae chore(deps): update base-image to v202607211135 (#30090) 2026-07-21 13:58:02 +02:00
Rust
1b4d41324e fix(server): file uploads for files with extension only filenames (#30024)
Co-authored-by: Daniel Dietzler <36593685+danieldietzler@users.noreply.github.com>
Co-authored-by: Daniel Dietzler <mail@ddietzler.dev>
2026-07-21 11:57:28 +00:00
renovate[bot]
8365cdd59e chore(deps): update dependency typescript to v7 (#29903)
Co-authored-by: Daniel Dietzler <mail@ddietzler.dev>
2026-07-21 13:36:57 +02:00
Mees Frensel
1cec7021e8 fix(web): space between device type and version (#30102) 2026-07-21 11:08:29 +00:00
renovate[bot]
1b0f473457 fix(deps): update typescript-projects (#30087)
Co-authored-by: Daniel Dietzler <mail@ddietzler.dev>
2026-07-21 11:01:23 +00:00
renovate[bot]
e918658cc1 chore(deps): update ghcr.io/jdx/mise docker tag to v2026.7.11 (#30086) 2026-07-21 12:24:32 +02:00
Nikola Nedevski
2e587fc7e8 feat(docs): add bulgarian readme (#29427)
* Added Bulgarian readme.md

* Updated collaborators link for BG readme collaborators section.
2026-07-21 00:10:08 -04:00
renovate[bot]
df970da59e chore(deps): update dependency eslint-plugin-unicorn to v70 - abandoned (#29684)
* chore(deps): update dependency eslint-plugin-unicorn to v70

* fix: linting

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Daniel Dietzler <mail@ddietzler.dev>
2026-07-20 23:47:14 -04:00
Rust
8061a2e5ff fix(web): fix Country/State filters when set to Unknown (#30026)
* fix(web): create emptyStringToNull helper func and apply it to country/state search payload generation

* chore: feedback

* fix: other empty string fields

---------

Co-authored-by: Jason Rasmussen <jrasm91@gmail.com>
2026-07-20 21:15:00 -05:00
Daniel Dietzler
77091b0107 fix: search statistics locked folder permissions (#30063)
fix; search statistics locked folder permissions
2026-07-20 20:08:42 -04:00
Daniel Dietzler
4c754f2999 fix: timebuckets locked permissions (#30066) 2026-07-20 20:07:56 -04:00
Timon
6fa3f2feac chore: bump openapi-generator version to v7.24.0 (#30067)
* remove patch in favor of [OpenAPITools/openapi-generator#23930](https://github.com/OpenAPITools/openapi-generator/issues/23930)

partially revert "chore(mobile): make openapi requests abortable (#28692)"

* gen client

* bump openapi-generator version to v7.24.0

* gen client

* enum migration changes

* drop enum exhaustion switch check
2026-07-20 20:06:23 -04:00
Adam Gastineau
3f6897ef80 chore(mobile): enable Xcode displaying issues from Flutter inline (#30080) 2026-07-20 14:16:12 -05:00
Matthias Keim
89d0a9d59f fix(web): attach file picker input to DOM so iOS Safari fires change … (#29660)
Co-authored-by: Daniel Dietzler <mail@ddietzler.dev>
2026-07-20 19:03:42 +00:00
Adam Gastineau
45a6ea84af fix(mobile): prevent crash on video widget dispose (#30078) 2026-07-20 19:00:37 +00:00
shenlong
bf64f3867b fix: do not show the whats new page on fresh login (#30072)
* fix: do not show the whats new page on fresh login

* review suggestion

---------

Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2026-07-20 12:24:01 -05:00
Priyanshu
4a4d468aa2 fix(web): refresh folder view after asset deletion (#29899) 2026-07-20 18:20:52 +02:00
Priyanshu
522def1ed6 fix(web): align ContextMenu z-index with design-system token (#30015)
Co-authored-by: priyanshuANDcoad <priyanshu23154050@akgec.ac.in>
Co-authored-by: Daniel Dietzler <mail@ddietzler.dev>
2026-07-20 13:54:59 +00:00
Timon
3adc3920fb chore: bump openapi-generator version to v7.23.0 (#28916) 2026-07-20 15:06:51 +02:00
Matthew Momjian
00cb50cc67 fix(docs): remove ref to synology channel (#30051)
synology channel
2026-07-19 21:22:17 -05:00
Alex
f2b0b696f6 fix: long press share quality override preference settings (#30030) 2026-07-18 12:07:18 -05:00
Luis Nachtigall
b5401eb120 fix(mobile): update album creation to use user-defined name from dialog (#30002)
fix: update album creation to use user-defined name from dialog
2026-07-17 23:45:20 +05:30
Ben Beckford
12fc8bac18 feat: workflow filter assets by upload path (#30000)
Co-authored-by: Daniel Dietzler <36593685+danieldietzler@users.noreply.github.com>
2026-07-17 09:46:48 +00:00
Ben Beckford
f2c00c107d fix(server): return workflow steps in ascending order (#29999) 2026-07-17 11:30:40 +02:00
shenlong
f19f30ec66 fix: reset override for kebab buttons (#29380)
Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2026-07-17 01:19:55 +05:30
shenlong
297316c7b8 refactor: owned action assets filter (#29382)
Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2026-07-16 19:49:30 +00:00
renovate[bot]
f18065b3c2 chore(deps): update dependency @types/node to ^24.13.3 (#29892) 2026-07-16 20:31:45 +02:00
renovate[bot]
a3dd19cd2f fix(deps): update typescript-projects (#29633)
Co-authored-by: Daniel Dietzler <mail@ddietzler.dev>
2026-07-16 20:14:05 +02:00
shenlong
9cea3f2375 chore: pump drift (#29113)
* chore: pump drift

* run codegen

---------

Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2026-07-16 17:32:30 +00:00
shenlong
507448d797 chore: pump home_widget (#29981)
Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2026-07-16 21:58:11 +05:30
Yaros
fe3eb7d865 feat(web): undo archive from toast (#27061)
Co-authored-by: Daniel Dietzler <36593685+danieldietzler@users.noreply.github.com>
2026-07-16 17:53:02 +02:00
Adam Gastineau
90dc328c22 chore(mobile): update maplibre_gl to 0.26.2 (#29772) 2026-07-16 07:47:48 -07:00
shenlong
a5623c41ec chore: pump flutter to 3.44.6 (#29886)
Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2026-07-16 20:17:03 +05:30
Adam Gastineau
3a016f4451 chore(mobile): drop support for iOS 14 (#29780)
chore(mobile): drop support for iOS 14 in advance of iOS and Xcode 27
2026-07-16 07:45:22 -07:00
Adam Gastineau
0af8284456 chore(mobile): expose signing and bundle identities through envvars and config (#29077)
* chore(mobile): expose signing and bundle identities through envvars and config

* Move instructions to now shared docs/developer/setup.md
2026-07-16 07:44:50 -07:00
renovate[bot]
ad7a9bd189 chore(deps): update prom/prometheus docker digest to 3c42b89 (#29667) 2026-07-16 14:01:41 +02:00
Ryan Rockey
80cdfebbc7 fix: zero byte image uploads (#29426) 2026-07-16 13:00:07 +02:00
Diogo Correia
c50a807bc1 feat(web): add wake lock when uploading assets (#29820) 2026-07-16 12:41:37 +02:00
bo0tzz
26e07548a7 chore: add GHSA as contact in security.txt (#29940) 2026-07-16 12:30:44 +02:00
cevdet
f73c53c563 chore(seo): remove redundant twitter metadata tags (#29801) 2026-07-16 12:29:38 +02:00
Mert
d2744f52b1 fix(server): disable heif security limit (#29954)
disable security limit
2026-07-16 04:40:33 -05:00
Yuxin Wang
d8ed7d7bb9 feat(cli): Add --visibility flag to immich CLI upload subcommand (#29614) 2026-07-15 14:11:03 -05:00
renovate[bot]
834cc54abc chore(deps): update ghcr.io/jdx/mise docker tag to v2026.7.7 (#29893) 2026-07-15 18:45:35 +00:00
Daniel Dietzler
eda605c534 fix(web): clear birth date (#29959) 2026-07-15 18:32:53 +00:00
Daniel Dietzler
b75e6520bb chore: mobile code checks for i18n changes (#29958) 2026-07-15 20:20:42 +02:00
Timon
4c2d2284aa refactor(server): album user route params (#29503) 2026-07-15 18:15:41 +00:00
Daniel Dietzler
5d29f3edc1 fix: mobile translations (#29955) 2026-07-15 18:02:29 +00:00
Mees Frensel
e00dcf7850 fix(web): URI encode slug and reduce confusion for users (#29796)
Co-authored-by: bwees <brandonwees@gmail.com>
2026-07-15 19:32:05 +02:00
Andreas Heinz
e7f773197f fix(server/workflow): add trigger for external libraries AssetCreate (#29597) 2026-07-15 19:30:33 +02:00
bo0tzz
2237078bd5 chore: bump create-workflow-token action to v3 (#29571) 2026-07-15 14:33:23 +02:00
Jorge Montejo
55dc579059 feat(web): Keep show more open on duplicates (#29734)
Co-authored-by: Daniel Dietzler <mail@ddietzler.dev>
2026-07-15 11:22:36 +00:00
Raul
b4530b0671 fix(server): workflow date filter, make end date inclusive (#29876)
Co-authored-by: Raul Plesa <raul.plesa@external.grifols.com>
Co-authored-by: Daniel Dietzler <mail@ddietzler.dev>
2026-07-15 10:50:03 +00:00
Lorenzo Montanari
a04e2ddeca fix(server): updated default CSP config to support videos from V3 player (#29830) 2026-07-15 12:49:18 +02:00
Kobi Hikri
5f2e8a4fcf chore(security.txt): bump expired Expires field (RFC 9116) (#29932) 2026-07-15 10:19:44 +00:00
Priyanshu
dc55b234d9 fix(web): use Container component for responsive admin maintenance la… (#29917)
Co-authored-by: priyanshuANDcoad <priyanshu23154050@akgec.ac.in>
2026-07-15 12:18:55 +02:00
Priyanshu
f6bf22d9ca fix(web): hide stack thumbnail tray in slideshow mode (#29918)
Co-authored-by: priyanshuANDcoad <priyanshu23154050@akgec.ac.in>
2026-07-15 12:10:35 +02:00
Santo Shakil
2cdbd0d00f fix(mobile): apply exif orientation to remote raw photos on android (#29906) 2026-07-14 19:10:41 -04:00
github-actions
cd308ad930 chore: version v3.0.3 2026-07-14 21:10:19 +00:00
Alex
12061f3bf8 fix: Ken Burns effect on sliver app bar jittery on Android (#29890) 2026-07-14 19:21:33 +00:00
Daniel Dietzler
2460d431af feat: system integrity docs (#29911) 2026-07-14 14:00:27 -05:00
Matthew Momjian
24556cd3fc fix(docs): update fdroid link (#29797)
Co-authored-by: Brandon Wees <brandonwees@gmail.com>
2026-07-14 20:04:59 +02:00
Adam Gastineau
c84ab54889 fix(mobile): clear zoom state when swapping assets (#29909) 2026-07-14 12:18:18 -05:00
renovate[bot]
19e59ebd98 chore(deps): update github-actions (#29894) 2026-07-14 18:44:32 +02:00
Mert
89b0d906e8 fix(server): set hvc1 tag when using hwa (#29908)
fix hvc1 check
2026-07-14 11:03:30 -04:00
Daniel Dietzler
3c43e8d6c1 fix: revert "fix(mobile): send a full datetime from the android memories widget" (#29913) 2026-07-14 14:41:43 +00:00
Daniel Dietzler
2ed8b2bddd fix: memory search date validation (#29907) 2026-07-14 16:34:38 +02:00
Santo Shakil
9c8d718ddc fix(mobile): send a full datetime from the android memories widget (#29689) 2026-07-14 09:25:06 -05:00
renovate[bot]
20e123c839 chore(deps): update base-image to v202607132058 (#29895) 2026-07-14 11:16:42 +02:00
Daniel Dietzler
35fcca6254 fix: locked visibility in search random endpoint (#29887) 2026-07-13 20:03:20 +00:00
shenlong
ffafc144c6 refactor: use single bucket for none grouping (#29880)
Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
Co-authored-by: Alex <alex.tran1502@gmail.com>
2026-07-13 15:01:59 -05:00
bo0tzz
89d31aaba1 feat: publish app releases to FUTO F-Droid repo (#29804)
* feat: publish app releases to FUTO F-Droid repo

* chore: separate parse step
2026-07-13 14:57:40 -05:00
Alex
f2ddace584 fix: system appbar icon color has incorrect color in asset viewer (#29885) 2026-07-13 14:50:38 -05:00
shenlong
99883096d6 fix: still part of live photo hidden on bg upload (#29884)
* fix: still part of live photo hidden on bg upload

* add foreground tests

---------

Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2026-07-13 14:50:06 -05:00
Daniel Dietzler
84dff19ca9 fix: unauthorized album owner update (#29883) 2026-07-13 11:46:18 -05:00
Priyanshu
da8774801b fix(mobile): correct person age singular pluralization (#29868)
The ICU plural string for person_age_years in i18n/en.json only defined
the other plural category, so the intl MessageFormat runtime always
fell back to it — displaying '1 years old' instead of '1 year old'.

Added the missing one plural category:

Before: {years, plural, other {# years}} old
After:  {years, plural, one {# year} other {# years}} old

No Dart source changes required. formatAge already uses the
localization system correctly.

Fixes #29865

Co-authored-by: priyanshuANDcoad <priyanshu23154050@akgec.ac.in>
2026-07-13 14:49:06 +00:00
Ben Beckford
9057ae9759 fix(web): closing plugin method picker selects "unknown" method (#29784) 2026-07-13 09:41:28 -05:00
Alex
557189d7a8 chore: add Adam to mobile's codeowner (#29816) 2026-07-10 15:10:52 -05:00
Mees Frensel
19313e75fd chore(web): fix testing deprecations (#29799) 2026-07-10 14:30:12 +02:00
Weblate (bot)
352c8086f9 chore(web): update translations (#29774)
Translate-URL: https://hosted.weblate.org/projects/immich/immich/be/
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/da/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/de/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/en_GB/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/eo/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/es/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/et/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/eu/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/fr/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/ga/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/gl/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/hi/
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/it/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/ko/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/lt/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/ml/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/mr/
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/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/sk/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/sl/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/sq/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/sr_Cyrl/
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/te/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/th/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/tr/
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/yue_Hant/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/zh_Hans/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/zh_Hant/
Translation: Immich/immich

Co-authored-by: Adam Havránek <adamhavra@seznam.cz>
Co-authored-by: Aiixi Bear <bearshen1019@outlook.com>
Co-authored-by: Aindriú Mac Giolla Eoin <aindriu80@gmail.com>
Co-authored-by: Alaksandr Łopart <laporte0723@gmail.com>
Co-authored-by: Alexis Rossfelder <rossfelderalexis@gmail.com>
Co-authored-by: AntonPalmqvist <apq@users.noreply.hosted.weblate.org>
Co-authored-by: Ardev Prisec <prisecardev@gmail.com>
Co-authored-by: Biepa <github@flippie.de>
Co-authored-by: Branko Kokanovic <branko@kokanovic.org>
Co-authored-by: Denis Pacquier <denis.pacquier@gmail.com>
Co-authored-by: DevServs <bonov@mail.ru>
Co-authored-by: Dusan Hlavaty <dhlavaty@gmail.com>
Co-authored-by: Emil Warelius <emilwarelius@gmail.com>
Co-authored-by: Enric Pagès i Gassull <enricpages@hotmail.com>
Co-authored-by: Fjuro <fjuro@users.noreply.hosted.weblate.org>
Co-authored-by: Frank Paul Silye <frankps@gmail.com>
Co-authored-by: Grzegorz Leks <grzegorz.leks@gmail.com>
Co-authored-by: Gyutaek Kang <gtkang2000@naver.com>
Co-authored-by: HackingAll <hacking.all.YT@gmail.com>
Co-authored-by: Happy <59247878+happy2452354@users.noreply.github.com>
Co-authored-by: Hosted Weblate user 157208 <benjamin3.hanke@gmail.com>
Co-authored-by: Hurricane_32 <rodrigorimo@hotmail.com>
Co-authored-by: Huy Trieu <thetop1102@gmail.com>
Co-authored-by: Hồ Nhất Duy <axicenia@gmail.com>
Co-authored-by: Indrek Haav <indrekhaav@users.noreply.hosted.weblate.org>
Co-authored-by: Ivan Dimitrov <idimitrov08@gmail.com>
Co-authored-by: Jesus Barba <jesusbarba11@gmail.com>
Co-authored-by: JiZPaper <JiZPaper@gmail.com>
Co-authored-by: Jins K Joy <mailjinskjoy@gmail.com>
Co-authored-by: Jonas Franke <90050350+JonasFranke@users.noreply.github.com>
Co-authored-by: Jordy H <jordy@hoebergen.net>
Co-authored-by: Jorge Montejo <jorgemon.lopez@gmail.com>
Co-authored-by: Jozef Gaal <preklady@mayday.sk>
Co-authored-by: Junghyuk Kwon <kwon@junghy.uk>
Co-authored-by: Ketan Avhad <ketaavha@gmail.com>
Co-authored-by: Kevin wang <jiqi963@gmail.com>
Co-authored-by: Klenner Martins Barros <klenne.al@gmail.com>
Co-authored-by: Liviu Roman <contact@liviuroman.com>
Co-authored-by: Luis Fernando Illapa <moxb@outlook.es>
Co-authored-by: Marc Portabella Navarro <portabella.marc@gmail.com>
Co-authored-by: MarcSerraPeralta <marcserraperalta@gmail.com>
Co-authored-by: Matjaž T. <matjaz@moj-svet.si>
Co-authored-by: Matteo De Carli <matteo.de.carli01@gmail.com>
Co-authored-by: Mees Frensel <meesfrensel@gmail.com>
Co-authored-by: MrKoby07 <hirmerkorbinian122@gmail.com>
Co-authored-by: Muxutruk <156070698+Muxutruk2@users.noreply.github.com>
Co-authored-by: Nagy Krisztián <nkgy17@gmail.com>
Co-authored-by: Nico Sendan <nico@sendan.com>
Co-authored-by: Nicola Bortoletto <nicola.bortoletto@live.com>
Co-authored-by: Ohne Sonne <ohne_sonne_1312@protonmail.com>
Co-authored-by: Okie <leopetrovic11@gmail.com>
Co-authored-by: Olaf Nielsen <solluh@mail.de>
Co-authored-by: PPNplus <ppnplus@protonmail.com>
Co-authored-by: Pavel Miniutka <pavel.miniutka@gmail.com>
Co-authored-by: PiTrick <dev@pitrick.link>
Co-authored-by: Rubber Ducky <rd1945@gmail.com>
Co-authored-by: Sebastian <sebastiankiwidk@gmail.com>
Co-authored-by: Simone Ognibene <ognibene2001@gmail.com>
Co-authored-by: Sylvain Pichon <service@spichon.fr>
Co-authored-by: TV Box <realceday.tvbox@gmail.com>
Co-authored-by: TheDevIsHere <bagigio2005@gmail.com>
Co-authored-by: Tiago <tiagojoaquimsamsung@gmail.com>
Co-authored-by: Tijs-B <tijs.bergmans@telenet.be>
Co-authored-by: Tim Morley <weblate.3919org@timsk.org>
Co-authored-by: Tom Golden <tmgldn@users.noreply.hosted.weblate.org>
Co-authored-by: Tomislav Renić <trenic@gmail.com>
Co-authored-by: Tushar Harsora <tusharharsora95@gmail.com>
Co-authored-by: User 123456789 <user123456789@users.noreply.hosted.weblate.org>
Co-authored-by: Vallabh Sharma <srivallabhsharma@gmail.com>
Co-authored-by: Yusuf Arda <yusufarda7813@gmail.com>
Co-authored-by: bittin1ddc447d824349b2 <bittin@reimu.nl>
Co-authored-by: bonjur <ivanpercsity08@gmail.com>
Co-authored-by: canxunwangluo <canxunwangluo@163.com>
Co-authored-by: kylo32 <kylo32@gmail.com>
Co-authored-by: nachtpfoetchen <nachtpfoetchen@posteo.de>
Co-authored-by: pluwen chen <pluwen@gmail.com>
Co-authored-by: puv <dev@puv.pm>
Co-authored-by: rubes <mail@armd.one>
Co-authored-by: vatahome <vataszilamer@pm.me>
Co-authored-by: waclaw66 <waclaw66@seznam.cz>
2026-07-09 20:01:50 +00:00
github-actions
443b856ac3 chore: version v3.0.2 2026-07-09 17:33:21 +00:00
Weblate (bot)
d18edfed34 chore(web): update translations (#29415)
Translate-URL: https://hosted.weblate.org/projects/immich/immich/be/
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/da/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/de/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/en_GB/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/eo/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/es/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/et/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/eu/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/fr/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/ga/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/gl/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/hi/
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/it/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/ko/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/lt/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/ml/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/mr/
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/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/sk/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/sl/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/sq/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/sr_Cyrl/
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/te/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/th/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/tr/
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/yue_Hant/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/zh_Hans/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/zh_Hant/
Translation: Immich/immich

Co-authored-by: Adam Havránek <adamhavra@seznam.cz>
Co-authored-by: Aiixi Bear <bearshen1019@outlook.com>
Co-authored-by: Aindriú Mac Giolla Eoin <aindriu80@gmail.com>
Co-authored-by: Alaksandr Łopart <laporte0723@gmail.com>
Co-authored-by: Alexis Rossfelder <rossfelderalexis@gmail.com>
Co-authored-by: AntonPalmqvist <apq@users.noreply.hosted.weblate.org>
Co-authored-by: Ardev Prisec <prisecardev@gmail.com>
Co-authored-by: Biepa <github@flippie.de>
Co-authored-by: Branko Kokanovic <branko@kokanovic.org>
Co-authored-by: Denis Pacquier <denis.pacquier@gmail.com>
Co-authored-by: DevServs <bonov@mail.ru>
Co-authored-by: Dusan Hlavaty <dhlavaty@gmail.com>
Co-authored-by: Emil Warelius <emilwarelius@gmail.com>
Co-authored-by: Enric Pagès i Gassull <enricpages@hotmail.com>
Co-authored-by: Fjuro <fjuro@users.noreply.hosted.weblate.org>
Co-authored-by: Frank Paul Silye <frankps@gmail.com>
Co-authored-by: Grzegorz Leks <grzegorz.leks@gmail.com>
Co-authored-by: Gyutaek Kang <gtkang2000@naver.com>
Co-authored-by: HackingAll <hacking.all.YT@gmail.com>
Co-authored-by: Happy <59247878+happy2452354@users.noreply.github.com>
Co-authored-by: Hosted Weblate user 157208 <benjamin3.hanke@gmail.com>
Co-authored-by: Hurricane_32 <rodrigorimo@hotmail.com>
Co-authored-by: Huy Trieu <thetop1102@gmail.com>
Co-authored-by: Hồ Nhất Duy <axicenia@gmail.com>
Co-authored-by: Indrek Haav <indrekhaav@users.noreply.hosted.weblate.org>
Co-authored-by: Ivan Dimitrov <idimitrov08@gmail.com>
Co-authored-by: Jesus Barba <jesusbarba11@gmail.com>
Co-authored-by: JiZPaper <JiZPaper@gmail.com>
Co-authored-by: Jins K Joy <mailjinskjoy@gmail.com>
Co-authored-by: Jonas Franke <90050350+JonasFranke@users.noreply.github.com>
Co-authored-by: Jordy H <jordy@hoebergen.net>
Co-authored-by: Jorge Montejo <jorgemon.lopez@gmail.com>
Co-authored-by: Jozef Gaal <preklady@mayday.sk>
Co-authored-by: Junghyuk Kwon <kwon@junghy.uk>
Co-authored-by: Ketan Avhad <ketaavha@gmail.com>
Co-authored-by: Kevin wang <jiqi963@gmail.com>
Co-authored-by: Klenner Martins Barros <klenne.al@gmail.com>
Co-authored-by: Liviu Roman <contact@liviuroman.com>
Co-authored-by: Luis Fernando Illapa <moxb@outlook.es>
Co-authored-by: Marc Portabella Navarro <portabella.marc@gmail.com>
Co-authored-by: MarcSerraPeralta <marcserraperalta@gmail.com>
Co-authored-by: Matjaž T. <matjaz@moj-svet.si>
Co-authored-by: Matteo De Carli <matteo.de.carli01@gmail.com>
Co-authored-by: Mees Frensel <meesfrensel@gmail.com>
Co-authored-by: MrKoby07 <hirmerkorbinian122@gmail.com>
Co-authored-by: Muxutruk <156070698+Muxutruk2@users.noreply.github.com>
Co-authored-by: Nagy Krisztián <nkgy17@gmail.com>
Co-authored-by: Nico Sendan <nico@sendan.com>
Co-authored-by: Nicola Bortoletto <nicola.bortoletto@live.com>
Co-authored-by: Ohne Sonne <ohne_sonne_1312@protonmail.com>
Co-authored-by: Okie <leopetrovic11@gmail.com>
Co-authored-by: Olaf Nielsen <solluh@mail.de>
Co-authored-by: PPNplus <ppnplus@protonmail.com>
Co-authored-by: Pavel Miniutka <pavel.miniutka@gmail.com>
Co-authored-by: PiTrick <dev@pitrick.link>
Co-authored-by: Rubber Ducky <rd1945@gmail.com>
Co-authored-by: Sebastian <sebastiankiwidk@gmail.com>
Co-authored-by: Simone Ognibene <ognibene2001@gmail.com>
Co-authored-by: Sylvain Pichon <service@spichon.fr>
Co-authored-by: TV Box <realceday.tvbox@gmail.com>
Co-authored-by: TheDevIsHere <bagigio2005@gmail.com>
Co-authored-by: Tiago <tiagojoaquimsamsung@gmail.com>
Co-authored-by: Tijs-B <tijs.bergmans@telenet.be>
Co-authored-by: Tim Morley <weblate.3919org@timsk.org>
Co-authored-by: Tom Golden <tmgldn@users.noreply.hosted.weblate.org>
Co-authored-by: Tomislav Renić <trenic@gmail.com>
Co-authored-by: Tushar Harsora <tusharharsora95@gmail.com>
Co-authored-by: User 123456789 <user123456789@users.noreply.hosted.weblate.org>
Co-authored-by: Vallabh Sharma <srivallabhsharma@gmail.com>
Co-authored-by: Yusuf Arda <yusufarda7813@gmail.com>
Co-authored-by: bittin1ddc447d824349b2 <bittin@reimu.nl>
Co-authored-by: bonjur <ivanpercsity08@gmail.com>
Co-authored-by: canxunwangluo <canxunwangluo@163.com>
Co-authored-by: kylo32 <kylo32@gmail.com>
Co-authored-by: nachtpfoetchen <nachtpfoetchen@posteo.de>
Co-authored-by: pluwen chen <pluwen@gmail.com>
Co-authored-by: puv <dev@puv.pm>
Co-authored-by: rubes <mail@armd.one>
Co-authored-by: vatahome <vataszilamer@pm.me>
Co-authored-by: waclaw66 <waclaw66@seznam.cz>
2026-07-09 17:30:32 +00:00
bo0tzz
f6d6dbc3c7 chore: remove pgvecto.rs from docs footnote (#29729)
* chore: remove pgvecto.rs from docs footnote

* Update docs/docs/install/environment-variables.md

Co-authored-by: Matthew Momjian <50788000+mmomjian@users.noreply.github.com>

---------

Co-authored-by: Matthew Momjian <50788000+mmomjian@users.noreply.github.com>
2026-07-09 11:36:26 -05:00
shenlong
4fe33289b8 fix: ignore log db corruptions (#29656)
* fix: ignore log db corruptions

* review suggestion

* log corruption recovery

---------

Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2026-07-09 11:36:04 -05:00
Mees Frensel
2db1e02cdf fix(web): always initialize language manager to set lang and dir (#29696) 2026-07-09 13:40:15 +00:00
Daniel Dietzler
fea0db02f3 fix: emit album update event when uploading assets through shared link (#29757) 2026-07-09 15:21:16 +02:00
Mees Frensel
4fd58885e2 fix(web): recursively unnest class arrays (#29755) 2026-07-09 12:39:12 +02:00
Ben Beckford
c5222c7579 chore(server): use bounded numbers in workflows (#29009)
* chore(server): use bounded numbers in workflows

* chore: increase precision for lat/lon filtering

* chore: re-add integer to a json schema type
2026-07-08 17:15:48 -07:00
Santo Shakil
c4f084f4a5 fix(mobile): play videos from search results and folder view (#29693)
* fix(mobile): play videos from search results and folder view

* pr suggestion

---------

Co-authored-by: Alex <alex.tran1502@gmail.com>
2026-07-08 20:22:35 +00:00
Daniel Dietzler
66e30d5fa1 fix: missing files integrity checks (#29737) 2026-07-08 14:53:46 -05:00
Daniel Dietzler
053a52aec8 fix: iso date parsing for three digit years (#29739) 2026-07-08 14:53:16 -05:00
Santo Shakil
2ad554fc61 fix(mobile): update timeline args on layout constraint changes (#29653)
* fix(mobile): update timeline args on layout constraint changes

* chore(mobile): temporary blank timeline diagnostics

* chore(mobile): error logs when the timeline goes blank

* chore(mobile): remove blank timeline debug logging

---------

Co-authored-by: Alex <alex.tran1502@gmail.com>
2026-07-08 14:13:32 -05:00
Kha Nguyen
427321a102 fix(web): clear asset selection when search context changes (#29738) 2026-07-08 14:08:39 -05:00
Daniel Dietzler
655cd01d57 fix: search metadata for album shared links (#29742) 2026-07-08 20:32:20 +02:00
Mert
cfd95f0196 fix(server): stream ffprobe packets (#29723) 2026-07-08 12:51:22 -04:00
renovate[bot]
439ace9dba chore(deps): update ghcr.io/jdx/mise docker tag to v2026.7.2 (#29678) 2026-07-08 13:22:06 +02:00
Ben Beckford
5c2fb2245a feat(server): date workflow filter (#29040) 2026-07-08 12:05:44 +02:00
shenlong
924e6251fd fix: do not log out on automatic url switch (#29715)
fix: sync headers to external endpoints

Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2026-07-07 21:21:37 -05:00
Mert
3aebb82905 fix(mobile): http request handling after isolate death (#29714)
* use ports for objc

* add task reaper

* cleanup, fewer allocations

* link to pr
2026-07-07 16:31:10 -04:00
Daniel Dietzler
7a9b9e01be feat: user agnostic database restore (#29512) 2026-07-07 21:16:48 +02:00
Daniel Dietzler
0cbe07c4d4 fix: integrity checks dto validation (#29621) 2026-07-07 20:49:26 +02:00
renovate[bot]
45ea248e8b chore(deps): update github-actions (#29677) 2026-07-07 19:56:57 +02:00
Adam Gastineau
8cbf0db3b3 fix(mobile): disable broken Xcode queue debugging (#29657) 2026-07-07 12:26:19 -05:00
Adam Gastineau
a225c962de fix(mobile): properly handle live photos locally (#29462)
* fix(mobile): properly handle live photos locally

* Added basic motion photo data tests

* Cover mismatched platforms and motion photos

* Revert unnecessary storage.repository changes

---------

Co-authored-by: Alex <alex.tran1502@gmail.com>
2026-07-07 12:25:43 -05:00
Mees Frensel
daabab8c1c fix(docs): update developer setup (#29661) 2026-07-07 17:07:35 +00:00
Brandon Wees
a4f28dafb2 fix(mobile): missing crop options and rotation scaling (#29645)
* fix: missing aspect ratio options in mobile editor

* fix: smooth rotation

* chore: comments

* chore: better preset management and tooling

* lint

---------

Co-authored-by: Alex <alex.tran1502@gmail.com>
2026-07-07 12:05:50 -05:00
Daniel Dietzler
704b287bba fix: integrity checks checksum pagination (#29516) 2026-07-07 18:42:14 +02:00
Ben Beckford
c7b4a798cb fix: incorrect grouping of recently added assets (#29666)
* fix: incorrect grouping of recently added assets

* chore(web): lint

* chore(web): refactor timeline utc to local dates

* chore(mobile): index remote asset upload dates

---------

Co-authored-by: Alex <alex.tran1502@gmail.com>
2026-07-07 11:21:58 -05:00
Daniel Dietzler
f8e78792b8 fix: oauth account linking (#29624) 2026-07-07 11:21:27 -05:00
shenlong
0ebb723b39 fix: wrap migrations in transaction (#29664)
Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2026-07-07 10:54:24 -05:00
Adam Gastineau
99a8354145 fix(mobile): match iOS scroll to top and tabbar scroll (#28469) (#29705)
Co-authored-by: Alex <alex.tran1502@gmail.com>
2026-07-07 10:53:35 -05:00
shenlong
7ee2f3644f fix(mobile): mark Live Photo motion part hidden on upload (#29699)
* fix(mobile): mark Live Photo motion part hidden on upload

* review suggestion

---------

Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2026-07-07 09:27:39 -05:00
bo0tzz
d2a46ade78 chore: webhook headers ordering (#29695) 2026-07-07 12:09:57 +00:00
Mert
68b04e8e8f chore: simplify hls description (#29680)
simplify hls description
2026-07-07 07:45:40 +00:00
Santo Shakil
58b1c5e8fb fix(mobile): fix 10-bit heic and avif colors on android (#29631)
* fix(mobile): fix 10-bit heic and avif colors on android

* fix(mobile): share the decode pool across flutter engines

* vectorize conversion

---------

Co-authored-by: mertalev <101130780+mertalev@users.noreply.github.com>
2026-07-06 21:16:09 +00:00
Matthew Momjian
6f45521610 fix(docs): clarify version policy (#29654)
* version policy

* Update docs/docs/install/upgrading.md

Co-authored-by: Daniel Dietzler <36593685+danieldietzler@users.noreply.github.com>

---------

Co-authored-by: Daniel Dietzler <36593685+danieldietzler@users.noreply.github.com>
2026-07-06 19:07:31 +00:00
Mert
69d568f289 feat(server): hls variant configuration (#29613)
* configure variants

* openapi

* dynamic level

* unnecessary width/height fields

* add web config

* allow 1440p and 2160p

* more helpful descriptions

* simplify level picker

* formatting

* integer instead of number
2026-07-06 14:57:48 -04:00
Daniel Dietzler
a6f0253544 chore: improve added to album messages (#29641) 2026-07-06 17:21:49 +02:00
Jins K Joy
32352fa375 feat(docs): add Malayalam language support in README files (#29593) 2026-07-06 17:15:00 +02:00
Tom B
6aa5651032 fix(docs): use correct heading name for DB dump (#29483)
match docs to actual UI copy
2026-07-06 10:13:22 -05:00
Daniel Dietzler
4e25db2989 fix: chinese browser locales recognition (#29622) 2026-07-06 09:42:47 -05:00
Daniel Dietzler
4f5e0d3505 chore: clearer album users dropdown (#29629) 2026-07-06 09:40:26 -05:00
Alex
b3abe0bb5d chore: keep selection state after sharing action (#29640) 2026-07-06 14:40:18 +00:00
renovate[bot]
c04acc8951 chore(deps): update base-image to v202607061334 (#29634) 2026-07-06 16:01:22 +02:00
renovate[bot]
af32a8ca7c chore(deps): update github-actions (#29400) 2026-07-06 15:25:52 +02:00
renovate[bot]
ed83fb3f5a chore(deps): update ghcr.io/jdx/mise docker tag to v2026.6.14 (#29632) 2026-07-06 14:54:02 +02:00
renovate[bot]
628de417e9 chore(deps): lock file maintenance (mise) (#29637) 2026-07-06 14:52:48 +02:00
renovate[bot]
be02c00afd chore(deps): lock file maintenance (mise) (#29636) 2026-07-06 14:52:42 +02:00
Wonsuk Choi
43abb9a92f fix(web): wrap hardcoded strings with i18n translation function (#29612)
Co-authored-by: Daniel Dietzler <36593685+danieldietzler@users.noreply.github.com>
2026-07-06 09:57:04 +00:00
TJ Horner
103585ceb9 fix(web): add ARIA label to ServerStatisticsCard (#29497)
Co-authored-by: Daniel Dietzler <36593685+danieldietzler@users.noreply.github.com>
2026-07-06 10:35:47 +02:00
Daniel Dietzler
6da890ee37 fix: web search timeline visibility (#29518) 2026-07-03 17:41:25 +02:00
Lukas Schaefer
e7ba8d3632 fix: live transcoding when viewing from a link share (#29471) 2026-07-03 11:34:37 -04:00
Lukas Schaefer
5645149e09 fix: downmix audio to stereo to fix broken channel layout (#29485)
* fix: downmix audio to stereo to fix broken channel layout

Signed-off-by: Lukas Schaefer <lukas@lschaefer.xyz>

* add todo

Signed-off-by: Lukas Schaefer <lukas@lschaefer.xyz>

---------

Signed-off-by: Lukas Schaefer <lukas@lschaefer.xyz>
2026-07-03 10:55:12 -04:00
Alex
5a104f9dbe chore: update docs with v3 tags (#29312) 2026-07-03 12:27:18 +02:00
bo0tzz
13d17b3a11 fix: re-add gCastEnabled negation (#29499) 2026-07-03 11:05:22 +02:00
github-actions
f77c8a4699 chore: version v3.0.1 2026-07-02 19:24:13 +00:00
Daniel Dietzler
bf4b020856 fix: setting input field description spacing for description snippet (#29433) 2026-07-02 14:06:03 -05:00
shenlong
f73796597c fix: albums broken on v2 mobile clients (#29446)
Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2026-07-02 14:04:37 -05:00
Ben Beckford
237734bb26 feat(web): recently added link in sidebar (#29039)
* feat(web): recently added link in sidebar

* chore(mobile): update openapi patches
2026-07-01 23:12:50 +00:00
Weblate (bot)
4b54fef82e chore(web): update translations (#29410)
Translate-URL: https://hosted.weblate.org/projects/immich/immich/ar/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/be/
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/da/
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/ga/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/gl/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/gu/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/he/
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/pt/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/ro/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/sq/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/sv/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/vi/
Translation: Immich/immich

Co-authored-by: Aindriú Mac Giolla Eoin <aindriu80@gmail.com>
Co-authored-by: Charles IdB <charles.issert2braux@gmail.com>
Co-authored-by: Dmitry Banny <dj.icecore@gmail.com>
Co-authored-by: Enric Pagès i Gassull <enricpages@hotmail.com>
Co-authored-by: Fjuro <fjuro@users.noreply.hosted.weblate.org>
Co-authored-by: HackingAll <hacking.all.YT@gmail.com>
Co-authored-by: Harsh Kevadia <kevadiyaharsh@gmail.com>
Co-authored-by: Hosted Weblate user 156232 <53017937+parol100@users.noreply.github.com>
Co-authored-by: Hurricane_32 <rodrigorimo@hotmail.com>
Co-authored-by: Hồ Nhất Duy <axicenia@gmail.com>
Co-authored-by: Ivan Dimitrov <idimitrov08@gmail.com>
Co-authored-by: Jeppe Nellemann <jepnel@proton.me>
Co-authored-by: Mahmoud Dwidar <modydodo2055@gmail.com>
Co-authored-by: Nicola Bortoletto <nicola.bortoletto@live.com>
Co-authored-by: Pavel Miniutka <pavel.miniutka@gmail.com>
Co-authored-by: Yago Raña Gayoso <yago.rana.gayoso@gmail.com>
Co-authored-by: bittin1ddc447d824349b2 <bittin@reimu.nl>
Co-authored-by: ikeno-web <ikeno@nextcore-consulting.com>
Co-authored-by: rubes <mail@armd.one>
2026-07-01 11:05:25 +00:00
Daniel Dietzler
0050332391 fix: e2e version test (#29412) 2026-07-01 12:41:45 +02:00
github-actions
05d838b560 chore: version v3.0.0 2026-06-30 20:03:47 +00:00
Weblate (bot)
82b70c1ab6 chore(web): update translations (#29347)
Translate-URL: https://hosted.weblate.org/projects/immich/immich/ar/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/be/
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/da/
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/ga/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/gl/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/gu/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/he/
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/pt/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/ro/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/sq/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/sv/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/vi/
Translation: Immich/immich

Co-authored-by: Aindriú Mac Giolla Eoin <aindriu80@gmail.com>
Co-authored-by: Charles IdB <charles.issert2braux@gmail.com>
Co-authored-by: Dmitry Banny <dj.icecore@gmail.com>
Co-authored-by: Enric Pagès i Gassull <enricpages@hotmail.com>
Co-authored-by: Fjuro <fjuro@users.noreply.hosted.weblate.org>
Co-authored-by: HackingAll <hacking.all.YT@gmail.com>
Co-authored-by: Harsh Kevadia <kevadiyaharsh@gmail.com>
Co-authored-by: Hosted Weblate user 156232 <53017937+parol100@users.noreply.github.com>
Co-authored-by: Hurricane_32 <rodrigorimo@hotmail.com>
Co-authored-by: Hồ Nhất Duy <axicenia@gmail.com>
Co-authored-by: Ivan Dimitrov <idimitrov08@gmail.com>
Co-authored-by: Jeppe Nellemann <jepnel@proton.me>
Co-authored-by: Mahmoud Dwidar <modydodo2055@gmail.com>
Co-authored-by: Nicola Bortoletto <nicola.bortoletto@live.com>
Co-authored-by: Pavel Miniutka <pavel.miniutka@gmail.com>
Co-authored-by: Yago Raña Gayoso <yago.rana.gayoso@gmail.com>
Co-authored-by: bittin1ddc447d824349b2 <bittin@reimu.nl>
Co-authored-by: ikeno-web <ikeno@nextcore-consulting.com>
Co-authored-by: rubes <mail@armd.one>
2026-06-30 20:01:21 +00:00
Daniel Dietzler
02506424a7 feat: integrity checks admin settings (#29406) 2026-06-30 14:58:51 -05:00
Ben Beckford
6a7a34d294 chore: make webhooks workflow-agnostic (#29404) 2026-06-30 15:27:30 -04:00
Alex
165bca4b0a feat: new feature message (#29388)
* feat: new feature board

* wip

* wip

* wip

* lint

* lint

* pr feedback

* pr feedback

* i18n

* i18n
2026-06-30 13:53:13 -05:00
Brandon Wees
d4b994301f fix: version compatability check (#29405) 2026-06-30 18:44:53 +00:00
Daniel Dietzler
deeb042a9e feat: honor album access permissions in search endpoints (#29352) 2026-06-29 22:27:22 +02:00
Daniel Dietzler
b4cc406a3f fix!: search endpoints visibility can be omitted (#29385) 2026-06-29 22:00:02 +02:00
Padhai_Kaneer
df383c1ead fix(server): face region coordinates parsing (#29333) 2026-06-29 17:05:20 +02:00
Santo Shakil
af2efda310 fix(mobile): apply exif orientation to android raw photos (#29337) 2026-06-29 10:59:09 -04:00
shenlong
5164feb5b4 chore: disable logs in tests (#29360)
Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2026-06-29 09:43:51 -04:00
shenlong
56eb69b328 chore: cleanup unit tests (#29357)
Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2026-06-29 09:38:00 -04:00
github-actions
ac74bca18b chore: version v3.0.0-rc.4 2026-06-27 13:14:47 +00:00
shenlong
e9d1951858 fix: dispatch menu onPressed manually (#29353)
Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2026-06-27 08:10:47 -05:00
Santo Shakil
6e1143e799 fix(mobile): hide video thumbnail when video is ready (#29012) 2026-06-26 22:20:40 -05:00
github-actions
09d4a6815f chore: version v3.0.0-rc.3 2026-06-26 16:49:02 +00:00
Weblate (bot)
23d1dbcb23 chore(web): update translations (#29290)
Translate-URL: https://hosted.weblate.org/projects/immich/immich/
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_GB/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/eo/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/et/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/eu/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/fr/
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/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/sk/
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/uk/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/vi/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/yue_Hant/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/zh_Hant/
Translation: Immich/immich

Co-authored-by: Abel Mislovic <mislovica@gmail.com>
Co-authored-by: Adjenz <adjenz@proton.me>
Co-authored-by: Andrea <ghosts.adm@gmail.com>
Co-authored-by: Conrad Menz <weblate@spamstopover.de>
Co-authored-by: Dan Bishop <d@nbishop.uk>
Co-authored-by: DevServs <bonov@mail.ru>
Co-authored-by: Dinesh R <dinodinu@gmail.com>
Co-authored-by: Enric Pagès i Gassull <enricpages@hotmail.com>
Co-authored-by: Happy <59247878+happy2452354@users.noreply.github.com>
Co-authored-by: Hồ Nhất Duy <axicenia@gmail.com>
Co-authored-by: Indrek Haav <indrekhaav@users.noreply.hosted.weblate.org>
Co-authored-by: Ivan Dimitrov <idimitrov08@gmail.com>
Co-authored-by: Jozef Gaal <preklady@mayday.sk>
Co-authored-by: KZeliop <kzeliop@gmail.com>
Co-authored-by: Kong <cptkong1@gmail.com>
Co-authored-by: Maj Mohar <maj.mohar4@gmail.com>
Co-authored-by: Matjaž T. <matjaz@moj-svet.si>
Co-authored-by: Muxutruk <156070698+Muxutruk2@users.noreply.github.com>
Co-authored-by: Nagy Krisztián <nkgy17@gmail.com>
Co-authored-by: Oleksandr Yurov <oyurov@icloud.com>
Co-authored-by: Sean Young <assanges@users.noreply.hosted.weblate.org>
Co-authored-by: Sylvain Pichon <service@spichon.fr>
Co-authored-by: Tijs-B <tijs.bergmans@telenet.be>
Co-authored-by: Tim Morley <weblate.3919org@timsk.org>
Co-authored-by: User 123456789 <user123456789@users.noreply.hosted.weblate.org>
Co-authored-by: Vincent Halman <vincent_halman@hotmail.com>
Co-authored-by: waclaw66 <waclaw66@seznam.cz>
2026-06-26 16:46:30 +00:00
Jason Rasmussen
6507b1f94c feat: pump doc references (#29331) 2026-06-26 16:45:06 +00:00
Santo Shakil
29949bebe4 fix(mobile): only toggle backup from the switch, not the whole row (#29236)
tapping anywhere on the enable backup row flipped backup on or off, so it was easy to toggle by accident. now only the switch does it.
2026-06-26 20:00:08 +05:30
Daniel Dietzler
d85e599ad9 feat: ultimate plugin type safety (#29340) 2026-06-26 16:27:19 +02:00
jameskimmel
b16cc496b2 docs: MS smtp guide (#29289)
Signed-off-by: jameskimmel <17176225+jameskimmel@users.noreply.github.com>
2026-06-26 16:16:38 +02:00
Ben Beckford
953ef5c047 feat: webhook workflow action (#29258)
Co-authored-by: Daniel Dietzler <mail@ddietzler.dev>
2026-06-26 16:08:45 +02:00
jullang
a876d4a9f1 fix: small typo in openapi-spec (#29308)
Co-authored-by: Daniel Dietzler <mail@ddietzler.dev>
2026-06-26 10:28:52 +00:00
Daniel Dietzler
688241a462 feat: plugin-sdk safety all around (#29323) 2026-06-25 18:23:55 -04:00
shenlong
cb1af3a8ec feat: favorite bottom sheet action (#29320)
* chore: cleanup partner action test

* feat: favorite bottom sheet action

* review suggestions

* implicit favorite handling

* feat: viewer favorite icon to action (#29321)

* feat: viewer favorite icon to action

* feat: advance info action

* implicit favorite handling

* feat: viewer favorite icon to action

# Conflicts:
#	mobile/lib/presentation/widgets/asset_viewer/viewer_top_app_bar.widget.dart

---------

Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>

* chore: timeline action test (#29324)

Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>

* clear selection only on success

---------

Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2026-06-25 16:55:06 -04:00
shenlong
49a821b0d0 chore: fix mobile test flakiness (#29325)
Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2026-06-25 22:56:59 +05:30
shenlong
3a7034d25e chore: cleanup partner action test (#29296)
Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2026-06-25 10:49:34 -04:00
Yaros
4099fa6b4a fix(mobile): app doesn't exit full-screen mode (#29301)
* fix(mobile): app doesn't exit full-screen mode

* chore: rename restoreSystemUI to restoreEdgeToEdge
2026-06-24 20:48:01 -05:00
Daniel Dietzler
9751530af8 feat: plugin wrapper type safety (#29300) 2026-06-24 15:22:35 -04:00
Daniel Dietzler
0931a19c5c fix: run test suite for plugin changes (#29311) 2026-06-24 16:29:46 +00:00
Alex
08b2e2c0b5 fix(docs): Revert v3 bump (#29310)
Revert "fix(docsc): v3 bump (#29246)"

This reverts commit dc7d57ff9a.
2026-06-24 11:18:37 -05:00
Santo Shakil
e5b50a55a4 fix(mobile): blank notifications page after enabling notifications (#29232)
the old notification toggles were removed in a cleanup, so once notifications were enabled the page had nothing left and went blank. show a "notifications enabled" status tile with a shortcut to the system notification settings instead.
2026-06-24 09:15:28 -05:00
shenlong
9d6c219276 fix: current viewer asset reactivity (#29282)
Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2026-06-23 18:40:24 +00:00
shenlong
f29f86542c feat: partner actions (#29281)
* feat: partner actions

# Conflicts:
#	i18n/en.json

* cleanup

* fix tests

* ci fix

---------

Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2026-06-23 23:50:59 +05:30
okxint
5165cf1d2f fix(mobile): force AssetViewerPage recreation on repeated view intents (#29235)
* fix(mobile): force AssetViewerPage recreation on repeated view intents

When View in Immich is triggered a second time while the viewer is
already open, auto_route's replaceAll reuses the existing route (same
type, null key) and Flutter keeps the old ConsumerState alive. The
PageController and preloader inside _AssetViewerState are late final,
so they never reset — the viewer stays frozen on the previous asset.

Passing UniqueKey() to AssetViewerRoute ensures each view intent
creates a fresh widget element, so initState runs, the PageController
is initialised from scratch, and the new TimelineService from the
updated ProviderScope override is picked up correctly.

Fixes #29230

* clean up

---------

Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2026-06-23 17:43:56 +00:00
shenlong
f4c8459484 feat: mobile actions (#29280)
Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2026-06-23 16:50:57 +00:00
Mees Frensel
22ec449e43 chore: remove unused i18n strings (#29288) 2026-06-23 18:02:29 +02:00
Weblate (bot)
0b1019c344 chore(web): update translations (#29204)
Translate-URL: https://hosted.weblate.org/projects/immich/immich/
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/bn/
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/de_CH/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/en_GB/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/eo/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/es/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/et/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/eu/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/fil/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/fr/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/ga/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/gsw/
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/lt/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/nb_NO/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/ne/
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_BR/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/ru/
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/tr/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/ur/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/vi/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/yue_Hant/
Translation: Immich/immich

Co-authored-by: Aindriú Mac Giolla Eoin <aindriu80@gmail.com>
Co-authored-by: AntonPalmqvist <apq@users.noreply.hosted.weblate.org>
Co-authored-by: Benjamin Kunz <benjamin.kunz.ch@gmail.com>
Co-authored-by: Cohinem <twitch9ofe@gmail.com>
Co-authored-by: Conrad Menz <weblate@spamstopover.de>
Co-authored-by: Damian Krysta <damian@krysta.dev>
Co-authored-by: DevServs <bonov@mail.ru>
Co-authored-by: Edmundas <edmius@gmail.com>
Co-authored-by: Enric Pagès i Gassull <enricpages@hotmail.com>
Co-authored-by: Fjuro <fjuro@users.noreply.hosted.weblate.org>
Co-authored-by: Hồ Nhất Duy <axicenia@gmail.com>
Co-authored-by: Indrek Haav <indrekhaav@users.noreply.hosted.weblate.org>
Co-authored-by: Insoo Seok <tocoyo@gmail.com>
Co-authored-by: Jayden Lo <jaydenlo08@gmail.com>
Co-authored-by: Leo Bottaro <github@leobottaro.com>
Co-authored-by: Luis Fernando Illapa <moxb@outlook.es>
Co-authored-by: Manar Aldroubi <droubi@gmail.com>
Co-authored-by: MarcSerraPeralta <marcserraperalta@gmail.com>
Co-authored-by: Matjaž T. <matjaz@moj-svet.si>
Co-authored-by: Mees Frensel <meesfrensel@gmail.com>
Co-authored-by: Melih Ozkan <malihozkan156@gmail.com>
Co-authored-by: Mr.Biswas <kallan.biswas001@gmail.com>
Co-authored-by: Muxutruk <156070698+Muxutruk2@users.noreply.github.com>
Co-authored-by: Nagy Krisztián <nkgy17@gmail.com>
Co-authored-by: Nicolas Ceballos <nicoshafes@gmail.com>
Co-authored-by: Para <ahh-produktivitet@tutanota.com>
Co-authored-by: Piero B. <biagini93@gmail.com>
Co-authored-by: PierreLapolla <plapolla9@gmail.com>
Co-authored-by: Ronnel <misc.woe@outlook.ph>
Co-authored-by: Sakib Iqbal <sakib.iqbal@gmail.com>
Co-authored-by: Saugat Tripathi <saugat.tripathi76@gmail.com>
Co-authored-by: Tim Morley <weblate.3919org@timsk.org>
Co-authored-by: Umair Jibran <wablate@umairjibran.com>
Co-authored-by: Unimpeded Lemur <yg7lh0fz3@mozmail.com>
Co-authored-by: User 123456789 <user123456789@users.noreply.hosted.weblate.org>
Co-authored-by: Vitor Coelho <vitorc195@gmail.com>
Co-authored-by: grgergo <gergo_g@proton.me>
Co-authored-by: muysup <79565421+MuySup@users.noreply.github.com>
Co-authored-by: Òscar Casajuana <elboletaire@underave.net>
2026-06-23 15:50:30 +00:00
Mees Frensel
06f3b4f259 refactor(web): simple actions (#29257) 2026-06-23 17:08:46 +02:00
Ben Beckford
99f94a363d chore(web): workflow property ordering (#29261)
* chore(web): workflow property ordering

* chore(web): extract schema property sorting to method
2026-06-23 13:03:33 +00:00
Daniel Dietzler
c3092b1c2c chore: basque was missing on mobile (#29284) 2026-06-23 08:57:05 -04:00
renovate[bot]
0656e7e231 chore(deps): update dependency typescript to v6 (#28772)
Co-authored-by: Daniel Dietzler <mail@ddietzler.dev>
2026-06-23 14:01:09 +02:00
renovate[bot]
1692b81b7c chore(deps): lock file maintenance (pub) (#28733) 2026-06-23 12:43:35 +02:00
renovate[bot]
ff2028c4c8 chore(deps): update prom/prometheus docker digest to a75c5a3 (#29271) 2026-06-23 12:43:16 +02:00
Timon
f22836e1bf refactor(server): describe check upload id as string (#29274) 2026-06-23 12:42:42 +02:00
renovate[bot]
7dd02ffbad chore(deps): update github-actions (#29272)
Co-authored-by: bo0tzz <git@bo0tzz.me>
2026-06-23 08:49:21 +00:00
shenlong
e51c4cb355 feat: column button (#29265)
* refactor: icon buttons implicit loading

* chore: cleanup

* feat: ui color override

* feat: column button

* feat: ui menu item (#29266)

Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>

---------

Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2026-06-22 20:56:00 -05:00
shenlong
d4102c0489 refactor: ui icon buttons implicit loading (#29263)
* refactor: icon buttons implicit loading

* chore: cleanup

* feat: ui color override (#29264)

Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>

---------

Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2026-06-22 16:24:52 -04:00
shenlong
30a73c1105 feat: mobile-ui snackbar (#29260)
Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2026-06-22 16:22:26 -04:00
Jason Rasmussen
ec7c0f9ec8 fix: sync backfill (#29267) 2026-06-22 16:19:01 -04:00
Brandon Wees
a5198e23a8 refactor: use SemVer classes for version compatability message (#29056)
* refactor: use SemVer classes for version compatability message

* chore: readd major version compatabilty messages

* fix: remove 1.106.0 check

(we dont support v1 servers anymore)
2026-06-22 11:28:56 -04:00
Mees Frensel
51f2905fcc fix(web): remove map's fullscreen button (#29192) 2026-06-22 16:58:07 +02:00
Vogeluff
3b7d75c18a feat(web): Add text-white-shadow to elements and increase the shadows effect (#29165)
* fix(web): increase text shadow strength for white text on thumbnails

* fix(web): fix class order for text-white-shadow

* fixup: format fix
2026-06-22 09:43:15 -05:00
Daniel Dietzler
c484bd99b6 fix: ignore external libraries for integrity report checksum check (#29248) 2026-06-22 13:56:24 +00:00
Anthony Clerici
c0bf5a4c56 fix(server): use VBR for QSV so the max bitrate is respected (#29240)
* fix(server): use VBR for QSV so the max bitrate is respected

* update test
2026-06-22 09:56:20 -04:00
MuySup
d9d50d2848 fix: turkish readme translation (#29234)
* Translation completed

3-2-1 rule translated

* Fix formatting of warning message in Turkish README
2026-06-22 09:55:58 -04:00
Daniel Dietzler
c7453a67fd fix: detail panel people reactivity and iterator consumption (#29250) 2026-06-22 15:47:09 +02:00
Daniel Dietzler
e918e3a313 feat: keyboard seeking for new video player (#29208) 2026-06-22 09:42:59 -04:00
Matthew Momjian
dc7d57ff9a fix(docsc): v3 bump (#29246)
v3 bump in docs
2026-06-21 20:44:58 -05:00
Alex
b24a617142 chore: bump mobile build (#29215) 2026-06-19 12:50:20 -05:00
Mees Frensel
62b00a1f26 refactor: slideshow and setalbumcover actions (#29211)
Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com>
2026-06-19 13:54:33 +00:00
Timon
95fc5e9682 docs: clarify duplicate exif merging intent (#29203) 2026-06-19 10:57:35 +02:00
github-actions
38920fc4ca chore: version v3.0.0-rc.2 2026-06-18 23:20:14 +00:00
Weblate (bot)
3abeb4df92 chore(web): update translations (#29162)
Translate-URL: https://hosted.weblate.org/projects/immich/immich/be/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/bg/
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/eo/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/es/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/eu/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/gl/
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/nb_NO/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/pt/
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/th/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/tr/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/yue_Hant/
Translation: Immich/immich

Co-authored-by: Erik Sikander <erik.sikander@gmail.com>
Co-authored-by: Fjuro <fjuro@users.noreply.hosted.weblate.org>
Co-authored-by: Frank Paul Silye <frankps@gmail.com>
Co-authored-by: HackingAll <hacking.all.YT@gmail.com>
Co-authored-by: Hurricane_32 <rodrigorimo@hotmail.com>
Co-authored-by: Ivan Dimitrov <idimitrov08@gmail.com>
Co-authored-by: Jayden Lo <jaydenlo08@gmail.com>
Co-authored-by: Jozef Gaal <preklady@mayday.sk>
Co-authored-by: Matjaž T. <matjaz@moj-svet.si>
Co-authored-by: Muxutruk <156070698+Muxutruk2@users.noreply.github.com>
Co-authored-by: Muxutruk <benat.eigurenzu@elorrieta-errekamari.com>
Co-authored-by: Nagy Krisztián <nkgy17@gmail.com>
Co-authored-by: Nicola Bortoletto <nicola.bortoletto@live.com>
Co-authored-by: PPNplus <ppnplus@protonmail.com>
Co-authored-by: Pavel Miniutka <pavel.miniutka@gmail.com>
Co-authored-by: Tim Morley <weblate.3919org@timsk.org>
Co-authored-by: Zekai Şahin <zekainihas@gmail.com>
Co-authored-by: bittin1ddc447d824349b2 <bittin@reimu.nl>
Co-authored-by: bones78 <jens@mueller-starck.de>
Co-authored-by: 김도윤 <wezardnote@gmail.com>
2026-06-18 23:17:29 +00:00
Daniel Dietzler
805bb84877 fix: defensive album owner migration (#29200) 2026-06-18 23:51:38 +02:00
Daniel Dietzler
a719552243 fix: rc version check (#29194) 2026-06-18 21:00:12 +00:00
Santo Shakil
9a5e7a8e47 fix(mobile): endless spinner on album selection when device has no albums (#28994)
* fix(mobile): endless spinner on album selection when device has no albums

* use a page scoped future provider for the loading state

* refactor(mobile): decide album selection empty state in the parent
2026-06-18 21:38:15 +05:30
Daniel Dietzler
62c6bb27e3 fix: workflow asset type filter required (#29196) 2026-06-18 16:04:28 +00:00
renovate[bot]
f3cb3cf98d fix(deps): update dependency nodemailer to v9 [security] (#29195) 2026-06-18 17:45:15 +02:00
Santo Shakil
c35abb2f66 fix(mobile): re-lock locked folder when the app is backgrounded (#29089)
* fix(mobile): re-lock locked folder when the app is backgrounded

* fix(mobile): simplify locked folder overlay condition

* fix(mobile): lock locked folder on pause instead of resume
2026-06-18 21:10:18 +05:30
Santo Shakil
793487e52c fix(mobile): refresh memories on resume and day change (#28983)
* fix(mobile): refresh memories on resume and day change

* fix(mobile): invalidate memories once after the resume sync
2026-06-18 20:46:20 +05:30
Ebin Santhosh
769c4015d3 fix(mobile): prevent duplicate login pages for unauthenticated share intent warm start (#29054) 2026-06-18 20:45:35 +05:30
renovate[bot]
c07cbe7ca8 chore(deps): update dependency multer to v2.2.0 [security] (#29183) 2026-06-18 16:31:10 +02:00
Timon
6d73bf4e36 refactor(web): move keyboard state into a store (#29181) 2026-06-18 16:29:26 +02:00
renovate[bot]
735f52a321 fix(deps): update typescript-projects (#29130)
Co-authored-by: Daniel Dietzler <mail@ddietzler.dev>
2026-06-18 16:21:35 +02:00
Daniel Dietzler
53fe26593c fix: asset type filter (#29190) 2026-06-18 15:30:34 +02:00
Daniel Dietzler
40cffcd414 fix: remove local-only step ids from workflow json (#29188) 2026-06-18 08:54:35 -04:00
Timon
48861b085e chore(server): organize integrity dtos (#29191) 2026-06-18 14:44:28 +02:00
Timon
09d0380804 refactor(server): use zod time validation (#29189) 2026-06-18 11:56:02 +00:00
Timon
83091d2834 fix(server): allow non-utc datetime offsets (#29186) 2026-06-18 11:27:11 +00:00
renovate[bot]
9a3071ae5c chore(deps): lock file maintenance machine-learning (mise) (#29152)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-06-17 19:34:45 -04:00
Mert
7ef2de6b53 feat(server): add hls playlist hint (#29150)
* add playlist hint

* update api

* unused import

* update crf in tests
2026-06-17 19:02:10 -04:00
Timon
cbe34d7931 fix(web): shift+click on GPS asset extends range selection in geolocation utility (#29022) 2026-06-17 18:53:23 +02:00
Rizwan
06c8d5a183 fix(web): use deterministic version name in svelte config (#29172) 2026-06-17 16:42:26 +00:00
Daniel Dietzler
ad9817c582 fix: web i18n (#29175) 2026-06-17 11:36:48 -05:00
Mees Frensel
14f6f2c04f refactor(web): simplify places page controls and use ui's Select (#29102) 2026-06-17 10:31:49 -04:00
Adam Gastineau
327521fa27 docs(mobile): point users towards shared setup docs (#29078) 2026-06-17 10:22:45 -04:00
Tom Vincent
3be803d0c0 docs(mobile-app): add Play App Signing certificate hash (#29168) 2026-06-17 14:19:04 +00:00
Jeevan Mohan Pawar
a364b56b1c fix(server): skip existing users when sharing albums (#28884)
Co-authored-by: Daniel Dietzler <mail@ddietzler.dev>
2026-06-17 15:54:20 +02:00
renovate[bot]
f9db76433e chore(deps): update github-actions to v1.313.0 (#29154)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-06-17 09:54:19 -04:00
Timon
3f2e51c5d4 refactor(server): use uuids in schemas (#29140) 2026-06-17 14:50:17 +02:00
Mees Frensel
430a2bbfd3 chore(server): add switch case exhaustiveness lint (#29029) 2026-06-17 12:04:41 +02:00
renovate[bot]
fbb0bc6e39 chore(deps): update ghcr.io/jdx/mise docker tag to v2026.6.10 (#29153) 2026-06-17 09:17:25 +00:00
Savely Krasovsky
8d30cfa2ce chore(ml): update intel graphics compiler and compute runtime (#29163)
chore(ml): update Intel graphics compiler and compute runtime to latest versions
2026-06-16 23:09:53 +00:00
github-actions
c9aa9ba711 chore: version v3.0.0-rc.1 2026-06-16 19:50:41 +00:00
Weblate (bot)
7cf904acbe chore(web): update translations (#29036)
Translate-URL: https://hosted.weblate.org/projects/immich/immich/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/ar/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/be/
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_GB/
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/ga/
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/lt/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/lv/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/ms/
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/pt_BR/
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/sq/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/uk/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/yue_Hant/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/zh_Hans/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/zh_Hant/
Translation: Immich/immich

Co-authored-by: Aindriú Mac Giolla Eoin <aindriu80@gmail.com>
Co-authored-by: Arif Budiman <arifpedia@gmail.com>
Co-authored-by: Bananenbrot <keram2810@outlook.de>
Co-authored-by: Charles Chase <cbs.chase927@passfwd.com>
Co-authored-by: Dan Bishop <d@nbishop.uk>
Co-authored-by: Dennis <dennis.kempel@hotmail.com>
Co-authored-by: DevServs <bonov@mail.ru>
Co-authored-by: Dániel Gál <galdaniel.school@gmail.com>
Co-authored-by: Enrico Jaupi <78313537@itiscastelli.onmicrosoft.com>
Co-authored-by: Erison Silva <hey@erison.work>
Co-authored-by: Fatah Rokbi <fatahrokbi@gmail.com>
Co-authored-by: Felipe Cabrera <me@felieppe.com>
Co-authored-by: Hurricane_32 <rodrigorimo@hotmail.com>
Co-authored-by: Jayden Lo <jaydenlo08@gmail.com>
Co-authored-by: Jozef Gaal <preklady@mayday.sk>
Co-authored-by: Mees Frensel <meesfrensel@gmail.com>
Co-authored-by: MrKoby07 <hirmerkorbinian122@gmail.com>
Co-authored-by: Mārtiņš Bruņenieks <martinsb@gmail.com>
Co-authored-by: Nagy Krisztián <nkgy17@gmail.com>
Co-authored-by: Nicola Bortoletto <nicola.bortoletto@live.com>
Co-authored-by: Nicolas Ceballos <nicoshafes@gmail.com>
Co-authored-by: Paul Comte <paulcomte1@gmail.com>
Co-authored-by: Pavel Miniutka <pavel.miniutka@gmail.com>
Co-authored-by: Popa <spammailsfuermich@gmail.com>
Co-authored-by: SAHOON KIM <sahuni@gmail.com>
Co-authored-by: Sylvain Pichon <service@spichon.fr>
Co-authored-by: Unimpeded Lemur <yg7lh0fz3@mozmail.com>
Co-authored-by: User 123456789 <user123456789@users.noreply.hosted.weblate.org>
Co-authored-by: Vegard Fladby <vegard@fladby.org>
Co-authored-by: anton garcias <isaga.percompartir@gmail.com>
Co-authored-by: azuziii <azuzii@proton.me>
Co-authored-by: dimetrius <dmitrytashin@gmail.com>
Co-authored-by: muziqaz <muziqaz@users.noreply.hosted.weblate.org>
Co-authored-by: rubes <mail@armd.one>
Co-authored-by: waclaw66 <waclaw66@seznam.cz>
2026-06-16 19:48:06 +00:00
renovate[bot]
983a005709 chore(deps): update github-actions to v3 (#29147) 2026-06-16 19:18:01 +00:00
Santo Shakil
cda499f2b5 fix(mobile): keep toasts off the dynamic island when keyboard is open (#29159) 2026-06-16 18:51:29 +00:00
Santo Shakil
12b7cd066b fix(mobile): show memories with no showAt/hideAt in the timeline lane (#29158) 2026-06-16 18:46:52 +00:00
shenlong
83b4dc1733 fix: scrolling state update after unmounted (#29149)
Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2026-06-16 13:14:09 -05:00
Daniel Dietzler
a9d64b30ad chore: update multi-runner-build (#29148) 2026-06-16 19:37:12 +02:00
renovate[bot]
3927eb6755 chore(deps): update base-image to v202606161235 (#29151) 2026-06-16 17:29:17 +00:00
renovate[bot]
27cfa0e788 chore(deps): update github-actions (#29146) 2026-06-16 19:22:40 +02:00
renovate[bot]
76c042abc7 chore(deps): lock file maintenance (mise) (#29091) 2026-06-16 19:08:09 +02:00
shenlong
0f49bcbd27 chore: do not optimize on cleanup (#29118) 2026-06-16 12:42:12 -04:00
Mees Frensel
df4a708aed chore(e2e): remove vite-tsconfig-paths (#29145) 2026-06-16 18:21:24 +02:00
renovate[bot]
34f78e3fd4 chore(deps): update github-actions (#29124) 2026-06-16 17:53:55 +02:00
Mees Frensel
3c2296b818 chore(web): use ui's Badge (#29143) 2026-06-16 17:12:25 +02:00
Daniel Dietzler
805ca1a2bf chore: maintenance page design tweaks (#29087) 2026-06-16 09:15:54 -05:00
Santo Shakil
9ee412110f fix(mobile): stop sync albums crashing on the main isolate (#29133)
the album sync provider read cancellationProvider, which only exists in the background isolate and throws on the main one. moved the cancel signal onto the isolate call path.

fixes #29125
fixes #29119
2026-06-16 09:14:33 -05:00
Brandon Wees
010220d588 fix: video thumbnail quality sharing (#29104) 2026-06-16 09:13:31 -05:00
shenlong
10fddf2d51 fix: resize map after scroll (#29112)
Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2026-06-16 09:13:28 -05:00
Mees Frensel
d307ab60ca fix: update datetimeRelative description to minutes instead of seconds (#29137) 2026-06-16 16:09:45 +02:00
Timon
ce59cc9241 docs(server): clarify AssetBulkUploadCheckItem.id is a correlation token (#29141) 2026-06-16 14:07:36 +00:00
Mees Frensel
1fa034125c fix(web): language selector (#29065) 2026-06-16 15:49:47 +02:00
okxint
8036dc4b8c fix(web): respect local timezone when building date range for search (#29128) 2026-06-16 13:45:49 +00:00
renovate[bot]
54895fb10e chore(deps): update node to ^24.13.2 (#29129) 2026-06-16 13:43:22 +00:00
Daniel Dietzler
e70a1163f3 fix: too strict cron expression validation (#29138) 2026-06-16 13:20:40 +00:00
Daniel Dietzler
a23a7c69ae fix: map settings (#29134) 2026-06-16 15:18:57 +02:00
Daniel Dietzler
f21a753aff fix: integrity report checksum query (#29136) 2026-06-16 15:11:20 +02:00
Timon
cc8d3b4107 fix(server): do not merge metadata when multiple duplicates are kept (#29035)
* fix(server): do not merge metadata when multiple duplicates are kept

* Update server/src/services/duplicate.service.spec.ts

Co-authored-by: Daniel Dietzler <36593685+danieldietzler@users.noreply.github.com>

---------

Co-authored-by: Daniel Dietzler <36593685+danieldietzler@users.noreply.github.com>
2026-06-15 16:05:04 -04:00
Alex
622a330d82 chore: slideshow transition improvement (#29079)
* chore: better slideshow transition

* chore: tune

* simplify setup

* better default

* fix: correctly zoom alternatively

* lint
2026-06-15 10:10:05 -05:00
Timon
5e8744a568 fix: lock transcoding options (#29076) 2026-06-15 10:50:00 -04:00
Timon
b633cc4f04 fix(server): hide partner archived asset locations from map (#29028) 2026-06-15 16:30:52 +02:00
Timon
a9ee6a7ce9 fix(web): show asset arrows (#29010)
* fix(web): show asset arrows

* lint
2026-06-15 10:22:59 -04:00
Daniel Dietzler
c273ccf2e2 feat: languages (#29088) 2026-06-15 16:01:09 +02:00
Mees Frensel
5f1a180d1a chore(web): remove dead code (#29092) 2026-06-15 14:54:51 +02:00
Brandon Wees
cc54de87aa fix(web): error loading image state (#29058) 2026-06-15 13:38:52 +02:00
Mees Frensel
a97e5999e4 chore(web): make login page email and password required (#29068) 2026-06-15 13:34:49 +02:00
okxint
46631b3786 fix(web): prevent upload status panel from overlapping album action bar (#29044) 2026-06-15 13:21:02 +02:00
Mees Frensel
5a3be158b9 fix(web): show album names in duplicate review (#29080) 2026-06-15 12:47:17 +02:00
maxinegardenas
b21af78454 fix(web): correctly handle person search with more than 100 results (#29002) 2026-06-12 20:04:52 +00:00
Santo Shakil
abd62d9295 fix(mobile): show like and comment options on album photo deep links (#29020) 2026-06-12 14:55:26 -05:00
Jason Rasmussen
e31d4aa909 fix: prerelease draft (#29034) 2026-06-12 12:43:19 -04:00
Daniel Dietzler
43b2d04e2c fix: version tests (#29032) 2026-06-12 15:54:42 +00:00
github-actions
e4dbe777a0 chore: version v3.0.0-rc.0 2026-06-12 14:55:39 +00:00
Weblate (bot)
d36aed4c5b chore: update translations (#27764)
chore(web): update translations















































































































































































































Translate-URL: https://hosted.weblate.org/projects/immich/immich/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/af/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/ar/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/az/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/be/
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/da/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/de/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/de_CH/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/el/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/eo/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/es/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/et/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/eu/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/fi/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/fil/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/fr/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/ga/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/gl/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/gsw/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/gu/
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/lt/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/lv/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/mn/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/mr/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/ms/
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/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/sk/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/sl/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/sq/
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/te/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/th/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/tr/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/uk/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/uz/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/vi/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/yue_Hant/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/zh_Hans/
Translate-URL: https://hosted.weblate.org/projects/immich/immich/zh_Hant/
Translation: Immich/immich

Co-authored-by: -J- <heyj0e@tuta.io>
Co-authored-by: 12LuA <Luca.strack@gmx.de>
Co-authored-by: AM <alex2539rulez@yahoo.com>
Co-authored-by: Abdel rahman Abdaldeen <abd.abdaldeen@gmail.com>
Co-authored-by: Abhijeet Bonde <abhijeetbonde19@gmail.com>
Co-authored-by: Adam <adammarzec2@protonmail.com>
Co-authored-by: Adam Havránek <adamhavra@seznam.cz>
Co-authored-by: Ah Tui <kit719@gmail.com>
Co-authored-by: Ahmed Khaleel Shihab <ahmed91shihab@gmail.com>
Co-authored-by: Aindriú Mac Giolla Eoin <aindriu80@gmail.com>
Co-authored-by: Alessandro Mandelli <mandelli.alessandro@ngi.it>
Co-authored-by: Alex <darkstylo@gmail.com>
Co-authored-by: Alvaro Samudio <alvarosamudio1@gmail.com>
Co-authored-by: Andreas Fjetland <andreas@fjet.no>
Co-authored-by: Andreas W. Pross <andreas.pross@styletronix.net>
Co-authored-by: Andrii Solianyk <asolianik2015@gmail.com>
Co-authored-by: Andrius <sndriuss@gmail.com>
Co-authored-by: AntonPalmqvist <apq@users.noreply.hosted.weblate.org>
Co-authored-by: Antonio Labate <antoniolabate19@gmail.com>
Co-authored-by: Arif Budiman <arifpedia@gmail.com>
Co-authored-by: Avihai Zarouk <myaulamyau@gmail.com>
Co-authored-by: Bartłomiej <20731216+Jarsey45@users.noreply.github.com>
Co-authored-by: Bas Wevers <baswevers@gmail.com>
Co-authored-by: Bat-Uyanga Batdelger <batuyanga@gmail.com>
Co-authored-by: Benjamin Serec <serec.benjamin@gmail.com>
Co-authored-by: Bonnie 20402 <darioperreira2013@gmail.com>
Co-authored-by: Bora Atıcı <boratici.acc@gmail.com>
Co-authored-by: BrekkeLiten <david@brek.ke>
Co-authored-by: Calvin Erfmann <calvin.erfmann@pm.me>
Co-authored-by: Carlo Beltrame <weblate@pendantmusic.ch>
Co-authored-by: Carlos de Freitas <cjcfreitas@gmail.com>
Co-authored-by: Charles Frégeau <fregeauc@outlook.com>
Co-authored-by: Climent Fernández Andújar <climentfean@gmail.com>
Co-authored-by: Clément Pingliez <pingliezclement@gmail.com>
Co-authored-by: Cédric <cedric@laubacher.io>
Co-authored-by: D S <weblate.2w8z9@slmail.me>
Co-authored-by: Dan <rattly@duck.com>
Co-authored-by: David Bono <dbono2454@gmail.com>
Co-authored-by: David Maneiro <david.maneiro8@gmail.com>
Co-authored-by: David Miguel Rodrigues Rosa <dmr.rosa@gmail.com>
Co-authored-by: Denis Pacquier <denis.pacquier@gmail.com>
Co-authored-by: DevServs <bonov@mail.ru>
Co-authored-by: Dmitry Banny <dj.icecore@gmail.com>
Co-authored-by: Dmytro Sergienko <dima.sergienko@gmail.com>
Co-authored-by: Don't use my name <maxabmeyer@gmail.com>
Co-authored-by: Dusan Hlavaty <dhlavaty@gmail.com>
Co-authored-by: Dániel Gál <galdaniel.school@gmail.com>
Co-authored-by: Elyas Sindi <elyassindi@proton.me>
Co-authored-by: Enric Pagès i Gassull <enricpages@hotmail.com>
Co-authored-by: Felix Noren <fnoren17@gmail.com>
Co-authored-by: Filipe Monteiro <pimonteiro@protonmail.com>
Co-authored-by: Fjuro <fjuro@users.noreply.hosted.weblate.org>
Co-authored-by: Focron <eliaelmas55@gmail.com>
Co-authored-by: Frank Paul Silye <frankps@gmail.com>
Co-authored-by: Gabriel <jellyfin.sensitize624@passmail.net>
Co-authored-by: Gnubblz <philipp@phild.de>
Co-authored-by: HackingAll <hacking.all.YT@gmail.com>
Co-authored-by: Hamza Foziljonov <hamza.uztranslator@gmail.com>
Co-authored-by: Hans Cats <hanscats@gmail.com>
Co-authored-by: Happy <59247878+happy2452354@users.noreply.github.com>
Co-authored-by: Haru Ijima <haruijimakun@gmail.com>
Co-authored-by: Hurricane_32 <rodrigorimo@hotmail.com>
Co-authored-by: Indrek Haav <indrekhaav@users.noreply.hosted.weblate.org>
Co-authored-by: Iren <iren.biggel@gmail.com>
Co-authored-by: Ivan Dimitrov <idimitrov08@gmail.com>
Co-authored-by: JPar99 <github.wad969@passmail.com>
Co-authored-by: Jarle K. Hopland <jarlekh@gmail.com>
Co-authored-by: Jayson <mrjaysonbulugagao@gmail.com>
Co-authored-by: Jeanré du Plessis <jeanreduplessis2000@gmail.com>
Co-authored-by: Jedediah Russell <john17three@protonmail.com>
Co-authored-by: Jeppe Nellemann <jepnel@proton.me>
Co-authored-by: Joel Molina Navarro <joelmolinanavarro21@gmail.com>
Co-authored-by: Jozef Gaal <preklady@mayday.sk>
Co-authored-by: João M. Gabaldi <opera-hidras-0r@icloud.com>
Co-authored-by: Juan Casimiro <jc7946033@gmail.com>
Co-authored-by: Julius Lehmann <julius.lehmann.privat@gmail.com>
Co-authored-by: KecskeTech <teonyitas@gmail.com>
Co-authored-by: Kristian Franceschini <kristian@kmsfhost.com>
Co-authored-by: Leo Bottaro <github@leobottaro.com>
Co-authored-by: Loonatiq <Loona9422@pm.me>
Co-authored-by: Lorenz Schmid <schmidlorenz@gmx.ch>
Co-authored-by: MSDNicrosoft <i@msdnicrosoft.work>
Co-authored-by: Manfred Bjørlin <manfred.bjorlin@gmail.com>
Co-authored-by: MarcSerraPeralta <marcserraperalta@gmail.com>
Co-authored-by: Marco Janssen <Marco@neverminds.net>
Co-authored-by: Marco Mertel <mertel.marco@gmail.com>
Co-authored-by: Marian Wolf <marian.wolf2008@gmail.com>
Co-authored-by: Martin <weblate.recoil725@passmail.net>
Co-authored-by: Matjaž T. <matjaz@moj-svet.si>
Co-authored-by: Matteo Morari <matteo.morari04@gmail.com>
Co-authored-by: Matthias Cramer <matthias.cramer@iway.ch>
Co-authored-by: Maxi Herczegh <maxiherczegh@outlook.com>
Co-authored-by: Maćvej Pažytnykh <ma.pazhitnykh@gmail.com>
Co-authored-by: Mees Frensel <meesfrensel@gmail.com>
Co-authored-by: Melih Ozkan <malihozkan156@gmail.com>
Co-authored-by: Mike Moolenaar <mike.moolenaar@posteo.nl>
Co-authored-by: Milos <milos@milic.in>
Co-authored-by: Molnár Bence Attila <it@bence0327.hu>
Co-authored-by: Mona Lisa <monalisa@users.noreply.hosted.weblate.org>
Co-authored-by: Mona Lisa <nickwick@users.noreply.hosted.weblate.org>
Co-authored-by: Mārtiņš Bruņenieks <martinsb@gmail.com>
Co-authored-by: NAL <niko.a.leinonen@gmail.com>
Co-authored-by: Nagy Krisztián <nkgy17@gmail.com>
Co-authored-by: Nandhakumar Subramanian <nandha.kumar790@gmail.com>
Co-authored-by: Nicholas Amadori <nico282@gmail.com>
Co-authored-by: Nicola Bortoletto <nicola.bortoletto@live.com>
Co-authored-by: Nuno Aparicio <nunoxyz@gmail.com>
Co-authored-by: OffsetMonkey538 <offsetmonkey538@gmail.com>
Co-authored-by: Olaf Nielsen <solluh@mail.de>
Co-authored-by: Oleksandr Yurov <oyurov@icloud.com>
Co-authored-by: Osama <laptooxz@proton.me>
Co-authored-by: PPNplus <ppnplus@protonmail.com>
Co-authored-by: Patrick Raths <piroh1990@gmail.com>
Co-authored-by: Pavel Miniutka <pavel.miniutka@gmail.com>
Co-authored-by: Pavlo Sydoriuk <sidopas@gmail.com>
Co-authored-by: Pazystamas <pazystamas@gmail.com>
Co-authored-by: Petri Hämäläinen <petri.hamalainen@mailbox.org>
Co-authored-by: Phillip Kang <phillipxkang@gmail.com>
Co-authored-by: Piero B. <biagini93@gmail.com>
Co-authored-by: Piero Bi <biagini93@ik.me>
Co-authored-by: PilgrimToHyperion <pilgrimtohyperion@gmail.com>
Co-authored-by: Piotr Pazhytnykh <pazhitnykhpetr@gmail.com>
Co-authored-by: PontusÖsterlindh <pontus@osterlindh.com>
Co-authored-by: Rafael Henrique <rafaelmobile124@gmail.com>
Co-authored-by: Raul <raul.plesa@gmail.com>
Co-authored-by: Ravuru Umesh <umeshravuru@gmail.com>
Co-authored-by: Remco <remco@pander.io>
Co-authored-by: Ricardo Tomazela do Prado <kao.prado@gmail.com>
Co-authored-by: Richiondrugs <riccardocastellano07@gmail.com>
Co-authored-by: Robert Virkus <robert.virkus@enough.de>
Co-authored-by: Robin Schanbacher <robin@schanbros.de>
Co-authored-by: Roger Pueyo Centelles <roger.pueyo@guifi.net>
Co-authored-by: Sebastian <sebastiankiwidk@gmail.com>
Co-authored-by: Seungbeom Ha <tmdqja75@gmail.com>
Co-authored-by: Shaw <shawyunz@gmail.com>
Co-authored-by: Simone Ognibene <ognibene2001@gmail.com>
Co-authored-by: Steffen Seubert <seubert.steffen@gmail.com>
Co-authored-by: Sven Kortekaas <github@skortekaas.nl>
Co-authored-by: Sylvain Pichon <service@spichon.fr>
Co-authored-by: TA <tobi@warsnich.de>
Co-authored-by: TLuce <thomas.luce@ik.me>
Co-authored-by: Taleh Rzayev <talehji@gmail.com>
Co-authored-by: Thomas van Gemert <dendolla@users.noreply.hosted.weblate.org>
Co-authored-by: Thế Anh Hoàng <the.anh.ls@gmail.com>
Co-authored-by: Tijs-B <tijs.bergmans@telenet.be>
Co-authored-by: Tim Morley <weblate.3919org@timsk.org>
Co-authored-by: Tom Mueller <muellertomgabsnichtmehr@gmail.com>
Co-authored-by: Tomislav Renić <trenic@gmail.com>
Co-authored-by: UDP <udp@users.noreply.hosted.weblate.org>
Co-authored-by: Ulices <hasecilu@tuta.io>
Co-authored-by: User 123456789 <user123456789@users.noreply.hosted.weblate.org>
Co-authored-by: VRADDB <dimitri.debruyne@vanroey.be>
Co-authored-by: Vegard Fladby <vegard@fladby.org>
Co-authored-by: Vishal Ghelani <vishal.ghelani@gmail.com>
Co-authored-by: WellsTsai <dan50907@gmail.com>
Co-authored-by: Yago Raña Gayoso <yago.rana.gayoso@gmail.com>
Co-authored-by: Yohsi <yohan.simard@proton.me>
Co-authored-by: Yolopix <13918281+y0lopix@users.noreply.github.com>
Co-authored-by: Yusuf Soyipek <yusuf@soyipek.com>
Co-authored-by: Zhigang Wu <wu.zhigang@xuan-ming.net>
Co-authored-by: Zillazapdos <harald.vagle.undheim@icloud.com>
Co-authored-by: adun <github.scariness216@passinbox.com>
Co-authored-by: anton garcias <isaga.percompartir@gmail.com>
Co-authored-by: arvissidorovs <arvis.sidorovs@gmail.com>
Co-authored-by: bittin1ddc447d824349b2 <bittin@reimu.nl>
Co-authored-by: bosund <bosund@gmail.com>
Co-authored-by: chamdim <chamdim@protonmail.com>
Co-authored-by: daniqss <danielqueijo14@gmail.com>
Co-authored-by: david7xw <davdavid7xw@gmail.com>
Co-authored-by: dkorecko <github@david.korecko.com>
Co-authored-by: eav5jhl0 <eav5jhl0@users.noreply.hosted.weblate.org>
Co-authored-by: fascinate722 <fascinate722@gmail.com>
Co-authored-by: guillermo <guillermoremesa@gmail.com>
Co-authored-by: h1nnak <gerlich@mailbox.org>
Co-authored-by: hanlie <Hanlie@users.noreply.hosted.weblate.org>
Co-authored-by: iwonder <iwonder@users.noreply.hosted.weblate.org>
Co-authored-by: jasoisjaso <jaso.bih@gmail.com>
Co-authored-by: jicetus. <jicetus@users.noreply.hosted.weblate.org>
Co-authored-by: jmilovic <krunazajecar@gmail.com>
Co-authored-by: josuloo99 <josuloidi1999@gmail.com>
Co-authored-by: jw2122 <johwol25@gmail.com>
Co-authored-by: kylo32 <kylo32@gmail.com>
Co-authored-by: miiyuh <itsazripp2@gmail.com>
Co-authored-by: millallo <millallo@tiscali.it>
Co-authored-by: muziqaz <muziqaz@users.noreply.hosted.weblate.org>
Co-authored-by: oliwia <mroskarez@gmail.com>
Co-authored-by: on9686 <on9686@gmail.com>
Co-authored-by: outsider-tabby-pox <outsider-tabby-pox@duck.com>
Co-authored-by: pneuly <pneuly@gmail.com>
Co-authored-by: pyccl <changcongliang@163.com>
Co-authored-by: rubes <mail@armd.one>
Co-authored-by: s0nprem0 <s0nprem0@proton.me>
Co-authored-by: scudo <whiteshield.tg@protonmail.com>
Co-authored-by: slick-daddy <129640104+slick-daddy@users.noreply.github.com>
Co-authored-by: tct123 <tct1234@protonmail.com>
Co-authored-by: traumanndylan <traumanndylan@gmail.com>
Co-authored-by: tvirolai <tuomo.virolainen@rebase.fi>
Co-authored-by: veiskiboi <vesahok@gmail.com>
Co-authored-by: waclaw66 <waclaw66@seznam.cz>
Co-authored-by: x0x0b <42596409+x0x0b@users.noreply.github.com>
Co-authored-by: Вячеслав Лукьяненко <madeinchuguev@gmail.com>
Co-authored-by: Сергій Савчук <serge.savchuk@gmail.com>
Co-authored-by: 星 <seirun124@gmail.com>
Co-authored-by: 정동걸 <i.jdk.dev@gmail.com>
2026-06-12 14:52:03 +00:00
Alex
f1da9d2429 chore: update perm for build mobile in release gha (#29027) 2026-06-12 14:34:11 +00:00
Mees Frensel
232ca3cf3f chore(web): clarify workflow triggers (#29026) 2026-06-12 19:43:57 +05:30
Matthew Momjian
50f1121459 fix(docs): v3 bumps (#29007)
* v3 bumps

* format
2026-06-12 09:34:35 -04:00
Mees Frensel
892397807c chore(web): add switch case exhaustiveness lint (#29015) 2026-06-12 09:30:19 -04:00
Timon
714c647937 fix(web): focus on scrollable element on load (#29004)
fix(web): focus on scrollable element on load
2026-06-12 12:59:16 +02:00
Spencer Stingley
c56f477a0f fix: Improving scroll behavior on image stacks that overflow the screen (#28885)
Co-authored-by: Spencer Stingley <accounts@blankcanvas.io>
Co-authored-by: Mees Frensel <33722705+meesfrensel@users.noreply.github.com>
2026-06-12 11:50:10 +02:00
shenlong
296cd40da9 refactor: nullable settings key (#28988)
Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2026-06-11 18:05:50 -05:00
Alex
a17276fd1e chore: remove incompatibility message warning (#28993)
* chore: remove incompatibility message warning

* lint
2026-06-11 18:05:19 -05:00
Jason Rasmussen
c3e23a6b3a fix: schema configuration (#29000) 2026-06-11 18:05:07 -05:00
Jason Rasmussen
13a7b4a276 fix: pump script (#28998) 2026-06-11 18:04:42 -05:00
renovate[bot]
563cff26bf chore(deps): update machine-learning (#28934)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-06-11 14:40:20 -04:00
Daniel Dietzler
e81b6778ca fix: workflow page reactivity issues (#28996) 2026-06-11 13:13:25 -05:00
Alex
aa6af7ce36 chore: workflow trigger i18n (#28992) 2026-06-11 10:55:18 -05:00
Santo Shakil
59d036a2ed fix(mobile): give android notification channels proper names (#28986) 2026-06-11 15:07:37 +00:00
renovate[bot]
7a5c014558 fix(deps): update typescript-projects (#28627)
Co-authored-by: Daniel Dietzler <mail@ddietzler.dev>
2026-06-11 17:02:54 +02:00
Santo Shakil
e2954b6411 fix(mobile): show albums whose assets are all trashed (#28985) 2026-06-11 09:41:02 -05:00
renovate[bot]
0fb18ed241 chore(deps): update dependency commander to v15 (#28936) 2026-06-11 12:18:25 +02:00
renovate[bot]
c0b3b08ce6 chore(deps): update exiftool to v35.21.0 (#28933) 2026-06-11 12:16:13 +02:00
Mees Frensel
e8a1084e5b fix(web): heatmap layout and date formatting (#28976)
* fix(web): heatmap layout and date formatting

* chore

---------

Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2026-06-11 08:36:34 +00:00
Santo Shakil
d227ba2d51 fix(mobile): stale details after editing asset date (#28977) 2026-06-10 21:32:02 -05:00
Santo Shakil
9cb94343d1 fix(mobile): keep timezone when editing asset date time (#28978)
* fix(mobile): keep timezone when editing asset date time

* fix(mobile): negative utc offsets with minutes off by an hour
2026-06-10 21:31:31 -05:00
Mert
aa126e377c fix(server): add hint header for segment after init.mp4 (#28867)
* add hint header for segment after init.mp4

* use zod

* actually validate

* update openapi

* linting
2026-06-10 19:18:36 -04:00
Paul Makles
74878628c8 feat: integrity check jobs (missing files, untracked files, checksums) (#24205)
Co-authored-by: Daniel Dietzler <mail@ddietzler.dev>
Signed-off-by: izzy <me@insrt.uk>
2026-06-10 21:02:27 +02:00
Mees Frensel
4ead3e697d chore(server): update asset not ready error messages (#28968) 2026-06-10 20:23:17 +02:00
Daniel Dietzler
fb798a8f29 chore: remove person workflow elements (#28974) 2026-06-10 18:49:33 +02:00
Santo Shakil
07813135b5 fix(mobile): deduplicate people in asset details panel (#28972) 2026-06-10 14:37:45 +00:00
Daniel Dietzler
92a75b0cd3 fix(web): person that is in the same asset multiple times (#28971) 2026-06-10 09:32:29 -05:00
Alex
8132e8a38c feat: image quality option in sharing (#28918)
* feat: share with quality options

* merge main

* clean up

* refactor

* translation

* translation

* add settings and default behavior

* fix: lint

* cleanup

* merge main

---------

Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2026-06-10 09:26:09 -05:00
Yaros
43f2f56530 fix(mobile): map timeline layout crash (#28878) 2026-06-10 14:02:36 +00:00
renovate[bot]
e580bb5d0a chore(deps): update github-actions (#28930)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-06-10 09:45:02 -04:00
bo0tzz
d3680871ef feat: warn if microservices worker is missing (#28869)
* feat: warn if microservices worker is missing

* fix: ci
2026-06-10 09:31:32 -04:00
Stefan Yoshovski
b9b1cc2f65 feat(web): warn before overwriting existing locations in geolocation utility (#28840) 2026-06-10 11:09:12 +00:00
Pedro Vieira
7d198956a6 fix(web): Prevent face editor from closing when dismissing tag confirmation (#28900) 2026-06-10 12:31:52 +02:00
Pedro Vieira
a7b5f81701 fix: normalize diacritics in person name search in Web & Mobile (#28887) 2026-06-10 12:05:07 +02:00
Timon
5c38373808 refactor(server): allow -1 rating again (#28886) 2026-06-10 10:55:51 +02:00
Ben Beckford
1ce961fbb3 feat: geolocation workflow filter (#28961)
* feat: geolocation workflow filter

* refactor: geolocation workflow filter

* feat: location filter workflow example
2026-06-10 05:05:01 +00:00
shenlong
4bc411b7c7 revert: clear album description sends null instead of empty string (#28956)
Revert "fix(mobile): clear album description sends null instead of empty string (#28817)"

Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2026-06-09 22:05:37 -05:00
Santo Shakil
11c1025271 fix(mobile): add album picker to archive bottom sheet (#28953) 2026-06-09 14:45:32 -05:00
Jason Rasmussen
8b5385f94b feat: add prerelease support to pump version (#28922)
refactor: pump script
2026-06-09 14:42:10 -04:00
Alex
d3438cf4a7 chore: improve OCR button and display on mobile (#28926)
* chore: improve OCR button and display on mobile

* Refactor

* format

* simplify ocr toggle button

---------

Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2026-06-09 13:20:18 -05:00
Alex
6c5c6a1035 fix: realign badge icon (#28951) 2026-06-09 11:44:29 -05:00
Santo Shakil
c928787b3e fix(mobile): show error when creating an album fails (#28942)
it failed silently when the server was down. also disable create for blank titles.
2026-06-09 16:41:32 +00:00
Santo Shakil
fe9ca4f40a fix(mobile): show memory and folder dates in local time (#28941) 2026-06-09 10:55:43 -05:00
Savely Krasovsky
a665cec920 feat(ml): update Intel graphics compiler and compute runtime (#28924)
feat(ml): update Intel graphics compiler and compute runtime to latest versions
2026-06-09 11:08:03 -04:00
Alex
568283a8eb fix: stale translation generation (#28949) 2026-06-09 14:28:48 +00:00
renovate[bot]
f382624e68 fix(deps): update @immich/ui to ^0.80.0 (#28935) 2026-06-09 11:19:41 +02:00
renovate[bot]
24dad15636 chore(deps): update grafana monorepo to v12.4.4 (#28931)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-06-09 00:05:01 -04:00
renovate[bot]
7ab533b57b chore(deps): update dependency vitest to v3.2.6 [security] (#28915)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-06-09 00:03:56 -04:00
Timon
d10153bbc7 fix(server): hide isFavorite from album asset sync stream (#28923)
* fix(server): hide isFavorite from album asset sync stream

* some tests

* Revert "some tests"

This reverts commit 3242e6961c.

* alter existing test to clear test's intent

* Reapply "some tests"

This reverts commit f1d4c47f5f.

* drop one

* sql
2026-06-09 00:03:03 -04:00
Timon
b846afeb08 chore(server): tests for hide isFavorite for partner assets (#28927) 2026-06-09 00:01:39 -04:00
shenlong
e222b19576 fix: do not handle drag without enough scrub area (#28921)
Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2026-06-08 16:47:08 -05:00
shenlong
1fee99cd2a ci: verify pigeon autogen output during static analysis (#28920)
Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2026-06-08 16:46:51 -05:00
bo0tzz
70bb7e4b7e fix: step name reference in fix-format.yml (#28912) 2026-06-08 14:32:34 -04:00
Yaros
f973927c68 docs: replace make for mise (#28913)
* docs: replace make for mise

* chore: remove makefile comment
2026-06-08 14:31:23 -04:00
Daniel Dietzler
e29267359e fix: detail panel faces reactivity issues (#28910) 2026-06-08 18:07:57 +02:00
joojoooo
164cda87a3 fix(web): use irot/imir tags for HEIF Orientation (#27820)
* fix(web): use irot/imir tags for HEIF Orientation

* ignore Exif Orientation for HEIF images per MIAF standard compliance

* add Rotation and Mirroring to exiftool numericTags

* add isHeifBasedImage function to detect HEIF-based image extensions

* add getHeifBasedOrientation method to map irot/imir tags to ExifOrientation

* removed mirroring, simplified code

* Removed "Based" in "heifBased"

---------

Co-authored-by: Jason Rasmussen <jason@rasm.me>
2026-06-08 09:33:28 -04:00
renovate[bot]
12d344efe0 chore(deps): update pnpm to v11 (#28773)
Co-authored-by: Daniel Dietzler <mail@ddietzler.dev>
2026-06-08 14:44:45 +02:00
Timon
474efd39f8 refactor(server): prevent sharing album with owner by filtering out user from albumUsers (#28891)
fix(server): prevent sharing album with owner by filtering out user from albumUsers
2026-06-07 17:46:26 -04:00
Timon
9e453440e6 refactor(server): deprecate PUT routes in favor of PATCH (#28859)
* add patch routes and deprecate put

* gen client
2026-06-07 09:40:01 -04:00
Timon
8860817c76 chore: global Java (#28874) 2026-06-07 09:36:28 -04:00
shenlong
3c108a8d22 fix: reload timeline on group by setting change (#28864)
Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2026-06-05 19:08:39 +00:00
Santo Shakil
8d553d6e9c chore: add santo to mobile codeowners (#28863) 2026-06-05 17:24:07 +00:00
Yaros
346b98ed4f feat(mobile): min face count per-user (#28805) 2026-06-05 13:16:07 -04:00
shenlong
60683bd91e fix: cross isolate drift watchers (#28862)
Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2026-06-05 12:15:41 -05:00
Daniel Dietzler
b6938614b2 feat: latest language requests (#28858) 2026-06-05 18:01:27 +02:00
bo0tzz
98961a1d36 fix: filter close-duplicates for org members (#28856) 2026-06-05 10:34:29 -05:00
Daniel Dietzler
5ae95102b4 chore: workflow drag and drop improvements (#28838) 2026-06-04 21:45:31 -05:00
shenlong
216d0ba365 fix: notify timeline updates after sync (#28846)
Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2026-06-04 18:17:04 -05:00
Timon
28e42f7e29 refactor(mobile): use Optional only on API boundary (#28845) 2026-06-05 04:29:13 +05:30
Yaros
733373c0ca feat(mobile): ocr support (#26523) 2026-06-05 04:29:03 +05:30
shenlong
5617d6ca7c ci: ios builds (#28847)
Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2026-06-05 03:50:39 +05:30
Brandon Wees
875dd2dead fix: check continue origins with URL constructor (#28835)
* fix: check origins with URL constructor

* fix: fallback

* chore: tests
2026-06-04 16:20:05 -04:00
Jason Rasmussen
9043bc8435 fix: error handling (#28843) 2026-06-04 16:19:16 -04:00
Abhijeet Sanjiv Bonde
b3d49045de feat: user upload heatmap (#28593)
* Feat - Heatmap

* Implemented Comments to prettify and code cleanup

* fixing code to pass cases.

* fixing errors for OpenAPI Clients

* Improving the code.

* Fix code

* Rerun generated client check

* Rerun generated client

* feat: command for user pages (#28554)

* fix(web): timeline stuttering with many assets in 1 day (#28509)

* fix(web): timeline stuttering with many assets in 1 day

* cache isInOrNearViewport per day

* skip inOrNearViewport check on first run

* chore(ml): allow insightface 1.x (#28595)

* chore(ml): allow insightface 1.x

The new insightface 1.0 release appears to have no breaking code changes nor relevant license changes ([before](2a78baec42/README.md), [after](70f3269ea6/README.md), c.f. fd7ddfef54/machine-learning/README.md), and it works on my machine.

* Update uv.lock

* please excuse my incompetence

* Triggering the actions.

* bad merge

* Fix code

* Code clear

* Resolve conflict

* Resolve conflict

* Resolve conflict

* Resolve errors

* Resolve errors

* Resolve errors more

* chore: clean up

---------

Co-authored-by: Alex <alex.tran1502@gmail.com>
Co-authored-by: Ben Beckford <ben@benjaminbeckford.com>
Co-authored-by: Aaron Liu <aaronliu0130@gmail.com>
Co-authored-by: Jason Rasmussen <jason@rasm.me>
2026-06-04 15:36:09 -04:00
shenlong
58528cad08 refactor: replace drift_flutter with drift_sqlite_async (#28440)
replace drift_flutter with drift_sqlite_async

Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2026-06-05 00:01:50 +05:30
Mees Frensel
99281de6ab refactor!: disallow star rating < 1 (#27896)
Co-authored-by: Daniel Dietzler <mail@ddietzler.dev>
Co-authored-by: timonrieger <mail@timonrieger.de>
2026-06-04 17:06:28 +00:00
Daniel Dietzler
6268d23d12 fix: restore video play/plause shortcut (#28837) 2026-06-04 18:51:16 +02:00
Daniel Dietzler
d7999ce1d1 feat: workflows drag and drop enhancements (#28764) 2026-06-04 10:46:51 -05:00
shenlong
6b0fd89cd2 refactor: partner-page (#28783)
* refactor: partner-page

* cleanup

---------

Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2026-06-04 09:05:45 -04:00
Daniel Dietzler
4b0adb7a1e fix: stack arrow navigation when not directly navigating to an asset (#28828) 2026-06-04 14:49:12 +02:00
shenlong
de70d19d20 feat: show notification and battery optimization warning (#26610)
* feat: show notification and battery optimization warning

* cleanup

---------

Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2026-06-04 12:24:39 +00:00
bo0tzz
7155bb1e80 chore: fix up docs placeholders (#28814) 2026-06-04 08:19:40 -04:00
Alex
fa08e72d30 chore: scope flutter install from mise (#28820)
* chore: scope flutter install from mise

* ci: scope use-mise to mobile directory

---------

Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2026-06-04 17:24:38 +05:30
Timon
e2de8c7c53 refactor(server)!: remove changeExpiryTime (#28816)
* fix(mobile): clear shared link password

* fix(mobile): clear shared link description

* fix(mobile): clear shared link expiry

* refactor(server)!: remove changeExpiryTime

* fix(mobile): clear shared link expiry

---------

Co-authored-by: Jason Rasmussen <jason@rasm.me>
2026-06-04 08:35:45 +00:00
Santo Shakil
429e181c8f fix(mobile): run iOS bg task phases in parallel (#28293)
onIosUpload runs sync local, sync remote, hash and handle backup
sequentially. on the bg refresh task path that's a 20s budget from
iOS, and sync + hash usually eat all of it before backup gets a turn
to enqueue any candidates.

these phases don't actually depend on each other. local + remote sync
touch different tables. hash works off whatever's already in drift.
handle backup reads candidates and just enqueues to URLSession bg.
anything one phase produces in this fire shows up to the others on
the next fire, and server-side dedup catches the rare race where
backup enqueues something sync remote was about to mark as already
uploaded.

so this runs all four concurrently via Future.wait, with hash getting
the full maxSeconds-1 budget instead of a fixed 5s. outer budget
timeout still caps everything before iOS expires.

second small change: getAssetsToHash orders by createdAt DESC instead
of id ASC to match getCandidates. when hash runs inside a refresh
fire it processes recent photos first.
2026-06-03 20:13:52 -05:00
winston
7f611d9031 test: fix tests when OpenVINO provider is available (#28802)
mocking `onnxruntime.get_available_providers()` to always use the CPU EP.
2026-06-03 20:52:08 -04:00
Timon
e94e22f3f8 fix(server): respect timezone in iso date string encoding (#28810) 2026-06-03 19:00:10 -04:00
Timon
4a8c3b60be fix(mobile): clear album description sends null instead of empty string (#28817) 2026-06-03 18:22:19 -04:00
Timon
2190aa72a8 refactor(server): zod int validation (#28804) 2026-06-03 18:21:07 -04:00
Timon
d21cb28526 fix(mobile): shared link edit sends explicit null instead of empty string (#28812)
* fix(mobile): clear shared link password

* fix(mobile): clear shared link description

* fix(mobile): clear shared link expiry
2026-06-03 18:19:35 -04:00
Timon
5c33eb3204 refactor(server)!: drop empty string to null conversion (#28808)
refactor(server): drop empty string to null conversion
2026-06-03 18:16:53 -04:00
Mert
137687bc0f fix(web): set src for progressive video player (#28813)
set src
2026-06-03 17:07:23 -04:00
Peter Ombodi
9d4a6614b1 feat(mobile): Android. Immich as a gallery / image viewer app (#26109)
* feat(mobile): handle Android ACTION_VIEW intent
- add ViewIntent Pigeon API and generated bindings
- implement Android ViewIntentPlugin + iOS no-op host
- route ExternalMediaViewer by ViewIntentAttachment
- buffer pending view intents and flush on user ready/resume

* feat(mobile): fallback to computed checksum for timeline match
- hash local asset on-demand when checksum missing
- search main timeline by localId or checksum before standalone viewer
- persist computed hash into local_asset_entity

* fix(mobile): proper handling is user authenticated

* feat(mobile): open ACTION_VIEW fallback in AssetViewer
drop ExternalMediaViewer route

* feat(mobile): add logger

* test(mobile): add unit tests for view intent pending/flush flow

* fix(mobile): fix format

* fix(mobile): remove redundant iOS code
update code related to LocalAsset model and asset viewer

* refactor(mobile): simplify view intent flow and support file-backed ACTION_VIEW assets
remove redundant view intent model/repository layer
handle transient ACTION_VIEW files in viewer/upload flow
clean up managed temp files for fallback assets

* refactor(mobile): extract MediaStore utils and resolve view intents via merged assets

* refactor(mobile): move deferred view intents into providers, split view-intent providers, and clean up ACTION_VIEW handling

* refactor(mobile): resolve merge conflicts
use NativeSyncApi for hash files instead method from removed BackgroundServicePlugin.kt

* style(mobile): format files

* style(mobile): format files #2

* refactor(mobile): lazily materialize view-intent files and clean up temp-file handling

* fix(mobile): flush pending view intents after login navigation

* refactor(mobile): split view intent handler by platform and trigger it from app events

* refactor(mobile): move view intent handling behind platform-specific factories

* refactor(mobile): simplify code

* fix(mobile): hand off deep-link viewer to main timeline after upload
Add MainTimelineHandoffCoordinator to switch the asset viewer to the main timeline once a view-intent asset is uploaded and becomes available, and guard viewer reload/navigation transitions to avoid race conditions and crashes.

* refactor(mobile): use remote asset ids for view intent handoff and simplify resolver

* refactor(mobile): resolve merge conflicts

* style(mobile): reformat code

* style(mobile): reformat code #2

* fix(mobile): stabilize Android view intent asset resolution and fallback viewer

* refactor(mobile): share AssetViewer pre-navigation state preparation

* fix(mobile): wait for main timeline before deferred view intent handoff

* refactor(mobile): decouple view intent asset resolver from providers

* fix(mobile): avoid double pop when canceling upload dialog

* fix(mobile): resolve view intent MIME type with fallbacks

* docs(mobile): clarify view intent fallback asset TODO

* fix(mobile): resolve merge conflicts

* cleanup

* lint

---------

Co-authored-by: Peter Ombodi <peter.ombodi@gmail.com>
Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2026-06-03 12:05:52 -05:00
Jason Rasmussen
e4352a7817 fix: error log on aborted uploads (#28806) 2026-06-03 12:47:38 -04:00
shenlong
911dde39c9 ci: verify mobile backward compatibility (#28786)
Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2026-06-03 15:21:23 +00:00
moversity
afa836181c fix(cli): prevent out-of-memory on file upload due to undici storing the request body (#28723)
fix(cli): add fetch param to prevent OOM of upload

Issue due to undici storing the entire request body in memory.
Related undici bug report: https://github.com/nodejs/undici/issues/4058

Fixes https://github.com/immich-app/immich/issues/28720

Signed-off-by: moversity <148445403+moversity@users.noreply.github.com>
2026-06-03 15:19:35 +00:00
Mert
963862b1b9 fix(mobile): proper background task cleanup (#28694)
* event-based cancellation

wire hash cancellation

await cleanup

remove forced kill

add regression tests

abort sync requests

fix cleanup ordering in teardown

exit isolate

test background sync

test sigabrt crash

cleanup

* abort local sync
2026-06-03 08:16:19 -04:00
Timon
96d521e149 feat(mobile): add three-state field serialization (#27231)
* bump to v7.22.0 and update patching

* gen client

* migrate mobile call sites
2026-06-03 08:13:17 -04:00
shenlong
1bb7517da0 chore: pump flutter to 3.44.1 (#28785)
Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2026-06-02 23:45:31 -05:00
shenlong
814c2e32e4 chore: patch minFaces and realtimeTranscoding (#28784)
Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2026-06-03 09:15:31 +05:30
immich-tofu[bot]
92841f311f Added Code of conduct 2026-06-02 21:57:50 +00:00
immich-tofu[bot]
9d2e576630 chore: modify .github/FUNDING.yml 2026-06-02 21:57:47 +00:00
immich-tofu[bot]
936418a464 chore: use immich.app email for security reports (#10594)
chore: use  immich.app email for security reports
2026-06-02 21:57:45 +00:00
Daniel Dietzler
84c75d95c7 fix: migration order (#28779) 2026-06-02 21:33:13 +00:00
shenlong
9287fa08c6 fix!: unauthorized face creation (#28561)
* fix: unauthorized face creation

* review changes

---------

Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2026-06-02 22:44:11 +05:30
renovate[bot]
408e1180ca chore(deps): update machine-learning (#28239)
* chore(deps): update machine-learning

* fix typing

* fix deprecation log

* no control socket

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: mertalev <101130780+mertalev@users.noreply.github.com>
2026-06-02 16:44:50 +00:00
renovate[bot]
07f19d2caa chore(deps): update base-image to v202606021219 (#28771) 2026-06-02 18:31:52 +02:00
Tim Jones
368cb7a4ad feat: minimum face count per user (#27452)
* add user metadata table and use to filter persons in person.getAllForUser query

* update PersonRepository.getAllForUser query

* remove minFaces from PersonSearchOptions interface

* fix person.getAllForUser query

* update types and openapi specs

* add minFaces field to user settings page

* remove old arg from tests

* add e2e test to verify minimumFace user preference

* add i18n label and description for english

* update default min faces

* fetch minFaces ML default and use as per-user default in frontend

* update e2e tests

* fix bugs in people getAllForUser query

* update person getNumberOfPeople query to reflect correct number of people according to minFaces threshold

* updated mobile openapi specs?

* use subquery in coalesce instead of join

* remove out of scope query update
2026-06-02 18:05:55 +02:00
Timon
109e0a7ad0 fix(mobile): invisible ink splashes in asset sheet (#28756) 2026-06-02 10:37:20 -05:00
Timon
59750dad7d feat: places in context search (#28768) 2026-06-02 17:19:59 +02:00
okxint
13ecfc8876 fix(web): prevent partner assets from being selected in geolocation utility (#28737)
Co-authored-by: Daniel Dietzler <mail@ddietzler.dev>
2026-06-02 15:05:15 +00:00
Min Idzelis
65d8b35f8b refactor(web): align gallery-viewer viewport naming and tunables (#28743) 2026-06-02 14:54:44 +02:00
renovate[bot]
942d3c648c chore(deps): lock file maintenance (npm) (#28729) 2026-06-02 14:51:55 +02:00
renovate[bot]
82db8be5ff chore(deps): update dependency testcontainers to v12 (#28763) 2026-06-02 12:05:42 +00:00
Min Idzelis
03554b24ad fix(web): skip thumbhash fade for offscreen thumbnails (#27335) 2026-06-02 13:42:33 +02:00
renovate[bot]
c5fb67c004 chore(deps): update dependency prettier-plugin-svelte to v4 (#28762) 2026-06-02 13:38:57 +02:00
renovate[bot]
40983b46c8 chore(deps): update dependency @vitest/coverage-v8 to v4 (#28761) 2026-06-02 13:37:34 +02:00
renovate[bot]
5dcdbf04ea chore(deps): update base-image to v202605121138 (#28760) 2026-06-02 11:47:20 +02:00
renovate[bot]
da8ed3eceb chore(deps): update docker.io/valkey/valkey:9 docker digest to 4963247 (#28622) 2026-06-02 08:09:27 +00:00
renovate[bot]
2afde23a5d chore(deps): update github-actions (#28750)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-06-02 00:39:19 -04:00
renovate[bot]
d57a152040 chore(deps): update prom/prometheus docker digest to 69f5241 (#28757)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-06-02 00:37:42 -04:00
renovate[bot]
728e92ea33 chore(deps): update dependency @immich/ui to v0.79.3 (#28758)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-06-02 00:37:10 -04:00
Mert
138e2d9158 feat(web): hls player (#28312)
* update e2e

* hls player

* fix transcoding restart on explicit quality selection

* move level filtering to manager

* move init to manager declaration

* refactor commit on release

* these lints...

* fix seek sometimes being ignored

* fix panic downswitch
2026-06-01 15:49:57 -04:00
Mert
7eabac6702 feat(server): hls with real-time transcoding (#28230)
* hls implementation

* fix stale state after ffmpeg exit
2026-06-01 18:52:29 +00:00
renovate[bot]
cf4789e008 chore(deps): update github-actions (major) (#28752) 2026-06-01 18:35:36 +00:00
renovate[bot]
412884fce3 chore(deps): update ghcr.io/jdx/mise docker tag to v2026.5.18 (#28749) 2026-06-01 19:47:53 +02:00
Jason Rasmussen
16aee2b869 fix: album name (#28751) 2026-06-01 19:45:24 +02:00
Daniel Dietzler
3f7af51531 fix: version check (#28746) 2026-06-01 13:41:08 -04:00
Brandon Wees
4eb100327e fix: disallow cross origin/non http protocols for continueUrl on login (#28706)
* fix: disallow cross origin/non http protocols for continueUrl on login

* chore: use Route helper

* fix: also use Route.continue in pin code prompt

* fix: typecheck
2026-06-01 13:38:26 -04:00
bo0tzz
69b1946484 feat: handle prereleases in publish workflows (#28701) 2026-06-01 17:11:45 +02:00
bo0tzz
61cd69a286 fix: strip rc suffix from iOS marketing version (#28741) 2026-06-01 09:56:43 -05:00
Daniel Dietzler
c8a1d0e400 feat: release candidate support (#28665)
Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2026-06-01 16:10:07 +02:00
Paul Makles
d120444a87 fix(devcontainer): update build cache volume (#28736) 2026-06-01 12:41:53 +00:00
1342 changed files with 120364 additions and 34936 deletions

View File

@@ -83,7 +83,7 @@
}
},
"features": {
"ghcr.io/devcontainers/features/docker-in-docker:2": {
"ghcr.io/devcontainers/features/docker-in-docker:4": {
// https://github.com/devcontainers/features/issues/1466
"moby": false
}

View File

@@ -15,7 +15,7 @@ services:
volumes:
- ${UPLOAD_LOCATION:-upload-devcontainer-volume}${UPLOAD_LOCATION:+/photos}:/data
- /etc/localtime:/etc/localtime:ro
- pnpm_store_server:/buildcache/pnpm-store
- build_cache:/buildcache
- ../packages/plugin-core:/build/plugins/immich-plugin-core
immich-web:
env_file: !reset []

1
.github/FUNDING.yml vendored
View File

@@ -1 +0,0 @@
custom: ['https://buy.immich.app', 'https://immich.store']

View File

@@ -15,7 +15,7 @@ jobs:
outputs:
uses_template: ${{ steps.check.outputs.uses_template }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
sparse-checkout: .github/pull_request_template.md
sparse-checkout-cone-mode: false

View File

@@ -51,10 +51,11 @@ jobs:
should_run: ${{ steps.check.outputs.should_run }}
steps:
- id: token
uses: immich-app/devtools/actions/create-workflow-token@caa599d954228439ea3e8ce1c3328f41ab120ee6 # create-workflow-token-action-v2.0.0
uses: immich-app/devtools/actions/create-workflow-token@1af396ae134e4bc3b63d947e672bc68bf4ff9dc5 # create-workflow-token-action-v3.0.0
with:
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
permission-contents: read
- name: Check what should run
id: check
@@ -79,21 +80,24 @@ jobs:
steps:
- id: token
uses: immich-app/devtools/actions/create-workflow-token@caa599d954228439ea3e8ce1c3328f41ab120ee6 # create-workflow-token-action-v2.0.0
uses: immich-app/devtools/actions/create-workflow-token@1af396ae134e4bc3b63d947e672bc68bf4ff9dc5 # create-workflow-token-action-v3.0.0
with:
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
permission-contents: read
permission-pull-requests: write
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
ref: ${{ inputs.ref }}
persist-credentials: false
token: ${{ steps.token.outputs.token }}
- name: Setup Mise
uses: immich-app/devtools/actions/use-mise@7b8610a904d57da241e4ddba17fa62b62b15aed4 # use-mise-action-v2.0.2
uses: immich-app/devtools/actions/use-mise@3bca63ca3c15020293b36b51737a3ee2c773340b # use-mise-action-v3.1.0
with:
github_token: ${{ steps.token.outputs.token }}
working_directory: ./mobile
- name: Create the Keystore
if: ${{ !github.event.pull_request.head.repo.fork }}
@@ -102,14 +106,14 @@ jobs:
working-directory: ./mobile
run: printf "%s" $KEY_JKS | base64 -d > android/key.jks
- uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
- uses: actions/setup-java@0f481fcb613427c0f801b606911222b5b6f3083a # v5.5.0
with:
distribution: 'zulu'
java-version: '17'
- name: Restore Gradle Cache
id: cache-gradle-restore
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: |
~/.gradle/caches
@@ -180,7 +184,7 @@ jobs:
- name: Save Gradle Cache
id: cache-gradle-save
uses: actions/cache/save@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
if: github.ref == 'refs/heads/main'
with:
path: |
@@ -197,28 +201,30 @@ jobs:
contents: read
# Run on main branch or workflow_dispatch, or on PRs/other branches (build only, no upload)
if: ${{ !github.event.pull_request.head.repo.fork && fromJSON(needs.pre-job.outputs.should_run).mobile == true }}
runs-on: macos-15
runs-on: macos-26
steps:
- id: token
uses: immich-app/devtools/actions/create-workflow-token@caa599d954228439ea3e8ce1c3328f41ab120ee6 # create-workflow-token-action-v2.0.0
uses: immich-app/devtools/actions/create-workflow-token@1af396ae134e4bc3b63d947e672bc68bf4ff9dc5 # create-workflow-token-action-v3.0.0
with:
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
permission-contents: read
- name: Select Xcode 26
run: sudo xcode-select -s /Applications/Xcode_26.2.app/Contents/Developer
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
ref: ${{ inputs.ref || github.sha }}
persist-credentials: false
- name: Setup Mise
uses: immich-app/devtools/actions/use-mise@7b8610a904d57da241e4ddba17fa62b62b15aed4 # use-mise-action-v2.0.2
uses: immich-app/devtools/actions/use-mise@3bca63ca3c15020293b36b51737a3ee2c773340b # use-mise-action-v3.1.0
with:
github_token: ${{ steps.token.outputs.token }}
working_directory: ./mobile
- name: Install Flutter dependencies
working-directory: ./mobile
@@ -235,7 +241,7 @@ jobs:
run: flutter build ios --config-only --no-codesign
- name: Setup Ruby
uses: ruby/setup-ruby@afeafc3d1ab54a631816aba4c914a0081c12ff2f # v1.310.0
uses: ruby/setup-ruby@d45b1a4e94b71acab930e56e79c6aa188764e7f9 # v1.316.0
with:
ruby-version: '3.3'
bundler-cache: true

View File

@@ -19,13 +19,15 @@ jobs:
actions: write
steps:
- id: token
uses: immich-app/devtools/actions/create-workflow-token@caa599d954228439ea3e8ce1c3328f41ab120ee6 # create-workflow-token-action-v2.0.0
uses: immich-app/devtools/actions/create-workflow-token@1af396ae134e4bc3b63d947e672bc68bf4ff9dc5 # create-workflow-token-action-v3.0.0
with:
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
permission-contents: read
permission-actions: write
- name: Check out code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
token: ${{ steps.token.outputs.token }}

View File

@@ -4,6 +4,7 @@ on:
pull_request:
paths:
- 'open-api/**'
- 'mobile/lib/utils/openapi_patching.dart'
- '.github/workflows/check-openapi.yml'
concurrency:
@@ -19,13 +20,47 @@ jobs:
contents: read
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
- name: Check for breaking API changes
uses: oasdiff/oasdiff-action/breaking@6147a58e5d1249a12f42fc864ab791d571a30015 # v0.0.47
uses: oasdiff/oasdiff-action/breaking@291b6594ce617f22f49af5898ee038ed0b46e8fe # v0.1.5
with:
base: https://raw.githubusercontent.com/${{ github.repository }}/main/open-api/immich-openapi-specs.json
revision: open-api/immich-openapi-specs.json
fail-on: ERR
review: false
check-mobile-patches:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
- name: Setup Mise
uses: immich-app/devtools/actions/use-mise@3bca63ca3c15020293b36b51737a3ee2c773340b # use-mise-action-v3.1.0
with:
github_token: ${{ github.token }}
working_directory: ./mobile
- name: Get packages
working-directory: ./mobile
run: flutter pub get
- name: Fetch base spec from main
run: |
curl -fsSL \
"https://raw.githubusercontent.com/${{ github.repository }}/main/open-api/immich-openapi-specs.json" \
-o /tmp/base-spec.json
- name: Check newly-required fields have a backward-compat patch
working-directory: ./mobile
env:
OPENAPI_BASE_SPEC: /tmp/base-spec.json
OPENAPI_REVISION_SPEC: ../open-api/immich-openapi-specs.json
run: flutter test test/openapi_patches_coverage.dart

View File

@@ -31,25 +31,28 @@ jobs:
working-directory: ./packages/cli
steps:
- id: token
uses: immich-app/devtools/actions/create-workflow-token@caa599d954228439ea3e8ce1c3328f41ab120ee6 # create-workflow-token-action-v2.0.0
uses: immich-app/devtools/actions/create-workflow-token@1af396ae134e4bc3b63d947e672bc68bf4ff9dc5 # create-workflow-token-action-v3.0.0
with:
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
permission-contents: read
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
token: ${{ steps.token.outputs.token }}
- name: Setup Mise
uses: immich-app/devtools/actions/use-mise@7b8610a904d57da241e4ddba17fa62b62b15aed4 # use-mise-action-v2.0.2
uses: immich-app/devtools/actions/use-mise@3bca63ca3c15020293b36b51737a3ee2c773340b # use-mise-action-v3.1.0
with:
github_token: ${{ steps.token.outputs.token }}
- name: Publish
if: ${{ github.event_name == 'release' }}
run: mise run ci-publish
env:
NPM_TAG: ${{ github.event.release.prerelease && 'rc' || 'latest' }}
run: mise run ci-publish -- --tag "$NPM_TAG"
docker:
name: Docker
@@ -61,25 +64,26 @@ jobs:
steps:
- id: token
uses: immich-app/devtools/actions/create-workflow-token@caa599d954228439ea3e8ce1c3328f41ab120ee6 # create-workflow-token-action-v2.0.0
uses: immich-app/devtools/actions/create-workflow-token@1af396ae134e4bc3b63d947e672bc68bf4ff9dc5 # create-workflow-token-action-v3.0.0
with:
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
permission-contents: read
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
token: ${{ steps.token.outputs.token }}
- name: Set up QEMU
uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4.0.0
uses: docker/setup-qemu-action@96fe6ef7f33517b61c61be40b68a1882f3264fb8 # v4.2.0
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0
uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0
- name: Login to GitHub Container Registry
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4.4.0
if: ${{ !github.event.pull_request.head.repo.fork }}
with:
registry: ghcr.io
@@ -94,7 +98,7 @@ jobs:
- name: Generate docker image tags
id: metadata
uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6.0.0
uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6.2.0
with:
flavor: |
latest=false
@@ -102,10 +106,10 @@ jobs:
name=ghcr.io/${{ github.repository_owner }}/immich-cli
tags: |
type=raw,value=${{ steps.package-version.outputs.version }},enable=${{ github.event_name == 'release' }}
type=raw,value=latest,enable=${{ github.event_name == 'release' }}
type=raw,value=latest,enable=${{ github.event_name == 'release' && !github.event.release.prerelease }}
- name: Build and push image
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
with:
file: packages/cli/Dockerfile
platforms: linux/amd64,linux/arm64

View File

@@ -14,7 +14,11 @@ jobs:
should_run: ${{ steps.should_run.outputs.run }}
steps:
- id: should_run
run: echo "run=${{ github.event_name == 'issues' || github.event.discussion.category.name == 'Feature Request' }}" >> $GITHUB_OUTPUT
run: |
echo "run=${{
(github.event_name == 'issues' || github.event.discussion.category.name == 'Feature Request')
&& !contains(fromJSON('["OWNER", "MEMBER", "COLLABORATOR"]'), github.event.issue.author_association || github.event.discussion.author_association)
}}" >> "$GITHUB_OUTPUT"
get_body:
runs-on: ubuntu-latest
@@ -35,7 +39,7 @@ jobs:
needs: [get_body, should_run]
if: ${{ needs.should_run.outputs.should_run == 'true' }}
container:
image: ghcr.io/immich-app/mdq:main@sha256:0a8b8867773a0f8368061f47578603f438349f8f1f28b0e16105f481e5c794e0
image: ghcr.io/immich-app/mdq:main@sha256:e3fe456f346b02c2f479462100763aee3ba8245768dfeac3c4262acb068e5c62
outputs:
checked: ${{ steps.get_checkbox.outputs.checked }}
steps:

View File

@@ -44,20 +44,21 @@ jobs:
steps:
- id: token
uses: immich-app/devtools/actions/create-workflow-token@caa599d954228439ea3e8ce1c3328f41ab120ee6 # create-workflow-token-action-v2.0.0
uses: immich-app/devtools/actions/create-workflow-token@1af396ae134e4bc3b63d947e672bc68bf4ff9dc5 # create-workflow-token-action-v3.0.0
with:
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
permission-contents: read
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
token: ${{ steps.token.outputs.token }}
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@9e0d7b8d25671d64c341c19c0152d693099fb5ba # v4.35.5
uses: github/codeql-action/init@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # v4.37.0
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
@@ -70,7 +71,7 @@ jobs:
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@9e0d7b8d25671d64c341c19c0152d693099fb5ba # v4.35.5
uses: github/codeql-action/autobuild@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # v4.37.0
# Command-line programs to run using the OS shell.
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
@@ -83,6 +84,6 @@ jobs:
# ./location_of_script_within_repo/buildscript.sh
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@9e0d7b8d25671d64c341c19c0152d693099fb5ba # v4.35.5
uses: github/codeql-action/analyze@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # v4.37.0
with:
category: '/language:${{matrix.language}}'

View File

@@ -23,10 +23,11 @@ jobs:
should_run: ${{ steps.check.outputs.should_run }}
steps:
- id: token
uses: immich-app/devtools/actions/create-workflow-token@caa599d954228439ea3e8ce1c3328f41ab120ee6 # create-workflow-token-action-v2.0.0
uses: immich-app/devtools/actions/create-workflow-token@1af396ae134e4bc3b63d947e672bc68bf4ff9dc5 # create-workflow-token-action-v3.0.0
with:
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
permission-contents: read
- name: Check what should run
id: check
@@ -60,7 +61,7 @@ jobs:
suffix: ['', '-cuda', '-rocm', '-openvino', '-armnn', '-rknn']
steps:
- name: Login to GitHub Container Registry
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4.4.0
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
@@ -90,7 +91,7 @@ jobs:
suffix: ['']
steps:
- name: Login to GitHub Container Registry
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4.4.0
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
@@ -132,7 +133,7 @@ jobs:
suffixes: '-rocm'
platforms: linux/amd64
runner-mapping: '{"linux/amd64": "pokedex-large"}'
uses: immich-app/devtools/.github/workflows/multi-runner-build.yml@5813c7c4f7016c748ae7ac5d5f684846649d4d20 # multi-runner-build-workflow-v2.4.0
uses: immich-app/devtools/.github/workflows/multi-runner-build.yml@50dc3a14f0606ecd8fc28d78d3a3c655115ab695 # multi-runner-build-workflow-v3.1.0
permissions:
contents: read
actions: read
@@ -147,7 +148,7 @@ jobs:
platforms: ${{ matrix.platforms }}
runner-mapping: ${{ matrix.runner-mapping }}
suffixes: ${{ matrix.suffixes }}
dockerhub-push: ${{ github.event_name == 'release' }}
dockerhub-push: ${{ github.event_name == 'release' && !github.event.release.prerelease }}
build-args: |
DEVICE=${{ matrix.device }}
@@ -155,7 +156,7 @@ jobs:
name: Build and Push Server
needs: pre-job
if: ${{ fromJSON(needs.pre-job.outputs.should_run).server == true }}
uses: immich-app/devtools/.github/workflows/multi-runner-build.yml@5813c7c4f7016c748ae7ac5d5f684846649d4d20 # multi-runner-build-workflow-v2.4.0
uses: immich-app/devtools/.github/workflows/multi-runner-build.yml@50dc3a14f0606ecd8fc28d78d3a3c655115ab695 # multi-runner-build-workflow-v3.1.0
permissions:
contents: read
actions: read
@@ -167,7 +168,7 @@ jobs:
image: immich-server
context: .
dockerfile: server/Dockerfile
dockerhub-push: ${{ github.event_name == 'release' }}
dockerhub-push: ${{ github.event_name == 'release' && !github.event.release.prerelease }}
build-args: |
DEVICE=cpu

View File

@@ -21,10 +21,11 @@ jobs:
should_run: ${{ steps.check.outputs.should_run }}
steps:
- id: token
uses: immich-app/devtools/actions/create-workflow-token@caa599d954228439ea3e8ce1c3328f41ab120ee6 # create-workflow-token-action-v2.0.0
uses: immich-app/devtools/actions/create-workflow-token@1af396ae134e4bc3b63d947e672bc68bf4ff9dc5 # create-workflow-token-action-v3.0.0
with:
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
permission-contents: read
- name: Check what should run
id: check
@@ -54,19 +55,20 @@ jobs:
steps:
- id: token
uses: immich-app/devtools/actions/create-workflow-token@caa599d954228439ea3e8ce1c3328f41ab120ee6 # create-workflow-token-action-v2.0.0
uses: immich-app/devtools/actions/create-workflow-token@1af396ae134e4bc3b63d947e672bc68bf4ff9dc5 # create-workflow-token-action-v3.0.0
with:
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
permission-contents: read
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
token: ${{ steps.token.outputs.token }}
- name: Setup Mise
uses: immich-app/devtools/actions/use-mise@7b8610a904d57da241e4ddba17fa62b62b15aed4 # use-mise-action-v2.0.2
uses: immich-app/devtools/actions/use-mise@3bca63ca3c15020293b36b51737a3ee2c773340b # use-mise-action-v3.1.0
with:
github_token: ${{ steps.token.outputs.token }}

View File

@@ -20,10 +20,12 @@ jobs:
artifact: ${{ steps.get-artifact.outputs.result }}
steps:
- id: token
uses: immich-app/devtools/actions/create-workflow-token@caa599d954228439ea3e8ce1c3328f41ab120ee6 # create-workflow-token-action-v2.0.0
uses: immich-app/devtools/actions/create-workflow-token@1af396ae134e4bc3b63d947e672bc68bf4ff9dc5 # create-workflow-token-action-v3.0.0
with:
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
permission-actions: read
permission-pull-requests: read
- if: ${{ github.event.workflow_run.conclusion != 'success' }}
run: echo 'The triggering workflow did not succeed' && exit 1
@@ -98,9 +100,16 @@ jobs:
shouldDeploy: true
};
} else if (eventType == "release") {
const tag = context.payload.workflow_run.head_branch;
const { data: release } = await github.rest.repos.getReleaseByTag({
owner: context.repo.owner,
repo: context.repo.repo,
tag,
});
parameters = {
event: "release",
name: context.payload.workflow_run.head_branch,
name: tag,
prerelease: release.prerelease,
shouldDeploy: !isFork
};
}
@@ -119,19 +128,22 @@ jobs:
if: ${{ fromJson(needs.checks.outputs.artifact).found && fromJson(needs.checks.outputs.parameters).shouldDeploy }}
steps:
- id: token
uses: immich-app/devtools/actions/create-workflow-token@caa599d954228439ea3e8ce1c3328f41ab120ee6 # create-workflow-token-action-v2.0.0
uses: immich-app/devtools/actions/create-workflow-token@1af396ae134e4bc3b63d947e672bc68bf4ff9dc5 # create-workflow-token-action-v3.0.0
with:
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
permission-contents: read
permission-actions: read
permission-pull-requests: write
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
token: ${{ steps.token.outputs.token }}
- name: Setup Mise
uses: immich-app/devtools/actions/use-mise@7b8610a904d57da241e4ddba17fa62b62b15aed4 # use-mise-action-v2.0.2
uses: immich-app/devtools/actions/use-mise@3bca63ca3c15020293b36b51737a3ee2c773340b # use-mise-action-v3.1.0
with:
github_token: ${{ steps.token.outputs.token }}
@@ -146,6 +158,7 @@ jobs:
const parameters = JSON.parse(process.env.PARAM_JSON);
core.setOutput("event", parameters.event);
core.setOutput("name", parameters.name);
core.setOutput("prerelease", parameters.prerelease);
core.setOutput("shouldDeploy", parameters.shouldDeploy);
- name: Download artifact
@@ -203,7 +216,7 @@ jobs:
run: mise run //docs:deploy
- name: Deploy Docs Release Domain
if: ${{ steps.parameters.outputs.event == 'release' }}
if: ${{ steps.parameters.outputs.event == 'release' && steps.parameters.outputs.prerelease != 'true' }}
env:
TF_VAR_prefix_name: ${{ steps.parameters.outputs.name}}
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}

View File

@@ -17,19 +17,21 @@ jobs:
pull-requests: write
steps:
- id: token
uses: immich-app/devtools/actions/create-workflow-token@caa599d954228439ea3e8ce1c3328f41ab120ee6 # create-workflow-token-action-v2.0.0
uses: immich-app/devtools/actions/create-workflow-token@1af396ae134e4bc3b63d947e672bc68bf4ff9dc5 # create-workflow-token-action-v3.0.0
with:
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
permission-contents: read
permission-pull-requests: write
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
token: ${{ steps.token.outputs.token }}
- name: Setup Mise
uses: immich-app/devtools/actions/use-mise@7b8610a904d57da241e4ddba17fa62b62b15aed4 # use-mise-action-v2.0.2
uses: immich-app/devtools/actions/use-mise@3bca63ca3c15020293b36b51737a3ee2c773340b # use-mise-action-v3.1.0
with:
github_token: ${{ steps.token.outputs.token }}

56
.github/workflows/fdroid.yml vendored Normal file
View File

@@ -0,0 +1,56 @@
name: Update F-Droid Repo
on:
release:
types: [published]
permissions: {}
jobs:
update-index:
runs-on: ubuntu-latest
if: ${{ !github.event.release.prerelease }}
permissions:
contents: read
steps:
- name: Checkout pubspec for versionCode
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
ref: ${{ github.event.release.tag_name }}
persist-credentials: false
sparse-checkout: mobile/pubspec.yaml
sparse-checkout-cone-mode: false
- name: Update F-Droid repo
env:
GITLAB_TOKEN: ${{ secrets.FDROID_REPO_TOKEN }}
RELEASE: ${{ toJSON(github.event.release) }}
TAG: ${{ github.event.release.tag_name }}
run: |
VERSION_CODE=$(yq '.version' mobile/pubspec.yaml | cut -d+ -f2)
export VERSION_CODE
NEW_ENTRY=$(yq -P '
[.assets[] | select(.name == "app-release.apk")] as $matches |
with(select($matches | length == 0); error("app-release.apk not found in release assets")) |
{
"url": $matches[0].browser_download_url,
"sha256sum": ($matches[0].digest | sub("^sha256:", "")),
"date": (.published_at | split("T") | .[0]),
"version-code": env(VERSION_CODE)
} | .date tag= "!!timestamp"
' <<< "$RELEASE")
export NEW_ENTRY
git clone --depth 1 "https://oauth2:${GITLAB_TOKEN}@gitlab.futo.org/fdroid/repo-v2.git" fdroid-repo
cd fdroid-repo
yq -i '
with(select([.apks[] | select(."version-code" == env(VERSION_CODE))] | length > 0); error("version-code already in index")) |
.apks = [env(NEW_ENTRY)] + .apks
' apps/Immich/index.yml
git config user.name "Immich Release Bot"
git config user.email "bot@immich.app"
git commit -am "Immich: add version-code ${VERSION_CODE} (${TAG})"
git push

View File

@@ -15,20 +15,22 @@ jobs:
pull-requests: write
steps:
- id: token
uses: immich-app/devtools/actions/create-workflow-token@caa599d954228439ea3e8ce1c3328f41ab120ee6 # create-workflow-token-action-v2.0.0
uses: immich-app/devtools/actions/create-workflow-token@1af396ae134e4bc3b63d947e672bc68bf4ff9dc5 # create-workflow-token-action-v3.0.0
with:
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
permission-contents: write
permission-pull-requests: write
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
ref: ${{ github.event.pull_request.head.ref }}
persist-credentials: true
token: ${{ steps.token.outputs.token }}
- name: Setup Mise
uses: immich-app/devtools/actions/use-mise@7b8610a904d57da241e4ddba17fa62b62b15aed4 # use-mise-action-v2.0.2
uses: immich-app/devtools/actions/use-mise@3bca63ca3c15020293b36b51737a3ee2c773340b # use-mise-action-v3.1.0
with:
github_token: ${{ steps.token.outputs.token }}
@@ -45,7 +47,7 @@ jobs:
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
if: always()
with:
github-token: ${{ steps.generate-token.outputs.token }}
github-token: ${{ steps.token.outputs.token }}
script: |
github.rest.issues.removeLabel({
issue_number: context.payload.pull_request.number,

View File

@@ -35,6 +35,8 @@ jobs:
with:
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
permission-contents: write
permission-pull-requests: write
- name: Find translation PR
id: find_pr

View File

@@ -14,13 +14,14 @@ jobs:
pull-requests: write
steps:
- id: token
uses: immich-app/devtools/actions/create-workflow-token@caa599d954228439ea3e8ce1c3328f41ab120ee6 # create-workflow-token-action-v2.0.0
uses: immich-app/devtools/actions/create-workflow-token@1af396ae134e4bc3b63d947e672bc68bf4ff9dc5 # create-workflow-token-action-v3.0.0
with:
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
permission-pull-requests: write
- name: Require PR to have a changelog label
uses: mheap/github-action-required-labels@0ac283b4e65c1fb28ce6079dea5546ceca98ccbe # v5.5.2
uses: mheap/github-action-required-labels@23e10fde7e062233401931a0eece796cd9bf3177 # v5.6.0
with:
token: ${{ steps.token.outputs.token }}
mode: exactly

View File

@@ -12,11 +12,13 @@ jobs:
runs-on: ubuntu-latest
steps:
- id: token
uses: immich-app/devtools/actions/create-workflow-token@caa599d954228439ea3e8ce1c3328f41ab120ee6 # create-workflow-token-action-v2.0.0
uses: immich-app/devtools/actions/create-workflow-token@1af396ae134e4bc3b63d947e672bc68bf4ff9dc5 # create-workflow-token-action-v3.0.0
with:
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
permission-contents: read
permission-pull-requests: write
- uses: actions/labeler@f27b608878404679385c85cfa523b85ccb86e213 # v6.1.0
- uses: actions/labeler@b8dd2d9be0f68b860e7dae5dae7d772984eacd6d # v6.2.0
with:
repo-token: ${{ steps.token.outputs.token }}

View File

@@ -3,16 +3,20 @@ name: Prepare new release
on:
workflow_dispatch:
inputs:
serverBump:
description: 'Bump server version'
releaseType:
description: 'Release type'
required: true
default: 'false'
type: choice
options:
- 'false'
- major
- minor
- patch
- premajor
- preminor
- prepatch
- prerelease
- release
mobileBump:
description: 'Bump mobile build number'
required: false
@@ -46,38 +50,46 @@ jobs:
outputs:
ref: ${{ steps.push-tag.outputs.commit_long_sha }}
version: ${{ steps.output.outputs.version }}
rc: ${{ steps.output.outputs.rc }}
permissions: {} # No job-level permissions are needed because it uses the app-token
steps:
- id: token
uses: immich-app/devtools/actions/create-workflow-token@caa599d954228439ea3e8ce1c3328f41ab120ee6 # create-workflow-token-action-v2.0.0
uses: immich-app/devtools/actions/create-workflow-token@1af396ae134e4bc3b63d947e672bc68bf4ff9dc5 # create-workflow-token-action-v3.0.0
with:
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
permission-contents: write
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
token: ${{ steps.token.outputs.token }}
persist-credentials: true
ref: main
- name: Setup Mise
uses: immich-app/devtools/actions/use-mise@7b8610a904d57da241e4ddba17fa62b62b15aed4 # use-mise-action-v2.0.2
uses: immich-app/devtools/actions/use-mise@3bca63ca3c15020293b36b51737a3ee2c773340b # use-mise-action-v3.1.0
with:
github_token: ${{ steps.token.outputs.token }}
# TODO move to mise
- name: Install uv
uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
uses: astral-sh/setup-uv@11f9893b081a58869d3b5fccaea48c9e9e46f990 # v8.3.2
- name: Bump version
env:
SERVER_BUMP: ${{ inputs.serverBump }}
RELEASE_TYPE: ${{ inputs.releaseType }}
MOBILE_BUMP: ${{ inputs.mobileBump }}
run: misc/release/pump-version.sh -s "${SERVER_BUMP}" -m "${MOBILE_BUMP}"
run: mise //:release --type "${RELEASE_TYPE}" --mobile "${MOBILE_BUMP}"
- id: output
run: echo "version=$IMMICH_VERSION" >> $GITHUB_OUTPUT
run: |
echo "version=$IMMICH_VERSION" >> $GITHUB_OUTPUT
if [[ "$IMMICH_VERSION" =~ -rc\.[0-9]+$ ]]; then
echo "rc=true" >> $GITHUB_OUTPUT
else
echo "rc=false" >> $GITHUB_OUTPUT
fi
- name: Commit and tag
id: push-tag
@@ -93,6 +105,7 @@ jobs:
needs: bump_version
permissions:
contents: read
pull-requests: write
secrets:
KEY_JKS: ${{ secrets.KEY_JKS }}
ALIAS: ${{ secrets.ALIAS }}
@@ -123,9 +136,11 @@ jobs:
with:
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
permission-contents: write
permission-actions: read
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
token: ${{ steps.generate-token.outputs.token }}
persist-credentials: false
@@ -137,9 +152,10 @@ jobs:
github-token: ${{ steps.generate-token.outputs.token }}
- name: Create draft release
uses: softprops/action-gh-release@3bb12739c298aeb8a4eeaf626c5b8d85266b0e65 # v2.6.2
uses: softprops/action-gh-release@718ea10b132b3b2eba29c1007bb80653f286566b # v3.0.1
with:
draft: true
prerelease: ${{ needs.bump_version.outputs.rc }}
tag_name: ${{ needs.bump_version.outputs.version }}
token: ${{ steps.generate-token.outputs.token }}
generate_release_notes: true

View File

@@ -14,10 +14,11 @@ jobs:
pull-requests: write
steps:
- id: token
uses: immich-app/devtools/actions/create-workflow-token@caa599d954228439ea3e8ce1c3328f41ab120ee6 # create-workflow-token-action-v2.0.0
uses: immich-app/devtools/actions/create-workflow-token@1af396ae134e4bc3b63d947e672bc68bf4ff9dc5 # create-workflow-token-action-v3.0.0
with:
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
permission-pull-requests: write
- uses: immich-app/devtools/actions/sticky-comment@0135acd12ad9f3369b94a2aa3c0ae8c835a4e926 # sticky-comment-action-v1.0.0
with:
@@ -32,10 +33,11 @@ jobs:
pull-requests: write
steps:
- id: token
uses: immich-app/devtools/actions/create-workflow-token@caa599d954228439ea3e8ce1c3328f41ab120ee6 # create-workflow-token-action-v2.0.0
uses: immich-app/devtools/actions/create-workflow-token@1af396ae134e4bc3b63d947e672bc68bf4ff9dc5 # create-workflow-token-action-v3.0.0
with:
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
permission-pull-requests: write
- uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:

View File

@@ -16,19 +16,20 @@ jobs:
packages: write
steps:
- id: token
uses: immich-app/devtools/actions/create-workflow-token@caa599d954228439ea3e8ce1c3328f41ab120ee6 # create-workflow-token-action-v2.0.0
uses: immich-app/devtools/actions/create-workflow-token@1af396ae134e4bc3b63d947e672bc68bf4ff9dc5 # create-workflow-token-action-v3.0.0
with:
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
permission-contents: read
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
token: ${{ steps.token.outputs.token }}
- name: Setup Mise
uses: immich-app/devtools/actions/use-mise@7b8610a904d57da241e4ddba17fa62b62b15aed4 # use-mise-action-v2.0.2
uses: immich-app/devtools/actions/use-mise@3bca63ca3c15020293b36b51737a3ee2c773340b # use-mise-action-v3.1.0
with:
github_token: ${{ steps.token.outputs.token }}
@@ -39,4 +40,6 @@ jobs:
run: pnpm --filter @immich/sdk build
- name: Publish
run: pnpm --filter @immich/sdk publish --provenance --no-git-checks
env:
NPM_TAG: ${{ github.event.release.prerelease && 'rc' || 'latest' }}
run: pnpm --filter @immich/sdk publish --provenance --no-git-checks --tag "$NPM_TAG"

View File

@@ -20,10 +20,11 @@ jobs:
should_run: ${{ steps.check.outputs.should_run }}
steps:
- id: token
uses: immich-app/devtools/actions/create-workflow-token@caa599d954228439ea3e8ce1c3328f41ab120ee6 # create-workflow-token-action-v2.0.0
uses: immich-app/devtools/actions/create-workflow-token@1af396ae134e4bc3b63d947e672bc68bf4ff9dc5 # create-workflow-token-action-v3.0.0
with:
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
permission-contents: read
- name: Check what should run
id: check
@@ -33,6 +34,7 @@ jobs:
filters: |
mobile:
- 'mobile/**'
- 'i18n/en.json'
force-filters: |
- '.github/workflows/static_analysis.yml'
force-events: 'workflow_dispatch,release'
@@ -49,21 +51,23 @@ jobs:
working-directory: ./mobile
steps:
- id: token
uses: immich-app/devtools/actions/create-workflow-token@caa599d954228439ea3e8ce1c3328f41ab120ee6 # create-workflow-token-action-v2.0.0
uses: immich-app/devtools/actions/create-workflow-token@1af396ae134e4bc3b63d947e672bc68bf4ff9dc5 # create-workflow-token-action-v3.0.0
with:
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
permission-contents: read
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
token: ${{ steps.token.outputs.token }}
- name: Setup Mise
uses: immich-app/devtools/actions/use-mise@7b8610a904d57da241e4ddba17fa62b62b15aed4 # use-mise-action-v2.0.2
uses: immich-app/devtools/actions/use-mise@3bca63ca3c15020293b36b51737a3ee2c773340b # use-mise-action-v3.1.0
with:
github_token: ${{ steps.token.outputs.token }}
working_directory: ./mobile
- name: Install dependencies
run: flutter pub get
@@ -89,6 +93,8 @@ jobs:
mobile/**/*.g.dart
mobile/**/*.gr.dart
mobile/**/*.drift.dart
mobile/**/*.g.swift
mobile/**/*.g.kt
- name: Verify files have not changed
if: steps.verify-changed-files.outputs.files_changed == 'true'

View File

@@ -17,10 +17,11 @@ jobs:
should_run: ${{ steps.check.outputs.should_run }}
steps:
- id: token
uses: immich-app/devtools/actions/create-workflow-token@caa599d954228439ea3e8ce1c3328f41ab120ee6 # create-workflow-token-action-v2.0.0
uses: immich-app/devtools/actions/create-workflow-token@1af396ae134e4bc3b63d947e672bc68bf4ff9dc5 # create-workflow-token-action-v3.0.0
with:
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
permission-contents: read
- name: Check what should run
id: check
@@ -28,6 +29,10 @@ jobs:
with:
github-token: ${{ steps.token.outputs.token }}
filters: |
scripts:
- 'packages/scripts/**'
- 'pnpm-lock.yaml'
- 'mise.toml'
i18n:
- 'i18n/**'
- 'mise.toml'
@@ -41,6 +46,8 @@ jobs:
- 'server/**'
- 'pnpm-lock.yaml'
- 'mise.toml'
- 'packages/plugin-core/**'
- 'packages/plugin-sdk/**'
cli:
- 'packages/cli/**'
- 'packages/sdk/**'
@@ -62,6 +69,35 @@ jobs:
- '.github/workflows/test.yml'
force-events: 'workflow_dispatch'
script-unit-tests:
name: Scripts unit tests
needs: pre-job
if: ${{ fromJSON(needs.pre-job.outputs.should_run).scripts == true }}
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- id: token
uses: immich-app/devtools/actions/create-workflow-token@1af396ae134e4bc3b63d947e672bc68bf4ff9dc5 # create-workflow-token-action-v3.0.0
with:
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
permission-contents: read
- name: Checkout code
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
token: ${{ steps.token.outputs.token }}
- name: Setup Mise
uses: immich-app/devtools/actions/use-mise@3bca63ca3c15020293b36b51737a3ee2c773340b # use-mise-action-v3.1.0
with:
github_token: ${{ steps.token.outputs.token }}
- name: Run unit tests
run: pnpm --filter @immich/scripts test
server-unit-tests:
name: Test & Lint Server
needs: pre-job
@@ -71,19 +107,20 @@ jobs:
contents: read
steps:
- id: token
uses: immich-app/devtools/actions/create-workflow-token@caa599d954228439ea3e8ce1c3328f41ab120ee6 # create-workflow-token-action-v2.0.0
uses: immich-app/devtools/actions/create-workflow-token@1af396ae134e4bc3b63d947e672bc68bf4ff9dc5 # create-workflow-token-action-v3.0.0
with:
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
permission-contents: read
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
token: ${{ steps.token.outputs.token }}
- name: Setup Mise
uses: immich-app/devtools/actions/use-mise@7b8610a904d57da241e4ddba17fa62b62b15aed4 # use-mise-action-v2.0.2
uses: immich-app/devtools/actions/use-mise@3bca63ca3c15020293b36b51737a3ee2c773340b # use-mise-action-v3.1.0
with:
github_token: ${{ steps.token.outputs.token }}
@@ -102,19 +139,20 @@ jobs:
working-directory: ./packages/cli
steps:
- id: token
uses: immich-app/devtools/actions/create-workflow-token@caa599d954228439ea3e8ce1c3328f41ab120ee6 # create-workflow-token-action-v2.0.0
uses: immich-app/devtools/actions/create-workflow-token@1af396ae134e4bc3b63d947e672bc68bf4ff9dc5 # create-workflow-token-action-v3.0.0
with:
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
permission-contents: read
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
token: ${{ steps.token.outputs.token }}
- name: Setup Mise
uses: immich-app/devtools/actions/use-mise@7b8610a904d57da241e4ddba17fa62b62b15aed4 # use-mise-action-v2.0.2
uses: immich-app/devtools/actions/use-mise@3bca63ca3c15020293b36b51737a3ee2c773340b # use-mise-action-v3.1.0
with:
github_token: ${{ steps.token.outputs.token }}
@@ -133,19 +171,20 @@ jobs:
working-directory: ./packages/cli
steps:
- id: token
uses: immich-app/devtools/actions/create-workflow-token@caa599d954228439ea3e8ce1c3328f41ab120ee6 # create-workflow-token-action-v2.0.0
uses: immich-app/devtools/actions/create-workflow-token@1af396ae134e4bc3b63d947e672bc68bf4ff9dc5 # create-workflow-token-action-v3.0.0
with:
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
permission-contents: read
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
token: ${{ steps.token.outputs.token }}
- name: Setup Mise
uses: immich-app/devtools/actions/use-mise@7b8610a904d57da241e4ddba17fa62b62b15aed4 # use-mise-action-v2.0.2
uses: immich-app/devtools/actions/use-mise@3bca63ca3c15020293b36b51737a3ee2c773340b # use-mise-action-v3.1.0
with:
github_token: ${{ steps.token.outputs.token }}
@@ -177,19 +216,20 @@ jobs:
working-directory: ./web
steps:
- id: token
uses: immich-app/devtools/actions/create-workflow-token@caa599d954228439ea3e8ce1c3328f41ab120ee6 # create-workflow-token-action-v2.0.0
uses: immich-app/devtools/actions/create-workflow-token@1af396ae134e4bc3b63d947e672bc68bf4ff9dc5 # create-workflow-token-action-v3.0.0
with:
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
permission-contents: read
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
token: ${{ steps.token.outputs.token }}
- name: Setup Mise
uses: immich-app/devtools/actions/use-mise@7b8610a904d57da241e4ddba17fa62b62b15aed4 # use-mise-action-v2.0.2
uses: immich-app/devtools/actions/use-mise@3bca63ca3c15020293b36b51737a3ee2c773340b # use-mise-action-v3.1.0
with:
github_token: ${{ steps.token.outputs.token }}
@@ -215,19 +255,20 @@ jobs:
working-directory: ./web
steps:
- id: token
uses: immich-app/devtools/actions/create-workflow-token@caa599d954228439ea3e8ce1c3328f41ab120ee6 # create-workflow-token-action-v2.0.0
uses: immich-app/devtools/actions/create-workflow-token@1af396ae134e4bc3b63d947e672bc68bf4ff9dc5 # create-workflow-token-action-v3.0.0
with:
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
permission-contents: read
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
token: ${{ steps.token.outputs.token }}
- name: Setup Mise
uses: immich-app/devtools/actions/use-mise@7b8610a904d57da241e4ddba17fa62b62b15aed4 # use-mise-action-v2.0.2
uses: immich-app/devtools/actions/use-mise@3bca63ca3c15020293b36b51737a3ee2c773340b # use-mise-action-v3.1.0
with:
github_token: ${{ steps.token.outputs.token }}
@@ -243,19 +284,20 @@ jobs:
contents: read
steps:
- id: token
uses: immich-app/devtools/actions/create-workflow-token@caa599d954228439ea3e8ce1c3328f41ab120ee6 # create-workflow-token-action-v2.0.0
uses: immich-app/devtools/actions/create-workflow-token@1af396ae134e4bc3b63d947e672bc68bf4ff9dc5 # create-workflow-token-action-v3.0.0
with:
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
permission-contents: read
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
token: ${{ steps.token.outputs.token }}
- name: Setup Mise
uses: immich-app/devtools/actions/use-mise@7b8610a904d57da241e4ddba17fa62b62b15aed4 # use-mise-action-v2.0.2
uses: immich-app/devtools/actions/use-mise@3bca63ca3c15020293b36b51737a3ee2c773340b # use-mise-action-v3.1.0
with:
github_token: ${{ steps.token.outputs.token }}
@@ -293,19 +335,20 @@ jobs:
working-directory: ./e2e
steps:
- id: token
uses: immich-app/devtools/actions/create-workflow-token@caa599d954228439ea3e8ce1c3328f41ab120ee6 # create-workflow-token-action-v2.0.0
uses: immich-app/devtools/actions/create-workflow-token@1af396ae134e4bc3b63d947e672bc68bf4ff9dc5 # create-workflow-token-action-v3.0.0
with:
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
permission-contents: read
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
token: ${{ steps.token.outputs.token }}
- name: Setup Mise
uses: immich-app/devtools/actions/use-mise@7b8610a904d57da241e4ddba17fa62b62b15aed4 # use-mise-action-v2.0.2
uses: immich-app/devtools/actions/use-mise@3bca63ca3c15020293b36b51737a3ee2c773340b # use-mise-action-v3.1.0
with:
github_token: ${{ steps.token.outputs.token }}
@@ -325,20 +368,21 @@ jobs:
working-directory: ./server
steps:
- id: token
uses: immich-app/devtools/actions/create-workflow-token@caa599d954228439ea3e8ce1c3328f41ab120ee6 # create-workflow-token-action-v2.0.0
uses: immich-app/devtools/actions/create-workflow-token@1af396ae134e4bc3b63d947e672bc68bf4ff9dc5 # create-workflow-token-action-v3.0.0
with:
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
permission-contents: read
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
submodules: 'recursive'
token: ${{ steps.token.outputs.token }}
- name: Setup Mise
uses: immich-app/devtools/actions/use-mise@7b8610a904d57da241e4ddba17fa62b62b15aed4 # use-mise-action-v2.0.2
uses: immich-app/devtools/actions/use-mise@3bca63ca3c15020293b36b51737a3ee2c773340b # use-mise-action-v3.1.0
with:
github_token: ${{ steps.token.outputs.token }}
@@ -361,23 +405,24 @@ jobs:
runner: [ubuntu-latest, ubuntu-24.04-arm]
steps:
- id: token
uses: immich-app/devtools/actions/create-workflow-token@caa599d954228439ea3e8ce1c3328f41ab120ee6 # create-workflow-token-action-v2.0.0
uses: immich-app/devtools/actions/create-workflow-token@1af396ae134e4bc3b63d947e672bc68bf4ff9dc5 # create-workflow-token-action-v3.0.0
with:
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
permission-contents: read
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
submodules: 'recursive'
token: ${{ steps.token.outputs.token }}
- name: Setup pnpm
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0
uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
- name: Setup Node
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version-file: '.nvmrc'
cache: 'pnpm'
@@ -438,23 +483,24 @@ jobs:
runner: [ubuntu-latest, ubuntu-24.04-arm]
steps:
- id: token
uses: immich-app/devtools/actions/create-workflow-token@caa599d954228439ea3e8ce1c3328f41ab120ee6 # create-workflow-token-action-v2.0.0
uses: immich-app/devtools/actions/create-workflow-token@1af396ae134e4bc3b63d947e672bc68bf4ff9dc5 # create-workflow-token-action-v3.0.0
with:
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
permission-contents: read
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
submodules: 'recursive'
token: ${{ steps.token.outputs.token }}
- name: Setup pnpm
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0
uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
- name: Setup Node
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version-file: '.nvmrc'
cache: 'pnpm'
@@ -546,20 +592,22 @@ jobs:
contents: read
steps:
- id: token
uses: immich-app/devtools/actions/create-workflow-token@caa599d954228439ea3e8ce1c3328f41ab120ee6 # create-workflow-token-action-v2.0.0
uses: immich-app/devtools/actions/create-workflow-token@1af396ae134e4bc3b63d947e672bc68bf4ff9dc5 # create-workflow-token-action-v3.0.0
with:
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
permission-contents: read
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
token: ${{ steps.token.outputs.token }}
- name: Setup Mise
uses: immich-app/devtools/actions/use-mise@7b8610a904d57da241e4ddba17fa62b62b15aed4 # use-mise-action-v2.0.2
uses: immich-app/devtools/actions/use-mise@3bca63ca3c15020293b36b51737a3ee2c773340b # use-mise-action-v3.1.0
with:
github_token: ${{ steps.token.outputs.token }}
working_directory: ./mobile
- name: Install dependencies
run: flutter pub get
@@ -583,18 +631,19 @@ jobs:
working-directory: ./machine-learning
steps:
- id: token
uses: immich-app/devtools/actions/create-workflow-token@caa599d954228439ea3e8ce1c3328f41ab120ee6 # create-workflow-token-action-v2.0.0
uses: immich-app/devtools/actions/create-workflow-token@1af396ae134e4bc3b63d947e672bc68bf4ff9dc5 # create-workflow-token-action-v3.0.0
with:
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
permission-contents: read
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
token: ${{ steps.token.outputs.token }}
- name: Setup Mise
uses: immich-app/devtools/actions/use-mise@7b8610a904d57da241e4ddba17fa62b62b15aed4 # use-mise-action-v2.0.2
uses: immich-app/devtools/actions/use-mise@3bca63ca3c15020293b36b51737a3ee2c773340b # use-mise-action-v3.1.0
with:
github_token: ${{ steps.token.outputs.token }}
@@ -613,19 +662,20 @@ jobs:
working-directory: ./.github
steps:
- id: token
uses: immich-app/devtools/actions/create-workflow-token@caa599d954228439ea3e8ce1c3328f41ab120ee6 # create-workflow-token-action-v2.0.0
uses: immich-app/devtools/actions/create-workflow-token@1af396ae134e4bc3b63d947e672bc68bf4ff9dc5 # create-workflow-token-action-v3.0.0
with:
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
permission-contents: read
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
token: ${{ steps.token.outputs.token }}
- name: Setup Mise
uses: immich-app/devtools/actions/use-mise@7b8610a904d57da241e4ddba17fa62b62b15aed4 # use-mise-action-v2.0.2
uses: immich-app/devtools/actions/use-mise@3bca63ca3c15020293b36b51737a3ee2c773340b # use-mise-action-v3.1.0
with:
github_token: ${{ steps.token.outputs.token }}
@@ -643,12 +693,13 @@ jobs:
contents: read
steps:
- id: token
uses: immich-app/devtools/actions/create-workflow-token@caa599d954228439ea3e8ce1c3328f41ab120ee6 # create-workflow-token-action-v2.0.0
uses: immich-app/devtools/actions/create-workflow-token@1af396ae134e4bc3b63d947e672bc68bf4ff9dc5 # create-workflow-token-action-v3.0.0
with:
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
permission-contents: read
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
token: ${{ steps.token.outputs.token }}
@@ -664,19 +715,20 @@ jobs:
contents: read
steps:
- id: token
uses: immich-app/devtools/actions/create-workflow-token@caa599d954228439ea3e8ce1c3328f41ab120ee6 # create-workflow-token-action-v2.0.0
uses: immich-app/devtools/actions/create-workflow-token@1af396ae134e4bc3b63d947e672bc68bf4ff9dc5 # create-workflow-token-action-v3.0.0
with:
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
permission-contents: read
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
token: ${{ steps.token.outputs.token }}
- name: Setup Mise
uses: immich-app/devtools/actions/use-mise@7b8610a904d57da241e4ddba17fa62b62b15aed4 # use-mise-action-v2.0.2
uses: immich-app/devtools/actions/use-mise@3bca63ca3c15020293b36b51737a3ee2c773340b # use-mise-action-v3.1.0
with:
github_token: ${{ steps.token.outputs.token }}
@@ -722,19 +774,20 @@ jobs:
- 5432:5432
steps:
- id: token
uses: immich-app/devtools/actions/create-workflow-token@caa599d954228439ea3e8ce1c3328f41ab120ee6 # create-workflow-token-action-v2.0.0
uses: immich-app/devtools/actions/create-workflow-token@1af396ae134e4bc3b63d947e672bc68bf4ff9dc5 # create-workflow-token-action-v3.0.0
with:
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
permission-contents: read
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
token: ${{ steps.token.outputs.token }}
- name: Setup Mise
uses: immich-app/devtools/actions/use-mise@7b8610a904d57da241e4ddba17fa62b62b15aed4 # use-mise-action-v2.0.2
uses: immich-app/devtools/actions/use-mise@3bca63ca3c15020293b36b51737a3ee2c773340b # use-mise-action-v3.1.0
with:
github_token: ${{ steps.token.outputs.token }}

View File

@@ -24,10 +24,11 @@ jobs:
should_run: ${{ steps.check.outputs.should_run }}
steps:
- id: token
uses: immich-app/devtools/actions/create-workflow-token@caa599d954228439ea3e8ce1c3328f41ab120ee6 # create-workflow-token-action-v2.0.0
uses: immich-app/devtools/actions/create-workflow-token@1af396ae134e4bc3b63d947e672bc68bf4ff9dc5 # create-workflow-token-action-v3.0.0
with:
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
permission-contents: read
- name: Check what should run
id: check
@@ -47,10 +48,11 @@ jobs:
if: ${{ fromJSON(needs.pre-job.outputs.should_run).i18n == true }}
steps:
- id: token
uses: immich-app/devtools/actions/create-workflow-token@caa599d954228439ea3e8ce1c3328f41ab120ee6 # create-workflow-token-action-v2.0.0
uses: immich-app/devtools/actions/create-workflow-token@1af396ae134e4bc3b63d947e672bc68bf4ff9dc5 # create-workflow-token-action-v3.0.0
with:
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
permission-pull-requests: read
- name: Bot review status
env:

37
.vscode/settings.json vendored
View File

@@ -29,9 +29,6 @@
"editor.formatOnSave": true,
"tailwindCSS.lint.suggestCanonicalClasses": "ignore"
},
"svelte.plugin.svelte.compilerWarnings": {
"state_referenced_locally": "ignore"
},
"[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true
@@ -43,36 +40,40 @@
"eslint.useFlatConfig": true,
"eslint.validate": ["javascript", "typescript", "svelte"],
"eslint.workingDirectories": [
{ "directory": "cli", "changeProcessCWD": true },
{ "directory": "e2e", "changeProcessCWD": true },
{ "directory": "server", "changeProcessCWD": true },
{ "directory": "web", "changeProcessCWD": true }
{ "changeProcessCWD": true, "directory": "cli" },
{ "changeProcessCWD": true, "directory": "e2e" },
{ "changeProcessCWD": true, "directory": "server" },
{ "changeProcessCWD": true, "directory": "web" }
],
"files.watcherExclude": {
"**/.jj/**": true,
"**/.git/**": true,
"**/node_modules/**": true,
"**/build/**": true,
"**/dist/**": true,
"**/.svelte-kit/**": true
},
"explorer.fileNesting.enabled": true,
"explorer.fileNesting.patterns": {
"*.dart": "${capture}.g.dart,${capture}.gr.dart,${capture}.drift.dart",
"*.js": "${capture}.spec.js,${capture}.mock.js",
"*.ts": "${capture}.spec.ts,${capture}.mock.ts",
"package.json": "package-lock.json, yarn.lock, pnpm-lock.yaml, bun.lockb, bun.lock, pnpm-workspace.yaml, .pnpmfile.cjs"
},
"files.watcherExclude": {
"**/.git/**": true,
"**/.jj/**": true,
"**/.svelte-kit/**": true,
"**/build/**": true,
"**/dist/**": true,
"**/node_modules/**": true
},
"js/ts.preferences.importModuleSpecifier": "non-relative",
"search.exclude": {
"**/node_modules": true,
"**/.svelte-kit": true,
"**/build": true,
"**/dist": true,
"**/.svelte-kit": true,
"**/node_modules": true,
"**/open-api/typescript-sdk/src": true
},
"svelte.enable-ts-plugin": true,
"svelte.plugin.svelte.compilerWarnings": {
"state_referenced_locally": "ignore"
},
"tailwindCSS.experimental.configFile": {
"web/src/app.css": "web/src/**"
},
"js/ts.preferences.importModuleSpecifier": "non-relative",
"vitest.maximumConfigs": 10
}

View File

@@ -4,4 +4,4 @@
/web/ @danieldietzler
/machine-learning/ @mertalev
/e2e/ @danieldietzler
/mobile/ @shenlong-tanwen
/mobile/ @shenlong-tanwen @santoshakil @agg23

View File

@@ -1,134 +0,0 @@
# Contributor Covenant Code of Conduct
## Our Pledge
We as members, contributors, and leaders pledge to make participation
in our community a harassment-free experience for everyone, regardless
of age, body size, visible or invisible disability, ethnicity, sex
characteristics, gender identity and expression, level of experience,
education, socio-economic status, nationality, personal appearance,
race, religion, or sexual identity and orientation.
We pledge to act and interact in ways that contribute to an open,
welcoming, diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for
our community include:
- Demonstrating empathy and kindness toward other people
- Being respectful of differing opinions, viewpoints, and experiences
- Giving and gracefully accepting constructive feedback
- Accepting responsibility and apologizing to those affected by our
mistakes, and learning from the experience
- Focusing on what is best not just for us as individuals, but for the
overall community
Examples of unacceptable behavior include:
- The use of sexualized language or imagery, and sexual attention or
advances of any kind
- Trolling, insulting or derogatory comments, and personal or
political attacks
- Public or private harassment
- Publishing others' private information, such as a physical or email
address, without their explicit permission
- Other conduct which could reasonably be considered inappropriate in
a professional setting
## Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our
standards of acceptable behavior and will take appropriate and fair
corrective action in response to any behavior that they deem
inappropriate, threatening, offensive, or harmful.
Community leaders have the right and responsibility to remove, edit,
or reject comments, commits, code, wiki edits, issues, and other
contributions that are not aligned to this Code of Conduct, and will
communicate reasons for moderation decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also
applies when an individual is officially representing the community in
public spaces. Examples of representing our community include using an
official e-mail address, posting via an official social media account,
or acting as an appointed representative at an online or offline
event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior
may be reported to the community leaders responsible for enforcement
at our Discord channel. All complaints
will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and
security of the reporter of any incident.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in
determining the consequences for any action they deem in violation of
this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior
deemed unprofessional or unwelcome in the community.
**Consequence**: A private, written warning from community leaders,
providing clarity around the nature of the violation and an
explanation of why the behavior was inappropriate. A public apology
may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series
of actions.
**Consequence**: A warning with consequences for continued
behavior. No interaction with the people involved, including
unsolicited interaction with those enforcing the Code of Conduct, for
a specified period of time. This includes avoiding interactions in
community spaces as well as external channels like social
media. Violating these terms may lead to a temporary or permanent ban.
### 3. Temporary Ban
**Community Impact**: A serious violation of community standards,
including sustained inappropriate behavior.
**Consequence**: A temporary ban from any sort of interaction or
public communication with the community for a specified period of
time. No public or private interaction with the people involved,
including unsolicited interaction with those enforcing the Code of
Conduct, is allowed during this period. Violating these terms may lead
to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of
community standards, including sustained inappropriate behavior,
harassment of an individual, or aggression toward or disparagement of
classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction
within the community.
## Attribution
This Code of Conduct is adapted from the [Contributor
Covenant][homepage], version 2.0, available at
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
Community Impact Guidelines were inspired by [Mozilla's code of
conduct enforcement ladder](https://github.com/mozilla/diversity).
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see the
FAQ at https://www.contributor-covenant.org/faq. Translations are
available at https://www.contributor-covenant.org/translations.

View File

@@ -32,11 +32,13 @@
<a href="readme_i18n/README_zh_TW.md">正體中文</a>
<a href="readme_i18n/README_uk_UA.md">Українська</a>
<a href="readme_i18n/README_ru_RU.md">Русский</a>
<a href="readme_i18n/README_bg_BG.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>
<a href="readme_i18n/README_vi_VN.md">Tiếng Việt</a>
<a href="readme_i18n/README_th_TH.md">ภาษาไทย</a>
<a href="readme_i18n/README_ml_IN.md">മലയാളം</a>
</p>

View File

@@ -1,5 +0,0 @@
# Security Policy
## Reporting a Vulnerability
Please report security issues to `security@immich.app`

View File

@@ -7,30 +7,37 @@ backend = "aqua:opentofu/opentofu"
[tools.opentofu."platforms.linux-arm64"]
checksum = "sha256:d4f2ab15776925864b049bb329d69682851de6f5204f256e9fa86d07a0308850"
url = "https://github.com/opentofu/opentofu/releases/download/v1.11.6/tofu_1.11.6_linux_arm64.tar.gz"
url_api = "https://api.github.com/repos/opentofu/opentofu/releases/assets/391536382"
[tools.opentofu."platforms.linux-arm64-musl"]
checksum = "sha256:d4f2ab15776925864b049bb329d69682851de6f5204f256e9fa86d07a0308850"
url = "https://github.com/opentofu/opentofu/releases/download/v1.11.6/tofu_1.11.6_linux_arm64.tar.gz"
url_api = "https://api.github.com/repos/opentofu/opentofu/releases/assets/391536382"
[tools.opentofu."platforms.linux-x64"]
checksum = "sha256:02800fafa2753a9f50c38483e2fdf5bc353fd62895eb9e25eec9a5145df3a69e"
url = "https://github.com/opentofu/opentofu/releases/download/v1.11.6/tofu_1.11.6_linux_amd64.tar.gz"
url_api = "https://api.github.com/repos/opentofu/opentofu/releases/assets/391536401"
[tools.opentofu."platforms.linux-x64-musl"]
checksum = "sha256:02800fafa2753a9f50c38483e2fdf5bc353fd62895eb9e25eec9a5145df3a69e"
url = "https://github.com/opentofu/opentofu/releases/download/v1.11.6/tofu_1.11.6_linux_amd64.tar.gz"
url_api = "https://api.github.com/repos/opentofu/opentofu/releases/assets/391536401"
[tools.opentofu."platforms.macos-arm64"]
checksum = "sha256:62d7fa8539e13b444827aa0a3b90c5972da5c47e8f8882d9dcf2e430e78840c1"
url = "https://github.com/opentofu/opentofu/releases/download/v1.11.6/tofu_1.11.6_darwin_arm64.tar.gz"
url_api = "https://api.github.com/repos/opentofu/opentofu/releases/assets/391536399"
[tools.opentofu."platforms.macos-x64"]
checksum = "sha256:1408cdef1c380f914565e6b4bb70794c6b163f195fcb233357f3d6c5745906b6"
url = "https://github.com/opentofu/opentofu/releases/download/v1.11.6/tofu_1.11.6_darwin_amd64.tar.gz"
url_api = "https://api.github.com/repos/opentofu/opentofu/releases/assets/391536384"
[tools.opentofu."platforms.windows-x64"]
checksum = "sha256:27323f70c875b8251bfd7e61a4cffc3ebff4e56ed1e611b955016f0c7077367e"
url = "https://github.com/opentofu/opentofu/releases/download/v1.11.6/tofu_1.11.6_windows_amd64.tar.gz"
url_api = "https://api.github.com/repos/opentofu/opentofu/releases/assets/391536406"
[[tools.terragrunt]]
version = "1.0.3"
@@ -39,27 +46,34 @@ backend = "aqua:gruntwork-io/terragrunt"
[tools.terragrunt."platforms.linux-arm64"]
checksum = "sha256:e5b60ab05b5214db694e6bc215d8124fb626e277cdb56b86f6147ae110d510fe"
url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.0.3/terragrunt_linux_arm64.tar.gz"
url_api = "https://api.github.com/repos/gruntwork-io/terragrunt/releases/assets/406654253"
[tools.terragrunt."platforms.linux-arm64-musl"]
checksum = "sha256:e5b60ab05b5214db694e6bc215d8124fb626e277cdb56b86f6147ae110d510fe"
url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.0.3/terragrunt_linux_arm64.tar.gz"
url_api = "https://api.github.com/repos/gruntwork-io/terragrunt/releases/assets/406654253"
[tools.terragrunt."platforms.linux-x64"]
checksum = "sha256:6d48049baf82e0bf9c804368dc85cbfeadc10955e33777e9e8de3e020b94b073"
url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.0.3/terragrunt_linux_amd64.tar.gz"
url_api = "https://api.github.com/repos/gruntwork-io/terragrunt/releases/assets/406654196"
[tools.terragrunt."platforms.linux-x64-musl"]
checksum = "sha256:6d48049baf82e0bf9c804368dc85cbfeadc10955e33777e9e8de3e020b94b073"
url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.0.3/terragrunt_linux_amd64.tar.gz"
url_api = "https://api.github.com/repos/gruntwork-io/terragrunt/releases/assets/406654196"
[tools.terragrunt."platforms.macos-arm64"]
checksum = "sha256:aacb5be2ca5475300cbce246dfbd8a45eb47510fbaa70fab8561c49ef5db03aa"
url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.0.3/terragrunt_darwin_arm64.tar.gz"
url_api = "https://api.github.com/repos/gruntwork-io/terragrunt/releases/assets/406654084"
[tools.terragrunt."platforms.macos-x64"]
checksum = "sha256:3133c2251e191aede8e3dd2a5b3aee2e91c5f08f88f117aee40eed9a24c8ef6b"
url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.0.3/terragrunt_darwin_amd64.tar.gz"
url_api = "https://api.github.com/repos/gruntwork-io/terragrunt/releases/assets/406653970"
[tools.terragrunt."platforms.windows-x64"]
checksum = "sha256:183b2745b4e04980a6bfa4450ff81956a12596ca22d70f7aaa793980f5b036db"
url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.0.3/terragrunt_windows_amd64.exe.tar.gz"
url_api = "https://api.github.com/repos/gruntwork-io/terragrunt/releases/assets/406654412"

View File

@@ -154,7 +154,7 @@ services:
redis:
container_name: immich_redis
image: docker.io/valkey/valkey:9@sha256:8436e10bc65c94886a91d4415b6a6dfa9cb5a306fb3b996e5bb67cd2b4854193
image: docker.io/valkey/valkey:9@sha256:8e8d64b405ce18f41b8e5ee20aa4687a8ed0022d1298f2ce31cdcf3a76e09411
healthcheck:
test: redis-cli ping || exit 1

View File

@@ -56,7 +56,7 @@ services:
redis:
container_name: immich_redis
image: docker.io/valkey/valkey:9@sha256:8436e10bc65c94886a91d4415b6a6dfa9cb5a306fb3b996e5bb67cd2b4854193
image: docker.io/valkey/valkey:9@sha256:8e8d64b405ce18f41b8e5ee20aa4687a8ed0022d1298f2ce31cdcf3a76e09411
healthcheck:
test: redis-cli ping || exit 1
restart: always
@@ -85,7 +85,7 @@ services:
container_name: immich_prometheus
ports:
- 9090:9090
image: prom/prometheus@sha256:e4254400b85610324913f0dc4acf92603d9984e7519414c5a12811aa6146acc3
image: prom/prometheus@sha256:3c42b892cf723fa54d2f262c37a0e1f80aa8c8ddb1da7b9b0df9455a35a7f893
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml
- prometheus-data:/prometheus
@@ -97,7 +97,7 @@ services:
command: ['./run.sh', '-disable-reporting']
ports:
- 3000:3000
image: grafana/grafana:12.4.3-ubuntu@sha256:ca3f764fdc48cebdf22dd206f33ecb0795a9a7210eacd1b5c02204aebd78b223
image: grafana/grafana:12.4.4-ubuntu@sha256:df2e7ef5f32f771794cf76bad5f2bceac227036460a2cc269a9045e5662abc58
volumes:
- grafana-data:/var/lib/grafana

View File

@@ -61,7 +61,7 @@ services:
redis:
container_name: immich_redis
image: docker.io/valkey/valkey:9@sha256:8436e10bc65c94886a91d4415b6a6dfa9cb5a306fb3b996e5bb67cd2b4854193
image: docker.io/valkey/valkey:9@sha256:8e8d64b405ce18f41b8e5ee20aa4687a8ed0022d1298f2ce31cdcf3a76e09411
user: '1000:1000'
security_opt:
- no-new-privileges:true

View File

@@ -49,7 +49,7 @@ services:
redis:
container_name: immich_redis
image: docker.io/valkey/valkey:9@sha256:8436e10bc65c94886a91d4415b6a6dfa9cb5a306fb3b996e5bb67cd2b4854193
image: docker.io/valkey/valkey:9@sha256:8e8d64b405ce18f41b8e5ee20aa4687a8ed0022d1298f2ce31cdcf3a76e09411
healthcheck:
test: redis-cli ping || exit 1
restart: always

View File

@@ -10,7 +10,7 @@ DB_DATA_LOCATION=./postgres
# TZ=Etc/UTC
# The Immich version to use. You can pin this to a specific version like "v2.1.0"
IMMICH_VERSION=v2
IMMICH_VERSION=v3
# Connection secret for postgres. You should change it to a random password
# Please use only the characters `A-Za-z0-9`, without special characters or spaces

View File

@@ -31,7 +31,7 @@ You can trigger a database backup manually:
1. Go to **Administration > Job Queues**
2. Click **Create job** in the top right
3. Select **Create Database Backup** and click **Confirm**
3. Select **Create Database Dump** and click **Confirm**
The backup will appear in `UPLOAD_LOCATION/backups` and counts toward your retention limit.

View File

@@ -14,6 +14,8 @@ Under Email, enter the required details to connect with an SMTP server.
You can use [this guide](/guides/smtp-gmail) to use Gmail's SMTP server.
You can use [this guide](/guides/smtp-microsoft365) to use Microsoft's SMTP server.
## 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:

View File

@@ -17,7 +17,7 @@ running `apt install postgresql-NN-pgvector`, where `NN` is your Postgres versio
You must install VectorChord into your instance of Postgres using their [instructions][vchord-install]. After installation, add `shared_preload_libraries = 'vchord.so'` to your `postgresql.conf`. If you already have some `shared_preload_libraries` set, you can separate each extension with a comma. For example, `shared_preload_libraries = 'pg_stat_statements, vchord.so'`.
:::note Supported versions
Immich is known to work with Postgres versions `>= 14, < 19`.
Immich is known to work with Postgres versions `>= 14, < 20`.
VectorChord is known to work with pgvector versions `>= 0.7, < 0.9`.
@@ -144,7 +144,7 @@ ALTER TABLE face_search ALTER COLUMN embedding SET DATA TYPE vector(512);
<details>
<summary>Migration steps</summary>
1. Ensure you have at least 0.7.0 of pgvector installed. If it is below that, please upgrade it and run the SQL command `ALTER EXTENSION vector UPDATE;` using psql or your choice of database client
1. Ensure you have at least `0.7.0` of pgvector installed. If it is below that, please upgrade it and run the SQL command `ALTER EXTENSION vector UPDATE;` using psql or your choice of database client
2. Follow the Prerequisites to install VectorChord
3. If Immich does not have superuser permissions, run the SQL command `CREATE EXTENSION vchord CASCADE;`
4. Remove the `DB_VECTOR_EXTENSION=pgvector` environmental variable as it will make Immich still use pgvector if set

View File

@@ -112,7 +112,7 @@ services:
traefik.enable: true
# increase readingTimeouts for the entrypoint used here
traefik.http.routers.immich.entrypoints: websecure
traefik.http.routers.immich.rule: Host(`immich.your-domain.com`)
traefik.http.routers.immich.rule: Host(`immich.example.com`)
traefik.http.services.immich.loadbalancer.server.port: 2283
```

View File

@@ -90,7 +90,7 @@ immich-admin list-users
[
{
id: 'e65e6f88-2a30-4dbe-8dd9-1885f4889b53',
email: 'immich@example.com.com',
email: 'immich@example.com',
name: 'Immich Admin',
storageLabel: 'admin',
externalPath: null,

View File

@@ -1,5 +1,30 @@
# System Integrity
## Integrity report
At a [customizable interval](https://my.immich.app/admin/system-settings?isOpen=integrity-checks), Immich runs integrity checks to ensure that your library is still intact and there are no corrupt files.
There are three kind of issues Immich checks for:
- Untracked files: the path was found in Immich's directories but it is not referenced in Immich's database
- Missing files: the path is found in the Immich internal database, but does not actually exist on disk
- Checksum mismatches: the checksum of the file stored in Immich's database does not match the actual file's checksum anymore
All three run nightly at 3am by default. For the "Checksum files" check, there are additional time and progress limits, as those are the most taxing checks. With these additional limits, Immich can slowly check checksums of your files over the course of a couple of days.
You can see the results of these checks on the [maintenance](https://my.immich.app/admin/maintenance) page. Here, you can also trigger a full scan (a _check_) for specific jobs, or all of them. In addition, you can also _refresh_ checks. This will only look at items that have currently been reported on, and check if those have been fixed.
### Common causes
Most common are untracked files. In many cases those are corrupted thumbnails or encoded videos that have been partially generated at some point and never got cleaned up properly. These are usually fine to delete, as both can always be regenerated at a later point. Other files will need to be investigated on a case-by-case basis by checking they already exist in Immich and thinking about how they might have gotten untracked.
:::info
You might want to run the _missing_ jobs for thumbnails and encoded videos (https://my.immich.app/admin/queues) to make sure all your assets have proper thumbnails and encoded videos. Watch the server logs while running the jobs, in case there are any issues with some assets.
:::
Missing files are files where Immich references them internally, but they don't actually exist on disk in that location. It could be that you deleted a file on disk from the internal library (don't do that, Immich doesn't support it). It could also be that there are issues with your file storage. Please carefully investigate missing files, and never hesitate to reach out on [our Discord](https://discord.immich.app) if you have any questions!
Checksum mismatches are often indicative of file system corruption. It could also be that you previously edited a file from the internal library on the disk, which is also not supported and will cause a checksum mismatch. Again, the recommended action is to look at any reported item individually, check it out, try to remember if you changed it or some metadata on it at some point. If you edited the file, the supported resolution is to delete the mismatched asset from Immich and reupload it as a new asset.
## Folder checks
:::info
@@ -40,7 +65,7 @@ The above error messages show that the server has previously (successfully) writ
### Ignoring the checks
:::warning
:::danger
The checks are designed to catch common problems that we have seen users have in the past, and often indicate there's something wrong that you should solve. If you know what you're doing and you want to disable them you can set the following environment variable:
:::

View File

@@ -7,7 +7,7 @@ Immich uses the [OpenAPI](https://swagger.io/specification/) standard to generat
OpenAPI is used to generate the client (Typescript, Dart) SDK. `openapi-generator-cli` can be installed [here](https://openapi-generator.tech/docs/installation/). The generated SDK is based on the `immich-openapi-specs.json` file, which is autogenerated by the server **when running in development mode**. The `immich-openapi-specs.json` file can be modified with `@nestjs/swagger` decorators used or referenced by controller endpoints. See the [NestJS OpenAPI docs](https://docs.nestjs.com/openapi/types-and-parameters) for more info. When you add a new endpoint or modify an existing one, you must run the server in development mode and run the command below to update the client SDK.
```bash
make open-api
mise open-api
```
You can find the generated client SDK in the `packages/sdk/client` for Typescript SDK and `mobile/openapi` for Dart SDK.

View File

@@ -218,7 +218,7 @@ When the Dev Container starts, it automatically:
- Debug ports: 9230 (workers), 9231 (API)
:::info
The Dev Container setup replaces the `make dev` command from the traditional setup. All services start automatically when you open the container.
The Dev Container setup replaces the `mise dev` command from the traditional setup. All services start automatically when you open the container.
:::
### Accessing Services

View File

@@ -12,11 +12,11 @@ Our [GitHub Repository](https://github.com/immich-app/immich) is a [monorepo](ht
| `.vscode/` | VSCode debug launch profiles |
| `packages/cli` | Source code for the CLI |
| `packages/sdk` | Source code for the generated OpenAPI SDK |
| `packages/scripts` | Scripts for version bumps and draft releases |
| `docker/` | Docker compose resources for dev, test, production |
| `design/` | Screenshots and logos for the README |
| `docs/` | Source code for the [https://immich.app](https://immich.app) website |
| `machine-learning/` | Source code for the `immich-machine-learning` docker image |
| `misc/release/` | Scripts for version bumps and draft releases |
| `mobile/` | Source code for the mobile app, both Android and iOS |
| `server/` | Source code for the `immich-server` docker image |
| `web/` | Source code for the `web` |

View File

@@ -2,7 +2,7 @@
A minimal devcontainer is supplied with this repository. All commands can be executed directly inside this container to avoid tedious installation of the environment.
:::warning
The provided devcontainer isn't complete at the moment. At least all dockerized steps in the Makefile won't work (`make dev`, ....). Feel free to contribute!
The provided devcontainer isn't complete at the moment. At least all dockerized steps in the Makefile won't work (`mise dev`, ....). Feel free to contribute!
:::
When contributing code through a pull request, please check the following:

View File

@@ -30,7 +30,7 @@ This environment includes the services below. Additional details are available i
- Redis
- PostgreSQL development database with exposed port `5432` so you can use any database client to access it
All the services are packaged to run as with single Docker Compose command.
All the services are packaged to run with a single Docker Compose command.
:::tip mise
[mise](https://mise.jdx.dev) is used throughout the project to manage tool versions and run tasks. [Install mise](https://mise.jdx.dev/installing-mise.html), then from the repo root run `mise trust` and `mise install` to get all required tools. Tasks for each service can be run from the repo root using `mise //namespace:task` (e.g. `mise //server:lint`). To list all available tasks, run `mise tasks ls --all`.
@@ -41,18 +41,18 @@ All the services are packaged to run as with single Docker Compose command.
1. Clone the project repo.
2. Run `cp docker/example.env docker/.env`.
3. Edit `docker/.env` to provide values for the required variable `UPLOAD_LOCATION`.
4. Install dependencies - `pnpm i`
4. Install dependencies - `mise x -- pnpm i`
5. From the root directory, run:
```bash title="Start development server"
make dev # required Makefile installed on the system.
mise dev
```
5. Access the dev instance in your browser at http://localhost:3000, or connect via the mobile app.
All the services will be started with hot-reloading enabled for a quick feedback loop.
You can access the web from `http://your-machine-ip:3000` or `http://localhost:3000` and access the server from the mobile app at `http://your-machine-ip:3000/api`
You can access the web from `http://your-machine-ip:3000` or `http://localhost:3000` and access the server from the mobile app at `http://your-machine-ip:3000`
**Notes:**
@@ -88,7 +88,7 @@ To see local changes to `@immich/ui` in Immich, do the following:
3. Uncomment the corresponding volume in web service of the `docker/docker-compose.dev.yml` file (`../../ui:/usr/src/ui`)
4. Uncomment the corresponding alias in the `web/vite.config.ts` file (`'@immich/ui': path.resolve(\_\_dirname, '../../ui/packages/ui')`)
5. Uncomment the import statement in `web/src/app.css` file `@import '../../../ui/packages/ui/dist/theme/default.css';` and comment out `@import '@immich/ui/theme/default.css';`
6. Start up the stack via `make dev`
6. Start up the stack via `mise dev`
7. After making changes in `@immich/ui`, rebuild it (`pnpm run build`)
### Mobile app
@@ -99,6 +99,21 @@ To see local changes to `@immich/ui` in Immich, do the following:
2. Run `mise //mobile:translation` to generate the translation file.
3. Change to the `mobile/` directory and run `flutter run` to start the app.
##### iOS Code Signing
The Immich Apple Team ID and bundle IDs are specified in `mobile/ios/Signing.xcconfig`. For local development, we provide an override mechanism.
Create `mobile/ios/Signing.local.xcconfig` and populate it with the necessary values needed to build and sign Immich yourself. This local override file is gitignored.
```
IMMICH_TEAM_ID = ABCDE12345
IMMICH_BUNDLE_ID_PROD = com.customuniqueid.immich
IMMICH_BUNDLE_ID_DEV = com.customuniqueid.immichdev
IMMICH_GROUP_ID = group.com.customuniqueid.immich
```
The environment values are used across Immich's targets and schemes to prevent redundant edits by contributors.
#### Translation
To add a new translation text, enter the key-value pair in the `i18n/en.json` in the root of the immich project. Then run:

View File

@@ -12,7 +12,7 @@ You need to run `mise //server:install` before _once_.
The e2e tests can be run by first starting up a test production environment via:
```bash
make e2e
mise e2e
```
Before you can run the tests, you need to run the following commands _once_:

View File

@@ -99,6 +99,7 @@ Options:
-H, --include-hidden Include hidden folders (default: false, env: IMMICH_INCLUDE_HIDDEN)
-a, --album Automatically create albums based on folder name (default: false, env: IMMICH_AUTO_CREATE_ALBUM)
-A, --album-name <name> Add all assets to specified album (env: IMMICH_ALBUM_NAME)
--visibility <visibility> Set the visibility of uploaded assets (choices: "archive", "timeline", "hidden", "locked", env: IMMICH_VISIBILITY)
-n, --dry-run Don't perform any actions, just show what will be done (default: false, env: IMMICH_DRY_RUN)
-c, --concurrency <number> Number of assets to upload at the same time (default: 4, env: IMMICH_UPLOAD_CONCURRENCY)
-j, --json-output Output detailed information in json format (default: false, env: IMMICH_JSON_OUTPUT)
@@ -176,6 +177,12 @@ By default, hidden files are skipped. If you want to include hidden files, use t
immich upload --include-hidden --recursive directory/
```
You can set the visibility of uploaded assets to `archive`, `timeline`, `hidden`, or `locked` with the `--visibility` option:
```bash
immich upload --visibility archive --recursive directory/
```
You can use the `--json-output` option to get a json printed which includes
three keys: `newFiles`, `duplicates` and `newAssets`. Due to some logging
output you will need to strip the first three lines of output to get the json.

View File

@@ -15,14 +15,14 @@ When using "Deduplicate All" or viewing suggestions, Immich automatically presel
### Synchronizing metadata
When resolving duplicates, metadata from trashed assets is automatically synchronized to the kept assets. The following metadata is synchronized:
When resolving duplicates, metadata from trashed assets is automatically synchronized to the kept asset. This synchronization only happens when **exactly one** asset is kept and at least one asset is trashed. When more than one asset is kept, metadata is not merged — the assets keep their own metadata and are simply removed from the duplicate group. The following metadata is synchronized:
| Name | Description |
| ----------- | ------------------------------------------------------------------------------------------------------------------------------- |
| Album | The kept assets will be added to _every_ album that the other assets in the group belong to. |
| Favorite | If any of the assets in the group have been added to favorites, every kept asset will also be added to favorites. |
| Rating | If one or more assets in the duplicate group have a rating, the highest rating is selected and synchronized to the kept assets. |
| Description | Descriptions from each asset are combined together and synchronized to all the kept assets. |
| Visibility | The most restrictive visibility is applied to the kept assets. |
| Location | Latitude and longitude are copied if all assets with geolocation data in the group share the same coordinates. |
| Tag | Tags from all assets in the group are merged and applied to every kept asset. |
| Name | Description |
| ----------- | ------------------------------------------------------------------------------------------------------------------------------ |
| Album | The kept asset will be added to _every_ album that the other assets in the group belong to. |
| Favorite | If any of the assets in the group have been added to favorites, the kept asset will also be added to favorites. |
| Rating | If one or more assets in the duplicate group have a rating, the highest rating is selected and synchronized to the kept asset. |
| Description | Descriptions from each asset are combined together and synchronized to the kept asset. |
| Visibility | The most restrictive visibility is applied to the kept asset. |
| Location | Latitude and longitude are copied if all assets with geolocation data in the group share the same coordinates. |
| Tag | Tags from all assets in the group are merged and applied to the kept asset. |

View File

@@ -13,7 +13,9 @@ import MobileAppBackup from '/docs/partials/_mobile-app-backup.md';
:::info Android verification
Below are the SHA-256 fingerprints for the certificates signing the android applications.
- Playstore / Github releases:
- Google Play releases:
`5A:22:C1:83:47:54:05:F5:49:C4:EB:9F:B2:6C:2E:93:A3:EF:9C:57:66:15:0A:7A:F3:8C:8D:3F:E5:15:CC:D6`
- GitHub releases:
`86:C5:C4:55:DF:AF:49:85:92:3A:8F:35:AD:B3:1D:0C:9E:0B:95:7D:7F:94:C2:D2:AF:6A:24:38:AA:96:00:20`
- F-Droid releases:
`FA:8B:43:95:F4:A6:47:71:A0:53:D1:C7:57:73:5F:A2:30:13:74:F5:3D:58:0D:D1:75:AA:F7:A1:35:72:9C:BF`

View File

@@ -17,7 +17,7 @@ services:
ports:
- "8888:80"
environment:
PGADMIN_DEFAULT_EMAIL: user-name@domain-name.com
PGADMIN_DEFAULT_EMAIL: admin@example.com
PGADMIN_DEFAULT_PASSWORD: strong-password
volumes:
- pgadmin-data:/var/lib/pgadmin

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

View File

@@ -0,0 +1,19 @@
# SMTP settings using Microsoft 365
This guide walks you through how to get the information you need to set up your Immich instance to send emails using Microsoft's SMTP server.
## Create an app password
You will need to generate an app password to use your Microsoft email in Immich. Depending on if you have a personal or business account, you can use https://go.microsoft.com/fwlink/?linkid=2274139 or https://myaccount.microsoft.com/securtiy-info respectively.
## Entering the SMTP credential in Immich
Entering your credential in Immich's email notification settings at `Administration -> Settings -> Notification Settings`
Host: smtp-mail.outlook.com
Port: 587
username: your mail address
Password: app password you created earlier
SMTPS: set it to disabled
<img src={require('./img/email-ms-settings.webp').default} width="80%" title="SMTP settings" />

View File

@@ -19,7 +19,7 @@ If this does not work, try running `docker compose up -d --force-recreate`.
| Variable | Description | Default | Containers |
| :----------------- | :------------------------------ | :-----: | :----------------------- |
| `IMMICH_VERSION` | Image tags | `v2` | server, machine learning |
| `IMMICH_VERSION` | Image tags | `v3` | server, machine learning |
| `UPLOAD_LOCATION` | Host path for uploads | | server |
| `DB_DATA_LOCATION` | Host path for Postgres database | | database |
@@ -89,7 +89,7 @@ Information on the current workers can be found [here](/administration/jobs-work
\*1: The values of `DB_USERNAME`, `DB_PASSWORD`, and `DB_DATABASE_NAME` are passed to the Postgres container as the variables `POSTGRES_USER`, `POSTGRES_PASSWORD`, and `POSTGRES_DB` in `docker-compose.yml`.
\*2: If not provided, the appropriate extension to use is auto-detected at startup by introspecting the database. When multiple extensions are installed, the order of preference is VectorChord, pgvecto.rs, pgvector.
\*2: If not provided, the appropriate extension to use is auto-detected at startup by inspecting the database. When multiple extensions are installed, the order of preference is VectorChord, pgvector.
\*3: Uses either [`postgresql.ssd.conf`](https://github.com/immich-app/base-images/blob/main/postgres/postgresql.ssd.conf) or [`postgresql.hdd.conf`](https://github.com/immich-app/base-images/blob/main/postgres/postgresql.hdd.conf) which mainly controls the Postgres `effective_io_concurrency` setting to allow for concurrenct IO on SSDs and sequential IO on HDDs.

View File

@@ -20,9 +20,11 @@ Hardware and software requirements for Immich:
- **RAM**: Minimum 6GB, recommended 8GB.
- **CPU**: Minimum 2 cores, recommended 4 cores.
- Immich runs on the `amd64` and `arm64` platforms.
Since `v2.6`, the machine learning container on `amd64` requires the `>= x86-64-v2` [microarchitecture level](https://en.wikipedia.org/wiki/X86-64#Microarchitecture_levels).
Since `v3`, the machine learning container on `amd64` requires the `>= x86-64-v2` [microarchitecture level](https://en.wikipedia.org/wiki/X86-64#Microarchitecture_levels).
Most CPUs released since ~2012 support this microarchitecture.
If you are using a virtual machine, ensure you have selected a [supported microarchitecture](https://pve.proxmox.com/pve-docs/chapter-qm.html#_qemu_cpu_types).
If you are unable to support this instruction set, the last version to support `x86-64-v1` is `v2.7.5`.
Note that this release is no longer supported, and you must run a matching `immich-server` version.
- **Storage**: Recommended Unix-compatible filesystem (EXT4, ZFS, APFS, etc.) with support for user/group ownership and permissions.
- The generation of thumbnails and transcoded video can increase the size of the photo library by 10-20% on average.

View File

@@ -7,7 +7,7 @@ sidebar_position: 85
:::note
This is a community contribution and not officially supported by the Immich team, but included here for convenience.
Community support can be found in the dedicated channel on the [Discord Server](https://discord.immich.app/).
Community support should be directed to Synology-specific support platforms.
:::
Immich can easily be installed on a Synology NAS using Container Manager within DSM. If you have not installed Container Manager already, you can install it in the Packages Center. Refer to the [Container Manager docs](https://kb.synology.com/en-us/DSM/help/ContainerManager/docker_desc?version=7) for more information on using Container Manager.

View File

@@ -121,7 +121,7 @@ alt="Go to Docker Tab and visit the address listed next to immich-web"
width="90%"
alt="Go to Docker Tab and visit the address listed next to immich-web"
/>
</details>
:::tip

View File

@@ -28,11 +28,15 @@ docker image prune
## Versioning Policy
Immich follows [semantic versioning][semver], which tags releases in the format `<major>.<minor>.<patch>`. We intend for breaking changes to be limited to major version releases.
You can configure your Docker image to point to the current major version by using a metatag, such as `:v2`.
Immich follows [semantic versioning][semver], which tags releases in the format `<major>.<minor>.<patch>`.
We intend for breaking changes, including those to the API or deployment, to be limited to major version releases.
You can configure your Docker image to point to the current major version by using a metatag, such as `:v3`. These metatags do not follow release candidates.
Currently, we have no plans to backport patches to earlier versions. We encourage all users to run the most recent release of Immich.
Switching back to an earlier version, even within the same minor release tag, is not supported.
The mobile app is typically compatible with the current and prior major version. However, the server is only compatible with the matching major version.
Thus, we recommend upgrading all mobile clients before upgrading the server to ensure compatibility.
We do not backport patches to earlier versions. We encourage all users to run the most recent stable release of Immich.
Downgrading to an earlier version, even within the same minor version, is not supported.
[semver]: https://semver.org/

View File

@@ -1,7 +1,7 @@
The mobile app can be downloaded from the following places:
- Obtainium: You can get your Obtainium config link from the [Utilities page of your Immich server](https://my.immich.app/utilities).
- [Google Play Store](https://play.google.com/store/apps/details?id=app.alextran.immich)
- [Apple App Store](https://apps.apple.com/us/app/immich/id1613945652)
- [F-Droid](https://f-droid.org/packages/app.alextran.immich)
- [GitHub Releases (apk)](https://github.com/immich-app/immich/releases)
- [Google Play Store](https://play.google.com/store/apps/details?id=app.alextran.immich)
- [GitHub Releases (APK)](https://github.com/immich-app/immich/releases)
- Obtainium: You can get your Obtainium config link from the [Utilities page of your Immich server](https://my.immich.app/utilities).
- [F-Droid](https://app.futo.org/fdroid/repo/)

View File

@@ -1,5 +1,8 @@
# @generated - this file is auto-generated by `mise lock` https://mise.en.dev/dev-tools/mise-lock.html
[[tools.wrangler]]
version = "4.66.0"
version = "4.110.0"
backend = "npm:wrangler"
[tools.wrangler.options]
allow_builds = '["esbuild", "sharp", "workerd"]'

View File

@@ -28,4 +28,4 @@ run = "prettier --write ."
run = "wrangler pages deploy build --project-name=${PROJECT_NAME} --branch=${BRANCH_NAME}"
[tools]
wrangler = "4.91.0"
wrangler = "4.111.0"

View File

@@ -40,7 +40,8 @@
"@docusaurus/tsconfig": "^3.10.0",
"@docusaurus/types": "^3.10.0",
"prettier": "^3.7.4",
"typescript": "^6.0.0"
"@typescript/native": "npm:typescript@^7.0.2",
"typescript": "npm:@typescript/typescript6@^6.0.2"
},
"browserslist": {
"production": [

View File

@@ -1,4 +1,8 @@
[
{
"label": "v3.0.3",
"url": "https://docs.v3.0.3.archive.immich.app"
},
{
"label": "v2.7.5",
"url": "https://docs.v2.7.5.archive.immich.app"

View File

@@ -4,7 +4,8 @@ services:
e2e-auth-server:
container_name: immich-e2e-auth-server
build:
context: ../packages/e2e-auth-server
context: ../
dockerfile: packages/e2e-auth-server/Dockerfile
ports:
- 2286:2286
@@ -44,7 +45,7 @@ services:
redis:
container_name: immich-e2e-redis
image: docker.io/valkey/valkey:9@sha256:8436e10bc65c94886a91d4415b6a6dfa9cb5a306fb3b996e5bb67cd2b4854193
image: docker.io/valkey/valkey:9@sha256:8e8d64b405ce18f41b8e5ee20aa4687a8ed0022d1298f2ce31cdcf3a76e09411
healthcheck:
test: redis-cli ping || exit 1

View File

@@ -41,9 +41,18 @@ export default typescriptEslint.config([
'@typescript-eslint/no-floating-promises': 'error',
'unicorn/prefer-module': 'off',
'unicorn/import-style': 'off',
'unicorn/consistent-boolean-name': 'off',
'unicorn/no-non-function-verb-prefix': 'off',
'unicorn/no-unreadable-for-of-expression': 'off',
'unicorn/max-nested-calls': 'off',
'unicorn/prefer-uint8array-base64': 'off',
'unicorn/isolated-functions': 'off',
'unicorn/prefer-promise-with-resolvers': 'off',
'unicorn/no-declarations-before-early-exit': 'off',
'unicorn/prefer-simple-condition-first': 'off',
curly: 2,
'prettier/prettier': 0,
'unicorn/prevent-abbreviations': 'off',
'unicorn/name-replacements': 'off',
'unicorn/filename-case': 'off',
'unicorn/no-null': 'off',
'unicorn/prefer-top-level-await': 'off',

View File

@@ -1,6 +1,6 @@
{
"name": "immich-e2e",
"version": "2.7.5",
"version": "3.0.3",
"description": "",
"main": "index.js",
"type": "module",
@@ -32,7 +32,7 @@
"@playwright/test": "^1.44.1",
"@socket.io/component-emitter": "^3.1.2",
"@types/luxon": "^3.4.2",
"@types/node": "^24.12.4",
"@types/node": "^24.13.3",
"@types/pg": "^8.15.1",
"@types/pngjs": "^6.0.4",
"@types/supertest": "^7.0.0",
@@ -40,7 +40,7 @@
"eslint": "^10.0.0",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-prettier": "^5.1.3",
"eslint-plugin-unicorn": "^64.0.0",
"eslint-plugin-unicorn": "^72.0.0",
"exiftool-vendored": "^35.0.0",
"globals": "^17.0.0",
"luxon": "^3.4.4",
@@ -51,10 +51,10 @@
"sharp": "^0.34.5",
"socket.io-client": "^4.7.4",
"supertest": "^7.0.0",
"typescript": "^6.0.0",
"@typescript/native": "npm:typescript@^7.0.2",
"typescript": "npm:@typescript/typescript6@^6.0.2",
"typescript-eslint": "^8.28.0",
"utimes": "^5.2.1",
"vite-tsconfig-paths": "^6.1.1",
"vitest": "^4.0.0"
}
}

View File

@@ -118,7 +118,7 @@ describe('/admin/database-backups', () => {
expect(status).toBe(201);
cookie = headers['set-cookie'][0].split(';')[0];
cookie = headers['set-cookie'][0].split(';', 1)[0];
await expect
.poll(
@@ -224,7 +224,7 @@ describe('/admin/database-backups', () => {
});
expect(status).toBe(201);
cookie = headers['set-cookie'][0].split(';')[0];
cookie = headers['set-cookie'][0].split(';', 1)[0];
await expect
.poll(
@@ -295,7 +295,7 @@ describe('/admin/database-backups', () => {
});
expect(status).toBe(201);
cookie = headers['set-cookie'][0].split(';')[0];
cookie = headers['set-cookie'][0].split(';', 1)[0];
await expect
.poll(

View File

@@ -85,7 +85,7 @@ describe('/admin/maintenance', () => {
expect(status).toBe(201);
cookie = headers['set-cookie'][0].split(';')[0];
cookie = headers['set-cookie'][0].split(';', 1)[0];
expect(cookie).toEqual(
expect.stringMatching(/^immich_maintenance_token=[A-Za-z0-9-_]*\.[A-Za-z0-9-_]*\.[A-Za-z0-9-_]*$/),
);
@@ -99,7 +99,7 @@ describe('/admin/maintenance', () => {
},
{
interval: 500,
timeout: 10_000,
timeout: 60_000,
},
)
.toBeTruthy();
@@ -149,7 +149,7 @@ describe('/admin/maintenance', () => {
const { status, body } = await request(app)
.post('/admin/maintenance/login')
.send({
token: cookie!.split('=')[1].trim(),
token: cookie!.split('=', 2)[1].trim(),
});
expect(status).toBe(201);
expect(body).toEqual(
@@ -190,7 +190,7 @@ describe('/admin/maintenance', () => {
},
{
interval: 500,
timeout: 10_000,
timeout: 60_000,
},
)
.toBeFalsy();

View File

@@ -27,7 +27,7 @@ test.describe('Maintenance', () => {
test('maintenance shows no options to users until they authenticate', async ({ page }) => {
const setCookie = await utils.enterMaintenance(admin.accessToken);
const cookie = setCookie
?.map((cookie) => cookie.split(';')[0].split('='))
?.map((cookie) => cookie.split(';', 1)[0].split('='))
?.find(([name]) => name === 'immich_maintenance_token');
expect(cookie).toBeTruthy();

View File

@@ -120,6 +120,7 @@ describe('/albums', () => {
}),
]);
// eslint-disable-next-line unicorn/no-unreadable-array-destructuring
[user2Albums[0]] = await Promise.all([
getAlbumInfo({ id: user2Albums[0].id }, { headers: asBearerAuth(user2.accessToken) }),
deleteUserAdmin({ id: user3.userId, userAdminDeleteDto: {} }, { headers: asBearerAuth(admin.accessToken) }),
@@ -504,13 +505,14 @@ describe('/albums', () => {
});
});
it('should not be able to share album with owner', async () => {
it('should deduplicate owner from albumUsers on create', async () => {
const { status, body } = await request(app)
.post('/albums')
.send({ albumName: 'New album', albumUsers: [{ role: AlbumUserRole.Editor, userId: user1.userId }] })
.set('Authorization', `Bearer ${user1.accessToken}`);
expect(status).toBe(400);
expect(body).toEqual(errorDto.badRequest('Cannot share album with owner'));
expect(status).toBe(201);
expect(body.albumUsers).toHaveLength(1);
expect(body.albumUsers[0]).toMatchObject({ role: AlbumUserRole.Owner, user: { id: user1.userId } });
});
});
@@ -729,8 +731,8 @@ describe('/albums', () => {
.set('Authorization', `Bearer ${user1.accessToken}`)
.send({ albumUsers: [{ userId: user1.userId, role: AlbumUserRole.Editor }] });
expect(status).toBe(400);
expect(body).toEqual(errorDto.badRequest('User already added'));
expect(status).toBe(200);
expect(body.albumUsers.length).toEqual(1);
});
it('should not be able to add existing user to shared album', async () => {
@@ -744,8 +746,8 @@ describe('/albums', () => {
.set('Authorization', `Bearer ${user1.accessToken}`)
.send({ albumUsers: [{ userId: user2.userId, role: AlbumUserRole.Editor }] });
expect(status).toBe(400);
expect(body).toEqual(errorDto.badRequest('User already added'));
expect(status).toBe(200);
expect(body.albumUsers.length).toEqual(2);
});
});
@@ -795,5 +797,22 @@ describe('/albums', () => {
expect(status).toBe(400);
expect(body).toEqual(errorDto.badRequest('Not found or no album.share access'));
});
it('should not allow an editor to change the role of an owner', async () => {
const album = await utils.createAlbum(user1.accessToken, {
albumName: 'testAlbum',
albumUsers: [{ userId: user2.userId, role: AlbumUserRole.Editor }],
});
expect(album.albumUsers[1].role).toEqual(AlbumUserRole.Editor);
const { status, body } = await request(app)
.put(`/albums/${album.id}/user/${user1.userId}`)
.set('Authorization', `Bearer ${user2.accessToken}`)
.send({ role: AlbumUserRole.Editor });
expect(status).toBe(400);
expect(body).toEqual(errorDto.badRequest('User is owner'));
});
});
});

View File

@@ -781,7 +781,7 @@ describe('/asset', () => {
exifImageWidth: 4032,
exifImageHeight: 3024,
latitude: 41.2203,
longitude: -96.071_625,
longitude: -96.071625,
make: 'Apple',
model: 'iPhone 7',
lensModel: 'iPhone 7 back camera 3.99mm f/1.8',
@@ -973,9 +973,9 @@ describe('/asset', () => {
fileSizeInByte: 31_175_472,
focalLength: 18.3,
iso: 100,
latitude: 36.613_24,
latitude: 36.61324,
lensModel: '18.3mm F2.8',
longitude: -121.897_85,
longitude: -121.89785,
make: 'RICOH IMAGING COMPANY, LTD.',
model: 'RICOH GR III',
orientation: '1',

View File

@@ -0,0 +1,669 @@
import {
AssetMediaResponseDto,
IntegrityReportResponseDto,
LoginResponseDto,
ManualJobName,
QueueCommand,
QueueName,
} from '@immich/sdk';
import { readFile } from 'node:fs/promises';
import { app, testAssetDir, utils } from 'src/utils';
import request from 'supertest';
import { afterEach, beforeAll, describe, expect, it } from 'vitest';
const assetFilepath = `${testAssetDir}/metadata/gps-position/thompson-springs.jpg`;
const asset1Filepath = `${testAssetDir}/albums/nature/el_torcal_rocks.jpg`;
const asset2Filepath = `${testAssetDir}/albums/nature/wood_anemones.jpg`;
describe('/admin/integrity', () => {
let admin: LoginResponseDto;
let asset: AssetMediaResponseDto;
let user1: LoginResponseDto;
let asset1: AssetMediaResponseDto;
let user2: LoginResponseDto;
let asset2: AssetMediaResponseDto;
beforeAll(async () => {
await utils.resetDatabase();
admin = await utils.adminSetup();
user1 = await utils.userSetup(admin.accessToken, {
email: '1@example.com',
name: '1',
password: '1',
});
user2 = await utils.userSetup(admin.accessToken, {
email: '2@example.com',
name: '2',
password: '2',
});
for (const queue of Object.values(QueueName)) {
if (queue === QueueName.IntegrityCheck) {
continue;
}
await utils.queueCommand(admin.accessToken, queue, {
command: QueueCommand.Pause,
});
}
asset = await utils.createAsset(admin.accessToken, {
assetData: {
filename: 'asset.jpg',
bytes: await readFile(assetFilepath),
},
});
asset1 = await utils.createAsset(user1.accessToken, {
assetData: {
filename: 'asset.jpg',
bytes: await readFile(asset1Filepath),
},
});
asset2 = await utils.createAsset(user2.accessToken, {
assetData: {
filename: 'asset.jpg',
bytes: await readFile(asset2Filepath),
},
});
await utils.mkFolder('/data/bak');
await utils.copyFolder(`/data/upload/${admin.userId}`, `/data/bak/${admin.userId}`);
for (const queue of Object.values(QueueName)) {
if (queue === QueueName.IntegrityCheck) {
continue;
}
await utils.queueCommand(admin.accessToken, queue, {
command: QueueCommand.Empty,
});
await utils.queueCommand(admin.accessToken, queue, {
command: QueueCommand.Resume,
});
}
});
afterEach(async () => {
await utils.deleteFolder(`/data/upload/${admin.userId}`);
await utils.copyFolder(`/data/bak/${admin.userId}`, `/data/upload/${admin.userId}`);
});
describe('POST /summary (& jobs)', async () => {
it.sequential('reports no issues', async () => {
await utils.createJob(admin.accessToken, {
name: ManualJobName.IntegrityUntrackedFiles,
});
await utils.createJob(admin.accessToken, {
name: ManualJobName.IntegrityMissingFiles,
});
await utils.createJob(admin.accessToken, {
name: ManualJobName.IntegrityChecksumMismatch,
});
await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck);
await utils.createJob(admin.accessToken, {
name: ManualJobName.IntegrityUntrackedFilesDeleteAll,
});
await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck);
const { status, body } = await request(app)
.get('/admin/integrity/summary')
.set('Authorization', `Bearer ${admin.accessToken}`)
.send();
expect(status).toBe(200);
expect(body).toEqual({
missing_file: 0,
untracked_file: 0,
checksum_mismatch: 0,
});
});
it.sequential('should detect an untracked file (job: check untracked files)', async () => {
await utils.putTextFile('untracked', `/data/upload/${admin.userId}/untracked1.png`);
await utils.createJob(admin.accessToken, {
name: ManualJobName.IntegrityUntrackedFiles,
});
await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck);
const { status, body } = await request(app)
.get('/admin/integrity/summary')
.set('Authorization', `Bearer ${admin.accessToken}`)
.send();
expect(status).toBe(200);
expect(body).toEqual(
expect.objectContaining({
untracked_file: 1,
}),
);
});
it.sequential('should detect outdated untracked file reports (job: refresh untracked files)', async () => {
// these should not be detected:
await utils.putTextFile('untracked', `/data/upload/${admin.userId}/untracked2.png`);
await utils.putTextFile('untracked', `/data/upload/${admin.userId}/untracked3.png`);
await utils.createJob(admin.accessToken, {
name: ManualJobName.IntegrityUntrackedFilesRefresh,
});
await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck);
const { status, body } = await request(app)
.get('/admin/integrity/summary')
.set('Authorization', `Bearer ${admin.accessToken}`)
.send();
expect(status).toBe(200);
expect(body).toEqual(
expect.objectContaining({
untracked_file: 0,
}),
);
});
it.sequential('should delete untracked files (job: delete all untracked file reports)', async () => {
await utils.putTextFile('untracked', `/data/upload/${admin.userId}/untracked1.png`);
await utils.createJob(admin.accessToken, {
name: ManualJobName.IntegrityUntrackedFiles,
});
await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck);
await utils.createJob(admin.accessToken, {
name: ManualJobName.IntegrityUntrackedFilesDeleteAll,
});
await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck);
const { status, body } = await request(app)
.get('/admin/integrity/summary')
.set('Authorization', `Bearer ${admin.accessToken}`)
.send();
expect(status).toBe(200);
expect(body).toEqual(
expect.objectContaining({
untracked_file: 0,
}),
);
});
it.sequential('should detect a missing file and not a checksum mismatch (job: check missing files)', async () => {
await utils.deleteFolder(`/data/upload/${admin.userId}`);
await utils.createJob(admin.accessToken, {
name: ManualJobName.IntegrityMissingFiles,
});
await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck);
const { status, body } = await request(app)
.get('/admin/integrity/summary')
.set('Authorization', `Bearer ${admin.accessToken}`)
.send();
expect(status).toBe(200);
expect(body).toEqual(
expect.objectContaining({
missing_file: 1,
checksum_mismatch: 0,
}),
);
});
it.sequential('should detect outdated missing file reports (job: refresh missing files)', async () => {
await utils.createJob(admin.accessToken, {
name: ManualJobName.IntegrityMissingFilesRefresh,
});
await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck);
const { status, body } = await request(app)
.get('/admin/integrity/summary')
.set('Authorization', `Bearer ${admin.accessToken}`)
.send();
expect(status).toBe(200);
expect(body).toEqual(
expect.objectContaining({
missing_file: 0,
checksum_mismatch: 0,
}),
);
});
it.sequential('should delete assets with missing files (job: delete all missing file reports)', async () => {
await utils.deleteFolder(`/data/upload/${user1.userId}`);
await utils.createJob(admin.accessToken, {
name: ManualJobName.IntegrityMissingFiles,
});
await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck);
const { status: listStatus, body: listBody } = await request(app)
.get('/admin/integrity/summary')
.set('Authorization', `Bearer ${admin.accessToken}`)
.send();
expect(listStatus).toBe(200);
expect(listBody).toEqual(
expect.objectContaining({
missing_file: 1,
}),
);
await utils.createJob(admin.accessToken, {
name: ManualJobName.IntegrityMissingFilesDeleteAll,
});
await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck);
const { status, body } = await request(app)
.get('/admin/integrity/summary')
.set('Authorization', `Bearer ${admin.accessToken}`)
.send();
expect(status).toBe(200);
expect(body).toEqual(
expect.objectContaining({
missing_file: 0,
}),
);
await expect(utils.getAssetInfo(user1.accessToken, asset1.id)).resolves.toEqual(
expect.objectContaining({
isTrashed: true,
}),
);
});
it.sequential('should detect a checksum mismatch (job: check file checksums)', async () => {
await utils.truncateFolder(`/data/upload/${admin.userId}`);
await utils.createJob(admin.accessToken, {
name: ManualJobName.IntegrityChecksumMismatch,
});
await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck);
const { status, body } = await request(app)
.get('/admin/integrity/summary')
.set('Authorization', `Bearer ${admin.accessToken}`)
.send();
expect(status).toBe(200);
expect(body).toEqual(
expect.objectContaining({
checksum_mismatch: 1,
}),
);
});
it.sequential('should detect outdated checksum mismatch reports (job: refresh file checksums)', async () => {
await utils.createJob(admin.accessToken, {
name: ManualJobName.IntegrityChecksumMismatchRefresh,
});
await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck);
const { status, body } = await request(app)
.get('/admin/integrity/summary')
.set('Authorization', `Bearer ${admin.accessToken}`)
.send();
expect(status).toBe(200);
expect(body).toEqual(
expect.objectContaining({
checksum_mismatch: 0,
}),
);
});
it.sequential(
'should delete assets with mismatched checksum (job: delete all checksum mismatch reports)',
async () => {
await utils.truncateFolder(`/data/upload/${user2.userId}`);
await utils.createJob(admin.accessToken, {
name: ManualJobName.IntegrityChecksumMismatch,
});
await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck);
const { status: listStatus, body: listBody } = await request(app)
.get('/admin/integrity/summary')
.set('Authorization', `Bearer ${admin.accessToken}`)
.send();
expect(listStatus).toBe(200);
expect(listBody).toEqual(
expect.objectContaining({
checksum_mismatch: 1,
}),
);
await utils.createJob(admin.accessToken, {
name: ManualJobName.IntegrityChecksumMismatchDeleteAll,
});
await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck);
const { status, body } = await request(app)
.get('/admin/integrity/summary')
.set('Authorization', `Bearer ${admin.accessToken}`)
.send();
expect(status).toBe(200);
expect(body).toEqual(
expect.objectContaining({
checksum_mismatch: 0,
}),
);
await expect(utils.getAssetInfo(user2.accessToken, asset2.id)).resolves.toEqual(
expect.objectContaining({
isTrashed: true,
}),
);
},
);
});
describe('POST /report', async () => {
it.sequential('reports untracked files', async () => {
await utils.putTextFile('untracked', `/data/upload/${admin.userId}/untracked1.png`);
await utils.createJob(admin.accessToken, {
name: ManualJobName.IntegrityUntrackedFiles,
});
await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck);
const { status, body } = await request(app)
.get('/admin/integrity/report?type=untracked_file')
.set('Authorization', `Bearer ${admin.accessToken}`)
.send();
expect(status).toBe(200);
expect(body).toEqual({
nextCursor: undefined,
items: expect.arrayContaining([
{
id: expect.any(String),
type: 'untracked_file',
path: `/data/upload/${admin.userId}/untracked1.png`,
assetId: null,
fileAssetId: null,
createdAt: expect.any(String),
},
]),
});
});
it.sequential('reports missing files', async () => {
await utils.deleteFolder(`/data/upload/${admin.userId}`);
await utils.createJob(admin.accessToken, {
name: ManualJobName.IntegrityMissingFiles,
});
await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck);
const { status, body } = await request(app)
.get('/admin/integrity/report?type=missing_file')
.set('Authorization', `Bearer ${admin.accessToken}`)
.send();
expect(status).toBe(200);
expect(body).toEqual({
nextCursor: undefined,
items: expect.arrayContaining([
{
id: expect.any(String),
type: 'missing_file',
path: expect.any(String),
assetId: asset.id,
fileAssetId: null,
createdAt: expect.any(String),
},
]),
});
});
it.sequential('reports checksum mismatched files', async () => {
await utils.truncateFolder(`/data/upload/${admin.userId}`);
await utils.createJob(admin.accessToken, {
name: ManualJobName.IntegrityChecksumMismatch,
});
await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck);
const { status, body } = await request(app)
.get('/admin/integrity/report?type=checksum_mismatch')
.set('Authorization', `Bearer ${admin.accessToken}`)
.send();
expect(status).toBe(200);
expect(body).toEqual({
nextCursor: undefined,
items: expect.arrayContaining([
{
id: expect.any(String),
type: 'checksum_mismatch',
path: expect.any(String),
assetId: asset.id,
fileAssetId: null,
createdAt: expect.any(String),
},
]),
});
});
});
describe('DELETE /report/:id', async () => {
it.sequential('delete untracked files', async () => {
await utils.putTextFile('untracked', `/data/upload/${admin.userId}/untracked1.png`);
await utils.createJob(admin.accessToken, {
name: ManualJobName.IntegrityUntrackedFiles,
});
await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck);
const { status: listStatus, body: listBody } = await request(app)
.get('/admin/integrity/report?type=untracked_file')
.set('Authorization', `Bearer ${admin.accessToken}`)
.send();
expect(listStatus).toBe(200);
const report = (listBody as IntegrityReportResponseDto).items.find(
(item) => item.path === `/data/upload/${admin.userId}/untracked1.png`,
)!;
const { status } = await request(app)
.delete(`/admin/integrity/report/${report.id}`)
.set('Authorization', `Bearer ${admin.accessToken}`)
.send();
expect(status).toBe(200);
await utils.createJob(admin.accessToken, {
name: ManualJobName.IntegrityUntrackedFiles,
});
await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck);
const { status: listStatus2, body: listBody2 } = await request(app)
.get('/admin/integrity/report?type=untracked_file')
.set('Authorization', `Bearer ${admin.accessToken}`)
.send();
expect(listStatus2).toBe(200);
expect(listBody2).not.toBe(
expect.objectContaining({
items: expect.arrayContaining([
expect.objectContaining({
id: report.id,
}),
]),
}),
);
});
it.sequential('delete assets missing files', async () => {
await utils.deleteFolder(`/data/upload/${admin.userId}`);
await utils.createJob(admin.accessToken, {
name: ManualJobName.IntegrityMissingFiles,
});
await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck);
const { status: listStatus, body: listBody } = await request(app)
.get('/admin/integrity/report?type=missing_file')
.set('Authorization', `Bearer ${admin.accessToken}`)
.send();
expect(listStatus).toBe(200);
expect(listBody.items.length).toBe(1);
const report = (listBody as IntegrityReportResponseDto).items[0];
const { status } = await request(app)
.delete(`/admin/integrity/report/${report.id}`)
.set('Authorization', `Bearer ${admin.accessToken}`)
.send();
expect(status).toBe(200);
await utils.createJob(admin.accessToken, {
name: ManualJobName.IntegrityMissingFiles,
});
await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck);
const { status: listStatus2, body: listBody2 } = await request(app)
.get('/admin/integrity/report?type=missing_file')
.set('Authorization', `Bearer ${admin.accessToken}`)
.send();
expect(listStatus2).toBe(200);
expect(listBody2.items.length).toBe(0);
});
it.sequential('delete assets with failing checksum', async () => {
await utils.truncateFolder(`/data/upload/${admin.userId}`);
await utils.createJob(admin.accessToken, {
name: ManualJobName.IntegrityChecksumMismatch,
});
await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck);
const { status: listStatus, body: listBody } = await request(app)
.get('/admin/integrity/report?type=checksum_mismatch')
.set('Authorization', `Bearer ${admin.accessToken}`)
.send();
expect(listStatus).toBe(200);
expect(listBody.items.length).toBe(1);
const report = (listBody as IntegrityReportResponseDto).items[0];
const { status } = await request(app)
.delete(`/admin/integrity/report/${report.id}`)
.set('Authorization', `Bearer ${admin.accessToken}`)
.send();
expect(status).toBe(200);
await utils.createJob(admin.accessToken, {
name: ManualJobName.IntegrityChecksumMismatch,
});
await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck);
const { status: listStatus2, body: listBody2 } = await request(app)
.get('/admin/integrity/report?type=checksum_mismatch')
.set('Authorization', `Bearer ${admin.accessToken}`)
.send();
expect(listStatus2).toBe(200);
expect(listBody2.items.length).toBe(0);
});
});
describe('GET /report/:type/csv', () => {
it.sequential('exports untracked files as csv', async () => {
await utils.putTextFile('untracked', `/data/upload/${admin.userId}/untracked1.png`);
await utils.createJob(admin.accessToken, {
name: ManualJobName.IntegrityUntrackedFiles,
});
await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck);
const { status, headers, text } = await request(app)
.get('/admin/integrity/report/untracked_file/csv')
.set('Authorization', `Bearer ${admin.accessToken}`)
.send();
expect(status).toBe(200);
expect(headers['content-type']).toContain('text/csv');
expect(headers['content-disposition']).toContain('.csv');
expect(text).toContain('id,type,assetId,fileAssetId,path');
expect(text).toContain(`untracked_file`);
expect(text).toContain(`/data/upload/${admin.userId}/untracked1.png`);
});
});
describe('GET /report/:id/file', () => {
it.sequential('downloads untracked file', async () => {
await utils.putTextFile('untracked-content', `/data/upload/${admin.userId}/untracked1.png`);
await utils.createJob(admin.accessToken, {
name: ManualJobName.IntegrityUntrackedFiles,
});
await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck);
const { body: listBody } = await request(app)
.get('/admin/integrity/report?type=untracked_file')
.set('Authorization', `Bearer ${admin.accessToken}`)
.send();
const report = (listBody as IntegrityReportResponseDto).items.find(
(item) => item.path === `/data/upload/${admin.userId}/untracked1.png`,
)!;
const { status, headers, body } = await request(app)
.get(`/admin/integrity/report/${report.id}/file`)
.set('Authorization', `Bearer ${admin.accessToken}`)
.buffer(true)
.send();
expect(status).toBe(200);
expect(headers['content-type']).toContain('application/octet-stream');
expect(body.toString()).toBe('untracked-content');
});
});
});

View File

@@ -2,6 +2,7 @@ import { AssetVisibility, LoginResponseDto } from '@immich/sdk';
import { readFile } from 'node:fs/promises';
import { basename, join } from 'node:path';
import { Socket } from 'socket.io-client';
import { createUserDto } from 'src/fixtures';
import { errorDto } from 'src/responses';
import { app, testAssetDir, utils } from 'src/utils';
import request from 'supertest';
@@ -9,28 +10,48 @@ import { afterAll, beforeAll, describe, expect, it } from 'vitest';
describe('/map', () => {
let websocket: Socket;
let partnerWebsocket: Socket;
let admin: LoginResponseDto;
let partner: LoginResponseDto;
let partnerArchivedAssetId: string;
let adminArchivedAssetId: string;
beforeAll(async () => {
await utils.resetDatabase();
admin = await utils.adminSetup({ onboarding: false });
partner = await utils.userSetup(admin.accessToken, createUserDto.user1);
websocket = await utils.connectWebsocket(admin.accessToken);
partnerWebsocket = await utils.connectWebsocket(partner.accessToken);
const files = ['formats/heic/IMG_2682.heic', 'metadata/gps-position/thompson-springs.jpg'];
const adminFiles = ['formats/heic/IMG_2682.heic', 'metadata/gps-position/thompson-springs.jpg'];
const adminArchivedFile = 'metadata/dates/datetimeoriginal-gps.jpg';
const partnerFile = 'metadata/gps-position/thompson-springs.jpg';
utils.resetEvents();
const uploadFile = async (input: string) => {
const uploadFile = async (accessToken: string, input: string) => {
const filepath = join(testAssetDir, input);
const { id } = await utils.createAsset(admin.accessToken, {
const { id } = await utils.createAsset(accessToken, {
assetData: { bytes: await readFile(filepath), filename: basename(filepath) },
});
await utils.waitForWebsocketEvent({ event: 'assetUpload', id });
return id;
};
await Promise.all(files.map((f) => uploadFile(f)));
await Promise.all(adminFiles.map((f) => uploadFile(admin.accessToken, f)));
[adminArchivedAssetId, partnerArchivedAssetId] = await Promise.all([
uploadFile(admin.accessToken, adminArchivedFile),
uploadFile(partner.accessToken, partnerFile),
]);
await Promise.all([
utils.archiveAssets(admin.accessToken, [adminArchivedAssetId]),
utils.archiveAssets(partner.accessToken, [partnerArchivedAssetId]),
utils.createPartner(partner.accessToken, admin.userId),
]);
});
afterAll(() => {
utils.disconnectWebsocket(websocket);
utils.disconnectWebsocket(partnerWebsocket);
});
describe('GET /map/markers', () => {
@@ -40,7 +61,6 @@ describe('/map', () => {
expect(body).toEqual(errorDto.unauthorized);
});
// TODO archive one of these assets
it('should get map markers for all non-archived assets', async () => {
const { status, body } = await request(app)
.get('/map/markers')
@@ -55,7 +75,7 @@ describe('/map', () => {
country: 'United States of America',
id: expect.any(String),
lat: expect.closeTo(39.115),
lon: expect.closeTo(-108.400_968),
lon: expect.closeTo(-108.400968),
state: 'Colorado',
},
{
@@ -63,13 +83,34 @@ describe('/map', () => {
country: 'United States of America',
id: expect.any(String),
lat: expect.closeTo(41.2203),
lon: expect.closeTo(-96.071_625),
lon: expect.closeTo(-96.071625),
state: 'Nebraska',
},
]);
});
// TODO archive one of these assets
it('should not expose partner archived asset locations', async () => {
const { status, body } = await request(app)
.get('/map/markers')
.query({ withPartners: true, isArchived: true })
.set('Authorization', `Bearer ${admin.accessToken}`);
expect(status).toBe(200);
const ids = body.map((m: { id: string }) => m.id);
expect(ids).not.toContain(partnerArchivedAssetId);
expect(ids).toContain(adminArchivedAssetId);
});
it('should include own archived asset locations', async () => {
const { status, body } = await request(app)
.get('/map/markers')
.query({ isArchived: true })
.set('Authorization', `Bearer ${admin.accessToken}`);
expect(status).toBe(200);
expect(body.map((m: { id: string }) => m.id)).toContain(adminArchivedAssetId);
});
it('should get all map markers', async () => {
const { status, body } = await request(app)
.get('/map/markers')
@@ -82,7 +123,7 @@ describe('/map', () => {
country: 'United States of America',
id: expect.any(String),
lat: expect.closeTo(39.115),
lon: expect.closeTo(-108.400_968),
lon: expect.closeTo(-108.400968),
state: 'Colorado',
},
{
@@ -90,7 +131,7 @@ describe('/map', () => {
country: 'United States of America',
id: expect.any(String),
lat: expect.closeTo(41.2203),
lon: expect.closeTo(-96.071_625),
lon: expect.closeTo(-96.071625),
state: 'Nebraska',
},
]);
@@ -147,20 +188,20 @@ describe('/map', () => {
const reverseGeocodeTestCases = [
{
name: 'Vaucluse',
lat: -33.858_977_058_663_13,
lon: 151.278_490_730_270_48,
lat: -33.85897705866313,
lon: 151.27849073027048,
results: [{ city: 'Vaucluse', state: 'New South Wales', country: 'Australia' }],
},
{
name: 'Ravenhall',
lat: -37.765_732_399_174_75,
lon: 144.752_453_164_883_3,
lat: -37.76573239917475,
lon: 144.7524531648833,
results: [{ city: 'Ravenhall', state: 'Victoria', country: 'Australia' }],
},
{
name: 'Scarborough',
lat: -31.894_346_156_789_997,
lon: 115.757_617_103_904_64,
lat: -31.894346156789997,
lon: 115.75761710390464,
results: [{ city: 'Scarborough', state: 'Western Australia', country: 'Australia' }],
},
];

View File

@@ -44,7 +44,7 @@ const loginWithOAuth = async (sub: OAuthUser | string, redirectUri?: string) =>
});
// login
const response1 = await redirect(url.replace(authServer.internal, authServer.external));
const response1 = await redirect(url.replace(authServer.internal, () => authServer.external));
const response2 = await request(authServer.external + response1.location)
.post('')
.set('Cookie', response1.cookies)

View File

@@ -4,6 +4,7 @@ import {
AssetVisibility,
deleteAssets,
LoginResponseDto,
SharedLinkType,
updateAsset,
} from '@immich/sdk';
import { DateTime } from 'luxon';
@@ -86,23 +87,23 @@ describe('/search', () => {
// note: the coordinates here are not the actual coordinates of the images and are random for most of them
const coordinates = [
{ latitude: 48.853_41, longitude: 2.3488 }, // paris
{ latitude: 35.6895, longitude: 139.691_71 }, // tokyo
{ latitude: 52.524_37, longitude: 13.410_53 }, // berlin
{ latitude: 1.314_663_1, longitude: 103.845_409_3 }, // singapore
{ latitude: 41.013_84, longitude: 28.949_66 }, // istanbul
{ latitude: 5.556_02, longitude: -0.1969 }, // accra
{ latitude: 37.544_270_6, longitude: -4.727_752_8 }, // andalusia
{ latitude: 23.133_02, longitude: -82.383_04 }, // havana
{ latitude: 41.694_11, longitude: 44.833_68 }, // tbilisi
{ latitude: 31.222_22, longitude: 121.458_06 }, // shanghai
{ latitude: 48.85341, longitude: 2.3488 }, // paris
{ latitude: 35.6895, longitude: 139.69171 }, // tokyo
{ latitude: 52.52437, longitude: 13.41053 }, // berlin
{ latitude: 1.3146631, longitude: 103.8454093 }, // singapore
{ latitude: 41.01384, longitude: 28.94966 }, // istanbul
{ latitude: 5.55602, longitude: -0.1969 }, // accra
{ latitude: 37.5442706, longitude: -4.7277528 }, // andalusia
{ latitude: 23.13302, longitude: -82.38304 }, // havana
{ latitude: 41.69411, longitude: 44.83368 }, // tbilisi
{ latitude: 31.22222, longitude: 121.45806 }, // shanghai
{ latitude: 38.9711, longitude: -109.7137 }, // thompson springs
{ latitude: 40.714_27, longitude: -74.005_97 }, // new york
{ latitude: 47.040_57, longitude: 9.068_04 }, // glarus
{ latitude: 32.771_52, longitude: -89.116_73 }, // philadelphia
{ latitude: 31.634_16, longitude: -7.999_94 }, // marrakesh
{ latitude: 38.523_735_4, longitude: -78.488_619_4 }, // tanners ridge
{ latitude: 59.938_63, longitude: 30.314_13 }, // st. petersburg
{ latitude: 40.71427, longitude: -74.00597 }, // new york
{ latitude: 47.04057, longitude: 9.06804 }, // glarus
{ latitude: 32.77152, longitude: -89.11673 }, // philadelphia
{ latitude: 31.63416, longitude: -7.99994 }, // marrakesh
{ latitude: 38.5237354, longitude: -78.4886194 }, // tanners ridge
{ latitude: 59.93863, longitude: 30.31413 }, // st. petersburg
{ latitude: 0, longitude: 0 }, // null island
];
@@ -111,7 +112,7 @@ describe('/search', () => {
);
await Promise.all(updates);
for (const [i] of coordinates.entries()) {
for (const i of coordinates.keys()) {
await utils.waitForWebsocketEvent({ event: 'assetUpdate', id: assets[i].id });
}
@@ -259,17 +260,6 @@ describe('/search', () => {
assets: [assetHeic],
}),
},
{
should: "should search city ('')",
deferred: () => ({
dto: {
city: '',
visibility: AssetVisibility.Timeline,
includeNull: true,
},
assets: [assetLast],
}),
},
{
should: 'should search city (null)',
deferred: () => ({
@@ -291,18 +281,6 @@ describe('/search', () => {
assets: [assetDensity],
}),
},
{
should: "should search state ('')",
deferred: () => ({
dto: {
state: '',
visibility: AssetVisibility.Timeline,
withExif: true,
includeNull: true,
},
assets: [assetLast, assetNotocactus],
}),
},
{
should: 'should search state (null)',
deferred: () => ({
@@ -324,17 +302,6 @@ describe('/search', () => {
assets: [assetFalcon],
}),
},
{
should: "should search country ('')",
deferred: () => ({
dto: {
country: '',
visibility: AssetVisibility.Timeline,
includeNull: true,
},
assets: [assetLast],
}),
},
{
should: 'should search country (null)',
deferred: () => ({
@@ -391,6 +358,32 @@ describe('/search', () => {
expect(body.assets.items).toHaveLength(assets.length);
});
}
it('should reject shared link access without an album filter', async () => {
const album = await utils.createAlbum(admin.accessToken, { albumName: 'foo' });
const sharedLink = await utils.createSharedLink(admin.accessToken, {
type: SharedLinkType.Album,
albumId: album.id,
});
const { status, body } = await request(app).post(`/search/metadata?key=${sharedLink.key}`).send({});
expect(status).toBe(400);
expect(body).toEqual({ message: 'Shared link access is only allowed in combination with an albumIds filter' });
});
it('should allow shared link access for albums', async () => {
const asset = await utils.createAsset(admin.accessToken);
const album = await utils.createAlbum(admin.accessToken, { albumName: 'foo', assetIds: [asset.id] });
const sharedLink = await utils.createSharedLink(admin.accessToken, {
type: SharedLinkType.Album,
albumId: album.id,
});
const { status, body } = await request(app)
.post(`/search/metadata?key=${sharedLink.key}`)
.send({ albumIds: [album.id] });
expect(status).toBe(200);
expect(body.assets.items).toEqual([expect.objectContaining({ id: asset.id })]);
});
});
describe('POST /search/random', () => {

View File

@@ -91,11 +91,14 @@ describe('/server', () => {
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),
});
expect(body).toEqual(
expect.objectContaining({
major: expect.any(Number),
minor: expect.any(Number),
patch: expect.any(Number),
}),
);
expect(Object.keys(body)).toEqual(expect.arrayContaining(['major', 'minor', 'patch', 'prerelease']));
});
});
@@ -115,6 +118,7 @@ describe('/server', () => {
oauthAutoLaunch: false,
ocr: false,
passwordLogin: true,
realtimeTranscoding: false,
search: true,
sidecar: true,
trash: true,
@@ -139,6 +143,7 @@ describe('/server', () => {
maintenanceMode: false,
mapDarkStyleUrl: 'https://tiles.immich.cloud/v1/style/dark.json',
mapLightStyleUrl: 'https://tiles.immich.cloud/v1/style/light.json',
minFaces: 3,
});
});
});

View File

@@ -21,18 +21,18 @@ describe('/system-config', () => {
const response1 = await request(app)
.put('/system-config')
.set('Authorization', `Bearer ${admin.accessToken}`)
.send({ ...config, newVersionCheck: { enabled: false } });
.send({ ...config, newVersionCheck: { enabled: false, channel: 'stable' } });
expect(response1.status).toBe(200);
expect(response1.body).toEqual({ ...config, newVersionCheck: { enabled: false } });
expect(response1.body).toEqual({ ...config, newVersionCheck: { enabled: false, channel: 'stable' } });
const response2 = await request(app)
.put('/system-config')
.set('Authorization', `Bearer ${admin.accessToken}`)
.send({ ...config, newVersionCheck: { enabled: true } });
.send({ ...config, newVersionCheck: { enabled: true, channel: 'stable' } });
expect(response2.status).toBe(200);
expect(response2.body).toEqual({ ...config, newVersionCheck: { enabled: true } });
expect(response2.body).toEqual({ ...config, newVersionCheck: { enabled: true, channel: 'stable' } });
});
it('should reject an invalid config entry', async () => {

View File

@@ -230,6 +230,21 @@ describe('/users', () => {
const after = await getMyPreferences({ headers: asBearerAuth(admin.accessToken) });
expect(after).toMatchObject({ download: { includeEmbeddedVideos: true } });
});
it('should update minimum face count to display people', async () => {
const before = await getMyPreferences({ headers: asBearerAuth(admin.accessToken) });
expect(before).toMatchObject({ people: { minimumFaces: 3 } });
const { status, body } = await request(app)
.put('/users/me/preferences')
.send({ people: { minimumFaces: 2 } })
.set('Authorization', `Bearer ${admin.accessToken}`);
expect(status).toBe(200);
expect(body).toMatchObject({ people: { minimumFaces: 2 } });
const after = await getMyPreferences({ headers: asBearerAuth(admin.accessToken) });
expect(after).toMatchObject({ people: { minimumFaces: 2 } });
});
});
describe('GET /users/:id', () => {

View File

@@ -31,7 +31,7 @@ describe(`immich login`, () => {
it('should login and save auth.yml with 600', async () => {
const admin = await utils.adminSetup();
const key = await utils.createApiKey(admin.accessToken, [Permission.All]);
const { stdout, stderr, exitCode } = await immichCli(['login', app, `${key.secret}`]);
const { stdout, stderr, exitCode } = await immichCli(['login', app, key.secret]);
expect(stdout.split('\n')).toEqual([
'Logging in to http://127.0.0.1:2285/api',
'Logged in as admin@immich.cloud',
@@ -48,7 +48,7 @@ describe(`immich login`, () => {
it('should login without /api in the url', async () => {
const admin = await utils.adminSetup();
const key = await utils.createApiKey(admin.accessToken, [Permission.All]);
const { stdout, stderr, exitCode } = await immichCli(['login', app.replaceAll('/api', ''), `${key.secret}`]);
const { stdout, stderr, exitCode } = await immichCli(['login', app.replaceAll('/api', ''), key.secret]);
expect(stdout.split('\n')).toEqual([
'Logging in to http://127.0.0.1:2285',
'Discovered API at http://127.0.0.1:2285/api',

View File

@@ -119,7 +119,9 @@ describe(`immich upload`, () => {
const baseDir = `/tmp/upload/`;
const testPaths = Object.keys(files).map((filePath) => `${baseDir}/${filePath}`);
testPaths.map((filePath) => utils.createImageFile(filePath));
for (const filePath of testPaths) {
utils.createImageFile(filePath);
}
const commandLine = paths.map((argument) => `${baseDir}/${argument}`);
@@ -135,7 +137,9 @@ describe(`immich upload`, () => {
const assets = await getAssetStatistics({}, { headers: asKeyAuth(key) });
expect(assets.total).toBe(expectedCount);
testPaths.map((filePath) => utils.removeImageFile(filePath));
for (const filePath of testPaths) {
utils.removeImageFile(filePath);
}
});
}
});

View File

@@ -0,0 +1,41 @@
import { LoginResponseDto, ManualJobName, QueueName } from '@immich/sdk';
import { expect, test } from '@playwright/test';
import { utils } from 'src/utils';
test.describe.configure({ mode: 'serial' });
test.describe.skip('Integrity', () => {
let admin: LoginResponseDto;
test.beforeAll(async () => {
utils.initSdk();
await utils.resetDatabase();
admin = await utils.adminSetup();
});
test('run integrity jobs to update stats', async ({ context, page }) => {
await utils.setAuthCookies(context, admin.accessToken);
await utils.createJob(admin.accessToken, {
name: ManualJobName.IntegrityUntrackedFiles,
});
await utils.waitForQueueFinish(admin.accessToken, QueueName.IntegrityCheck);
await page.goto('/admin/maintenance');
const count = page.getByText('Untracked Files').locator('..').locator('..').locator('div').nth(1);
const previousCount = Number.parseInt((await count.textContent()) ?? '');
await utils.mkFolder(`/data/upload/${admin.userId}`);
await utils.putTextFile('untracked', `/data/upload/${admin.userId}/untracked1.png`);
const checkButton = page.getByText('Integrity Report').locator('..').getByRole('button', { name: 'Check All' });
await checkButton.click();
await expect(checkButton).toBeEnabled();
await expect(count).toContainText((previousCount + 1).toString());
});
});

View File

@@ -21,7 +21,7 @@ export const randomImageFromString = async (
let seedNumber = 0;
for (let i = 0; i < seed.length; i++) {
seedNumber = (seedNumber << 5) - seedNumber + (seed.codePointAt(i) ?? 0);
seedNumber = seedNumber & seedNumber; // Convert to 32bit integer
seedNumber &= seedNumber; // Convert to 32bit integer
}
return randomImage(new SeededRandom(Math.abs(seedNumber)), { width, height });
};

View File

@@ -64,7 +64,7 @@ export function generateAsset(
const asset: MockTimelineAsset = {
id: assetId,
ownerId,
ratio: Number.parseFloat(ratio.split(':')[0]) / Number.parseFloat(ratio.split(':')[1]),
ratio: Number(ratio.split(':', 1)[0]) / Number(ratio.split(':', 2)[1]),
thumbhash: generateThumbhash(rng),
localDateTime: date.toISOString(),
fileCreatedAt: date.toISOString(),
@@ -214,7 +214,7 @@ export function generateTimelineData(config: TimelineConfig): MockTimelineData {
}
// Create a mock album from random assets
const allAssets = [...buckets.values()].flat();
const allAssets = buckets.values().toArray().flat();
// Select 10-30 random assets for the album (or all assets if less than 10)
const albumSize = Math.min(allAssets.length, globalRng.nextInt(10, 31));

View File

@@ -172,11 +172,7 @@ function shouldIncludeAsset(
if (isArchived !== undefined && actuallyArchived !== isArchived) {
return false;
}
if (isFavorite !== undefined && actuallyFavorited !== isFavorite) {
return false;
}
return true;
return isFavorite === undefined || actuallyFavorited === isFavorite;
}
/**
* Get summary for all buckets (mimics getTimeBuckets API)
@@ -361,7 +357,7 @@ export function getAsset(
owner?: UserResponseDto,
): AssetResponseDto | undefined {
// Search through all buckets for the asset
const buckets = [...timelineData.buckets.values()];
const buckets = timelineData.buckets.values().toArray();
for (const assets of buckets) {
const asset = assets.find((a) => a.id === assetId);
if (asset) {
@@ -395,7 +391,7 @@ export function getAlbum(
// Get the actual asset objects from the timeline data
const albumAssets: AssetResponseDto[] = [];
const allAssets = [...timelineData.buckets.values()].flat();
const allAssets = timelineData.buckets.values().toArray().flat();
for (const assetId of album.assetIds) {
const assetConfig = allAssets.find((a) => a.id === assetId);

View File

@@ -143,7 +143,7 @@ export function validateTimelineConfig(config: TimelineConfig): void {
}
// Validate seed if provided
if (config.seed !== undefined && (config.seed < 0 || !Number.isInteger(config.seed))) {
if (config.seed !== undefined && (config.seed < 0 || !Number.isSafeInteger(config.seed))) {
throw new Error('Seed must be a non-negative integer');
}

View File

@@ -153,11 +153,8 @@ export function getMockAsset(
const isInDifferentPeriod = (date1: DateTime, date2: DateTime): boolean => {
if (unit === 'day') {
return !date1.startOf('day').equals(date2.startOf('day'));
} else if (unit === 'month') {
return date1.year !== date2.year || date1.month !== date2.month;
} else {
return date1.year !== date2.year;
}
return unit === 'month' ? date1.year !== date2.year || date1.month !== date2.month : date1.year !== date2.year;
};
if (direction === 'next') {

View File

@@ -82,6 +82,9 @@ export const setupBaseMockApiRoutes = async (context: BrowserContext, adminUserI
cast: {
gCastEnabled: false,
},
recentlyAdded: {
sidebarWeb: false,
},
},
});
});

View File

@@ -40,7 +40,8 @@ export const setupTimelineMockApiRoutes = async (
contentType: 'application/json',
json: getTimeBuckets(timelineRestData, isTrashed, isArchived, isFavorite, albumId, changes),
});
} else if (pathname === '/api/timeline/bucket') {
}
if (pathname === '/api/timeline/bucket') {
const timeBucket = url.searchParams.get('timeBucket');
if (!timeBucket) {
return route.continue();

View File

@@ -38,7 +38,7 @@ test.describe('asset-viewer', () => {
assets.push(...timeBucket);
}
for (const yearMonth of timelineRestData.buckets.keys()) {
const [year, month] = yearMonth.split('-');
const [year, month] = yearMonth.split('-', 2);
yearMonths.push(`${year}-${Number(month)}`);
}
});

View File

@@ -110,6 +110,7 @@ export async function enableTagsPreference(context: BrowserContext) {
download: { archiveSize: 4_294_967_296, includeEmbeddedVideos: false },
purchase: { showSupportBadge: true, hideBuyButtonUntil: '2100-02-12T00:00:00.000Z' },
cast: { gCastEnabled: false },
recentlyAdded: { sidebarWeb: false },
},
});
});

View File

@@ -1,3 +1,4 @@
/* eslint-disable unicorn/no-this-outside-of-class */
import type { AssetResponseDto } from '@immich/sdk';
import { expect, Page } from '@playwright/test';

View File

@@ -49,7 +49,7 @@ test.describe('Timeline', () => {
assets.push(...timeBucket);
}
for (const yearMonth of timelineRestData.buckets.keys()) {
const [year, month] = yearMonth.split('-');
const [year, month] = yearMonth.split('-', 2);
yearMonths.push(`${year}-${Number(month)}`);
}
});

View File

@@ -7,7 +7,7 @@ export const sleep = (ms: number) => {
};
export const padYearMonth = (yearMonth: string) => {
const [year, month] = yearMonth.split('-');
const [year, month] = yearMonth.split('-', 2);
return `${year}-${month.padStart(2, '0')}`;
};
@@ -72,6 +72,7 @@ export const thumbnailUtils = {
},
async queryThumbnailInViewport(page: Page, collector: (assetId: string) => boolean) {
const assetIds: string[] = [];
// eslint-disable-next-line unicorn/no-this-outside-of-class
for (const thumb of await this.locator(page).all()) {
const box = await thumb.boundingBox();
if (box) {
@@ -151,6 +152,7 @@ export const timelineUtils = {
page.evaluate(() => {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
return document.querySelector('#asset-grid').scrollTop;
});
await expect.poll(queryTop).toBeGreaterThan(0);
@@ -177,6 +179,7 @@ export const assetViewerUtils = {
page.evaluate(() => {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
// eslint-disable-next-line unicorn/no-optional-chaining-on-undeclared-variable
return document.activeElement?.dataset?.asset;
});
await expect(poll(page, activeElement, (result) => result === assetId)).resolves.toBe(assetId);

View File

@@ -1,3 +1,4 @@
/* eslint-disable unicorn/no-top-level-assignment-in-function */
import {
AssetMediaCreateDto,
AssetMediaResponseDto,
@@ -177,7 +178,7 @@ export const utils = {
resetDatabase: async (tables?: string[]) => {
client = await utils.connectDatabase();
tables = tables || [
tables ||= [
// TODO e2e test for deleting a stack, since it is quite complex
'stack',
'library',
@@ -192,6 +193,7 @@ export const utils = {
'user',
'system_metadata',
'tag',
'integrity_report',
];
const truncateTables = tables.filter((table) => table !== 'system_metadata');
@@ -303,7 +305,7 @@ export const utils = {
},
adminSetup: async (options?: AdminSetupOptions) => {
options = options || { onboarding: true };
options ||= { onboarding: true };
await signUpAdmin({ signUpDto: signupDto.admin });
const response = await login({ loginCredentialDto: loginDto.admin });
@@ -544,6 +546,7 @@ export const utils = {
{
headers: asBearerAuth(accessToken),
fetch: (...args: Parameters<typeof fetch>) =>
// eslint-disable-next-line unicorn/no-invalid-argument-count, unicorn/prefer-await
fetch(...args).then((response) => {
setCookie = response.headers.getSetCookie();
return response;
@@ -559,10 +562,54 @@ export const utils = {
mkdirSync(`${testAssetDir}/temp`, { recursive: true });
},
putFile(source: string, dest: string) {
return executeCommand('docker', ['cp', source, `immich-e2e-server:${dest}`]).promise;
},
async putTextFile(contents: string, dest: string) {
const dir = await mkdtemp(join(tmpdir(), 'test-'));
const fn = join(dir, 'file');
await pipeline(Readable.from(contents), createWriteStream(fn));
return executeCommand('docker', ['cp', fn, `immich-e2e-server:${dest}`]).promise;
},
async move(source: string, dest: string) {
return executeCommand('docker', ['exec', 'immich-e2e-server', 'mv', source, dest]).promise;
},
async copyFolder(source: string, dest: string) {
return executeCommand('docker', ['exec', 'immich-e2e-server', 'cp', '-r', source, dest]).promise;
},
async deleteFile(path: string) {
return executeCommand('docker', ['exec', 'immich-e2e-server', 'rm', path]).promise;
},
async deleteFolder(path: string) {
return executeCommand('docker', ['exec', 'immich-e2e-server', 'rm', '-r', path]).promise;
},
async truncateFolder(path: string) {
return executeCommand('docker', [
'exec',
'immich-e2e-server',
'find',
path,
'-type',
'f',
'-exec',
'truncate',
'-s',
'1',
'{}',
';',
]).promise;
},
async mkFolder(path: string) {
return executeCommand('docker', ['exec', 'immich-e2e-server', 'mkdir', '-p', path]).promise;
},
createBackup: async (accessToken: string) => {
await utils.createJob(accessToken, {
name: ManualJobName.BackupDatabase,
@@ -579,10 +626,8 @@ export const utils = {
resetBackups: async (accessToken: string) => {
const { backups } = await listDatabaseBackups({ headers: asBearerAuth(accessToken) });
const backupFiles = backups.map((b) => b.filename);
await deleteDatabaseBackup(
{ databaseBackupDeleteDto: { backups: backupFiles } },
{ databaseBackupDeleteDto: { backups: backups.map((dto) => dto.filename) } },
{ headers: asBearerAuth(accessToken) },
);
},
@@ -631,7 +676,7 @@ export const utils = {
cliLogin: async (accessToken: string) => {
const key = await utils.createApiKey(accessToken, [Permission.All]);
await immichCli(['login', app, `${key.secret}`]);
await immichCli(['login', app, key.secret]);
return key.secret;
},
@@ -663,6 +708,7 @@ export const utils = {
},
};
// eslint-disable-next-line unicorn/no-top-level-side-effects
utils.initSdk();
if (!existsSync(`${testAssetDir}/albums`)) {

View File

@@ -10,6 +10,7 @@
"allowSyntheticDefaultImports": true,
"resolveJsonModule": true,
"target": "es2023",
"lib": ["dom", "ESNext"],
"sourceMap": true,
"outDir": "./dist",
"incremental": true,

View File

@@ -1,4 +1,3 @@
import tsconfigPaths from 'vite-tsconfig-paths';
import { defineConfig } from 'vitest/config';
const skipDockerSetup = process.env.VITEST_DISABLE_DOCKER_SETUP === 'true';
@@ -24,5 +23,7 @@ export default defineConfig({
maxWorkers: 1,
isolate: false,
},
plugins: [tsconfigPaths()],
resolve: {
tsconfigPaths: true,
},
});

View File

@@ -1,4 +1,3 @@
import tsconfigPaths from 'vite-tsconfig-paths';
import { defineConfig } from 'vitest/config';
const skipDockerSetup = process.env.VITEST_DISABLE_DOCKER_SETUP === 'true';
@@ -24,5 +23,7 @@ export default defineConfig({
maxWorkers: 1,
isolate: false,
},
plugins: [tsconfigPaths()],
resolve: {
tsconfigPaths: true,
},
});

View File

@@ -5,31 +5,34 @@
"acknowledge": "Neem kennis",
"action": "Aksie",
"action_common_update": "Werk by",
"action_description": "n Stel van aksies om op die gefiltreerde bates uit te voer",
"actions": "Aksies",
"active": "Aktief",
"active_count": "Aktief: {count}",
"activity": "Aktiwiteite",
"activity_changed": "Aktiwiteit is {enabled, select, true {geaktiveer} other {gedeaktiveer}}",
"add": "Voeg toe",
"add_a_description": "Voeg n beskrywing toe",
"add_a_location": "Voeg n ligging toe",
"add_a_name": "Voeg n naam toe",
"add_a_title": "Voeg n titel toe",
"add_action": "Voeg aksie toe",
"add_assets": "Voeg bates by",
"add_birthday": "Voeg n verjaarsdag toe",
"add_endpoint": "Voeg eindpunt toe",
"add_exclusion_pattern": "Voeg uitsluitingspatroon toe",
"add_location": "Voeg ligging toe",
"add_more_users": "Voeg meer gebruikers toe",
"add_partner": "Voeg vennoot toe",
"add_path": "Voeg pad toe",
"add_photos": "Voeg fotos toe",
"add_tag": "Voeg etiket toe",
"add_to": "Voeg toe tot…",
"add_to_album": "Voeg toe tot album",
"add_to_album_bottom_sheet_added": "Tot {album} toegevoeg",
"add_to_album_bottom_sheet_already_exists": "Reeds in {album}",
"add_to_album_bottom_sheet_some_local_assets": "Sommige plaaslike bates kon nie toe gevoeg word tot die album nie",
"add_to_albums": "Voeg toe tot albums",
"add_to_albums_count": "Voeg toe tot albums ({count})",
"add_to_shared_album": "Voeg toe tot gedeelde album",
"add_to_bottom_bar": "Voeg toe",
"add_upload_to_stack": "Voeg oplaai by stapel",
"add_url": "Voeg bronadres toe",
"added_to_archive": "Tot argief toegevoeg",
"added_to_favorites": "Tot gunstelinge toegevoeg",
@@ -46,6 +49,7 @@
"backup_database": "Skep Databasisstortlêer",
"backup_database_enable_description": "Aktiveer databasisstortlêers",
"backup_keep_last_amount": "Aantal vorige stortlêers om te hou",
"backup_onboarding_2_description": "plaaslike kopieë op verskillende toestelle. Dit sluit die hooflêers en n rugsteun van daardie lêers plaaslik in.",
"backup_onboarding_3_description": "totale kopieë van u data, insluitend die oorspronklike lêers. Dit sluit 1 kopie op n ander perseel en 2 lokale kopieë in.",
"backup_onboarding_description": "n <backblaze-link>3-2-1-rugsteunstrategie</backblaze-link> word sterk aanbeveel om u data veilig te hou. Hou kopieë van u fotos/videos sowel as die Immich-databasis vir n volledige rugsteunoplossing.",
"backup_onboarding_footer": "Lees hierdie <link>dokument</link> vir meer inligting oor hoe om n rugsteunkopie van Immich te maak.",
@@ -61,6 +65,7 @@
"confirm_reprocess_all_faces": "Is u seker u wil alle gesigte herverwerk? Dit sal ook genoemde mense skoonmaak.",
"confirm_user_password_reset": "Is u seker u wil {user} se wagwoord terugstel?",
"confirm_user_pin_code_reset": "Is u seker u wil {user} se PIN-kode herstel?",
"copy_config_to_clipboard_description": "Kopieer die huidige stelselkonfigurasie as n JSONobjek na die klipbord",
"create_job": "Skep taak",
"cron_expression": "Cron-uitdrukking",
"cron_expression_description": "Stel die skanderingsinterval in met die cron-formaat. Kyk gerus na bv. <link>Crontab Guru</link> vir meer inligting",
@@ -68,6 +73,8 @@
"disable_login": "Deaktiveer aantekening",
"duplicate_detection_job_description": "Begin masjienleer op items om soortgelyke beelde op te spoor. Maak staat op Slimsoek",
"exclusion_pattern_description": "Met uitsluitingspatrone kan u lêers en vouers ignoreer wanneer u u biblioteek skandeer. Dit is nuttig as u vouers het wat lêers bevat wat u nie wil invoer nie, soos RAW-lêers.",
"export_config_as_json_description": "Laai die huidige stelselkonfigurasie af as n JSONlêer",
"external_libraries_page_description": "Admin eksterne biblioteekbladsy",
"face_detection": "Gesigherkenning",
"face_detection_description": "Identifiseer die gesigte in media d.m.v. masjienleer. Vir videos word slegs die duimnael oorweeg. “Herlaai” (ver)werk al die media weer. “Stel terug” verwyder alle huidige gesigdata. “Onverwerk” plaas items in die ry wat nog nie verwerk is nie. Geïdentifiseerde gesigte sal ná voltooiing van Gesigidentifikasie vir Gesigherkenning in die ry geplaas word om hulle in bestaande of nuwe persone te groepeer.",
"facial_recognition_job_description": "Groepeer gesigte in mense. Die stap is vinniger nadat Gesigherkenning klaar is. “Herstel” (her-)groepeer alle gesigte. “Vermiste” plaas gesigte in ry wat nie n persoon gekoppel het nie.",
@@ -129,7 +136,6 @@
"advanced": "Gevorderd",
"albums": "Albums",
"all": "Alle",
"anti_clockwise": "Linksom",
"archive": "Argief",
"asset_skipped": "Oorgeslaan",
"asset_uploaded": "Opgelaai",
@@ -141,7 +147,6 @@
"camera": "Kamera",
"cancel": "Kanselleer",
"city": "Stad",
"clockwise": "Regsom",
"close": "Sluit",
"color": "Kleur",
"confirm": "Bevestig",
@@ -182,14 +187,12 @@
"unsaved_change": "Onbewaarde verandering",
"unselect_all": "Ontkies alles",
"unselect_all_duplicates": "Ontkies alle duplikate",
"unselect_all_in": "Ontkies alles in {group}",
"unstack": "Ontstapel",
"unstack_action_prompt": "{count} ongestapel",
"unstacked_assets_count": "{count, plural, one {# item} other {# items}} ontstapel",
"unsupported_field_type": "Onondersteunde veldtipe",
"unsupported_file_type": "Lêer {file} kan nie opgelaai word nie omdat die lêertipe {type} nie ondersteun word nie.",
"untagged": "Sonder etiket",
"untitled_workflow": "Naamlose werkvloei",
"up_next": "Volgende",
"update_location_action_prompt": "Werk die ligging van {count} gekose items by met:",
"updated_at": "Bygewerk",
@@ -197,8 +200,6 @@
"upload": "Laai op",
"upload_concurrency": "Aantal gelyktydige oplaaie",
"upload_details": "Oplaaidetails",
"upload_dialog_info": "Wil u n rugsteun maak van die gekose item(s) op die bediener?",
"upload_dialog_title": "Laai item op",
"upload_error_with_count": "Oplaaifout vir {count, plural, one {# item} other {# items}}",
"upload_errors": "Oplaai voltooi met {count, plural, one {# fout} other {# foute}}, verfris die blad om die nuwe items te sien.",
"upload_finished": "Klaar opgelaai",
@@ -227,7 +228,6 @@
"user_privacy": "Gebruikersprivaatheid",
"user_purchase_settings": "Koop",
"user_purchase_settings_description": "Bestuur u aankoop",
"user_role_set": "Stel {user} in as {role}",
"user_usage_detail": "Gedetailleerde gebruik van gebruikers",
"user_usage_stats": "Statistieke vir rekeninggebruik",
"user_usage_stats_description": "Bekyk statistieke van rekeninggebruik",
@@ -237,7 +237,6 @@
"utilities": "Gereedskap",
"validate": "Valideer",
"validate_endpoint_error": "Voer asb. n geldige bronadres in",
"validation_error": "Valideerfout",
"variables": "Veranderlikes",
"version": "Weergawe",
"version_announcement_closing": "Jou friend, Alex",
@@ -249,7 +248,6 @@
"video_hover_setting_description": "Speel videoduimnael wanneer muis oor item hang. Selfs indien gedeaktiveer kan afspeel begin deur oor die afspeelknop te hang.",
"videos": "Videos",
"videos_count": "{count, plural, one {# video} other {# videos}}",
"videos_only": "Slegs videos",
"view": "Bekyk",
"view_album": "Bekyk album",
"view_all": "Bekyk alle",
@@ -258,21 +256,16 @@
"view_details": "Bekyk detail",
"view_in_timeline": "Bekyk in tydlyn",
"view_link": "Bekyk skakel",
"view_links": "Bekyk skakels",
"view_name": "Bekyk",
"view_next_asset": "Bekyk volgende item",
"view_previous_asset": "Bekyk vorige item",
"view_qr_code": "Bekyk QR-kode",
"view_similar_photos": "Bekyk soortgelyke fotos",
"view_stack": "Bekyk stapel",
"view_user": "Bekyk gebruiker",
"viewer_remove_from_stack": "Verwyder van stapel",
"viewer_stack_use_as_main_asset": "Gebruik as hoofitem",
"viewer_unstack": "Ontstapel",
"visibility": "Sigbaarheid",
"visibility_changed": "Sigbaarheid verander vir {count, plural, one {# mens} other {# mense}}",
"visual": "Visueel",
"visual_builder": "Visuele bouer",
"waiting": "Wag",
"waiting_count": "Wagtend: {count}",
"warning": "Waarskuwing",
@@ -291,7 +284,6 @@
"workflow_navigation_prompt": "Is u seker u wil verlaat sonder om u veranderinge te bewaar?",
"workflow_summary": "Werkvloei-opsomming",
"workflow_update_success": "Werkvloei suksesvol bygewerk",
"workflow_updated": "Werkvloei bygewerk",
"workflows": "Werkvloeie",
"workflows_help_text": "Werkvloeie outomatiseer aksies op u items gebaseer op snellers en filters",
"wrong_pin_code": "Verkeerde PIN-kode",
@@ -301,6 +293,5 @@
"you_dont_have_any_shared_links": "U het geen gedeelde skakels nie",
"your_wifi_name": "U Wi-Fi-naam",
"zero_to_clear_rating": "druk 0 om itemgradering te wis",
"zoom_image": "Zoem in",
"zoom_to_bounds": "Zoem na rande"
"zoom_image": "Zoem in"
}

File diff suppressed because it is too large Load Diff

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