diff --git a/.github/workflows/cli.yml b/.github/workflows/cli.yml
index b6371d4a66..e7effc8551 100644
--- a/.github/workflows/cli.yml
+++ b/.github/workflows/cli.yml
@@ -88,7 +88,7 @@ jobs:
type=raw,value=latest,enable=${{ github.event_name == 'release' }}
- name: Build and push image
- uses: docker/build-push-action@v6.11.0
+ uses: docker/build-push-action@v6.12.0
with:
file: cli/Dockerfile
platforms: linux/amd64,linux/arm64
diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml
index 7df1b69e07..d9672ff371 100644
--- a/.github/workflows/docker.yml
+++ b/.github/workflows/docker.yml
@@ -177,7 +177,7 @@ jobs:
fi
- name: Build and push image
- uses: docker/build-push-action@v6.11.0
+ uses: docker/build-push-action@v6.12.0
with:
context: ${{ env.context }}
file: ${{ env.file }}
@@ -268,7 +268,7 @@ jobs:
fi
- name: Build and push image
- uses: docker/build-push-action@v6.11.0
+ uses: docker/build-push-action@v6.12.0
with:
context: ${{ env.context }}
file: ${{ env.file }}
diff --git a/cli/.nvmrc b/cli/.nvmrc
index 6fa8dec4cd..d5b283a3ac 100644
--- a/cli/.nvmrc
+++ b/cli/.nvmrc
@@ -1 +1 @@
-22.13.0
+22.13.1
diff --git a/cli/package-lock.json b/cli/package-lock.json
index ce6d14dc35..472c7b7b5f 100644
--- a/cli/package-lock.json
+++ b/cli/package-lock.json
@@ -24,7 +24,7 @@
"@types/cli-progress": "^3.11.0",
"@types/lodash-es": "^4.17.12",
"@types/mock-fs": "^4.13.1",
- "@types/node": "^22.10.5",
+ "@types/node": "^22.10.7",
"@typescript-eslint/eslint-plugin": "^8.15.0",
"@typescript-eslint/parser": "^8.15.0",
"@vitest/coverage-v8": "^2.0.5",
@@ -59,7 +59,7 @@
"@oazapfts/runtime": "^1.0.2"
},
"devDependencies": {
- "@types/node": "^22.10.5",
+ "@types/node": "^22.10.7",
"typescript": "^5.3.3"
}
},
@@ -1397,9 +1397,9 @@
}
},
"node_modules/@types/node": {
- "version": "22.10.5",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.5.tgz",
- "integrity": "sha512-F8Q+SeGimwOo86fiovQh8qiXfFEh2/ocYv7tU5pJ3EXMSSxk1Joj5wefpFK2fHTf/N6HKGSxIDBT9f3gCxXPkQ==",
+ "version": "22.10.7",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.7.tgz",
+ "integrity": "sha512-V09KvXxFiutGp6B7XkpaDXlNadZxrzajcY50EuoLIpQ6WWYCSvf19lVIazzfIzQvhUN2HjX12spLojTnhuKlGg==",
"dev": true,
"license": "MIT",
"dependencies": {
diff --git a/cli/package.json b/cli/package.json
index 740bf11fed..d2ba17ea7c 100644
--- a/cli/package.json
+++ b/cli/package.json
@@ -20,7 +20,7 @@
"@types/cli-progress": "^3.11.0",
"@types/lodash-es": "^4.17.12",
"@types/mock-fs": "^4.13.1",
- "@types/node": "^22.10.5",
+ "@types/node": "^22.10.7",
"@typescript-eslint/eslint-plugin": "^8.15.0",
"@typescript-eslint/parser": "^8.15.0",
"@vitest/coverage-v8": "^2.0.5",
@@ -67,6 +67,6 @@
"lodash-es": "^4.17.21"
},
"volta": {
- "node": "22.13.0"
+ "node": "22.13.1"
}
}
diff --git a/docs/.nvmrc b/docs/.nvmrc
index 6fa8dec4cd..d5b283a3ac 100644
--- a/docs/.nvmrc
+++ b/docs/.nvmrc
@@ -1 +1 @@
-22.13.0
+22.13.1
diff --git a/docs/docs/FAQ.mdx b/docs/docs/FAQ.mdx
index 71ddcf0d33..c605c564cd 100644
--- a/docs/docs/FAQ.mdx
+++ b/docs/docs/FAQ.mdx
@@ -160,6 +160,35 @@ For example, say you have existing transcodes with the policy "Videos higher tha
No. Our design principle is that the original assets should always be untouched.
+### How can I mount a CIFS/Samba volume within Docker?
+
+If you aren't able to or prefer not to mount Samba on the host (such as Windows environment), you can mount the volume within Docker.
+Below is an example in the `docker-compose.yml`.
+
+Change your username, password, local IP, and share name, and see below where the line `- originals:/usr/src/app/originals`,
+corrolates to the section where the volume `originals` was created. You can call this whatever you like, and map it to the docker container as you like.
+For example you could change `originals:` to `Photos:`, and change `- originals:/usr/src/app/originals` to `Photos:/usr/src/app/photos`.
+
+```diff
+...
+services:
+ immich-server:
+...
+ volumes:
+ # Do not edit the next line. If you want to change the media storage location on your system, edit the value of UPLOAD_LOCATION in the .env file
+ - ${UPLOAD_LOCATION}:/usr/src/app/upload
+ - /etc/localtime:/etc/localtime:ro
++ - originals:/usr/src/app/originals
+...
+volumes:
+ model-cache:
++ originals:
++ driver_opts:
++ type: cifs
++ o: 'iocharset=utf8,username=USERNAMEHERE,password=PASSWORDHERE,rw' # change to `ro` if read only desired
++ device: '//localipaddress/sharename'
+```
+
---
## Albums
diff --git a/docs/docs/features/monitoring.md b/docs/docs/features/monitoring.md
index c7ce817e71..64377ec073 100644
--- a/docs/docs/features/monitoring.md
+++ b/docs/docs/features/monitoring.md
@@ -68,7 +68,7 @@ After bringing down the containers with `docker compose down` and back up with `
:::note
To see exactly what metrics are made available, you can additionally add `8081:8081` to the server container's ports and `8082:8082` to the microservices container's ports.
Visiting the `/metrics` endpoint for these services will show the same raw data that Prometheus collects.
-To configure these ports see [`IMMICH_API_METRICS_PORT` & `IMMICH_MICROSERVICES_METRICS_PORT`](../install/environment-variables/#general).
+To configure these ports see [`IMMICH_API_METRICS_PORT` & `IMMICH_MICROSERVICES_METRICS_PORT`](/docs/install/environment-variables/#general).
:::
### Usage
diff --git a/docs/docs/guides/custom-locations.md b/docs/docs/guides/custom-locations.md
index 514008611d..08f75b3e9d 100644
--- a/docs/docs/guides/custom-locations.md
+++ b/docs/docs/guides/custom-locations.md
@@ -49,5 +49,3 @@ The `thumbs/` folder contains both the small thumbnails displayed in the timelin
The storage metrics of the Immich server will track available storage at `UPLOAD_LOCATION`, so the administrator must set up some sort of monitoring to ensure the storage does not run out of space. The `profile/` folder is much smaller, usually less than 1 MB.
:::
-
-Thanks to [Jrasm91](https://github.com/immich-app/immich/discussions/2110#discussioncomment-5477767) for writing the guide.
diff --git a/docs/docs/install/environment-variables.md b/docs/docs/install/environment-variables.md
index 71d3b9996c..9e8abee1a8 100644
--- a/docs/docs/install/environment-variables.md
+++ b/docs/docs/install/environment-variables.md
@@ -159,10 +159,10 @@ Redis (Sentinel) URL example JSON before encoding:
| `MACHINE_LEARNING_WORKERS`\*2 | Number of worker processes to spawn | `1` | machine learning |
| `MACHINE_LEARNING_HTTP_KEEPALIVE_TIMEOUT_S`\*3 | HTTP Keep-alive time in seconds | `2` | machine learning |
| `MACHINE_LEARNING_WORKER_TIMEOUT` | Maximum time (s) of unresponsiveness before a worker is killed | `120` (`300` if using OpenVINO) | machine learning |
-| `MACHINE_LEARNING_PRELOAD__CLIP__TEXTUAL` | Name of the textual CLIP model to be preloaded and kept in cache | | machine learning |
-| `MACHINE_LEARNING_PRELOAD__CLIP__VISUAL` | Name of the visual CLIP model to be preloaded and kept in cache | | machine learning |
-| `MACHINE_LEARNING_PRELOAD__FACIAL_RECOGNITION__RECOGNITION` | Name of the recognition portion of the facial recognition model to be preloaded and kept in cache | | machine learning |
-| `MACHINE_LEARNING_PRELOAD__FACIAL_RECOGNITION__DETECTION` | Name of the detection portion of the facial recognition model to be preloaded and kept in cache | | machine learning |
+| `MACHINE_LEARNING_PRELOAD__CLIP__TEXTUAL` | Comma-separated list of (textual) CLIP model(s) to preload and cache | | machine learning |
+| `MACHINE_LEARNING_PRELOAD__CLIP__VISUAL` | Comma-separated list of (visual) CLIP model(s) to preload and cache | | machine learning |
+| `MACHINE_LEARNING_PRELOAD__FACIAL_RECOGNITION__RECOGNITION` | Comma-separated list of (recognition) facial recognition model(s) to preload and cache | | machine learning |
+| `MACHINE_LEARNING_PRELOAD__FACIAL_RECOGNITION__DETECTION` | Comma-separated list of (detection) facial recognition model(s) to preload and cache | | machine learning |
| `MACHINE_LEARNING_ANN` | Enable ARM-NN hardware acceleration if supported | `True` | machine learning |
| `MACHINE_LEARNING_ANN_FP16_TURBO` | Execute operations in FP16 precision: increasing speed, reducing precision (applies only to ARM-NN) | `False` | machine learning |
| `MACHINE_LEARNING_ANN_TUNING_LEVEL` | ARM-NN GPU tuning level (1: rapid, 2: normal, 3: exhaustive) | `2` | machine learning |
diff --git a/docs/package.json b/docs/package.json
index a77a41656a..0574e83c6f 100644
--- a/docs/package.json
+++ b/docs/package.json
@@ -55,6 +55,6 @@
"node": ">=20"
},
"volta": {
- "node": "22.13.0"
+ "node": "22.13.1"
}
}
diff --git a/docs/src/pages/index.tsx b/docs/src/pages/index.tsx
index a5dbc7aa98..b3cf10b810 100644
--- a/docs/src/pages/index.tsx
+++ b/docs/src/pages/index.tsx
@@ -73,9 +73,9 @@ function HomepageHeader() {
/>
diff --git a/e2e/.nvmrc b/e2e/.nvmrc
index 6fa8dec4cd..d5b283a3ac 100644
--- a/e2e/.nvmrc
+++ b/e2e/.nvmrc
@@ -1 +1 @@
-22.13.0
+22.13.1
diff --git a/e2e/package-lock.json b/e2e/package-lock.json
index e8aa5f4411..7029d8eda4 100644
--- a/e2e/package-lock.json
+++ b/e2e/package-lock.json
@@ -15,7 +15,7 @@
"@immich/sdk": "file:../open-api/typescript-sdk",
"@playwright/test": "^1.44.1",
"@types/luxon": "^3.4.2",
- "@types/node": "^22.10.5",
+ "@types/node": "^22.10.7",
"@types/oidc-provider": "^8.5.1",
"@types/pg": "^8.11.0",
"@types/pngjs": "^6.0.4",
@@ -64,7 +64,7 @@
"@types/cli-progress": "^3.11.0",
"@types/lodash-es": "^4.17.12",
"@types/mock-fs": "^4.13.1",
- "@types/node": "^22.10.5",
+ "@types/node": "^22.10.7",
"@typescript-eslint/eslint-plugin": "^8.15.0",
"@typescript-eslint/parser": "^8.15.0",
"@vitest/coverage-v8": "^2.0.5",
@@ -99,7 +99,7 @@
"@oazapfts/runtime": "^1.0.2"
},
"devDependencies": {
- "@types/node": "^22.10.5",
+ "@types/node": "^22.10.7",
"typescript": "^5.3.3"
}
},
@@ -1658,9 +1658,9 @@
"dev": true
},
"node_modules/@types/node": {
- "version": "22.10.5",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.5.tgz",
- "integrity": "sha512-F8Q+SeGimwOo86fiovQh8qiXfFEh2/ocYv7tU5pJ3EXMSSxk1Joj5wefpFK2fHTf/N6HKGSxIDBT9f3gCxXPkQ==",
+ "version": "22.10.7",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.7.tgz",
+ "integrity": "sha512-V09KvXxFiutGp6B7XkpaDXlNadZxrzajcY50EuoLIpQ6WWYCSvf19lVIazzfIzQvhUN2HjX12spLojTnhuKlGg==",
"dev": true,
"license": "MIT",
"dependencies": {
diff --git a/e2e/package.json b/e2e/package.json
index 88b63d157b..7779f86467 100644
--- a/e2e/package.json
+++ b/e2e/package.json
@@ -25,7 +25,7 @@
"@immich/sdk": "file:../open-api/typescript-sdk",
"@playwright/test": "^1.44.1",
"@types/luxon": "^3.4.2",
- "@types/node": "^22.10.5",
+ "@types/node": "^22.10.7",
"@types/oidc-provider": "^8.5.1",
"@types/pg": "^8.11.0",
"@types/pngjs": "^6.0.4",
@@ -53,6 +53,6 @@
"vitest": "^2.0.5"
},
"volta": {
- "node": "22.13.0"
+ "node": "22.13.1"
}
}
diff --git a/e2e/src/api/specs/album.e2e-spec.ts b/e2e/src/api/specs/album.e2e-spec.ts
index 5c101a0793..5b40234e8d 100644
--- a/e2e/src/api/specs/album.e2e-spec.ts
+++ b/e2e/src/api/specs/album.e2e-spec.ts
@@ -142,6 +142,10 @@ describe('/albums', () => {
...user1Albums[0],
assets: [expect.objectContaining({ isFavorite: false })],
lastModifiedAssetTimestamp: expect.any(String),
+ startDate: expect.any(String),
+ endDate: expect.any(String),
+ shared: true,
+ albumUsers: expect.any(Array),
});
});
@@ -299,6 +303,10 @@ describe('/albums', () => {
...user1Albums[0],
assets: [expect.objectContaining({ id: user1Albums[0].assets[0].id })],
lastModifiedAssetTimestamp: expect.any(String),
+ startDate: expect.any(String),
+ endDate: expect.any(String),
+ albumUsers: expect.any(Array),
+ shared: true,
});
});
@@ -330,6 +338,10 @@ describe('/albums', () => {
...user1Albums[0],
assets: [expect.objectContaining({ id: user1Albums[0].assets[0].id })],
lastModifiedAssetTimestamp: expect.any(String),
+ startDate: expect.any(String),
+ endDate: expect.any(String),
+ albumUsers: expect.any(Array),
+ shared: true,
});
});
@@ -344,6 +356,10 @@ describe('/albums', () => {
assets: [],
assetCount: 1,
lastModifiedAssetTimestamp: expect.any(String),
+ endDate: expect.any(String),
+ startDate: expect.any(String),
+ albumUsers: expect.any(Array),
+ shared: true,
});
});
});
diff --git a/e2e/src/api/specs/person.e2e-spec.ts b/e2e/src/api/specs/person.e2e-spec.ts
index d6ccf8265f..bb838bbae3 100644
--- a/e2e/src/api/specs/person.e2e-spec.ts
+++ b/e2e/src/api/specs/person.e2e-spec.ts
@@ -200,7 +200,7 @@ describe('/people', () => {
expect(body).toMatchObject({
id: expect.any(String),
name: 'New Person',
- birthDate: '1990-01-01',
+ birthDate: '1990-01-01T00:00:00.000Z',
});
});
});
@@ -244,7 +244,7 @@ describe('/people', () => {
.set('Authorization', `Bearer ${admin.accessToken}`)
.send({ birthDate: '1990-01-01' });
expect(status).toBe(200);
- expect(body).toMatchObject({ birthDate: '1990-01-01' });
+ expect(body).toMatchObject({ birthDate: '1990-01-01T00:00:00.000Z' });
});
it('should clear a date of birth', async () => {
diff --git a/machine-learning/Dockerfile b/machine-learning/Dockerfile
index 2b238555c0..3aaa48f864 100644
--- a/machine-learning/Dockerfile
+++ b/machine-learning/Dockerfile
@@ -1,6 +1,6 @@
ARG DEVICE=cpu
-FROM python:3.11-bookworm@sha256:f997d3f71b7dcff3f937703c02861437f2b41a94e1ddbd1b5fa357ee99f5cce4 AS builder-cpu
+FROM python:3.11-bookworm@sha256:adb581d8ed80edd03efd4dcad66db115b9ce8de8522b01720b9f3e6146f0884c AS builder-cpu
FROM builder-cpu AS builder-openvino
diff --git a/machine-learning/app/main.py b/machine-learning/app/main.py
index fb6f84499a..0f50257a4e 100644
--- a/machine-learning/app/main.py
+++ b/machine-learning/app/main.py
@@ -77,29 +77,31 @@ async def lifespan(_: FastAPI) -> AsyncGenerator[None, None]:
async def preload_models(preload: PreloadModelData) -> None:
log.info(f"Preloading models: clip:{preload.clip} facial_recognition:{preload.facial_recognition}")
+ async def load_models(model_string: str, model_type: ModelType, model_task: ModelTask) -> None:
+ for model_name in model_string.split(","):
+ model_name = model_name.strip()
+ model = await model_cache.get(model_name, model_type, model_task)
+ await load(model)
+
if preload.clip.textual is not None:
- model = await model_cache.get(preload.clip.textual, ModelType.TEXTUAL, ModelTask.SEARCH)
- await load(model)
+ await load_models(preload.clip.textual, ModelType.TEXTUAL, ModelTask.SEARCH)
if preload.clip.visual is not None:
- model = await model_cache.get(preload.clip.visual, ModelType.VISUAL, ModelTask.SEARCH)
- await load(model)
+ await load_models(preload.clip.visual, ModelType.VISUAL, ModelTask.SEARCH)
if preload.facial_recognition.detection is not None:
- model = await model_cache.get(
+ await load_models(
preload.facial_recognition.detection,
ModelType.DETECTION,
ModelTask.FACIAL_RECOGNITION,
)
- await load(model)
if preload.facial_recognition.recognition is not None:
- model = await model_cache.get(
+ await load_models(
preload.facial_recognition.recognition,
ModelType.RECOGNITION,
ModelTask.FACIAL_RECOGNITION,
)
- await load(model)
if preload.clip_fallback is not None:
log.warning(
diff --git a/machine-learning/app/models/clip/textual.py b/machine-learning/app/models/clip/textual.py
index 32c28ea2bb..d338f29296 100644
--- a/machine-learning/app/models/clip/textual.py
+++ b/machine-learning/app/models/clip/textual.py
@@ -10,7 +10,7 @@ from tokenizers import Encoding, Tokenizer
from app.config import log
from app.models.base import InferenceModel
-from app.models.transforms import clean_text
+from app.models.transforms import clean_text, serialize_np_array
from app.schemas import ModelSession, ModelTask, ModelType
@@ -18,9 +18,9 @@ class BaseCLIPTextualEncoder(InferenceModel):
depends = []
identity = (ModelType.TEXTUAL, ModelTask.SEARCH)
- def _predict(self, inputs: str, **kwargs: Any) -> NDArray[np.float32]:
+ def _predict(self, inputs: str, **kwargs: Any) -> str:
res: NDArray[np.float32] = self.session.run(None, self.tokenize(inputs))[0][0]
- return res
+ return serialize_np_array(res)
def _load(self) -> ModelSession:
session = super()._load()
diff --git a/machine-learning/app/models/clip/visual.py b/machine-learning/app/models/clip/visual.py
index 48058c961a..64be8e0657 100644
--- a/machine-learning/app/models/clip/visual.py
+++ b/machine-learning/app/models/clip/visual.py
@@ -10,7 +10,15 @@ from PIL import Image
from app.config import log
from app.models.base import InferenceModel
-from app.models.transforms import crop_pil, decode_pil, get_pil_resampling, normalize, resize_pil, to_numpy
+from app.models.transforms import (
+ crop_pil,
+ decode_pil,
+ get_pil_resampling,
+ normalize,
+ resize_pil,
+ serialize_np_array,
+ to_numpy,
+)
from app.schemas import ModelSession, ModelTask, ModelType
@@ -18,10 +26,10 @@ class BaseCLIPVisualEncoder(InferenceModel):
depends = []
identity = (ModelType.VISUAL, ModelTask.SEARCH)
- def _predict(self, inputs: Image.Image | bytes, **kwargs: Any) -> NDArray[np.float32]:
+ def _predict(self, inputs: Image.Image | bytes, **kwargs: Any) -> str:
image = decode_pil(inputs)
res: NDArray[np.float32] = self.session.run(None, self.transform(image))[0][0]
- return res
+ return serialize_np_array(res)
@abstractmethod
def transform(self, image: Image.Image) -> dict[str, NDArray[np.float32]]:
diff --git a/machine-learning/app/models/facial_recognition/recognition.py b/machine-learning/app/models/facial_recognition/recognition.py
index dcfb6b530e..044f19b06f 100644
--- a/machine-learning/app/models/facial_recognition/recognition.py
+++ b/machine-learning/app/models/facial_recognition/recognition.py
@@ -12,7 +12,7 @@ from PIL import Image
from app.config import log, settings
from app.models.base import InferenceModel
-from app.models.transforms import decode_cv2
+from app.models.transforms import decode_cv2, serialize_np_array
from app.schemas import FaceDetectionOutput, FacialRecognitionOutput, ModelFormat, ModelSession, ModelTask, ModelType
@@ -61,7 +61,7 @@ class FaceRecognizer(InferenceModel):
return [
{
"boundingBox": {"x1": x1, "y1": y1, "x2": x2, "y2": y2},
- "embedding": embedding,
+ "embedding": serialize_np_array(embedding),
"score": score,
}
for (x1, y1, x2, y2), embedding, score in zip(faces["boxes"], embeddings, faces["scores"])
diff --git a/machine-learning/app/models/transforms.py b/machine-learning/app/models/transforms.py
index bb03103d4b..e70763a07f 100644
--- a/machine-learning/app/models/transforms.py
+++ b/machine-learning/app/models/transforms.py
@@ -4,6 +4,7 @@ from typing import IO
import cv2
import numpy as np
+import orjson
from numpy.typing import NDArray
from PIL import Image
@@ -69,3 +70,9 @@ def clean_text(text: str, canonicalize: bool = False) -> str:
if canonicalize:
text = text.translate(_PUNCTUATION_TRANS).lower()
return text
+
+
+# this allows the client to use the array as a string without deserializing only to serialize back to a string
+# TODO: use this in a less invasive way
+def serialize_np_array(arr: NDArray[np.float32]) -> str:
+ return orjson.dumps(arr, option=orjson.OPT_SERIALIZE_NUMPY).decode()
diff --git a/machine-learning/app/schemas.py b/machine-learning/app/schemas.py
index 8f019e178d..6f9076807d 100644
--- a/machine-learning/app/schemas.py
+++ b/machine-learning/app/schemas.py
@@ -80,7 +80,7 @@ class FaceDetectionOutput(TypedDict):
class DetectedFace(TypedDict):
boundingBox: BoundingBox
- embedding: npt.NDArray[np.float32]
+ embedding: str
score: float
diff --git a/machine-learning/app/test_main.py b/machine-learning/app/test_main.py
index 29a5b50e1f..74bd8aa324 100644
--- a/machine-learning/app/test_main.py
+++ b/machine-learning/app/test_main.py
@@ -10,6 +10,7 @@ from unittest import mock
import cv2
import numpy as np
import onnxruntime as ort
+import orjson
import pytest
from fastapi import HTTPException
from fastapi.testclient import TestClient
@@ -396,11 +397,11 @@ class TestCLIP:
mocked.run.return_value = [[self.embedding]]
clip_encoder = OpenClipVisualEncoder("ViT-B-32__openai", cache_dir="test_cache")
- embedding = clip_encoder.predict(pil_image)
-
- assert isinstance(embedding, np.ndarray)
- assert embedding.shape[0] == clip_model_cfg["embed_dim"]
- assert embedding.dtype == np.float32
+ embedding_str = clip_encoder.predict(pil_image)
+ assert isinstance(embedding_str, str)
+ embedding = orjson.loads(embedding_str)
+ assert isinstance(embedding, list)
+ assert len(embedding) == clip_model_cfg["embed_dim"]
mocked.run.assert_called_once()
def test_basic_text(
@@ -418,11 +419,11 @@ class TestCLIP:
mocker.patch("app.models.clip.textual.Tokenizer.from_file", autospec=True)
clip_encoder = OpenClipTextualEncoder("ViT-B-32__openai", cache_dir="test_cache")
- embedding = clip_encoder.predict("test search query")
-
- assert isinstance(embedding, np.ndarray)
- assert embedding.shape[0] == clip_model_cfg["embed_dim"]
- assert embedding.dtype == np.float32
+ embedding_str = clip_encoder.predict("test search query")
+ assert isinstance(embedding_str, str)
+ embedding = orjson.loads(embedding_str)
+ assert isinstance(embedding, list)
+ assert len(embedding) == clip_model_cfg["embed_dim"]
mocked.run.assert_called_once()
def test_openclip_tokenizer(
@@ -558,8 +559,11 @@ class TestFaceRecognition:
assert isinstance(face.get("boundingBox"), dict)
assert set(face["boundingBox"]) == {"x1", "y1", "x2", "y2"}
assert all(isinstance(val, np.float32) for val in face["boundingBox"].values())
- assert isinstance(face.get("embedding"), np.ndarray)
- assert face["embedding"].shape[0] == 512
+ embedding_str = face.get("embedding")
+ assert isinstance(embedding_str, str)
+ embedding = orjson.loads(embedding_str)
+ assert isinstance(embedding, list)
+ assert len(embedding) == 512
assert isinstance(face.get("score", None), np.float32)
rec_model.get_feat.assert_called_once()
@@ -930,8 +934,10 @@ class TestPredictionEndpoints:
actual = response.json()
assert response.status_code == 200
assert isinstance(actual, dict)
- assert isinstance(actual.get("clip", None), list)
- assert np.allclose(expected, actual["clip"])
+ embedding = actual.get("clip", None)
+ assert isinstance(embedding, str)
+ parsed_embedding = orjson.loads(embedding)
+ assert np.allclose(expected, parsed_embedding)
def test_clip_text_endpoint(self, responses: dict[str, Any], deployed_app: TestClient) -> None:
expected = responses["clip"]["text"]
@@ -951,8 +957,10 @@ class TestPredictionEndpoints:
actual = response.json()
assert response.status_code == 200
assert isinstance(actual, dict)
- assert isinstance(actual.get("clip", None), list)
- assert np.allclose(expected, actual["clip"])
+ embedding = actual.get("clip", None)
+ assert isinstance(embedding, str)
+ parsed_embedding = orjson.loads(embedding)
+ assert np.allclose(expected, parsed_embedding)
def test_face_endpoint(self, pil_image: Image.Image, responses: dict[str, Any], deployed_app: TestClient) -> None:
byte_image = BytesIO()
@@ -983,5 +991,8 @@ class TestPredictionEndpoints:
for expected_face, actual_face in zip(responses["facial-recognition"], actual["facial-recognition"]):
assert expected_face["boundingBox"] == actual_face["boundingBox"]
- assert np.allclose(expected_face["embedding"], actual_face["embedding"])
+ embedding = actual_face.get("embedding", None)
+ assert isinstance(embedding, str)
+ parsed_embedding = orjson.loads(embedding)
+ assert np.allclose(expected_face["embedding"], parsed_embedding)
assert np.allclose(expected_face["score"], actual_face["score"])
diff --git a/machine-learning/poetry.lock b/machine-learning/poetry.lock
index dd25d59cf7..732411534f 100644
--- a/machine-learning/poetry.lock
+++ b/machine-learning/poetry.lock
@@ -1625,13 +1625,13 @@ test = ["pytest (>=7.4)", "pytest-cov (>=4.1)"]
[[package]]
name = "locust"
-version = "2.32.5"
+version = "2.32.6"
description = "Developer-friendly load testing framework"
optional = false
python-versions = ">=3.9"
files = [
- {file = "locust-2.32.5-py3-none-any.whl", hash = "sha256:2f49509868ffc2e368be40921c6825f92147c84e997206760a85dab3058f5efb"},
- {file = "locust-2.32.5.tar.gz", hash = "sha256:ea7bc1e8ce2520e8893c471b4b0a56a4f53b01b4b618adfe8d2c8ab2728b5821"},
+ {file = "locust-2.32.6-py3-none-any.whl", hash = "sha256:d5c0e4f73134415d250087034431cf3ea42ca695d3dee7f10812287cacb6c4ef"},
+ {file = "locust-2.32.6.tar.gz", hash = "sha256:6600cc308398e724764aacc56ccddf6cfcd0127c4c92dedd5c4979dd37ef5b15"},
]
[package.dependencies]
@@ -1649,8 +1649,8 @@ psutil = ">=5.9.1"
pywin32 = {version = "*", markers = "sys_platform == \"win32\""}
pyzmq = ">=25.0.0"
requests = [
- {version = ">=2.26.0", markers = "python_full_version <= \"3.11.0\""},
{version = ">=2.32.2", markers = "python_full_version > \"3.11.0\""},
+ {version = ">=2.26.0", markers = "python_full_version <= \"3.11.0\""},
]
setuptools = ">=70.0.0"
tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""}
@@ -2165,26 +2165,26 @@ sympy = "*"
[[package]]
name = "opencv-python-headless"
-version = "4.10.0.84"
+version = "4.11.0.86"
description = "Wrapper package for OpenCV python bindings."
optional = false
python-versions = ">=3.6"
files = [
- {file = "opencv-python-headless-4.10.0.84.tar.gz", hash = "sha256:f2017c6101d7c2ef8d7bc3b414c37ff7f54d64413a1847d89970b6b7069b4e1a"},
- {file = "opencv_python_headless-4.10.0.84-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:a4f4bcb07d8f8a7704d9c8564c224c8b064c63f430e95b61ac0bffaa374d330e"},
- {file = "opencv_python_headless-4.10.0.84-cp37-abi3-macosx_12_0_x86_64.whl", hash = "sha256:5ae454ebac0eb0a0b932e3406370aaf4212e6a3fdb5038cc86c7aea15a6851da"},
- {file = "opencv_python_headless-4.10.0.84-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:46071015ff9ab40fccd8a163da0ee14ce9846349f06c6c8c0f2870856ffa45db"},
- {file = "opencv_python_headless-4.10.0.84-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:377d08a7e48a1405b5e84afcbe4798464ce7ee17081c1c23619c8b398ff18295"},
- {file = "opencv_python_headless-4.10.0.84-cp37-abi3-win32.whl", hash = "sha256:9092404b65458ed87ce932f613ffbb1106ed2c843577501e5768912360fc50ec"},
- {file = "opencv_python_headless-4.10.0.84-cp37-abi3-win_amd64.whl", hash = "sha256:afcf28bd1209dd58810d33defb622b325d3cbe49dcd7a43a902982c33e5fad05"},
+ {file = "opencv-python-headless-4.11.0.86.tar.gz", hash = "sha256:996eb282ca4b43ec6a3972414de0e2331f5d9cda2b41091a49739c19fb843798"},
+ {file = "opencv_python_headless-4.11.0.86-cp37-abi3-macosx_13_0_arm64.whl", hash = "sha256:48128188ade4a7e517237c8e1e11a9cdf5c282761473383e77beb875bb1e61ca"},
+ {file = "opencv_python_headless-4.11.0.86-cp37-abi3-macosx_13_0_x86_64.whl", hash = "sha256:a66c1b286a9de872c343ee7c3553b084244299714ebb50fbdcd76f07ebbe6c81"},
+ {file = "opencv_python_headless-4.11.0.86-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6efabcaa9df731f29e5ea9051776715b1bdd1845d7c9530065c7951d2a2899eb"},
+ {file = "opencv_python_headless-4.11.0.86-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e0a27c19dd1f40ddff94976cfe43066fbbe9dfbb2ec1907d66c19caef42a57b"},
+ {file = "opencv_python_headless-4.11.0.86-cp37-abi3-win32.whl", hash = "sha256:f447d8acbb0b6f2808da71fddd29c1cdd448d2bc98f72d9bb78a7a898fc9621b"},
+ {file = "opencv_python_headless-4.11.0.86-cp37-abi3-win_amd64.whl", hash = "sha256:6c304df9caa7a6a5710b91709dd4786bf20a74d57672b3c31f7033cc638174ca"},
]
[package.dependencies]
numpy = [
+ {version = ">=1.26.0", markers = "python_version >= \"3.12\""},
{version = ">=1.23.5", markers = "python_version >= \"3.11\" and python_version < \"3.12\""},
{version = ">=1.21.4", markers = "python_version >= \"3.10\" and platform_system == \"Darwin\" and python_version < \"3.11\""},
{version = ">=1.21.2", markers = "platform_system != \"Darwin\" and python_version >= \"3.10\" and python_version < \"3.11\""},
- {version = ">=1.26.0", markers = "python_version >= \"3.12\""},
]
[[package]]
@@ -3137,29 +3137,29 @@ files = [
[[package]]
name = "ruff"
-version = "0.9.1"
+version = "0.9.2"
description = "An extremely fast Python linter and code formatter, written in Rust."
optional = false
python-versions = ">=3.7"
files = [
- {file = "ruff-0.9.1-py3-none-linux_armv6l.whl", hash = "sha256:84330dda7abcc270e6055551aca93fdde1b0685fc4fd358f26410f9349cf1743"},
- {file = "ruff-0.9.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:3cae39ba5d137054b0e5b472aee3b78a7c884e61591b100aeb544bcd1fc38d4f"},
- {file = "ruff-0.9.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:50c647ff96f4ba288db0ad87048257753733763b409b2faf2ea78b45c8bb7fcb"},
- {file = "ruff-0.9.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f0c8b149e9c7353cace7d698e1656ffcf1e36e50f8ea3b5d5f7f87ff9986a7ca"},
- {file = "ruff-0.9.1-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:beb3298604540c884d8b282fe7625651378e1986c25df51dec5b2f60cafc31ce"},
- {file = "ruff-0.9.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:39d0174ccc45c439093971cc06ed3ac4dc545f5e8bdacf9f067adf879544d969"},
- {file = "ruff-0.9.1-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:69572926c0f0c9912288915214ca9b2809525ea263603370b9e00bed2ba56dbd"},
- {file = "ruff-0.9.1-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:937267afce0c9170d6d29f01fcd1f4378172dec6760a9f4dface48cdabf9610a"},
- {file = "ruff-0.9.1-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:186c2313de946f2c22bdf5954b8dd083e124bcfb685732cfb0beae0c47233d9b"},
- {file = "ruff-0.9.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f94942a3bb767675d9a051867c036655fe9f6c8a491539156a6f7e6b5f31831"},
- {file = "ruff-0.9.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:728d791b769cc28c05f12c280f99e8896932e9833fef1dd8756a6af2261fd1ab"},
- {file = "ruff-0.9.1-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:2f312c86fb40c5c02b44a29a750ee3b21002bd813b5233facdaf63a51d9a85e1"},
- {file = "ruff-0.9.1-py3-none-musllinux_1_2_i686.whl", hash = "sha256:ae017c3a29bee341ba584f3823f805abbe5fe9cd97f87ed07ecbf533c4c88366"},
- {file = "ruff-0.9.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:5dc40a378a0e21b4cfe2b8a0f1812a6572fc7b230ef12cd9fac9161aa91d807f"},
- {file = "ruff-0.9.1-py3-none-win32.whl", hash = "sha256:46ebf5cc106cf7e7378ca3c28ce4293b61b449cd121b98699be727d40b79ba72"},
- {file = "ruff-0.9.1-py3-none-win_amd64.whl", hash = "sha256:342a824b46ddbcdddd3abfbb332fa7fcaac5488bf18073e841236aadf4ad5c19"},
- {file = "ruff-0.9.1-py3-none-win_arm64.whl", hash = "sha256:1cd76c7f9c679e6e8f2af8f778367dca82b95009bc7b1a85a47f1521ae524fa7"},
- {file = "ruff-0.9.1.tar.gz", hash = "sha256:fd2b25ecaf907d6458fa842675382c8597b3c746a2dde6717fe3415425df0c17"},
+ {file = "ruff-0.9.2-py3-none-linux_armv6l.whl", hash = "sha256:80605a039ba1454d002b32139e4970becf84b5fee3a3c3bf1c2af6f61a784347"},
+ {file = "ruff-0.9.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b9aab82bb20afd5f596527045c01e6ae25a718ff1784cb92947bff1f83068b00"},
+ {file = "ruff-0.9.2-py3-none-macosx_11_0_arm64.whl", hash = "sha256:fbd337bac1cfa96be615f6efcd4bc4d077edbc127ef30e2b8ba2a27e18c054d4"},
+ {file = "ruff-0.9.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:82b35259b0cbf8daa22a498018e300b9bb0174c2bbb7bcba593935158a78054d"},
+ {file = "ruff-0.9.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8b6a9701d1e371bf41dca22015c3f89769da7576884d2add7317ec1ec8cb9c3c"},
+ {file = "ruff-0.9.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9cc53e68b3c5ae41e8faf83a3b89f4a5d7b2cb666dff4b366bb86ed2a85b481f"},
+ {file = "ruff-0.9.2-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:8efd9da7a1ee314b910da155ca7e8953094a7c10d0c0a39bfde3fcfd2a015684"},
+ {file = "ruff-0.9.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3292c5a22ea9a5f9a185e2d131dc7f98f8534a32fb6d2ee7b9944569239c648d"},
+ {file = "ruff-0.9.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1a605fdcf6e8b2d39f9436d343d1f0ff70c365a1e681546de0104bef81ce88df"},
+ {file = "ruff-0.9.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c547f7f256aa366834829a08375c297fa63386cbe5f1459efaf174086b564247"},
+ {file = "ruff-0.9.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:d18bba3d3353ed916e882521bc3e0af403949dbada344c20c16ea78f47af965e"},
+ {file = "ruff-0.9.2-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:b338edc4610142355ccf6b87bd356729b62bf1bc152a2fad5b0c7dc04af77bfe"},
+ {file = "ruff-0.9.2-py3-none-musllinux_1_2_i686.whl", hash = "sha256:492a5e44ad9b22a0ea98cf72e40305cbdaf27fac0d927f8bc9e1df316dcc96eb"},
+ {file = "ruff-0.9.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:af1e9e9fe7b1f767264d26b1075ac4ad831c7db976911fa362d09b2d0356426a"},
+ {file = "ruff-0.9.2-py3-none-win32.whl", hash = "sha256:71cbe22e178c5da20e1514e1e01029c73dc09288a8028a5d3446e6bba87a5145"},
+ {file = "ruff-0.9.2-py3-none-win_amd64.whl", hash = "sha256:c5e1d6abc798419cf46eed03f54f2e0c3adb1ad4b801119dedf23fcaf69b55b5"},
+ {file = "ruff-0.9.2-py3-none-win_arm64.whl", hash = "sha256:a1b63fa24149918f8b37cef2ee6fff81f24f0d74b6f0bdc37bc3e1f2143e41c6"},
+ {file = "ruff-0.9.2.tar.gz", hash = "sha256:b5eceb334d55fae5f316f783437392642ae18e16dcf4f1858d55d3c2a0f8f5d0"},
]
[[package]]
diff --git a/mobile/lib/repositories/album_media.repository.dart b/mobile/lib/repositories/album_media.repository.dart
index dac9ccd4da..c3795f75df 100644
--- a/mobile/lib/repositories/album_media.repository.dart
+++ b/mobile/lib/repositories/album_media.repository.dart
@@ -14,6 +14,7 @@ class AlbumMediaRepository implements IAlbumMediaRepository {
final List
assetPathEntities =
await PhotoManager.getAssetPathList(
hasAll: true,
+ filterOption: FilterOptionGroup(containsPathModified: true),
);
return assetPathEntities.map(_toAlbum).toList();
}
diff --git a/open-api/typescript-sdk/.nvmrc b/open-api/typescript-sdk/.nvmrc
index 6fa8dec4cd..d5b283a3ac 100644
--- a/open-api/typescript-sdk/.nvmrc
+++ b/open-api/typescript-sdk/.nvmrc
@@ -1 +1 @@
-22.13.0
+22.13.1
diff --git a/open-api/typescript-sdk/package-lock.json b/open-api/typescript-sdk/package-lock.json
index ddcc46658a..1dee25f3f8 100644
--- a/open-api/typescript-sdk/package-lock.json
+++ b/open-api/typescript-sdk/package-lock.json
@@ -12,7 +12,7 @@
"@oazapfts/runtime": "^1.0.2"
},
"devDependencies": {
- "@types/node": "^22.10.5",
+ "@types/node": "^22.10.7",
"typescript": "^5.3.3"
}
},
@@ -22,9 +22,9 @@
"integrity": "sha512-8tKiYffhwTGHSHYGnZ3oneLGCjX0po/XAXQ5Ng9fqKkvIdl/xz8+Vh8i+6xjzZqvZ2pLVpUcuSfnvNI/x67L0g=="
},
"node_modules/@types/node": {
- "version": "22.10.5",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.5.tgz",
- "integrity": "sha512-F8Q+SeGimwOo86fiovQh8qiXfFEh2/ocYv7tU5pJ3EXMSSxk1Joj5wefpFK2fHTf/N6HKGSxIDBT9f3gCxXPkQ==",
+ "version": "22.10.7",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.7.tgz",
+ "integrity": "sha512-V09KvXxFiutGp6B7XkpaDXlNadZxrzajcY50EuoLIpQ6WWYCSvf19lVIazzfIzQvhUN2HjX12spLojTnhuKlGg==",
"dev": true,
"license": "MIT",
"dependencies": {
diff --git a/open-api/typescript-sdk/package.json b/open-api/typescript-sdk/package.json
index 5f9603554c..91c3c22e14 100644
--- a/open-api/typescript-sdk/package.json
+++ b/open-api/typescript-sdk/package.json
@@ -19,7 +19,7 @@
"@oazapfts/runtime": "^1.0.2"
},
"devDependencies": {
- "@types/node": "^22.10.5",
+ "@types/node": "^22.10.7",
"typescript": "^5.3.3"
},
"repository": {
@@ -28,6 +28,6 @@
"directory": "open-api/typescript-sdk"
},
"volta": {
- "node": "22.13.0"
+ "node": "22.13.1"
}
}
diff --git a/server/.nvmrc b/server/.nvmrc
index 6fa8dec4cd..d5b283a3ac 100644
--- a/server/.nvmrc
+++ b/server/.nvmrc
@@ -1 +1 @@
-22.13.0
+22.13.1
diff --git a/server/package-lock.json b/server/package-lock.json
index bb00426c0f..f8eca4a70c 100644
--- a/server/package-lock.json
+++ b/server/package-lock.json
@@ -86,7 +86,7 @@
"@types/lodash": "^4.14.197",
"@types/mock-fs": "^4.13.1",
"@types/multer": "^1.4.7",
- "@types/node": "^22.10.5",
+ "@types/node": "^22.10.7",
"@types/nodemailer": "^6.4.14",
"@types/picomatch": "^3.0.0",
"@types/pngjs": "^6.0.5",
@@ -104,6 +104,7 @@
"globals": "^15.9.0",
"kysely-codegen": "^0.16.3",
"mock-fs": "^5.2.0",
+ "node-addon-api": "^8.3.0",
"pngjs": "^7.0.0",
"prettier": "^3.0.2",
"prettier-plugin-organize-imports": "^4.0.0",
@@ -5128,9 +5129,9 @@
}
},
"node_modules/@types/node": {
- "version": "22.10.5",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.5.tgz",
- "integrity": "sha512-F8Q+SeGimwOo86fiovQh8qiXfFEh2/ocYv7tU5pJ3EXMSSxk1Joj5wefpFK2fHTf/N6HKGSxIDBT9f3gCxXPkQ==",
+ "version": "22.10.7",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.7.tgz",
+ "integrity": "sha512-V09KvXxFiutGp6B7XkpaDXlNadZxrzajcY50EuoLIpQ6WWYCSvf19lVIazzfIzQvhUN2HjX12spLojTnhuKlGg==",
"license": "MIT",
"dependencies": {
"undici-types": "~6.20.0"
@@ -6436,6 +6437,12 @@
"tweetnacl": "^0.14.3"
}
},
+ "node_modules/bcrypt/node_modules/node-addon-api": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz",
+ "integrity": "sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==",
+ "license": "MIT"
+ },
"node_modules/bignumber.js": {
"version": "9.1.2",
"resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.2.tgz",
@@ -11128,10 +11135,14 @@
"license": "MIT"
},
"node_modules/node-addon-api": {
- "version": "5.1.0",
- "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz",
- "integrity": "sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==",
- "license": "MIT"
+ "version": "8.3.0",
+ "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.3.0.tgz",
+ "integrity": "sha512-8VOpLHFrOQlAH+qA0ZzuGRlALRA6/LVh8QJldbrC4DY0hXoMP0l4Acq8TzFC018HztWiRqyCEj2aTWY2UvnJUg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^18 || ^20 || >= 21"
+ }
},
"node_modules/node-emoji": {
"version": "1.11.0",
@@ -16205,44 +16216,6 @@
"engines": {
"node": ">= 14"
}
- },
- "node_modules/zip-stream/node_modules/buffer": {
- "version": "6.0.3",
- "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
- "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/feross"
- },
- {
- "type": "patreon",
- "url": "https://www.patreon.com/feross"
- },
- {
- "type": "consulting",
- "url": "https://feross.org/support"
- }
- ],
- "dependencies": {
- "base64-js": "^1.3.1",
- "ieee754": "^1.2.1"
- }
- },
- "node_modules/zip-stream/node_modules/readable-stream": {
- "version": "4.5.2",
- "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.5.2.tgz",
- "integrity": "sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==",
- "dependencies": {
- "abort-controller": "^3.0.0",
- "buffer": "^6.0.3",
- "events": "^3.3.0",
- "process": "^0.11.10",
- "string_decoder": "^1.3.0"
- },
- "engines": {
- "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
- }
}
}
}
diff --git a/server/package.json b/server/package.json
index 98226b20d9..981c031386 100644
--- a/server/package.json
+++ b/server/package.json
@@ -112,7 +112,7 @@
"@types/lodash": "^4.14.197",
"@types/mock-fs": "^4.13.1",
"@types/multer": "^1.4.7",
- "@types/node": "^22.10.5",
+ "@types/node": "^22.10.7",
"@types/nodemailer": "^6.4.14",
"@types/picomatch": "^3.0.0",
"@types/pngjs": "^6.0.5",
@@ -130,6 +130,7 @@
"globals": "^15.9.0",
"kysely-codegen": "^0.16.3",
"mock-fs": "^5.2.0",
+ "node-addon-api": "^8.3.0",
"pngjs": "^7.0.0",
"prettier": "^3.0.2",
"prettier-plugin-organize-imports": "^4.0.0",
@@ -144,6 +145,6 @@
"vitest": "^2.0.5"
},
"volta": {
- "node": "22.13.0"
+ "node": "22.13.1"
}
}
diff --git a/server/src/cores/storage.core.ts b/server/src/cores/storage.core.ts
index d26829d633..7285ff2163 100644
--- a/server/src/cores/storage.core.ts
+++ b/server/src/cores/storage.core.ts
@@ -5,13 +5,13 @@ import { AssetEntity } from 'src/entities/asset.entity';
import { PersonEntity } from 'src/entities/person.entity';
import { AssetFileType, AssetPathType, ImageFormat, PathType, PersonPathType, StorageFolder } from 'src/enum';
import { IAssetRepository } from 'src/interfaces/asset.interface';
-import { IConfigRepository } from 'src/interfaces/config.interface';
import { ICryptoRepository } from 'src/interfaces/crypto.interface';
import { ILoggerRepository } from 'src/interfaces/logger.interface';
import { IMoveRepository } from 'src/interfaces/move.interface';
import { IPersonRepository } from 'src/interfaces/person.interface';
import { IStorageRepository } from 'src/interfaces/storage.interface';
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
+import { IConfigRepository } from 'src/types';
import { getAssetFiles } from 'src/utils/asset.util';
import { getConfig } from 'src/utils/config';
diff --git a/server/src/decorators.ts b/server/src/decorators.ts
index 047b9ec4a7..bb037ee097 100644
--- a/server/src/decorators.ts
+++ b/server/src/decorators.ts
@@ -100,6 +100,7 @@ export const DummyValue = {
DATE: new Date(),
TIME_BUCKET: '2024-01-01T00:00:00.000Z',
BOOLEAN: true,
+ VECTOR: '[1, 2, 3]',
};
export const GENERATE_SQL_KEY = 'generate-sql-key';
diff --git a/server/src/dtos/album.dto.ts b/server/src/dtos/album.dto.ts
index 76f4fdfc98..2f99b958c4 100644
--- a/server/src/dtos/album.dto.ts
+++ b/server/src/dtos/album.dto.ts
@@ -29,7 +29,7 @@ export class AddUsersDto {
albumUsers!: AlbumUserAddDto[];
}
-class AlbumUserCreateDto {
+export class AlbumUserCreateDto {
@ValidateUUID()
userId!: string;
diff --git a/server/src/entities/asset.entity.ts b/server/src/entities/asset.entity.ts
index 401f599d6f..69d4345f44 100644
--- a/server/src/entities/asset.entity.ts
+++ b/server/src/entities/asset.entity.ts
@@ -248,7 +248,7 @@ export function hasPeopleCte(db: Kysely, personIds: string[]) {
.select('assetId')
.where('personId', '=', anyUuid(personIds!))
.groupBy('assetId')
- .having((eb) => eb.fn.count('personId'), '>=', personIds.length),
+ .having((eb) => eb.fn.count('personId').distinct(), '=', personIds.length),
);
}
diff --git a/server/src/entities/face-search.entity.ts b/server/src/entities/face-search.entity.ts
index 2887453862..e907ba6c9e 100644
--- a/server/src/entities/face-search.entity.ts
+++ b/server/src/entities/face-search.entity.ts
@@ -11,10 +11,6 @@ export class FaceSearchEntity {
faceId!: string;
@Index('face_index', { synchronize: false })
- @Column({
- type: 'float4',
- array: true,
- transformer: { from: JSON.parse, to: (v) => `[${v}]` },
- })
- embedding!: number[];
+ @Column({ type: 'float4', array: true })
+ embedding!: string;
}
diff --git a/server/src/entities/smart-search.entity.ts b/server/src/entities/smart-search.entity.ts
index 66017152ea..42245a17fb 100644
--- a/server/src/entities/smart-search.entity.ts
+++ b/server/src/entities/smart-search.entity.ts
@@ -11,6 +11,6 @@ export class SmartSearchEntity {
assetId!: string;
@Index('clip_index', { synchronize: false })
- @Column({ type: 'float4', array: true, transformer: { from: JSON.parse, to: (v) => v } })
- embedding!: number[];
+ @Column({ type: 'float4', array: true })
+ embedding!: string;
}
diff --git a/server/src/entities/system-metadata.entity.ts b/server/src/entities/system-metadata.entity.ts
index 0a03a55403..678b8f701a 100644
--- a/server/src/entities/system-metadata.entity.ts
+++ b/server/src/entities/system-metadata.entity.ts
@@ -1,6 +1,7 @@
import { SystemConfig } from 'src/config';
import { StorageFolder, SystemMetadataKey } from 'src/enum';
-import { Column, DeepPartial, Entity, PrimaryColumn } from 'typeorm';
+import { DeepPartial } from 'src/types';
+import { Column, Entity, PrimaryColumn } from 'typeorm';
@Entity('system_metadata')
export class SystemMetadataEntity {
diff --git a/server/src/entities/user-metadata.entity.ts b/server/src/entities/user-metadata.entity.ts
index c342cb71f8..2c901426c3 100644
--- a/server/src/entities/user-metadata.entity.ts
+++ b/server/src/entities/user-metadata.entity.ts
@@ -1,7 +1,8 @@
import { UserEntity } from 'src/entities/user.entity';
import { UserAvatarColor, UserMetadataKey } from 'src/enum';
+import { DeepPartial } from 'src/types';
import { HumanReadableSize } from 'src/utils/bytes';
-import { Column, DeepPartial, Entity, ManyToOne, PrimaryColumn } from 'typeorm';
+import { Column, Entity, ManyToOne, PrimaryColumn } from 'typeorm';
@Entity('user_metadata')
export class UserMetadataEntity {
diff --git a/server/src/interfaces/access.interface.ts b/server/src/interfaces/access.interface.ts
deleted file mode 100644
index d8d7b4e807..0000000000
--- a/server/src/interfaces/access.interface.ts
+++ /dev/null
@@ -1,53 +0,0 @@
-import { AlbumUserRole } from 'src/enum';
-
-export const IAccessRepository = 'IAccessRepository';
-
-export interface IAccessRepository {
- activity: {
- checkOwnerAccess(userId: string, activityIds: Set): Promise>;
- checkAlbumOwnerAccess(userId: string, activityIds: Set): Promise>;
- checkCreateAccess(userId: string, albumIds: Set): Promise>;
- };
-
- asset: {
- checkOwnerAccess(userId: string, assetIds: Set): Promise>;
- checkAlbumAccess(userId: string, assetIds: Set): Promise>;
- checkPartnerAccess(userId: string, assetIds: Set): Promise>;
- checkSharedLinkAccess(sharedLinkId: string, assetIds: Set): Promise>;
- };
-
- authDevice: {
- checkOwnerAccess(userId: string, deviceIds: Set): Promise>;
- };
-
- album: {
- checkOwnerAccess(userId: string, albumIds: Set): Promise>;
- checkSharedAlbumAccess(userId: string, albumIds: Set, access: AlbumUserRole): Promise>;
- checkSharedLinkAccess(sharedLinkId: string, albumIds: Set): Promise>;
- };
-
- timeline: {
- checkPartnerAccess(userId: string, partnerIds: Set): Promise>;
- };
-
- memory: {
- checkOwnerAccess(userId: string, memoryIds: Set): Promise>;
- };
-
- person: {
- checkFaceOwnerAccess(userId: string, assetFaceId: Set): Promise>;
- checkOwnerAccess(userId: string, personIds: Set): Promise>;
- };
-
- partner: {
- checkUpdateAccess(userId: string, partnerIds: Set): Promise>;
- };
-
- stack: {
- checkOwnerAccess(userId: string, stackIds: Set): Promise>;
- };
-
- tag: {
- checkOwnerAccess(userId: string, tagIds: Set): Promise>;
- };
-}
diff --git a/server/src/interfaces/album.interface.ts b/server/src/interfaces/album.interface.ts
index 24c64bdc9d..7af1bd97e1 100644
--- a/server/src/interfaces/album.interface.ts
+++ b/server/src/interfaces/album.interface.ts
@@ -1,3 +1,6 @@
+import { Insertable, Updateable } from 'kysely';
+import { Albums } from 'src/db';
+import { AlbumUserCreateDto } from 'src/dtos/album.dto';
import { AlbumEntity } from 'src/entities/album.entity';
import { IBulkAsset } from 'src/utils/asset.util';
@@ -15,7 +18,7 @@ export interface AlbumInfoOptions {
}
export interface IAlbumRepository extends IBulkAsset {
- getById(id: string, options: AlbumInfoOptions): Promise;
+ getById(id: string, options: AlbumInfoOptions): Promise;
getByAssetId(ownerId: string, assetId: string): Promise;
removeAsset(assetId: string): Promise;
getMetadataForIds(ids: string[]): Promise;
@@ -25,8 +28,8 @@ export interface IAlbumRepository extends IBulkAsset {
restoreAll(userId: string): Promise;
softDeleteAll(userId: string): Promise;
deleteAll(userId: string): Promise;
- create(album: Partial): Promise;
- update(album: Partial): Promise;
+ create(album: Insertable, assetIds: string[], albumUsers: AlbumUserCreateDto[]): Promise;
+ update(id: string, album: Updateable): Promise;
delete(id: string): Promise;
updateThumbnails(): Promise;
}
diff --git a/server/src/interfaces/api-key.interface.ts b/server/src/interfaces/api-key.interface.ts
deleted file mode 100644
index 473a2b8019..0000000000
--- a/server/src/interfaces/api-key.interface.ts
+++ /dev/null
@@ -1,19 +0,0 @@
-import { Insertable } from 'kysely';
-import { ApiKeys } from 'src/db';
-import { APIKeyEntity } from 'src/entities/api-key.entity';
-import { AuthApiKey } from 'src/types';
-
-export const IKeyRepository = 'IKeyRepository';
-
-export interface IKeyRepository {
- create(dto: Insertable): Promise;
- update(userId: string, id: string, dto: Partial): Promise;
- delete(userId: string, id: string): Promise;
- /**
- * Includes the hashed `key` for verification
- * @param id
- */
- getKey(hashedToken: string): Promise;
- getById(userId: string, id: string): Promise;
- getByUserId(userId: string): Promise;
-}
diff --git a/server/src/interfaces/audit.interface.ts b/server/src/interfaces/audit.interface.ts
deleted file mode 100644
index 0b9f19d8db..0000000000
--- a/server/src/interfaces/audit.interface.ts
+++ /dev/null
@@ -1,14 +0,0 @@
-import { DatabaseAction, EntityType } from 'src/enum';
-
-export const IAuditRepository = 'IAuditRepository';
-
-export interface AuditSearch {
- action?: DatabaseAction;
- entityType?: EntityType;
- userIds: string[];
-}
-
-export interface IAuditRepository {
- getAfter(since: Date, options: AuditSearch): Promise;
- removeBefore(before: Date): Promise;
-}
diff --git a/server/src/interfaces/config.interface.ts b/server/src/interfaces/config.interface.ts
deleted file mode 100644
index 8b45078039..0000000000
--- a/server/src/interfaces/config.interface.ts
+++ /dev/null
@@ -1,98 +0,0 @@
-import { RegisterQueueOptions } from '@nestjs/bullmq';
-import { QueueOptions } from 'bullmq';
-import { RedisOptions } from 'ioredis';
-import { KyselyConfig } from 'kysely';
-import { ClsModuleOptions } from 'nestjs-cls';
-import { OpenTelemetryModuleOptions } from 'nestjs-otel/lib/interfaces';
-import { ImmichEnvironment, ImmichTelemetry, ImmichWorker, LogLevel } from 'src/enum';
-import { DatabaseConnectionParams, VectorExtension } from 'src/interfaces/database.interface';
-import { PostgresConnectionOptions } from 'typeorm/driver/postgres/PostgresConnectionOptions.js';
-
-export const IConfigRepository = 'IConfigRepository';
-
-export interface EnvData {
- host?: string;
- port: number;
- environment: ImmichEnvironment;
- configFile?: string;
- logLevel?: LogLevel;
-
- buildMetadata: {
- build?: string;
- buildUrl?: string;
- buildImage?: string;
- buildImageUrl?: string;
- repository?: string;
- repositoryUrl?: string;
- sourceRef?: string;
- sourceCommit?: string;
- sourceUrl?: string;
- thirdPartySourceUrl?: string;
- thirdPartyBugFeatureUrl?: string;
- thirdPartyDocumentationUrl?: string;
- thirdPartySupportUrl?: string;
- };
-
- bull: {
- config: QueueOptions;
- queues: RegisterQueueOptions[];
- };
-
- cls: {
- config: ClsModuleOptions;
- };
-
- database: {
- config: { typeorm: PostgresConnectionOptions & DatabaseConnectionParams; kysely: KyselyConfig };
- skipMigrations: boolean;
- vectorExtension: VectorExtension;
- };
-
- licensePublicKey: {
- client: string;
- server: string;
- };
-
- network: {
- trustedProxies: string[];
- };
-
- otel: OpenTelemetryModuleOptions;
-
- resourcePaths: {
- lockFile: string;
- geodata: {
- dateFile: string;
- admin1: string;
- admin2: string;
- cities500: string;
- naturalEarthCountriesPath: string;
- };
- web: {
- root: string;
- indexHtml: string;
- };
- };
-
- redis: RedisOptions;
-
- telemetry: {
- apiPort: number;
- microservicesPort: number;
- metrics: Set;
- };
-
- storage: {
- ignoreMountCheckErrors: boolean;
- };
-
- workers: ImmichWorker[];
-
- noColor: boolean;
- nodeVersion?: string;
-}
-
-export interface IConfigRepository {
- getEnv(): EnvData;
- getWorker(): ImmichWorker | undefined;
-}
diff --git a/server/src/interfaces/machine-learning.interface.ts b/server/src/interfaces/machine-learning.interface.ts
index 372aa0c7cd..934091ef8e 100644
--- a/server/src/interfaces/machine-learning.interface.ts
+++ b/server/src/interfaces/machine-learning.interface.ts
@@ -28,10 +28,10 @@ export type FaceDetectionOptions = ModelOptions & { minScore: number };
type VisualResponse = { imageHeight: number; imageWidth: number };
export type ClipVisualRequest = { [ModelTask.SEARCH]: { [ModelType.VISUAL]: ModelOptions } };
-export type ClipVisualResponse = { [ModelTask.SEARCH]: number[] } & VisualResponse;
+export type ClipVisualResponse = { [ModelTask.SEARCH]: string } & VisualResponse;
export type ClipTextualRequest = { [ModelTask.SEARCH]: { [ModelType.TEXTUAL]: ModelOptions } };
-export type ClipTextualResponse = { [ModelTask.SEARCH]: number[] };
+export type ClipTextualResponse = { [ModelTask.SEARCH]: string };
export type FacialRecognitionRequest = {
[ModelTask.FACIAL_RECOGNITION]: {
@@ -42,7 +42,7 @@ export type FacialRecognitionRequest = {
export interface Face {
boundingBox: BoundingBox;
- embedding: number[];
+ embedding: string;
score: number;
}
@@ -51,7 +51,7 @@ export type DetectedFaces = { faces: Face[] } & VisualResponse;
export type MachineLearningRequest = ClipVisualRequest | ClipTextualRequest | FacialRecognitionRequest;
export interface IMachineLearningRepository {
- encodeImage(urls: string[], imagePath: string, config: ModelOptions): Promise;
- encodeText(urls: string[], text: string, config: ModelOptions): Promise;
+ encodeImage(urls: string[], imagePath: string, config: ModelOptions): Promise;
+ encodeText(urls: string[], text: string, config: ModelOptions): Promise;
detectFaces(urls: string[], imagePath: string, config: FaceDetectionOptions): Promise;
}
diff --git a/server/src/interfaces/person.interface.ts b/server/src/interfaces/person.interface.ts
index dc89f5c1b0..d1404d829a 100644
--- a/server/src/interfaces/person.interface.ts
+++ b/server/src/interfaces/person.interface.ts
@@ -1,9 +1,10 @@
+import { Insertable, Updateable } from 'kysely';
+import { AssetFaces, FaceSearch, Person } from 'src/db';
import { AssetFaceEntity } from 'src/entities/asset-face.entity';
-import { FaceSearchEntity } from 'src/entities/face-search.entity';
import { PersonEntity } from 'src/entities/person.entity';
import { SourceType } from 'src/enum';
import { Paginated, PaginationOptions } from 'src/utils/pagination';
-import { FindManyOptions, FindOptionsRelations, FindOptionsSelect } from 'typeorm';
+import { FindOptionsRelations } from 'typeorm';
export const IPersonRepository = 'IPersonRepository';
@@ -48,29 +49,31 @@ export interface DeleteFacesOptions {
export type UnassignFacesOptions = DeleteFacesOptions;
+export type SelectFaceOptions = Partial<{ [K in keyof AssetFaceEntity]: boolean }>;
+
export interface IPersonRepository {
- getAll(pagination: PaginationOptions, options?: FindManyOptions): Paginated;
+ getAll(options?: Partial): AsyncIterableIterator;
getAllForUser(pagination: PaginationOptions, userId: string, options: PersonSearchOptions): Paginated;
getAllWithoutFaces(): Promise;
getById(personId: string): Promise;
getByName(userId: string, personName: string, options: PersonNameSearchOptions): Promise;
getDistinctNames(userId: string, options: PersonNameSearchOptions): Promise;
- create(person: Partial): Promise;
- createAll(people: Partial[]): Promise;
+ create(person: Insertable): Promise;
+ createAll(people: Insertable[]): Promise;
delete(entities: PersonEntity[]): Promise;
deleteFaces(options: DeleteFacesOptions): Promise;
refreshFaces(
- facesToAdd: Partial[],
+ facesToAdd: Insertable[],
faceIdsToRemove: string[],
- embeddingsToAdd?: FaceSearchEntity[],
+ embeddingsToAdd?: Insertable[],
): Promise;
- getAllFaces(pagination: PaginationOptions, options?: FindManyOptions): Paginated;
+ getAllFaces(options?: Partial): AsyncIterableIterator;
getFaceById(id: string): Promise;
getFaceByIdWithAssets(
id: string,
relations?: FindOptionsRelations,
- select?: FindOptionsSelect,
+ select?: SelectFaceOptions,
): Promise;
getFaces(assetId: string): Promise;
getFacesByIds(ids: AssetFaceId[]): Promise;
@@ -80,7 +83,7 @@ export interface IPersonRepository {
getNumberOfPeople(userId: string): Promise;
reassignFaces(data: UpdateFacesData): Promise;
unassignFaces(options: UnassignFacesOptions): Promise;
- update(person: Partial): Promise;
- updateAll(people: Partial[]): Promise;
+ update(person: Updateable & { id: string }): Promise;
+ updateAll(people: Insertable[]): Promise;
getLatestFaceDate(): Promise;
}
diff --git a/server/src/interfaces/search.interface.ts b/server/src/interfaces/search.interface.ts
index 0de8ef07d5..bb76ff7b1f 100644
--- a/server/src/interfaces/search.interface.ts
+++ b/server/src/interfaces/search.interface.ts
@@ -104,7 +104,7 @@ export interface SearchExifOptions {
}
export interface SearchEmbeddingOptions {
- embedding: number[];
+ embedding: string;
userIds: string[];
}
@@ -152,7 +152,7 @@ export interface FaceEmbeddingSearch extends SearchEmbeddingOptions {
export interface AssetDuplicateSearch {
assetId: string;
- embedding: number[];
+ embedding: string;
maxDistance: number;
type: AssetType;
userIds: string[];
@@ -192,7 +192,7 @@ export interface ISearchRepository {
searchDuplicates(options: AssetDuplicateSearch): Promise;
searchFaces(search: FaceEmbeddingSearch): Promise;
searchRandom(size: number, options: AssetSearchOptions): Promise;
- upsert(assetId: string, embedding: number[]): Promise;
+ upsert(assetId: string, embedding: string): Promise;
searchPlaces(placeName: string): Promise;
getAssetsByCity(userIds: string[]): Promise;
deleteAllSearchEmbeddings(): Promise;
diff --git a/server/src/interfaces/stack.interface.ts b/server/src/interfaces/stack.interface.ts
index 378f63fd95..a9fb8cec76 100644
--- a/server/src/interfaces/stack.interface.ts
+++ b/server/src/interfaces/stack.interface.ts
@@ -1,3 +1,4 @@
+import { Updateable } from 'kysely';
import { StackEntity } from 'src/entities/stack.entity';
export const IStackRepository = 'IStackRepository';
@@ -10,8 +11,8 @@ export interface StackSearch {
export interface IStackRepository {
search(query: StackSearch): Promise;
create(stack: { ownerId: string; assetIds: string[] }): Promise;
- update(stack: Pick & Partial): Promise;
+ update(id: string, entity: Updateable): Promise;
delete(id: string): Promise;
deleteAll(ids: string[]): Promise;
- getById(id: string): Promise;
+ getById(id: string): Promise;
}
diff --git a/server/src/interfaces/view.interface.ts b/server/src/interfaces/view.interface.ts
deleted file mode 100644
index f819160002..0000000000
--- a/server/src/interfaces/view.interface.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-import { AssetEntity } from 'src/entities/asset.entity';
-
-export const IViewRepository = 'IViewRepository';
-
-export interface IViewRepository {
- getAssetsByOriginalPath(userId: string, partialPath: string): Promise;
- getUniqueOriginalPaths(userId: string): Promise;
-}
diff --git a/server/src/middleware/websocket.adapter.ts b/server/src/middleware/websocket.adapter.ts
index da5e5e9816..64bb1f9ea5 100644
--- a/server/src/middleware/websocket.adapter.ts
+++ b/server/src/middleware/websocket.adapter.ts
@@ -3,7 +3,7 @@ import { IoAdapter } from '@nestjs/platform-socket.io';
import { createAdapter } from '@socket.io/redis-adapter';
import { Redis } from 'ioredis';
import { ServerOptions } from 'socket.io';
-import { IConfigRepository } from 'src/interfaces/config.interface';
+import { ConfigRepository } from 'src/repositories/config.repository';
export class WebSocketAdapter extends IoAdapter {
constructor(private app: INestApplicationContext) {
@@ -11,7 +11,7 @@ export class WebSocketAdapter extends IoAdapter {
}
createIOServer(port: number, options?: ServerOptions): any {
- const { redis } = this.app.get(IConfigRepository).getEnv();
+ const { redis } = this.app.get(ConfigRepository).getEnv();
const server = super.createIOServer(port, options);
const pubClient = new Redis(redis);
const subClient = pubClient.duplicate();
diff --git a/server/src/queries/album.repository.sql b/server/src/queries/album.repository.sql
index 196a1d1609..89c9e3b4a9 100644
--- a/server/src/queries/album.repository.sql
+++ b/server/src/queries/album.repository.sql
@@ -1,460 +1,490 @@
-- NOTE: This file is auto generated by ./sql-generator
-- AlbumRepository.getById
-SELECT DISTINCT
- "distinctAlias"."AlbumEntity_id" AS "ids_AlbumEntity_id"
-FROM
+select
+ "albums".*,
(
- SELECT
- "AlbumEntity"."id" AS "AlbumEntity_id",
- "AlbumEntity"."ownerId" AS "AlbumEntity_ownerId",
- "AlbumEntity"."albumName" AS "AlbumEntity_albumName",
- "AlbumEntity"."description" AS "AlbumEntity_description",
- "AlbumEntity"."createdAt" AS "AlbumEntity_createdAt",
- "AlbumEntity"."updatedAt" AS "AlbumEntity_updatedAt",
- "AlbumEntity"."deletedAt" AS "AlbumEntity_deletedAt",
- "AlbumEntity"."albumThumbnailAssetId" AS "AlbumEntity_albumThumbnailAssetId",
- "AlbumEntity"."isActivityEnabled" AS "AlbumEntity_isActivityEnabled",
- "AlbumEntity"."order" AS "AlbumEntity_order",
- "AlbumEntity__AlbumEntity_owner"."id" AS "AlbumEntity__AlbumEntity_owner_id",
- "AlbumEntity__AlbumEntity_owner"."name" AS "AlbumEntity__AlbumEntity_owner_name",
- "AlbumEntity__AlbumEntity_owner"."isAdmin" AS "AlbumEntity__AlbumEntity_owner_isAdmin",
- "AlbumEntity__AlbumEntity_owner"."email" AS "AlbumEntity__AlbumEntity_owner_email",
- "AlbumEntity__AlbumEntity_owner"."storageLabel" AS "AlbumEntity__AlbumEntity_owner_storageLabel",
- "AlbumEntity__AlbumEntity_owner"."oauthId" AS "AlbumEntity__AlbumEntity_owner_oauthId",
- "AlbumEntity__AlbumEntity_owner"."profileImagePath" AS "AlbumEntity__AlbumEntity_owner_profileImagePath",
- "AlbumEntity__AlbumEntity_owner"."shouldChangePassword" AS "AlbumEntity__AlbumEntity_owner_shouldChangePassword",
- "AlbumEntity__AlbumEntity_owner"."createdAt" AS "AlbumEntity__AlbumEntity_owner_createdAt",
- "AlbumEntity__AlbumEntity_owner"."deletedAt" AS "AlbumEntity__AlbumEntity_owner_deletedAt",
- "AlbumEntity__AlbumEntity_owner"."status" AS "AlbumEntity__AlbumEntity_owner_status",
- "AlbumEntity__AlbumEntity_owner"."updatedAt" AS "AlbumEntity__AlbumEntity_owner_updatedAt",
- "AlbumEntity__AlbumEntity_owner"."quotaSizeInBytes" AS "AlbumEntity__AlbumEntity_owner_quotaSizeInBytes",
- "AlbumEntity__AlbumEntity_owner"."quotaUsageInBytes" AS "AlbumEntity__AlbumEntity_owner_quotaUsageInBytes",
- "AlbumEntity__AlbumEntity_owner"."profileChangedAt" AS "AlbumEntity__AlbumEntity_owner_profileChangedAt",
- "AlbumEntity__AlbumEntity_albumUsers"."albumsId" AS "AlbumEntity__AlbumEntity_albumUsers_albumsId",
- "AlbumEntity__AlbumEntity_albumUsers"."usersId" AS "AlbumEntity__AlbumEntity_albumUsers_usersId",
- "AlbumEntity__AlbumEntity_albumUsers"."role" AS "AlbumEntity__AlbumEntity_albumUsers_role",
- "a641d58cf46d4a391ba060ac4dc337665c69ffea"."id" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_id",
- "a641d58cf46d4a391ba060ac4dc337665c69ffea"."name" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_name",
- "a641d58cf46d4a391ba060ac4dc337665c69ffea"."isAdmin" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_isAdmin",
- "a641d58cf46d4a391ba060ac4dc337665c69ffea"."email" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_email",
- "a641d58cf46d4a391ba060ac4dc337665c69ffea"."storageLabel" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_storageLabel",
- "a641d58cf46d4a391ba060ac4dc337665c69ffea"."oauthId" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_oauthId",
- "a641d58cf46d4a391ba060ac4dc337665c69ffea"."profileImagePath" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_profileImagePath",
- "a641d58cf46d4a391ba060ac4dc337665c69ffea"."shouldChangePassword" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_shouldChangePassword",
- "a641d58cf46d4a391ba060ac4dc337665c69ffea"."createdAt" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_createdAt",
- "a641d58cf46d4a391ba060ac4dc337665c69ffea"."deletedAt" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_deletedAt",
- "a641d58cf46d4a391ba060ac4dc337665c69ffea"."status" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_status",
- "a641d58cf46d4a391ba060ac4dc337665c69ffea"."updatedAt" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_updatedAt",
- "a641d58cf46d4a391ba060ac4dc337665c69ffea"."quotaSizeInBytes" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_quotaSizeInBytes",
- "a641d58cf46d4a391ba060ac4dc337665c69ffea"."quotaUsageInBytes" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_quotaUsageInBytes",
- "a641d58cf46d4a391ba060ac4dc337665c69ffea"."profileChangedAt" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_profileChangedAt",
- "AlbumEntity__AlbumEntity_sharedLinks"."id" AS "AlbumEntity__AlbumEntity_sharedLinks_id",
- "AlbumEntity__AlbumEntity_sharedLinks"."description" AS "AlbumEntity__AlbumEntity_sharedLinks_description",
- "AlbumEntity__AlbumEntity_sharedLinks"."password" AS "AlbumEntity__AlbumEntity_sharedLinks_password",
- "AlbumEntity__AlbumEntity_sharedLinks"."userId" AS "AlbumEntity__AlbumEntity_sharedLinks_userId",
- "AlbumEntity__AlbumEntity_sharedLinks"."key" AS "AlbumEntity__AlbumEntity_sharedLinks_key",
- "AlbumEntity__AlbumEntity_sharedLinks"."type" AS "AlbumEntity__AlbumEntity_sharedLinks_type",
- "AlbumEntity__AlbumEntity_sharedLinks"."createdAt" AS "AlbumEntity__AlbumEntity_sharedLinks_createdAt",
- "AlbumEntity__AlbumEntity_sharedLinks"."expiresAt" AS "AlbumEntity__AlbumEntity_sharedLinks_expiresAt",
- "AlbumEntity__AlbumEntity_sharedLinks"."allowUpload" AS "AlbumEntity__AlbumEntity_sharedLinks_allowUpload",
- "AlbumEntity__AlbumEntity_sharedLinks"."allowDownload" AS "AlbumEntity__AlbumEntity_sharedLinks_allowDownload",
- "AlbumEntity__AlbumEntity_sharedLinks"."showExif" AS "AlbumEntity__AlbumEntity_sharedLinks_showExif",
- "AlbumEntity__AlbumEntity_sharedLinks"."albumId" AS "AlbumEntity__AlbumEntity_sharedLinks_albumId"
- FROM
- "albums" "AlbumEntity"
- LEFT JOIN "users" "AlbumEntity__AlbumEntity_owner" ON "AlbumEntity__AlbumEntity_owner"."id" = "AlbumEntity"."ownerId"
- AND (
- "AlbumEntity__AlbumEntity_owner"."deletedAt" IS NULL
- )
- LEFT JOIN "albums_shared_users_users" "AlbumEntity__AlbumEntity_albumUsers" ON "AlbumEntity__AlbumEntity_albumUsers"."albumsId" = "AlbumEntity"."id"
- LEFT JOIN "users" "a641d58cf46d4a391ba060ac4dc337665c69ffea" ON "a641d58cf46d4a391ba060ac4dc337665c69ffea"."id" = "AlbumEntity__AlbumEntity_albumUsers"."usersId"
- AND (
- "a641d58cf46d4a391ba060ac4dc337665c69ffea"."deletedAt" IS NULL
- )
- LEFT JOIN "shared_links" "AlbumEntity__AlbumEntity_sharedLinks" ON "AlbumEntity__AlbumEntity_sharedLinks"."albumId" = "AlbumEntity"."id"
- WHERE
- ((("AlbumEntity"."id" = $1)))
- AND ("AlbumEntity"."deletedAt" IS NULL)
- ) "distinctAlias"
-ORDER BY
- "AlbumEntity_id" ASC
-LIMIT
- 1
+ select
+ to_json(obj)
+ from
+ (
+ select
+ "id",
+ "email",
+ "createdAt",
+ "profileImagePath",
+ "isAdmin",
+ "shouldChangePassword",
+ "deletedAt",
+ "oauthId",
+ "updatedAt",
+ "storageLabel",
+ "name",
+ "quotaSizeInBytes",
+ "quotaUsageInBytes",
+ "status",
+ "profileChangedAt"
+ from
+ "users"
+ where
+ "users"."id" = "albums"."ownerId"
+ ) as obj
+ ) as "owner",
+ (
+ select
+ coalesce(json_agg(agg), '[]')
+ from
+ (
+ select
+ "album_users".*,
+ (
+ select
+ to_json(obj)
+ from
+ (
+ select
+ "id",
+ "email",
+ "createdAt",
+ "profileImagePath",
+ "isAdmin",
+ "shouldChangePassword",
+ "deletedAt",
+ "oauthId",
+ "updatedAt",
+ "storageLabel",
+ "name",
+ "quotaSizeInBytes",
+ "quotaUsageInBytes",
+ "status",
+ "profileChangedAt"
+ from
+ "users"
+ where
+ "users"."id" = "album_users"."usersId"
+ ) as obj
+ ) as "user"
+ from
+ "albums_shared_users_users" as "album_users"
+ where
+ "album_users"."albumsId" = "albums"."id"
+ ) as agg
+ ) as "albumUsers",
+ (
+ select
+ coalesce(json_agg(agg), '[]')
+ from
+ (
+ select
+ *
+ from
+ "shared_links"
+ where
+ "shared_links"."albumId" = "albums"."id"
+ ) as agg
+ ) as "sharedLinks"
+from
+ "albums"
+where
+ "albums"."id" = $1
+ and "albums"."deletedAt" is null
-- AlbumRepository.getByAssetId
-SELECT
- "AlbumEntity"."id" AS "AlbumEntity_id",
- "AlbumEntity"."ownerId" AS "AlbumEntity_ownerId",
- "AlbumEntity"."albumName" AS "AlbumEntity_albumName",
- "AlbumEntity"."description" AS "AlbumEntity_description",
- "AlbumEntity"."createdAt" AS "AlbumEntity_createdAt",
- "AlbumEntity"."updatedAt" AS "AlbumEntity_updatedAt",
- "AlbumEntity"."deletedAt" AS "AlbumEntity_deletedAt",
- "AlbumEntity"."albumThumbnailAssetId" AS "AlbumEntity_albumThumbnailAssetId",
- "AlbumEntity"."isActivityEnabled" AS "AlbumEntity_isActivityEnabled",
- "AlbumEntity"."order" AS "AlbumEntity_order",
- "AlbumEntity__AlbumEntity_owner"."id" AS "AlbumEntity__AlbumEntity_owner_id",
- "AlbumEntity__AlbumEntity_owner"."name" AS "AlbumEntity__AlbumEntity_owner_name",
- "AlbumEntity__AlbumEntity_owner"."isAdmin" AS "AlbumEntity__AlbumEntity_owner_isAdmin",
- "AlbumEntity__AlbumEntity_owner"."email" AS "AlbumEntity__AlbumEntity_owner_email",
- "AlbumEntity__AlbumEntity_owner"."storageLabel" AS "AlbumEntity__AlbumEntity_owner_storageLabel",
- "AlbumEntity__AlbumEntity_owner"."oauthId" AS "AlbumEntity__AlbumEntity_owner_oauthId",
- "AlbumEntity__AlbumEntity_owner"."profileImagePath" AS "AlbumEntity__AlbumEntity_owner_profileImagePath",
- "AlbumEntity__AlbumEntity_owner"."shouldChangePassword" AS "AlbumEntity__AlbumEntity_owner_shouldChangePassword",
- "AlbumEntity__AlbumEntity_owner"."createdAt" AS "AlbumEntity__AlbumEntity_owner_createdAt",
- "AlbumEntity__AlbumEntity_owner"."deletedAt" AS "AlbumEntity__AlbumEntity_owner_deletedAt",
- "AlbumEntity__AlbumEntity_owner"."status" AS "AlbumEntity__AlbumEntity_owner_status",
- "AlbumEntity__AlbumEntity_owner"."updatedAt" AS "AlbumEntity__AlbumEntity_owner_updatedAt",
- "AlbumEntity__AlbumEntity_owner"."quotaSizeInBytes" AS "AlbumEntity__AlbumEntity_owner_quotaSizeInBytes",
- "AlbumEntity__AlbumEntity_owner"."quotaUsageInBytes" AS "AlbumEntity__AlbumEntity_owner_quotaUsageInBytes",
- "AlbumEntity__AlbumEntity_owner"."profileChangedAt" AS "AlbumEntity__AlbumEntity_owner_profileChangedAt",
- "AlbumEntity__AlbumEntity_albumUsers"."albumsId" AS "AlbumEntity__AlbumEntity_albumUsers_albumsId",
- "AlbumEntity__AlbumEntity_albumUsers"."usersId" AS "AlbumEntity__AlbumEntity_albumUsers_usersId",
- "AlbumEntity__AlbumEntity_albumUsers"."role" AS "AlbumEntity__AlbumEntity_albumUsers_role",
- "a641d58cf46d4a391ba060ac4dc337665c69ffea"."id" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_id",
- "a641d58cf46d4a391ba060ac4dc337665c69ffea"."name" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_name",
- "a641d58cf46d4a391ba060ac4dc337665c69ffea"."isAdmin" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_isAdmin",
- "a641d58cf46d4a391ba060ac4dc337665c69ffea"."email" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_email",
- "a641d58cf46d4a391ba060ac4dc337665c69ffea"."storageLabel" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_storageLabel",
- "a641d58cf46d4a391ba060ac4dc337665c69ffea"."oauthId" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_oauthId",
- "a641d58cf46d4a391ba060ac4dc337665c69ffea"."profileImagePath" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_profileImagePath",
- "a641d58cf46d4a391ba060ac4dc337665c69ffea"."shouldChangePassword" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_shouldChangePassword",
- "a641d58cf46d4a391ba060ac4dc337665c69ffea"."createdAt" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_createdAt",
- "a641d58cf46d4a391ba060ac4dc337665c69ffea"."deletedAt" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_deletedAt",
- "a641d58cf46d4a391ba060ac4dc337665c69ffea"."status" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_status",
- "a641d58cf46d4a391ba060ac4dc337665c69ffea"."updatedAt" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_updatedAt",
- "a641d58cf46d4a391ba060ac4dc337665c69ffea"."quotaSizeInBytes" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_quotaSizeInBytes",
- "a641d58cf46d4a391ba060ac4dc337665c69ffea"."quotaUsageInBytes" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_quotaUsageInBytes",
- "a641d58cf46d4a391ba060ac4dc337665c69ffea"."profileChangedAt" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_profileChangedAt"
-FROM
- "albums" "AlbumEntity"
- LEFT JOIN "users" "AlbumEntity__AlbumEntity_owner" ON "AlbumEntity__AlbumEntity_owner"."id" = "AlbumEntity"."ownerId"
- AND (
- "AlbumEntity__AlbumEntity_owner"."deletedAt" IS NULL
- )
- LEFT JOIN "albums_shared_users_users" "AlbumEntity__AlbumEntity_albumUsers" ON "AlbumEntity__AlbumEntity_albumUsers"."albumsId" = "AlbumEntity"."id"
- LEFT JOIN "users" "a641d58cf46d4a391ba060ac4dc337665c69ffea" ON "a641d58cf46d4a391ba060ac4dc337665c69ffea"."id" = "AlbumEntity__AlbumEntity_albumUsers"."usersId"
- AND (
- "a641d58cf46d4a391ba060ac4dc337665c69ffea"."deletedAt" IS NULL
- )
- LEFT JOIN "albums_assets_assets" "AlbumEntity_AlbumEntity__AlbumEntity_assets" ON "AlbumEntity_AlbumEntity__AlbumEntity_assets"."albumsId" = "AlbumEntity"."id"
- LEFT JOIN "assets" "AlbumEntity__AlbumEntity_assets" ON "AlbumEntity__AlbumEntity_assets"."id" = "AlbumEntity_AlbumEntity__AlbumEntity_assets"."assetsId"
- AND (
- "AlbumEntity__AlbumEntity_assets"."deletedAt" IS NULL
- )
-WHERE
+select
+ "albums".*,
+ (
+ select
+ to_json(obj)
+ from
+ (
+ select
+ "id",
+ "email",
+ "createdAt",
+ "profileImagePath",
+ "isAdmin",
+ "shouldChangePassword",
+ "deletedAt",
+ "oauthId",
+ "updatedAt",
+ "storageLabel",
+ "name",
+ "quotaSizeInBytes",
+ "quotaUsageInBytes",
+ "status",
+ "profileChangedAt"
+ from
+ "users"
+ where
+ "users"."id" = "albums"."ownerId"
+ ) as obj
+ ) as "owner",
+ (
+ select
+ coalesce(json_agg(agg), '[]')
+ from
+ (
+ select
+ "album_users".*,
+ (
+ select
+ to_json(obj)
+ from
+ (
+ select
+ "id",
+ "email",
+ "createdAt",
+ "profileImagePath",
+ "isAdmin",
+ "shouldChangePassword",
+ "deletedAt",
+ "oauthId",
+ "updatedAt",
+ "storageLabel",
+ "name",
+ "quotaSizeInBytes",
+ "quotaUsageInBytes",
+ "status",
+ "profileChangedAt"
+ from
+ "users"
+ where
+ "users"."id" = "album_users"."usersId"
+ ) as obj
+ ) as "user"
+ from
+ "albums_shared_users_users" as "album_users"
+ where
+ "album_users"."albumsId" = "albums"."id"
+ ) as agg
+ ) as "albumUsers"
+from
+ "albums"
+ left join "albums_assets_assets" as "album_assets" on "album_assets"."albumsId" = "albums"."id"
+ left join "albums_shared_users_users" as "album_users" on "album_users"."albumsId" = "albums"."id"
+where
(
(
- (
- (
- ("AlbumEntity"."ownerId" = $1)
- AND ((("AlbumEntity__AlbumEntity_assets"."id" = $2)))
- )
- )
- OR (
- (
- (
- (
- (
- "AlbumEntity__AlbumEntity_albumUsers"."usersId" = $3
- )
- )
- )
- AND ((("AlbumEntity__AlbumEntity_assets"."id" = $4)))
- )
- )
+ "albums"."ownerId" = $1
+ and "album_assets"."assetsId" = $2
+ )
+ or (
+ "album_users"."usersId" = $3
+ and "album_assets"."assetsId" = $4
)
)
- AND ("AlbumEntity"."deletedAt" IS NULL)
-ORDER BY
- "AlbumEntity"."createdAt" DESC
+ and "albums"."deletedAt" is null
+order by
+ "albums"."createdAt" desc,
+ "albums"."createdAt" desc
-- AlbumRepository.getMetadataForIds
-SELECT
- "album"."id" AS "album_id",
- MIN("assets"."fileCreatedAt") AS "start_date",
- MAX("assets"."fileCreatedAt") AS "end_date",
- COUNT("assets"."id") AS "asset_count"
-FROM
- "albums" "album"
- LEFT JOIN "albums_assets_assets" "album_assets" ON "album_assets"."albumsId" = "album"."id"
- LEFT JOIN "assets" "assets" ON "assets"."id" = "album_assets"."assetsId"
- AND "assets"."deletedAt" IS NULL
-WHERE
- ("album"."id" IN ($1))
- AND ("album"."deletedAt" IS NULL)
-GROUP BY
- "album"."id"
+select
+ "albums"."id",
+ min("assets"."fileCreatedAt") as "startDate",
+ max("assets"."fileCreatedAt") as "endDate",
+ count("assets"."id") as "assetCount"
+from
+ "albums"
+ left join "albums_assets_assets" as "album_assets" on "album_assets"."albumsId" = "albums"."id"
+ left join "assets" on "assets"."id" = "album_assets"."assetsId"
+where
+ "albums"."id" in ($1)
+group by
+ "albums"."id"
-- AlbumRepository.getOwned
-SELECT
- "AlbumEntity"."id" AS "AlbumEntity_id",
- "AlbumEntity"."ownerId" AS "AlbumEntity_ownerId",
- "AlbumEntity"."albumName" AS "AlbumEntity_albumName",
- "AlbumEntity"."description" AS "AlbumEntity_description",
- "AlbumEntity"."createdAt" AS "AlbumEntity_createdAt",
- "AlbumEntity"."updatedAt" AS "AlbumEntity_updatedAt",
- "AlbumEntity"."deletedAt" AS "AlbumEntity_deletedAt",
- "AlbumEntity"."albumThumbnailAssetId" AS "AlbumEntity_albumThumbnailAssetId",
- "AlbumEntity"."isActivityEnabled" AS "AlbumEntity_isActivityEnabled",
- "AlbumEntity"."order" AS "AlbumEntity_order",
- "AlbumEntity__AlbumEntity_albumUsers"."albumsId" AS "AlbumEntity__AlbumEntity_albumUsers_albumsId",
- "AlbumEntity__AlbumEntity_albumUsers"."usersId" AS "AlbumEntity__AlbumEntity_albumUsers_usersId",
- "AlbumEntity__AlbumEntity_albumUsers"."role" AS "AlbumEntity__AlbumEntity_albumUsers_role",
- "a641d58cf46d4a391ba060ac4dc337665c69ffea"."id" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_id",
- "a641d58cf46d4a391ba060ac4dc337665c69ffea"."name" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_name",
- "a641d58cf46d4a391ba060ac4dc337665c69ffea"."isAdmin" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_isAdmin",
- "a641d58cf46d4a391ba060ac4dc337665c69ffea"."email" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_email",
- "a641d58cf46d4a391ba060ac4dc337665c69ffea"."storageLabel" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_storageLabel",
- "a641d58cf46d4a391ba060ac4dc337665c69ffea"."oauthId" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_oauthId",
- "a641d58cf46d4a391ba060ac4dc337665c69ffea"."profileImagePath" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_profileImagePath",
- "a641d58cf46d4a391ba060ac4dc337665c69ffea"."shouldChangePassword" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_shouldChangePassword",
- "a641d58cf46d4a391ba060ac4dc337665c69ffea"."createdAt" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_createdAt",
- "a641d58cf46d4a391ba060ac4dc337665c69ffea"."deletedAt" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_deletedAt",
- "a641d58cf46d4a391ba060ac4dc337665c69ffea"."status" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_status",
- "a641d58cf46d4a391ba060ac4dc337665c69ffea"."updatedAt" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_updatedAt",
- "a641d58cf46d4a391ba060ac4dc337665c69ffea"."quotaSizeInBytes" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_quotaSizeInBytes",
- "a641d58cf46d4a391ba060ac4dc337665c69ffea"."quotaUsageInBytes" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_quotaUsageInBytes",
- "a641d58cf46d4a391ba060ac4dc337665c69ffea"."profileChangedAt" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_profileChangedAt",
- "AlbumEntity__AlbumEntity_sharedLinks"."id" AS "AlbumEntity__AlbumEntity_sharedLinks_id",
- "AlbumEntity__AlbumEntity_sharedLinks"."description" AS "AlbumEntity__AlbumEntity_sharedLinks_description",
- "AlbumEntity__AlbumEntity_sharedLinks"."password" AS "AlbumEntity__AlbumEntity_sharedLinks_password",
- "AlbumEntity__AlbumEntity_sharedLinks"."userId" AS "AlbumEntity__AlbumEntity_sharedLinks_userId",
- "AlbumEntity__AlbumEntity_sharedLinks"."key" AS "AlbumEntity__AlbumEntity_sharedLinks_key",
- "AlbumEntity__AlbumEntity_sharedLinks"."type" AS "AlbumEntity__AlbumEntity_sharedLinks_type",
- "AlbumEntity__AlbumEntity_sharedLinks"."createdAt" AS "AlbumEntity__AlbumEntity_sharedLinks_createdAt",
- "AlbumEntity__AlbumEntity_sharedLinks"."expiresAt" AS "AlbumEntity__AlbumEntity_sharedLinks_expiresAt",
- "AlbumEntity__AlbumEntity_sharedLinks"."allowUpload" AS "AlbumEntity__AlbumEntity_sharedLinks_allowUpload",
- "AlbumEntity__AlbumEntity_sharedLinks"."allowDownload" AS "AlbumEntity__AlbumEntity_sharedLinks_allowDownload",
- "AlbumEntity__AlbumEntity_sharedLinks"."showExif" AS "AlbumEntity__AlbumEntity_sharedLinks_showExif",
- "AlbumEntity__AlbumEntity_sharedLinks"."albumId" AS "AlbumEntity__AlbumEntity_sharedLinks_albumId",
- "AlbumEntity__AlbumEntity_owner"."id" AS "AlbumEntity__AlbumEntity_owner_id",
- "AlbumEntity__AlbumEntity_owner"."name" AS "AlbumEntity__AlbumEntity_owner_name",
- "AlbumEntity__AlbumEntity_owner"."isAdmin" AS "AlbumEntity__AlbumEntity_owner_isAdmin",
- "AlbumEntity__AlbumEntity_owner"."email" AS "AlbumEntity__AlbumEntity_owner_email",
- "AlbumEntity__AlbumEntity_owner"."storageLabel" AS "AlbumEntity__AlbumEntity_owner_storageLabel",
- "AlbumEntity__AlbumEntity_owner"."oauthId" AS "AlbumEntity__AlbumEntity_owner_oauthId",
- "AlbumEntity__AlbumEntity_owner"."profileImagePath" AS "AlbumEntity__AlbumEntity_owner_profileImagePath",
- "AlbumEntity__AlbumEntity_owner"."shouldChangePassword" AS "AlbumEntity__AlbumEntity_owner_shouldChangePassword",
- "AlbumEntity__AlbumEntity_owner"."createdAt" AS "AlbumEntity__AlbumEntity_owner_createdAt",
- "AlbumEntity__AlbumEntity_owner"."deletedAt" AS "AlbumEntity__AlbumEntity_owner_deletedAt",
- "AlbumEntity__AlbumEntity_owner"."status" AS "AlbumEntity__AlbumEntity_owner_status",
- "AlbumEntity__AlbumEntity_owner"."updatedAt" AS "AlbumEntity__AlbumEntity_owner_updatedAt",
- "AlbumEntity__AlbumEntity_owner"."quotaSizeInBytes" AS "AlbumEntity__AlbumEntity_owner_quotaSizeInBytes",
- "AlbumEntity__AlbumEntity_owner"."quotaUsageInBytes" AS "AlbumEntity__AlbumEntity_owner_quotaUsageInBytes",
- "AlbumEntity__AlbumEntity_owner"."profileChangedAt" AS "AlbumEntity__AlbumEntity_owner_profileChangedAt"
-FROM
- "albums" "AlbumEntity"
- LEFT JOIN "albums_shared_users_users" "AlbumEntity__AlbumEntity_albumUsers" ON "AlbumEntity__AlbumEntity_albumUsers"."albumsId" = "AlbumEntity"."id"
- LEFT JOIN "users" "a641d58cf46d4a391ba060ac4dc337665c69ffea" ON "a641d58cf46d4a391ba060ac4dc337665c69ffea"."id" = "AlbumEntity__AlbumEntity_albumUsers"."usersId"
- AND (
- "a641d58cf46d4a391ba060ac4dc337665c69ffea"."deletedAt" IS NULL
- )
- LEFT JOIN "shared_links" "AlbumEntity__AlbumEntity_sharedLinks" ON "AlbumEntity__AlbumEntity_sharedLinks"."albumId" = "AlbumEntity"."id"
- LEFT JOIN "users" "AlbumEntity__AlbumEntity_owner" ON "AlbumEntity__AlbumEntity_owner"."id" = "AlbumEntity"."ownerId"
- AND (
- "AlbumEntity__AlbumEntity_owner"."deletedAt" IS NULL
- )
-WHERE
- ((("AlbumEntity"."ownerId" = $1)))
- AND ("AlbumEntity"."deletedAt" IS NULL)
-ORDER BY
- "AlbumEntity"."createdAt" DESC
+select
+ "albums".*,
+ (
+ select
+ to_json(obj)
+ from
+ (
+ select
+ "id",
+ "email",
+ "createdAt",
+ "profileImagePath",
+ "isAdmin",
+ "shouldChangePassword",
+ "deletedAt",
+ "oauthId",
+ "updatedAt",
+ "storageLabel",
+ "name",
+ "quotaSizeInBytes",
+ "quotaUsageInBytes",
+ "status",
+ "profileChangedAt"
+ from
+ "users"
+ where
+ "users"."id" = "albums"."ownerId"
+ ) as obj
+ ) as "owner",
+ (
+ select
+ coalesce(json_agg(agg), '[]')
+ from
+ (
+ select
+ "album_users".*,
+ (
+ select
+ to_json(obj)
+ from
+ (
+ select
+ "id",
+ "email",
+ "createdAt",
+ "profileImagePath",
+ "isAdmin",
+ "shouldChangePassword",
+ "deletedAt",
+ "oauthId",
+ "updatedAt",
+ "storageLabel",
+ "name",
+ "quotaSizeInBytes",
+ "quotaUsageInBytes",
+ "status",
+ "profileChangedAt"
+ from
+ "users"
+ where
+ "users"."id" = "album_users"."usersId"
+ ) as obj
+ ) as "user"
+ from
+ "albums_shared_users_users" as "album_users"
+ where
+ "album_users"."albumsId" = "albums"."id"
+ ) as agg
+ ) as "albumUsers",
+ (
+ select
+ coalesce(json_agg(agg), '[]')
+ from
+ (
+ select
+ *
+ from
+ "shared_links"
+ where
+ "shared_links"."albumId" = "albums"."id"
+ ) as agg
+ ) as "sharedLinks"
+from
+ "albums"
+where
+ "albums"."ownerId" = $1
+ and "albums"."deletedAt" is null
+order by
+ "albums"."createdAt" desc
-- AlbumRepository.getShared
-SELECT
- "AlbumEntity"."id" AS "AlbumEntity_id",
- "AlbumEntity"."ownerId" AS "AlbumEntity_ownerId",
- "AlbumEntity"."albumName" AS "AlbumEntity_albumName",
- "AlbumEntity"."description" AS "AlbumEntity_description",
- "AlbumEntity"."createdAt" AS "AlbumEntity_createdAt",
- "AlbumEntity"."updatedAt" AS "AlbumEntity_updatedAt",
- "AlbumEntity"."deletedAt" AS "AlbumEntity_deletedAt",
- "AlbumEntity"."albumThumbnailAssetId" AS "AlbumEntity_albumThumbnailAssetId",
- "AlbumEntity"."isActivityEnabled" AS "AlbumEntity_isActivityEnabled",
- "AlbumEntity"."order" AS "AlbumEntity_order",
- "AlbumEntity__AlbumEntity_albumUsers"."albumsId" AS "AlbumEntity__AlbumEntity_albumUsers_albumsId",
- "AlbumEntity__AlbumEntity_albumUsers"."usersId" AS "AlbumEntity__AlbumEntity_albumUsers_usersId",
- "AlbumEntity__AlbumEntity_albumUsers"."role" AS "AlbumEntity__AlbumEntity_albumUsers_role",
- "a641d58cf46d4a391ba060ac4dc337665c69ffea"."id" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_id",
- "a641d58cf46d4a391ba060ac4dc337665c69ffea"."name" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_name",
- "a641d58cf46d4a391ba060ac4dc337665c69ffea"."isAdmin" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_isAdmin",
- "a641d58cf46d4a391ba060ac4dc337665c69ffea"."email" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_email",
- "a641d58cf46d4a391ba060ac4dc337665c69ffea"."storageLabel" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_storageLabel",
- "a641d58cf46d4a391ba060ac4dc337665c69ffea"."oauthId" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_oauthId",
- "a641d58cf46d4a391ba060ac4dc337665c69ffea"."profileImagePath" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_profileImagePath",
- "a641d58cf46d4a391ba060ac4dc337665c69ffea"."shouldChangePassword" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_shouldChangePassword",
- "a641d58cf46d4a391ba060ac4dc337665c69ffea"."createdAt" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_createdAt",
- "a641d58cf46d4a391ba060ac4dc337665c69ffea"."deletedAt" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_deletedAt",
- "a641d58cf46d4a391ba060ac4dc337665c69ffea"."status" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_status",
- "a641d58cf46d4a391ba060ac4dc337665c69ffea"."updatedAt" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_updatedAt",
- "a641d58cf46d4a391ba060ac4dc337665c69ffea"."quotaSizeInBytes" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_quotaSizeInBytes",
- "a641d58cf46d4a391ba060ac4dc337665c69ffea"."quotaUsageInBytes" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_quotaUsageInBytes",
- "a641d58cf46d4a391ba060ac4dc337665c69ffea"."profileChangedAt" AS "a641d58cf46d4a391ba060ac4dc337665c69ffea_profileChangedAt",
- "AlbumEntity__AlbumEntity_sharedLinks"."id" AS "AlbumEntity__AlbumEntity_sharedLinks_id",
- "AlbumEntity__AlbumEntity_sharedLinks"."description" AS "AlbumEntity__AlbumEntity_sharedLinks_description",
- "AlbumEntity__AlbumEntity_sharedLinks"."password" AS "AlbumEntity__AlbumEntity_sharedLinks_password",
- "AlbumEntity__AlbumEntity_sharedLinks"."userId" AS "AlbumEntity__AlbumEntity_sharedLinks_userId",
- "AlbumEntity__AlbumEntity_sharedLinks"."key" AS "AlbumEntity__AlbumEntity_sharedLinks_key",
- "AlbumEntity__AlbumEntity_sharedLinks"."type" AS "AlbumEntity__AlbumEntity_sharedLinks_type",
- "AlbumEntity__AlbumEntity_sharedLinks"."createdAt" AS "AlbumEntity__AlbumEntity_sharedLinks_createdAt",
- "AlbumEntity__AlbumEntity_sharedLinks"."expiresAt" AS "AlbumEntity__AlbumEntity_sharedLinks_expiresAt",
- "AlbumEntity__AlbumEntity_sharedLinks"."allowUpload" AS "AlbumEntity__AlbumEntity_sharedLinks_allowUpload",
- "AlbumEntity__AlbumEntity_sharedLinks"."allowDownload" AS "AlbumEntity__AlbumEntity_sharedLinks_allowDownload",
- "AlbumEntity__AlbumEntity_sharedLinks"."showExif" AS "AlbumEntity__AlbumEntity_sharedLinks_showExif",
- "AlbumEntity__AlbumEntity_sharedLinks"."albumId" AS "AlbumEntity__AlbumEntity_sharedLinks_albumId",
- "AlbumEntity__AlbumEntity_owner"."id" AS "AlbumEntity__AlbumEntity_owner_id",
- "AlbumEntity__AlbumEntity_owner"."name" AS "AlbumEntity__AlbumEntity_owner_name",
- "AlbumEntity__AlbumEntity_owner"."isAdmin" AS "AlbumEntity__AlbumEntity_owner_isAdmin",
- "AlbumEntity__AlbumEntity_owner"."email" AS "AlbumEntity__AlbumEntity_owner_email",
- "AlbumEntity__AlbumEntity_owner"."storageLabel" AS "AlbumEntity__AlbumEntity_owner_storageLabel",
- "AlbumEntity__AlbumEntity_owner"."oauthId" AS "AlbumEntity__AlbumEntity_owner_oauthId",
- "AlbumEntity__AlbumEntity_owner"."profileImagePath" AS "AlbumEntity__AlbumEntity_owner_profileImagePath",
- "AlbumEntity__AlbumEntity_owner"."shouldChangePassword" AS "AlbumEntity__AlbumEntity_owner_shouldChangePassword",
- "AlbumEntity__AlbumEntity_owner"."createdAt" AS "AlbumEntity__AlbumEntity_owner_createdAt",
- "AlbumEntity__AlbumEntity_owner"."deletedAt" AS "AlbumEntity__AlbumEntity_owner_deletedAt",
- "AlbumEntity__AlbumEntity_owner"."status" AS "AlbumEntity__AlbumEntity_owner_status",
- "AlbumEntity__AlbumEntity_owner"."updatedAt" AS "AlbumEntity__AlbumEntity_owner_updatedAt",
- "AlbumEntity__AlbumEntity_owner"."quotaSizeInBytes" AS "AlbumEntity__AlbumEntity_owner_quotaSizeInBytes",
- "AlbumEntity__AlbumEntity_owner"."quotaUsageInBytes" AS "AlbumEntity__AlbumEntity_owner_quotaUsageInBytes",
- "AlbumEntity__AlbumEntity_owner"."profileChangedAt" AS "AlbumEntity__AlbumEntity_owner_profileChangedAt"
-FROM
- "albums" "AlbumEntity"
- LEFT JOIN "albums_shared_users_users" "AlbumEntity__AlbumEntity_albumUsers" ON "AlbumEntity__AlbumEntity_albumUsers"."albumsId" = "AlbumEntity"."id"
- LEFT JOIN "users" "a641d58cf46d4a391ba060ac4dc337665c69ffea" ON "a641d58cf46d4a391ba060ac4dc337665c69ffea"."id" = "AlbumEntity__AlbumEntity_albumUsers"."usersId"
- AND (
- "a641d58cf46d4a391ba060ac4dc337665c69ffea"."deletedAt" IS NULL
- )
- LEFT JOIN "shared_links" "AlbumEntity__AlbumEntity_sharedLinks" ON "AlbumEntity__AlbumEntity_sharedLinks"."albumId" = "AlbumEntity"."id"
- LEFT JOIN "users" "AlbumEntity__AlbumEntity_owner" ON "AlbumEntity__AlbumEntity_owner"."id" = "AlbumEntity"."ownerId"
- AND (
- "AlbumEntity__AlbumEntity_owner"."deletedAt" IS NULL
- )
-WHERE
+select distinct
+ on ("albums"."createdAt") "albums".*,
(
- (
+ select
+ coalesce(json_agg(agg), '[]')
+ from
(
- (
+ select
+ "album_users".*,
(
- (
+ select
+ to_json(obj)
+ from
(
- "AlbumEntity__AlbumEntity_albumUsers"."usersId" = $1
- )
- )
- )
- )
- )
- OR (
- (
- (
- (
- (
- "AlbumEntity__AlbumEntity_sharedLinks"."userId" = $2
- )
- )
- )
- )
- )
- OR (
- (
- ("AlbumEntity"."ownerId" = $3)
- AND (
- (
- (
- NOT (
- "AlbumEntity__AlbumEntity_albumUsers"."usersId" IS NULL
- )
- )
- )
- )
- )
- )
+ select
+ "id",
+ "email",
+ "createdAt",
+ "profileImagePath",
+ "isAdmin",
+ "shouldChangePassword",
+ "deletedAt",
+ "oauthId",
+ "updatedAt",
+ "storageLabel",
+ "name",
+ "quotaSizeInBytes",
+ "quotaUsageInBytes",
+ "status",
+ "profileChangedAt"
+ from
+ "users"
+ where
+ "users"."id" = "album_users"."usersId"
+ ) as obj
+ ) as "user"
+ from
+ "albums_shared_users_users" as "album_users"
+ where
+ "album_users"."albumsId" = "albums"."id"
+ ) as agg
+ ) as "albumUsers",
+ (
+ select
+ to_json(obj)
+ from
+ (
+ select
+ "id",
+ "email",
+ "createdAt",
+ "profileImagePath",
+ "isAdmin",
+ "shouldChangePassword",
+ "deletedAt",
+ "oauthId",
+ "updatedAt",
+ "storageLabel",
+ "name",
+ "quotaSizeInBytes",
+ "quotaUsageInBytes",
+ "status",
+ "profileChangedAt"
+ from
+ "users"
+ where
+ "users"."id" = "albums"."ownerId"
+ ) as obj
+ ) as "owner",
+ (
+ select
+ coalesce(json_agg(agg), '[]')
+ from
+ (
+ select
+ *
+ from
+ "shared_links"
+ where
+ "shared_links"."albumId" = "albums"."id"
+ ) as agg
+ ) as "sharedLinks"
+from
+ "albums"
+ left join "albums_shared_users_users" as "shared_albums" on "shared_albums"."albumsId" = "albums"."id"
+ left join "shared_links" on "shared_links"."albumId" = "albums"."id"
+where
+ (
+ "shared_albums"."usersId" = $1
+ or "shared_links"."userId" = $2
+ or (
+ "albums"."ownerId" = $3
+ and "shared_albums"."usersId" is not null
)
)
- AND ("AlbumEntity"."deletedAt" IS NULL)
-ORDER BY
- "AlbumEntity"."createdAt" DESC
+ and "albums"."deletedAt" is null
+order by
+ "albums"."createdAt" desc
-- AlbumRepository.getNotShared
-SELECT
- "AlbumEntity"."id" AS "AlbumEntity_id",
- "AlbumEntity"."ownerId" AS "AlbumEntity_ownerId",
- "AlbumEntity"."albumName" AS "AlbumEntity_albumName",
- "AlbumEntity"."description" AS "AlbumEntity_description",
- "AlbumEntity"."createdAt" AS "AlbumEntity_createdAt",
- "AlbumEntity"."updatedAt" AS "AlbumEntity_updatedAt",
- "AlbumEntity"."deletedAt" AS "AlbumEntity_deletedAt",
- "AlbumEntity"."albumThumbnailAssetId" AS "AlbumEntity_albumThumbnailAssetId",
- "AlbumEntity"."isActivityEnabled" AS "AlbumEntity_isActivityEnabled",
- "AlbumEntity"."order" AS "AlbumEntity_order",
- "AlbumEntity__AlbumEntity_albumUsers"."albumsId" AS "AlbumEntity__AlbumEntity_albumUsers_albumsId",
- "AlbumEntity__AlbumEntity_albumUsers"."usersId" AS "AlbumEntity__AlbumEntity_albumUsers_usersId",
- "AlbumEntity__AlbumEntity_albumUsers"."role" AS "AlbumEntity__AlbumEntity_albumUsers_role",
- "AlbumEntity__AlbumEntity_sharedLinks"."id" AS "AlbumEntity__AlbumEntity_sharedLinks_id",
- "AlbumEntity__AlbumEntity_sharedLinks"."description" AS "AlbumEntity__AlbumEntity_sharedLinks_description",
- "AlbumEntity__AlbumEntity_sharedLinks"."password" AS "AlbumEntity__AlbumEntity_sharedLinks_password",
- "AlbumEntity__AlbumEntity_sharedLinks"."userId" AS "AlbumEntity__AlbumEntity_sharedLinks_userId",
- "AlbumEntity__AlbumEntity_sharedLinks"."key" AS "AlbumEntity__AlbumEntity_sharedLinks_key",
- "AlbumEntity__AlbumEntity_sharedLinks"."type" AS "AlbumEntity__AlbumEntity_sharedLinks_type",
- "AlbumEntity__AlbumEntity_sharedLinks"."createdAt" AS "AlbumEntity__AlbumEntity_sharedLinks_createdAt",
- "AlbumEntity__AlbumEntity_sharedLinks"."expiresAt" AS "AlbumEntity__AlbumEntity_sharedLinks_expiresAt",
- "AlbumEntity__AlbumEntity_sharedLinks"."allowUpload" AS "AlbumEntity__AlbumEntity_sharedLinks_allowUpload",
- "AlbumEntity__AlbumEntity_sharedLinks"."allowDownload" AS "AlbumEntity__AlbumEntity_sharedLinks_allowDownload",
- "AlbumEntity__AlbumEntity_sharedLinks"."showExif" AS "AlbumEntity__AlbumEntity_sharedLinks_showExif",
- "AlbumEntity__AlbumEntity_sharedLinks"."albumId" AS "AlbumEntity__AlbumEntity_sharedLinks_albumId",
- "AlbumEntity__AlbumEntity_owner"."id" AS "AlbumEntity__AlbumEntity_owner_id",
- "AlbumEntity__AlbumEntity_owner"."name" AS "AlbumEntity__AlbumEntity_owner_name",
- "AlbumEntity__AlbumEntity_owner"."isAdmin" AS "AlbumEntity__AlbumEntity_owner_isAdmin",
- "AlbumEntity__AlbumEntity_owner"."email" AS "AlbumEntity__AlbumEntity_owner_email",
- "AlbumEntity__AlbumEntity_owner"."storageLabel" AS "AlbumEntity__AlbumEntity_owner_storageLabel",
- "AlbumEntity__AlbumEntity_owner"."oauthId" AS "AlbumEntity__AlbumEntity_owner_oauthId",
- "AlbumEntity__AlbumEntity_owner"."profileImagePath" AS "AlbumEntity__AlbumEntity_owner_profileImagePath",
- "AlbumEntity__AlbumEntity_owner"."shouldChangePassword" AS "AlbumEntity__AlbumEntity_owner_shouldChangePassword",
- "AlbumEntity__AlbumEntity_owner"."createdAt" AS "AlbumEntity__AlbumEntity_owner_createdAt",
- "AlbumEntity__AlbumEntity_owner"."deletedAt" AS "AlbumEntity__AlbumEntity_owner_deletedAt",
- "AlbumEntity__AlbumEntity_owner"."status" AS "AlbumEntity__AlbumEntity_owner_status",
- "AlbumEntity__AlbumEntity_owner"."updatedAt" AS "AlbumEntity__AlbumEntity_owner_updatedAt",
- "AlbumEntity__AlbumEntity_owner"."quotaSizeInBytes" AS "AlbumEntity__AlbumEntity_owner_quotaSizeInBytes",
- "AlbumEntity__AlbumEntity_owner"."quotaUsageInBytes" AS "AlbumEntity__AlbumEntity_owner_quotaUsageInBytes",
- "AlbumEntity__AlbumEntity_owner"."profileChangedAt" AS "AlbumEntity__AlbumEntity_owner_profileChangedAt"
-FROM
- "albums" "AlbumEntity"
- LEFT JOIN "albums_shared_users_users" "AlbumEntity__AlbumEntity_albumUsers" ON "AlbumEntity__AlbumEntity_albumUsers"."albumsId" = "AlbumEntity"."id"
- LEFT JOIN "shared_links" "AlbumEntity__AlbumEntity_sharedLinks" ON "AlbumEntity__AlbumEntity_sharedLinks"."albumId" = "AlbumEntity"."id"
- LEFT JOIN "users" "AlbumEntity__AlbumEntity_owner" ON "AlbumEntity__AlbumEntity_owner"."id" = "AlbumEntity"."ownerId"
- AND (
- "AlbumEntity__AlbumEntity_owner"."deletedAt" IS NULL
- )
-WHERE
+select distinct
+ on ("albums"."createdAt") "albums".*,
(
- (
- ("AlbumEntity"."ownerId" = $1)
- AND (
- (
+ select
+ coalesce(json_agg(agg), '[]')
+ from
+ (
+ select
+ "album_users".*,
(
- "AlbumEntity__AlbumEntity_albumUsers"."usersId" IS NULL
- )
- )
- )
- AND (
- (
- (
- "AlbumEntity__AlbumEntity_sharedLinks"."id" IS NULL
- )
- )
- )
- )
- )
- AND ("AlbumEntity"."deletedAt" IS NULL)
-ORDER BY
- "AlbumEntity"."createdAt" DESC
+ select
+ to_json(obj)
+ from
+ (
+ select
+ "id",
+ "email",
+ "createdAt",
+ "profileImagePath",
+ "isAdmin",
+ "shouldChangePassword",
+ "deletedAt",
+ "oauthId",
+ "updatedAt",
+ "storageLabel",
+ "name",
+ "quotaSizeInBytes",
+ "quotaUsageInBytes",
+ "status",
+ "profileChangedAt"
+ from
+ "users"
+ where
+ "users"."id" = "album_users"."usersId"
+ ) as obj
+ ) as "user"
+ from
+ "albums_shared_users_users" as "album_users"
+ where
+ "album_users"."albumsId" = "albums"."id"
+ ) as agg
+ ) as "albumUsers",
+ (
+ select
+ to_json(obj)
+ from
+ (
+ select
+ "id",
+ "email",
+ "createdAt",
+ "profileImagePath",
+ "isAdmin",
+ "shouldChangePassword",
+ "deletedAt",
+ "oauthId",
+ "updatedAt",
+ "storageLabel",
+ "name",
+ "quotaSizeInBytes",
+ "quotaUsageInBytes",
+ "status",
+ "profileChangedAt"
+ from
+ "users"
+ where
+ "users"."id" = "albums"."ownerId"
+ ) as obj
+ ) as "owner",
+ (
+ select
+ coalesce(json_agg(agg), '[]')
+ from
+ (
+ select
+ *
+ from
+ "shared_links"
+ where
+ "shared_links"."albumId" = "albums"."id"
+ ) as agg
+ ) as "sharedLinks"
+from
+ "albums"
+ left join "albums_shared_users_users" as "shared_albums" on "shared_albums"."albumsId" = "albums"."id"
+ left join "shared_links" on "shared_links"."albumId" = "albums"."id"
+where
+ "albums"."ownerId" = $1
+ and "shared_albums"."usersId" is null
+ and "shared_links"."userId" is null
+ and "albums"."deletedAt" is null
+order by
+ "albums"."createdAt" desc
-- AlbumRepository.getAssetIds
-SELECT
- "albums_assets"."assetsId" AS "assetId"
-FROM
- "albums_assets_assets" "albums_assets"
-WHERE
- "albums_assets"."albumsId" = $1
- AND "albums_assets"."assetsId" IN ($2)
+select
+ *
+from
+ "albums_assets_assets"
+where
+ "albums_assets_assets"."albumsId" = $1
+ and "albums_assets_assets"."assetsId" in ($2)
diff --git a/server/src/queries/person.repository.sql b/server/src/queries/person.repository.sql
index a7e683fca1..2c06d7c3f2 100644
--- a/server/src/queries/person.repository.sql
+++ b/server/src/queries/person.repository.sql
@@ -1,342 +1,252 @@
-- NOTE: This file is auto generated by ./sql-generator
-- PersonRepository.reassignFaces
-UPDATE "asset_faces"
-SET
+update "asset_faces"
+set
"personId" = $1
-WHERE
- "personId" = $2
+where
+ "asset_faces"."personId" = $2
--- PersonRepository.getAllForUser
-SELECT
- "person"."id" AS "person_id",
- "person"."createdAt" AS "person_createdAt",
- "person"."updatedAt" AS "person_updatedAt",
- "person"."ownerId" AS "person_ownerId",
- "person"."name" AS "person_name",
- "person"."birthDate" AS "person_birthDate",
- "person"."thumbnailPath" AS "person_thumbnailPath",
- "person"."faceAssetId" AS "person_faceAssetId",
- "person"."isHidden" AS "person_isHidden"
-FROM
- "person" "person"
- INNER JOIN "asset_faces" "face" ON "face"."personId" = "person"."id"
- INNER JOIN "assets" "asset" ON "asset"."id" = "face"."assetId"
- AND ("asset"."deletedAt" IS NULL)
-WHERE
- "person"."ownerId" = $1
- AND "asset"."isArchived" = false
- AND "person"."isHidden" = false
-GROUP BY
- "person"."id"
-HAVING
- "person"."name" != ''
- OR COUNT("face"."assetId") >= $2
-ORDER BY
- "person"."isHidden" ASC,
- NULLIF("person"."name", '') IS NULL ASC,
- COUNT("face"."assetId") DESC,
- NULLIF("person"."name", '') ASC NULLS LAST,
- "person"."createdAt" ASC
-LIMIT
- 11
-OFFSET
- 10
+-- PersonRepository.unassignFaces
+update "asset_faces"
+set
+ "personId" = $1
+where
+ "asset_faces"."sourceType" = $2
+VACUUM
+ANALYZE asset_faces,
+face_search,
+person
+REINDEX TABLE asset_faces
+REINDEX TABLE person
+
+-- PersonRepository.delete
+delete from "person"
+where
+ "person"."id" in ($1)
+
+-- PersonRepository.deleteFaces
+delete from "asset_faces"
+where
+ "asset_faces"."sourceType" = $1
+VACUUM
+ANALYZE asset_faces,
+face_search,
+person
+REINDEX TABLE asset_faces
+REINDEX TABLE person
-- PersonRepository.getAllWithoutFaces
-SELECT
- "person"."id" AS "person_id",
- "person"."createdAt" AS "person_createdAt",
- "person"."updatedAt" AS "person_updatedAt",
- "person"."ownerId" AS "person_ownerId",
- "person"."name" AS "person_name",
- "person"."birthDate" AS "person_birthDate",
- "person"."thumbnailPath" AS "person_thumbnailPath",
- "person"."faceAssetId" AS "person_faceAssetId",
- "person"."isHidden" AS "person_isHidden"
-FROM
- "person" "person"
- LEFT JOIN "asset_faces" "face" ON "face"."personId" = "person"."id"
-GROUP BY
+select
+ "person".*
+from
+ "person"
+ left join "asset_faces" on "asset_faces"."personId" = "person"."id"
+group by
"person"."id"
-HAVING
- COUNT("face"."assetId") = 0
+having
+ count("asset_faces"."assetId") = $1
-- PersonRepository.getFaces
-SELECT
- "AssetFaceEntity"."id" AS "AssetFaceEntity_id",
- "AssetFaceEntity"."assetId" AS "AssetFaceEntity_assetId",
- "AssetFaceEntity"."personId" AS "AssetFaceEntity_personId",
- "AssetFaceEntity"."imageWidth" AS "AssetFaceEntity_imageWidth",
- "AssetFaceEntity"."imageHeight" AS "AssetFaceEntity_imageHeight",
- "AssetFaceEntity"."boundingBoxX1" AS "AssetFaceEntity_boundingBoxX1",
- "AssetFaceEntity"."boundingBoxY1" AS "AssetFaceEntity_boundingBoxY1",
- "AssetFaceEntity"."boundingBoxX2" AS "AssetFaceEntity_boundingBoxX2",
- "AssetFaceEntity"."boundingBoxY2" AS "AssetFaceEntity_boundingBoxY2",
- "AssetFaceEntity"."sourceType" AS "AssetFaceEntity_sourceType",
- "AssetFaceEntity__AssetFaceEntity_person"."id" AS "AssetFaceEntity__AssetFaceEntity_person_id",
- "AssetFaceEntity__AssetFaceEntity_person"."createdAt" AS "AssetFaceEntity__AssetFaceEntity_person_createdAt",
- "AssetFaceEntity__AssetFaceEntity_person"."updatedAt" AS "AssetFaceEntity__AssetFaceEntity_person_updatedAt",
- "AssetFaceEntity__AssetFaceEntity_person"."ownerId" AS "AssetFaceEntity__AssetFaceEntity_person_ownerId",
- "AssetFaceEntity__AssetFaceEntity_person"."name" AS "AssetFaceEntity__AssetFaceEntity_person_name",
- "AssetFaceEntity__AssetFaceEntity_person"."birthDate" AS "AssetFaceEntity__AssetFaceEntity_person_birthDate",
- "AssetFaceEntity__AssetFaceEntity_person"."thumbnailPath" AS "AssetFaceEntity__AssetFaceEntity_person_thumbnailPath",
- "AssetFaceEntity__AssetFaceEntity_person"."faceAssetId" AS "AssetFaceEntity__AssetFaceEntity_person_faceAssetId",
- "AssetFaceEntity__AssetFaceEntity_person"."isHidden" AS "AssetFaceEntity__AssetFaceEntity_person_isHidden"
-FROM
- "asset_faces" "AssetFaceEntity"
- LEFT JOIN "person" "AssetFaceEntity__AssetFaceEntity_person" ON "AssetFaceEntity__AssetFaceEntity_person"."id" = "AssetFaceEntity"."personId"
-WHERE
- (("AssetFaceEntity"."assetId" = $1))
-ORDER BY
- "AssetFaceEntity"."boundingBoxX1" ASC
+select
+ "asset_faces".*,
+ (
+ select
+ to_json(obj)
+ from
+ (
+ select
+ "person".*
+ from
+ "person"
+ where
+ "person"."id" = "asset_faces"."personId"
+ ) as obj
+ ) as "person"
+from
+ "asset_faces"
+where
+ "asset_faces"."assetId" = $1
+order by
+ "asset_faces"."boundingBoxX1" asc
-- PersonRepository.getFaceById
-SELECT DISTINCT
- "distinctAlias"."AssetFaceEntity_id" AS "ids_AssetFaceEntity_id"
-FROM
+select
+ "asset_faces".*,
(
- SELECT
- "AssetFaceEntity"."id" AS "AssetFaceEntity_id",
- "AssetFaceEntity"."assetId" AS "AssetFaceEntity_assetId",
- "AssetFaceEntity"."personId" AS "AssetFaceEntity_personId",
- "AssetFaceEntity"."imageWidth" AS "AssetFaceEntity_imageWidth",
- "AssetFaceEntity"."imageHeight" AS "AssetFaceEntity_imageHeight",
- "AssetFaceEntity"."boundingBoxX1" AS "AssetFaceEntity_boundingBoxX1",
- "AssetFaceEntity"."boundingBoxY1" AS "AssetFaceEntity_boundingBoxY1",
- "AssetFaceEntity"."boundingBoxX2" AS "AssetFaceEntity_boundingBoxX2",
- "AssetFaceEntity"."boundingBoxY2" AS "AssetFaceEntity_boundingBoxY2",
- "AssetFaceEntity"."sourceType" AS "AssetFaceEntity_sourceType",
- "AssetFaceEntity__AssetFaceEntity_person"."id" AS "AssetFaceEntity__AssetFaceEntity_person_id",
- "AssetFaceEntity__AssetFaceEntity_person"."createdAt" AS "AssetFaceEntity__AssetFaceEntity_person_createdAt",
- "AssetFaceEntity__AssetFaceEntity_person"."updatedAt" AS "AssetFaceEntity__AssetFaceEntity_person_updatedAt",
- "AssetFaceEntity__AssetFaceEntity_person"."ownerId" AS "AssetFaceEntity__AssetFaceEntity_person_ownerId",
- "AssetFaceEntity__AssetFaceEntity_person"."name" AS "AssetFaceEntity__AssetFaceEntity_person_name",
- "AssetFaceEntity__AssetFaceEntity_person"."birthDate" AS "AssetFaceEntity__AssetFaceEntity_person_birthDate",
- "AssetFaceEntity__AssetFaceEntity_person"."thumbnailPath" AS "AssetFaceEntity__AssetFaceEntity_person_thumbnailPath",
- "AssetFaceEntity__AssetFaceEntity_person"."faceAssetId" AS "AssetFaceEntity__AssetFaceEntity_person_faceAssetId",
- "AssetFaceEntity__AssetFaceEntity_person"."isHidden" AS "AssetFaceEntity__AssetFaceEntity_person_isHidden"
- FROM
- "asset_faces" "AssetFaceEntity"
- LEFT JOIN "person" "AssetFaceEntity__AssetFaceEntity_person" ON "AssetFaceEntity__AssetFaceEntity_person"."id" = "AssetFaceEntity"."personId"
- WHERE
- (("AssetFaceEntity"."id" = $1))
- ) "distinctAlias"
-ORDER BY
- "AssetFaceEntity_id" ASC
-LIMIT
- 1
+ select
+ to_json(obj)
+ from
+ (
+ select
+ "person".*
+ from
+ "person"
+ where
+ "person"."id" = "asset_faces"."personId"
+ ) as obj
+ ) as "person"
+from
+ "asset_faces"
+where
+ "asset_faces"."id" = $1
-- PersonRepository.getFaceByIdWithAssets
-SELECT DISTINCT
- "distinctAlias"."AssetFaceEntity_id" AS "ids_AssetFaceEntity_id"
-FROM
+select
+ "asset_faces".*,
(
- SELECT
- "AssetFaceEntity"."id" AS "AssetFaceEntity_id",
- "AssetFaceEntity"."assetId" AS "AssetFaceEntity_assetId",
- "AssetFaceEntity"."personId" AS "AssetFaceEntity_personId",
- "AssetFaceEntity"."imageWidth" AS "AssetFaceEntity_imageWidth",
- "AssetFaceEntity"."imageHeight" AS "AssetFaceEntity_imageHeight",
- "AssetFaceEntity"."boundingBoxX1" AS "AssetFaceEntity_boundingBoxX1",
- "AssetFaceEntity"."boundingBoxY1" AS "AssetFaceEntity_boundingBoxY1",
- "AssetFaceEntity"."boundingBoxX2" AS "AssetFaceEntity_boundingBoxX2",
- "AssetFaceEntity"."boundingBoxY2" AS "AssetFaceEntity_boundingBoxY2",
- "AssetFaceEntity"."sourceType" AS "AssetFaceEntity_sourceType",
- "AssetFaceEntity__AssetFaceEntity_person"."id" AS "AssetFaceEntity__AssetFaceEntity_person_id",
- "AssetFaceEntity__AssetFaceEntity_person"."createdAt" AS "AssetFaceEntity__AssetFaceEntity_person_createdAt",
- "AssetFaceEntity__AssetFaceEntity_person"."updatedAt" AS "AssetFaceEntity__AssetFaceEntity_person_updatedAt",
- "AssetFaceEntity__AssetFaceEntity_person"."ownerId" AS "AssetFaceEntity__AssetFaceEntity_person_ownerId",
- "AssetFaceEntity__AssetFaceEntity_person"."name" AS "AssetFaceEntity__AssetFaceEntity_person_name",
- "AssetFaceEntity__AssetFaceEntity_person"."birthDate" AS "AssetFaceEntity__AssetFaceEntity_person_birthDate",
- "AssetFaceEntity__AssetFaceEntity_person"."thumbnailPath" AS "AssetFaceEntity__AssetFaceEntity_person_thumbnailPath",
- "AssetFaceEntity__AssetFaceEntity_person"."faceAssetId" AS "AssetFaceEntity__AssetFaceEntity_person_faceAssetId",
- "AssetFaceEntity__AssetFaceEntity_person"."isHidden" AS "AssetFaceEntity__AssetFaceEntity_person_isHidden",
- "AssetFaceEntity__AssetFaceEntity_asset"."id" AS "AssetFaceEntity__AssetFaceEntity_asset_id",
- "AssetFaceEntity__AssetFaceEntity_asset"."deviceAssetId" AS "AssetFaceEntity__AssetFaceEntity_asset_deviceAssetId",
- "AssetFaceEntity__AssetFaceEntity_asset"."ownerId" AS "AssetFaceEntity__AssetFaceEntity_asset_ownerId",
- "AssetFaceEntity__AssetFaceEntity_asset"."libraryId" AS "AssetFaceEntity__AssetFaceEntity_asset_libraryId",
- "AssetFaceEntity__AssetFaceEntity_asset"."deviceId" AS "AssetFaceEntity__AssetFaceEntity_asset_deviceId",
- "AssetFaceEntity__AssetFaceEntity_asset"."type" AS "AssetFaceEntity__AssetFaceEntity_asset_type",
- "AssetFaceEntity__AssetFaceEntity_asset"."status" AS "AssetFaceEntity__AssetFaceEntity_asset_status",
- "AssetFaceEntity__AssetFaceEntity_asset"."originalPath" AS "AssetFaceEntity__AssetFaceEntity_asset_originalPath",
- "AssetFaceEntity__AssetFaceEntity_asset"."thumbhash" AS "AssetFaceEntity__AssetFaceEntity_asset_thumbhash",
- "AssetFaceEntity__AssetFaceEntity_asset"."encodedVideoPath" AS "AssetFaceEntity__AssetFaceEntity_asset_encodedVideoPath",
- "AssetFaceEntity__AssetFaceEntity_asset"."createdAt" AS "AssetFaceEntity__AssetFaceEntity_asset_createdAt",
- "AssetFaceEntity__AssetFaceEntity_asset"."updatedAt" AS "AssetFaceEntity__AssetFaceEntity_asset_updatedAt",
- "AssetFaceEntity__AssetFaceEntity_asset"."deletedAt" AS "AssetFaceEntity__AssetFaceEntity_asset_deletedAt",
- "AssetFaceEntity__AssetFaceEntity_asset"."fileCreatedAt" AS "AssetFaceEntity__AssetFaceEntity_asset_fileCreatedAt",
- "AssetFaceEntity__AssetFaceEntity_asset"."localDateTime" AS "AssetFaceEntity__AssetFaceEntity_asset_localDateTime",
- "AssetFaceEntity__AssetFaceEntity_asset"."fileModifiedAt" AS "AssetFaceEntity__AssetFaceEntity_asset_fileModifiedAt",
- "AssetFaceEntity__AssetFaceEntity_asset"."isFavorite" AS "AssetFaceEntity__AssetFaceEntity_asset_isFavorite",
- "AssetFaceEntity__AssetFaceEntity_asset"."isArchived" AS "AssetFaceEntity__AssetFaceEntity_asset_isArchived",
- "AssetFaceEntity__AssetFaceEntity_asset"."isExternal" AS "AssetFaceEntity__AssetFaceEntity_asset_isExternal",
- "AssetFaceEntity__AssetFaceEntity_asset"."isOffline" AS "AssetFaceEntity__AssetFaceEntity_asset_isOffline",
- "AssetFaceEntity__AssetFaceEntity_asset"."checksum" AS "AssetFaceEntity__AssetFaceEntity_asset_checksum",
- "AssetFaceEntity__AssetFaceEntity_asset"."duration" AS "AssetFaceEntity__AssetFaceEntity_asset_duration",
- "AssetFaceEntity__AssetFaceEntity_asset"."isVisible" AS "AssetFaceEntity__AssetFaceEntity_asset_isVisible",
- "AssetFaceEntity__AssetFaceEntity_asset"."livePhotoVideoId" AS "AssetFaceEntity__AssetFaceEntity_asset_livePhotoVideoId",
- "AssetFaceEntity__AssetFaceEntity_asset"."originalFileName" AS "AssetFaceEntity__AssetFaceEntity_asset_originalFileName",
- "AssetFaceEntity__AssetFaceEntity_asset"."sidecarPath" AS "AssetFaceEntity__AssetFaceEntity_asset_sidecarPath",
- "AssetFaceEntity__AssetFaceEntity_asset"."stackId" AS "AssetFaceEntity__AssetFaceEntity_asset_stackId",
- "AssetFaceEntity__AssetFaceEntity_asset"."duplicateId" AS "AssetFaceEntity__AssetFaceEntity_asset_duplicateId"
- FROM
- "asset_faces" "AssetFaceEntity"
- LEFT JOIN "person" "AssetFaceEntity__AssetFaceEntity_person" ON "AssetFaceEntity__AssetFaceEntity_person"."id" = "AssetFaceEntity"."personId"
- LEFT JOIN "assets" "AssetFaceEntity__AssetFaceEntity_asset" ON "AssetFaceEntity__AssetFaceEntity_asset"."id" = "AssetFaceEntity"."assetId"
- AND (
- "AssetFaceEntity__AssetFaceEntity_asset"."deletedAt" IS NULL
- )
- WHERE
- (("AssetFaceEntity"."id" = $1))
- ) "distinctAlias"
-ORDER BY
- "AssetFaceEntity_id" ASC
-LIMIT
- 1
+ select
+ to_json(obj)
+ from
+ (
+ select
+ "person".*
+ from
+ "person"
+ where
+ "person"."id" = "asset_faces"."personId"
+ ) as obj
+ ) as "person",
+ (
+ select
+ to_json(obj)
+ from
+ (
+ select
+ "assets".*
+ from
+ "assets"
+ where
+ "assets"."id" = "asset_faces"."assetId"
+ ) as obj
+ ) as "asset"
+from
+ "asset_faces"
+where
+ "asset_faces"."id" = $1
-- PersonRepository.reassignFace
-UPDATE "asset_faces"
-SET
+update "asset_faces"
+set
"personId" = $1
-WHERE
- "id" = $2
+where
+ "asset_faces"."id" = $2
-- PersonRepository.getByName
-SELECT
- "person"."id" AS "person_id",
- "person"."createdAt" AS "person_createdAt",
- "person"."updatedAt" AS "person_updatedAt",
- "person"."ownerId" AS "person_ownerId",
- "person"."name" AS "person_name",
- "person"."birthDate" AS "person_birthDate",
- "person"."thumbnailPath" AS "person_thumbnailPath",
- "person"."faceAssetId" AS "person_faceAssetId",
- "person"."isHidden" AS "person_isHidden"
-FROM
- "person" "person"
-WHERE
- "person"."ownerId" = $1
- AND (
- LOWER("person"."name") LIKE $2
- OR LOWER("person"."name") LIKE $3
- )
-LIMIT
- 1000
-
--- PersonRepository.getDistinctNames
-SELECT DISTINCT
- ON (lower("person"."name")) "person"."id" AS "person_id",
- "person"."name" AS "person_name"
-FROM
- "person" "person"
-WHERE
- "person"."ownerId" = $1
- AND "person"."name" != ''
-
--- PersonRepository.getStatistics
-SELECT
- COUNT(DISTINCT ("asset"."id")) AS "count"
-FROM
- "asset_faces" "face"
- LEFT JOIN "assets" "asset" ON "asset"."id" = "face"."assetId"
- AND ("asset"."deletedAt" IS NULL)
-WHERE
- "face"."personId" = $1
- AND "asset"."isArchived" = false
- AND "asset"."deletedAt" IS NULL
- AND "asset"."livePhotoVideoId" IS NULL
-
--- PersonRepository.getNumberOfPeople
-SELECT
- COUNT(DISTINCT ("person"."id")) AS "total",
- COUNT(DISTINCT ("person"."id")) FILTER (
- WHERE
- "person"."isHidden" = true
- ) AS "hidden"
-FROM
- "person" "person"
- INNER JOIN "asset_faces" "face" ON "face"."personId" = "person"."id"
- INNER JOIN "assets" "asset" ON "asset"."id" = "face"."assetId"
- AND ("asset"."deletedAt" IS NULL)
-WHERE
- "person"."ownerId" = $1
- AND "asset"."isArchived" = false
-
--- PersonRepository.getFacesByIds
-SELECT
- "AssetFaceEntity"."id" AS "AssetFaceEntity_id",
- "AssetFaceEntity"."assetId" AS "AssetFaceEntity_assetId",
- "AssetFaceEntity"."personId" AS "AssetFaceEntity_personId",
- "AssetFaceEntity"."imageWidth" AS "AssetFaceEntity_imageWidth",
- "AssetFaceEntity"."imageHeight" AS "AssetFaceEntity_imageHeight",
- "AssetFaceEntity"."boundingBoxX1" AS "AssetFaceEntity_boundingBoxX1",
- "AssetFaceEntity"."boundingBoxY1" AS "AssetFaceEntity_boundingBoxY1",
- "AssetFaceEntity"."boundingBoxX2" AS "AssetFaceEntity_boundingBoxX2",
- "AssetFaceEntity"."boundingBoxY2" AS "AssetFaceEntity_boundingBoxY2",
- "AssetFaceEntity"."sourceType" AS "AssetFaceEntity_sourceType",
- "AssetFaceEntity__AssetFaceEntity_asset"."id" AS "AssetFaceEntity__AssetFaceEntity_asset_id",
- "AssetFaceEntity__AssetFaceEntity_asset"."deviceAssetId" AS "AssetFaceEntity__AssetFaceEntity_asset_deviceAssetId",
- "AssetFaceEntity__AssetFaceEntity_asset"."ownerId" AS "AssetFaceEntity__AssetFaceEntity_asset_ownerId",
- "AssetFaceEntity__AssetFaceEntity_asset"."libraryId" AS "AssetFaceEntity__AssetFaceEntity_asset_libraryId",
- "AssetFaceEntity__AssetFaceEntity_asset"."deviceId" AS "AssetFaceEntity__AssetFaceEntity_asset_deviceId",
- "AssetFaceEntity__AssetFaceEntity_asset"."type" AS "AssetFaceEntity__AssetFaceEntity_asset_type",
- "AssetFaceEntity__AssetFaceEntity_asset"."status" AS "AssetFaceEntity__AssetFaceEntity_asset_status",
- "AssetFaceEntity__AssetFaceEntity_asset"."originalPath" AS "AssetFaceEntity__AssetFaceEntity_asset_originalPath",
- "AssetFaceEntity__AssetFaceEntity_asset"."thumbhash" AS "AssetFaceEntity__AssetFaceEntity_asset_thumbhash",
- "AssetFaceEntity__AssetFaceEntity_asset"."encodedVideoPath" AS "AssetFaceEntity__AssetFaceEntity_asset_encodedVideoPath",
- "AssetFaceEntity__AssetFaceEntity_asset"."createdAt" AS "AssetFaceEntity__AssetFaceEntity_asset_createdAt",
- "AssetFaceEntity__AssetFaceEntity_asset"."updatedAt" AS "AssetFaceEntity__AssetFaceEntity_asset_updatedAt",
- "AssetFaceEntity__AssetFaceEntity_asset"."deletedAt" AS "AssetFaceEntity__AssetFaceEntity_asset_deletedAt",
- "AssetFaceEntity__AssetFaceEntity_asset"."fileCreatedAt" AS "AssetFaceEntity__AssetFaceEntity_asset_fileCreatedAt",
- "AssetFaceEntity__AssetFaceEntity_asset"."localDateTime" AS "AssetFaceEntity__AssetFaceEntity_asset_localDateTime",
- "AssetFaceEntity__AssetFaceEntity_asset"."fileModifiedAt" AS "AssetFaceEntity__AssetFaceEntity_asset_fileModifiedAt",
- "AssetFaceEntity__AssetFaceEntity_asset"."isFavorite" AS "AssetFaceEntity__AssetFaceEntity_asset_isFavorite",
- "AssetFaceEntity__AssetFaceEntity_asset"."isArchived" AS "AssetFaceEntity__AssetFaceEntity_asset_isArchived",
- "AssetFaceEntity__AssetFaceEntity_asset"."isExternal" AS "AssetFaceEntity__AssetFaceEntity_asset_isExternal",
- "AssetFaceEntity__AssetFaceEntity_asset"."isOffline" AS "AssetFaceEntity__AssetFaceEntity_asset_isOffline",
- "AssetFaceEntity__AssetFaceEntity_asset"."checksum" AS "AssetFaceEntity__AssetFaceEntity_asset_checksum",
- "AssetFaceEntity__AssetFaceEntity_asset"."duration" AS "AssetFaceEntity__AssetFaceEntity_asset_duration",
- "AssetFaceEntity__AssetFaceEntity_asset"."isVisible" AS "AssetFaceEntity__AssetFaceEntity_asset_isVisible",
- "AssetFaceEntity__AssetFaceEntity_asset"."livePhotoVideoId" AS "AssetFaceEntity__AssetFaceEntity_asset_livePhotoVideoId",
- "AssetFaceEntity__AssetFaceEntity_asset"."originalFileName" AS "AssetFaceEntity__AssetFaceEntity_asset_originalFileName",
- "AssetFaceEntity__AssetFaceEntity_asset"."sidecarPath" AS "AssetFaceEntity__AssetFaceEntity_asset_sidecarPath",
- "AssetFaceEntity__AssetFaceEntity_asset"."stackId" AS "AssetFaceEntity__AssetFaceEntity_asset_stackId",
- "AssetFaceEntity__AssetFaceEntity_asset"."duplicateId" AS "AssetFaceEntity__AssetFaceEntity_asset_duplicateId"
-FROM
- "asset_faces" "AssetFaceEntity"
- LEFT JOIN "assets" "AssetFaceEntity__AssetFaceEntity_asset" ON "AssetFaceEntity__AssetFaceEntity_asset"."id" = "AssetFaceEntity"."assetId"
-WHERE
+select
+ "person".*
+from
+ "person"
+where
(
- (
- (
- ("AssetFaceEntity"."assetId" = $1)
- AND ("AssetFaceEntity"."personId" = $2)
- )
+ "person"."ownerId" = $1
+ and (
+ lower("person"."name") like $2
+ or lower("person"."name") like $3
)
)
+limit
+ $4
+
+-- PersonRepository.getDistinctNames
+select distinct
+ on (lower("person"."name")) "person"."id",
+ "person"."name"
+from
+ "person"
+where
+ (
+ "person"."ownerId" = $1
+ and "person"."name" != $2
+ )
+
+-- PersonRepository.getStatistics
+select
+ count(distinct ("assets"."id")) as "count"
+from
+ "asset_faces"
+ left join "assets" on "assets"."id" = "asset_faces"."assetId"
+ and "asset_faces"."personId" = $1
+ and "assets"."isArchived" = $2
+ and "assets"."deletedAt" is null
+ and "assets"."livePhotoVideoId" is null
+
+-- PersonRepository.getNumberOfPeople
+select
+ count(distinct ("person"."id")) as "total",
+ count(distinct ("person"."id")) filter (
+ where
+ "person"."isHidden" = $1
+ ) as "hidden"
+from
+ "person"
+ inner join "asset_faces" on "asset_faces"."personId" = "person"."id"
+ inner join "assets" on "assets"."id" = "asset_faces"."assetId"
+ and "assets"."deletedAt" is null
+ and "assets"."isArchived" = $2
+where
+ "person"."ownerId" = $3
+
+-- PersonRepository.refreshFaces
+with
+ "added_embeddings" as (
+ insert into
+ "face_search" ("faceId", "embedding")
+ values
+ ($1, $2)
+ )
+select
+from
+ (
+ select
+ 1
+ ) as "dummy"
+
+-- PersonRepository.getFacesByIds
+select
+ "asset_faces".*,
+ (
+ select
+ to_json(obj)
+ from
+ (
+ select
+ "assets".*
+ from
+ "assets"
+ where
+ "assets"."id" = "asset_faces"."assetId"
+ ) as obj
+ ) as "asset",
+ (
+ select
+ to_json(obj)
+ from
+ (
+ select
+ "person".*
+ from
+ "person"
+ where
+ "person"."id" = "asset_faces"."personId"
+ ) as obj
+ ) as "person"
+from
+ "asset_faces"
+where
+ "asset_faces"."assetId" in ($1)
+ and "asset_faces"."personId" in ($2)
-- PersonRepository.getRandomFace
-SELECT
- "AssetFaceEntity"."id" AS "AssetFaceEntity_id",
- "AssetFaceEntity"."assetId" AS "AssetFaceEntity_assetId",
- "AssetFaceEntity"."personId" AS "AssetFaceEntity_personId",
- "AssetFaceEntity"."imageWidth" AS "AssetFaceEntity_imageWidth",
- "AssetFaceEntity"."imageHeight" AS "AssetFaceEntity_imageHeight",
- "AssetFaceEntity"."boundingBoxX1" AS "AssetFaceEntity_boundingBoxX1",
- "AssetFaceEntity"."boundingBoxY1" AS "AssetFaceEntity_boundingBoxY1",
- "AssetFaceEntity"."boundingBoxX2" AS "AssetFaceEntity_boundingBoxX2",
- "AssetFaceEntity"."boundingBoxY2" AS "AssetFaceEntity_boundingBoxY2",
- "AssetFaceEntity"."sourceType" AS "AssetFaceEntity_sourceType"
-FROM
- "asset_faces" "AssetFaceEntity"
-WHERE
- (("AssetFaceEntity"."personId" = $1))
-LIMIT
- 1
+select
+ "asset_faces".*
+from
+ "asset_faces"
+where
+ "asset_faces"."personId" = $1
-- PersonRepository.getLatestFaceDate
-SELECT
- MAX("jobStatus"."facesRecognizedAt")::text AS "latestDate"
-FROM
- "asset_job_status" "jobStatus"
+select
+ max("asset_job_status"."facesRecognizedAt")::text as "latestDate"
+from
+ "asset_job_status"
diff --git a/server/src/queries/search.repository.sql b/server/src/queries/search.repository.sql
index a6e93bd480..784babfc02 100644
--- a/server/src/queries/search.repository.sql
+++ b/server/src/queries/search.repository.sql
@@ -76,7 +76,7 @@ where
and "assets"."isArchived" = $5
and "assets"."deletedAt" is null
order by
- smart_search.embedding <= > $6::vector
+ smart_search.embedding <= > $6
limit
$7
offset
@@ -88,7 +88,7 @@ with
select
"assets"."id" as "assetId",
"assets"."duplicateId",
- smart_search.embedding <= > $1::vector as "distance"
+ smart_search.embedding <= > $1 as "distance"
from
"assets"
inner join "smart_search" on "assets"."id" = "smart_search"."assetId"
@@ -99,7 +99,7 @@ with
and "assets"."type" = $4
and "assets"."id" != $5::uuid
order by
- smart_search.embedding <= > $6::vector
+ smart_search.embedding <= > $6
limit
$7
)
@@ -116,7 +116,7 @@ with
select
"asset_faces"."id",
"asset_faces"."personId",
- face_search.embedding <= > $1::vector as "distance"
+ face_search.embedding <= > $1 as "distance"
from
"asset_faces"
inner join "assets" on "assets"."id" = "asset_faces"."assetId"
@@ -125,7 +125,7 @@ with
"assets"."ownerId" = any ($2::uuid [])
and "assets"."deletedAt" is null
order by
- face_search.embedding <= > $3::vector
+ face_search.embedding <= > $3
limit
$4
)
diff --git a/server/src/queries/stack.repository.sql b/server/src/queries/stack.repository.sql
index f7da019f05..54f86c94af 100644
--- a/server/src/queries/stack.repository.sql
+++ b/server/src/queries/stack.repository.sql
@@ -1,257 +1,95 @@
-- NOTE: This file is auto generated by ./sql-generator
-- StackRepository.search
-SELECT
- "StackEntity"."id" AS "StackEntity_id",
- "StackEntity"."ownerId" AS "StackEntity_ownerId",
- "StackEntity"."primaryAssetId" AS "StackEntity_primaryAssetId",
- "StackEntity__StackEntity_assets"."id" AS "StackEntity__StackEntity_assets_id",
- "StackEntity__StackEntity_assets"."deviceAssetId" AS "StackEntity__StackEntity_assets_deviceAssetId",
- "StackEntity__StackEntity_assets"."ownerId" AS "StackEntity__StackEntity_assets_ownerId",
- "StackEntity__StackEntity_assets"."libraryId" AS "StackEntity__StackEntity_assets_libraryId",
- "StackEntity__StackEntity_assets"."deviceId" AS "StackEntity__StackEntity_assets_deviceId",
- "StackEntity__StackEntity_assets"."type" AS "StackEntity__StackEntity_assets_type",
- "StackEntity__StackEntity_assets"."status" AS "StackEntity__StackEntity_assets_status",
- "StackEntity__StackEntity_assets"."originalPath" AS "StackEntity__StackEntity_assets_originalPath",
- "StackEntity__StackEntity_assets"."thumbhash" AS "StackEntity__StackEntity_assets_thumbhash",
- "StackEntity__StackEntity_assets"."encodedVideoPath" AS "StackEntity__StackEntity_assets_encodedVideoPath",
- "StackEntity__StackEntity_assets"."createdAt" AS "StackEntity__StackEntity_assets_createdAt",
- "StackEntity__StackEntity_assets"."updatedAt" AS "StackEntity__StackEntity_assets_updatedAt",
- "StackEntity__StackEntity_assets"."deletedAt" AS "StackEntity__StackEntity_assets_deletedAt",
- "StackEntity__StackEntity_assets"."fileCreatedAt" AS "StackEntity__StackEntity_assets_fileCreatedAt",
- "StackEntity__StackEntity_assets"."localDateTime" AS "StackEntity__StackEntity_assets_localDateTime",
- "StackEntity__StackEntity_assets"."fileModifiedAt" AS "StackEntity__StackEntity_assets_fileModifiedAt",
- "StackEntity__StackEntity_assets"."isFavorite" AS "StackEntity__StackEntity_assets_isFavorite",
- "StackEntity__StackEntity_assets"."isArchived" AS "StackEntity__StackEntity_assets_isArchived",
- "StackEntity__StackEntity_assets"."isExternal" AS "StackEntity__StackEntity_assets_isExternal",
- "StackEntity__StackEntity_assets"."isOffline" AS "StackEntity__StackEntity_assets_isOffline",
- "StackEntity__StackEntity_assets"."checksum" AS "StackEntity__StackEntity_assets_checksum",
- "StackEntity__StackEntity_assets"."duration" AS "StackEntity__StackEntity_assets_duration",
- "StackEntity__StackEntity_assets"."isVisible" AS "StackEntity__StackEntity_assets_isVisible",
- "StackEntity__StackEntity_assets"."livePhotoVideoId" AS "StackEntity__StackEntity_assets_livePhotoVideoId",
- "StackEntity__StackEntity_assets"."originalFileName" AS "StackEntity__StackEntity_assets_originalFileName",
- "StackEntity__StackEntity_assets"."sidecarPath" AS "StackEntity__StackEntity_assets_sidecarPath",
- "StackEntity__StackEntity_assets"."stackId" AS "StackEntity__StackEntity_assets_stackId",
- "StackEntity__StackEntity_assets"."duplicateId" AS "StackEntity__StackEntity_assets_duplicateId",
- "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."assetId" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_assetId",
- "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."description" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_description",
- "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."exifImageWidth" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_exifImageWidth",
- "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."exifImageHeight" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_exifImageHeight",
- "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."fileSizeInByte" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_fileSizeInByte",
- "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."orientation" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_orientation",
- "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."dateTimeOriginal" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_dateTimeOriginal",
- "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."modifyDate" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_modifyDate",
- "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."timeZone" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_timeZone",
- "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."latitude" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_latitude",
- "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."longitude" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_longitude",
- "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."projectionType" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_projectionType",
- "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."city" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_city",
- "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."livePhotoCID" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_livePhotoCID",
- "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."autoStackId" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_autoStackId",
- "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."state" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_state",
- "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."country" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_country",
- "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."make" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_make",
- "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."model" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_model",
- "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."lensModel" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_lensModel",
- "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."fNumber" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_fNumber",
- "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."focalLength" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_focalLength",
- "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."iso" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_iso",
- "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."exposureTime" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_exposureTime",
- "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."profileDescription" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_profileDescription",
- "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."colorspace" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_colorspace",
- "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."bitsPerSample" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_bitsPerSample",
- "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."rating" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_rating",
- "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."fps" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_fps"
-FROM
- "asset_stack" "StackEntity"
- LEFT JOIN "assets" "StackEntity__StackEntity_assets" ON "StackEntity__StackEntity_assets"."stackId" = "StackEntity"."id"
- AND (
- "StackEntity__StackEntity_assets"."deletedAt" IS NULL
- )
- LEFT JOIN "exif" "01db479afeb88793eed8e0d1dde6ccfccf1698b9" ON "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."assetId" = "StackEntity__StackEntity_assets"."id"
-WHERE
- (("StackEntity"."ownerId" = $1))
+select
+ "asset_stack".*,
+ (
+ select
+ coalesce(json_agg(agg), '[]')
+ from
+ (
+ select
+ *
+ from
+ "assets"
+ where
+ "assets"."deletedAt" is null
+ and "assets"."stackId" = "asset_stack"."id"
+ ) as agg
+ ) as "assets"
+from
+ "asset_stack"
+where
+ "asset_stack"."ownerId" = $1
-- StackRepository.delete
-SELECT DISTINCT
- "distinctAlias"."StackEntity_id" AS "ids_StackEntity_id",
- "distinctAlias"."StackEntity__StackEntity_assets_fileCreatedAt"
-FROM
+select
+ *,
(
- SELECT
- "StackEntity"."id" AS "StackEntity_id",
- "StackEntity"."ownerId" AS "StackEntity_ownerId",
- "StackEntity"."primaryAssetId" AS "StackEntity_primaryAssetId",
- "StackEntity__StackEntity_assets"."id" AS "StackEntity__StackEntity_assets_id",
- "StackEntity__StackEntity_assets"."deviceAssetId" AS "StackEntity__StackEntity_assets_deviceAssetId",
- "StackEntity__StackEntity_assets"."ownerId" AS "StackEntity__StackEntity_assets_ownerId",
- "StackEntity__StackEntity_assets"."libraryId" AS "StackEntity__StackEntity_assets_libraryId",
- "StackEntity__StackEntity_assets"."deviceId" AS "StackEntity__StackEntity_assets_deviceId",
- "StackEntity__StackEntity_assets"."type" AS "StackEntity__StackEntity_assets_type",
- "StackEntity__StackEntity_assets"."status" AS "StackEntity__StackEntity_assets_status",
- "StackEntity__StackEntity_assets"."originalPath" AS "StackEntity__StackEntity_assets_originalPath",
- "StackEntity__StackEntity_assets"."thumbhash" AS "StackEntity__StackEntity_assets_thumbhash",
- "StackEntity__StackEntity_assets"."encodedVideoPath" AS "StackEntity__StackEntity_assets_encodedVideoPath",
- "StackEntity__StackEntity_assets"."createdAt" AS "StackEntity__StackEntity_assets_createdAt",
- "StackEntity__StackEntity_assets"."updatedAt" AS "StackEntity__StackEntity_assets_updatedAt",
- "StackEntity__StackEntity_assets"."deletedAt" AS "StackEntity__StackEntity_assets_deletedAt",
- "StackEntity__StackEntity_assets"."fileCreatedAt" AS "StackEntity__StackEntity_assets_fileCreatedAt",
- "StackEntity__StackEntity_assets"."localDateTime" AS "StackEntity__StackEntity_assets_localDateTime",
- "StackEntity__StackEntity_assets"."fileModifiedAt" AS "StackEntity__StackEntity_assets_fileModifiedAt",
- "StackEntity__StackEntity_assets"."isFavorite" AS "StackEntity__StackEntity_assets_isFavorite",
- "StackEntity__StackEntity_assets"."isArchived" AS "StackEntity__StackEntity_assets_isArchived",
- "StackEntity__StackEntity_assets"."isExternal" AS "StackEntity__StackEntity_assets_isExternal",
- "StackEntity__StackEntity_assets"."isOffline" AS "StackEntity__StackEntity_assets_isOffline",
- "StackEntity__StackEntity_assets"."checksum" AS "StackEntity__StackEntity_assets_checksum",
- "StackEntity__StackEntity_assets"."duration" AS "StackEntity__StackEntity_assets_duration",
- "StackEntity__StackEntity_assets"."isVisible" AS "StackEntity__StackEntity_assets_isVisible",
- "StackEntity__StackEntity_assets"."livePhotoVideoId" AS "StackEntity__StackEntity_assets_livePhotoVideoId",
- "StackEntity__StackEntity_assets"."originalFileName" AS "StackEntity__StackEntity_assets_originalFileName",
- "StackEntity__StackEntity_assets"."sidecarPath" AS "StackEntity__StackEntity_assets_sidecarPath",
- "StackEntity__StackEntity_assets"."stackId" AS "StackEntity__StackEntity_assets_stackId",
- "StackEntity__StackEntity_assets"."duplicateId" AS "StackEntity__StackEntity_assets_duplicateId",
- "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."assetId" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_assetId",
- "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."description" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_description",
- "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."exifImageWidth" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_exifImageWidth",
- "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."exifImageHeight" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_exifImageHeight",
- "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."fileSizeInByte" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_fileSizeInByte",
- "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."orientation" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_orientation",
- "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."dateTimeOriginal" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_dateTimeOriginal",
- "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."modifyDate" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_modifyDate",
- "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."timeZone" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_timeZone",
- "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."latitude" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_latitude",
- "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."longitude" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_longitude",
- "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."projectionType" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_projectionType",
- "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."city" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_city",
- "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."livePhotoCID" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_livePhotoCID",
- "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."autoStackId" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_autoStackId",
- "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."state" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_state",
- "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."country" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_country",
- "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."make" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_make",
- "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."model" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_model",
- "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."lensModel" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_lensModel",
- "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."fNumber" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_fNumber",
- "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."focalLength" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_focalLength",
- "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."iso" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_iso",
- "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."exposureTime" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_exposureTime",
- "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."profileDescription" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_profileDescription",
- "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."colorspace" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_colorspace",
- "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."bitsPerSample" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_bitsPerSample",
- "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."rating" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_rating",
- "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."fps" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_fps",
- "3e3064f11b97177a1e1ce3c77ecf32850343aba1"."id" AS "3e3064f11b97177a1e1ce3c77ecf32850343aba1_id",
- "3e3064f11b97177a1e1ce3c77ecf32850343aba1"."value" AS "3e3064f11b97177a1e1ce3c77ecf32850343aba1_value",
- "3e3064f11b97177a1e1ce3c77ecf32850343aba1"."createdAt" AS "3e3064f11b97177a1e1ce3c77ecf32850343aba1_createdAt",
- "3e3064f11b97177a1e1ce3c77ecf32850343aba1"."updatedAt" AS "3e3064f11b97177a1e1ce3c77ecf32850343aba1_updatedAt",
- "3e3064f11b97177a1e1ce3c77ecf32850343aba1"."color" AS "3e3064f11b97177a1e1ce3c77ecf32850343aba1_color",
- "3e3064f11b97177a1e1ce3c77ecf32850343aba1"."parentId" AS "3e3064f11b97177a1e1ce3c77ecf32850343aba1_parentId",
- "3e3064f11b97177a1e1ce3c77ecf32850343aba1"."userId" AS "3e3064f11b97177a1e1ce3c77ecf32850343aba1_userId"
- FROM
- "asset_stack" "StackEntity"
- LEFT JOIN "assets" "StackEntity__StackEntity_assets" ON "StackEntity__StackEntity_assets"."stackId" = "StackEntity"."id"
- AND (
- "StackEntity__StackEntity_assets"."deletedAt" IS NULL
- )
- LEFT JOIN "exif" "01db479afeb88793eed8e0d1dde6ccfccf1698b9" ON "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."assetId" = "StackEntity__StackEntity_assets"."id"
- LEFT JOIN "tag_asset" "4f1c9474d4596aede2814ee2eb938eecf7a93b95" ON "4f1c9474d4596aede2814ee2eb938eecf7a93b95"."assetsId" = "StackEntity__StackEntity_assets"."id"
- LEFT JOIN "tags" "3e3064f11b97177a1e1ce3c77ecf32850343aba1" ON "3e3064f11b97177a1e1ce3c77ecf32850343aba1"."id" = "4f1c9474d4596aede2814ee2eb938eecf7a93b95"."tagsId"
- WHERE
- (("StackEntity"."id" = $1))
- ) "distinctAlias"
-ORDER BY
- "distinctAlias"."StackEntity__StackEntity_assets_fileCreatedAt" ASC,
- "StackEntity_id" ASC
-LIMIT
- 1
+ select
+ coalesce(json_agg(agg), '[]')
+ from
+ (
+ select
+ *,
+ (
+ select
+ coalesce(json_agg(agg), '[]')
+ from
+ (
+ select
+ "tags".*
+ from
+ "tags"
+ inner join "tag_asset" on "tags"."id" = "tag_asset"."tagsId"
+ where
+ "tag_asset"."assetsId" = "assets"."id"
+ ) as agg
+ ) as "tags"
+ from
+ "assets"
+ where
+ "assets"."deletedAt" is null
+ and "assets"."stackId" = "asset_stack"."id"
+ ) as agg
+ ) as "assets"
+from
+ "asset_stack"
+where
+ "id" = $1::uuid
-- StackRepository.getById
-SELECT DISTINCT
- "distinctAlias"."StackEntity_id" AS "ids_StackEntity_id",
- "distinctAlias"."StackEntity__StackEntity_assets_fileCreatedAt"
-FROM
+select
+ *,
(
- SELECT
- "StackEntity"."id" AS "StackEntity_id",
- "StackEntity"."ownerId" AS "StackEntity_ownerId",
- "StackEntity"."primaryAssetId" AS "StackEntity_primaryAssetId",
- "StackEntity__StackEntity_assets"."id" AS "StackEntity__StackEntity_assets_id",
- "StackEntity__StackEntity_assets"."deviceAssetId" AS "StackEntity__StackEntity_assets_deviceAssetId",
- "StackEntity__StackEntity_assets"."ownerId" AS "StackEntity__StackEntity_assets_ownerId",
- "StackEntity__StackEntity_assets"."libraryId" AS "StackEntity__StackEntity_assets_libraryId",
- "StackEntity__StackEntity_assets"."deviceId" AS "StackEntity__StackEntity_assets_deviceId",
- "StackEntity__StackEntity_assets"."type" AS "StackEntity__StackEntity_assets_type",
- "StackEntity__StackEntity_assets"."status" AS "StackEntity__StackEntity_assets_status",
- "StackEntity__StackEntity_assets"."originalPath" AS "StackEntity__StackEntity_assets_originalPath",
- "StackEntity__StackEntity_assets"."thumbhash" AS "StackEntity__StackEntity_assets_thumbhash",
- "StackEntity__StackEntity_assets"."encodedVideoPath" AS "StackEntity__StackEntity_assets_encodedVideoPath",
- "StackEntity__StackEntity_assets"."createdAt" AS "StackEntity__StackEntity_assets_createdAt",
- "StackEntity__StackEntity_assets"."updatedAt" AS "StackEntity__StackEntity_assets_updatedAt",
- "StackEntity__StackEntity_assets"."deletedAt" AS "StackEntity__StackEntity_assets_deletedAt",
- "StackEntity__StackEntity_assets"."fileCreatedAt" AS "StackEntity__StackEntity_assets_fileCreatedAt",
- "StackEntity__StackEntity_assets"."localDateTime" AS "StackEntity__StackEntity_assets_localDateTime",
- "StackEntity__StackEntity_assets"."fileModifiedAt" AS "StackEntity__StackEntity_assets_fileModifiedAt",
- "StackEntity__StackEntity_assets"."isFavorite" AS "StackEntity__StackEntity_assets_isFavorite",
- "StackEntity__StackEntity_assets"."isArchived" AS "StackEntity__StackEntity_assets_isArchived",
- "StackEntity__StackEntity_assets"."isExternal" AS "StackEntity__StackEntity_assets_isExternal",
- "StackEntity__StackEntity_assets"."isOffline" AS "StackEntity__StackEntity_assets_isOffline",
- "StackEntity__StackEntity_assets"."checksum" AS "StackEntity__StackEntity_assets_checksum",
- "StackEntity__StackEntity_assets"."duration" AS "StackEntity__StackEntity_assets_duration",
- "StackEntity__StackEntity_assets"."isVisible" AS "StackEntity__StackEntity_assets_isVisible",
- "StackEntity__StackEntity_assets"."livePhotoVideoId" AS "StackEntity__StackEntity_assets_livePhotoVideoId",
- "StackEntity__StackEntity_assets"."originalFileName" AS "StackEntity__StackEntity_assets_originalFileName",
- "StackEntity__StackEntity_assets"."sidecarPath" AS "StackEntity__StackEntity_assets_sidecarPath",
- "StackEntity__StackEntity_assets"."stackId" AS "StackEntity__StackEntity_assets_stackId",
- "StackEntity__StackEntity_assets"."duplicateId" AS "StackEntity__StackEntity_assets_duplicateId",
- "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."assetId" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_assetId",
- "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."description" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_description",
- "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."exifImageWidth" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_exifImageWidth",
- "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."exifImageHeight" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_exifImageHeight",
- "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."fileSizeInByte" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_fileSizeInByte",
- "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."orientation" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_orientation",
- "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."dateTimeOriginal" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_dateTimeOriginal",
- "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."modifyDate" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_modifyDate",
- "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."timeZone" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_timeZone",
- "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."latitude" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_latitude",
- "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."longitude" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_longitude",
- "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."projectionType" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_projectionType",
- "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."city" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_city",
- "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."livePhotoCID" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_livePhotoCID",
- "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."autoStackId" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_autoStackId",
- "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."state" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_state",
- "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."country" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_country",
- "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."make" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_make",
- "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."model" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_model",
- "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."lensModel" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_lensModel",
- "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."fNumber" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_fNumber",
- "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."focalLength" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_focalLength",
- "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."iso" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_iso",
- "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."exposureTime" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_exposureTime",
- "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."profileDescription" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_profileDescription",
- "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."colorspace" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_colorspace",
- "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."bitsPerSample" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_bitsPerSample",
- "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."rating" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_rating",
- "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."fps" AS "01db479afeb88793eed8e0d1dde6ccfccf1698b9_fps",
- "3e3064f11b97177a1e1ce3c77ecf32850343aba1"."id" AS "3e3064f11b97177a1e1ce3c77ecf32850343aba1_id",
- "3e3064f11b97177a1e1ce3c77ecf32850343aba1"."value" AS "3e3064f11b97177a1e1ce3c77ecf32850343aba1_value",
- "3e3064f11b97177a1e1ce3c77ecf32850343aba1"."createdAt" AS "3e3064f11b97177a1e1ce3c77ecf32850343aba1_createdAt",
- "3e3064f11b97177a1e1ce3c77ecf32850343aba1"."updatedAt" AS "3e3064f11b97177a1e1ce3c77ecf32850343aba1_updatedAt",
- "3e3064f11b97177a1e1ce3c77ecf32850343aba1"."color" AS "3e3064f11b97177a1e1ce3c77ecf32850343aba1_color",
- "3e3064f11b97177a1e1ce3c77ecf32850343aba1"."parentId" AS "3e3064f11b97177a1e1ce3c77ecf32850343aba1_parentId",
- "3e3064f11b97177a1e1ce3c77ecf32850343aba1"."userId" AS "3e3064f11b97177a1e1ce3c77ecf32850343aba1_userId"
- FROM
- "asset_stack" "StackEntity"
- LEFT JOIN "assets" "StackEntity__StackEntity_assets" ON "StackEntity__StackEntity_assets"."stackId" = "StackEntity"."id"
- AND (
- "StackEntity__StackEntity_assets"."deletedAt" IS NULL
- )
- LEFT JOIN "exif" "01db479afeb88793eed8e0d1dde6ccfccf1698b9" ON "01db479afeb88793eed8e0d1dde6ccfccf1698b9"."assetId" = "StackEntity__StackEntity_assets"."id"
- LEFT JOIN "tag_asset" "4f1c9474d4596aede2814ee2eb938eecf7a93b95" ON "4f1c9474d4596aede2814ee2eb938eecf7a93b95"."assetsId" = "StackEntity__StackEntity_assets"."id"
- LEFT JOIN "tags" "3e3064f11b97177a1e1ce3c77ecf32850343aba1" ON "3e3064f11b97177a1e1ce3c77ecf32850343aba1"."id" = "4f1c9474d4596aede2814ee2eb938eecf7a93b95"."tagsId"
- WHERE
- (("StackEntity"."id" = $1))
- ) "distinctAlias"
-ORDER BY
- "distinctAlias"."StackEntity__StackEntity_assets_fileCreatedAt" ASC,
- "StackEntity_id" ASC
-LIMIT
- 1
+ select
+ coalesce(json_agg(agg), '[]')
+ from
+ (
+ select
+ *,
+ (
+ select
+ coalesce(json_agg(agg), '[]')
+ from
+ (
+ select
+ "tags".*
+ from
+ "tags"
+ inner join "tag_asset" on "tags"."id" = "tag_asset"."tagsId"
+ where
+ "tag_asset"."assetsId" = "assets"."id"
+ ) as agg
+ ) as "tags"
+ from
+ "assets"
+ where
+ "assets"."deletedAt" is null
+ and "assets"."stackId" = "asset_stack"."id"
+ ) as agg
+ ) as "assets"
+from
+ "asset_stack"
+where
+ "id" = $1::uuid
diff --git a/server/src/repositories/access.repository.ts b/server/src/repositories/access.repository.ts
index 15288b94fa..9fa8b6243c 100644
--- a/server/src/repositories/access.repository.ts
+++ b/server/src/repositories/access.repository.ts
@@ -1,33 +1,18 @@
-import { Injectable } from '@nestjs/common';
import { Kysely, sql } from 'kysely';
import { InjectKysely } from 'nestjs-kysely';
import { DB } from 'src/db';
import { ChunkedSet, DummyValue, GenerateSql } from 'src/decorators';
-
import { AlbumUserRole } from 'src/enum';
-import { IAccessRepository } from 'src/interfaces/access.interface';
import { asUuid } from 'src/utils/database';
-type IActivityAccess = IAccessRepository['activity'];
-type IAlbumAccess = IAccessRepository['album'];
-type IAssetAccess = IAccessRepository['asset'];
-type IAuthDeviceAccess = IAccessRepository['authDevice'];
-type IMemoryAccess = IAccessRepository['memory'];
-type IPersonAccess = IAccessRepository['person'];
-type IPartnerAccess = IAccessRepository['partner'];
-type IStackAccess = IAccessRepository['stack'];
-type ITagAccess = IAccessRepository['tag'];
-type ITimelineAccess = IAccessRepository['timeline'];
-
-@Injectable()
-class ActivityAccess implements IActivityAccess {
+class ActivityAccess {
constructor(private db: Kysely) {}
@GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID_SET] })
@ChunkedSet({ paramIndex: 1 })
- async checkOwnerAccess(userId: string, activityIds: Set): Promise> {
+ async checkOwnerAccess(userId: string, activityIds: Set) {
if (activityIds.size === 0) {
- return new Set();
+ return new Set();
}
return this.db
@@ -36,17 +21,14 @@ class ActivityAccess implements IActivityAccess {
.where('activity.id', 'in', [...activityIds])
.where('activity.userId', '=', userId)
.execute()
- .then((activities) => {
- console.log('activities', activities);
- return new Set(activities.map((activity) => activity.id));
- });
+ .then((activities) => new Set(activities.map((activity) => activity.id)));
}
@GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID_SET] })
@ChunkedSet({ paramIndex: 1 })
- async checkAlbumOwnerAccess(userId: string, activityIds: Set): Promise> {
+ async checkAlbumOwnerAccess(userId: string, activityIds: Set) {
if (activityIds.size === 0) {
- return new Set();
+ return new Set();
}
return this.db
@@ -61,9 +43,9 @@ class ActivityAccess implements IActivityAccess {
@GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID_SET] })
@ChunkedSet({ paramIndex: 1 })
- async checkCreateAccess(userId: string, albumIds: Set): Promise> {
+ async checkCreateAccess(userId: string, albumIds: Set) {
if (albumIds.size === 0) {
- return new Set();
+ return new Set();
}
return this.db
@@ -80,14 +62,14 @@ class ActivityAccess implements IActivityAccess {
}
}
-class AlbumAccess implements IAlbumAccess {
+class AlbumAccess {
constructor(private db: Kysely) {}
@GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID_SET] })
@ChunkedSet({ paramIndex: 1 })
- async checkOwnerAccess(userId: string, albumIds: Set): Promise> {
+ async checkOwnerAccess(userId: string, albumIds: Set) {
if (albumIds.size === 0) {
- return new Set();
+ return new Set();
}
return this.db
@@ -102,9 +84,9 @@ class AlbumAccess implements IAlbumAccess {
@GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID_SET] })
@ChunkedSet({ paramIndex: 1 })
- async checkSharedAlbumAccess(userId: string, albumIds: Set, access: AlbumUserRole): Promise> {
+ async checkSharedAlbumAccess(userId: string, albumIds: Set, access: AlbumUserRole) {
if (albumIds.size === 0) {
- return new Set();
+ return new Set();
}
const accessRole =
@@ -125,9 +107,9 @@ class AlbumAccess implements IAlbumAccess {
@GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID_SET] })
@ChunkedSet({ paramIndex: 1 })
- async checkSharedLinkAccess(sharedLinkId: string, albumIds: Set): Promise> {
+ async checkSharedLinkAccess(sharedLinkId: string, albumIds: Set) {
if (albumIds.size === 0) {
- return new Set();
+ return new Set();
}
return this.db
@@ -142,14 +124,14 @@ class AlbumAccess implements IAlbumAccess {
}
}
-class AssetAccess implements IAssetAccess {
+class AssetAccess {
constructor(private db: Kysely) {}
@GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID_SET] })
@ChunkedSet({ paramIndex: 1 })
- async checkAlbumAccess(userId: string, assetIds: Set): Promise> {
+ async checkAlbumAccess(userId: string, assetIds: Set) {
if (assetIds.size === 0) {
- return new Set();
+ return new Set();
}
return this.db
@@ -185,9 +167,9 @@ class AssetAccess implements IAssetAccess {
@GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID_SET] })
@ChunkedSet({ paramIndex: 1 })
- async checkOwnerAccess(userId: string, assetIds: Set): Promise> {
+ async checkOwnerAccess(userId: string, assetIds: Set) {
if (assetIds.size === 0) {
- return new Set();
+ return new Set();
}
return this.db
@@ -201,9 +183,9 @@ class AssetAccess implements IAssetAccess {
@GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID_SET] })
@ChunkedSet({ paramIndex: 1 })
- async checkPartnerAccess(userId: string, assetIds: Set): Promise> {
+ async checkPartnerAccess(userId: string, assetIds: Set) {
if (assetIds.size === 0) {
- return new Set();
+ return new Set();
}
return this.db
@@ -224,9 +206,9 @@ class AssetAccess implements IAssetAccess {
@GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID_SET] })
@ChunkedSet({ paramIndex: 1 })
- async checkSharedLinkAccess(sharedLinkId: string, assetIds: Set): Promise> {
+ async checkSharedLinkAccess(sharedLinkId: string, assetIds: Set) {
if (assetIds.size === 0) {
- return new Set();
+ return new Set();
}
return this.db
@@ -276,14 +258,14 @@ class AssetAccess implements IAssetAccess {
}
}
-class AuthDeviceAccess implements IAuthDeviceAccess {
+class AuthDeviceAccess {
constructor(private db: Kysely) {}
@GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID_SET] })
@ChunkedSet({ paramIndex: 1 })
- async checkOwnerAccess(userId: string, deviceIds: Set): Promise> {
+ async checkOwnerAccess(userId: string, deviceIds: Set) {
if (deviceIds.size === 0) {
- return new Set();
+ return new Set();
}
return this.db
@@ -296,14 +278,14 @@ class AuthDeviceAccess implements IAuthDeviceAccess {
}
}
-class StackAccess implements IStackAccess {
+class StackAccess {
constructor(private db: Kysely) {}
@GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID_SET] })
@ChunkedSet({ paramIndex: 1 })
- async checkOwnerAccess(userId: string, stackIds: Set): Promise> {
+ async checkOwnerAccess(userId: string, stackIds: Set) {
if (stackIds.size === 0) {
- return new Set();
+ return new Set();
}
return this.db
@@ -316,14 +298,14 @@ class StackAccess implements IStackAccess {
}
}
-class TimelineAccess implements ITimelineAccess {
+class TimelineAccess {
constructor(private db: Kysely) {}
@GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID_SET] })
@ChunkedSet({ paramIndex: 1 })
- async checkPartnerAccess(userId: string, partnerIds: Set): Promise> {
+ async checkPartnerAccess(userId: string, partnerIds: Set) {
if (partnerIds.size === 0) {
- return new Set();
+ return new Set();
}
return this.db
@@ -336,14 +318,14 @@ class TimelineAccess implements ITimelineAccess {
}
}
-class MemoryAccess implements IMemoryAccess {
+class MemoryAccess {
constructor(private db: Kysely) {}
@GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID_SET] })
@ChunkedSet({ paramIndex: 1 })
- async checkOwnerAccess(userId: string, memoryIds: Set): Promise> {
+ async checkOwnerAccess(userId: string, memoryIds: Set) {
if (memoryIds.size === 0) {
- return new Set();
+ return new Set();
}
return this.db
@@ -357,14 +339,14 @@ class MemoryAccess implements IMemoryAccess {
}
}
-class PersonAccess implements IPersonAccess {
+class PersonAccess {
constructor(private db: Kysely) {}
@GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID_SET] })
@ChunkedSet({ paramIndex: 1 })
- async checkOwnerAccess(userId: string, personIds: Set): Promise> {
+ async checkOwnerAccess(userId: string, personIds: Set) {
if (personIds.size === 0) {
- return new Set();
+ return new Set();
}
return this.db
@@ -378,9 +360,9 @@ class PersonAccess implements IPersonAccess {
@GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID_SET] })
@ChunkedSet({ paramIndex: 1 })
- async checkFaceOwnerAccess(userId: string, assetFaceIds: Set): Promise> {
+ async checkFaceOwnerAccess(userId: string, assetFaceIds: Set) {
if (assetFaceIds.size === 0) {
- return new Set();
+ return new Set();
}
return this.db
@@ -396,14 +378,14 @@ class PersonAccess implements IPersonAccess {
}
}
-class PartnerAccess implements IPartnerAccess {
+class PartnerAccess {
constructor(private db: Kysely) {}
@GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID_SET] })
@ChunkedSet({ paramIndex: 1 })
- async checkUpdateAccess(userId: string, partnerIds: Set): Promise> {
+ async checkUpdateAccess(userId: string, partnerIds: Set) {
if (partnerIds.size === 0) {
- return new Set();
+ return new Set();
}
return this.db
@@ -416,14 +398,14 @@ class PartnerAccess implements IPartnerAccess {
}
}
-class TagAccess implements ITagAccess {
+class TagAccess {
constructor(private db: Kysely) {}
@GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID_SET] })
@ChunkedSet({ paramIndex: 1 })
- async checkOwnerAccess(userId: string, tagIds: Set): Promise> {
+ async checkOwnerAccess(userId: string, tagIds: Set) {
if (tagIds.size === 0) {
- return new Set();
+ return new Set();
}
return this.db
@@ -436,17 +418,17 @@ class TagAccess implements ITagAccess {
}
}
-export class AccessRepository implements IAccessRepository {
- activity: IActivityAccess;
- album: IAlbumAccess;
- asset: IAssetAccess;
- authDevice: IAuthDeviceAccess;
- memory: IMemoryAccess;
- person: IPersonAccess;
- partner: IPartnerAccess;
- stack: IStackAccess;
- tag: ITagAccess;
- timeline: ITimelineAccess;
+export class AccessRepository {
+ activity: ActivityAccess;
+ album: AlbumAccess;
+ asset: AssetAccess;
+ authDevice: AuthDeviceAccess;
+ memory: MemoryAccess;
+ person: PersonAccess;
+ partner: PartnerAccess;
+ stack: StackAccess;
+ tag: TagAccess;
+ timeline: TimelineAccess;
constructor(@InjectKysely() db: Kysely) {
this.activity = new ActivityAccess(db);
diff --git a/server/src/repositories/album.repository.ts b/server/src/repositories/album.repository.ts
index 8ac352e945..bae91349f5 100644
--- a/server/src/repositories/album.repository.ts
+++ b/server/src/repositories/album.repository.ts
@@ -1,72 +1,116 @@
import { Injectable } from '@nestjs/common';
-import { InjectDataSource, InjectRepository } from '@nestjs/typeorm';
+import { InjectRepository } from '@nestjs/typeorm';
+import { ExpressionBuilder, Insertable, Kysely, sql, Updateable } from 'kysely';
+import { jsonArrayFrom, jsonObjectFrom } from 'kysely/helpers/postgres';
+import { InjectKysely } from 'nestjs-kysely';
+import { Albums, DB } from 'src/db';
import { Chunked, ChunkedArray, ChunkedSet, DummyValue, GenerateSql } from 'src/decorators';
+import { AlbumUserCreateDto } from 'src/dtos/album.dto';
import { AlbumEntity } from 'src/entities/album.entity';
-import { AssetEntity } from 'src/entities/asset.entity';
import { AlbumAssetCount, AlbumInfoOptions, IAlbumRepository } from 'src/interfaces/album.interface';
-import {
- DataSource,
- EntityManager,
- FindOptionsOrder,
- FindOptionsRelations,
- In,
- IsNull,
- Not,
- Repository,
-} from 'typeorm';
+import { Repository } from 'typeorm';
-const withoutDeletedUsers = (album: T) => {
- if (album) {
- album.albumUsers = album.albumUsers.filter((albumUser) => albumUser.user && !albumUser.user.deletedAt);
- }
- return album;
+const userColumns = [
+ 'id',
+ 'email',
+ 'createdAt',
+ 'profileImagePath',
+ 'isAdmin',
+ 'shouldChangePassword',
+ 'deletedAt',
+ 'oauthId',
+ 'updatedAt',
+ 'storageLabel',
+ 'name',
+ 'quotaSizeInBytes',
+ 'quotaUsageInBytes',
+ 'status',
+ 'profileChangedAt',
+] as const;
+
+const withOwner = (eb: ExpressionBuilder) => {
+ return jsonObjectFrom(eb.selectFrom('users').select(userColumns).whereRef('users.id', '=', 'albums.ownerId')).as(
+ 'owner',
+ );
+};
+
+const withAlbumUsers = (eb: ExpressionBuilder) => {
+ return jsonArrayFrom(
+ eb
+ .selectFrom('albums_shared_users_users as album_users')
+ .selectAll('album_users')
+ .select((eb) =>
+ jsonObjectFrom(eb.selectFrom('users').select(userColumns).whereRef('users.id', '=', 'album_users.usersId')).as(
+ 'user',
+ ),
+ )
+ .whereRef('album_users.albumsId', '=', 'albums.id'),
+ ).as('albumUsers');
+};
+
+const withSharedLink = (eb: ExpressionBuilder) => {
+ return jsonArrayFrom(eb.selectFrom('shared_links').selectAll().whereRef('shared_links.albumId', '=', 'albums.id')).as(
+ 'sharedLinks',
+ );
+};
+
+const withAssets = (eb: ExpressionBuilder) => {
+ return eb
+ .selectFrom((eb) =>
+ eb
+ .selectFrom('assets')
+ .selectAll('assets')
+ .innerJoin('exif', 'assets.id', 'exif.assetId')
+ .select((eb) => eb.fn.toJson('exif').as('exifInfo'))
+ .innerJoin('albums_assets_assets', 'albums_assets_assets.assetsId', 'assets.id')
+ .whereRef('albums_assets_assets.albumsId', '=', 'albums.id')
+ .orderBy('assets.fileCreatedAt', 'desc')
+ .as('asset'),
+ )
+ .select((eb) => eb.fn.jsonAgg('asset').as('assets'))
+ .as('assets');
};
@Injectable()
export class AlbumRepository implements IAlbumRepository {
constructor(
- @InjectRepository(AssetEntity) private assetRepository: Repository,
@InjectRepository(AlbumEntity) private repository: Repository,
- @InjectDataSource() private dataSource: DataSource,
+ @InjectKysely() private db: Kysely,
) {}
@GenerateSql({ params: [DummyValue.UUID, {}] })
- async getById(id: string, options: AlbumInfoOptions): Promise {
- const relations: FindOptionsRelations = {
- owner: true,
- albumUsers: { user: true },
- assets: false,
- sharedLinks: true,
- };
-
- const order: FindOptionsOrder = {};
-
- if (options.withAssets) {
- relations.assets = {
- exifInfo: true,
- };
-
- order.assets = {
- fileCreatedAt: 'DESC',
- };
- }
-
- const album = await this.repository.findOne({ where: { id }, relations, order });
- return withoutDeletedUsers(album);
+ async getById(id: string, options: AlbumInfoOptions): Promise {
+ return this.db
+ .selectFrom('albums')
+ .selectAll('albums')
+ .where('albums.id', '=', id)
+ .where('albums.deletedAt', 'is', null)
+ .select(withOwner)
+ .select(withAlbumUsers)
+ .select(withSharedLink)
+ .$if(options.withAssets, (eb) => eb.select(withAssets))
+ .executeTakeFirst() as Promise;
}
@GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID] })
async getByAssetId(ownerId: string, assetId: string): Promise {
- const albums = await this.repository.find({
- where: [
- { ownerId, assets: { id: assetId } },
- { albumUsers: { userId: ownerId }, assets: { id: assetId } },
- ],
- relations: { owner: true, albumUsers: { user: true } },
- order: { createdAt: 'DESC' },
- });
-
- return albums.map((album) => withoutDeletedUsers(album));
+ return this.db
+ .selectFrom('albums')
+ .selectAll('albums')
+ .leftJoin('albums_assets_assets as album_assets', 'album_assets.albumsId', 'albums.id')
+ .leftJoin('albums_shared_users_users as album_users', 'album_users.albumsId', 'albums.id')
+ .where((eb) =>
+ eb.or([
+ eb.and([eb('albums.ownerId', '=', ownerId), eb('album_assets.assetsId', '=', assetId)]),
+ eb.and([eb('album_users.usersId', '=', ownerId), eb('album_assets.assetsId', '=', assetId)]),
+ ]),
+ )
+ .where('albums.deletedAt', 'is', null)
+ .orderBy('albums.createdAt', 'desc')
+ .select(withOwner)
+ .select(withAlbumUsers)
+ .orderBy('albums.createdAt', 'desc')
+ .execute() as unknown as Promise;
}
@GenerateSql({ params: [[DummyValue.UUID]] })
@@ -77,36 +121,38 @@ export class AlbumRepository implements IAlbumRepository {
return [];
}
- // Only possible with query builder because of GROUP BY.
- const albumMetadatas = await this.repository
- .createQueryBuilder('album')
- .select('album.id')
- .addSelect('MIN(assets.fileCreatedAt)', 'start_date')
- .addSelect('MAX(assets.fileCreatedAt)', 'end_date')
- .addSelect('COUNT(assets.id)', 'asset_count')
- .leftJoin('albums_assets_assets', 'album_assets', 'album_assets.albumsId = album.id')
- .leftJoin('assets', 'assets', 'assets.id = album_assets.assetsId')
- .where('album.id IN (:...ids)', { ids })
- .groupBy('album.id')
- .getRawMany();
+ const metadatas = await this.db
+ .selectFrom('albums')
+ .leftJoin('albums_assets_assets as album_assets', 'album_assets.albumsId', 'albums.id')
+ .leftJoin('assets', 'assets.id', 'album_assets.assetsId')
+ .select('albums.id')
+ .select((eb) => eb.fn.min('assets.fileCreatedAt').as('startDate'))
+ .select((eb) => eb.fn.max('assets.fileCreatedAt').as('endDate'))
+ .select((eb) => eb.fn.count('assets.id').as('assetCount'))
+ .where('albums.id', 'in', ids)
+ .groupBy('albums.id')
+ .execute();
- return albumMetadatas.map((metadatas) => ({
- albumId: metadatas['album_id'],
- assetCount: Number(metadatas['asset_count']),
- startDate: metadatas['end_date'] ? new Date(metadatas['start_date']) : undefined,
- endDate: metadatas['end_date'] ? new Date(metadatas['end_date']) : undefined,
+ return metadatas.map((metadatas) => ({
+ albumId: metadatas.id,
+ assetCount: Number(metadatas.assetCount),
+ startDate: metadatas.startDate ? new Date(metadatas.startDate) : undefined,
+ endDate: metadatas.endDate ? new Date(metadatas.endDate) : undefined,
}));
}
@GenerateSql({ params: [DummyValue.UUID] })
async getOwned(ownerId: string): Promise {
- const albums = await this.repository.find({
- relations: { albumUsers: { user: true }, sharedLinks: true, owner: true },
- where: { ownerId },
- order: { createdAt: 'DESC' },
- });
-
- return albums.map((album) => withoutDeletedUsers(album));
+ return this.db
+ .selectFrom('albums')
+ .selectAll('albums')
+ .select(withOwner)
+ .select(withAlbumUsers)
+ .select(withSharedLink)
+ .where('albums.ownerId', '=', ownerId)
+ .where('albums.deletedAt', 'is', null)
+ .orderBy('albums.createdAt', 'desc')
+ .execute() as unknown as Promise;
}
/**
@@ -114,17 +160,25 @@ export class AlbumRepository implements IAlbumRepository {
*/
@GenerateSql({ params: [DummyValue.UUID] })
async getShared(ownerId: string): Promise {
- const albums = await this.repository.find({
- relations: { albumUsers: { user: true }, sharedLinks: true, owner: true },
- where: [
- { albumUsers: { userId: ownerId } },
- { sharedLinks: { userId: ownerId } },
- { ownerId, albumUsers: { user: Not(IsNull()) } },
- ],
- order: { createdAt: 'DESC' },
- });
-
- return albums.map((album) => withoutDeletedUsers(album));
+ return this.db
+ .selectFrom('albums')
+ .selectAll('albums')
+ .distinctOn('albums.createdAt')
+ .leftJoin('albums_shared_users_users as shared_albums', 'shared_albums.albumsId', 'albums.id')
+ .leftJoin('shared_links', 'shared_links.albumId', 'albums.id')
+ .where((eb) =>
+ eb.or([
+ eb('shared_albums.usersId', '=', ownerId),
+ eb('shared_links.userId', '=', ownerId),
+ eb.and([eb('albums.ownerId', '=', ownerId), eb('shared_albums.usersId', 'is not', null)]),
+ ]),
+ )
+ .where('albums.deletedAt', 'is', null)
+ .select(withAlbumUsers)
+ .select(withOwner)
+ .select(withSharedLink)
+ .orderBy('albums.createdAt', 'desc')
+ .execute() as unknown as Promise;
}
/**
@@ -132,35 +186,37 @@ export class AlbumRepository implements IAlbumRepository {
*/
@GenerateSql({ params: [DummyValue.UUID] })
async getNotShared(ownerId: string): Promise {
- const albums = await this.repository.find({
- relations: { albumUsers: true, sharedLinks: true, owner: true },
- where: { ownerId, albumUsers: { user: IsNull() }, sharedLinks: { id: IsNull() } },
- order: { createdAt: 'DESC' },
- });
-
- return albums.map((album) => withoutDeletedUsers(album));
+ return this.db
+ .selectFrom('albums')
+ .selectAll('albums')
+ .distinctOn('albums.createdAt')
+ .leftJoin('albums_shared_users_users as shared_albums', 'shared_albums.albumsId', 'albums.id')
+ .leftJoin('shared_links', 'shared_links.albumId', 'albums.id')
+ .where('albums.ownerId', '=', ownerId)
+ .where('shared_albums.usersId', 'is', null)
+ .where('shared_links.userId', 'is', null)
+ .where('albums.deletedAt', 'is', null)
+ .select(withAlbumUsers)
+ .select(withOwner)
+ .select(withSharedLink)
+ .orderBy('albums.createdAt', 'desc')
+ .execute() as unknown as Promise;
}
async restoreAll(userId: string): Promise {
- await this.repository.restore({ ownerId: userId });
+ await this.db.updateTable('albums').set({ deletedAt: null }).where('ownerId', '=', userId).execute();
}
async softDeleteAll(userId: string): Promise {
- await this.repository.softDelete({ ownerId: userId });
+ await this.db.updateTable('albums').set({ deletedAt: new Date() }).where('ownerId', '=', userId).execute();
}
async deleteAll(userId: string): Promise {
- await this.repository.delete({ ownerId: userId });
+ await this.db.deleteFrom('albums').where('ownerId', '=', userId).execute();
}
async removeAsset(assetId: string): Promise {
- // Using dataSource, because there is no direct access to albums_assets_assets.
- await this.dataSource
- .createQueryBuilder()
- .delete()
- .from('albums_assets_assets')
- .where('"albums_assets_assets"."assetsId" = :assetId', { assetId })
- .execute();
+ await this.db.deleteFrom('albums_assets_assets').where('albums_assets_assets.assetsId', '=', assetId).execute();
}
@Chunked({ paramIndex: 1 })
@@ -169,14 +225,10 @@ export class AlbumRepository implements IAlbumRepository {
return;
}
- await this.dataSource
- .createQueryBuilder()
- .delete()
- .from('albums_assets_assets')
- .where({
- albumsId: albumId,
- assetsId: In(assetIds),
- })
+ await this.db
+ .deleteFrom('albums_assets_assets')
+ .where('albums_assets_assets.albumsId', '=', albumId)
+ .where('albums_assets_assets.assetsId', 'in', assetIds)
.execute();
}
@@ -194,73 +246,80 @@ export class AlbumRepository implements IAlbumRepository {
return new Set();
}
- const results = await this.dataSource
- .createQueryBuilder()
- .select('albums_assets.assetsId', 'assetId')
- .from('albums_assets_assets', 'albums_assets')
- .where('"albums_assets"."albumsId" = :albumId', { albumId })
- .andWhere('"albums_assets"."assetsId" IN (:...assetIds)', { assetIds })
- .getRawMany<{ assetId: string }>();
-
- return new Set(results.map(({ assetId }) => assetId));
+ return this.db
+ .selectFrom('albums_assets_assets')
+ .selectAll()
+ .where('albums_assets_assets.albumsId', '=', albumId)
+ .where('albums_assets_assets.assetsId', 'in', assetIds)
+ .execute()
+ .then((results) => new Set(results.map(({ assetsId }) => assetsId)));
}
async addAssetIds(albumId: string, assetIds: string[]): Promise {
- await this.addAssets(this.dataSource.manager, albumId, assetIds);
+ await this.addAssets(this.db, albumId, assetIds);
}
- create(album: Partial): Promise {
- return this.dataSource.transaction