Compare commits

..

1 Commits

Author SHA1 Message Date
Jonathan Jogenfors 43d66c7a36 ignore perm errors 2025-02-07 00:30:28 +01:00
119 changed files with 1249 additions and 2825 deletions
+3 -3
View File
@@ -1,12 +1,12 @@
{ {
"name": "@immich/cli", "name": "@immich/cli",
"version": "2.2.49", "version": "2.2.48",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "@immich/cli", "name": "@immich/cli",
"version": "2.2.49", "version": "2.2.48",
"license": "GNU Affero General Public License version 3", "license": "GNU Affero General Public License version 3",
"dependencies": { "dependencies": {
"fast-glob": "^3.3.2", "fast-glob": "^3.3.2",
@@ -52,7 +52,7 @@
}, },
"../open-api/typescript-sdk": { "../open-api/typescript-sdk": {
"name": "@immich/sdk", "name": "@immich/sdk",
"version": "1.126.0", "version": "1.125.7",
"dev": true, "dev": true,
"license": "GNU Affero General Public License version 3", "license": "GNU Affero General Public License version 3",
"dependencies": { "dependencies": {
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@immich/cli", "name": "@immich/cli",
"version": "2.2.49", "version": "2.2.48",
"description": "Command Line Interface (CLI) for Immich", "description": "Command Line Interface (CLI) for Immich",
"type": "module", "type": "module",
"exports": "./dist/index.js", "exports": "./dist/index.js",
+30 -30
View File
@@ -2,37 +2,37 @@
# Manual edits may be lost in future updates. # Manual edits may be lost in future updates.
provider "registry.opentofu.org/cloudflare/cloudflare" { provider "registry.opentofu.org/cloudflare/cloudflare" {
version = "4.52.0" version = "4.50.0"
constraints = "4.52.0" constraints = "4.50.0"
hashes = [ hashes = [
"h1:2BEJyXJtYC4B4nda/WCYUmuJYDaYk88F8t1pwPzr0iQ=", "h1:0qvD5ZKn2tMZ8cOjQrUSITIC9tKCZbrSaSswV9lOyiU=",
"h1:4IASk5SESeWKQ7JU0+M7KApuF5mZyklvwMXPBabim3c=", "h1:4N0gplrZ0zOsJv3Kx1VfIx2FwrZHbYU0Un2yfiLZIGQ=",
"h1:5ImZxxALSnWfH/4EXw/wFirSmk5Tr0ACmcysy51AafE=", "h1:81AMQq4kNKU/35U8ElQegUxG4E6xB0erIjG5xVmjIyo=",
"h1:6TJ3dxLSin4ZKBJLsZDn95H2ZYnGm8S7GGHvvXuuMQU=", "h1:EEQNADUmV3IL6x00yzy04i7OCSLeOMgM9XQkV3w71gA=",
"h1:IzTUjg9kQ4N3qizP9CjYLeHwjsuGgtxwXvfUQWyOLcA=", "h1:HD0KI7td6oiSSAnJNn8UPSGf+hKiTo4JVQYfAiU1SqM=",
"h1:NTaOQfYINA0YTG/V1/9+SYtgX1it63+cBugj4WK4FWc=", "h1:Hl+o5LtcvZg2f3l1hh9vaG/DFK6k+dTIZSeM0lXyfpo=",
"h1:PXH48LuJn329sCfMXprdMDk51EZaWFyajVvS03qhQLs=", "h1:ZUO2oIJ6jtZdvl816h0cEIiIeZ/fFCF64+abGEVxZZM=",
"h1:Pi5M+GeoMSN2eJ6QnIeXjBf19O+rby/74CfB2ocpv20=", "h1:Zio80fnEeUKdlSOhTVskMEFSLUQ6TMsMKnXc+Dy2P2A=",
"h1:ShXZ2ZjBvm3thfoPPzPT8+OhyismnydQVkUAfI8X12w=", "h1:aLLvg36evTyqjtXGV2MjAV8imktXFmry7p/xCu9GQC4=",
"h1:WQ9hu0Wge2msBbODfottCSKgu8oKUrw4Opz+fDPVVHk=", "h1:azL05eWyy2V8SWkbZZImPWvv8ynG4eqmrbZhjXBDFug=",
"h1:Z5yXML2DE0uH9UU+M0ut9JMQAORcwVZz1CxBHzeBmao=", "h1:ckMysHY4fJmr7o58XMi+DdgOTB/U/Mf1u1JA9ly3g/I=",
"h1:jqI2qKknpleS3JDSplyGYHMu0u9K/tor1ZOjFwDgEMk=", "h1:jxOwjDNjt5WCb4YjjiMsman91O8Y+MAPz6UwJ4a6F+0=",
"h1:kgfutDh14Q5nw4eg6qGFamFxIiY8Ae0FPKRBLDOzpcI=", "h1:u4OfnjSLa4Wk1IUFAzrvMnGgr8MvRHEWVDHEScPK2E8=",
"h1:zCAO7GZmfYhWb+i6TfqlqhMeDyPZWGio2IzEzAh3YTs=", "h1:wQkR1oeSkzlHn3rnVuLJRJLBHlg4EHt7Y64DeTjfkjQ=",
"zh:19be1a91c982b902c42aba47766860dfa5dc151eed1e95fd39ca642229381ef0", "zh:0ef99ed39472a94e6a0d6fa733cf0a46bce3bf66eba2873efae8846efdddc237",
"zh:1de451c4d1ecf7efbe67b6dace3426ba810711afdd644b0f1b870364c8ae91f8", "zh:2929cbbffcead171d45c88e4a7a59e9c013ea775dafa68b10da8db7cd04b6140",
"zh:352b4a2120173298622e669258744554339d959ac3a95607b117a48ee4a83238", "zh:462601c87118088e1a718842e367af7d8e7620598d426980a6d6b33de759865e",
"zh:3c6f1346d9154afbd2d558fabb4b0150fc8d559aa961254144fe1bc17fe6032f", "zh:56766eb62a74a9d88d9efb8486dd3a0c5c9db873d0a980ae9ef1e8af27d74231",
"zh:4c4c92d53fb535b1e0eff26f222bbd627b97d3b4c891ec9c321268676d06152f", "zh:6b4e8810d99498a5a20a5872982a0f1354e79cfc4a7dfe7cc656f1c7eaae47d8",
"zh:53276f68006c9ceb7cdb10a6ccf91a5c1eadd1407a28edb5741e84e88d7e29e8", "zh:6d65bdb4ec94b6eecc8abe26d94e2ca09262dc1e7a9934db829f418be0119920",
"zh:7925a97773948171a63d4f65bb81ee92fd6d07a447e36012977313293a5435c9", "zh:71adeaf31e41a358ec6095004062e43f56ee7d4b2504e5613ab351d511695641",
"zh:7dfb0a4496cfe032437386d0a2cd9229a1956e9c30bd920923c141b0f0440060",
"zh:890df766e9b839623b1f0437355032a3c006226a6c200cd911e15ee1a9014e9f", "zh:890df766e9b839623b1f0437355032a3c006226a6c200cd911e15ee1a9014e9f",
"zh:8d4aa79f0a414bb4163d771063c70cd991c8fac6c766e685bac2ee12903c5bd6", "zh:89761c15908ccc2cf9c50bb5cb3be45d3ad0c45fc7c608c6b95f48c0288b7160",
"zh:a67540c13565616a7e7e51ee9366e88b0dc60046e1d75c72680e150bd02725bb", "zh:8cc5d7c5939da89cfd01f3e51c84f3576564783acea9db86bd9e32049805ed96",
"zh:a936383a4767f5393f38f622e92bf2d0c03fe04b69c284951f27345766c7b31b", "zh:987cff8225b1dd436cdcb4fc6228689ae7e4281de6896412a2a9a3325c49f05e",
"zh:d4887d73c466ff036eecf50ad6404ba38fd82ea4855296b1846d244b0f13c380", "zh:991e83ebb89867d71e01a1c215ed159efb425683b0a44707be8579eb0a337f06",
"zh:e9093c8bd5b6cd99c81666e315197791781b8f93afa14fc2e0f732d1bb2a44b7", "zh:ab8177ae2d8f5cfa90043a6f867435012cae115f6061b832a7e2462e0ae87a67",
"zh:efd3b3f1ec59a37f635aa1d4efcf178734c2fcf8ddb0d56ea690bec342da8672", "zh:d1ca34df1398f201274a6a18102975148c10ca15aa43cfc56cc9897620929509",
"zh:d34946f70201baf6dda03e3b294c6bbe40d95d0278e97b9f636ded94822b24ac",
] ]
} }
@@ -5,7 +5,7 @@ terraform {
required_providers { required_providers {
cloudflare = { cloudflare = {
source = "cloudflare/cloudflare" source = "cloudflare/cloudflare"
version = "4.52.0" version = "4.50.0"
} }
} }
} }
+30 -30
View File
@@ -2,37 +2,37 @@
# Manual edits may be lost in future updates. # Manual edits may be lost in future updates.
provider "registry.opentofu.org/cloudflare/cloudflare" { provider "registry.opentofu.org/cloudflare/cloudflare" {
version = "4.52.0" version = "4.50.0"
constraints = "4.52.0" constraints = "4.50.0"
hashes = [ hashes = [
"h1:2BEJyXJtYC4B4nda/WCYUmuJYDaYk88F8t1pwPzr0iQ=", "h1:0qvD5ZKn2tMZ8cOjQrUSITIC9tKCZbrSaSswV9lOyiU=",
"h1:4IASk5SESeWKQ7JU0+M7KApuF5mZyklvwMXPBabim3c=", "h1:4N0gplrZ0zOsJv3Kx1VfIx2FwrZHbYU0Un2yfiLZIGQ=",
"h1:5ImZxxALSnWfH/4EXw/wFirSmk5Tr0ACmcysy51AafE=", "h1:81AMQq4kNKU/35U8ElQegUxG4E6xB0erIjG5xVmjIyo=",
"h1:6TJ3dxLSin4ZKBJLsZDn95H2ZYnGm8S7GGHvvXuuMQU=", "h1:EEQNADUmV3IL6x00yzy04i7OCSLeOMgM9XQkV3w71gA=",
"h1:IzTUjg9kQ4N3qizP9CjYLeHwjsuGgtxwXvfUQWyOLcA=", "h1:HD0KI7td6oiSSAnJNn8UPSGf+hKiTo4JVQYfAiU1SqM=",
"h1:NTaOQfYINA0YTG/V1/9+SYtgX1it63+cBugj4WK4FWc=", "h1:Hl+o5LtcvZg2f3l1hh9vaG/DFK6k+dTIZSeM0lXyfpo=",
"h1:PXH48LuJn329sCfMXprdMDk51EZaWFyajVvS03qhQLs=", "h1:ZUO2oIJ6jtZdvl816h0cEIiIeZ/fFCF64+abGEVxZZM=",
"h1:Pi5M+GeoMSN2eJ6QnIeXjBf19O+rby/74CfB2ocpv20=", "h1:Zio80fnEeUKdlSOhTVskMEFSLUQ6TMsMKnXc+Dy2P2A=",
"h1:ShXZ2ZjBvm3thfoPPzPT8+OhyismnydQVkUAfI8X12w=", "h1:aLLvg36evTyqjtXGV2MjAV8imktXFmry7p/xCu9GQC4=",
"h1:WQ9hu0Wge2msBbODfottCSKgu8oKUrw4Opz+fDPVVHk=", "h1:azL05eWyy2V8SWkbZZImPWvv8ynG4eqmrbZhjXBDFug=",
"h1:Z5yXML2DE0uH9UU+M0ut9JMQAORcwVZz1CxBHzeBmao=", "h1:ckMysHY4fJmr7o58XMi+DdgOTB/U/Mf1u1JA9ly3g/I=",
"h1:jqI2qKknpleS3JDSplyGYHMu0u9K/tor1ZOjFwDgEMk=", "h1:jxOwjDNjt5WCb4YjjiMsman91O8Y+MAPz6UwJ4a6F+0=",
"h1:kgfutDh14Q5nw4eg6qGFamFxIiY8Ae0FPKRBLDOzpcI=", "h1:u4OfnjSLa4Wk1IUFAzrvMnGgr8MvRHEWVDHEScPK2E8=",
"h1:zCAO7GZmfYhWb+i6TfqlqhMeDyPZWGio2IzEzAh3YTs=", "h1:wQkR1oeSkzlHn3rnVuLJRJLBHlg4EHt7Y64DeTjfkjQ=",
"zh:19be1a91c982b902c42aba47766860dfa5dc151eed1e95fd39ca642229381ef0", "zh:0ef99ed39472a94e6a0d6fa733cf0a46bce3bf66eba2873efae8846efdddc237",
"zh:1de451c4d1ecf7efbe67b6dace3426ba810711afdd644b0f1b870364c8ae91f8", "zh:2929cbbffcead171d45c88e4a7a59e9c013ea775dafa68b10da8db7cd04b6140",
"zh:352b4a2120173298622e669258744554339d959ac3a95607b117a48ee4a83238", "zh:462601c87118088e1a718842e367af7d8e7620598d426980a6d6b33de759865e",
"zh:3c6f1346d9154afbd2d558fabb4b0150fc8d559aa961254144fe1bc17fe6032f", "zh:56766eb62a74a9d88d9efb8486dd3a0c5c9db873d0a980ae9ef1e8af27d74231",
"zh:4c4c92d53fb535b1e0eff26f222bbd627b97d3b4c891ec9c321268676d06152f", "zh:6b4e8810d99498a5a20a5872982a0f1354e79cfc4a7dfe7cc656f1c7eaae47d8",
"zh:53276f68006c9ceb7cdb10a6ccf91a5c1eadd1407a28edb5741e84e88d7e29e8", "zh:6d65bdb4ec94b6eecc8abe26d94e2ca09262dc1e7a9934db829f418be0119920",
"zh:7925a97773948171a63d4f65bb81ee92fd6d07a447e36012977313293a5435c9", "zh:71adeaf31e41a358ec6095004062e43f56ee7d4b2504e5613ab351d511695641",
"zh:7dfb0a4496cfe032437386d0a2cd9229a1956e9c30bd920923c141b0f0440060",
"zh:890df766e9b839623b1f0437355032a3c006226a6c200cd911e15ee1a9014e9f", "zh:890df766e9b839623b1f0437355032a3c006226a6c200cd911e15ee1a9014e9f",
"zh:8d4aa79f0a414bb4163d771063c70cd991c8fac6c766e685bac2ee12903c5bd6", "zh:89761c15908ccc2cf9c50bb5cb3be45d3ad0c45fc7c608c6b95f48c0288b7160",
"zh:a67540c13565616a7e7e51ee9366e88b0dc60046e1d75c72680e150bd02725bb", "zh:8cc5d7c5939da89cfd01f3e51c84f3576564783acea9db86bd9e32049805ed96",
"zh:a936383a4767f5393f38f622e92bf2d0c03fe04b69c284951f27345766c7b31b", "zh:987cff8225b1dd436cdcb4fc6228689ae7e4281de6896412a2a9a3325c49f05e",
"zh:d4887d73c466ff036eecf50ad6404ba38fd82ea4855296b1846d244b0f13c380", "zh:991e83ebb89867d71e01a1c215ed159efb425683b0a44707be8579eb0a337f06",
"zh:e9093c8bd5b6cd99c81666e315197791781b8f93afa14fc2e0f732d1bb2a44b7", "zh:ab8177ae2d8f5cfa90043a6f867435012cae115f6061b832a7e2462e0ae87a67",
"zh:efd3b3f1ec59a37f635aa1d4efcf178734c2fcf8ddb0d56ea690bec342da8672", "zh:d1ca34df1398f201274a6a18102975148c10ca15aa43cfc56cc9897620929509",
"zh:d34946f70201baf6dda03e3b294c6bbe40d95d0278e97b9f636ded94822b24ac",
] ]
} }
+1 -1
View File
@@ -5,7 +5,7 @@ terraform {
required_providers { required_providers {
cloudflare = { cloudflare = {
source = "cloudflare/cloudflare" source = "cloudflare/cloudflare"
version = "4.52.0" version = "4.50.0"
} }
} }
} }
+1 -1
View File
@@ -103,7 +103,7 @@ services:
command: ['./run.sh', '-disable-reporting'] command: ['./run.sh', '-disable-reporting']
ports: ports:
- 3000:3000 - 3000:3000
image: grafana/grafana:11.5.1-ubuntu@sha256:9a4ab78cec1a2ec7d1ca5dfd5aacec6412706a1bc9e971fc7184e2f6696a63f5 image: grafana/grafana:11.4.0-ubuntu@sha256:afccec22ba0e4815cca1d2bf3836e414322390dc78d77f1851976ffa8d61051c
volumes: volumes:
- grafana-data:/var/lib/grafana - grafana-data:/var/lib/grafana
+1 -1
View File
@@ -76,7 +76,7 @@ To see local changes to `@immich/ui` in Immich, do the following:
### Mobile app ### Mobile app
The mobile app `(/mobile)` will required Flutter toolchain 3.13.x and FVM to be installed on your system. The mobile app `(/mobile)` will required Flutter toolchain 3.13.x to be installed on your system.
Please refer to the [Flutter's official documentation](https://flutter.dev/docs/get-started/install) for more information on setting up the toolchain on your machine. Please refer to the [Flutter's official documentation](https://flutter.dev/docs/get-started/install) for more information on setting up the toolchain on your machine.
@@ -11,7 +11,7 @@ You do not need to redo any machine learning jobs after enabling hardware accele
- ARM NN (Mali) - ARM NN (Mali)
- CUDA (NVIDIA GPUs with [compute capability](https://developer.nvidia.com/cuda-gpus) 5.2 or higher) - CUDA (NVIDIA GPUs with [compute capability](https://developer.nvidia.com/cuda-gpus) 5.2 or higher)
- OpenVINO (Intel GPUs such as Iris Xe and Arc) - OpenVINO (Intel discrete GPUs such as Iris Xe and Arc)
## Limitations ## Limitations
@@ -43,9 +43,8 @@ You do not need to redo any machine learning jobs after enabling hardware accele
#### OpenVINO #### OpenVINO
- Integrated GPUs are more likely to experience issues than discrete GPUs, especially for older processors or servers with low RAM. - The server must have a discrete GPU, i.e. Iris Xe or Arc. Expect issues when attempting to use integrated graphics.
- Ensure the server's kernel version is new enough to use the device for hardware accceleration. - Ensure the server's kernel version is new enough to use the device for hardware accceleration.
- Expect higher RAM usage when using OpenVINO compared to CPU processing.
## Setup ## Setup
+1 -1
View File
@@ -44,7 +44,7 @@ export default function VersionSwitcher(): JSX.Element {
return ( return (
versions.length > 0 && ( versions.length > 0 && (
<DropdownNavbarItem <DropdownNavbarItem
className="version-switcher-34ab39" className="navbar__item"
label={label} label={label}
mobile={windowSize === 'mobile'} mobile={windowSize === 'mobile'}
items={versions.map(({ label, url }) => ({ items={versions.map(({ label, url }) => ({
-5
View File
@@ -75,11 +75,6 @@ div[class^='announcementBar_'] {
font-weight: 500; font-weight: 500;
} }
/* workaround for version switcher PR 15894 */
div[class*='navbar__items'] > li:has(a[class*='version-switcher-34ab39']) {
display: none;
}
code { code {
font-weight: 600; font-weight: 600;
} }
-4
View File
@@ -1,8 +1,4 @@
[ [
{
"label": "v1.126.0",
"url": "https://v1.126.0.archive.immich.app"
},
{ {
"label": "v1.125.7", "label": "v1.125.7",
"url": "https://v1.125.7.archive.immich.app" "url": "https://v1.125.7.archive.immich.app"
+3
View File
@@ -32,6 +32,9 @@ services:
- database - database
ports: ports:
- 2285:2285 - 2285:2285
cap_drop:
# We need this to perform testing on permission errors
- DAC_OVERRIDE
redis: redis:
image: redis:6.2-alpine@sha256:905c4ee67b8e0aa955331960d2aa745781e6bd89afc44a8584bfd13bc890f0ae image: redis:6.2-alpine@sha256:905c4ee67b8e0aa955331960d2aa745781e6bd89afc44a8584bfd13bc890f0ae
+4 -4
View File
@@ -1,12 +1,12 @@
{ {
"name": "immich-e2e", "name": "immich-e2e",
"version": "1.126.0", "version": "1.125.7",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "immich-e2e", "name": "immich-e2e",
"version": "1.126.0", "version": "1.125.7",
"license": "GNU Affero General Public License version 3", "license": "GNU Affero General Public License version 3",
"devDependencies": { "devDependencies": {
"@eslint/eslintrc": "^3.1.0", "@eslint/eslintrc": "^3.1.0",
@@ -45,7 +45,7 @@
}, },
"../cli": { "../cli": {
"name": "@immich/cli", "name": "@immich/cli",
"version": "2.2.49", "version": "2.2.48",
"dev": true, "dev": true,
"license": "GNU Affero General Public License version 3", "license": "GNU Affero General Public License version 3",
"dependencies": { "dependencies": {
@@ -92,7 +92,7 @@
}, },
"../open-api/typescript-sdk": { "../open-api/typescript-sdk": {
"name": "@immich/sdk", "name": "@immich/sdk",
"version": "1.126.0", "version": "1.125.7",
"dev": true, "dev": true,
"license": "GNU Affero General Public License version 3", "license": "GNU Affero General Public License version 3",
"dependencies": { "dependencies": {
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "immich-e2e", "name": "immich-e2e",
"version": "1.126.0", "version": "1.125.7",
"description": "", "description": "",
"main": "index.js", "main": "index.js",
"type": "module", "type": "module",
+29 -1
View File
@@ -1,5 +1,5 @@
import { LibraryResponseDto, LoginResponseDto, getAllLibraries, scanLibrary } from '@immich/sdk'; import { LibraryResponseDto, LoginResponseDto, getAllLibraries, scanLibrary } from '@immich/sdk';
import { cpSync, existsSync, rmSync, unlinkSync } from 'node:fs'; import { chmodSync, cpSync, existsSync, promises, rmSync, unlinkSync } from 'node:fs';
import { Socket } from 'socket.io-client'; import { Socket } from 'socket.io-client';
import { userDto, uuidDto } from 'src/fixtures'; import { userDto, uuidDto } from 'src/fixtures';
import { errorDto } from 'src/responses'; import { errorDto } from 'src/responses';
@@ -492,6 +492,34 @@ describe('/libraries', () => {
utils.removeImageFile(`${testAssetDir}/temp/folder${char}2/asset2.png`); utils.removeImageFile(`${testAssetDir}/temp/folder${char}2/asset2.png`);
}); });
it('should handle permission errors on import paths without error', async () => {
const library = await utils.createLibrary(admin.accessToken, {
ownerId: admin.userId,
importPaths: [`${testAssetDirInternal}/temp/`],
});
const stat = await promises.stat(`${testAssetDir}/temp/directoryA`);
const mode = stat.mode;
chmodSync(`${testAssetDir}/temp/directoryB`, 0o000);
const { status } = await request(app)
.post(`/libraries/${library.id}/scan`)
.set('Authorization', `Bearer ${admin.accessToken}`)
.send();
expect(status).toBe(204);
await utils.waitForQueueFinish(admin.accessToken, 'library');
chmodSync(`${testAssetDir}/temp/directoryB`, mode);
const { assets } = await utils.searchAssets(admin.accessToken, { libraryId: library.id });
expect(assets.count).toBe(1);
expect(assets.items.find((asset) => asset.originalPath.includes('directoryA'))).toBeDefined();
expect(assets.items.find((asset) => asset.originalPath.includes('directoryB'))).not.toBeDefined();
});
it('should reimport a modified file', async () => { it('should reimport a modified file', async () => {
const library = await utils.createLibrary(admin.accessToken, { const library = await utils.createLibrary(admin.accessToken, {
ownerId: admin.userId, ownerId: admin.userId,
-19
View File
@@ -195,7 +195,6 @@ describe('/people', () => {
.send({ .send({
name: 'New Person', name: 'New Person',
birthDate: '1990-01-01', birthDate: '1990-01-01',
color: '#333',
}); });
expect(status).toBe(201); expect(status).toBe(201);
expect(body).toMatchObject({ expect(body).toMatchObject({
@@ -274,24 +273,6 @@ describe('/people', () => {
expect(body).toMatchObject({ birthDate: null }); expect(body).toMatchObject({ birthDate: null });
}); });
it('should set a color', async () => {
const { status, body } = await request(app)
.put(`/people/${visiblePerson.id}`)
.set('Authorization', `Bearer ${admin.accessToken}`)
.send({ color: '#555' });
expect(status).toBe(200);
expect(body).toMatchObject({ color: '#555' });
});
it('should clear a color', async () => {
const { status, body } = await request(app)
.put(`/people/${visiblePerson.id}`)
.set('Authorization', `Bearer ${admin.accessToken}`)
.send({ color: null });
expect(status).toBe(200);
expect(body.color).toBeUndefined();
});
it('should mark a person as favorite', async () => { it('should mark a person as favorite', async () => {
const person = await utils.createPerson(admin.accessToken, { const person = await utils.createPerson(admin.accessToken, {
name: 'visible_person', name: 'visible_person',
-24
View File
@@ -150,30 +150,6 @@ describe('/shared-links', () => {
); );
}); });
it('should filter on albumId', async () => {
const { status, body } = await request(app)
.get(`/shared-links?albumId=${album.id}`)
.set('Authorization', `Bearer ${user1.accessToken}`);
expect(status).toBe(200);
expect(body).toHaveLength(2);
expect(body).toEqual(
expect.arrayContaining([
expect.objectContaining({ id: linkWithAlbum.id }),
expect.objectContaining({ id: linkWithPassword.id }),
]),
);
});
it('should find 0 albums', async () => {
const { status, body } = await request(app)
.get(`/shared-links?albumId=${uuidDto.notFound}`)
.set('Authorization', `Bearer ${user1.accessToken}`);
expect(status).toBe(200);
expect(body).toHaveLength(0);
});
it('should not get shared links created by other users', async () => { it('should not get shared links created by other users', async () => {
const { status, body } = await request(app) const { status, body } = await request(app)
.get('/shared-links') .get('/shared-links')
-7
View File
@@ -436,7 +436,6 @@
"back_close_deselect": "Back, close, or deselect", "back_close_deselect": "Back, close, or deselect",
"backward": "Backward", "backward": "Backward",
"birthdate_saved": "Date of birth saved successfully", "birthdate_saved": "Date of birth saved successfully",
"show_shared_links": "Show shared links",
"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", "bugs_and_feature_requests": "Bugs & Feature Requests",
@@ -769,10 +768,8 @@
"go_to_search": "Go to search", "go_to_search": "Go to search",
"go_to_folder": "Go to folder", "go_to_folder": "Go to folder",
"group_albums_by": "Group albums by...", "group_albums_by": "Group albums by...",
"group_country": "Group by country",
"group_no": "No grouping", "group_no": "No grouping",
"group_owner": "Group by owner", "group_owner": "Group by owner",
"group_places_by": "Group places by...",
"group_year": "Group by year", "group_year": "Group by year",
"has_quota": "Has quota", "has_quota": "Has quota",
"hi_user": "Hi {name} ({email})", "hi_user": "Hi {name} ({email})",
@@ -805,7 +802,6 @@
"include_shared_albums": "Include shared albums", "include_shared_albums": "Include shared albums",
"include_shared_partner_assets": "Include shared partner assets", "include_shared_partner_assets": "Include shared partner assets",
"individual_share": "Individual share", "individual_share": "Individual share",
"individual_shares": "Individual shares",
"info": "Info", "info": "Info",
"interval": { "interval": {
"day_at_onepm": "Every day at 1pm", "day_at_onepm": "Every day at 1pm",
@@ -991,7 +987,6 @@
"pick_a_location": "Pick a location", "pick_a_location": "Pick a location",
"place": "Place", "place": "Place",
"places": "Places", "places": "Places",
"places_count": "{count, plural, one {{count, number} Place} other {{count, number} Places}}",
"play": "Play", "play": "Play",
"play_memories": "Play memories", "play_memories": "Play memories",
"play_motion_photo": "Play Motion Photo", "play_motion_photo": "Play Motion Photo",
@@ -1174,7 +1169,6 @@
"shared_from_partner": "Photos from {partner}", "shared_from_partner": "Photos from {partner}",
"shared_link_options": "Shared link options", "shared_link_options": "Shared link options",
"shared_links": "Shared links", "shared_links": "Shared links",
"shared_links_description": "Share photos and videos with a link",
"shared_photos_and_videos_count": "{assetCount, plural, other {# shared photos & videos.}}", "shared_photos_and_videos_count": "{assetCount, plural, other {# shared photos & videos.}}",
"shared_with_partner": "Shared with {partner}", "shared_with_partner": "Shared with {partner}",
"sharing": "Sharing", "sharing": "Sharing",
@@ -1284,7 +1278,6 @@
"unfavorite": "Unfavorite", "unfavorite": "Unfavorite",
"unhide_person": "Unhide person", "unhide_person": "Unhide person",
"unknown": "Unknown", "unknown": "Unknown",
"unknown_country": "Unknown Country",
"unknown_year": "Unknown Year", "unknown_year": "Unknown Year",
"unlimited": "Unlimited", "unlimited": "Unlimited",
"unlink_motion_video": "Unlink motion video", "unlink_motion_video": "Unlink motion video",
+1 -1
View File
@@ -1,6 +1,6 @@
[tool.poetry] [tool.poetry]
name = "machine-learning" name = "machine-learning"
version = "1.126.0" version = "1.125.7"
description = "" description = ""
authors = ["Hau Tran <alex.tran1502@gmail.com>"] authors = ["Hau Tran <alex.tran1502@gmail.com>"]
readme = "README.md" readme = "README.md"
+2 -2
View File
@@ -35,8 +35,8 @@ platform :android do
task: 'bundle', task: 'bundle',
build_type: 'Release', build_type: 'Release',
properties: { properties: {
"android.injected.version.code" => 183, "android.injected.version.code" => 182,
"android.injected.version.name" => "1.126.0", "android.injected.version.name" => "1.125.7",
} }
) )
upload_to_play_store(skip_upload_apk: true, skip_upload_images: true, skip_upload_screenshots: true, aab: '../build/app/outputs/bundle/release/app-release.aab') upload_to_play_store(skip_upload_apk: true, skip_upload_images: true, skip_upload_screenshots: true, aab: '../build/app/outputs/bundle/release/app-release.aab')
+1 -1
View File
@@ -19,7 +19,7 @@ platform :ios do
desc "iOS Release" desc "iOS Release"
lane :release do lane :release do
increment_version_number( increment_version_number(
version_number: "1.126.0" version_number: "1.125.7"
) )
increment_build_number( increment_build_number(
build_number: latest_testflight_build_number + 1, build_number: latest_testflight_build_number + 1,
+1 -3
View File
@@ -3,7 +3,7 @@ Immich API
This Dart package is automatically generated by the [OpenAPI Generator](https://openapi-generator.tech) project: This Dart package is automatically generated by the [OpenAPI Generator](https://openapi-generator.tech) project:
- API version: 1.126.0 - API version: 1.125.7
- Generator version: 7.8.0 - Generator version: 7.8.0
- Build package: org.openapitools.codegen.languages.DartClientCodegen - Build package: org.openapitools.codegen.languages.DartClientCodegen
@@ -408,8 +408,6 @@ Class | Method | HTTP request | Description
- [SharedLinkEditDto](doc//SharedLinkEditDto.md) - [SharedLinkEditDto](doc//SharedLinkEditDto.md)
- [SharedLinkResponseDto](doc//SharedLinkResponseDto.md) - [SharedLinkResponseDto](doc//SharedLinkResponseDto.md)
- [SharedLinkType](doc//SharedLinkType.md) - [SharedLinkType](doc//SharedLinkType.md)
- [SharedLinksResponse](doc//SharedLinksResponse.md)
- [SharedLinksUpdate](doc//SharedLinksUpdate.md)
- [SignUpDto](doc//SignUpDto.md) - [SignUpDto](doc//SignUpDto.md)
- [SmartSearchDto](doc//SmartSearchDto.md) - [SmartSearchDto](doc//SmartSearchDto.md)
- [SourceType](doc//SourceType.md) - [SourceType](doc//SourceType.md)
-2
View File
@@ -221,8 +221,6 @@ part 'model/shared_link_create_dto.dart';
part 'model/shared_link_edit_dto.dart'; part 'model/shared_link_edit_dto.dart';
part 'model/shared_link_response_dto.dart'; part 'model/shared_link_response_dto.dart';
part 'model/shared_link_type.dart'; part 'model/shared_link_type.dart';
part 'model/shared_links_response.dart';
part 'model/shared_links_update.dart';
part 'model/sign_up_dto.dart'; part 'model/sign_up_dto.dart';
part 'model/smart_search_dto.dart'; part 'model/smart_search_dto.dart';
part 'model/source_type.dart'; part 'model/source_type.dart';
+3 -13
View File
@@ -127,10 +127,7 @@ class SharedLinksApi {
} }
/// Performs an HTTP 'GET /shared-links' operation and returns the [Response]. /// Performs an HTTP 'GET /shared-links' operation and returns the [Response].
/// Parameters: Future<Response> getAllSharedLinksWithHttpInfo() async {
///
/// * [String] albumId:
Future<Response> getAllSharedLinksWithHttpInfo({ String? albumId, }) async {
// ignore: prefer_const_declarations // ignore: prefer_const_declarations
final path = r'/shared-links'; final path = r'/shared-links';
@@ -141,10 +138,6 @@ class SharedLinksApi {
final headerParams = <String, String>{}; final headerParams = <String, String>{};
final formParams = <String, String>{}; final formParams = <String, String>{};
if (albumId != null) {
queryParams.addAll(_queryParams('', 'albumId', albumId));
}
const contentTypes = <String>[]; const contentTypes = <String>[];
@@ -159,11 +152,8 @@ class SharedLinksApi {
); );
} }
/// Parameters: Future<List<SharedLinkResponseDto>?> getAllSharedLinks() async {
/// final response = await getAllSharedLinksWithHttpInfo();
/// * [String] albumId:
Future<List<SharedLinkResponseDto>?> getAllSharedLinks({ String? albumId, }) async {
final response = await getAllSharedLinksWithHttpInfo( albumId: albumId, );
if (response.statusCode >= HttpStatus.badRequest) { if (response.statusCode >= HttpStatus.badRequest) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response)); throw ApiException(response.statusCode, await _decodeBodyBytes(response));
} }
-4
View File
@@ -496,10 +496,6 @@ class ApiClient {
return SharedLinkResponseDto.fromJson(value); return SharedLinkResponseDto.fromJson(value);
case 'SharedLinkType': case 'SharedLinkType':
return SharedLinkTypeTypeTransformer().decode(value); return SharedLinkTypeTypeTransformer().decode(value);
case 'SharedLinksResponse':
return SharedLinksResponse.fromJson(value);
case 'SharedLinksUpdate':
return SharedLinksUpdate.fromJson(value);
case 'SignUpDto': case 'SignUpDto':
return SignUpDto.fromJson(value); return SignUpDto.fromJson(value);
case 'SmartSearchDto': case 'SmartSearchDto':
+1 -12
View File
@@ -14,7 +14,6 @@ class PeopleUpdateItem {
/// Returns a new [PeopleUpdateItem] instance. /// Returns a new [PeopleUpdateItem] instance.
PeopleUpdateItem({ PeopleUpdateItem({
this.birthDate, this.birthDate,
this.color,
this.featureFaceAssetId, this.featureFaceAssetId,
required this.id, required this.id,
this.isFavorite, this.isFavorite,
@@ -25,8 +24,6 @@ class PeopleUpdateItem {
/// Person date of birth. Note: the mobile app cannot currently set the birth date to null. /// Person date of birth. Note: the mobile app cannot currently set the birth date to null.
DateTime? birthDate; DateTime? birthDate;
String? color;
/// Asset is used to get the feature face thumbnail. /// Asset is used to get the feature face thumbnail.
/// ///
/// Please note: This property should have been non-nullable! Since the specification file /// Please note: This property should have been non-nullable! Since the specification file
@@ -68,7 +65,6 @@ class PeopleUpdateItem {
@override @override
bool operator ==(Object other) => identical(this, other) || other is PeopleUpdateItem && bool operator ==(Object other) => identical(this, other) || other is PeopleUpdateItem &&
other.birthDate == birthDate && other.birthDate == birthDate &&
other.color == color &&
other.featureFaceAssetId == featureFaceAssetId && other.featureFaceAssetId == featureFaceAssetId &&
other.id == id && other.id == id &&
other.isFavorite == isFavorite && other.isFavorite == isFavorite &&
@@ -79,7 +75,6 @@ class PeopleUpdateItem {
int get hashCode => int get hashCode =>
// ignore: unnecessary_parenthesis // ignore: unnecessary_parenthesis
(birthDate == null ? 0 : birthDate!.hashCode) + (birthDate == null ? 0 : birthDate!.hashCode) +
(color == null ? 0 : color!.hashCode) +
(featureFaceAssetId == null ? 0 : featureFaceAssetId!.hashCode) + (featureFaceAssetId == null ? 0 : featureFaceAssetId!.hashCode) +
(id.hashCode) + (id.hashCode) +
(isFavorite == null ? 0 : isFavorite!.hashCode) + (isFavorite == null ? 0 : isFavorite!.hashCode) +
@@ -87,7 +82,7 @@ class PeopleUpdateItem {
(name == null ? 0 : name!.hashCode); (name == null ? 0 : name!.hashCode);
@override @override
String toString() => 'PeopleUpdateItem[birthDate=$birthDate, color=$color, featureFaceAssetId=$featureFaceAssetId, id=$id, isFavorite=$isFavorite, isHidden=$isHidden, name=$name]'; String toString() => 'PeopleUpdateItem[birthDate=$birthDate, featureFaceAssetId=$featureFaceAssetId, id=$id, isFavorite=$isFavorite, isHidden=$isHidden, name=$name]';
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
final json = <String, dynamic>{}; final json = <String, dynamic>{};
@@ -96,11 +91,6 @@ class PeopleUpdateItem {
} else { } else {
// json[r'birthDate'] = null; // json[r'birthDate'] = null;
} }
if (this.color != null) {
json[r'color'] = this.color;
} else {
// json[r'color'] = null;
}
if (this.featureFaceAssetId != null) { if (this.featureFaceAssetId != null) {
json[r'featureFaceAssetId'] = this.featureFaceAssetId; json[r'featureFaceAssetId'] = this.featureFaceAssetId;
} else { } else {
@@ -135,7 +125,6 @@ class PeopleUpdateItem {
return PeopleUpdateItem( return PeopleUpdateItem(
birthDate: mapDateTime(json, r'birthDate', r''), birthDate: mapDateTime(json, r'birthDate', r''),
color: mapValueOfType<String>(json, r'color'),
featureFaceAssetId: mapValueOfType<String>(json, r'featureFaceAssetId'), featureFaceAssetId: mapValueOfType<String>(json, r'featureFaceAssetId'),
id: mapValueOfType<String>(json, r'id')!, id: mapValueOfType<String>(json, r'id')!,
isFavorite: mapValueOfType<bool>(json, r'isFavorite'), isFavorite: mapValueOfType<bool>(json, r'isFavorite'),
+1 -12
View File
@@ -14,7 +14,6 @@ class PersonCreateDto {
/// Returns a new [PersonCreateDto] instance. /// Returns a new [PersonCreateDto] instance.
PersonCreateDto({ PersonCreateDto({
this.birthDate, this.birthDate,
this.color,
this.isFavorite, this.isFavorite,
this.isHidden, this.isHidden,
this.name, this.name,
@@ -23,8 +22,6 @@ class PersonCreateDto {
/// Person date of birth. Note: the mobile app cannot currently set the birth date to null. /// Person date of birth. Note: the mobile app cannot currently set the birth date to null.
DateTime? birthDate; DateTime? birthDate;
String? color;
/// ///
/// Please note: This property should have been non-nullable! Since the specification file /// 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 /// does not include a default value (using the "default:" property), however, the generated
@@ -54,7 +51,6 @@ class PersonCreateDto {
@override @override
bool operator ==(Object other) => identical(this, other) || other is PersonCreateDto && bool operator ==(Object other) => identical(this, other) || other is PersonCreateDto &&
other.birthDate == birthDate && other.birthDate == birthDate &&
other.color == color &&
other.isFavorite == isFavorite && other.isFavorite == isFavorite &&
other.isHidden == isHidden && other.isHidden == isHidden &&
other.name == name; other.name == name;
@@ -63,13 +59,12 @@ class PersonCreateDto {
int get hashCode => int get hashCode =>
// ignore: unnecessary_parenthesis // ignore: unnecessary_parenthesis
(birthDate == null ? 0 : birthDate!.hashCode) + (birthDate == null ? 0 : birthDate!.hashCode) +
(color == null ? 0 : color!.hashCode) +
(isFavorite == null ? 0 : isFavorite!.hashCode) + (isFavorite == null ? 0 : isFavorite!.hashCode) +
(isHidden == null ? 0 : isHidden!.hashCode) + (isHidden == null ? 0 : isHidden!.hashCode) +
(name == null ? 0 : name!.hashCode); (name == null ? 0 : name!.hashCode);
@override @override
String toString() => 'PersonCreateDto[birthDate=$birthDate, color=$color, isFavorite=$isFavorite, isHidden=$isHidden, name=$name]'; String toString() => 'PersonCreateDto[birthDate=$birthDate, isFavorite=$isFavorite, isHidden=$isHidden, name=$name]';
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
final json = <String, dynamic>{}; final json = <String, dynamic>{};
@@ -78,11 +73,6 @@ class PersonCreateDto {
} else { } else {
// json[r'birthDate'] = null; // json[r'birthDate'] = null;
} }
if (this.color != null) {
json[r'color'] = this.color;
} else {
// json[r'color'] = null;
}
if (this.isFavorite != null) { if (this.isFavorite != null) {
json[r'isFavorite'] = this.isFavorite; json[r'isFavorite'] = this.isFavorite;
} else { } else {
@@ -111,7 +101,6 @@ class PersonCreateDto {
return PersonCreateDto( return PersonCreateDto(
birthDate: mapDateTime(json, r'birthDate', r''), birthDate: mapDateTime(json, r'birthDate', r''),
color: mapValueOfType<String>(json, r'color'),
isFavorite: mapValueOfType<bool>(json, r'isFavorite'), isFavorite: mapValueOfType<bool>(json, r'isFavorite'),
isHidden: mapValueOfType<bool>(json, r'isHidden'), isHidden: mapValueOfType<bool>(json, r'isHidden'),
name: mapValueOfType<String>(json, r'name'), name: mapValueOfType<String>(json, r'name'),
+1 -19
View File
@@ -14,7 +14,6 @@ class PersonResponseDto {
/// Returns a new [PersonResponseDto] instance. /// Returns a new [PersonResponseDto] instance.
PersonResponseDto({ PersonResponseDto({
required this.birthDate, required this.birthDate,
this.color,
required this.id, required this.id,
this.isFavorite, this.isFavorite,
required this.isHidden, required this.isHidden,
@@ -25,15 +24,6 @@ class PersonResponseDto {
DateTime? birthDate; DateTime? birthDate;
/// This property was added in v1.126.0
///
/// 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? color;
String id; String id;
/// This property was added in v1.126.0 /// This property was added in v1.126.0
@@ -63,7 +53,6 @@ class PersonResponseDto {
@override @override
bool operator ==(Object other) => identical(this, other) || other is PersonResponseDto && bool operator ==(Object other) => identical(this, other) || other is PersonResponseDto &&
other.birthDate == birthDate && other.birthDate == birthDate &&
other.color == color &&
other.id == id && other.id == id &&
other.isFavorite == isFavorite && other.isFavorite == isFavorite &&
other.isHidden == isHidden && other.isHidden == isHidden &&
@@ -75,7 +64,6 @@ class PersonResponseDto {
int get hashCode => int get hashCode =>
// ignore: unnecessary_parenthesis // ignore: unnecessary_parenthesis
(birthDate == null ? 0 : birthDate!.hashCode) + (birthDate == null ? 0 : birthDate!.hashCode) +
(color == null ? 0 : color!.hashCode) +
(id.hashCode) + (id.hashCode) +
(isFavorite == null ? 0 : isFavorite!.hashCode) + (isFavorite == null ? 0 : isFavorite!.hashCode) +
(isHidden.hashCode) + (isHidden.hashCode) +
@@ -84,7 +72,7 @@ class PersonResponseDto {
(updatedAt == null ? 0 : updatedAt!.hashCode); (updatedAt == null ? 0 : updatedAt!.hashCode);
@override @override
String toString() => 'PersonResponseDto[birthDate=$birthDate, color=$color, id=$id, isFavorite=$isFavorite, isHidden=$isHidden, name=$name, thumbnailPath=$thumbnailPath, updatedAt=$updatedAt]'; String toString() => 'PersonResponseDto[birthDate=$birthDate, id=$id, isFavorite=$isFavorite, isHidden=$isHidden, name=$name, thumbnailPath=$thumbnailPath, updatedAt=$updatedAt]';
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
final json = <String, dynamic>{}; final json = <String, dynamic>{};
@@ -92,11 +80,6 @@ class PersonResponseDto {
json[r'birthDate'] = _dateFormatter.format(this.birthDate!.toUtc()); json[r'birthDate'] = _dateFormatter.format(this.birthDate!.toUtc());
} else { } else {
// json[r'birthDate'] = null; // json[r'birthDate'] = null;
}
if (this.color != null) {
json[r'color'] = this.color;
} else {
// json[r'color'] = null;
} }
json[r'id'] = this.id; json[r'id'] = this.id;
if (this.isFavorite != null) { if (this.isFavorite != null) {
@@ -125,7 +108,6 @@ class PersonResponseDto {
return PersonResponseDto( return PersonResponseDto(
birthDate: mapDateTime(json, r'birthDate', r''), birthDate: mapDateTime(json, r'birthDate', r''),
color: mapValueOfType<String>(json, r'color'),
id: mapValueOfType<String>(json, r'id')!, id: mapValueOfType<String>(json, r'id')!,
isFavorite: mapValueOfType<bool>(json, r'isFavorite'), isFavorite: mapValueOfType<bool>(json, r'isFavorite'),
isHidden: mapValueOfType<bool>(json, r'isHidden')!, isHidden: mapValueOfType<bool>(json, r'isHidden')!,
+1 -12
View File
@@ -14,7 +14,6 @@ class PersonUpdateDto {
/// Returns a new [PersonUpdateDto] instance. /// Returns a new [PersonUpdateDto] instance.
PersonUpdateDto({ PersonUpdateDto({
this.birthDate, this.birthDate,
this.color,
this.featureFaceAssetId, this.featureFaceAssetId,
this.isFavorite, this.isFavorite,
this.isHidden, this.isHidden,
@@ -24,8 +23,6 @@ class PersonUpdateDto {
/// Person date of birth. Note: the mobile app cannot currently set the birth date to null. /// Person date of birth. Note: the mobile app cannot currently set the birth date to null.
DateTime? birthDate; DateTime? birthDate;
String? color;
/// Asset is used to get the feature face thumbnail. /// Asset is used to get the feature face thumbnail.
/// ///
/// Please note: This property should have been non-nullable! Since the specification file /// Please note: This property should have been non-nullable! Since the specification file
@@ -64,7 +61,6 @@ class PersonUpdateDto {
@override @override
bool operator ==(Object other) => identical(this, other) || other is PersonUpdateDto && bool operator ==(Object other) => identical(this, other) || other is PersonUpdateDto &&
other.birthDate == birthDate && other.birthDate == birthDate &&
other.color == color &&
other.featureFaceAssetId == featureFaceAssetId && other.featureFaceAssetId == featureFaceAssetId &&
other.isFavorite == isFavorite && other.isFavorite == isFavorite &&
other.isHidden == isHidden && other.isHidden == isHidden &&
@@ -74,14 +70,13 @@ class PersonUpdateDto {
int get hashCode => int get hashCode =>
// ignore: unnecessary_parenthesis // ignore: unnecessary_parenthesis
(birthDate == null ? 0 : birthDate!.hashCode) + (birthDate == null ? 0 : birthDate!.hashCode) +
(color == null ? 0 : color!.hashCode) +
(featureFaceAssetId == null ? 0 : featureFaceAssetId!.hashCode) + (featureFaceAssetId == null ? 0 : featureFaceAssetId!.hashCode) +
(isFavorite == null ? 0 : isFavorite!.hashCode) + (isFavorite == null ? 0 : isFavorite!.hashCode) +
(isHidden == null ? 0 : isHidden!.hashCode) + (isHidden == null ? 0 : isHidden!.hashCode) +
(name == null ? 0 : name!.hashCode); (name == null ? 0 : name!.hashCode);
@override @override
String toString() => 'PersonUpdateDto[birthDate=$birthDate, color=$color, featureFaceAssetId=$featureFaceAssetId, isFavorite=$isFavorite, isHidden=$isHidden, name=$name]'; String toString() => 'PersonUpdateDto[birthDate=$birthDate, featureFaceAssetId=$featureFaceAssetId, isFavorite=$isFavorite, isHidden=$isHidden, name=$name]';
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
final json = <String, dynamic>{}; final json = <String, dynamic>{};
@@ -90,11 +85,6 @@ class PersonUpdateDto {
} else { } else {
// json[r'birthDate'] = null; // json[r'birthDate'] = null;
} }
if (this.color != null) {
json[r'color'] = this.color;
} else {
// json[r'color'] = null;
}
if (this.featureFaceAssetId != null) { if (this.featureFaceAssetId != null) {
json[r'featureFaceAssetId'] = this.featureFaceAssetId; json[r'featureFaceAssetId'] = this.featureFaceAssetId;
} else { } else {
@@ -128,7 +118,6 @@ class PersonUpdateDto {
return PersonUpdateDto( return PersonUpdateDto(
birthDate: mapDateTime(json, r'birthDate', r''), birthDate: mapDateTime(json, r'birthDate', r''),
color: mapValueOfType<String>(json, r'color'),
featureFaceAssetId: mapValueOfType<String>(json, r'featureFaceAssetId'), featureFaceAssetId: mapValueOfType<String>(json, r'featureFaceAssetId'),
isFavorite: mapValueOfType<bool>(json, r'isFavorite'), isFavorite: mapValueOfType<bool>(json, r'isFavorite'),
isHidden: mapValueOfType<bool>(json, r'isHidden'), isHidden: mapValueOfType<bool>(json, r'isHidden'),
+1 -19
View File
@@ -14,7 +14,6 @@ class PersonWithFacesResponseDto {
/// Returns a new [PersonWithFacesResponseDto] instance. /// Returns a new [PersonWithFacesResponseDto] instance.
PersonWithFacesResponseDto({ PersonWithFacesResponseDto({
required this.birthDate, required this.birthDate,
this.color,
this.faces = const [], this.faces = const [],
required this.id, required this.id,
this.isFavorite, this.isFavorite,
@@ -26,15 +25,6 @@ class PersonWithFacesResponseDto {
DateTime? birthDate; DateTime? birthDate;
/// This property was added in v1.126.0
///
/// 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? color;
List<AssetFaceWithoutPersonResponseDto> faces; List<AssetFaceWithoutPersonResponseDto> faces;
String id; String id;
@@ -66,7 +56,6 @@ class PersonWithFacesResponseDto {
@override @override
bool operator ==(Object other) => identical(this, other) || other is PersonWithFacesResponseDto && bool operator ==(Object other) => identical(this, other) || other is PersonWithFacesResponseDto &&
other.birthDate == birthDate && other.birthDate == birthDate &&
other.color == color &&
_deepEquality.equals(other.faces, faces) && _deepEquality.equals(other.faces, faces) &&
other.id == id && other.id == id &&
other.isFavorite == isFavorite && other.isFavorite == isFavorite &&
@@ -79,7 +68,6 @@ class PersonWithFacesResponseDto {
int get hashCode => int get hashCode =>
// ignore: unnecessary_parenthesis // ignore: unnecessary_parenthesis
(birthDate == null ? 0 : birthDate!.hashCode) + (birthDate == null ? 0 : birthDate!.hashCode) +
(color == null ? 0 : color!.hashCode) +
(faces.hashCode) + (faces.hashCode) +
(id.hashCode) + (id.hashCode) +
(isFavorite == null ? 0 : isFavorite!.hashCode) + (isFavorite == null ? 0 : isFavorite!.hashCode) +
@@ -89,7 +77,7 @@ class PersonWithFacesResponseDto {
(updatedAt == null ? 0 : updatedAt!.hashCode); (updatedAt == null ? 0 : updatedAt!.hashCode);
@override @override
String toString() => 'PersonWithFacesResponseDto[birthDate=$birthDate, color=$color, faces=$faces, id=$id, isFavorite=$isFavorite, isHidden=$isHidden, name=$name, thumbnailPath=$thumbnailPath, updatedAt=$updatedAt]'; String toString() => 'PersonWithFacesResponseDto[birthDate=$birthDate, faces=$faces, id=$id, isFavorite=$isFavorite, isHidden=$isHidden, name=$name, thumbnailPath=$thumbnailPath, updatedAt=$updatedAt]';
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
final json = <String, dynamic>{}; final json = <String, dynamic>{};
@@ -97,11 +85,6 @@ class PersonWithFacesResponseDto {
json[r'birthDate'] = _dateFormatter.format(this.birthDate!.toUtc()); json[r'birthDate'] = _dateFormatter.format(this.birthDate!.toUtc());
} else { } else {
// json[r'birthDate'] = null; // json[r'birthDate'] = null;
}
if (this.color != null) {
json[r'color'] = this.color;
} else {
// json[r'color'] = null;
} }
json[r'faces'] = this.faces; json[r'faces'] = this.faces;
json[r'id'] = this.id; json[r'id'] = this.id;
@@ -131,7 +114,6 @@ class PersonWithFacesResponseDto {
return PersonWithFacesResponseDto( return PersonWithFacesResponseDto(
birthDate: mapDateTime(json, r'birthDate', r''), birthDate: mapDateTime(json, r'birthDate', r''),
color: mapValueOfType<String>(json, r'color'),
faces: AssetFaceWithoutPersonResponseDto.listFromJson(json[r'faces']), faces: AssetFaceWithoutPersonResponseDto.listFromJson(json[r'faces']),
id: mapValueOfType<String>(json, r'id')!, id: mapValueOfType<String>(json, r'id')!,
isFavorite: mapValueOfType<bool>(json, r'isFavorite'), isFavorite: mapValueOfType<bool>(json, r'isFavorite'),
-107
View File
@@ -1,107 +0,0 @@
//
// AUTO-GENERATED FILE, DO NOT MODIFY!
//
// @dart=2.18
// ignore_for_file: unused_element, unused_import
// ignore_for_file: always_put_required_named_parameters_first
// ignore_for_file: constant_identifier_names
// ignore_for_file: lines_longer_than_80_chars
part of openapi.api;
class SharedLinksResponse {
/// Returns a new [SharedLinksResponse] instance.
SharedLinksResponse({
this.enabled = true,
this.sidebarWeb = false,
});
bool enabled;
bool sidebarWeb;
@override
bool operator ==(Object other) => identical(this, other) || other is SharedLinksResponse &&
other.enabled == enabled &&
other.sidebarWeb == sidebarWeb;
@override
int get hashCode =>
// ignore: unnecessary_parenthesis
(enabled.hashCode) +
(sidebarWeb.hashCode);
@override
String toString() => 'SharedLinksResponse[enabled=$enabled, sidebarWeb=$sidebarWeb]';
Map<String, dynamic> toJson() {
final json = <String, dynamic>{};
json[r'enabled'] = this.enabled;
json[r'sidebarWeb'] = this.sidebarWeb;
return json;
}
/// Returns a new [SharedLinksResponse] instance and imports its values from
/// [value] if it's a [Map], null otherwise.
// ignore: prefer_constructors_over_static_methods
static SharedLinksResponse? fromJson(dynamic value) {
upgradeDto(value, "SharedLinksResponse");
if (value is Map) {
final json = value.cast<String, dynamic>();
return SharedLinksResponse(
enabled: mapValueOfType<bool>(json, r'enabled')!,
sidebarWeb: mapValueOfType<bool>(json, r'sidebarWeb')!,
);
}
return null;
}
static List<SharedLinksResponse> listFromJson(dynamic json, {bool growable = false,}) {
final result = <SharedLinksResponse>[];
if (json is List && json.isNotEmpty) {
for (final row in json) {
final value = SharedLinksResponse.fromJson(row);
if (value != null) {
result.add(value);
}
}
}
return result.toList(growable: growable);
}
static Map<String, SharedLinksResponse> mapFromJson(dynamic json) {
final map = <String, SharedLinksResponse>{};
if (json is Map && json.isNotEmpty) {
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
for (final entry in json.entries) {
final value = SharedLinksResponse.fromJson(entry.value);
if (value != null) {
map[entry.key] = value;
}
}
}
return map;
}
// maps a json object with a list of SharedLinksResponse-objects as value to a dart map
static Map<String, List<SharedLinksResponse>> mapListFromJson(dynamic json, {bool growable = false,}) {
final map = <String, List<SharedLinksResponse>>{};
if (json is Map && json.isNotEmpty) {
// ignore: parameter_assignments
json = json.cast<String, dynamic>();
for (final entry in json.entries) {
map[entry.key] = SharedLinksResponse.listFromJson(entry.value, growable: growable,);
}
}
return map;
}
/// The list of required keys that must be present in a JSON.
static const requiredKeys = <String>{
'enabled',
'sidebarWeb',
};
}
-125
View File
@@ -1,125 +0,0 @@
//
// AUTO-GENERATED FILE, DO NOT MODIFY!
//
// @dart=2.18
// ignore_for_file: unused_element, unused_import
// ignore_for_file: always_put_required_named_parameters_first
// ignore_for_file: constant_identifier_names
// ignore_for_file: lines_longer_than_80_chars
part of openapi.api;
class SharedLinksUpdate {
/// Returns a new [SharedLinksUpdate] instance.
SharedLinksUpdate({
this.enabled,
this.sidebarWeb,
});
///
/// 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.
///
bool? enabled;
///
/// 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.
///
bool? sidebarWeb;
@override
bool operator ==(Object other) => identical(this, other) || other is SharedLinksUpdate &&
other.enabled == enabled &&
other.sidebarWeb == sidebarWeb;
@override
int get hashCode =>
// ignore: unnecessary_parenthesis
(enabled == null ? 0 : enabled!.hashCode) +
(sidebarWeb == null ? 0 : sidebarWeb!.hashCode);
@override
String toString() => 'SharedLinksUpdate[enabled=$enabled, sidebarWeb=$sidebarWeb]';
Map<String, dynamic> toJson() {
final json = <String, dynamic>{};
if (this.enabled != null) {
json[r'enabled'] = this.enabled;
} else {
// json[r'enabled'] = null;
}
if (this.sidebarWeb != null) {
json[r'sidebarWeb'] = this.sidebarWeb;
} else {
// json[r'sidebarWeb'] = null;
}
return json;
}
/// Returns a new [SharedLinksUpdate] instance and imports its values from
/// [value] if it's a [Map], null otherwise.
// ignore: prefer_constructors_over_static_methods
static SharedLinksUpdate? fromJson(dynamic value) {
upgradeDto(value, "SharedLinksUpdate");
if (value is Map) {
final json = value.cast<String, dynamic>();
return SharedLinksUpdate(
enabled: mapValueOfType<bool>(json, r'enabled'),
sidebarWeb: mapValueOfType<bool>(json, r'sidebarWeb'),
);
}
return null;
}
static List<SharedLinksUpdate> listFromJson(dynamic json, {bool growable = false,}) {
final result = <SharedLinksUpdate>[];
if (json is List && json.isNotEmpty) {
for (final row in json) {
final value = SharedLinksUpdate.fromJson(row);
if (value != null) {
result.add(value);
}
}
}
return result.toList(growable: growable);
}
static Map<String, SharedLinksUpdate> mapFromJson(dynamic json) {
final map = <String, SharedLinksUpdate>{};
if (json is Map && json.isNotEmpty) {
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
for (final entry in json.entries) {
final value = SharedLinksUpdate.fromJson(entry.value);
if (value != null) {
map[entry.key] = value;
}
}
}
return map;
}
// maps a json object with a list of SharedLinksUpdate-objects as value to a dart map
static Map<String, List<SharedLinksUpdate>> mapListFromJson(dynamic json, {bool growable = false,}) {
final map = <String, List<SharedLinksUpdate>>{};
if (json is Map && json.isNotEmpty) {
// ignore: parameter_assignments
json = json.cast<String, dynamic>();
for (final entry in json.entries) {
map[entry.key] = SharedLinksUpdate.listFromJson(entry.value, growable: growable,);
}
}
return map;
}
/// The list of required keys that must be present in a JSON.
static const requiredKeys = <String>{
};
}
+1 -9
View File
@@ -21,7 +21,6 @@ class UserPreferencesResponseDto {
required this.people, required this.people,
required this.purchase, required this.purchase,
required this.ratings, required this.ratings,
required this.sharedLinks,
required this.tags, required this.tags,
}); });
@@ -41,8 +40,6 @@ class UserPreferencesResponseDto {
RatingsResponse ratings; RatingsResponse ratings;
SharedLinksResponse sharedLinks;
TagsResponse tags; TagsResponse tags;
@override @override
@@ -55,7 +52,6 @@ class UserPreferencesResponseDto {
other.people == people && other.people == people &&
other.purchase == purchase && other.purchase == purchase &&
other.ratings == ratings && other.ratings == ratings &&
other.sharedLinks == sharedLinks &&
other.tags == tags; other.tags == tags;
@override @override
@@ -69,11 +65,10 @@ class UserPreferencesResponseDto {
(people.hashCode) + (people.hashCode) +
(purchase.hashCode) + (purchase.hashCode) +
(ratings.hashCode) + (ratings.hashCode) +
(sharedLinks.hashCode) +
(tags.hashCode); (tags.hashCode);
@override @override
String toString() => 'UserPreferencesResponseDto[avatar=$avatar, download=$download, emailNotifications=$emailNotifications, folders=$folders, memories=$memories, people=$people, purchase=$purchase, ratings=$ratings, sharedLinks=$sharedLinks, tags=$tags]'; String toString() => 'UserPreferencesResponseDto[avatar=$avatar, download=$download, emailNotifications=$emailNotifications, folders=$folders, memories=$memories, people=$people, purchase=$purchase, ratings=$ratings, tags=$tags]';
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
final json = <String, dynamic>{}; final json = <String, dynamic>{};
@@ -85,7 +80,6 @@ class UserPreferencesResponseDto {
json[r'people'] = this.people; json[r'people'] = this.people;
json[r'purchase'] = this.purchase; json[r'purchase'] = this.purchase;
json[r'ratings'] = this.ratings; json[r'ratings'] = this.ratings;
json[r'sharedLinks'] = this.sharedLinks;
json[r'tags'] = this.tags; json[r'tags'] = this.tags;
return json; return json;
} }
@@ -107,7 +101,6 @@ class UserPreferencesResponseDto {
people: PeopleResponse.fromJson(json[r'people'])!, people: PeopleResponse.fromJson(json[r'people'])!,
purchase: PurchaseResponse.fromJson(json[r'purchase'])!, purchase: PurchaseResponse.fromJson(json[r'purchase'])!,
ratings: RatingsResponse.fromJson(json[r'ratings'])!, ratings: RatingsResponse.fromJson(json[r'ratings'])!,
sharedLinks: SharedLinksResponse.fromJson(json[r'sharedLinks'])!,
tags: TagsResponse.fromJson(json[r'tags'])!, tags: TagsResponse.fromJson(json[r'tags'])!,
); );
} }
@@ -164,7 +157,6 @@ class UserPreferencesResponseDto {
'people', 'people',
'purchase', 'purchase',
'ratings', 'ratings',
'sharedLinks',
'tags', 'tags',
}; };
} }
+1 -18
View File
@@ -21,7 +21,6 @@ class UserPreferencesUpdateDto {
this.people, this.people,
this.purchase, this.purchase,
this.ratings, this.ratings,
this.sharedLinks,
this.tags, this.tags,
}); });
@@ -89,14 +88,6 @@ class UserPreferencesUpdateDto {
/// ///
RatingsUpdate? ratings; RatingsUpdate? ratings;
///
/// 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.
///
SharedLinksUpdate? sharedLinks;
/// ///
/// Please note: This property should have been non-nullable! Since the specification file /// 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 /// does not include a default value (using the "default:" property), however, the generated
@@ -115,7 +106,6 @@ class UserPreferencesUpdateDto {
other.people == people && other.people == people &&
other.purchase == purchase && other.purchase == purchase &&
other.ratings == ratings && other.ratings == ratings &&
other.sharedLinks == sharedLinks &&
other.tags == tags; other.tags == tags;
@override @override
@@ -129,11 +119,10 @@ class UserPreferencesUpdateDto {
(people == null ? 0 : people!.hashCode) + (people == null ? 0 : people!.hashCode) +
(purchase == null ? 0 : purchase!.hashCode) + (purchase == null ? 0 : purchase!.hashCode) +
(ratings == null ? 0 : ratings!.hashCode) + (ratings == null ? 0 : ratings!.hashCode) +
(sharedLinks == null ? 0 : sharedLinks!.hashCode) +
(tags == null ? 0 : tags!.hashCode); (tags == null ? 0 : tags!.hashCode);
@override @override
String toString() => 'UserPreferencesUpdateDto[avatar=$avatar, download=$download, emailNotifications=$emailNotifications, folders=$folders, memories=$memories, people=$people, purchase=$purchase, ratings=$ratings, sharedLinks=$sharedLinks, tags=$tags]'; String toString() => 'UserPreferencesUpdateDto[avatar=$avatar, download=$download, emailNotifications=$emailNotifications, folders=$folders, memories=$memories, people=$people, purchase=$purchase, ratings=$ratings, tags=$tags]';
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
final json = <String, dynamic>{}; final json = <String, dynamic>{};
@@ -177,11 +166,6 @@ class UserPreferencesUpdateDto {
} else { } else {
// json[r'ratings'] = null; // json[r'ratings'] = null;
} }
if (this.sharedLinks != null) {
json[r'sharedLinks'] = this.sharedLinks;
} else {
// json[r'sharedLinks'] = null;
}
if (this.tags != null) { if (this.tags != null) {
json[r'tags'] = this.tags; json[r'tags'] = this.tags;
} else { } else {
@@ -207,7 +191,6 @@ class UserPreferencesUpdateDto {
people: PeopleUpdate.fromJson(json[r'people']), people: PeopleUpdate.fromJson(json[r'people']),
purchase: PurchaseUpdate.fromJson(json[r'purchase']), purchase: PurchaseUpdate.fromJson(json[r'purchase']),
ratings: RatingsUpdate.fromJson(json[r'ratings']), ratings: RatingsUpdate.fromJson(json[r'ratings']),
sharedLinks: SharedLinksUpdate.fromJson(json[r'sharedLinks']),
tags: TagsUpdate.fromJson(json[r'tags']), tags: TagsUpdate.fromJson(json[r'tags']),
); );
} }
+1 -1
View File
@@ -2,7 +2,7 @@ name: immich_mobile
description: Immich - selfhosted backup media file on mobile phone description: Immich - selfhosted backup media file on mobile phone
publish_to: 'none' publish_to: 'none'
version: 1.126.0+183 version: 1.125.7+182
environment: environment:
sdk: '>=3.3.0 <4.0.0' sdk: '>=3.3.0 <4.0.0'
+3 -67
View File
@@ -5230,17 +5230,7 @@
"/shared-links": { "/shared-links": {
"get": { "get": {
"operationId": "getAllSharedLinks", "operationId": "getAllSharedLinks",
"parameters": [ "parameters": [],
{
"name": "albumId",
"required": false,
"in": "query",
"schema": {
"format": "uuid",
"type": "string"
}
}
],
"responses": { "responses": {
"200": { "200": {
"content": { "content": {
@@ -7468,7 +7458,7 @@
"info": { "info": {
"title": "Immich", "title": "Immich",
"description": "Immich API", "description": "Immich API",
"version": "1.126.0", "version": "1.125.7",
"contact": {} "contact": {}
}, },
"tags": [], "tags": [],
@@ -10296,10 +10286,6 @@
"nullable": true, "nullable": true,
"type": "string" "type": "string"
}, },
"color": {
"nullable": true,
"type": "string"
},
"featureFaceAssetId": { "featureFaceAssetId": {
"description": "Asset is used to get the feature face thumbnail.", "description": "Asset is used to get the feature face thumbnail.",
"type": "string" "type": "string"
@@ -10416,10 +10402,6 @@
"nullable": true, "nullable": true,
"type": "string" "type": "string"
}, },
"color": {
"nullable": true,
"type": "string"
},
"isFavorite": { "isFavorite": {
"type": "boolean" "type": "boolean"
}, },
@@ -10441,10 +10423,6 @@
"nullable": true, "nullable": true,
"type": "string" "type": "string"
}, },
"color": {
"description": "This property was added in v1.126.0",
"type": "string"
},
"id": { "id": {
"type": "string" "type": "string"
}, },
@@ -10495,10 +10473,6 @@
"nullable": true, "nullable": true,
"type": "string" "type": "string"
}, },
"color": {
"nullable": true,
"type": "string"
},
"featureFaceAssetId": { "featureFaceAssetId": {
"description": "Asset is used to get the feature face thumbnail.", "description": "Asset is used to get the feature face thumbnail.",
"type": "string" "type": "string"
@@ -10524,10 +10498,6 @@
"nullable": true, "nullable": true,
"type": "string" "type": "string"
}, },
"color": {
"description": "This property was added in v1.126.0",
"type": "string"
},
"faces": { "faces": {
"items": { "items": {
"$ref": "#/components/schemas/AssetFaceWithoutPersonResponseDto" "$ref": "#/components/schemas/AssetFaceWithoutPersonResponseDto"
@@ -11524,34 +11494,6 @@
], ],
"type": "string" "type": "string"
}, },
"SharedLinksResponse": {
"properties": {
"enabled": {
"default": true,
"type": "boolean"
},
"sidebarWeb": {
"default": false,
"type": "boolean"
}
},
"required": [
"enabled",
"sidebarWeb"
],
"type": "object"
},
"SharedLinksUpdate": {
"properties": {
"enabled": {
"type": "boolean"
},
"sidebarWeb": {
"type": "boolean"
}
},
"type": "object"
},
"SignUpDto": { "SignUpDto": {
"properties": { "properties": {
"email": { "email": {
@@ -12669,6 +12611,7 @@
"properties": { "properties": {
"color": { "color": {
"nullable": true, "nullable": true,
"pattern": "^#?([0-9A-F]{3}|[0-9A-F]{4}|[0-9A-F]{6}|[0-9A-F]{8})$",
"type": "string" "type": "string"
} }
}, },
@@ -13198,9 +13141,6 @@
"ratings": { "ratings": {
"$ref": "#/components/schemas/RatingsResponse" "$ref": "#/components/schemas/RatingsResponse"
}, },
"sharedLinks": {
"$ref": "#/components/schemas/SharedLinksResponse"
},
"tags": { "tags": {
"$ref": "#/components/schemas/TagsResponse" "$ref": "#/components/schemas/TagsResponse"
} }
@@ -13214,7 +13154,6 @@
"people", "people",
"purchase", "purchase",
"ratings", "ratings",
"sharedLinks",
"tags" "tags"
], ],
"type": "object" "type": "object"
@@ -13245,9 +13184,6 @@
"ratings": { "ratings": {
"$ref": "#/components/schemas/RatingsUpdate" "$ref": "#/components/schemas/RatingsUpdate"
}, },
"sharedLinks": {
"$ref": "#/components/schemas/SharedLinksUpdate"
},
"tags": { "tags": {
"$ref": "#/components/schemas/TagsUpdate" "$ref": "#/components/schemas/TagsUpdate"
} }
+2 -2
View File
@@ -1,12 +1,12 @@
{ {
"name": "@immich/sdk", "name": "@immich/sdk",
"version": "1.126.0", "version": "1.125.7",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "@immich/sdk", "name": "@immich/sdk",
"version": "1.126.0", "version": "1.125.7",
"license": "GNU Affero General Public License version 3", "license": "GNU Affero General Public License version 3",
"dependencies": { "dependencies": {
"@oazapfts/runtime": "^1.0.2" "@oazapfts/runtime": "^1.0.2"
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@immich/sdk", "name": "@immich/sdk",
"version": "1.126.0", "version": "1.125.7",
"description": "Auto-generated TypeScript SDK for the Immich API", "description": "Auto-generated TypeScript SDK for the Immich API",
"type": "module", "type": "module",
"main": "./build/index.js", "main": "./build/index.js",
+3 -24
View File
@@ -1,6 +1,6 @@
/** /**
* Immich * Immich
* 1.126.0 * 1.125.7
* DO NOT MODIFY - This file has been generated using oazapfts. * DO NOT MODIFY - This file has been generated using oazapfts.
* See https://www.npmjs.com/package/oazapfts * See https://www.npmjs.com/package/oazapfts
*/ */
@@ -113,10 +113,6 @@ export type PurchaseResponse = {
export type RatingsResponse = { export type RatingsResponse = {
enabled: boolean; enabled: boolean;
}; };
export type SharedLinksResponse = {
enabled: boolean;
sidebarWeb: boolean;
};
export type TagsResponse = { export type TagsResponse = {
enabled: boolean; enabled: boolean;
sidebarWeb: boolean; sidebarWeb: boolean;
@@ -130,7 +126,6 @@ export type UserPreferencesResponseDto = {
people: PeopleResponse; people: PeopleResponse;
purchase: PurchaseResponse; purchase: PurchaseResponse;
ratings: RatingsResponse; ratings: RatingsResponse;
sharedLinks: SharedLinksResponse;
tags: TagsResponse; tags: TagsResponse;
}; };
export type AvatarUpdate = { export type AvatarUpdate = {
@@ -163,10 +158,6 @@ export type PurchaseUpdate = {
export type RatingsUpdate = { export type RatingsUpdate = {
enabled?: boolean; enabled?: boolean;
}; };
export type SharedLinksUpdate = {
enabled?: boolean;
sidebarWeb?: boolean;
};
export type TagsUpdate = { export type TagsUpdate = {
enabled?: boolean; enabled?: boolean;
sidebarWeb?: boolean; sidebarWeb?: boolean;
@@ -180,7 +171,6 @@ export type UserPreferencesUpdateDto = {
people?: PeopleUpdate; people?: PeopleUpdate;
purchase?: PurchaseUpdate; purchase?: PurchaseUpdate;
ratings?: RatingsUpdate; ratings?: RatingsUpdate;
sharedLinks?: SharedLinksUpdate;
tags?: TagsUpdate; tags?: TagsUpdate;
}; };
export type AlbumUserResponseDto = { export type AlbumUserResponseDto = {
@@ -223,8 +213,6 @@ export type AssetFaceWithoutPersonResponseDto = {
}; };
export type PersonWithFacesResponseDto = { export type PersonWithFacesResponseDto = {
birthDate: string | null; birthDate: string | null;
/** This property was added in v1.126.0 */
color?: string;
faces: AssetFaceWithoutPersonResponseDto[]; faces: AssetFaceWithoutPersonResponseDto[];
id: string; id: string;
/** This property was added in v1.126.0 */ /** This property was added in v1.126.0 */
@@ -505,8 +493,6 @@ export type DuplicateResponseDto = {
}; };
export type PersonResponseDto = { export type PersonResponseDto = {
birthDate: string | null; birthDate: string | null;
/** This property was added in v1.126.0 */
color?: string;
id: string; id: string;
/** This property was added in v1.126.0 */ /** This property was added in v1.126.0 */
isFavorite?: boolean; isFavorite?: boolean;
@@ -707,7 +693,6 @@ export type PersonCreateDto = {
/** Person date of birth. /** Person date of birth.
Note: the mobile app cannot currently set the birth date to null. */ Note: the mobile app cannot currently set the birth date to null. */
birthDate?: string | null; birthDate?: string | null;
color?: string | null;
isFavorite?: boolean; isFavorite?: boolean;
/** Person visibility */ /** Person visibility */
isHidden?: boolean; isHidden?: boolean;
@@ -718,7 +703,6 @@ export type PeopleUpdateItem = {
/** Person date of birth. /** Person date of birth.
Note: the mobile app cannot currently set the birth date to null. */ Note: the mobile app cannot currently set the birth date to null. */
birthDate?: string | null; birthDate?: string | null;
color?: string | null;
/** Asset is used to get the feature face thumbnail. */ /** Asset is used to get the feature face thumbnail. */
featureFaceAssetId?: string; featureFaceAssetId?: string;
/** Person id. */ /** Person id. */
@@ -736,7 +720,6 @@ export type PersonUpdateDto = {
/** Person date of birth. /** Person date of birth.
Note: the mobile app cannot currently set the birth date to null. */ Note: the mobile app cannot currently set the birth date to null. */
birthDate?: string | null; birthDate?: string | null;
color?: string | null;
/** Asset is used to get the feature face thumbnail. */ /** Asset is used to get the feature face thumbnail. */
featureFaceAssetId?: string; featureFaceAssetId?: string;
isFavorite?: boolean; isFavorite?: boolean;
@@ -2762,15 +2745,11 @@ export function deleteSession({ id }: {
method: "DELETE" method: "DELETE"
})); }));
} }
export function getAllSharedLinks({ albumId }: { export function getAllSharedLinks(opts?: Oazapfts.RequestOpts) {
albumId?: string;
}, opts?: Oazapfts.RequestOpts) {
return oazapfts.ok(oazapfts.fetchJson<{ return oazapfts.ok(oazapfts.fetchJson<{
status: 200; status: 200;
data: SharedLinkResponseDto[]; data: SharedLinkResponseDto[];
}>(`/shared-links${QS.query(QS.explode({ }>("/shared-links", {
albumId
}))}`, {
...opts ...opts
})); }));
} }
+2 -2
View File
@@ -1,5 +1,5 @@
# dev build # dev build
FROM ghcr.io/immich-app/base-server-dev:20250204@sha256:8b203f19f4d5cf4619b60ee5f50d6d4b5ea3745747f5e5170d1b7404ebeb0792 AS dev FROM ghcr.io/immich-app/base-server-dev:20250123@sha256:04eba5cd87d61bc3d20a3915b2302f04d08fbc329c55ee0cde103c502f59f412 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
@@ -42,7 +42,7 @@ RUN npm run build
# prod build # prod build
FROM ghcr.io/immich-app/base-server-prod:20250204@sha256:2af3da713d5ab3ccca23b216b747557ea6016117e72deac101e35069ccaf9b5e FROM ghcr.io/immich-app/base-server-prod:20250123@sha256:591739983913f82672d8191258f3a1a24c123db0d619ff91fca8fef431ee1338
WORKDIR /usr/src/app WORKDIR /usr/src/app
ENV NODE_ENV=production \ ENV NODE_ENV=production \
+726 -1316
View File
File diff suppressed because it is too large Load Diff
+16 -16
View File
@@ -1,6 +1,6 @@
{ {
"name": "immich", "name": "immich",
"version": "1.126.0", "version": "1.125.7",
"description": "", "description": "",
"author": "", "author": "",
"private": true, "private": true,
@@ -35,16 +35,16 @@
"email:dev": "email dev -p 3050 --dir src/emails" "email:dev": "email dev -p 3050 --dir src/emails"
}, },
"dependencies": { "dependencies": {
"@nestjs/bullmq": "^11.0.1", "@nestjs/bullmq": "^11.0.0",
"@nestjs/common": "^11.0.4", "@nestjs/common": "^10.2.2",
"@nestjs/core": "^11.0.4", "@nestjs/core": "^10.2.2",
"@nestjs/event-emitter": "^3.0.0", "@nestjs/event-emitter": "^3.0.0",
"@nestjs/platform-express": "^11.0.4", "@nestjs/platform-express": "^10.2.2",
"@nestjs/platform-socket.io": "^11.0.4", "@nestjs/platform-socket.io": "^10.2.2",
"@nestjs/schedule": "^5.0.0", "@nestjs/schedule": "^5.0.0",
"@nestjs/swagger": "^11.0.2", "@nestjs/swagger": "^8.0.0",
"@nestjs/typeorm": "^11.0.0", "@nestjs/typeorm": "^10.0.0",
"@nestjs/websockets": "^11.0.4", "@nestjs/websockets": "^10.2.2",
"@opentelemetry/auto-instrumentations-node": "^0.55.0", "@opentelemetry/auto-instrumentations-node": "^0.55.0",
"@opentelemetry/context-async-hooks": "^1.24.0", "@opentelemetry/context-async-hooks": "^1.24.0",
"@opentelemetry/exporter-prometheus": "^0.57.0", "@opentelemetry/exporter-prometheus": "^0.57.0",
@@ -60,7 +60,7 @@
"class-validator": "^0.14.0", "class-validator": "^0.14.0",
"cookie-parser": "^1.4.6", "cookie-parser": "^1.4.6",
"exiftool-vendored": "^28.3.1", "exiftool-vendored": "^28.3.1",
"fast-glob": "^3.3.2", "fast-glob": "github:etnoy/fast-glob#built",
"fluent-ffmpeg": "^2.1.2", "fluent-ffmpeg": "^2.1.2",
"geo-tz": "^8.0.0", "geo-tz": "^8.0.0",
"handlebars": "^4.7.8", "handlebars": "^4.7.8",
@@ -72,9 +72,9 @@
"kysely-postgres-js": "^2.0.0", "kysely-postgres-js": "^2.0.0",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"luxon": "^3.4.2", "luxon": "^3.4.2",
"nest-commander": "^3.16.0", "nest-commander": "^3.11.1",
"nestjs-cls": "^5.0.0", "nestjs-cls": "^4.3.0",
"nestjs-kysely": "^1.1.0", "nestjs-kysely": "^1.0.0",
"nestjs-otel": "^6.0.0", "nestjs-otel": "^6.0.0",
"nodemailer": "^6.9.13", "nodemailer": "^6.9.13",
"openid-client": "^5.4.3", "openid-client": "^5.4.3",
@@ -97,9 +97,9 @@
"devDependencies": { "devDependencies": {
"@eslint/eslintrc": "^3.1.0", "@eslint/eslintrc": "^3.1.0",
"@eslint/js": "^9.8.0", "@eslint/js": "^9.8.0",
"@nestjs/cli": "^11.0.2", "@nestjs/cli": "^10.1.16",
"@nestjs/schematics": "^11.0.0", "@nestjs/schematics": "^10.0.2",
"@nestjs/testing": "^11.0.4", "@nestjs/testing": "^10.2.2",
"@swc/core": "^1.4.14", "@swc/core": "^1.4.14",
"@testcontainers/postgresql": "^10.2.1", "@testcontainers/postgresql": "^10.2.1",
"@types/archiver": "^6.0.0", "@types/archiver": "^6.0.0",
@@ -9,7 +9,6 @@ import {
SharedLinkEditDto, SharedLinkEditDto,
SharedLinkPasswordDto, SharedLinkPasswordDto,
SharedLinkResponseDto, SharedLinkResponseDto,
SharedLinkSearchDto,
} from 'src/dtos/shared-link.dto'; } from 'src/dtos/shared-link.dto';
import { ImmichCookie, Permission } from 'src/enum'; import { ImmichCookie, Permission } from 'src/enum';
import { Auth, Authenticated, GetLoginDetails } from 'src/middleware/auth.guard'; import { Auth, Authenticated, GetLoginDetails } from 'src/middleware/auth.guard';
@@ -25,8 +24,8 @@ export class SharedLinkController {
@Get() @Get()
@Authenticated({ permission: Permission.SHARED_LINK_READ }) @Authenticated({ permission: Permission.SHARED_LINK_READ })
getAllSharedLinks(@Auth() auth: AuthDto, @Query() dto: SharedLinkSearchDto): Promise<SharedLinkResponseDto[]> { getAllSharedLinks(@Auth() auth: AuthDto): Promise<SharedLinkResponseDto[]> {
return this.service.getAll(auth, dto); return this.service.getAll(auth);
} }
@Get('me') @Get('me')
+2 -1
View File
@@ -9,7 +9,8 @@ import { ICryptoRepository } from 'src/interfaces/crypto.interface';
import { IMoveRepository } from 'src/interfaces/move.interface'; 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 { IConfigRepository, ILoggingRepository, ISystemMetadataRepository } from 'src/types'; import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
import { IConfigRepository, ILoggingRepository } from 'src/types';
import { getAssetFiles } from 'src/utils/asset.util'; import { getAssetFiles } from 'src/utils/asset.util';
import { getConfig } from 'src/utils/config'; import { getConfig } from 'src/utils/config';
-1
View File
@@ -276,7 +276,6 @@ export interface Partners {
export interface Person { export interface Person {
birthDate: Timestamp | null; birthDate: Timestamp | null;
color: string | null;
createdAt: Generated<Timestamp>; createdAt: Generated<Timestamp>;
faceAssetId: string | null; faceAssetId: string | null;
id: Generated<string>; id: Generated<string>;
+1 -15
View File
@@ -7,14 +7,7 @@ import { AuthDto } from 'src/dtos/auth.dto';
import { AssetFaceEntity } from 'src/entities/asset-face.entity'; import { AssetFaceEntity } from 'src/entities/asset-face.entity';
import { PersonEntity } from 'src/entities/person.entity'; import { PersonEntity } from 'src/entities/person.entity';
import { SourceType } from 'src/enum'; import { SourceType } from 'src/enum';
import { import { IsDateStringFormat, MaxDateString, Optional, ValidateBoolean, ValidateUUID } from 'src/validation';
IsDateStringFormat,
MaxDateString,
Optional,
ValidateBoolean,
ValidateHexColor,
ValidateUUID,
} from 'src/validation';
export class PersonCreateDto { export class PersonCreateDto {
/** /**
@@ -42,10 +35,6 @@ export class PersonCreateDto {
@ValidateBoolean({ optional: true }) @ValidateBoolean({ optional: true })
isFavorite?: boolean; isFavorite?: boolean;
@Optional({ emptyToNull: true, nullable: true })
@ValidateHexColor()
color?: string | null;
} }
export class PersonUpdateDto extends PersonCreateDto { export class PersonUpdateDto extends PersonCreateDto {
@@ -113,8 +102,6 @@ export class PersonResponseDto {
updatedAt?: Date; updatedAt?: Date;
@PropertyLifecycle({ addedAt: 'v1.126.0' }) @PropertyLifecycle({ addedAt: 'v1.126.0' })
isFavorite?: boolean; isFavorite?: boolean;
@PropertyLifecycle({ addedAt: 'v1.126.0' })
color?: string;
} }
export class PersonWithFacesResponseDto extends PersonResponseDto { export class PersonWithFacesResponseDto extends PersonResponseDto {
@@ -189,7 +176,6 @@ export function mapPerson(person: PersonEntity): PersonResponseDto {
thumbnailPath: person.thumbnailPath, thumbnailPath: person.thumbnailPath,
isHidden: person.isHidden, isHidden: person.isHidden,
isFavorite: person.isFavorite, isFavorite: person.isFavorite,
color: person.color ?? undefined,
updatedAt: person.updatedAt, updatedAt: person.updatedAt,
}; };
} }
+2 -2
View File
@@ -1,4 +1,4 @@
import { SessionItem } from 'src/types'; import { SessionEntity } from 'src/entities/session.entity';
export class SessionResponseDto { export class SessionResponseDto {
id!: string; id!: string;
@@ -9,7 +9,7 @@ export class SessionResponseDto {
deviceOS!: string; deviceOS!: string;
} }
export const mapSession = (entity: SessionItem, currentId?: string): SessionResponseDto => ({ export const mapSession = (entity: SessionEntity, currentId?: string): SessionResponseDto => ({
id: entity.id, id: entity.id,
createdAt: entity.createdAt.toISOString(), createdAt: entity.createdAt.toISOString(),
updatedAt: entity.updatedAt.toISOString(), updatedAt: entity.updatedAt.toISOString(),
-5
View File
@@ -7,11 +7,6 @@ import { SharedLinkEntity } from 'src/entities/shared-link.entity';
import { SharedLinkType } from 'src/enum'; import { SharedLinkType } from 'src/enum';
import { Optional, ValidateBoolean, ValidateDate, ValidateUUID } from 'src/validation'; import { Optional, ValidateBoolean, ValidateDate, ValidateUUID } from 'src/validation';
export class SharedLinkSearchDto {
@ValidateUUID({ optional: true })
albumId?: string;
}
export class SharedLinkCreateDto { export class SharedLinkCreateDto {
@IsEnum(SharedLinkType) @IsEnum(SharedLinkType)
@ApiProperty({ enum: SharedLinkType, enumName: 'SharedLinkType' }) @ApiProperty({ enum: SharedLinkType, enumName: 'SharedLinkType' })
+5 -3
View File
@@ -1,7 +1,8 @@
import { ApiProperty } from '@nestjs/swagger'; import { ApiProperty } from '@nestjs/swagger';
import { Transform } from 'class-transformer';
import { IsHexColor, IsNotEmpty, IsString } from 'class-validator'; import { IsHexColor, IsNotEmpty, IsString } from 'class-validator';
import { TagEntity } from 'src/entities/tag.entity'; import { TagEntity } from 'src/entities/tag.entity';
import { Optional, ValidateHexColor, ValidateUUID } from 'src/validation'; import { Optional, ValidateUUID } from 'src/validation';
export class TagCreateDto { export class TagCreateDto {
@IsString() @IsString()
@@ -17,8 +18,9 @@ export class TagCreateDto {
} }
export class TagUpdateDto { export class TagUpdateDto {
@Optional({ emptyToNull: true, nullable: true }) @Optional({ nullable: true, emptyToNull: true })
@ValidateHexColor() @IsHexColor()
@Transform(({ value }) => (typeof value === 'string' && value[0] !== '#' ? `#${value}` : value))
color?: string | null; color?: string | null;
} }
-19
View File
@@ -38,14 +38,6 @@ class PeopleUpdate {
sidebarWeb?: boolean; sidebarWeb?: boolean;
} }
class SharedLinksUpdate {
@ValidateBoolean({ optional: true })
enabled?: boolean;
@ValidateBoolean({ optional: true })
sidebarWeb?: boolean;
}
class TagsUpdate { class TagsUpdate {
@ValidateBoolean({ optional: true }) @ValidateBoolean({ optional: true })
enabled?: boolean; enabled?: boolean;
@@ -106,11 +98,6 @@ export class UserPreferencesUpdateDto {
@Type(() => RatingsUpdate) @Type(() => RatingsUpdate)
ratings?: RatingsUpdate; ratings?: RatingsUpdate;
@Optional()
@ValidateNested()
@Type(() => SharedLinksUpdate)
sharedLinks?: SharedLinksUpdate;
@Optional() @Optional()
@ValidateNested() @ValidateNested()
@Type(() => TagsUpdate) @Type(() => TagsUpdate)
@@ -165,11 +152,6 @@ class TagsResponse {
sidebarWeb: boolean = true; sidebarWeb: boolean = true;
} }
class SharedLinksResponse {
enabled: boolean = true;
sidebarWeb: boolean = false;
}
class EmailNotificationsResponse { class EmailNotificationsResponse {
enabled!: boolean; enabled!: boolean;
albumInvite!: boolean; albumInvite!: boolean;
@@ -193,7 +175,6 @@ export class UserPreferencesResponseDto implements UserPreferences {
memories!: MemoriesResponse; memories!: MemoriesResponse;
people!: PeopleResponse; people!: PeopleResponse;
ratings!: RatingsResponse; ratings!: RatingsResponse;
sharedLinks!: SharedLinksResponse;
tags!: TagsResponse; tags!: TagsResponse;
avatar!: AvatarResponse; avatar!: AvatarResponse;
emailNotifications!: EmailNotificationsResponse; emailNotifications!: EmailNotificationsResponse;
-3
View File
@@ -52,7 +52,4 @@ export class PersonEntity {
@Column({ default: false }) @Column({ default: false })
isFavorite!: boolean; isFavorite!: boolean;
@Column({ type: 'varchar', nullable: true, default: null })
color?: string | null;
} }
@@ -34,10 +34,6 @@ export interface UserPreferences {
ratings: { ratings: {
enabled: boolean; enabled: boolean;
}; };
sharedLinks: {
enabled: boolean;
sidebarWeb: boolean;
};
tags: { tags: {
enabled: boolean; enabled: boolean;
sidebarWeb: boolean; sidebarWeb: boolean;
@@ -78,10 +74,6 @@ export const getDefaultPreferences = (user: { email: string }): UserPreferences
enabled: true, enabled: true,
sidebarWeb: false, sidebarWeb: false,
}, },
sharedLinks: {
enabled: true,
sidebarWeb: false,
},
ratings: { ratings: {
enabled: false, enabled: false,
}, },
@@ -0,0 +1,25 @@
import { ChildProcessWithoutNullStreams, SpawnOptionsWithoutStdio } from 'node:child_process';
import { Readable } from 'node:stream';
export interface ImmichReadStream {
stream: Readable;
type?: string;
length?: number;
}
export interface ImmichZipStream extends ImmichReadStream {
addFile: (inputPath: string, filename: string) => void;
finalize: () => Promise<void>;
}
export interface DiskUsage {
available: number;
free: number;
total: number;
}
export const IProcessRepository = 'IProcessRepository';
export interface IProcessRepository {
spawn(command: string, args?: readonly string[], options?: SpawnOptionsWithoutStdio): ChildProcessWithoutNullStreams;
}
@@ -0,0 +1,17 @@
import { Insertable, Updateable } from 'kysely';
import { Sessions } from 'src/db';
import { SessionEntity } from 'src/entities/session.entity';
export const ISessionRepository = 'ISessionRepository';
type E = SessionEntity;
export type SessionSearchOptions = { updatedBefore: Date };
export interface ISessionRepository {
search(options: SessionSearchOptions): Promise<SessionEntity[]>;
create(dto: Insertable<Sessions>): Promise<SessionEntity>;
update(id: string, dto: Updateable<Sessions>): Promise<SessionEntity>;
delete(id: string): Promise<void>;
getByToken(token: string): Promise<E | undefined>;
getByUserId(userId: string): Promise<E[]>;
}
@@ -4,13 +4,8 @@ import { SharedLinkEntity } from 'src/entities/shared-link.entity';
export const ISharedLinkRepository = 'ISharedLinkRepository'; export const ISharedLinkRepository = 'ISharedLinkRepository';
export type SharedLinkSearchOptions = {
userId: string;
albumId?: string;
};
export interface ISharedLinkRepository { export interface ISharedLinkRepository {
getAll(options: SharedLinkSearchOptions): Promise<SharedLinkEntity[]>; getAll(userId: string): Promise<SharedLinkEntity[]>;
get(userId: string, id: string): Promise<SharedLinkEntity | undefined>; get(userId: string, id: string): Promise<SharedLinkEntity | undefined>;
getByKey(key: Buffer): Promise<SharedLinkEntity | undefined>; getByKey(key: Buffer): Promise<SharedLinkEntity | undefined>;
create(entity: Insertable<SharedLinks> & { assetIds?: string[] }): Promise<SharedLinkEntity>; create(entity: Insertable<SharedLinks> & { assetIds?: string[] }): Promise<SharedLinkEntity>;
@@ -0,0 +1,10 @@
import { SystemMetadata } from 'src/entities/system-metadata.entity';
export const ISystemMetadataRepository = 'ISystemMetadataRepository';
export interface ISystemMetadataRepository {
get<T extends keyof SystemMetadata>(key: T): Promise<SystemMetadata[T] | null>;
set<T extends keyof SystemMetadata>(key: T, value: SystemMetadata[T]): Promise<void>;
delete<T extends keyof SystemMetadata>(key: T): Promise<void>;
readFile(filename: string): Promise<string>;
}
@@ -1,14 +0,0 @@
import { MigrationInterface, QueryRunner } from "typeorm";
export class AddPersonColor1738889177573 implements MigrationInterface {
name = 'AddPersonColor1738889177573'
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "person" ADD "color" character varying`);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "person" DROP COLUMN "color"`);
}
}
+6 -3
View File
@@ -9,10 +9,13 @@ import { IMachineLearningRepository } from 'src/interfaces/machine-learning.inte
import { IMoveRepository } from 'src/interfaces/move.interface'; import { IMoveRepository } from 'src/interfaces/move.interface';
import { IPartnerRepository } from 'src/interfaces/partner.interface'; import { IPartnerRepository } from 'src/interfaces/partner.interface';
import { IPersonRepository } from 'src/interfaces/person.interface'; import { IPersonRepository } from 'src/interfaces/person.interface';
import { IProcessRepository } from 'src/interfaces/process.interface';
import { ISearchRepository } from 'src/interfaces/search.interface'; import { ISearchRepository } from 'src/interfaces/search.interface';
import { ISessionRepository } from 'src/interfaces/session.interface';
import { ISharedLinkRepository } from 'src/interfaces/shared-link.interface'; import { ISharedLinkRepository } from 'src/interfaces/shared-link.interface';
import { IStackRepository } from 'src/interfaces/stack.interface'; import { IStackRepository } from 'src/interfaces/stack.interface';
import { IStorageRepository } from 'src/interfaces/storage.interface'; import { IStorageRepository } from 'src/interfaces/storage.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 { AccessRepository } from 'src/repositories/access.repository'; import { AccessRepository } from 'src/repositories/access.repository';
@@ -70,10 +73,7 @@ export const repositories = [
MetadataRepository, MetadataRepository,
NotificationRepository, NotificationRepository,
OAuthRepository, OAuthRepository,
ProcessRepository,
SessionRepository,
ServerInfoRepository, ServerInfoRepository,
SystemMetadataRepository,
TelemetryRepository, TelemetryRepository,
TrashRepository, TrashRepository,
ViewRepository, ViewRepository,
@@ -92,10 +92,13 @@ export const providers = [
{ provide: IMoveRepository, useClass: MoveRepository }, { provide: IMoveRepository, useClass: MoveRepository },
{ provide: IPartnerRepository, useClass: PartnerRepository }, { provide: IPartnerRepository, useClass: PartnerRepository },
{ provide: IPersonRepository, useClass: PersonRepository }, { provide: IPersonRepository, useClass: PersonRepository },
{ provide: IProcessRepository, useClass: ProcessRepository },
{ provide: ISearchRepository, useClass: SearchRepository }, { provide: ISearchRepository, useClass: SearchRepository },
{ provide: ISessionRepository, useClass: SessionRepository },
{ provide: ISharedLinkRepository, useClass: SharedLinkRepository }, { provide: ISharedLinkRepository, useClass: SharedLinkRepository },
{ provide: IStackRepository, useClass: StackRepository }, { provide: IStackRepository, useClass: StackRepository },
{ provide: IStorageRepository, useClass: StorageRepository }, { provide: IStorageRepository, useClass: StorageRepository },
{ provide: ISystemMetadataRepository, useClass: SystemMetadataRepository },
{ provide: ITagRepository, useClass: TagRepository }, { provide: ITagRepository, useClass: TagRepository },
{ provide: IUserRepository, useClass: UserRepository }, { provide: IUserRepository, useClass: UserRepository },
]; ];
+3 -3
View File
@@ -1,4 +1,4 @@
import { Injectable } from '@nestjs/common'; import { Inject, Injectable } from '@nestjs/common';
import { getName } from 'i18n-iso-countries'; import { getName } from 'i18n-iso-countries';
import { Expression, Kysely, sql, SqlBool } from 'kysely'; import { Expression, Kysely, sql, SqlBool } from 'kysely';
import { InjectKysely } from 'nestjs-kysely'; import { InjectKysely } from 'nestjs-kysely';
@@ -11,9 +11,9 @@ import { DB, GeodataPlaces, NaturalearthCountries } from 'src/db';
import { DummyValue, GenerateSql } from 'src/decorators'; import { DummyValue, GenerateSql } from 'src/decorators';
import { NaturalEarthCountriesTempEntity } from 'src/entities/natural-earth-countries.entity'; import { NaturalEarthCountriesTempEntity } from 'src/entities/natural-earth-countries.entity';
import { LogLevel, SystemMetadataKey } from 'src/enum'; import { LogLevel, SystemMetadataKey } from 'src/enum';
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
import { ConfigRepository } from 'src/repositories/config.repository'; import { ConfigRepository } from 'src/repositories/config.repository';
import { LoggingRepository } from 'src/repositories/logging.repository'; import { LoggingRepository } from 'src/repositories/logging.repository';
import { SystemMetadataRepository } from 'src/repositories/system-metadata.repository';
export interface MapMarkerSearchOptions { export interface MapMarkerSearchOptions {
isArchived?: boolean; isArchived?: boolean;
@@ -48,7 +48,7 @@ interface MapDB extends DB {
export class MapRepository { export class MapRepository {
constructor( constructor(
private configRepository: ConfigRepository, private configRepository: ConfigRepository,
private metadataRepository: SystemMetadataRepository, @Inject(ISystemMetadataRepository) private metadataRepository: ISystemMetadataRepository,
private logger: LoggingRepository, private logger: LoggingRepository,
@InjectKysely() private db: Kysely<MapDB>, @InjectKysely() private db: Kysely<MapDB>,
) { ) {
+1 -6
View File
@@ -43,12 +43,7 @@ export class OAuthRepository {
const params = client.callbackParams(url); const params = client.callbackParams(url);
try { try {
const tokens = await client.callback(redirectUrl, params, { state: params.state }); const tokens = await client.callback(redirectUrl, params, { state: params.state });
const profile = await client.userinfo<OAuthProfile>(tokens.access_token || ''); return await client.userinfo<OAuthProfile>(tokens.access_token || '');
if (!profile.sub) {
throw new Error('Unexpected profile response, no `sub`');
}
return profile;
} catch (error: Error | any) { } catch (error: Error | any) {
if (error.message.includes('unexpected JWT alg received')) { if (error.message.includes('unexpected JWT alg received')) {
this.logger.warn( this.logger.warn(
@@ -1,11 +1,13 @@
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { ChildProcessWithoutNullStreams, spawn, SpawnOptionsWithoutStdio } from 'node:child_process'; import { ChildProcessWithoutNullStreams, spawn, SpawnOptionsWithoutStdio } from 'node:child_process';
import { IProcessRepository } from 'src/interfaces/process.interface';
import { LoggingRepository } from 'src/repositories/logging.repository'; import { LoggingRepository } from 'src/repositories/logging.repository';
import { StorageRepository } from 'src/repositories/storage.repository';
@Injectable() @Injectable()
export class ProcessRepository { export class ProcessRepository implements IProcessRepository {
constructor(private logger: LoggingRepository) { constructor(private logger: LoggingRepository) {
this.logger.setContext(ProcessRepository.name); this.logger.setContext(StorageRepository.name);
} }
spawn(command: string, args: readonly string[], options?: SpawnOptionsWithoutStdio): ChildProcessWithoutNullStreams { spawn(command: string, args: readonly string[], options?: SpawnOptionsWithoutStdio): ChildProcessWithoutNullStreams {
+20 -15
View File
@@ -3,37 +3,36 @@ import { Insertable, Kysely, Updateable } from 'kysely';
import { InjectKysely } from 'nestjs-kysely'; import { InjectKysely } from 'nestjs-kysely';
import { DB, Sessions } from 'src/db'; import { DB, Sessions } from 'src/db';
import { DummyValue, GenerateSql } from 'src/decorators'; import { DummyValue, GenerateSql } from 'src/decorators';
import { withUser } from 'src/entities/session.entity'; import { SessionEntity, withUser } from 'src/entities/session.entity';
import { ISessionRepository, SessionSearchOptions } from 'src/interfaces/session.interface';
import { asUuid } from 'src/utils/database'; import { asUuid } from 'src/utils/database';
export type SessionSearchOptions = { updatedBefore: Date };
@Injectable() @Injectable()
export class SessionRepository { export class SessionRepository implements ISessionRepository {
constructor(@InjectKysely() private db: Kysely<DB>) {} constructor(@InjectKysely() private db: Kysely<DB>) {}
@GenerateSql({ params: [{ updatedBefore: DummyValue.DATE }] }) @GenerateSql({ params: [{ updatedBefore: DummyValue.DATE }] })
search(options: SessionSearchOptions) { search(options: SessionSearchOptions): Promise<SessionEntity[]> {
return this.db return this.db
.selectFrom('sessions') .selectFrom('sessions')
.selectAll() .selectAll()
.where('sessions.updatedAt', '<=', options.updatedBefore) .where('sessions.updatedAt', '<=', options.updatedBefore)
.execute(); .execute() as Promise<SessionEntity[]>;
} }
@GenerateSql({ params: [DummyValue.STRING] }) @GenerateSql({ params: [DummyValue.STRING] })
getByToken(token: string) { getByToken(token: string): Promise<SessionEntity | undefined> {
return this.db return this.db
.selectFrom('sessions') .selectFrom('sessions')
.innerJoinLateral(withUser, (join) => join.onTrue()) .innerJoinLateral(withUser, (join) => join.onTrue())
.selectAll('sessions') .selectAll('sessions')
.select((eb) => eb.fn.toJson('user').as('user')) .select((eb) => eb.fn.toJson('user').as('user'))
.where('sessions.token', '=', token) .where('sessions.token', '=', token)
.executeTakeFirst(); .executeTakeFirst() as Promise<SessionEntity | undefined>;
} }
@GenerateSql({ params: [DummyValue.UUID] }) @GenerateSql({ params: [DummyValue.UUID] })
getByUserId(userId: string) { getByUserId(userId: string): Promise<SessionEntity[]> {
return this.db return this.db
.selectFrom('sessions') .selectFrom('sessions')
.innerJoinLateral(withUser, (join) => join.onTrue()) .innerJoinLateral(withUser, (join) => join.onTrue())
@@ -42,24 +41,30 @@ export class SessionRepository {
.where('sessions.userId', '=', userId) .where('sessions.userId', '=', userId)
.orderBy('sessions.updatedAt', 'desc') .orderBy('sessions.updatedAt', 'desc')
.orderBy('sessions.createdAt', 'desc') .orderBy('sessions.createdAt', 'desc')
.execute(); .execute() as unknown as Promise<SessionEntity[]>;
} }
create(dto: Insertable<Sessions>) { async create(dto: Insertable<Sessions>): Promise<SessionEntity> {
return this.db.insertInto('sessions').values(dto).returningAll().executeTakeFirstOrThrow(); const { id, token, userId, createdAt, updatedAt, deviceType, deviceOS } = await this.db
.insertInto('sessions')
.values(dto)
.returningAll()
.executeTakeFirstOrThrow();
return { id, token, userId, createdAt, updatedAt, deviceType, deviceOS } as SessionEntity;
} }
update(id: string, dto: Updateable<Sessions>) { update(id: string, dto: Updateable<Sessions>): Promise<SessionEntity> {
return this.db return this.db
.updateTable('sessions') .updateTable('sessions')
.set(dto) .set(dto)
.where('sessions.id', '=', asUuid(id)) .where('sessions.id', '=', asUuid(id))
.returningAll() .returningAll()
.executeTakeFirstOrThrow(); .executeTakeFirstOrThrow() as Promise<SessionEntity>;
} }
@GenerateSql({ params: [DummyValue.UUID] }) @GenerateSql({ params: [DummyValue.UUID] })
async delete(id: string) { async delete(id: string): Promise<void> {
await this.db.deleteFrom('sessions').where('id', '=', asUuid(id)).execute(); await this.db.deleteFrom('sessions').where('id', '=', asUuid(id)).execute();
} }
} }
@@ -7,7 +7,7 @@ import { DB, SharedLinks } from 'src/db';
import { DummyValue, GenerateSql } from 'src/decorators'; import { DummyValue, GenerateSql } from 'src/decorators';
import { SharedLinkEntity } from 'src/entities/shared-link.entity'; import { SharedLinkEntity } from 'src/entities/shared-link.entity';
import { SharedLinkType } from 'src/enum'; import { SharedLinkType } from 'src/enum';
import { ISharedLinkRepository, SharedLinkSearchOptions } from 'src/interfaces/shared-link.interface'; import { ISharedLinkRepository } from 'src/interfaces/shared-link.interface';
@Injectable() @Injectable()
export class SharedLinkRepository implements ISharedLinkRepository { export class SharedLinkRepository implements ISharedLinkRepository {
@@ -93,7 +93,7 @@ export class SharedLinkRepository implements ISharedLinkRepository {
} }
@GenerateSql({ params: [DummyValue.UUID] }) @GenerateSql({ params: [DummyValue.UUID] })
getAll({ userId, albumId }: SharedLinkSearchOptions): Promise<SharedLinkEntity[]> { getAll(userId: string): Promise<SharedLinkEntity[]> {
return this.db return this.db
.selectFrom('shared_links') .selectFrom('shared_links')
.selectAll('shared_links') .selectAll('shared_links')
@@ -149,7 +149,6 @@ export class SharedLinkRepository implements ISharedLinkRepository {
) )
.select((eb) => eb.fn.toJson('album').as('album')) .select((eb) => eb.fn.toJson('album').as('album'))
.where((eb) => eb.or([eb('shared_links.type', '=', SharedLinkType.INDIVIDUAL), eb('album.id', 'is not', null)])) .where((eb) => eb.or([eb('shared_links.type', '=', SharedLinkType.INDIVIDUAL), eb('album.id', 'is not', null)]))
.$if(!!albumId, (eb) => eb.where('shared_links.albumId', '=', albumId!))
.orderBy('shared_links.createdAt', 'desc') .orderBy('shared_links.createdAt', 'desc')
.distinctOn(['shared_links.createdAt']) .distinctOn(['shared_links.createdAt'])
.execute() as unknown as Promise<SharedLinkEntity[]>; .execute() as unknown as Promise<SharedLinkEntity[]>;
@@ -2,6 +2,7 @@ import { Injectable } from '@nestjs/common';
import archiver from 'archiver'; import archiver from 'archiver';
import chokidar, { WatchOptions } from 'chokidar'; import chokidar, { WatchOptions } from 'chokidar';
import { escapePath, glob, globStream } from 'fast-glob'; import { escapePath, glob, globStream } from 'fast-glob';
import { ErrnoException } from 'fast-glob/out/types';
import { constants, createReadStream, createWriteStream, existsSync, mkdirSync } from 'node:fs'; import { constants, createReadStream, createWriteStream, existsSync, mkdirSync } from 'node:fs';
import fs from 'node:fs/promises'; import fs from 'node:fs/promises';
import path from 'node:path'; import path from 'node:path';
@@ -170,6 +171,19 @@ export class StorageRepository implements IStorageRepository {
}); });
} }
private errorHandler = (error: ErrnoException) => {
if (error.code === 'ENOENT') {
this.logger.warn(`Path ${error.path} does not exist, ignoring.`);
return true;
} else if (error.code === 'EACCES') {
this.logger.warn(`Permission denied for path ${error.path}, ignoring.`);
return true;
}
this.logger.error(`Error while walking path ${error.path}: ${error.message}`);
return false;
};
async *walk(walkOptions: WalkOptionsDto): AsyncGenerator<string[]> { async *walk(walkOptions: WalkOptionsDto): AsyncGenerator<string[]> {
const { pathsToCrawl, exclusionPatterns, includeHidden } = walkOptions; const { pathsToCrawl, exclusionPatterns, includeHidden } = walkOptions;
if (pathsToCrawl.length === 0) { if (pathsToCrawl.length === 0) {
@@ -185,6 +199,7 @@ export class StorageRepository implements IStorageRepository {
onlyFiles: true, onlyFiles: true,
dot: includeHidden, dot: includeHidden,
ignore: exclusionPatterns, ignore: exclusionPatterns,
errorHandler: this.errorHandler,
}); });
let batch: string[] = []; let batch: string[] = [];
@@ -5,11 +5,12 @@ import { readFile } from 'node:fs/promises';
import { DB, SystemMetadata as DbSystemMetadata } from 'src/db'; import { DB, SystemMetadata as DbSystemMetadata } from 'src/db';
import { GenerateSql } from 'src/decorators'; import { GenerateSql } from 'src/decorators';
import { SystemMetadata } from 'src/entities/system-metadata.entity'; import { SystemMetadata } from 'src/entities/system-metadata.entity';
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
type Upsert = Insertable<DbSystemMetadata>; type Upsert = Insertable<DbSystemMetadata>;
@Injectable() @Injectable()
export class SystemMetadataRepository { export class SystemMetadataRepository implements ISystemMetadataRepository {
constructor(@InjectKysely() private db: Kysely<DB>) {} constructor(@InjectKysely() private db: Kysely<DB>) {}
@GenerateSql({ params: ['metadata_key'] }) @GenerateSql({ params: ['metadata_key'] })
+1 -1
View File
@@ -9,9 +9,9 @@ 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 { IPartnerRepository } from 'src/interfaces/partner.interface'; 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 { IUserRepository } from 'src/interfaces/user.interface'; import { IUserRepository } from 'src/interfaces/user.interface';
import { AssetService } from 'src/services/asset.service'; import { AssetService } from 'src/services/asset.service';
import { ISystemMetadataRepository } from 'src/types';
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 { faceStub } from 'test/fixtures/face.stub'; import { faceStub } from 'test/fixtures/face.stub';
+7 -5
View File
@@ -5,10 +5,12 @@ import { UserEntity } from 'src/entities/user.entity';
import { AuthType, Permission } from 'src/enum'; import { AuthType, Permission } from 'src/enum';
import { ICryptoRepository } from 'src/interfaces/crypto.interface'; import { ICryptoRepository } from 'src/interfaces/crypto.interface';
import { IEventRepository } from 'src/interfaces/event.interface'; import { IEventRepository } from 'src/interfaces/event.interface';
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 { IUserRepository } from 'src/interfaces/user.interface'; import { IUserRepository } from 'src/interfaces/user.interface';
import { AuthService } from 'src/services/auth.service'; import { AuthService } from 'src/services/auth.service';
import { IApiKeyRepository, IOAuthRepository, ISessionRepository, ISystemMetadataRepository } from 'src/types'; import { IApiKeyRepository, IOAuthRepository } from 'src/types';
import { keyStub } from 'test/fixtures/api-key.stub'; import { keyStub } from 'test/fixtures/api-key.stub';
import { authStub } from 'test/fixtures/auth.stub'; import { authStub } from 'test/fixtures/auth.stub';
import { sessionStub } from 'test/fixtures/session.stub'; import { sessionStub } from 'test/fixtures/session.stub';
@@ -256,7 +258,7 @@ describe('AuthService', () => {
it('should validate using authorization header', async () => { it('should validate using authorization header', async () => {
userMock.get.mockResolvedValue(userStub.user1); userMock.get.mockResolvedValue(userStub.user1);
sessionMock.getByToken.mockResolvedValue(sessionStub.valid as any); sessionMock.getByToken.mockResolvedValue(sessionStub.valid);
await expect( await expect(
sut.authenticate({ sut.authenticate({
headers: { authorization: 'Bearer auth_token' }, headers: { authorization: 'Bearer auth_token' },
@@ -361,7 +363,7 @@ describe('AuthService', () => {
}); });
it('should return an auth dto', async () => { it('should return an auth dto', async () => {
sessionMock.getByToken.mockResolvedValue(sessionStub.valid as any); sessionMock.getByToken.mockResolvedValue(sessionStub.valid);
await expect( await expect(
sut.authenticate({ sut.authenticate({
headers: { cookie: 'immich_access_token=auth_token' }, headers: { cookie: 'immich_access_token=auth_token' },
@@ -375,7 +377,7 @@ describe('AuthService', () => {
}); });
it('should throw if admin route and not an admin', async () => { it('should throw if admin route and not an admin', async () => {
sessionMock.getByToken.mockResolvedValue(sessionStub.valid as any); sessionMock.getByToken.mockResolvedValue(sessionStub.valid);
await expect( await expect(
sut.authenticate({ sut.authenticate({
headers: { cookie: 'immich_access_token=auth_token' }, headers: { cookie: 'immich_access_token=auth_token' },
@@ -386,7 +388,7 @@ describe('AuthService', () => {
}); });
it('should update when access time exceeds an hour', async () => { it('should update when access time exceeds an hour', async () => {
sessionMock.getByToken.mockResolvedValue(sessionStub.inactive as any); sessionMock.getByToken.mockResolvedValue(sessionStub.inactive);
sessionMock.update.mockResolvedValue(sessionStub.valid); sessionMock.update.mockResolvedValue(sessionStub.valid);
await expect( await expect(
sut.authenticate({ sut.authenticate({
+1 -2
View File
@@ -17,7 +17,6 @@ import {
mapLoginResponse, mapLoginResponse,
} from 'src/dtos/auth.dto'; } from 'src/dtos/auth.dto';
import { UserAdminResponseDto, mapUserAdmin } from 'src/dtos/user.dto'; import { UserAdminResponseDto, mapUserAdmin } from 'src/dtos/user.dto';
import { SessionEntity } from 'src/entities/session.entity';
import { UserEntity } from 'src/entities/user.entity'; import { UserEntity } from 'src/entities/user.entity';
import { AuthType, ImmichCookie, ImmichHeader, ImmichQuery, Permission } from 'src/enum'; import { AuthType, ImmichCookie, ImmichHeader, ImmichQuery, Permission } from 'src/enum';
import { OAuthProfile } from 'src/repositories/oauth.repository'; import { OAuthProfile } from 'src/repositories/oauth.repository';
@@ -339,7 +338,7 @@ export class AuthService extends BaseService {
await this.sessionRepository.update(session.id, { id: session.id, updatedAt: new Date() }); await this.sessionRepository.update(session.id, { id: session.id, updatedAt: new Date() });
} }
return { user: session.user as unknown as UserEntity, session: session as unknown as SessionEntity }; return { user: session.user, session };
} }
throw new UnauthorizedException('Invalid user token'); throw new UnauthorizedException('Invalid user token');
+3 -1
View File
@@ -4,9 +4,11 @@ import { StorageCore } from 'src/cores/storage.core';
import { ImmichWorker, StorageFolder } from 'src/enum'; import { ImmichWorker, StorageFolder } from 'src/enum';
import { IDatabaseRepository } from 'src/interfaces/database.interface'; import { IDatabaseRepository } from 'src/interfaces/database.interface';
import { JobStatus } from 'src/interfaces/job.interface'; import { JobStatus } from 'src/interfaces/job.interface';
import { IProcessRepository } from 'src/interfaces/process.interface';
import { IStorageRepository } from 'src/interfaces/storage.interface'; import { IStorageRepository } from 'src/interfaces/storage.interface';
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
import { BackupService } from 'src/services/backup.service'; import { BackupService } from 'src/services/backup.service';
import { IConfigRepository, ICronRepository, IProcessRepository, ISystemMetadataRepository } from 'src/types'; import { IConfigRepository, ICronRepository } from 'src/types';
import { systemConfigStub } from 'test/fixtures/system-config.stub'; import { systemConfigStub } from 'test/fixtures/system-config.stub';
import { mockSpawn, newTestService } from 'test/utils'; import { mockSpawn, newTestService } from 'test/utils';
import { describe, Mocked } from 'vitest'; import { describe, Mocked } from 'vitest';
+6 -6
View File
@@ -17,10 +17,13 @@ import { IMachineLearningRepository } from 'src/interfaces/machine-learning.inte
import { IMoveRepository } from 'src/interfaces/move.interface'; import { IMoveRepository } from 'src/interfaces/move.interface';
import { IPartnerRepository } from 'src/interfaces/partner.interface'; import { IPartnerRepository } from 'src/interfaces/partner.interface';
import { IPersonRepository } from 'src/interfaces/person.interface'; import { IPersonRepository } from 'src/interfaces/person.interface';
import { IProcessRepository } from 'src/interfaces/process.interface';
import { ISearchRepository } from 'src/interfaces/search.interface'; import { ISearchRepository } from 'src/interfaces/search.interface';
import { ISessionRepository } from 'src/interfaces/session.interface';
import { ISharedLinkRepository } from 'src/interfaces/shared-link.interface'; import { ISharedLinkRepository } from 'src/interfaces/shared-link.interface';
import { IStackRepository } from 'src/interfaces/stack.interface'; import { IStackRepository } from 'src/interfaces/stack.interface';
import { IStorageRepository } from 'src/interfaces/storage.interface'; import { IStorageRepository } from 'src/interfaces/storage.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 { AccessRepository } from 'src/repositories/access.repository'; import { AccessRepository } from 'src/repositories/access.repository';
@@ -37,10 +40,7 @@ import { MemoryRepository } from 'src/repositories/memory.repository';
import { MetadataRepository } from 'src/repositories/metadata.repository'; import { MetadataRepository } from 'src/repositories/metadata.repository';
import { NotificationRepository } from 'src/repositories/notification.repository'; import { NotificationRepository } from 'src/repositories/notification.repository';
import { OAuthRepository } from 'src/repositories/oauth.repository'; import { OAuthRepository } from 'src/repositories/oauth.repository';
import { ProcessRepository } from 'src/repositories/process.repository';
import { ServerInfoRepository } from 'src/repositories/server-info.repository'; import { ServerInfoRepository } from 'src/repositories/server-info.repository';
import { SessionRepository } from 'src/repositories/session.repository';
import { SystemMetadataRepository } from 'src/repositories/system-metadata.repository';
import { TelemetryRepository } from 'src/repositories/telemetry.repository'; import { TelemetryRepository } from 'src/repositories/telemetry.repository';
import { TrashRepository } from 'src/repositories/trash.repository'; import { TrashRepository } from 'src/repositories/trash.repository';
import { VersionHistoryRepository } from 'src/repositories/version-history.repository'; import { VersionHistoryRepository } from 'src/repositories/version-history.repository';
@@ -77,14 +77,14 @@ export class BaseService {
protected oauthRepository: OAuthRepository, protected oauthRepository: OAuthRepository,
@Inject(IPartnerRepository) protected partnerRepository: IPartnerRepository, @Inject(IPartnerRepository) protected partnerRepository: IPartnerRepository,
@Inject(IPersonRepository) protected personRepository: IPersonRepository, @Inject(IPersonRepository) protected personRepository: IPersonRepository,
protected processRepository: ProcessRepository, @Inject(IProcessRepository) protected processRepository: IProcessRepository,
@Inject(ISearchRepository) protected searchRepository: ISearchRepository, @Inject(ISearchRepository) protected searchRepository: ISearchRepository,
protected serverInfoRepository: ServerInfoRepository, protected serverInfoRepository: ServerInfoRepository,
protected sessionRepository: SessionRepository, @Inject(ISessionRepository) protected sessionRepository: ISessionRepository,
@Inject(ISharedLinkRepository) protected sharedLinkRepository: ISharedLinkRepository, @Inject(ISharedLinkRepository) protected sharedLinkRepository: ISharedLinkRepository,
@Inject(IStackRepository) protected stackRepository: IStackRepository, @Inject(IStackRepository) protected stackRepository: IStackRepository,
@Inject(IStorageRepository) protected storageRepository: IStorageRepository, @Inject(IStorageRepository) protected storageRepository: IStorageRepository,
protected systemMetadataRepository: SystemMetadataRepository, @Inject(ISystemMetadataRepository) protected systemMetadataRepository: ISystemMetadataRepository,
@Inject(ITagRepository) protected tagRepository: ITagRepository, @Inject(ITagRepository) protected tagRepository: ITagRepository,
protected telemetryRepository: TelemetryRepository, protected telemetryRepository: TelemetryRepository,
protected trashRepository: TrashRepository, protected trashRepository: TrashRepository,
+1 -1
View File
@@ -1,6 +1,6 @@
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
import { IUserRepository } from 'src/interfaces/user.interface'; import { IUserRepository } from 'src/interfaces/user.interface';
import { CliService } from 'src/services/cli.service'; import { CliService } from 'src/services/cli.service';
import { ISystemMetadataRepository } from 'src/types';
import { userStub } from 'test/fixtures/user.stub'; import { userStub } from 'test/fixtures/user.stub';
import { newTestService } from 'test/utils'; import { newTestService } from 'test/utils';
import { Mocked, describe, it } from 'vitest'; import { Mocked, describe, it } from 'vitest';
@@ -1,9 +1,10 @@
import { IAssetRepository, WithoutProperty } from 'src/interfaces/asset.interface'; import { IAssetRepository, WithoutProperty } from 'src/interfaces/asset.interface';
import { IJobRepository, JobName, JobStatus } from 'src/interfaces/job.interface'; import { IJobRepository, JobName, JobStatus } from 'src/interfaces/job.interface';
import { ISearchRepository } from 'src/interfaces/search.interface'; import { ISearchRepository } from 'src/interfaces/search.interface';
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
import { DuplicateService } from 'src/services/duplicate.service'; import { DuplicateService } from 'src/services/duplicate.service';
import { SearchService } from 'src/services/search.service'; import { SearchService } from 'src/services/search.service';
import { ILoggingRepository, ISystemMetadataRepository } from 'src/types'; import { ILoggingRepository } from 'src/types';
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 { newTestService } from 'test/utils'; import { newTestService } from 'test/utils';
+2 -1
View File
@@ -18,8 +18,9 @@ import { IJobRepository, JobCounts, JobName, JobStatus } from 'src/interfaces/jo
import { IMoveRepository } from 'src/interfaces/move.interface'; 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 { MediaService } from 'src/services/media.service'; import { MediaService } from 'src/services/media.service';
import { ILoggingRepository, IMediaRepository, ISystemMetadataRepository, RawImageInfo } from 'src/types'; import { ILoggingRepository, IMediaRepository, RawImageInfo } from 'src/types';
import { assetStub } from 'test/fixtures/asset.stub'; import { assetStub } from 'test/fixtures/asset.stub';
import { faceStub } from 'test/fixtures/face.stub'; import { faceStub } from 'test/fixtures/face.stub';
import { probeStub } from 'test/fixtures/media.stub'; import { probeStub } from 'test/fixtures/media.stub';
+2 -7
View File
@@ -12,17 +12,12 @@ 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 { 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 { 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 { ImmichTags } from 'src/repositories/metadata.repository'; import { ImmichTags } from 'src/repositories/metadata.repository';
import { MetadataService } from 'src/services/metadata.service'; import { MetadataService } from 'src/services/metadata.service';
import { import { IConfigRepository, IMapRepository, IMediaRepository, IMetadataRepository } from 'src/types';
IConfigRepository,
IMapRepository,
IMediaRepository,
IMetadataRepository,
ISystemMetadataRepository,
} from 'src/types';
import { assetStub } from 'test/fixtures/asset.stub'; import { assetStub } from 'test/fixtures/asset.stub';
import { fileStub } from 'test/fixtures/file.stub'; import { fileStub } from 'test/fixtures/file.stub';
import { probeStub } from 'test/fixtures/media.stub'; import { probeStub } from 'test/fixtures/media.stub';
@@ -8,10 +8,11 @@ import { IAlbumRepository } from 'src/interfaces/album.interface';
import { IAssetRepository } from 'src/interfaces/asset.interface'; import { IAssetRepository } from 'src/interfaces/asset.interface';
import { IEventRepository } from 'src/interfaces/event.interface'; import { IEventRepository } from 'src/interfaces/event.interface';
import { IJobRepository, INotifyAlbumUpdateJob, JobName, JobStatus } from 'src/interfaces/job.interface'; import { IJobRepository, INotifyAlbumUpdateJob, JobName, JobStatus } from 'src/interfaces/job.interface';
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
import { IUserRepository } from 'src/interfaces/user.interface'; import { IUserRepository } from 'src/interfaces/user.interface';
import { EmailTemplate } from 'src/repositories/notification.repository'; import { EmailTemplate } from 'src/repositories/notification.repository';
import { NotificationService } from 'src/services/notification.service'; import { NotificationService } from 'src/services/notification.service';
import { INotificationRepository, ISystemMetadataRepository } from 'src/types'; import { INotificationRepository } from 'src/types';
import { albumStub } from 'test/fixtures/album.stub'; import { albumStub } from 'test/fixtures/album.stub';
import { assetStub } from 'test/fixtures/asset.stub'; import { assetStub } from 'test/fixtures/asset.stub';
import { userStub } from 'test/fixtures/user.stub'; import { userStub } from 'test/fixtures/user.stub';
+4 -3
View File
@@ -10,8 +10,9 @@ import { DetectedFaces, IMachineLearningRepository } from 'src/interfaces/machin
import { IPersonRepository } from 'src/interfaces/person.interface'; import { IPersonRepository } from 'src/interfaces/person.interface';
import { FaceSearchResult, ISearchRepository } from 'src/interfaces/search.interface'; import { FaceSearchResult, 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 { PersonService } from 'src/services/person.service'; import { PersonService } from 'src/services/person.service';
import { IMediaRepository, ISystemMetadataRepository } from 'src/types'; import { IMediaRepository } from 'src/types';
import { ImmichFileResponse } from 'src/utils/file'; import { ImmichFileResponse } from 'src/utils/file';
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';
@@ -354,7 +355,7 @@ describe(PersonService.name, () => {
sut.reassignFaces(authStub.admin, personStub.noName.id, { sut.reassignFaces(authStub.admin, personStub.noName.id, {
data: [{ personId: personStub.withName.id, assetId: assetStub.image.id }], data: [{ personId: personStub.withName.id, assetId: assetStub.image.id }],
}), }),
).resolves.toBeDefined(); ).resolves.toEqual([personStub.noName]);
expect(jobMock.queueAll).toHaveBeenCalledWith([ expect(jobMock.queueAll).toHaveBeenCalledWith([
{ {
@@ -447,7 +448,7 @@ describe(PersonService.name, () => {
it('should create a new person', async () => { it('should create a new person', async () => {
personMock.create.mockResolvedValue(personStub.primaryPerson); personMock.create.mockResolvedValue(personStub.primaryPerson);
await expect(sut.create(authStub.admin, {})).resolves.toBeDefined(); await expect(sut.create(authStub.admin, {})).resolves.toBe(personStub.primaryPerson);
expect(personMock.create).toHaveBeenCalledWith({ ownerId: authStub.admin.user.id }); expect(personMock.create).toHaveBeenCalledWith({ ownerId: authStub.admin.user.id });
}); });
+4 -8
View File
@@ -104,7 +104,7 @@ export class PersonService extends BaseService {
await this.personRepository.reassignFace(face.id, personId); await this.personRepository.reassignFace(face.id, personId);
} }
result.push(mapPerson(person)); result.push(person);
} }
if (changeFeaturePhoto.length > 0) { if (changeFeaturePhoto.length > 0) {
// Remove duplicates // Remove duplicates
@@ -178,23 +178,20 @@ export class PersonService extends BaseService {
}); });
} }
async create(auth: AuthDto, dto: PersonCreateDto): Promise<PersonResponseDto> { create(auth: AuthDto, dto: PersonCreateDto): Promise<PersonResponseDto> {
const person = await this.personRepository.create({ return this.personRepository.create({
ownerId: auth.user.id, ownerId: auth.user.id,
name: dto.name, name: dto.name,
birthDate: dto.birthDate, birthDate: dto.birthDate,
isHidden: dto.isHidden, isHidden: dto.isHidden,
isFavorite: dto.isFavorite, isFavorite: dto.isFavorite,
color: dto.color,
}); });
return mapPerson(person);
} }
async update(auth: AuthDto, id: string, dto: PersonUpdateDto): Promise<PersonResponseDto> { async update(auth: AuthDto, id: string, dto: PersonUpdateDto): Promise<PersonResponseDto> {
await this.requireAccess({ auth, permission: Permission.PERSON_UPDATE, ids: [id] }); await this.requireAccess({ auth, permission: Permission.PERSON_UPDATE, ids: [id] });
const { name, birthDate, isHidden, featureFaceAssetId: assetId, isFavorite, color } = dto; const { name, birthDate, isHidden, featureFaceAssetId: assetId, isFavorite } = dto;
// TODO: set by faceId directly // TODO: set by faceId directly
let faceId: string | undefined = undefined; let faceId: string | undefined = undefined;
if (assetId) { if (assetId) {
@@ -214,7 +211,6 @@ export class PersonService extends BaseService {
birthDate, birthDate,
isHidden, isHidden,
isFavorite, isFavorite,
color,
}); });
if (assetId) { if (assetId) {
@@ -31,8 +31,6 @@ describe(SearchService.name, () => {
it('should pass options to search', async () => { it('should pass options to search', async () => {
const { name } = personStub.withName; const { name } = personStub.withName;
personMock.getByName.mockResolvedValue([]);
await sut.searchPerson(authStub.user1, { name, withHidden: false }); await sut.searchPerson(authStub.user1, { name, withHidden: false });
expect(personMock.getByName).toHaveBeenCalledWith(authStub.user1.user.id, name, { withHidden: false }); expect(personMock.getByName).toHaveBeenCalledWith(authStub.user1.user.id, name, { withHidden: false });
+3 -4
View File
@@ -1,9 +1,8 @@
import { BadRequestException, Injectable } from '@nestjs/common'; import { BadRequestException, Injectable } from '@nestjs/common';
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 { mapPerson, PersonResponseDto } from 'src/dtos/person.dto'; import { PersonResponseDto } from 'src/dtos/person.dto';
import { import {
mapPlaces,
MetadataSearchDto, MetadataSearchDto,
PlacesResponseDto, PlacesResponseDto,
RandomSearchDto, RandomSearchDto,
@@ -13,6 +12,7 @@ import {
SearchSuggestionRequestDto, SearchSuggestionRequestDto,
SearchSuggestionType, SearchSuggestionType,
SmartSearchDto, SmartSearchDto,
mapPlaces,
} from 'src/dtos/search.dto'; } from 'src/dtos/search.dto';
import { AssetEntity } from 'src/entities/asset.entity'; import { AssetEntity } from 'src/entities/asset.entity';
import { AssetOrder } from 'src/enum'; import { AssetOrder } from 'src/enum';
@@ -24,8 +24,7 @@ import { isSmartSearchEnabled } from 'src/utils/misc';
@Injectable() @Injectable()
export class SearchService extends BaseService { export class SearchService extends BaseService {
async searchPerson(auth: AuthDto, dto: SearchPeopleDto): Promise<PersonResponseDto[]> { async searchPerson(auth: AuthDto, dto: SearchPeopleDto): Promise<PersonResponseDto[]> {
const people = await this.personRepository.getByName(auth.user.id, dto.name, { withHidden: dto.withHidden }); return this.personRepository.getByName(auth.user.id, dto.name, { withHidden: dto.withHidden });
return people.map((person) => mapPerson(person));
} }
async searchPlaces(dto: SearchPlacesDto): Promise<PlacesResponseDto[]> { async searchPlaces(dto: SearchPlacesDto): Promise<PlacesResponseDto[]> {
+1 -1
View File
@@ -1,8 +1,8 @@
import { SystemMetadataKey } from 'src/enum'; import { SystemMetadataKey } from 'src/enum';
import { IStorageRepository } from 'src/interfaces/storage.interface'; import { IStorageRepository } from 'src/interfaces/storage.interface';
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
import { IUserRepository } from 'src/interfaces/user.interface'; import { IUserRepository } from 'src/interfaces/user.interface';
import { ServerService } from 'src/services/server.service'; import { ServerService } from 'src/services/server.service';
import { ISystemMetadataRepository } from 'src/types';
import { newTestService } from 'test/utils'; import { newTestService } from 'test/utils';
import { Mocked } from 'vitest'; import { Mocked } from 'vitest';
+5 -3
View File
@@ -1,6 +1,7 @@
import { UserEntity } from 'src/entities/user.entity';
import { JobStatus } from 'src/interfaces/job.interface'; import { JobStatus } from 'src/interfaces/job.interface';
import { ISessionRepository } from 'src/interfaces/session.interface';
import { SessionService } from 'src/services/session.service'; import { SessionService } from 'src/services/session.service';
import { ISessionRepository } from 'src/types';
import { authStub } from 'test/fixtures/auth.stub'; import { authStub } from 'test/fixtures/auth.stub';
import { sessionStub } from 'test/fixtures/session.stub'; import { sessionStub } from 'test/fixtures/session.stub';
import { IAccessRepositoryMock } from 'test/repositories/access.repository.mock'; import { IAccessRepositoryMock } from 'test/repositories/access.repository.mock';
@@ -37,6 +38,7 @@ describe('SessionService', () => {
deviceType: '', deviceType: '',
id: '123', id: '123',
token: '420', token: '420',
user: {} as UserEntity,
userId: '42', userId: '42',
}, },
]); ]);
@@ -48,7 +50,7 @@ describe('SessionService', () => {
describe('getAll', () => { describe('getAll', () => {
it('should get the devices', async () => { it('should get the devices', async () => {
sessionMock.getByUserId.mockResolvedValue([sessionStub.valid as any, sessionStub.inactive]); sessionMock.getByUserId.mockResolvedValue([sessionStub.valid, sessionStub.inactive]);
await expect(sut.getAll(authStub.user1)).resolves.toEqual([ await expect(sut.getAll(authStub.user1)).resolves.toEqual([
{ {
createdAt: '2021-01-01T00:00:00.000Z', createdAt: '2021-01-01T00:00:00.000Z',
@@ -74,7 +76,7 @@ describe('SessionService', () => {
describe('logoutDevices', () => { describe('logoutDevices', () => {
it('should logout all devices', async () => { it('should logout all devices', async () => {
sessionMock.getByUserId.mockResolvedValue([sessionStub.inactive, sessionStub.valid] as any[]); sessionMock.getByUserId.mockResolvedValue([sessionStub.inactive, sessionStub.valid]);
await sut.deleteAll(authStub.user1); await sut.deleteAll(authStub.user1);
@@ -29,11 +29,11 @@ describe(SharedLinkService.name, () => {
describe('getAll', () => { describe('getAll', () => {
it('should return all shared links for a user', async () => { it('should return all shared links for a user', async () => {
sharedLinkMock.getAll.mockResolvedValue([sharedLinkStub.expired, sharedLinkStub.valid]); sharedLinkMock.getAll.mockResolvedValue([sharedLinkStub.expired, sharedLinkStub.valid]);
await expect(sut.getAll(authStub.user1, {})).resolves.toEqual([ await expect(sut.getAll(authStub.user1)).resolves.toEqual([
sharedLinkResponseStub.expired, sharedLinkResponseStub.expired,
sharedLinkResponseStub.valid, sharedLinkResponseStub.valid,
]); ]);
expect(sharedLinkMock.getAll).toHaveBeenCalledWith({ userId: authStub.user1.user.id }); expect(sharedLinkMock.getAll).toHaveBeenCalledWith(authStub.user1.user.id);
}); });
}); });
+2 -5
View File
@@ -9,7 +9,6 @@ import {
SharedLinkEditDto, SharedLinkEditDto,
SharedLinkPasswordDto, SharedLinkPasswordDto,
SharedLinkResponseDto, SharedLinkResponseDto,
SharedLinkSearchDto,
} from 'src/dtos/shared-link.dto'; } from 'src/dtos/shared-link.dto';
import { SharedLinkEntity } from 'src/entities/shared-link.entity'; import { SharedLinkEntity } from 'src/entities/shared-link.entity';
import { Permission, SharedLinkType } from 'src/enum'; import { Permission, SharedLinkType } from 'src/enum';
@@ -18,10 +17,8 @@ import { getExternalDomain, OpenGraphTags } from 'src/utils/misc';
@Injectable() @Injectable()
export class SharedLinkService extends BaseService { export class SharedLinkService extends BaseService {
async getAll(auth: AuthDto, { albumId }: SharedLinkSearchDto): Promise<SharedLinkResponseDto[]> { async getAll(auth: AuthDto): Promise<SharedLinkResponseDto[]> {
return this.sharedLinkRepository return this.sharedLinkRepository.getAll(auth.user.id).then((links) => links.map((link) => mapSharedLink(link)));
.getAll({ userId: auth.user.id, albumId })
.then((links) => links.map((link) => mapSharedLink(link)));
} }
async getMine(auth: AuthDto, dto: SharedLinkPasswordDto): Promise<SharedLinkResponseDto> { async getMine(auth: AuthDto, dto: SharedLinkPasswordDto): Promise<SharedLinkResponseDto> {
@@ -5,8 +5,9 @@ import { IDatabaseRepository } from 'src/interfaces/database.interface';
import { IJobRepository, JobName, JobStatus } from 'src/interfaces/job.interface'; import { IJobRepository, JobName, JobStatus } from 'src/interfaces/job.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 { SmartInfoService } from 'src/services/smart-info.service'; import { SmartInfoService } from 'src/services/smart-info.service';
import { IConfigRepository, ISystemMetadataRepository } from 'src/types'; import { IConfigRepository } from 'src/types';
import { getCLIPModelInfo } from 'src/utils/misc'; import { getCLIPModelInfo } from 'src/utils/misc';
import { assetStub } from 'test/fixtures/asset.stub'; import { assetStub } from 'test/fixtures/asset.stub';
import { systemConfigStub } from 'test/fixtures/system-config.stub'; import { systemConfigStub } from 'test/fixtures/system-config.stub';
@@ -8,9 +8,9 @@ import { ICryptoRepository } from 'src/interfaces/crypto.interface';
import { JobStatus } from 'src/interfaces/job.interface'; import { JobStatus } from 'src/interfaces/job.interface';
import { IMoveRepository } from 'src/interfaces/move.interface'; import { IMoveRepository } from 'src/interfaces/move.interface';
import { IStorageRepository } from 'src/interfaces/storage.interface'; import { IStorageRepository } from 'src/interfaces/storage.interface';
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
import { IUserRepository } from 'src/interfaces/user.interface'; import { IUserRepository } from 'src/interfaces/user.interface';
import { StorageTemplateService } from 'src/services/storage-template.service'; import { StorageTemplateService } from 'src/services/storage-template.service';
import { ISystemMetadataRepository } from 'src/types';
import { albumStub } from 'test/fixtures/album.stub'; import { albumStub } from 'test/fixtures/album.stub';
import { assetStub } from 'test/fixtures/asset.stub'; import { assetStub } from 'test/fixtures/asset.stub';
import { userStub } from 'test/fixtures/user.stub'; import { userStub } from 'test/fixtures/user.stub';
+2 -1
View File
@@ -1,7 +1,8 @@
import { SystemMetadataKey } from 'src/enum'; import { SystemMetadataKey } from 'src/enum';
import { IStorageRepository } from 'src/interfaces/storage.interface'; import { IStorageRepository } from 'src/interfaces/storage.interface';
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
import { StorageService } from 'src/services/storage.service'; import { StorageService } from 'src/services/storage.service';
import { IConfigRepository, ILoggingRepository, ISystemMetadataRepository } from 'src/types'; import { IConfigRepository, ILoggingRepository } from 'src/types';
import { ImmichStartupError } from 'src/utils/misc'; import { ImmichStartupError } from 'src/utils/misc';
import { mockEnvData } from 'test/repositories/config.repository.mock'; import { mockEnvData } from 'test/repositories/config.repository.mock';
import { newTestService } from 'test/utils'; import { newTestService } from 'test/utils';
@@ -14,8 +14,9 @@ import {
} from 'src/enum'; } from 'src/enum';
import { IEventRepository } from 'src/interfaces/event.interface'; import { IEventRepository } from 'src/interfaces/event.interface';
import { QueueName } from 'src/interfaces/job.interface'; import { QueueName } from 'src/interfaces/job.interface';
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
import { SystemConfigService } from 'src/services/system-config.service'; import { SystemConfigService } from 'src/services/system-config.service';
import { DeepPartial, IConfigRepository, ILoggingRepository, ISystemMetadataRepository } from 'src/types'; import { DeepPartial, IConfigRepository, ILoggingRepository } from 'src/types';
import { mockEnvData } from 'test/repositories/config.repository.mock'; import { mockEnvData } from 'test/repositories/config.repository.mock';
import { newTestService } from 'test/utils'; import { newTestService } from 'test/utils';
import { Mocked } from 'vitest'; import { Mocked } from 'vitest';
@@ -1,6 +1,6 @@
import { SystemMetadataKey } from 'src/enum'; import { SystemMetadataKey } from 'src/enum';
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
import { SystemMetadataService } from 'src/services/system-metadata.service'; import { SystemMetadataService } from 'src/services/system-metadata.service';
import { ISystemMetadataRepository } from 'src/types';
import { newTestService } from 'test/utils'; import { newTestService } from 'test/utils';
import { Mocked } from 'vitest'; import { Mocked } from 'vitest';
+1 -1
View File
@@ -4,9 +4,9 @@ import { CacheControl, UserMetadataKey } from 'src/enum';
import { IAlbumRepository } from 'src/interfaces/album.interface'; import { IAlbumRepository } from 'src/interfaces/album.interface';
import { IJobRepository, JobName } from 'src/interfaces/job.interface'; import { IJobRepository, JobName } from 'src/interfaces/job.interface';
import { IStorageRepository } from 'src/interfaces/storage.interface'; import { IStorageRepository } from 'src/interfaces/storage.interface';
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
import { IUserRepository } from 'src/interfaces/user.interface'; import { IUserRepository } from 'src/interfaces/user.interface';
import { UserService } from 'src/services/user.service'; import { UserService } from 'src/services/user.service';
import { ISystemMetadataRepository } from 'src/types';
import { ImmichFileResponse } from 'src/utils/file'; import { ImmichFileResponse } from 'src/utils/file';
import { authStub } from 'test/fixtures/auth.stub'; import { authStub } from 'test/fixtures/auth.stub';
import { systemConfigStub } from 'test/fixtures/system-config.stub'; import { systemConfigStub } from 'test/fixtures/system-config.stub';
+2 -7
View File
@@ -4,14 +4,9 @@ import { serverVersion } from 'src/constants';
import { ImmichEnvironment, SystemMetadataKey } from 'src/enum'; import { ImmichEnvironment, SystemMetadataKey } from 'src/enum';
import { 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 { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
import { VersionService } from 'src/services/version.service'; import { VersionService } from 'src/services/version.service';
import { import { IConfigRepository, ILoggingRepository, IServerInfoRepository, IVersionHistoryRepository } from 'src/types';
IConfigRepository,
ILoggingRepository,
IServerInfoRepository,
ISystemMetadataRepository,
IVersionHistoryRepository,
} from 'src/types';
import { mockEnvData } from 'test/repositories/config.repository.mock'; import { mockEnvData } from 'test/repositories/config.repository.mock';
import { newTestService } from 'test/utils'; import { newTestService } from 'test/utils';
import { Mocked } from 'vitest'; import { Mocked } from 'vitest';
-8
View File
@@ -14,10 +14,7 @@ import { MemoryRepository } from 'src/repositories/memory.repository';
import { MetadataRepository } from 'src/repositories/metadata.repository'; import { MetadataRepository } from 'src/repositories/metadata.repository';
import { NotificationRepository } from 'src/repositories/notification.repository'; import { NotificationRepository } from 'src/repositories/notification.repository';
import { OAuthRepository } from 'src/repositories/oauth.repository'; import { OAuthRepository } from 'src/repositories/oauth.repository';
import { ProcessRepository } from 'src/repositories/process.repository';
import { ServerInfoRepository } from 'src/repositories/server-info.repository'; import { ServerInfoRepository } from 'src/repositories/server-info.repository';
import { SessionRepository } from 'src/repositories/session.repository';
import { SystemMetadataRepository } from 'src/repositories/system-metadata.repository';
import { MetricGroupRepository, TelemetryRepository } from 'src/repositories/telemetry.repository'; import { MetricGroupRepository, TelemetryRepository } from 'src/repositories/telemetry.repository';
import { TrashRepository } from 'src/repositories/trash.repository'; import { TrashRepository } from 'src/repositories/trash.repository';
import { VersionHistoryRepository } from 'src/repositories/version-history.repository'; import { VersionHistoryRepository } from 'src/repositories/version-history.repository';
@@ -61,10 +58,7 @@ export type IMetadataRepository = RepositoryInterface<MetadataRepository>;
export type IMetricGroupRepository = RepositoryInterface<MetricGroupRepository>; export type IMetricGroupRepository = RepositoryInterface<MetricGroupRepository>;
export type INotificationRepository = RepositoryInterface<NotificationRepository>; export type INotificationRepository = RepositoryInterface<NotificationRepository>;
export type IOAuthRepository = RepositoryInterface<OAuthRepository>; export type IOAuthRepository = RepositoryInterface<OAuthRepository>;
export type IProcessRepository = RepositoryInterface<ProcessRepository>;
export type ISessionRepository = RepositoryInterface<SessionRepository>;
export type IServerInfoRepository = RepositoryInterface<ServerInfoRepository>; export type IServerInfoRepository = RepositoryInterface<ServerInfoRepository>;
export type ISystemMetadataRepository = RepositoryInterface<SystemMetadataRepository>;
export type ITelemetryRepository = RepositoryInterface<TelemetryRepository>; export type ITelemetryRepository = RepositoryInterface<TelemetryRepository>;
export type ITrashRepository = RepositoryInterface<TrashRepository>; export type ITrashRepository = RepositoryInterface<TrashRepository>;
export type IViewRepository = RepositoryInterface<ViewRepository>; export type IViewRepository = RepositoryInterface<ViewRepository>;
@@ -83,8 +77,6 @@ export type MemoryItem =
| Awaited<ReturnType<IMemoryRepository['create']>> | Awaited<ReturnType<IMemoryRepository['create']>>
| Awaited<ReturnType<IMemoryRepository['search']>>[0]; | Awaited<ReturnType<IMemoryRepository['search']>>[0];
export type SessionItem = Awaited<ReturnType<ISessionRepository['getByUserId']>>[0];
export interface CropOptions { export interface CropOptions {
top: number; top: number;
left: number; left: number;
+2 -1
View File
@@ -7,7 +7,8 @@ import { SystemConfig, defaults } from 'src/config';
import { SystemConfigDto } from 'src/dtos/system-config.dto'; import { SystemConfigDto } from 'src/dtos/system-config.dto';
import { SystemMetadataKey } from 'src/enum'; import { SystemMetadataKey } from 'src/enum';
import { DatabaseLock } from 'src/interfaces/database.interface'; import { DatabaseLock } from 'src/interfaces/database.interface';
import { DeepPartial, IConfigRepository, ILoggingRepository, ISystemMetadataRepository } from 'src/types'; import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
import { DeepPartial, IConfigRepository, ILoggingRepository } from 'src/types';
import { getKeysDeep, unsetDeep } from 'src/utils/misc'; import { getKeysDeep, unsetDeep } from 'src/utils/misc';
export type SystemConfigValidator = (config: SystemConfig, newConfig: SystemConfig) => void | Promise<void>; export type SystemConfigValidator = (config: SystemConfig, newConfig: SystemConfig) => void | Promise<void>;
-10
View File
@@ -12,7 +12,6 @@ import {
IsArray, IsArray,
IsBoolean, IsBoolean,
IsDate, IsDate,
IsHexColor,
IsNotEmpty, IsNotEmpty,
IsOptional, IsOptional,
IsString, IsString,
@@ -98,15 +97,6 @@ export function Optional({ nullable, emptyToNull, ...validationOptions }: Option
return applyDecorators(...decorators); return applyDecorators(...decorators);
} }
export const ValidateHexColor = () => {
const decorators = [
IsHexColor(),
Transform(({ value }) => (typeof value === 'string' && value[0] !== '#' ? `#${value}` : value)),
];
return applyDecorators(...decorators);
};
type UUIDOptions = { optional?: boolean; each?: boolean; nullable?: boolean }; type UUIDOptions = { optional?: boolean; each?: boolean; nullable?: boolean };
export const ValidateUUID = (options?: UUIDOptions) => { export const ValidateUUID = (options?: UUIDOptions) => {
const { optional, each, nullable } = { optional: false, each: false, nullable: false, ...options }; const { optional, each, nullable } = { optional: false, each: false, nullable: false, ...options };
@@ -1,4 +1,4 @@
import { IProcessRepository } from 'src/types'; import { IProcessRepository } from 'src/interfaces/process.interface';
import { Mocked, vitest } from 'vitest'; import { Mocked, vitest } from 'vitest';
export const newProcessRepositoryMock = (): Mocked<IProcessRepository> => { export const newProcessRepositoryMock = (): Mocked<IProcessRepository> => {
@@ -1,4 +1,4 @@
import { ISessionRepository } from 'src/types'; import { ISessionRepository } from 'src/interfaces/session.interface';
import { Mocked, vitest } from 'vitest'; import { Mocked, vitest } from 'vitest';
export const newSessionRepositoryMock = (): Mocked<ISessionRepository> => { export const newSessionRepositoryMock = (): Mocked<ISessionRepository> => {
@@ -1,4 +1,4 @@
import { ISystemMetadataRepository } from 'src/types'; import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
import { clearConfigCache } from 'src/utils/config'; import { clearConfigCache } from 'src/utils/config';
import { Mocked, vitest } from 'vitest'; import { Mocked, vitest } from 'vitest';
+3 -9
View File
@@ -15,10 +15,7 @@ import { MemoryRepository } from 'src/repositories/memory.repository';
import { MetadataRepository } from 'src/repositories/metadata.repository'; import { MetadataRepository } from 'src/repositories/metadata.repository';
import { NotificationRepository } from 'src/repositories/notification.repository'; import { NotificationRepository } from 'src/repositories/notification.repository';
import { OAuthRepository } from 'src/repositories/oauth.repository'; import { OAuthRepository } from 'src/repositories/oauth.repository';
import { ProcessRepository } from 'src/repositories/process.repository';
import { ServerInfoRepository } from 'src/repositories/server-info.repository'; import { ServerInfoRepository } from 'src/repositories/server-info.repository';
import { SessionRepository } from 'src/repositories/session.repository';
import { SystemMetadataRepository } from 'src/repositories/system-metadata.repository';
import { TelemetryRepository } from 'src/repositories/telemetry.repository'; import { TelemetryRepository } from 'src/repositories/telemetry.repository';
import { TrashRepository } from 'src/repositories/trash.repository'; import { TrashRepository } from 'src/repositories/trash.repository';
import { VersionHistoryRepository } from 'src/repositories/version-history.repository'; import { VersionHistoryRepository } from 'src/repositories/version-history.repository';
@@ -38,10 +35,7 @@ import {
IMetadataRepository, IMetadataRepository,
INotificationRepository, INotificationRepository,
IOAuthRepository, IOAuthRepository,
IProcessRepository,
IServerInfoRepository, IServerInfoRepository,
ISessionRepository,
ISystemMetadataRepository,
ITrashRepository, ITrashRepository,
IVersionHistoryRepository, IVersionHistoryRepository,
IViewRepository, IViewRepository,
@@ -169,14 +163,14 @@ export const newTestService = <T extends BaseService>(
oauthMock as IOAuthRepository as OAuthRepository, oauthMock as IOAuthRepository as OAuthRepository,
partnerMock, partnerMock,
personMock, personMock,
processMock as IProcessRepository as ProcessRepository, processMock,
searchMock, searchMock,
serverInfoMock as IServerInfoRepository as ServerInfoRepository, serverInfoMock as IServerInfoRepository as ServerInfoRepository,
sessionMock as ISessionRepository as SessionRepository, sessionMock,
sharedLinkMock, sharedLinkMock,
stackMock, stackMock,
storageMock, storageMock,
systemMock as ISystemMetadataRepository as SystemMetadataRepository, systemMock,
tagMock, tagMock,
telemetryMock as unknown as TelemetryRepository, telemetryMock as unknown as TelemetryRepository,
trashMock as ITrashRepository as TrashRepository, trashMock as ITrashRepository as TrashRepository,
+3 -3
View File
@@ -1,12 +1,12 @@
{ {
"name": "immich-web", "name": "immich-web",
"version": "1.126.0", "version": "1.125.7",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "immich-web", "name": "immich-web",
"version": "1.126.0", "version": "1.125.7",
"license": "GNU Affero General Public License version 3", "license": "GNU Affero General Public License version 3",
"dependencies": { "dependencies": {
"@formatjs/icu-messageformat-parser": "^2.9.8", "@formatjs/icu-messageformat-parser": "^2.9.8",
@@ -77,7 +77,7 @@
}, },
"../open-api/typescript-sdk": { "../open-api/typescript-sdk": {
"name": "@immich/sdk", "name": "@immich/sdk",
"version": "1.126.0", "version": "1.125.7",
"license": "GNU Affero General Public License version 3", "license": "GNU Affero General Public License version 3",
"dependencies": { "dependencies": {
"@oazapfts/runtime": "^1.0.2" "@oazapfts/runtime": "^1.0.2"
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "immich-web", "name": "immich-web",
"version": "1.126.0", "version": "1.125.7",
"license": "GNU Affero General Public License version 3", "license": "GNU Affero General Public License version 3",
"scripts": { "scripts": {
"dev": "vite dev --host 0.0.0.0 --port 3000", "dev": "vite dev --host 0.0.0.0 --port 3000",

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