mirror of
https://github.com/immich-app/immich.git
synced 2026-07-23 13:54:05 +03:00
Compare commits
423 Commits
refactor/f
...
fix/auto-h
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5302d6ea0e | ||
|
|
564cda5088 | ||
|
|
a7f1d495c6 | ||
|
|
b9f6c4aaf2 | ||
|
|
23778551f7 | ||
|
|
457da17c5d | ||
|
|
12f741d911 | ||
|
|
a0c7b81142 | ||
|
|
20997823d8 | ||
|
|
8a875f5978 | ||
|
|
8435d219e4 | ||
|
|
8bbc252703 | ||
|
|
2d3cf8ffaa | ||
|
|
33cba2f9af | ||
|
|
75053ebfba | ||
|
|
ba9ec7c80e | ||
|
|
99370b2ac4 | ||
|
|
0f5bf6071f | ||
|
|
998fd5f6a8 | ||
|
|
f413c446e3 | ||
|
|
c9539a4aa8 | ||
|
|
a560e08693 | ||
|
|
49c0baf076 | ||
|
|
3485dbffda | ||
|
|
e508781125 | ||
|
|
a30ebab70c | ||
|
|
5d7283e44d | ||
|
|
73329a8ce8 | ||
|
|
d5adfb97dd | ||
|
|
b70cb58bc8 | ||
|
|
943bfafb7a | ||
|
|
ee4bd3f833 | ||
|
|
7a7303aceb | ||
|
|
0a7c067b20 | ||
|
|
c8de56c422 | ||
|
|
0e4572c37b | ||
|
|
14a2776453 | ||
|
|
59bc81423c | ||
|
|
e6fff3b15e | ||
|
|
755df23b22 | ||
|
|
899f547053 | ||
|
|
4938fd4c89 | ||
|
|
e81a79224c | ||
|
|
7b0f58b2c1 | ||
|
|
ae8398ffe4 | ||
|
|
aa08dad1f5 | ||
|
|
ce022233ae | ||
|
|
1b4d41324e | ||
|
|
8365cdd59e | ||
|
|
1cec7021e8 | ||
|
|
1b0f473457 | ||
|
|
e918658cc1 | ||
|
|
2e587fc7e8 | ||
|
|
df970da59e | ||
|
|
8061a2e5ff | ||
|
|
77091b0107 | ||
|
|
4c754f2999 | ||
|
|
6fa3f2feac | ||
|
|
3f6897ef80 | ||
|
|
89d0a9d59f | ||
|
|
45a6ea84af | ||
|
|
bf64f3867b | ||
|
|
4a4d468aa2 | ||
|
|
522def1ed6 | ||
|
|
3adc3920fb | ||
|
|
00cb50cc67 | ||
|
|
f2b0b696f6 | ||
|
|
b5401eb120 | ||
|
|
12fc8bac18 | ||
|
|
f2c00c107d | ||
|
|
f19f30ec66 | ||
|
|
297316c7b8 | ||
|
|
f18065b3c2 | ||
|
|
a3dd19cd2f | ||
|
|
9cea3f2375 | ||
|
|
507448d797 | ||
|
|
fe3eb7d865 | ||
|
|
90dc328c22 | ||
|
|
a5623c41ec | ||
|
|
3a016f4451 | ||
|
|
0af8284456 | ||
|
|
ad7a9bd189 | ||
|
|
80cdfebbc7 | ||
|
|
c50a807bc1 | ||
|
|
26e07548a7 | ||
|
|
f73c53c563 | ||
|
|
d2744f52b1 | ||
|
|
d8ed7d7bb9 | ||
|
|
834cc54abc | ||
|
|
eda605c534 | ||
|
|
b75e6520bb | ||
|
|
4c2d2284aa | ||
|
|
5d29f3edc1 | ||
|
|
e00dcf7850 | ||
|
|
e7f773197f | ||
|
|
2237078bd5 | ||
|
|
55dc579059 | ||
|
|
b4530b0671 | ||
|
|
a04e2ddeca | ||
|
|
5f2e8a4fcf | ||
|
|
dc55b234d9 | ||
|
|
f6bf22d9ca | ||
|
|
2cdbd0d00f | ||
|
|
cd308ad930 | ||
|
|
12061f3bf8 | ||
|
|
2460d431af | ||
|
|
24556cd3fc | ||
|
|
c84ab54889 | ||
|
|
19e59ebd98 | ||
|
|
89b0d906e8 | ||
|
|
3c43e8d6c1 | ||
|
|
2ed8b2bddd | ||
|
|
9c8d718ddc | ||
|
|
20e123c839 | ||
|
|
35fcca6254 | ||
|
|
ffafc144c6 | ||
|
|
89d31aaba1 | ||
|
|
f2ddace584 | ||
|
|
99883096d6 | ||
|
|
84dff19ca9 | ||
|
|
da8774801b | ||
|
|
9057ae9759 | ||
|
|
557189d7a8 | ||
|
|
19313e75fd | ||
|
|
352c8086f9 | ||
|
|
443b856ac3 | ||
|
|
d18edfed34 | ||
|
|
f6d6dbc3c7 | ||
|
|
4fe33289b8 | ||
|
|
2db1e02cdf | ||
|
|
fea0db02f3 | ||
|
|
4fd58885e2 | ||
|
|
c5222c7579 | ||
|
|
c4f084f4a5 | ||
|
|
66e30d5fa1 | ||
|
|
053a52aec8 | ||
|
|
2ad554fc61 | ||
|
|
427321a102 | ||
|
|
655cd01d57 | ||
|
|
cfd95f0196 | ||
|
|
439ace9dba | ||
|
|
5c2fb2245a | ||
|
|
924e6251fd | ||
|
|
3aebb82905 | ||
|
|
7a9b9e01be | ||
|
|
0cbe07c4d4 | ||
|
|
45ea248e8b | ||
|
|
8cbf0db3b3 | ||
|
|
a225c962de | ||
|
|
daabab8c1c | ||
|
|
a4f28dafb2 | ||
|
|
704b287bba | ||
|
|
c7b4a798cb | ||
|
|
f8e78792b8 | ||
|
|
0ebb723b39 | ||
|
|
99a8354145 | ||
|
|
7ee2f3644f | ||
|
|
d2a46ade78 | ||
|
|
68b04e8e8f | ||
|
|
58b1c5e8fb | ||
|
|
6f45521610 | ||
|
|
69d568f289 | ||
|
|
a6f0253544 | ||
|
|
32352fa375 | ||
|
|
6aa5651032 | ||
|
|
4e25db2989 | ||
|
|
4f5e0d3505 | ||
|
|
b3abe0bb5d | ||
|
|
c04acc8951 | ||
|
|
af32a8ca7c | ||
|
|
ed83fb3f5a | ||
|
|
628de417e9 | ||
|
|
be02c00afd | ||
|
|
43abb9a92f | ||
|
|
103585ceb9 | ||
|
|
6da890ee37 | ||
|
|
e7ba8d3632 | ||
|
|
5645149e09 | ||
|
|
5a104f9dbe | ||
|
|
13d17b3a11 | ||
|
|
f77c8a4699 | ||
|
|
bf4b020856 | ||
|
|
f73796597c | ||
|
|
237734bb26 | ||
|
|
4b54fef82e | ||
|
|
0050332391 | ||
|
|
05d838b560 | ||
|
|
82b70c1ab6 | ||
|
|
02506424a7 | ||
|
|
6a7a34d294 | ||
|
|
165bca4b0a | ||
|
|
d4b994301f | ||
|
|
deeb042a9e | ||
|
|
b4cc406a3f | ||
|
|
df383c1ead | ||
|
|
af2efda310 | ||
|
|
5164feb5b4 | ||
|
|
56eb69b328 | ||
|
|
ac74bca18b | ||
|
|
e9d1951858 | ||
|
|
6e1143e799 | ||
|
|
09d4a6815f | ||
|
|
23d1dbcb23 | ||
|
|
6507b1f94c | ||
|
|
29949bebe4 | ||
|
|
d85e599ad9 | ||
|
|
b16cc496b2 | ||
|
|
953ef5c047 | ||
|
|
a876d4a9f1 | ||
|
|
688241a462 | ||
|
|
cb1af3a8ec | ||
|
|
49a821b0d0 | ||
|
|
3a7034d25e | ||
|
|
4099fa6b4a | ||
|
|
9751530af8 | ||
|
|
0931a19c5c | ||
|
|
08b2e2c0b5 | ||
|
|
e5b50a55a4 | ||
|
|
9d6c219276 | ||
|
|
f29f86542c | ||
|
|
5165cf1d2f | ||
|
|
f4c8459484 | ||
|
|
22ec449e43 | ||
|
|
0b1019c344 | ||
|
|
06f3b4f259 | ||
|
|
99f94a363d | ||
|
|
c3092b1c2c | ||
|
|
0656e7e231 | ||
|
|
1692b81b7c | ||
|
|
ff2028c4c8 | ||
|
|
f22836e1bf | ||
|
|
7dd02ffbad | ||
|
|
e51c4cb355 | ||
|
|
d4102c0489 | ||
|
|
30a73c1105 | ||
|
|
ec7c0f9ec8 | ||
|
|
a5198e23a8 | ||
|
|
51f2905fcc | ||
|
|
3b7d75c18a | ||
|
|
c484bd99b6 | ||
|
|
c0bf5a4c56 | ||
|
|
d9d50d2848 | ||
|
|
c7453a67fd | ||
|
|
e918e3a313 | ||
|
|
dc7d57ff9a | ||
|
|
b24a617142 | ||
|
|
62b00a1f26 | ||
|
|
95fc5e9682 | ||
|
|
38920fc4ca | ||
|
|
3abeb4df92 | ||
|
|
805bb84877 | ||
|
|
a719552243 | ||
|
|
9a5e7a8e47 | ||
|
|
62c6bb27e3 | ||
|
|
f3cb3cf98d | ||
|
|
c35abb2f66 | ||
|
|
793487e52c | ||
|
|
769c4015d3 | ||
|
|
c07cbe7ca8 | ||
|
|
6d73bf4e36 | ||
|
|
735f52a321 | ||
|
|
53fe26593c | ||
|
|
40cffcd414 | ||
|
|
48861b085e | ||
|
|
09d0380804 | ||
|
|
83091d2834 | ||
|
|
9a3071ae5c | ||
|
|
7ef2de6b53 | ||
|
|
cbe34d7931 | ||
|
|
06c8d5a183 | ||
|
|
ad9817c582 | ||
|
|
14f6f2c04f | ||
|
|
327521fa27 | ||
|
|
3be803d0c0 | ||
|
|
a364b56b1c | ||
|
|
f9db76433e | ||
|
|
3f2e51c5d4 | ||
|
|
430a2bbfd3 | ||
|
|
fbb0bc6e39 | ||
|
|
8d30cfa2ce | ||
|
|
c9aa9ba711 | ||
|
|
7cf904acbe | ||
|
|
983a005709 | ||
|
|
cda499f2b5 | ||
|
|
12b7cd066b | ||
|
|
83b4dc1733 | ||
|
|
a9d64b30ad | ||
|
|
3927eb6755 | ||
|
|
27cfa0e788 | ||
|
|
76c042abc7 | ||
|
|
0f49bcbd27 | ||
|
|
df4a708aed | ||
|
|
34f78e3fd4 | ||
|
|
3c2296b818 | ||
|
|
805ca1a2bf | ||
|
|
9ee412110f | ||
|
|
010220d588 | ||
|
|
10fddf2d51 | ||
|
|
d307ab60ca | ||
|
|
ce59cc9241 | ||
|
|
1fa034125c | ||
|
|
8036dc4b8c | ||
|
|
54895fb10e | ||
|
|
e70a1163f3 | ||
|
|
a23a7c69ae | ||
|
|
f21a753aff | ||
|
|
cc8d3b4107 | ||
|
|
622a330d82 | ||
|
|
5e8744a568 | ||
|
|
b633cc4f04 | ||
|
|
a9ee6a7ce9 | ||
|
|
c273ccf2e2 | ||
|
|
5f1a180d1a | ||
|
|
cc54de87aa | ||
|
|
a97e5999e4 | ||
|
|
46631b3786 | ||
|
|
5a3be158b9 | ||
|
|
b21af78454 | ||
|
|
abd62d9295 | ||
|
|
e31d4aa909 | ||
|
|
43b2d04e2c | ||
|
|
e4dbe777a0 | ||
|
|
d36aed4c5b | ||
|
|
f1da9d2429 | ||
|
|
232ca3cf3f | ||
|
|
50f1121459 | ||
|
|
892397807c | ||
|
|
714c647937 | ||
|
|
c56f477a0f | ||
|
|
296cd40da9 | ||
|
|
a17276fd1e | ||
|
|
c3e23a6b3a | ||
|
|
13a7b4a276 | ||
|
|
563cff26bf | ||
|
|
e81b6778ca | ||
|
|
aa6af7ce36 | ||
|
|
59d036a2ed | ||
|
|
7a5c014558 | ||
|
|
e2954b6411 | ||
|
|
0fb18ed241 | ||
|
|
c0b3b08ce6 | ||
|
|
e8a1084e5b | ||
|
|
d227ba2d51 | ||
|
|
9cb94343d1 | ||
|
|
aa126e377c | ||
|
|
74878628c8 | ||
|
|
4ead3e697d | ||
|
|
fb798a8f29 | ||
|
|
07813135b5 | ||
|
|
92a75b0cd3 | ||
|
|
8132e8a38c | ||
|
|
43f2f56530 | ||
|
|
e580bb5d0a | ||
|
|
d3680871ef | ||
|
|
b9b1cc2f65 | ||
|
|
7d198956a6 | ||
|
|
a7b5f81701 | ||
|
|
5c38373808 | ||
|
|
1ce961fbb3 | ||
|
|
4bc411b7c7 | ||
|
|
11c1025271 | ||
|
|
8b5385f94b | ||
|
|
d3438cf4a7 | ||
|
|
6c5c6a1035 | ||
|
|
c928787b3e | ||
|
|
fe9ca4f40a | ||
|
|
a665cec920 | ||
|
|
568283a8eb | ||
|
|
f382624e68 | ||
|
|
24dad15636 | ||
|
|
7ab533b57b | ||
|
|
d10153bbc7 | ||
|
|
b846afeb08 | ||
|
|
e222b19576 | ||
|
|
1fee99cd2a | ||
|
|
70bb7e4b7e | ||
|
|
f973927c68 | ||
|
|
e29267359e | ||
|
|
164cda87a3 | ||
|
|
12d344efe0 | ||
|
|
474efd39f8 | ||
|
|
9e453440e6 | ||
|
|
8860817c76 | ||
|
|
3c108a8d22 | ||
|
|
8d553d6e9c | ||
|
|
346b98ed4f | ||
|
|
60683bd91e | ||
|
|
b6938614b2 | ||
|
|
98961a1d36 | ||
|
|
5ae95102b4 | ||
|
|
216d0ba365 | ||
|
|
28e42f7e29 | ||
|
|
733373c0ca | ||
|
|
5617d6ca7c | ||
|
|
875dd2dead | ||
|
|
9043bc8435 | ||
|
|
b3d49045de | ||
|
|
58528cad08 | ||
|
|
99281de6ab | ||
|
|
6268d23d12 | ||
|
|
d7999ce1d1 | ||
|
|
6b0fd89cd2 | ||
|
|
4b0adb7a1e | ||
|
|
de70d19d20 | ||
|
|
7155bb1e80 | ||
|
|
fa08e72d30 | ||
|
|
e2de8c7c53 | ||
|
|
429e181c8f | ||
|
|
7f611d9031 | ||
|
|
e94e22f3f8 | ||
|
|
4a8c3b60be | ||
|
|
2190aa72a8 | ||
|
|
d21cb28526 | ||
|
|
5c33eb3204 | ||
|
|
137687bc0f | ||
|
|
9d4a6614b1 | ||
|
|
e4352a7817 | ||
|
|
911dde39c9 | ||
|
|
afa836181c | ||
|
|
963862b1b9 | ||
|
|
96d521e149 | ||
|
|
1bb7517da0 | ||
|
|
814c2e32e4 |
@@ -83,7 +83,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"features": {
|
"features": {
|
||||||
"ghcr.io/devcontainers/features/docker-in-docker:2": {
|
"ghcr.io/devcontainers/features/docker-in-docker:4": {
|
||||||
// https://github.com/devcontainers/features/issues/1466
|
// https://github.com/devcontainers/features/issues/1466
|
||||||
"moby": false
|
"moby": false
|
||||||
}
|
}
|
||||||
|
|||||||
2
.github/workflows/auto-close.yml
vendored
2
.github/workflows/auto-close.yml
vendored
@@ -15,7 +15,7 @@ jobs:
|
|||||||
outputs:
|
outputs:
|
||||||
uses_template: ${{ steps.check.outputs.uses_template }}
|
uses_template: ${{ steps.check.outputs.uses_template }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||||
with:
|
with:
|
||||||
sparse-checkout: .github/pull_request_template.md
|
sparse-checkout: .github/pull_request_template.md
|
||||||
sparse-checkout-cone-mode: false
|
sparse-checkout-cone-mode: false
|
||||||
|
|||||||
30
.github/workflows/build-mobile.yml
vendored
30
.github/workflows/build-mobile.yml
vendored
@@ -51,10 +51,11 @@ jobs:
|
|||||||
should_run: ${{ steps.check.outputs.should_run }}
|
should_run: ${{ steps.check.outputs.should_run }}
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@9db058b2e6eec20e07760b0e17a0505c78ec3191 # create-workflow-token-action-v2.0.1
|
uses: immich-app/devtools/actions/create-workflow-token@1af396ae134e4bc3b63d947e672bc68bf4ff9dc5 # create-workflow-token-action-v3.0.0
|
||||||
with:
|
with:
|
||||||
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
permission-contents: read
|
||||||
|
|
||||||
- name: Check what should run
|
- name: Check what should run
|
||||||
id: check
|
id: check
|
||||||
@@ -79,21 +80,24 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@9db058b2e6eec20e07760b0e17a0505c78ec3191 # create-workflow-token-action-v2.0.1
|
uses: immich-app/devtools/actions/create-workflow-token@1af396ae134e4bc3b63d947e672bc68bf4ff9dc5 # create-workflow-token-action-v3.0.0
|
||||||
with:
|
with:
|
||||||
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
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:
|
with:
|
||||||
ref: ${{ inputs.ref }}
|
ref: ${{ inputs.ref }}
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
token: ${{ steps.token.outputs.token }}
|
token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
- name: Setup Mise
|
- 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:
|
with:
|
||||||
github_token: ${{ steps.token.outputs.token }}
|
github_token: ${{ steps.token.outputs.token }}
|
||||||
|
working_directory: ./mobile
|
||||||
|
|
||||||
- name: Create the Keystore
|
- name: Create the Keystore
|
||||||
if: ${{ !github.event.pull_request.head.repo.fork }}
|
if: ${{ !github.event.pull_request.head.repo.fork }}
|
||||||
@@ -102,14 +106,14 @@ jobs:
|
|||||||
working-directory: ./mobile
|
working-directory: ./mobile
|
||||||
run: printf "%s" $KEY_JKS | base64 -d > android/key.jks
|
run: printf "%s" $KEY_JKS | base64 -d > android/key.jks
|
||||||
|
|
||||||
- uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
|
- uses: actions/setup-java@03ad4de0992f5dab5e18fcb136590ce7c4a0ac95 # v5.6.0
|
||||||
with:
|
with:
|
||||||
distribution: 'zulu'
|
distribution: 'zulu'
|
||||||
java-version: '17'
|
java-version: '17'
|
||||||
|
|
||||||
- name: Restore Gradle Cache
|
- name: Restore Gradle Cache
|
||||||
id: cache-gradle-restore
|
id: cache-gradle-restore
|
||||||
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
~/.gradle/caches
|
~/.gradle/caches
|
||||||
@@ -180,7 +184,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Save Gradle Cache
|
- name: Save Gradle Cache
|
||||||
id: cache-gradle-save
|
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'
|
if: github.ref == 'refs/heads/main'
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
@@ -197,28 +201,30 @@ jobs:
|
|||||||
contents: read
|
contents: read
|
||||||
# Run on main branch or workflow_dispatch, or on PRs/other branches (build only, no upload)
|
# 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 }}
|
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:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@9db058b2e6eec20e07760b0e17a0505c78ec3191 # create-workflow-token-action-v2.0.1
|
uses: immich-app/devtools/actions/create-workflow-token@1af396ae134e4bc3b63d947e672bc68bf4ff9dc5 # create-workflow-token-action-v3.0.0
|
||||||
with:
|
with:
|
||||||
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
permission-contents: read
|
||||||
|
|
||||||
- name: Select Xcode 26
|
- name: Select Xcode 26
|
||||||
run: sudo xcode-select -s /Applications/Xcode_26.2.app/Contents/Developer
|
run: sudo xcode-select -s /Applications/Xcode_26.2.app/Contents/Developer
|
||||||
|
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||||
with:
|
with:
|
||||||
ref: ${{ inputs.ref || github.sha }}
|
ref: ${{ inputs.ref || github.sha }}
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
|
||||||
- name: Setup Mise
|
- 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:
|
with:
|
||||||
github_token: ${{ steps.token.outputs.token }}
|
github_token: ${{ steps.token.outputs.token }}
|
||||||
|
working_directory: ./mobile
|
||||||
|
|
||||||
- name: Install Flutter dependencies
|
- name: Install Flutter dependencies
|
||||||
working-directory: ./mobile
|
working-directory: ./mobile
|
||||||
@@ -235,7 +241,7 @@ jobs:
|
|||||||
run: flutter build ios --config-only --no-codesign
|
run: flutter build ios --config-only --no-codesign
|
||||||
|
|
||||||
- name: Setup Ruby
|
- name: Setup Ruby
|
||||||
uses: ruby/setup-ruby@afeafc3d1ab54a631816aba4c914a0081c12ff2f # v1.310.0
|
uses: ruby/setup-ruby@003a5c4d8d6321bd302e38f6f0ec593f77f06600 # v1.319.0
|
||||||
with:
|
with:
|
||||||
ruby-version: '3.3'
|
ruby-version: '3.3'
|
||||||
bundler-cache: true
|
bundler-cache: true
|
||||||
|
|||||||
6
.github/workflows/cache-cleanup.yml
vendored
6
.github/workflows/cache-cleanup.yml
vendored
@@ -19,13 +19,15 @@ jobs:
|
|||||||
actions: write
|
actions: write
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@9db058b2e6eec20e07760b0e17a0505c78ec3191 # create-workflow-token-action-v2.0.1
|
uses: immich-app/devtools/actions/create-workflow-token@1af396ae134e4bc3b63d947e672bc68bf4ff9dc5 # create-workflow-token-action-v3.0.0
|
||||||
with:
|
with:
|
||||||
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
permission-contents: read
|
||||||
|
permission-actions: write
|
||||||
|
|
||||||
- name: Check out code
|
- name: Check out code
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
token: ${{ steps.token.outputs.token }}
|
token: ${{ steps.token.outputs.token }}
|
||||||
|
|||||||
39
.github/workflows/check-openapi.yml
vendored
39
.github/workflows/check-openapi.yml
vendored
@@ -4,6 +4,7 @@ on:
|
|||||||
pull_request:
|
pull_request:
|
||||||
paths:
|
paths:
|
||||||
- 'open-api/**'
|
- 'open-api/**'
|
||||||
|
- 'mobile/lib/utils/openapi_patching.dart'
|
||||||
- '.github/workflows/check-openapi.yml'
|
- '.github/workflows/check-openapi.yml'
|
||||||
|
|
||||||
concurrency:
|
concurrency:
|
||||||
@@ -19,13 +20,47 @@ jobs:
|
|||||||
contents: read
|
contents: read
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
|
||||||
- name: Check for breaking API changes
|
- name: Check for breaking API changes
|
||||||
uses: oasdiff/oasdiff-action/breaking@50e6a3413e5aa9c3ae4d8393c34745be44288b46 # v0.0.48
|
uses: oasdiff/oasdiff-action/breaking@024f6c399f9a21ada1addb0f9a36ce1bfac995f1 # v0.1.6
|
||||||
with:
|
with:
|
||||||
base: https://raw.githubusercontent.com/${{ github.repository }}/main/open-api/immich-openapi-specs.json
|
base: https://raw.githubusercontent.com/${{ github.repository }}/main/open-api/immich-openapi-specs.json
|
||||||
revision: open-api/immich-openapi-specs.json
|
revision: open-api/immich-openapi-specs.json
|
||||||
fail-on: ERR
|
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
|
||||||
|
|||||||
22
.github/workflows/cli.yml
vendored
22
.github/workflows/cli.yml
vendored
@@ -31,19 +31,20 @@ jobs:
|
|||||||
working-directory: ./packages/cli
|
working-directory: ./packages/cli
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@9db058b2e6eec20e07760b0e17a0505c78ec3191 # create-workflow-token-action-v2.0.1
|
uses: immich-app/devtools/actions/create-workflow-token@1af396ae134e4bc3b63d947e672bc68bf4ff9dc5 # create-workflow-token-action-v3.0.0
|
||||||
with:
|
with:
|
||||||
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
permission-contents: read
|
||||||
|
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
token: ${{ steps.token.outputs.token }}
|
token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
- name: Setup Mise
|
- 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:
|
with:
|
||||||
github_token: ${{ steps.token.outputs.token }}
|
github_token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
@@ -63,25 +64,26 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@9db058b2e6eec20e07760b0e17a0505c78ec3191 # create-workflow-token-action-v2.0.1
|
uses: immich-app/devtools/actions/create-workflow-token@1af396ae134e4bc3b63d947e672bc68bf4ff9dc5 # create-workflow-token-action-v3.0.0
|
||||||
with:
|
with:
|
||||||
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
permission-contents: read
|
||||||
|
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
token: ${{ steps.token.outputs.token }}
|
token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@06116385d9baf250c9f4dcb4858b16962ea869c3 # v4.1.0
|
uses: docker/setup-qemu-action@96fe6ef7f33517b61c61be40b68a1882f3264fb8 # v4.2.0
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0
|
uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0
|
||||||
|
|
||||||
- name: Login to GitHub Container Registry
|
- name: Login to GitHub Container Registry
|
||||||
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
|
uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4.4.0
|
||||||
if: ${{ !github.event.pull_request.head.repo.fork }}
|
if: ${{ !github.event.pull_request.head.repo.fork }}
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
@@ -96,7 +98,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Generate docker image tags
|
- name: Generate docker image tags
|
||||||
id: metadata
|
id: metadata
|
||||||
uses: docker/metadata-action@80c7e94dd9b9319bd5eb7a0e0fe9291e23a2a2e9 # v6.1.0
|
uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6.2.0
|
||||||
with:
|
with:
|
||||||
flavor: |
|
flavor: |
|
||||||
latest=false
|
latest=false
|
||||||
@@ -107,7 +109,7 @@ jobs:
|
|||||||
type=raw,value=latest,enable=${{ github.event_name == 'release' && !github.event.release.prerelease }}
|
type=raw,value=latest,enable=${{ github.event_name == 'release' && !github.event.release.prerelease }}
|
||||||
|
|
||||||
- name: Build and push image
|
- name: Build and push image
|
||||||
uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7.2.0
|
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
|
||||||
with:
|
with:
|
||||||
file: packages/cli/Dockerfile
|
file: packages/cli/Dockerfile
|
||||||
platforms: linux/amd64,linux/arm64
|
platforms: linux/amd64,linux/arm64
|
||||||
|
|||||||
8
.github/workflows/close-duplicates.yml
vendored
8
.github/workflows/close-duplicates.yml
vendored
@@ -14,7 +14,11 @@ jobs:
|
|||||||
should_run: ${{ steps.should_run.outputs.run }}
|
should_run: ${{ steps.should_run.outputs.run }}
|
||||||
steps:
|
steps:
|
||||||
- id: should_run
|
- 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:
|
get_body:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
@@ -35,7 +39,7 @@ jobs:
|
|||||||
needs: [get_body, should_run]
|
needs: [get_body, should_run]
|
||||||
if: ${{ needs.should_run.outputs.should_run == 'true' }}
|
if: ${{ needs.should_run.outputs.should_run == 'true' }}
|
||||||
container:
|
container:
|
||||||
image: ghcr.io/immich-app/mdq:main@sha256:e73f60195b39748c4876f23e3e6cd22a68a9754acec8aef1fd6979fd52cd2c9f
|
image: ghcr.io/immich-app/mdq:main@sha256:d603f5618c1fc9f2b4d506ffed54ba6bcfec83d56b893b5fa8b3f899a402fa0a
|
||||||
outputs:
|
outputs:
|
||||||
checked: ${{ steps.get_checkbox.outputs.checked }}
|
checked: ${{ steps.get_checkbox.outputs.checked }}
|
||||||
steps:
|
steps:
|
||||||
|
|||||||
11
.github/workflows/codeql-analysis.yml
vendored
11
.github/workflows/codeql-analysis.yml
vendored
@@ -44,20 +44,21 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@9db058b2e6eec20e07760b0e17a0505c78ec3191 # create-workflow-token-action-v2.0.1
|
uses: immich-app/devtools/actions/create-workflow-token@1af396ae134e4bc3b63d947e672bc68bf4ff9dc5 # create-workflow-token-action-v3.0.0
|
||||||
with:
|
with:
|
||||||
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
permission-contents: read
|
||||||
|
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
token: ${{ steps.token.outputs.token }}
|
token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
# Initializes the CodeQL tools for scanning.
|
# Initializes the CodeQL tools for scanning.
|
||||||
- name: Initialize CodeQL
|
- name: Initialize CodeQL
|
||||||
uses: github/codeql-action/init@7211b7c8077ea37d8641b6271f6a365a22a5fbfa # v4.36.0
|
uses: github/codeql-action/init@7188fc363630916deb702c7fdcf4e481b751f97a # v4.37.1
|
||||||
with:
|
with:
|
||||||
languages: ${{ matrix.language }}
|
languages: ${{ matrix.language }}
|
||||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
# 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).
|
# 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)
|
# If this step fails, then you should remove it and run the build manually (see below)
|
||||||
- name: Autobuild
|
- name: Autobuild
|
||||||
uses: github/codeql-action/autobuild@7211b7c8077ea37d8641b6271f6a365a22a5fbfa # v4.36.0
|
uses: github/codeql-action/autobuild@7188fc363630916deb702c7fdcf4e481b751f97a # v4.37.1
|
||||||
|
|
||||||
# ℹ️ Command-line programs to run using the OS shell.
|
# ℹ️ 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
|
# 📚 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
|
# ./location_of_script_within_repo/buildscript.sh
|
||||||
|
|
||||||
- name: Perform CodeQL Analysis
|
- name: Perform CodeQL Analysis
|
||||||
uses: github/codeql-action/analyze@7211b7c8077ea37d8641b6271f6a365a22a5fbfa # v4.36.0
|
uses: github/codeql-action/analyze@7188fc363630916deb702c7fdcf4e481b751f97a # v4.37.1
|
||||||
with:
|
with:
|
||||||
category: '/language:${{matrix.language}}'
|
category: '/language:${{matrix.language}}'
|
||||||
|
|||||||
11
.github/workflows/docker.yml
vendored
11
.github/workflows/docker.yml
vendored
@@ -23,10 +23,11 @@ jobs:
|
|||||||
should_run: ${{ steps.check.outputs.should_run }}
|
should_run: ${{ steps.check.outputs.should_run }}
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@9db058b2e6eec20e07760b0e17a0505c78ec3191 # create-workflow-token-action-v2.0.1
|
uses: immich-app/devtools/actions/create-workflow-token@1af396ae134e4bc3b63d947e672bc68bf4ff9dc5 # create-workflow-token-action-v3.0.0
|
||||||
with:
|
with:
|
||||||
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
permission-contents: read
|
||||||
|
|
||||||
- name: Check what should run
|
- name: Check what should run
|
||||||
id: check
|
id: check
|
||||||
@@ -60,7 +61,7 @@ jobs:
|
|||||||
suffix: ['', '-cuda', '-rocm', '-openvino', '-armnn', '-rknn']
|
suffix: ['', '-cuda', '-rocm', '-openvino', '-armnn', '-rknn']
|
||||||
steps:
|
steps:
|
||||||
- name: Login to GitHub Container Registry
|
- name: Login to GitHub Container Registry
|
||||||
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
|
uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4.4.0
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.repository_owner }}
|
username: ${{ github.repository_owner }}
|
||||||
@@ -90,7 +91,7 @@ jobs:
|
|||||||
suffix: ['']
|
suffix: ['']
|
||||||
steps:
|
steps:
|
||||||
- name: Login to GitHub Container Registry
|
- name: Login to GitHub Container Registry
|
||||||
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
|
uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4.4.0
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.repository_owner }}
|
username: ${{ github.repository_owner }}
|
||||||
@@ -132,7 +133,7 @@ jobs:
|
|||||||
suffixes: '-rocm'
|
suffixes: '-rocm'
|
||||||
platforms: linux/amd64
|
platforms: linux/amd64
|
||||||
runner-mapping: '{"linux/amd64": "pokedex-large"}'
|
runner-mapping: '{"linux/amd64": "pokedex-large"}'
|
||||||
uses: immich-app/devtools/.github/workflows/multi-runner-build.yml@db54dcf16fbb12c43479a23749ceea0ad1b4a704 # multi-runner-build-workflow-v3.0.0
|
uses: immich-app/devtools/.github/workflows/multi-runner-build.yml@50dc3a14f0606ecd8fc28d78d3a3c655115ab695 # multi-runner-build-workflow-v3.1.0
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
actions: read
|
actions: read
|
||||||
@@ -155,7 +156,7 @@ jobs:
|
|||||||
name: Build and Push Server
|
name: Build and Push Server
|
||||||
needs: pre-job
|
needs: pre-job
|
||||||
if: ${{ fromJSON(needs.pre-job.outputs.should_run).server == true }}
|
if: ${{ fromJSON(needs.pre-job.outputs.should_run).server == true }}
|
||||||
uses: immich-app/devtools/.github/workflows/multi-runner-build.yml@db54dcf16fbb12c43479a23749ceea0ad1b4a704 # multi-runner-build-workflow-v3.0.0
|
uses: immich-app/devtools/.github/workflows/multi-runner-build.yml@50dc3a14f0606ecd8fc28d78d3a3c655115ab695 # multi-runner-build-workflow-v3.1.0
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
actions: read
|
actions: read
|
||||||
|
|||||||
10
.github/workflows/docs-build.yml
vendored
10
.github/workflows/docs-build.yml
vendored
@@ -21,10 +21,11 @@ jobs:
|
|||||||
should_run: ${{ steps.check.outputs.should_run }}
|
should_run: ${{ steps.check.outputs.should_run }}
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@9db058b2e6eec20e07760b0e17a0505c78ec3191 # create-workflow-token-action-v2.0.1
|
uses: immich-app/devtools/actions/create-workflow-token@1af396ae134e4bc3b63d947e672bc68bf4ff9dc5 # create-workflow-token-action-v3.0.0
|
||||||
with:
|
with:
|
||||||
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
permission-contents: read
|
||||||
|
|
||||||
- name: Check what should run
|
- name: Check what should run
|
||||||
id: check
|
id: check
|
||||||
@@ -54,19 +55,20 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@9db058b2e6eec20e07760b0e17a0505c78ec3191 # create-workflow-token-action-v2.0.1
|
uses: immich-app/devtools/actions/create-workflow-token@1af396ae134e4bc3b63d947e672bc68bf4ff9dc5 # create-workflow-token-action-v3.0.0
|
||||||
with:
|
with:
|
||||||
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
permission-contents: read
|
||||||
|
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
token: ${{ steps.token.outputs.token }}
|
token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
- name: Setup Mise
|
- 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:
|
with:
|
||||||
github_token: ${{ steps.token.outputs.token }}
|
github_token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
|
|||||||
13
.github/workflows/docs-deploy.yml
vendored
13
.github/workflows/docs-deploy.yml
vendored
@@ -20,10 +20,12 @@ jobs:
|
|||||||
artifact: ${{ steps.get-artifact.outputs.result }}
|
artifact: ${{ steps.get-artifact.outputs.result }}
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@9db058b2e6eec20e07760b0e17a0505c78ec3191 # create-workflow-token-action-v2.0.1
|
uses: immich-app/devtools/actions/create-workflow-token@1af396ae134e4bc3b63d947e672bc68bf4ff9dc5 # create-workflow-token-action-v3.0.0
|
||||||
with:
|
with:
|
||||||
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
permission-actions: read
|
||||||
|
permission-pull-requests: read
|
||||||
|
|
||||||
- if: ${{ github.event.workflow_run.conclusion != 'success' }}
|
- if: ${{ github.event.workflow_run.conclusion != 'success' }}
|
||||||
run: echo 'The triggering workflow did not succeed' && exit 1
|
run: echo 'The triggering workflow did not succeed' && exit 1
|
||||||
@@ -126,19 +128,22 @@ jobs:
|
|||||||
if: ${{ fromJson(needs.checks.outputs.artifact).found && fromJson(needs.checks.outputs.parameters).shouldDeploy }}
|
if: ${{ fromJson(needs.checks.outputs.artifact).found && fromJson(needs.checks.outputs.parameters).shouldDeploy }}
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@9db058b2e6eec20e07760b0e17a0505c78ec3191 # create-workflow-token-action-v2.0.1
|
uses: immich-app/devtools/actions/create-workflow-token@1af396ae134e4bc3b63d947e672bc68bf4ff9dc5 # create-workflow-token-action-v3.0.0
|
||||||
with:
|
with:
|
||||||
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
permission-contents: read
|
||||||
|
permission-actions: read
|
||||||
|
permission-pull-requests: write
|
||||||
|
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
token: ${{ steps.token.outputs.token }}
|
token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
- name: Setup Mise
|
- 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:
|
with:
|
||||||
github_token: ${{ steps.token.outputs.token }}
|
github_token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
|
|||||||
8
.github/workflows/docs-destroy.yml
vendored
8
.github/workflows/docs-destroy.yml
vendored
@@ -17,19 +17,21 @@ jobs:
|
|||||||
pull-requests: write
|
pull-requests: write
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@9db058b2e6eec20e07760b0e17a0505c78ec3191 # create-workflow-token-action-v2.0.1
|
uses: immich-app/devtools/actions/create-workflow-token@1af396ae134e4bc3b63d947e672bc68bf4ff9dc5 # create-workflow-token-action-v3.0.0
|
||||||
with:
|
with:
|
||||||
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
permission-contents: read
|
||||||
|
permission-pull-requests: write
|
||||||
|
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
token: ${{ steps.token.outputs.token }}
|
token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
- name: Setup Mise
|
- 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:
|
with:
|
||||||
github_token: ${{ steps.token.outputs.token }}
|
github_token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
|
|||||||
56
.github/workflows/fdroid.yml
vendored
Normal file
56
.github/workflows/fdroid.yml
vendored
Normal 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
|
||||||
10
.github/workflows/fix-format.yml
vendored
10
.github/workflows/fix-format.yml
vendored
@@ -15,20 +15,22 @@ jobs:
|
|||||||
pull-requests: write
|
pull-requests: write
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@9db058b2e6eec20e07760b0e17a0505c78ec3191 # create-workflow-token-action-v2.0.1
|
uses: immich-app/devtools/actions/create-workflow-token@1af396ae134e4bc3b63d947e672bc68bf4ff9dc5 # create-workflow-token-action-v3.0.0
|
||||||
with:
|
with:
|
||||||
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
permission-contents: write
|
||||||
|
permission-pull-requests: write
|
||||||
|
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||||
with:
|
with:
|
||||||
ref: ${{ github.event.pull_request.head.ref }}
|
ref: ${{ github.event.pull_request.head.ref }}
|
||||||
persist-credentials: true
|
persist-credentials: true
|
||||||
token: ${{ steps.token.outputs.token }}
|
token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
- name: Setup Mise
|
- 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:
|
with:
|
||||||
github_token: ${{ steps.token.outputs.token }}
|
github_token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
@@ -45,7 +47,7 @@ jobs:
|
|||||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||||
if: always()
|
if: always()
|
||||||
with:
|
with:
|
||||||
github-token: ${{ steps.generate-token.outputs.token }}
|
github-token: ${{ steps.token.outputs.token }}
|
||||||
script: |
|
script: |
|
||||||
github.rest.issues.removeLabel({
|
github.rest.issues.removeLabel({
|
||||||
issue_number: context.payload.pull_request.number,
|
issue_number: context.payload.pull_request.number,
|
||||||
|
|||||||
2
.github/workflows/merge-translations.yml
vendored
2
.github/workflows/merge-translations.yml
vendored
@@ -35,6 +35,8 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
permission-contents: write
|
||||||
|
permission-pull-requests: write
|
||||||
|
|
||||||
- name: Find translation PR
|
- name: Find translation PR
|
||||||
id: find_pr
|
id: find_pr
|
||||||
|
|||||||
5
.github/workflows/pr-label-validation.yml
vendored
5
.github/workflows/pr-label-validation.yml
vendored
@@ -14,13 +14,14 @@ jobs:
|
|||||||
pull-requests: write
|
pull-requests: write
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@9db058b2e6eec20e07760b0e17a0505c78ec3191 # create-workflow-token-action-v2.0.1
|
uses: immich-app/devtools/actions/create-workflow-token@1af396ae134e4bc3b63d947e672bc68bf4ff9dc5 # create-workflow-token-action-v3.0.0
|
||||||
with:
|
with:
|
||||||
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
permission-pull-requests: write
|
||||||
|
|
||||||
- name: Require PR to have a changelog label
|
- 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:
|
with:
|
||||||
token: ${{ steps.token.outputs.token }}
|
token: ${{ steps.token.outputs.token }}
|
||||||
mode: exactly
|
mode: exactly
|
||||||
|
|||||||
6
.github/workflows/pr-labeler.yml
vendored
6
.github/workflows/pr-labeler.yml
vendored
@@ -12,11 +12,13 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@9db058b2e6eec20e07760b0e17a0505c78ec3191 # create-workflow-token-action-v2.0.1
|
uses: immich-app/devtools/actions/create-workflow-token@1af396ae134e4bc3b63d947e672bc68bf4ff9dc5 # create-workflow-token-action-v3.0.0
|
||||||
with:
|
with:
|
||||||
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
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:
|
with:
|
||||||
repo-token: ${{ steps.token.outputs.token }}
|
repo-token: ${{ steps.token.outputs.token }}
|
||||||
|
|||||||
40
.github/workflows/prepare-release.yml
vendored
40
.github/workflows/prepare-release.yml
vendored
@@ -3,16 +3,20 @@ name: Prepare new release
|
|||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
inputs:
|
inputs:
|
||||||
serverBump:
|
releaseType:
|
||||||
description: 'Bump server version'
|
description: 'Release type'
|
||||||
required: true
|
required: true
|
||||||
default: 'false'
|
default: 'false'
|
||||||
type: choice
|
type: choice
|
||||||
options:
|
options:
|
||||||
- 'false'
|
- 'false'
|
||||||
- major
|
|
||||||
- minor
|
- minor
|
||||||
- patch
|
- patch
|
||||||
|
- premajor
|
||||||
|
- preminor
|
||||||
|
- prepatch
|
||||||
|
- prerelease
|
||||||
|
- release
|
||||||
mobileBump:
|
mobileBump:
|
||||||
description: 'Bump mobile build number'
|
description: 'Bump mobile build number'
|
||||||
required: false
|
required: false
|
||||||
@@ -46,38 +50,46 @@ jobs:
|
|||||||
outputs:
|
outputs:
|
||||||
ref: ${{ steps.push-tag.outputs.commit_long_sha }}
|
ref: ${{ steps.push-tag.outputs.commit_long_sha }}
|
||||||
version: ${{ steps.output.outputs.version }}
|
version: ${{ steps.output.outputs.version }}
|
||||||
|
rc: ${{ steps.output.outputs.rc }}
|
||||||
permissions: {} # No job-level permissions are needed because it uses the app-token
|
permissions: {} # No job-level permissions are needed because it uses the app-token
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@9db058b2e6eec20e07760b0e17a0505c78ec3191 # create-workflow-token-action-v2.0.1
|
uses: immich-app/devtools/actions/create-workflow-token@1af396ae134e4bc3b63d947e672bc68bf4ff9dc5 # create-workflow-token-action-v3.0.0
|
||||||
with:
|
with:
|
||||||
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
permission-contents: write
|
||||||
|
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||||
with:
|
with:
|
||||||
token: ${{ steps.token.outputs.token }}
|
token: ${{ steps.token.outputs.token }}
|
||||||
persist-credentials: true
|
persist-credentials: true
|
||||||
ref: main
|
ref: main
|
||||||
|
|
||||||
- name: Setup Mise
|
- 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:
|
with:
|
||||||
github_token: ${{ steps.token.outputs.token }}
|
github_token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
# TODO move to mise
|
# TODO move to mise
|
||||||
- name: Install uv
|
- name: Install uv
|
||||||
uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
|
uses: astral-sh/setup-uv@11f9893b081a58869d3b5fccaea48c9e9e46f990 # v8.3.2
|
||||||
|
|
||||||
- name: Bump version
|
- name: Bump version
|
||||||
env:
|
env:
|
||||||
SERVER_BUMP: ${{ inputs.serverBump }}
|
RELEASE_TYPE: ${{ inputs.releaseType }}
|
||||||
MOBILE_BUMP: ${{ inputs.mobileBump }}
|
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
|
- 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
|
- name: Commit and tag
|
||||||
id: push-tag
|
id: push-tag
|
||||||
@@ -93,6 +105,7 @@ jobs:
|
|||||||
needs: bump_version
|
needs: bump_version
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
|
pull-requests: write
|
||||||
secrets:
|
secrets:
|
||||||
KEY_JKS: ${{ secrets.KEY_JKS }}
|
KEY_JKS: ${{ secrets.KEY_JKS }}
|
||||||
ALIAS: ${{ secrets.ALIAS }}
|
ALIAS: ${{ secrets.ALIAS }}
|
||||||
@@ -123,9 +136,11 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
permission-contents: write
|
||||||
|
permission-actions: read
|
||||||
|
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||||
with:
|
with:
|
||||||
token: ${{ steps.generate-token.outputs.token }}
|
token: ${{ steps.generate-token.outputs.token }}
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
@@ -137,9 +152,10 @@ jobs:
|
|||||||
github-token: ${{ steps.generate-token.outputs.token }}
|
github-token: ${{ steps.generate-token.outputs.token }}
|
||||||
|
|
||||||
- name: Create draft release
|
- name: Create draft release
|
||||||
uses: softprops/action-gh-release@b4309332981a82ec1c5618f44dd2e27cc8bfbfda # v3.0.0
|
uses: softprops/action-gh-release@3d0d9888cb7fd7b750713d6e236d1fcb99157228 # v3.0.2
|
||||||
with:
|
with:
|
||||||
draft: true
|
draft: true
|
||||||
|
prerelease: ${{ needs.bump_version.outputs.rc }}
|
||||||
tag_name: ${{ needs.bump_version.outputs.version }}
|
tag_name: ${{ needs.bump_version.outputs.version }}
|
||||||
token: ${{ steps.generate-token.outputs.token }}
|
token: ${{ steps.generate-token.outputs.token }}
|
||||||
generate_release_notes: true
|
generate_release_notes: true
|
||||||
|
|||||||
6
.github/workflows/preview-label.yaml
vendored
6
.github/workflows/preview-label.yaml
vendored
@@ -14,10 +14,11 @@ jobs:
|
|||||||
pull-requests: write
|
pull-requests: write
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@9db058b2e6eec20e07760b0e17a0505c78ec3191 # create-workflow-token-action-v2.0.1
|
uses: immich-app/devtools/actions/create-workflow-token@1af396ae134e4bc3b63d947e672bc68bf4ff9dc5 # create-workflow-token-action-v3.0.0
|
||||||
with:
|
with:
|
||||||
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
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
|
- uses: immich-app/devtools/actions/sticky-comment@0135acd12ad9f3369b94a2aa3c0ae8c835a4e926 # sticky-comment-action-v1.0.0
|
||||||
with:
|
with:
|
||||||
@@ -32,10 +33,11 @@ jobs:
|
|||||||
pull-requests: write
|
pull-requests: write
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@9db058b2e6eec20e07760b0e17a0505c78ec3191 # create-workflow-token-action-v2.0.1
|
uses: immich-app/devtools/actions/create-workflow-token@1af396ae134e4bc3b63d947e672bc68bf4ff9dc5 # create-workflow-token-action-v3.0.0
|
||||||
with:
|
with:
|
||||||
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
permission-pull-requests: write
|
||||||
|
|
||||||
- uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
- uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||||
with:
|
with:
|
||||||
|
|||||||
7
.github/workflows/sdk.yml
vendored
7
.github/workflows/sdk.yml
vendored
@@ -16,19 +16,20 @@ jobs:
|
|||||||
packages: write
|
packages: write
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@9db058b2e6eec20e07760b0e17a0505c78ec3191 # create-workflow-token-action-v2.0.1
|
uses: immich-app/devtools/actions/create-workflow-token@1af396ae134e4bc3b63d947e672bc68bf4ff9dc5 # create-workflow-token-action-v3.0.0
|
||||||
with:
|
with:
|
||||||
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
permission-contents: read
|
||||||
|
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
token: ${{ steps.token.outputs.token }}
|
token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
- name: Setup Mise
|
- 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:
|
with:
|
||||||
github_token: ${{ steps.token.outputs.token }}
|
github_token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
|
|||||||
14
.github/workflows/static_analysis.yml
vendored
14
.github/workflows/static_analysis.yml
vendored
@@ -20,10 +20,11 @@ jobs:
|
|||||||
should_run: ${{ steps.check.outputs.should_run }}
|
should_run: ${{ steps.check.outputs.should_run }}
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@9db058b2e6eec20e07760b0e17a0505c78ec3191 # create-workflow-token-action-v2.0.1
|
uses: immich-app/devtools/actions/create-workflow-token@1af396ae134e4bc3b63d947e672bc68bf4ff9dc5 # create-workflow-token-action-v3.0.0
|
||||||
with:
|
with:
|
||||||
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
permission-contents: read
|
||||||
|
|
||||||
- name: Check what should run
|
- name: Check what should run
|
||||||
id: check
|
id: check
|
||||||
@@ -33,6 +34,7 @@ jobs:
|
|||||||
filters: |
|
filters: |
|
||||||
mobile:
|
mobile:
|
||||||
- 'mobile/**'
|
- 'mobile/**'
|
||||||
|
- 'i18n/en.json'
|
||||||
force-filters: |
|
force-filters: |
|
||||||
- '.github/workflows/static_analysis.yml'
|
- '.github/workflows/static_analysis.yml'
|
||||||
force-events: 'workflow_dispatch,release'
|
force-events: 'workflow_dispatch,release'
|
||||||
@@ -49,21 +51,23 @@ jobs:
|
|||||||
working-directory: ./mobile
|
working-directory: ./mobile
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@9db058b2e6eec20e07760b0e17a0505c78ec3191 # create-workflow-token-action-v2.0.1
|
uses: immich-app/devtools/actions/create-workflow-token@1af396ae134e4bc3b63d947e672bc68bf4ff9dc5 # create-workflow-token-action-v3.0.0
|
||||||
with:
|
with:
|
||||||
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
permission-contents: read
|
||||||
|
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
token: ${{ steps.token.outputs.token }}
|
token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
- name: Setup Mise
|
- 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:
|
with:
|
||||||
github_token: ${{ steps.token.outputs.token }}
|
github_token: ${{ steps.token.outputs.token }}
|
||||||
|
working_directory: ./mobile
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: flutter pub get
|
run: flutter pub get
|
||||||
@@ -89,6 +93,8 @@ jobs:
|
|||||||
mobile/**/*.g.dart
|
mobile/**/*.g.dart
|
||||||
mobile/**/*.gr.dart
|
mobile/**/*.gr.dart
|
||||||
mobile/**/*.drift.dart
|
mobile/**/*.drift.dart
|
||||||
|
mobile/**/*.g.swift
|
||||||
|
mobile/**/*.g.kt
|
||||||
|
|
||||||
- name: Verify files have not changed
|
- name: Verify files have not changed
|
||||||
if: steps.verify-changed-files.outputs.files_changed == 'true'
|
if: steps.verify-changed-files.outputs.files_changed == 'true'
|
||||||
|
|||||||
153
.github/workflows/test.yml
vendored
153
.github/workflows/test.yml
vendored
@@ -17,10 +17,11 @@ jobs:
|
|||||||
should_run: ${{ steps.check.outputs.should_run }}
|
should_run: ${{ steps.check.outputs.should_run }}
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@9db058b2e6eec20e07760b0e17a0505c78ec3191 # create-workflow-token-action-v2.0.1
|
uses: immich-app/devtools/actions/create-workflow-token@1af396ae134e4bc3b63d947e672bc68bf4ff9dc5 # create-workflow-token-action-v3.0.0
|
||||||
with:
|
with:
|
||||||
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
permission-contents: read
|
||||||
|
|
||||||
- name: Check what should run
|
- name: Check what should run
|
||||||
id: check
|
id: check
|
||||||
@@ -28,6 +29,10 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
github-token: ${{ steps.token.outputs.token }}
|
github-token: ${{ steps.token.outputs.token }}
|
||||||
filters: |
|
filters: |
|
||||||
|
scripts:
|
||||||
|
- 'packages/scripts/**'
|
||||||
|
- 'pnpm-lock.yaml'
|
||||||
|
- 'mise.toml'
|
||||||
i18n:
|
i18n:
|
||||||
- 'i18n/**'
|
- 'i18n/**'
|
||||||
- 'mise.toml'
|
- 'mise.toml'
|
||||||
@@ -41,6 +46,8 @@ jobs:
|
|||||||
- 'server/**'
|
- 'server/**'
|
||||||
- 'pnpm-lock.yaml'
|
- 'pnpm-lock.yaml'
|
||||||
- 'mise.toml'
|
- 'mise.toml'
|
||||||
|
- 'packages/plugin-core/**'
|
||||||
|
- 'packages/plugin-sdk/**'
|
||||||
cli:
|
cli:
|
||||||
- 'packages/cli/**'
|
- 'packages/cli/**'
|
||||||
- 'packages/sdk/**'
|
- 'packages/sdk/**'
|
||||||
@@ -62,6 +69,35 @@ jobs:
|
|||||||
- '.github/workflows/test.yml'
|
- '.github/workflows/test.yml'
|
||||||
force-events: 'workflow_dispatch'
|
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:
|
server-unit-tests:
|
||||||
name: Test & Lint Server
|
name: Test & Lint Server
|
||||||
needs: pre-job
|
needs: pre-job
|
||||||
@@ -71,19 +107,20 @@ jobs:
|
|||||||
contents: read
|
contents: read
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@9db058b2e6eec20e07760b0e17a0505c78ec3191 # create-workflow-token-action-v2.0.1
|
uses: immich-app/devtools/actions/create-workflow-token@1af396ae134e4bc3b63d947e672bc68bf4ff9dc5 # create-workflow-token-action-v3.0.0
|
||||||
with:
|
with:
|
||||||
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
permission-contents: read
|
||||||
|
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
token: ${{ steps.token.outputs.token }}
|
token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
- name: Setup Mise
|
- 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:
|
with:
|
||||||
github_token: ${{ steps.token.outputs.token }}
|
github_token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
@@ -102,19 +139,20 @@ jobs:
|
|||||||
working-directory: ./packages/cli
|
working-directory: ./packages/cli
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@9db058b2e6eec20e07760b0e17a0505c78ec3191 # create-workflow-token-action-v2.0.1
|
uses: immich-app/devtools/actions/create-workflow-token@1af396ae134e4bc3b63d947e672bc68bf4ff9dc5 # create-workflow-token-action-v3.0.0
|
||||||
with:
|
with:
|
||||||
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
permission-contents: read
|
||||||
|
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
token: ${{ steps.token.outputs.token }}
|
token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
- name: Setup Mise
|
- 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:
|
with:
|
||||||
github_token: ${{ steps.token.outputs.token }}
|
github_token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
@@ -133,19 +171,20 @@ jobs:
|
|||||||
working-directory: ./packages/cli
|
working-directory: ./packages/cli
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@9db058b2e6eec20e07760b0e17a0505c78ec3191 # create-workflow-token-action-v2.0.1
|
uses: immich-app/devtools/actions/create-workflow-token@1af396ae134e4bc3b63d947e672bc68bf4ff9dc5 # create-workflow-token-action-v3.0.0
|
||||||
with:
|
with:
|
||||||
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
permission-contents: read
|
||||||
|
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
token: ${{ steps.token.outputs.token }}
|
token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
- name: Setup Mise
|
- 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:
|
with:
|
||||||
github_token: ${{ steps.token.outputs.token }}
|
github_token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
@@ -177,19 +216,20 @@ jobs:
|
|||||||
working-directory: ./web
|
working-directory: ./web
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@9db058b2e6eec20e07760b0e17a0505c78ec3191 # create-workflow-token-action-v2.0.1
|
uses: immich-app/devtools/actions/create-workflow-token@1af396ae134e4bc3b63d947e672bc68bf4ff9dc5 # create-workflow-token-action-v3.0.0
|
||||||
with:
|
with:
|
||||||
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
permission-contents: read
|
||||||
|
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
token: ${{ steps.token.outputs.token }}
|
token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
- name: Setup Mise
|
- 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:
|
with:
|
||||||
github_token: ${{ steps.token.outputs.token }}
|
github_token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
@@ -215,19 +255,20 @@ jobs:
|
|||||||
working-directory: ./web
|
working-directory: ./web
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@9db058b2e6eec20e07760b0e17a0505c78ec3191 # create-workflow-token-action-v2.0.1
|
uses: immich-app/devtools/actions/create-workflow-token@1af396ae134e4bc3b63d947e672bc68bf4ff9dc5 # create-workflow-token-action-v3.0.0
|
||||||
with:
|
with:
|
||||||
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
permission-contents: read
|
||||||
|
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
token: ${{ steps.token.outputs.token }}
|
token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
- name: Setup Mise
|
- 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:
|
with:
|
||||||
github_token: ${{ steps.token.outputs.token }}
|
github_token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
@@ -243,19 +284,20 @@ jobs:
|
|||||||
contents: read
|
contents: read
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@9db058b2e6eec20e07760b0e17a0505c78ec3191 # create-workflow-token-action-v2.0.1
|
uses: immich-app/devtools/actions/create-workflow-token@1af396ae134e4bc3b63d947e672bc68bf4ff9dc5 # create-workflow-token-action-v3.0.0
|
||||||
with:
|
with:
|
||||||
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
permission-contents: read
|
||||||
|
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
token: ${{ steps.token.outputs.token }}
|
token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
- name: Setup Mise
|
- 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:
|
with:
|
||||||
github_token: ${{ steps.token.outputs.token }}
|
github_token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
@@ -293,19 +335,20 @@ jobs:
|
|||||||
working-directory: ./e2e
|
working-directory: ./e2e
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@9db058b2e6eec20e07760b0e17a0505c78ec3191 # create-workflow-token-action-v2.0.1
|
uses: immich-app/devtools/actions/create-workflow-token@1af396ae134e4bc3b63d947e672bc68bf4ff9dc5 # create-workflow-token-action-v3.0.0
|
||||||
with:
|
with:
|
||||||
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
permission-contents: read
|
||||||
|
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
token: ${{ steps.token.outputs.token }}
|
token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
- name: Setup Mise
|
- 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:
|
with:
|
||||||
github_token: ${{ steps.token.outputs.token }}
|
github_token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
@@ -325,20 +368,21 @@ jobs:
|
|||||||
working-directory: ./server
|
working-directory: ./server
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@9db058b2e6eec20e07760b0e17a0505c78ec3191 # create-workflow-token-action-v2.0.1
|
uses: immich-app/devtools/actions/create-workflow-token@1af396ae134e4bc3b63d947e672bc68bf4ff9dc5 # create-workflow-token-action-v3.0.0
|
||||||
with:
|
with:
|
||||||
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
permission-contents: read
|
||||||
|
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
submodules: 'recursive'
|
submodules: 'recursive'
|
||||||
token: ${{ steps.token.outputs.token }}
|
token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
- name: Setup Mise
|
- 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:
|
with:
|
||||||
github_token: ${{ steps.token.outputs.token }}
|
github_token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
@@ -361,23 +405,24 @@ jobs:
|
|||||||
runner: [ubuntu-latest, ubuntu-24.04-arm]
|
runner: [ubuntu-latest, ubuntu-24.04-arm]
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@9db058b2e6eec20e07760b0e17a0505c78ec3191 # create-workflow-token-action-v2.0.1
|
uses: immich-app/devtools/actions/create-workflow-token@1af396ae134e4bc3b63d947e672bc68bf4ff9dc5 # create-workflow-token-action-v3.0.0
|
||||||
with:
|
with:
|
||||||
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
permission-contents: read
|
||||||
|
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
submodules: 'recursive'
|
submodules: 'recursive'
|
||||||
token: ${{ steps.token.outputs.token }}
|
token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
- name: Setup pnpm
|
- name: Setup pnpm
|
||||||
uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8
|
uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
|
||||||
|
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||||
with:
|
with:
|
||||||
node-version-file: '.nvmrc'
|
node-version-file: '.nvmrc'
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
@@ -438,23 +483,24 @@ jobs:
|
|||||||
runner: [ubuntu-latest, ubuntu-24.04-arm]
|
runner: [ubuntu-latest, ubuntu-24.04-arm]
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@9db058b2e6eec20e07760b0e17a0505c78ec3191 # create-workflow-token-action-v2.0.1
|
uses: immich-app/devtools/actions/create-workflow-token@1af396ae134e4bc3b63d947e672bc68bf4ff9dc5 # create-workflow-token-action-v3.0.0
|
||||||
with:
|
with:
|
||||||
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
permission-contents: read
|
||||||
|
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
submodules: 'recursive'
|
submodules: 'recursive'
|
||||||
token: ${{ steps.token.outputs.token }}
|
token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
- name: Setup pnpm
|
- name: Setup pnpm
|
||||||
uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8
|
uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
|
||||||
|
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||||
with:
|
with:
|
||||||
node-version-file: '.nvmrc'
|
node-version-file: '.nvmrc'
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
@@ -546,20 +592,22 @@ jobs:
|
|||||||
contents: read
|
contents: read
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@9db058b2e6eec20e07760b0e17a0505c78ec3191 # create-workflow-token-action-v2.0.1
|
uses: immich-app/devtools/actions/create-workflow-token@1af396ae134e4bc3b63d947e672bc68bf4ff9dc5 # create-workflow-token-action-v3.0.0
|
||||||
with:
|
with:
|
||||||
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
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:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
token: ${{ steps.token.outputs.token }}
|
token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
- name: Setup Mise
|
- 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:
|
with:
|
||||||
github_token: ${{ steps.token.outputs.token }}
|
github_token: ${{ steps.token.outputs.token }}
|
||||||
|
working_directory: ./mobile
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: flutter pub get
|
run: flutter pub get
|
||||||
@@ -583,18 +631,19 @@ jobs:
|
|||||||
working-directory: ./machine-learning
|
working-directory: ./machine-learning
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@9db058b2e6eec20e07760b0e17a0505c78ec3191 # create-workflow-token-action-v2.0.1
|
uses: immich-app/devtools/actions/create-workflow-token@1af396ae134e4bc3b63d947e672bc68bf4ff9dc5 # create-workflow-token-action-v3.0.0
|
||||||
with:
|
with:
|
||||||
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
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:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
token: ${{ steps.token.outputs.token }}
|
token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
- name: Setup Mise
|
- 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:
|
with:
|
||||||
github_token: ${{ steps.token.outputs.token }}
|
github_token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
@@ -613,19 +662,20 @@ jobs:
|
|||||||
working-directory: ./.github
|
working-directory: ./.github
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@9db058b2e6eec20e07760b0e17a0505c78ec3191 # create-workflow-token-action-v2.0.1
|
uses: immich-app/devtools/actions/create-workflow-token@1af396ae134e4bc3b63d947e672bc68bf4ff9dc5 # create-workflow-token-action-v3.0.0
|
||||||
with:
|
with:
|
||||||
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
permission-contents: read
|
||||||
|
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
token: ${{ steps.token.outputs.token }}
|
token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
- name: Setup Mise
|
- 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:
|
with:
|
||||||
github_token: ${{ steps.token.outputs.token }}
|
github_token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
@@ -643,12 +693,13 @@ jobs:
|
|||||||
contents: read
|
contents: read
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@9db058b2e6eec20e07760b0e17a0505c78ec3191 # create-workflow-token-action-v2.0.1
|
uses: immich-app/devtools/actions/create-workflow-token@1af396ae134e4bc3b63d947e672bc68bf4ff9dc5 # create-workflow-token-action-v3.0.0
|
||||||
with:
|
with:
|
||||||
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
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:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
token: ${{ steps.token.outputs.token }}
|
token: ${{ steps.token.outputs.token }}
|
||||||
@@ -664,19 +715,20 @@ jobs:
|
|||||||
contents: read
|
contents: read
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@9db058b2e6eec20e07760b0e17a0505c78ec3191 # create-workflow-token-action-v2.0.1
|
uses: immich-app/devtools/actions/create-workflow-token@1af396ae134e4bc3b63d947e672bc68bf4ff9dc5 # create-workflow-token-action-v3.0.0
|
||||||
with:
|
with:
|
||||||
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
permission-contents: read
|
||||||
|
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
token: ${{ steps.token.outputs.token }}
|
token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
- name: Setup Mise
|
- 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:
|
with:
|
||||||
github_token: ${{ steps.token.outputs.token }}
|
github_token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
@@ -722,19 +774,20 @@ jobs:
|
|||||||
- 5432:5432
|
- 5432:5432
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@9db058b2e6eec20e07760b0e17a0505c78ec3191 # create-workflow-token-action-v2.0.1
|
uses: immich-app/devtools/actions/create-workflow-token@1af396ae134e4bc3b63d947e672bc68bf4ff9dc5 # create-workflow-token-action-v3.0.0
|
||||||
with:
|
with:
|
||||||
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
permission-contents: read
|
||||||
|
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
token: ${{ steps.token.outputs.token }}
|
token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
- name: Setup Mise
|
- 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:
|
with:
|
||||||
github_token: ${{ steps.token.outputs.token }}
|
github_token: ${{ steps.token.outputs.token }}
|
||||||
|
|
||||||
|
|||||||
6
.github/workflows/weblate-lock.yml
vendored
6
.github/workflows/weblate-lock.yml
vendored
@@ -24,10 +24,11 @@ jobs:
|
|||||||
should_run: ${{ steps.check.outputs.should_run }}
|
should_run: ${{ steps.check.outputs.should_run }}
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@9db058b2e6eec20e07760b0e17a0505c78ec3191 # create-workflow-token-action-v2.0.1
|
uses: immich-app/devtools/actions/create-workflow-token@1af396ae134e4bc3b63d947e672bc68bf4ff9dc5 # create-workflow-token-action-v3.0.0
|
||||||
with:
|
with:
|
||||||
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
permission-contents: read
|
||||||
|
|
||||||
- name: Check what should run
|
- name: Check what should run
|
||||||
id: check
|
id: check
|
||||||
@@ -47,10 +48,11 @@ jobs:
|
|||||||
if: ${{ fromJSON(needs.pre-job.outputs.should_run).i18n == true }}
|
if: ${{ fromJSON(needs.pre-job.outputs.should_run).i18n == true }}
|
||||||
steps:
|
steps:
|
||||||
- id: token
|
- id: token
|
||||||
uses: immich-app/devtools/actions/create-workflow-token@9db058b2e6eec20e07760b0e17a0505c78ec3191 # create-workflow-token-action-v2.0.1
|
uses: immich-app/devtools/actions/create-workflow-token@1af396ae134e4bc3b63d947e672bc68bf4ff9dc5 # create-workflow-token-action-v3.0.0
|
||||||
with:
|
with:
|
||||||
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
client-id: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
permission-pull-requests: read
|
||||||
|
|
||||||
- name: Bot review status
|
- name: Bot review status
|
||||||
env:
|
env:
|
||||||
|
|||||||
37
.vscode/settings.json
vendored
37
.vscode/settings.json
vendored
@@ -29,9 +29,6 @@
|
|||||||
"editor.formatOnSave": true,
|
"editor.formatOnSave": true,
|
||||||
"tailwindCSS.lint.suggestCanonicalClasses": "ignore"
|
"tailwindCSS.lint.suggestCanonicalClasses": "ignore"
|
||||||
},
|
},
|
||||||
"svelte.plugin.svelte.compilerWarnings": {
|
|
||||||
"state_referenced_locally": "ignore"
|
|
||||||
},
|
|
||||||
"[typescript]": {
|
"[typescript]": {
|
||||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||||
"editor.formatOnSave": true
|
"editor.formatOnSave": true
|
||||||
@@ -43,36 +40,40 @@
|
|||||||
"eslint.useFlatConfig": true,
|
"eslint.useFlatConfig": true,
|
||||||
"eslint.validate": ["javascript", "typescript", "svelte"],
|
"eslint.validate": ["javascript", "typescript", "svelte"],
|
||||||
"eslint.workingDirectories": [
|
"eslint.workingDirectories": [
|
||||||
{ "directory": "cli", "changeProcessCWD": true },
|
{ "changeProcessCWD": true, "directory": "cli" },
|
||||||
{ "directory": "e2e", "changeProcessCWD": true },
|
{ "changeProcessCWD": true, "directory": "e2e" },
|
||||||
{ "directory": "server", "changeProcessCWD": true },
|
{ "changeProcessCWD": true, "directory": "server" },
|
||||||
{ "directory": "web", "changeProcessCWD": true }
|
{ "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.enabled": true,
|
||||||
"explorer.fileNesting.patterns": {
|
"explorer.fileNesting.patterns": {
|
||||||
"*.dart": "${capture}.g.dart,${capture}.gr.dart,${capture}.drift.dart",
|
"*.dart": "${capture}.g.dart,${capture}.gr.dart,${capture}.drift.dart",
|
||||||
|
"*.js": "${capture}.spec.js,${capture}.mock.js",
|
||||||
"*.ts": "${capture}.spec.ts,${capture}.mock.ts",
|
"*.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"
|
"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": {
|
"search.exclude": {
|
||||||
"**/node_modules": true,
|
"**/.svelte-kit": true,
|
||||||
"**/build": true,
|
"**/build": true,
|
||||||
"**/dist": true,
|
"**/dist": true,
|
||||||
"**/.svelte-kit": true,
|
"**/node_modules": true,
|
||||||
"**/open-api/typescript-sdk/src": true
|
"**/open-api/typescript-sdk/src": true
|
||||||
},
|
},
|
||||||
"svelte.enable-ts-plugin": true,
|
"svelte.enable-ts-plugin": true,
|
||||||
|
"svelte.plugin.svelte.compilerWarnings": {
|
||||||
|
"state_referenced_locally": "ignore"
|
||||||
|
},
|
||||||
"tailwindCSS.experimental.configFile": {
|
"tailwindCSS.experimental.configFile": {
|
||||||
"web/src/app.css": "web/src/**"
|
"web/src/app.css": "web/src/**"
|
||||||
},
|
},
|
||||||
"js/ts.preferences.importModuleSpecifier": "non-relative",
|
|
||||||
"vitest.maximumConfigs": 10
|
"vitest.maximumConfigs": 10
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,4 +4,4 @@
|
|||||||
/web/ @danieldietzler
|
/web/ @danieldietzler
|
||||||
/machine-learning/ @mertalev
|
/machine-learning/ @mertalev
|
||||||
/e2e/ @danieldietzler
|
/e2e/ @danieldietzler
|
||||||
/mobile/ @shenlong-tanwen
|
/mobile/ @shenlong-tanwen @santoshakil @agg23
|
||||||
|
|||||||
@@ -32,11 +32,13 @@
|
|||||||
<a href="readme_i18n/README_zh_TW.md">正體中文</a>
|
<a href="readme_i18n/README_zh_TW.md">正體中文</a>
|
||||||
<a href="readme_i18n/README_uk_UA.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_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_pt_BR.md">Português Brasileiro</a>
|
||||||
<a href="readme_i18n/README_sv_SE.md">Svenska</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_ar_JO.md">العربية</a>
|
||||||
<a href="readme_i18n/README_vi_VN.md">Tiếng Việt</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_th_TH.md">ภาษาไทย</a>
|
||||||
|
<a href="readme_i18n/README_ml_IN.md">മലയാളം</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -7,30 +7,37 @@ backend = "aqua:opentofu/opentofu"
|
|||||||
[tools.opentofu."platforms.linux-arm64"]
|
[tools.opentofu."platforms.linux-arm64"]
|
||||||
checksum = "sha256:d4f2ab15776925864b049bb329d69682851de6f5204f256e9fa86d07a0308850"
|
checksum = "sha256:d4f2ab15776925864b049bb329d69682851de6f5204f256e9fa86d07a0308850"
|
||||||
url = "https://github.com/opentofu/opentofu/releases/download/v1.11.6/tofu_1.11.6_linux_arm64.tar.gz"
|
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"]
|
[tools.opentofu."platforms.linux-arm64-musl"]
|
||||||
checksum = "sha256:d4f2ab15776925864b049bb329d69682851de6f5204f256e9fa86d07a0308850"
|
checksum = "sha256:d4f2ab15776925864b049bb329d69682851de6f5204f256e9fa86d07a0308850"
|
||||||
url = "https://github.com/opentofu/opentofu/releases/download/v1.11.6/tofu_1.11.6_linux_arm64.tar.gz"
|
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"]
|
[tools.opentofu."platforms.linux-x64"]
|
||||||
checksum = "sha256:02800fafa2753a9f50c38483e2fdf5bc353fd62895eb9e25eec9a5145df3a69e"
|
checksum = "sha256:02800fafa2753a9f50c38483e2fdf5bc353fd62895eb9e25eec9a5145df3a69e"
|
||||||
url = "https://github.com/opentofu/opentofu/releases/download/v1.11.6/tofu_1.11.6_linux_amd64.tar.gz"
|
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"]
|
[tools.opentofu."platforms.linux-x64-musl"]
|
||||||
checksum = "sha256:02800fafa2753a9f50c38483e2fdf5bc353fd62895eb9e25eec9a5145df3a69e"
|
checksum = "sha256:02800fafa2753a9f50c38483e2fdf5bc353fd62895eb9e25eec9a5145df3a69e"
|
||||||
url = "https://github.com/opentofu/opentofu/releases/download/v1.11.6/tofu_1.11.6_linux_amd64.tar.gz"
|
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"]
|
[tools.opentofu."platforms.macos-arm64"]
|
||||||
checksum = "sha256:62d7fa8539e13b444827aa0a3b90c5972da5c47e8f8882d9dcf2e430e78840c1"
|
checksum = "sha256:62d7fa8539e13b444827aa0a3b90c5972da5c47e8f8882d9dcf2e430e78840c1"
|
||||||
url = "https://github.com/opentofu/opentofu/releases/download/v1.11.6/tofu_1.11.6_darwin_arm64.tar.gz"
|
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"]
|
[tools.opentofu."platforms.macos-x64"]
|
||||||
checksum = "sha256:1408cdef1c380f914565e6b4bb70794c6b163f195fcb233357f3d6c5745906b6"
|
checksum = "sha256:1408cdef1c380f914565e6b4bb70794c6b163f195fcb233357f3d6c5745906b6"
|
||||||
url = "https://github.com/opentofu/opentofu/releases/download/v1.11.6/tofu_1.11.6_darwin_amd64.tar.gz"
|
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"]
|
[tools.opentofu."platforms.windows-x64"]
|
||||||
checksum = "sha256:27323f70c875b8251bfd7e61a4cffc3ebff4e56ed1e611b955016f0c7077367e"
|
checksum = "sha256:27323f70c875b8251bfd7e61a4cffc3ebff4e56ed1e611b955016f0c7077367e"
|
||||||
url = "https://github.com/opentofu/opentofu/releases/download/v1.11.6/tofu_1.11.6_windows_amd64.tar.gz"
|
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]]
|
[[tools.terragrunt]]
|
||||||
version = "1.0.3"
|
version = "1.0.3"
|
||||||
@@ -39,27 +46,34 @@ backend = "aqua:gruntwork-io/terragrunt"
|
|||||||
[tools.terragrunt."platforms.linux-arm64"]
|
[tools.terragrunt."platforms.linux-arm64"]
|
||||||
checksum = "sha256:e5b60ab05b5214db694e6bc215d8124fb626e277cdb56b86f6147ae110d510fe"
|
checksum = "sha256:e5b60ab05b5214db694e6bc215d8124fb626e277cdb56b86f6147ae110d510fe"
|
||||||
url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.0.3/terragrunt_linux_arm64.tar.gz"
|
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"]
|
[tools.terragrunt."platforms.linux-arm64-musl"]
|
||||||
checksum = "sha256:e5b60ab05b5214db694e6bc215d8124fb626e277cdb56b86f6147ae110d510fe"
|
checksum = "sha256:e5b60ab05b5214db694e6bc215d8124fb626e277cdb56b86f6147ae110d510fe"
|
||||||
url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.0.3/terragrunt_linux_arm64.tar.gz"
|
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"]
|
[tools.terragrunt."platforms.linux-x64"]
|
||||||
checksum = "sha256:6d48049baf82e0bf9c804368dc85cbfeadc10955e33777e9e8de3e020b94b073"
|
checksum = "sha256:6d48049baf82e0bf9c804368dc85cbfeadc10955e33777e9e8de3e020b94b073"
|
||||||
url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.0.3/terragrunt_linux_amd64.tar.gz"
|
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"]
|
[tools.terragrunt."platforms.linux-x64-musl"]
|
||||||
checksum = "sha256:6d48049baf82e0bf9c804368dc85cbfeadc10955e33777e9e8de3e020b94b073"
|
checksum = "sha256:6d48049baf82e0bf9c804368dc85cbfeadc10955e33777e9e8de3e020b94b073"
|
||||||
url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.0.3/terragrunt_linux_amd64.tar.gz"
|
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"]
|
[tools.terragrunt."platforms.macos-arm64"]
|
||||||
checksum = "sha256:aacb5be2ca5475300cbce246dfbd8a45eb47510fbaa70fab8561c49ef5db03aa"
|
checksum = "sha256:aacb5be2ca5475300cbce246dfbd8a45eb47510fbaa70fab8561c49ef5db03aa"
|
||||||
url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.0.3/terragrunt_darwin_arm64.tar.gz"
|
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"]
|
[tools.terragrunt."platforms.macos-x64"]
|
||||||
checksum = "sha256:3133c2251e191aede8e3dd2a5b3aee2e91c5f08f88f117aee40eed9a24c8ef6b"
|
checksum = "sha256:3133c2251e191aede8e3dd2a5b3aee2e91c5f08f88f117aee40eed9a24c8ef6b"
|
||||||
url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.0.3/terragrunt_darwin_amd64.tar.gz"
|
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"]
|
[tools.terragrunt."platforms.windows-x64"]
|
||||||
checksum = "sha256:183b2745b4e04980a6bfa4450ff81956a12596ca22d70f7aaa793980f5b036db"
|
checksum = "sha256:183b2745b4e04980a6bfa4450ff81956a12596ca22d70f7aaa793980f5b036db"
|
||||||
url = "https://github.com/gruntwork-io/terragrunt/releases/download/v1.0.3/terragrunt_windows_amd64.exe.tar.gz"
|
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"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[tools]
|
[tools]
|
||||||
terragrunt = "1.0.3"
|
terragrunt = "1.1.1"
|
||||||
opentofu = "1.11.6"
|
opentofu = "1.12.4"
|
||||||
|
|
||||||
[tasks."tg:fmt"]
|
[tasks."tg:fmt"]
|
||||||
run = "terragrunt hclfmt"
|
run = "terragrunt hclfmt"
|
||||||
|
|||||||
@@ -2,37 +2,35 @@
|
|||||||
# Manual edits may be lost in future updates.
|
# Manual edits may be lost in future updates.
|
||||||
|
|
||||||
provider "registry.opentofu.org/cloudflare/cloudflare" {
|
provider "registry.opentofu.org/cloudflare/cloudflare" {
|
||||||
version = "4.52.7"
|
version = "4.52.8"
|
||||||
constraints = "4.52.7"
|
constraints = "4.52.8"
|
||||||
hashes = [
|
hashes = [
|
||||||
"h1:+O72J3QYiZtYmYYZM/Eh0f4NNfl1BvjX1eju43qTQsQ=",
|
"h1:2uxVaMaGPDJljXqYzuD6xtyfOKdNudtGIgawRTkFJIs=",
|
||||||
"h1:0oqjYIPXcXh7XiDiKI085cHDYQQ5mh8kDl9dmBtvtog=",
|
"h1:34HL1GDpqYdn2iEC+85pCi+NddZf30qH3fJ8y3P6CAM=",
|
||||||
"h1:4b4ESb87MGv5bnadgYe7sK5rEkKMZhbkQcwPubQTsR4=",
|
"h1:4Z+uD+YY/7HrqdypM7DyUK+RtWepoLBc9fqtRQYZx4I=",
|
||||||
"h1:6mTr3eA1Ddb348lLmJuyvn98z4KF+ejqaUEJ76D1rzQ=",
|
"h1:8Ss6wlJL2BHvkIZU1iaHvyUntqNhPFZ0fl6mjsxhctc=",
|
||||||
"h1:9/3YH+9k9HqsvFtbmBf7SO2+xqZeZrXNKzLkjNuhUEA=",
|
"h1:AYeWWMTWaNg01N3hivxr/6YlG41j1RietMnft9vLjUI=",
|
||||||
"h1:Jcq4tBWgyH4/2JsojNBSRaN0mcItVMchO+lynonrlqc=",
|
"h1:BGRNOzo8NUgbx5RwpWkWmr38f/s3txb7mzQhqfM5blI=",
|
||||||
"h1:Y4Vv/2RdP0Q+uxqhOxzOdKxuuEMjXPDcU0vPc5bCQzI=",
|
"h1:EEXRMdC8bPlpLuOU8u+dE8mFJe3BpquFdH6vXcKseY4=",
|
||||||
"h1:a0gW8FBKsbP9Fi0HEDoy49WIbEWVHk9+BR4/iwuBdDQ=",
|
"h1:ENp29e6O6OnaHJlbtmRlyjlAmm3Wt4mGctWUpBY3d0g=",
|
||||||
"h1:gElv6iqJtg8OKN77gbw+MjrkrQmJHPkkMEi1J+0xkpU=",
|
"h1:IAtBAbGVe1npj/n1NvCIZkSYGl7qaPYT+FnVdZ2CZ00=",
|
||||||
"h1:oslXUugD/NQ+duJgT4BhKQyfGbuFOANknMvR73fiOeM=",
|
"h1:QRGGKngrxLORr4CImKXLDDJL5s67zUNBDAgk6FIIFNk=",
|
||||||
"h1:pPItIWii5oymR+geZB219ROSPuSODPLTlM4S/u8xLvM=",
|
"h1:bNql6cC+MfDbaJK3q7a8C1m3A21o7e5mrjBIGtk0RdI=",
|
||||||
"h1:u67GWw8GwD9NDlDzp9Y5VRnSQGcCrE8rSpkGPaBpDl0=",
|
"h1:gmNv+PEOkYDdu+c15bfpcoAE3EI7pgmov9RMf3GZLsI=",
|
||||||
"h1:uUUa9dY0XQOycI8pxg16PFFtL0WCTi9uEJz8trTQ7pU=",
|
"h1:yieZ7NWRYQcLYHUevzDDrrmDkoQICM+3ZeX4i/mw9ME=",
|
||||||
"h1:y3rV8KF2q6GEMANNlf5EkKJurlfbKlIKpjGcdxoy7pQ=",
|
"zh:08b305329a680a9213b2d8e642fbce7e4d97a524b1d2cef59e190ba9d678c477",
|
||||||
"zh:0c904ce31a4c6c4a5b3bf7ff1560e77c0cc7e2450c8553ded8e8c90398e1418b",
|
"zh:47975bd711ee18a46e589822171fa87474a552b332bfc8dea8fd1a64504eed8d",
|
||||||
"zh:36183d310c36373fe4cb936b83c595c6fd3b0a94bc7827f28e5789ccbf59752e",
|
"zh:5640d0d226bbafff3395542456c29feb942ca9c55ac01b4245a34c0590a33363",
|
||||||
"zh:556a568a6f0235e8f41647de9e4d3a1e7b1d6502df8b19b54ec441f1c653ea10",
|
"zh:5b0ad839fafba938c60a95d6b4a865843643591e97573ed2b9c4af3754064f74",
|
||||||
"zh:633ebbd5b0245e75e500ef9be4d9e62288f97e8da3baaa51323892a786d90285",
|
|
||||||
"zh:6acfe60cf52a65ba8f044f748548d2119e7f4fd7f8ebcb14698960d87c68f529",
|
|
||||||
"zh:890df766e9b839623b1f0437355032a3c006226a6c200cd911e15ee1a9014e9f",
|
"zh:890df766e9b839623b1f0437355032a3c006226a6c200cd911e15ee1a9014e9f",
|
||||||
"zh:904acc31ebb9d6ef68c792074b30532ee61bf515f19e0a3c75b46f126cca1f13",
|
"zh:93a9bc1139f5c02a44fdbf51fb2ce0891e2a42033b66febb928ccf72e870408f",
|
||||||
"zh:a1d0a81246afc8750286d3f6fe7a8fbe6460dd2662407b28dbfbabb612e5fa9d",
|
"zh:977f75cdf365686aa16ae02dcc0fc1769bae6f86be6e165393756c940bfb4af0",
|
||||||
"zh:a41a36fe253fc365fe2b7ffc749624688b2693b4634862fda161179ab100029f",
|
"zh:9afcda2660b3dc6ee6329acea532a719225bd1f6cf46f695feb2d77160847c1b",
|
||||||
"zh:a7ef269e77ffa8715c8945a2c14322c7ff159ea44c15f62505f3cbb2cae3b32d",
|
"zh:9e3da67b1b05b03d1f0c18b8677edf3797815b5dd49629f10eb2f457dccbed30",
|
||||||
"zh:b01aa3bed30610633b762df64332b26f8844a68c3960cebcb30f04918efc67fe",
|
"zh:b8d7da230f5266367c1b6b1cf31aa39087e231a87d55b71bb9d5c854ca1fcfe6",
|
||||||
"zh:b069cc2cd18cae10757df3ae030508eac8d55de7e49eda7a5e3e11f2f7fe6455",
|
"zh:cc96e7cd7350456b7a11a53d1c76c73a542de8359f9750f637ff865e8e77be91",
|
||||||
"zh:b2d2c6313729ebb7465dceece374049e2d08bda34473901be9ff46a8836d42b2",
|
"zh:da1f58d067def243047bb7178cb197e2b9c3a791a9eb380ad92b73290615ae29",
|
||||||
"zh:db0e114edaf4bc2f3d4769958807c83022bfbc619a00bdf4c4bd17faa4ab2d8b",
|
"zh:ed5f3e1f59a338bcdce0a2537a8a2f299cfe18c168626799b2a9c97af3d8d3cb",
|
||||||
"zh:ecc0aa8b9044f664fd2aaf8fa992d976578f78478980555b4b8f6148e8d1a5fe",
|
"zh:f16cc31f73a58a26ffe2d93223eddb7c340623a4e2ef38c6091766fd611e3e11",
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ terraform {
|
|||||||
required_providers {
|
required_providers {
|
||||||
cloudflare = {
|
cloudflare = {
|
||||||
source = "cloudflare/cloudflare"
|
source = "cloudflare/cloudflare"
|
||||||
version = "4.52.7"
|
version = "4.52.8"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,37 +2,35 @@
|
|||||||
# Manual edits may be lost in future updates.
|
# Manual edits may be lost in future updates.
|
||||||
|
|
||||||
provider "registry.opentofu.org/cloudflare/cloudflare" {
|
provider "registry.opentofu.org/cloudflare/cloudflare" {
|
||||||
version = "4.52.7"
|
version = "4.52.8"
|
||||||
constraints = "4.52.7"
|
constraints = "4.52.8"
|
||||||
hashes = [
|
hashes = [
|
||||||
"h1:+O72J3QYiZtYmYYZM/Eh0f4NNfl1BvjX1eju43qTQsQ=",
|
"h1:2uxVaMaGPDJljXqYzuD6xtyfOKdNudtGIgawRTkFJIs=",
|
||||||
"h1:0oqjYIPXcXh7XiDiKI085cHDYQQ5mh8kDl9dmBtvtog=",
|
"h1:34HL1GDpqYdn2iEC+85pCi+NddZf30qH3fJ8y3P6CAM=",
|
||||||
"h1:4b4ESb87MGv5bnadgYe7sK5rEkKMZhbkQcwPubQTsR4=",
|
"h1:4Z+uD+YY/7HrqdypM7DyUK+RtWepoLBc9fqtRQYZx4I=",
|
||||||
"h1:6mTr3eA1Ddb348lLmJuyvn98z4KF+ejqaUEJ76D1rzQ=",
|
"h1:8Ss6wlJL2BHvkIZU1iaHvyUntqNhPFZ0fl6mjsxhctc=",
|
||||||
"h1:9/3YH+9k9HqsvFtbmBf7SO2+xqZeZrXNKzLkjNuhUEA=",
|
"h1:AYeWWMTWaNg01N3hivxr/6YlG41j1RietMnft9vLjUI=",
|
||||||
"h1:Jcq4tBWgyH4/2JsojNBSRaN0mcItVMchO+lynonrlqc=",
|
"h1:BGRNOzo8NUgbx5RwpWkWmr38f/s3txb7mzQhqfM5blI=",
|
||||||
"h1:Y4Vv/2RdP0Q+uxqhOxzOdKxuuEMjXPDcU0vPc5bCQzI=",
|
"h1:EEXRMdC8bPlpLuOU8u+dE8mFJe3BpquFdH6vXcKseY4=",
|
||||||
"h1:a0gW8FBKsbP9Fi0HEDoy49WIbEWVHk9+BR4/iwuBdDQ=",
|
"h1:ENp29e6O6OnaHJlbtmRlyjlAmm3Wt4mGctWUpBY3d0g=",
|
||||||
"h1:gElv6iqJtg8OKN77gbw+MjrkrQmJHPkkMEi1J+0xkpU=",
|
"h1:IAtBAbGVe1npj/n1NvCIZkSYGl7qaPYT+FnVdZ2CZ00=",
|
||||||
"h1:oslXUugD/NQ+duJgT4BhKQyfGbuFOANknMvR73fiOeM=",
|
"h1:QRGGKngrxLORr4CImKXLDDJL5s67zUNBDAgk6FIIFNk=",
|
||||||
"h1:pPItIWii5oymR+geZB219ROSPuSODPLTlM4S/u8xLvM=",
|
"h1:bNql6cC+MfDbaJK3q7a8C1m3A21o7e5mrjBIGtk0RdI=",
|
||||||
"h1:u67GWw8GwD9NDlDzp9Y5VRnSQGcCrE8rSpkGPaBpDl0=",
|
"h1:gmNv+PEOkYDdu+c15bfpcoAE3EI7pgmov9RMf3GZLsI=",
|
||||||
"h1:uUUa9dY0XQOycI8pxg16PFFtL0WCTi9uEJz8trTQ7pU=",
|
"h1:yieZ7NWRYQcLYHUevzDDrrmDkoQICM+3ZeX4i/mw9ME=",
|
||||||
"h1:y3rV8KF2q6GEMANNlf5EkKJurlfbKlIKpjGcdxoy7pQ=",
|
"zh:08b305329a680a9213b2d8e642fbce7e4d97a524b1d2cef59e190ba9d678c477",
|
||||||
"zh:0c904ce31a4c6c4a5b3bf7ff1560e77c0cc7e2450c8553ded8e8c90398e1418b",
|
"zh:47975bd711ee18a46e589822171fa87474a552b332bfc8dea8fd1a64504eed8d",
|
||||||
"zh:36183d310c36373fe4cb936b83c595c6fd3b0a94bc7827f28e5789ccbf59752e",
|
"zh:5640d0d226bbafff3395542456c29feb942ca9c55ac01b4245a34c0590a33363",
|
||||||
"zh:556a568a6f0235e8f41647de9e4d3a1e7b1d6502df8b19b54ec441f1c653ea10",
|
"zh:5b0ad839fafba938c60a95d6b4a865843643591e97573ed2b9c4af3754064f74",
|
||||||
"zh:633ebbd5b0245e75e500ef9be4d9e62288f97e8da3baaa51323892a786d90285",
|
|
||||||
"zh:6acfe60cf52a65ba8f044f748548d2119e7f4fd7f8ebcb14698960d87c68f529",
|
|
||||||
"zh:890df766e9b839623b1f0437355032a3c006226a6c200cd911e15ee1a9014e9f",
|
"zh:890df766e9b839623b1f0437355032a3c006226a6c200cd911e15ee1a9014e9f",
|
||||||
"zh:904acc31ebb9d6ef68c792074b30532ee61bf515f19e0a3c75b46f126cca1f13",
|
"zh:93a9bc1139f5c02a44fdbf51fb2ce0891e2a42033b66febb928ccf72e870408f",
|
||||||
"zh:a1d0a81246afc8750286d3f6fe7a8fbe6460dd2662407b28dbfbabb612e5fa9d",
|
"zh:977f75cdf365686aa16ae02dcc0fc1769bae6f86be6e165393756c940bfb4af0",
|
||||||
"zh:a41a36fe253fc365fe2b7ffc749624688b2693b4634862fda161179ab100029f",
|
"zh:9afcda2660b3dc6ee6329acea532a719225bd1f6cf46f695feb2d77160847c1b",
|
||||||
"zh:a7ef269e77ffa8715c8945a2c14322c7ff159ea44c15f62505f3cbb2cae3b32d",
|
"zh:9e3da67b1b05b03d1f0c18b8677edf3797815b5dd49629f10eb2f457dccbed30",
|
||||||
"zh:b01aa3bed30610633b762df64332b26f8844a68c3960cebcb30f04918efc67fe",
|
"zh:b8d7da230f5266367c1b6b1cf31aa39087e231a87d55b71bb9d5c854ca1fcfe6",
|
||||||
"zh:b069cc2cd18cae10757df3ae030508eac8d55de7e49eda7a5e3e11f2f7fe6455",
|
"zh:cc96e7cd7350456b7a11a53d1c76c73a542de8359f9750f637ff865e8e77be91",
|
||||||
"zh:b2d2c6313729ebb7465dceece374049e2d08bda34473901be9ff46a8836d42b2",
|
"zh:da1f58d067def243047bb7178cb197e2b9c3a791a9eb380ad92b73290615ae29",
|
||||||
"zh:db0e114edaf4bc2f3d4769958807c83022bfbc619a00bdf4c4bd17faa4ab2d8b",
|
"zh:ed5f3e1f59a338bcdce0a2537a8a2f299cfe18c168626799b2a9c97af3d8d3cb",
|
||||||
"zh:ecc0aa8b9044f664fd2aaf8fa992d976578f78478980555b4b8f6148e8d1a5fe",
|
"zh:f16cc31f73a58a26ffe2d93223eddb7c340623a4e2ef38c6091766fd611e3e11",
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ terraform {
|
|||||||
required_providers {
|
required_providers {
|
||||||
cloudflare = {
|
cloudflare = {
|
||||||
source = "cloudflare/cloudflare"
|
source = "cloudflare/cloudflare"
|
||||||
version = "4.52.7"
|
version = "4.52.8"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -154,7 +154,7 @@ services:
|
|||||||
|
|
||||||
redis:
|
redis:
|
||||||
container_name: immich_redis
|
container_name: immich_redis
|
||||||
image: docker.io/valkey/valkey:9@sha256:4963247afc4cd33c7d3b2d2816b9f7f8eeebab148d29056c2ca4d7cbc966f2d9
|
image: docker.io/valkey/valkey:9@sha256:8e8d64b405ce18f41b8e5ee20aa4687a8ed0022d1298f2ce31cdcf3a76e09411
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: redis-cli ping || exit 1
|
test: redis-cli ping || exit 1
|
||||||
|
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ services:
|
|||||||
|
|
||||||
redis:
|
redis:
|
||||||
container_name: immich_redis
|
container_name: immich_redis
|
||||||
image: docker.io/valkey/valkey:9@sha256:4963247afc4cd33c7d3b2d2816b9f7f8eeebab148d29056c2ca4d7cbc966f2d9
|
image: docker.io/valkey/valkey:9@sha256:8e8d64b405ce18f41b8e5ee20aa4687a8ed0022d1298f2ce31cdcf3a76e09411
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: redis-cli ping || exit 1
|
test: redis-cli ping || exit 1
|
||||||
restart: always
|
restart: always
|
||||||
@@ -85,7 +85,7 @@ services:
|
|||||||
container_name: immich_prometheus
|
container_name: immich_prometheus
|
||||||
ports:
|
ports:
|
||||||
- 9090:9090
|
- 9090:9090
|
||||||
image: prom/prometheus@sha256:69f5241418838263316593f7274a304b095c40bcf22e57272865da91bd60a8ac
|
image: prom/prometheus@sha256:3c42b892cf723fa54d2f262c37a0e1f80aa8c8ddb1da7b9b0df9455a35a7f893
|
||||||
volumes:
|
volumes:
|
||||||
- ./prometheus.yml:/etc/prometheus/prometheus.yml
|
- ./prometheus.yml:/etc/prometheus/prometheus.yml
|
||||||
- prometheus-data:/prometheus
|
- prometheus-data:/prometheus
|
||||||
@@ -97,7 +97,7 @@ services:
|
|||||||
command: ['./run.sh', '-disable-reporting']
|
command: ['./run.sh', '-disable-reporting']
|
||||||
ports:
|
ports:
|
||||||
- 3000:3000
|
- 3000:3000
|
||||||
image: grafana/grafana:12.4.3-ubuntu@sha256:ca3f764fdc48cebdf22dd206f33ecb0795a9a7210eacd1b5c02204aebd78b223
|
image: grafana/grafana:12.4.5-ubuntu@sha256:00396460e499415c828b7c298f19287c8a0f95e72412ee37ac11723655c2d6b9
|
||||||
volumes:
|
volumes:
|
||||||
- grafana-data:/var/lib/grafana
|
- grafana-data:/var/lib/grafana
|
||||||
|
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ services:
|
|||||||
|
|
||||||
redis:
|
redis:
|
||||||
container_name: immich_redis
|
container_name: immich_redis
|
||||||
image: docker.io/valkey/valkey:9@sha256:4963247afc4cd33c7d3b2d2816b9f7f8eeebab148d29056c2ca4d7cbc966f2d9
|
image: docker.io/valkey/valkey:9@sha256:8e8d64b405ce18f41b8e5ee20aa4687a8ed0022d1298f2ce31cdcf3a76e09411
|
||||||
user: '1000:1000'
|
user: '1000:1000'
|
||||||
security_opt:
|
security_opt:
|
||||||
- no-new-privileges:true
|
- no-new-privileges:true
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ services:
|
|||||||
|
|
||||||
redis:
|
redis:
|
||||||
container_name: immich_redis
|
container_name: immich_redis
|
||||||
image: docker.io/valkey/valkey:9@sha256:4963247afc4cd33c7d3b2d2816b9f7f8eeebab148d29056c2ca4d7cbc966f2d9
|
image: docker.io/valkey/valkey:9@sha256:8e8d64b405ce18f41b8e5ee20aa4687a8ed0022d1298f2ce31cdcf3a76e09411
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: redis-cli ping || exit 1
|
test: redis-cli ping || exit 1
|
||||||
restart: always
|
restart: always
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ DB_DATA_LOCATION=./postgres
|
|||||||
# TZ=Etc/UTC
|
# TZ=Etc/UTC
|
||||||
|
|
||||||
# The Immich version to use. You can pin this to a specific version like "v2.1.0"
|
# 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
|
# 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
|
# Please use only the characters `A-Za-z0-9`, without special characters or spaces
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ You can trigger a database backup manually:
|
|||||||
|
|
||||||
1. Go to **Administration > Job Queues**
|
1. Go to **Administration > Job Queues**
|
||||||
2. Click **Create job** in the top right
|
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.
|
The backup will appear in `UPLOAD_LOCATION/backups` and counts toward your retention limit.
|
||||||
|
|
||||||
|
|||||||
@@ -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-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
|
## 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:
|
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:
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 8.9 KiB |
@@ -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'`.
|
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
|
:::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`.
|
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>
|
<details>
|
||||||
<summary>Migration steps</summary>
|
<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
|
2. Follow the Prerequisites to install VectorChord
|
||||||
3. If Immich does not have superuser permissions, run the SQL command `CREATE EXTENSION vchord CASCADE;`
|
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
|
4. Remove the `DB_VECTOR_EXTENSION=pgvector` environmental variable as it will make Immich still use pgvector if set
|
||||||
|
|||||||
@@ -112,7 +112,7 @@ services:
|
|||||||
traefik.enable: true
|
traefik.enable: true
|
||||||
# increase readingTimeouts for the entrypoint used here
|
# increase readingTimeouts for the entrypoint used here
|
||||||
traefik.http.routers.immich.entrypoints: websecure
|
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
|
traefik.http.services.immich.loadbalancer.server.port: 2283
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ Found Admin:
|
|||||||
- Email=admin@example.com
|
- Email=admin@example.com
|
||||||
- Name=Immich Admin
|
- Name=Immich Admin
|
||||||
? Please choose a new password (optional) immich-is-cool
|
? Please choose a new password (optional) immich-is-cool
|
||||||
|
? Invalidate existing sessions? Yes
|
||||||
The admin password has been updated.
|
The admin password has been updated.
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -90,7 +91,7 @@ immich-admin list-users
|
|||||||
[
|
[
|
||||||
{
|
{
|
||||||
id: 'e65e6f88-2a30-4dbe-8dd9-1885f4889b53',
|
id: 'e65e6f88-2a30-4dbe-8dd9-1885f4889b53',
|
||||||
email: 'immich@example.com.com',
|
email: 'immich@example.com',
|
||||||
name: 'Immich Admin',
|
name: 'Immich Admin',
|
||||||
storageLabel: 'admin',
|
storageLabel: 'admin',
|
||||||
externalPath: null,
|
externalPath: null,
|
||||||
|
|||||||
@@ -1,5 +1,30 @@
|
|||||||
# System Integrity
|
# 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
|
## Folder checks
|
||||||
|
|
||||||
:::info
|
:::info
|
||||||
@@ -40,7 +65,7 @@ The above error messages show that the server has previously (successfully) writ
|
|||||||
|
|
||||||
### Ignoring the checks
|
### 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:
|
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:
|
||||||
:::
|
:::
|
||||||
|
|
||||||
|
|||||||
@@ -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.
|
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
|
```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.
|
You can find the generated client SDK in the `packages/sdk/client` for Typescript SDK and `mobile/openapi` for Dart SDK.
|
||||||
|
|||||||
@@ -218,7 +218,7 @@ When the Dev Container starts, it automatically:
|
|||||||
- Debug ports: 9230 (workers), 9231 (API)
|
- Debug ports: 9230 (workers), 9231 (API)
|
||||||
|
|
||||||
:::info
|
:::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
|
### Accessing Services
|
||||||
|
|||||||
@@ -12,11 +12,11 @@ Our [GitHub Repository](https://github.com/immich-app/immich) is a [monorepo](ht
|
|||||||
| `.vscode/` | VSCode debug launch profiles |
|
| `.vscode/` | VSCode debug launch profiles |
|
||||||
| `packages/cli` | Source code for the CLI |
|
| `packages/cli` | Source code for the CLI |
|
||||||
| `packages/sdk` | Source code for the generated OpenAPI SDK |
|
| `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 |
|
| `docker/` | Docker compose resources for dev, test, production |
|
||||||
| `design/` | Screenshots and logos for the README |
|
| `design/` | Screenshots and logos for the README |
|
||||||
| `docs/` | Source code for the [https://immich.app](https://immich.app) website |
|
| `docs/` | Source code for the [https://immich.app](https://immich.app) website |
|
||||||
| `machine-learning/` | Source code for the `immich-machine-learning` docker image |
|
| `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 |
|
| `mobile/` | Source code for the mobile app, both Android and iOS |
|
||||||
| `server/` | Source code for the `immich-server` docker image |
|
| `server/` | Source code for the `immich-server` docker image |
|
||||||
| `web/` | Source code for the `web` |
|
| `web/` | Source code for the `web` |
|
||||||
|
|||||||
@@ -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.
|
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
|
:::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:
|
When contributing code through a pull request, please check the following:
|
||||||
|
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ This environment includes the services below. Additional details are available i
|
|||||||
- Redis
|
- Redis
|
||||||
- PostgreSQL development database with exposed port `5432` so you can use any database client to access it
|
- 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
|
:::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`.
|
[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.
|
1. Clone the project repo.
|
||||||
2. Run `cp docker/example.env docker/.env`.
|
2. Run `cp docker/example.env docker/.env`.
|
||||||
3. Edit `docker/.env` to provide values for the required variable `UPLOAD_LOCATION`.
|
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:
|
5. From the root directory, run:
|
||||||
|
|
||||||
```bash title="Start development server"
|
```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.
|
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.
|
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:**
|
**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`)
|
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')`)
|
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';`
|
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`)
|
7. After making changes in `@immich/ui`, rebuild it (`pnpm run build`)
|
||||||
|
|
||||||
### Mobile app
|
### 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.
|
2. Run `mise //mobile:translation` to generate the translation file.
|
||||||
3. Change to the `mobile/` directory and run `flutter run` to start the app.
|
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
|
#### 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:
|
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:
|
||||||
|
|||||||
@@ -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:
|
The e2e tests can be run by first starting up a test production environment via:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
make e2e
|
mise e2e
|
||||||
```
|
```
|
||||||
|
|
||||||
Before you can run the tests, you need to run the following commands _once_:
|
Before you can run the tests, you need to run the following commands _once_:
|
||||||
|
|||||||
@@ -99,6 +99,7 @@ Options:
|
|||||||
-H, --include-hidden Include hidden folders (default: false, env: IMMICH_INCLUDE_HIDDEN)
|
-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 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)
|
-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)
|
-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)
|
-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)
|
-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/
|
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
|
You can use the `--json-output` option to get a json printed which includes
|
||||||
three keys: `newFiles`, `duplicates` and `newAssets`. Due to some logging
|
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.
|
output you will need to strip the first three lines of output to get the json.
|
||||||
|
|||||||
@@ -15,14 +15,14 @@ When using "Deduplicate All" or viewing suggestions, Immich automatically presel
|
|||||||
|
|
||||||
### Synchronizing metadata
|
### 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 |
|
| Name | Description |
|
||||||
| ----------- | ------------------------------------------------------------------------------------------------------------------------------- |
|
| ----------- | ------------------------------------------------------------------------------------------------------------------------------ |
|
||||||
| Album | The kept assets will be added to _every_ album that the other assets in the group belong to. |
|
| 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, every kept asset will also be added to favorites. |
|
| 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 assets. |
|
| 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 all the kept assets. |
|
| Description | Descriptions from each asset are combined together and synchronized to the kept asset. |
|
||||||
| Visibility | The most restrictive visibility is applied to the kept assets. |
|
| 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. |
|
| 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. |
|
| Tag | Tags from all assets in the group are merged and applied to the kept asset. |
|
||||||
|
|||||||
@@ -13,7 +13,9 @@ import MobileAppBackup from '/docs/partials/_mobile-app-backup.md';
|
|||||||
:::info Android verification
|
:::info Android verification
|
||||||
Below are the SHA-256 fingerprints for the certificates signing the android applications.
|
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`
|
`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:
|
- 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`
|
`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`
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ services:
|
|||||||
ports:
|
ports:
|
||||||
- "8888:80"
|
- "8888:80"
|
||||||
environment:
|
environment:
|
||||||
PGADMIN_DEFAULT_EMAIL: user-name@domain-name.com
|
PGADMIN_DEFAULT_EMAIL: admin@example.com
|
||||||
PGADMIN_DEFAULT_PASSWORD: strong-password
|
PGADMIN_DEFAULT_PASSWORD: strong-password
|
||||||
volumes:
|
volumes:
|
||||||
- pgadmin-data:/var/lib/pgadmin
|
- pgadmin-data:/var/lib/pgadmin
|
||||||
|
|||||||
BIN
docs/docs/guides/img/email-ms-settings.webp
Normal file
BIN
docs/docs/guides/img/email-ms-settings.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 102 KiB |
19
docs/docs/guides/smtp-microsoft365.md
Normal file
19
docs/docs/guides/smtp-microsoft365.md
Normal 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" />
|
||||||
@@ -19,7 +19,7 @@ If this does not work, try running `docker compose up -d --force-recreate`.
|
|||||||
|
|
||||||
| Variable | Description | Default | Containers |
|
| 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 |
|
| `UPLOAD_LOCATION` | Host path for uploads | | server |
|
||||||
| `DB_DATA_LOCATION` | Host path for Postgres database | | database |
|
| `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`.
|
\*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.
|
\*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.
|
||||||
|
|
||||||
|
|||||||
@@ -20,9 +20,11 @@ Hardware and software requirements for Immich:
|
|||||||
- **RAM**: Minimum 6GB, recommended 8GB.
|
- **RAM**: Minimum 6GB, recommended 8GB.
|
||||||
- **CPU**: Minimum 2 cores, recommended 4 cores.
|
- **CPU**: Minimum 2 cores, recommended 4 cores.
|
||||||
- Immich runs on the `amd64` and `arm64` platforms.
|
- 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.
|
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 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.
|
- **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.
|
- The generation of thumbnails and transcoded video can increase the size of the photo library by 10-20% on average.
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ sidebar_position: 85
|
|||||||
:::note
|
:::note
|
||||||
This is a community contribution and not officially supported by the Immich team, but included here for convenience.
|
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.
|
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.
|
||||||
|
|||||||
@@ -121,7 +121,7 @@ alt="Go to Docker Tab and visit the address listed next to immich-web"
|
|||||||
width="90%"
|
width="90%"
|
||||||
alt="Go to Docker Tab and visit the address listed next to immich-web"
|
alt="Go to Docker Tab and visit the address listed next to immich-web"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
:::tip
|
:::tip
|
||||||
|
|||||||
@@ -28,11 +28,15 @@ docker image prune
|
|||||||
|
|
||||||
## Versioning Policy
|
## 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.
|
Immich follows [semantic versioning][semver], which tags releases in the format `<major>.<minor>.<patch>`.
|
||||||
You can configure your Docker image to point to the current major version by using a metatag, such as `:v2`.
|
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.
|
The mobile app is typically compatible with the current and prior major version. However, the server is only compatible with the matching major version.
|
||||||
Switching back to an earlier version, even within the same minor release tag, is not supported.
|
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/
|
[semver]: https://semver.org/
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
The mobile app can be downloaded from the following places:
|
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)
|
- [Apple App Store](https://apps.apple.com/us/app/immich/id1613945652)
|
||||||
- [F-Droid](https://f-droid.org/packages/app.alextran.immich)
|
- [Google Play Store](https://play.google.com/store/apps/details?id=app.alextran.immich)
|
||||||
- [GitHub Releases (apk)](https://github.com/immich-app/immich/releases)
|
- [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/)
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
# @generated - this file is auto-generated by `mise lock` https://mise.en.dev/dev-tools/mise-lock.html
|
# @generated - this file is auto-generated by `mise lock` https://mise.en.dev/dev-tools/mise-lock.html
|
||||||
|
|
||||||
[[tools.wrangler]]
|
[[tools.wrangler]]
|
||||||
version = "4.66.0"
|
version = "4.110.0"
|
||||||
backend = "npm:wrangler"
|
backend = "npm:wrangler"
|
||||||
|
|
||||||
|
[tools.wrangler.options]
|
||||||
|
allow_builds = '["esbuild", "sharp", "workerd"]'
|
||||||
|
|||||||
@@ -28,4 +28,4 @@ run = "prettier --write ."
|
|||||||
run = "wrangler pages deploy build --project-name=${PROJECT_NAME} --branch=${BRANCH_NAME}"
|
run = "wrangler pages deploy build --project-name=${PROJECT_NAME} --branch=${BRANCH_NAME}"
|
||||||
|
|
||||||
[tools]
|
[tools]
|
||||||
wrangler = "4.91.0"
|
wrangler = "4.111.0"
|
||||||
|
|||||||
@@ -40,7 +40,8 @@
|
|||||||
"@docusaurus/tsconfig": "^3.10.0",
|
"@docusaurus/tsconfig": "^3.10.0",
|
||||||
"@docusaurus/types": "^3.10.0",
|
"@docusaurus/types": "^3.10.0",
|
||||||
"prettier": "^3.7.4",
|
"prettier": "^3.7.4",
|
||||||
"typescript": "^6.0.0"
|
"@typescript/native": "npm:typescript@^7.0.2",
|
||||||
|
"typescript": "npm:@typescript/typescript6@^6.0.2"
|
||||||
},
|
},
|
||||||
"browserslist": {
|
"browserslist": {
|
||||||
"production": [
|
"production": [
|
||||||
|
|||||||
4
docs/static/archived-versions.json
vendored
4
docs/static/archived-versions.json
vendored
@@ -1,4 +1,8 @@
|
|||||||
[
|
[
|
||||||
|
{
|
||||||
|
"label": "v3.0.3",
|
||||||
|
"url": "https://docs.v3.0.3.archive.immich.app"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"label": "v2.7.5",
|
"label": "v2.7.5",
|
||||||
"url": "https://docs.v2.7.5.archive.immich.app"
|
"url": "https://docs.v2.7.5.archive.immich.app"
|
||||||
|
|||||||
@@ -4,7 +4,8 @@ services:
|
|||||||
e2e-auth-server:
|
e2e-auth-server:
|
||||||
container_name: immich-e2e-auth-server
|
container_name: immich-e2e-auth-server
|
||||||
build:
|
build:
|
||||||
context: ../packages/e2e-auth-server
|
context: ../
|
||||||
|
dockerfile: packages/e2e-auth-server/Dockerfile
|
||||||
ports:
|
ports:
|
||||||
- 2286:2286
|
- 2286:2286
|
||||||
|
|
||||||
@@ -44,7 +45,7 @@ services:
|
|||||||
|
|
||||||
redis:
|
redis:
|
||||||
container_name: immich-e2e-redis
|
container_name: immich-e2e-redis
|
||||||
image: docker.io/valkey/valkey:9@sha256:4963247afc4cd33c7d3b2d2816b9f7f8eeebab148d29056c2ca4d7cbc966f2d9
|
image: docker.io/valkey/valkey:9@sha256:8e8d64b405ce18f41b8e5ee20aa4687a8ed0022d1298f2ce31cdcf3a76e09411
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: redis-cli ping || exit 1
|
test: redis-cli ping || exit 1
|
||||||
|
|
||||||
|
|||||||
@@ -41,9 +41,18 @@ export default typescriptEslint.config([
|
|||||||
'@typescript-eslint/no-floating-promises': 'error',
|
'@typescript-eslint/no-floating-promises': 'error',
|
||||||
'unicorn/prefer-module': 'off',
|
'unicorn/prefer-module': 'off',
|
||||||
'unicorn/import-style': '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,
|
curly: 2,
|
||||||
'prettier/prettier': 0,
|
'prettier/prettier': 0,
|
||||||
'unicorn/prevent-abbreviations': 'off',
|
'unicorn/name-replacements': 'off',
|
||||||
'unicorn/filename-case': 'off',
|
'unicorn/filename-case': 'off',
|
||||||
'unicorn/no-null': 'off',
|
'unicorn/no-null': 'off',
|
||||||
'unicorn/prefer-top-level-await': 'off',
|
'unicorn/prefer-top-level-await': 'off',
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "immich-e2e",
|
"name": "immich-e2e",
|
||||||
"version": "2.7.5",
|
"version": "3.0.3",
|
||||||
"description": "",
|
"description": "",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
@@ -32,7 +32,7 @@
|
|||||||
"@playwright/test": "^1.44.1",
|
"@playwright/test": "^1.44.1",
|
||||||
"@socket.io/component-emitter": "^3.1.2",
|
"@socket.io/component-emitter": "^3.1.2",
|
||||||
"@types/luxon": "^3.4.2",
|
"@types/luxon": "^3.4.2",
|
||||||
"@types/node": "^24.12.4",
|
"@types/node": "^24.13.3",
|
||||||
"@types/pg": "^8.15.1",
|
"@types/pg": "^8.15.1",
|
||||||
"@types/pngjs": "^6.0.4",
|
"@types/pngjs": "^6.0.4",
|
||||||
"@types/supertest": "^7.0.0",
|
"@types/supertest": "^7.0.0",
|
||||||
@@ -40,7 +40,7 @@
|
|||||||
"eslint": "^10.0.0",
|
"eslint": "^10.0.0",
|
||||||
"eslint-config-prettier": "^10.1.8",
|
"eslint-config-prettier": "^10.1.8",
|
||||||
"eslint-plugin-prettier": "^5.1.3",
|
"eslint-plugin-prettier": "^5.1.3",
|
||||||
"eslint-plugin-unicorn": "^64.0.0",
|
"eslint-plugin-unicorn": "^72.0.0",
|
||||||
"exiftool-vendored": "^35.0.0",
|
"exiftool-vendored": "^35.0.0",
|
||||||
"globals": "^17.0.0",
|
"globals": "^17.0.0",
|
||||||
"luxon": "^3.4.4",
|
"luxon": "^3.4.4",
|
||||||
@@ -51,10 +51,10 @@
|
|||||||
"sharp": "^0.34.5",
|
"sharp": "^0.34.5",
|
||||||
"socket.io-client": "^4.7.4",
|
"socket.io-client": "^4.7.4",
|
||||||
"supertest": "^7.0.0",
|
"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",
|
"typescript-eslint": "^8.28.0",
|
||||||
"utimes": "^5.2.1",
|
"utimes": "^5.2.1",
|
||||||
"vite-tsconfig-paths": "^6.1.1",
|
|
||||||
"vitest": "^4.0.0"
|
"vitest": "^4.0.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -118,7 +118,7 @@ describe('/admin/database-backups', () => {
|
|||||||
|
|
||||||
expect(status).toBe(201);
|
expect(status).toBe(201);
|
||||||
|
|
||||||
cookie = headers['set-cookie'][0].split(';')[0];
|
cookie = headers['set-cookie'][0].split(';', 1)[0];
|
||||||
|
|
||||||
await expect
|
await expect
|
||||||
.poll(
|
.poll(
|
||||||
@@ -224,7 +224,7 @@ describe('/admin/database-backups', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
expect(status).toBe(201);
|
expect(status).toBe(201);
|
||||||
cookie = headers['set-cookie'][0].split(';')[0];
|
cookie = headers['set-cookie'][0].split(';', 1)[0];
|
||||||
|
|
||||||
await expect
|
await expect
|
||||||
.poll(
|
.poll(
|
||||||
@@ -295,7 +295,7 @@ describe('/admin/database-backups', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
expect(status).toBe(201);
|
expect(status).toBe(201);
|
||||||
cookie = headers['set-cookie'][0].split(';')[0];
|
cookie = headers['set-cookie'][0].split(';', 1)[0];
|
||||||
|
|
||||||
await expect
|
await expect
|
||||||
.poll(
|
.poll(
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ describe('/admin/maintenance', () => {
|
|||||||
|
|
||||||
expect(status).toBe(201);
|
expect(status).toBe(201);
|
||||||
|
|
||||||
cookie = headers['set-cookie'][0].split(';')[0];
|
cookie = headers['set-cookie'][0].split(';', 1)[0];
|
||||||
expect(cookie).toEqual(
|
expect(cookie).toEqual(
|
||||||
expect.stringMatching(/^immich_maintenance_token=[A-Za-z0-9-_]*\.[A-Za-z0-9-_]*\.[A-Za-z0-9-_]*$/),
|
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,
|
interval: 500,
|
||||||
timeout: 10_000,
|
timeout: 60_000,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.toBeTruthy();
|
.toBeTruthy();
|
||||||
@@ -149,7 +149,7 @@ describe('/admin/maintenance', () => {
|
|||||||
const { status, body } = await request(app)
|
const { status, body } = await request(app)
|
||||||
.post('/admin/maintenance/login')
|
.post('/admin/maintenance/login')
|
||||||
.send({
|
.send({
|
||||||
token: cookie!.split('=')[1].trim(),
|
token: cookie!.split('=', 2)[1].trim(),
|
||||||
});
|
});
|
||||||
expect(status).toBe(201);
|
expect(status).toBe(201);
|
||||||
expect(body).toEqual(
|
expect(body).toEqual(
|
||||||
@@ -190,7 +190,7 @@ describe('/admin/maintenance', () => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
interval: 500,
|
interval: 500,
|
||||||
timeout: 10_000,
|
timeout: 60_000,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.toBeFalsy();
|
.toBeFalsy();
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ test.describe('Maintenance', () => {
|
|||||||
test('maintenance shows no options to users until they authenticate', async ({ page }) => {
|
test('maintenance shows no options to users until they authenticate', async ({ page }) => {
|
||||||
const setCookie = await utils.enterMaintenance(admin.accessToken);
|
const setCookie = await utils.enterMaintenance(admin.accessToken);
|
||||||
const cookie = setCookie
|
const cookie = setCookie
|
||||||
?.map((cookie) => cookie.split(';')[0].split('='))
|
?.map((cookie) => cookie.split(';', 1)[0].split('='))
|
||||||
?.find(([name]) => name === 'immich_maintenance_token');
|
?.find(([name]) => name === 'immich_maintenance_token');
|
||||||
|
|
||||||
expect(cookie).toBeTruthy();
|
expect(cookie).toBeTruthy();
|
||||||
|
|||||||
@@ -120,6 +120,7 @@ describe('/albums', () => {
|
|||||||
}),
|
}),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
// eslint-disable-next-line unicorn/no-unreadable-array-destructuring
|
||||||
[user2Albums[0]] = await Promise.all([
|
[user2Albums[0]] = await Promise.all([
|
||||||
getAlbumInfo({ id: user2Albums[0].id }, { headers: asBearerAuth(user2.accessToken) }),
|
getAlbumInfo({ id: user2Albums[0].id }, { headers: asBearerAuth(user2.accessToken) }),
|
||||||
deleteUserAdmin({ id: user3.userId, userAdminDeleteDto: {} }, { headers: asBearerAuth(admin.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)
|
const { status, body } = await request(app)
|
||||||
.post('/albums')
|
.post('/albums')
|
||||||
.send({ albumName: 'New album', albumUsers: [{ role: AlbumUserRole.Editor, userId: user1.userId }] })
|
.send({ albumName: 'New album', albumUsers: [{ role: AlbumUserRole.Editor, userId: user1.userId }] })
|
||||||
.set('Authorization', `Bearer ${user1.accessToken}`);
|
.set('Authorization', `Bearer ${user1.accessToken}`);
|
||||||
expect(status).toBe(400);
|
expect(status).toBe(201);
|
||||||
expect(body).toEqual(errorDto.badRequest('Cannot share album with owner'));
|
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}`)
|
.set('Authorization', `Bearer ${user1.accessToken}`)
|
||||||
.send({ albumUsers: [{ userId: user1.userId, role: AlbumUserRole.Editor }] });
|
.send({ albumUsers: [{ userId: user1.userId, role: AlbumUserRole.Editor }] });
|
||||||
|
|
||||||
expect(status).toBe(400);
|
expect(status).toBe(200);
|
||||||
expect(body).toEqual(errorDto.badRequest('User already added'));
|
expect(body.albumUsers.length).toEqual(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not be able to add existing user to shared album', async () => {
|
it('should not be able to add existing user to shared album', async () => {
|
||||||
@@ -744,8 +746,8 @@ describe('/albums', () => {
|
|||||||
.set('Authorization', `Bearer ${user1.accessToken}`)
|
.set('Authorization', `Bearer ${user1.accessToken}`)
|
||||||
.send({ albumUsers: [{ userId: user2.userId, role: AlbumUserRole.Editor }] });
|
.send({ albumUsers: [{ userId: user2.userId, role: AlbumUserRole.Editor }] });
|
||||||
|
|
||||||
expect(status).toBe(400);
|
expect(status).toBe(200);
|
||||||
expect(body).toEqual(errorDto.badRequest('User already added'));
|
expect(body.albumUsers.length).toEqual(2);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -795,5 +797,22 @@ describe('/albums', () => {
|
|||||||
expect(status).toBe(400);
|
expect(status).toBe(400);
|
||||||
expect(body).toEqual(errorDto.badRequest('Not found or no album.share access'));
|
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'));
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -781,7 +781,7 @@ describe('/asset', () => {
|
|||||||
exifImageWidth: 4032,
|
exifImageWidth: 4032,
|
||||||
exifImageHeight: 3024,
|
exifImageHeight: 3024,
|
||||||
latitude: 41.2203,
|
latitude: 41.2203,
|
||||||
longitude: -96.071_625,
|
longitude: -96.071625,
|
||||||
make: 'Apple',
|
make: 'Apple',
|
||||||
model: 'iPhone 7',
|
model: 'iPhone 7',
|
||||||
lensModel: 'iPhone 7 back camera 3.99mm f/1.8',
|
lensModel: 'iPhone 7 back camera 3.99mm f/1.8',
|
||||||
@@ -973,9 +973,9 @@ describe('/asset', () => {
|
|||||||
fileSizeInByte: 31_175_472,
|
fileSizeInByte: 31_175_472,
|
||||||
focalLength: 18.3,
|
focalLength: 18.3,
|
||||||
iso: 100,
|
iso: 100,
|
||||||
latitude: 36.613_24,
|
latitude: 36.61324,
|
||||||
lensModel: '18.3mm F2.8',
|
lensModel: '18.3mm F2.8',
|
||||||
longitude: -121.897_85,
|
longitude: -121.89785,
|
||||||
make: 'RICOH IMAGING COMPANY, LTD.',
|
make: 'RICOH IMAGING COMPANY, LTD.',
|
||||||
model: 'RICOH GR III',
|
model: 'RICOH GR III',
|
||||||
orientation: '1',
|
orientation: '1',
|
||||||
|
|||||||
669
e2e/src/specs/server/api/integrity.e2e-spec.ts
Normal file
669
e2e/src/specs/server/api/integrity.e2e-spec.ts
Normal 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');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -2,6 +2,7 @@ import { AssetVisibility, LoginResponseDto } from '@immich/sdk';
|
|||||||
import { readFile } from 'node:fs/promises';
|
import { readFile } from 'node:fs/promises';
|
||||||
import { basename, join } from 'node:path';
|
import { basename, join } from 'node:path';
|
||||||
import { Socket } from 'socket.io-client';
|
import { Socket } from 'socket.io-client';
|
||||||
|
import { createUserDto } from 'src/fixtures';
|
||||||
import { errorDto } from 'src/responses';
|
import { errorDto } from 'src/responses';
|
||||||
import { app, testAssetDir, utils } from 'src/utils';
|
import { app, testAssetDir, utils } from 'src/utils';
|
||||||
import request from 'supertest';
|
import request from 'supertest';
|
||||||
@@ -9,28 +10,48 @@ import { afterAll, beforeAll, describe, expect, it } from 'vitest';
|
|||||||
|
|
||||||
describe('/map', () => {
|
describe('/map', () => {
|
||||||
let websocket: Socket;
|
let websocket: Socket;
|
||||||
|
let partnerWebsocket: Socket;
|
||||||
let admin: LoginResponseDto;
|
let admin: LoginResponseDto;
|
||||||
|
let partner: LoginResponseDto;
|
||||||
|
let partnerArchivedAssetId: string;
|
||||||
|
let adminArchivedAssetId: string;
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
await utils.resetDatabase();
|
await utils.resetDatabase();
|
||||||
admin = await utils.adminSetup({ onboarding: false });
|
admin = await utils.adminSetup({ onboarding: false });
|
||||||
|
partner = await utils.userSetup(admin.accessToken, createUserDto.user1);
|
||||||
|
|
||||||
websocket = await utils.connectWebsocket(admin.accessToken);
|
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();
|
utils.resetEvents();
|
||||||
const uploadFile = async (input: string) => {
|
const uploadFile = async (accessToken: string, input: string) => {
|
||||||
const filepath = join(testAssetDir, input);
|
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) },
|
assetData: { bytes: await readFile(filepath), filename: basename(filepath) },
|
||||||
});
|
});
|
||||||
await utils.waitForWebsocketEvent({ event: 'assetUpload', id });
|
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(() => {
|
afterAll(() => {
|
||||||
utils.disconnectWebsocket(websocket);
|
utils.disconnectWebsocket(websocket);
|
||||||
|
utils.disconnectWebsocket(partnerWebsocket);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('GET /map/markers', () => {
|
describe('GET /map/markers', () => {
|
||||||
@@ -40,7 +61,6 @@ describe('/map', () => {
|
|||||||
expect(body).toEqual(errorDto.unauthorized);
|
expect(body).toEqual(errorDto.unauthorized);
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO archive one of these assets
|
|
||||||
it('should get map markers for all non-archived assets', async () => {
|
it('should get map markers for all non-archived assets', async () => {
|
||||||
const { status, body } = await request(app)
|
const { status, body } = await request(app)
|
||||||
.get('/map/markers')
|
.get('/map/markers')
|
||||||
@@ -55,7 +75,7 @@ describe('/map', () => {
|
|||||||
country: 'United States of America',
|
country: 'United States of America',
|
||||||
id: expect.any(String),
|
id: expect.any(String),
|
||||||
lat: expect.closeTo(39.115),
|
lat: expect.closeTo(39.115),
|
||||||
lon: expect.closeTo(-108.400_968),
|
lon: expect.closeTo(-108.400968),
|
||||||
state: 'Colorado',
|
state: 'Colorado',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -63,13 +83,34 @@ describe('/map', () => {
|
|||||||
country: 'United States of America',
|
country: 'United States of America',
|
||||||
id: expect.any(String),
|
id: expect.any(String),
|
||||||
lat: expect.closeTo(41.2203),
|
lat: expect.closeTo(41.2203),
|
||||||
lon: expect.closeTo(-96.071_625),
|
lon: expect.closeTo(-96.071625),
|
||||||
state: 'Nebraska',
|
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 () => {
|
it('should get all map markers', async () => {
|
||||||
const { status, body } = await request(app)
|
const { status, body } = await request(app)
|
||||||
.get('/map/markers')
|
.get('/map/markers')
|
||||||
@@ -82,7 +123,7 @@ describe('/map', () => {
|
|||||||
country: 'United States of America',
|
country: 'United States of America',
|
||||||
id: expect.any(String),
|
id: expect.any(String),
|
||||||
lat: expect.closeTo(39.115),
|
lat: expect.closeTo(39.115),
|
||||||
lon: expect.closeTo(-108.400_968),
|
lon: expect.closeTo(-108.400968),
|
||||||
state: 'Colorado',
|
state: 'Colorado',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -90,7 +131,7 @@ describe('/map', () => {
|
|||||||
country: 'United States of America',
|
country: 'United States of America',
|
||||||
id: expect.any(String),
|
id: expect.any(String),
|
||||||
lat: expect.closeTo(41.2203),
|
lat: expect.closeTo(41.2203),
|
||||||
lon: expect.closeTo(-96.071_625),
|
lon: expect.closeTo(-96.071625),
|
||||||
state: 'Nebraska',
|
state: 'Nebraska',
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
@@ -147,20 +188,20 @@ describe('/map', () => {
|
|||||||
const reverseGeocodeTestCases = [
|
const reverseGeocodeTestCases = [
|
||||||
{
|
{
|
||||||
name: 'Vaucluse',
|
name: 'Vaucluse',
|
||||||
lat: -33.858_977_058_663_13,
|
lat: -33.85897705866313,
|
||||||
lon: 151.278_490_730_270_48,
|
lon: 151.27849073027048,
|
||||||
results: [{ city: 'Vaucluse', state: 'New South Wales', country: 'Australia' }],
|
results: [{ city: 'Vaucluse', state: 'New South Wales', country: 'Australia' }],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Ravenhall',
|
name: 'Ravenhall',
|
||||||
lat: -37.765_732_399_174_75,
|
lat: -37.76573239917475,
|
||||||
lon: 144.752_453_164_883_3,
|
lon: 144.7524531648833,
|
||||||
results: [{ city: 'Ravenhall', state: 'Victoria', country: 'Australia' }],
|
results: [{ city: 'Ravenhall', state: 'Victoria', country: 'Australia' }],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Scarborough',
|
name: 'Scarborough',
|
||||||
lat: -31.894_346_156_789_997,
|
lat: -31.894346156789997,
|
||||||
lon: 115.757_617_103_904_64,
|
lon: 115.75761710390464,
|
||||||
results: [{ city: 'Scarborough', state: 'Western Australia', country: 'Australia' }],
|
results: [{ city: 'Scarborough', state: 'Western Australia', country: 'Australia' }],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ const loginWithOAuth = async (sub: OAuthUser | string, redirectUri?: string) =>
|
|||||||
});
|
});
|
||||||
|
|
||||||
// login
|
// 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)
|
const response2 = await request(authServer.external + response1.location)
|
||||||
.post('')
|
.post('')
|
||||||
.set('Cookie', response1.cookies)
|
.set('Cookie', response1.cookies)
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import {
|
|||||||
AssetVisibility,
|
AssetVisibility,
|
||||||
deleteAssets,
|
deleteAssets,
|
||||||
LoginResponseDto,
|
LoginResponseDto,
|
||||||
|
SharedLinkType,
|
||||||
updateAsset,
|
updateAsset,
|
||||||
} from '@immich/sdk';
|
} from '@immich/sdk';
|
||||||
import { DateTime } from 'luxon';
|
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
|
// note: the coordinates here are not the actual coordinates of the images and are random for most of them
|
||||||
const coordinates = [
|
const coordinates = [
|
||||||
{ latitude: 48.853_41, longitude: 2.3488 }, // paris
|
{ latitude: 48.85341, longitude: 2.3488 }, // paris
|
||||||
{ latitude: 35.6895, longitude: 139.691_71 }, // tokyo
|
{ latitude: 35.6895, longitude: 139.69171 }, // tokyo
|
||||||
{ latitude: 52.524_37, longitude: 13.410_53 }, // berlin
|
{ latitude: 52.52437, longitude: 13.41053 }, // berlin
|
||||||
{ latitude: 1.314_663_1, longitude: 103.845_409_3 }, // singapore
|
{ latitude: 1.3146631, longitude: 103.8454093 }, // singapore
|
||||||
{ latitude: 41.013_84, longitude: 28.949_66 }, // istanbul
|
{ latitude: 41.01384, longitude: 28.94966 }, // istanbul
|
||||||
{ latitude: 5.556_02, longitude: -0.1969 }, // accra
|
{ latitude: 5.55602, longitude: -0.1969 }, // accra
|
||||||
{ latitude: 37.544_270_6, longitude: -4.727_752_8 }, // andalusia
|
{ latitude: 37.5442706, longitude: -4.7277528 }, // andalusia
|
||||||
{ latitude: 23.133_02, longitude: -82.383_04 }, // havana
|
{ latitude: 23.13302, longitude: -82.38304 }, // havana
|
||||||
{ latitude: 41.694_11, longitude: 44.833_68 }, // tbilisi
|
{ latitude: 41.69411, longitude: 44.83368 }, // tbilisi
|
||||||
{ latitude: 31.222_22, longitude: 121.458_06 }, // shanghai
|
{ latitude: 31.22222, longitude: 121.45806 }, // shanghai
|
||||||
{ latitude: 38.9711, longitude: -109.7137 }, // thompson springs
|
{ latitude: 38.9711, longitude: -109.7137 }, // thompson springs
|
||||||
{ latitude: 40.714_27, longitude: -74.005_97 }, // new york
|
{ latitude: 40.71427, longitude: -74.00597 }, // new york
|
||||||
{ latitude: 47.040_57, longitude: 9.068_04 }, // glarus
|
{ latitude: 47.04057, longitude: 9.06804 }, // glarus
|
||||||
{ latitude: 32.771_52, longitude: -89.116_73 }, // philadelphia
|
{ latitude: 32.77152, longitude: -89.11673 }, // philadelphia
|
||||||
{ latitude: 31.634_16, longitude: -7.999_94 }, // marrakesh
|
{ latitude: 31.63416, longitude: -7.99994 }, // marrakesh
|
||||||
{ latitude: 38.523_735_4, longitude: -78.488_619_4 }, // tanners ridge
|
{ latitude: 38.5237354, longitude: -78.4886194 }, // tanners ridge
|
||||||
{ latitude: 59.938_63, longitude: 30.314_13 }, // st. petersburg
|
{ latitude: 59.93863, longitude: 30.31413 }, // st. petersburg
|
||||||
{ latitude: 0, longitude: 0 }, // null island
|
{ latitude: 0, longitude: 0 }, // null island
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -111,7 +112,7 @@ describe('/search', () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
await Promise.all(updates);
|
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 });
|
await utils.waitForWebsocketEvent({ event: 'assetUpdate', id: assets[i].id });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -259,17 +260,6 @@ describe('/search', () => {
|
|||||||
assets: [assetHeic],
|
assets: [assetHeic],
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
{
|
|
||||||
should: "should search city ('')",
|
|
||||||
deferred: () => ({
|
|
||||||
dto: {
|
|
||||||
city: '',
|
|
||||||
visibility: AssetVisibility.Timeline,
|
|
||||||
includeNull: true,
|
|
||||||
},
|
|
||||||
assets: [assetLast],
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
should: 'should search city (null)',
|
should: 'should search city (null)',
|
||||||
deferred: () => ({
|
deferred: () => ({
|
||||||
@@ -291,18 +281,6 @@ describe('/search', () => {
|
|||||||
assets: [assetDensity],
|
assets: [assetDensity],
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
{
|
|
||||||
should: "should search state ('')",
|
|
||||||
deferred: () => ({
|
|
||||||
dto: {
|
|
||||||
state: '',
|
|
||||||
visibility: AssetVisibility.Timeline,
|
|
||||||
withExif: true,
|
|
||||||
includeNull: true,
|
|
||||||
},
|
|
||||||
assets: [assetLast, assetNotocactus],
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
should: 'should search state (null)',
|
should: 'should search state (null)',
|
||||||
deferred: () => ({
|
deferred: () => ({
|
||||||
@@ -324,17 +302,6 @@ describe('/search', () => {
|
|||||||
assets: [assetFalcon],
|
assets: [assetFalcon],
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
{
|
|
||||||
should: "should search country ('')",
|
|
||||||
deferred: () => ({
|
|
||||||
dto: {
|
|
||||||
country: '',
|
|
||||||
visibility: AssetVisibility.Timeline,
|
|
||||||
includeNull: true,
|
|
||||||
},
|
|
||||||
assets: [assetLast],
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
should: 'should search country (null)',
|
should: 'should search country (null)',
|
||||||
deferred: () => ({
|
deferred: () => ({
|
||||||
@@ -391,6 +358,32 @@ describe('/search', () => {
|
|||||||
expect(body.assets.items).toHaveLength(assets.length);
|
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', () => {
|
describe('POST /search/random', () => {
|
||||||
|
|||||||
@@ -91,12 +91,14 @@ describe('/server', () => {
|
|||||||
it('should respond with the server version', async () => {
|
it('should respond with the server version', async () => {
|
||||||
const { status, body } = await request(app).get('/server/version');
|
const { status, body } = await request(app).get('/server/version');
|
||||||
expect(status).toBe(200);
|
expect(status).toBe(200);
|
||||||
expect(body).toEqual({
|
expect(body).toEqual(
|
||||||
major: expect.any(Number),
|
expect.objectContaining({
|
||||||
minor: expect.any(Number),
|
major: expect.any(Number),
|
||||||
patch: expect.any(Number),
|
minor: expect.any(Number),
|
||||||
prerelease: null,
|
patch: expect.any(Number),
|
||||||
});
|
}),
|
||||||
|
);
|
||||||
|
expect(Object.keys(body)).toEqual(expect.arrayContaining(['major', 'minor', 'patch', 'prerelease']));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ describe(`immich login`, () => {
|
|||||||
it('should login and save auth.yml with 600', async () => {
|
it('should login and save auth.yml with 600', async () => {
|
||||||
const admin = await utils.adminSetup();
|
const admin = await utils.adminSetup();
|
||||||
const key = await utils.createApiKey(admin.accessToken, [Permission.All]);
|
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([
|
expect(stdout.split('\n')).toEqual([
|
||||||
'Logging in to http://127.0.0.1:2285/api',
|
'Logging in to http://127.0.0.1:2285/api',
|
||||||
'Logged in as admin@immich.cloud',
|
'Logged in as admin@immich.cloud',
|
||||||
@@ -48,7 +48,7 @@ describe(`immich login`, () => {
|
|||||||
it('should login without /api in the url', async () => {
|
it('should login without /api in the url', async () => {
|
||||||
const admin = await utils.adminSetup();
|
const admin = await utils.adminSetup();
|
||||||
const key = await utils.createApiKey(admin.accessToken, [Permission.All]);
|
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([
|
expect(stdout.split('\n')).toEqual([
|
||||||
'Logging in to http://127.0.0.1:2285',
|
'Logging in to http://127.0.0.1:2285',
|
||||||
'Discovered API at http://127.0.0.1:2285/api',
|
'Discovered API at http://127.0.0.1:2285/api',
|
||||||
|
|||||||
@@ -119,7 +119,9 @@ describe(`immich upload`, () => {
|
|||||||
const baseDir = `/tmp/upload/`;
|
const baseDir = `/tmp/upload/`;
|
||||||
|
|
||||||
const testPaths = Object.keys(files).map((filePath) => `${baseDir}/${filePath}`);
|
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}`);
|
const commandLine = paths.map((argument) => `${baseDir}/${argument}`);
|
||||||
|
|
||||||
@@ -135,7 +137,9 @@ describe(`immich upload`, () => {
|
|||||||
const assets = await getAssetStatistics({}, { headers: asKeyAuth(key) });
|
const assets = await getAssetStatistics({}, { headers: asKeyAuth(key) });
|
||||||
expect(assets.total).toBe(expectedCount);
|
expect(assets.total).toBe(expectedCount);
|
||||||
|
|
||||||
testPaths.map((filePath) => utils.removeImageFile(filePath));
|
for (const filePath of testPaths) {
|
||||||
|
utils.removeImageFile(filePath);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -646,7 +650,7 @@ describe(`immich upload`, () => {
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
expect(stdout).toBe('');
|
expect(stdout).toBe('');
|
||||||
expect(stderr).toEqual(`error: option '-n, --dry-run' cannot be used with option '-h, --skip-hash'`);
|
expect(stderr).toEqual(`error: option '-n, --dry-run' cannot be used with option '--skip-hash'`);
|
||||||
expect(exitCode).not.toBe(0);
|
expect(exitCode).not.toBe(0);
|
||||||
|
|
||||||
const assets = await getAssetStatistics({}, { headers: asKeyAuth(key) });
|
const assets = await getAssetStatistics({}, { headers: asKeyAuth(key) });
|
||||||
|
|||||||
@@ -65,6 +65,10 @@ describe(`immich-admin`, () => {
|
|||||||
child.stdout.on('data', (chunk) => {
|
child.stdout.on('data', (chunk) => {
|
||||||
data += chunk;
|
data += chunk;
|
||||||
if (data.includes('Please choose a new password (optional)')) {
|
if (data.includes('Please choose a new password (optional)')) {
|
||||||
|
child.stdin.write('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.includes('Invalidate existing sessions?')) {
|
||||||
child.stdin.end('\n');
|
child.stdin.end('\n');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
41
e2e/src/specs/web/integrity.e2e-spec.ts
Normal file
41
e2e/src/specs/web/integrity.e2e-spec.ts
Normal 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());
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -21,7 +21,7 @@ export const randomImageFromString = async (
|
|||||||
let seedNumber = 0;
|
let seedNumber = 0;
|
||||||
for (let i = 0; i < seed.length; i++) {
|
for (let i = 0; i < seed.length; i++) {
|
||||||
seedNumber = (seedNumber << 5) - seedNumber + (seed.codePointAt(i) ?? 0);
|
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 });
|
return randomImage(new SeededRandom(Math.abs(seedNumber)), { width, height });
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ export function generateAsset(
|
|||||||
const asset: MockTimelineAsset = {
|
const asset: MockTimelineAsset = {
|
||||||
id: assetId,
|
id: assetId,
|
||||||
ownerId,
|
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),
|
thumbhash: generateThumbhash(rng),
|
||||||
localDateTime: date.toISOString(),
|
localDateTime: date.toISOString(),
|
||||||
fileCreatedAt: date.toISOString(),
|
fileCreatedAt: date.toISOString(),
|
||||||
@@ -214,7 +214,7 @@ export function generateTimelineData(config: TimelineConfig): MockTimelineData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create a mock album from random assets
|
// 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)
|
// 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));
|
const albumSize = Math.min(allAssets.length, globalRng.nextInt(10, 31));
|
||||||
|
|||||||
@@ -172,11 +172,7 @@ function shouldIncludeAsset(
|
|||||||
if (isArchived !== undefined && actuallyArchived !== isArchived) {
|
if (isArchived !== undefined && actuallyArchived !== isArchived) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (isFavorite !== undefined && actuallyFavorited !== isFavorite) {
|
return isFavorite === undefined || actuallyFavorited === isFavorite;
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Get summary for all buckets (mimics getTimeBuckets API)
|
* Get summary for all buckets (mimics getTimeBuckets API)
|
||||||
@@ -361,7 +357,7 @@ export function getAsset(
|
|||||||
owner?: UserResponseDto,
|
owner?: UserResponseDto,
|
||||||
): AssetResponseDto | undefined {
|
): AssetResponseDto | undefined {
|
||||||
// Search through all buckets for the asset
|
// Search through all buckets for the asset
|
||||||
const buckets = [...timelineData.buckets.values()];
|
const buckets = timelineData.buckets.values().toArray();
|
||||||
for (const assets of buckets) {
|
for (const assets of buckets) {
|
||||||
const asset = assets.find((a) => a.id === assetId);
|
const asset = assets.find((a) => a.id === assetId);
|
||||||
if (asset) {
|
if (asset) {
|
||||||
@@ -395,7 +391,7 @@ export function getAlbum(
|
|||||||
|
|
||||||
// Get the actual asset objects from the timeline data
|
// Get the actual asset objects from the timeline data
|
||||||
const albumAssets: AssetResponseDto[] = [];
|
const albumAssets: AssetResponseDto[] = [];
|
||||||
const allAssets = [...timelineData.buckets.values()].flat();
|
const allAssets = timelineData.buckets.values().toArray().flat();
|
||||||
|
|
||||||
for (const assetId of album.assetIds) {
|
for (const assetId of album.assetIds) {
|
||||||
const assetConfig = allAssets.find((a) => a.id === assetId);
|
const assetConfig = allAssets.find((a) => a.id === assetId);
|
||||||
|
|||||||
@@ -143,7 +143,7 @@ export function validateTimelineConfig(config: TimelineConfig): void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Validate seed if provided
|
// 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');
|
throw new Error('Seed must be a non-negative integer');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -153,11 +153,8 @@ export function getMockAsset(
|
|||||||
const isInDifferentPeriod = (date1: DateTime, date2: DateTime): boolean => {
|
const isInDifferentPeriod = (date1: DateTime, date2: DateTime): boolean => {
|
||||||
if (unit === 'day') {
|
if (unit === 'day') {
|
||||||
return !date1.startOf('day').equals(date2.startOf('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') {
|
if (direction === 'next') {
|
||||||
|
|||||||
@@ -82,6 +82,9 @@ export const setupBaseMockApiRoutes = async (context: BrowserContext, adminUserI
|
|||||||
cast: {
|
cast: {
|
||||||
gCastEnabled: false,
|
gCastEnabled: false,
|
||||||
},
|
},
|
||||||
|
recentlyAdded: {
|
||||||
|
sidebarWeb: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -40,7 +40,8 @@ export const setupTimelineMockApiRoutes = async (
|
|||||||
contentType: 'application/json',
|
contentType: 'application/json',
|
||||||
json: getTimeBuckets(timelineRestData, isTrashed, isArchived, isFavorite, albumId, changes),
|
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');
|
const timeBucket = url.searchParams.get('timeBucket');
|
||||||
if (!timeBucket) {
|
if (!timeBucket) {
|
||||||
return route.continue();
|
return route.continue();
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ test.describe('asset-viewer', () => {
|
|||||||
assets.push(...timeBucket);
|
assets.push(...timeBucket);
|
||||||
}
|
}
|
||||||
for (const yearMonth of timelineRestData.buckets.keys()) {
|
for (const yearMonth of timelineRestData.buckets.keys()) {
|
||||||
const [year, month] = yearMonth.split('-');
|
const [year, month] = yearMonth.split('-', 2);
|
||||||
yearMonths.push(`${year}-${Number(month)}`);
|
yearMonths.push(`${year}-${Number(month)}`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -110,6 +110,7 @@ export async function enableTagsPreference(context: BrowserContext) {
|
|||||||
download: { archiveSize: 4_294_967_296, includeEmbeddedVideos: false },
|
download: { archiveSize: 4_294_967_296, includeEmbeddedVideos: false },
|
||||||
purchase: { showSupportBadge: true, hideBuyButtonUntil: '2100-02-12T00:00:00.000Z' },
|
purchase: { showSupportBadge: true, hideBuyButtonUntil: '2100-02-12T00:00:00.000Z' },
|
||||||
cast: { gCastEnabled: false },
|
cast: { gCastEnabled: false },
|
||||||
|
recentlyAdded: { sidebarWeb: false },
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
/* eslint-disable unicorn/no-this-outside-of-class */
|
||||||
import type { AssetResponseDto } from '@immich/sdk';
|
import type { AssetResponseDto } from '@immich/sdk';
|
||||||
import { expect, Page } from '@playwright/test';
|
import { expect, Page } from '@playwright/test';
|
||||||
|
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ test.describe('Timeline', () => {
|
|||||||
assets.push(...timeBucket);
|
assets.push(...timeBucket);
|
||||||
}
|
}
|
||||||
for (const yearMonth of timelineRestData.buckets.keys()) {
|
for (const yearMonth of timelineRestData.buckets.keys()) {
|
||||||
const [year, month] = yearMonth.split('-');
|
const [year, month] = yearMonth.split('-', 2);
|
||||||
yearMonths.push(`${year}-${Number(month)}`);
|
yearMonths.push(`${year}-${Number(month)}`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ export const sleep = (ms: number) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const padYearMonth = (yearMonth: string) => {
|
export const padYearMonth = (yearMonth: string) => {
|
||||||
const [year, month] = yearMonth.split('-');
|
const [year, month] = yearMonth.split('-', 2);
|
||||||
return `${year}-${month.padStart(2, '0')}`;
|
return `${year}-${month.padStart(2, '0')}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -72,6 +72,7 @@ export const thumbnailUtils = {
|
|||||||
},
|
},
|
||||||
async queryThumbnailInViewport(page: Page, collector: (assetId: string) => boolean) {
|
async queryThumbnailInViewport(page: Page, collector: (assetId: string) => boolean) {
|
||||||
const assetIds: string[] = [];
|
const assetIds: string[] = [];
|
||||||
|
// eslint-disable-next-line unicorn/no-this-outside-of-class
|
||||||
for (const thumb of await this.locator(page).all()) {
|
for (const thumb of await this.locator(page).all()) {
|
||||||
const box = await thumb.boundingBox();
|
const box = await thumb.boundingBox();
|
||||||
if (box) {
|
if (box) {
|
||||||
@@ -151,6 +152,7 @@ export const timelineUtils = {
|
|||||||
page.evaluate(() => {
|
page.evaluate(() => {
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
|
|
||||||
return document.querySelector('#asset-grid').scrollTop;
|
return document.querySelector('#asset-grid').scrollTop;
|
||||||
});
|
});
|
||||||
await expect.poll(queryTop).toBeGreaterThan(0);
|
await expect.poll(queryTop).toBeGreaterThan(0);
|
||||||
@@ -177,6 +179,7 @@ export const assetViewerUtils = {
|
|||||||
page.evaluate(() => {
|
page.evaluate(() => {
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
|
// eslint-disable-next-line unicorn/no-optional-chaining-on-undeclared-variable
|
||||||
return document.activeElement?.dataset?.asset;
|
return document.activeElement?.dataset?.asset;
|
||||||
});
|
});
|
||||||
await expect(poll(page, activeElement, (result) => result === assetId)).resolves.toBe(assetId);
|
await expect(poll(page, activeElement, (result) => result === assetId)).resolves.toBe(assetId);
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
/* eslint-disable unicorn/no-top-level-assignment-in-function */
|
||||||
import {
|
import {
|
||||||
AssetMediaCreateDto,
|
AssetMediaCreateDto,
|
||||||
AssetMediaResponseDto,
|
AssetMediaResponseDto,
|
||||||
@@ -177,7 +178,7 @@ export const utils = {
|
|||||||
resetDatabase: async (tables?: string[]) => {
|
resetDatabase: async (tables?: string[]) => {
|
||||||
client = await utils.connectDatabase();
|
client = await utils.connectDatabase();
|
||||||
|
|
||||||
tables = tables || [
|
tables ||= [
|
||||||
// TODO e2e test for deleting a stack, since it is quite complex
|
// TODO e2e test for deleting a stack, since it is quite complex
|
||||||
'stack',
|
'stack',
|
||||||
'library',
|
'library',
|
||||||
@@ -192,6 +193,7 @@ export const utils = {
|
|||||||
'user',
|
'user',
|
||||||
'system_metadata',
|
'system_metadata',
|
||||||
'tag',
|
'tag',
|
||||||
|
'integrity_report',
|
||||||
];
|
];
|
||||||
|
|
||||||
const truncateTables = tables.filter((table) => table !== 'system_metadata');
|
const truncateTables = tables.filter((table) => table !== 'system_metadata');
|
||||||
@@ -303,7 +305,7 @@ export const utils = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
adminSetup: async (options?: AdminSetupOptions) => {
|
adminSetup: async (options?: AdminSetupOptions) => {
|
||||||
options = options || { onboarding: true };
|
options ||= { onboarding: true };
|
||||||
|
|
||||||
await signUpAdmin({ signUpDto: signupDto.admin });
|
await signUpAdmin({ signUpDto: signupDto.admin });
|
||||||
const response = await login({ loginCredentialDto: loginDto.admin });
|
const response = await login({ loginCredentialDto: loginDto.admin });
|
||||||
@@ -544,6 +546,7 @@ export const utils = {
|
|||||||
{
|
{
|
||||||
headers: asBearerAuth(accessToken),
|
headers: asBearerAuth(accessToken),
|
||||||
fetch: (...args: Parameters<typeof fetch>) =>
|
fetch: (...args: Parameters<typeof fetch>) =>
|
||||||
|
// eslint-disable-next-line unicorn/no-invalid-argument-count, unicorn/prefer-await
|
||||||
fetch(...args).then((response) => {
|
fetch(...args).then((response) => {
|
||||||
setCookie = response.headers.getSetCookie();
|
setCookie = response.headers.getSetCookie();
|
||||||
return response;
|
return response;
|
||||||
@@ -559,10 +562,54 @@ export const utils = {
|
|||||||
mkdirSync(`${testAssetDir}/temp`, { recursive: true });
|
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) {
|
async move(source: string, dest: string) {
|
||||||
return executeCommand('docker', ['exec', 'immich-e2e-server', 'mv', source, dest]).promise;
|
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) => {
|
createBackup: async (accessToken: string) => {
|
||||||
await utils.createJob(accessToken, {
|
await utils.createJob(accessToken, {
|
||||||
name: ManualJobName.BackupDatabase,
|
name: ManualJobName.BackupDatabase,
|
||||||
@@ -579,10 +626,8 @@ export const utils = {
|
|||||||
|
|
||||||
resetBackups: async (accessToken: string) => {
|
resetBackups: async (accessToken: string) => {
|
||||||
const { backups } = await listDatabaseBackups({ headers: asBearerAuth(accessToken) });
|
const { backups } = await listDatabaseBackups({ headers: asBearerAuth(accessToken) });
|
||||||
|
|
||||||
const backupFiles = backups.map((b) => b.filename);
|
|
||||||
await deleteDatabaseBackup(
|
await deleteDatabaseBackup(
|
||||||
{ databaseBackupDeleteDto: { backups: backupFiles } },
|
{ databaseBackupDeleteDto: { backups: backups.map((dto) => dto.filename) } },
|
||||||
{ headers: asBearerAuth(accessToken) },
|
{ headers: asBearerAuth(accessToken) },
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@@ -631,7 +676,7 @@ export const utils = {
|
|||||||
|
|
||||||
cliLogin: async (accessToken: string) => {
|
cliLogin: async (accessToken: string) => {
|
||||||
const key = await utils.createApiKey(accessToken, [Permission.All]);
|
const key = await utils.createApiKey(accessToken, [Permission.All]);
|
||||||
await immichCli(['login', app, `${key.secret}`]);
|
await immichCli(['login', app, key.secret]);
|
||||||
return key.secret;
|
return key.secret;
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -663,6 +708,7 @@ export const utils = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// eslint-disable-next-line unicorn/no-top-level-side-effects
|
||||||
utils.initSdk();
|
utils.initSdk();
|
||||||
|
|
||||||
if (!existsSync(`${testAssetDir}/albums`)) {
|
if (!existsSync(`${testAssetDir}/albums`)) {
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
"allowSyntheticDefaultImports": true,
|
"allowSyntheticDefaultImports": true,
|
||||||
"resolveJsonModule": true,
|
"resolveJsonModule": true,
|
||||||
"target": "es2023",
|
"target": "es2023",
|
||||||
|
"lib": ["dom", "ESNext"],
|
||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
"outDir": "./dist",
|
"outDir": "./dist",
|
||||||
"incremental": true,
|
"incremental": true,
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import tsconfigPaths from 'vite-tsconfig-paths';
|
|
||||||
import { defineConfig } from 'vitest/config';
|
import { defineConfig } from 'vitest/config';
|
||||||
|
|
||||||
const skipDockerSetup = process.env.VITEST_DISABLE_DOCKER_SETUP === 'true';
|
const skipDockerSetup = process.env.VITEST_DISABLE_DOCKER_SETUP === 'true';
|
||||||
@@ -24,5 +23,7 @@ export default defineConfig({
|
|||||||
maxWorkers: 1,
|
maxWorkers: 1,
|
||||||
isolate: false,
|
isolate: false,
|
||||||
},
|
},
|
||||||
plugins: [tsconfigPaths()],
|
resolve: {
|
||||||
|
tsconfigPaths: true,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import tsconfigPaths from 'vite-tsconfig-paths';
|
|
||||||
import { defineConfig } from 'vitest/config';
|
import { defineConfig } from 'vitest/config';
|
||||||
|
|
||||||
const skipDockerSetup = process.env.VITEST_DISABLE_DOCKER_SETUP === 'true';
|
const skipDockerSetup = process.env.VITEST_DISABLE_DOCKER_SETUP === 'true';
|
||||||
@@ -24,5 +23,7 @@ export default defineConfig({
|
|||||||
maxWorkers: 1,
|
maxWorkers: 1,
|
||||||
isolate: false,
|
isolate: false,
|
||||||
},
|
},
|
||||||
plugins: [tsconfigPaths()],
|
resolve: {
|
||||||
|
tsconfigPaths: true,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
33
i18n/af.json
33
i18n/af.json
@@ -5,31 +5,34 @@
|
|||||||
"acknowledge": "Neem kennis",
|
"acknowledge": "Neem kennis",
|
||||||
"action": "Aksie",
|
"action": "Aksie",
|
||||||
"action_common_update": "Werk by",
|
"action_common_update": "Werk by",
|
||||||
|
"action_description": "’n Stel van aksies om op die gefiltreerde bates uit te voer",
|
||||||
"actions": "Aksies",
|
"actions": "Aksies",
|
||||||
"active": "Aktief",
|
"active": "Aktief",
|
||||||
|
"active_count": "Aktief: {count}",
|
||||||
"activity": "Aktiwiteite",
|
"activity": "Aktiwiteite",
|
||||||
"activity_changed": "Aktiwiteit is {enabled, select, true {geaktiveer} other {gedeaktiveer}}",
|
|
||||||
"add": "Voeg toe",
|
"add": "Voeg toe",
|
||||||
"add_a_description": "Voeg ’n beskrywing toe",
|
"add_a_description": "Voeg ’n beskrywing toe",
|
||||||
"add_a_location": "Voeg ’n ligging toe",
|
"add_a_location": "Voeg ’n ligging toe",
|
||||||
"add_a_name": "Voeg ’n naam toe",
|
"add_a_name": "Voeg ’n naam toe",
|
||||||
"add_a_title": "Voeg ’n titel 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_birthday": "Voeg ’n verjaarsdag toe",
|
||||||
"add_endpoint": "Voeg eindpunt toe",
|
"add_endpoint": "Voeg eindpunt toe",
|
||||||
"add_exclusion_pattern": "Voeg uitsluitingspatroon toe",
|
"add_exclusion_pattern": "Voeg uitsluitingspatroon toe",
|
||||||
"add_location": "Voeg ligging toe",
|
"add_location": "Voeg ligging toe",
|
||||||
"add_more_users": "Voeg meer gebruikers toe",
|
|
||||||
"add_partner": "Voeg vennoot toe",
|
"add_partner": "Voeg vennoot toe",
|
||||||
"add_path": "Voeg pad toe",
|
|
||||||
"add_photos": "Voeg foto’s toe",
|
"add_photos": "Voeg foto’s toe",
|
||||||
"add_tag": "Voeg etiket toe",
|
"add_tag": "Voeg etiket toe",
|
||||||
"add_to": "Voeg toe tot…",
|
"add_to": "Voeg toe tot…",
|
||||||
"add_to_album": "Voeg toe tot album",
|
"add_to_album": "Voeg toe tot album",
|
||||||
"add_to_album_bottom_sheet_added": "Tot {album} toegevoeg",
|
"add_to_album_bottom_sheet_added": "Tot {album} toegevoeg",
|
||||||
"add_to_album_bottom_sheet_already_exists": "Reeds in {album}",
|
"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": "Voeg toe tot albums",
|
||||||
"add_to_albums_count": "Voeg toe tot albums ({count})",
|
"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",
|
"add_url": "Voeg bronadres toe",
|
||||||
"added_to_archive": "Tot argief toegevoeg",
|
"added_to_archive": "Tot argief toegevoeg",
|
||||||
"added_to_favorites": "Tot gunstelinge toegevoeg",
|
"added_to_favorites": "Tot gunstelinge toegevoeg",
|
||||||
@@ -46,6 +49,7 @@
|
|||||||
"backup_database": "Skep Databasisstortlêer",
|
"backup_database": "Skep Databasisstortlêer",
|
||||||
"backup_database_enable_description": "Aktiveer databasisstortlêers",
|
"backup_database_enable_description": "Aktiveer databasisstortlêers",
|
||||||
"backup_keep_last_amount": "Aantal vorige stortlêers om te hou",
|
"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_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 foto’s/video’s sowel as die Immich-databasis vir ’n volledige rugsteunoplossing.",
|
"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 foto’s/video’s 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.",
|
"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_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_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?",
|
"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 JSON‑objek na die klipbord",
|
||||||
"create_job": "Skep taak",
|
"create_job": "Skep taak",
|
||||||
"cron_expression": "Cron-uitdrukking",
|
"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",
|
"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",
|
"disable_login": "Deaktiveer aantekening",
|
||||||
"duplicate_detection_job_description": "Begin masjienleer op items om soortgelyke beelde op te spoor. Maak staat op Slimsoek",
|
"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.",
|
"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 JSON‑lêer",
|
||||||
|
"external_libraries_page_description": "Admin eksterne biblioteekbladsy",
|
||||||
"face_detection": "Gesigherkenning",
|
"face_detection": "Gesigherkenning",
|
||||||
"face_detection_description": "Identifiseer die gesigte in media d.m.v. masjienleer. Vir video’s 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.",
|
"face_detection_description": "Identifiseer die gesigte in media d.m.v. masjienleer. Vir video’s 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.",
|
"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",
|
"advanced": "Gevorderd",
|
||||||
"albums": "Albums",
|
"albums": "Albums",
|
||||||
"all": "Alle",
|
"all": "Alle",
|
||||||
"anti_clockwise": "Linksom",
|
|
||||||
"archive": "Argief",
|
"archive": "Argief",
|
||||||
"asset_skipped": "Oorgeslaan",
|
"asset_skipped": "Oorgeslaan",
|
||||||
"asset_uploaded": "Opgelaai",
|
"asset_uploaded": "Opgelaai",
|
||||||
@@ -141,7 +147,6 @@
|
|||||||
"camera": "Kamera",
|
"camera": "Kamera",
|
||||||
"cancel": "Kanselleer",
|
"cancel": "Kanselleer",
|
||||||
"city": "Stad",
|
"city": "Stad",
|
||||||
"clockwise": "Regsom",
|
|
||||||
"close": "Sluit",
|
"close": "Sluit",
|
||||||
"color": "Kleur",
|
"color": "Kleur",
|
||||||
"confirm": "Bevestig",
|
"confirm": "Bevestig",
|
||||||
@@ -182,14 +187,12 @@
|
|||||||
"unsaved_change": "Onbewaarde verandering",
|
"unsaved_change": "Onbewaarde verandering",
|
||||||
"unselect_all": "Ontkies alles",
|
"unselect_all": "Ontkies alles",
|
||||||
"unselect_all_duplicates": "Ontkies alle duplikate",
|
"unselect_all_duplicates": "Ontkies alle duplikate",
|
||||||
"unselect_all_in": "Ontkies alles in {group}",
|
|
||||||
"unstack": "Ontstapel",
|
"unstack": "Ontstapel",
|
||||||
"unstack_action_prompt": "{count} ongestapel",
|
"unstack_action_prompt": "{count} ongestapel",
|
||||||
"unstacked_assets_count": "{count, plural, one {# item} other {# items}} ontstapel",
|
"unstacked_assets_count": "{count, plural, one {# item} other {# items}} ontstapel",
|
||||||
"unsupported_field_type": "Onondersteunde veldtipe",
|
"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.",
|
"unsupported_file_type": "Lêer {file} kan nie opgelaai word nie omdat die lêertipe {type} nie ondersteun word nie.",
|
||||||
"untagged": "Sonder etiket",
|
"untagged": "Sonder etiket",
|
||||||
"untitled_workflow": "Naamlose werkvloei",
|
|
||||||
"up_next": "Volgende",
|
"up_next": "Volgende",
|
||||||
"update_location_action_prompt": "Werk die ligging van {count} gekose items by met:",
|
"update_location_action_prompt": "Werk die ligging van {count} gekose items by met:",
|
||||||
"updated_at": "Bygewerk",
|
"updated_at": "Bygewerk",
|
||||||
@@ -197,8 +200,6 @@
|
|||||||
"upload": "Laai op",
|
"upload": "Laai op",
|
||||||
"upload_concurrency": "Aantal gelyktydige oplaaie",
|
"upload_concurrency": "Aantal gelyktydige oplaaie",
|
||||||
"upload_details": "Oplaaidetails",
|
"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_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_errors": "Oplaai voltooi met {count, plural, one {# fout} other {# foute}}, verfris die blad om die nuwe items te sien.",
|
||||||
"upload_finished": "Klaar opgelaai",
|
"upload_finished": "Klaar opgelaai",
|
||||||
@@ -227,7 +228,6 @@
|
|||||||
"user_privacy": "Gebruikersprivaatheid",
|
"user_privacy": "Gebruikersprivaatheid",
|
||||||
"user_purchase_settings": "Koop",
|
"user_purchase_settings": "Koop",
|
||||||
"user_purchase_settings_description": "Bestuur u aankoop",
|
"user_purchase_settings_description": "Bestuur u aankoop",
|
||||||
"user_role_set": "Stel {user} in as {role}",
|
|
||||||
"user_usage_detail": "Gedetailleerde gebruik van gebruikers",
|
"user_usage_detail": "Gedetailleerde gebruik van gebruikers",
|
||||||
"user_usage_stats": "Statistieke vir rekeninggebruik",
|
"user_usage_stats": "Statistieke vir rekeninggebruik",
|
||||||
"user_usage_stats_description": "Bekyk statistieke van rekeninggebruik",
|
"user_usage_stats_description": "Bekyk statistieke van rekeninggebruik",
|
||||||
@@ -237,7 +237,6 @@
|
|||||||
"utilities": "Gereedskap",
|
"utilities": "Gereedskap",
|
||||||
"validate": "Valideer",
|
"validate": "Valideer",
|
||||||
"validate_endpoint_error": "Voer asb. ’n geldige bronadres in",
|
"validate_endpoint_error": "Voer asb. ’n geldige bronadres in",
|
||||||
"validation_error": "Valideerfout",
|
|
||||||
"variables": "Veranderlikes",
|
"variables": "Veranderlikes",
|
||||||
"version": "Weergawe",
|
"version": "Weergawe",
|
||||||
"version_announcement_closing": "Jou friend, Alex",
|
"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.",
|
"video_hover_setting_description": "Speel videoduimnael wanneer muis oor item hang. Selfs indien gedeaktiveer kan afspeel begin deur oor die afspeelknop te hang.",
|
||||||
"videos": "Video’s",
|
"videos": "Video’s",
|
||||||
"videos_count": "{count, plural, one {# video} other {# video’s}}",
|
"videos_count": "{count, plural, one {# video} other {# video’s}}",
|
||||||
"videos_only": "Slegs video’s",
|
|
||||||
"view": "Bekyk",
|
"view": "Bekyk",
|
||||||
"view_album": "Bekyk album",
|
"view_album": "Bekyk album",
|
||||||
"view_all": "Bekyk alle",
|
"view_all": "Bekyk alle",
|
||||||
@@ -258,21 +256,16 @@
|
|||||||
"view_details": "Bekyk detail",
|
"view_details": "Bekyk detail",
|
||||||
"view_in_timeline": "Bekyk in tydlyn",
|
"view_in_timeline": "Bekyk in tydlyn",
|
||||||
"view_link": "Bekyk skakel",
|
"view_link": "Bekyk skakel",
|
||||||
"view_links": "Bekyk skakels",
|
|
||||||
"view_name": "Bekyk",
|
"view_name": "Bekyk",
|
||||||
"view_next_asset": "Bekyk volgende item",
|
"view_next_asset": "Bekyk volgende item",
|
||||||
"view_previous_asset": "Bekyk vorige item",
|
"view_previous_asset": "Bekyk vorige item",
|
||||||
"view_qr_code": "Bekyk QR-kode",
|
"view_qr_code": "Bekyk QR-kode",
|
||||||
"view_similar_photos": "Bekyk soortgelyke foto’s",
|
"view_similar_photos": "Bekyk soortgelyke foto’s",
|
||||||
"view_stack": "Bekyk stapel",
|
"view_stack": "Bekyk stapel",
|
||||||
"view_user": "Bekyk gebruiker",
|
|
||||||
"viewer_remove_from_stack": "Verwyder van stapel",
|
"viewer_remove_from_stack": "Verwyder van stapel",
|
||||||
"viewer_stack_use_as_main_asset": "Gebruik as hoofitem",
|
|
||||||
"viewer_unstack": "Ontstapel",
|
|
||||||
"visibility": "Sigbaarheid",
|
"visibility": "Sigbaarheid",
|
||||||
"visibility_changed": "Sigbaarheid verander vir {count, plural, one {# mens} other {# mense}}",
|
"visibility_changed": "Sigbaarheid verander vir {count, plural, one {# mens} other {# mense}}",
|
||||||
"visual": "Visueel",
|
"visual": "Visueel",
|
||||||
"visual_builder": "Visuele bouer",
|
|
||||||
"waiting": "Wag",
|
"waiting": "Wag",
|
||||||
"waiting_count": "Wagtend: {count}",
|
"waiting_count": "Wagtend: {count}",
|
||||||
"warning": "Waarskuwing",
|
"warning": "Waarskuwing",
|
||||||
@@ -291,7 +284,6 @@
|
|||||||
"workflow_navigation_prompt": "Is u seker u wil verlaat sonder om u veranderinge te bewaar?",
|
"workflow_navigation_prompt": "Is u seker u wil verlaat sonder om u veranderinge te bewaar?",
|
||||||
"workflow_summary": "Werkvloei-opsomming",
|
"workflow_summary": "Werkvloei-opsomming",
|
||||||
"workflow_update_success": "Werkvloei suksesvol bygewerk",
|
"workflow_update_success": "Werkvloei suksesvol bygewerk",
|
||||||
"workflow_updated": "Werkvloei bygewerk",
|
|
||||||
"workflows": "Werkvloeie",
|
"workflows": "Werkvloeie",
|
||||||
"workflows_help_text": "Werkvloeie outomatiseer aksies op u items gebaseer op snellers en filters",
|
"workflows_help_text": "Werkvloeie outomatiseer aksies op u items gebaseer op snellers en filters",
|
||||||
"wrong_pin_code": "Verkeerde PIN-kode",
|
"wrong_pin_code": "Verkeerde PIN-kode",
|
||||||
@@ -301,6 +293,5 @@
|
|||||||
"you_dont_have_any_shared_links": "U het geen gedeelde skakels nie",
|
"you_dont_have_any_shared_links": "U het geen gedeelde skakels nie",
|
||||||
"your_wifi_name": "U Wi-Fi-naam",
|
"your_wifi_name": "U Wi-Fi-naam",
|
||||||
"zero_to_clear_rating": "druk 0 om itemgradering te wis",
|
"zero_to_clear_rating": "druk 0 om itemgradering te wis",
|
||||||
"zoom_image": "Zoem in",
|
"zoom_image": "Zoem in"
|
||||||
"zoom_to_bounds": "Zoem na rande"
|
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user