mirror of
https://github.com/immich-app/immich.git
synced 2025-07-09 03:06:56 -04:00
Compare commits
22 Commits
3ae2dcce33
...
df1af73ef6
Author | SHA1 | Date | |
---|---|---|---|
|
df1af73ef6 | ||
|
d46e50213a | ||
|
49486f2d26 | ||
|
eac189a9e5 | ||
|
3b968707a7 | ||
|
67aa124de9 | ||
|
076d8808bb | ||
|
67ddba0b13 | ||
|
3eccff4306 | ||
|
ecb5cb00eb | ||
|
06048b6db9 | ||
|
f0ad6627a5 | ||
|
14e6d23eeb | ||
|
d772cc6c6a | ||
|
fe33732958 | ||
|
a019fb670e | ||
|
f63d251490 | ||
|
dfc2d5002b | ||
|
904d948d82 | ||
|
85b5c31251 | ||
|
d456ce30f6 | ||
|
26daa53e2a |
4
.github/workflows/docker-cleanup.yml
vendored
4
.github/workflows/docker-cleanup.yml
vendored
@ -22,7 +22,7 @@ concurrency:
|
|||||||
jobs:
|
jobs:
|
||||||
cleanup-images:
|
cleanup-images:
|
||||||
name: Cleanup Stale Images Tags for ${{ matrix.primary-name }}
|
name: Cleanup Stale Images Tags for ${{ matrix.primary-name }}
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-24.04
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
@ -48,7 +48,7 @@ jobs:
|
|||||||
|
|
||||||
cleanup-untagged-images:
|
cleanup-untagged-images:
|
||||||
name: Cleanup Untagged Images Tags for ${{ matrix.primary-name }}
|
name: Cleanup Untagged Images Tags for ${{ matrix.primary-name }}
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-24.04
|
||||||
needs:
|
needs:
|
||||||
- cleanup-images
|
- cleanup-images
|
||||||
strategy:
|
strategy:
|
||||||
|
167
cli/package-lock.json
generated
167
cli/package-lock.json
generated
@ -24,7 +24,7 @@
|
|||||||
"@types/cli-progress": "^3.11.0",
|
"@types/cli-progress": "^3.11.0",
|
||||||
"@types/lodash-es": "^4.17.12",
|
"@types/lodash-es": "^4.17.12",
|
||||||
"@types/mock-fs": "^4.13.1",
|
"@types/mock-fs": "^4.13.1",
|
||||||
"@types/node": "^20.16.5",
|
"@types/node": "^20.16.9",
|
||||||
"@typescript-eslint/eslint-plugin": "^8.0.0",
|
"@typescript-eslint/eslint-plugin": "^8.0.0",
|
||||||
"@typescript-eslint/parser": "^8.0.0",
|
"@typescript-eslint/parser": "^8.0.0",
|
||||||
"@vitest/coverage-v8": "^2.0.5",
|
"@vitest/coverage-v8": "^2.0.5",
|
||||||
@ -59,7 +59,7 @@
|
|||||||
"@oazapfts/runtime": "^1.0.2"
|
"@oazapfts/runtime": "^1.0.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^20.16.5",
|
"@types/node": "^20.16.9",
|
||||||
"typescript": "^5.3.3"
|
"typescript": "^5.3.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -765,6 +765,16 @@
|
|||||||
"node": "*"
|
"node": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@eslint/core": {
|
||||||
|
"version": "0.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.6.0.tgz",
|
||||||
|
"integrity": "sha512-8I2Q8ykA4J0x0o7cg67FPVnehcqWTBehu/lmY+bolPFHGjh49YzGBMXTvpqVgEbBdvNCSxj6iFgiIyHzf03lzg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"engines": {
|
||||||
|
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@eslint/eslintrc": {
|
"node_modules/@eslint/eslintrc": {
|
||||||
"version": "3.1.0",
|
"version": "3.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.1.0.tgz",
|
||||||
@ -825,9 +835,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@eslint/js": {
|
"node_modules/@eslint/js": {
|
||||||
"version": "9.10.0",
|
"version": "9.11.1",
|
||||||
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.10.0.tgz",
|
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.11.1.tgz",
|
||||||
"integrity": "sha512-fuXtbiP5GWIn8Fz+LWoOMVf/Jxm+aajZYkhi6CuEm4SxymFM+eUWzbO9qXT+L0iCkL5+KGYMCSGxo686H19S1g==",
|
"integrity": "sha512-/qu+TWz8WwPWc7/HcIJKi+c+MOm46GdVaSlTTQcaqaL53+GsoA6MxWp5PtTx48qbSP7ylM1Kn7nhvkugfJvRSA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
@ -845,9 +855,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@eslint/plugin-kit": {
|
"node_modules/@eslint/plugin-kit": {
|
||||||
"version": "0.1.0",
|
"version": "0.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.0.tgz",
|
||||||
"integrity": "sha512-autAXT203ixhqei9xt+qkYOvY8l6LAFIdT2UXc/RPNeUVfqRF1BV94GTJyVPFKT8nFM6MyVJhjLj9E8JWvf5zQ==",
|
"integrity": "sha512-vH9PiIMMwvhCx31Af3HiGzsVNULDbyVkHXwlemn/B0TFj/00ho3y55efXrUZTfQipxoHC5u4xq6zblww1zm1Ig==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@ -1312,6 +1322,13 @@
|
|||||||
"integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==",
|
"integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/json-schema": {
|
||||||
|
"version": "7.0.15",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
|
||||||
|
"integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@types/lodash": {
|
"node_modules/@types/lodash": {
|
||||||
"version": "4.17.0",
|
"version": "4.17.0",
|
||||||
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.0.tgz",
|
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.0.tgz",
|
||||||
@ -1337,9 +1354,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@types/node": {
|
"node_modules/@types/node": {
|
||||||
"version": "20.16.5",
|
"version": "20.16.10",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.16.5.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.16.10.tgz",
|
||||||
"integrity": "sha512-VwYCweNo3ERajwy0IUlqqcyZ8/A7Zwa9ZP3MnENWcB11AejO+tLy3pu850goUW2FC/IJMdZUfKpX/yxL1gymCA==",
|
"integrity": "sha512-vQUKgWTjEIRFCvK6CyriPH3MZYiYlNy0fKiEYHWbcoWLEgs4opurGGKlebrTLqdSMIbXImH6XExNiIyNUv3WpA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@ -1353,17 +1370,17 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/eslint-plugin": {
|
"node_modules/@typescript-eslint/eslint-plugin": {
|
||||||
"version": "8.6.0",
|
"version": "8.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.7.0.tgz",
|
||||||
"integrity": "sha512-UOaz/wFowmoh2G6Mr9gw60B1mm0MzUtm6Ic8G2yM1Le6gyj5Loi/N+O5mocugRGY+8OeeKmkMmbxNqUCq3B4Sg==",
|
"integrity": "sha512-RIHOoznhA3CCfSTFiB6kBGLQtB/sox+pJ6jeFu6FxJvqL8qRxq/FfGO/UhsGgQM9oGdXkV4xUgli+dt26biB6A==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@eslint-community/regexpp": "^4.10.0",
|
"@eslint-community/regexpp": "^4.10.0",
|
||||||
"@typescript-eslint/scope-manager": "8.6.0",
|
"@typescript-eslint/scope-manager": "8.7.0",
|
||||||
"@typescript-eslint/type-utils": "8.6.0",
|
"@typescript-eslint/type-utils": "8.7.0",
|
||||||
"@typescript-eslint/utils": "8.6.0",
|
"@typescript-eslint/utils": "8.7.0",
|
||||||
"@typescript-eslint/visitor-keys": "8.6.0",
|
"@typescript-eslint/visitor-keys": "8.7.0",
|
||||||
"graphemer": "^1.4.0",
|
"graphemer": "^1.4.0",
|
||||||
"ignore": "^5.3.1",
|
"ignore": "^5.3.1",
|
||||||
"natural-compare": "^1.4.0",
|
"natural-compare": "^1.4.0",
|
||||||
@ -1387,16 +1404,16 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/parser": {
|
"node_modules/@typescript-eslint/parser": {
|
||||||
"version": "8.6.0",
|
"version": "8.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.7.0.tgz",
|
||||||
"integrity": "sha512-eQcbCuA2Vmw45iGfcyG4y6rS7BhWfz9MQuk409WD47qMM+bKCGQWXxvoOs1DUp+T7UBMTtRTVT+kXr7Sh4O9Ow==",
|
"integrity": "sha512-lN0btVpj2unxHlNYLI//BQ7nzbMJYBVQX5+pbNXvGYazdlgYonMn4AhhHifQ+J4fGRYA/m1DjaQjx+fDetqBOQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "BSD-2-Clause",
|
"license": "BSD-2-Clause",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/scope-manager": "8.6.0",
|
"@typescript-eslint/scope-manager": "8.7.0",
|
||||||
"@typescript-eslint/types": "8.6.0",
|
"@typescript-eslint/types": "8.7.0",
|
||||||
"@typescript-eslint/typescript-estree": "8.6.0",
|
"@typescript-eslint/typescript-estree": "8.7.0",
|
||||||
"@typescript-eslint/visitor-keys": "8.6.0",
|
"@typescript-eslint/visitor-keys": "8.7.0",
|
||||||
"debug": "^4.3.4"
|
"debug": "^4.3.4"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
@ -1416,14 +1433,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/scope-manager": {
|
"node_modules/@typescript-eslint/scope-manager": {
|
||||||
"version": "8.6.0",
|
"version": "8.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.7.0.tgz",
|
||||||
"integrity": "sha512-ZuoutoS5y9UOxKvpc/GkvF4cuEmpokda4wRg64JEia27wX+PysIE9q+lzDtlHHgblwUWwo5/Qn+/WyTUvDwBHw==",
|
"integrity": "sha512-87rC0k3ZlDOuz82zzXRtQ7Akv3GKhHs0ti4YcbAJtaomllXoSO8hi7Ix3ccEvCd824dy9aIX+j3d2UMAfCtVpg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/types": "8.6.0",
|
"@typescript-eslint/types": "8.7.0",
|
||||||
"@typescript-eslint/visitor-keys": "8.6.0"
|
"@typescript-eslint/visitor-keys": "8.7.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||||
@ -1434,14 +1451,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/type-utils": {
|
"node_modules/@typescript-eslint/type-utils": {
|
||||||
"version": "8.6.0",
|
"version": "8.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.7.0.tgz",
|
||||||
"integrity": "sha512-dtePl4gsuenXVwC7dVNlb4mGDcKjDT/Ropsk4za/ouMBPplCLyznIaR+W65mvCvsyS97dymoBRrioEXI7k0XIg==",
|
"integrity": "sha512-tl0N0Mj3hMSkEYhLkjREp54OSb/FI6qyCzfiiclvJvOqre6hsZTGSnHtmFLDU8TIM62G7ygEa1bI08lcuRwEnQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/typescript-estree": "8.6.0",
|
"@typescript-eslint/typescript-estree": "8.7.0",
|
||||||
"@typescript-eslint/utils": "8.6.0",
|
"@typescript-eslint/utils": "8.7.0",
|
||||||
"debug": "^4.3.4",
|
"debug": "^4.3.4",
|
||||||
"ts-api-utils": "^1.3.0"
|
"ts-api-utils": "^1.3.0"
|
||||||
},
|
},
|
||||||
@ -1459,9 +1476,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/types": {
|
"node_modules/@typescript-eslint/types": {
|
||||||
"version": "8.6.0",
|
"version": "8.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.7.0.tgz",
|
||||||
"integrity": "sha512-rojqFZGd4MQxw33SrOy09qIDS8WEldM8JWtKQLAjf/X5mGSeEFh5ixQlxssMNyPslVIk9yzWqXCsV2eFhYrYUw==",
|
"integrity": "sha512-LLt4BLHFwSfASHSF2K29SZ+ZCsbQOM+LuarPjRUuHm+Qd09hSe3GCeaQbcCr+Mik+0QFRmep/FyZBO6fJ64U3w==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
@ -1473,14 +1490,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/typescript-estree": {
|
"node_modules/@typescript-eslint/typescript-estree": {
|
||||||
"version": "8.6.0",
|
"version": "8.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.7.0.tgz",
|
||||||
"integrity": "sha512-MOVAzsKJIPIlLK239l5s06YXjNqpKTVhBVDnqUumQJja5+Y94V3+4VUFRA0G60y2jNnTVwRCkhyGQpavfsbq/g==",
|
"integrity": "sha512-MC8nmcGHsmfAKxwnluTQpNqceniT8SteVwd2voYlmiSWGOtjvGXdPl17dYu2797GVscK30Z04WRM28CrKS9WOg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "BSD-2-Clause",
|
"license": "BSD-2-Clause",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/types": "8.6.0",
|
"@typescript-eslint/types": "8.7.0",
|
||||||
"@typescript-eslint/visitor-keys": "8.6.0",
|
"@typescript-eslint/visitor-keys": "8.7.0",
|
||||||
"debug": "^4.3.4",
|
"debug": "^4.3.4",
|
||||||
"fast-glob": "^3.3.2",
|
"fast-glob": "^3.3.2",
|
||||||
"is-glob": "^4.0.3",
|
"is-glob": "^4.0.3",
|
||||||
@ -1502,16 +1519,16 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/utils": {
|
"node_modules/@typescript-eslint/utils": {
|
||||||
"version": "8.6.0",
|
"version": "8.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.7.0.tgz",
|
||||||
"integrity": "sha512-eNp9cWnYf36NaOVjkEUznf6fEgVy1TWpE0o52e4wtojjBx7D1UV2WAWGzR+8Y5lVFtpMLPwNbC67T83DWSph4A==",
|
"integrity": "sha512-ZbdUdwsl2X/s3CiyAu3gOlfQzpbuG3nTWKPoIvAu1pu5r8viiJvv2NPN2AqArL35NCYtw/lrPPfM4gxrMLNLPw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@eslint-community/eslint-utils": "^4.4.0",
|
"@eslint-community/eslint-utils": "^4.4.0",
|
||||||
"@typescript-eslint/scope-manager": "8.6.0",
|
"@typescript-eslint/scope-manager": "8.7.0",
|
||||||
"@typescript-eslint/types": "8.6.0",
|
"@typescript-eslint/types": "8.7.0",
|
||||||
"@typescript-eslint/typescript-estree": "8.6.0"
|
"@typescript-eslint/typescript-estree": "8.7.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||||
@ -1525,13 +1542,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/visitor-keys": {
|
"node_modules/@typescript-eslint/visitor-keys": {
|
||||||
"version": "8.6.0",
|
"version": "8.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.7.0.tgz",
|
||||||
"integrity": "sha512-wapVFfZg9H0qOYh4grNVQiMklJGluQrOUiOhYRrQWhx7BY/+I1IYb8BczWNbbUpO+pqy0rDciv3lQH5E1bCLrg==",
|
"integrity": "sha512-b1tx0orFCCh/THWPQa2ZwWzvOeyzzp36vkJYOpVg0u8UVOIsfVrnuC9FqAw9gRKn+rG2VmWQ/zDJZzkxUnj/XQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/types": "8.6.0",
|
"@typescript-eslint/types": "8.7.0",
|
||||||
"eslint-visitor-keys": "^3.4.3"
|
"eslint-visitor-keys": "^3.4.3"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
@ -2170,21 +2187,24 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/eslint": {
|
"node_modules/eslint": {
|
||||||
"version": "9.10.0",
|
"version": "9.11.1",
|
||||||
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.10.0.tgz",
|
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.11.1.tgz",
|
||||||
"integrity": "sha512-Y4D0IgtBZfOcOUAIQTSXBKoNGfY0REGqHJG6+Q81vNippW5YlKjHFj4soMxamKK1NXHUWuBZTLdU3Km+L/pcHw==",
|
"integrity": "sha512-MobhYKIoAO1s1e4VUrgx1l1Sk2JBR/Gqjjgw8+mfgoLE2xwsHur4gdfTxyTgShrhvdVFTaJSgMiQBl1jv/AWxg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@eslint-community/eslint-utils": "^4.2.0",
|
"@eslint-community/eslint-utils": "^4.2.0",
|
||||||
"@eslint-community/regexpp": "^4.11.0",
|
"@eslint-community/regexpp": "^4.11.0",
|
||||||
"@eslint/config-array": "^0.18.0",
|
"@eslint/config-array": "^0.18.0",
|
||||||
|
"@eslint/core": "^0.6.0",
|
||||||
"@eslint/eslintrc": "^3.1.0",
|
"@eslint/eslintrc": "^3.1.0",
|
||||||
"@eslint/js": "9.10.0",
|
"@eslint/js": "9.11.1",
|
||||||
"@eslint/plugin-kit": "^0.1.0",
|
"@eslint/plugin-kit": "^0.2.0",
|
||||||
"@humanwhocodes/module-importer": "^1.0.1",
|
"@humanwhocodes/module-importer": "^1.0.1",
|
||||||
"@humanwhocodes/retry": "^0.3.0",
|
"@humanwhocodes/retry": "^0.3.0",
|
||||||
"@nodelib/fs.walk": "^1.2.8",
|
"@nodelib/fs.walk": "^1.2.8",
|
||||||
|
"@types/estree": "^1.0.6",
|
||||||
|
"@types/json-schema": "^7.0.15",
|
||||||
"ajv": "^6.12.4",
|
"ajv": "^6.12.4",
|
||||||
"chalk": "^4.0.0",
|
"chalk": "^4.0.0",
|
||||||
"cross-spawn": "^7.0.2",
|
"cross-spawn": "^7.0.2",
|
||||||
@ -2335,6 +2355,13 @@
|
|||||||
"url": "https://opencollective.com/eslint"
|
"url": "https://opencollective.com/eslint"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/eslint/node_modules/@types/estree": {
|
||||||
|
"version": "1.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz",
|
||||||
|
"integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/eslint/node_modules/brace-expansion": {
|
"node_modules/eslint/node_modules/brace-expansion": {
|
||||||
"version": "1.1.11",
|
"version": "1.1.11",
|
||||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
||||||
@ -2347,9 +2374,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/eslint/node_modules/eslint-visitor-keys": {
|
"node_modules/eslint/node_modules/eslint-visitor-keys": {
|
||||||
"version": "4.0.0",
|
"version": "4.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.1.0.tgz",
|
||||||
"integrity": "sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw==",
|
"integrity": "sha512-Q7lok0mqMUSf5a/AdAZkA5a/gHcO6snwQClVNNvFKCAVlxXucdU8pKydU5ZVZjBx5xr37vGbFFWtLQYreLzrZg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"engines": {
|
"engines": {
|
||||||
@ -3449,21 +3476,17 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/prettier-plugin-organize-imports": {
|
"node_modules/prettier-plugin-organize-imports": {
|
||||||
"version": "4.0.0",
|
"version": "4.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/prettier-plugin-organize-imports/-/prettier-plugin-organize-imports-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/prettier-plugin-organize-imports/-/prettier-plugin-organize-imports-4.1.0.tgz",
|
||||||
"integrity": "sha512-vnKSdgv9aOlqKeEFGhf9SCBsTyzDSyScy1k7E0R1Uo4L0cTcOV7c1XQaT7jfXIOc/p08WLBfN2QUQA9zDSZMxA==",
|
"integrity": "sha512-5aWRdCgv645xaa58X8lOxzZoiHAldAPChljr/MT0crXVOWTZ+Svl4hIWlz+niYSlO6ikE5UXkN1JrRvIP2ut0A==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@vue/language-plugin-pug": "^2.0.24",
|
|
||||||
"prettier": ">=2.0",
|
"prettier": ">=2.0",
|
||||||
"typescript": ">=2.9",
|
"typescript": ">=2.9",
|
||||||
"vue-tsc": "^2.0.24"
|
"vue-tsc": "^2.1.0"
|
||||||
},
|
},
|
||||||
"peerDependenciesMeta": {
|
"peerDependenciesMeta": {
|
||||||
"@vue/language-plugin-pug": {
|
|
||||||
"optional": true
|
|
||||||
},
|
|
||||||
"vue-tsc": {
|
"vue-tsc": {
|
||||||
"optional": true
|
"optional": true
|
||||||
}
|
}
|
||||||
@ -4155,9 +4178,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/vite": {
|
"node_modules/vite": {
|
||||||
"version": "5.4.6",
|
"version": "5.4.8",
|
||||||
"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.6.tgz",
|
"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.8.tgz",
|
||||||
"integrity": "sha512-IeL5f8OO5nylsgzd9tq4qD2QqI0k2CQLGrWD0rCN0EQJZpBK5vJAx0I+GDkMOXxQX/OfFHMuLIx6ddAxGX/k+Q==",
|
"integrity": "sha512-FqrItQ4DT1NC4zCUqMB4c4AZORMKIa0m8/URVCZ77OZ/QSNeJ54bU1vrFADbDsuwfIPcgknRkmqakQcgnL4GiQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
@ -20,7 +20,7 @@
|
|||||||
"@types/cli-progress": "^3.11.0",
|
"@types/cli-progress": "^3.11.0",
|
||||||
"@types/lodash-es": "^4.17.12",
|
"@types/lodash-es": "^4.17.12",
|
||||||
"@types/mock-fs": "^4.13.1",
|
"@types/mock-fs": "^4.13.1",
|
||||||
"@types/node": "^20.16.5",
|
"@types/node": "^20.16.9",
|
||||||
"@typescript-eslint/eslint-plugin": "^8.0.0",
|
"@typescript-eslint/eslint-plugin": "^8.0.0",
|
||||||
"@typescript-eslint/parser": "^8.0.0",
|
"@typescript-eslint/parser": "^8.0.0",
|
||||||
"@vitest/coverage-v8": "^2.0.5",
|
"@vitest/coverage-v8": "^2.0.5",
|
||||||
|
@ -36,6 +36,10 @@ services:
|
|||||||
IMMICH_BUILD_URL: https://github.com/immich-app/immich/actions/runs/9654404849
|
IMMICH_BUILD_URL: https://github.com/immich-app/immich/actions/runs/9654404849
|
||||||
IMMICH_BUILD_IMAGE: development
|
IMMICH_BUILD_IMAGE: development
|
||||||
IMMICH_BUILD_IMAGE_URL: https://github.com/immich-app/immich/pkgs/container/immich-server
|
IMMICH_BUILD_IMAGE_URL: https://github.com/immich-app/immich/pkgs/container/immich-server
|
||||||
|
IMMICH_THIRD_PARTY_SOURCE_URL: https://github.com/immich-app/immich/
|
||||||
|
IMMICH_THIRD_PARTY_BUG_FEATURE_URL: https://github.com/immich-app/immich/issues
|
||||||
|
IMMICH_THIRD_PARTY_DOCUMENTATION_URL: https://immich.app/docs
|
||||||
|
IMMICH_THIRD_PARTY_SUPPORT_URL: https://immich.app/docs/third-party
|
||||||
ulimits:
|
ulimits:
|
||||||
nofile:
|
nofile:
|
||||||
soft: 1048576
|
soft: 1048576
|
||||||
|
BIN
docs/docs/features/img/folder-view.png
Normal file
BIN
docs/docs/features/img/folder-view.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.5 MiB |
@ -149,6 +149,22 @@ If you get an error here, please rename the other external library to something
|
|||||||
|
|
||||||
Within seconds, the assets from the old-pics and videos folders should show up in the main timeline.
|
Within seconds, the assets from the old-pics and videos folders should show up in the main timeline.
|
||||||
|
|
||||||
|
### Folder view
|
||||||
|
|
||||||
|
:::info
|
||||||
|
This feature also exists for assets uploaded other than through external libraries.
|
||||||
|
:::tip
|
||||||
|
You can use the storage template migration feature for the best experience with uploaded assets in this view.
|
||||||
|
:::
|
||||||
|
|
||||||
|
You can browse your photos and videos by folder like in a file explorer.
|
||||||
|
|
||||||
|
Enable this feature from the Users Settings > Features > Folders.
|
||||||
|
|
||||||
|
The UI is currently only available for the web; mobile will come in a subsequent release.
|
||||||
|
|
||||||
|
<img src={require('./img/folder-view.png').default} width="75%" title='Folder-view' />
|
||||||
|
|
||||||
### Set Custom Scan Interval
|
### Set Custom Scan Interval
|
||||||
|
|
||||||
:::note
|
:::note
|
||||||
|
@ -27,3 +27,21 @@ The beta release channel allows users to test upcoming changes before they are o
|
|||||||
:::info
|
:::info
|
||||||
You can enable automatic backup on supported devices. For more information see [Automatic Backup](/docs/features/automatic-backup.md).
|
You can enable automatic backup on supported devices. For more information see [Automatic Backup](/docs/features/automatic-backup.md).
|
||||||
:::
|
:::
|
||||||
|
|
||||||
|
## Album Sync
|
||||||
|
|
||||||
|
You can sync or mirror an album from your phone to the Immich server on your account. For example, if you select Recents, Camera and Videos album for backup, the corresponding album with the same name will be created on the server. Once the assets from those albums are uploaded, they will be put into the target albums automatically.
|
||||||
|
|
||||||
|
### Album Synchronization Highlights
|
||||||
|
|
||||||
|
- **One-Way Sync:** Synchronization is one-way, from the device to the server.
|
||||||
|
|
||||||
|
- **Name Matching:** If an album on the server has the same name as the album on the device, images from the device will be merged with the existing images in the server album.
|
||||||
|
|
||||||
|
- **Shared Albums:** If the matching album on the server is shared, the new photos merged into the album will also be shared.
|
||||||
|
|
||||||
|
- **Album Structure:** When an album is created for the first time, its structure is based on the initial state. Future updates made on the phone (such as deleting or repositioning photos) will not be reflected in Immich.
|
||||||
|
|
||||||
|
- **User-Specific Sync:** Album synchronization is unique to each server user and does not sync between different users or partners.
|
||||||
|
|
||||||
|
- **Mobile-Only Feature:** Album synchronization is currently only available on mobile. For similar options on a computer, refer to [Libraries](/docs/features/libraries) for further details.
|
||||||
|
29
docs/package-lock.json
generated
29
docs/package-lock.json
generated
@ -12715,9 +12715,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/picocolors": {
|
"node_modules/picocolors": {
|
||||||
"version": "1.0.1",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz",
|
||||||
"integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==",
|
"integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==",
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
"node_modules/picomatch": {
|
"node_modules/picomatch": {
|
||||||
@ -12829,9 +12829,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/postcss": {
|
"node_modules/postcss": {
|
||||||
"version": "8.4.40",
|
"version": "8.4.47",
|
||||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.40.tgz",
|
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz",
|
||||||
"integrity": "sha512-YF2kKIUzAofPMpfH6hOi2cGnv/HrUlfucspc7pDyvv7kGdqXrfj8SCl/t8owkEgKEuu8ZcRjSOxFxVLqwChZ2Q==",
|
"integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==",
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
"type": "opencollective",
|
"type": "opencollective",
|
||||||
@ -12849,8 +12849,8 @@
|
|||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"nanoid": "^3.3.7",
|
"nanoid": "^3.3.7",
|
||||||
"picocolors": "^1.0.1",
|
"picocolors": "^1.1.0",
|
||||||
"source-map-js": "^1.2.0"
|
"source-map-js": "^1.2.1"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^10 || ^12 || >=14"
|
"node": "^10 || ^12 || >=14"
|
||||||
@ -15670,9 +15670,10 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/source-map-js": {
|
"node_modules/source-map-js": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
|
||||||
"integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==",
|
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
@ -16091,9 +16092,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/tailwindcss": {
|
"node_modules/tailwindcss": {
|
||||||
"version": "3.4.12",
|
"version": "3.4.13",
|
||||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.12.tgz",
|
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.13.tgz",
|
||||||
"integrity": "sha512-Htf/gHj2+soPb9UayUNci/Ja3d8pTmu9ONTfh4QY8r3MATTZOzmv6UYWF7ZwikEIC8okpfqmGqrmDehua8mF8w==",
|
"integrity": "sha512-KqjHOJKogOUt5Bs752ykCeiwvi0fKVkr5oqsFNt/8px/tA8scFPIlkygsf6jXrfCqGHz7VflA6+yytWuM+XhFw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@alloc/quick-lru": "^5.2.0",
|
"@alloc/quick-lru": "^5.2.0",
|
||||||
|
241
e2e/package-lock.json
generated
241
e2e/package-lock.json
generated
@ -15,7 +15,7 @@
|
|||||||
"@immich/sdk": "file:../open-api/typescript-sdk",
|
"@immich/sdk": "file:../open-api/typescript-sdk",
|
||||||
"@playwright/test": "^1.44.1",
|
"@playwright/test": "^1.44.1",
|
||||||
"@types/luxon": "^3.4.2",
|
"@types/luxon": "^3.4.2",
|
||||||
"@types/node": "^20.16.5",
|
"@types/node": "^20.16.9",
|
||||||
"@types/oidc-provider": "^8.5.1",
|
"@types/oidc-provider": "^8.5.1",
|
||||||
"@types/pg": "^8.11.0",
|
"@types/pg": "^8.11.0",
|
||||||
"@types/pngjs": "^6.0.4",
|
"@types/pngjs": "^6.0.4",
|
||||||
@ -64,7 +64,7 @@
|
|||||||
"@types/cli-progress": "^3.11.0",
|
"@types/cli-progress": "^3.11.0",
|
||||||
"@types/lodash-es": "^4.17.12",
|
"@types/lodash-es": "^4.17.12",
|
||||||
"@types/mock-fs": "^4.13.1",
|
"@types/mock-fs": "^4.13.1",
|
||||||
"@types/node": "^20.16.5",
|
"@types/node": "^20.16.9",
|
||||||
"@typescript-eslint/eslint-plugin": "^8.0.0",
|
"@typescript-eslint/eslint-plugin": "^8.0.0",
|
||||||
"@typescript-eslint/parser": "^8.0.0",
|
"@typescript-eslint/parser": "^8.0.0",
|
||||||
"@vitest/coverage-v8": "^2.0.5",
|
"@vitest/coverage-v8": "^2.0.5",
|
||||||
@ -99,7 +99,7 @@
|
|||||||
"@oazapfts/runtime": "^1.0.2"
|
"@oazapfts/runtime": "^1.0.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^20.16.5",
|
"@types/node": "^20.16.9",
|
||||||
"typescript": "^5.3.3"
|
"typescript": "^5.3.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -784,6 +784,16 @@
|
|||||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@eslint/core": {
|
||||||
|
"version": "0.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.6.0.tgz",
|
||||||
|
"integrity": "sha512-8I2Q8ykA4J0x0o7cg67FPVnehcqWTBehu/lmY+bolPFHGjh49YzGBMXTvpqVgEbBdvNCSxj6iFgiIyHzf03lzg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"engines": {
|
||||||
|
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@eslint/eslintrc": {
|
"node_modules/@eslint/eslintrc": {
|
||||||
"version": "3.1.0",
|
"version": "3.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.1.0.tgz",
|
||||||
@ -822,9 +832,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@eslint/js": {
|
"node_modules/@eslint/js": {
|
||||||
"version": "9.10.0",
|
"version": "9.11.1",
|
||||||
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.10.0.tgz",
|
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.11.1.tgz",
|
||||||
"integrity": "sha512-fuXtbiP5GWIn8Fz+LWoOMVf/Jxm+aajZYkhi6CuEm4SxymFM+eUWzbO9qXT+L0iCkL5+KGYMCSGxo686H19S1g==",
|
"integrity": "sha512-/qu+TWz8WwPWc7/HcIJKi+c+MOm46GdVaSlTTQcaqaL53+GsoA6MxWp5PtTx48qbSP7ylM1Kn7nhvkugfJvRSA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
@ -842,9 +852,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@eslint/plugin-kit": {
|
"node_modules/@eslint/plugin-kit": {
|
||||||
"version": "0.1.0",
|
"version": "0.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.0.tgz",
|
||||||
"integrity": "sha512-autAXT203ixhqei9xt+qkYOvY8l6LAFIdT2UXc/RPNeUVfqRF1BV94GTJyVPFKT8nFM6MyVJhjLj9E8JWvf5zQ==",
|
"integrity": "sha512-vH9PiIMMwvhCx31Af3HiGzsVNULDbyVkHXwlemn/B0TFj/00ho3y55efXrUZTfQipxoHC5u4xq6zblww1zm1Ig==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@ -1121,10 +1131,11 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@photostructure/tz-lookup": {
|
"node_modules/@photostructure/tz-lookup": {
|
||||||
"version": "10.0.0",
|
"version": "11.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/@photostructure/tz-lookup/-/tz-lookup-10.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/@photostructure/tz-lookup/-/tz-lookup-11.0.0.tgz",
|
||||||
"integrity": "sha512-8ZAjoj/irCuvUlyEinQ/HB6A8hP3bD1dgTOZvfl1b9nAwqniutFDHOQRcGM6Crea68bOwPj010f0Z4KkmuLHEA==",
|
"integrity": "sha512-QMV5/dWtY/MdVPXZs/EApqzyhnqDq1keYEqpS+Xj2uidyaqw2Nk/fWcsszdruIXjdqp1VoWNzsgrO6bUHU1mFw==",
|
||||||
"dev": true
|
"dev": true,
|
||||||
|
"license": "CC0-1.0"
|
||||||
},
|
},
|
||||||
"node_modules/@pkgjs/parseargs": {
|
"node_modules/@pkgjs/parseargs": {
|
||||||
"version": "0.11.0",
|
"version": "0.11.0",
|
||||||
@ -1149,13 +1160,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@playwright/test": {
|
"node_modules/@playwright/test": {
|
||||||
"version": "1.47.1",
|
"version": "1.47.2",
|
||||||
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.47.1.tgz",
|
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.47.2.tgz",
|
||||||
"integrity": "sha512-dbWpcNQZ5nj16m+A5UNScYx7HX5trIy7g4phrcitn+Nk83S32EBX/CLU4hiF4RGKX/yRc93AAqtfaXB7JWBd4Q==",
|
"integrity": "sha512-jTXRsoSPONAs8Za9QEQdyjFn+0ZQFjCiIztAIF6bi1HqhBzG9Ma7g1WotyiGqFSBRZjIEqMdT8RUlbk1QVhzCQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"playwright": "1.47.1"
|
"playwright": "1.47.2"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
"playwright": "cli.js"
|
"playwright": "cli.js"
|
||||||
@ -1519,6 +1530,13 @@
|
|||||||
"integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==",
|
"integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/json-schema": {
|
||||||
|
"version": "7.0.15",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
|
||||||
|
"integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@types/keygrip": {
|
"node_modules/@types/keygrip": {
|
||||||
"version": "1.0.6",
|
"version": "1.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/@types/keygrip/-/keygrip-1.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/@types/keygrip/-/keygrip-1.0.6.tgz",
|
||||||
@ -1569,9 +1587,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/@types/node": {
|
"node_modules/@types/node": {
|
||||||
"version": "20.16.5",
|
"version": "20.16.10",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.16.5.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.16.10.tgz",
|
||||||
"integrity": "sha512-VwYCweNo3ERajwy0IUlqqcyZ8/A7Zwa9ZP3MnENWcB11AejO+tLy3pu850goUW2FC/IJMdZUfKpX/yxL1gymCA==",
|
"integrity": "sha512-vQUKgWTjEIRFCvK6CyriPH3MZYiYlNy0fKiEYHWbcoWLEgs4opurGGKlebrTLqdSMIbXImH6XExNiIyNUv3WpA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@ -1733,17 +1751,17 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/eslint-plugin": {
|
"node_modules/@typescript-eslint/eslint-plugin": {
|
||||||
"version": "8.6.0",
|
"version": "8.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.7.0.tgz",
|
||||||
"integrity": "sha512-UOaz/wFowmoh2G6Mr9gw60B1mm0MzUtm6Ic8G2yM1Le6gyj5Loi/N+O5mocugRGY+8OeeKmkMmbxNqUCq3B4Sg==",
|
"integrity": "sha512-RIHOoznhA3CCfSTFiB6kBGLQtB/sox+pJ6jeFu6FxJvqL8qRxq/FfGO/UhsGgQM9oGdXkV4xUgli+dt26biB6A==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@eslint-community/regexpp": "^4.10.0",
|
"@eslint-community/regexpp": "^4.10.0",
|
||||||
"@typescript-eslint/scope-manager": "8.6.0",
|
"@typescript-eslint/scope-manager": "8.7.0",
|
||||||
"@typescript-eslint/type-utils": "8.6.0",
|
"@typescript-eslint/type-utils": "8.7.0",
|
||||||
"@typescript-eslint/utils": "8.6.0",
|
"@typescript-eslint/utils": "8.7.0",
|
||||||
"@typescript-eslint/visitor-keys": "8.6.0",
|
"@typescript-eslint/visitor-keys": "8.7.0",
|
||||||
"graphemer": "^1.4.0",
|
"graphemer": "^1.4.0",
|
||||||
"ignore": "^5.3.1",
|
"ignore": "^5.3.1",
|
||||||
"natural-compare": "^1.4.0",
|
"natural-compare": "^1.4.0",
|
||||||
@ -1767,16 +1785,16 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/parser": {
|
"node_modules/@typescript-eslint/parser": {
|
||||||
"version": "8.6.0",
|
"version": "8.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.7.0.tgz",
|
||||||
"integrity": "sha512-eQcbCuA2Vmw45iGfcyG4y6rS7BhWfz9MQuk409WD47qMM+bKCGQWXxvoOs1DUp+T7UBMTtRTVT+kXr7Sh4O9Ow==",
|
"integrity": "sha512-lN0btVpj2unxHlNYLI//BQ7nzbMJYBVQX5+pbNXvGYazdlgYonMn4AhhHifQ+J4fGRYA/m1DjaQjx+fDetqBOQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "BSD-2-Clause",
|
"license": "BSD-2-Clause",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/scope-manager": "8.6.0",
|
"@typescript-eslint/scope-manager": "8.7.0",
|
||||||
"@typescript-eslint/types": "8.6.0",
|
"@typescript-eslint/types": "8.7.0",
|
||||||
"@typescript-eslint/typescript-estree": "8.6.0",
|
"@typescript-eslint/typescript-estree": "8.7.0",
|
||||||
"@typescript-eslint/visitor-keys": "8.6.0",
|
"@typescript-eslint/visitor-keys": "8.7.0",
|
||||||
"debug": "^4.3.4"
|
"debug": "^4.3.4"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
@ -1796,14 +1814,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/scope-manager": {
|
"node_modules/@typescript-eslint/scope-manager": {
|
||||||
"version": "8.6.0",
|
"version": "8.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.7.0.tgz",
|
||||||
"integrity": "sha512-ZuoutoS5y9UOxKvpc/GkvF4cuEmpokda4wRg64JEia27wX+PysIE9q+lzDtlHHgblwUWwo5/Qn+/WyTUvDwBHw==",
|
"integrity": "sha512-87rC0k3ZlDOuz82zzXRtQ7Akv3GKhHs0ti4YcbAJtaomllXoSO8hi7Ix3ccEvCd824dy9aIX+j3d2UMAfCtVpg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/types": "8.6.0",
|
"@typescript-eslint/types": "8.7.0",
|
||||||
"@typescript-eslint/visitor-keys": "8.6.0"
|
"@typescript-eslint/visitor-keys": "8.7.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||||
@ -1814,14 +1832,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/type-utils": {
|
"node_modules/@typescript-eslint/type-utils": {
|
||||||
"version": "8.6.0",
|
"version": "8.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.7.0.tgz",
|
||||||
"integrity": "sha512-dtePl4gsuenXVwC7dVNlb4mGDcKjDT/Ropsk4za/ouMBPplCLyznIaR+W65mvCvsyS97dymoBRrioEXI7k0XIg==",
|
"integrity": "sha512-tl0N0Mj3hMSkEYhLkjREp54OSb/FI6qyCzfiiclvJvOqre6hsZTGSnHtmFLDU8TIM62G7ygEa1bI08lcuRwEnQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/typescript-estree": "8.6.0",
|
"@typescript-eslint/typescript-estree": "8.7.0",
|
||||||
"@typescript-eslint/utils": "8.6.0",
|
"@typescript-eslint/utils": "8.7.0",
|
||||||
"debug": "^4.3.4",
|
"debug": "^4.3.4",
|
||||||
"ts-api-utils": "^1.3.0"
|
"ts-api-utils": "^1.3.0"
|
||||||
},
|
},
|
||||||
@ -1839,9 +1857,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/types": {
|
"node_modules/@typescript-eslint/types": {
|
||||||
"version": "8.6.0",
|
"version": "8.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.7.0.tgz",
|
||||||
"integrity": "sha512-rojqFZGd4MQxw33SrOy09qIDS8WEldM8JWtKQLAjf/X5mGSeEFh5ixQlxssMNyPslVIk9yzWqXCsV2eFhYrYUw==",
|
"integrity": "sha512-LLt4BLHFwSfASHSF2K29SZ+ZCsbQOM+LuarPjRUuHm+Qd09hSe3GCeaQbcCr+Mik+0QFRmep/FyZBO6fJ64U3w==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
@ -1853,14 +1871,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/typescript-estree": {
|
"node_modules/@typescript-eslint/typescript-estree": {
|
||||||
"version": "8.6.0",
|
"version": "8.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.7.0.tgz",
|
||||||
"integrity": "sha512-MOVAzsKJIPIlLK239l5s06YXjNqpKTVhBVDnqUumQJja5+Y94V3+4VUFRA0G60y2jNnTVwRCkhyGQpavfsbq/g==",
|
"integrity": "sha512-MC8nmcGHsmfAKxwnluTQpNqceniT8SteVwd2voYlmiSWGOtjvGXdPl17dYu2797GVscK30Z04WRM28CrKS9WOg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "BSD-2-Clause",
|
"license": "BSD-2-Clause",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/types": "8.6.0",
|
"@typescript-eslint/types": "8.7.0",
|
||||||
"@typescript-eslint/visitor-keys": "8.6.0",
|
"@typescript-eslint/visitor-keys": "8.7.0",
|
||||||
"debug": "^4.3.4",
|
"debug": "^4.3.4",
|
||||||
"fast-glob": "^3.3.2",
|
"fast-glob": "^3.3.2",
|
||||||
"is-glob": "^4.0.3",
|
"is-glob": "^4.0.3",
|
||||||
@ -1908,16 +1926,16 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/utils": {
|
"node_modules/@typescript-eslint/utils": {
|
||||||
"version": "8.6.0",
|
"version": "8.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.7.0.tgz",
|
||||||
"integrity": "sha512-eNp9cWnYf36NaOVjkEUznf6fEgVy1TWpE0o52e4wtojjBx7D1UV2WAWGzR+8Y5lVFtpMLPwNbC67T83DWSph4A==",
|
"integrity": "sha512-ZbdUdwsl2X/s3CiyAu3gOlfQzpbuG3nTWKPoIvAu1pu5r8viiJvv2NPN2AqArL35NCYtw/lrPPfM4gxrMLNLPw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@eslint-community/eslint-utils": "^4.4.0",
|
"@eslint-community/eslint-utils": "^4.4.0",
|
||||||
"@typescript-eslint/scope-manager": "8.6.0",
|
"@typescript-eslint/scope-manager": "8.7.0",
|
||||||
"@typescript-eslint/types": "8.6.0",
|
"@typescript-eslint/types": "8.7.0",
|
||||||
"@typescript-eslint/typescript-estree": "8.6.0"
|
"@typescript-eslint/typescript-estree": "8.7.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||||
@ -1931,13 +1949,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/visitor-keys": {
|
"node_modules/@typescript-eslint/visitor-keys": {
|
||||||
"version": "8.6.0",
|
"version": "8.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.7.0.tgz",
|
||||||
"integrity": "sha512-wapVFfZg9H0qOYh4grNVQiMklJGluQrOUiOhYRrQWhx7BY/+I1IYb8BczWNbbUpO+pqy0rDciv3lQH5E1bCLrg==",
|
"integrity": "sha512-b1tx0orFCCh/THWPQa2ZwWzvOeyzzp36vkJYOpVg0u8UVOIsfVrnuC9FqAw9gRKn+rG2VmWQ/zDJZzkxUnj/XQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/types": "8.6.0",
|
"@typescript-eslint/types": "8.7.0",
|
||||||
"eslint-visitor-keys": "^3.4.3"
|
"eslint-visitor-keys": "^3.4.3"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
@ -2854,16 +2872,17 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/engine.io-client": {
|
"node_modules/engine.io-client": {
|
||||||
"version": "6.5.4",
|
"version": "6.6.1",
|
||||||
"resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.5.4.tgz",
|
"resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.6.1.tgz",
|
||||||
"integrity": "sha512-GeZeeRjpD2qf49cZQ0Wvh/8NJNfeXkXXcoGh+F77oEAgo9gUHwT1fCRxSNU+YEEaysOJTnsFHmM5oAcPy4ntvQ==",
|
"integrity": "sha512-aYuoak7I+R83M/BBPIOs2to51BmFIpC1wZe6zZzMrT2llVsHy5cvcmdsJgP2Qz6smHu+sD9oexiSUAVd8OfBPw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@socket.io/component-emitter": "~3.1.0",
|
"@socket.io/component-emitter": "~3.1.0",
|
||||||
"debug": "~4.3.1",
|
"debug": "~4.3.1",
|
||||||
"engine.io-parser": "~5.2.1",
|
"engine.io-parser": "~5.2.1",
|
||||||
"ws": "~8.17.1",
|
"ws": "~8.17.1",
|
||||||
"xmlhttprequest-ssl": "~2.0.0"
|
"xmlhttprequest-ssl": "~2.1.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/engine.io-parser": {
|
"node_modules/engine.io-parser": {
|
||||||
@ -2972,21 +2991,24 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/eslint": {
|
"node_modules/eslint": {
|
||||||
"version": "9.10.0",
|
"version": "9.11.1",
|
||||||
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.10.0.tgz",
|
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.11.1.tgz",
|
||||||
"integrity": "sha512-Y4D0IgtBZfOcOUAIQTSXBKoNGfY0REGqHJG6+Q81vNippW5YlKjHFj4soMxamKK1NXHUWuBZTLdU3Km+L/pcHw==",
|
"integrity": "sha512-MobhYKIoAO1s1e4VUrgx1l1Sk2JBR/Gqjjgw8+mfgoLE2xwsHur4gdfTxyTgShrhvdVFTaJSgMiQBl1jv/AWxg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@eslint-community/eslint-utils": "^4.2.0",
|
"@eslint-community/eslint-utils": "^4.2.0",
|
||||||
"@eslint-community/regexpp": "^4.11.0",
|
"@eslint-community/regexpp": "^4.11.0",
|
||||||
"@eslint/config-array": "^0.18.0",
|
"@eslint/config-array": "^0.18.0",
|
||||||
|
"@eslint/core": "^0.6.0",
|
||||||
"@eslint/eslintrc": "^3.1.0",
|
"@eslint/eslintrc": "^3.1.0",
|
||||||
"@eslint/js": "9.10.0",
|
"@eslint/js": "9.11.1",
|
||||||
"@eslint/plugin-kit": "^0.1.0",
|
"@eslint/plugin-kit": "^0.2.0",
|
||||||
"@humanwhocodes/module-importer": "^1.0.1",
|
"@humanwhocodes/module-importer": "^1.0.1",
|
||||||
"@humanwhocodes/retry": "^0.3.0",
|
"@humanwhocodes/retry": "^0.3.0",
|
||||||
"@nodelib/fs.walk": "^1.2.8",
|
"@nodelib/fs.walk": "^1.2.8",
|
||||||
|
"@types/estree": "^1.0.6",
|
||||||
|
"@types/json-schema": "^7.0.15",
|
||||||
"ajv": "^6.12.4",
|
"ajv": "^6.12.4",
|
||||||
"chalk": "^4.0.0",
|
"chalk": "^4.0.0",
|
||||||
"cross-spawn": "^7.0.2",
|
"cross-spawn": "^7.0.2",
|
||||||
@ -3138,9 +3160,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/eslint/node_modules/eslint-visitor-keys": {
|
"node_modules/eslint/node_modules/eslint-visitor-keys": {
|
||||||
"version": "4.0.0",
|
"version": "4.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.1.0.tgz",
|
||||||
"integrity": "sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw==",
|
"integrity": "sha512-Q7lok0mqMUSf5a/AdAZkA5a/gHcO6snwQClVNNvFKCAVlxXucdU8pKydU5ZVZjBx5xr37vGbFFWtLQYreLzrZg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"engines": {
|
"engines": {
|
||||||
@ -3246,27 +3268,27 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/exiftool-vendored": {
|
"node_modules/exiftool-vendored": {
|
||||||
"version": "28.2.1",
|
"version": "28.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/exiftool-vendored/-/exiftool-vendored-28.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/exiftool-vendored/-/exiftool-vendored-28.3.0.tgz",
|
||||||
"integrity": "sha512-D3YsKErr3BbjKeJzUVsv6CVZ+SQNgAJKPFWVLXu0CBtr24FNuE3CZBXWKWysGu0MjzeDCNwQrQI5+bXUFeiYVA==",
|
"integrity": "sha512-2DOSOvj5c1gkbKtubAnlGglxdYp9h55n0GxjK2nypVivoaCdgP/le3MOZRKgEUNObfJHmYHj4u/NnYVneu/gUw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@photostructure/tz-lookup": "^10.0.0",
|
"@photostructure/tz-lookup": "^11.0.0",
|
||||||
"@types/luxon": "^3.4.2",
|
"@types/luxon": "^3.4.2",
|
||||||
"batch-cluster": "^13.0.0",
|
"batch-cluster": "^13.0.0",
|
||||||
"he": "^1.2.0",
|
"he": "^1.2.0",
|
||||||
"luxon": "^3.5.0"
|
"luxon": "^3.5.0"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"exiftool-vendored.exe": "12.91.0",
|
"exiftool-vendored.exe": "12.96.0",
|
||||||
"exiftool-vendored.pl": "12.91.0"
|
"exiftool-vendored.pl": "12.96.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/exiftool-vendored.exe": {
|
"node_modules/exiftool-vendored.exe": {
|
||||||
"version": "12.91.0",
|
"version": "12.96.0",
|
||||||
"resolved": "https://registry.npmjs.org/exiftool-vendored.exe/-/exiftool-vendored.exe-12.91.0.tgz",
|
"resolved": "https://registry.npmjs.org/exiftool-vendored.exe/-/exiftool-vendored.exe-12.96.0.tgz",
|
||||||
"integrity": "sha512-nxcoGBaJL/D+Wb0jVe8qwyV8QZpRcCzU0aCKhG0S1XNGWGjJJJ4QV851aobcfDwI4NluFOdqkjTSf32pVijvHg==",
|
"integrity": "sha512-pKPN9F/Evw2yyO5/+ml3spbXIqejzOxyF7jEnj8tLU2JPSmIlziPUZ75XIhcPbilX86jVKmuiso7FUDicOg8pQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
@ -3275,9 +3297,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/exiftool-vendored.pl": {
|
"node_modules/exiftool-vendored.pl": {
|
||||||
"version": "12.91.0",
|
"version": "12.96.0",
|
||||||
"resolved": "https://registry.npmjs.org/exiftool-vendored.pl/-/exiftool-vendored.pl-12.91.0.tgz",
|
"resolved": "https://registry.npmjs.org/exiftool-vendored.pl/-/exiftool-vendored.pl-12.96.0.tgz",
|
||||||
"integrity": "sha512-GZMy9+Jiv8/C7R4uYe1kWtXsAaJdgVezTwYa+wDeoqvReHiX2t5uzkCrzWdjo4LGl5mPQkyKhN7/uPLYk5Ak6w==",
|
"integrity": "sha512-v4nGnovAMBsTfOWhwAcOiRiq/8kuJOo3GUMHNpug7Mr4jLz3tmWEo7DdNyOYmpcvWbA6smOTG0SmwsrY8fsW+A==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
@ -4142,9 +4164,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/jose": {
|
"node_modules/jose": {
|
||||||
"version": "5.9.2",
|
"version": "5.9.3",
|
||||||
"resolved": "https://registry.npmjs.org/jose/-/jose-5.9.2.tgz",
|
"resolved": "https://registry.npmjs.org/jose/-/jose-5.9.3.tgz",
|
||||||
"integrity": "sha512-ILI2xx/I57b20sd7rHZvgiiQrmp2mcotwsAH+5ajbpFQbrYVQdNHYlQhoA5cFb78CgtBOxtC05TeA+mcgkuCqQ==",
|
"integrity": "sha512-egLIoYSpcd+QUF+UHgobt5YzI2Pkw/H39ou9suW687MY6PmCwPmkNV/4TNjn1p2tX5xO3j0d0sq5hiYE24bSlg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"funding": {
|
"funding": {
|
||||||
@ -5135,13 +5157,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/playwright": {
|
"node_modules/playwright": {
|
||||||
"version": "1.47.1",
|
"version": "1.47.2",
|
||||||
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.47.1.tgz",
|
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.47.2.tgz",
|
||||||
"integrity": "sha512-SUEKi6947IqYbKxRiqnbUobVZY4bF1uu+ZnZNJX9DfU1tlf2UhWfvVjLf01pQx9URsOr18bFVUKXmanYWhbfkw==",
|
"integrity": "sha512-nx1cLMmQWqmA3UsnjaaokyoUpdVaaDhJhMoxX2qj3McpjnsqFHs516QAKYhqHAgOP+oCFTEOCOAaD1RgD/RQfA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"playwright-core": "1.47.1"
|
"playwright-core": "1.47.2"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
"playwright": "cli.js"
|
"playwright": "cli.js"
|
||||||
@ -5154,9 +5176,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/playwright-core": {
|
"node_modules/playwright-core": {
|
||||||
"version": "1.47.1",
|
"version": "1.47.2",
|
||||||
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.47.1.tgz",
|
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.47.2.tgz",
|
||||||
"integrity": "sha512-i1iyJdLftqtt51mEk6AhYFaAJCDx0xQ/O5NU8EKaWFgMjItPVma542Nh/Aq8aLCjIJSzjaiEQGW/nyqLkGF1OQ==",
|
"integrity": "sha512-3JvMfF+9LJfe16l7AbSmU555PaTl2tPyQsVInqm3id16pdDfvZ8TTZ/pyzmkbDrZTQefyzU7AIHlZqQnxpqHVQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"bin": {
|
"bin": {
|
||||||
@ -5296,21 +5318,17 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/prettier-plugin-organize-imports": {
|
"node_modules/prettier-plugin-organize-imports": {
|
||||||
"version": "4.0.0",
|
"version": "4.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/prettier-plugin-organize-imports/-/prettier-plugin-organize-imports-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/prettier-plugin-organize-imports/-/prettier-plugin-organize-imports-4.1.0.tgz",
|
||||||
"integrity": "sha512-vnKSdgv9aOlqKeEFGhf9SCBsTyzDSyScy1k7E0R1Uo4L0cTcOV7c1XQaT7jfXIOc/p08WLBfN2QUQA9zDSZMxA==",
|
"integrity": "sha512-5aWRdCgv645xaa58X8lOxzZoiHAldAPChljr/MT0crXVOWTZ+Svl4hIWlz+niYSlO6ikE5UXkN1JrRvIP2ut0A==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@vue/language-plugin-pug": "^2.0.24",
|
|
||||||
"prettier": ">=2.0",
|
"prettier": ">=2.0",
|
||||||
"typescript": ">=2.9",
|
"typescript": ">=2.9",
|
||||||
"vue-tsc": "^2.0.24"
|
"vue-tsc": "^2.1.0"
|
||||||
},
|
},
|
||||||
"peerDependenciesMeta": {
|
"peerDependenciesMeta": {
|
||||||
"@vue/language-plugin-pug": {
|
|
||||||
"optional": true
|
|
||||||
},
|
|
||||||
"vue-tsc": {
|
"vue-tsc": {
|
||||||
"optional": true
|
"optional": true
|
||||||
}
|
}
|
||||||
@ -5796,14 +5814,15 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/socket.io-client": {
|
"node_modules/socket.io-client": {
|
||||||
"version": "4.7.5",
|
"version": "4.8.0",
|
||||||
"resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.7.5.tgz",
|
"resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.8.0.tgz",
|
||||||
"integrity": "sha512-sJ/tqHOCe7Z50JCBCXrsY3I2k03iOiUe+tj1OmKeD2lXPiGH/RUCdTZFoqVyN7l1MnpIzPrGtLcijffmeouNlQ==",
|
"integrity": "sha512-C0jdhD5yQahMws9alf/yvtsMGTaIDBnZ8Rb5HU56svyq0l5LIrGzIDZZD5pHQlmzxLuU91Gz+VpQMKgCTNYtkw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@socket.io/component-emitter": "~3.1.0",
|
"@socket.io/component-emitter": "~3.1.0",
|
||||||
"debug": "~4.3.2",
|
"debug": "~4.3.2",
|
||||||
"engine.io-client": "~6.5.2",
|
"engine.io-client": "~6.6.1",
|
||||||
"socket.io-parser": "~4.2.4"
|
"socket.io-parser": "~4.2.4"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
@ -6732,9 +6751,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/xmlhttprequest-ssl": {
|
"node_modules/xmlhttprequest-ssl": {
|
||||||
"version": "2.0.0",
|
"version": "2.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.1.1.tgz",
|
||||||
"integrity": "sha512-QKxVRxiRACQcVuQEYFsI1hhkrMlrXHPegbbd1yn9UHOmRxY+si12nQYzri3vbzt8VdTTRviqcKxcyllFas5z2A==",
|
"integrity": "sha512-ptjR8YSJIXoA3Mbv5po7RtSYHO6mZr8s7i5VGmEk7QY2pQWyT1o0N+W1gKbOyJPUCGXGnuw0wqe8f0L6Y0ny7g==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.4.0"
|
"node": ">=0.4.0"
|
||||||
|
@ -25,7 +25,7 @@
|
|||||||
"@immich/sdk": "file:../open-api/typescript-sdk",
|
"@immich/sdk": "file:../open-api/typescript-sdk",
|
||||||
"@playwright/test": "^1.44.1",
|
"@playwright/test": "^1.44.1",
|
||||||
"@types/luxon": "^3.4.2",
|
"@types/luxon": "^3.4.2",
|
||||||
"@types/node": "^20.16.5",
|
"@types/node": "^20.16.9",
|
||||||
"@types/oidc-provider": "^8.5.1",
|
"@types/oidc-provider": "^8.5.1",
|
||||||
"@types/pg": "^8.11.0",
|
"@types/pg": "^8.11.0",
|
||||||
"@types/pngjs": "^6.0.4",
|
"@types/pngjs": "^6.0.4",
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
ARG DEVICE=cpu
|
ARG DEVICE=cpu
|
||||||
|
|
||||||
FROM python:3.11-bookworm@sha256:e456ff58048f52f121025159e68bf16248c4122c8b96fadffd89331df50c9994 AS builder-cpu
|
FROM python:3.11-bookworm@sha256:3cdce69fd5663ca47c420ec4d4df8e3545519a4030372f7d2064fb1be2279844 AS builder-cpu
|
||||||
|
|
||||||
FROM builder-cpu AS builder-openvino
|
FROM builder-cpu AS builder-openvino
|
||||||
|
|
||||||
@ -34,7 +34,7 @@ RUN python3 -m venv /opt/venv
|
|||||||
COPY poetry.lock pyproject.toml ./
|
COPY poetry.lock pyproject.toml ./
|
||||||
RUN poetry install --sync --no-interaction --no-ansi --no-root --with ${DEVICE} --without dev
|
RUN poetry install --sync --no-interaction --no-ansi --no-root --with ${DEVICE} --without dev
|
||||||
|
|
||||||
FROM python:3.11-slim-bookworm@sha256:585cf0799407efc267fe1cce318322ec26e015ac1b3d77f2517d50bc3acfc232 AS prod-cpu
|
FROM python:3.11-slim-bookworm@sha256:5501a4fe605abe24de87c2f3d6cf9fd760354416a0cad0296cf284fddcdca9e2 AS prod-cpu
|
||||||
|
|
||||||
FROM prod-cpu AS prod-openvino
|
FROM prod-cpu AS prod-openvino
|
||||||
|
|
||||||
|
18
machine-learning/poetry.lock
generated
18
machine-learning/poetry.lock
generated
@ -2,13 +2,13 @@
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "aiocache"
|
name = "aiocache"
|
||||||
version = "0.12.2"
|
version = "0.12.3"
|
||||||
description = "multi backend asyncio cache"
|
description = "multi backend asyncio cache"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = "*"
|
python-versions = "*"
|
||||||
files = [
|
files = [
|
||||||
{file = "aiocache-0.12.2-py2.py3-none-any.whl", hash = "sha256:9b6fa30634ab0bfc3ecc44928a91ff07c6ea16d27d55469636b296ebc6eb5918"},
|
{file = "aiocache-0.12.3-py2.py3-none-any.whl", hash = "sha256:889086fc24710f431937b87ad3720a289f7fc31c4fd8b68e9f918b9bacd8270d"},
|
||||||
{file = "aiocache-0.12.2.tar.gz", hash = "sha256:b41c9a145b050a5dcbae1599f847db6dd445193b1f3bd172d8e0fe0cb9e96684"},
|
{file = "aiocache-0.12.3.tar.gz", hash = "sha256:f528b27bf4d436b497a1d0d1a8f59a542c153ab1e37c3621713cb376d44c4713"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
@ -1237,13 +1237,13 @@ zstd = ["zstandard (>=0.18.0)"]
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "huggingface-hub"
|
name = "huggingface-hub"
|
||||||
version = "0.25.0"
|
version = "0.25.1"
|
||||||
description = "Client library to download and publish models, datasets and other repos on the huggingface.co hub"
|
description = "Client library to download and publish models, datasets and other repos on the huggingface.co hub"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.8.0"
|
python-versions = ">=3.8.0"
|
||||||
files = [
|
files = [
|
||||||
{file = "huggingface_hub-0.25.0-py3-none-any.whl", hash = "sha256:e2f357b35d72d5012cfd127108c4e14abcd61ba4ebc90a5a374dc2456cb34e12"},
|
{file = "huggingface_hub-0.25.1-py3-none-any.whl", hash = "sha256:a5158ded931b3188f54ea9028097312cb0acd50bffaaa2612014c3c526b44972"},
|
||||||
{file = "huggingface_hub-0.25.0.tar.gz", hash = "sha256:fb5fbe6c12fcd99d187ec7db95db9110fb1a20505f23040a5449a717c1a0db4d"},
|
{file = "huggingface_hub-0.25.1.tar.gz", hash = "sha256:9ff7cb327343211fbd06e2b149b8f362fd1e389454f3f14c6db75a4999ee20ff"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
@ -1531,13 +1531,13 @@ test = ["pytest (>=7.4)", "pytest-cov (>=4.1)"]
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "locust"
|
name = "locust"
|
||||||
version = "2.31.6"
|
version = "2.31.8"
|
||||||
description = "Developer-friendly load testing framework"
|
description = "Developer-friendly load testing framework"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.9"
|
python-versions = ">=3.9"
|
||||||
files = [
|
files = [
|
||||||
{file = "locust-2.31.6-py3-none-any.whl", hash = "sha256:004c963c7a588dc15d57d710cdc6a262d85b57936d7fad3c38ac0657aa98fc3b"},
|
{file = "locust-2.31.8-py3-none-any.whl", hash = "sha256:4194e3d4a0472f1206c51532ed527017f3da1a7d1037ca4b2f0735d5dcd2f78f"},
|
||||||
{file = "locust-2.31.6.tar.gz", hash = "sha256:03b6da0491d6a0b905692d9ac128d9deec403f40dc605c481a90dbab5126318c"},
|
{file = "locust-2.31.8.tar.gz", hash = "sha256:b240c0d3e1724317d9211e81e99fbe42a3469071ef4d34d2ae6a727776d56377"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
|
@ -186,10 +186,10 @@ packages:
|
|||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
name: lints
|
name: lints
|
||||||
sha256: "976c774dd944a42e83e2467f4cc670daef7eed6295b10b36ae8c85bcbf828235"
|
sha256: "3315600f3fb3b135be672bf4a178c55f274bebe368325ae18462c89ac1e3b413"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.0.0"
|
version: "5.0.0"
|
||||||
logging:
|
logging:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -367,4 +367,4 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "3.1.2"
|
version: "3.1.2"
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=3.4.0 <4.0.0"
|
dart: ">=3.5.0 <4.0.0"
|
||||||
|
@ -11,4 +11,4 @@ dependencies:
|
|||||||
glob: ^2.1.2
|
glob: ^2.1.2
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
lints: ^4.0.0
|
lints: ^5.0.0
|
||||||
|
@ -0,0 +1 @@
|
|||||||
|
{"client":{"name":"basic","version":0,"file-system":"device-agnostic","perform-ownership-analysis":"no"},"targets":{"":["<all>"]},"commands":{"<all>":{"tool":"phony","inputs":["<WorkspaceHeaderMapVFSFilesWritten>"],"outputs":["<all>"]},"P0:::Gate WorkspaceHeaderMapVFSFilesWritten":{"tool":"phony","inputs":[],"outputs":["<WorkspaceHeaderMapVFSFilesWritten>"]}}}
|
@ -16,8 +16,12 @@ class FileMediaRepository implements IFileMediaRepository {
|
|||||||
required String title,
|
required String title,
|
||||||
String? relativePath,
|
String? relativePath,
|
||||||
}) async {
|
}) async {
|
||||||
final entity = await PhotoManager.editor
|
final entity = await PhotoManager.editor.saveImage(
|
||||||
.saveImage(data, title: title, relativePath: relativePath);
|
data,
|
||||||
|
filename: title,
|
||||||
|
title: title,
|
||||||
|
relativePath: relativePath,
|
||||||
|
);
|
||||||
return AssetMediaRepository.toAsset(entity);
|
return AssetMediaRepository.toAsset(entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
library photo_view;
|
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import 'package:immich_mobile/widgets/photo_view/src/controller/photo_view_controller.dart';
|
import 'package:immich_mobile/widgets/photo_view/src/controller/photo_view_controller.dart';
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
library photo_view_gallery;
|
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
import 'package:immich_mobile/widgets/photo_view/photo_view.dart'
|
import 'package:immich_mobile/widgets/photo_view/photo_view.dart'
|
||||||
|
@ -28,6 +28,10 @@ class ServerAboutResponseDto {
|
|||||||
this.sourceCommit,
|
this.sourceCommit,
|
||||||
this.sourceRef,
|
this.sourceRef,
|
||||||
this.sourceUrl,
|
this.sourceUrl,
|
||||||
|
this.thirdPartyBugFeatureUrl,
|
||||||
|
this.thirdPartyDocumentationUrl,
|
||||||
|
this.thirdPartySourceUrl,
|
||||||
|
this.thirdPartySupportUrl,
|
||||||
required this.version,
|
required this.version,
|
||||||
required this.versionUrl,
|
required this.versionUrl,
|
||||||
});
|
});
|
||||||
@ -146,6 +150,38 @@ class ServerAboutResponseDto {
|
|||||||
///
|
///
|
||||||
String? sourceUrl;
|
String? sourceUrl;
|
||||||
|
|
||||||
|
///
|
||||||
|
/// Please note: This property should have been non-nullable! Since the specification file
|
||||||
|
/// does not include a default value (using the "default:" property), however, the generated
|
||||||
|
/// source code must fall back to having a nullable type.
|
||||||
|
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||||
|
///
|
||||||
|
String? thirdPartyBugFeatureUrl;
|
||||||
|
|
||||||
|
///
|
||||||
|
/// Please note: This property should have been non-nullable! Since the specification file
|
||||||
|
/// does not include a default value (using the "default:" property), however, the generated
|
||||||
|
/// source code must fall back to having a nullable type.
|
||||||
|
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||||
|
///
|
||||||
|
String? thirdPartyDocumentationUrl;
|
||||||
|
|
||||||
|
///
|
||||||
|
/// Please note: This property should have been non-nullable! Since the specification file
|
||||||
|
/// does not include a default value (using the "default:" property), however, the generated
|
||||||
|
/// source code must fall back to having a nullable type.
|
||||||
|
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||||
|
///
|
||||||
|
String? thirdPartySourceUrl;
|
||||||
|
|
||||||
|
///
|
||||||
|
/// Please note: This property should have been non-nullable! Since the specification file
|
||||||
|
/// does not include a default value (using the "default:" property), however, the generated
|
||||||
|
/// source code must fall back to having a nullable type.
|
||||||
|
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||||
|
///
|
||||||
|
String? thirdPartySupportUrl;
|
||||||
|
|
||||||
String version;
|
String version;
|
||||||
|
|
||||||
String versionUrl;
|
String versionUrl;
|
||||||
@ -167,6 +203,10 @@ class ServerAboutResponseDto {
|
|||||||
other.sourceCommit == sourceCommit &&
|
other.sourceCommit == sourceCommit &&
|
||||||
other.sourceRef == sourceRef &&
|
other.sourceRef == sourceRef &&
|
||||||
other.sourceUrl == sourceUrl &&
|
other.sourceUrl == sourceUrl &&
|
||||||
|
other.thirdPartyBugFeatureUrl == thirdPartyBugFeatureUrl &&
|
||||||
|
other.thirdPartyDocumentationUrl == thirdPartyDocumentationUrl &&
|
||||||
|
other.thirdPartySourceUrl == thirdPartySourceUrl &&
|
||||||
|
other.thirdPartySupportUrl == thirdPartySupportUrl &&
|
||||||
other.version == version &&
|
other.version == version &&
|
||||||
other.versionUrl == versionUrl;
|
other.versionUrl == versionUrl;
|
||||||
|
|
||||||
@ -188,11 +228,15 @@ class ServerAboutResponseDto {
|
|||||||
(sourceCommit == null ? 0 : sourceCommit!.hashCode) +
|
(sourceCommit == null ? 0 : sourceCommit!.hashCode) +
|
||||||
(sourceRef == null ? 0 : sourceRef!.hashCode) +
|
(sourceRef == null ? 0 : sourceRef!.hashCode) +
|
||||||
(sourceUrl == null ? 0 : sourceUrl!.hashCode) +
|
(sourceUrl == null ? 0 : sourceUrl!.hashCode) +
|
||||||
|
(thirdPartyBugFeatureUrl == null ? 0 : thirdPartyBugFeatureUrl!.hashCode) +
|
||||||
|
(thirdPartyDocumentationUrl == null ? 0 : thirdPartyDocumentationUrl!.hashCode) +
|
||||||
|
(thirdPartySourceUrl == null ? 0 : thirdPartySourceUrl!.hashCode) +
|
||||||
|
(thirdPartySupportUrl == null ? 0 : thirdPartySupportUrl!.hashCode) +
|
||||||
(version.hashCode) +
|
(version.hashCode) +
|
||||||
(versionUrl.hashCode);
|
(versionUrl.hashCode);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() => 'ServerAboutResponseDto[build=$build, buildImage=$buildImage, buildImageUrl=$buildImageUrl, buildUrl=$buildUrl, exiftool=$exiftool, ffmpeg=$ffmpeg, imagemagick=$imagemagick, libvips=$libvips, licensed=$licensed, nodejs=$nodejs, repository=$repository, repositoryUrl=$repositoryUrl, sourceCommit=$sourceCommit, sourceRef=$sourceRef, sourceUrl=$sourceUrl, version=$version, versionUrl=$versionUrl]';
|
String toString() => 'ServerAboutResponseDto[build=$build, buildImage=$buildImage, buildImageUrl=$buildImageUrl, buildUrl=$buildUrl, exiftool=$exiftool, ffmpeg=$ffmpeg, imagemagick=$imagemagick, libvips=$libvips, licensed=$licensed, nodejs=$nodejs, repository=$repository, repositoryUrl=$repositoryUrl, sourceCommit=$sourceCommit, sourceRef=$sourceRef, sourceUrl=$sourceUrl, thirdPartyBugFeatureUrl=$thirdPartyBugFeatureUrl, thirdPartyDocumentationUrl=$thirdPartyDocumentationUrl, thirdPartySourceUrl=$thirdPartySourceUrl, thirdPartySupportUrl=$thirdPartySupportUrl, version=$version, versionUrl=$versionUrl]';
|
||||||
|
|
||||||
Map<String, dynamic> toJson() {
|
Map<String, dynamic> toJson() {
|
||||||
final json = <String, dynamic>{};
|
final json = <String, dynamic>{};
|
||||||
@ -266,6 +310,26 @@ class ServerAboutResponseDto {
|
|||||||
json[r'sourceUrl'] = this.sourceUrl;
|
json[r'sourceUrl'] = this.sourceUrl;
|
||||||
} else {
|
} else {
|
||||||
// json[r'sourceUrl'] = null;
|
// json[r'sourceUrl'] = null;
|
||||||
|
}
|
||||||
|
if (this.thirdPartyBugFeatureUrl != null) {
|
||||||
|
json[r'thirdPartyBugFeatureUrl'] = this.thirdPartyBugFeatureUrl;
|
||||||
|
} else {
|
||||||
|
// json[r'thirdPartyBugFeatureUrl'] = null;
|
||||||
|
}
|
||||||
|
if (this.thirdPartyDocumentationUrl != null) {
|
||||||
|
json[r'thirdPartyDocumentationUrl'] = this.thirdPartyDocumentationUrl;
|
||||||
|
} else {
|
||||||
|
// json[r'thirdPartyDocumentationUrl'] = null;
|
||||||
|
}
|
||||||
|
if (this.thirdPartySourceUrl != null) {
|
||||||
|
json[r'thirdPartySourceUrl'] = this.thirdPartySourceUrl;
|
||||||
|
} else {
|
||||||
|
// json[r'thirdPartySourceUrl'] = null;
|
||||||
|
}
|
||||||
|
if (this.thirdPartySupportUrl != null) {
|
||||||
|
json[r'thirdPartySupportUrl'] = this.thirdPartySupportUrl;
|
||||||
|
} else {
|
||||||
|
// json[r'thirdPartySupportUrl'] = null;
|
||||||
}
|
}
|
||||||
json[r'version'] = this.version;
|
json[r'version'] = this.version;
|
||||||
json[r'versionUrl'] = this.versionUrl;
|
json[r'versionUrl'] = this.versionUrl;
|
||||||
@ -296,6 +360,10 @@ class ServerAboutResponseDto {
|
|||||||
sourceCommit: mapValueOfType<String>(json, r'sourceCommit'),
|
sourceCommit: mapValueOfType<String>(json, r'sourceCommit'),
|
||||||
sourceRef: mapValueOfType<String>(json, r'sourceRef'),
|
sourceRef: mapValueOfType<String>(json, r'sourceRef'),
|
||||||
sourceUrl: mapValueOfType<String>(json, r'sourceUrl'),
|
sourceUrl: mapValueOfType<String>(json, r'sourceUrl'),
|
||||||
|
thirdPartyBugFeatureUrl: mapValueOfType<String>(json, r'thirdPartyBugFeatureUrl'),
|
||||||
|
thirdPartyDocumentationUrl: mapValueOfType<String>(json, r'thirdPartyDocumentationUrl'),
|
||||||
|
thirdPartySourceUrl: mapValueOfType<String>(json, r'thirdPartySourceUrl'),
|
||||||
|
thirdPartySupportUrl: mapValueOfType<String>(json, r'thirdPartySupportUrl'),
|
||||||
version: mapValueOfType<String>(json, r'version')!,
|
version: mapValueOfType<String>(json, r'version')!,
|
||||||
versionUrl: mapValueOfType<String>(json, r'versionUrl')!,
|
versionUrl: mapValueOfType<String>(json, r'versionUrl')!,
|
||||||
);
|
);
|
||||||
|
@ -540,10 +540,10 @@ packages:
|
|||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
name: flutter_lints
|
name: flutter_lints
|
||||||
sha256: "3f41d009ba7172d5ff9be5f6e6e6abb4300e263aab8866d2a0842ed2a70f8f0c"
|
sha256: "5398f14efa795ffb7a33e9b6a08798b26a180edac4ad7db3f231e40f82ce11e1"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.0.0"
|
version: "5.0.0"
|
||||||
flutter_local_notifications:
|
flutter_local_notifications:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -940,10 +940,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: lints
|
name: lints
|
||||||
sha256: "976c774dd944a42e83e2467f4cc670daef7eed6295b10b36ae8c85bcbf828235"
|
sha256: "3315600f3fb3b135be672bf4a178c55f274bebe368325ae18462c89ac1e3b413"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.0.0"
|
version: "5.0.0"
|
||||||
logging:
|
logging:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -1211,10 +1211,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: photo_manager
|
name: photo_manager
|
||||||
sha256: "1e8bbe46a6858870e34c976aafd85378bed221ce31c1201961eba9ad3d94df9f"
|
sha256: "32a1ce1095aeaaa792a29f28c1f74613aa75109f21c2d4ab85be3ad9964230a4"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.2.3"
|
version: "3.5.0"
|
||||||
photo_manager_image_provider:
|
photo_manager_image_provider:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -1861,5 +1861,5 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "3.1.2"
|
version: "3.1.2"
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=3.4.0 <4.0.0"
|
dart: ">=3.5.0 <4.0.0"
|
||||||
flutter: ">=3.24.3"
|
flutter: ">=3.24.3"
|
||||||
|
@ -13,7 +13,7 @@ dependencies:
|
|||||||
sdk: flutter
|
sdk: flutter
|
||||||
|
|
||||||
path_provider_ios:
|
path_provider_ios:
|
||||||
photo_manager: ^3.2.3
|
photo_manager: ^3.5.0
|
||||||
photo_manager_image_provider: ^2.1.1
|
photo_manager_image_provider: ^2.1.1
|
||||||
flutter_hooks: ^0.20.4
|
flutter_hooks: ^0.20.4
|
||||||
hooks_riverpod: ^2.4.9
|
hooks_riverpod: ^2.4.9
|
||||||
@ -87,7 +87,7 @@ dependency_overrides:
|
|||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
flutter_lints: ^4.0.0
|
flutter_lints: ^5.0.0
|
||||||
build_runner: ^2.4.8
|
build_runner: ^2.4.8
|
||||||
auto_route_generator: ^9.0.0
|
auto_route_generator: ^9.0.0
|
||||||
flutter_launcher_icons: ^0.13.1
|
flutter_launcher_icons: ^0.13.1
|
||||||
|
@ -10780,6 +10780,18 @@
|
|||||||
"sourceUrl": {
|
"sourceUrl": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"thirdPartyBugFeatureUrl": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"thirdPartyDocumentationUrl": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"thirdPartySourceUrl": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"thirdPartySupportUrl": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"version": {
|
"version": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
8
open-api/typescript-sdk/package-lock.json
generated
8
open-api/typescript-sdk/package-lock.json
generated
@ -12,7 +12,7 @@
|
|||||||
"@oazapfts/runtime": "^1.0.2"
|
"@oazapfts/runtime": "^1.0.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^20.16.5",
|
"@types/node": "^20.16.9",
|
||||||
"typescript": "^5.3.3"
|
"typescript": "^5.3.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -22,9 +22,9 @@
|
|||||||
"integrity": "sha512-8tKiYffhwTGHSHYGnZ3oneLGCjX0po/XAXQ5Ng9fqKkvIdl/xz8+Vh8i+6xjzZqvZ2pLVpUcuSfnvNI/x67L0g=="
|
"integrity": "sha512-8tKiYffhwTGHSHYGnZ3oneLGCjX0po/XAXQ5Ng9fqKkvIdl/xz8+Vh8i+6xjzZqvZ2pLVpUcuSfnvNI/x67L0g=="
|
||||||
},
|
},
|
||||||
"node_modules/@types/node": {
|
"node_modules/@types/node": {
|
||||||
"version": "20.16.5",
|
"version": "20.16.10",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.16.5.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.16.10.tgz",
|
||||||
"integrity": "sha512-VwYCweNo3ERajwy0IUlqqcyZ8/A7Zwa9ZP3MnENWcB11AejO+tLy3pu850goUW2FC/IJMdZUfKpX/yxL1gymCA==",
|
"integrity": "sha512-vQUKgWTjEIRFCvK6CyriPH3MZYiYlNy0fKiEYHWbcoWLEgs4opurGGKlebrTLqdSMIbXImH6XExNiIyNUv3WpA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
@ -19,7 +19,7 @@
|
|||||||
"@oazapfts/runtime": "^1.0.2"
|
"@oazapfts/runtime": "^1.0.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^20.16.5",
|
"@types/node": "^20.16.9",
|
||||||
"typescript": "^5.3.3"
|
"typescript": "^5.3.3"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
|
@ -917,6 +917,10 @@ export type ServerAboutResponseDto = {
|
|||||||
sourceCommit?: string;
|
sourceCommit?: string;
|
||||||
sourceRef?: string;
|
sourceRef?: string;
|
||||||
sourceUrl?: string;
|
sourceUrl?: string;
|
||||||
|
thirdPartyBugFeatureUrl?: string;
|
||||||
|
thirdPartyDocumentationUrl?: string;
|
||||||
|
thirdPartySourceUrl?: string;
|
||||||
|
thirdPartySupportUrl?: string;
|
||||||
version: string;
|
version: string;
|
||||||
versionUrl: string;
|
versionUrl: string;
|
||||||
};
|
};
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
# dev build
|
# dev build
|
||||||
FROM ghcr.io/immich-app/base-server-dev:20240924@sha256:fff4358d435065a626c64a4c015cbfce6ee714b05fabe39aa0d83d8cff3951f2 AS dev
|
FROM ghcr.io/immich-app/base-server-dev:20241001@sha256:bb10832c2567f5625df68bb790523e85a358031ddcb3d7ac98b669f62ed8de27 AS dev
|
||||||
|
|
||||||
RUN apt-get install --no-install-recommends -yqq tini
|
RUN apt-get install --no-install-recommends -yqq tini
|
||||||
WORKDIR /usr/src/app
|
WORKDIR /usr/src/app
|
||||||
@ -41,7 +41,7 @@ RUN npm run build
|
|||||||
|
|
||||||
|
|
||||||
# prod build
|
# prod build
|
||||||
FROM ghcr.io/immich-app/base-server-prod:20240924@sha256:af3089fe48d7ff162594bd7edfffa56ba4e7014ad10ad69c4ebfd428e39b06ff
|
FROM ghcr.io/immich-app/base-server-prod:20241001@sha256:a9a0745a486e9cbd73fa06b49168e985f8f2c1be0fca9fb0a8e06916246c7087
|
||||||
|
|
||||||
WORKDIR /usr/src/app
|
WORKDIR /usr/src/app
|
||||||
ENV NODE_ENV=production \
|
ENV NODE_ENV=production \
|
||||||
|
421
server/package-lock.json
generated
421
server/package-lock.json
generated
@ -83,7 +83,7 @@
|
|||||||
"@types/lodash": "^4.14.197",
|
"@types/lodash": "^4.14.197",
|
||||||
"@types/mock-fs": "^4.13.1",
|
"@types/mock-fs": "^4.13.1",
|
||||||
"@types/multer": "^1.4.7",
|
"@types/multer": "^1.4.7",
|
||||||
"@types/node": "^20.16.5",
|
"@types/node": "^20.16.9",
|
||||||
"@types/nodemailer": "^6.4.14",
|
"@types/nodemailer": "^6.4.14",
|
||||||
"@types/picomatch": "^3.0.0",
|
"@types/picomatch": "^3.0.0",
|
||||||
"@types/react": "^18.3.4",
|
"@types/react": "^18.3.4",
|
||||||
@ -1138,6 +1138,15 @@
|
|||||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@eslint/core": {
|
||||||
|
"version": "0.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.6.0.tgz",
|
||||||
|
"integrity": "sha512-8I2Q8ykA4J0x0o7cg67FPVnehcqWTBehu/lmY+bolPFHGjh49YzGBMXTvpqVgEbBdvNCSxj6iFgiIyHzf03lzg==",
|
||||||
|
"dev": true,
|
||||||
|
"engines": {
|
||||||
|
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@eslint/eslintrc": {
|
"node_modules/@eslint/eslintrc": {
|
||||||
"version": "3.1.0",
|
"version": "3.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.1.0.tgz",
|
||||||
@ -1198,9 +1207,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/@eslint/js": {
|
"node_modules/@eslint/js": {
|
||||||
"version": "9.10.0",
|
"version": "9.11.1",
|
||||||
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.10.0.tgz",
|
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.11.1.tgz",
|
||||||
"integrity": "sha512-fuXtbiP5GWIn8Fz+LWoOMVf/Jxm+aajZYkhi6CuEm4SxymFM+eUWzbO9qXT+L0iCkL5+KGYMCSGxo686H19S1g==",
|
"integrity": "sha512-/qu+TWz8WwPWc7/HcIJKi+c+MOm46GdVaSlTTQcaqaL53+GsoA6MxWp5PtTx48qbSP7ylM1Kn7nhvkugfJvRSA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||||
@ -1216,9 +1225,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@eslint/plugin-kit": {
|
"node_modules/@eslint/plugin-kit": {
|
||||||
"version": "0.1.0",
|
"version": "0.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.0.tgz",
|
||||||
"integrity": "sha512-autAXT203ixhqei9xt+qkYOvY8l6LAFIdT2UXc/RPNeUVfqRF1BV94GTJyVPFKT8nFM6MyVJhjLj9E8JWvf5zQ==",
|
"integrity": "sha512-vH9PiIMMwvhCx31Af3HiGzsVNULDbyVkHXwlemn/B0TFj/00ho3y55efXrUZTfQipxoHC5u4xq6zblww1zm1Ig==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"levn": "^0.4.1"
|
"levn": "^0.4.1"
|
||||||
@ -2043,9 +2052,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@nestjs/common": {
|
"node_modules/@nestjs/common": {
|
||||||
"version": "10.4.3",
|
"version": "10.4.4",
|
||||||
"resolved": "https://registry.npmjs.org/@nestjs/common/-/common-10.4.3.tgz",
|
"resolved": "https://registry.npmjs.org/@nestjs/common/-/common-10.4.4.tgz",
|
||||||
"integrity": "sha512-4hbLd3XIJubHSylYd/1WSi4VQvG68KM/ECYpMDqA3k3J1/T17SAg40sDoq3ZoO5OZgU0xuNyjuISdOTjs11qVg==",
|
"integrity": "sha512-0j2/zqRw9nvHV1GKTktER8B/hIC/Z8CYFjN/ZqUuvwayCH+jZZBhCR2oRyuvLTXdnlSmtCAg2xvQ0ULqQvzqhA==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"iterare": "1.2.1",
|
"iterare": "1.2.1",
|
||||||
"tslib": "2.7.0",
|
"tslib": "2.7.0",
|
||||||
@ -2188,9 +2197,9 @@
|
|||||||
"integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA=="
|
"integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA=="
|
||||||
},
|
},
|
||||||
"node_modules/@nestjs/platform-socket.io": {
|
"node_modules/@nestjs/platform-socket.io": {
|
||||||
"version": "10.4.3",
|
"version": "10.4.4",
|
||||||
"resolved": "https://registry.npmjs.org/@nestjs/platform-socket.io/-/platform-socket.io-10.4.3.tgz",
|
"resolved": "https://registry.npmjs.org/@nestjs/platform-socket.io/-/platform-socket.io-10.4.4.tgz",
|
||||||
"integrity": "sha512-jTatT8q15LB5CFWsaIez3IigMixt7tNGJ4QLlRJ5NggPOPKRZssJnloODyEadFNHJjZiyufp5/NoPKBtNMf+lg==",
|
"integrity": "sha512-5GEYUA3sNbX2jOBP6FmrIK/zv9VCdvpdr4Sef1OKvt1U0qsV1YgmWPWDPumZM77n5DI0VHSJPyo7yjZaEKWOiQ==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"socket.io": "4.7.5",
|
"socket.io": "4.7.5",
|
||||||
"tslib": "2.7.0"
|
"tslib": "2.7.0"
|
||||||
@ -2290,9 +2299,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@nestjs/testing": {
|
"node_modules/@nestjs/testing": {
|
||||||
"version": "10.4.3",
|
"version": "10.4.4",
|
||||||
"resolved": "https://registry.npmjs.org/@nestjs/testing/-/testing-10.4.3.tgz",
|
"resolved": "https://registry.npmjs.org/@nestjs/testing/-/testing-10.4.4.tgz",
|
||||||
"integrity": "sha512-SBNWrMU51YAlYmW86wyjlGZ2uLnASNiOPD0lBcNIlxxei0b05/aI3nh7OPuxbXQUdedUJfPq2d2jZj4TRG4S0w==",
|
"integrity": "sha512-qRGFj51A5RM7JqA8pcyEwSLA3Y0dle/PAZ8oxP0suimoCusRY3Tk7wYqutZdCNj1ATb678SDaUZDHk2pwSv9/g==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"tslib": "2.7.0"
|
"tslib": "2.7.0"
|
||||||
@ -2338,9 +2347,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@nestjs/websockets": {
|
"node_modules/@nestjs/websockets": {
|
||||||
"version": "10.4.3",
|
"version": "10.4.4",
|
||||||
"resolved": "https://registry.npmjs.org/@nestjs/websockets/-/websockets-10.4.3.tgz",
|
"resolved": "https://registry.npmjs.org/@nestjs/websockets/-/websockets-10.4.4.tgz",
|
||||||
"integrity": "sha512-EW5/GR0jImJwrb8+YpHPoFN2tlhYQzVE2yAN5Se5sygUr/ZFMNAG84sd79NmWGd4RxoxR0aFH9nRycQ/0Ebe5w==",
|
"integrity": "sha512-ZHnak04i/iKBS0csjJa7K6D6xdsB0Yz6duJuCR7xGLItchFK+Ne21m9rEF8ffvW74U7UAYkQHBgD5242LBBYiQ==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"iterare": "1.2.1",
|
"iterare": "1.2.1",
|
||||||
"object-hash": "3.0.0",
|
"object-hash": "3.0.0",
|
||||||
@ -4198,9 +4207,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@photostructure/tz-lookup": {
|
"node_modules/@photostructure/tz-lookup": {
|
||||||
"version": "10.0.0",
|
"version": "11.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/@photostructure/tz-lookup/-/tz-lookup-10.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/@photostructure/tz-lookup/-/tz-lookup-11.0.0.tgz",
|
||||||
"integrity": "sha512-8ZAjoj/irCuvUlyEinQ/HB6A8hP3bD1dgTOZvfl1b9nAwqniutFDHOQRcGM6Crea68bOwPj010f0Z4KkmuLHEA=="
|
"integrity": "sha512-QMV5/dWtY/MdVPXZs/EApqzyhnqDq1keYEqpS+Xj2uidyaqw2Nk/fWcsszdruIXjdqp1VoWNzsgrO6bUHU1mFw=="
|
||||||
},
|
},
|
||||||
"node_modules/@pkgjs/parseargs": {
|
"node_modules/@pkgjs/parseargs": {
|
||||||
"version": "0.11.0",
|
"version": "0.11.0",
|
||||||
@ -5332,9 +5341,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/@types/lodash": {
|
"node_modules/@types/lodash": {
|
||||||
"version": "4.17.7",
|
"version": "4.17.9",
|
||||||
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.7.tgz",
|
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.9.tgz",
|
||||||
"integrity": "sha512-8wTvZawATi/lsmNu10/j2hk1KEP0IvjubqPE3cu1Xz7xfXXt5oCq3SNUz4fMIP4XGF9Ky+Ue2tBA3hcS7LSBlA==",
|
"integrity": "sha512-w9iWudx1XWOHW5lQRS9iKpK/XuRhnN+0T7HvdCCd802FYkT1AMTnxndJHGrNJwRoRHkslGr4S29tjm1cT7x/7w==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/@types/luxon": {
|
"node_modules/@types/luxon": {
|
||||||
@ -5389,9 +5398,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@types/node": {
|
"node_modules/@types/node": {
|
||||||
"version": "20.16.5",
|
"version": "20.16.10",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.16.5.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.16.10.tgz",
|
||||||
"integrity": "sha512-VwYCweNo3ERajwy0IUlqqcyZ8/A7Zwa9ZP3MnENWcB11AejO+tLy3pu850goUW2FC/IJMdZUfKpX/yxL1gymCA==",
|
"integrity": "sha512-vQUKgWTjEIRFCvK6CyriPH3MZYiYlNy0fKiEYHWbcoWLEgs4opurGGKlebrTLqdSMIbXImH6XExNiIyNUv3WpA==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"undici-types": "~6.19.2"
|
"undici-types": "~6.19.2"
|
||||||
}
|
}
|
||||||
@ -5506,9 +5515,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/@types/react": {
|
"node_modules/@types/react": {
|
||||||
"version": "18.3.8",
|
"version": "18.3.9",
|
||||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.8.tgz",
|
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.9.tgz",
|
||||||
"integrity": "sha512-syBUrW3/XpnW4WJ41Pft+I+aPoDVbrBVQGEnbD7NijDGlVC+8gV/XKRY+7vMDlfPpbwYt0l1vd/Sj8bJGMbs9Q==",
|
"integrity": "sha512-+BpAVyTpJkNWWSSnaLBk6ePpHLOGJKnEQNbINNovPWzvEUyAe3e+/d494QdEh71RekM/qV7lw6jzf1HGrJyAtQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/prop-types": "*",
|
"@types/prop-types": "*",
|
||||||
@ -5625,16 +5634,16 @@
|
|||||||
"integrity": "sha512-c/hzNDBh7eRF+KbCf+OoZxKbnkpaK/cKp9iLQWqB7muXtM+MtL9SUUH8vCFcLn6dH1Qm05jiexK0ofWY7TfOhQ=="
|
"integrity": "sha512-c/hzNDBh7eRF+KbCf+OoZxKbnkpaK/cKp9iLQWqB7muXtM+MtL9SUUH8vCFcLn6dH1Qm05jiexK0ofWY7TfOhQ=="
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/eslint-plugin": {
|
"node_modules/@typescript-eslint/eslint-plugin": {
|
||||||
"version": "8.6.0",
|
"version": "8.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.7.0.tgz",
|
||||||
"integrity": "sha512-UOaz/wFowmoh2G6Mr9gw60B1mm0MzUtm6Ic8G2yM1Le6gyj5Loi/N+O5mocugRGY+8OeeKmkMmbxNqUCq3B4Sg==",
|
"integrity": "sha512-RIHOoznhA3CCfSTFiB6kBGLQtB/sox+pJ6jeFu6FxJvqL8qRxq/FfGO/UhsGgQM9oGdXkV4xUgli+dt26biB6A==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@eslint-community/regexpp": "^4.10.0",
|
"@eslint-community/regexpp": "^4.10.0",
|
||||||
"@typescript-eslint/scope-manager": "8.6.0",
|
"@typescript-eslint/scope-manager": "8.7.0",
|
||||||
"@typescript-eslint/type-utils": "8.6.0",
|
"@typescript-eslint/type-utils": "8.7.0",
|
||||||
"@typescript-eslint/utils": "8.6.0",
|
"@typescript-eslint/utils": "8.7.0",
|
||||||
"@typescript-eslint/visitor-keys": "8.6.0",
|
"@typescript-eslint/visitor-keys": "8.7.0",
|
||||||
"graphemer": "^1.4.0",
|
"graphemer": "^1.4.0",
|
||||||
"ignore": "^5.3.1",
|
"ignore": "^5.3.1",
|
||||||
"natural-compare": "^1.4.0",
|
"natural-compare": "^1.4.0",
|
||||||
@ -5658,15 +5667,15 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/parser": {
|
"node_modules/@typescript-eslint/parser": {
|
||||||
"version": "8.6.0",
|
"version": "8.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.7.0.tgz",
|
||||||
"integrity": "sha512-eQcbCuA2Vmw45iGfcyG4y6rS7BhWfz9MQuk409WD47qMM+bKCGQWXxvoOs1DUp+T7UBMTtRTVT+kXr7Sh4O9Ow==",
|
"integrity": "sha512-lN0btVpj2unxHlNYLI//BQ7nzbMJYBVQX5+pbNXvGYazdlgYonMn4AhhHifQ+J4fGRYA/m1DjaQjx+fDetqBOQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/scope-manager": "8.6.0",
|
"@typescript-eslint/scope-manager": "8.7.0",
|
||||||
"@typescript-eslint/types": "8.6.0",
|
"@typescript-eslint/types": "8.7.0",
|
||||||
"@typescript-eslint/typescript-estree": "8.6.0",
|
"@typescript-eslint/typescript-estree": "8.7.0",
|
||||||
"@typescript-eslint/visitor-keys": "8.6.0",
|
"@typescript-eslint/visitor-keys": "8.7.0",
|
||||||
"debug": "^4.3.4"
|
"debug": "^4.3.4"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
@ -5686,13 +5695,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/scope-manager": {
|
"node_modules/@typescript-eslint/scope-manager": {
|
||||||
"version": "8.6.0",
|
"version": "8.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.7.0.tgz",
|
||||||
"integrity": "sha512-ZuoutoS5y9UOxKvpc/GkvF4cuEmpokda4wRg64JEia27wX+PysIE9q+lzDtlHHgblwUWwo5/Qn+/WyTUvDwBHw==",
|
"integrity": "sha512-87rC0k3ZlDOuz82zzXRtQ7Akv3GKhHs0ti4YcbAJtaomllXoSO8hi7Ix3ccEvCd824dy9aIX+j3d2UMAfCtVpg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/types": "8.6.0",
|
"@typescript-eslint/types": "8.7.0",
|
||||||
"@typescript-eslint/visitor-keys": "8.6.0"
|
"@typescript-eslint/visitor-keys": "8.7.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||||
@ -5703,13 +5712,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/type-utils": {
|
"node_modules/@typescript-eslint/type-utils": {
|
||||||
"version": "8.6.0",
|
"version": "8.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.7.0.tgz",
|
||||||
"integrity": "sha512-dtePl4gsuenXVwC7dVNlb4mGDcKjDT/Ropsk4za/ouMBPplCLyznIaR+W65mvCvsyS97dymoBRrioEXI7k0XIg==",
|
"integrity": "sha512-tl0N0Mj3hMSkEYhLkjREp54OSb/FI6qyCzfiiclvJvOqre6hsZTGSnHtmFLDU8TIM62G7ygEa1bI08lcuRwEnQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/typescript-estree": "8.6.0",
|
"@typescript-eslint/typescript-estree": "8.7.0",
|
||||||
"@typescript-eslint/utils": "8.6.0",
|
"@typescript-eslint/utils": "8.7.0",
|
||||||
"debug": "^4.3.4",
|
"debug": "^4.3.4",
|
||||||
"ts-api-utils": "^1.3.0"
|
"ts-api-utils": "^1.3.0"
|
||||||
},
|
},
|
||||||
@ -5727,9 +5736,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/types": {
|
"node_modules/@typescript-eslint/types": {
|
||||||
"version": "8.6.0",
|
"version": "8.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.7.0.tgz",
|
||||||
"integrity": "sha512-rojqFZGd4MQxw33SrOy09qIDS8WEldM8JWtKQLAjf/X5mGSeEFh5ixQlxssMNyPslVIk9yzWqXCsV2eFhYrYUw==",
|
"integrity": "sha512-LLt4BLHFwSfASHSF2K29SZ+ZCsbQOM+LuarPjRUuHm+Qd09hSe3GCeaQbcCr+Mik+0QFRmep/FyZBO6fJ64U3w==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||||
@ -5740,13 +5749,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/typescript-estree": {
|
"node_modules/@typescript-eslint/typescript-estree": {
|
||||||
"version": "8.6.0",
|
"version": "8.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.7.0.tgz",
|
||||||
"integrity": "sha512-MOVAzsKJIPIlLK239l5s06YXjNqpKTVhBVDnqUumQJja5+Y94V3+4VUFRA0G60y2jNnTVwRCkhyGQpavfsbq/g==",
|
"integrity": "sha512-MC8nmcGHsmfAKxwnluTQpNqceniT8SteVwd2voYlmiSWGOtjvGXdPl17dYu2797GVscK30Z04WRM28CrKS9WOg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/types": "8.6.0",
|
"@typescript-eslint/types": "8.7.0",
|
||||||
"@typescript-eslint/visitor-keys": "8.6.0",
|
"@typescript-eslint/visitor-keys": "8.7.0",
|
||||||
"debug": "^4.3.4",
|
"debug": "^4.3.4",
|
||||||
"fast-glob": "^3.3.2",
|
"fast-glob": "^3.3.2",
|
||||||
"is-glob": "^4.0.3",
|
"is-glob": "^4.0.3",
|
||||||
@ -5792,15 +5801,15 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/utils": {
|
"node_modules/@typescript-eslint/utils": {
|
||||||
"version": "8.6.0",
|
"version": "8.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.7.0.tgz",
|
||||||
"integrity": "sha512-eNp9cWnYf36NaOVjkEUznf6fEgVy1TWpE0o52e4wtojjBx7D1UV2WAWGzR+8Y5lVFtpMLPwNbC67T83DWSph4A==",
|
"integrity": "sha512-ZbdUdwsl2X/s3CiyAu3gOlfQzpbuG3nTWKPoIvAu1pu5r8viiJvv2NPN2AqArL35NCYtw/lrPPfM4gxrMLNLPw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@eslint-community/eslint-utils": "^4.4.0",
|
"@eslint-community/eslint-utils": "^4.4.0",
|
||||||
"@typescript-eslint/scope-manager": "8.6.0",
|
"@typescript-eslint/scope-manager": "8.7.0",
|
||||||
"@typescript-eslint/types": "8.6.0",
|
"@typescript-eslint/types": "8.7.0",
|
||||||
"@typescript-eslint/typescript-estree": "8.6.0"
|
"@typescript-eslint/typescript-estree": "8.7.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||||
@ -5814,12 +5823,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/visitor-keys": {
|
"node_modules/@typescript-eslint/visitor-keys": {
|
||||||
"version": "8.6.0",
|
"version": "8.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.7.0.tgz",
|
||||||
"integrity": "sha512-wapVFfZg9H0qOYh4grNVQiMklJGluQrOUiOhYRrQWhx7BY/+I1IYb8BczWNbbUpO+pqy0rDciv3lQH5E1bCLrg==",
|
"integrity": "sha512-b1tx0orFCCh/THWPQa2ZwWzvOeyzzp36vkJYOpVg0u8UVOIsfVrnuC9FqAw9gRKn+rG2VmWQ/zDJZzkxUnj/XQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/types": "8.6.0",
|
"@typescript-eslint/types": "8.7.0",
|
||||||
"eslint-visitor-keys": "^3.4.3"
|
"eslint-visitor-keys": "^3.4.3"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
@ -8167,20 +8176,23 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/eslint": {
|
"node_modules/eslint": {
|
||||||
"version": "9.10.0",
|
"version": "9.11.1",
|
||||||
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.10.0.tgz",
|
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.11.1.tgz",
|
||||||
"integrity": "sha512-Y4D0IgtBZfOcOUAIQTSXBKoNGfY0REGqHJG6+Q81vNippW5YlKjHFj4soMxamKK1NXHUWuBZTLdU3Km+L/pcHw==",
|
"integrity": "sha512-MobhYKIoAO1s1e4VUrgx1l1Sk2JBR/Gqjjgw8+mfgoLE2xwsHur4gdfTxyTgShrhvdVFTaJSgMiQBl1jv/AWxg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@eslint-community/eslint-utils": "^4.2.0",
|
"@eslint-community/eslint-utils": "^4.2.0",
|
||||||
"@eslint-community/regexpp": "^4.11.0",
|
"@eslint-community/regexpp": "^4.11.0",
|
||||||
"@eslint/config-array": "^0.18.0",
|
"@eslint/config-array": "^0.18.0",
|
||||||
|
"@eslint/core": "^0.6.0",
|
||||||
"@eslint/eslintrc": "^3.1.0",
|
"@eslint/eslintrc": "^3.1.0",
|
||||||
"@eslint/js": "9.10.0",
|
"@eslint/js": "9.11.1",
|
||||||
"@eslint/plugin-kit": "^0.1.0",
|
"@eslint/plugin-kit": "^0.2.0",
|
||||||
"@humanwhocodes/module-importer": "^1.0.1",
|
"@humanwhocodes/module-importer": "^1.0.1",
|
||||||
"@humanwhocodes/retry": "^0.3.0",
|
"@humanwhocodes/retry": "^0.3.0",
|
||||||
"@nodelib/fs.walk": "^1.2.8",
|
"@nodelib/fs.walk": "^1.2.8",
|
||||||
|
"@types/estree": "^1.0.6",
|
||||||
|
"@types/json-schema": "^7.0.15",
|
||||||
"ajv": "^6.12.4",
|
"ajv": "^6.12.4",
|
||||||
"chalk": "^4.0.0",
|
"chalk": "^4.0.0",
|
||||||
"cross-spawn": "^7.0.2",
|
"cross-spawn": "^7.0.2",
|
||||||
@ -8328,6 +8340,12 @@
|
|||||||
"url": "https://opencollective.com/eslint"
|
"url": "https://opencollective.com/eslint"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/eslint/node_modules/@types/estree": {
|
||||||
|
"version": "1.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz",
|
||||||
|
"integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"node_modules/eslint/node_modules/ajv": {
|
"node_modules/eslint/node_modules/ajv": {
|
||||||
"version": "6.12.6",
|
"version": "6.12.6",
|
||||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
|
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
|
||||||
@ -8345,9 +8363,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/eslint/node_modules/eslint-visitor-keys": {
|
"node_modules/eslint/node_modules/eslint-visitor-keys": {
|
||||||
"version": "4.0.0",
|
"version": "4.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.1.0.tgz",
|
||||||
"integrity": "sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw==",
|
"integrity": "sha512-Q7lok0mqMUSf5a/AdAZkA5a/gHcO6snwQClVNNvFKCAVlxXucdU8pKydU5ZVZjBx5xr37vGbFFWtLQYreLzrZg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||||
@ -8497,34 +8515,34 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/exiftool-vendored": {
|
"node_modules/exiftool-vendored": {
|
||||||
"version": "28.2.1",
|
"version": "28.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/exiftool-vendored/-/exiftool-vendored-28.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/exiftool-vendored/-/exiftool-vendored-28.3.0.tgz",
|
||||||
"integrity": "sha512-D3YsKErr3BbjKeJzUVsv6CVZ+SQNgAJKPFWVLXu0CBtr24FNuE3CZBXWKWysGu0MjzeDCNwQrQI5+bXUFeiYVA==",
|
"integrity": "sha512-2DOSOvj5c1gkbKtubAnlGglxdYp9h55n0GxjK2nypVivoaCdgP/le3MOZRKgEUNObfJHmYHj4u/NnYVneu/gUw==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@photostructure/tz-lookup": "^10.0.0",
|
"@photostructure/tz-lookup": "^11.0.0",
|
||||||
"@types/luxon": "^3.4.2",
|
"@types/luxon": "^3.4.2",
|
||||||
"batch-cluster": "^13.0.0",
|
"batch-cluster": "^13.0.0",
|
||||||
"he": "^1.2.0",
|
"he": "^1.2.0",
|
||||||
"luxon": "^3.5.0"
|
"luxon": "^3.5.0"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"exiftool-vendored.exe": "12.91.0",
|
"exiftool-vendored.exe": "12.96.0",
|
||||||
"exiftool-vendored.pl": "12.91.0"
|
"exiftool-vendored.pl": "12.96.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/exiftool-vendored.exe": {
|
"node_modules/exiftool-vendored.exe": {
|
||||||
"version": "12.91.0",
|
"version": "12.96.0",
|
||||||
"resolved": "https://registry.npmjs.org/exiftool-vendored.exe/-/exiftool-vendored.exe-12.91.0.tgz",
|
"resolved": "https://registry.npmjs.org/exiftool-vendored.exe/-/exiftool-vendored.exe-12.96.0.tgz",
|
||||||
"integrity": "sha512-nxcoGBaJL/D+Wb0jVe8qwyV8QZpRcCzU0aCKhG0S1XNGWGjJJJ4QV851aobcfDwI4NluFOdqkjTSf32pVijvHg==",
|
"integrity": "sha512-pKPN9F/Evw2yyO5/+ml3spbXIqejzOxyF7jEnj8tLU2JPSmIlziPUZ75XIhcPbilX86jVKmuiso7FUDicOg8pQ==",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
"win32"
|
"win32"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/exiftool-vendored.pl": {
|
"node_modules/exiftool-vendored.pl": {
|
||||||
"version": "12.91.0",
|
"version": "12.96.0",
|
||||||
"resolved": "https://registry.npmjs.org/exiftool-vendored.pl/-/exiftool-vendored.pl-12.91.0.tgz",
|
"resolved": "https://registry.npmjs.org/exiftool-vendored.pl/-/exiftool-vendored.pl-12.96.0.tgz",
|
||||||
"integrity": "sha512-GZMy9+Jiv8/C7R4uYe1kWtXsAaJdgVezTwYa+wDeoqvReHiX2t5uzkCrzWdjo4LGl5mPQkyKhN7/uPLYk5Ak6w==",
|
"integrity": "sha512-v4nGnovAMBsTfOWhwAcOiRiq/8kuJOo3GUMHNpug7Mr4jLz3tmWEo7DdNyOYmpcvWbA6smOTG0SmwsrY8fsW+A==",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
"!win32"
|
"!win32"
|
||||||
@ -11669,20 +11687,16 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/prettier-plugin-organize-imports": {
|
"node_modules/prettier-plugin-organize-imports": {
|
||||||
"version": "4.0.0",
|
"version": "4.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/prettier-plugin-organize-imports/-/prettier-plugin-organize-imports-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/prettier-plugin-organize-imports/-/prettier-plugin-organize-imports-4.1.0.tgz",
|
||||||
"integrity": "sha512-vnKSdgv9aOlqKeEFGhf9SCBsTyzDSyScy1k7E0R1Uo4L0cTcOV7c1XQaT7jfXIOc/p08WLBfN2QUQA9zDSZMxA==",
|
"integrity": "sha512-5aWRdCgv645xaa58X8lOxzZoiHAldAPChljr/MT0crXVOWTZ+Svl4hIWlz+niYSlO6ikE5UXkN1JrRvIP2ut0A==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@vue/language-plugin-pug": "^2.0.24",
|
|
||||||
"prettier": ">=2.0",
|
"prettier": ">=2.0",
|
||||||
"typescript": ">=2.9",
|
"typescript": ">=2.9",
|
||||||
"vue-tsc": "^2.0.24"
|
"vue-tsc": "^2.1.0"
|
||||||
},
|
},
|
||||||
"peerDependenciesMeta": {
|
"peerDependenciesMeta": {
|
||||||
"@vue/language-plugin-pug": {
|
|
||||||
"optional": true
|
|
||||||
},
|
|
||||||
"vue-tsc": {
|
"vue-tsc": {
|
||||||
"optional": true
|
"optional": true
|
||||||
}
|
}
|
||||||
@ -15977,6 +15991,12 @@
|
|||||||
"minimatch": "^3.1.2"
|
"minimatch": "^3.1.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@eslint/core": {
|
||||||
|
"version": "0.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.6.0.tgz",
|
||||||
|
"integrity": "sha512-8I2Q8ykA4J0x0o7cg67FPVnehcqWTBehu/lmY+bolPFHGjh49YzGBMXTvpqVgEbBdvNCSxj6iFgiIyHzf03lzg==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"@eslint/eslintrc": {
|
"@eslint/eslintrc": {
|
||||||
"version": "3.1.0",
|
"version": "3.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.1.0.tgz",
|
||||||
@ -16021,9 +16041,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@eslint/js": {
|
"@eslint/js": {
|
||||||
"version": "9.10.0",
|
"version": "9.11.1",
|
||||||
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.10.0.tgz",
|
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.11.1.tgz",
|
||||||
"integrity": "sha512-fuXtbiP5GWIn8Fz+LWoOMVf/Jxm+aajZYkhi6CuEm4SxymFM+eUWzbO9qXT+L0iCkL5+KGYMCSGxo686H19S1g==",
|
"integrity": "sha512-/qu+TWz8WwPWc7/HcIJKi+c+MOm46GdVaSlTTQcaqaL53+GsoA6MxWp5PtTx48qbSP7ylM1Kn7nhvkugfJvRSA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"@eslint/object-schema": {
|
"@eslint/object-schema": {
|
||||||
@ -16033,9 +16053,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"@eslint/plugin-kit": {
|
"@eslint/plugin-kit": {
|
||||||
"version": "0.1.0",
|
"version": "0.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.0.tgz",
|
||||||
"integrity": "sha512-autAXT203ixhqei9xt+qkYOvY8l6LAFIdT2UXc/RPNeUVfqRF1BV94GTJyVPFKT8nFM6MyVJhjLj9E8JWvf5zQ==",
|
"integrity": "sha512-vH9PiIMMwvhCx31Af3HiGzsVNULDbyVkHXwlemn/B0TFj/00ho3y55efXrUZTfQipxoHC5u4xq6zblww1zm1Ig==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"levn": "^0.4.1"
|
"levn": "^0.4.1"
|
||||||
@ -16512,9 +16532,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@nestjs/common": {
|
"@nestjs/common": {
|
||||||
"version": "10.4.3",
|
"version": "10.4.4",
|
||||||
"resolved": "https://registry.npmjs.org/@nestjs/common/-/common-10.4.3.tgz",
|
"resolved": "https://registry.npmjs.org/@nestjs/common/-/common-10.4.4.tgz",
|
||||||
"integrity": "sha512-4hbLd3XIJubHSylYd/1WSi4VQvG68KM/ECYpMDqA3k3J1/T17SAg40sDoq3ZoO5OZgU0xuNyjuISdOTjs11qVg==",
|
"integrity": "sha512-0j2/zqRw9nvHV1GKTktER8B/hIC/Z8CYFjN/ZqUuvwayCH+jZZBhCR2oRyuvLTXdnlSmtCAg2xvQ0ULqQvzqhA==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"iterare": "1.2.1",
|
"iterare": "1.2.1",
|
||||||
"tslib": "2.7.0",
|
"tslib": "2.7.0",
|
||||||
@ -16592,9 +16612,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@nestjs/platform-socket.io": {
|
"@nestjs/platform-socket.io": {
|
||||||
"version": "10.4.3",
|
"version": "10.4.4",
|
||||||
"resolved": "https://registry.npmjs.org/@nestjs/platform-socket.io/-/platform-socket.io-10.4.3.tgz",
|
"resolved": "https://registry.npmjs.org/@nestjs/platform-socket.io/-/platform-socket.io-10.4.4.tgz",
|
||||||
"integrity": "sha512-jTatT8q15LB5CFWsaIez3IigMixt7tNGJ4QLlRJ5NggPOPKRZssJnloODyEadFNHJjZiyufp5/NoPKBtNMf+lg==",
|
"integrity": "sha512-5GEYUA3sNbX2jOBP6FmrIK/zv9VCdvpdr4Sef1OKvt1U0qsV1YgmWPWDPumZM77n5DI0VHSJPyo7yjZaEKWOiQ==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"socket.io": "4.7.5",
|
"socket.io": "4.7.5",
|
||||||
"tslib": "2.7.0"
|
"tslib": "2.7.0"
|
||||||
@ -16658,9 +16678,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@nestjs/testing": {
|
"@nestjs/testing": {
|
||||||
"version": "10.4.3",
|
"version": "10.4.4",
|
||||||
"resolved": "https://registry.npmjs.org/@nestjs/testing/-/testing-10.4.3.tgz",
|
"resolved": "https://registry.npmjs.org/@nestjs/testing/-/testing-10.4.4.tgz",
|
||||||
"integrity": "sha512-SBNWrMU51YAlYmW86wyjlGZ2uLnASNiOPD0lBcNIlxxei0b05/aI3nh7OPuxbXQUdedUJfPq2d2jZj4TRG4S0w==",
|
"integrity": "sha512-qRGFj51A5RM7JqA8pcyEwSLA3Y0dle/PAZ8oxP0suimoCusRY3Tk7wYqutZdCNj1ATb678SDaUZDHk2pwSv9/g==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"tslib": "2.7.0"
|
"tslib": "2.7.0"
|
||||||
@ -16683,9 +16703,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@nestjs/websockets": {
|
"@nestjs/websockets": {
|
||||||
"version": "10.4.3",
|
"version": "10.4.4",
|
||||||
"resolved": "https://registry.npmjs.org/@nestjs/websockets/-/websockets-10.4.3.tgz",
|
"resolved": "https://registry.npmjs.org/@nestjs/websockets/-/websockets-10.4.4.tgz",
|
||||||
"integrity": "sha512-EW5/GR0jImJwrb8+YpHPoFN2tlhYQzVE2yAN5Se5sygUr/ZFMNAG84sd79NmWGd4RxoxR0aFH9nRycQ/0Ebe5w==",
|
"integrity": "sha512-ZHnak04i/iKBS0csjJa7K6D6xdsB0Yz6duJuCR7xGLItchFK+Ne21m9rEF8ffvW74U7UAYkQHBgD5242LBBYiQ==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"iterare": "1.2.1",
|
"iterare": "1.2.1",
|
||||||
"object-hash": "3.0.0",
|
"object-hash": "3.0.0",
|
||||||
@ -17860,9 +17880,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@photostructure/tz-lookup": {
|
"@photostructure/tz-lookup": {
|
||||||
"version": "10.0.0",
|
"version": "11.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/@photostructure/tz-lookup/-/tz-lookup-10.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/@photostructure/tz-lookup/-/tz-lookup-11.0.0.tgz",
|
||||||
"integrity": "sha512-8ZAjoj/irCuvUlyEinQ/HB6A8hP3bD1dgTOZvfl1b9nAwqniutFDHOQRcGM6Crea68bOwPj010f0Z4KkmuLHEA=="
|
"integrity": "sha512-QMV5/dWtY/MdVPXZs/EApqzyhnqDq1keYEqpS+Xj2uidyaqw2Nk/fWcsszdruIXjdqp1VoWNzsgrO6bUHU1mFw=="
|
||||||
},
|
},
|
||||||
"@pkgjs/parseargs": {
|
"@pkgjs/parseargs": {
|
||||||
"version": "0.11.0",
|
"version": "0.11.0",
|
||||||
@ -18644,9 +18664,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"@types/lodash": {
|
"@types/lodash": {
|
||||||
"version": "4.17.7",
|
"version": "4.17.9",
|
||||||
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.7.tgz",
|
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.9.tgz",
|
||||||
"integrity": "sha512-8wTvZawATi/lsmNu10/j2hk1KEP0IvjubqPE3cu1Xz7xfXXt5oCq3SNUz4fMIP4XGF9Ky+Ue2tBA3hcS7LSBlA==",
|
"integrity": "sha512-w9iWudx1XWOHW5lQRS9iKpK/XuRhnN+0T7HvdCCd802FYkT1AMTnxndJHGrNJwRoRHkslGr4S29tjm1cT7x/7w==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"@types/luxon": {
|
"@types/luxon": {
|
||||||
@ -18701,9 +18721,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@types/node": {
|
"@types/node": {
|
||||||
"version": "20.16.5",
|
"version": "20.16.10",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.16.5.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.16.10.tgz",
|
||||||
"integrity": "sha512-VwYCweNo3ERajwy0IUlqqcyZ8/A7Zwa9ZP3MnENWcB11AejO+tLy3pu850goUW2FC/IJMdZUfKpX/yxL1gymCA==",
|
"integrity": "sha512-vQUKgWTjEIRFCvK6CyriPH3MZYiYlNy0fKiEYHWbcoWLEgs4opurGGKlebrTLqdSMIbXImH6XExNiIyNUv3WpA==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"undici-types": "~6.19.2"
|
"undici-types": "~6.19.2"
|
||||||
}
|
}
|
||||||
@ -18805,9 +18825,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"@types/react": {
|
"@types/react": {
|
||||||
"version": "18.3.8",
|
"version": "18.3.9",
|
||||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.8.tgz",
|
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.9.tgz",
|
||||||
"integrity": "sha512-syBUrW3/XpnW4WJ41Pft+I+aPoDVbrBVQGEnbD7NijDGlVC+8gV/XKRY+7vMDlfPpbwYt0l1vd/Sj8bJGMbs9Q==",
|
"integrity": "sha512-+BpAVyTpJkNWWSSnaLBk6ePpHLOGJKnEQNbINNovPWzvEUyAe3e+/d494QdEh71RekM/qV7lw6jzf1HGrJyAtQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@types/prop-types": "*",
|
"@types/prop-types": "*",
|
||||||
@ -18924,16 +18944,16 @@
|
|||||||
"integrity": "sha512-c/hzNDBh7eRF+KbCf+OoZxKbnkpaK/cKp9iLQWqB7muXtM+MtL9SUUH8vCFcLn6dH1Qm05jiexK0ofWY7TfOhQ=="
|
"integrity": "sha512-c/hzNDBh7eRF+KbCf+OoZxKbnkpaK/cKp9iLQWqB7muXtM+MtL9SUUH8vCFcLn6dH1Qm05jiexK0ofWY7TfOhQ=="
|
||||||
},
|
},
|
||||||
"@typescript-eslint/eslint-plugin": {
|
"@typescript-eslint/eslint-plugin": {
|
||||||
"version": "8.6.0",
|
"version": "8.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.7.0.tgz",
|
||||||
"integrity": "sha512-UOaz/wFowmoh2G6Mr9gw60B1mm0MzUtm6Ic8G2yM1Le6gyj5Loi/N+O5mocugRGY+8OeeKmkMmbxNqUCq3B4Sg==",
|
"integrity": "sha512-RIHOoznhA3CCfSTFiB6kBGLQtB/sox+pJ6jeFu6FxJvqL8qRxq/FfGO/UhsGgQM9oGdXkV4xUgli+dt26biB6A==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@eslint-community/regexpp": "^4.10.0",
|
"@eslint-community/regexpp": "^4.10.0",
|
||||||
"@typescript-eslint/scope-manager": "8.6.0",
|
"@typescript-eslint/scope-manager": "8.7.0",
|
||||||
"@typescript-eslint/type-utils": "8.6.0",
|
"@typescript-eslint/type-utils": "8.7.0",
|
||||||
"@typescript-eslint/utils": "8.6.0",
|
"@typescript-eslint/utils": "8.7.0",
|
||||||
"@typescript-eslint/visitor-keys": "8.6.0",
|
"@typescript-eslint/visitor-keys": "8.7.0",
|
||||||
"graphemer": "^1.4.0",
|
"graphemer": "^1.4.0",
|
||||||
"ignore": "^5.3.1",
|
"ignore": "^5.3.1",
|
||||||
"natural-compare": "^1.4.0",
|
"natural-compare": "^1.4.0",
|
||||||
@ -18941,54 +18961,54 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@typescript-eslint/parser": {
|
"@typescript-eslint/parser": {
|
||||||
"version": "8.6.0",
|
"version": "8.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.7.0.tgz",
|
||||||
"integrity": "sha512-eQcbCuA2Vmw45iGfcyG4y6rS7BhWfz9MQuk409WD47qMM+bKCGQWXxvoOs1DUp+T7UBMTtRTVT+kXr7Sh4O9Ow==",
|
"integrity": "sha512-lN0btVpj2unxHlNYLI//BQ7nzbMJYBVQX5+pbNXvGYazdlgYonMn4AhhHifQ+J4fGRYA/m1DjaQjx+fDetqBOQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@typescript-eslint/scope-manager": "8.6.0",
|
"@typescript-eslint/scope-manager": "8.7.0",
|
||||||
"@typescript-eslint/types": "8.6.0",
|
"@typescript-eslint/types": "8.7.0",
|
||||||
"@typescript-eslint/typescript-estree": "8.6.0",
|
"@typescript-eslint/typescript-estree": "8.7.0",
|
||||||
"@typescript-eslint/visitor-keys": "8.6.0",
|
"@typescript-eslint/visitor-keys": "8.7.0",
|
||||||
"debug": "^4.3.4"
|
"debug": "^4.3.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@typescript-eslint/scope-manager": {
|
"@typescript-eslint/scope-manager": {
|
||||||
"version": "8.6.0",
|
"version": "8.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.7.0.tgz",
|
||||||
"integrity": "sha512-ZuoutoS5y9UOxKvpc/GkvF4cuEmpokda4wRg64JEia27wX+PysIE9q+lzDtlHHgblwUWwo5/Qn+/WyTUvDwBHw==",
|
"integrity": "sha512-87rC0k3ZlDOuz82zzXRtQ7Akv3GKhHs0ti4YcbAJtaomllXoSO8hi7Ix3ccEvCd824dy9aIX+j3d2UMAfCtVpg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@typescript-eslint/types": "8.6.0",
|
"@typescript-eslint/types": "8.7.0",
|
||||||
"@typescript-eslint/visitor-keys": "8.6.0"
|
"@typescript-eslint/visitor-keys": "8.7.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@typescript-eslint/type-utils": {
|
"@typescript-eslint/type-utils": {
|
||||||
"version": "8.6.0",
|
"version": "8.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.7.0.tgz",
|
||||||
"integrity": "sha512-dtePl4gsuenXVwC7dVNlb4mGDcKjDT/Ropsk4za/ouMBPplCLyznIaR+W65mvCvsyS97dymoBRrioEXI7k0XIg==",
|
"integrity": "sha512-tl0N0Mj3hMSkEYhLkjREp54OSb/FI6qyCzfiiclvJvOqre6hsZTGSnHtmFLDU8TIM62G7ygEa1bI08lcuRwEnQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@typescript-eslint/typescript-estree": "8.6.0",
|
"@typescript-eslint/typescript-estree": "8.7.0",
|
||||||
"@typescript-eslint/utils": "8.6.0",
|
"@typescript-eslint/utils": "8.7.0",
|
||||||
"debug": "^4.3.4",
|
"debug": "^4.3.4",
|
||||||
"ts-api-utils": "^1.3.0"
|
"ts-api-utils": "^1.3.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@typescript-eslint/types": {
|
"@typescript-eslint/types": {
|
||||||
"version": "8.6.0",
|
"version": "8.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.7.0.tgz",
|
||||||
"integrity": "sha512-rojqFZGd4MQxw33SrOy09qIDS8WEldM8JWtKQLAjf/X5mGSeEFh5ixQlxssMNyPslVIk9yzWqXCsV2eFhYrYUw==",
|
"integrity": "sha512-LLt4BLHFwSfASHSF2K29SZ+ZCsbQOM+LuarPjRUuHm+Qd09hSe3GCeaQbcCr+Mik+0QFRmep/FyZBO6fJ64U3w==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"@typescript-eslint/typescript-estree": {
|
"@typescript-eslint/typescript-estree": {
|
||||||
"version": "8.6.0",
|
"version": "8.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.7.0.tgz",
|
||||||
"integrity": "sha512-MOVAzsKJIPIlLK239l5s06YXjNqpKTVhBVDnqUumQJja5+Y94V3+4VUFRA0G60y2jNnTVwRCkhyGQpavfsbq/g==",
|
"integrity": "sha512-MC8nmcGHsmfAKxwnluTQpNqceniT8SteVwd2voYlmiSWGOtjvGXdPl17dYu2797GVscK30Z04WRM28CrKS9WOg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@typescript-eslint/types": "8.6.0",
|
"@typescript-eslint/types": "8.7.0",
|
||||||
"@typescript-eslint/visitor-keys": "8.6.0",
|
"@typescript-eslint/visitor-keys": "8.7.0",
|
||||||
"debug": "^4.3.4",
|
"debug": "^4.3.4",
|
||||||
"fast-glob": "^3.3.2",
|
"fast-glob": "^3.3.2",
|
||||||
"is-glob": "^4.0.3",
|
"is-glob": "^4.0.3",
|
||||||
@ -19018,24 +19038,24 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@typescript-eslint/utils": {
|
"@typescript-eslint/utils": {
|
||||||
"version": "8.6.0",
|
"version": "8.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.7.0.tgz",
|
||||||
"integrity": "sha512-eNp9cWnYf36NaOVjkEUznf6fEgVy1TWpE0o52e4wtojjBx7D1UV2WAWGzR+8Y5lVFtpMLPwNbC67T83DWSph4A==",
|
"integrity": "sha512-ZbdUdwsl2X/s3CiyAu3gOlfQzpbuG3nTWKPoIvAu1pu5r8viiJvv2NPN2AqArL35NCYtw/lrPPfM4gxrMLNLPw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@eslint-community/eslint-utils": "^4.4.0",
|
"@eslint-community/eslint-utils": "^4.4.0",
|
||||||
"@typescript-eslint/scope-manager": "8.6.0",
|
"@typescript-eslint/scope-manager": "8.7.0",
|
||||||
"@typescript-eslint/types": "8.6.0",
|
"@typescript-eslint/types": "8.7.0",
|
||||||
"@typescript-eslint/typescript-estree": "8.6.0"
|
"@typescript-eslint/typescript-estree": "8.7.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@typescript-eslint/visitor-keys": {
|
"@typescript-eslint/visitor-keys": {
|
||||||
"version": "8.6.0",
|
"version": "8.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.7.0.tgz",
|
||||||
"integrity": "sha512-wapVFfZg9H0qOYh4grNVQiMklJGluQrOUiOhYRrQWhx7BY/+I1IYb8BczWNbbUpO+pqy0rDciv3lQH5E1bCLrg==",
|
"integrity": "sha512-b1tx0orFCCh/THWPQa2ZwWzvOeyzzp36vkJYOpVg0u8UVOIsfVrnuC9FqAw9gRKn+rG2VmWQ/zDJZzkxUnj/XQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@typescript-eslint/types": "8.6.0",
|
"@typescript-eslint/types": "8.7.0",
|
||||||
"eslint-visitor-keys": "^3.4.3"
|
"eslint-visitor-keys": "^3.4.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -20786,20 +20806,23 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"eslint": {
|
"eslint": {
|
||||||
"version": "9.10.0",
|
"version": "9.11.1",
|
||||||
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.10.0.tgz",
|
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.11.1.tgz",
|
||||||
"integrity": "sha512-Y4D0IgtBZfOcOUAIQTSXBKoNGfY0REGqHJG6+Q81vNippW5YlKjHFj4soMxamKK1NXHUWuBZTLdU3Km+L/pcHw==",
|
"integrity": "sha512-MobhYKIoAO1s1e4VUrgx1l1Sk2JBR/Gqjjgw8+mfgoLE2xwsHur4gdfTxyTgShrhvdVFTaJSgMiQBl1jv/AWxg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@eslint-community/eslint-utils": "^4.2.0",
|
"@eslint-community/eslint-utils": "^4.2.0",
|
||||||
"@eslint-community/regexpp": "^4.11.0",
|
"@eslint-community/regexpp": "^4.11.0",
|
||||||
"@eslint/config-array": "^0.18.0",
|
"@eslint/config-array": "^0.18.0",
|
||||||
|
"@eslint/core": "^0.6.0",
|
||||||
"@eslint/eslintrc": "^3.1.0",
|
"@eslint/eslintrc": "^3.1.0",
|
||||||
"@eslint/js": "9.10.0",
|
"@eslint/js": "9.11.1",
|
||||||
"@eslint/plugin-kit": "^0.1.0",
|
"@eslint/plugin-kit": "^0.2.0",
|
||||||
"@humanwhocodes/module-importer": "^1.0.1",
|
"@humanwhocodes/module-importer": "^1.0.1",
|
||||||
"@humanwhocodes/retry": "^0.3.0",
|
"@humanwhocodes/retry": "^0.3.0",
|
||||||
"@nodelib/fs.walk": "^1.2.8",
|
"@nodelib/fs.walk": "^1.2.8",
|
||||||
|
"@types/estree": "^1.0.6",
|
||||||
|
"@types/json-schema": "^7.0.15",
|
||||||
"ajv": "^6.12.4",
|
"ajv": "^6.12.4",
|
||||||
"chalk": "^4.0.0",
|
"chalk": "^4.0.0",
|
||||||
"cross-spawn": "^7.0.2",
|
"cross-spawn": "^7.0.2",
|
||||||
@ -20827,6 +20850,12 @@
|
|||||||
"text-table": "^0.2.0"
|
"text-table": "^0.2.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@types/estree": {
|
||||||
|
"version": "1.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz",
|
||||||
|
"integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"ajv": {
|
"ajv": {
|
||||||
"version": "6.12.6",
|
"version": "6.12.6",
|
||||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
|
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
|
||||||
@ -20840,9 +20869,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"eslint-visitor-keys": {
|
"eslint-visitor-keys": {
|
||||||
"version": "4.0.0",
|
"version": "4.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.1.0.tgz",
|
||||||
"integrity": "sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw==",
|
"integrity": "sha512-Q7lok0mqMUSf5a/AdAZkA5a/gHcO6snwQClVNNvFKCAVlxXucdU8pKydU5ZVZjBx5xr37vGbFFWtLQYreLzrZg==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"glob-parent": {
|
"glob-parent": {
|
||||||
@ -21004,29 +21033,29 @@
|
|||||||
"integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q=="
|
"integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q=="
|
||||||
},
|
},
|
||||||
"exiftool-vendored": {
|
"exiftool-vendored": {
|
||||||
"version": "28.2.1",
|
"version": "28.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/exiftool-vendored/-/exiftool-vendored-28.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/exiftool-vendored/-/exiftool-vendored-28.3.0.tgz",
|
||||||
"integrity": "sha512-D3YsKErr3BbjKeJzUVsv6CVZ+SQNgAJKPFWVLXu0CBtr24FNuE3CZBXWKWysGu0MjzeDCNwQrQI5+bXUFeiYVA==",
|
"integrity": "sha512-2DOSOvj5c1gkbKtubAnlGglxdYp9h55n0GxjK2nypVivoaCdgP/le3MOZRKgEUNObfJHmYHj4u/NnYVneu/gUw==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@photostructure/tz-lookup": "^10.0.0",
|
"@photostructure/tz-lookup": "^11.0.0",
|
||||||
"@types/luxon": "^3.4.2",
|
"@types/luxon": "^3.4.2",
|
||||||
"batch-cluster": "^13.0.0",
|
"batch-cluster": "^13.0.0",
|
||||||
"exiftool-vendored.exe": "12.91.0",
|
"exiftool-vendored.exe": "12.96.0",
|
||||||
"exiftool-vendored.pl": "12.91.0",
|
"exiftool-vendored.pl": "12.96.0",
|
||||||
"he": "^1.2.0",
|
"he": "^1.2.0",
|
||||||
"luxon": "^3.5.0"
|
"luxon": "^3.5.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"exiftool-vendored.exe": {
|
"exiftool-vendored.exe": {
|
||||||
"version": "12.91.0",
|
"version": "12.96.0",
|
||||||
"resolved": "https://registry.npmjs.org/exiftool-vendored.exe/-/exiftool-vendored.exe-12.91.0.tgz",
|
"resolved": "https://registry.npmjs.org/exiftool-vendored.exe/-/exiftool-vendored.exe-12.96.0.tgz",
|
||||||
"integrity": "sha512-nxcoGBaJL/D+Wb0jVe8qwyV8QZpRcCzU0aCKhG0S1XNGWGjJJJ4QV851aobcfDwI4NluFOdqkjTSf32pVijvHg==",
|
"integrity": "sha512-pKPN9F/Evw2yyO5/+ml3spbXIqejzOxyF7jEnj8tLU2JPSmIlziPUZ75XIhcPbilX86jVKmuiso7FUDicOg8pQ==",
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
"exiftool-vendored.pl": {
|
"exiftool-vendored.pl": {
|
||||||
"version": "12.91.0",
|
"version": "12.96.0",
|
||||||
"resolved": "https://registry.npmjs.org/exiftool-vendored.pl/-/exiftool-vendored.pl-12.91.0.tgz",
|
"resolved": "https://registry.npmjs.org/exiftool-vendored.pl/-/exiftool-vendored.pl-12.96.0.tgz",
|
||||||
"integrity": "sha512-GZMy9+Jiv8/C7R4uYe1kWtXsAaJdgVezTwYa+wDeoqvReHiX2t5uzkCrzWdjo4LGl5mPQkyKhN7/uPLYk5Ak6w==",
|
"integrity": "sha512-v4nGnovAMBsTfOWhwAcOiRiq/8kuJOo3GUMHNpug7Mr4jLz3tmWEo7DdNyOYmpcvWbA6smOTG0SmwsrY8fsW+A==",
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
"express": {
|
"express": {
|
||||||
@ -23312,9 +23341,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"prettier-plugin-organize-imports": {
|
"prettier-plugin-organize-imports": {
|
||||||
"version": "4.0.0",
|
"version": "4.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/prettier-plugin-organize-imports/-/prettier-plugin-organize-imports-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/prettier-plugin-organize-imports/-/prettier-plugin-organize-imports-4.1.0.tgz",
|
||||||
"integrity": "sha512-vnKSdgv9aOlqKeEFGhf9SCBsTyzDSyScy1k7E0R1Uo4L0cTcOV7c1XQaT7jfXIOc/p08WLBfN2QUQA9zDSZMxA==",
|
"integrity": "sha512-5aWRdCgv645xaa58X8lOxzZoiHAldAPChljr/MT0crXVOWTZ+Svl4hIWlz+niYSlO6ikE5UXkN1JrRvIP2ut0A==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {}
|
"requires": {}
|
||||||
},
|
},
|
||||||
|
@ -109,7 +109,7 @@
|
|||||||
"@types/lodash": "^4.14.197",
|
"@types/lodash": "^4.14.197",
|
||||||
"@types/mock-fs": "^4.13.1",
|
"@types/mock-fs": "^4.13.1",
|
||||||
"@types/multer": "^1.4.7",
|
"@types/multer": "^1.4.7",
|
||||||
"@types/node": "^20.16.5",
|
"@types/node": "^20.16.9",
|
||||||
"@types/nodemailer": "^6.4.14",
|
"@types/nodemailer": "^6.4.14",
|
||||||
"@types/picomatch": "^3.0.0",
|
"@types/picomatch": "^3.0.0",
|
||||||
"@types/react": "^18.3.4",
|
"@types/react": "^18.3.4",
|
||||||
|
@ -415,6 +415,10 @@ export const getBuildMetadata = () => ({
|
|||||||
sourceRef: process.env.IMMICH_SOURCE_REF,
|
sourceRef: process.env.IMMICH_SOURCE_REF,
|
||||||
sourceCommit: process.env.IMMICH_SOURCE_COMMIT,
|
sourceCommit: process.env.IMMICH_SOURCE_COMMIT,
|
||||||
sourceUrl: process.env.IMMICH_SOURCE_URL,
|
sourceUrl: process.env.IMMICH_SOURCE_URL,
|
||||||
|
thirdPartySourceUrl: process.env.IMMICH_THIRD_PARTY_SOURCE_URL,
|
||||||
|
thirdPartyBugFeatureUrl: process.env.IMMICH_THIRD_PARTY_BUG_FEATURE_URL,
|
||||||
|
thirdPartyDocumentationUrl: process.env.IMMICH_THIRD_PARTY_DOCUMENTATION_URL,
|
||||||
|
thirdPartySupportUrl: process.env.IMMICH_THIRD_PARTY_SUPPORT_URL,
|
||||||
});
|
});
|
||||||
|
|
||||||
const clientLicensePublicKeyProd =
|
const clientLicensePublicKeyProd =
|
||||||
|
@ -23,7 +23,6 @@ export const ONE_HOUR = Duration.fromObject({ hours: 1 });
|
|||||||
export const envName = (process.env.IMMICH_ENV || 'production').toUpperCase();
|
export const envName = (process.env.IMMICH_ENV || 'production').toUpperCase();
|
||||||
export const isDev = () => process.env.IMMICH_ENV === 'development';
|
export const isDev = () => process.env.IMMICH_ENV === 'development';
|
||||||
export const APP_MEDIA_LOCATION = process.env.IMMICH_MEDIA_LOCATION || './upload';
|
export const APP_MEDIA_LOCATION = process.env.IMMICH_MEDIA_LOCATION || './upload';
|
||||||
export const WEB_ROOT = process.env.IMMICH_WEB_ROOT || '/usr/src/app/www';
|
|
||||||
const HOST_SERVER_PORT = process.env.IMMICH_PORT || '2283';
|
const HOST_SERVER_PORT = process.env.IMMICH_PORT || '2283';
|
||||||
export const DEFAULT_EXTERNAL_DOMAIN = 'http://localhost:' + HOST_SERVER_PORT;
|
export const DEFAULT_EXTERNAL_DOMAIN = 'http://localhost:' + HOST_SERVER_PORT;
|
||||||
|
|
||||||
|
@ -66,7 +66,7 @@ export class ServerInfoController {
|
|||||||
@Get('config')
|
@Get('config')
|
||||||
@EndpointLifecycle({ deprecatedAt: 'v1.107.0' })
|
@EndpointLifecycle({ deprecatedAt: 'v1.107.0' })
|
||||||
getServerConfig(): Promise<ServerConfigDto> {
|
getServerConfig(): Promise<ServerConfigDto> {
|
||||||
return this.service.getConfig();
|
return this.service.getSystemConfig();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get('statistics')
|
@Get('statistics')
|
||||||
|
@ -58,7 +58,7 @@ export class ServerController {
|
|||||||
|
|
||||||
@Get('config')
|
@Get('config')
|
||||||
getServerConfig(): Promise<ServerConfigDto> {
|
getServerConfig(): Promise<ServerConfigDto> {
|
||||||
return this.service.getConfig();
|
return this.service.getSystemConfig();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get('statistics')
|
@Get('statistics')
|
||||||
|
@ -13,7 +13,7 @@ export class SystemConfigController {
|
|||||||
@Get()
|
@Get()
|
||||||
@Authenticated({ permission: Permission.SYSTEM_CONFIG_READ, admin: true })
|
@Authenticated({ permission: Permission.SYSTEM_CONFIG_READ, admin: true })
|
||||||
getConfig(): Promise<SystemConfigDto> {
|
getConfig(): Promise<SystemConfigDto> {
|
||||||
return this.service.getConfig();
|
return this.service.getSystemConfig();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get('defaults')
|
@Get('defaults')
|
||||||
@ -25,7 +25,7 @@ export class SystemConfigController {
|
|||||||
@Put()
|
@Put()
|
||||||
@Authenticated({ permission: Permission.SYSTEM_CONFIG_UPDATE, admin: true })
|
@Authenticated({ permission: Permission.SYSTEM_CONFIG_UPDATE, admin: true })
|
||||||
updateConfig(@Body() dto: SystemConfigDto): Promise<SystemConfigDto> {
|
updateConfig(@Body() dto: SystemConfigDto): Promise<SystemConfigDto> {
|
||||||
return this.service.updateConfig(dto);
|
return this.service.updateSystemConfig(dto);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get('storage-template-options')
|
@Get('storage-template-options')
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import { randomUUID } from 'node:crypto';
|
import { randomUUID } from 'node:crypto';
|
||||||
import { dirname, join, resolve } from 'node:path';
|
import { dirname, join, resolve } from 'node:path';
|
||||||
import { APP_MEDIA_LOCATION } from 'src/constants';
|
import { APP_MEDIA_LOCATION } from 'src/constants';
|
||||||
import { SystemConfigCore } from 'src/cores/system-config.core';
|
|
||||||
import { AssetEntity } from 'src/entities/asset.entity';
|
import { AssetEntity } from 'src/entities/asset.entity';
|
||||||
import { PersonEntity } from 'src/entities/person.entity';
|
import { PersonEntity } from 'src/entities/person.entity';
|
||||||
import { AssetFileType, AssetPathType, ImageFormat, PathType, PersonPathType, StorageFolder } from 'src/enum';
|
import { AssetFileType, AssetPathType, ImageFormat, PathType, PersonPathType, StorageFolder } from 'src/enum';
|
||||||
@ -13,6 +12,7 @@ import { IPersonRepository } from 'src/interfaces/person.interface';
|
|||||||
import { IStorageRepository } from 'src/interfaces/storage.interface';
|
import { IStorageRepository } from 'src/interfaces/storage.interface';
|
||||||
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
|
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
|
||||||
import { getAssetFiles } from 'src/utils/asset.util';
|
import { getAssetFiles } from 'src/utils/asset.util';
|
||||||
|
import { getConfig } from 'src/utils/config';
|
||||||
|
|
||||||
export const THUMBNAIL_DIR = resolve(join(APP_MEDIA_LOCATION, StorageFolder.THUMBNAILS));
|
export const THUMBNAIL_DIR = resolve(join(APP_MEDIA_LOCATION, StorageFolder.THUMBNAILS));
|
||||||
export const ENCODED_VIDEO_DIR = resolve(join(APP_MEDIA_LOCATION, StorageFolder.ENCODED_VIDEO));
|
export const ENCODED_VIDEO_DIR = resolve(join(APP_MEDIA_LOCATION, StorageFolder.ENCODED_VIDEO));
|
||||||
@ -34,18 +34,15 @@ export type GeneratedAssetType = GeneratedImageType | AssetPathType.ENCODED_VIDE
|
|||||||
let instance: StorageCore | null;
|
let instance: StorageCore | null;
|
||||||
|
|
||||||
export class StorageCore {
|
export class StorageCore {
|
||||||
private configCore;
|
|
||||||
private constructor(
|
private constructor(
|
||||||
private assetRepository: IAssetRepository,
|
private assetRepository: IAssetRepository,
|
||||||
private cryptoRepository: ICryptoRepository,
|
private cryptoRepository: ICryptoRepository,
|
||||||
private moveRepository: IMoveRepository,
|
private moveRepository: IMoveRepository,
|
||||||
private personRepository: IPersonRepository,
|
private personRepository: IPersonRepository,
|
||||||
private storageRepository: IStorageRepository,
|
private storageRepository: IStorageRepository,
|
||||||
systemMetadataRepository: ISystemMetadataRepository,
|
private systemMetadataRepository: ISystemMetadataRepository,
|
||||||
private logger: ILoggerRepository,
|
private logger: ILoggerRepository,
|
||||||
) {
|
) {}
|
||||||
this.configCore = SystemConfigCore.create(systemMetadataRepository, this.logger);
|
|
||||||
}
|
|
||||||
|
|
||||||
static create(
|
static create(
|
||||||
assetRepository: IAssetRepository,
|
assetRepository: IAssetRepository,
|
||||||
@ -248,7 +245,8 @@ export class StorageCore {
|
|||||||
this.logger.warn(`Unable to complete move. File size mismatch: ${newPathSize} !== ${oldPathSize}`);
|
this.logger.warn(`Unable to complete move. File size mismatch: ${newPathSize} !== ${oldPathSize}`);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const config = await this.configCore.getConfig({ withCache: true });
|
const repos = { metadataRepo: this.systemMetadataRepository, logger: this.logger };
|
||||||
|
const config = await getConfig(repos, { withCache: true });
|
||||||
if (assetInfo && config.storageTemplate.hashVerificationEnabled) {
|
if (assetInfo && config.storageTemplate.hashVerificationEnabled) {
|
||||||
const { checksum } = assetInfo;
|
const { checksum } = assetInfo;
|
||||||
const newChecksum = await this.cryptoRepository.hashFile(newPath);
|
const newChecksum = await this.cryptoRepository.hashFile(newPath);
|
||||||
|
@ -1,143 +0,0 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
|
||||||
import AsyncLock from 'async-lock';
|
|
||||||
import { plainToInstance } from 'class-transformer';
|
|
||||||
import { validate } from 'class-validator';
|
|
||||||
import { load as loadYaml } from 'js-yaml';
|
|
||||||
import * as _ from 'lodash';
|
|
||||||
import { SystemConfig, defaults } from 'src/config';
|
|
||||||
import { SystemConfigDto } from 'src/dtos/system-config.dto';
|
|
||||||
import { SystemMetadataKey } from 'src/enum';
|
|
||||||
import { DatabaseLock } from 'src/interfaces/database.interface';
|
|
||||||
import { ILoggerRepository } from 'src/interfaces/logger.interface';
|
|
||||||
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
|
|
||||||
import { getKeysDeep, unsetDeep } from 'src/utils/misc';
|
|
||||||
import { DeepPartial } from 'typeorm';
|
|
||||||
|
|
||||||
export type SystemConfigValidator = (config: SystemConfig, newConfig: SystemConfig) => void | Promise<void>;
|
|
||||||
|
|
||||||
let instance: SystemConfigCore | null;
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class SystemConfigCore {
|
|
||||||
private readonly asyncLock = new AsyncLock();
|
|
||||||
private config: SystemConfig | null = null;
|
|
||||||
private lastUpdated: number | null = null;
|
|
||||||
|
|
||||||
private constructor(
|
|
||||||
private repository: ISystemMetadataRepository,
|
|
||||||
private logger: ILoggerRepository,
|
|
||||||
) {}
|
|
||||||
|
|
||||||
static create(repository: ISystemMetadataRepository, logger: ILoggerRepository) {
|
|
||||||
if (!instance) {
|
|
||||||
instance = new SystemConfigCore(repository, logger);
|
|
||||||
}
|
|
||||||
return instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
static reset() {
|
|
||||||
instance = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
invalidateCache() {
|
|
||||||
this.config = null;
|
|
||||||
this.lastUpdated = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
async getConfig({ withCache }: { withCache: boolean }): Promise<SystemConfig> {
|
|
||||||
if (!withCache || !this.config) {
|
|
||||||
const lastUpdated = this.lastUpdated;
|
|
||||||
await this.asyncLock.acquire(DatabaseLock[DatabaseLock.GetSystemConfig], async () => {
|
|
||||||
if (lastUpdated === this.lastUpdated) {
|
|
||||||
this.config = await this.buildConfig();
|
|
||||||
this.lastUpdated = Date.now();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.config!;
|
|
||||||
}
|
|
||||||
|
|
||||||
async updateConfig(newConfig: SystemConfig): Promise<SystemConfig> {
|
|
||||||
// get the difference between the new config and the default config
|
|
||||||
const partialConfig: DeepPartial<SystemConfig> = {};
|
|
||||||
for (const property of getKeysDeep(defaults)) {
|
|
||||||
const newValue = _.get(newConfig, property);
|
|
||||||
const isEmpty = newValue === undefined || newValue === null || newValue === '';
|
|
||||||
const defaultValue = _.get(defaults, property);
|
|
||||||
const isEqual = newValue === defaultValue || _.isEqual(newValue, defaultValue);
|
|
||||||
|
|
||||||
if (isEmpty || isEqual) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
_.set(partialConfig, property, newValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.repository.set(SystemMetadataKey.SYSTEM_CONFIG, partialConfig);
|
|
||||||
|
|
||||||
return this.getConfig({ withCache: false });
|
|
||||||
}
|
|
||||||
|
|
||||||
isUsingConfigFile() {
|
|
||||||
return !!process.env.IMMICH_CONFIG_FILE;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async buildConfig() {
|
|
||||||
// load partial
|
|
||||||
const partial = this.isUsingConfigFile()
|
|
||||||
? await this.loadFromFile(process.env.IMMICH_CONFIG_FILE as string)
|
|
||||||
: await this.repository.get(SystemMetadataKey.SYSTEM_CONFIG);
|
|
||||||
|
|
||||||
// merge with defaults
|
|
||||||
const config = _.cloneDeep(defaults);
|
|
||||||
for (const property of getKeysDeep(partial)) {
|
|
||||||
_.set(config, property, _.get(partial, property));
|
|
||||||
}
|
|
||||||
|
|
||||||
// check for extra properties
|
|
||||||
const unknownKeys = _.cloneDeep(config);
|
|
||||||
for (const property of getKeysDeep(defaults)) {
|
|
||||||
unsetDeep(unknownKeys, property);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!_.isEmpty(unknownKeys)) {
|
|
||||||
this.logger.warn(`Unknown keys found: ${JSON.stringify(unknownKeys, null, 2)}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// validate full config
|
|
||||||
const errors = await validate(plainToInstance(SystemConfigDto, config));
|
|
||||||
if (errors.length > 0) {
|
|
||||||
if (this.isUsingConfigFile()) {
|
|
||||||
throw new Error(`Invalid value(s) in file: ${errors}`);
|
|
||||||
} else {
|
|
||||||
this.logger.error('Validation error', errors);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (config.server.externalDomain.length > 0) {
|
|
||||||
config.server.externalDomain = new URL(config.server.externalDomain).origin;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!config.ffmpeg.acceptedVideoCodecs.includes(config.ffmpeg.targetVideoCodec)) {
|
|
||||||
config.ffmpeg.acceptedVideoCodecs.push(config.ffmpeg.targetVideoCodec);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!config.ffmpeg.acceptedAudioCodecs.includes(config.ffmpeg.targetAudioCodec)) {
|
|
||||||
config.ffmpeg.acceptedAudioCodecs.push(config.ffmpeg.targetAudioCodec);
|
|
||||||
}
|
|
||||||
|
|
||||||
return config;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async loadFromFile(filepath: string) {
|
|
||||||
try {
|
|
||||||
const file = await this.repository.readFile(filepath);
|
|
||||||
return loadYaml(file.toString()) as unknown;
|
|
||||||
} catch (error: Error | any) {
|
|
||||||
this.logger.error(`Unable to load configuration file: ${filepath}`);
|
|
||||||
this.logger.error(error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,51 +0,0 @@
|
|||||||
import { BadRequestException } from '@nestjs/common';
|
|
||||||
import sanitize from 'sanitize-filename';
|
|
||||||
import { SALT_ROUNDS } from 'src/constants';
|
|
||||||
import { UserEntity } from 'src/entities/user.entity';
|
|
||||||
import { ICryptoRepository } from 'src/interfaces/crypto.interface';
|
|
||||||
import { IUserRepository } from 'src/interfaces/user.interface';
|
|
||||||
|
|
||||||
let instance: UserCore | null;
|
|
||||||
|
|
||||||
export class UserCore {
|
|
||||||
private constructor(
|
|
||||||
private cryptoRepository: ICryptoRepository,
|
|
||||||
private userRepository: IUserRepository,
|
|
||||||
) {}
|
|
||||||
|
|
||||||
static create(cryptoRepository: ICryptoRepository, userRepository: IUserRepository) {
|
|
||||||
if (!instance) {
|
|
||||||
instance = new UserCore(cryptoRepository, userRepository);
|
|
||||||
}
|
|
||||||
|
|
||||||
return instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
static reset() {
|
|
||||||
instance = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
async createUser(dto: Partial<UserEntity> & { email: string }): Promise<UserEntity> {
|
|
||||||
const user = await this.userRepository.getByEmail(dto.email);
|
|
||||||
if (user) {
|
|
||||||
throw new BadRequestException('User exists');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!dto.isAdmin) {
|
|
||||||
const localAdmin = await this.userRepository.getAdmin();
|
|
||||||
if (!localAdmin) {
|
|
||||||
throw new BadRequestException('The first registered account must the administrator.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const payload: Partial<UserEntity> = { ...dto };
|
|
||||||
if (payload.password) {
|
|
||||||
payload.password = await this.cryptoRepository.hashBcrypt(payload.password, SALT_ROUNDS);
|
|
||||||
}
|
|
||||||
if (payload.storageLabel) {
|
|
||||||
payload.storageLabel = sanitize(payload.storageLabel.replaceAll('.', ''));
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.userRepository.create(payload);
|
|
||||||
}
|
|
||||||
}
|
|
@ -30,6 +30,11 @@ export class ServerAboutResponseDto {
|
|||||||
exiftool?: string;
|
exiftool?: string;
|
||||||
|
|
||||||
licensed!: boolean;
|
licensed!: boolean;
|
||||||
|
|
||||||
|
thirdPartySourceUrl?: string;
|
||||||
|
thirdPartyBugFeatureUrl?: string;
|
||||||
|
thirdPartyDocumentationUrl?: string;
|
||||||
|
thirdPartySupportUrl?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ServerStorageResponseDto {
|
export class ServerStorageResponseDto {
|
||||||
|
@ -62,36 +62,20 @@ export type EmitHandler<T extends EmitEvent> = (...args: ArgsOf<T>) => Promise<v
|
|||||||
export type ArgOf<T extends EmitEvent> = EventMap[T][0];
|
export type ArgOf<T extends EmitEvent> = EventMap[T][0];
|
||||||
export type ArgsOf<T extends EmitEvent> = EventMap[T];
|
export type ArgsOf<T extends EmitEvent> = EventMap[T];
|
||||||
|
|
||||||
export enum ClientEvent {
|
|
||||||
UPLOAD_SUCCESS = 'on_upload_success',
|
|
||||||
USER_DELETE = 'on_user_delete',
|
|
||||||
ASSET_DELETE = 'on_asset_delete',
|
|
||||||
ASSET_TRASH = 'on_asset_trash',
|
|
||||||
ASSET_UPDATE = 'on_asset_update',
|
|
||||||
ASSET_HIDDEN = 'on_asset_hidden',
|
|
||||||
ASSET_RESTORE = 'on_asset_restore',
|
|
||||||
ASSET_STACK_UPDATE = 'on_asset_stack_update',
|
|
||||||
PERSON_THUMBNAIL = 'on_person_thumbnail',
|
|
||||||
SERVER_VERSION = 'on_server_version',
|
|
||||||
CONFIG_UPDATE = 'on_config_update',
|
|
||||||
NEW_RELEASE = 'on_new_release',
|
|
||||||
SESSION_DELETE = 'on_session_delete',
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ClientEventMap {
|
export interface ClientEventMap {
|
||||||
[ClientEvent.UPLOAD_SUCCESS]: AssetResponseDto;
|
on_upload_success: [AssetResponseDto];
|
||||||
[ClientEvent.USER_DELETE]: string;
|
on_user_delete: [string];
|
||||||
[ClientEvent.ASSET_DELETE]: string;
|
on_asset_delete: [string];
|
||||||
[ClientEvent.ASSET_TRASH]: string[];
|
on_asset_trash: [string[]];
|
||||||
[ClientEvent.ASSET_UPDATE]: AssetResponseDto;
|
on_asset_update: [AssetResponseDto];
|
||||||
[ClientEvent.ASSET_HIDDEN]: string;
|
on_asset_hidden: [string];
|
||||||
[ClientEvent.ASSET_RESTORE]: string[];
|
on_asset_restore: [string[]];
|
||||||
[ClientEvent.ASSET_STACK_UPDATE]: string[];
|
on_asset_stack_update: string[];
|
||||||
[ClientEvent.PERSON_THUMBNAIL]: string;
|
on_person_thumbnail: [string];
|
||||||
[ClientEvent.SERVER_VERSION]: ServerVersionResponseDto;
|
on_server_version: [ServerVersionResponseDto];
|
||||||
[ClientEvent.CONFIG_UPDATE]: Record<string, never>;
|
on_config_update: [];
|
||||||
[ClientEvent.NEW_RELEASE]: ReleaseNotification;
|
on_new_release: [ReleaseNotification];
|
||||||
[ClientEvent.SESSION_DELETE]: string;
|
on_session_delete: [string];
|
||||||
}
|
}
|
||||||
|
|
||||||
export type EventItem<T extends EmitEvent> = {
|
export type EventItem<T extends EmitEvent> = {
|
||||||
@ -107,11 +91,11 @@ export interface IEventRepository {
|
|||||||
/**
|
/**
|
||||||
* Send to connected clients for a specific user
|
* Send to connected clients for a specific user
|
||||||
*/
|
*/
|
||||||
clientSend<E extends keyof ClientEventMap>(event: E, room: string, data: ClientEventMap[E]): void;
|
clientSend<E extends keyof ClientEventMap>(event: E, room: string, ...data: ClientEventMap[E]): void;
|
||||||
/**
|
/**
|
||||||
* Send to all connected clients
|
* Send to all connected clients
|
||||||
*/
|
*/
|
||||||
clientBroadcast<E extends keyof ClientEventMap>(event: E, data: ClientEventMap[E]): void;
|
clientBroadcast<E extends keyof ClientEventMap>(event: E, ...data: ClientEventMap[E]): void;
|
||||||
/**
|
/**
|
||||||
* Send to all connected servers
|
* Send to all connected servers
|
||||||
*/
|
*/
|
||||||
|
16
server/src/migrations/1727781844613-IsOfflineSetDeletedAt.ts
Normal file
16
server/src/migrations/1727781844613-IsOfflineSetDeletedAt.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||||
|
|
||||||
|
export class IsOfflineSetDeletedAt1727781844613 implements MigrationInterface {
|
||||||
|
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(
|
||||||
|
`UPDATE assets SET "deletedAt" = now() WHERE "isOffline" = true AND "deletedAt" IS NULL`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(
|
||||||
|
`UPDATE assets SET "deletedAt" = null WHERE "isOffline" = true`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -106,12 +106,12 @@ export class EventRepository implements OnGatewayConnection, OnGatewayDisconnect
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
clientSend<E extends keyof ClientEventMap>(event: E, room: string, data: ClientEventMap[E]) {
|
clientSend<T extends keyof ClientEventMap>(event: T, room: string, ...data: ClientEventMap[T]) {
|
||||||
this.server?.to(room).emit(event, data);
|
this.server?.to(room).emit(event, ...data);
|
||||||
}
|
}
|
||||||
|
|
||||||
clientBroadcast<E extends keyof ClientEventMap>(event: E, data: ClientEventMap[E]) {
|
clientBroadcast<T extends keyof ClientEventMap>(event: T, ...data: ClientEventMap[T]) {
|
||||||
this.server?.emit(event, data);
|
this.server?.emit(event, ...data);
|
||||||
}
|
}
|
||||||
|
|
||||||
serverSend<T extends ServerEvents>(event: T, ...args: ArgsOf<T>): void {
|
serverSend<T extends ServerEvents>(event: T, ...args: ArgsOf<T>): void {
|
||||||
|
@ -15,6 +15,7 @@ export class MetadataRepository implements IMetadataRepository {
|
|||||||
defaultVideosToUTC: true,
|
defaultVideosToUTC: true,
|
||||||
backfillTimezones: true,
|
backfillTimezones: true,
|
||||||
inferTimezoneFromDatestamps: true,
|
inferTimezoneFromDatestamps: true,
|
||||||
|
inferTimezoneFromTimeStamp: true,
|
||||||
useMWG: true,
|
useMWG: true,
|
||||||
numericTags: [...DefaultReadTaskOptions.numericTags, 'FocalLength'],
|
numericTags: [...DefaultReadTaskOptions.numericTags, 'FocalLength'],
|
||||||
/* eslint unicorn/no-array-callback-reference: off, unicorn/no-array-method-this-argument: off */
|
/* eslint unicorn/no-array-callback-reference: off, unicorn/no-array-method-this-argument: off */
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import { BadRequestException, Inject } from '@nestjs/common';
|
import { BadRequestException, Inject } from '@nestjs/common';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import { DateTime, Duration } from 'luxon';
|
import { DateTime, Duration } from 'luxon';
|
||||||
import { SystemConfigCore } from 'src/cores/system-config.core';
|
|
||||||
import {
|
import {
|
||||||
AssetResponseDto,
|
AssetResponseDto,
|
||||||
MemoryLaneResponseDto,
|
MemoryLaneResponseDto,
|
||||||
@ -38,13 +37,12 @@ import { IPartnerRepository } from 'src/interfaces/partner.interface';
|
|||||||
import { IStackRepository } from 'src/interfaces/stack.interface';
|
import { IStackRepository } from 'src/interfaces/stack.interface';
|
||||||
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
|
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
|
||||||
import { IUserRepository } from 'src/interfaces/user.interface';
|
import { IUserRepository } from 'src/interfaces/user.interface';
|
||||||
|
import { BaseService } from 'src/services/base.service';
|
||||||
import { requireAccess } from 'src/utils/access';
|
import { requireAccess } from 'src/utils/access';
|
||||||
import { getAssetFiles, getMyPartnerIds, onAfterUnlink, onBeforeLink, onBeforeUnlink } from 'src/utils/asset.util';
|
import { getAssetFiles, getMyPartnerIds, onAfterUnlink, onBeforeLink, onBeforeUnlink } from 'src/utils/asset.util';
|
||||||
import { usePagination } from 'src/utils/pagination';
|
import { usePagination } from 'src/utils/pagination';
|
||||||
|
|
||||||
export class AssetService {
|
export class AssetService extends BaseService {
|
||||||
private configCore: SystemConfigCore;
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(IAccessRepository) private access: IAccessRepository,
|
@Inject(IAccessRepository) private access: IAccessRepository,
|
||||||
@Inject(IAssetRepository) private assetRepository: IAssetRepository,
|
@Inject(IAssetRepository) private assetRepository: IAssetRepository,
|
||||||
@ -54,10 +52,10 @@ export class AssetService {
|
|||||||
@Inject(IEventRepository) private eventRepository: IEventRepository,
|
@Inject(IEventRepository) private eventRepository: IEventRepository,
|
||||||
@Inject(IPartnerRepository) private partnerRepository: IPartnerRepository,
|
@Inject(IPartnerRepository) private partnerRepository: IPartnerRepository,
|
||||||
@Inject(IStackRepository) private stackRepository: IStackRepository,
|
@Inject(IStackRepository) private stackRepository: IStackRepository,
|
||||||
@Inject(ILoggerRepository) private logger: ILoggerRepository,
|
@Inject(ILoggerRepository) logger: ILoggerRepository,
|
||||||
) {
|
) {
|
||||||
|
super(systemMetadataRepository, logger);
|
||||||
this.logger.setContext(AssetService.name);
|
this.logger.setContext(AssetService.name);
|
||||||
this.configCore = SystemConfigCore.create(systemMetadataRepository, this.logger);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async getMemoryLane(auth: AuthDto, dto: MemoryLaneDto): Promise<MemoryLaneResponseDto[]> {
|
async getMemoryLane(auth: AuthDto, dto: MemoryLaneDto): Promise<MemoryLaneResponseDto[]> {
|
||||||
@ -214,7 +212,7 @@ export class AssetService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async handleAssetDeletionCheck(): Promise<JobStatus> {
|
async handleAssetDeletionCheck(): Promise<JobStatus> {
|
||||||
const config = await this.configCore.getConfig({ withCache: false });
|
const config = await this.getConfig({ withCache: false });
|
||||||
const trashedDays = config.trash.enabled ? config.trash.days : 0;
|
const trashedDays = config.trash.enabled ? config.trash.days : 0;
|
||||||
const trashedBefore = DateTime.now()
|
const trashedBefore = DateTime.now()
|
||||||
.minus(Duration.fromObject({ days: trashedDays }))
|
.minus(Duration.fromObject({ days: trashedDays }))
|
||||||
|
@ -13,8 +13,6 @@ import { IncomingHttpHeaders } from 'node:http';
|
|||||||
import { Issuer, UserinfoResponse, custom, generators } from 'openid-client';
|
import { Issuer, UserinfoResponse, custom, generators } from 'openid-client';
|
||||||
import { SystemConfig } from 'src/config';
|
import { SystemConfig } from 'src/config';
|
||||||
import { LOGIN_URL, MOBILE_REDIRECT, SALT_ROUNDS } from 'src/constants';
|
import { LOGIN_URL, MOBILE_REDIRECT, SALT_ROUNDS } from 'src/constants';
|
||||||
import { SystemConfigCore } from 'src/cores/system-config.core';
|
|
||||||
import { UserCore } from 'src/cores/user.core';
|
|
||||||
import {
|
import {
|
||||||
AuthDto,
|
AuthDto,
|
||||||
ChangePasswordDto,
|
ChangePasswordDto,
|
||||||
@ -40,8 +38,10 @@ import { ISessionRepository } from 'src/interfaces/session.interface';
|
|||||||
import { ISharedLinkRepository } from 'src/interfaces/shared-link.interface';
|
import { ISharedLinkRepository } from 'src/interfaces/shared-link.interface';
|
||||||
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
|
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
|
||||||
import { IUserRepository } from 'src/interfaces/user.interface';
|
import { IUserRepository } from 'src/interfaces/user.interface';
|
||||||
|
import { BaseService } from 'src/services/base.service';
|
||||||
import { isGranted } from 'src/utils/access';
|
import { isGranted } from 'src/utils/access';
|
||||||
import { HumanReadableSize } from 'src/utils/bytes';
|
import { HumanReadableSize } from 'src/utils/bytes';
|
||||||
|
import { createUser } from 'src/utils/user';
|
||||||
|
|
||||||
export interface LoginDetails {
|
export interface LoginDetails {
|
||||||
isSecure: boolean;
|
isSecure: boolean;
|
||||||
@ -70,29 +70,25 @@ export type ValidateRequest = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class AuthService {
|
export class AuthService extends BaseService {
|
||||||
private configCore: SystemConfigCore;
|
|
||||||
private userCore: UserCore;
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(ICryptoRepository) private cryptoRepository: ICryptoRepository,
|
@Inject(ICryptoRepository) private cryptoRepository: ICryptoRepository,
|
||||||
@Inject(IEventRepository) private eventRepository: IEventRepository,
|
@Inject(IEventRepository) private eventRepository: IEventRepository,
|
||||||
@Inject(ISystemMetadataRepository) systemMetadataRepository: ISystemMetadataRepository,
|
@Inject(ISystemMetadataRepository) systemMetadataRepository: ISystemMetadataRepository,
|
||||||
@Inject(ILoggerRepository) private logger: ILoggerRepository,
|
@Inject(ILoggerRepository) logger: ILoggerRepository,
|
||||||
@Inject(IUserRepository) private userRepository: IUserRepository,
|
@Inject(IUserRepository) private userRepository: IUserRepository,
|
||||||
@Inject(ISessionRepository) private sessionRepository: ISessionRepository,
|
@Inject(ISessionRepository) private sessionRepository: ISessionRepository,
|
||||||
@Inject(ISharedLinkRepository) private sharedLinkRepository: ISharedLinkRepository,
|
@Inject(ISharedLinkRepository) private sharedLinkRepository: ISharedLinkRepository,
|
||||||
@Inject(IKeyRepository) private keyRepository: IKeyRepository,
|
@Inject(IKeyRepository) private keyRepository: IKeyRepository,
|
||||||
) {
|
) {
|
||||||
|
super(systemMetadataRepository, logger);
|
||||||
this.logger.setContext(AuthService.name);
|
this.logger.setContext(AuthService.name);
|
||||||
this.configCore = SystemConfigCore.create(systemMetadataRepository, logger);
|
|
||||||
this.userCore = UserCore.create(cryptoRepository, userRepository);
|
|
||||||
|
|
||||||
custom.setHttpOptionsDefaults({ timeout: 30_000 });
|
custom.setHttpOptionsDefaults({ timeout: 30_000 });
|
||||||
}
|
}
|
||||||
|
|
||||||
async login(dto: LoginCredentialDto, details: LoginDetails) {
|
async login(dto: LoginCredentialDto, details: LoginDetails) {
|
||||||
const config = await this.configCore.getConfig({ withCache: false });
|
const config = await this.getConfig({ withCache: false });
|
||||||
if (!config.passwordLogin.enabled) {
|
if (!config.passwordLogin.enabled) {
|
||||||
throw new UnauthorizedException('Password login has been disabled');
|
throw new UnauthorizedException('Password login has been disabled');
|
||||||
}
|
}
|
||||||
@ -150,13 +146,16 @@ export class AuthService {
|
|||||||
throw new BadRequestException('The server already has an admin');
|
throw new BadRequestException('The server already has an admin');
|
||||||
}
|
}
|
||||||
|
|
||||||
const admin = await this.userCore.createUser({
|
const admin = await createUser(
|
||||||
|
{ userRepo: this.userRepository, cryptoRepo: this.cryptoRepository },
|
||||||
|
{
|
||||||
isAdmin: true,
|
isAdmin: true,
|
||||||
email: dto.email,
|
email: dto.email,
|
||||||
name: dto.name,
|
name: dto.name,
|
||||||
password: dto.password,
|
password: dto.password,
|
||||||
storageLabel: 'admin',
|
storageLabel: 'admin',
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
return mapUserAdmin(admin);
|
return mapUserAdmin(admin);
|
||||||
}
|
}
|
||||||
@ -211,7 +210,7 @@ export class AuthService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async authorize(dto: OAuthConfigDto): Promise<OAuthAuthorizeResponseDto> {
|
async authorize(dto: OAuthConfigDto): Promise<OAuthAuthorizeResponseDto> {
|
||||||
const config = await this.configCore.getConfig({ withCache: false });
|
const config = await this.getConfig({ withCache: false });
|
||||||
if (!config.oauth.enabled) {
|
if (!config.oauth.enabled) {
|
||||||
throw new BadRequestException('OAuth is not enabled');
|
throw new BadRequestException('OAuth is not enabled');
|
||||||
}
|
}
|
||||||
@ -227,7 +226,7 @@ export class AuthService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async callback(dto: OAuthCallbackDto, loginDetails: LoginDetails) {
|
async callback(dto: OAuthCallbackDto, loginDetails: LoginDetails) {
|
||||||
const config = await this.configCore.getConfig({ withCache: false });
|
const config = await this.getConfig({ withCache: false });
|
||||||
const profile = await this.getOAuthProfile(config, dto.url);
|
const profile = await this.getOAuthProfile(config, dto.url);
|
||||||
const { autoRegister, defaultStorageQuota, storageLabelClaim, storageQuotaClaim } = config.oauth;
|
const { autoRegister, defaultStorageQuota, storageLabelClaim, storageQuotaClaim } = config.oauth;
|
||||||
this.logger.debug(`Logging in with OAuth: ${JSON.stringify(profile)}`);
|
this.logger.debug(`Logging in with OAuth: ${JSON.stringify(profile)}`);
|
||||||
@ -271,20 +270,23 @@ export class AuthService {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const userName = profile.name ?? `${profile.given_name || ''} ${profile.family_name || ''}`;
|
const userName = profile.name ?? `${profile.given_name || ''} ${profile.family_name || ''}`;
|
||||||
user = await this.userCore.createUser({
|
user = await createUser(
|
||||||
|
{ userRepo: this.userRepository, cryptoRepo: this.cryptoRepository },
|
||||||
|
{
|
||||||
name: userName,
|
name: userName,
|
||||||
email: profile.email,
|
email: profile.email,
|
||||||
oauthId: profile.sub,
|
oauthId: profile.sub,
|
||||||
quotaSizeInBytes: storageQuota * HumanReadableSize.GiB || null,
|
quotaSizeInBytes: storageQuota * HumanReadableSize.GiB || null,
|
||||||
storageLabel: storageLabel || null,
|
storageLabel: storageLabel || null,
|
||||||
});
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.createLoginResponse(user, loginDetails);
|
return this.createLoginResponse(user, loginDetails);
|
||||||
}
|
}
|
||||||
|
|
||||||
async link(auth: AuthDto, dto: OAuthCallbackDto): Promise<UserAdminResponseDto> {
|
async link(auth: AuthDto, dto: OAuthCallbackDto): Promise<UserAdminResponseDto> {
|
||||||
const config = await this.configCore.getConfig({ withCache: false });
|
const config = await this.getConfig({ withCache: false });
|
||||||
const { sub: oauthId } = await this.getOAuthProfile(config, dto.url);
|
const { sub: oauthId } = await this.getOAuthProfile(config, dto.url);
|
||||||
const duplicate = await this.userRepository.getByOAuthId(oauthId);
|
const duplicate = await this.userRepository.getByOAuthId(oauthId);
|
||||||
if (duplicate && duplicate.id !== auth.user.id) {
|
if (duplicate && duplicate.id !== auth.user.id) {
|
||||||
@ -306,7 +308,7 @@ export class AuthService {
|
|||||||
return LOGIN_URL;
|
return LOGIN_URL;
|
||||||
}
|
}
|
||||||
|
|
||||||
const config = await this.configCore.getConfig({ withCache: false });
|
const config = await this.getConfig({ withCache: false });
|
||||||
if (!config.oauth.enabled) {
|
if (!config.oauth.enabled) {
|
||||||
return LOGIN_URL;
|
return LOGIN_URL;
|
||||||
}
|
}
|
||||||
|
32
server/src/services/base.service.ts
Normal file
32
server/src/services/base.service.ts
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import { Inject } from '@nestjs/common';
|
||||||
|
import { SystemConfig } from 'src/config';
|
||||||
|
import { ILoggerRepository } from 'src/interfaces/logger.interface';
|
||||||
|
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
|
||||||
|
import { getConfig, updateConfig } from 'src/utils/config';
|
||||||
|
|
||||||
|
export class BaseService {
|
||||||
|
constructor(
|
||||||
|
@Inject(ISystemMetadataRepository) protected systemMetadataRepository: ISystemMetadataRepository,
|
||||||
|
@Inject(ILoggerRepository) protected logger: ILoggerRepository,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
getConfig(options: { withCache: boolean }) {
|
||||||
|
return getConfig(
|
||||||
|
{
|
||||||
|
metadataRepo: this.systemMetadataRepository,
|
||||||
|
logger: this.logger,
|
||||||
|
},
|
||||||
|
options,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateConfig(newConfig: SystemConfig) {
|
||||||
|
return updateConfig(
|
||||||
|
{
|
||||||
|
metadataRepo: this.systemMetadataRepository,
|
||||||
|
logger: this.logger,
|
||||||
|
},
|
||||||
|
newConfig,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -1,24 +1,22 @@
|
|||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import { SALT_ROUNDS } from 'src/constants';
|
import { SALT_ROUNDS } from 'src/constants';
|
||||||
import { SystemConfigCore } from 'src/cores/system-config.core';
|
|
||||||
import { UserAdminResponseDto, mapUserAdmin } from 'src/dtos/user.dto';
|
import { UserAdminResponseDto, mapUserAdmin } from 'src/dtos/user.dto';
|
||||||
import { ICryptoRepository } from 'src/interfaces/crypto.interface';
|
import { ICryptoRepository } from 'src/interfaces/crypto.interface';
|
||||||
import { ILoggerRepository } from 'src/interfaces/logger.interface';
|
import { ILoggerRepository } from 'src/interfaces/logger.interface';
|
||||||
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
|
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
|
||||||
import { IUserRepository } from 'src/interfaces/user.interface';
|
import { IUserRepository } from 'src/interfaces/user.interface';
|
||||||
|
import { BaseService } from 'src/services/base.service';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class CliService {
|
export class CliService extends BaseService {
|
||||||
private configCore: SystemConfigCore;
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(ICryptoRepository) private cryptoRepository: ICryptoRepository,
|
@Inject(ICryptoRepository) private cryptoRepository: ICryptoRepository,
|
||||||
@Inject(ISystemMetadataRepository) systemMetadataRepository: ISystemMetadataRepository,
|
@Inject(ISystemMetadataRepository) systemMetadataRepository: ISystemMetadataRepository,
|
||||||
@Inject(IUserRepository) private userRepository: IUserRepository,
|
@Inject(IUserRepository) private userRepository: IUserRepository,
|
||||||
@Inject(ILoggerRepository) private logger: ILoggerRepository,
|
@Inject(ILoggerRepository) logger: ILoggerRepository,
|
||||||
) {
|
) {
|
||||||
|
super(systemMetadataRepository, logger);
|
||||||
this.logger.setContext(CliService.name);
|
this.logger.setContext(CliService.name);
|
||||||
this.configCore = SystemConfigCore.create(systemMetadataRepository, this.logger);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async listUsers(): Promise<UserAdminResponseDto[]> {
|
async listUsers(): Promise<UserAdminResponseDto[]> {
|
||||||
@ -42,26 +40,26 @@ export class CliService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async disablePasswordLogin(): Promise<void> {
|
async disablePasswordLogin(): Promise<void> {
|
||||||
const config = await this.configCore.getConfig({ withCache: false });
|
const config = await this.getConfig({ withCache: false });
|
||||||
config.passwordLogin.enabled = false;
|
config.passwordLogin.enabled = false;
|
||||||
await this.configCore.updateConfig(config);
|
await this.updateConfig(config);
|
||||||
}
|
}
|
||||||
|
|
||||||
async enablePasswordLogin(): Promise<void> {
|
async enablePasswordLogin(): Promise<void> {
|
||||||
const config = await this.configCore.getConfig({ withCache: false });
|
const config = await this.getConfig({ withCache: false });
|
||||||
config.passwordLogin.enabled = true;
|
config.passwordLogin.enabled = true;
|
||||||
await this.configCore.updateConfig(config);
|
await this.updateConfig(config);
|
||||||
}
|
}
|
||||||
|
|
||||||
async disableOAuthLogin(): Promise<void> {
|
async disableOAuthLogin(): Promise<void> {
|
||||||
const config = await this.configCore.getConfig({ withCache: false });
|
const config = await this.getConfig({ withCache: false });
|
||||||
config.oauth.enabled = false;
|
config.oauth.enabled = false;
|
||||||
await this.configCore.updateConfig(config);
|
await this.updateConfig(config);
|
||||||
}
|
}
|
||||||
|
|
||||||
async enableOAuthLogin(): Promise<void> {
|
async enableOAuthLogin(): Promise<void> {
|
||||||
const config = await this.configCore.getConfig({ withCache: false });
|
const config = await this.getConfig({ withCache: false });
|
||||||
config.oauth.enabled = true;
|
config.oauth.enabled = true;
|
||||||
await this.configCore.updateConfig(config);
|
await this.updateConfig(config);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import { SystemConfigCore } from 'src/cores/system-config.core';
|
|
||||||
import { mapAsset } from 'src/dtos/asset-response.dto';
|
import { mapAsset } from 'src/dtos/asset-response.dto';
|
||||||
import { AuthDto } from 'src/dtos/auth.dto';
|
import { AuthDto } from 'src/dtos/auth.dto';
|
||||||
import { DuplicateResponseDto, mapDuplicateResponse } from 'src/dtos/duplicate.dto';
|
import { DuplicateResponseDto, mapDuplicateResponse } from 'src/dtos/duplicate.dto';
|
||||||
@ -17,24 +16,23 @@ import {
|
|||||||
import { ILoggerRepository } from 'src/interfaces/logger.interface';
|
import { ILoggerRepository } from 'src/interfaces/logger.interface';
|
||||||
import { AssetDuplicateResult, ISearchRepository } from 'src/interfaces/search.interface';
|
import { AssetDuplicateResult, ISearchRepository } from 'src/interfaces/search.interface';
|
||||||
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
|
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
|
||||||
|
import { BaseService } from 'src/services/base.service';
|
||||||
import { getAssetFiles } from 'src/utils/asset.util';
|
import { getAssetFiles } from 'src/utils/asset.util';
|
||||||
import { isDuplicateDetectionEnabled } from 'src/utils/misc';
|
import { isDuplicateDetectionEnabled } from 'src/utils/misc';
|
||||||
import { usePagination } from 'src/utils/pagination';
|
import { usePagination } from 'src/utils/pagination';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class DuplicateService {
|
export class DuplicateService extends BaseService {
|
||||||
private configCore: SystemConfigCore;
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(ISystemMetadataRepository) systemMetadataRepository: ISystemMetadataRepository,
|
@Inject(ISystemMetadataRepository) systemMetadataRepository: ISystemMetadataRepository,
|
||||||
@Inject(ISearchRepository) private searchRepository: ISearchRepository,
|
@Inject(ISearchRepository) private searchRepository: ISearchRepository,
|
||||||
@Inject(IAssetRepository) private assetRepository: IAssetRepository,
|
@Inject(IAssetRepository) private assetRepository: IAssetRepository,
|
||||||
@Inject(ILoggerRepository) private logger: ILoggerRepository,
|
@Inject(ILoggerRepository) logger: ILoggerRepository,
|
||||||
@Inject(ICryptoRepository) private cryptoRepository: ICryptoRepository,
|
@Inject(ICryptoRepository) private cryptoRepository: ICryptoRepository,
|
||||||
@Inject(IJobRepository) private jobRepository: IJobRepository,
|
@Inject(IJobRepository) private jobRepository: IJobRepository,
|
||||||
) {
|
) {
|
||||||
|
super(systemMetadataRepository, logger);
|
||||||
this.logger.setContext(DuplicateService.name);
|
this.logger.setContext(DuplicateService.name);
|
||||||
this.configCore = SystemConfigCore.create(systemMetadataRepository, logger);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async getDuplicates(auth: AuthDto): Promise<DuplicateResponseDto[]> {
|
async getDuplicates(auth: AuthDto): Promise<DuplicateResponseDto[]> {
|
||||||
@ -44,7 +42,7 @@ export class DuplicateService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async handleQueueSearchDuplicates({ force }: IBaseJob): Promise<JobStatus> {
|
async handleQueueSearchDuplicates({ force }: IBaseJob): Promise<JobStatus> {
|
||||||
const { machineLearning } = await this.configCore.getConfig({ withCache: false });
|
const { machineLearning } = await this.getConfig({ withCache: false });
|
||||||
if (!isDuplicateDetectionEnabled(machineLearning)) {
|
if (!isDuplicateDetectionEnabled(machineLearning)) {
|
||||||
return JobStatus.SKIPPED;
|
return JobStatus.SKIPPED;
|
||||||
}
|
}
|
||||||
@ -65,7 +63,7 @@ export class DuplicateService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async handleSearchDuplicates({ id }: IEntityJob): Promise<JobStatus> {
|
async handleSearchDuplicates({ id }: IEntityJob): Promise<JobStatus> {
|
||||||
const { machineLearning } = await this.configCore.getConfig({ withCache: true });
|
const { machineLearning } = await this.getConfig({ withCache: true });
|
||||||
if (!isDuplicateDetectionEnabled(machineLearning)) {
|
if (!isDuplicateDetectionEnabled(machineLearning)) {
|
||||||
return JobStatus.SKIPPED;
|
return JobStatus.SKIPPED;
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,11 @@
|
|||||||
import { BadRequestException, Inject, Injectable } from '@nestjs/common';
|
import { BadRequestException, Inject, Injectable } from '@nestjs/common';
|
||||||
import { snakeCase } from 'lodash';
|
import { snakeCase } from 'lodash';
|
||||||
import { SystemConfigCore } from 'src/cores/system-config.core';
|
|
||||||
import { OnEvent } from 'src/decorators';
|
import { OnEvent } from 'src/decorators';
|
||||||
import { mapAsset } from 'src/dtos/asset-response.dto';
|
import { mapAsset } from 'src/dtos/asset-response.dto';
|
||||||
import { AllJobStatusResponseDto, JobCommandDto, JobCreateDto, JobStatusDto } from 'src/dtos/job.dto';
|
import { AllJobStatusResponseDto, JobCommandDto, JobCreateDto, JobStatusDto } from 'src/dtos/job.dto';
|
||||||
import { AssetType, ManualJobName } from 'src/enum';
|
import { AssetType, ManualJobName } from 'src/enum';
|
||||||
import { IAssetRepository } from 'src/interfaces/asset.interface';
|
import { IAssetRepository } from 'src/interfaces/asset.interface';
|
||||||
import { ArgOf, ClientEvent, IEventRepository } from 'src/interfaces/event.interface';
|
import { ArgOf, IEventRepository } from 'src/interfaces/event.interface';
|
||||||
import {
|
import {
|
||||||
ConcurrentQueueName,
|
ConcurrentQueueName,
|
||||||
IJobRepository,
|
IJobRepository,
|
||||||
@ -22,6 +21,7 @@ import { ILoggerRepository } from 'src/interfaces/logger.interface';
|
|||||||
import { IMetricRepository } from 'src/interfaces/metric.interface';
|
import { IMetricRepository } from 'src/interfaces/metric.interface';
|
||||||
import { IPersonRepository } from 'src/interfaces/person.interface';
|
import { IPersonRepository } from 'src/interfaces/person.interface';
|
||||||
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
|
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
|
||||||
|
import { BaseService } from 'src/services/base.service';
|
||||||
|
|
||||||
const asJobItem = (dto: JobCreateDto): JobItem => {
|
const asJobItem = (dto: JobCreateDto): JobItem => {
|
||||||
switch (dto.name) {
|
switch (dto.name) {
|
||||||
@ -44,8 +44,7 @@ const asJobItem = (dto: JobCreateDto): JobItem => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class JobService {
|
export class JobService extends BaseService {
|
||||||
private configCore: SystemConfigCore;
|
|
||||||
private isMicroservices = false;
|
private isMicroservices = false;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@ -55,10 +54,10 @@ export class JobService {
|
|||||||
@Inject(ISystemMetadataRepository) systemMetadataRepository: ISystemMetadataRepository,
|
@Inject(ISystemMetadataRepository) systemMetadataRepository: ISystemMetadataRepository,
|
||||||
@Inject(IPersonRepository) private personRepository: IPersonRepository,
|
@Inject(IPersonRepository) private personRepository: IPersonRepository,
|
||||||
@Inject(IMetricRepository) private metricRepository: IMetricRepository,
|
@Inject(IMetricRepository) private metricRepository: IMetricRepository,
|
||||||
@Inject(ILoggerRepository) private logger: ILoggerRepository,
|
@Inject(ILoggerRepository) logger: ILoggerRepository,
|
||||||
) {
|
) {
|
||||||
|
super(systemMetadataRepository, logger);
|
||||||
this.logger.setContext(JobService.name);
|
this.logger.setContext(JobService.name);
|
||||||
this.configCore = SystemConfigCore.create(systemMetadataRepository, logger);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@OnEvent({ name: 'app.bootstrap' })
|
@OnEvent({ name: 'app.bootstrap' })
|
||||||
@ -198,7 +197,7 @@ export class JobService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async init(jobHandlers: Record<JobName, JobHandler>) {
|
async init(jobHandlers: Record<JobName, JobHandler>) {
|
||||||
const config = await this.configCore.getConfig({ withCache: false });
|
const config = await this.getConfig({ withCache: false });
|
||||||
for (const queueName of Object.values(QueueName)) {
|
for (const queueName of Object.values(QueueName)) {
|
||||||
let concurrency = 1;
|
let concurrency = 1;
|
||||||
|
|
||||||
@ -279,7 +278,7 @@ export class JobService {
|
|||||||
if (item.data.source === 'sidecar-write') {
|
if (item.data.source === 'sidecar-write') {
|
||||||
const [asset] = await this.assetRepository.getByIdsWithAllRelations([item.data.id]);
|
const [asset] = await this.assetRepository.getByIdsWithAllRelations([item.data.id]);
|
||||||
if (asset) {
|
if (asset) {
|
||||||
this.eventRepository.clientSend(ClientEvent.ASSET_UPDATE, asset.ownerId, mapAsset(asset));
|
this.eventRepository.clientSend('on_asset_update', asset.ownerId, mapAsset(asset));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
await this.jobRepository.queue({ name: JobName.LINK_LIVE_PHOTOS, data: item.data });
|
await this.jobRepository.queue({ name: JobName.LINK_LIVE_PHOTOS, data: item.data });
|
||||||
@ -302,7 +301,7 @@ export class JobService {
|
|||||||
const { id } = item.data;
|
const { id } = item.data;
|
||||||
const person = await this.personRepository.getById(id);
|
const person = await this.personRepository.getById(id);
|
||||||
if (person) {
|
if (person) {
|
||||||
this.eventRepository.clientSend(ClientEvent.PERSON_THUMBNAIL, person.ownerId, person.id);
|
this.eventRepository.clientSend('on_person_thumbnail', person.ownerId, person.id);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -331,7 +330,7 @@ export class JobService {
|
|||||||
|
|
||||||
await this.jobRepository.queueAll(jobs);
|
await this.jobRepository.queueAll(jobs);
|
||||||
if (asset.isVisible) {
|
if (asset.isVisible) {
|
||||||
this.eventRepository.clientSend(ClientEvent.UPLOAD_SUCCESS, asset.ownerId, mapAsset(asset));
|
this.eventRepository.clientSend('on_upload_success', asset.ownerId, mapAsset(asset));
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
@ -345,7 +344,7 @@ export class JobService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
case JobName.USER_DELETION: {
|
case JobName.USER_DELETION: {
|
||||||
this.eventRepository.clientBroadcast(ClientEvent.USER_DELETE, item.data.id);
|
this.eventRepository.clientBroadcast('on_user_delete', item.data.id);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,6 @@ import { R_OK } from 'node:constants';
|
|||||||
import path, { basename, parse } from 'node:path';
|
import path, { basename, parse } from 'node:path';
|
||||||
import picomatch from 'picomatch';
|
import picomatch from 'picomatch';
|
||||||
import { StorageCore } from 'src/cores/storage.core';
|
import { StorageCore } from 'src/cores/storage.core';
|
||||||
import { SystemConfigCore } from 'src/cores/system-config.core';
|
|
||||||
import { OnEvent } from 'src/decorators';
|
import { OnEvent } from 'src/decorators';
|
||||||
import {
|
import {
|
||||||
CreateLibraryDto,
|
CreateLibraryDto,
|
||||||
@ -35,14 +34,14 @@ import { ILibraryRepository } from 'src/interfaces/library.interface';
|
|||||||
import { ILoggerRepository } from 'src/interfaces/logger.interface';
|
import { ILoggerRepository } from 'src/interfaces/logger.interface';
|
||||||
import { IStorageRepository } from 'src/interfaces/storage.interface';
|
import { IStorageRepository } from 'src/interfaces/storage.interface';
|
||||||
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
|
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
|
||||||
|
import { BaseService } from 'src/services/base.service';
|
||||||
import { mimeTypes } from 'src/utils/mime-types';
|
import { mimeTypes } from 'src/utils/mime-types';
|
||||||
import { handlePromiseError } from 'src/utils/misc';
|
import { handlePromiseError } from 'src/utils/misc';
|
||||||
import { usePagination } from 'src/utils/pagination';
|
import { usePagination } from 'src/utils/pagination';
|
||||||
import { validateCronExpression } from 'src/validation';
|
import { validateCronExpression } from 'src/validation';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class LibraryService {
|
export class LibraryService extends BaseService {
|
||||||
private configCore: SystemConfigCore;
|
|
||||||
private watchLibraries = false;
|
private watchLibraries = false;
|
||||||
private watchLock = false;
|
private watchLock = false;
|
||||||
private watchers: Record<string, () => Promise<void>> = {};
|
private watchers: Record<string, () => Promise<void>> = {};
|
||||||
@ -55,15 +54,15 @@ export class LibraryService {
|
|||||||
@Inject(ILibraryRepository) private repository: ILibraryRepository,
|
@Inject(ILibraryRepository) private repository: ILibraryRepository,
|
||||||
@Inject(IStorageRepository) private storageRepository: IStorageRepository,
|
@Inject(IStorageRepository) private storageRepository: IStorageRepository,
|
||||||
@Inject(IDatabaseRepository) private databaseRepository: IDatabaseRepository,
|
@Inject(IDatabaseRepository) private databaseRepository: IDatabaseRepository,
|
||||||
@Inject(ILoggerRepository) private logger: ILoggerRepository,
|
@Inject(ILoggerRepository) logger: ILoggerRepository,
|
||||||
) {
|
) {
|
||||||
|
super(systemMetadataRepository, logger);
|
||||||
this.logger.setContext(LibraryService.name);
|
this.logger.setContext(LibraryService.name);
|
||||||
this.configCore = SystemConfigCore.create(systemMetadataRepository, this.logger);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@OnEvent({ name: 'app.bootstrap' })
|
@OnEvent({ name: 'app.bootstrap' })
|
||||||
async onBootstrap() {
|
async onBootstrap() {
|
||||||
const config = await this.configCore.getConfig({ withCache: false });
|
const config = await this.getConfig({ withCache: false });
|
||||||
|
|
||||||
const { watch, scan } = config.library;
|
const { watch, scan } = config.library;
|
||||||
|
|
||||||
@ -141,9 +140,15 @@ export class LibraryService {
|
|||||||
onAdd: (path) => {
|
onAdd: (path) => {
|
||||||
const handler = async () => {
|
const handler = async () => {
|
||||||
this.logger.debug(`File add event received for ${path} in library ${library.id}}`);
|
this.logger.debug(`File add event received for ${path} in library ${library.id}}`);
|
||||||
|
if (matcher(path)) {
|
||||||
|
const asset = await this.assetRepository.getByLibraryIdAndOriginalPath(library.id, path);
|
||||||
|
if (asset) {
|
||||||
|
await this.syncAssets(library, [asset.id]);
|
||||||
|
}
|
||||||
if (matcher(path)) {
|
if (matcher(path)) {
|
||||||
await this.syncFiles(library, [path]);
|
await this.syncFiles(library, [path]);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
return handlePromiseError(handler(), this.logger);
|
return handlePromiseError(handler(), this.logger);
|
||||||
},
|
},
|
||||||
@ -605,7 +610,7 @@ export class LibraryService {
|
|||||||
this.logger.log(`Scanning library ${library.id} for removed assets`);
|
this.logger.log(`Scanning library ${library.id} for removed assets`);
|
||||||
|
|
||||||
const onlineAssets = usePagination(JOBS_LIBRARY_PAGINATION_SIZE, (pagination) =>
|
const onlineAssets = usePagination(JOBS_LIBRARY_PAGINATION_SIZE, (pagination) =>
|
||||||
this.assetRepository.getAll(pagination, { libraryId: job.id }),
|
this.assetRepository.getAll(pagination, { libraryId: job.id, withDeleted: true }),
|
||||||
);
|
);
|
||||||
|
|
||||||
let assetCount = 0;
|
let assetCount = 0;
|
||||||
|
@ -1,34 +1,26 @@
|
|||||||
import { IAlbumRepository } from 'src/interfaces/album.interface';
|
import { IAlbumRepository } from 'src/interfaces/album.interface';
|
||||||
import { ILoggerRepository } from 'src/interfaces/logger.interface';
|
|
||||||
import { IMapRepository } from 'src/interfaces/map.interface';
|
import { IMapRepository } from 'src/interfaces/map.interface';
|
||||||
import { IPartnerRepository } from 'src/interfaces/partner.interface';
|
import { IPartnerRepository } from 'src/interfaces/partner.interface';
|
||||||
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
|
|
||||||
import { MapService } from 'src/services/map.service';
|
import { MapService } from 'src/services/map.service';
|
||||||
import { assetStub } from 'test/fixtures/asset.stub';
|
import { assetStub } from 'test/fixtures/asset.stub';
|
||||||
import { authStub } from 'test/fixtures/auth.stub';
|
import { authStub } from 'test/fixtures/auth.stub';
|
||||||
import { newAlbumRepositoryMock } from 'test/repositories/album.repository.mock';
|
import { newAlbumRepositoryMock } from 'test/repositories/album.repository.mock';
|
||||||
import { newLoggerRepositoryMock } from 'test/repositories/logger.repository.mock';
|
|
||||||
import { newMapRepositoryMock } from 'test/repositories/map.repository.mock';
|
import { newMapRepositoryMock } from 'test/repositories/map.repository.mock';
|
||||||
import { newPartnerRepositoryMock } from 'test/repositories/partner.repository.mock';
|
import { newPartnerRepositoryMock } from 'test/repositories/partner.repository.mock';
|
||||||
import { newSystemMetadataRepositoryMock } from 'test/repositories/system-metadata.repository.mock';
|
|
||||||
import { Mocked } from 'vitest';
|
import { Mocked } from 'vitest';
|
||||||
|
|
||||||
describe(MapService.name, () => {
|
describe(MapService.name, () => {
|
||||||
let sut: MapService;
|
let sut: MapService;
|
||||||
let albumMock: Mocked<IAlbumRepository>;
|
let albumMock: Mocked<IAlbumRepository>;
|
||||||
let loggerMock: Mocked<ILoggerRepository>;
|
|
||||||
let partnerMock: Mocked<IPartnerRepository>;
|
let partnerMock: Mocked<IPartnerRepository>;
|
||||||
let mapMock: Mocked<IMapRepository>;
|
let mapMock: Mocked<IMapRepository>;
|
||||||
let systemMetadataMock: Mocked<ISystemMetadataRepository>;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
albumMock = newAlbumRepositoryMock();
|
albumMock = newAlbumRepositoryMock();
|
||||||
loggerMock = newLoggerRepositoryMock();
|
|
||||||
partnerMock = newPartnerRepositoryMock();
|
partnerMock = newPartnerRepositoryMock();
|
||||||
mapMock = newMapRepositoryMock();
|
mapMock = newMapRepositoryMock();
|
||||||
systemMetadataMock = newSystemMetadataRepositoryMock();
|
|
||||||
|
|
||||||
sut = new MapService(albumMock, loggerMock, partnerMock, mapMock, systemMetadataMock);
|
sut = new MapService(albumMock, partnerMock, mapMock);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('getMapMarkers', () => {
|
describe('getMapMarkers', () => {
|
||||||
|
@ -1,27 +1,17 @@
|
|||||||
import { Inject } from '@nestjs/common';
|
import { Inject } from '@nestjs/common';
|
||||||
import { SystemConfigCore } from 'src/cores/system-config.core';
|
|
||||||
import { AuthDto } from 'src/dtos/auth.dto';
|
import { AuthDto } from 'src/dtos/auth.dto';
|
||||||
import { MapMarkerDto, MapMarkerResponseDto, MapReverseGeocodeDto } from 'src/dtos/map.dto';
|
import { MapMarkerDto, MapMarkerResponseDto, MapReverseGeocodeDto } from 'src/dtos/map.dto';
|
||||||
import { IAlbumRepository } from 'src/interfaces/album.interface';
|
import { IAlbumRepository } from 'src/interfaces/album.interface';
|
||||||
import { ILoggerRepository } from 'src/interfaces/logger.interface';
|
|
||||||
import { IMapRepository } from 'src/interfaces/map.interface';
|
import { IMapRepository } from 'src/interfaces/map.interface';
|
||||||
import { IPartnerRepository } from 'src/interfaces/partner.interface';
|
import { IPartnerRepository } from 'src/interfaces/partner.interface';
|
||||||
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
|
|
||||||
import { getMyPartnerIds } from 'src/utils/asset.util';
|
import { getMyPartnerIds } from 'src/utils/asset.util';
|
||||||
|
|
||||||
export class MapService {
|
export class MapService {
|
||||||
private configCore: SystemConfigCore;
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(IAlbumRepository) private albumRepository: IAlbumRepository,
|
@Inject(IAlbumRepository) private albumRepository: IAlbumRepository,
|
||||||
@Inject(ILoggerRepository) private logger: ILoggerRepository,
|
|
||||||
@Inject(IPartnerRepository) private partnerRepository: IPartnerRepository,
|
@Inject(IPartnerRepository) private partnerRepository: IPartnerRepository,
|
||||||
@Inject(IMapRepository) private mapRepository: IMapRepository,
|
@Inject(IMapRepository) private mapRepository: IMapRepository,
|
||||||
@Inject(ISystemMetadataRepository) private systemMetadataRepository: ISystemMetadataRepository,
|
) {}
|
||||||
) {
|
|
||||||
this.logger.setContext(MapService.name);
|
|
||||||
this.configCore = SystemConfigCore.create(systemMetadataRepository, logger);
|
|
||||||
}
|
|
||||||
|
|
||||||
async getMapMarkers(auth: AuthDto, options: MapMarkerDto): Promise<MapMarkerResponseDto[]> {
|
async getMapMarkers(auth: AuthDto, options: MapMarkerDto): Promise<MapMarkerResponseDto[]> {
|
||||||
const userIds = [auth.user.id];
|
const userIds = [auth.user.id];
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import { dirname } from 'node:path';
|
import { dirname } from 'node:path';
|
||||||
|
|
||||||
import { StorageCore } from 'src/cores/storage.core';
|
import { StorageCore } from 'src/cores/storage.core';
|
||||||
import { SystemConfigCore } from 'src/cores/system-config.core';
|
|
||||||
import { SystemConfigFFmpegDto } from 'src/dtos/system-config.dto';
|
import { SystemConfigFFmpegDto } from 'src/dtos/system-config.dto';
|
||||||
import { AssetEntity } from 'src/entities/asset.entity';
|
import { AssetEntity } from 'src/entities/asset.entity';
|
||||||
import {
|
import {
|
||||||
@ -43,14 +41,14 @@ import { IMoveRepository } from 'src/interfaces/move.interface';
|
|||||||
import { IPersonRepository } from 'src/interfaces/person.interface';
|
import { IPersonRepository } from 'src/interfaces/person.interface';
|
||||||
import { IStorageRepository } from 'src/interfaces/storage.interface';
|
import { IStorageRepository } from 'src/interfaces/storage.interface';
|
||||||
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
|
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
|
||||||
|
import { BaseService } from 'src/services/base.service';
|
||||||
import { getAssetFiles } from 'src/utils/asset.util';
|
import { getAssetFiles } from 'src/utils/asset.util';
|
||||||
import { BaseConfig, ThumbnailConfig } from 'src/utils/media';
|
import { BaseConfig, ThumbnailConfig } from 'src/utils/media';
|
||||||
import { mimeTypes } from 'src/utils/mime-types';
|
import { mimeTypes } from 'src/utils/mime-types';
|
||||||
import { usePagination } from 'src/utils/pagination';
|
import { usePagination } from 'src/utils/pagination';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class MediaService {
|
export class MediaService extends BaseService {
|
||||||
private configCore: SystemConfigCore;
|
|
||||||
private storageCore: StorageCore;
|
private storageCore: StorageCore;
|
||||||
private maliOpenCL?: boolean;
|
private maliOpenCL?: boolean;
|
||||||
private devices?: string[];
|
private devices?: string[];
|
||||||
@ -64,10 +62,10 @@ export class MediaService {
|
|||||||
@Inject(ISystemMetadataRepository) systemMetadataRepository: ISystemMetadataRepository,
|
@Inject(ISystemMetadataRepository) systemMetadataRepository: ISystemMetadataRepository,
|
||||||
@Inject(IMoveRepository) moveRepository: IMoveRepository,
|
@Inject(IMoveRepository) moveRepository: IMoveRepository,
|
||||||
@Inject(ICryptoRepository) cryptoRepository: ICryptoRepository,
|
@Inject(ICryptoRepository) cryptoRepository: ICryptoRepository,
|
||||||
@Inject(ILoggerRepository) private logger: ILoggerRepository,
|
@Inject(ILoggerRepository) logger: ILoggerRepository,
|
||||||
) {
|
) {
|
||||||
|
super(systemMetadataRepository, logger);
|
||||||
this.logger.setContext(MediaService.name);
|
this.logger.setContext(MediaService.name);
|
||||||
this.configCore = SystemConfigCore.create(systemMetadataRepository, this.logger);
|
|
||||||
this.storageCore = StorageCore.create(
|
this.storageCore = StorageCore.create(
|
||||||
assetRepository,
|
assetRepository,
|
||||||
cryptoRepository,
|
cryptoRepository,
|
||||||
@ -161,7 +159,7 @@ export class MediaService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async handleAssetMigration({ id }: IEntityJob): Promise<JobStatus> {
|
async handleAssetMigration({ id }: IEntityJob): Promise<JobStatus> {
|
||||||
const { image } = await this.configCore.getConfig({ withCache: true });
|
const { image } = await this.getConfig({ withCache: true });
|
||||||
const [asset] = await this.assetRepository.getByIds([id], { files: true });
|
const [asset] = await this.assetRepository.getByIds([id], { files: true });
|
||||||
if (!asset) {
|
if (!asset) {
|
||||||
return JobStatus.FAILED;
|
return JobStatus.FAILED;
|
||||||
@ -235,7 +233,7 @@ export class MediaService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async generateImageThumbnails(asset: AssetEntity) {
|
private async generateImageThumbnails(asset: AssetEntity) {
|
||||||
const { image } = await this.configCore.getConfig({ withCache: true });
|
const { image } = await this.getConfig({ withCache: true });
|
||||||
const previewPath = StorageCore.getImagePath(asset, AssetPathType.PREVIEW, image.preview.format);
|
const previewPath = StorageCore.getImagePath(asset, AssetPathType.PREVIEW, image.preview.format);
|
||||||
const thumbnailPath = StorageCore.getImagePath(asset, AssetPathType.THUMBNAIL, image.thumbnail.format);
|
const thumbnailPath = StorageCore.getImagePath(asset, AssetPathType.THUMBNAIL, image.thumbnail.format);
|
||||||
this.storageCore.ensureFolders(previewPath);
|
this.storageCore.ensureFolders(previewPath);
|
||||||
@ -269,7 +267,7 @@ export class MediaService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async generateVideoThumbnails(asset: AssetEntity) {
|
private async generateVideoThumbnails(asset: AssetEntity) {
|
||||||
const { image, ffmpeg } = await this.configCore.getConfig({ withCache: true });
|
const { image, ffmpeg } = await this.getConfig({ withCache: true });
|
||||||
const previewPath = StorageCore.getImagePath(asset, AssetPathType.PREVIEW, image.preview.format);
|
const previewPath = StorageCore.getImagePath(asset, AssetPathType.PREVIEW, image.preview.format);
|
||||||
const thumbnailPath = StorageCore.getImagePath(asset, AssetPathType.THUMBNAIL, image.thumbnail.format);
|
const thumbnailPath = StorageCore.getImagePath(asset, AssetPathType.THUMBNAIL, image.thumbnail.format);
|
||||||
this.storageCore.ensureFolders(previewPath);
|
this.storageCore.ensureFolders(previewPath);
|
||||||
@ -339,7 +337,7 @@ export class MediaService {
|
|||||||
return JobStatus.FAILED;
|
return JobStatus.FAILED;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { ffmpeg } = await this.configCore.getConfig({ withCache: true });
|
const { ffmpeg } = await this.getConfig({ withCache: true });
|
||||||
const target = this.getTranscodeTarget(ffmpeg, mainVideoStream, mainAudioStream);
|
const target = this.getTranscodeTarget(ffmpeg, mainVideoStream, mainAudioStream);
|
||||||
if (target === TranscodeTarget.NONE && !this.isRemuxRequired(ffmpeg, format)) {
|
if (target === TranscodeTarget.NONE && !this.isRemuxRequired(ffmpeg, format)) {
|
||||||
if (asset.encodedVideoPath) {
|
if (asset.encodedVideoPath) {
|
||||||
|
@ -7,7 +7,6 @@ import { constants } from 'node:fs/promises';
|
|||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
import { SystemConfig } from 'src/config';
|
import { SystemConfig } from 'src/config';
|
||||||
import { StorageCore } from 'src/cores/storage.core';
|
import { StorageCore } from 'src/cores/storage.core';
|
||||||
import { SystemConfigCore } from 'src/cores/system-config.core';
|
|
||||||
import { OnEvent } from 'src/decorators';
|
import { OnEvent } from 'src/decorators';
|
||||||
import { AssetFaceEntity } from 'src/entities/asset-face.entity';
|
import { AssetFaceEntity } from 'src/entities/asset-face.entity';
|
||||||
import { AssetEntity } from 'src/entities/asset.entity';
|
import { AssetEntity } from 'src/entities/asset.entity';
|
||||||
@ -39,6 +38,7 @@ import { IStorageRepository } from 'src/interfaces/storage.interface';
|
|||||||
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
|
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
|
||||||
import { ITagRepository } from 'src/interfaces/tag.interface';
|
import { ITagRepository } from 'src/interfaces/tag.interface';
|
||||||
import { IUserRepository } from 'src/interfaces/user.interface';
|
import { IUserRepository } from 'src/interfaces/user.interface';
|
||||||
|
import { BaseService } from 'src/services/base.service';
|
||||||
import { isFaceImportEnabled } from 'src/utils/misc';
|
import { isFaceImportEnabled } from 'src/utils/misc';
|
||||||
import { usePagination } from 'src/utils/pagination';
|
import { usePagination } from 'src/utils/pagination';
|
||||||
import { upsertTags } from 'src/utils/tag';
|
import { upsertTags } from 'src/utils/tag';
|
||||||
@ -97,9 +97,8 @@ const validateRange = (value: number | undefined, min: number, max: number): Non
|
|||||||
};
|
};
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class MetadataService {
|
export class MetadataService extends BaseService {
|
||||||
private storageCore: StorageCore;
|
private storageCore: StorageCore;
|
||||||
private configCore: SystemConfigCore;
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(IAlbumRepository) private albumRepository: IAlbumRepository,
|
@Inject(IAlbumRepository) private albumRepository: IAlbumRepository,
|
||||||
@ -117,10 +116,10 @@ export class MetadataService {
|
|||||||
@Inject(ISystemMetadataRepository) systemMetadataRepository: ISystemMetadataRepository,
|
@Inject(ISystemMetadataRepository) systemMetadataRepository: ISystemMetadataRepository,
|
||||||
@Inject(ITagRepository) private tagRepository: ITagRepository,
|
@Inject(ITagRepository) private tagRepository: ITagRepository,
|
||||||
@Inject(IUserRepository) private userRepository: IUserRepository,
|
@Inject(IUserRepository) private userRepository: IUserRepository,
|
||||||
@Inject(ILoggerRepository) private logger: ILoggerRepository,
|
@Inject(ILoggerRepository) logger: ILoggerRepository,
|
||||||
) {
|
) {
|
||||||
|
super(systemMetadataRepository, logger);
|
||||||
this.logger.setContext(MetadataService.name);
|
this.logger.setContext(MetadataService.name);
|
||||||
this.configCore = SystemConfigCore.create(systemMetadataRepository, this.logger);
|
|
||||||
this.storageCore = StorageCore.create(
|
this.storageCore = StorageCore.create(
|
||||||
assetRepository,
|
assetRepository,
|
||||||
cryptoRepository,
|
cryptoRepository,
|
||||||
@ -137,7 +136,7 @@ export class MetadataService {
|
|||||||
if (app !== 'microservices') {
|
if (app !== 'microservices') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const config = await this.configCore.getConfig({ withCache: false });
|
const config = await this.getConfig({ withCache: false });
|
||||||
await this.init(config);
|
await this.init(config);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -222,7 +221,7 @@ export class MetadataService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async handleMetadataExtraction({ id }: IEntityJob): Promise<JobStatus> {
|
async handleMetadataExtraction({ id }: IEntityJob): Promise<JobStatus> {
|
||||||
const { metadata, reverseGeocoding } = await this.configCore.getConfig({ withCache: true });
|
const { metadata, reverseGeocoding } = await this.getConfig({ withCache: true });
|
||||||
const [asset] = await this.assetRepository.getByIds([id]);
|
const [asset] = await this.assetRepository.getByIds([id]);
|
||||||
if (!asset) {
|
if (!asset) {
|
||||||
return JobStatus.FAILED;
|
return JobStatus.FAILED;
|
||||||
|
@ -6,7 +6,7 @@ import { AssetFileEntity } from 'src/entities/asset-files.entity';
|
|||||||
import { AssetFileType, UserMetadataKey } from 'src/enum';
|
import { AssetFileType, UserMetadataKey } from 'src/enum';
|
||||||
import { IAlbumRepository } from 'src/interfaces/album.interface';
|
import { IAlbumRepository } from 'src/interfaces/album.interface';
|
||||||
import { IAssetRepository } from 'src/interfaces/asset.interface';
|
import { IAssetRepository } from 'src/interfaces/asset.interface';
|
||||||
import { ClientEvent, IEventRepository } from 'src/interfaces/event.interface';
|
import { IEventRepository } from 'src/interfaces/event.interface';
|
||||||
import { IJobRepository, JobName, JobStatus } from 'src/interfaces/job.interface';
|
import { IJobRepository, JobName, JobStatus } from 'src/interfaces/job.interface';
|
||||||
import { ILoggerRepository } from 'src/interfaces/logger.interface';
|
import { ILoggerRepository } from 'src/interfaces/logger.interface';
|
||||||
import { EmailTemplate, INotificationRepository } from 'src/interfaces/notification.interface';
|
import { EmailTemplate, INotificationRepository } from 'src/interfaces/notification.interface';
|
||||||
@ -104,7 +104,7 @@ describe(NotificationService.name, () => {
|
|||||||
it('should emit client and server events', () => {
|
it('should emit client and server events', () => {
|
||||||
const update = { newConfig: defaults };
|
const update = { newConfig: defaults };
|
||||||
expect(sut.onConfigUpdate(update)).toBeUndefined();
|
expect(sut.onConfigUpdate(update)).toBeUndefined();
|
||||||
expect(eventMock.clientBroadcast).toHaveBeenCalledWith(ClientEvent.CONFIG_UPDATE, {});
|
expect(eventMock.clientBroadcast).toHaveBeenCalledWith('on_config_update');
|
||||||
expect(eventMock.serverSend).toHaveBeenCalledWith('config.update', update);
|
expect(eventMock.serverSend).toHaveBeenCalledWith('config.update', update);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -236,28 +236,28 @@ describe(NotificationService.name, () => {
|
|||||||
describe('onStackCreate', () => {
|
describe('onStackCreate', () => {
|
||||||
it('should send connected clients an event', () => {
|
it('should send connected clients an event', () => {
|
||||||
sut.onStackCreate({ stackId: 'stack-id', userId: 'user-id' });
|
sut.onStackCreate({ stackId: 'stack-id', userId: 'user-id' });
|
||||||
expect(eventMock.clientSend).toHaveBeenCalledWith('on_asset_stack_update', 'user-id', []);
|
expect(eventMock.clientSend).toHaveBeenCalledWith('on_asset_stack_update', 'user-id');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('onStackUpdate', () => {
|
describe('onStackUpdate', () => {
|
||||||
it('should send connected clients an event', () => {
|
it('should send connected clients an event', () => {
|
||||||
sut.onStackUpdate({ stackId: 'stack-id', userId: 'user-id' });
|
sut.onStackUpdate({ stackId: 'stack-id', userId: 'user-id' });
|
||||||
expect(eventMock.clientSend).toHaveBeenCalledWith('on_asset_stack_update', 'user-id', []);
|
expect(eventMock.clientSend).toHaveBeenCalledWith('on_asset_stack_update', 'user-id');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('onStackDelete', () => {
|
describe('onStackDelete', () => {
|
||||||
it('should send connected clients an event', () => {
|
it('should send connected clients an event', () => {
|
||||||
sut.onStackDelete({ stackId: 'stack-id', userId: 'user-id' });
|
sut.onStackDelete({ stackId: 'stack-id', userId: 'user-id' });
|
||||||
expect(eventMock.clientSend).toHaveBeenCalledWith('on_asset_stack_update', 'user-id', []);
|
expect(eventMock.clientSend).toHaveBeenCalledWith('on_asset_stack_update', 'user-id');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('onStacksDelete', () => {
|
describe('onStacksDelete', () => {
|
||||||
it('should send connected clients an event', () => {
|
it('should send connected clients an event', () => {
|
||||||
sut.onStacksDelete({ stackIds: ['stack-id'], userId: 'user-id' });
|
sut.onStacksDelete({ stackIds: ['stack-id'], userId: 'user-id' });
|
||||||
expect(eventMock.clientSend).toHaveBeenCalledWith('on_asset_stack_update', 'user-id', []);
|
expect(eventMock.clientSend).toHaveBeenCalledWith('on_asset_stack_update', 'user-id');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1,12 +1,11 @@
|
|||||||
import { BadRequestException, Inject, Injectable } from '@nestjs/common';
|
import { BadRequestException, Inject, Injectable } from '@nestjs/common';
|
||||||
import { DEFAULT_EXTERNAL_DOMAIN } from 'src/constants';
|
import { DEFAULT_EXTERNAL_DOMAIN } from 'src/constants';
|
||||||
import { SystemConfigCore } from 'src/cores/system-config.core';
|
|
||||||
import { OnEvent } from 'src/decorators';
|
import { OnEvent } from 'src/decorators';
|
||||||
import { SystemConfigSmtpDto } from 'src/dtos/system-config.dto';
|
import { SystemConfigSmtpDto } from 'src/dtos/system-config.dto';
|
||||||
import { AlbumEntity } from 'src/entities/album.entity';
|
import { AlbumEntity } from 'src/entities/album.entity';
|
||||||
import { IAlbumRepository } from 'src/interfaces/album.interface';
|
import { IAlbumRepository } from 'src/interfaces/album.interface';
|
||||||
import { IAssetRepository } from 'src/interfaces/asset.interface';
|
import { IAssetRepository } from 'src/interfaces/asset.interface';
|
||||||
import { ArgOf, ClientEvent, IEventRepository } from 'src/interfaces/event.interface';
|
import { ArgOf, IEventRepository } from 'src/interfaces/event.interface';
|
||||||
import {
|
import {
|
||||||
IEmailJob,
|
IEmailJob,
|
||||||
IJobRepository,
|
IJobRepository,
|
||||||
@ -20,32 +19,31 @@ import { ILoggerRepository } from 'src/interfaces/logger.interface';
|
|||||||
import { EmailImageAttachment, EmailTemplate, INotificationRepository } from 'src/interfaces/notification.interface';
|
import { EmailImageAttachment, EmailTemplate, INotificationRepository } from 'src/interfaces/notification.interface';
|
||||||
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
|
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
|
||||||
import { IUserRepository } from 'src/interfaces/user.interface';
|
import { IUserRepository } from 'src/interfaces/user.interface';
|
||||||
|
import { BaseService } from 'src/services/base.service';
|
||||||
import { getAssetFiles } from 'src/utils/asset.util';
|
import { getAssetFiles } from 'src/utils/asset.util';
|
||||||
import { getFilenameExtension } from 'src/utils/file';
|
import { getFilenameExtension } from 'src/utils/file';
|
||||||
import { isEqualObject } from 'src/utils/object';
|
import { isEqualObject } from 'src/utils/object';
|
||||||
import { getPreferences } from 'src/utils/preferences';
|
import { getPreferences } from 'src/utils/preferences';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class NotificationService {
|
export class NotificationService extends BaseService {
|
||||||
private configCore: SystemConfigCore;
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(IEventRepository) private eventRepository: IEventRepository,
|
@Inject(IEventRepository) private eventRepository: IEventRepository,
|
||||||
@Inject(ISystemMetadataRepository) systemMetadataRepository: ISystemMetadataRepository,
|
@Inject(ISystemMetadataRepository) systemMetadataRepository: ISystemMetadataRepository,
|
||||||
@Inject(INotificationRepository) private notificationRepository: INotificationRepository,
|
@Inject(INotificationRepository) private notificationRepository: INotificationRepository,
|
||||||
@Inject(IUserRepository) private userRepository: IUserRepository,
|
@Inject(IUserRepository) private userRepository: IUserRepository,
|
||||||
@Inject(IJobRepository) private jobRepository: IJobRepository,
|
@Inject(IJobRepository) private jobRepository: IJobRepository,
|
||||||
@Inject(ILoggerRepository) private logger: ILoggerRepository,
|
@Inject(ILoggerRepository) logger: ILoggerRepository,
|
||||||
@Inject(IAssetRepository) private assetRepository: IAssetRepository,
|
@Inject(IAssetRepository) private assetRepository: IAssetRepository,
|
||||||
@Inject(IAlbumRepository) private albumRepository: IAlbumRepository,
|
@Inject(IAlbumRepository) private albumRepository: IAlbumRepository,
|
||||||
) {
|
) {
|
||||||
|
super(systemMetadataRepository, logger);
|
||||||
this.logger.setContext(NotificationService.name);
|
this.logger.setContext(NotificationService.name);
|
||||||
this.configCore = SystemConfigCore.create(systemMetadataRepository, logger);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@OnEvent({ name: 'config.update' })
|
@OnEvent({ name: 'config.update' })
|
||||||
onConfigUpdate({ oldConfig, newConfig }: ArgOf<'config.update'>) {
|
onConfigUpdate({ oldConfig, newConfig }: ArgOf<'config.update'>) {
|
||||||
this.eventRepository.clientBroadcast(ClientEvent.CONFIG_UPDATE, {});
|
this.eventRepository.clientBroadcast('on_config_update');
|
||||||
this.eventRepository.serverSend('config.update', { oldConfig, newConfig });
|
this.eventRepository.serverSend('config.update', { oldConfig, newConfig });
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -66,7 +64,7 @@ export class NotificationService {
|
|||||||
|
|
||||||
@OnEvent({ name: 'asset.hide' })
|
@OnEvent({ name: 'asset.hide' })
|
||||||
onAssetHide({ assetId, userId }: ArgOf<'asset.hide'>) {
|
onAssetHide({ assetId, userId }: ArgOf<'asset.hide'>) {
|
||||||
this.eventRepository.clientSend(ClientEvent.ASSET_HIDDEN, userId, assetId);
|
this.eventRepository.clientSend('on_asset_hidden', userId, assetId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@OnEvent({ name: 'asset.show' })
|
@OnEvent({ name: 'asset.show' })
|
||||||
@ -76,42 +74,42 @@ export class NotificationService {
|
|||||||
|
|
||||||
@OnEvent({ name: 'asset.trash' })
|
@OnEvent({ name: 'asset.trash' })
|
||||||
onAssetTrash({ assetId, userId }: ArgOf<'asset.trash'>) {
|
onAssetTrash({ assetId, userId }: ArgOf<'asset.trash'>) {
|
||||||
this.eventRepository.clientSend(ClientEvent.ASSET_TRASH, userId, [assetId]);
|
this.eventRepository.clientSend('on_asset_trash', userId, [assetId]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@OnEvent({ name: 'asset.delete' })
|
@OnEvent({ name: 'asset.delete' })
|
||||||
onAssetDelete({ assetId, userId }: ArgOf<'asset.delete'>) {
|
onAssetDelete({ assetId, userId }: ArgOf<'asset.delete'>) {
|
||||||
this.eventRepository.clientSend(ClientEvent.ASSET_DELETE, userId, assetId);
|
this.eventRepository.clientSend('on_asset_delete', userId, assetId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@OnEvent({ name: 'assets.trash' })
|
@OnEvent({ name: 'assets.trash' })
|
||||||
onAssetsTrash({ assetIds, userId }: ArgOf<'assets.trash'>) {
|
onAssetsTrash({ assetIds, userId }: ArgOf<'assets.trash'>) {
|
||||||
this.eventRepository.clientSend(ClientEvent.ASSET_TRASH, userId, assetIds);
|
this.eventRepository.clientSend('on_asset_trash', userId, assetIds);
|
||||||
}
|
}
|
||||||
|
|
||||||
@OnEvent({ name: 'assets.restore' })
|
@OnEvent({ name: 'assets.restore' })
|
||||||
onAssetsRestore({ assetIds, userId }: ArgOf<'assets.restore'>) {
|
onAssetsRestore({ assetIds, userId }: ArgOf<'assets.restore'>) {
|
||||||
this.eventRepository.clientSend(ClientEvent.ASSET_RESTORE, userId, assetIds);
|
this.eventRepository.clientSend('on_asset_restore', userId, assetIds);
|
||||||
}
|
}
|
||||||
|
|
||||||
@OnEvent({ name: 'stack.create' })
|
@OnEvent({ name: 'stack.create' })
|
||||||
onStackCreate({ userId }: ArgOf<'stack.create'>) {
|
onStackCreate({ userId }: ArgOf<'stack.create'>) {
|
||||||
this.eventRepository.clientSend(ClientEvent.ASSET_STACK_UPDATE, userId, []);
|
this.eventRepository.clientSend('on_asset_stack_update', userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@OnEvent({ name: 'stack.update' })
|
@OnEvent({ name: 'stack.update' })
|
||||||
onStackUpdate({ userId }: ArgOf<'stack.update'>) {
|
onStackUpdate({ userId }: ArgOf<'stack.update'>) {
|
||||||
this.eventRepository.clientSend(ClientEvent.ASSET_STACK_UPDATE, userId, []);
|
this.eventRepository.clientSend('on_asset_stack_update', userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@OnEvent({ name: 'stack.delete' })
|
@OnEvent({ name: 'stack.delete' })
|
||||||
onStackDelete({ userId }: ArgOf<'stack.delete'>) {
|
onStackDelete({ userId }: ArgOf<'stack.delete'>) {
|
||||||
this.eventRepository.clientSend(ClientEvent.ASSET_STACK_UPDATE, userId, []);
|
this.eventRepository.clientSend('on_asset_stack_update', userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@OnEvent({ name: 'stacks.delete' })
|
@OnEvent({ name: 'stacks.delete' })
|
||||||
onStacksDelete({ userId }: ArgOf<'stacks.delete'>) {
|
onStacksDelete({ userId }: ArgOf<'stacks.delete'>) {
|
||||||
this.eventRepository.clientSend(ClientEvent.ASSET_STACK_UPDATE, userId, []);
|
this.eventRepository.clientSend('on_asset_stack_update', userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@OnEvent({ name: 'user.signup' })
|
@OnEvent({ name: 'user.signup' })
|
||||||
@ -134,7 +132,7 @@ export class NotificationService {
|
|||||||
@OnEvent({ name: 'session.delete' })
|
@OnEvent({ name: 'session.delete' })
|
||||||
onSessionDelete({ sessionId }: ArgOf<'session.delete'>) {
|
onSessionDelete({ sessionId }: ArgOf<'session.delete'>) {
|
||||||
// after the response is sent
|
// after the response is sent
|
||||||
setTimeout(() => this.eventRepository.clientSend(ClientEvent.SESSION_DELETE, sessionId, sessionId), 500);
|
setTimeout(() => this.eventRepository.clientSend('on_session_delete', sessionId, sessionId), 500);
|
||||||
}
|
}
|
||||||
|
|
||||||
async sendTestEmail(id: string, dto: SystemConfigSmtpDto) {
|
async sendTestEmail(id: string, dto: SystemConfigSmtpDto) {
|
||||||
@ -149,7 +147,7 @@ export class NotificationService {
|
|||||||
throw new BadRequestException('Failed to verify SMTP configuration', { cause: error });
|
throw new BadRequestException('Failed to verify SMTP configuration', { cause: error });
|
||||||
}
|
}
|
||||||
|
|
||||||
const { server } = await this.configCore.getConfig({ withCache: false });
|
const { server } = await this.getConfig({ withCache: false });
|
||||||
const { html, text } = await this.notificationRepository.renderEmail({
|
const { html, text } = await this.notificationRepository.renderEmail({
|
||||||
template: EmailTemplate.TEST_EMAIL,
|
template: EmailTemplate.TEST_EMAIL,
|
||||||
data: {
|
data: {
|
||||||
@ -177,7 +175,7 @@ export class NotificationService {
|
|||||||
return JobStatus.SKIPPED;
|
return JobStatus.SKIPPED;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { server } = await this.configCore.getConfig({ withCache: true });
|
const { server } = await this.getConfig({ withCache: true });
|
||||||
const { html, text } = await this.notificationRepository.renderEmail({
|
const { html, text } = await this.notificationRepository.renderEmail({
|
||||||
template: EmailTemplate.WELCOME,
|
template: EmailTemplate.WELCOME,
|
||||||
data: {
|
data: {
|
||||||
@ -220,7 +218,7 @@ export class NotificationService {
|
|||||||
|
|
||||||
const attachment = await this.getAlbumThumbnailAttachment(album);
|
const attachment = await this.getAlbumThumbnailAttachment(album);
|
||||||
|
|
||||||
const { server } = await this.configCore.getConfig({ withCache: false });
|
const { server } = await this.getConfig({ withCache: false });
|
||||||
const { html, text } = await this.notificationRepository.renderEmail({
|
const { html, text } = await this.notificationRepository.renderEmail({
|
||||||
template: EmailTemplate.ALBUM_INVITE,
|
template: EmailTemplate.ALBUM_INVITE,
|
||||||
data: {
|
data: {
|
||||||
@ -262,7 +260,7 @@ export class NotificationService {
|
|||||||
const recipients = [...album.albumUsers.map((user) => user.user), owner].filter((user) => user.id !== senderId);
|
const recipients = [...album.albumUsers.map((user) => user.user), owner].filter((user) => user.id !== senderId);
|
||||||
const attachment = await this.getAlbumThumbnailAttachment(album);
|
const attachment = await this.getAlbumThumbnailAttachment(album);
|
||||||
|
|
||||||
const { server } = await this.configCore.getConfig({ withCache: false });
|
const { server } = await this.getConfig({ withCache: false });
|
||||||
|
|
||||||
for (const recipient of recipients) {
|
for (const recipient of recipients) {
|
||||||
const user = await this.userRepository.get(recipient.id, { withDeleted: false });
|
const user = await this.userRepository.get(recipient.id, { withDeleted: false });
|
||||||
@ -303,7 +301,7 @@ export class NotificationService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async handleSendEmail(data: IEmailJob): Promise<JobStatus> {
|
async handleSendEmail(data: IEmailJob): Promise<JobStatus> {
|
||||||
const { notifications } = await this.configCore.getConfig({ withCache: false });
|
const { notifications } = await this.getConfig({ withCache: false });
|
||||||
if (!notifications.smtp.enabled) {
|
if (!notifications.smtp.enabled) {
|
||||||
return JobStatus.SKIPPED;
|
return JobStatus.SKIPPED;
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import { BadRequestException, Inject, Injectable, NotFoundException } from '@nestjs/common';
|
import { BadRequestException, Inject, Injectable, NotFoundException } from '@nestjs/common';
|
||||||
import { FACE_THUMBNAIL_SIZE } from 'src/constants';
|
import { FACE_THUMBNAIL_SIZE } from 'src/constants';
|
||||||
import { StorageCore } from 'src/cores/storage.core';
|
import { StorageCore } from 'src/cores/storage.core';
|
||||||
import { SystemConfigCore } from 'src/cores/system-config.core';
|
|
||||||
import { BulkIdErrorReason, BulkIdResponseDto } from 'src/dtos/asset-ids.response.dto';
|
import { BulkIdErrorReason, BulkIdResponseDto } from 'src/dtos/asset-ids.response.dto';
|
||||||
import { AssetResponseDto, mapAsset } from 'src/dtos/asset-response.dto';
|
import { AssetResponseDto, mapAsset } from 'src/dtos/asset-response.dto';
|
||||||
import { AuthDto } from 'src/dtos/auth.dto';
|
import { AuthDto } from 'src/dtos/auth.dto';
|
||||||
@ -55,6 +54,7 @@ import { IPersonRepository, UpdateFacesData } from 'src/interfaces/person.interf
|
|||||||
import { ISearchRepository } from 'src/interfaces/search.interface';
|
import { ISearchRepository } from 'src/interfaces/search.interface';
|
||||||
import { IStorageRepository } from 'src/interfaces/storage.interface';
|
import { IStorageRepository } from 'src/interfaces/storage.interface';
|
||||||
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
|
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
|
||||||
|
import { BaseService } from 'src/services/base.service';
|
||||||
import { checkAccess, requireAccess } from 'src/utils/access';
|
import { checkAccess, requireAccess } from 'src/utils/access';
|
||||||
import { getAssetFiles } from 'src/utils/asset.util';
|
import { getAssetFiles } from 'src/utils/asset.util';
|
||||||
import { ImmichFileResponse } from 'src/utils/file';
|
import { ImmichFileResponse } from 'src/utils/file';
|
||||||
@ -64,8 +64,7 @@ import { usePagination } from 'src/utils/pagination';
|
|||||||
import { IsNull } from 'typeorm';
|
import { IsNull } from 'typeorm';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class PersonService {
|
export class PersonService extends BaseService {
|
||||||
private configCore: SystemConfigCore;
|
|
||||||
private storageCore: StorageCore;
|
private storageCore: StorageCore;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@ -75,15 +74,15 @@ export class PersonService {
|
|||||||
@Inject(IMoveRepository) moveRepository: IMoveRepository,
|
@Inject(IMoveRepository) moveRepository: IMoveRepository,
|
||||||
@Inject(IMediaRepository) private mediaRepository: IMediaRepository,
|
@Inject(IMediaRepository) private mediaRepository: IMediaRepository,
|
||||||
@Inject(IPersonRepository) private repository: IPersonRepository,
|
@Inject(IPersonRepository) private repository: IPersonRepository,
|
||||||
@Inject(ISystemMetadataRepository) private systemMetadataRepository: ISystemMetadataRepository,
|
@Inject(ISystemMetadataRepository) systemMetadataRepository: ISystemMetadataRepository,
|
||||||
@Inject(IStorageRepository) private storageRepository: IStorageRepository,
|
@Inject(IStorageRepository) private storageRepository: IStorageRepository,
|
||||||
@Inject(IJobRepository) private jobRepository: IJobRepository,
|
@Inject(IJobRepository) private jobRepository: IJobRepository,
|
||||||
@Inject(ISearchRepository) private smartInfoRepository: ISearchRepository,
|
@Inject(ISearchRepository) private smartInfoRepository: ISearchRepository,
|
||||||
@Inject(ICryptoRepository) private cryptoRepository: ICryptoRepository,
|
@Inject(ICryptoRepository) private cryptoRepository: ICryptoRepository,
|
||||||
@Inject(ILoggerRepository) private logger: ILoggerRepository,
|
@Inject(ILoggerRepository) logger: ILoggerRepository,
|
||||||
) {
|
) {
|
||||||
|
super(systemMetadataRepository, logger);
|
||||||
this.logger.setContext(PersonService.name);
|
this.logger.setContext(PersonService.name);
|
||||||
this.configCore = SystemConfigCore.create(systemMetadataRepository, this.logger);
|
|
||||||
this.storageCore = StorageCore.create(
|
this.storageCore = StorageCore.create(
|
||||||
assetRepository,
|
assetRepository,
|
||||||
cryptoRepository,
|
cryptoRepository,
|
||||||
@ -102,7 +101,7 @@ export class PersonService {
|
|||||||
skip: (page - 1) * size,
|
skip: (page - 1) * size,
|
||||||
};
|
};
|
||||||
|
|
||||||
const { machineLearning } = await this.configCore.getConfig({ withCache: false });
|
const { machineLearning } = await this.getConfig({ withCache: false });
|
||||||
const { items, hasNextPage } = await this.repository.getAllForUser(pagination, auth.user.id, {
|
const { items, hasNextPage } = await this.repository.getAllForUser(pagination, auth.user.id, {
|
||||||
minimumFaceCount: machineLearning.facialRecognition.minFaces,
|
minimumFaceCount: machineLearning.facialRecognition.minFaces,
|
||||||
withHidden,
|
withHidden,
|
||||||
@ -283,7 +282,7 @@ export class PersonService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async handleQueueDetectFaces({ force }: IBaseJob): Promise<JobStatus> {
|
async handleQueueDetectFaces({ force }: IBaseJob): Promise<JobStatus> {
|
||||||
const { machineLearning } = await this.configCore.getConfig({ withCache: false });
|
const { machineLearning } = await this.getConfig({ withCache: false });
|
||||||
if (!isFacialRecognitionEnabled(machineLearning)) {
|
if (!isFacialRecognitionEnabled(machineLearning)) {
|
||||||
return JobStatus.SKIPPED;
|
return JobStatus.SKIPPED;
|
||||||
}
|
}
|
||||||
@ -314,7 +313,7 @@ export class PersonService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async handleDetectFaces({ id }: IEntityJob): Promise<JobStatus> {
|
async handleDetectFaces({ id }: IEntityJob): Promise<JobStatus> {
|
||||||
const { machineLearning } = await this.configCore.getConfig({ withCache: true });
|
const { machineLearning } = await this.getConfig({ withCache: true });
|
||||||
if (!isFacialRecognitionEnabled(machineLearning)) {
|
if (!isFacialRecognitionEnabled(machineLearning)) {
|
||||||
return JobStatus.SKIPPED;
|
return JobStatus.SKIPPED;
|
||||||
}
|
}
|
||||||
@ -375,7 +374,7 @@ export class PersonService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async handleQueueRecognizeFaces({ force, nightly }: INightlyJob): Promise<JobStatus> {
|
async handleQueueRecognizeFaces({ force, nightly }: INightlyJob): Promise<JobStatus> {
|
||||||
const { machineLearning } = await this.configCore.getConfig({ withCache: false });
|
const { machineLearning } = await this.getConfig({ withCache: false });
|
||||||
if (!isFacialRecognitionEnabled(machineLearning)) {
|
if (!isFacialRecognitionEnabled(machineLearning)) {
|
||||||
return JobStatus.SKIPPED;
|
return JobStatus.SKIPPED;
|
||||||
}
|
}
|
||||||
@ -425,7 +424,7 @@ export class PersonService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async handleRecognizeFaces({ id, deferred }: IDeferrableJob): Promise<JobStatus> {
|
async handleRecognizeFaces({ id, deferred }: IDeferrableJob): Promise<JobStatus> {
|
||||||
const { machineLearning } = await this.configCore.getConfig({ withCache: true });
|
const { machineLearning } = await this.getConfig({ withCache: true });
|
||||||
if (!isFacialRecognitionEnabled(machineLearning)) {
|
if (!isFacialRecognitionEnabled(machineLearning)) {
|
||||||
return JobStatus.SKIPPED;
|
return JobStatus.SKIPPED;
|
||||||
}
|
}
|
||||||
@ -519,7 +518,7 @@ export class PersonService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async handleGeneratePersonThumbnail(data: IEntityJob): Promise<JobStatus> {
|
async handleGeneratePersonThumbnail(data: IEntityJob): Promise<JobStatus> {
|
||||||
const { machineLearning, metadata, image } = await this.configCore.getConfig({ withCache: true });
|
const { machineLearning, metadata, image } = await this.getConfig({ withCache: true });
|
||||||
if (!isFacialRecognitionEnabled(machineLearning) && !isFaceImportEnabled(metadata)) {
|
if (!isFacialRecognitionEnabled(machineLearning) && !isFaceImportEnabled(metadata)) {
|
||||||
return JobStatus.SKIPPED;
|
return JobStatus.SKIPPED;
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import { BadRequestException, Inject, Injectable } from '@nestjs/common';
|
import { BadRequestException, Inject, Injectable } from '@nestjs/common';
|
||||||
import { SystemConfigCore } from 'src/cores/system-config.core';
|
|
||||||
import { AssetMapOptions, AssetResponseDto, mapAsset } from 'src/dtos/asset-response.dto';
|
import { AssetMapOptions, AssetResponseDto, mapAsset } from 'src/dtos/asset-response.dto';
|
||||||
import { AuthDto } from 'src/dtos/auth.dto';
|
import { AuthDto } from 'src/dtos/auth.dto';
|
||||||
import { PersonResponseDto } from 'src/dtos/person.dto';
|
import { PersonResponseDto } from 'src/dtos/person.dto';
|
||||||
@ -24,13 +23,12 @@ import { IPartnerRepository } from 'src/interfaces/partner.interface';
|
|||||||
import { IPersonRepository } from 'src/interfaces/person.interface';
|
import { IPersonRepository } from 'src/interfaces/person.interface';
|
||||||
import { ISearchRepository, SearchExploreItem } from 'src/interfaces/search.interface';
|
import { ISearchRepository, SearchExploreItem } from 'src/interfaces/search.interface';
|
||||||
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
|
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
|
||||||
|
import { BaseService } from 'src/services/base.service';
|
||||||
import { getMyPartnerIds } from 'src/utils/asset.util';
|
import { getMyPartnerIds } from 'src/utils/asset.util';
|
||||||
import { isSmartSearchEnabled } from 'src/utils/misc';
|
import { isSmartSearchEnabled } from 'src/utils/misc';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class SearchService {
|
export class SearchService extends BaseService {
|
||||||
private configCore: SystemConfigCore;
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(ISystemMetadataRepository) systemMetadataRepository: ISystemMetadataRepository,
|
@Inject(ISystemMetadataRepository) systemMetadataRepository: ISystemMetadataRepository,
|
||||||
@Inject(IMachineLearningRepository) private machineLearning: IMachineLearningRepository,
|
@Inject(IMachineLearningRepository) private machineLearning: IMachineLearningRepository,
|
||||||
@ -38,10 +36,10 @@ export class SearchService {
|
|||||||
@Inject(ISearchRepository) private searchRepository: ISearchRepository,
|
@Inject(ISearchRepository) private searchRepository: ISearchRepository,
|
||||||
@Inject(IAssetRepository) private assetRepository: IAssetRepository,
|
@Inject(IAssetRepository) private assetRepository: IAssetRepository,
|
||||||
@Inject(IPartnerRepository) private partnerRepository: IPartnerRepository,
|
@Inject(IPartnerRepository) private partnerRepository: IPartnerRepository,
|
||||||
@Inject(ILoggerRepository) private logger: ILoggerRepository,
|
@Inject(ILoggerRepository) logger: ILoggerRepository,
|
||||||
) {
|
) {
|
||||||
|
super(systemMetadataRepository, logger);
|
||||||
this.logger.setContext(SearchService.name);
|
this.logger.setContext(SearchService.name);
|
||||||
this.configCore = SystemConfigCore.create(systemMetadataRepository, logger);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async searchPerson(auth: AuthDto, dto: SearchPeopleDto): Promise<PersonResponseDto[]> {
|
async searchPerson(auth: AuthDto, dto: SearchPeopleDto): Promise<PersonResponseDto[]> {
|
||||||
@ -101,7 +99,7 @@ export class SearchService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async searchSmart(auth: AuthDto, dto: SmartSearchDto): Promise<SearchResponseDto> {
|
async searchSmart(auth: AuthDto, dto: SmartSearchDto): Promise<SearchResponseDto> {
|
||||||
const { machineLearning } = await this.configCore.getConfig({ withCache: false });
|
const { machineLearning } = await this.getConfig({ withCache: false });
|
||||||
if (!isSmartSearchEnabled(machineLearning)) {
|
if (!isSmartSearchEnabled(machineLearning)) {
|
||||||
throw new BadRequestException('Smart search is not enabled');
|
throw new BadRequestException('Smart search is not enabled');
|
||||||
}
|
}
|
||||||
|
@ -176,9 +176,9 @@ describe(ServerService.name, () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('getConfig', () => {
|
describe('getSystemConfig', () => {
|
||||||
it('should respond the server configuration', async () => {
|
it('should respond the server configuration', async () => {
|
||||||
await expect(sut.getConfig()).resolves.toEqual({
|
await expect(sut.getSystemConfig()).resolves.toEqual({
|
||||||
loginPageMessage: '',
|
loginPageMessage: '',
|
||||||
oauthButtonText: 'Login with OAuth',
|
oauthButtonText: 'Login with OAuth',
|
||||||
trashDays: 30,
|
trashDays: 30,
|
||||||
|
@ -2,7 +2,6 @@ import { BadRequestException, Inject, Injectable, NotFoundException } from '@nes
|
|||||||
import { getBuildMetadata, getServerLicensePublicKey } from 'src/config';
|
import { getBuildMetadata, getServerLicensePublicKey } from 'src/config';
|
||||||
import { serverVersion } from 'src/constants';
|
import { serverVersion } from 'src/constants';
|
||||||
import { StorageCore } from 'src/cores/storage.core';
|
import { StorageCore } from 'src/cores/storage.core';
|
||||||
import { SystemConfigCore } from 'src/cores/system-config.core';
|
|
||||||
import { OnEvent } from 'src/decorators';
|
import { OnEvent } from 'src/decorators';
|
||||||
import { LicenseKeyDto, LicenseResponseDto } from 'src/dtos/license.dto';
|
import { LicenseKeyDto, LicenseResponseDto } from 'src/dtos/license.dto';
|
||||||
import {
|
import {
|
||||||
@ -22,24 +21,24 @@ import { IServerInfoRepository } from 'src/interfaces/server-info.interface';
|
|||||||
import { IStorageRepository } from 'src/interfaces/storage.interface';
|
import { IStorageRepository } from 'src/interfaces/storage.interface';
|
||||||
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
|
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
|
||||||
import { IUserRepository, UserStatsQueryResponse } from 'src/interfaces/user.interface';
|
import { IUserRepository, UserStatsQueryResponse } from 'src/interfaces/user.interface';
|
||||||
|
import { BaseService } from 'src/services/base.service';
|
||||||
import { asHumanReadable } from 'src/utils/bytes';
|
import { asHumanReadable } from 'src/utils/bytes';
|
||||||
|
import { isUsingConfigFile } from 'src/utils/config';
|
||||||
import { mimeTypes } from 'src/utils/mime-types';
|
import { mimeTypes } from 'src/utils/mime-types';
|
||||||
import { isDuplicateDetectionEnabled, isFacialRecognitionEnabled, isSmartSearchEnabled } from 'src/utils/misc';
|
import { isDuplicateDetectionEnabled, isFacialRecognitionEnabled, isSmartSearchEnabled } from 'src/utils/misc';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ServerService {
|
export class ServerService extends BaseService {
|
||||||
private configCore: SystemConfigCore;
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(IUserRepository) private userRepository: IUserRepository,
|
@Inject(IUserRepository) private userRepository: IUserRepository,
|
||||||
@Inject(IStorageRepository) private storageRepository: IStorageRepository,
|
@Inject(IStorageRepository) private storageRepository: IStorageRepository,
|
||||||
@Inject(ISystemMetadataRepository) private systemMetadataRepository: ISystemMetadataRepository,
|
@Inject(ISystemMetadataRepository) systemMetadataRepository: ISystemMetadataRepository,
|
||||||
@Inject(IServerInfoRepository) private serverInfoRepository: IServerInfoRepository,
|
@Inject(IServerInfoRepository) private serverInfoRepository: IServerInfoRepository,
|
||||||
@Inject(ILoggerRepository) private logger: ILoggerRepository,
|
@Inject(ILoggerRepository) logger: ILoggerRepository,
|
||||||
@Inject(ICryptoRepository) private cryptoRepository: ICryptoRepository,
|
@Inject(ICryptoRepository) private cryptoRepository: ICryptoRepository,
|
||||||
) {
|
) {
|
||||||
|
super(systemMetadataRepository, logger);
|
||||||
this.logger.setContext(ServerService.name);
|
this.logger.setContext(ServerService.name);
|
||||||
this.configCore = SystemConfigCore.create(systemMetadataRepository, this.logger);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@OnEvent({ name: 'app.bootstrap' })
|
@OnEvent({ name: 'app.bootstrap' })
|
||||||
@ -91,7 +90,7 @@ export class ServerService {
|
|||||||
|
|
||||||
async getFeatures(): Promise<ServerFeaturesDto> {
|
async getFeatures(): Promise<ServerFeaturesDto> {
|
||||||
const { reverseGeocoding, metadata, map, machineLearning, trash, oauth, passwordLogin, notifications } =
|
const { reverseGeocoding, metadata, map, machineLearning, trash, oauth, passwordLogin, notifications } =
|
||||||
await this.configCore.getConfig({ withCache: false });
|
await this.getConfig({ withCache: false });
|
||||||
|
|
||||||
return {
|
return {
|
||||||
smartSearch: isSmartSearchEnabled(machineLearning),
|
smartSearch: isSmartSearchEnabled(machineLearning),
|
||||||
@ -106,18 +105,18 @@ export class ServerService {
|
|||||||
oauth: oauth.enabled,
|
oauth: oauth.enabled,
|
||||||
oauthAutoLaunch: oauth.autoLaunch,
|
oauthAutoLaunch: oauth.autoLaunch,
|
||||||
passwordLogin: passwordLogin.enabled,
|
passwordLogin: passwordLogin.enabled,
|
||||||
configFile: this.configCore.isUsingConfigFile(),
|
configFile: isUsingConfigFile(),
|
||||||
email: notifications.smtp.enabled,
|
email: notifications.smtp.enabled,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async getTheme() {
|
async getTheme() {
|
||||||
const { theme } = await this.configCore.getConfig({ withCache: false });
|
const { theme } = await this.getConfig({ withCache: false });
|
||||||
return theme;
|
return theme;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getConfig(): Promise<ServerConfigDto> {
|
async getSystemConfig(): Promise<ServerConfigDto> {
|
||||||
const config = await this.configCore.getConfig({ withCache: false });
|
const config = await this.getConfig({ withCache: false });
|
||||||
const isInitialized = await this.userRepository.hasAdmin();
|
const isInitialized = await this.userRepository.hasAdmin();
|
||||||
const onboarding = await this.systemMetadataRepository.get(SystemMetadataKey.ADMIN_ONBOARDING);
|
const onboarding = await this.systemMetadataRepository.get(SystemMetadataKey.ADMIN_ONBOARDING);
|
||||||
|
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import { BadRequestException, ForbiddenException, Inject, Injectable, UnauthorizedException } from '@nestjs/common';
|
import { BadRequestException, ForbiddenException, Inject, Injectable, UnauthorizedException } from '@nestjs/common';
|
||||||
import { DEFAULT_EXTERNAL_DOMAIN } from 'src/constants';
|
import { DEFAULT_EXTERNAL_DOMAIN } from 'src/constants';
|
||||||
import { SystemConfigCore } from 'src/cores/system-config.core';
|
|
||||||
import { AssetIdErrorReason, AssetIdsResponseDto } from 'src/dtos/asset-ids.response.dto';
|
import { AssetIdErrorReason, AssetIdsResponseDto } from 'src/dtos/asset-ids.response.dto';
|
||||||
import { AssetIdsDto } from 'src/dtos/asset.dto';
|
import { AssetIdsDto } from 'src/dtos/asset.dto';
|
||||||
import { AuthDto } from 'src/dtos/auth.dto';
|
import { AuthDto } from 'src/dtos/auth.dto';
|
||||||
@ -20,22 +19,21 @@ import { ICryptoRepository } from 'src/interfaces/crypto.interface';
|
|||||||
import { ILoggerRepository } from 'src/interfaces/logger.interface';
|
import { ILoggerRepository } from 'src/interfaces/logger.interface';
|
||||||
import { ISharedLinkRepository } from 'src/interfaces/shared-link.interface';
|
import { ISharedLinkRepository } from 'src/interfaces/shared-link.interface';
|
||||||
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
|
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
|
||||||
|
import { BaseService } from 'src/services/base.service';
|
||||||
import { checkAccess, requireAccess } from 'src/utils/access';
|
import { checkAccess, requireAccess } from 'src/utils/access';
|
||||||
import { OpenGraphTags } from 'src/utils/misc';
|
import { OpenGraphTags } from 'src/utils/misc';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class SharedLinkService {
|
export class SharedLinkService extends BaseService {
|
||||||
private configCore: SystemConfigCore;
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(IAccessRepository) private access: IAccessRepository,
|
@Inject(IAccessRepository) private access: IAccessRepository,
|
||||||
@Inject(ICryptoRepository) private cryptoRepository: ICryptoRepository,
|
@Inject(ICryptoRepository) private cryptoRepository: ICryptoRepository,
|
||||||
@Inject(ILoggerRepository) private logger: ILoggerRepository,
|
@Inject(ILoggerRepository) logger: ILoggerRepository,
|
||||||
@Inject(ISharedLinkRepository) private repository: ISharedLinkRepository,
|
@Inject(ISharedLinkRepository) private repository: ISharedLinkRepository,
|
||||||
@Inject(ISystemMetadataRepository) systemMetadataRepository: ISystemMetadataRepository,
|
@Inject(ISystemMetadataRepository) systemMetadataRepository: ISystemMetadataRepository,
|
||||||
) {
|
) {
|
||||||
|
super(systemMetadataRepository, logger);
|
||||||
this.logger.setContext(SharedLinkService.name);
|
this.logger.setContext(SharedLinkService.name);
|
||||||
this.configCore = SystemConfigCore.create(systemMetadataRepository, this.logger);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getAll(auth: AuthDto): Promise<SharedLinkResponseDto[]> {
|
getAll(auth: AuthDto): Promise<SharedLinkResponseDto[]> {
|
||||||
@ -195,7 +193,7 @@ export class SharedLinkService {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const config = await this.configCore.getConfig({ withCache: true });
|
const config = await this.getConfig({ withCache: true });
|
||||||
const sharedLink = await this.findOrFail(auth.sharedLink.userId, auth.sharedLink.id);
|
const sharedLink = await this.findOrFail(auth.sharedLink.userId, auth.sharedLink.id);
|
||||||
const assetId = sharedLink.album?.albumThumbnailAssetId || sharedLink.assets[0]?.id;
|
const assetId = sharedLink.album?.albumThumbnailAssetId || sharedLink.assets[0]?.id;
|
||||||
const assetCount = sharedLink.assets.length > 0 ? sharedLink.assets.length : sharedLink.album?.assets.length || 0;
|
const assetCount = sharedLink.assets.length > 0 ? sharedLink.assets.length : sharedLink.album?.assets.length || 0;
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import { SystemConfig } from 'src/config';
|
import { SystemConfig } from 'src/config';
|
||||||
import { SystemConfigCore } from 'src/cores/system-config.core';
|
|
||||||
import { OnEvent } from 'src/decorators';
|
import { OnEvent } from 'src/decorators';
|
||||||
import { IAssetRepository, WithoutProperty } from 'src/interfaces/asset.interface';
|
import { IAssetRepository, WithoutProperty } from 'src/interfaces/asset.interface';
|
||||||
import { DatabaseLock, IDatabaseRepository } from 'src/interfaces/database.interface';
|
import { DatabaseLock, IDatabaseRepository } from 'src/interfaces/database.interface';
|
||||||
@ -18,14 +17,13 @@ import { ILoggerRepository } from 'src/interfaces/logger.interface';
|
|||||||
import { IMachineLearningRepository } from 'src/interfaces/machine-learning.interface';
|
import { IMachineLearningRepository } from 'src/interfaces/machine-learning.interface';
|
||||||
import { ISearchRepository } from 'src/interfaces/search.interface';
|
import { ISearchRepository } from 'src/interfaces/search.interface';
|
||||||
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
|
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
|
||||||
|
import { BaseService } from 'src/services/base.service';
|
||||||
import { getAssetFiles } from 'src/utils/asset.util';
|
import { getAssetFiles } from 'src/utils/asset.util';
|
||||||
import { getCLIPModelInfo, isSmartSearchEnabled } from 'src/utils/misc';
|
import { getCLIPModelInfo, isSmartSearchEnabled } from 'src/utils/misc';
|
||||||
import { usePagination } from 'src/utils/pagination';
|
import { usePagination } from 'src/utils/pagination';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class SmartInfoService {
|
export class SmartInfoService extends BaseService {
|
||||||
private configCore: SystemConfigCore;
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(IAssetRepository) private assetRepository: IAssetRepository,
|
@Inject(IAssetRepository) private assetRepository: IAssetRepository,
|
||||||
@Inject(IDatabaseRepository) private databaseRepository: IDatabaseRepository,
|
@Inject(IDatabaseRepository) private databaseRepository: IDatabaseRepository,
|
||||||
@ -33,10 +31,10 @@ export class SmartInfoService {
|
|||||||
@Inject(IMachineLearningRepository) private machineLearning: IMachineLearningRepository,
|
@Inject(IMachineLearningRepository) private machineLearning: IMachineLearningRepository,
|
||||||
@Inject(ISearchRepository) private repository: ISearchRepository,
|
@Inject(ISearchRepository) private repository: ISearchRepository,
|
||||||
@Inject(ISystemMetadataRepository) systemMetadataRepository: ISystemMetadataRepository,
|
@Inject(ISystemMetadataRepository) systemMetadataRepository: ISystemMetadataRepository,
|
||||||
@Inject(ILoggerRepository) private logger: ILoggerRepository,
|
@Inject(ILoggerRepository) logger: ILoggerRepository,
|
||||||
) {
|
) {
|
||||||
|
super(systemMetadataRepository, logger);
|
||||||
this.logger.setContext(SmartInfoService.name);
|
this.logger.setContext(SmartInfoService.name);
|
||||||
this.configCore = SystemConfigCore.create(systemMetadataRepository, this.logger);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@OnEvent({ name: 'app.bootstrap' })
|
@OnEvent({ name: 'app.bootstrap' })
|
||||||
@ -45,7 +43,7 @@ export class SmartInfoService {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const config = await this.configCore.getConfig({ withCache: false });
|
const config = await this.getConfig({ withCache: false });
|
||||||
await this.init(config);
|
await this.init(config);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -106,7 +104,7 @@ export class SmartInfoService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async handleQueueEncodeClip({ force }: IBaseJob): Promise<JobStatus> {
|
async handleQueueEncodeClip({ force }: IBaseJob): Promise<JobStatus> {
|
||||||
const { machineLearning } = await this.configCore.getConfig({ withCache: false });
|
const { machineLearning } = await this.getConfig({ withCache: false });
|
||||||
if (!isSmartSearchEnabled(machineLearning)) {
|
if (!isSmartSearchEnabled(machineLearning)) {
|
||||||
return JobStatus.SKIPPED;
|
return JobStatus.SKIPPED;
|
||||||
}
|
}
|
||||||
@ -131,7 +129,7 @@ export class SmartInfoService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async handleEncodeClip({ id }: IEntityJob): Promise<JobStatus> {
|
async handleEncodeClip({ id }: IEntityJob): Promise<JobStatus> {
|
||||||
const { machineLearning } = await this.configCore.getConfig({ withCache: true });
|
const { machineLearning } = await this.getConfig({ withCache: true });
|
||||||
if (!isSmartSearchEnabled(machineLearning)) {
|
if (!isSmartSearchEnabled(machineLearning)) {
|
||||||
return JobStatus.SKIPPED;
|
return JobStatus.SKIPPED;
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,6 @@ import {
|
|||||||
supportedYearTokens,
|
supportedYearTokens,
|
||||||
} from 'src/constants';
|
} from 'src/constants';
|
||||||
import { StorageCore } from 'src/cores/storage.core';
|
import { StorageCore } from 'src/cores/storage.core';
|
||||||
import { SystemConfigCore } from 'src/cores/system-config.core';
|
|
||||||
import { OnEvent } from 'src/decorators';
|
import { OnEvent } from 'src/decorators';
|
||||||
import { AssetEntity } from 'src/entities/asset.entity';
|
import { AssetEntity } from 'src/entities/asset.entity';
|
||||||
import { AssetPathType, AssetType, StorageFolder } from 'src/enum';
|
import { AssetPathType, AssetType, StorageFolder } from 'src/enum';
|
||||||
@ -29,6 +28,7 @@ import { IPersonRepository } from 'src/interfaces/person.interface';
|
|||||||
import { IStorageRepository } from 'src/interfaces/storage.interface';
|
import { IStorageRepository } from 'src/interfaces/storage.interface';
|
||||||
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
|
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
|
||||||
import { IUserRepository } from 'src/interfaces/user.interface';
|
import { IUserRepository } from 'src/interfaces/user.interface';
|
||||||
|
import { BaseService } from 'src/services/base.service';
|
||||||
import { getLivePhotoMotionFilename } from 'src/utils/file';
|
import { getLivePhotoMotionFilename } from 'src/utils/file';
|
||||||
import { usePagination } from 'src/utils/pagination';
|
import { usePagination } from 'src/utils/pagination';
|
||||||
|
|
||||||
@ -45,8 +45,7 @@ interface RenderMetadata {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class StorageTemplateService {
|
export class StorageTemplateService extends BaseService {
|
||||||
private configCore: SystemConfigCore;
|
|
||||||
private storageCore: StorageCore;
|
private storageCore: StorageCore;
|
||||||
private _template: {
|
private _template: {
|
||||||
compiled: HandlebarsTemplateDelegate<any>;
|
compiled: HandlebarsTemplateDelegate<any>;
|
||||||
@ -71,10 +70,10 @@ export class StorageTemplateService {
|
|||||||
@Inject(IUserRepository) private userRepository: IUserRepository,
|
@Inject(IUserRepository) private userRepository: IUserRepository,
|
||||||
@Inject(ICryptoRepository) cryptoRepository: ICryptoRepository,
|
@Inject(ICryptoRepository) cryptoRepository: ICryptoRepository,
|
||||||
@Inject(IDatabaseRepository) private databaseRepository: IDatabaseRepository,
|
@Inject(IDatabaseRepository) private databaseRepository: IDatabaseRepository,
|
||||||
@Inject(ILoggerRepository) private logger: ILoggerRepository,
|
@Inject(ILoggerRepository) logger: ILoggerRepository,
|
||||||
) {
|
) {
|
||||||
|
super(systemMetadataRepository, logger);
|
||||||
this.logger.setContext(StorageTemplateService.name);
|
this.logger.setContext(StorageTemplateService.name);
|
||||||
this.configCore = SystemConfigCore.create(systemMetadataRepository, this.logger);
|
|
||||||
this.storageCore = StorageCore.create(
|
this.storageCore = StorageCore.create(
|
||||||
assetRepository,
|
assetRepository,
|
||||||
cryptoRepository,
|
cryptoRepository,
|
||||||
@ -117,7 +116,7 @@ export class StorageTemplateService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async handleMigrationSingle({ id }: IEntityJob): Promise<JobStatus> {
|
async handleMigrationSingle({ id }: IEntityJob): Promise<JobStatus> {
|
||||||
const config = await this.configCore.getConfig({ withCache: true });
|
const config = await this.getConfig({ withCache: true });
|
||||||
const storageTemplateEnabled = config.storageTemplate.enabled;
|
const storageTemplateEnabled = config.storageTemplate.enabled;
|
||||||
if (!storageTemplateEnabled) {
|
if (!storageTemplateEnabled) {
|
||||||
return JobStatus.SKIPPED;
|
return JobStatus.SKIPPED;
|
||||||
@ -147,7 +146,7 @@ export class StorageTemplateService {
|
|||||||
|
|
||||||
async handleMigration(): Promise<JobStatus> {
|
async handleMigration(): Promise<JobStatus> {
|
||||||
this.logger.log('Starting storage template migration');
|
this.logger.log('Starting storage template migration');
|
||||||
const { storageTemplate } = await this.configCore.getConfig({ withCache: true });
|
const { storageTemplate } = await this.getConfig({ withCache: true });
|
||||||
const { enabled } = storageTemplate;
|
const { enabled } = storageTemplate;
|
||||||
if (!enabled) {
|
if (!enabled) {
|
||||||
this.logger.log('Storage template migration disabled, skipping');
|
this.logger.log('Storage template migration disabled, skipping');
|
||||||
|
@ -216,7 +216,7 @@ describe(SystemConfigService.name, () => {
|
|||||||
it('should return the default config', async () => {
|
it('should return the default config', async () => {
|
||||||
systemMock.get.mockResolvedValue({});
|
systemMock.get.mockResolvedValue({});
|
||||||
|
|
||||||
await expect(sut.getConfig()).resolves.toEqual(defaults);
|
await expect(sut.getSystemConfig()).resolves.toEqual(defaults);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should merge the overrides', async () => {
|
it('should merge the overrides', async () => {
|
||||||
@ -227,7 +227,7 @@ describe(SystemConfigService.name, () => {
|
|||||||
user: { deleteDelay: 15 },
|
user: { deleteDelay: 15 },
|
||||||
});
|
});
|
||||||
|
|
||||||
await expect(sut.getConfig()).resolves.toEqual(updatedConfig);
|
await expect(sut.getSystemConfig()).resolves.toEqual(updatedConfig);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should load the config from a json file', async () => {
|
it('should load the config from a json file', async () => {
|
||||||
@ -235,7 +235,7 @@ describe(SystemConfigService.name, () => {
|
|||||||
|
|
||||||
systemMock.readFile.mockResolvedValue(JSON.stringify(partialConfig));
|
systemMock.readFile.mockResolvedValue(JSON.stringify(partialConfig));
|
||||||
|
|
||||||
await expect(sut.getConfig()).resolves.toEqual(updatedConfig);
|
await expect(sut.getSystemConfig()).resolves.toEqual(updatedConfig);
|
||||||
|
|
||||||
expect(systemMock.readFile).toHaveBeenCalledWith('immich-config.json');
|
expect(systemMock.readFile).toHaveBeenCalledWith('immich-config.json');
|
||||||
});
|
});
|
||||||
@ -245,7 +245,7 @@ describe(SystemConfigService.name, () => {
|
|||||||
|
|
||||||
systemMock.readFile.mockResolvedValue(`{ "ffmpeg2": true, "ffmpeg2": true }`);
|
systemMock.readFile.mockResolvedValue(`{ "ffmpeg2": true, "ffmpeg2": true }`);
|
||||||
|
|
||||||
await expect(sut.getConfig()).rejects.toBeInstanceOf(Error);
|
await expect(sut.getSystemConfig()).rejects.toBeInstanceOf(Error);
|
||||||
|
|
||||||
expect(systemMock.readFile).toHaveBeenCalledWith('immich-config.json');
|
expect(systemMock.readFile).toHaveBeenCalledWith('immich-config.json');
|
||||||
expect(loggerMock.error).toHaveBeenCalledTimes(2);
|
expect(loggerMock.error).toHaveBeenCalledTimes(2);
|
||||||
@ -269,7 +269,7 @@ describe(SystemConfigService.name, () => {
|
|||||||
`;
|
`;
|
||||||
systemMock.readFile.mockResolvedValue(partialConfig);
|
systemMock.readFile.mockResolvedValue(partialConfig);
|
||||||
|
|
||||||
await expect(sut.getConfig()).resolves.toEqual(updatedConfig);
|
await expect(sut.getSystemConfig()).resolves.toEqual(updatedConfig);
|
||||||
|
|
||||||
expect(systemMock.readFile).toHaveBeenCalledWith('immich-config.yaml');
|
expect(systemMock.readFile).toHaveBeenCalledWith('immich-config.yaml');
|
||||||
});
|
});
|
||||||
@ -278,7 +278,7 @@ describe(SystemConfigService.name, () => {
|
|||||||
process.env.IMMICH_CONFIG_FILE = 'immich-config.json';
|
process.env.IMMICH_CONFIG_FILE = 'immich-config.json';
|
||||||
systemMock.readFile.mockResolvedValue(JSON.stringify({}));
|
systemMock.readFile.mockResolvedValue(JSON.stringify({}));
|
||||||
|
|
||||||
await expect(sut.getConfig()).resolves.toEqual(defaults);
|
await expect(sut.getSystemConfig()).resolves.toEqual(defaults);
|
||||||
|
|
||||||
expect(systemMock.readFile).toHaveBeenCalledWith('immich-config.json');
|
expect(systemMock.readFile).toHaveBeenCalledWith('immich-config.json');
|
||||||
});
|
});
|
||||||
@ -288,7 +288,7 @@ describe(SystemConfigService.name, () => {
|
|||||||
const partialConfig = { machineLearning: { url: 'immich_machine_learning' } };
|
const partialConfig = { machineLearning: { url: 'immich_machine_learning' } };
|
||||||
systemMock.readFile.mockResolvedValue(JSON.stringify(partialConfig));
|
systemMock.readFile.mockResolvedValue(JSON.stringify(partialConfig));
|
||||||
|
|
||||||
const config = await sut.getConfig();
|
const config = await sut.getSystemConfig();
|
||||||
expect(config.machineLearning.url).toEqual('immich_machine_learning');
|
expect(config.machineLearning.url).toEqual('immich_machine_learning');
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -304,7 +304,7 @@ describe(SystemConfigService.name, () => {
|
|||||||
const partialConfig = { server: { externalDomain } };
|
const partialConfig = { server: { externalDomain } };
|
||||||
systemMock.readFile.mockResolvedValue(JSON.stringify(partialConfig));
|
systemMock.readFile.mockResolvedValue(JSON.stringify(partialConfig));
|
||||||
|
|
||||||
const config = await sut.getConfig();
|
const config = await sut.getSystemConfig();
|
||||||
expect(config.server.externalDomain).toEqual(result ?? 'https://demo.immich.app');
|
expect(config.server.externalDomain).toEqual(result ?? 'https://demo.immich.app');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -316,7 +316,7 @@ describe(SystemConfigService.name, () => {
|
|||||||
`;
|
`;
|
||||||
systemMock.readFile.mockResolvedValue(partialConfig);
|
systemMock.readFile.mockResolvedValue(partialConfig);
|
||||||
|
|
||||||
await sut.getConfig();
|
await sut.getSystemConfig();
|
||||||
expect(loggerMock.warn).toHaveBeenCalled();
|
expect(loggerMock.warn).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -335,10 +335,10 @@ describe(SystemConfigService.name, () => {
|
|||||||
systemMock.readFile.mockResolvedValue(JSON.stringify(test.config));
|
systemMock.readFile.mockResolvedValue(JSON.stringify(test.config));
|
||||||
|
|
||||||
if (test.warn) {
|
if (test.warn) {
|
||||||
await sut.getConfig();
|
await sut.getSystemConfig();
|
||||||
expect(loggerMock.warn).toHaveBeenCalled();
|
expect(loggerMock.warn).toHaveBeenCalled();
|
||||||
} else {
|
} else {
|
||||||
await expect(sut.getConfig()).rejects.toBeInstanceOf(Error);
|
await expect(sut.getSystemConfig()).rejects.toBeInstanceOf(Error);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -382,7 +382,7 @@ describe(SystemConfigService.name, () => {
|
|||||||
describe('updateConfig', () => {
|
describe('updateConfig', () => {
|
||||||
it('should update the config and emit an event', async () => {
|
it('should update the config and emit an event', async () => {
|
||||||
systemMock.get.mockResolvedValue(partialConfig);
|
systemMock.get.mockResolvedValue(partialConfig);
|
||||||
await expect(sut.updateConfig(updatedConfig)).resolves.toEqual(updatedConfig);
|
await expect(sut.updateSystemConfig(updatedConfig)).resolves.toEqual(updatedConfig);
|
||||||
expect(eventMock.emit).toHaveBeenCalledWith(
|
expect(eventMock.emit).toHaveBeenCalledWith(
|
||||||
'config.update',
|
'config.update',
|
||||||
expect.objectContaining({ oldConfig: expect.any(Object), newConfig: updatedConfig }),
|
expect.objectContaining({ oldConfig: expect.any(Object), newConfig: updatedConfig }),
|
||||||
@ -392,7 +392,7 @@ describe(SystemConfigService.name, () => {
|
|||||||
it('should throw an error if a config file is in use', async () => {
|
it('should throw an error if a config file is in use', async () => {
|
||||||
process.env.IMMICH_CONFIG_FILE = 'immich-config.json';
|
process.env.IMMICH_CONFIG_FILE = 'immich-config.json';
|
||||||
systemMock.readFile.mockResolvedValue(JSON.stringify({}));
|
systemMock.readFile.mockResolvedValue(JSON.stringify({}));
|
||||||
await expect(sut.updateConfig(defaults)).rejects.toBeInstanceOf(BadRequestException);
|
await expect(sut.updateSystemConfig(defaults)).rejects.toBeInstanceOf(BadRequestException);
|
||||||
expect(systemMock.set).not.toHaveBeenCalled();
|
expect(systemMock.set).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -12,36 +12,35 @@ import {
|
|||||||
supportedWeekTokens,
|
supportedWeekTokens,
|
||||||
supportedYearTokens,
|
supportedYearTokens,
|
||||||
} from 'src/constants';
|
} from 'src/constants';
|
||||||
import { SystemConfigCore } from 'src/cores/system-config.core';
|
|
||||||
import { OnEvent } from 'src/decorators';
|
import { OnEvent } from 'src/decorators';
|
||||||
import { SystemConfigDto, SystemConfigTemplateStorageOptionDto, mapConfig } from 'src/dtos/system-config.dto';
|
import { SystemConfigDto, SystemConfigTemplateStorageOptionDto, mapConfig } from 'src/dtos/system-config.dto';
|
||||||
import { LogLevel } from 'src/enum';
|
import { LogLevel } from 'src/enum';
|
||||||
import { ArgOf, IEventRepository } from 'src/interfaces/event.interface';
|
import { ArgOf, IEventRepository } from 'src/interfaces/event.interface';
|
||||||
import { ILoggerRepository } from 'src/interfaces/logger.interface';
|
import { ILoggerRepository } from 'src/interfaces/logger.interface';
|
||||||
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
|
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
|
||||||
|
import { BaseService } from 'src/services/base.service';
|
||||||
|
import { clearConfigCache, isUsingConfigFile } from 'src/utils/config';
|
||||||
import { toPlainObject } from 'src/utils/object';
|
import { toPlainObject } from 'src/utils/object';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class SystemConfigService {
|
export class SystemConfigService extends BaseService {
|
||||||
private core: SystemConfigCore;
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(ISystemMetadataRepository) repository: ISystemMetadataRepository,
|
@Inject(ISystemMetadataRepository) systemMetadataRepository: ISystemMetadataRepository,
|
||||||
@Inject(IEventRepository) private eventRepository: IEventRepository,
|
@Inject(IEventRepository) private eventRepository: IEventRepository,
|
||||||
@Inject(ILoggerRepository) private logger: ILoggerRepository,
|
@Inject(ILoggerRepository) logger: ILoggerRepository,
|
||||||
) {
|
) {
|
||||||
|
super(systemMetadataRepository, logger);
|
||||||
this.logger.setContext(SystemConfigService.name);
|
this.logger.setContext(SystemConfigService.name);
|
||||||
this.core = SystemConfigCore.create(repository, this.logger);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@OnEvent({ name: 'app.bootstrap', priority: -100 })
|
@OnEvent({ name: 'app.bootstrap', priority: -100 })
|
||||||
async onBootstrap() {
|
async onBootstrap() {
|
||||||
const config = await this.core.getConfig({ withCache: false });
|
const config = await this.getConfig({ withCache: false });
|
||||||
await this.eventRepository.emit('config.update', { newConfig: config });
|
await this.eventRepository.emit('config.update', { newConfig: config });
|
||||||
}
|
}
|
||||||
|
|
||||||
async getConfig(): Promise<SystemConfigDto> {
|
async getSystemConfig(): Promise<SystemConfigDto> {
|
||||||
const config = await this.core.getConfig({ withCache: false });
|
const config = await this.getConfig({ withCache: false });
|
||||||
return mapConfig(config);
|
return mapConfig(config);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -57,7 +56,7 @@ export class SystemConfigService {
|
|||||||
this.logger.setLogLevel(level);
|
this.logger.setLogLevel(level);
|
||||||
this.logger.log(`LogLevel=${level} ${envLevel ? '(set via IMMICH_LOG_LEVEL)' : '(set via system config)'}`);
|
this.logger.log(`LogLevel=${level} ${envLevel ? '(set via IMMICH_LOG_LEVEL)' : '(set via system config)'}`);
|
||||||
// TODO only do this if the event is a socket.io event
|
// TODO only do this if the event is a socket.io event
|
||||||
this.core.invalidateCache();
|
clearConfigCache();
|
||||||
}
|
}
|
||||||
|
|
||||||
@OnEvent({ name: 'config.validate' })
|
@OnEvent({ name: 'config.validate' })
|
||||||
@ -67,12 +66,12 @@ export class SystemConfigService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateConfig(dto: SystemConfigDto): Promise<SystemConfigDto> {
|
async updateSystemConfig(dto: SystemConfigDto): Promise<SystemConfigDto> {
|
||||||
if (this.core.isUsingConfigFile()) {
|
if (isUsingConfigFile()) {
|
||||||
throw new BadRequestException('Cannot update configuration while IMMICH_CONFIG_FILE is in use');
|
throw new BadRequestException('Cannot update configuration while IMMICH_CONFIG_FILE is in use');
|
||||||
}
|
}
|
||||||
|
|
||||||
const oldConfig = await this.core.getConfig({ withCache: false });
|
const oldConfig = await this.getConfig({ withCache: false });
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.eventRepository.emit('config.validate', { newConfig: toPlainObject(dto), oldConfig });
|
await this.eventRepository.emit('config.validate', { newConfig: toPlainObject(dto), oldConfig });
|
||||||
@ -81,7 +80,7 @@ export class SystemConfigService {
|
|||||||
throw new BadRequestException(error instanceof Error ? error.message : error);
|
throw new BadRequestException(error instanceof Error ? error.message : error);
|
||||||
}
|
}
|
||||||
|
|
||||||
const newConfig = await this.core.updateConfig(dto);
|
const newConfig = await this.updateConfig(dto);
|
||||||
|
|
||||||
await this.eventRepository.emit('config.update', { newConfig, oldConfig });
|
await this.eventRepository.emit('config.update', { newConfig, oldConfig });
|
||||||
|
|
||||||
@ -104,7 +103,7 @@ export class SystemConfigService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async getCustomCss(): Promise<string> {
|
async getCustomCss(): Promise<string> {
|
||||||
const { theme } = await this.core.getConfig({ withCache: false });
|
const { theme } = await this.getConfig({ withCache: false });
|
||||||
return theme.customCss;
|
return theme.customCss;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import { BadRequestException, ForbiddenException, Inject, Injectable } from '@nestjs/common';
|
import { BadRequestException, ForbiddenException, Inject, Injectable } from '@nestjs/common';
|
||||||
import { SALT_ROUNDS } from 'src/constants';
|
import { SALT_ROUNDS } from 'src/constants';
|
||||||
import { UserCore } from 'src/cores/user.core';
|
|
||||||
import { AuthDto } from 'src/dtos/auth.dto';
|
import { AuthDto } from 'src/dtos/auth.dto';
|
||||||
import { UserPreferencesResponseDto, UserPreferencesUpdateDto, mapPreferences } from 'src/dtos/user-preferences.dto';
|
import { UserPreferencesResponseDto, UserPreferencesUpdateDto, mapPreferences } from 'src/dtos/user-preferences.dto';
|
||||||
import {
|
import {
|
||||||
@ -19,11 +18,10 @@ import { IJobRepository, JobName } from 'src/interfaces/job.interface';
|
|||||||
import { ILoggerRepository } from 'src/interfaces/logger.interface';
|
import { ILoggerRepository } from 'src/interfaces/logger.interface';
|
||||||
import { IUserRepository, UserFindOptions } from 'src/interfaces/user.interface';
|
import { IUserRepository, UserFindOptions } from 'src/interfaces/user.interface';
|
||||||
import { getPreferences, getPreferencesPartial, mergePreferences } from 'src/utils/preferences';
|
import { getPreferences, getPreferencesPartial, mergePreferences } from 'src/utils/preferences';
|
||||||
|
import { createUser } from 'src/utils/user';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class UserAdminService {
|
export class UserAdminService {
|
||||||
private userCore: UserCore;
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(IAlbumRepository) private albumRepository: IAlbumRepository,
|
@Inject(IAlbumRepository) private albumRepository: IAlbumRepository,
|
||||||
@Inject(ICryptoRepository) private cryptoRepository: ICryptoRepository,
|
@Inject(ICryptoRepository) private cryptoRepository: ICryptoRepository,
|
||||||
@ -32,7 +30,6 @@ export class UserAdminService {
|
|||||||
@Inject(IUserRepository) private userRepository: IUserRepository,
|
@Inject(IUserRepository) private userRepository: IUserRepository,
|
||||||
@Inject(ILoggerRepository) private logger: ILoggerRepository,
|
@Inject(ILoggerRepository) private logger: ILoggerRepository,
|
||||||
) {
|
) {
|
||||||
this.userCore = UserCore.create(cryptoRepository, userRepository);
|
|
||||||
this.logger.setContext(UserAdminService.name);
|
this.logger.setContext(UserAdminService.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -43,7 +40,7 @@ export class UserAdminService {
|
|||||||
|
|
||||||
async create(dto: UserAdminCreateDto): Promise<UserAdminResponseDto> {
|
async create(dto: UserAdminCreateDto): Promise<UserAdminResponseDto> {
|
||||||
const { notify, ...rest } = dto;
|
const { notify, ...rest } = dto;
|
||||||
const user = await this.userCore.createUser(rest);
|
const user = await createUser({ userRepo: this.userRepository, cryptoRepo: this.cryptoRepository }, rest);
|
||||||
|
|
||||||
await this.eventRepository.emit('user.signup', {
|
await this.eventRepository.emit('user.signup', {
|
||||||
notify: !!notify,
|
notify: !!notify,
|
||||||
|
@ -3,7 +3,6 @@ import { DateTime } from 'luxon';
|
|||||||
import { getClientLicensePublicKey, getServerLicensePublicKey } from 'src/config';
|
import { getClientLicensePublicKey, getServerLicensePublicKey } from 'src/config';
|
||||||
import { SALT_ROUNDS } from 'src/constants';
|
import { SALT_ROUNDS } from 'src/constants';
|
||||||
import { StorageCore } from 'src/cores/storage.core';
|
import { StorageCore } from 'src/cores/storage.core';
|
||||||
import { SystemConfigCore } from 'src/cores/system-config.core';
|
|
||||||
import { AuthDto } from 'src/dtos/auth.dto';
|
import { AuthDto } from 'src/dtos/auth.dto';
|
||||||
import { LicenseKeyDto, LicenseResponseDto } from 'src/dtos/license.dto';
|
import { LicenseKeyDto, LicenseResponseDto } from 'src/dtos/license.dto';
|
||||||
import { UserPreferencesResponseDto, UserPreferencesUpdateDto, mapPreferences } from 'src/dtos/user-preferences.dto';
|
import { UserPreferencesResponseDto, UserPreferencesUpdateDto, mapPreferences } from 'src/dtos/user-preferences.dto';
|
||||||
@ -19,13 +18,12 @@ import { ILoggerRepository } from 'src/interfaces/logger.interface';
|
|||||||
import { IStorageRepository } from 'src/interfaces/storage.interface';
|
import { IStorageRepository } from 'src/interfaces/storage.interface';
|
||||||
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
|
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
|
||||||
import { IUserRepository, UserFindOptions } from 'src/interfaces/user.interface';
|
import { IUserRepository, UserFindOptions } from 'src/interfaces/user.interface';
|
||||||
|
import { BaseService } from 'src/services/base.service';
|
||||||
import { ImmichFileResponse } from 'src/utils/file';
|
import { ImmichFileResponse } from 'src/utils/file';
|
||||||
import { getPreferences, getPreferencesPartial, mergePreferences } from 'src/utils/preferences';
|
import { getPreferences, getPreferencesPartial, mergePreferences } from 'src/utils/preferences';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class UserService {
|
export class UserService extends BaseService {
|
||||||
private configCore: SystemConfigCore;
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(IAlbumRepository) private albumRepository: IAlbumRepository,
|
@Inject(IAlbumRepository) private albumRepository: IAlbumRepository,
|
||||||
@Inject(ICryptoRepository) private cryptoRepository: ICryptoRepository,
|
@Inject(ICryptoRepository) private cryptoRepository: ICryptoRepository,
|
||||||
@ -33,10 +31,10 @@ export class UserService {
|
|||||||
@Inject(IStorageRepository) private storageRepository: IStorageRepository,
|
@Inject(IStorageRepository) private storageRepository: IStorageRepository,
|
||||||
@Inject(ISystemMetadataRepository) systemMetadataRepository: ISystemMetadataRepository,
|
@Inject(ISystemMetadataRepository) systemMetadataRepository: ISystemMetadataRepository,
|
||||||
@Inject(IUserRepository) private userRepository: IUserRepository,
|
@Inject(IUserRepository) private userRepository: IUserRepository,
|
||||||
@Inject(ILoggerRepository) private logger: ILoggerRepository,
|
@Inject(ILoggerRepository) logger: ILoggerRepository,
|
||||||
) {
|
) {
|
||||||
|
super(systemMetadataRepository, logger);
|
||||||
this.logger.setContext(UserService.name);
|
this.logger.setContext(UserService.name);
|
||||||
this.configCore = SystemConfigCore.create(systemMetadataRepository, this.logger);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async search(): Promise<UserResponseDto[]> {
|
async search(): Promise<UserResponseDto[]> {
|
||||||
@ -189,7 +187,7 @@ export class UserService {
|
|||||||
|
|
||||||
async handleUserDeleteCheck(): Promise<JobStatus> {
|
async handleUserDeleteCheck(): Promise<JobStatus> {
|
||||||
const users = await this.userRepository.getDeletedUsers();
|
const users = await this.userRepository.getDeletedUsers();
|
||||||
const config = await this.configCore.getConfig({ withCache: false });
|
const config = await this.getConfig({ withCache: false });
|
||||||
await this.jobRepository.queueAll(
|
await this.jobRepository.queueAll(
|
||||||
users.flatMap((user) =>
|
users.flatMap((user) =>
|
||||||
this.isReadyForDeletion(user, config.user.deleteDelay)
|
this.isReadyForDeletion(user, config.user.deleteDelay)
|
||||||
@ -201,7 +199,7 @@ export class UserService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async handleUserDelete({ id, force }: IEntityJob): Promise<JobStatus> {
|
async handleUserDelete({ id, force }: IEntityJob): Promise<JobStatus> {
|
||||||
const config = await this.configCore.getConfig({ withCache: false });
|
const config = await this.getConfig({ withCache: false });
|
||||||
const user = await this.userRepository.get(id, { withDeleted: true });
|
const user = await this.userRepository.get(id, { withDeleted: true });
|
||||||
if (!user) {
|
if (!user) {
|
||||||
return JobStatus.FAILED;
|
return JobStatus.FAILED;
|
||||||
|
@ -2,16 +2,16 @@ import { Inject, Injectable } from '@nestjs/common';
|
|||||||
import { DateTime } from 'luxon';
|
import { DateTime } from 'luxon';
|
||||||
import semver, { SemVer } from 'semver';
|
import semver, { SemVer } from 'semver';
|
||||||
import { isDev, serverVersion } from 'src/constants';
|
import { isDev, serverVersion } from 'src/constants';
|
||||||
import { SystemConfigCore } from 'src/cores/system-config.core';
|
|
||||||
import { OnEvent } from 'src/decorators';
|
import { OnEvent } from 'src/decorators';
|
||||||
import { ReleaseNotification, ServerVersionResponseDto } from 'src/dtos/server.dto';
|
import { ReleaseNotification, ServerVersionResponseDto } from 'src/dtos/server.dto';
|
||||||
import { VersionCheckMetadata } from 'src/entities/system-metadata.entity';
|
import { VersionCheckMetadata } from 'src/entities/system-metadata.entity';
|
||||||
import { SystemMetadataKey } from 'src/enum';
|
import { SystemMetadataKey } from 'src/enum';
|
||||||
import { ArgOf, ClientEvent, IEventRepository } from 'src/interfaces/event.interface';
|
import { ArgOf, IEventRepository } from 'src/interfaces/event.interface';
|
||||||
import { IJobRepository, JobName, JobStatus } from 'src/interfaces/job.interface';
|
import { IJobRepository, JobName, JobStatus } from 'src/interfaces/job.interface';
|
||||||
import { ILoggerRepository } from 'src/interfaces/logger.interface';
|
import { ILoggerRepository } from 'src/interfaces/logger.interface';
|
||||||
import { IServerInfoRepository } from 'src/interfaces/server-info.interface';
|
import { IServerInfoRepository } from 'src/interfaces/server-info.interface';
|
||||||
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
|
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
|
||||||
|
import { BaseService } from 'src/services/base.service';
|
||||||
|
|
||||||
const asNotification = ({ checkedAt, releaseVersion }: VersionCheckMetadata): ReleaseNotification => {
|
const asNotification = ({ checkedAt, releaseVersion }: VersionCheckMetadata): ReleaseNotification => {
|
||||||
return {
|
return {
|
||||||
@ -23,18 +23,16 @@ const asNotification = ({ checkedAt, releaseVersion }: VersionCheckMetadata): Re
|
|||||||
};
|
};
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class VersionService {
|
export class VersionService extends BaseService {
|
||||||
private configCore: SystemConfigCore;
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(IEventRepository) private eventRepository: IEventRepository,
|
@Inject(IEventRepository) private eventRepository: IEventRepository,
|
||||||
@Inject(IJobRepository) private jobRepository: IJobRepository,
|
@Inject(IJobRepository) private jobRepository: IJobRepository,
|
||||||
@Inject(IServerInfoRepository) private repository: IServerInfoRepository,
|
@Inject(IServerInfoRepository) private repository: IServerInfoRepository,
|
||||||
@Inject(ISystemMetadataRepository) private systemMetadataRepository: ISystemMetadataRepository,
|
@Inject(ISystemMetadataRepository) systemMetadataRepository: ISystemMetadataRepository,
|
||||||
@Inject(ILoggerRepository) private logger: ILoggerRepository,
|
@Inject(ILoggerRepository) logger: ILoggerRepository,
|
||||||
) {
|
) {
|
||||||
|
super(systemMetadataRepository, logger);
|
||||||
this.logger.setContext(VersionService.name);
|
this.logger.setContext(VersionService.name);
|
||||||
this.configCore = SystemConfigCore.create(systemMetadataRepository, this.logger);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@OnEvent({ name: 'app.bootstrap' })
|
@OnEvent({ name: 'app.bootstrap' })
|
||||||
@ -58,7 +56,7 @@ export class VersionService {
|
|||||||
return JobStatus.SKIPPED;
|
return JobStatus.SKIPPED;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { newVersionCheck } = await this.configCore.getConfig({ withCache: true });
|
const { newVersionCheck } = await this.getConfig({ withCache: true });
|
||||||
if (!newVersionCheck.enabled) {
|
if (!newVersionCheck.enabled) {
|
||||||
return JobStatus.SKIPPED;
|
return JobStatus.SKIPPED;
|
||||||
}
|
}
|
||||||
@ -80,7 +78,7 @@ export class VersionService {
|
|||||||
|
|
||||||
if (semver.gt(releaseVersion, serverVersion)) {
|
if (semver.gt(releaseVersion, serverVersion)) {
|
||||||
this.logger.log(`Found ${releaseVersion}, released at ${new Date(publishedAt).toLocaleString()}`);
|
this.logger.log(`Found ${releaseVersion}, released at ${new Date(publishedAt).toLocaleString()}`);
|
||||||
this.eventRepository.clientBroadcast(ClientEvent.NEW_RELEASE, asNotification(metadata));
|
this.eventRepository.clientBroadcast('on_new_release', asNotification(metadata));
|
||||||
}
|
}
|
||||||
} catch (error: Error | any) {
|
} catch (error: Error | any) {
|
||||||
this.logger.warn(`Unable to run version check: ${error}`, error?.stack);
|
this.logger.warn(`Unable to run version check: ${error}`, error?.stack);
|
||||||
@ -92,10 +90,10 @@ export class VersionService {
|
|||||||
|
|
||||||
@OnEvent({ name: 'websocket.connect' })
|
@OnEvent({ name: 'websocket.connect' })
|
||||||
async onWebsocketConnection({ userId }: ArgOf<'websocket.connect'>) {
|
async onWebsocketConnection({ userId }: ArgOf<'websocket.connect'>) {
|
||||||
this.eventRepository.clientSend(ClientEvent.SERVER_VERSION, userId, serverVersion);
|
this.eventRepository.clientSend('on_server_version', userId, serverVersion);
|
||||||
const metadata = await this.systemMetadataRepository.get(SystemMetadataKey.VERSION_CHECK_STATE);
|
const metadata = await this.systemMetadataRepository.get(SystemMetadataKey.VERSION_CHECK_STATE);
|
||||||
if (metadata) {
|
if (metadata) {
|
||||||
this.eventRepository.clientSend(ClientEvent.NEW_RELEASE, userId, asNotification(metadata));
|
this.eventRepository.clientSend('on_new_release', userId, asNotification(metadata));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
129
server/src/utils/config.ts
Normal file
129
server/src/utils/config.ts
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
import AsyncLock from 'async-lock';
|
||||||
|
import { plainToInstance } from 'class-transformer';
|
||||||
|
import { validate } from 'class-validator';
|
||||||
|
import { load as loadYaml } from 'js-yaml';
|
||||||
|
import * as _ from 'lodash';
|
||||||
|
import { SystemConfig, defaults } from 'src/config';
|
||||||
|
import { SystemConfigDto } from 'src/dtos/system-config.dto';
|
||||||
|
import { SystemMetadataKey } from 'src/enum';
|
||||||
|
import { DatabaseLock } from 'src/interfaces/database.interface';
|
||||||
|
import { ILoggerRepository } from 'src/interfaces/logger.interface';
|
||||||
|
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
|
||||||
|
import { getKeysDeep, unsetDeep } from 'src/utils/misc';
|
||||||
|
import { DeepPartial } from 'typeorm';
|
||||||
|
|
||||||
|
export type SystemConfigValidator = (config: SystemConfig, newConfig: SystemConfig) => void | Promise<void>;
|
||||||
|
|
||||||
|
type RepoDeps = {
|
||||||
|
metadataRepo: ISystemMetadataRepository;
|
||||||
|
logger: ILoggerRepository;
|
||||||
|
};
|
||||||
|
|
||||||
|
const asyncLock = new AsyncLock();
|
||||||
|
let config: SystemConfig | null = null;
|
||||||
|
let lastUpdated: number | null = null;
|
||||||
|
|
||||||
|
export const clearConfigCache = () => {
|
||||||
|
config = null;
|
||||||
|
lastUpdated = null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const isUsingConfigFile = () => {
|
||||||
|
return !!process.env.IMMICH_CONFIG_FILE;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getConfig = async (repos: RepoDeps, { withCache }: { withCache: boolean }): Promise<SystemConfig> => {
|
||||||
|
if (!withCache || !config) {
|
||||||
|
const timestamp = lastUpdated;
|
||||||
|
await asyncLock.acquire(DatabaseLock[DatabaseLock.GetSystemConfig], async () => {
|
||||||
|
if (timestamp === lastUpdated) {
|
||||||
|
config = await buildConfig(repos);
|
||||||
|
lastUpdated = Date.now();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return config!;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const updateConfig = async (repos: RepoDeps, newConfig: SystemConfig): Promise<SystemConfig> => {
|
||||||
|
const { metadataRepo } = repos;
|
||||||
|
// get the difference between the new config and the default config
|
||||||
|
const partialConfig: DeepPartial<SystemConfig> = {};
|
||||||
|
for (const property of getKeysDeep(defaults)) {
|
||||||
|
const newValue = _.get(newConfig, property);
|
||||||
|
const isEmpty = newValue === undefined || newValue === null || newValue === '';
|
||||||
|
const defaultValue = _.get(defaults, property);
|
||||||
|
const isEqual = newValue === defaultValue || _.isEqual(newValue, defaultValue);
|
||||||
|
|
||||||
|
if (isEmpty || isEqual) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
_.set(partialConfig, property, newValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
await metadataRepo.set(SystemMetadataKey.SYSTEM_CONFIG, partialConfig);
|
||||||
|
|
||||||
|
return getConfig(repos, { withCache: false });
|
||||||
|
};
|
||||||
|
|
||||||
|
const loadFromFile = async ({ metadataRepo, logger }: RepoDeps, filepath: string) => {
|
||||||
|
try {
|
||||||
|
const file = await metadataRepo.readFile(filepath);
|
||||||
|
return loadYaml(file.toString()) as unknown;
|
||||||
|
} catch (error: Error | any) {
|
||||||
|
logger.error(`Unable to load configuration file: ${filepath}`);
|
||||||
|
logger.error(error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const buildConfig = async (repos: RepoDeps) => {
|
||||||
|
const { metadataRepo, logger } = repos;
|
||||||
|
|
||||||
|
// load partial
|
||||||
|
const partial = isUsingConfigFile()
|
||||||
|
? await loadFromFile(repos, process.env.IMMICH_CONFIG_FILE as string)
|
||||||
|
: await metadataRepo.get(SystemMetadataKey.SYSTEM_CONFIG);
|
||||||
|
|
||||||
|
// merge with defaults
|
||||||
|
const config = _.cloneDeep(defaults);
|
||||||
|
for (const property of getKeysDeep(partial)) {
|
||||||
|
_.set(config, property, _.get(partial, property));
|
||||||
|
}
|
||||||
|
|
||||||
|
// check for extra properties
|
||||||
|
const unknownKeys = _.cloneDeep(config);
|
||||||
|
for (const property of getKeysDeep(defaults)) {
|
||||||
|
unsetDeep(unknownKeys, property);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_.isEmpty(unknownKeys)) {
|
||||||
|
logger.warn(`Unknown keys found: ${JSON.stringify(unknownKeys, null, 2)}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// validate full config
|
||||||
|
const errors = await validate(plainToInstance(SystemConfigDto, config));
|
||||||
|
if (errors.length > 0) {
|
||||||
|
if (isUsingConfigFile()) {
|
||||||
|
throw new Error(`Invalid value(s) in file: ${errors}`);
|
||||||
|
} else {
|
||||||
|
logger.error('Validation error', errors);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.server.externalDomain.length > 0) {
|
||||||
|
config.server.externalDomain = new URL(config.server.externalDomain).origin;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!config.ffmpeg.acceptedVideoCodecs.includes(config.ffmpeg.targetVideoCodec)) {
|
||||||
|
config.ffmpeg.acceptedVideoCodecs.push(config.ffmpeg.targetVideoCodec);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!config.ffmpeg.acceptedAudioCodecs.includes(config.ffmpeg.targetAudioCodec)) {
|
||||||
|
config.ffmpeg.acceptedAudioCodecs.push(config.ffmpeg.targetAudioCodec);
|
||||||
|
}
|
||||||
|
|
||||||
|
return config;
|
||||||
|
};
|
35
server/src/utils/user.ts
Normal file
35
server/src/utils/user.ts
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import { BadRequestException } from '@nestjs/common';
|
||||||
|
import sanitize from 'sanitize-filename';
|
||||||
|
import { SALT_ROUNDS } from 'src/constants';
|
||||||
|
import { UserEntity } from 'src/entities/user.entity';
|
||||||
|
import { ICryptoRepository } from 'src/interfaces/crypto.interface';
|
||||||
|
import { IUserRepository } from 'src/interfaces/user.interface';
|
||||||
|
|
||||||
|
type RepoDeps = { userRepo: IUserRepository; cryptoRepo: ICryptoRepository };
|
||||||
|
|
||||||
|
export const createUser = async (
|
||||||
|
{ userRepo, cryptoRepo }: RepoDeps,
|
||||||
|
dto: Partial<UserEntity> & { email: string },
|
||||||
|
): Promise<UserEntity> => {
|
||||||
|
const user = await userRepo.getByEmail(dto.email);
|
||||||
|
if (user) {
|
||||||
|
throw new BadRequestException('User exists');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!dto.isAdmin) {
|
||||||
|
const localAdmin = await userRepo.getAdmin();
|
||||||
|
if (!localAdmin) {
|
||||||
|
throw new BadRequestException('The first registered account must the administrator.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const payload: Partial<UserEntity> = { ...dto };
|
||||||
|
if (payload.password) {
|
||||||
|
payload.password = await cryptoRepo.hashBcrypt(payload.password, SALT_ROUNDS);
|
||||||
|
}
|
||||||
|
if (payload.storageLabel) {
|
||||||
|
payload.storageLabel = sanitize(payload.storageLabel.replaceAll('.', ''));
|
||||||
|
}
|
||||||
|
|
||||||
|
return userRepo.create(payload);
|
||||||
|
};
|
@ -5,8 +5,8 @@ export const newEventRepositoryMock = (): Mocked<IEventRepository> => {
|
|||||||
return {
|
return {
|
||||||
on: vitest.fn() as any,
|
on: vitest.fn() as any,
|
||||||
emit: vitest.fn() as any,
|
emit: vitest.fn() as any,
|
||||||
clientSend: vitest.fn(),
|
clientSend: vitest.fn() as any,
|
||||||
clientBroadcast: vitest.fn(),
|
clientBroadcast: vitest.fn() as any,
|
||||||
serverSend: vitest.fn(),
|
serverSend: vitest.fn(),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -1,12 +1,9 @@
|
|||||||
import { SystemConfigCore } from 'src/cores/system-config.core';
|
|
||||||
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
|
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
|
||||||
|
import { clearConfigCache } from 'src/utils/config';
|
||||||
import { Mocked, vitest } from 'vitest';
|
import { Mocked, vitest } from 'vitest';
|
||||||
|
|
||||||
export const newSystemMetadataRepositoryMock = (reset = true): Mocked<ISystemMetadataRepository> => {
|
export const newSystemMetadataRepositoryMock = (): Mocked<ISystemMetadataRepository> => {
|
||||||
if (reset) {
|
clearConfigCache();
|
||||||
SystemConfigCore.reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
get: vitest.fn() as any,
|
get: vitest.fn() as any,
|
||||||
set: vitest.fn(),
|
set: vitest.fn(),
|
||||||
|
@ -1,12 +1,7 @@
|
|||||||
import { UserCore } from 'src/cores/user.core';
|
|
||||||
import { IUserRepository } from 'src/interfaces/user.interface';
|
import { IUserRepository } from 'src/interfaces/user.interface';
|
||||||
import { Mocked, vitest } from 'vitest';
|
import { Mocked, vitest } from 'vitest';
|
||||||
|
|
||||||
export const newUserRepositoryMock = (reset = true): Mocked<IUserRepository> => {
|
export const newUserRepositoryMock = (): Mocked<IUserRepository> => {
|
||||||
if (reset) {
|
|
||||||
UserCore.reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
get: vitest.fn(),
|
get: vitest.fn(),
|
||||||
getAdmin: vitest.fn(),
|
getAdmin: vitest.fn(),
|
||||||
|
277
web/package-lock.json
generated
277
web/package-lock.json
generated
@ -80,7 +80,7 @@
|
|||||||
"@oazapfts/runtime": "^1.0.2"
|
"@oazapfts/runtime": "^1.0.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^20.16.5",
|
"@types/node": "^20.16.9",
|
||||||
"typescript": "^5.3.3"
|
"typescript": "^5.3.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -657,6 +657,16 @@
|
|||||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@eslint/core": {
|
||||||
|
"version": "0.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.6.0.tgz",
|
||||||
|
"integrity": "sha512-8I2Q8ykA4J0x0o7cg67FPVnehcqWTBehu/lmY+bolPFHGjh49YzGBMXTvpqVgEbBdvNCSxj6iFgiIyHzf03lzg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"engines": {
|
||||||
|
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@eslint/eslintrc": {
|
"node_modules/@eslint/eslintrc": {
|
||||||
"version": "3.1.0",
|
"version": "3.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.1.0.tgz",
|
||||||
@ -726,9 +736,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@eslint/js": {
|
"node_modules/@eslint/js": {
|
||||||
"version": "9.10.0",
|
"version": "9.11.1",
|
||||||
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.10.0.tgz",
|
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.11.1.tgz",
|
||||||
"integrity": "sha512-fuXtbiP5GWIn8Fz+LWoOMVf/Jxm+aajZYkhi6CuEm4SxymFM+eUWzbO9qXT+L0iCkL5+KGYMCSGxo686H19S1g==",
|
"integrity": "sha512-/qu+TWz8WwPWc7/HcIJKi+c+MOm46GdVaSlTTQcaqaL53+GsoA6MxWp5PtTx48qbSP7ylM1Kn7nhvkugfJvRSA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
@ -746,9 +756,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@eslint/plugin-kit": {
|
"node_modules/@eslint/plugin-kit": {
|
||||||
"version": "0.1.0",
|
"version": "0.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.0.tgz",
|
||||||
"integrity": "sha512-autAXT203ixhqei9xt+qkYOvY8l6LAFIdT2UXc/RPNeUVfqRF1BV94GTJyVPFKT8nFM6MyVJhjLj9E8JWvf5zQ==",
|
"integrity": "sha512-vH9PiIMMwvhCx31Af3HiGzsVNULDbyVkHXwlemn/B0TFj/00ho3y55efXrUZTfQipxoHC5u4xq6zblww1zm1Ig==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@ -759,9 +769,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@faker-js/faker": {
|
"node_modules/@faker-js/faker": {
|
||||||
"version": "9.0.1",
|
"version": "9.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-9.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-9.0.2.tgz",
|
||||||
"integrity": "sha512-4mDeYIgM3By7X6t5E6eYwLAa+2h4DeZDF7thhzIg6XB76jeEvMwadYAMCFJL/R4AnEBcAUO9+gL0vhy3s+qvZA==",
|
"integrity": "sha512-nI/FP30ZGXb+UaR7yXawVTH40NVKXPIx0tA3GKjkKLjorqBoMAeq4iSEacl8mJmpVhOCDa0vYHwYDmOOcFMrYw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
@ -1575,30 +1585,30 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@photo-sphere-viewer/core": {
|
"node_modules/@photo-sphere-viewer/core": {
|
||||||
"version": "5.10.0",
|
"version": "5.10.1",
|
||||||
"resolved": "https://registry.npmjs.org/@photo-sphere-viewer/core/-/core-5.10.0.tgz",
|
"resolved": "https://registry.npmjs.org/@photo-sphere-viewer/core/-/core-5.10.1.tgz",
|
||||||
"integrity": "sha512-VRXPTq6bFxkf5YqmijXLTKV+K05ZZHDoccXBsoxWsS9wKA5gCfKLlebRxQBBazmnwr1KmsLbAIsd1ZGbLdhI4g==",
|
"integrity": "sha512-xdvPbfQqLl8tggqNDMcczbQGNQHbA4N9u3RCuzYzhrN2PBE2ihL5TsH85smkH4GcRrJKypSzwXF7rDrQhcs7aQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"three": "^0.167.0"
|
"three": "^0.168.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@photo-sphere-viewer/equirectangular-video-adapter": {
|
"node_modules/@photo-sphere-viewer/equirectangular-video-adapter": {
|
||||||
"version": "5.10.0",
|
"version": "5.10.1",
|
||||||
"resolved": "https://registry.npmjs.org/@photo-sphere-viewer/equirectangular-video-adapter/-/equirectangular-video-adapter-5.10.0.tgz",
|
"resolved": "https://registry.npmjs.org/@photo-sphere-viewer/equirectangular-video-adapter/-/equirectangular-video-adapter-5.10.1.tgz",
|
||||||
"integrity": "sha512-qnxPDsT88x+mdiC//+/VnFe9z4ANz/xHjQbtm9zMNwVOryZdo1hhHtp3k80+ZnkBCF3tA8TkcEpiLifhWrHWPw==",
|
"integrity": "sha512-159vPvsqPJ2prxnWpRH8QSaT+QlCOIac8XmhmkfwBoMqTZ8B1P+JWyuKYaDpqz4Bk/K+kncVBMNVvdro6bIccw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@photo-sphere-viewer/core": "5.10.0"
|
"@photo-sphere-viewer/core": "5.10.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@photo-sphere-viewer/video-plugin": {
|
"node_modules/@photo-sphere-viewer/video-plugin": {
|
||||||
"version": "5.10.0",
|
"version": "5.10.1",
|
||||||
"resolved": "https://registry.npmjs.org/@photo-sphere-viewer/video-plugin/-/video-plugin-5.10.0.tgz",
|
"resolved": "https://registry.npmjs.org/@photo-sphere-viewer/video-plugin/-/video-plugin-5.10.1.tgz",
|
||||||
"integrity": "sha512-Zym35YVdNwx6Kng/P9vOJ/UmLkarJNmjYUsP2hllQVl6Xy/iznfzE9NLCr5gJ3fmLT7ICkuz5dLfdDUxOl6Btw==",
|
"integrity": "sha512-rIoXvHuuB+qg8I0wqbe4S3YUXiliN4kTeV5Xx70dyPFtroGVEKpbvBZ8ygy029np0xALMkeKvi6cYiB40Lj1Pw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@photo-sphere-viewer/core": "5.10.0"
|
"@photo-sphere-viewer/core": "5.10.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@pkgjs/parseargs": {
|
"node_modules/@pkgjs/parseargs": {
|
||||||
@ -2240,6 +2250,13 @@
|
|||||||
"@types/geojson": "*"
|
"@types/geojson": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/json-schema": {
|
||||||
|
"version": "7.0.15",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
|
||||||
|
"integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@types/justified-layout": {
|
"node_modules/@types/justified-layout": {
|
||||||
"version": "4.1.4",
|
"version": "4.1.4",
|
||||||
"resolved": "https://registry.npmjs.org/@types/justified-layout/-/justified-layout-4.1.4.tgz",
|
"resolved": "https://registry.npmjs.org/@types/justified-layout/-/justified-layout-4.1.4.tgz",
|
||||||
@ -2318,17 +2335,17 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/eslint-plugin": {
|
"node_modules/@typescript-eslint/eslint-plugin": {
|
||||||
"version": "8.6.0",
|
"version": "8.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.7.0.tgz",
|
||||||
"integrity": "sha512-UOaz/wFowmoh2G6Mr9gw60B1mm0MzUtm6Ic8G2yM1Le6gyj5Loi/N+O5mocugRGY+8OeeKmkMmbxNqUCq3B4Sg==",
|
"integrity": "sha512-RIHOoznhA3CCfSTFiB6kBGLQtB/sox+pJ6jeFu6FxJvqL8qRxq/FfGO/UhsGgQM9oGdXkV4xUgli+dt26biB6A==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@eslint-community/regexpp": "^4.10.0",
|
"@eslint-community/regexpp": "^4.10.0",
|
||||||
"@typescript-eslint/scope-manager": "8.6.0",
|
"@typescript-eslint/scope-manager": "8.7.0",
|
||||||
"@typescript-eslint/type-utils": "8.6.0",
|
"@typescript-eslint/type-utils": "8.7.0",
|
||||||
"@typescript-eslint/utils": "8.6.0",
|
"@typescript-eslint/utils": "8.7.0",
|
||||||
"@typescript-eslint/visitor-keys": "8.6.0",
|
"@typescript-eslint/visitor-keys": "8.7.0",
|
||||||
"graphemer": "^1.4.0",
|
"graphemer": "^1.4.0",
|
||||||
"ignore": "^5.3.1",
|
"ignore": "^5.3.1",
|
||||||
"natural-compare": "^1.4.0",
|
"natural-compare": "^1.4.0",
|
||||||
@ -2352,16 +2369,16 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/parser": {
|
"node_modules/@typescript-eslint/parser": {
|
||||||
"version": "8.6.0",
|
"version": "8.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.7.0.tgz",
|
||||||
"integrity": "sha512-eQcbCuA2Vmw45iGfcyG4y6rS7BhWfz9MQuk409WD47qMM+bKCGQWXxvoOs1DUp+T7UBMTtRTVT+kXr7Sh4O9Ow==",
|
"integrity": "sha512-lN0btVpj2unxHlNYLI//BQ7nzbMJYBVQX5+pbNXvGYazdlgYonMn4AhhHifQ+J4fGRYA/m1DjaQjx+fDetqBOQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "BSD-2-Clause",
|
"license": "BSD-2-Clause",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/scope-manager": "8.6.0",
|
"@typescript-eslint/scope-manager": "8.7.0",
|
||||||
"@typescript-eslint/types": "8.6.0",
|
"@typescript-eslint/types": "8.7.0",
|
||||||
"@typescript-eslint/typescript-estree": "8.6.0",
|
"@typescript-eslint/typescript-estree": "8.7.0",
|
||||||
"@typescript-eslint/visitor-keys": "8.6.0",
|
"@typescript-eslint/visitor-keys": "8.7.0",
|
||||||
"debug": "^4.3.4"
|
"debug": "^4.3.4"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
@ -2381,14 +2398,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/scope-manager": {
|
"node_modules/@typescript-eslint/scope-manager": {
|
||||||
"version": "8.6.0",
|
"version": "8.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.7.0.tgz",
|
||||||
"integrity": "sha512-ZuoutoS5y9UOxKvpc/GkvF4cuEmpokda4wRg64JEia27wX+PysIE9q+lzDtlHHgblwUWwo5/Qn+/WyTUvDwBHw==",
|
"integrity": "sha512-87rC0k3ZlDOuz82zzXRtQ7Akv3GKhHs0ti4YcbAJtaomllXoSO8hi7Ix3ccEvCd824dy9aIX+j3d2UMAfCtVpg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/types": "8.6.0",
|
"@typescript-eslint/types": "8.7.0",
|
||||||
"@typescript-eslint/visitor-keys": "8.6.0"
|
"@typescript-eslint/visitor-keys": "8.7.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||||
@ -2399,14 +2416,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/type-utils": {
|
"node_modules/@typescript-eslint/type-utils": {
|
||||||
"version": "8.6.0",
|
"version": "8.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.7.0.tgz",
|
||||||
"integrity": "sha512-dtePl4gsuenXVwC7dVNlb4mGDcKjDT/Ropsk4za/ouMBPplCLyznIaR+W65mvCvsyS97dymoBRrioEXI7k0XIg==",
|
"integrity": "sha512-tl0N0Mj3hMSkEYhLkjREp54OSb/FI6qyCzfiiclvJvOqre6hsZTGSnHtmFLDU8TIM62G7ygEa1bI08lcuRwEnQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/typescript-estree": "8.6.0",
|
"@typescript-eslint/typescript-estree": "8.7.0",
|
||||||
"@typescript-eslint/utils": "8.6.0",
|
"@typescript-eslint/utils": "8.7.0",
|
||||||
"debug": "^4.3.4",
|
"debug": "^4.3.4",
|
||||||
"ts-api-utils": "^1.3.0"
|
"ts-api-utils": "^1.3.0"
|
||||||
},
|
},
|
||||||
@ -2424,9 +2441,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/types": {
|
"node_modules/@typescript-eslint/types": {
|
||||||
"version": "8.6.0",
|
"version": "8.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.7.0.tgz",
|
||||||
"integrity": "sha512-rojqFZGd4MQxw33SrOy09qIDS8WEldM8JWtKQLAjf/X5mGSeEFh5ixQlxssMNyPslVIk9yzWqXCsV2eFhYrYUw==",
|
"integrity": "sha512-LLt4BLHFwSfASHSF2K29SZ+ZCsbQOM+LuarPjRUuHm+Qd09hSe3GCeaQbcCr+Mik+0QFRmep/FyZBO6fJ64U3w==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
@ -2438,14 +2455,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/typescript-estree": {
|
"node_modules/@typescript-eslint/typescript-estree": {
|
||||||
"version": "8.6.0",
|
"version": "8.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.7.0.tgz",
|
||||||
"integrity": "sha512-MOVAzsKJIPIlLK239l5s06YXjNqpKTVhBVDnqUumQJja5+Y94V3+4VUFRA0G60y2jNnTVwRCkhyGQpavfsbq/g==",
|
"integrity": "sha512-MC8nmcGHsmfAKxwnluTQpNqceniT8SteVwd2voYlmiSWGOtjvGXdPl17dYu2797GVscK30Z04WRM28CrKS9WOg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "BSD-2-Clause",
|
"license": "BSD-2-Clause",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/types": "8.6.0",
|
"@typescript-eslint/types": "8.7.0",
|
||||||
"@typescript-eslint/visitor-keys": "8.6.0",
|
"@typescript-eslint/visitor-keys": "8.7.0",
|
||||||
"debug": "^4.3.4",
|
"debug": "^4.3.4",
|
||||||
"fast-glob": "^3.3.2",
|
"fast-glob": "^3.3.2",
|
||||||
"is-glob": "^4.0.3",
|
"is-glob": "^4.0.3",
|
||||||
@ -2493,16 +2510,16 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/utils": {
|
"node_modules/@typescript-eslint/utils": {
|
||||||
"version": "8.6.0",
|
"version": "8.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.7.0.tgz",
|
||||||
"integrity": "sha512-eNp9cWnYf36NaOVjkEUznf6fEgVy1TWpE0o52e4wtojjBx7D1UV2WAWGzR+8Y5lVFtpMLPwNbC67T83DWSph4A==",
|
"integrity": "sha512-ZbdUdwsl2X/s3CiyAu3gOlfQzpbuG3nTWKPoIvAu1pu5r8viiJvv2NPN2AqArL35NCYtw/lrPPfM4gxrMLNLPw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@eslint-community/eslint-utils": "^4.4.0",
|
"@eslint-community/eslint-utils": "^4.4.0",
|
||||||
"@typescript-eslint/scope-manager": "8.6.0",
|
"@typescript-eslint/scope-manager": "8.7.0",
|
||||||
"@typescript-eslint/types": "8.6.0",
|
"@typescript-eslint/types": "8.7.0",
|
||||||
"@typescript-eslint/typescript-estree": "8.6.0"
|
"@typescript-eslint/typescript-estree": "8.7.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||||
@ -2516,13 +2533,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/visitor-keys": {
|
"node_modules/@typescript-eslint/visitor-keys": {
|
||||||
"version": "8.6.0",
|
"version": "8.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.7.0.tgz",
|
||||||
"integrity": "sha512-wapVFfZg9H0qOYh4grNVQiMklJGluQrOUiOhYRrQWhx7BY/+I1IYb8BczWNbbUpO+pqy0rDciv3lQH5E1bCLrg==",
|
"integrity": "sha512-b1tx0orFCCh/THWPQa2ZwWzvOeyzzp36vkJYOpVg0u8UVOIsfVrnuC9FqAw9gRKn+rG2VmWQ/zDJZzkxUnj/XQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/types": "8.6.0",
|
"@typescript-eslint/types": "8.7.0",
|
||||||
"eslint-visitor-keys": "^3.4.3"
|
"eslint-visitor-keys": "^3.4.3"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
@ -3581,35 +3598,16 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/engine.io-client": {
|
"node_modules/engine.io-client": {
|
||||||
"version": "6.5.2",
|
"version": "6.6.1",
|
||||||
"resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.5.2.tgz",
|
"resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.6.1.tgz",
|
||||||
"integrity": "sha512-CQZqbrpEYnrpGqC07a9dJDz4gePZUgTPMU3NKJPSeQOyw27Tst4Pl3FemKoFGAlHzgZmKjoRmiJvbWfhCXUlIg==",
|
"integrity": "sha512-aYuoak7I+R83M/BBPIOs2to51BmFIpC1wZe6zZzMrT2llVsHy5cvcmdsJgP2Qz6smHu+sD9oexiSUAVd8OfBPw==",
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@socket.io/component-emitter": "~3.1.0",
|
"@socket.io/component-emitter": "~3.1.0",
|
||||||
"debug": "~4.3.1",
|
"debug": "~4.3.1",
|
||||||
"engine.io-parser": "~5.2.1",
|
"engine.io-parser": "~5.2.1",
|
||||||
"ws": "~8.11.0",
|
"ws": "~8.17.1",
|
||||||
"xmlhttprequest-ssl": "~2.0.0"
|
"xmlhttprequest-ssl": "~2.1.1"
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/engine.io-client/node_modules/ws": {
|
|
||||||
"version": "8.11.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz",
|
|
||||||
"integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=10.0.0"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"bufferutil": "^4.0.1",
|
|
||||||
"utf-8-validate": "^5.0.2"
|
|
||||||
},
|
|
||||||
"peerDependenciesMeta": {
|
|
||||||
"bufferutil": {
|
|
||||||
"optional": true
|
|
||||||
},
|
|
||||||
"utf-8-validate": {
|
|
||||||
"optional": true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/engine.io-parser": {
|
"node_modules/engine.io-parser": {
|
||||||
@ -3750,21 +3748,24 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/eslint": {
|
"node_modules/eslint": {
|
||||||
"version": "9.10.0",
|
"version": "9.11.1",
|
||||||
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.10.0.tgz",
|
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.11.1.tgz",
|
||||||
"integrity": "sha512-Y4D0IgtBZfOcOUAIQTSXBKoNGfY0REGqHJG6+Q81vNippW5YlKjHFj4soMxamKK1NXHUWuBZTLdU3Km+L/pcHw==",
|
"integrity": "sha512-MobhYKIoAO1s1e4VUrgx1l1Sk2JBR/Gqjjgw8+mfgoLE2xwsHur4gdfTxyTgShrhvdVFTaJSgMiQBl1jv/AWxg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@eslint-community/eslint-utils": "^4.2.0",
|
"@eslint-community/eslint-utils": "^4.2.0",
|
||||||
"@eslint-community/regexpp": "^4.11.0",
|
"@eslint-community/regexpp": "^4.11.0",
|
||||||
"@eslint/config-array": "^0.18.0",
|
"@eslint/config-array": "^0.18.0",
|
||||||
|
"@eslint/core": "^0.6.0",
|
||||||
"@eslint/eslintrc": "^3.1.0",
|
"@eslint/eslintrc": "^3.1.0",
|
||||||
"@eslint/js": "9.10.0",
|
"@eslint/js": "9.11.1",
|
||||||
"@eslint/plugin-kit": "^0.1.0",
|
"@eslint/plugin-kit": "^0.2.0",
|
||||||
"@humanwhocodes/module-importer": "^1.0.1",
|
"@humanwhocodes/module-importer": "^1.0.1",
|
||||||
"@humanwhocodes/retry": "^0.3.0",
|
"@humanwhocodes/retry": "^0.3.0",
|
||||||
"@nodelib/fs.walk": "^1.2.8",
|
"@nodelib/fs.walk": "^1.2.8",
|
||||||
|
"@types/estree": "^1.0.6",
|
||||||
|
"@types/json-schema": "^7.0.15",
|
||||||
"ajv": "^6.12.4",
|
"ajv": "^6.12.4",
|
||||||
"chalk": "^4.0.0",
|
"chalk": "^4.0.0",
|
||||||
"cross-spawn": "^7.0.2",
|
"cross-spawn": "^7.0.2",
|
||||||
@ -3947,6 +3948,13 @@
|
|||||||
"url": "https://opencollective.com/eslint"
|
"url": "https://opencollective.com/eslint"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/eslint/node_modules/@types/estree": {
|
||||||
|
"version": "1.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz",
|
||||||
|
"integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/eslint/node_modules/ansi-styles": {
|
"node_modules/eslint/node_modules/ansi-styles": {
|
||||||
"version": "4.3.0",
|
"version": "4.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
||||||
@ -4014,9 +4022,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/eslint/node_modules/eslint-scope": {
|
"node_modules/eslint/node_modules/eslint-scope": {
|
||||||
"version": "8.0.2",
|
"version": "8.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.1.0.tgz",
|
||||||
"integrity": "sha512-6E4xmrTw5wtxnLA5wYL3WDfhZ/1bUBGOXV0zQvVRDOtrR8D0p6W7fs3JweNYhwRYeGvd/1CKX2se0/2s7Q/nJA==",
|
"integrity": "sha512-14dSvlhaVhKKsa9Fx1l8A17s7ah7Ef7wCakJ10LYk6+GYmP9yDti2oq2SEwcyndt6knfcZyhyxwY3i9yL78EQw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "BSD-2-Clause",
|
"license": "BSD-2-Clause",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@ -4031,9 +4039,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/eslint/node_modules/eslint-visitor-keys": {
|
"node_modules/eslint/node_modules/eslint-visitor-keys": {
|
||||||
"version": "4.0.0",
|
"version": "4.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.1.0.tgz",
|
||||||
"integrity": "sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw==",
|
"integrity": "sha512-Q7lok0mqMUSf5a/AdAZkA5a/gHcO6snwQClVNNvFKCAVlxXucdU8pKydU5ZVZjBx5xr37vGbFFWtLQYreLzrZg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"engines": {
|
"engines": {
|
||||||
@ -4044,15 +4052,15 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/eslint/node_modules/espree": {
|
"node_modules/eslint/node_modules/espree": {
|
||||||
"version": "10.1.0",
|
"version": "10.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/espree/-/espree-10.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/espree/-/espree-10.2.0.tgz",
|
||||||
"integrity": "sha512-M1M6CpiE6ffoigIOWYO9UDP8TMUw9kqb21tf+08IgDYjCsOvCuDt4jQcZmoYxx+w7zlKw9/N0KXfto+I8/FrXA==",
|
"integrity": "sha512-upbkBJbckcCNBDBDXEbuhjbP68n+scUd3k/U2EkyM9nw+I/jPiL4cLF/Al06CF96wRltFda16sxDFrxsI1v0/g==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "BSD-2-Clause",
|
"license": "BSD-2-Clause",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"acorn": "^8.12.0",
|
"acorn": "^8.12.0",
|
||||||
"acorn-jsx": "^5.3.2",
|
"acorn-jsx": "^5.3.2",
|
||||||
"eslint-visitor-keys": "^4.0.0"
|
"eslint-visitor-keys": "^4.1.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||||
@ -6092,21 +6100,17 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/prettier-plugin-organize-imports": {
|
"node_modules/prettier-plugin-organize-imports": {
|
||||||
"version": "4.0.0",
|
"version": "4.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/prettier-plugin-organize-imports/-/prettier-plugin-organize-imports-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/prettier-plugin-organize-imports/-/prettier-plugin-organize-imports-4.1.0.tgz",
|
||||||
"integrity": "sha512-vnKSdgv9aOlqKeEFGhf9SCBsTyzDSyScy1k7E0R1Uo4L0cTcOV7c1XQaT7jfXIOc/p08WLBfN2QUQA9zDSZMxA==",
|
"integrity": "sha512-5aWRdCgv645xaa58X8lOxzZoiHAldAPChljr/MT0crXVOWTZ+Svl4hIWlz+niYSlO6ikE5UXkN1JrRvIP2ut0A==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@vue/language-plugin-pug": "^2.0.24",
|
|
||||||
"prettier": ">=2.0",
|
"prettier": ">=2.0",
|
||||||
"typescript": ">=2.9",
|
"typescript": ">=2.9",
|
||||||
"vue-tsc": "^2.0.24"
|
"vue-tsc": "^2.1.0"
|
||||||
},
|
},
|
||||||
"peerDependenciesMeta": {
|
"peerDependenciesMeta": {
|
||||||
"@vue/language-plugin-pug": {
|
|
||||||
"optional": true
|
|
||||||
},
|
|
||||||
"vue-tsc": {
|
"vue-tsc": {
|
||||||
"optional": true
|
"optional": true
|
||||||
}
|
}
|
||||||
@ -6125,9 +6129,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/prettier-plugin-svelte": {
|
"node_modules/prettier-plugin-svelte": {
|
||||||
"version": "3.2.6",
|
"version": "3.2.7",
|
||||||
"resolved": "https://registry.npmjs.org/prettier-plugin-svelte/-/prettier-plugin-svelte-3.2.6.tgz",
|
"resolved": "https://registry.npmjs.org/prettier-plugin-svelte/-/prettier-plugin-svelte-3.2.7.tgz",
|
||||||
"integrity": "sha512-Y1XWLw7vXUQQZmgv1JAEiLcErqUniAF2wO7QJsw8BVMvpLET2dI5WpEIEJx1r11iHVdSMzQxivyfrH9On9t2IQ==",
|
"integrity": "sha512-/Dswx/ea0lV34If1eDcG3nulQ63YNr5KPDfMsjbdtpSWOxKKJ7nAc2qlVuYwEvCr4raIuredNoR7K4JCkmTGaQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
@ -6740,13 +6744,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/socket.io-client": {
|
"node_modules/socket.io-client": {
|
||||||
"version": "4.7.5",
|
"version": "4.8.0",
|
||||||
"resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.7.5.tgz",
|
"resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.8.0.tgz",
|
||||||
"integrity": "sha512-sJ/tqHOCe7Z50JCBCXrsY3I2k03iOiUe+tj1OmKeD2lXPiGH/RUCdTZFoqVyN7l1MnpIzPrGtLcijffmeouNlQ==",
|
"integrity": "sha512-C0jdhD5yQahMws9alf/yvtsMGTaIDBnZ8Rb5HU56svyq0l5LIrGzIDZZD5pHQlmzxLuU91Gz+VpQMKgCTNYtkw==",
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@socket.io/component-emitter": "~3.1.0",
|
"@socket.io/component-emitter": "~3.1.0",
|
||||||
"debug": "~4.3.2",
|
"debug": "~4.3.2",
|
||||||
"engine.io-client": "~6.5.2",
|
"engine.io-client": "~6.6.1",
|
||||||
"socket.io-parser": "~4.2.4"
|
"socket.io-parser": "~4.2.4"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
@ -7652,9 +7657,9 @@
|
|||||||
"peer": true
|
"peer": true
|
||||||
},
|
},
|
||||||
"node_modules/tailwindcss": {
|
"node_modules/tailwindcss": {
|
||||||
"version": "3.4.12",
|
"version": "3.4.13",
|
||||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.12.tgz",
|
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.13.tgz",
|
||||||
"integrity": "sha512-Htf/gHj2+soPb9UayUNci/Ja3d8pTmu9ONTfh4QY8r3MATTZOzmv6UYWF7ZwikEIC8okpfqmGqrmDehua8mF8w==",
|
"integrity": "sha512-KqjHOJKogOUt5Bs752ykCeiwvi0fKVkr5oqsFNt/8px/tA8scFPIlkygsf6jXrfCqGHz7VflA6+yytWuM+XhFw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@ -7837,9 +7842,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/three": {
|
"node_modules/three": {
|
||||||
"version": "0.167.1",
|
"version": "0.168.0",
|
||||||
"resolved": "https://registry.npmjs.org/three/-/three-0.167.1.tgz",
|
"resolved": "https://registry.npmjs.org/three/-/three-0.168.0.tgz",
|
||||||
"integrity": "sha512-gYTLJA/UQip6J/tJvl91YYqlZF47+D/kxiWrbTon35ZHlXEN0VOo+Qke2walF1/x92v55H6enomymg4Dak52kw==",
|
"integrity": "sha512-6m6jXtDwMJEK/GGMbAOTSAmxNdzKvvBzgd7q8bE/7Tr6m7PaBh5kKLrN7faWtlglXbzj7sVba48Idwx+NRsZXw==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/thumbhash": {
|
"node_modules/thumbhash": {
|
||||||
@ -8147,9 +8152,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/vite": {
|
"node_modules/vite": {
|
||||||
"version": "5.4.6",
|
"version": "5.4.8",
|
||||||
"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.6.tgz",
|
"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.8.tgz",
|
||||||
"integrity": "sha512-IeL5f8OO5nylsgzd9tq4qD2QqI0k2CQLGrWD0rCN0EQJZpBK5vJAx0I+GDkMOXxQX/OfFHMuLIx6ddAxGX/k+Q==",
|
"integrity": "sha512-FqrItQ4DT1NC4zCUqMB4c4AZORMKIa0m8/URVCZ77OZ/QSNeJ54bU1vrFADbDsuwfIPcgknRkmqakQcgnL4GiQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@ -8539,12 +8544,10 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/ws": {
|
"node_modules/ws": {
|
||||||
"version": "8.14.2",
|
"version": "8.17.1",
|
||||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.14.2.tgz",
|
"resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz",
|
||||||
"integrity": "sha512-wEBG1ftX4jcglPxgFCMJmZ2PLtSbJ2Peg6TmpJFTbe9GZYOQCDPdMYu/Tm0/bGZkw8paZnJY45J4K2PZrLYq8g==",
|
"integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==",
|
||||||
"dev": true,
|
"license": "MIT",
|
||||||
"optional": true,
|
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=10.0.0"
|
"node": ">=10.0.0"
|
||||||
},
|
},
|
||||||
@ -8581,9 +8584,9 @@
|
|||||||
"peer": true
|
"peer": true
|
||||||
},
|
},
|
||||||
"node_modules/xmlhttprequest-ssl": {
|
"node_modules/xmlhttprequest-ssl": {
|
||||||
"version": "2.0.0",
|
"version": "2.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.1.1.tgz",
|
||||||
"integrity": "sha512-QKxVRxiRACQcVuQEYFsI1hhkrMlrXHPegbbd1yn9UHOmRxY+si12nQYzri3vbzt8VdTTRviqcKxcyllFas5z2A==",
|
"integrity": "sha512-ptjR8YSJIXoA3Mbv5po7RtSYHO6mZr8s7i5VGmEk7QY2pQWyT1o0N+W1gKbOyJPUCGXGnuw0wqe8f0L6Y0ny7g==",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.4.0"
|
"node": ">=0.4.0"
|
||||||
}
|
}
|
||||||
|
@ -13,6 +13,8 @@
|
|||||||
<link rel="icon" type="image/png" sizes="96x96" href="/favicon-96.png" />
|
<link rel="icon" type="image/png" sizes="96x96" href="/favicon-96.png" />
|
||||||
<link rel="icon" type="image/png" sizes="144x144" href="/favicon-144.png" />
|
<link rel="icon" type="image/png" sizes="144x144" href="/favicon-144.png" />
|
||||||
<link rel="apple-touch-icon" sizes="180x180" href="/apple-icon-180.png" />
|
<link rel="apple-touch-icon" sizes="180x180" href="/apple-icon-180.png" />
|
||||||
|
<link rel="preload" as="font" type="font/ttf" href="%app.font%" />
|
||||||
|
<link rel="preload" as="font" type="font/ttf" href="%app.monofont%" />
|
||||||
%sveltekit.head%
|
%sveltekit.head%
|
||||||
<style>
|
<style>
|
||||||
/* prevent FOUC */
|
/* prevent FOUC */
|
||||||
|
12
web/src/hooks.server.ts
Normal file
12
web/src/hooks.server.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import overpass from '$lib/assets/fonts/overpass/Overpass.ttf?url';
|
||||||
|
import overpassMono from '$lib/assets/fonts/overpass/OverpassMono.ttf?url';
|
||||||
|
import type { Handle } from '@sveltejs/kit';
|
||||||
|
|
||||||
|
// only used during the build to replace the variables from app.html
|
||||||
|
export const handle = (async ({ event, resolve }) => {
|
||||||
|
return resolve(event, {
|
||||||
|
transformPageChunk: ({ html }) => {
|
||||||
|
return html.replace('%app.font%', overpass).replace('%app.monofont%', overpassMono);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}) satisfies Handle;
|
@ -4,3 +4,6 @@ export const sunPath =
|
|||||||
|
|
||||||
export const moonViewBox = '0 0 20 20';
|
export const moonViewBox = '0 0 20 20';
|
||||||
export const sunViewBox = '0 0 20 20';
|
export const sunViewBox = '0 0 20 20';
|
||||||
|
|
||||||
|
export const discordPath =
|
||||||
|
'M 9.1367188 3.8691406 C 9.1217187 3.8691406 9.1067969 3.8700938 9.0917969 3.8710938 C 8.9647969 3.8810937 5.9534375 4.1403594 4.0234375 5.6933594 C 3.0154375 6.6253594 1 12.073203 1 16.783203 C 1 16.866203 1.0215 16.946531 1.0625 17.019531 C 2.4535 19.462531 6.2473281 20.102859 7.1113281 20.130859 L 7.1269531 20.130859 C 7.2799531 20.130859 7.4236719 20.057594 7.5136719 19.933594 L 8.3886719 18.732422 C 6.0296719 18.122422 4.8248594 17.086391 4.7558594 17.025391 C 4.5578594 16.850391 4.5378906 16.549563 4.7128906 16.351562 C 4.8068906 16.244563 4.9383125 16.189453 5.0703125 16.189453 C 5.1823125 16.189453 5.2957188 16.228594 5.3867188 16.308594 C 5.4157187 16.334594 7.6340469 18.216797 11.998047 18.216797 C 16.370047 18.216797 18.589328 16.325641 18.611328 16.306641 C 18.702328 16.227641 18.815734 16.189453 18.927734 16.189453 C 19.059734 16.189453 19.190156 16.243562 19.285156 16.351562 C 19.459156 16.549563 19.441141 16.851391 19.244141 17.025391 C 19.174141 17.087391 17.968375 18.120469 15.609375 18.730469 L 16.484375 19.933594 C 16.574375 20.057594 16.718094 20.130859 16.871094 20.130859 L 16.886719 20.130859 C 17.751719 20.103859 21.5465 19.463531 22.9375 17.019531 C 22.9785 16.947531 23 16.866203 23 16.783203 C 23 12.073203 20.984172 6.624875 19.951172 5.671875 C 18.047172 4.140875 15.036203 3.8820937 14.908203 3.8710938 C 14.895203 3.8700938 14.880188 3.8691406 14.867188 3.8691406 C 14.681188 3.8691406 14.510594 3.9793906 14.433594 4.1503906 C 14.427594 4.1623906 14.362062 4.3138281 14.289062 4.5488281 C 15.548063 4.7608281 17.094141 5.1895937 18.494141 6.0585938 C 18.718141 6.1975938 18.787437 6.4917969 18.648438 6.7167969 C 18.558438 6.8627969 18.402188 6.9433594 18.242188 6.9433594 C 18.156188 6.9433594 18.069234 6.9200937 17.990234 6.8710938 C 15.584234 5.3800938 12.578 5.3046875 12 5.3046875 C 11.422 5.3046875 8.4157187 5.3810469 6.0117188 6.8730469 C 5.9327188 6.9210469 5.8457656 6.9433594 5.7597656 6.9433594 C 5.5997656 6.9433594 5.4425625 6.86475 5.3515625 6.71875 C 5.2115625 6.49375 5.2818594 6.1985938 5.5058594 6.0585938 C 6.9058594 5.1905937 8.4528906 4.7627812 9.7128906 4.5507812 C 9.6388906 4.3147813 9.5714062 4.1643437 9.5664062 4.1523438 C 9.4894063 3.9813438 9.3217188 3.8691406 9.1367188 3.8691406 z M 12 7.3046875 C 12.296 7.3046875 14.950594 7.3403125 16.933594 8.5703125 C 17.326594 8.8143125 17.777234 8.9453125 18.240234 8.9453125 C 18.633234 8.9453125 19.010656 8.8555 19.347656 8.6875 C 19.964656 10.2405 20.690828 12.686219 20.923828 15.199219 C 20.883828 15.143219 20.840922 15.089109 20.794922 15.037109 C 20.324922 14.498109 19.644687 14.191406 18.929688 14.191406 C 18.332687 14.191406 17.754078 14.405437 17.330078 14.773438 C 17.257078 14.832437 15.505 16.21875 12 16.21875 C 8.496 16.21875 6.7450313 14.834687 6.7070312 14.804688 C 6.2540312 14.407687 5.6742656 14.189453 5.0722656 14.189453 C 4.3612656 14.189453 3.6838438 14.494391 3.2148438 15.025391 C 3.1658438 15.080391 3.1201719 15.138266 3.0761719 15.197266 C 3.3091719 12.686266 4.0344375 10.235594 4.6484375 8.6835938 C 4.9864375 8.8525938 5.3657656 8.9433594 5.7597656 8.9433594 C 6.2217656 8.9433594 6.6724531 8.8143125 7.0644531 8.5703125 C 9.0494531 7.3393125 11.704 7.3046875 12 7.3046875 z M 8.890625 10.044922 C 7.966625 10.044922 7.2167969 10.901031 7.2167969 11.957031 C 7.2167969 13.013031 7.965625 13.869141 8.890625 13.869141 C 9.815625 13.869141 10.564453 13.013031 10.564453 11.957031 C 10.564453 10.900031 9.815625 10.044922 8.890625 10.044922 z M 15.109375 10.044922 C 14.185375 10.044922 13.435547 10.901031 13.435547 11.957031 C 13.435547 13.013031 14.184375 13.869141 15.109375 13.869141 C 16.034375 13.869141 16.783203 13.013031 16.783203 11.957031 C 16.783203 10.900031 16.033375 10.044922 15.109375 10.044922 z';
|
||||||
|
@ -0,0 +1,131 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import FullScreenModal from '$lib/components/shared-components/full-screen-modal.svelte';
|
||||||
|
import Portal from '$lib/components/shared-components/portal/portal.svelte';
|
||||||
|
import { type ServerAboutResponseDto } from '@immich/sdk';
|
||||||
|
import { t } from 'svelte-i18n';
|
||||||
|
import Icon from '$lib/components/elements/icon.svelte';
|
||||||
|
import { mdiBugOutline, mdiFaceAgent, mdiGit, mdiGithub, mdiInformationOutline } from '@mdi/js';
|
||||||
|
import { discordPath } from '$lib/assets/svg-paths';
|
||||||
|
|
||||||
|
export let onClose: () => void;
|
||||||
|
|
||||||
|
export let info: ServerAboutResponseDto;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Portal>
|
||||||
|
<FullScreenModal title={$t('support_and_feedback')} {onClose}>
|
||||||
|
<p>{$t('official_immich_resources')}</p>
|
||||||
|
<div class="flex flex-col sm:grid sm:grid-cols-2 gap-2 mt-3">
|
||||||
|
<div>
|
||||||
|
<a href="https://{info.version}.archive.immich.app/docs/overview/introduction" target="_blank" rel="noreferrer">
|
||||||
|
<Icon path={mdiInformationOutline} size="1.5em" class="inline-block" />
|
||||||
|
<p
|
||||||
|
class="font-medium text-immich-primary dark:text-immich-dark-primary text-sm underline inline-block"
|
||||||
|
id="documentation-label"
|
||||||
|
>
|
||||||
|
{$t('documentation')}
|
||||||
|
</p>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<a href="https://github.com/immich-app/immich/" target="_blank" rel="noreferrer">
|
||||||
|
<Icon path={mdiGithub} size="1.5em" class="inline-block" />
|
||||||
|
<p
|
||||||
|
class="font-medium text-immich-primary dark:text-immich-dark-primary text-sm underline inline-block"
|
||||||
|
id="github-label"
|
||||||
|
>
|
||||||
|
{$t('source')}
|
||||||
|
</p>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<a href="https://discord.immich.app" target="_blank" rel="noreferrer">
|
||||||
|
<Icon path={discordPath} class="inline-block" size="1.5em" />
|
||||||
|
<p
|
||||||
|
class="font-medium text-immich-primary dark:text-immich-dark-primary text-sm underline inline-block"
|
||||||
|
id="github-label"
|
||||||
|
>
|
||||||
|
{$t('discord')}
|
||||||
|
</p>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<a href="https://github.com/immich-app/immich/issues/new/choose" target="_blank" rel="noreferrer">
|
||||||
|
<Icon path={mdiBugOutline} size="1.5em" class="inline-block" />
|
||||||
|
<p
|
||||||
|
class="font-medium text-immich-primary dark:text-immich-dark-primary text-sm underline inline-block"
|
||||||
|
id="github-label"
|
||||||
|
>
|
||||||
|
{$t('bugs_and_feature_requests')}
|
||||||
|
</p>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{#if info.thirdPartyBugFeatureUrl || info.thirdPartySourceUrl || info.thirdPartyDocumentationUrl || info.thirdPartySupportUrl}
|
||||||
|
<p class="mt-3">{$t('third_party_resources')}</p>
|
||||||
|
<p class="text-xs mt-1">
|
||||||
|
{$t('support_third_party_description')}
|
||||||
|
</p>
|
||||||
|
<div class="flex flex-col sm:grid sm:grid-cols-2 gap-2 mt-3">
|
||||||
|
{#if info.thirdPartyDocumentationUrl}
|
||||||
|
<div>
|
||||||
|
<a href={info.thirdPartyDocumentationUrl} target="_blank" rel="noreferrer">
|
||||||
|
<Icon path={mdiInformationOutline} size="1.5em" class="inline-block" />
|
||||||
|
<p
|
||||||
|
class="font-medium text-immich-primary dark:text-immich-dark-primary text-sm underline inline-block"
|
||||||
|
id="documentation-label"
|
||||||
|
>
|
||||||
|
{$t('documentation')}
|
||||||
|
</p>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if info.thirdPartySourceUrl}
|
||||||
|
<div>
|
||||||
|
<a href={info.thirdPartySourceUrl} target="_blank" rel="noreferrer">
|
||||||
|
<Icon path={mdiGit} size="1.5em" class="inline-block" />
|
||||||
|
<p
|
||||||
|
class="font-medium text-immich-primary dark:text-immich-dark-primary text-sm underline inline-block"
|
||||||
|
id="github-label"
|
||||||
|
>
|
||||||
|
{$t('source')}
|
||||||
|
</p>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if info.thirdPartySupportUrl}
|
||||||
|
<div>
|
||||||
|
<a href={info.thirdPartySupportUrl} target="_blank" rel="noreferrer">
|
||||||
|
<Icon path={mdiFaceAgent} class="inline-block" size="1.5em" />
|
||||||
|
<p
|
||||||
|
class="font-medium text-immich-primary dark:text-immich-dark-primary text-sm underline inline-block"
|
||||||
|
id="github-label"
|
||||||
|
>
|
||||||
|
{$t('support')}
|
||||||
|
</p>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if info.thirdPartyBugFeatureUrl}
|
||||||
|
<div>
|
||||||
|
<a href={info.thirdPartyBugFeatureUrl} target="_blank" rel="noreferrer">
|
||||||
|
<Icon path={mdiBugOutline} size="1.5em" class="inline-block" />
|
||||||
|
<p
|
||||||
|
class="font-medium text-immich-primary dark:text-immich-dark-primary text-sm underline inline-block"
|
||||||
|
id="github-label"
|
||||||
|
>
|
||||||
|
{$t('bugs_and_feature_requests')}
|
||||||
|
</p>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</FullScreenModal>
|
||||||
|
</Portal>
|
@ -8,32 +8,45 @@
|
|||||||
import { featureFlags } from '$lib/stores/server-config.store';
|
import { featureFlags } from '$lib/stores/server-config.store';
|
||||||
import { user } from '$lib/stores/user.store';
|
import { user } from '$lib/stores/user.store';
|
||||||
import { handleLogout } from '$lib/utils/auth';
|
import { handleLogout } from '$lib/utils/auth';
|
||||||
import { logout } from '@immich/sdk';
|
import { getAboutInfo, logout, type ServerAboutResponseDto } from '@immich/sdk';
|
||||||
import { mdiMagnify, mdiTrayArrowUp } from '@mdi/js';
|
import { mdiHelpCircleOutline, mdiMagnify, mdiTrayArrowUp } from '@mdi/js';
|
||||||
import { t } from 'svelte-i18n';
|
import { t } from 'svelte-i18n';
|
||||||
import { fade } from 'svelte/transition';
|
import { fade } from 'svelte/transition';
|
||||||
import { AppRoute } from '../../../constants';
|
import { AppRoute } from '$lib/constants';
|
||||||
import ImmichLogo from '../immich-logo.svelte';
|
import ImmichLogo from '$lib/components/shared-components/immich-logo.svelte';
|
||||||
import SearchBar from '../search-bar/search-bar.svelte';
|
import SearchBar from '$lib/components/shared-components/search-bar/search-bar.svelte';
|
||||||
import ThemeButton from '../theme-button.svelte';
|
import ThemeButton from '../theme-button.svelte';
|
||||||
import UserAvatar from '../user-avatar.svelte';
|
import UserAvatar from '../user-avatar.svelte';
|
||||||
import AccountInfoPanel from './account-info-panel.svelte';
|
import AccountInfoPanel from './account-info-panel.svelte';
|
||||||
|
import HelpAndFeedbackModal from '$lib/components/shared-components/help-and-feedback-modal.svelte';
|
||||||
|
import { onMount } from 'svelte';
|
||||||
|
|
||||||
export let showUploadButton = true;
|
export let showUploadButton = true;
|
||||||
export let onUploadClick: () => void;
|
export let onUploadClick: () => void;
|
||||||
|
|
||||||
let shouldShowAccountInfo = false;
|
let shouldShowAccountInfo = false;
|
||||||
let shouldShowAccountInfoPanel = false;
|
let shouldShowAccountInfoPanel = false;
|
||||||
|
let shouldShowHelpPanel = false;
|
||||||
let innerWidth: number;
|
let innerWidth: number;
|
||||||
|
|
||||||
const onLogout = async () => {
|
const onLogout = async () => {
|
||||||
const { redirectUri } = await logout();
|
const { redirectUri } = await logout();
|
||||||
await handleLogout(redirectUri);
|
await handleLogout(redirectUri);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let aboutInfo: ServerAboutResponseDto;
|
||||||
|
|
||||||
|
onMount(async () => {
|
||||||
|
aboutInfo = await getAboutInfo();
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:window bind:innerWidth />
|
<svelte:window bind:innerWidth />
|
||||||
|
|
||||||
|
{#if shouldShowHelpPanel}
|
||||||
|
<HelpAndFeedbackModal onClose={() => (shouldShowHelpPanel = false)} info={aboutInfo} />
|
||||||
|
{/if}
|
||||||
|
|
||||||
<section id="dashboard-navbar" class="fixed z-[900] h-[var(--navbar-height)] w-screen text-sm">
|
<section id="dashboard-navbar" class="fixed z-[900] h-[var(--navbar-height)] w-screen text-sm">
|
||||||
<SkipLink text={$t('skip_to_content')} />
|
<SkipLink text={$t('skip_to_content')} />
|
||||||
<div
|
<div
|
||||||
@ -49,7 +62,7 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<section class="flex place-items-center justify-end gap-2 md:gap-4 w-full sm:w-auto">
|
<section class="flex place-items-center justify-end gap-1 md:gap-2 w-full sm:w-auto">
|
||||||
{#if $featureFlags.search}
|
{#if $featureFlags.search}
|
||||||
<CircleIconButton
|
<CircleIconButton
|
||||||
href={AppRoute.SEARCH}
|
href={AppRoute.SEARCH}
|
||||||
@ -63,6 +76,20 @@
|
|||||||
|
|
||||||
<ThemeButton padding="2" />
|
<ThemeButton padding="2" />
|
||||||
|
|
||||||
|
<div
|
||||||
|
use:clickOutside={{
|
||||||
|
onEscape: () => (shouldShowHelpPanel = false),
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CircleIconButton
|
||||||
|
id="support-feedback-button"
|
||||||
|
title={$t('support_and_feedback')}
|
||||||
|
icon={mdiHelpCircleOutline}
|
||||||
|
on:click={() => (shouldShowHelpPanel = !shouldShowHelpPanel)}
|
||||||
|
padding="1"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
{#if !$page.url.pathname.includes('/admin') && showUploadButton}
|
{#if !$page.url.pathname.includes('/admin') && showUploadButton}
|
||||||
<LinkButton on:click={onUploadClick} class="hidden lg:block">
|
<LinkButton on:click={onUploadClick} class="hidden lg:block">
|
||||||
<div class="flex gap-2">
|
<div class="flex gap-2">
|
||||||
@ -87,7 +114,7 @@
|
|||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="flex"
|
class="flex pl-2"
|
||||||
on:mouseover={() => (shouldShowAccountInfo = true)}
|
on:mouseover={() => (shouldShowAccountInfo = true)}
|
||||||
on:focus={() => (shouldShowAccountInfo = true)}
|
on:focus={() => (shouldShowAccountInfo = true)}
|
||||||
on:blur={() => (shouldShowAccountInfo = false)}
|
on:blur={() => (shouldShowAccountInfo = false)}
|
||||||
|
@ -416,6 +416,7 @@
|
|||||||
"birthdate_saved": "Date of birth saved successfully",
|
"birthdate_saved": "Date of birth saved successfully",
|
||||||
"birthdate_set_description": "Date of birth is used to calculate the age of this person at the time of a photo.",
|
"birthdate_set_description": "Date of birth is used to calculate the age of this person at the time of a photo.",
|
||||||
"blurred_background": "Blurred background",
|
"blurred_background": "Blurred background",
|
||||||
|
"bugs_and_feature_requests": "Bugs & Feature Requests",
|
||||||
"build": "Build",
|
"build": "Build",
|
||||||
"build_image": "Build Image",
|
"build_image": "Build Image",
|
||||||
"bulk_delete_duplicates_confirmation": "Are you sure you want to bulk delete {count, plural, one {# duplicate asset} other {# duplicate assets}}? This will keep the largest asset of each group and permanently delete all other duplicates. You cannot undo this action!",
|
"bulk_delete_duplicates_confirmation": "Are you sure you want to bulk delete {count, plural, one {# duplicate asset} other {# duplicate assets}}? This will keep the largest asset of each group and permanently delete all other duplicates. You cannot undo this action!",
|
||||||
@ -521,6 +522,7 @@
|
|||||||
"direction": "Direction",
|
"direction": "Direction",
|
||||||
"disabled": "Disabled",
|
"disabled": "Disabled",
|
||||||
"disallow_edits": "Disallow edits",
|
"disallow_edits": "Disallow edits",
|
||||||
|
"discord": "Discord",
|
||||||
"discover": "Discover",
|
"discover": "Discover",
|
||||||
"dismiss_all_errors": "Dismiss all errors",
|
"dismiss_all_errors": "Dismiss all errors",
|
||||||
"dismiss_error": "Dismiss error",
|
"dismiss_error": "Dismiss error",
|
||||||
@ -529,6 +531,7 @@
|
|||||||
"display_original_photos": "Display original photos",
|
"display_original_photos": "Display original photos",
|
||||||
"display_original_photos_setting_description": "Prefer to display the original photo when viewing an asset rather than thumbnails when the original asset is web-compatible. This may result in slower photo display speeds.",
|
"display_original_photos_setting_description": "Prefer to display the original photo when viewing an asset rather than thumbnails when the original asset is web-compatible. This may result in slower photo display speeds.",
|
||||||
"do_not_show_again": "Do not show this message again",
|
"do_not_show_again": "Do not show this message again",
|
||||||
|
"documentation": "Documentation",
|
||||||
"done": "Done",
|
"done": "Done",
|
||||||
"download": "Download",
|
"download": "Download",
|
||||||
"download_include_embedded_motion_videos": "Embedded videos",
|
"download_include_embedded_motion_videos": "Embedded videos",
|
||||||
@ -882,6 +885,7 @@
|
|||||||
"notifications": "Notifications",
|
"notifications": "Notifications",
|
||||||
"notifications_setting_description": "Manage notifications",
|
"notifications_setting_description": "Manage notifications",
|
||||||
"oauth": "OAuth",
|
"oauth": "OAuth",
|
||||||
|
"official_immich_resources": "Official Immich Resources",
|
||||||
"offline": "Offline",
|
"offline": "Offline",
|
||||||
"offline_paths": "Offline paths",
|
"offline_paths": "Offline paths",
|
||||||
"offline_paths_description": "These results may be due to manual deletion of files that are not part of an external library.",
|
"offline_paths_description": "These results may be due to manual deletion of files that are not part of an external library.",
|
||||||
@ -1188,6 +1192,9 @@
|
|||||||
"submit": "Submit",
|
"submit": "Submit",
|
||||||
"suggestions": "Suggestions",
|
"suggestions": "Suggestions",
|
||||||
"sunrise_on_the_beach": "Sunrise on the beach",
|
"sunrise_on_the_beach": "Sunrise on the beach",
|
||||||
|
"support": "Support",
|
||||||
|
"support_and_feedback": "Support & Feedback",
|
||||||
|
"support_third_party_description": "Your immich installation was packaged by a third-party. Issues you experience may be caused by that package, so please raise issues with them in the first instance using the links below.",
|
||||||
"swap_merge_direction": "Swap merge direction",
|
"swap_merge_direction": "Swap merge direction",
|
||||||
"sync": "Sync",
|
"sync": "Sync",
|
||||||
"tag": "Tag",
|
"tag": "Tag",
|
||||||
@ -1203,6 +1210,7 @@
|
|||||||
"theme_selection": "Theme selection",
|
"theme_selection": "Theme selection",
|
||||||
"theme_selection_description": "Automatically set the theme to light or dark based on your browser's system preference",
|
"theme_selection_description": "Automatically set the theme to light or dark based on your browser's system preference",
|
||||||
"they_will_be_merged_together": "They will be merged together",
|
"they_will_be_merged_together": "They will be merged together",
|
||||||
|
"third_party_resources": "Third-Party Resources",
|
||||||
"time_based_memories": "Time-based memories",
|
"time_based_memories": "Time-based memories",
|
||||||
"timezone": "Timezone",
|
"timezone": "Timezone",
|
||||||
"to_archive": "Archive",
|
"to_archive": "Archive",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user