mirror of
https://github.com/immich-app/immich.git
synced 2025-07-09 03:04:16 -04:00
Merge branch 'main' into rknn-toolkit-lite2
This commit is contained in:
commit
4e42fbc091
@ -1,4 +1,4 @@
|
||||
FROM node:22.12.0-alpine3.20@sha256:96cc8323e25c8cc6ddcb8b965e135cfd57846e8003ec0d7bcec16c5fd5f6d39f AS core
|
||||
FROM node:22.13.0-alpine3.20@sha256:db8dcb90326a0116375414e9a7c068a6b87a4422b7da37b5c6cd026f7c7835d3 AS core
|
||||
|
||||
WORKDIR /usr/src/open-api/typescript-sdk
|
||||
COPY open-api/typescript-sdk/package*.json open-api/typescript-sdk/tsconfig*.json ./
|
||||
|
@ -71,6 +71,7 @@ services:
|
||||
- ../web:/usr/src/app
|
||||
- ../i18n:/usr/src/i18n
|
||||
- ../open-api/:/usr/src/open-api/
|
||||
# - ../../ui:/usr/ui
|
||||
- /usr/src/app/node_modules
|
||||
ulimits:
|
||||
nofile:
|
||||
|
@ -63,6 +63,17 @@ If you only want to do web development connected to an existing, remote backend,
|
||||
IMMICH_SERVER_URL=https://demo.immich.app/ npm run dev
|
||||
```
|
||||
|
||||
#### `@immich/ui`
|
||||
|
||||
To see local changes to `@immich/ui` in Immich, do the following:
|
||||
|
||||
1. Install `@immich/ui` as a sibling to `immich/`, for example `/home/user/immich` and `/home/user/ui`
|
||||
1. Build the `@immich/ui` project via `npm run build`
|
||||
1. Uncomment the corresponding volume in web service of the `docker/docker-compose.dev.yaml` file (`../../ui:/usr/ui`)
|
||||
1. Uncomment the corresponding alias in the `web/vite.config.js` file (`'@immich/ui': path.resolve(\_\_dirname, '../../ui')`)
|
||||
1. Start up the stack via `make dev`
|
||||
1. After making changes in `@immich/ui`, rebuild it (`npm run build`)
|
||||
|
||||
### Mobile app
|
||||
|
||||
The mobile app `(/mobile)` will required Flutter toolchain 3.13.x to be installed on your system.
|
||||
|
@ -148,26 +148,29 @@ Redis (Sentinel) URL example JSON before encoding:
|
||||
|
||||
## Machine Learning
|
||||
|
||||
| Variable | Description | Default | Containers |
|
||||
| :-------------------------------------------------------- | :-------------------------------------------------------------------------------------------------- | :-----------------------------: | :--------------- |
|
||||
| `MACHINE_LEARNING_MODEL_TTL` | Inactivity time (s) before a model is unloaded (disabled if \<= 0) | `300` | machine learning |
|
||||
| `MACHINE_LEARNING_MODEL_TTL_POLL_S` | Interval (s) between checks for the model TTL (disabled if \<= 0) | `10` | machine learning |
|
||||
| `MACHINE_LEARNING_CACHE_FOLDER` | Directory where models are downloaded | `/cache` | machine learning |
|
||||
| `MACHINE_LEARNING_REQUEST_THREADS`<sup>\*1</sup> | Thread count of the request thread pool (disabled if \<= 0) | number of CPU cores | machine learning |
|
||||
| `MACHINE_LEARNING_MODEL_INTER_OP_THREADS` | Number of parallel model operations | `1` | machine learning |
|
||||
| `MACHINE_LEARNING_MODEL_INTRA_OP_THREADS` | Number of threads for each model operation | `2` | machine learning |
|
||||
| `MACHINE_LEARNING_WORKERS`<sup>\*2</sup> | Number of worker processes to spawn | `1` | machine learning |
|
||||
| `MACHINE_LEARNING_HTTP_KEEPALIVE_TIMEOUT_S`<sup>\*3</sup> | 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` | Name of a CLIP model to be preloaded and kept in cache | | machine learning |
|
||||
| `MACHINE_LEARNING_PRELOAD__FACIAL_RECOGNITION` | Name of a facial recognition model to be preloaded and kept in 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 |
|
||||
| `MACHINE_LEARNING_DEVICE_IDS`<sup>\*4</sup> | Device IDs to use in multi-GPU environments | `0` | machine learning |
|
||||
| `MACHINE_LEARNING_MAX_BATCH_SIZE__FACIAL_RECOGNITION` | Set the maximum number of faces that will be processed at once by the facial recognition model | None (`1` if using OpenVINO) | machine learning |
|
||||
| `MACHINE_LEARNING_RKNN` | Enable RKNN hardware acceleration if supported | `True` | machine learning |
|
||||
| `MACHINE_LEARNING_RKNN_THREADS` | How many threads of RKNN runtime should be spinned up while inferencing. | `1` | machine learning |
|
||||
|
||||
| Variable | Description | Default | Containers |
|
||||
| :---------------------------------------------------------- | :-------------------------------------------------------------------------------------------------- | :-----------------------------: | :--------------- |
|
||||
| `MACHINE_LEARNING_MODEL_TTL` | Inactivity time (s) before a model is unloaded (disabled if \<= 0) | `300` | machine learning |
|
||||
| `MACHINE_LEARNING_MODEL_TTL_POLL_S` | Interval (s) between checks for the model TTL (disabled if \<= 0) | `10` | machine learning |
|
||||
| `MACHINE_LEARNING_CACHE_FOLDER` | Directory where models are downloaded | `/cache` | machine learning |
|
||||
| `MACHINE_LEARNING_REQUEST_THREADS`<sup>\*1</sup> | Thread count of the request thread pool (disabled if \<= 0) | number of CPU cores | machine learning |
|
||||
| `MACHINE_LEARNING_MODEL_INTER_OP_THREADS` | Number of parallel model operations | `1` | machine learning |
|
||||
| `MACHINE_LEARNING_MODEL_INTRA_OP_THREADS` | Number of threads for each model operation | `2` | machine learning |
|
||||
| `MACHINE_LEARNING_WORKERS`<sup>\*2</sup> | Number of worker processes to spawn | `1` | machine learning |
|
||||
| `MACHINE_LEARNING_HTTP_KEEPALIVE_TIMEOUT_S`<sup>\*3</sup> | 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_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 |
|
||||
| `MACHINE_LEARNING_DEVICE_IDS`<sup>\*4</sup> | Device IDs to use in multi-GPU environments | `0` | machine learning |
|
||||
| `MACHINE_LEARNING_MAX_BATCH_SIZE__FACIAL_RECOGNITION` | Set the maximum number of faces that will be processed at once by the facial recognition model | None (`1` if using OpenVINO) | machine learning |
|
||||
| `MACHINE_LEARNING_RKNN` | Enable RKNN hardware acceleration if supported | `True` | machine learning |
|
||||
| `MACHINE_LEARNING_RKNN_THREADS` | How many threads of RKNN runtime should be spinned up while inferencing. | `1` | machine learning |
|
||||
|
||||
\*1: It is recommended to begin with this parameter when changing the concurrency levels of the machine learning service and then tune the other ones.
|
||||
|
||||
|
@ -13,8 +13,8 @@ import request from 'supertest';
|
||||
import { beforeAll, describe, expect, it } from 'vitest';
|
||||
|
||||
const authServer = {
|
||||
internal: 'http://auth-server:3000',
|
||||
external: 'http://127.0.0.1:3000',
|
||||
internal: 'http://auth-server:2286',
|
||||
external: 'http://127.0.0.1:2286',
|
||||
};
|
||||
|
||||
const mobileOverrideRedirectUri = 'https://photos.immich.app/oauth/mobile-redirect';
|
||||
|
@ -51,7 +51,7 @@ const setup = async () => {
|
||||
const { privateKey, publicKey } = await generateKeyPair('RS256');
|
||||
|
||||
const redirectUris = ['http://127.0.0.1:2285/auth/login', 'https://photos.immich.app/oauth/mobile-redirect'];
|
||||
const port = 3000;
|
||||
const port = 2286;
|
||||
const host = '0.0.0.0';
|
||||
const oidc = new Provider(`http://${host}:${port}`, {
|
||||
renderError: async (ctx, out, error) => {
|
||||
|
@ -1,6 +1,6 @@
|
||||
ARG DEVICE=cpu
|
||||
|
||||
FROM python:3.11-bookworm@sha256:b337e1fd27dbacda505219f713789bf82766694095876769ea10c2d34b4f470b AS builder-cpu
|
||||
FROM python:3.11-bookworm@sha256:f997d3f71b7dcff3f937703c02861437f2b41a94e1ddbd1b5fa357ee99f5cce4 AS builder-cpu
|
||||
|
||||
FROM builder-cpu AS builder-openvino
|
||||
|
||||
@ -36,7 +36,7 @@ RUN python3 -m venv /opt/venv
|
||||
COPY poetry.lock pyproject.toml ./
|
||||
RUN poetry install --sync --no-interaction --no-ansi --no-root --with ${DEVICE} --without dev
|
||||
|
||||
FROM python:3.11-slim-bookworm@sha256:873952659a04188d2a62d5f7e30fd673d2559432a847a8ad5fcaf9cbd085e9ed AS prod-cpu
|
||||
FROM python:3.11-slim-bookworm@sha256:6ed5bff4d7d377e2a27d9285553b8c21cfccc4f00881de1b24c9bc8d90016e82 AS prod-cpu
|
||||
|
||||
FROM prod-cpu AS prod-openvino
|
||||
|
||||
|
@ -14,9 +14,29 @@ from uvicorn import Server
|
||||
from uvicorn.workers import UvicornWorker
|
||||
|
||||
|
||||
class ClipSettings(BaseModel):
|
||||
textual: str | None = None
|
||||
visual: str | None = None
|
||||
|
||||
|
||||
class FacialRecognitionSettings(BaseModel):
|
||||
recognition: str | None = None
|
||||
detection: str | None = None
|
||||
|
||||
|
||||
class PreloadModelData(BaseModel):
|
||||
clip: str | None = None
|
||||
facial_recognition: str | None = None
|
||||
clip_fallback: str | None = os.getenv("MACHINE_LEARNING_PRELOAD__CLIP", None)
|
||||
facial_recognition_fallback: str | None = os.getenv("MACHINE_LEARNING_PRELOAD__FACIAL_RECOGNITION", None)
|
||||
if clip_fallback is not None:
|
||||
os.environ["MACHINE_LEARNING_PRELOAD__CLIP__TEXTUAL"] = clip_fallback
|
||||
os.environ["MACHINE_LEARNING_PRELOAD__CLIP__VISUAL"] = clip_fallback
|
||||
del os.environ["MACHINE_LEARNING_PRELOAD__CLIP"]
|
||||
if facial_recognition_fallback is not None:
|
||||
os.environ["MACHINE_LEARNING_PRELOAD__FACIAL_RECOGNITION__RECOGNITION"] = facial_recognition_fallback
|
||||
os.environ["MACHINE_LEARNING_PRELOAD__FACIAL_RECOGNITION__DETECTION"] = facial_recognition_fallback
|
||||
del os.environ["MACHINE_LEARNING_PRELOAD__FACIAL_RECOGNITION"]
|
||||
clip: ClipSettings = ClipSettings()
|
||||
facial_recognition: FacialRecognitionSettings = FacialRecognitionSettings()
|
||||
|
||||
|
||||
class MaxBatchSize(BaseModel):
|
||||
|
@ -75,21 +75,46 @@ async def lifespan(_: FastAPI) -> AsyncGenerator[None, None]:
|
||||
|
||||
|
||||
async def preload_models(preload: PreloadModelData) -> None:
|
||||
log.info(f"Preloading models: {preload}")
|
||||
if preload.clip is not None:
|
||||
model = await model_cache.get(preload.clip, ModelType.TEXTUAL, ModelTask.SEARCH)
|
||||
log.info(f"Preloading models: clip:{preload.clip} facial_recognition:{preload.facial_recognition}")
|
||||
|
||||
if preload.clip.textual is not None:
|
||||
model = await model_cache.get(preload.clip.textual, ModelType.TEXTUAL, ModelTask.SEARCH)
|
||||
await load(model)
|
||||
|
||||
model = await model_cache.get(preload.clip, ModelType.VISUAL, ModelTask.SEARCH)
|
||||
if preload.clip.visual is not None:
|
||||
model = await model_cache.get(preload.clip.visual, ModelType.VISUAL, ModelTask.SEARCH)
|
||||
await load(model)
|
||||
|
||||
if preload.facial_recognition is not None:
|
||||
model = await model_cache.get(preload.facial_recognition, ModelType.DETECTION, ModelTask.FACIAL_RECOGNITION)
|
||||
if preload.facial_recognition.detection is not None:
|
||||
model = await model_cache.get(
|
||||
preload.facial_recognition.detection,
|
||||
ModelType.DETECTION,
|
||||
ModelTask.FACIAL_RECOGNITION,
|
||||
)
|
||||
await load(model)
|
||||
|
||||
model = await model_cache.get(preload.facial_recognition, ModelType.RECOGNITION, ModelTask.FACIAL_RECOGNITION)
|
||||
if preload.facial_recognition.recognition is not None:
|
||||
model = await model_cache.get(
|
||||
preload.facial_recognition.recognition,
|
||||
ModelType.RECOGNITION,
|
||||
ModelTask.FACIAL_RECOGNITION,
|
||||
)
|
||||
await load(model)
|
||||
|
||||
if preload.clip_fallback is not None:
|
||||
log.warning(
|
||||
"Deprecated env variable: 'MACHINE_LEARNING_PRELOAD__CLIP'. "
|
||||
"Use 'MACHINE_LEARNING_PRELOAD__CLIP__TEXTUAL' and "
|
||||
"'MACHINE_LEARNING_PRELOAD__CLIP__VISUAL' instead."
|
||||
)
|
||||
|
||||
if preload.facial_recognition_fallback is not None:
|
||||
log.warning(
|
||||
"Deprecated env variable: 'MACHINE_LEARNING_PRELOAD__FACIAL_RECOGNITION'. "
|
||||
"Use 'MACHINE_LEARNING_PRELOAD__FACIAL_RECOGNITION__DETECTION' and "
|
||||
"'MACHINE_LEARNING_PRELOAD__FACIAL_RECOGNITION__RECOGNITION' instead."
|
||||
)
|
||||
|
||||
|
||||
def update_state() -> Iterator[None]:
|
||||
global active_requests, last_called
|
||||
|
@ -750,11 +750,13 @@ class TestCache:
|
||||
await model_cache.get("test_model_name", ModelType.TEXTUAL, ModelTask.SEARCH)
|
||||
|
||||
async def test_preloads_clip_models(self, monkeypatch: MonkeyPatch, mock_get_model: mock.Mock) -> None:
|
||||
os.environ["MACHINE_LEARNING_PRELOAD__CLIP"] = "ViT-B-32__openai"
|
||||
os.environ["MACHINE_LEARNING_PRELOAD__CLIP__TEXTUAL"] = "ViT-B-32__openai"
|
||||
os.environ["MACHINE_LEARNING_PRELOAD__CLIP__VISUAL"] = "ViT-B-32__openai"
|
||||
|
||||
settings = Settings()
|
||||
assert settings.preload is not None
|
||||
assert settings.preload.clip == "ViT-B-32__openai"
|
||||
assert settings.preload.clip.textual == "ViT-B-32__openai"
|
||||
assert settings.preload.clip.visual == "ViT-B-32__openai"
|
||||
|
||||
model_cache = ModelCache()
|
||||
monkeypatch.setattr("app.main.model_cache", model_cache)
|
||||
@ -771,11 +773,13 @@ class TestCache:
|
||||
async def test_preloads_facial_recognition_models(
|
||||
self, monkeypatch: MonkeyPatch, mock_get_model: mock.Mock
|
||||
) -> None:
|
||||
os.environ["MACHINE_LEARNING_PRELOAD__FACIAL_RECOGNITION"] = "buffalo_s"
|
||||
os.environ["MACHINE_LEARNING_PRELOAD__FACIAL_RECOGNITION__DETECTION"] = "buffalo_s"
|
||||
os.environ["MACHINE_LEARNING_PRELOAD__FACIAL_RECOGNITION__RECOGNITION"] = "buffalo_s"
|
||||
|
||||
settings = Settings()
|
||||
assert settings.preload is not None
|
||||
assert settings.preload.facial_recognition == "buffalo_s"
|
||||
assert settings.preload.facial_recognition.detection == "buffalo_s"
|
||||
assert settings.preload.facial_recognition.recognition == "buffalo_s"
|
||||
|
||||
model_cache = ModelCache()
|
||||
monkeypatch.setattr("app.main.model_cache", model_cache)
|
||||
@ -790,13 +794,17 @@ class TestCache:
|
||||
)
|
||||
|
||||
async def test_preloads_all_models(self, monkeypatch: MonkeyPatch, mock_get_model: mock.Mock) -> None:
|
||||
os.environ["MACHINE_LEARNING_PRELOAD__CLIP"] = "ViT-B-32__openai"
|
||||
os.environ["MACHINE_LEARNING_PRELOAD__FACIAL_RECOGNITION"] = "buffalo_s"
|
||||
os.environ["MACHINE_LEARNING_PRELOAD__CLIP__TEXTUAL"] = "ViT-B-32__openai"
|
||||
os.environ["MACHINE_LEARNING_PRELOAD__CLIP__VISUAL"] = "ViT-B-32__openai"
|
||||
os.environ["MACHINE_LEARNING_PRELOAD__FACIAL_RECOGNITION__RECOGNITION"] = "buffalo_s"
|
||||
os.environ["MACHINE_LEARNING_PRELOAD__FACIAL_RECOGNITION__DETECTION"] = "buffalo_s"
|
||||
|
||||
settings = Settings()
|
||||
assert settings.preload is not None
|
||||
assert settings.preload.clip == "ViT-B-32__openai"
|
||||
assert settings.preload.facial_recognition == "buffalo_s"
|
||||
assert settings.preload.clip.visual == "ViT-B-32__openai"
|
||||
assert settings.preload.clip.textual == "ViT-B-32__openai"
|
||||
assert settings.preload.facial_recognition.recognition == "buffalo_s"
|
||||
assert settings.preload.facial_recognition.detection == "buffalo_s"
|
||||
|
||||
model_cache = ModelCache()
|
||||
monkeypatch.setattr("app.main.model_cache", model_cache)
|
||||
|
6
machine-learning/poetry.lock
generated
6
machine-learning/poetry.lock
generated
@ -2498,13 +2498,13 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "pydantic"
|
||||
version = "2.10.4"
|
||||
version = "2.10.5"
|
||||
description = "Data validation using Python type hints"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "pydantic-2.10.4-py3-none-any.whl", hash = "sha256:597e135ea68be3a37552fb524bc7d0d66dcf93d395acd93a00682f1efcb8ee3d"},
|
||||
{file = "pydantic-2.10.4.tar.gz", hash = "sha256:82f12e9723da6de4fe2ba888b5971157b3be7ad914267dea8f05f82b28254f06"},
|
||||
{file = "pydantic-2.10.5-py3-none-any.whl", hash = "sha256:4dd4e322dbe55472cb7ca7e73f4b63574eecccf2835ffa2af9021ce113c83c53"},
|
||||
{file = "pydantic-2.10.5.tar.gz", hash = "sha256:278b38dbbaec562011d659ee05f63346951b3a248a6f3642e1bc68894ea2b4ff"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
|
@ -43,7 +43,7 @@
|
||||
android:name="com.google.firebase.messaging.default_notification_icon"
|
||||
android:resource="@drawable/notification_icon" />
|
||||
|
||||
<activity android:name=".MainActivity" android:exported="true" android:launchMode="singleTop"
|
||||
<activity android:name=".MainActivity" android:exported="true" android:launchMode="singleTask"
|
||||
android:theme="@style/LaunchTheme"
|
||||
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
||||
android:hardwareAccelerated="true" android:windowSoftInputMode="adjustResize">
|
||||
@ -61,6 +61,34 @@
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
|
||||
|
||||
<!--TODO:
|
||||
Add this filter if you want to handle shared images-->
|
||||
<intent-filter android:label="Upload to Immich">
|
||||
<action android:name="android.intent.action.SEND" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<data android:mimeType="image/*" />
|
||||
</intent-filter>
|
||||
|
||||
<intent-filter android:label="Upload to Immich">
|
||||
<action android:name="android.intent.action.SEND_MULTIPLE" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<data android:mimeType="image/*" />
|
||||
</intent-filter>
|
||||
|
||||
<!--TODO:
|
||||
Add this filter if you want to handle shared videos-->
|
||||
<intent-filter android:label="Upload to Immich">
|
||||
<action android:name="android.intent.action.SEND" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<data android:mimeType="video/*" />
|
||||
</intent-filter>
|
||||
<intent-filter android:label="Upload to Immich">
|
||||
<action android:name="android.intent.action.SEND_MULTIPLE" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<data android:mimeType="video/*" />
|
||||
</intent-filter>
|
||||
|
||||
</activity>
|
||||
|
||||
|
||||
@ -96,4 +124,4 @@
|
||||
<data android:scheme="geo" />
|
||||
</intent>
|
||||
</queries>
|
||||
</manifest>
|
||||
</manifest>
|
@ -657,5 +657,15 @@
|
||||
"viewer_stack_use_as_main_asset": "Use as Main Asset",
|
||||
"viewer_unstack": "Un-Stack",
|
||||
"wifi_name": "WiFi Name",
|
||||
"your_wifi_name": "Your WiFi name"
|
||||
"your_wifi_name": "Your WiFi name",
|
||||
"upload": "Upload",
|
||||
"uploading": "Uploading",
|
||||
"shared_intent_upload_button_progress_text": "{} / {} Uploaded",
|
||||
"enqueued": "Enqueued",
|
||||
"not_selected": "Not selected",
|
||||
"completed": "Completed",
|
||||
"failed": "Failed",
|
||||
"paused": "Paused",
|
||||
"canceled": "Canceled",
|
||||
"upload_to_immich": "Upload to Immich ({})"
|
||||
}
|
||||
|
@ -32,6 +32,13 @@ target 'Runner' do
|
||||
use_modular_headers!
|
||||
|
||||
flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
|
||||
|
||||
# share_handler addition start
|
||||
target 'ShareExtension' do
|
||||
inherit! :search_paths
|
||||
pod "share_handler_ios_models", :path => ".symlinks/plugins/share_handler_ios/ios/Models"
|
||||
end
|
||||
# share_handler addition end
|
||||
end
|
||||
|
||||
post_install do |installer|
|
||||
|
@ -82,9 +82,17 @@ PODS:
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- SAMKeychain (1.5.3)
|
||||
- SDWebImage (5.19.4):
|
||||
- SDWebImage/Core (= 5.19.4)
|
||||
- SDWebImage/Core (5.19.4)
|
||||
- SDWebImage (5.20.0):
|
||||
- SDWebImage/Core (= 5.20.0)
|
||||
- SDWebImage/Core (5.20.0)
|
||||
- share_handler_ios (0.0.14):
|
||||
- Flutter
|
||||
- share_handler_ios/share_handler_ios_models (= 0.0.14)
|
||||
- share_handler_ios_models
|
||||
- share_handler_ios/share_handler_ios_models (0.0.14):
|
||||
- Flutter
|
||||
- share_handler_ios_models
|
||||
- share_handler_ios_models (0.0.9)
|
||||
- share_plus (0.0.1):
|
||||
- Flutter
|
||||
- shared_preferences_foundation (0.0.1):
|
||||
@ -94,7 +102,7 @@ PODS:
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- SwiftyGif (5.4.5)
|
||||
- Toast (4.0.0)
|
||||
- Toast (4.1.1)
|
||||
- url_launcher_ios (0.0.1):
|
||||
- Flutter
|
||||
- wakelock_plus (0.0.1):
|
||||
@ -123,6 +131,8 @@ DEPENDENCIES:
|
||||
- path_provider_ios (from `.symlinks/plugins/path_provider_ios/ios`)
|
||||
- permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`)
|
||||
- photo_manager (from `.symlinks/plugins/photo_manager/ios`)
|
||||
- share_handler_ios (from `.symlinks/plugins/share_handler_ios/ios`)
|
||||
- share_handler_ios_models (from `.symlinks/plugins/share_handler_ios/ios/Models`)
|
||||
- share_plus (from `.symlinks/plugins/share_plus/ios`)
|
||||
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
|
||||
- sqflite (from `.symlinks/plugins/sqflite/darwin`)
|
||||
@ -184,6 +194,10 @@ EXTERNAL SOURCES:
|
||||
:path: ".symlinks/plugins/permission_handler_apple/ios"
|
||||
photo_manager:
|
||||
:path: ".symlinks/plugins/photo_manager/ios"
|
||||
share_handler_ios:
|
||||
:path: ".symlinks/plugins/share_handler_ios/ios"
|
||||
share_handler_ios_models:
|
||||
:path: ".symlinks/plugins/share_handler_ios/ios/Models"
|
||||
share_plus:
|
||||
:path: ".symlinks/plugins/share_plus/ios"
|
||||
shared_preferences_foundation:
|
||||
@ -222,15 +236,17 @@ SPEC CHECKSUMS:
|
||||
permission_handler_apple: 9878588469a2b0d0fc1e048d9f43605f92e6cec2
|
||||
photo_manager: ff695c7a1dd5bc379974953a2b5c0a293f7c4c8a
|
||||
SAMKeychain: 483e1c9f32984d50ca961e26818a534283b4cd5c
|
||||
SDWebImage: 066c47b573f408f18caa467d71deace7c0f8280d
|
||||
SDWebImage: 73c6079366fea25fa4bb9640d5fb58f0893facd8
|
||||
share_handler_ios: 6dd3a4ac5ca0d955274aec712ba0ecdcaf583e7c
|
||||
share_handler_ios_models: fc638c9b4330dc7f082586c92aee9dfa0b87b871
|
||||
share_plus: 8b6f8b3447e494cca5317c8c3073de39b3600d1f
|
||||
shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78
|
||||
sqflite: 673a0e54cc04b7d6dba8d24fb8095b31c3a99eec
|
||||
SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4
|
||||
Toast: 91b396c56ee72a5790816f40d3a94dd357abc196
|
||||
Toast: 1f5ea13423a1e6674c4abdac5be53587ae481c4e
|
||||
url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe
|
||||
wakelock_plus: 78ec7c5b202cab7761af8e2b2b3d0671be6c4ae1
|
||||
|
||||
PODFILE CHECKSUM: 2282844f7aed70427ae663932332dad1225156c8
|
||||
PODFILE CHECKSUM: 03b7eead4ee77b9e778179eeb0f3b5513617451c
|
||||
|
||||
COCOAPODS: 1.15.2
|
||||
|
@ -9,6 +9,7 @@
|
||||
/* Begin PBXBuildFile section */
|
||||
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
|
||||
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
|
||||
3B6A31FED0FC846D6BD69BBC /* Pods_ShareExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 357FC57E54FD0F51795CF28A /* Pods_ShareExtension.framework */; };
|
||||
65F32F31299BD2F800CE9261 /* BackgroundServicePlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65F32F30299BD2F800CE9261 /* BackgroundServicePlugin.swift */; };
|
||||
65F32F33299D349D00CE9261 /* BackgroundSyncWorker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65F32F32299D349D00CE9261 /* BackgroundSyncWorker.swift */; };
|
||||
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
|
||||
@ -16,8 +17,21 @@
|
||||
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
|
||||
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
|
||||
D218389C4A4C4693F141F7D1 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 886774DBDDE6B35BF2B4F2CD /* Pods_Runner.framework */; };
|
||||
FAC6F89B2D287C890078CB2F /* ShareExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = FAC6F8902D287C890078CB2F /* ShareExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||
FAC6F8B72D287F120078CB2F /* ShareViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAC6F8B52D287F120078CB2F /* ShareViewController.swift */; };
|
||||
FAC6F8B92D287F120078CB2F /* MainInterface.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = FAC6F8B32D287F120078CB2F /* MainInterface.storyboard */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
FAC6F8982D287C890078CB2F /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 97C146E61CF9000F007C117D /* Project object */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = FAC6F88F2D287C890078CB2F;
|
||||
remoteInfo = ShareExtension;
|
||||
};
|
||||
/* End PBXContainerItemProxy section */
|
||||
|
||||
/* Begin PBXCopyFilesBuildPhase section */
|
||||
9705A1C41CF9048500538489 /* Embed Frameworks */ = {
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
@ -29,13 +43,26 @@
|
||||
name = "Embed Frameworks";
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
FAC6F89A2D287C890078CB2F /* Embed Foundation Extensions */ = {
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
dstPath = "";
|
||||
dstSubfolderSpec = 13;
|
||||
files = (
|
||||
FAC6F89B2D287C890078CB2F /* ShareExtension.appex in Embed Foundation Extensions */,
|
||||
);
|
||||
name = "Embed Foundation Extensions";
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXCopyFilesBuildPhase section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
|
||||
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
|
||||
2E3441B73560D0F6FD25E04F /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
357FC57E54FD0F51795CF28A /* Pods_ShareExtension.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_ShareExtension.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
|
||||
571EAA93D77181C7C98C2EA6 /* Pods-ShareExtension.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ShareExtension.release.xcconfig"; path = "Target Support Files/Pods-ShareExtension/Pods-ShareExtension.release.xcconfig"; sourceTree = "<group>"; };
|
||||
65F32F30299BD2F800CE9261 /* BackgroundServicePlugin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackgroundServicePlugin.swift; sourceTree = "<group>"; };
|
||||
65F32F32299D349D00CE9261 /* BackgroundSyncWorker.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BackgroundSyncWorker.swift; sourceTree = "<group>"; };
|
||||
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
|
||||
@ -49,9 +76,16 @@
|
||||
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
|
||||
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
B1FBA9EE014DE20271B0FE77 /* Pods-ShareExtension.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ShareExtension.profile.xcconfig"; path = "Target Support Files/Pods-ShareExtension/Pods-ShareExtension.profile.xcconfig"; sourceTree = "<group>"; };
|
||||
E0E99CDC17B3EB7FA8BA2332 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
|
||||
F7101BB0391A314774615E89 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; };
|
||||
F8A35EA3C3E01BD66AFDE0E5 /* Pods-ShareExtension.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ShareExtension.debug.xcconfig"; path = "Target Support Files/Pods-ShareExtension/Pods-ShareExtension.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
FA9973382CF6DF4B000EF859 /* Runner.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Runner.entitlements; sourceTree = "<group>"; };
|
||||
FAC6F8902D287C890078CB2F /* ShareExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = ShareExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
FAC6F8B12D287F120078CB2F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
FAC6F8B22D287F120078CB2F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/MainInterface.storyboard; sourceTree = "<group>"; };
|
||||
FAC6F8B42D287F120078CB2F /* ShareExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = ShareExtension.entitlements; sourceTree = "<group>"; };
|
||||
FAC6F8B52D287F120078CB2F /* ShareViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareViewController.swift; sourceTree = "<group>"; };
|
||||
FAC7416727DB9F5500C668D8 /* RunnerProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = RunnerProfile.entitlements; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
@ -64,6 +98,14 @@
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
FAC6F88D2D287C890078CB2F /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
3B6A31FED0FC846D6BD69BBC /* Pods_ShareExtension.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
@ -73,6 +115,9 @@
|
||||
2E3441B73560D0F6FD25E04F /* Pods-Runner.debug.xcconfig */,
|
||||
E0E99CDC17B3EB7FA8BA2332 /* Pods-Runner.release.xcconfig */,
|
||||
F7101BB0391A314774615E89 /* Pods-Runner.profile.xcconfig */,
|
||||
F8A35EA3C3E01BD66AFDE0E5 /* Pods-ShareExtension.debug.xcconfig */,
|
||||
571EAA93D77181C7C98C2EA6 /* Pods-ShareExtension.release.xcconfig */,
|
||||
B1FBA9EE014DE20271B0FE77 /* Pods-ShareExtension.profile.xcconfig */,
|
||||
);
|
||||
path = Pods;
|
||||
sourceTree = "<group>";
|
||||
@ -81,6 +126,7 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
886774DBDDE6B35BF2B4F2CD /* Pods_Runner.framework */,
|
||||
357FC57E54FD0F51795CF28A /* Pods_ShareExtension.framework */,
|
||||
);
|
||||
name = Frameworks;
|
||||
sourceTree = "<group>";
|
||||
@ -110,6 +156,7 @@
|
||||
children = (
|
||||
9740EEB11CF90186004384FC /* Flutter */,
|
||||
97C146F01CF9000F007C117D /* Runner */,
|
||||
FAC6F8B62D287F120078CB2F /* ShareExtension */,
|
||||
97C146EF1CF9000F007C117D /* Products */,
|
||||
0FB772A5B9601143383626CA /* Pods */,
|
||||
1754452DD81DA6620E279E51 /* Frameworks */,
|
||||
@ -120,6 +167,7 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
97C146EE1CF9000F007C117D /* Immich-Debug.app */,
|
||||
FAC6F8902D287C890078CB2F /* ShareExtension.appex */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
@ -142,6 +190,17 @@
|
||||
path = Runner;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
FAC6F8B62D287F120078CB2F /* ShareExtension */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
FAC6F8B12D287F120078CB2F /* Info.plist */,
|
||||
FAC6F8B32D287F120078CB2F /* MainInterface.storyboard */,
|
||||
FAC6F8B42D287F120078CB2F /* ShareExtension.entitlements */,
|
||||
FAC6F8B52D287F120078CB2F /* ShareViewController.swift */,
|
||||
);
|
||||
path = ShareExtension;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
@ -155,6 +214,7 @@
|
||||
97C146EB1CF9000F007C117D /* Frameworks */,
|
||||
97C146EC1CF9000F007C117D /* Resources */,
|
||||
9705A1C41CF9048500538489 /* Embed Frameworks */,
|
||||
FAC6F89A2D287C890078CB2F /* Embed Foundation Extensions */,
|
||||
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
|
||||
D218A34AEE62BC1EF119F5B0 /* [CP] Embed Pods Frameworks */,
|
||||
6724EEB7D74949FA08581154 /* [CP] Copy Pods Resources */,
|
||||
@ -162,12 +222,31 @@
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
FAC6F8992D287C890078CB2F /* PBXTargetDependency */,
|
||||
);
|
||||
name = Runner;
|
||||
productName = Runner;
|
||||
productReference = 97C146EE1CF9000F007C117D /* Immich-Debug.app */;
|
||||
productType = "com.apple.product-type.application";
|
||||
};
|
||||
FAC6F88F2D287C890078CB2F /* ShareExtension */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = FAC6F8A02D287C890078CB2F /* Build configuration list for PBXNativeTarget "ShareExtension" */;
|
||||
buildPhases = (
|
||||
3BEF3D71D97E337D921C0EB5 /* [CP] Check Pods Manifest.lock */,
|
||||
FAC6F88C2D287C890078CB2F /* Sources */,
|
||||
FAC6F88D2D287C890078CB2F /* Frameworks */,
|
||||
FAC6F88E2D287C890078CB2F /* Resources */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = ShareExtension;
|
||||
productName = ShareExtension;
|
||||
productReference = FAC6F8902D287C890078CB2F /* ShareExtension.appex */;
|
||||
productType = "com.apple.product-type.app-extension";
|
||||
};
|
||||
/* End PBXNativeTarget section */
|
||||
|
||||
/* Begin PBXProject section */
|
||||
@ -175,6 +254,7 @@
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
BuildIndependentTargetsInParallel = YES;
|
||||
LastSwiftUpdateCheck = 1600;
|
||||
LastUpgradeCheck = 1510;
|
||||
ORGANIZATIONNAME = "";
|
||||
TargetAttributes = {
|
||||
@ -182,6 +262,9 @@
|
||||
CreatedOnToolsVersion = 7.3.1;
|
||||
LastSwiftMigration = 1100;
|
||||
};
|
||||
FAC6F88F2D287C890078CB2F = {
|
||||
CreatedOnToolsVersion = 16.0;
|
||||
};
|
||||
};
|
||||
};
|
||||
buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
|
||||
@ -198,6 +281,7 @@
|
||||
projectRoot = "";
|
||||
targets = (
|
||||
97C146ED1CF9000F007C117D /* Runner */,
|
||||
FAC6F88F2D287C890078CB2F /* ShareExtension */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
@ -214,6 +298,14 @@
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
FAC6F88E2D287C890078CB2F /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
FAC6F8B92D287F120078CB2F /* MainInterface.storyboard in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXShellScriptBuildPhase section */
|
||||
@ -233,6 +325,28 @@
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
|
||||
};
|
||||
3BEF3D71D97E337D921C0EB5 /* [CP] Check Pods Manifest.lock */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
);
|
||||
inputPaths = (
|
||||
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
|
||||
"${PODS_ROOT}/Manifest.lock",
|
||||
);
|
||||
name = "[CP] Check Pods Manifest.lock";
|
||||
outputFileListPaths = (
|
||||
);
|
||||
outputPaths = (
|
||||
"$(DERIVED_FILE_DIR)/Pods-ShareExtension-checkManifestLockResult.txt",
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
4044AF030EF7D8721844FFBA /* [CP] Check Pods Manifest.lock */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
@ -318,8 +432,24 @@
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
FAC6F88C2D287C890078CB2F /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
FAC6F8B72D287F120078CB2F /* ShareViewController.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXTargetDependency section */
|
||||
FAC6F8992D287C890078CB2F /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = FAC6F88F2D287C890078CB2F /* ShareExtension */;
|
||||
targetProxy = FAC6F8982D287C890078CB2F /* PBXContainerItemProxy */;
|
||||
};
|
||||
/* End PBXTargetDependency section */
|
||||
|
||||
/* Begin PBXVariantGroup section */
|
||||
97C146FA1CF9000F007C117D /* Main.storyboard */ = {
|
||||
isa = PBXVariantGroup;
|
||||
@ -337,6 +467,14 @@
|
||||
name = LaunchScreen.storyboard;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
FAC6F8B32D287F120078CB2F /* MainInterface.storyboard */ = {
|
||||
isa = PBXVariantGroup;
|
||||
children = (
|
||||
FAC6F8B22D287F120078CB2F /* Base */,
|
||||
);
|
||||
name = MainInterface.storyboard;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXVariantGroup section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
@ -404,6 +542,7 @@
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 187;
|
||||
CUSTOM_GROUP_ID = group.app.immich.share;
|
||||
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
@ -547,6 +686,7 @@
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 187;
|
||||
CUSTOM_GROUP_ID = group.app.immich.share;
|
||||
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
@ -576,6 +716,7 @@
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 187;
|
||||
CUSTOM_GROUP_ID = group.app.immich.share;
|
||||
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
@ -594,6 +735,129 @@
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
FAC6F89C2D287C890078CB2F /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = F8A35EA3C3E01BD66AFDE0E5 /* Pods-ShareExtension.debug.xcconfig */;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
CUSTOM_GROUP_ID = group.app.immich.share;
|
||||
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = ShareExtension/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = ShareExtension;
|
||||
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||
MARKETING_VERSION = 1.0;
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = app.alextran.immich.vdebug.ShareExtension;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
FAC6F89D2D287C890078CB2F /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 571EAA93D77181C7C98C2EA6 /* Pods-ShareExtension.release.xcconfig */;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
CUSTOM_GROUP_ID = group.app.immich.share;
|
||||
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = ShareExtension/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = ShareExtension;
|
||||
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||
MARKETING_VERSION = 1.0;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = app.alextran.immich.ShareExtension;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
FAC6F89E2D287C890078CB2F /* Profile */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = B1FBA9EE014DE20271B0FE77 /* Pods-ShareExtension.profile.xcconfig */;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
||||
CLANG_ENABLE_OBJC_WEAK = YES;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
CUSTOM_GROUP_ID = group.app.immich.share;
|
||||
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = ShareExtension/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = ShareExtension;
|
||||
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
|
||||
MARKETING_VERSION = 1.0;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = app.alextran.immich.profile.ShareExtension;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Profile;
|
||||
};
|
||||
/* End XCBuildConfiguration section */
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
@ -617,6 +881,16 @@
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
FAC6F8A02D287C890078CB2F /* Build configuration list for PBXNativeTarget "ShareExtension" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
FAC6F89C2D287C890078CB2F /* Debug */,
|
||||
FAC6F89D2D287C890078CB2F /* Release */,
|
||||
FAC6F89E2D287C890078CB2F /* Profile */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
};
|
||||
rootObject = 97C146E61CF9000F007C117D /* Project object */;
|
||||
|
@ -2,6 +2,8 @@
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>AppGroupId</key>
|
||||
<string>$(CUSTOM_GROUP_ID)</string>
|
||||
<key>BGTaskSchedulerPermittedIdentifiers</key>
|
||||
<array>
|
||||
<string>app.alextran.immich.backgroundFetch</string>
|
||||
@ -13,6 +15,24 @@
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>${PRODUCT_NAME}</string>
|
||||
<key>CFBundleDocumentTypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>ShareHandler</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Alternate</string>
|
||||
<key>LSItemContentTypes</key>
|
||||
<array>
|
||||
<string>public.file-url</string>
|
||||
<string>public.image</string>
|
||||
<string>public.text</string>
|
||||
<string>public.movie</string>
|
||||
<string>public.url</string>
|
||||
<string>public.data</string>
|
||||
</array>
|
||||
</dict>
|
||||
</array>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
@ -61,6 +81,17 @@
|
||||
<string>1.124.0</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleURLTypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Editor</string>
|
||||
<key>CFBundleURLSchemes</key>
|
||||
<array>
|
||||
<string>ShareMedia-$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
</array>
|
||||
</dict>
|
||||
</array>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>187</string>
|
||||
<key>FLTEnableImpeller</key>
|
||||
@ -73,6 +104,8 @@
|
||||
</array>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>LSSupportsOpeningDocumentsInPlace</key>
|
||||
<string>No</string>
|
||||
<key>MGLMapboxMetricsEnabledSettingShownInApp</key>
|
||||
<true/>
|
||||
<key>NSAppTransportSecurity</key>
|
||||
@ -94,6 +127,10 @@
|
||||
<string>We need to manage backup your photos album</string>
|
||||
<key>NSPhotoLibraryUsageDescription</key>
|
||||
<string>We need to manage backup your photos album</string>
|
||||
<key>NSUserActivityTypes</key>
|
||||
<array>
|
||||
<string>INSendMessageIntent</string>
|
||||
</array>
|
||||
<key>UIApplicationSupportsIndirectInputEvents</key>
|
||||
<true/>
|
||||
<key>UIBackgroundModes</key>
|
||||
|
@ -4,5 +4,9 @@
|
||||
<dict>
|
||||
<key>com.apple.developer.networking.wifi-info</key>
|
||||
<true/>
|
||||
<key>com.apple.security.application-groups</key>
|
||||
<array>
|
||||
<string>group.app.immich.share</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
|
@ -6,5 +6,9 @@
|
||||
<string>development</string>
|
||||
<key>com.apple.developer.networking.wifi-info</key>
|
||||
<true/>
|
||||
<key>com.apple.security.application-groups</key>
|
||||
<array>
|
||||
<string>group.app.immich.share</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
|
@ -0,0 +1,24 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="13122.16" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="j1y-V4-xli">
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13104.12"/>
|
||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--Share View Controller-->
|
||||
<scene sceneID="ceB-am-kn3">
|
||||
<objects>
|
||||
<viewController id="j1y-V4-xli" customClass="ShareViewController" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<view key="view" opaque="NO" contentMode="scaleToFill" id="wbc-yd-nQP">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.0" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<viewLayoutGuide key="safeArea" id="1Xd-am-t49"/>
|
||||
</view>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="CEy-Cv-SGf" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
</scene>
|
||||
</scenes>
|
||||
</document>
|
35
mobile/ios/ShareExtension/Info.plist
Normal file
35
mobile/ios/ShareExtension/Info.plist
Normal file
@ -0,0 +1,35 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>AppGroupId</key>
|
||||
<string>$(CUSTOM_GROUP_ID)</string>
|
||||
<key>NSExtension</key>
|
||||
<dict>
|
||||
<key>NSExtensionAttributes</key>
|
||||
<dict>
|
||||
<key>IntentsSupported</key>
|
||||
<array>
|
||||
<string>INSendMessageIntent</string>
|
||||
</array>
|
||||
<key>NSExtensionActivationRule</key>
|
||||
<string>SUBQUERY ( extensionItems, $extensionItem, SUBQUERY ( $extensionItem.attachments,
|
||||
$attachment, ( ANY $attachment.registeredTypeIdentifiers UTI-CONFORMS-TO "public.file-url"
|
||||
|| ANY $attachment.registeredTypeIdentifiers UTI-CONFORMS-TO "public.image" || ANY
|
||||
$attachment.registeredTypeIdentifiers UTI-CONFORMS-TO "public.text" || ANY
|
||||
$attachment.registeredTypeIdentifiers UTI-CONFORMS-TO "public.movie" || ANY
|
||||
$attachment.registeredTypeIdentifiers UTI-CONFORMS-TO "public.url" ) ).@count > 0
|
||||
).@count > 0 </string>
|
||||
<key>PHSupportedMediaTypes</key>
|
||||
<array>
|
||||
<string>Video</string>
|
||||
<string>Image</string>
|
||||
</array>
|
||||
</dict>
|
||||
<key>NSExtensionMainStoryboard</key>
|
||||
<string>MainInterface</string>
|
||||
<key>NSExtensionPointIdentifier</key>
|
||||
<string>com.apple.share-services</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
10
mobile/ios/ShareExtension/ShareExtension.entitlements
Normal file
10
mobile/ios/ShareExtension/ShareExtension.entitlements
Normal file
@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>com.apple.security.application-groups</key>
|
||||
<array>
|
||||
<string>group.app.immich.share</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
3
mobile/ios/ShareExtension/ShareViewController.swift
Normal file
3
mobile/ios/ShareExtension/ShareViewController.swift
Normal file
@ -0,0 +1,3 @@
|
||||
import share_handler_ios_models
|
||||
|
||||
class ShareViewController: ShareHandlerIosViewController {}
|
@ -1 +1,3 @@
|
||||
const int noDbId = -9223372036854775808; // from Isar
|
||||
const double downloadCompleted = -1;
|
||||
const double downloadFailed = -2;
|
||||
|
@ -13,6 +13,7 @@ abstract interface class IAlbumRepository implements IDatabaseRepository {
|
||||
String name, {
|
||||
bool? shared,
|
||||
bool? remote,
|
||||
bool? owner,
|
||||
});
|
||||
|
||||
Future<List<Album>> getAll({
|
||||
|
7
mobile/lib/interfaces/share_handler.interface.dart
Normal file
7
mobile/lib/interfaces/share_handler.interface.dart
Normal file
@ -0,0 +1,7 @@
|
||||
import 'package:immich_mobile/models/upload/share_intent_attachment.model.dart';
|
||||
|
||||
abstract interface class IShareHandlerRepository {
|
||||
void Function(List<ShareIntentAttachment>)? onSharedMedia;
|
||||
|
||||
Future<void> init();
|
||||
}
|
11
mobile/lib/interfaces/upload.interface.dart
Normal file
11
mobile/lib/interfaces/upload.interface.dart
Normal file
@ -0,0 +1,11 @@
|
||||
import 'package:background_downloader/background_downloader.dart';
|
||||
|
||||
abstract interface class IUploadRepository {
|
||||
void Function(TaskStatusUpdate)? onUploadStatus;
|
||||
void Function(TaskProgressUpdate)? onTaskProgress;
|
||||
|
||||
Future<bool> upload(UploadTask task);
|
||||
Future<bool> cancel(String id);
|
||||
Future<void> deleteAllTrackingRecords();
|
||||
Future<void> deleteRecordsWithIds(List<String> id);
|
||||
}
|
@ -4,6 +4,7 @@ import 'dart:io';
|
||||
import 'package:background_downloader/background_downloader.dart';
|
||||
import 'package:device_info_plus/device_info_plus.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:immich_mobile/providers/asset_viewer/share_intent_upload.provider.dart';
|
||||
import 'package:intl/date_symbol_data_local.dart';
|
||||
import 'package:timezone/data/latest.dart';
|
||||
import 'package:isar/isar.dart';
|
||||
@ -107,10 +108,12 @@ Future<void> initApp() async {
|
||||
progressBar: true,
|
||||
);
|
||||
|
||||
FileDownloader().trackTasksInGroup(
|
||||
await FileDownloader().trackTasksInGroup(
|
||||
downloadGroupLivePhoto,
|
||||
markDownloadedComplete: false,
|
||||
);
|
||||
|
||||
await FileDownloader().trackTasks();
|
||||
}
|
||||
|
||||
Future<Isar> loadDb() async {
|
||||
@ -208,6 +211,8 @@ class ImmichAppState extends ConsumerState<ImmichApp>
|
||||
// needs to be delayed so that EasyLocalization is working
|
||||
ref.read(backgroundServiceProvider).resumeServiceIfEnabled();
|
||||
});
|
||||
|
||||
ref.read(shareIntentUploadProvider.notifier).init();
|
||||
}
|
||||
|
||||
@override
|
||||
|
114
mobile/lib/models/upload/share_intent_attachment.model.dart
Normal file
114
mobile/lib/models/upload/share_intent_attachment.model.dart
Normal file
@ -0,0 +1,114 @@
|
||||
// ignore_for_file: public_member_api_docs, sort_constructors_first
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:immich_mobile/utils/bytes_units.dart';
|
||||
import 'package:path/path.dart';
|
||||
|
||||
enum ShareIntentAttachmentType {
|
||||
image,
|
||||
video,
|
||||
}
|
||||
|
||||
enum UploadStatus {
|
||||
enqueued,
|
||||
running,
|
||||
complete,
|
||||
notFound,
|
||||
failed,
|
||||
canceled,
|
||||
waitingtoRetry,
|
||||
paused,
|
||||
}
|
||||
|
||||
class ShareIntentAttachment {
|
||||
final String path;
|
||||
|
||||
// enum
|
||||
final ShareIntentAttachmentType type;
|
||||
|
||||
// enum
|
||||
final UploadStatus status;
|
||||
|
||||
final double uploadProgress;
|
||||
|
||||
final int fileLength;
|
||||
|
||||
ShareIntentAttachment({
|
||||
required this.path,
|
||||
required this.type,
|
||||
required this.status,
|
||||
this.uploadProgress = 0,
|
||||
this.fileLength = 0,
|
||||
});
|
||||
|
||||
int get id => hash(path);
|
||||
|
||||
File get file => File(path);
|
||||
|
||||
String get fileName => basename(file.path);
|
||||
|
||||
bool get isImage => type == ShareIntentAttachmentType.image;
|
||||
|
||||
bool get isVideo => type == ShareIntentAttachmentType.video;
|
||||
|
||||
String? _fileSize;
|
||||
|
||||
String get fileSize => _fileSize ??= formatHumanReadableBytes(fileLength, 2);
|
||||
|
||||
ShareIntentAttachment copyWith({
|
||||
String? path,
|
||||
ShareIntentAttachmentType? type,
|
||||
UploadStatus? status,
|
||||
double? uploadProgress,
|
||||
}) {
|
||||
return ShareIntentAttachment(
|
||||
path: path ?? this.path,
|
||||
type: type ?? this.type,
|
||||
status: status ?? this.status,
|
||||
uploadProgress: uploadProgress ?? this.uploadProgress,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toMap() {
|
||||
return <String, dynamic>{
|
||||
'path': path,
|
||||
'type': type.index,
|
||||
'status': status.index,
|
||||
'uploadProgress': uploadProgress,
|
||||
};
|
||||
}
|
||||
|
||||
factory ShareIntentAttachment.fromMap(Map<String, dynamic> map) {
|
||||
return ShareIntentAttachment(
|
||||
path: map['path'] as String,
|
||||
type: ShareIntentAttachmentType.values[map['type'] as int],
|
||||
status: UploadStatus.values[map['status'] as int],
|
||||
uploadProgress: map['uploadProgress'] as double,
|
||||
);
|
||||
}
|
||||
|
||||
String toJson() => json.encode(toMap());
|
||||
|
||||
factory ShareIntentAttachment.fromJson(String source) =>
|
||||
ShareIntentAttachment.fromMap(
|
||||
json.decode(source) as Map<String, dynamic>,
|
||||
);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'ShareIntentAttachment(path: $path, type: $type, status: $status, uploadProgress: $uploadProgress)';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(covariant ShareIntentAttachment other) {
|
||||
if (identical(this, other)) return true;
|
||||
|
||||
return other.path == path && other.type == type;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
return path.hashCode ^ type.hashCode;
|
||||
}
|
||||
}
|
@ -32,7 +32,7 @@ class AlbumSharedUserIcons extends HookConsumerWidget {
|
||||
}
|
||||
|
||||
return GestureDetector(
|
||||
onTap: () => context.pushRoute(AlbumOptionsRoute()),
|
||||
onTap: () => context.pushRoute(const AlbumOptionsRoute()),
|
||||
child: SizedBox(
|
||||
height: 50,
|
||||
child: ListView.builder(
|
||||
|
@ -13,6 +13,9 @@ class LargeLeadingTile extends StatelessWidget {
|
||||
horizontal: 16.0,
|
||||
),
|
||||
this.borderRadius = 20.0,
|
||||
this.trailing,
|
||||
this.selected = false,
|
||||
this.disabled = false,
|
||||
});
|
||||
|
||||
final Widget leading;
|
||||
@ -21,30 +24,43 @@ class LargeLeadingTile extends StatelessWidget {
|
||||
final Widget? subtitle;
|
||||
final EdgeInsetsGeometry leadingPadding;
|
||||
final double borderRadius;
|
||||
|
||||
final Widget? trailing;
|
||||
final bool selected;
|
||||
final bool disabled;
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return InkWell(
|
||||
borderRadius: BorderRadius.circular(borderRadius),
|
||||
onTap: onTap,
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Padding(
|
||||
padding: leadingPadding,
|
||||
child: leading,
|
||||
),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SizedBox(
|
||||
width: context.width * 0.6,
|
||||
child: title,
|
||||
onTap: disabled ? null : onTap,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: selected
|
||||
? Theme.of(context).primaryColor.withAlpha(30)
|
||||
: Colors.transparent,
|
||||
borderRadius: BorderRadius.circular(borderRadius),
|
||||
),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Padding(
|
||||
padding: leadingPadding,
|
||||
child: leading,
|
||||
),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SizedBox(
|
||||
width: context.width * 0.6,
|
||||
child: title,
|
||||
),
|
||||
subtitle ?? const SizedBox.shrink(),
|
||||
],
|
||||
),
|
||||
subtitle ?? const SizedBox.shrink(),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
if (trailing != null) trailing!,
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -34,10 +34,12 @@ class PhotosPage extends HookConsumerWidget {
|
||||
Future(() => ref.read(assetProvider.notifier).getAllAsset());
|
||||
Future(() => ref.read(albumProvider.notifier).refreshRemoteAlbums());
|
||||
ref.read(serverInfoProvider.notifier).getServerInfo();
|
||||
|
||||
return;
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
Widget buildLoadingIndicator() {
|
||||
Timer(const Duration(seconds: 2), () => tipOneOpacity.value = 1);
|
||||
|
||||
|
263
mobile/lib/pages/share_intent/share_intent.page.dart
Normal file
263
mobile/lib/pages/share_intent/share_intent.page.dart
Normal file
@ -0,0 +1,263 @@
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||
import 'package:immich_mobile/models/upload/share_intent_attachment.model.dart';
|
||||
|
||||
import 'package:immich_mobile/pages/common/large_leading_tile.dart';
|
||||
import 'package:immich_mobile/providers/asset_viewer/share_intent_upload.provider.dart';
|
||||
import 'package:immich_mobile/entities/store.entity.dart' as db_store;
|
||||
|
||||
@RoutePage()
|
||||
class ShareIntentPage extends HookConsumerWidget {
|
||||
const ShareIntentPage({super.key, required this.attachments});
|
||||
|
||||
final List<ShareIntentAttachment> attachments;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final currentEndpoint =
|
||||
db_store.Store.get(db_store.StoreKey.serverEndpoint);
|
||||
final candidates = ref.watch(shareIntentUploadProvider);
|
||||
final isUploaded = useState(false);
|
||||
|
||||
void removeAttachment(ShareIntentAttachment attachment) {
|
||||
ref.read(shareIntentUploadProvider.notifier).removeAttachment(attachment);
|
||||
}
|
||||
|
||||
void addAttachments(List<ShareIntentAttachment> attachments) {
|
||||
ref.read(shareIntentUploadProvider.notifier).addAttachments(attachments);
|
||||
}
|
||||
|
||||
void upload() async {
|
||||
for (final attachment in candidates) {
|
||||
await ref
|
||||
.read(shareIntentUploadProvider.notifier)
|
||||
.upload(attachment.file);
|
||||
}
|
||||
|
||||
isUploaded.value = true;
|
||||
}
|
||||
|
||||
bool isSelected(ShareIntentAttachment attachment) {
|
||||
return candidates.contains(attachment);
|
||||
}
|
||||
|
||||
void toggleSelection(ShareIntentAttachment attachment) {
|
||||
if (isSelected(attachment)) {
|
||||
removeAttachment(attachment);
|
||||
} else {
|
||||
addAttachments([attachment]);
|
||||
}
|
||||
}
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Column(
|
||||
children: [
|
||||
const Text('upload_to_immich').tr(
|
||||
args: [
|
||||
candidates.length.toString(),
|
||||
],
|
||||
),
|
||||
Text(
|
||||
currentEndpoint,
|
||||
style: context.textTheme.labelMedium?.copyWith(
|
||||
color: context.colorScheme.onSurface.withAlpha(200),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
body: ListView.builder(
|
||||
itemCount: attachments.length,
|
||||
itemBuilder: (context, index) {
|
||||
final attachment = attachments[index];
|
||||
final target = candidates.firstWhere(
|
||||
(element) => element.id == attachment.id,
|
||||
orElse: () => attachment,
|
||||
);
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 4.0,
|
||||
horizontal: 16,
|
||||
),
|
||||
child: LargeLeadingTile(
|
||||
onTap: () => toggleSelection(attachment),
|
||||
disabled: isUploaded.value,
|
||||
selected: isSelected(attachment),
|
||||
leading: Stack(
|
||||
children: [
|
||||
ClipRRect(
|
||||
borderRadius: const BorderRadius.all(Radius.circular(16)),
|
||||
child: attachment.isImage
|
||||
? Image.file(
|
||||
attachment.file,
|
||||
width: 64,
|
||||
height: 64,
|
||||
fit: BoxFit.cover,
|
||||
)
|
||||
: const SizedBox(
|
||||
width: 64,
|
||||
height: 64,
|
||||
child: Center(
|
||||
child: Icon(
|
||||
Icons.videocam,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (attachment.isImage)
|
||||
const Positioned(
|
||||
top: 8,
|
||||
right: 8,
|
||||
child: Icon(
|
||||
Icons.image,
|
||||
color: Colors.white,
|
||||
size: 20,
|
||||
shadows: [
|
||||
Shadow(
|
||||
offset: Offset(0, 0),
|
||||
blurRadius: 8.0,
|
||||
color: Colors.black45,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
title: Text(
|
||||
attachment.fileName,
|
||||
style: context.textTheme.titleSmall,
|
||||
),
|
||||
subtitle: Text(
|
||||
attachment.fileSize,
|
||||
style: context.textTheme.labelLarge,
|
||||
),
|
||||
trailing: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||
child: UploadStatusIcon(
|
||||
selected: isSelected(attachment),
|
||||
status: target.status,
|
||||
progress: target.uploadProgress,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
bottomNavigationBar: SafeArea(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: SizedBox(
|
||||
height: 48,
|
||||
child: ElevatedButton(
|
||||
onPressed: isUploaded.value ? null : upload,
|
||||
child: isUploaded.value
|
||||
? UploadingText(candidates: candidates)
|
||||
: const Text('upload').tr(),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class UploadingText extends StatelessWidget {
|
||||
const UploadingText({super.key, required this.candidates});
|
||||
final List<ShareIntentAttachment> candidates;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final uploadedCount = candidates.where((element) {
|
||||
return element.status == UploadStatus.complete;
|
||||
}).length;
|
||||
|
||||
return const Text("shared_intent_upload_button_progress_text")
|
||||
.tr(args: [uploadedCount.toString(), candidates.length.toString()]);
|
||||
}
|
||||
}
|
||||
|
||||
class UploadStatusIcon extends StatelessWidget {
|
||||
const UploadStatusIcon({
|
||||
super.key,
|
||||
required this.status,
|
||||
required this.selected,
|
||||
this.progress = 0,
|
||||
});
|
||||
|
||||
final UploadStatus status;
|
||||
final double progress;
|
||||
final bool selected;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (!selected) {
|
||||
return Icon(
|
||||
Icons.check_circle_outline_rounded,
|
||||
color: context.colorScheme.onSurface.withAlpha(100),
|
||||
semanticLabel: 'not_selected'.tr(),
|
||||
);
|
||||
}
|
||||
|
||||
final statusIcon = switch (status) {
|
||||
UploadStatus.enqueued => Icon(
|
||||
Icons.check_circle_rounded,
|
||||
color: context.primaryColor,
|
||||
semanticLabel: 'enqueued'.tr(),
|
||||
),
|
||||
UploadStatus.running => Stack(
|
||||
alignment: AlignmentDirectional.center,
|
||||
children: [
|
||||
SizedBox(
|
||||
width: 40,
|
||||
height: 40,
|
||||
child: TweenAnimationBuilder(
|
||||
tween: Tween<double>(begin: 0.0, end: progress),
|
||||
duration: const Duration(milliseconds: 500),
|
||||
builder: (context, value, _) => CircularProgressIndicator(
|
||||
backgroundColor: context.colorScheme.surfaceContainerLow,
|
||||
strokeWidth: 3,
|
||||
value: value,
|
||||
semanticsLabel: 'uploading'.tr(),
|
||||
),
|
||||
),
|
||||
),
|
||||
Text(
|
||||
(progress * 100).toStringAsFixed(0),
|
||||
style: context.textTheme.labelSmall?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
UploadStatus.complete => Icon(
|
||||
Icons.check_circle_rounded,
|
||||
color: Colors.green,
|
||||
semanticLabel: 'completed'.tr(),
|
||||
),
|
||||
UploadStatus.notFound || UploadStatus.failed => Icon(
|
||||
Icons.error_rounded,
|
||||
color: Colors.red,
|
||||
semanticLabel: 'failed'.tr(),
|
||||
),
|
||||
UploadStatus.canceled => Icon(
|
||||
Icons.cancel_rounded,
|
||||
color: Colors.red,
|
||||
semanticLabel: 'canceled'.tr(),
|
||||
),
|
||||
UploadStatus.waitingtoRetry || UploadStatus.paused => Icon(
|
||||
Icons.pause_circle_rounded,
|
||||
color: context.primaryColor,
|
||||
semanticLabel: 'paused'.tr(),
|
||||
),
|
||||
};
|
||||
|
||||
return statusIcon;
|
||||
}
|
||||
}
|
@ -46,8 +46,18 @@ class AlbumNotifier extends StateNotifier<List<Album>> {
|
||||
) =>
|
||||
_albumService.createAlbum(albumTitle, assets, []);
|
||||
|
||||
Future<Album?> getAlbumByName(String albumName, {bool remoteOnly = false}) =>
|
||||
_albumService.getAlbumByName(albumName, remoteOnly);
|
||||
Future<Album?> getAlbumByName(
|
||||
String albumName, {
|
||||
bool? remote,
|
||||
bool? shared,
|
||||
bool? owner,
|
||||
}) =>
|
||||
_albumService.getAlbumByName(
|
||||
albumName,
|
||||
remote: remote,
|
||||
shared: shared,
|
||||
owner: owner,
|
||||
);
|
||||
|
||||
/// Create an album on the server with the same name as the selected album for backup
|
||||
/// First this will check if the album already exists on the server with name
|
||||
@ -55,7 +65,7 @@ class AlbumNotifier extends StateNotifier<List<Album>> {
|
||||
Future<void> createSyncAlbum(
|
||||
String albumName,
|
||||
) async {
|
||||
final album = await getAlbumByName(albumName, remoteOnly: true);
|
||||
final album = await getAlbumByName(albumName, remote: true, owner: true);
|
||||
if (album != null) {
|
||||
return;
|
||||
}
|
||||
|
@ -0,0 +1,143 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:background_downloader/background_downloader.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/constants/constants.dart';
|
||||
import 'package:immich_mobile/extensions/string_extensions.dart';
|
||||
import 'package:immich_mobile/models/upload/share_intent_attachment.model.dart';
|
||||
import 'package:immich_mobile/routing/router.dart';
|
||||
import 'package:immich_mobile/services/share_intent_service.dart';
|
||||
import 'package:immich_mobile/services/upload.service.dart';
|
||||
|
||||
final shareIntentUploadProvider = StateNotifierProvider<
|
||||
ShareIntentUploadStateNotifier, List<ShareIntentAttachment>>(
|
||||
((ref) => ShareIntentUploadStateNotifier(
|
||||
ref.watch(appRouterProvider),
|
||||
ref.watch(uploadServiceProvider),
|
||||
ref.watch(shareIntentServiceProvider),
|
||||
)),
|
||||
);
|
||||
|
||||
class ShareIntentUploadStateNotifier
|
||||
extends StateNotifier<List<ShareIntentAttachment>> {
|
||||
final AppRouter router;
|
||||
final UploadService _uploadService;
|
||||
final ShareIntentService _shareIntentService;
|
||||
|
||||
ShareIntentUploadStateNotifier(
|
||||
this.router,
|
||||
this._uploadService,
|
||||
this._shareIntentService,
|
||||
) : super([]) {
|
||||
_uploadService.onUploadStatus = _uploadStatusCallback;
|
||||
_uploadService.onTaskProgress = _taskProgressCallback;
|
||||
}
|
||||
|
||||
void init() {
|
||||
_shareIntentService.onSharedMedia = onSharedMedia;
|
||||
_shareIntentService.init();
|
||||
}
|
||||
|
||||
void onSharedMedia(List<ShareIntentAttachment> attachments) {
|
||||
router.removeWhere((route) => route.name == "ShareIntentRoute");
|
||||
clearAttachments();
|
||||
addAttachments(attachments);
|
||||
router.push(ShareIntentRoute(attachments: attachments));
|
||||
}
|
||||
|
||||
void addAttachments(List<ShareIntentAttachment> attachments) {
|
||||
if (attachments.isEmpty) {
|
||||
return;
|
||||
}
|
||||
state = [...state, ...attachments];
|
||||
}
|
||||
|
||||
void removeAttachment(ShareIntentAttachment attachment) {
|
||||
final updatedState =
|
||||
state.where((element) => element != attachment).toList();
|
||||
if (updatedState.length != state.length) {
|
||||
state = updatedState;
|
||||
}
|
||||
}
|
||||
|
||||
void clearAttachments() {
|
||||
if (state.isEmpty) {
|
||||
return;
|
||||
}
|
||||
|
||||
state = [];
|
||||
}
|
||||
|
||||
void _updateUploadStatus(TaskStatusUpdate task, TaskStatus status) async {
|
||||
if (status == TaskStatus.canceled) {
|
||||
return;
|
||||
}
|
||||
|
||||
final taskId = task.task.taskId;
|
||||
final uploadStatus = switch (task.status) {
|
||||
TaskStatus.complete => UploadStatus.complete,
|
||||
TaskStatus.failed => UploadStatus.failed,
|
||||
TaskStatus.canceled => UploadStatus.canceled,
|
||||
TaskStatus.enqueued => UploadStatus.enqueued,
|
||||
TaskStatus.running => UploadStatus.running,
|
||||
TaskStatus.paused => UploadStatus.paused,
|
||||
TaskStatus.notFound => UploadStatus.notFound,
|
||||
TaskStatus.waitingToRetry => UploadStatus.waitingtoRetry
|
||||
};
|
||||
|
||||
state = [
|
||||
for (final attachment in state)
|
||||
if (attachment.id == taskId.toInt())
|
||||
attachment.copyWith(status: uploadStatus)
|
||||
else
|
||||
attachment,
|
||||
];
|
||||
}
|
||||
|
||||
void _uploadStatusCallback(TaskStatusUpdate update) {
|
||||
_updateUploadStatus(update, update.status);
|
||||
|
||||
switch (update.status) {
|
||||
case TaskStatus.complete:
|
||||
if (update.responseStatusCode == 200) {
|
||||
if (kDebugMode) {
|
||||
debugPrint("[COMPLETE] ${update.task.taskId} - DUPLICATE");
|
||||
}
|
||||
} else {
|
||||
if (kDebugMode) {
|
||||
debugPrint("[COMPLETE] ${update.task.taskId}");
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void _taskProgressCallback(TaskProgressUpdate update) {
|
||||
// Ignore if the task is cancled or completed
|
||||
if (update.progress == downloadFailed ||
|
||||
update.progress == downloadCompleted) {
|
||||
return;
|
||||
}
|
||||
|
||||
final taskId = update.task.taskId;
|
||||
state = [
|
||||
for (final attachment in state)
|
||||
if (attachment.id == taskId.toInt())
|
||||
attachment.copyWith(uploadProgress: update.progress)
|
||||
else
|
||||
attachment,
|
||||
];
|
||||
}
|
||||
|
||||
Future<void> upload(File file) {
|
||||
return _uploadService.upload(file);
|
||||
}
|
||||
|
||||
Future<bool> cancelUpload(String id) {
|
||||
return _uploadService.cancelUpload(id);
|
||||
}
|
||||
}
|
@ -34,11 +34,25 @@ class AlbumRepository extends DatabaseRepository implements IAlbumRepository {
|
||||
Future<Album> create(Album album) => txn(() => db.albums.store(album));
|
||||
|
||||
@override
|
||||
Future<Album?> getByName(String name, {bool? shared, bool? remote}) {
|
||||
Future<Album?> getByName(
|
||||
String name, {
|
||||
bool? shared,
|
||||
bool? remote,
|
||||
bool? owner,
|
||||
}) {
|
||||
var query = db.albums.filter().nameEqualTo(name);
|
||||
if (shared != null) {
|
||||
query = query.sharedEqualTo(shared);
|
||||
}
|
||||
if (owner == true) {
|
||||
query = query.owner(
|
||||
(q) => q.isarIdEqualTo(Store.get(StoreKey.currentUser).isarId),
|
||||
);
|
||||
} else if (owner == false) {
|
||||
query = query.owner(
|
||||
(q) => q.not().isarIdEqualTo(Store.get(StoreKey.currentUser).isarId),
|
||||
);
|
||||
}
|
||||
if (remote == true) {
|
||||
query = query.localIdIsNull();
|
||||
} else if (remote == false) {
|
||||
|
63
mobile/lib/repositories/share_handler.repository.dart
Normal file
63
mobile/lib/repositories/share_handler.repository.dart
Normal file
@ -0,0 +1,63 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/interfaces/share_handler.interface.dart';
|
||||
import 'package:immich_mobile/models/upload/share_intent_attachment.model.dart';
|
||||
import 'package:share_handler/share_handler.dart';
|
||||
|
||||
final shareHandlerRepositoryProvider = Provider(
|
||||
(ref) => ShareHandlerRepository(),
|
||||
);
|
||||
|
||||
class ShareHandlerRepository implements IShareHandlerRepository {
|
||||
ShareHandlerRepository();
|
||||
|
||||
@override
|
||||
void Function(List<ShareIntentAttachment> attachments)? onSharedMedia;
|
||||
|
||||
@override
|
||||
Future<void> init() async {
|
||||
final handler = ShareHandlerPlatform.instance;
|
||||
final media = await handler.getInitialSharedMedia();
|
||||
|
||||
if (media != null && media.attachments != null) {
|
||||
onSharedMedia?.call(_buildPayload(media.attachments!));
|
||||
}
|
||||
|
||||
handler.sharedMediaStream.listen((SharedMedia media) {
|
||||
if (media.attachments != null) {
|
||||
onSharedMedia?.call(_buildPayload(media.attachments!));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
List<ShareIntentAttachment> _buildPayload(
|
||||
List<SharedAttachment?> attachments,
|
||||
) {
|
||||
final payload = <ShareIntentAttachment>[];
|
||||
|
||||
for (final attachment in attachments) {
|
||||
if (attachment == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
final type = attachment.type == SharedAttachmentType.image
|
||||
? ShareIntentAttachmentType.image
|
||||
: ShareIntentAttachmentType.video;
|
||||
|
||||
final fileLength = File(attachment.path).lengthSync();
|
||||
|
||||
payload.add(
|
||||
ShareIntentAttachment(
|
||||
path: attachment.path,
|
||||
type: type,
|
||||
status: UploadStatus.enqueued,
|
||||
uploadProgress: 0.0,
|
||||
fileLength: fileLength,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return payload;
|
||||
}
|
||||
}
|
42
mobile/lib/repositories/upload.repository.dart
Normal file
42
mobile/lib/repositories/upload.repository.dart
Normal file
@ -0,0 +1,42 @@
|
||||
import 'package:background_downloader/background_downloader.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/interfaces/upload.interface.dart';
|
||||
import 'package:immich_mobile/utils/upload.dart';
|
||||
|
||||
final uploadRepositoryProvider = Provider((ref) => UploadRepository());
|
||||
|
||||
class UploadRepository implements IUploadRepository {
|
||||
@override
|
||||
void Function(TaskStatusUpdate)? onUploadStatus;
|
||||
|
||||
@override
|
||||
void Function(TaskProgressUpdate)? onTaskProgress;
|
||||
|
||||
UploadRepository() {
|
||||
FileDownloader().registerCallbacks(
|
||||
group: uploadGroup,
|
||||
taskStatusCallback: (update) => onUploadStatus?.call(update),
|
||||
taskProgressCallback: (update) => onTaskProgress?.call(update),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> upload(UploadTask task) {
|
||||
return FileDownloader().enqueue(task);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> deleteAllTrackingRecords() {
|
||||
return FileDownloader().database.deleteAllRecords();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> cancel(String id) {
|
||||
return FileDownloader().cancelTaskWithId(id);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> deleteRecordsWithIds(List<String> ids) {
|
||||
return FileDownloader().database.deleteRecordsWithIds(ids);
|
||||
}
|
||||
}
|
@ -8,6 +8,7 @@ import 'package:immich_mobile/entities/user.entity.dart';
|
||||
import 'package:immich_mobile/models/memories/memory.model.dart';
|
||||
import 'package:immich_mobile/models/search/search_filter.model.dart';
|
||||
import 'package:immich_mobile/models/shared_link/shared_link.model.dart';
|
||||
import 'package:immich_mobile/models/upload/share_intent_attachment.model.dart';
|
||||
import 'package:immich_mobile/pages/backup/album_preview.page.dart';
|
||||
import 'package:immich_mobile/pages/backup/backup_album_selection.page.dart';
|
||||
import 'package:immich_mobile/pages/backup/backup_controller.page.dart';
|
||||
@ -57,6 +58,7 @@ import 'package:immich_mobile/pages/library/partner/partner.page.dart';
|
||||
import 'package:immich_mobile/pages/library/partner/partner_detail.page.dart';
|
||||
import 'package:immich_mobile/pages/library/shared_link/shared_link.page.dart';
|
||||
import 'package:immich_mobile/pages/library/shared_link/shared_link_edit.page.dart';
|
||||
import 'package:immich_mobile/pages/share_intent/share_intent.page.dart';
|
||||
import 'package:immich_mobile/providers/api.provider.dart';
|
||||
import 'package:immich_mobile/providers/gallery_permission.provider.dart';
|
||||
import 'package:immich_mobile/routing/auth_guard.dart';
|
||||
@ -277,6 +279,10 @@ class AppRouter extends RootStackRouter {
|
||||
page: NativeVideoViewerRoute.page,
|
||||
guards: [_authGuard, _duplicateGuard],
|
||||
),
|
||||
AutoRoute(
|
||||
page: ShareIntentRoute.page,
|
||||
guards: [_authGuard, _duplicateGuard],
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -136,15 +136,10 @@ class AlbumAssetSelectionRouteArgs {
|
||||
|
||||
/// generated route for
|
||||
/// [AlbumOptionsPage]
|
||||
class AlbumOptionsRoute extends PageRouteInfo<AlbumOptionsRouteArgs> {
|
||||
AlbumOptionsRoute({
|
||||
Key? key,
|
||||
List<PageRouteInfo>? children,
|
||||
}) : super(
|
||||
class AlbumOptionsRoute extends PageRouteInfo<void> {
|
||||
const AlbumOptionsRoute({List<PageRouteInfo>? children})
|
||||
: super(
|
||||
AlbumOptionsRoute.name,
|
||||
args: AlbumOptionsRouteArgs(
|
||||
key: key,
|
||||
),
|
||||
initialChildren: children,
|
||||
);
|
||||
|
||||
@ -153,25 +148,11 @@ class AlbumOptionsRoute extends PageRouteInfo<AlbumOptionsRouteArgs> {
|
||||
static PageInfo page = PageInfo(
|
||||
name,
|
||||
builder: (data) {
|
||||
final args = data.argsAs<AlbumOptionsRouteArgs>();
|
||||
return AlbumOptionsPage(
|
||||
key: args.key,
|
||||
);
|
||||
return const AlbumOptionsPage();
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
class AlbumOptionsRouteArgs {
|
||||
const AlbumOptionsRouteArgs({this.key});
|
||||
|
||||
final Key? key;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'AlbumOptionsRouteArgs{key: $key}';
|
||||
}
|
||||
}
|
||||
|
||||
/// generated route for
|
||||
/// [AlbumPreviewPage]
|
||||
class AlbumPreviewRoute extends PageRouteInfo<AlbumPreviewRouteArgs> {
|
||||
@ -1453,6 +1434,52 @@ class SettingsSubRouteArgs {
|
||||
}
|
||||
}
|
||||
|
||||
/// generated route for
|
||||
/// [ShareIntentPage]
|
||||
class ShareIntentRoute extends PageRouteInfo<ShareIntentRouteArgs> {
|
||||
ShareIntentRoute({
|
||||
Key? key,
|
||||
required List<ShareIntentAttachment> attachments,
|
||||
List<PageRouteInfo>? children,
|
||||
}) : super(
|
||||
ShareIntentRoute.name,
|
||||
args: ShareIntentRouteArgs(
|
||||
key: key,
|
||||
attachments: attachments,
|
||||
),
|
||||
initialChildren: children,
|
||||
);
|
||||
|
||||
static const String name = 'ShareIntentRoute';
|
||||
|
||||
static PageInfo page = PageInfo(
|
||||
name,
|
||||
builder: (data) {
|
||||
final args = data.argsAs<ShareIntentRouteArgs>();
|
||||
return ShareIntentPage(
|
||||
key: args.key,
|
||||
attachments: args.attachments,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
class ShareIntentRouteArgs {
|
||||
const ShareIntentRouteArgs({
|
||||
this.key,
|
||||
required this.attachments,
|
||||
});
|
||||
|
||||
final Key? key;
|
||||
|
||||
final List<ShareIntentAttachment> attachments;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'ShareIntentRouteArgs{key: $key, attachments: $attachments}';
|
||||
}
|
||||
}
|
||||
|
||||
/// generated route for
|
||||
/// [SharedLinkEditPage]
|
||||
class SharedLinkEditRoute extends PageRouteInfo<SharedLinkEditRouteArgs> {
|
||||
|
@ -170,7 +170,12 @@ class AlbumService {
|
||||
try {
|
||||
await _userService.refreshUsers();
|
||||
final (sharedAlbum, ownedAlbum) = await (
|
||||
// Note: `shared: true` is required to get albums that don't belong to
|
||||
// us due to unusual behaviour on the API but this will also return our
|
||||
// own shared albums
|
||||
_albumApiRepository.getAll(shared: true),
|
||||
// Passing null (or nothing) for `shared` returns only albums that
|
||||
// explicitly belong to us
|
||||
_albumApiRepository.getAll(shared: null)
|
||||
).wait;
|
||||
|
||||
@ -212,7 +217,7 @@ class AlbumService {
|
||||
for (int round = 0;; round++) {
|
||||
final proposedName = "$baseName${round == 0 ? "" : " ($round)"}";
|
||||
|
||||
if (null == await _albumRepository.getByName(proposedName)) {
|
||||
if (null == await _albumRepository.getByName(proposedName, owner: true)) {
|
||||
return proposedName;
|
||||
}
|
||||
}
|
||||
@ -408,8 +413,18 @@ class AlbumService {
|
||||
}
|
||||
}
|
||||
|
||||
Future<Album?> getAlbumByName(String name, bool remoteOnly) =>
|
||||
_albumRepository.getByName(name, remote: remoteOnly ? true : null);
|
||||
Future<Album?> getAlbumByName(
|
||||
String name, {
|
||||
bool? remote,
|
||||
bool? shared,
|
||||
bool? owner,
|
||||
}) =>
|
||||
_albumRepository.getByName(
|
||||
name,
|
||||
remote: remote,
|
||||
shared: shared,
|
||||
owner: owner,
|
||||
);
|
||||
|
||||
///
|
||||
/// Add the uploaded asset to the selected albums
|
||||
@ -419,7 +434,7 @@ class AlbumService {
|
||||
List<String> assetIds,
|
||||
) async {
|
||||
for (final albumName in albumNames) {
|
||||
Album? album = await getAlbumByName(albumName, true);
|
||||
Album? album = await getAlbumByName(albumName, remote: true, owner: true);
|
||||
album ??= await createAlbum(albumName, []);
|
||||
if (album != null && album.remoteId != null) {
|
||||
await _albumApiRepository.addAssets(album.remoteId!, assetIds);
|
||||
|
23
mobile/lib/services/share_intent_service.dart
Normal file
23
mobile/lib/services/share_intent_service.dart
Normal file
@ -0,0 +1,23 @@
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/models/upload/share_intent_attachment.model.dart';
|
||||
import 'package:immich_mobile/repositories/share_handler.repository.dart';
|
||||
|
||||
final shareIntentServiceProvider = Provider(
|
||||
(ref) => ShareIntentService(
|
||||
ref.watch(shareHandlerRepositoryProvider),
|
||||
),
|
||||
);
|
||||
|
||||
class ShareIntentService {
|
||||
final ShareHandlerRepository shareHandlerRepository;
|
||||
void Function(List<ShareIntentAttachment> attachments)? onSharedMedia;
|
||||
|
||||
ShareIntentService(
|
||||
this.shareHandlerRepository,
|
||||
);
|
||||
|
||||
void init() {
|
||||
shareHandlerRepository.onSharedMedia = onSharedMedia;
|
||||
shareHandlerRepository.init();
|
||||
}
|
||||
}
|
94
mobile/lib/services/upload.service.dart
Normal file
94
mobile/lib/services/upload.service.dart
Normal file
@ -0,0 +1,94 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:background_downloader/background_downloader.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/entities/store.entity.dart';
|
||||
import 'package:immich_mobile/interfaces/upload.interface.dart';
|
||||
import 'package:immich_mobile/repositories/upload.repository.dart';
|
||||
import 'package:immich_mobile/services/api.service.dart';
|
||||
import 'package:immich_mobile/utils/upload.dart';
|
||||
import 'package:path/path.dart';
|
||||
// import 'package:logging/logging.dart';
|
||||
|
||||
final uploadServiceProvider = Provider(
|
||||
(ref) => UploadService(
|
||||
ref.watch(uploadRepositoryProvider),
|
||||
),
|
||||
);
|
||||
|
||||
class UploadService {
|
||||
final IUploadRepository _uploadRepository;
|
||||
// final Logger _log = Logger("UploadService");
|
||||
void Function(TaskStatusUpdate)? onUploadStatus;
|
||||
void Function(TaskProgressUpdate)? onTaskProgress;
|
||||
|
||||
UploadService(
|
||||
this._uploadRepository,
|
||||
) {
|
||||
_uploadRepository.onUploadStatus = _onUploadCallback;
|
||||
_uploadRepository.onTaskProgress = _onTaskProgressCallback;
|
||||
}
|
||||
|
||||
void _onTaskProgressCallback(TaskProgressUpdate update) {
|
||||
onTaskProgress?.call(update);
|
||||
}
|
||||
|
||||
void _onUploadCallback(TaskStatusUpdate update) {
|
||||
onUploadStatus?.call(update);
|
||||
}
|
||||
|
||||
Future<bool> cancelUpload(String id) {
|
||||
return FileDownloader().cancelTaskWithId(id);
|
||||
}
|
||||
|
||||
Future<void> upload(File file) async {
|
||||
final task = await _buildUploadTask(
|
||||
hash(file.path).toString(),
|
||||
file,
|
||||
);
|
||||
|
||||
await _uploadRepository.upload(task);
|
||||
}
|
||||
|
||||
Future<UploadTask> _buildUploadTask(
|
||||
String id,
|
||||
File file, {
|
||||
Map<String, String>? fields,
|
||||
}) async {
|
||||
final serverEndpoint = Store.get(StoreKey.serverEndpoint);
|
||||
final url = Uri.parse('$serverEndpoint/assets').toString();
|
||||
final headers = ApiService.getRequestHeaders();
|
||||
final deviceId = Store.get(StoreKey.deviceId);
|
||||
|
||||
final (baseDirectory, directory, filename) =
|
||||
await Task.split(filePath: file.path);
|
||||
final stats = await file.stat();
|
||||
final fileCreatedAt = stats.changed;
|
||||
final fileModifiedAt = stats.modified;
|
||||
|
||||
final fieldsMap = {
|
||||
'filename': filename,
|
||||
'deviceAssetId': id,
|
||||
'deviceId': deviceId,
|
||||
'fileCreatedAt': fileCreatedAt.toUtc().toIso8601String(),
|
||||
'fileModifiedAt': fileModifiedAt.toUtc().toIso8601String(),
|
||||
'isFavorite': 'false',
|
||||
'duration': '0',
|
||||
if (fields != null) ...fields,
|
||||
};
|
||||
|
||||
return UploadTask(
|
||||
taskId: id,
|
||||
httpRequestMethod: 'POST',
|
||||
url: url,
|
||||
headers: headers,
|
||||
filename: filename,
|
||||
fields: fieldsMap,
|
||||
baseDirectory: baseDirectory,
|
||||
directory: directory,
|
||||
fileField: 'assetData',
|
||||
group: uploadGroup,
|
||||
updates: Updates.statusAndProgress,
|
||||
);
|
||||
}
|
||||
}
|
@ -1,3 +1,5 @@
|
||||
import 'dart:math';
|
||||
|
||||
String formatBytes(int bytes) {
|
||||
const units = ['B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB'];
|
||||
|
||||
@ -14,3 +16,10 @@ String formatBytes(int bytes) {
|
||||
|
||||
return "${remainder.toStringAsFixed(magnitude == 0 ? 0 : 1)} ${units[magnitude]}";
|
||||
}
|
||||
|
||||
String formatHumanReadableBytes(int bytes, int decimals) {
|
||||
if (bytes <= 0) return "0 B";
|
||||
const suffixes = ["B", "KB", "MB", "GB", "TB"];
|
||||
var i = (log(bytes) / log(1024)).floor();
|
||||
return '${(bytes / pow(1024, i)).toStringAsFixed(decimals)} ${suffixes[i]}';
|
||||
}
|
||||
|
1
mobile/lib/utils/upload.dart
Normal file
1
mobile/lib/utils/upload.dart
Normal file
@ -0,0 +1 @@
|
||||
const uploadGroup = 'upload_group';
|
@ -206,7 +206,7 @@ class AlbumViewerAppbar extends HookConsumerWidget
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.settings_rounded),
|
||||
onTap: () => context.navigateTo(AlbumOptionsRoute()),
|
||||
onTap: () => context.navigateTo(const AlbumOptionsRoute()),
|
||||
title: const Text(
|
||||
"translated_text_options",
|
||||
style: TextStyle(fontWeight: FontWeight.w500),
|
||||
|
@ -1328,6 +1328,38 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.3.8"
|
||||
share_handler:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: share_handler
|
||||
sha256: "76575533be04df3fecbebd3c5b5325a8271b5973131f8b8b0ab8490c395a5d37"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.0.22"
|
||||
share_handler_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: share_handler_android
|
||||
sha256: "124dcc914fb7ecd89076d3dc28435b98fe2129a988bf7742f7a01dcb66a95667"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.0.9"
|
||||
share_handler_ios:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: share_handler_ios
|
||||
sha256: cdc21f88f336a944157a8e9ceb191525cee3b082d6eb6c2082488e4f09dc3ece
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.0.15"
|
||||
share_handler_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: share_handler_platform_interface
|
||||
sha256: "7a4df95a87b326b2f07458d937f2281874567c364b7b7ebe4e7d50efaae5f106"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.0.6"
|
||||
share_plus:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -77,6 +77,7 @@ dependencies:
|
||||
image_picker: ^1.0.7 # only used to select user profile image from system gallery -> we can simply select an image from within immich?
|
||||
logging: ^1.2.0
|
||||
file_picker: ^8.0.0+1
|
||||
share_handler: ^0.0.22
|
||||
|
||||
# This is uncommented in F-Droid build script
|
||||
# Taken from https://github.com/Myzel394/locus/blob/445013d22ec1d759027d4303bd65b30c5c8588c8/pubspec.yaml#L105
|
||||
|
@ -6,6 +6,10 @@
|
||||
],
|
||||
"minimumReleaseAge": "5 days",
|
||||
"packageRules": [
|
||||
{
|
||||
"groupName": "@immich/ui",
|
||||
"matchPackageNames": ["@immich/ui"]
|
||||
},
|
||||
{
|
||||
"matchFileNames": [
|
||||
"cli/**",
|
||||
|
@ -10,6 +10,7 @@ node_modules
|
||||
coverage
|
||||
dist
|
||||
**/migrations/**
|
||||
db.d.ts
|
||||
|
||||
# Ignore files for PNPM, NPM and YARN
|
||||
pnpm-lock.yaml
|
||||
|
@ -1,5 +1,5 @@
|
||||
# dev build
|
||||
FROM ghcr.io/immich-app/base-server-dev:20250107@sha256:d00ab37e1c1ed87b799d6509fbc825a721ca0723c59c67955217826882017d38 AS dev
|
||||
FROM ghcr.io/immich-app/base-server-dev:20250114@sha256:fce0404484bde5afc38a4399c6b25895eb079a666d269f199c93dfbfdd5b26b6 AS dev
|
||||
|
||||
RUN apt-get install --no-install-recommends -yqq tini
|
||||
WORKDIR /usr/src/app
|
||||
@ -25,7 +25,7 @@ COPY --from=dev /usr/src/app/node_modules/@img ./node_modules/@img
|
||||
COPY --from=dev /usr/src/app/node_modules/exiftool-vendored.pl ./node_modules/exiftool-vendored.pl
|
||||
|
||||
# web build
|
||||
FROM node:22.12.0-alpine3.20@sha256:96cc8323e25c8cc6ddcb8b965e135cfd57846e8003ec0d7bcec16c5fd5f6d39f AS web
|
||||
FROM node:22.13.0-alpine3.20@sha256:db8dcb90326a0116375414e9a7c068a6b87a4422b7da37b5c6cd026f7c7835d3 AS web
|
||||
|
||||
WORKDIR /usr/src/open-api/typescript-sdk
|
||||
COPY open-api/typescript-sdk/package*.json open-api/typescript-sdk/tsconfig*.json ./
|
||||
@ -42,7 +42,7 @@ RUN npm run build
|
||||
|
||||
|
||||
# prod build
|
||||
FROM ghcr.io/immich-app/base-server-prod:20250107@sha256:78e92f113103271d43a3b050370b21b31c3c14792d3d23b18b542581a440c72b
|
||||
FROM ghcr.io/immich-app/base-server-prod:20250114@sha256:94ec8a36cdf11691810c4aeccee1b49b00348e17f6b6781d87dd48a74e6c6787
|
||||
|
||||
WORKDIR /usr/src/app
|
||||
ENV NODE_ENV=production \
|
||||
|
@ -29,6 +29,7 @@
|
||||
"typeorm:migrations:revert": "typeorm migration:revert -d ./dist/bin/database.js",
|
||||
"typeorm:schema:drop": "typeorm query -d ./dist/bin/database.js 'DROP schema public cascade; CREATE schema public;'",
|
||||
"typeorm:schema:reset": "npm run typeorm:schema:drop && npm run typeorm:migrations:run",
|
||||
"kysely:codegen": "npx kysely-codegen --include-pattern=\"(public|vectors).*\" --dialect postgres --url postgres://postgres:postgres@localhost/immich --log-level debug --out-file=./src/db.d.ts",
|
||||
"sync:open-api": "node ./dist/bin/sync-open-api.js",
|
||||
"sync:sql": "node ./dist/bin/sync-sql.js",
|
||||
"email:dev": "email dev -p 3050 --dir src/emails"
|
||||
|
@ -20,14 +20,14 @@ import { ErrorInterceptor } from 'src/middleware/error.interceptor';
|
||||
import { FileUploadInterceptor } from 'src/middleware/file-upload.interceptor';
|
||||
import { GlobalExceptionFilter } from 'src/middleware/global-exception.filter';
|
||||
import { LoggingInterceptor } from 'src/middleware/logging.interceptor';
|
||||
import { repositories } from 'src/repositories';
|
||||
import { providers, repositories } from 'src/repositories';
|
||||
import { ConfigRepository } from 'src/repositories/config.repository';
|
||||
import { teardownTelemetry } from 'src/repositories/telemetry.repository';
|
||||
import { services } from 'src/services';
|
||||
import { CliService } from 'src/services/cli.service';
|
||||
import { DatabaseService } from 'src/services/database.service';
|
||||
|
||||
const common = [...services, ...repositories];
|
||||
const common = [...services, ...providers, ...repositories];
|
||||
|
||||
const middleware = [
|
||||
FileUploadInterceptor,
|
||||
@ -73,7 +73,7 @@ class BaseModule implements OnModuleInit, OnModuleDestroy {
|
||||
}
|
||||
|
||||
async onModuleInit() {
|
||||
this.telemetryRepository.setup({ repositories: repositories.map(({ useClass }) => useClass) });
|
||||
this.telemetryRepository.setup({ repositories: [...providers.map(({ useClass }) => useClass), ...repositories] });
|
||||
|
||||
this.jobRepository.setup({ services });
|
||||
if (this.worker === ImmichWorker.MICROSERVICES) {
|
||||
|
@ -12,7 +12,7 @@ import { format } from 'sql-formatter';
|
||||
import { GENERATE_SQL_KEY, GenerateSqlQueries } from 'src/decorators';
|
||||
import { entities } from 'src/entities';
|
||||
import { ILoggerRepository } from 'src/interfaces/logger.interface';
|
||||
import { repositories } from 'src/repositories';
|
||||
import { providers, repositories } from 'src/repositories';
|
||||
import { AccessRepository } from 'src/repositories/access.repository';
|
||||
import { ConfigRepository } from 'src/repositories/config.repository';
|
||||
import { AuthService } from 'src/services/auth.service';
|
||||
@ -43,7 +43,7 @@ export class SqlLogger implements Logger {
|
||||
|
||||
const reflector = new Reflector();
|
||||
|
||||
type Repository = (typeof repositories)[0]['useClass'];
|
||||
type Repository = (typeof providers)[0]['useClass'];
|
||||
type Provider = { provide: any; useClass: Repository };
|
||||
type SqlGeneratorOptions = { targetDir: string };
|
||||
|
||||
@ -57,7 +57,11 @@ class SqlGenerator {
|
||||
async run() {
|
||||
try {
|
||||
await this.setup();
|
||||
for (const repository of repositories) {
|
||||
const targets = [
|
||||
...providers,
|
||||
...repositories.map((repository) => ({ provide: repository, useClass: repository as any })),
|
||||
];
|
||||
for (const repository of targets) {
|
||||
if (repository.provide === ILoggerRepository) {
|
||||
continue;
|
||||
}
|
||||
@ -86,6 +90,7 @@ class SqlGenerator {
|
||||
this.sqlLogger.logQuery(event.query.sql);
|
||||
} else if (event.level === 'error') {
|
||||
this.sqlLogger.logQueryError(event.error as Error, event.query.sql);
|
||||
this.sqlLogger.logQuery(event.query.sql);
|
||||
}
|
||||
},
|
||||
}),
|
||||
@ -98,7 +103,7 @@ class SqlGenerator {
|
||||
TypeOrmModule.forFeature(entities),
|
||||
OpenTelemetryModule.forRoot(otel),
|
||||
],
|
||||
providers: [...repositories, AuthService, SchedulerRegistry],
|
||||
providers: [...providers, ...repositories, AuthService, SchedulerRegistry],
|
||||
}).compile();
|
||||
|
||||
this.app = await moduleFixture.createNestApplication().init();
|
||||
|
3
server/src/database.ts
Normal file
3
server/src/database.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export const columns = {
|
||||
userDto: ['id', 'name', 'email', 'profileImagePath', 'profileChangedAt'],
|
||||
} as const;
|
23
server/src/db.d.ts
vendored
23
server/src/db.d.ts
vendored
@ -3,16 +3,21 @@
|
||||
* Please do not edit it manually.
|
||||
*/
|
||||
|
||||
import type { ColumnType } from 'kysely';
|
||||
import type { ColumnType } from "kysely";
|
||||
|
||||
export type ArrayType<T> = ArrayTypeImpl<T> extends (infer U)[] ? U[] : ArrayTypeImpl<T>;
|
||||
export type ArrayType<T> = ArrayTypeImpl<T> extends (infer U)[]
|
||||
? U[]
|
||||
: ArrayTypeImpl<T>;
|
||||
|
||||
export type ArrayTypeImpl<T> = T extends ColumnType<infer S, infer I, infer U> ? ColumnType<S[], I[], U[]> : T[];
|
||||
export type ArrayTypeImpl<T> = T extends ColumnType<infer S, infer I, infer U>
|
||||
? ColumnType<S[], I[], U[]>
|
||||
: T[];
|
||||
|
||||
export type AssetsStatusEnum = 'active' | 'deleted' | 'trashed';
|
||||
export type AssetsStatusEnum = "active" | "deleted" | "trashed";
|
||||
|
||||
export type Generated<T> =
|
||||
T extends ColumnType<infer S, infer I, infer U> ? ColumnType<S, I | undefined, U> : ColumnType<T, T | undefined, T>;
|
||||
export type Generated<T> = T extends ColumnType<infer S, infer I, infer U>
|
||||
? ColumnType<S, I | undefined, U>
|
||||
: ColumnType<T, T | undefined, T>;
|
||||
|
||||
export type Int8 = ColumnType<string, bigint | number | string, bigint | number | string>;
|
||||
|
||||
@ -28,7 +33,7 @@ export type JsonPrimitive = boolean | number | string | null;
|
||||
|
||||
export type JsonValue = JsonArray | JsonObject | JsonPrimitive;
|
||||
|
||||
export type Sourcetype = 'exif' | 'machine-learning';
|
||||
export type Sourcetype = "exif" | "machine-learning";
|
||||
|
||||
export type Timestamp = ColumnType<Date, Date | string, Date | string>;
|
||||
|
||||
@ -257,7 +262,7 @@ export interface NaturalearthCountries {
|
||||
admin: string;
|
||||
admin_a3: string;
|
||||
coordinates: string;
|
||||
id: number;
|
||||
id: Generated<number>;
|
||||
type: string;
|
||||
}
|
||||
|
||||
@ -433,6 +438,6 @@ export interface DB {
|
||||
tags_closure: TagsClosure;
|
||||
user_metadata: UserMetadata;
|
||||
users: Users;
|
||||
'vectors.pg_vector_index_stat': VectorsPgVectorIndexStat;
|
||||
"vectors.pg_vector_index_stat": VectorsPgVectorIndexStat;
|
||||
version_history: VersionHistory;
|
||||
}
|
||||
|
@ -1,7 +1,8 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { IsEnum, IsNotEmpty, IsString, ValidateIf } from 'class-validator';
|
||||
import { UserResponseDto, mapUser } from 'src/dtos/user.dto';
|
||||
import { ActivityEntity } from 'src/entities/activity.entity';
|
||||
import { mapUser, UserResponseDto } from 'src/dtos/user.dto';
|
||||
import { UserEntity } from 'src/entities/user.entity';
|
||||
import { ActivityItem } from 'src/types';
|
||||
import { Optional, ValidateUUID } from 'src/validation';
|
||||
|
||||
export enum ReactionType {
|
||||
@ -67,13 +68,13 @@ export class ActivityCreateDto extends ActivityDto {
|
||||
comment?: string;
|
||||
}
|
||||
|
||||
export function mapActivity(activity: ActivityEntity): ActivityResponseDto {
|
||||
export const mapActivity = (activity: ActivityItem): ActivityResponseDto => {
|
||||
return {
|
||||
id: activity.id,
|
||||
assetId: activity.assetId,
|
||||
createdAt: activity.createdAt,
|
||||
comment: activity.comment,
|
||||
type: activity.isLiked ? ReactionType.LIKE : ReactionType.COMMENT,
|
||||
user: mapUser(activity.user),
|
||||
user: mapUser(activity.user as unknown as UserEntity),
|
||||
};
|
||||
}
|
||||
};
|
||||
|
@ -1,13 +0,0 @@
|
||||
import { Insertable } from 'kysely';
|
||||
import { Activity } from 'src/db';
|
||||
import { ActivityEntity } from 'src/entities/activity.entity';
|
||||
import { ActivitySearch } from 'src/repositories/activity.repository';
|
||||
|
||||
export const IActivityRepository = 'IActivityRepository';
|
||||
|
||||
export interface IActivityRepository {
|
||||
search(options: ActivitySearch): Promise<ActivityEntity[]>;
|
||||
create(activity: Insertable<Activity>): Promise<ActivityEntity>;
|
||||
delete(id: string): Promise<void>;
|
||||
getStatistics(options: { albumId: string; assetId?: string }): Promise<number>;
|
||||
}
|
@ -1,14 +1,18 @@
|
||||
import { AlbumUserEntity } from 'src/entities/album-user.entity';
|
||||
import { Insertable, Selectable, Updateable } from 'kysely';
|
||||
import { AlbumsSharedUsersUsers } from 'src/db';
|
||||
|
||||
export const IAlbumUserRepository = 'IAlbumUserRepository';
|
||||
|
||||
export type AlbumPermissionId = {
|
||||
albumId: string;
|
||||
userId: string;
|
||||
albumsId: string;
|
||||
usersId: string;
|
||||
};
|
||||
|
||||
export interface IAlbumUserRepository {
|
||||
create(albumUser: Partial<AlbumUserEntity>): Promise<AlbumUserEntity>;
|
||||
update({ userId, albumId }: AlbumPermissionId, albumPermission: Partial<AlbumUserEntity>): Promise<AlbumUserEntity>;
|
||||
delete({ userId, albumId }: AlbumPermissionId): Promise<void>;
|
||||
create(albumUser: Insertable<AlbumsSharedUsersUsers>): Promise<Selectable<AlbumsSharedUsersUsers>>;
|
||||
update(
|
||||
id: AlbumPermissionId,
|
||||
albumPermission: Updateable<AlbumsSharedUsersUsers>,
|
||||
): Promise<Selectable<AlbumsSharedUsersUsers>>;
|
||||
delete(id: AlbumPermissionId): Promise<void>;
|
||||
}
|
||||
|
@ -1,3 +1,5 @@
|
||||
import { Insertable, Updateable } from 'kysely';
|
||||
import { Libraries } from 'src/db';
|
||||
import { LibraryStatsResponseDto } from 'src/dtos/library.dto';
|
||||
import { LibraryEntity } from 'src/entities/library.entity';
|
||||
|
||||
@ -6,10 +8,10 @@ export const ILibraryRepository = 'ILibraryRepository';
|
||||
export interface ILibraryRepository {
|
||||
getAll(withDeleted?: boolean): Promise<LibraryEntity[]>;
|
||||
getAllDeleted(): Promise<LibraryEntity[]>;
|
||||
get(id: string, withDeleted?: boolean): Promise<LibraryEntity | null>;
|
||||
create(library: Partial<LibraryEntity>): Promise<LibraryEntity>;
|
||||
get(id: string, withDeleted?: boolean): Promise<LibraryEntity | undefined>;
|
||||
create(library: Insertable<Libraries>): Promise<LibraryEntity>;
|
||||
delete(id: string): Promise<void>;
|
||||
softDelete(id: string): Promise<void>;
|
||||
update(library: Partial<LibraryEntity>): Promise<LibraryEntity>;
|
||||
update(id: string, library: Updateable<Libraries>): Promise<LibraryEntity>;
|
||||
getStatistics(id: string): Promise<LibraryStatsResponseDto | undefined>;
|
||||
}
|
||||
|
@ -1,4 +1,6 @@
|
||||
import { MemoryEntity } from 'src/entities/memory.entity';
|
||||
import { Insertable, Updateable } from 'kysely';
|
||||
import { Memories } from 'src/db';
|
||||
import { MemoryEntity, OnThisDayData } from 'src/entities/memory.entity';
|
||||
import { IBulkAsset } from 'src/utils/asset.util';
|
||||
|
||||
export const IMemoryRepository = 'IMemoryRepository';
|
||||
@ -6,7 +8,10 @@ export const IMemoryRepository = 'IMemoryRepository';
|
||||
export interface IMemoryRepository extends IBulkAsset {
|
||||
search(ownerId: string): Promise<MemoryEntity[]>;
|
||||
get(id: string): Promise<MemoryEntity | null>;
|
||||
create(memory: Partial<MemoryEntity>): Promise<MemoryEntity>;
|
||||
update(memory: Partial<MemoryEntity>): Promise<MemoryEntity>;
|
||||
create(
|
||||
memory: Omit<Insertable<Memories>, 'data'> & { data: OnThisDayData },
|
||||
assetIds: Set<string>,
|
||||
): Promise<MemoryEntity>;
|
||||
update(id: string, memory: Updateable<Memories>): Promise<MemoryEntity>;
|
||||
delete(id: string): Promise<void>;
|
||||
}
|
||||
|
@ -1,3 +1,5 @@
|
||||
import { Updateable } from 'kysely';
|
||||
import { Partners } from 'src/db';
|
||||
import { PartnerEntity } from 'src/entities/partner.entity';
|
||||
|
||||
export interface PartnerIds {
|
||||
@ -14,8 +16,8 @@ export const IPartnerRepository = 'IPartnerRepository';
|
||||
|
||||
export interface IPartnerRepository {
|
||||
getAll(userId: string): Promise<PartnerEntity[]>;
|
||||
get(partner: PartnerIds): Promise<PartnerEntity | null>;
|
||||
get(partner: PartnerIds): Promise<PartnerEntity | undefined>;
|
||||
create(partner: PartnerIds): Promise<PartnerEntity>;
|
||||
remove(entity: PartnerEntity): Promise<void>;
|
||||
update(entity: Partial<PartnerEntity>): Promise<PartnerEntity>;
|
||||
remove(partner: PartnerIds): Promise<void>;
|
||||
update(partner: PartnerIds, entity: Updateable<Partners>): Promise<PartnerEntity>;
|
||||
}
|
||||
|
@ -1,180 +1,137 @@
|
||||
-- NOTE: This file is auto generated by ./sql-generator
|
||||
|
||||
-- AccessRepository.activity.checkOwnerAccess
|
||||
SELECT
|
||||
"ActivityEntity"."id" AS "ActivityEntity_id"
|
||||
FROM
|
||||
"activity" "ActivityEntity"
|
||||
WHERE
|
||||
(
|
||||
("ActivityEntity"."id" IN ($1))
|
||||
AND ("ActivityEntity"."userId" = $2)
|
||||
)
|
||||
select
|
||||
"activity"."id"
|
||||
from
|
||||
"activity"
|
||||
where
|
||||
"activity"."id" in ($1)
|
||||
and "activity"."userId" = $2
|
||||
|
||||
-- AccessRepository.activity.checkAlbumOwnerAccess
|
||||
SELECT
|
||||
"ActivityEntity"."id" AS "ActivityEntity_id"
|
||||
FROM
|
||||
"activity" "ActivityEntity"
|
||||
LEFT JOIN "albums" "ActivityEntity__ActivityEntity_album" ON "ActivityEntity__ActivityEntity_album"."id" = "ActivityEntity"."albumId"
|
||||
AND (
|
||||
"ActivityEntity__ActivityEntity_album"."deletedAt" IS NULL
|
||||
)
|
||||
WHERE
|
||||
(
|
||||
("ActivityEntity"."id" IN ($1))
|
||||
AND (
|
||||
(
|
||||
(
|
||||
"ActivityEntity__ActivityEntity_album"."ownerId" = $2
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
select
|
||||
"activity"."id"
|
||||
from
|
||||
"activity"
|
||||
left join "albums" on "activity"."albumId" = "albums"."id"
|
||||
and "albums"."deletedAt" is null
|
||||
where
|
||||
"activity"."id" in ($1)
|
||||
and "albums"."ownerId" = $2::uuid
|
||||
|
||||
-- AccessRepository.activity.checkCreateAccess
|
||||
SELECT
|
||||
"album"."id" AS "album_id"
|
||||
FROM
|
||||
"albums" "album"
|
||||
LEFT JOIN "albums_shared_users_users" "album_albumUsers_users" ON "album_albumUsers_users"."albumsId" = "album"."id"
|
||||
LEFT JOIN "users" "albumUsers" ON "albumUsers"."id" = "album_albumUsers_users"."usersId"
|
||||
AND ("albumUsers"."deletedAt" IS NULL)
|
||||
WHERE
|
||||
(
|
||||
"album"."id" IN ($1)
|
||||
AND "album"."isActivityEnabled" = true
|
||||
AND (
|
||||
"album"."ownerId" = $2
|
||||
OR "albumUsers"."id" = $2
|
||||
)
|
||||
select
|
||||
"albums"."id"
|
||||
from
|
||||
"albums"
|
||||
left join "albums_shared_users_users" as "albumUsers" on "albumUsers"."albumsId" = "albums"."id"
|
||||
left join "users" on "users"."id" = "albumUsers"."usersId"
|
||||
and "users"."deletedAt" is null
|
||||
where
|
||||
"albums"."id" in ($1)
|
||||
and "albums"."isActivityEnabled" = $2
|
||||
and (
|
||||
"albums"."ownerId" = $3
|
||||
or "users"."id" = $4
|
||||
)
|
||||
AND ("album"."deletedAt" IS NULL)
|
||||
and "albums"."deletedAt" is null
|
||||
|
||||
-- AccessRepository.album.checkOwnerAccess
|
||||
SELECT
|
||||
"AlbumEntity"."id" AS "AlbumEntity_id"
|
||||
FROM
|
||||
"albums" "AlbumEntity"
|
||||
WHERE
|
||||
(
|
||||
(
|
||||
("AlbumEntity"."id" IN ($1))
|
||||
AND ("AlbumEntity"."ownerId" = $2)
|
||||
)
|
||||
)
|
||||
AND ("AlbumEntity"."deletedAt" IS NULL)
|
||||
select
|
||||
"albums"."id"
|
||||
from
|
||||
"albums"
|
||||
where
|
||||
"albums"."id" in ($1)
|
||||
and "albums"."ownerId" = $2
|
||||
and "albums"."deletedAt" is null
|
||||
|
||||
-- AccessRepository.album.checkSharedAlbumAccess
|
||||
SELECT
|
||||
"AlbumEntity"."id" AS "AlbumEntity_id"
|
||||
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
|
||||
)
|
||||
WHERE
|
||||
(
|
||||
(
|
||||
("AlbumEntity"."id" IN ($1))
|
||||
AND (
|
||||
(
|
||||
(
|
||||
(
|
||||
(
|
||||
"a641d58cf46d4a391ba060ac4dc337665c69ffea"."id" = $2
|
||||
)
|
||||
)
|
||||
)
|
||||
AND (
|
||||
"AlbumEntity__AlbumEntity_albumUsers"."role" IN ($3, $4)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
AND ("AlbumEntity"."deletedAt" IS NULL)
|
||||
select
|
||||
"albums"."id"
|
||||
from
|
||||
"albums"
|
||||
left join "albums_shared_users_users" as "albumUsers" on "albumUsers"."albumsId" = "albums"."id"
|
||||
left join "users" on "users"."id" = "albumUsers"."usersId"
|
||||
and "users"."deletedAt" is null
|
||||
where
|
||||
"albums"."id" in ($1)
|
||||
and "albums"."deletedAt" is null
|
||||
and "users"."id" = $2
|
||||
and "albumUsers"."role" in ($3, $4)
|
||||
|
||||
-- AccessRepository.album.checkSharedLinkAccess
|
||||
SELECT
|
||||
"SharedLinkEntity"."albumId" AS "SharedLinkEntity_albumId",
|
||||
"SharedLinkEntity"."id" AS "SharedLinkEntity_id"
|
||||
FROM
|
||||
"shared_links" "SharedLinkEntity"
|
||||
WHERE
|
||||
(
|
||||
("SharedLinkEntity"."id" = $1)
|
||||
AND ("SharedLinkEntity"."albumId" IN ($2))
|
||||
)
|
||||
select
|
||||
"shared_links"."albumId"
|
||||
from
|
||||
"shared_links"
|
||||
where
|
||||
"shared_links"."id" = $1
|
||||
and "shared_links"."albumId" in ($2)
|
||||
|
||||
-- AccessRepository.asset.checkAlbumAccess
|
||||
SELECT
|
||||
"asset"."id" AS "assetId",
|
||||
"asset"."livePhotoVideoId" AS "livePhotoVideoId"
|
||||
FROM
|
||||
"albums" "album"
|
||||
INNER JOIN "albums_assets_assets" "album_asset" ON "album_asset"."albumsId" = "album"."id"
|
||||
INNER JOIN "assets" "asset" ON "asset"."id" = "album_asset"."assetsId"
|
||||
AND ("asset"."deletedAt" IS NULL)
|
||||
LEFT JOIN "albums_shared_users_users" "album_albumUsers_users" ON "album_albumUsers_users"."albumsId" = "album"."id"
|
||||
LEFT JOIN "users" "albumUsers" ON "albumUsers"."id" = "album_albumUsers_users"."usersId"
|
||||
AND ("albumUsers"."deletedAt" IS NULL)
|
||||
WHERE
|
||||
(
|
||||
array["asset"."id", "asset"."livePhotoVideoId"] && array[$1]::uuid []
|
||||
AND (
|
||||
"album"."ownerId" = $2
|
||||
OR "albumUsers"."id" = $2
|
||||
)
|
||||
select
|
||||
"assets"."id",
|
||||
"assets"."livePhotoVideoId"
|
||||
from
|
||||
"albums"
|
||||
inner join "albums_assets_assets" as "albumAssets" on "albums"."id" = "albumAssets"."albumsId"
|
||||
inner join "assets" on "assets"."id" = "albumAssets"."assetsId"
|
||||
and "assets"."deletedAt" is null
|
||||
left join "albums_shared_users_users" as "albumUsers" on "albumUsers"."albumsId" = "albums"."id"
|
||||
left join "users" on "users"."id" = "albumUsers"."usersId"
|
||||
and "users"."deletedAt" is null
|
||||
where
|
||||
array["assets"."id", "assets"."livePhotoVideoId"] && array[$1]::uuid []
|
||||
and (
|
||||
"albums"."ownerId" = $2
|
||||
or "users"."id" = $3
|
||||
)
|
||||
AND ("album"."deletedAt" IS NULL)
|
||||
and "albums"."deletedAt" is null
|
||||
|
||||
-- AccessRepository.asset.checkOwnerAccess
|
||||
SELECT
|
||||
"AssetEntity"."id" AS "AssetEntity_id"
|
||||
FROM
|
||||
"assets" "AssetEntity"
|
||||
WHERE
|
||||
(
|
||||
("AssetEntity"."id" IN ($1))
|
||||
AND ("AssetEntity"."ownerId" = $2)
|
||||
)
|
||||
select
|
||||
"assets"."id"
|
||||
from
|
||||
"assets"
|
||||
where
|
||||
"assets"."id" in ($1)
|
||||
and "assets"."ownerId" = $2
|
||||
|
||||
-- AccessRepository.asset.checkPartnerAccess
|
||||
SELECT
|
||||
"asset"."id" AS "assetId"
|
||||
FROM
|
||||
"partners" "partner"
|
||||
INNER JOIN "users" "sharedBy" ON "sharedBy"."id" = "partner"."sharedById"
|
||||
AND ("sharedBy"."deletedAt" IS NULL)
|
||||
INNER JOIN "assets" "asset" ON "asset"."ownerId" = "sharedBy"."id"
|
||||
AND ("asset"."deletedAt" IS NULL)
|
||||
WHERE
|
||||
select
|
||||
"assets"."id"
|
||||
from
|
||||
"partners" as "partner"
|
||||
inner join "users" as "sharedBy" on "sharedBy"."id" = "partner"."sharedById"
|
||||
and "sharedBy"."deletedAt" is null
|
||||
inner join "assets" on "assets"."ownerId" = "sharedBy"."id"
|
||||
and "assets"."deletedAt" is null
|
||||
where
|
||||
"partner"."sharedWithId" = $1
|
||||
AND "asset"."isArchived" = false
|
||||
AND "asset"."id" IN ($2)
|
||||
and "assets"."isArchived" = $2
|
||||
and "assets"."id" in ($3)
|
||||
|
||||
-- AccessRepository.asset.checkSharedLinkAccess
|
||||
SELECT
|
||||
"assets"."id" AS "assetId",
|
||||
"assets"."livePhotoVideoId" AS "assetLivePhotoVideoId",
|
||||
"albumAssets"."id" AS "albumAssetId",
|
||||
"albumAssets"."livePhotoVideoId" AS "albumAssetLivePhotoVideoId"
|
||||
FROM
|
||||
"shared_links" "sharedLink"
|
||||
LEFT JOIN "albums" "album" ON "album"."id" = "sharedLink"."albumId"
|
||||
AND ("album"."deletedAt" IS NULL)
|
||||
LEFT JOIN "shared_link__asset" "assets_sharedLink" ON "assets_sharedLink"."sharedLinksId" = "sharedLink"."id"
|
||||
LEFT JOIN "assets" "assets" ON "assets"."id" = "assets_sharedLink"."assetsId"
|
||||
AND ("assets"."deletedAt" IS NULL)
|
||||
LEFT JOIN "albums_assets_assets" "album_albumAssets" ON "album_albumAssets"."albumsId" = "album"."id"
|
||||
LEFT JOIN "assets" "albumAssets" ON "albumAssets"."id" = "album_albumAssets"."assetsId"
|
||||
AND ("albumAssets"."deletedAt" IS NULL)
|
||||
WHERE
|
||||
"sharedLink"."id" = $1
|
||||
AND array[
|
||||
select
|
||||
"assets"."id" as "assetId",
|
||||
"assets"."livePhotoVideoId" as "assetLivePhotoVideoId",
|
||||
"albumAssets"."id" as "albumAssetId",
|
||||
"albumAssets"."livePhotoVideoId" as "albumAssetLivePhotoVideoId"
|
||||
from
|
||||
"shared_links"
|
||||
left join "albums" on "albums"."id" = "shared_links"."albumId"
|
||||
and "albums"."deletedAt" is null
|
||||
left join "shared_link__asset" on "shared_link__asset"."sharedLinksId" = "shared_links"."id"
|
||||
left join "assets" on "assets"."id" = "shared_link__asset"."assetsId"
|
||||
and "assets"."deletedAt" is null
|
||||
left join "albums_assets_assets" on "albums_assets_assets"."albumsId" = "albums"."id"
|
||||
left join "assets" as "albumAssets" on "albumAssets"."id" = "albums_assets_assets"."assetsId"
|
||||
and "albumAssets"."deletedAt" is null
|
||||
where
|
||||
"shared_links"."id" = $1
|
||||
and array[
|
||||
"assets"."id",
|
||||
"assets"."livePhotoVideoId",
|
||||
"albumAssets"."id",
|
||||
@ -182,100 +139,76 @@ WHERE
|
||||
] && array[$2]::uuid []
|
||||
|
||||
-- AccessRepository.authDevice.checkOwnerAccess
|
||||
SELECT
|
||||
"SessionEntity"."id" AS "SessionEntity_id"
|
||||
FROM
|
||||
"sessions" "SessionEntity"
|
||||
WHERE
|
||||
(
|
||||
("SessionEntity"."userId" = $1)
|
||||
AND ("SessionEntity"."id" IN ($2))
|
||||
)
|
||||
select
|
||||
"sessions"."id"
|
||||
from
|
||||
"sessions"
|
||||
where
|
||||
"sessions"."userId" = $1
|
||||
and "sessions"."id" in ($2)
|
||||
|
||||
-- AccessRepository.memory.checkOwnerAccess
|
||||
SELECT
|
||||
"MemoryEntity"."id" AS "MemoryEntity_id"
|
||||
FROM
|
||||
"memories" "MemoryEntity"
|
||||
WHERE
|
||||
(
|
||||
(
|
||||
("MemoryEntity"."id" IN ($1))
|
||||
AND ("MemoryEntity"."ownerId" = $2)
|
||||
)
|
||||
)
|
||||
AND ("MemoryEntity"."deletedAt" IS NULL)
|
||||
select
|
||||
"memories"."id"
|
||||
from
|
||||
"memories"
|
||||
where
|
||||
"memories"."id" in ($1)
|
||||
and "memories"."ownerId" = $2
|
||||
and "memories"."deletedAt" is null
|
||||
|
||||
-- AccessRepository.person.checkOwnerAccess
|
||||
SELECT
|
||||
"PersonEntity"."id" AS "PersonEntity_id"
|
||||
FROM
|
||||
"person" "PersonEntity"
|
||||
WHERE
|
||||
(
|
||||
("PersonEntity"."id" IN ($1))
|
||||
AND ("PersonEntity"."ownerId" = $2)
|
||||
)
|
||||
select
|
||||
"person"."id"
|
||||
from
|
||||
"person"
|
||||
where
|
||||
"person"."id" in ($1)
|
||||
and "person"."ownerId" = $2
|
||||
|
||||
-- AccessRepository.person.checkFaceOwnerAccess
|
||||
SELECT
|
||||
"AssetFaceEntity"."id" AS "AssetFaceEntity_id"
|
||||
FROM
|
||||
"asset_faces" "AssetFaceEntity"
|
||||
LEFT JOIN "assets" "AssetFaceEntity__AssetFaceEntity_asset" ON "AssetFaceEntity__AssetFaceEntity_asset"."id" = "AssetFaceEntity"."assetId"
|
||||
AND (
|
||||
"AssetFaceEntity__AssetFaceEntity_asset"."deletedAt" IS NULL
|
||||
)
|
||||
WHERE
|
||||
(
|
||||
("AssetFaceEntity"."id" IN ($1))
|
||||
AND (
|
||||
(
|
||||
(
|
||||
"AssetFaceEntity__AssetFaceEntity_asset"."ownerId" = $2
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
select
|
||||
"asset_faces"."id"
|
||||
from
|
||||
"asset_faces"
|
||||
left join "assets" on "assets"."id" = "asset_faces"."assetId"
|
||||
and "assets"."deletedAt" is null
|
||||
where
|
||||
"asset_faces"."id" in ($1)
|
||||
and "assets"."ownerId" = $2
|
||||
|
||||
-- AccessRepository.partner.checkUpdateAccess
|
||||
SELECT
|
||||
"partner"."sharedById" AS "partner_sharedById",
|
||||
"partner"."sharedWithId" AS "partner_sharedWithId"
|
||||
FROM
|
||||
"partners" "partner"
|
||||
WHERE
|
||||
"partner"."sharedById" IN ($1)
|
||||
AND "partner"."sharedWithId" = $2
|
||||
select
|
||||
"partners"."sharedById"
|
||||
from
|
||||
"partners"
|
||||
where
|
||||
"partners"."sharedById" in ($1)
|
||||
and "partners"."sharedWithId" = $2
|
||||
|
||||
-- AccessRepository.stack.checkOwnerAccess
|
||||
SELECT
|
||||
"StackEntity"."id" AS "StackEntity_id"
|
||||
FROM
|
||||
"asset_stack" "StackEntity"
|
||||
WHERE
|
||||
(
|
||||
("StackEntity"."id" IN ($1))
|
||||
AND ("StackEntity"."ownerId" = $2)
|
||||
)
|
||||
select
|
||||
"stacks"."id"
|
||||
from
|
||||
"asset_stack" as "stacks"
|
||||
where
|
||||
"stacks"."id" in ($1)
|
||||
and "stacks"."ownerId" = $2
|
||||
|
||||
-- AccessRepository.tag.checkOwnerAccess
|
||||
SELECT
|
||||
"TagEntity"."id" AS "TagEntity_id"
|
||||
FROM
|
||||
"tags" "TagEntity"
|
||||
WHERE
|
||||
(
|
||||
("TagEntity"."id" IN ($1))
|
||||
AND ("TagEntity"."userId" = $2)
|
||||
)
|
||||
select
|
||||
"tags"."id"
|
||||
from
|
||||
"tags"
|
||||
where
|
||||
"tags"."id" in ($1)
|
||||
and "tags"."userId" = $2
|
||||
|
||||
-- AccessRepository.timeline.checkPartnerAccess
|
||||
SELECT
|
||||
"partner"."sharedById" AS "partner_sharedById",
|
||||
"partner"."sharedWithId" AS "partner_sharedWithId"
|
||||
FROM
|
||||
"partners" "partner"
|
||||
WHERE
|
||||
"partner"."sharedById" IN ($1)
|
||||
AND "partner"."sharedWithId" = $2
|
||||
select
|
||||
"partners"."sharedById"
|
||||
from
|
||||
"partners"
|
||||
where
|
||||
"partners"."sharedById" in ($1)
|
||||
and "partners"."sharedWithId" = $2
|
||||
|
@ -9,7 +9,11 @@ select
|
||||
from
|
||||
(
|
||||
select
|
||||
*
|
||||
"id",
|
||||
"name",
|
||||
"email",
|
||||
"profileImagePath",
|
||||
"profileChangedAt"
|
||||
from
|
||||
"users"
|
||||
where
|
||||
|
25
server/src/queries/album.user.repository.sql
Normal file
25
server/src/queries/album.user.repository.sql
Normal file
@ -0,0 +1,25 @@
|
||||
-- NOTE: This file is auto generated by ./sql-generator
|
||||
|
||||
-- AlbumUserRepository.create
|
||||
insert into
|
||||
"albums_shared_users_users" ("usersId", "albumsId")
|
||||
values
|
||||
($1, $2)
|
||||
returning
|
||||
*
|
||||
|
||||
-- AlbumUserRepository.update
|
||||
update "albums_shared_users_users"
|
||||
set
|
||||
"role" = $1
|
||||
where
|
||||
"usersId" = $2
|
||||
and "albumsId" = $3
|
||||
returning
|
||||
*
|
||||
|
||||
-- AlbumUserRepository.delete
|
||||
delete from "albums_shared_users_users"
|
||||
where
|
||||
"usersId" = $1
|
||||
and "albumsId" = $2
|
@ -1,150 +1,137 @@
|
||||
-- NOTE: This file is auto generated by ./sql-generator
|
||||
|
||||
-- LibraryRepository.get
|
||||
SELECT DISTINCT
|
||||
"distinctAlias"."LibraryEntity_id" AS "ids_LibraryEntity_id"
|
||||
FROM
|
||||
select
|
||||
"libraries".*,
|
||||
(
|
||||
SELECT
|
||||
"LibraryEntity"."id" AS "LibraryEntity_id",
|
||||
"LibraryEntity"."name" AS "LibraryEntity_name",
|
||||
"LibraryEntity"."ownerId" AS "LibraryEntity_ownerId",
|
||||
"LibraryEntity"."importPaths" AS "LibraryEntity_importPaths",
|
||||
"LibraryEntity"."exclusionPatterns" AS "LibraryEntity_exclusionPatterns",
|
||||
"LibraryEntity"."createdAt" AS "LibraryEntity_createdAt",
|
||||
"LibraryEntity"."updatedAt" AS "LibraryEntity_updatedAt",
|
||||
"LibraryEntity"."deletedAt" AS "LibraryEntity_deletedAt",
|
||||
"LibraryEntity"."refreshedAt" AS "LibraryEntity_refreshedAt",
|
||||
"LibraryEntity__LibraryEntity_owner"."id" AS "LibraryEntity__LibraryEntity_owner_id",
|
||||
"LibraryEntity__LibraryEntity_owner"."name" AS "LibraryEntity__LibraryEntity_owner_name",
|
||||
"LibraryEntity__LibraryEntity_owner"."isAdmin" AS "LibraryEntity__LibraryEntity_owner_isAdmin",
|
||||
"LibraryEntity__LibraryEntity_owner"."email" AS "LibraryEntity__LibraryEntity_owner_email",
|
||||
"LibraryEntity__LibraryEntity_owner"."storageLabel" AS "LibraryEntity__LibraryEntity_owner_storageLabel",
|
||||
"LibraryEntity__LibraryEntity_owner"."oauthId" AS "LibraryEntity__LibraryEntity_owner_oauthId",
|
||||
"LibraryEntity__LibraryEntity_owner"."profileImagePath" AS "LibraryEntity__LibraryEntity_owner_profileImagePath",
|
||||
"LibraryEntity__LibraryEntity_owner"."shouldChangePassword" AS "LibraryEntity__LibraryEntity_owner_shouldChangePassword",
|
||||
"LibraryEntity__LibraryEntity_owner"."createdAt" AS "LibraryEntity__LibraryEntity_owner_createdAt",
|
||||
"LibraryEntity__LibraryEntity_owner"."deletedAt" AS "LibraryEntity__LibraryEntity_owner_deletedAt",
|
||||
"LibraryEntity__LibraryEntity_owner"."status" AS "LibraryEntity__LibraryEntity_owner_status",
|
||||
"LibraryEntity__LibraryEntity_owner"."updatedAt" AS "LibraryEntity__LibraryEntity_owner_updatedAt",
|
||||
"LibraryEntity__LibraryEntity_owner"."quotaSizeInBytes" AS "LibraryEntity__LibraryEntity_owner_quotaSizeInBytes",
|
||||
"LibraryEntity__LibraryEntity_owner"."quotaUsageInBytes" AS "LibraryEntity__LibraryEntity_owner_quotaUsageInBytes",
|
||||
"LibraryEntity__LibraryEntity_owner"."profileChangedAt" AS "LibraryEntity__LibraryEntity_owner_profileChangedAt"
|
||||
FROM
|
||||
"libraries" "LibraryEntity"
|
||||
LEFT JOIN "users" "LibraryEntity__LibraryEntity_owner" ON "LibraryEntity__LibraryEntity_owner"."id" = "LibraryEntity"."ownerId"
|
||||
AND (
|
||||
"LibraryEntity__LibraryEntity_owner"."deletedAt" IS NULL
|
||||
)
|
||||
WHERE
|
||||
((("LibraryEntity"."id" = $1)))
|
||||
AND ("LibraryEntity"."deletedAt" IS NULL)
|
||||
) "distinctAlias"
|
||||
ORDER BY
|
||||
"LibraryEntity_id" ASC
|
||||
LIMIT
|
||||
1
|
||||
select
|
||||
to_json(obj)
|
||||
from
|
||||
(
|
||||
select
|
||||
"users"."id",
|
||||
"users"."email",
|
||||
"users"."createdAt",
|
||||
"users"."profileImagePath",
|
||||
"users"."isAdmin",
|
||||
"users"."shouldChangePassword",
|
||||
"users"."deletedAt",
|
||||
"users"."oauthId",
|
||||
"users"."updatedAt",
|
||||
"users"."storageLabel",
|
||||
"users"."name",
|
||||
"users"."quotaSizeInBytes",
|
||||
"users"."quotaUsageInBytes",
|
||||
"users"."status",
|
||||
"users"."profileChangedAt"
|
||||
from
|
||||
"users"
|
||||
where
|
||||
"users"."id" = "libraries"."ownerId"
|
||||
) as obj
|
||||
) as "owner"
|
||||
from
|
||||
"libraries"
|
||||
where
|
||||
"libraries"."id" = $1
|
||||
and "libraries"."deletedAt" is null
|
||||
|
||||
-- LibraryRepository.getAll
|
||||
SELECT
|
||||
"LibraryEntity"."id" AS "LibraryEntity_id",
|
||||
"LibraryEntity"."name" AS "LibraryEntity_name",
|
||||
"LibraryEntity"."ownerId" AS "LibraryEntity_ownerId",
|
||||
"LibraryEntity"."importPaths" AS "LibraryEntity_importPaths",
|
||||
"LibraryEntity"."exclusionPatterns" AS "LibraryEntity_exclusionPatterns",
|
||||
"LibraryEntity"."createdAt" AS "LibraryEntity_createdAt",
|
||||
"LibraryEntity"."updatedAt" AS "LibraryEntity_updatedAt",
|
||||
"LibraryEntity"."deletedAt" AS "LibraryEntity_deletedAt",
|
||||
"LibraryEntity"."refreshedAt" AS "LibraryEntity_refreshedAt",
|
||||
"LibraryEntity__LibraryEntity_owner"."id" AS "LibraryEntity__LibraryEntity_owner_id",
|
||||
"LibraryEntity__LibraryEntity_owner"."name" AS "LibraryEntity__LibraryEntity_owner_name",
|
||||
"LibraryEntity__LibraryEntity_owner"."isAdmin" AS "LibraryEntity__LibraryEntity_owner_isAdmin",
|
||||
"LibraryEntity__LibraryEntity_owner"."email" AS "LibraryEntity__LibraryEntity_owner_email",
|
||||
"LibraryEntity__LibraryEntity_owner"."storageLabel" AS "LibraryEntity__LibraryEntity_owner_storageLabel",
|
||||
"LibraryEntity__LibraryEntity_owner"."oauthId" AS "LibraryEntity__LibraryEntity_owner_oauthId",
|
||||
"LibraryEntity__LibraryEntity_owner"."profileImagePath" AS "LibraryEntity__LibraryEntity_owner_profileImagePath",
|
||||
"LibraryEntity__LibraryEntity_owner"."shouldChangePassword" AS "LibraryEntity__LibraryEntity_owner_shouldChangePassword",
|
||||
"LibraryEntity__LibraryEntity_owner"."createdAt" AS "LibraryEntity__LibraryEntity_owner_createdAt",
|
||||
"LibraryEntity__LibraryEntity_owner"."deletedAt" AS "LibraryEntity__LibraryEntity_owner_deletedAt",
|
||||
"LibraryEntity__LibraryEntity_owner"."status" AS "LibraryEntity__LibraryEntity_owner_status",
|
||||
"LibraryEntity__LibraryEntity_owner"."updatedAt" AS "LibraryEntity__LibraryEntity_owner_updatedAt",
|
||||
"LibraryEntity__LibraryEntity_owner"."quotaSizeInBytes" AS "LibraryEntity__LibraryEntity_owner_quotaSizeInBytes",
|
||||
"LibraryEntity__LibraryEntity_owner"."quotaUsageInBytes" AS "LibraryEntity__LibraryEntity_owner_quotaUsageInBytes",
|
||||
"LibraryEntity__LibraryEntity_owner"."profileChangedAt" AS "LibraryEntity__LibraryEntity_owner_profileChangedAt"
|
||||
FROM
|
||||
"libraries" "LibraryEntity"
|
||||
LEFT JOIN "users" "LibraryEntity__LibraryEntity_owner" ON "LibraryEntity__LibraryEntity_owner"."id" = "LibraryEntity"."ownerId"
|
||||
AND (
|
||||
"LibraryEntity__LibraryEntity_owner"."deletedAt" IS NULL
|
||||
)
|
||||
WHERE
|
||||
"LibraryEntity"."deletedAt" IS NULL
|
||||
ORDER BY
|
||||
"LibraryEntity"."createdAt" ASC
|
||||
select
|
||||
"libraries".*,
|
||||
(
|
||||
select
|
||||
to_json(obj)
|
||||
from
|
||||
(
|
||||
select
|
||||
"users"."id",
|
||||
"users"."email",
|
||||
"users"."createdAt",
|
||||
"users"."profileImagePath",
|
||||
"users"."isAdmin",
|
||||
"users"."shouldChangePassword",
|
||||
"users"."deletedAt",
|
||||
"users"."oauthId",
|
||||
"users"."updatedAt",
|
||||
"users"."storageLabel",
|
||||
"users"."name",
|
||||
"users"."quotaSizeInBytes",
|
||||
"users"."quotaUsageInBytes",
|
||||
"users"."status",
|
||||
"users"."profileChangedAt"
|
||||
from
|
||||
"users"
|
||||
where
|
||||
"users"."id" = "libraries"."ownerId"
|
||||
) as obj
|
||||
) as "owner"
|
||||
from
|
||||
"libraries"
|
||||
where
|
||||
"libraries"."deletedAt" is null
|
||||
order by
|
||||
"createdAt" asc
|
||||
|
||||
-- LibraryRepository.getAllDeleted
|
||||
SELECT
|
||||
"LibraryEntity"."id" AS "LibraryEntity_id",
|
||||
"LibraryEntity"."name" AS "LibraryEntity_name",
|
||||
"LibraryEntity"."ownerId" AS "LibraryEntity_ownerId",
|
||||
"LibraryEntity"."importPaths" AS "LibraryEntity_importPaths",
|
||||
"LibraryEntity"."exclusionPatterns" AS "LibraryEntity_exclusionPatterns",
|
||||
"LibraryEntity"."createdAt" AS "LibraryEntity_createdAt",
|
||||
"LibraryEntity"."updatedAt" AS "LibraryEntity_updatedAt",
|
||||
"LibraryEntity"."deletedAt" AS "LibraryEntity_deletedAt",
|
||||
"LibraryEntity"."refreshedAt" AS "LibraryEntity_refreshedAt",
|
||||
"LibraryEntity__LibraryEntity_owner"."id" AS "LibraryEntity__LibraryEntity_owner_id",
|
||||
"LibraryEntity__LibraryEntity_owner"."name" AS "LibraryEntity__LibraryEntity_owner_name",
|
||||
"LibraryEntity__LibraryEntity_owner"."isAdmin" AS "LibraryEntity__LibraryEntity_owner_isAdmin",
|
||||
"LibraryEntity__LibraryEntity_owner"."email" AS "LibraryEntity__LibraryEntity_owner_email",
|
||||
"LibraryEntity__LibraryEntity_owner"."storageLabel" AS "LibraryEntity__LibraryEntity_owner_storageLabel",
|
||||
"LibraryEntity__LibraryEntity_owner"."oauthId" AS "LibraryEntity__LibraryEntity_owner_oauthId",
|
||||
"LibraryEntity__LibraryEntity_owner"."profileImagePath" AS "LibraryEntity__LibraryEntity_owner_profileImagePath",
|
||||
"LibraryEntity__LibraryEntity_owner"."shouldChangePassword" AS "LibraryEntity__LibraryEntity_owner_shouldChangePassword",
|
||||
"LibraryEntity__LibraryEntity_owner"."createdAt" AS "LibraryEntity__LibraryEntity_owner_createdAt",
|
||||
"LibraryEntity__LibraryEntity_owner"."deletedAt" AS "LibraryEntity__LibraryEntity_owner_deletedAt",
|
||||
"LibraryEntity__LibraryEntity_owner"."status" AS "LibraryEntity__LibraryEntity_owner_status",
|
||||
"LibraryEntity__LibraryEntity_owner"."updatedAt" AS "LibraryEntity__LibraryEntity_owner_updatedAt",
|
||||
"LibraryEntity__LibraryEntity_owner"."quotaSizeInBytes" AS "LibraryEntity__LibraryEntity_owner_quotaSizeInBytes",
|
||||
"LibraryEntity__LibraryEntity_owner"."quotaUsageInBytes" AS "LibraryEntity__LibraryEntity_owner_quotaUsageInBytes",
|
||||
"LibraryEntity__LibraryEntity_owner"."profileChangedAt" AS "LibraryEntity__LibraryEntity_owner_profileChangedAt"
|
||||
FROM
|
||||
"libraries" "LibraryEntity"
|
||||
LEFT JOIN "users" "LibraryEntity__LibraryEntity_owner" ON "LibraryEntity__LibraryEntity_owner"."id" = "LibraryEntity"."ownerId"
|
||||
WHERE
|
||||
((NOT ("LibraryEntity"."deletedAt" IS NULL)))
|
||||
ORDER BY
|
||||
"LibraryEntity"."createdAt" ASC
|
||||
select
|
||||
"libraries".*,
|
||||
(
|
||||
select
|
||||
to_json(obj)
|
||||
from
|
||||
(
|
||||
select
|
||||
"users"."id",
|
||||
"users"."email",
|
||||
"users"."createdAt",
|
||||
"users"."profileImagePath",
|
||||
"users"."isAdmin",
|
||||
"users"."shouldChangePassword",
|
||||
"users"."deletedAt",
|
||||
"users"."oauthId",
|
||||
"users"."updatedAt",
|
||||
"users"."storageLabel",
|
||||
"users"."name",
|
||||
"users"."quotaSizeInBytes",
|
||||
"users"."quotaUsageInBytes",
|
||||
"users"."status",
|
||||
"users"."profileChangedAt"
|
||||
from
|
||||
"users"
|
||||
where
|
||||
"users"."id" = "libraries"."ownerId"
|
||||
) as obj
|
||||
) as "owner"
|
||||
from
|
||||
"libraries"
|
||||
where
|
||||
"libraries"."deletedAt" is not null
|
||||
order by
|
||||
"createdAt" asc
|
||||
|
||||
-- LibraryRepository.getStatistics
|
||||
SELECT
|
||||
"libraries"."id" AS "libraries_id",
|
||||
"libraries"."name" AS "libraries_name",
|
||||
"libraries"."ownerId" AS "libraries_ownerId",
|
||||
"libraries"."importPaths" AS "libraries_importPaths",
|
||||
"libraries"."exclusionPatterns" AS "libraries_exclusionPatterns",
|
||||
"libraries"."createdAt" AS "libraries_createdAt",
|
||||
"libraries"."updatedAt" AS "libraries_updatedAt",
|
||||
"libraries"."deletedAt" AS "libraries_deletedAt",
|
||||
"libraries"."refreshedAt" AS "libraries_refreshedAt",
|
||||
COUNT("assets"."id") FILTER (
|
||||
WHERE
|
||||
"assets"."type" = 'IMAGE'
|
||||
AND "assets"."isVisible"
|
||||
) AS "photos",
|
||||
COUNT("assets"."id") FILTER (
|
||||
WHERE
|
||||
"assets"."type" = 'VIDEO'
|
||||
AND "assets"."isVisible"
|
||||
) AS "videos",
|
||||
COALESCE(SUM("exif"."fileSizeInByte"), 0) AS "usage"
|
||||
FROM
|
||||
"libraries" "libraries"
|
||||
LEFT JOIN "assets" "assets" ON "assets"."libraryId" = "libraries"."id"
|
||||
AND ("assets"."deletedAt" IS NULL)
|
||||
LEFT JOIN "exif" "exif" ON "exif"."assetId" = "assets"."id"
|
||||
WHERE
|
||||
("libraries"."id" = $1)
|
||||
AND ("libraries"."deletedAt" IS NULL)
|
||||
GROUP BY
|
||||
select
|
||||
count("assets"."id") filter (
|
||||
where
|
||||
(
|
||||
"assets"."type" = $1
|
||||
and "assets"."isVisible" = $2
|
||||
)
|
||||
) as "photos",
|
||||
count(*) filter (
|
||||
where
|
||||
(
|
||||
"assets"."type" = $3
|
||||
and "assets"."isVisible" = $4
|
||||
)
|
||||
) as "videos",
|
||||
coalesce(sum("exif"."fileSizeInByte"), $5) as "usage"
|
||||
from
|
||||
"libraries"
|
||||
inner join "assets" on "assets"."libraryId" = "libraries"."id"
|
||||
inner join "exif" on "exif"."assetId" = "assets"."id"
|
||||
where
|
||||
"libraries"."id" = $6
|
||||
group by
|
||||
"libraries"."id"
|
||||
|
@ -1,10 +1,85 @@
|
||||
-- NOTE: This file is auto generated by ./sql-generator
|
||||
|
||||
-- MemoryRepository.search
|
||||
select
|
||||
*
|
||||
from
|
||||
"memories"
|
||||
where
|
||||
"ownerId" = $1
|
||||
order by
|
||||
"memoryAt" desc
|
||||
|
||||
-- MemoryRepository.get
|
||||
select
|
||||
"memories".*,
|
||||
(
|
||||
select
|
||||
coalesce(json_agg(agg), '[]')
|
||||
from
|
||||
(
|
||||
select
|
||||
"assets".*
|
||||
from
|
||||
"assets"
|
||||
inner join "memories_assets_assets" on "assets"."id" = "memories_assets_assets"."assetsId"
|
||||
where
|
||||
"memories_assets_assets"."memoriesId" = "memories"."id"
|
||||
and "assets"."deletedAt" is null
|
||||
) as agg
|
||||
) as "assets"
|
||||
from
|
||||
"memories"
|
||||
where
|
||||
"id" = $1
|
||||
and "deletedAt" is null
|
||||
|
||||
-- MemoryRepository.update
|
||||
update "memories"
|
||||
set
|
||||
"ownerId" = $1,
|
||||
"isSaved" = $2
|
||||
where
|
||||
"id" = $3
|
||||
select
|
||||
"memories".*,
|
||||
(
|
||||
select
|
||||
coalesce(json_agg(agg), '[]')
|
||||
from
|
||||
(
|
||||
select
|
||||
"assets".*
|
||||
from
|
||||
"assets"
|
||||
inner join "memories_assets_assets" on "assets"."id" = "memories_assets_assets"."assetsId"
|
||||
where
|
||||
"memories_assets_assets"."memoriesId" = "memories"."id"
|
||||
and "assets"."deletedAt" is null
|
||||
) as agg
|
||||
) as "assets"
|
||||
from
|
||||
"memories"
|
||||
where
|
||||
"id" = $1
|
||||
and "deletedAt" is null
|
||||
|
||||
-- MemoryRepository.delete
|
||||
delete from "memories"
|
||||
where
|
||||
"id" = $1
|
||||
|
||||
-- MemoryRepository.getAssetIds
|
||||
SELECT
|
||||
"memories_assets"."assetsId" AS "assetId"
|
||||
FROM
|
||||
"memories_assets_assets" "memories_assets"
|
||||
WHERE
|
||||
"memories_assets"."memoriesId" = $1
|
||||
AND "memories_assets"."assetsId" IN ($2)
|
||||
select
|
||||
"assetsId"
|
||||
from
|
||||
"memories_assets_assets"
|
||||
where
|
||||
"memoriesId" = $1
|
||||
and "assetsId" in ($2)
|
||||
|
||||
-- MemoryRepository.addAssetIds
|
||||
insert into
|
||||
"memories_assets_assets" ("memoriesId", "assetsId")
|
||||
values
|
||||
($1, $2)
|
||||
|
189
server/src/queries/partner.repository.sql
Normal file
189
server/src/queries/partner.repository.sql
Normal file
@ -0,0 +1,189 @@
|
||||
-- NOTE: This file is auto generated by ./sql-generator
|
||||
|
||||
-- PartnerRepository.getAll
|
||||
select
|
||||
"partners".*,
|
||||
(
|
||||
select
|
||||
to_json(obj)
|
||||
from
|
||||
(
|
||||
select
|
||||
"id",
|
||||
"name",
|
||||
"email",
|
||||
"profileImagePath",
|
||||
"profileChangedAt"
|
||||
from
|
||||
"users" as "sharedBy"
|
||||
where
|
||||
"sharedBy"."id" = "partners"."sharedById"
|
||||
) as obj
|
||||
) as "sharedBy",
|
||||
(
|
||||
select
|
||||
to_json(obj)
|
||||
from
|
||||
(
|
||||
select
|
||||
"id",
|
||||
"name",
|
||||
"email",
|
||||
"profileImagePath",
|
||||
"profileChangedAt"
|
||||
from
|
||||
"users" as "sharedWith"
|
||||
where
|
||||
"sharedWith"."id" = "partners"."sharedWithId"
|
||||
) as obj
|
||||
) as "sharedWith"
|
||||
from
|
||||
"partners"
|
||||
inner join "users" as "sharedBy" on "partners"."sharedById" = "sharedBy"."id"
|
||||
and "sharedBy"."deletedAt" is null
|
||||
inner join "users" as "sharedWith" on "partners"."sharedWithId" = "sharedWith"."id"
|
||||
and "sharedWith"."deletedAt" is null
|
||||
where
|
||||
(
|
||||
"sharedWithId" = $1
|
||||
or "sharedById" = $2
|
||||
)
|
||||
|
||||
-- PartnerRepository.get
|
||||
select
|
||||
"partners".*,
|
||||
(
|
||||
select
|
||||
to_json(obj)
|
||||
from
|
||||
(
|
||||
select
|
||||
"id",
|
||||
"name",
|
||||
"email",
|
||||
"profileImagePath",
|
||||
"profileChangedAt"
|
||||
from
|
||||
"users" as "sharedBy"
|
||||
where
|
||||
"sharedBy"."id" = "partners"."sharedById"
|
||||
) as obj
|
||||
) as "sharedBy",
|
||||
(
|
||||
select
|
||||
to_json(obj)
|
||||
from
|
||||
(
|
||||
select
|
||||
"id",
|
||||
"name",
|
||||
"email",
|
||||
"profileImagePath",
|
||||
"profileChangedAt"
|
||||
from
|
||||
"users" as "sharedWith"
|
||||
where
|
||||
"sharedWith"."id" = "partners"."sharedWithId"
|
||||
) as obj
|
||||
) as "sharedWith"
|
||||
from
|
||||
"partners"
|
||||
inner join "users" as "sharedBy" on "partners"."sharedById" = "sharedBy"."id"
|
||||
and "sharedBy"."deletedAt" is null
|
||||
inner join "users" as "sharedWith" on "partners"."sharedWithId" = "sharedWith"."id"
|
||||
and "sharedWith"."deletedAt" is null
|
||||
where
|
||||
"sharedWithId" = $1
|
||||
and "sharedById" = $2
|
||||
|
||||
-- PartnerRepository.create
|
||||
insert into
|
||||
"partners" ("sharedWithId", "sharedById")
|
||||
values
|
||||
($1, $2)
|
||||
returning
|
||||
*,
|
||||
(
|
||||
select
|
||||
to_json(obj)
|
||||
from
|
||||
(
|
||||
select
|
||||
"id",
|
||||
"name",
|
||||
"email",
|
||||
"profileImagePath",
|
||||
"profileChangedAt"
|
||||
from
|
||||
"users" as "sharedBy"
|
||||
where
|
||||
"sharedBy"."id" = "partners"."sharedById"
|
||||
) as obj
|
||||
) as "sharedBy",
|
||||
(
|
||||
select
|
||||
to_json(obj)
|
||||
from
|
||||
(
|
||||
select
|
||||
"id",
|
||||
"name",
|
||||
"email",
|
||||
"profileImagePath",
|
||||
"profileChangedAt"
|
||||
from
|
||||
"users" as "sharedWith"
|
||||
where
|
||||
"sharedWith"."id" = "partners"."sharedWithId"
|
||||
) as obj
|
||||
) as "sharedWith"
|
||||
|
||||
-- PartnerRepository.update
|
||||
update "partners"
|
||||
set
|
||||
"inTimeline" = $1
|
||||
where
|
||||
"sharedWithId" = $2
|
||||
and "sharedById" = $3
|
||||
returning
|
||||
*,
|
||||
(
|
||||
select
|
||||
to_json(obj)
|
||||
from
|
||||
(
|
||||
select
|
||||
"id",
|
||||
"name",
|
||||
"email",
|
||||
"profileImagePath",
|
||||
"profileChangedAt"
|
||||
from
|
||||
"users" as "sharedBy"
|
||||
where
|
||||
"sharedBy"."id" = "partners"."sharedById"
|
||||
) as obj
|
||||
) as "sharedBy",
|
||||
(
|
||||
select
|
||||
to_json(obj)
|
||||
from
|
||||
(
|
||||
select
|
||||
"id",
|
||||
"name",
|
||||
"email",
|
||||
"profileImagePath",
|
||||
"profileChangedAt"
|
||||
from
|
||||
"users" as "sharedWith"
|
||||
where
|
||||
"sharedWith"."id" = "partners"."sharedWithId"
|
||||
) as obj
|
||||
) as "sharedWith"
|
||||
|
||||
-- PartnerRepository.remove
|
||||
delete from "partners"
|
||||
where
|
||||
"sharedWithId" = $1
|
||||
and "sharedById" = $2
|
257
server/src/queries/stack.repository.sql
Normal file
257
server/src/queries/stack.repository.sql
Normal file
@ -0,0 +1,257 @@
|
||||
-- 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))
|
||||
|
||||
-- StackRepository.delete
|
||||
SELECT DISTINCT
|
||||
"distinctAlias"."StackEntity_id" AS "ids_StackEntity_id",
|
||||
"distinctAlias"."StackEntity__StackEntity_assets_fileCreatedAt"
|
||||
FROM
|
||||
(
|
||||
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
|
||||
|
||||
-- StackRepository.getById
|
||||
SELECT DISTINCT
|
||||
"distinctAlias"."StackEntity_id" AS "ids_StackEntity_id",
|
||||
"distinctAlias"."StackEntity__StackEntity_assets_fileCreatedAt"
|
||||
FROM
|
||||
(
|
||||
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
|
@ -1,21 +1,12 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Kysely, sql } from 'kysely';
|
||||
import { InjectKysely } from 'nestjs-kysely';
|
||||
import { DB } from 'src/db';
|
||||
import { ChunkedSet, DummyValue, GenerateSql } from 'src/decorators';
|
||||
import { ActivityEntity } from 'src/entities/activity.entity';
|
||||
import { AlbumEntity } from 'src/entities/album.entity';
|
||||
import { AssetFaceEntity } from 'src/entities/asset-face.entity';
|
||||
import { AssetEntity } from 'src/entities/asset.entity';
|
||||
import { LibraryEntity } from 'src/entities/library.entity';
|
||||
import { MemoryEntity } from 'src/entities/memory.entity';
|
||||
import { PartnerEntity } from 'src/entities/partner.entity';
|
||||
import { PersonEntity } from 'src/entities/person.entity';
|
||||
import { SessionEntity } from 'src/entities/session.entity';
|
||||
import { SharedLinkEntity } from 'src/entities/shared-link.entity';
|
||||
import { StackEntity } from 'src/entities/stack.entity';
|
||||
import { TagEntity } from 'src/entities/tag.entity';
|
||||
|
||||
import { AlbumUserRole } from 'src/enum';
|
||||
import { IAccessRepository } from 'src/interfaces/access.interface';
|
||||
import { Brackets, In, Repository } from 'typeorm';
|
||||
import { asUuid } from 'src/utils/database';
|
||||
|
||||
type IActivityAccess = IAccessRepository['activity'];
|
||||
type IAlbumAccess = IAccessRepository['album'];
|
||||
@ -30,10 +21,7 @@ type ITimelineAccess = IAccessRepository['timeline'];
|
||||
|
||||
@Injectable()
|
||||
class ActivityAccess implements IActivityAccess {
|
||||
constructor(
|
||||
private activityRepository: Repository<ActivityEntity>,
|
||||
private albumRepository: Repository<AlbumEntity>,
|
||||
) {}
|
||||
constructor(private db: Kysely<DB>) {}
|
||||
|
||||
@GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID_SET] })
|
||||
@ChunkedSet({ paramIndex: 1 })
|
||||
@ -42,15 +30,16 @@ class ActivityAccess implements IActivityAccess {
|
||||
return new Set();
|
||||
}
|
||||
|
||||
return this.activityRepository
|
||||
.find({
|
||||
select: { id: true },
|
||||
where: {
|
||||
id: In([...activityIds]),
|
||||
userId,
|
||||
},
|
||||
})
|
||||
.then((activities) => new Set(activities.map((activity) => activity.id)));
|
||||
return this.db
|
||||
.selectFrom('activity')
|
||||
.select('activity.id')
|
||||
.where('activity.id', 'in', [...activityIds])
|
||||
.where('activity.userId', '=', userId)
|
||||
.execute()
|
||||
.then((activities) => {
|
||||
console.log('activities', activities);
|
||||
return new Set(activities.map((activity) => activity.id));
|
||||
});
|
||||
}
|
||||
|
||||
@GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID_SET] })
|
||||
@ -60,16 +49,13 @@ class ActivityAccess implements IActivityAccess {
|
||||
return new Set();
|
||||
}
|
||||
|
||||
return this.activityRepository
|
||||
.find({
|
||||
select: { id: true },
|
||||
where: {
|
||||
id: In([...activityIds]),
|
||||
album: {
|
||||
ownerId: userId,
|
||||
},
|
||||
},
|
||||
})
|
||||
return this.db
|
||||
.selectFrom('activity')
|
||||
.select('activity.id')
|
||||
.leftJoin('albums', (join) => join.onRef('activity.albumId', '=', 'albums.id').on('albums.deletedAt', 'is', null))
|
||||
.where('activity.id', 'in', [...activityIds])
|
||||
.whereRef('albums.ownerId', '=', asUuid(userId))
|
||||
.execute()
|
||||
.then((activities) => new Set(activities.map((activity) => activity.id)));
|
||||
}
|
||||
|
||||
@ -80,28 +66,22 @@ class ActivityAccess implements IActivityAccess {
|
||||
return new Set();
|
||||
}
|
||||
|
||||
return this.albumRepository
|
||||
.createQueryBuilder('album')
|
||||
.select('album.id')
|
||||
.leftJoin('album.albumUsers', 'album_albumUsers_users')
|
||||
.leftJoin('album_albumUsers_users.user', 'albumUsers')
|
||||
.where('album.id IN (:...albumIds)', { albumIds: [...albumIds] })
|
||||
.andWhere('album.isActivityEnabled = true')
|
||||
.andWhere(
|
||||
new Brackets((qb) => {
|
||||
qb.where('album.ownerId = :userId', { userId }).orWhere('albumUsers.id = :userId', { userId });
|
||||
}),
|
||||
)
|
||||
.getMany()
|
||||
return this.db
|
||||
.selectFrom('albums')
|
||||
.select('albums.id')
|
||||
.leftJoin('albums_shared_users_users as albumUsers', 'albumUsers.albumsId', 'albums.id')
|
||||
.leftJoin('users', (join) => join.onRef('users.id', '=', 'albumUsers.usersId').on('users.deletedAt', 'is', null))
|
||||
.where('albums.id', 'in', [...albumIds])
|
||||
.where('albums.isActivityEnabled', '=', true)
|
||||
.where((eb) => eb.or([eb('albums.ownerId', '=', userId), eb('users.id', '=', userId)]))
|
||||
.where('albums.deletedAt', 'is', null)
|
||||
.execute()
|
||||
.then((albums) => new Set(albums.map((album) => album.id)));
|
||||
}
|
||||
}
|
||||
|
||||
class AlbumAccess implements IAlbumAccess {
|
||||
constructor(
|
||||
private albumRepository: Repository<AlbumEntity>,
|
||||
private sharedLinkRepository: Repository<SharedLinkEntity>,
|
||||
) {}
|
||||
constructor(private db: Kysely<DB>) {}
|
||||
|
||||
@GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID_SET] })
|
||||
@ChunkedSet({ paramIndex: 1 })
|
||||
@ -110,14 +90,13 @@ class AlbumAccess implements IAlbumAccess {
|
||||
return new Set();
|
||||
}
|
||||
|
||||
return this.albumRepository
|
||||
.find({
|
||||
select: { id: true },
|
||||
where: {
|
||||
id: In([...albumIds]),
|
||||
ownerId: userId,
|
||||
},
|
||||
})
|
||||
return this.db
|
||||
.selectFrom('albums')
|
||||
.select('albums.id')
|
||||
.where('albums.id', 'in', [...albumIds])
|
||||
.where('albums.ownerId', '=', userId)
|
||||
.where('albums.deletedAt', 'is', null)
|
||||
.execute()
|
||||
.then((albums) => new Set(albums.map((album) => album.id)));
|
||||
}
|
||||
|
||||
@ -128,19 +107,19 @@ class AlbumAccess implements IAlbumAccess {
|
||||
return new Set();
|
||||
}
|
||||
|
||||
return this.albumRepository
|
||||
.find({
|
||||
select: { id: true },
|
||||
where: {
|
||||
id: In([...albumIds]),
|
||||
albumUsers: {
|
||||
user: { id: userId },
|
||||
// If editor access is needed we check for it, otherwise both are accepted
|
||||
role:
|
||||
access === AlbumUserRole.EDITOR ? AlbumUserRole.EDITOR : In([AlbumUserRole.EDITOR, AlbumUserRole.VIEWER]),
|
||||
},
|
||||
},
|
||||
})
|
||||
const accessRole =
|
||||
access === AlbumUserRole.EDITOR ? [AlbumUserRole.EDITOR] : [AlbumUserRole.EDITOR, AlbumUserRole.VIEWER];
|
||||
|
||||
return this.db
|
||||
.selectFrom('albums')
|
||||
.select('albums.id')
|
||||
.leftJoin('albums_shared_users_users as albumUsers', 'albumUsers.albumsId', 'albums.id')
|
||||
.leftJoin('users', (join) => join.onRef('users.id', '=', 'albumUsers.usersId').on('users.deletedAt', 'is', null))
|
||||
.where('albums.id', 'in', [...albumIds])
|
||||
.where('albums.deletedAt', 'is', null)
|
||||
.where('users.id', '=', userId)
|
||||
.where('albumUsers.role', 'in', [...accessRole])
|
||||
.execute()
|
||||
.then((albums) => new Set(albums.map((album) => album.id)));
|
||||
}
|
||||
|
||||
@ -151,14 +130,12 @@ class AlbumAccess implements IAlbumAccess {
|
||||
return new Set();
|
||||
}
|
||||
|
||||
return this.sharedLinkRepository
|
||||
.find({
|
||||
select: { albumId: true },
|
||||
where: {
|
||||
id: sharedLinkId,
|
||||
albumId: In([...albumIds]),
|
||||
},
|
||||
})
|
||||
return this.db
|
||||
.selectFrom('shared_links')
|
||||
.select('shared_links.albumId')
|
||||
.where('shared_links.id', '=', sharedLinkId)
|
||||
.where('shared_links.albumId', 'in', [...albumIds])
|
||||
.execute()
|
||||
.then(
|
||||
(sharedLinks) => new Set(sharedLinks.flatMap((sharedLink) => (sharedLink.albumId ? [sharedLink.albumId] : []))),
|
||||
);
|
||||
@ -166,12 +143,7 @@ class AlbumAccess implements IAlbumAccess {
|
||||
}
|
||||
|
||||
class AssetAccess implements IAssetAccess {
|
||||
constructor(
|
||||
private albumRepository: Repository<AlbumEntity>,
|
||||
private assetRepository: Repository<AssetEntity>,
|
||||
private partnerRepository: Repository<PartnerEntity>,
|
||||
private sharedLinkRepository: Repository<SharedLinkEntity>,
|
||||
) {}
|
||||
constructor(private db: Kysely<DB>) {}
|
||||
|
||||
@GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID_SET] })
|
||||
@ChunkedSet({ paramIndex: 1 })
|
||||
@ -180,30 +152,31 @@ class AssetAccess implements IAssetAccess {
|
||||
return new Set();
|
||||
}
|
||||
|
||||
return this.albumRepository
|
||||
.createQueryBuilder('album')
|
||||
.innerJoin('album.assets', 'asset')
|
||||
.leftJoin('album.albumUsers', 'album_albumUsers_users')
|
||||
.leftJoin('album_albumUsers_users.user', 'albumUsers')
|
||||
.select('asset.id', 'assetId')
|
||||
.addSelect('asset.livePhotoVideoId', 'livePhotoVideoId')
|
||||
.where('array["asset"."id", "asset"."livePhotoVideoId"] && array[:...assetIds]::uuid[]', {
|
||||
assetIds: [...assetIds],
|
||||
})
|
||||
.andWhere(
|
||||
new Brackets((qb) => {
|
||||
qb.where('album.ownerId = :userId', { userId }).orWhere('albumUsers.id = :userId', { userId });
|
||||
}),
|
||||
return this.db
|
||||
.selectFrom('albums')
|
||||
.innerJoin('albums_assets_assets as albumAssets', 'albums.id', 'albumAssets.albumsId')
|
||||
.innerJoin('assets', (join) =>
|
||||
join.onRef('assets.id', '=', 'albumAssets.assetsId').on('assets.deletedAt', 'is', null),
|
||||
)
|
||||
.getRawMany()
|
||||
.then((rows) => {
|
||||
.leftJoin('albums_shared_users_users as albumUsers', 'albumUsers.albumsId', 'albums.id')
|
||||
.leftJoin('users', (join) => join.onRef('users.id', '=', 'albumUsers.usersId').on('users.deletedAt', 'is', null))
|
||||
.select(['assets.id', 'assets.livePhotoVideoId'])
|
||||
.where(
|
||||
sql`array["assets"."id", "assets"."livePhotoVideoId"]`,
|
||||
'&&',
|
||||
sql`array[${sql.join([...assetIds])}]::uuid[] `,
|
||||
)
|
||||
.where((eb) => eb.or([eb('albums.ownerId', '=', userId), eb('users.id', '=', userId)]))
|
||||
.where('albums.deletedAt', 'is', null)
|
||||
.execute()
|
||||
.then((assets) => {
|
||||
const allowedIds = new Set<string>();
|
||||
for (const row of rows) {
|
||||
if (row.assetId && assetIds.has(row.assetId)) {
|
||||
allowedIds.add(row.assetId);
|
||||
for (const asset of assets) {
|
||||
if (asset.id && assetIds.has(asset.id)) {
|
||||
allowedIds.add(asset.id);
|
||||
}
|
||||
if (row.livePhotoVideoId && assetIds.has(row.livePhotoVideoId)) {
|
||||
allowedIds.add(row.livePhotoVideoId);
|
||||
if (asset.livePhotoVideoId && assetIds.has(asset.livePhotoVideoId)) {
|
||||
allowedIds.add(asset.livePhotoVideoId);
|
||||
}
|
||||
}
|
||||
return allowedIds;
|
||||
@ -217,15 +190,12 @@ class AssetAccess implements IAssetAccess {
|
||||
return new Set();
|
||||
}
|
||||
|
||||
return this.assetRepository
|
||||
.find({
|
||||
select: { id: true },
|
||||
where: {
|
||||
id: In([...assetIds]),
|
||||
ownerId: userId,
|
||||
},
|
||||
withDeleted: true,
|
||||
})
|
||||
return this.db
|
||||
.selectFrom('assets')
|
||||
.select('assets.id')
|
||||
.where('assets.id', 'in', [...assetIds])
|
||||
.where('assets.ownerId', '=', userId)
|
||||
.execute()
|
||||
.then((assets) => new Set(assets.map((asset) => asset.id)));
|
||||
}
|
||||
|
||||
@ -236,16 +206,20 @@ class AssetAccess implements IAssetAccess {
|
||||
return new Set();
|
||||
}
|
||||
|
||||
return this.partnerRepository
|
||||
.createQueryBuilder('partner')
|
||||
.innerJoin('partner.sharedBy', 'sharedBy')
|
||||
.innerJoin('sharedBy.assets', 'asset')
|
||||
.select('asset.id', 'assetId')
|
||||
.where('partner.sharedWithId = :userId', { userId })
|
||||
.andWhere('asset.isArchived = false')
|
||||
.andWhere('asset.id IN (:...assetIds)', { assetIds: [...assetIds] })
|
||||
.getRawMany()
|
||||
.then((rows) => new Set(rows.map((row) => row.assetId)));
|
||||
return this.db
|
||||
.selectFrom('partners as partner')
|
||||
.innerJoin('users as sharedBy', (join) =>
|
||||
join.onRef('sharedBy.id', '=', 'partner.sharedById').on('sharedBy.deletedAt', 'is', null),
|
||||
)
|
||||
.innerJoin('assets', (join) =>
|
||||
join.onRef('assets.ownerId', '=', 'sharedBy.id').on('assets.deletedAt', 'is', null),
|
||||
)
|
||||
.select('assets.id')
|
||||
.where('partner.sharedWithId', '=', userId)
|
||||
.where('assets.isArchived', '=', false)
|
||||
.where('assets.id', 'in', [...assetIds])
|
||||
.execute()
|
||||
.then((assets) => new Set(assets.map((asset) => asset.id)));
|
||||
}
|
||||
|
||||
@GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID_SET] })
|
||||
@ -255,23 +229,32 @@ class AssetAccess implements IAssetAccess {
|
||||
return new Set();
|
||||
}
|
||||
|
||||
return this.sharedLinkRepository
|
||||
.createQueryBuilder('sharedLink')
|
||||
.leftJoin('sharedLink.album', 'album')
|
||||
.leftJoin('sharedLink.assets', 'assets')
|
||||
.leftJoin('album.assets', 'albumAssets')
|
||||
.select('assets.id', 'assetId')
|
||||
.addSelect('albumAssets.id', 'albumAssetId')
|
||||
.addSelect('assets.livePhotoVideoId', 'assetLivePhotoVideoId')
|
||||
.addSelect('albumAssets.livePhotoVideoId', 'albumAssetLivePhotoVideoId')
|
||||
.where('sharedLink.id = :sharedLinkId', { sharedLinkId })
|
||||
.andWhere(
|
||||
'array["assets"."id", "assets"."livePhotoVideoId", "albumAssets"."id", "albumAssets"."livePhotoVideoId"] && array[:...assetIds]::uuid[]',
|
||||
{
|
||||
assetIds: [...assetIds],
|
||||
},
|
||||
return this.db
|
||||
.selectFrom('shared_links')
|
||||
.leftJoin('albums', (join) =>
|
||||
join.onRef('albums.id', '=', 'shared_links.albumId').on('albums.deletedAt', 'is', null),
|
||||
)
|
||||
.getRawMany()
|
||||
.leftJoin('shared_link__asset', 'shared_link__asset.sharedLinksId', 'shared_links.id')
|
||||
.leftJoin('assets', (join) =>
|
||||
join.onRef('assets.id', '=', 'shared_link__asset.assetsId').on('assets.deletedAt', 'is', null),
|
||||
)
|
||||
.leftJoin('albums_assets_assets', 'albums_assets_assets.albumsId', 'albums.id')
|
||||
.leftJoin('assets as albumAssets', (join) =>
|
||||
join.onRef('albumAssets.id', '=', 'albums_assets_assets.assetsId').on('albumAssets.deletedAt', 'is', null),
|
||||
)
|
||||
.select([
|
||||
'assets.id as assetId',
|
||||
'assets.livePhotoVideoId as assetLivePhotoVideoId',
|
||||
'albumAssets.id as albumAssetId',
|
||||
'albumAssets.livePhotoVideoId as albumAssetLivePhotoVideoId',
|
||||
])
|
||||
.where('shared_links.id', '=', sharedLinkId)
|
||||
.where(
|
||||
sql`array["assets"."id", "assets"."livePhotoVideoId", "albumAssets"."id", "albumAssets"."livePhotoVideoId"]`,
|
||||
'&&',
|
||||
sql`array[${sql.join([...assetIds])}]::uuid[] `,
|
||||
)
|
||||
.execute()
|
||||
.then((rows) => {
|
||||
const allowedIds = new Set<string>();
|
||||
for (const row of rows) {
|
||||
@ -294,7 +277,7 @@ class AssetAccess implements IAssetAccess {
|
||||
}
|
||||
|
||||
class AuthDeviceAccess implements IAuthDeviceAccess {
|
||||
constructor(private sessionRepository: Repository<SessionEntity>) {}
|
||||
constructor(private db: Kysely<DB>) {}
|
||||
|
||||
@GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID_SET] })
|
||||
@ChunkedSet({ paramIndex: 1 })
|
||||
@ -303,20 +286,18 @@ class AuthDeviceAccess implements IAuthDeviceAccess {
|
||||
return new Set();
|
||||
}
|
||||
|
||||
return this.sessionRepository
|
||||
.find({
|
||||
select: { id: true },
|
||||
where: {
|
||||
userId,
|
||||
id: In([...deviceIds]),
|
||||
},
|
||||
})
|
||||
return this.db
|
||||
.selectFrom('sessions')
|
||||
.select('sessions.id')
|
||||
.where('sessions.userId', '=', userId)
|
||||
.where('sessions.id', 'in', [...deviceIds])
|
||||
.execute()
|
||||
.then((tokens) => new Set(tokens.map((token) => token.id)));
|
||||
}
|
||||
}
|
||||
|
||||
class StackAccess implements IStackAccess {
|
||||
constructor(private stackRepository: Repository<StackEntity>) {}
|
||||
constructor(private db: Kysely<DB>) {}
|
||||
|
||||
@GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID_SET] })
|
||||
@ChunkedSet({ paramIndex: 1 })
|
||||
@ -325,20 +306,18 @@ class StackAccess implements IStackAccess {
|
||||
return new Set();
|
||||
}
|
||||
|
||||
return this.stackRepository
|
||||
.find({
|
||||
select: { id: true },
|
||||
where: {
|
||||
id: In([...stackIds]),
|
||||
ownerId: userId,
|
||||
},
|
||||
})
|
||||
return this.db
|
||||
.selectFrom('asset_stack as stacks')
|
||||
.select('stacks.id')
|
||||
.where('stacks.id', 'in', [...stackIds])
|
||||
.where('stacks.ownerId', '=', userId)
|
||||
.execute()
|
||||
.then((stacks) => new Set(stacks.map((stack) => stack.id)));
|
||||
}
|
||||
}
|
||||
|
||||
class TimelineAccess implements ITimelineAccess {
|
||||
constructor(private partnerRepository: Repository<PartnerEntity>) {}
|
||||
constructor(private db: Kysely<DB>) {}
|
||||
|
||||
@GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID_SET] })
|
||||
@ChunkedSet({ paramIndex: 1 })
|
||||
@ -347,18 +326,18 @@ class TimelineAccess implements ITimelineAccess {
|
||||
return new Set();
|
||||
}
|
||||
|
||||
return this.partnerRepository
|
||||
.createQueryBuilder('partner')
|
||||
.select('partner.sharedById')
|
||||
.where('partner.sharedById IN (:...partnerIds)', { partnerIds: [...partnerIds] })
|
||||
.andWhere('partner.sharedWithId = :userId', { userId })
|
||||
.getMany()
|
||||
return this.db
|
||||
.selectFrom('partners')
|
||||
.select('partners.sharedById')
|
||||
.where('partners.sharedById', 'in', [...partnerIds])
|
||||
.where('partners.sharedWithId', '=', userId)
|
||||
.execute()
|
||||
.then((partners) => new Set(partners.map((partner) => partner.sharedById)));
|
||||
}
|
||||
}
|
||||
|
||||
class MemoryAccess implements IMemoryAccess {
|
||||
constructor(private memoryRepository: Repository<MemoryEntity>) {}
|
||||
constructor(private db: Kysely<DB>) {}
|
||||
|
||||
@GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID_SET] })
|
||||
@ChunkedSet({ paramIndex: 1 })
|
||||
@ -367,23 +346,19 @@ class MemoryAccess implements IMemoryAccess {
|
||||
return new Set();
|
||||
}
|
||||
|
||||
return this.memoryRepository
|
||||
.find({
|
||||
select: { id: true },
|
||||
where: {
|
||||
id: In([...memoryIds]),
|
||||
ownerId: userId,
|
||||
},
|
||||
})
|
||||
return this.db
|
||||
.selectFrom('memories')
|
||||
.select('memories.id')
|
||||
.where('memories.id', 'in', [...memoryIds])
|
||||
.where('memories.ownerId', '=', userId)
|
||||
.where('memories.deletedAt', 'is', null)
|
||||
.execute()
|
||||
.then((memories) => new Set(memories.map((memory) => memory.id)));
|
||||
}
|
||||
}
|
||||
|
||||
class PersonAccess implements IPersonAccess {
|
||||
constructor(
|
||||
private assetFaceRepository: Repository<AssetFaceEntity>,
|
||||
private personRepository: Repository<PersonEntity>,
|
||||
) {}
|
||||
constructor(private db: Kysely<DB>) {}
|
||||
|
||||
@GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID_SET] })
|
||||
@ChunkedSet({ paramIndex: 1 })
|
||||
@ -392,14 +367,12 @@ class PersonAccess implements IPersonAccess {
|
||||
return new Set();
|
||||
}
|
||||
|
||||
return this.personRepository
|
||||
.find({
|
||||
select: { id: true },
|
||||
where: {
|
||||
id: In([...personIds]),
|
||||
ownerId: userId,
|
||||
},
|
||||
})
|
||||
return this.db
|
||||
.selectFrom('person')
|
||||
.select('person.id')
|
||||
.where('person.id', 'in', [...personIds])
|
||||
.where('person.ownerId', '=', userId)
|
||||
.execute()
|
||||
.then((persons) => new Set(persons.map((person) => person.id)));
|
||||
}
|
||||
|
||||
@ -410,22 +383,21 @@ class PersonAccess implements IPersonAccess {
|
||||
return new Set();
|
||||
}
|
||||
|
||||
return this.assetFaceRepository
|
||||
.find({
|
||||
select: { id: true },
|
||||
where: {
|
||||
id: In([...assetFaceIds]),
|
||||
asset: {
|
||||
ownerId: userId,
|
||||
},
|
||||
},
|
||||
})
|
||||
return this.db
|
||||
.selectFrom('asset_faces')
|
||||
.select('asset_faces.id')
|
||||
.leftJoin('assets', (join) =>
|
||||
join.onRef('assets.id', '=', 'asset_faces.assetId').on('assets.deletedAt', 'is', null),
|
||||
)
|
||||
.where('asset_faces.id', 'in', [...assetFaceIds])
|
||||
.where('assets.ownerId', '=', userId)
|
||||
.execute()
|
||||
.then((faces) => new Set(faces.map((face) => face.id)));
|
||||
}
|
||||
}
|
||||
|
||||
class PartnerAccess implements IPartnerAccess {
|
||||
constructor(private partnerRepository: Repository<PartnerEntity>) {}
|
||||
constructor(private db: Kysely<DB>) {}
|
||||
|
||||
@GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID_SET] })
|
||||
@ChunkedSet({ paramIndex: 1 })
|
||||
@ -434,18 +406,18 @@ class PartnerAccess implements IPartnerAccess {
|
||||
return new Set();
|
||||
}
|
||||
|
||||
return this.partnerRepository
|
||||
.createQueryBuilder('partner')
|
||||
.select('partner.sharedById')
|
||||
.where('partner.sharedById IN (:...partnerIds)', { partnerIds: [...partnerIds] })
|
||||
.andWhere('partner.sharedWithId = :userId', { userId })
|
||||
.getMany()
|
||||
return this.db
|
||||
.selectFrom('partners')
|
||||
.select('partners.sharedById')
|
||||
.where('partners.sharedById', 'in', [...partnerIds])
|
||||
.where('partners.sharedWithId', '=', userId)
|
||||
.execute()
|
||||
.then((partners) => new Set(partners.map((partner) => partner.sharedById)));
|
||||
}
|
||||
}
|
||||
|
||||
class TagAccess implements ITagAccess {
|
||||
constructor(private tagRepository: Repository<TagEntity>) {}
|
||||
constructor(private db: Kysely<DB>) {}
|
||||
|
||||
@GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID_SET] })
|
||||
@ChunkedSet({ paramIndex: 1 })
|
||||
@ -454,14 +426,12 @@ class TagAccess implements ITagAccess {
|
||||
return new Set();
|
||||
}
|
||||
|
||||
return this.tagRepository
|
||||
.find({
|
||||
select: { id: true },
|
||||
where: {
|
||||
id: In([...tagIds]),
|
||||
userId,
|
||||
},
|
||||
})
|
||||
return this.db
|
||||
.selectFrom('tags')
|
||||
.select('tags.id')
|
||||
.where('tags.id', 'in', [...tagIds])
|
||||
.where('tags.userId', '=', userId)
|
||||
.execute()
|
||||
.then((tags) => new Set(tags.map((tag) => tag.id)));
|
||||
}
|
||||
}
|
||||
@ -478,29 +448,16 @@ export class AccessRepository implements IAccessRepository {
|
||||
tag: ITagAccess;
|
||||
timeline: ITimelineAccess;
|
||||
|
||||
constructor(
|
||||
@InjectRepository(ActivityEntity) activityRepository: Repository<ActivityEntity>,
|
||||
@InjectRepository(AssetEntity) assetRepository: Repository<AssetEntity>,
|
||||
@InjectRepository(AlbumEntity) albumRepository: Repository<AlbumEntity>,
|
||||
@InjectRepository(LibraryEntity) libraryRepository: Repository<LibraryEntity>,
|
||||
@InjectRepository(MemoryEntity) memoryRepository: Repository<MemoryEntity>,
|
||||
@InjectRepository(PartnerEntity) partnerRepository: Repository<PartnerEntity>,
|
||||
@InjectRepository(PersonEntity) personRepository: Repository<PersonEntity>,
|
||||
@InjectRepository(AssetFaceEntity) assetFaceRepository: Repository<AssetFaceEntity>,
|
||||
@InjectRepository(SharedLinkEntity) sharedLinkRepository: Repository<SharedLinkEntity>,
|
||||
@InjectRepository(SessionEntity) sessionRepository: Repository<SessionEntity>,
|
||||
@InjectRepository(StackEntity) stackRepository: Repository<StackEntity>,
|
||||
@InjectRepository(TagEntity) tagRepository: Repository<TagEntity>,
|
||||
) {
|
||||
this.activity = new ActivityAccess(activityRepository, albumRepository);
|
||||
this.album = new AlbumAccess(albumRepository, sharedLinkRepository);
|
||||
this.asset = new AssetAccess(albumRepository, assetRepository, partnerRepository, sharedLinkRepository);
|
||||
this.authDevice = new AuthDeviceAccess(sessionRepository);
|
||||
this.memory = new MemoryAccess(memoryRepository);
|
||||
this.person = new PersonAccess(assetFaceRepository, personRepository);
|
||||
this.partner = new PartnerAccess(partnerRepository);
|
||||
this.stack = new StackAccess(stackRepository);
|
||||
this.tag = new TagAccess(tagRepository);
|
||||
this.timeline = new TimelineAccess(partnerRepository);
|
||||
constructor(@InjectKysely() db: Kysely<DB>) {
|
||||
this.activity = new ActivityAccess(db);
|
||||
this.album = new AlbumAccess(db);
|
||||
this.asset = new AssetAccess(db);
|
||||
this.authDevice = new AuthDeviceAccess(db);
|
||||
this.memory = new MemoryAccess(db);
|
||||
this.person = new PersonAccess(db);
|
||||
this.partner = new PartnerAccess(db);
|
||||
this.stack = new StackAccess(db);
|
||||
this.tag = new TagAccess(db);
|
||||
this.timeline = new TimelineAccess(db);
|
||||
}
|
||||
}
|
||||
|
@ -2,10 +2,9 @@ import { Injectable } from '@nestjs/common';
|
||||
import { ExpressionBuilder, Insertable, Kysely } from 'kysely';
|
||||
import { jsonObjectFrom } from 'kysely/helpers/postgres';
|
||||
import { InjectKysely } from 'nestjs-kysely';
|
||||
import { columns } from 'src/database';
|
||||
import { Activity, DB } from 'src/db';
|
||||
import { DummyValue, GenerateSql } from 'src/decorators';
|
||||
import { ActivityEntity } from 'src/entities/activity.entity';
|
||||
import { IActivityRepository } from 'src/interfaces/activity.interface';
|
||||
import { asUuid } from 'src/utils/database';
|
||||
|
||||
export interface ActivitySearch {
|
||||
@ -19,18 +18,18 @@ const withUser = (eb: ExpressionBuilder<DB, 'activity'>) => {
|
||||
return jsonObjectFrom(
|
||||
eb
|
||||
.selectFrom('users')
|
||||
.selectAll()
|
||||
.select(columns.userDto)
|
||||
.whereRef('users.id', '=', 'activity.userId')
|
||||
.where('users.deletedAt', 'is', null),
|
||||
).as('user');
|
||||
};
|
||||
|
||||
@Injectable()
|
||||
export class ActivityRepository implements IActivityRepository {
|
||||
export class ActivityRepository {
|
||||
constructor(@InjectKysely() private db: Kysely<DB>) {}
|
||||
|
||||
@GenerateSql({ params: [{ albumId: DummyValue.UUID }] })
|
||||
search(options: ActivitySearch): Promise<ActivityEntity[]> {
|
||||
search(options: ActivitySearch) {
|
||||
const { userId, assetId, albumId, isLiked } = options;
|
||||
|
||||
return this.db
|
||||
@ -44,14 +43,14 @@ export class ActivityRepository implements IActivityRepository {
|
||||
.$if(!!albumId, (qb) => qb.where('activity.albumId', '=', albumId!))
|
||||
.$if(isLiked !== undefined, (qb) => qb.where('activity.isLiked', '=', isLiked!))
|
||||
.orderBy('activity.createdAt', 'asc')
|
||||
.execute() as unknown as Promise<ActivityEntity[]>;
|
||||
.execute();
|
||||
}
|
||||
|
||||
async create(activity: Insertable<Activity>) {
|
||||
return this.save(activity);
|
||||
}
|
||||
|
||||
async delete(id: string): Promise<void> {
|
||||
async delete(id: string) {
|
||||
await this.db.deleteFrom('activity').where('id', '=', asUuid(id)).execute();
|
||||
}
|
||||
|
||||
@ -79,6 +78,6 @@ export class ActivityRepository implements IActivityRepository {
|
||||
.selectAll('activity')
|
||||
.select(withUser)
|
||||
.where('activity.id', '=', asUuid(id))
|
||||
.executeTakeFirstOrThrow() as unknown as Promise<ActivityEntity>;
|
||||
.executeTakeFirstOrThrow();
|
||||
}
|
||||
}
|
||||
|
@ -1,26 +1,40 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { AlbumUserEntity } from 'src/entities/album-user.entity';
|
||||
import { Insertable, Kysely, Selectable, Updateable } from 'kysely';
|
||||
import { InjectKysely } from 'nestjs-kysely';
|
||||
import { AlbumsSharedUsersUsers, DB } from 'src/db';
|
||||
import { DummyValue, GenerateSql } from 'src/decorators';
|
||||
import { AlbumUserRole } from 'src/enum';
|
||||
import { AlbumPermissionId, IAlbumUserRepository } from 'src/interfaces/album-user.interface';
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
@Injectable()
|
||||
export class AlbumUserRepository implements IAlbumUserRepository {
|
||||
constructor(@InjectRepository(AlbumUserEntity) private repository: Repository<AlbumUserEntity>) {}
|
||||
constructor(@InjectKysely() private db: Kysely<DB>) {}
|
||||
|
||||
async create(albumUser: Partial<AlbumUserEntity>): Promise<AlbumUserEntity> {
|
||||
const { userId, albumId } = await this.repository.save(albumUser);
|
||||
return this.repository.findOneOrFail({ where: { userId, albumId } });
|
||||
@GenerateSql({ params: [{ usersId: DummyValue.UUID, albumsId: DummyValue.UUID }] })
|
||||
create(albumUser: Insertable<AlbumsSharedUsersUsers>): Promise<Selectable<AlbumsSharedUsersUsers>> {
|
||||
return this.db.insertInto('albums_shared_users_users').values(albumUser).returningAll().executeTakeFirstOrThrow();
|
||||
}
|
||||
|
||||
async update({ userId, albumId }: AlbumPermissionId, dto: Partial<AlbumUserEntity>): Promise<AlbumUserEntity> {
|
||||
await this.repository.update({ userId, albumId }, dto);
|
||||
return this.repository.findOneOrFail({
|
||||
where: { userId, albumId },
|
||||
});
|
||||
@GenerateSql({ params: [{ usersId: DummyValue.UUID, albumsId: DummyValue.UUID }, { role: AlbumUserRole.VIEWER }] })
|
||||
update(
|
||||
{ usersId, albumsId }: AlbumPermissionId,
|
||||
dto: Updateable<AlbumsSharedUsersUsers>,
|
||||
): Promise<Selectable<AlbumsSharedUsersUsers>> {
|
||||
return this.db
|
||||
.updateTable('albums_shared_users_users')
|
||||
.set(dto)
|
||||
.where('usersId', '=', usersId)
|
||||
.where('albumsId', '=', albumsId)
|
||||
.returningAll()
|
||||
.executeTakeFirstOrThrow();
|
||||
}
|
||||
|
||||
async delete({ userId, albumId }: AlbumPermissionId): Promise<void> {
|
||||
await this.repository.delete({ userId, albumId });
|
||||
@GenerateSql({ params: [{ usersId: DummyValue.UUID, albumsId: DummyValue.UUID }] })
|
||||
async delete({ usersId, albumsId }: AlbumPermissionId): Promise<void> {
|
||||
await this.db
|
||||
.deleteFrom('albums_shared_users_users')
|
||||
.where('usersId', '=', usersId)
|
||||
.where('albumsId', '=', albumsId)
|
||||
.execute();
|
||||
}
|
||||
}
|
||||
|
@ -83,7 +83,7 @@ describe('getEnv', () => {
|
||||
config: {
|
||||
kysely: {
|
||||
dialect: expect.any(PostgresJSDialect),
|
||||
log: ['error'],
|
||||
log: expect.any(Function),
|
||||
},
|
||||
typeorm: expect.objectContaining({
|
||||
type: 'postgres',
|
||||
|
@ -5,7 +5,7 @@ import { Request, Response } from 'express';
|
||||
import { PostgresJSDialect } from 'kysely-postgres-js';
|
||||
import { CLS_ID } from 'nestjs-cls';
|
||||
import { join, resolve } from 'node:path';
|
||||
import postgres from 'postgres';
|
||||
import postgres, { Notice } from 'postgres';
|
||||
import { citiesFile, excludePaths, IWorker } from 'src/constants';
|
||||
import { Telemetry } from 'src/decorators';
|
||||
import { EnvDto } from 'src/dtos/env.dto';
|
||||
@ -99,6 +99,11 @@ const getEnv = (): EnvData => {
|
||||
}
|
||||
|
||||
const driverOptions = {
|
||||
onnotice: (notice: Notice) => {
|
||||
if (notice['severity'] !== 'NOTICE') {
|
||||
console.warn('Postgres notice:', notice);
|
||||
}
|
||||
},
|
||||
max: 10,
|
||||
types: {
|
||||
date: {
|
||||
@ -194,7 +199,16 @@ const getEnv = (): EnvData => {
|
||||
dialect: new PostgresJSDialect({
|
||||
postgres: databaseUrl ? postgres(databaseUrl, driverOptions) : postgres({ ...parts, ...driverOptions }),
|
||||
}),
|
||||
log: ['error'] as const,
|
||||
log(event) {
|
||||
if (event.level === 'error') {
|
||||
console.error('Query failed :', {
|
||||
durationMs: event.queryDurationMillis,
|
||||
error: event.error,
|
||||
sql: event.query.sql,
|
||||
params: event.query.parameters,
|
||||
});
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
|
@ -1,5 +1,4 @@
|
||||
import { IAccessRepository } from 'src/interfaces/access.interface';
|
||||
import { IActivityRepository } from 'src/interfaces/activity.interface';
|
||||
import { IAlbumUserRepository } from 'src/interfaces/album-user.interface';
|
||||
import { IAlbumRepository } from 'src/interfaces/album.interface';
|
||||
import { IKeyRepository } from 'src/interfaces/api-key.interface';
|
||||
@ -78,8 +77,12 @@ import { VersionHistoryRepository } from 'src/repositories/version-history.repos
|
||||
import { ViewRepository } from 'src/repositories/view-repository';
|
||||
|
||||
export const repositories = [
|
||||
//
|
||||
ActivityRepository,
|
||||
];
|
||||
|
||||
export const providers = [
|
||||
{ provide: IAccessRepository, useClass: AccessRepository },
|
||||
{ provide: IActivityRepository, useClass: ActivityRepository },
|
||||
{ provide: IAlbumRepository, useClass: AlbumRepository },
|
||||
{ provide: IAlbumUserRepository, useClass: AlbumUserRepository },
|
||||
{ provide: IAssetRepository, useClass: AssetRepository },
|
||||
|
@ -1,84 +1,122 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { ExpressionBuilder, Insertable, Kysely, Updateable } from 'kysely';
|
||||
import { jsonObjectFrom } from 'kysely/helpers/postgres';
|
||||
import { InjectKysely } from 'nestjs-kysely';
|
||||
import { DB, Libraries } from 'src/db';
|
||||
import { DummyValue, GenerateSql } from 'src/decorators';
|
||||
import { LibraryStatsResponseDto } from 'src/dtos/library.dto';
|
||||
import { LibraryEntity } from 'src/entities/library.entity';
|
||||
import { AssetType } from 'src/enum';
|
||||
import { ILibraryRepository } from 'src/interfaces/library.interface';
|
||||
import { IsNull, Not } from 'typeorm';
|
||||
import { Repository } from 'typeorm/repository/Repository.js';
|
||||
|
||||
const userColumns = [
|
||||
'users.id',
|
||||
'users.email',
|
||||
'users.createdAt',
|
||||
'users.profileImagePath',
|
||||
'users.isAdmin',
|
||||
'users.shouldChangePassword',
|
||||
'users.deletedAt',
|
||||
'users.oauthId',
|
||||
'users.updatedAt',
|
||||
'users.storageLabel',
|
||||
'users.name',
|
||||
'users.quotaSizeInBytes',
|
||||
'users.quotaUsageInBytes',
|
||||
'users.status',
|
||||
'users.profileChangedAt',
|
||||
] as const;
|
||||
|
||||
const withOwner = (eb: ExpressionBuilder<DB, 'libraries'>) => {
|
||||
return jsonObjectFrom(eb.selectFrom('users').whereRef('users.id', '=', 'libraries.ownerId').select(userColumns)).as(
|
||||
'owner',
|
||||
);
|
||||
};
|
||||
|
||||
@Injectable()
|
||||
export class LibraryRepository implements ILibraryRepository {
|
||||
constructor(@InjectRepository(LibraryEntity) private repository: Repository<LibraryEntity>) {}
|
||||
constructor(@InjectKysely() private db: Kysely<DB>) {}
|
||||
|
||||
@GenerateSql({ params: [DummyValue.UUID] })
|
||||
get(id: string, withDeleted = false): Promise<LibraryEntity | null> {
|
||||
return this.repository.findOneOrFail({
|
||||
where: {
|
||||
id,
|
||||
},
|
||||
relations: { owner: true },
|
||||
withDeleted,
|
||||
});
|
||||
get(id: string, withDeleted = false): Promise<LibraryEntity | undefined> {
|
||||
return this.db
|
||||
.selectFrom('libraries')
|
||||
.selectAll('libraries')
|
||||
.select(withOwner)
|
||||
.where('libraries.id', '=', id)
|
||||
.$if(!withDeleted, (qb) => qb.where('libraries.deletedAt', 'is', null))
|
||||
.executeTakeFirst() as Promise<LibraryEntity | undefined>;
|
||||
}
|
||||
|
||||
@GenerateSql({ params: [] })
|
||||
getAll(withDeleted = false): Promise<LibraryEntity[]> {
|
||||
return this.repository.find({
|
||||
relations: {
|
||||
owner: true,
|
||||
},
|
||||
order: {
|
||||
createdAt: 'ASC',
|
||||
},
|
||||
withDeleted,
|
||||
});
|
||||
return this.db
|
||||
.selectFrom('libraries')
|
||||
.selectAll('libraries')
|
||||
.select(withOwner)
|
||||
.orderBy('createdAt', 'asc')
|
||||
.$if(!withDeleted, (qb) => qb.where('libraries.deletedAt', 'is', null))
|
||||
.execute() as unknown as Promise<LibraryEntity[]>;
|
||||
}
|
||||
|
||||
@GenerateSql()
|
||||
getAllDeleted(): Promise<LibraryEntity[]> {
|
||||
return this.repository.find({
|
||||
where: {
|
||||
deletedAt: Not(IsNull()),
|
||||
},
|
||||
relations: {
|
||||
owner: true,
|
||||
},
|
||||
order: {
|
||||
createdAt: 'ASC',
|
||||
},
|
||||
withDeleted: true,
|
||||
});
|
||||
return this.db
|
||||
.selectFrom('libraries')
|
||||
.selectAll('libraries')
|
||||
.select(withOwner)
|
||||
.where('libraries.deletedAt', 'is not', null)
|
||||
.orderBy('createdAt', 'asc')
|
||||
.execute() as unknown as Promise<LibraryEntity[]>;
|
||||
}
|
||||
|
||||
create(library: Omit<LibraryEntity, 'id' | 'createdAt' | 'updatedAt' | 'ownerId'>): Promise<LibraryEntity> {
|
||||
return this.repository.save(library);
|
||||
create(library: Insertable<Libraries>): Promise<LibraryEntity> {
|
||||
return this.db
|
||||
.insertInto('libraries')
|
||||
.values(library)
|
||||
.returningAll()
|
||||
.executeTakeFirstOrThrow() as Promise<LibraryEntity>;
|
||||
}
|
||||
|
||||
async delete(id: string): Promise<void> {
|
||||
await this.repository.delete({ id });
|
||||
await this.db.deleteFrom('libraries').where('libraries.id', '=', id).execute();
|
||||
}
|
||||
|
||||
async softDelete(id: string): Promise<void> {
|
||||
await this.repository.softDelete({ id });
|
||||
await this.db.updateTable('libraries').set({ deletedAt: new Date() }).where('libraries.id', '=', id).execute();
|
||||
}
|
||||
|
||||
async update(library: Partial<LibraryEntity>): Promise<LibraryEntity> {
|
||||
return this.save(library);
|
||||
update(id: string, library: Updateable<Libraries>): Promise<LibraryEntity> {
|
||||
return this.db
|
||||
.updateTable('libraries')
|
||||
.set(library)
|
||||
.where('libraries.id', '=', id)
|
||||
.returningAll()
|
||||
.executeTakeFirstOrThrow() as Promise<LibraryEntity>;
|
||||
}
|
||||
|
||||
@GenerateSql({ params: [DummyValue.UUID] })
|
||||
async getStatistics(id: string): Promise<LibraryStatsResponseDto | undefined> {
|
||||
const stats = await this.repository
|
||||
.createQueryBuilder('libraries')
|
||||
.addSelect(`COUNT(assets.id) FILTER (WHERE assets.type = 'IMAGE' AND assets.isVisible)`, 'photos')
|
||||
.addSelect(`COUNT(assets.id) FILTER (WHERE assets.type = 'VIDEO' AND assets.isVisible)`, 'videos')
|
||||
.addSelect('COALESCE(SUM(exif.fileSizeInByte), 0)', 'usage')
|
||||
.leftJoin('libraries.assets', 'assets')
|
||||
.leftJoin('assets.exifInfo', 'exif')
|
||||
const stats = await this.db
|
||||
.selectFrom('libraries')
|
||||
.innerJoin('assets', 'assets.libraryId', 'libraries.id')
|
||||
.innerJoin('exif', 'exif.assetId', 'assets.id')
|
||||
.select((eb) =>
|
||||
eb.fn
|
||||
.count('assets.id')
|
||||
.filterWhere((eb) => eb.and([eb('assets.type', '=', AssetType.IMAGE), eb('assets.isVisible', '=', true)]))
|
||||
.as('photos'),
|
||||
)
|
||||
.select((eb) =>
|
||||
eb.fn
|
||||
.countAll()
|
||||
.filterWhere((eb) => eb.and([eb('assets.type', '=', AssetType.VIDEO), eb('assets.isVisible', '=', true)]))
|
||||
.as('videos'),
|
||||
)
|
||||
.select((eb) => eb.fn.coalesce((eb) => eb.fn.sum('exif.fileSizeInByte'), eb.val(0)).as('usage'))
|
||||
.groupBy('libraries.id')
|
||||
.where('libraries.id = :id', { id })
|
||||
.getRawOne();
|
||||
.where('libraries.id', '=', id)
|
||||
.executeTakeFirst();
|
||||
|
||||
if (!stats) {
|
||||
return;
|
||||
@ -91,9 +129,4 @@ export class LibraryRepository implements ILibraryRepository {
|
||||
total: Number(stats.photos) + Number(stats.videos),
|
||||
};
|
||||
}
|
||||
|
||||
private async save(library: Partial<LibraryEntity>) {
|
||||
const { id } = await this.repository.save(library);
|
||||
return this.repository.findOneByOrFail({ id });
|
||||
}
|
||||
}
|
||||
|
@ -1,17 +1,15 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { InjectDataSource, InjectRepository } from '@nestjs/typeorm';
|
||||
import { getName } from 'i18n-iso-countries';
|
||||
import { Expression, Kysely, sql, SqlBool } from 'kysely';
|
||||
import { InjectKysely } from 'nestjs-kysely';
|
||||
import { randomUUID } from 'node:crypto';
|
||||
import { createReadStream, existsSync } from 'node:fs';
|
||||
import { readFile } from 'node:fs/promises';
|
||||
import readLine from 'node:readline';
|
||||
import { citiesFile } from 'src/constants';
|
||||
import { AssetEntity } from 'src/entities/asset.entity';
|
||||
import { GeodataPlacesEntity, GeodataPlacesTempEntity } from 'src/entities/geodata-places.entity';
|
||||
import {
|
||||
NaturalEarthCountriesEntity,
|
||||
NaturalEarthCountriesTempEntity,
|
||||
} from 'src/entities/natural-earth-countries.entity';
|
||||
import { DB, GeodataPlaces, NaturalearthCountries } from 'src/db';
|
||||
import { AssetEntity, withExif } from 'src/entities/asset.entity';
|
||||
import { NaturalEarthCountriesTempEntity } from 'src/entities/natural-earth-countries.entity';
|
||||
import { LogLevel, SystemMetadataKey } from 'src/enum';
|
||||
import { IConfigRepository } from 'src/interfaces/config.interface';
|
||||
import { ILoggerRepository } from 'src/interfaces/logger.interface';
|
||||
@ -23,21 +21,19 @@ import {
|
||||
ReverseGeocodeResult,
|
||||
} from 'src/interfaces/map.interface';
|
||||
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
|
||||
import { OptionalBetween } from 'src/utils/database';
|
||||
import { DataSource, In, IsNull, Not, Repository } from 'typeorm';
|
||||
import { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity.js';
|
||||
|
||||
interface MapDB extends DB {
|
||||
geodata_places_tmp: GeodataPlaces;
|
||||
naturalearth_countries_tmp: NaturalearthCountries;
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class MapRepository implements IMapRepository {
|
||||
constructor(
|
||||
@InjectRepository(AssetEntity) private assetRepository: Repository<AssetEntity>,
|
||||
@InjectRepository(GeodataPlacesEntity) private geodataPlacesRepository: Repository<GeodataPlacesEntity>,
|
||||
@InjectRepository(NaturalEarthCountriesEntity)
|
||||
private naturalEarthCountriesRepository: Repository<NaturalEarthCountriesEntity>,
|
||||
@InjectDataSource() private dataSource: DataSource,
|
||||
@Inject(IConfigRepository) private configRepository: IConfigRepository,
|
||||
@Inject(ISystemMetadataRepository) private metadataRepository: ISystemMetadataRepository,
|
||||
@Inject(ILoggerRepository) private logger: ILoggerRepository,
|
||||
@InjectKysely() private db: Kysely<MapDB>,
|
||||
) {
|
||||
this.logger.setContext(MapRepository.name);
|
||||
}
|
||||
@ -70,39 +66,34 @@ export class MapRepository implements IMapRepository {
|
||||
): Promise<MapMarker[]> {
|
||||
const { isArchived, isFavorite, fileCreatedAfter, fileCreatedBefore } = options;
|
||||
|
||||
const where = {
|
||||
isVisible: true,
|
||||
isArchived,
|
||||
exifInfo: {
|
||||
latitude: Not(IsNull()),
|
||||
longitude: Not(IsNull()),
|
||||
},
|
||||
isFavorite,
|
||||
fileCreatedAt: OptionalBetween(fileCreatedAfter, fileCreatedBefore),
|
||||
};
|
||||
const assets = (await this.db
|
||||
.selectFrom('assets')
|
||||
.$call(withExif)
|
||||
.select('id')
|
||||
.leftJoin('albums_assets_assets', (join) => join.onRef('assets.id', '=', 'albums_assets_assets.assetsId'))
|
||||
.where('isVisible', '=', true)
|
||||
.$if(isArchived !== undefined, (q) => q.where('isArchived', '=', isArchived!))
|
||||
.$if(isFavorite !== undefined, (q) => q.where('isFavorite', '=', isFavorite!))
|
||||
.$if(fileCreatedAfter !== undefined, (q) => q.where('fileCreatedAt', '>=', fileCreatedAfter!))
|
||||
.$if(fileCreatedBefore !== undefined, (q) => q.where('fileCreatedAt', '<=', fileCreatedBefore!))
|
||||
.where('deletedAt', 'is', null)
|
||||
.where('exif.latitude', 'is not', null)
|
||||
.where('exif.longitude', 'is not', null)
|
||||
.where((eb) => {
|
||||
const ors: Expression<SqlBool>[] = [];
|
||||
|
||||
const assets = await this.assetRepository.find({
|
||||
select: {
|
||||
id: true,
|
||||
exifInfo: {
|
||||
city: true,
|
||||
state: true,
|
||||
country: true,
|
||||
latitude: true,
|
||||
longitude: true,
|
||||
},
|
||||
},
|
||||
where: [
|
||||
{ ...where, ownerId: In([...ownerIds]) },
|
||||
{ ...where, albums: { id: In([...albumIds]) } },
|
||||
],
|
||||
relations: {
|
||||
exifInfo: true,
|
||||
},
|
||||
order: {
|
||||
fileCreatedAt: 'DESC',
|
||||
},
|
||||
});
|
||||
if (ownerIds.length > 0) {
|
||||
ors.push(eb('ownerId', 'in', ownerIds));
|
||||
}
|
||||
|
||||
if (albumIds.length > 0) {
|
||||
ors.push(eb('albums_assets_assets.albumsId', 'in', albumIds));
|
||||
}
|
||||
|
||||
return eb.or(ors);
|
||||
})
|
||||
.orderBy('fileCreatedAt', 'desc')
|
||||
.execute()) as any as AssetEntity[];
|
||||
|
||||
return assets.map((asset) => ({
|
||||
id: asset.id,
|
||||
@ -117,15 +108,19 @@ export class MapRepository implements IMapRepository {
|
||||
async reverseGeocode(point: GeoPoint): Promise<ReverseGeocodeResult> {
|
||||
this.logger.debug(`Request: ${point.latitude},${point.longitude}`);
|
||||
|
||||
const response = await this.geodataPlacesRepository
|
||||
.createQueryBuilder('geoplaces')
|
||||
const response = await this.db
|
||||
.selectFrom('geodata_places')
|
||||
.selectAll()
|
||||
.where(
|
||||
'earth_box(ll_to_earth_public(:latitude, :longitude), 25000) @> ll_to_earth_public(latitude, longitude)',
|
||||
point,
|
||||
sql`earth_box(ll_to_earth_public(${point.latitude}, ${point.longitude}), 25000)`,
|
||||
'@>',
|
||||
sql`ll_to_earth_public(latitude, longitude)`,
|
||||
)
|
||||
.orderBy(
|
||||
sql`(earth_distance(ll_to_earth_public(${point.latitude}, ${point.longitude}), ll_to_earth_public(latitude, longitude)))`,
|
||||
)
|
||||
.orderBy('earth_distance(ll_to_earth_public(:latitude, :longitude), ll_to_earth_public(latitude, longitude))')
|
||||
.limit(1)
|
||||
.getOne();
|
||||
.executeTakeFirst();
|
||||
|
||||
if (response) {
|
||||
if (this.logger.isLevelEnabled(LogLevel.VERBOSE)) {
|
||||
@ -143,11 +138,12 @@ export class MapRepository implements IMapRepository {
|
||||
`Response from database for reverse geocoding latitude: ${point.latitude}, longitude: ${point.longitude} was null`,
|
||||
);
|
||||
|
||||
const ne_response = await this.naturalEarthCountriesRepository
|
||||
.createQueryBuilder('naturalearth_countries')
|
||||
.where('coordinates @> point (:longitude, :latitude)', point)
|
||||
const ne_response = await this.db
|
||||
.selectFrom('naturalearth_countries')
|
||||
.selectAll()
|
||||
.where('coordinates', '@>', sql<string>`point(${point.longitude}, ${point.latitude})`)
|
||||
.limit(1)
|
||||
.getOne();
|
||||
.executeTakeFirst();
|
||||
|
||||
if (!ne_response) {
|
||||
this.logger.warn(
|
||||
@ -176,10 +172,11 @@ export class MapRepository implements IMapRepository {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.dataSource.query('DROP TABLE IF EXISTS naturalearth_countries_tmp');
|
||||
await this.dataSource.query(
|
||||
'CREATE TABLE naturalearth_countries_tmp (LIKE naturalearth_countries INCLUDING ALL EXCLUDING INDEXES)',
|
||||
await this.db.schema.dropTable('naturalearth_countries_tmp').ifExists().execute();
|
||||
await sql`CREATE TABLE naturalearth_countries_tmp (LIKE naturalearth_countries INCLUDING ALL EXCLUDING INDEXES)`.execute(
|
||||
this.db,
|
||||
);
|
||||
|
||||
const entities: Omit<NaturalEarthCountriesTempEntity, 'id'>[] = [];
|
||||
for (const feature of geoJSONData.features) {
|
||||
for (const entry of feature.geometry.coordinates) {
|
||||
@ -196,14 +193,14 @@ export class MapRepository implements IMapRepository {
|
||||
}
|
||||
}
|
||||
}
|
||||
await this.dataSource.manager.insert(NaturalEarthCountriesTempEntity, entities);
|
||||
await this.db.insertInto('naturalearth_countries_tmp').values(entities).execute();
|
||||
|
||||
await this.dataSource.query(`ALTER TABLE naturalearth_countries_tmp ADD PRIMARY KEY (id) WITH (FILLFACTOR = 100)`);
|
||||
await sql`ALTER TABLE naturalearth_countries_tmp ADD PRIMARY KEY (id) WITH (FILLFACTOR = 100)`.execute(this.db);
|
||||
|
||||
await this.dataSource.transaction(async (manager) => {
|
||||
await manager.query('ALTER TABLE naturalearth_countries RENAME TO naturalearth_countries_old');
|
||||
await manager.query('ALTER TABLE naturalearth_countries_tmp RENAME TO naturalearth_countries');
|
||||
await manager.query('DROP TABLE naturalearth_countries_old');
|
||||
await this.db.transaction().execute(async (manager) => {
|
||||
await manager.schema.alterTable('naturalearth_countries').renameTo('naturalearth_countries_old').execute();
|
||||
await manager.schema.alterTable('naturalearth_countries_tmp').renameTo('naturalearth_countries').execute();
|
||||
await manager.schema.dropTable('naturalearth_countries_old').execute();
|
||||
});
|
||||
}
|
||||
|
||||
@ -214,17 +211,15 @@ export class MapRepository implements IMapRepository {
|
||||
this.loadAdmin(resourcePaths.geodata.admin2),
|
||||
]);
|
||||
|
||||
await this.dataSource.query('DROP TABLE IF EXISTS geodata_places_tmp');
|
||||
await this.dataSource.query(
|
||||
'CREATE TABLE geodata_places_tmp (LIKE geodata_places INCLUDING ALL EXCLUDING INDEXES)',
|
||||
);
|
||||
await this.db.schema.dropTable('geodata_places_tmp').ifExists().execute();
|
||||
await sql`CREATE TABLE geodata_places_tmp (LIKE geodata_places INCLUDING ALL EXCLUDING INDEXES)`.execute(this.db);
|
||||
await this.loadCities500(admin1, admin2);
|
||||
await this.createGeodataIndices();
|
||||
|
||||
await this.dataSource.transaction(async (manager) => {
|
||||
await manager.query('ALTER TABLE geodata_places RENAME TO geodata_places_old');
|
||||
await manager.query('ALTER TABLE geodata_places_tmp RENAME TO geodata_places');
|
||||
await manager.query('DROP TABLE geodata_places_old');
|
||||
await this.db.transaction().execute(async (manager) => {
|
||||
await manager.schema.alterTable('geodata_places').renameTo('geodata_places_old').execute();
|
||||
await manager.schema.alterTable('geodata_places_tmp').renameTo('geodata_places').execute();
|
||||
await manager.schema.dropTable('geodata_places_old').execute();
|
||||
});
|
||||
}
|
||||
|
||||
@ -236,7 +231,7 @@ export class MapRepository implements IMapRepository {
|
||||
}
|
||||
|
||||
const input = createReadStream(cities500, { highWaterMark: 512 * 1024 * 1024 });
|
||||
let bufferGeodata: QueryDeepPartialEntity<GeodataPlacesTempEntity>[] = [];
|
||||
let bufferGeodata = [];
|
||||
const lineReader = readLine.createInterface({ input });
|
||||
let count = 0;
|
||||
|
||||
@ -257,19 +252,23 @@ export class MapRepository implements IMapRepository {
|
||||
admin1Code: lineSplit[10],
|
||||
admin2Code: lineSplit[11],
|
||||
modificationDate: lineSplit[18],
|
||||
admin1Name: admin1Map.get(`${lineSplit[8]}.${lineSplit[10]}`),
|
||||
admin2Name: admin2Map.get(`${lineSplit[8]}.${lineSplit[10]}.${lineSplit[11]}`),
|
||||
admin1Name: admin1Map.get(`${lineSplit[8]}.${lineSplit[10]}`) ?? null,
|
||||
admin2Name: admin2Map.get(`${lineSplit[8]}.${lineSplit[10]}.${lineSplit[11]}`) ?? null,
|
||||
};
|
||||
bufferGeodata.push(geoData);
|
||||
if (bufferGeodata.length >= 5000) {
|
||||
const curLength = bufferGeodata.length;
|
||||
futures.push(
|
||||
this.dataSource.manager.insert(GeodataPlacesTempEntity, bufferGeodata).then(() => {
|
||||
count += curLength;
|
||||
if (count % 10_000 === 0) {
|
||||
this.logger.log(`${count} geodata records imported`);
|
||||
}
|
||||
}),
|
||||
this.db
|
||||
.insertInto('geodata_places_tmp')
|
||||
.values(bufferGeodata)
|
||||
.execute()
|
||||
.then(() => {
|
||||
count += curLength;
|
||||
if (count % 10_000 === 0) {
|
||||
this.logger.log(`${count} geodata records imported`);
|
||||
}
|
||||
}),
|
||||
);
|
||||
bufferGeodata = [];
|
||||
// leave spare connection for other queries
|
||||
@ -280,7 +279,7 @@ export class MapRepository implements IMapRepository {
|
||||
}
|
||||
}
|
||||
|
||||
await this.dataSource.manager.insert(GeodataPlacesTempEntity, bufferGeodata);
|
||||
await this.db.insertInto('geodata_places_tmp').values(bufferGeodata).execute();
|
||||
}
|
||||
|
||||
private async loadAdmin(filePath: string) {
|
||||
@ -303,24 +302,28 @@ export class MapRepository implements IMapRepository {
|
||||
|
||||
private createGeodataIndices() {
|
||||
return Promise.all([
|
||||
this.dataSource.query(`ALTER TABLE geodata_places_tmp ADD PRIMARY KEY (id) WITH (FILLFACTOR = 100)`),
|
||||
this.dataSource.query(`
|
||||
CREATE INDEX IDX_geodata_gist_earthcoord_${randomUUID().replaceAll('-', '_')}
|
||||
ON geodata_places_tmp
|
||||
USING gist (ll_to_earth_public(latitude, longitude))
|
||||
WITH (fillfactor = 100)`),
|
||||
this.dataSource.query(`
|
||||
CREATE INDEX idx_geodata_places_name_${randomUUID().replaceAll('-', '_')}
|
||||
ON geodata_places_tmp
|
||||
USING gin (f_unaccent(name) gin_trgm_ops)`),
|
||||
this.dataSource.query(`
|
||||
CREATE INDEX idx_geodata_places_admin1_name_${randomUUID().replaceAll('-', '_')}
|
||||
ON geodata_places_tmp
|
||||
USING gin (f_unaccent("admin1Name") gin_trgm_ops)`),
|
||||
this.dataSource.query(`
|
||||
CREATE INDEX idx_geodata_places_admin2_name_${randomUUID().replaceAll('-', '_')}
|
||||
ON geodata_places_tmp
|
||||
USING gin (f_unaccent("admin2Name") gin_trgm_ops)`),
|
||||
sql`ALTER TABLE geodata_places_tmp ADD PRIMARY KEY (id) WITH (FILLFACTOR = 100)`.execute(this.db),
|
||||
sql`
|
||||
CREATE INDEX IDX_geodata_gist_earthcoord_${sql.raw(randomUUID().replaceAll('-', '_'))}
|
||||
ON geodata_places_tmp
|
||||
USING gist (ll_to_earth_public(latitude, longitude))
|
||||
WITH (fillfactor = 100)
|
||||
`.execute(this.db),
|
||||
this.db.schema
|
||||
.createIndex(`idx_geodata_places_country_code_${randomUUID().replaceAll('-', '_')}`)
|
||||
.on('geodata_places_tmp')
|
||||
.using('gin (f_unaccent(name) gin_trgm_ops)')
|
||||
.execute(),
|
||||
this.db.schema
|
||||
.createIndex(`idx_geodata_places_country_code_${randomUUID().replaceAll('-', '_')}`)
|
||||
.on('geodata_places_tmp')
|
||||
.using('gin (f_unaccent("admin1Name") gin_trgm_ops)')
|
||||
.execute(),
|
||||
this.db.schema
|
||||
.createIndex(`idx_geodata_places_admin2_name_${randomUUID().replaceAll('-', '_')}`)
|
||||
.on('geodata_places_tmp')
|
||||
.using('gin (f_unaccent("admin2Name") gin_trgm_ops)')
|
||||
.execute(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
@ -1,49 +1,55 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectDataSource, InjectRepository } from '@nestjs/typeorm';
|
||||
import { Insertable, Kysely, Updateable } from 'kysely';
|
||||
import { jsonArrayFrom } from 'kysely/helpers/postgres';
|
||||
import { InjectKysely } from 'nestjs-kysely';
|
||||
import { DB, Memories } from 'src/db';
|
||||
import { Chunked, ChunkedSet, DummyValue, GenerateSql } from 'src/decorators';
|
||||
import { MemoryEntity } from 'src/entities/memory.entity';
|
||||
import { IMemoryRepository } from 'src/interfaces/memory.interface';
|
||||
import { DataSource, In, Repository } from 'typeorm';
|
||||
|
||||
@Injectable()
|
||||
export class MemoryRepository implements IMemoryRepository {
|
||||
constructor(
|
||||
@InjectRepository(MemoryEntity) private repository: Repository<MemoryEntity>,
|
||||
@InjectDataSource() private dataSource: DataSource,
|
||||
) {}
|
||||
constructor(@InjectKysely() private db: Kysely<DB>) {}
|
||||
|
||||
@GenerateSql({ params: [DummyValue.UUID] })
|
||||
search(ownerId: string): Promise<MemoryEntity[]> {
|
||||
return this.repository.find({
|
||||
where: {
|
||||
ownerId,
|
||||
},
|
||||
order: {
|
||||
memoryAt: 'DESC',
|
||||
},
|
||||
});
|
||||
return this.db
|
||||
.selectFrom('memories')
|
||||
.selectAll()
|
||||
.where('ownerId', '=', ownerId)
|
||||
.orderBy('memoryAt', 'desc')
|
||||
.execute() as Promise<MemoryEntity[]>;
|
||||
}
|
||||
|
||||
@GenerateSql({ params: [DummyValue.UUID] })
|
||||
get(id: string): Promise<MemoryEntity | null> {
|
||||
return this.repository.findOne({
|
||||
where: {
|
||||
id,
|
||||
},
|
||||
relations: {
|
||||
assets: true,
|
||||
},
|
||||
return this.getByIdBuilder(id).executeTakeFirst() as unknown as Promise<MemoryEntity | null>;
|
||||
}
|
||||
|
||||
async create(memory: Insertable<Memories>, assetIds: Set<string>): Promise<MemoryEntity> {
|
||||
const id = await this.db.transaction().execute(async (tx) => {
|
||||
const { id } = await tx.insertInto('memories').values(memory).returning('id').executeTakeFirstOrThrow();
|
||||
|
||||
if (assetIds.size > 0) {
|
||||
const values = [...assetIds].map((assetId) => ({ memoriesId: id, assetsId: assetId }));
|
||||
await tx.insertInto('memories_assets_assets').values(values).execute();
|
||||
}
|
||||
|
||||
return id;
|
||||
});
|
||||
|
||||
return this.getByIdBuilder(id).executeTakeFirstOrThrow() as unknown as Promise<MemoryEntity>;
|
||||
}
|
||||
|
||||
create(memory: Partial<MemoryEntity>): Promise<MemoryEntity> {
|
||||
return this.save(memory);
|
||||
}
|
||||
|
||||
update(memory: Partial<MemoryEntity>): Promise<MemoryEntity> {
|
||||
return this.save(memory);
|
||||
@GenerateSql({ params: [DummyValue.UUID, { ownerId: DummyValue.UUID, isSaved: true }] })
|
||||
async update(id: string, memory: Updateable<Memories>): Promise<MemoryEntity> {
|
||||
await this.db.updateTable('memories').set(memory).where('id', '=', id).execute();
|
||||
return this.getByIdBuilder(id).executeTakeFirstOrThrow() as unknown as Promise<MemoryEntity>;
|
||||
}
|
||||
|
||||
@GenerateSql({ params: [DummyValue.UUID] })
|
||||
async delete(id: string): Promise<void> {
|
||||
await this.repository.delete({ id });
|
||||
await this.db.deleteFrom('memories').where('id', '=', id).execute();
|
||||
}
|
||||
|
||||
@GenerateSql({ params: [DummyValue.UUID, [DummyValue.UUID]] })
|
||||
@ -53,46 +59,49 @@ export class MemoryRepository implements IMemoryRepository {
|
||||
return new Set();
|
||||
}
|
||||
|
||||
const results = await this.dataSource
|
||||
.createQueryBuilder()
|
||||
.select('memories_assets.assetsId', 'assetId')
|
||||
.from('memories_assets_assets', 'memories_assets')
|
||||
.where('"memories_assets"."memoriesId" = :memoryId', { memoryId: id })
|
||||
.andWhere('memories_assets.assetsId IN (:...assetIds)', { assetIds })
|
||||
.getRawMany<{ assetId: string }>();
|
||||
const results = await this.db
|
||||
.selectFrom('memories_assets_assets')
|
||||
.select(['assetsId'])
|
||||
.where('memoriesId', '=', id)
|
||||
.where('assetsId', 'in', assetIds)
|
||||
.execute();
|
||||
|
||||
return new Set(results.map(({ assetId }) => assetId));
|
||||
return new Set(results.map(({ assetsId }) => assetsId));
|
||||
}
|
||||
|
||||
@GenerateSql({ params: [DummyValue.UUID, [DummyValue.UUID]] })
|
||||
async addAssetIds(id: string, assetIds: string[]): Promise<void> {
|
||||
await this.dataSource
|
||||
.createQueryBuilder()
|
||||
.insert()
|
||||
.into('memories_assets_assets', ['memoriesId', 'assetsId'])
|
||||
await this.db
|
||||
.insertInto('memories_assets_assets')
|
||||
.values(assetIds.map((assetId) => ({ memoriesId: id, assetsId: assetId })))
|
||||
.execute();
|
||||
}
|
||||
|
||||
@Chunked({ paramIndex: 1 })
|
||||
@GenerateSql({ params: [DummyValue.UUID, [DummyValue.UUID]] })
|
||||
async removeAssetIds(id: string, assetIds: string[]): Promise<void> {
|
||||
await this.dataSource
|
||||
.createQueryBuilder()
|
||||
.delete()
|
||||
.from('memories_assets_assets')
|
||||
.where({
|
||||
memoriesId: id,
|
||||
assetsId: In(assetIds),
|
||||
})
|
||||
await this.db
|
||||
.deleteFrom('memories_assets_assets')
|
||||
.where('memoriesId', '=', id)
|
||||
.where('assetsId', 'in', assetIds)
|
||||
.execute();
|
||||
}
|
||||
|
||||
private async save(memory: Partial<MemoryEntity>): Promise<MemoryEntity> {
|
||||
const { id } = await this.repository.save(memory);
|
||||
return this.repository.findOneOrFail({
|
||||
where: { id },
|
||||
relations: {
|
||||
assets: true,
|
||||
},
|
||||
});
|
||||
private getByIdBuilder(id: string) {
|
||||
return this.db
|
||||
.selectFrom('memories')
|
||||
.selectAll('memories')
|
||||
.select((eb) =>
|
||||
jsonArrayFrom(
|
||||
eb
|
||||
.selectFrom('assets')
|
||||
.selectAll('assets')
|
||||
.innerJoin('memories_assets_assets', 'assets.id', 'memories_assets_assets.assetsId')
|
||||
.whereRef('memories_assets_assets.memoriesId', '=', 'memories.id')
|
||||
.where('assets.deletedAt', 'is', null),
|
||||
).as('assets'),
|
||||
)
|
||||
.where('id', '=', id)
|
||||
.where('deletedAt', 'is', null);
|
||||
}
|
||||
}
|
||||
|
@ -1,37 +1,93 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { ExpressionBuilder, Insertable, JoinBuilder, Kysely, Updateable } from 'kysely';
|
||||
import { jsonObjectFrom } from 'kysely/helpers/postgres';
|
||||
import { InjectKysely } from 'nestjs-kysely';
|
||||
import { DB, Partners, Users } from 'src/db';
|
||||
import { DummyValue, GenerateSql } from 'src/decorators';
|
||||
import { PartnerEntity } from 'src/entities/partner.entity';
|
||||
import { IPartnerRepository, PartnerIds } from 'src/interfaces/partner.interface';
|
||||
import { DeepPartial, Repository } from 'typeorm';
|
||||
|
||||
const columns = ['id', 'name', 'email', 'profileImagePath', 'profileChangedAt'] as const;
|
||||
|
||||
const onSharedBy = (join: JoinBuilder<DB & { sharedBy: Users }, 'partners' | 'sharedBy'>) =>
|
||||
join.onRef('partners.sharedById', '=', 'sharedBy.id').on('sharedBy.deletedAt', 'is', null);
|
||||
|
||||
const onSharedWith = (join: JoinBuilder<DB & { sharedWith: Users }, 'partners' | 'sharedWith'>) =>
|
||||
join.onRef('partners.sharedWithId', '=', 'sharedWith.id').on('sharedWith.deletedAt', 'is', null);
|
||||
|
||||
const withSharedBy = (eb: ExpressionBuilder<DB, 'partners'>) => {
|
||||
return jsonObjectFrom(
|
||||
eb.selectFrom('users as sharedBy').select(columns).whereRef('sharedBy.id', '=', 'partners.sharedById'),
|
||||
).as('sharedBy');
|
||||
};
|
||||
|
||||
const withSharedWith = (eb: ExpressionBuilder<DB, 'partners'>) => {
|
||||
return jsonObjectFrom(
|
||||
eb.selectFrom('users as sharedWith').select(columns).whereRef('sharedWith.id', '=', 'partners.sharedWithId'),
|
||||
).as('sharedWith');
|
||||
};
|
||||
|
||||
@Injectable()
|
||||
export class PartnerRepository implements IPartnerRepository {
|
||||
constructor(@InjectRepository(PartnerEntity) private repository: Repository<PartnerEntity>) {}
|
||||
constructor(@InjectKysely() private db: Kysely<DB>) {}
|
||||
|
||||
@GenerateSql({ params: [DummyValue.UUID] })
|
||||
getAll(userId: string): Promise<PartnerEntity[]> {
|
||||
return this.repository.find({ where: [{ sharedWithId: userId }, { sharedById: userId }] });
|
||||
return this.db
|
||||
.selectFrom('partners')
|
||||
.innerJoin('users as sharedBy', onSharedBy)
|
||||
.innerJoin('users as sharedWith', onSharedWith)
|
||||
.selectAll('partners')
|
||||
.select(withSharedBy)
|
||||
.select(withSharedWith)
|
||||
.where((eb) => eb.or([eb('sharedWithId', '=', userId), eb('sharedById', '=', userId)]))
|
||||
.execute() as Promise<PartnerEntity[]>;
|
||||
}
|
||||
|
||||
get({ sharedWithId, sharedById }: PartnerIds): Promise<PartnerEntity | null> {
|
||||
return this.repository.findOne({ where: { sharedById, sharedWithId } });
|
||||
@GenerateSql({ params: [{ sharedWithId: DummyValue.UUID, sharedById: DummyValue.UUID }] })
|
||||
get({ sharedWithId, sharedById }: PartnerIds): Promise<PartnerEntity | undefined> {
|
||||
return this.db
|
||||
.selectFrom('partners')
|
||||
.innerJoin('users as sharedBy', onSharedBy)
|
||||
.innerJoin('users as sharedWith', onSharedWith)
|
||||
.selectAll('partners')
|
||||
.select(withSharedBy)
|
||||
.select(withSharedWith)
|
||||
.where('sharedWithId', '=', sharedWithId)
|
||||
.where('sharedById', '=', sharedById)
|
||||
.executeTakeFirst() as unknown as Promise<PartnerEntity | undefined>;
|
||||
}
|
||||
|
||||
create({ sharedById, sharedWithId }: PartnerIds): Promise<PartnerEntity> {
|
||||
return this.save({ sharedBy: { id: sharedById }, sharedWith: { id: sharedWithId } });
|
||||
@GenerateSql({ params: [{ sharedWithId: DummyValue.UUID, sharedById: DummyValue.UUID }] })
|
||||
create(values: Insertable<Partners>): Promise<PartnerEntity> {
|
||||
return this.db
|
||||
.insertInto('partners')
|
||||
.values(values)
|
||||
.returningAll()
|
||||
.returning(withSharedBy)
|
||||
.returning(withSharedWith)
|
||||
.executeTakeFirstOrThrow() as unknown as Promise<PartnerEntity>;
|
||||
}
|
||||
|
||||
async remove(entity: PartnerEntity): Promise<void> {
|
||||
await this.repository.remove(entity);
|
||||
@GenerateSql({ params: [{ sharedWithId: DummyValue.UUID, sharedById: DummyValue.UUID }, { inTimeline: true }] })
|
||||
update({ sharedWithId, sharedById }: PartnerIds, values: Updateable<Partners>): Promise<PartnerEntity> {
|
||||
return this.db
|
||||
.updateTable('partners')
|
||||
.set(values)
|
||||
.where('sharedWithId', '=', sharedWithId)
|
||||
.where('sharedById', '=', sharedById)
|
||||
.returningAll()
|
||||
.returning(withSharedBy)
|
||||
.returning(withSharedWith)
|
||||
.executeTakeFirstOrThrow() as unknown as Promise<PartnerEntity>;
|
||||
}
|
||||
|
||||
update(entity: Partial<PartnerEntity>): Promise<PartnerEntity> {
|
||||
return this.save(entity);
|
||||
}
|
||||
|
||||
private async save(entity: DeepPartial<PartnerEntity>): Promise<PartnerEntity> {
|
||||
await this.repository.save(entity);
|
||||
return this.repository.findOneOrFail({
|
||||
where: { sharedById: entity.sharedById, sharedWithId: entity.sharedWithId },
|
||||
});
|
||||
@GenerateSql({ params: [{ sharedWithId: DummyValue.UUID, sharedById: DummyValue.UUID }] })
|
||||
async remove({ sharedWithId, sharedById }: PartnerIds): Promise<void> {
|
||||
await this.db
|
||||
.deleteFrom('partners')
|
||||
.where('sharedWithId', '=', sharedWithId)
|
||||
.where('sharedById', '=', sharedById)
|
||||
.execute();
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectDataSource, InjectRepository } from '@nestjs/typeorm';
|
||||
import { DummyValue, GenerateSql } from 'src/decorators';
|
||||
import { AssetEntity } from 'src/entities/asset.entity';
|
||||
import { StackEntity } from 'src/entities/stack.entity';
|
||||
import { IStackRepository, StackSearch } from 'src/interfaces/stack.interface';
|
||||
@ -12,6 +13,7 @@ export class StackRepository implements IStackRepository {
|
||||
@InjectRepository(StackEntity) private repository: Repository<StackEntity>,
|
||||
) {}
|
||||
|
||||
@GenerateSql({ params: [{ ownerId: DummyValue.UUID }] })
|
||||
search(query: StackSearch): Promise<StackEntity[]> {
|
||||
return this.repository.find({
|
||||
where: {
|
||||
@ -80,6 +82,7 @@ export class StackRepository implements IStackRepository {
|
||||
});
|
||||
}
|
||||
|
||||
@GenerateSql({ params: [DummyValue.UUID] })
|
||||
async delete(id: string): Promise<void> {
|
||||
const stack = await this.getById(id);
|
||||
if (!stack) {
|
||||
@ -119,6 +122,7 @@ export class StackRepository implements IStackRepository {
|
||||
return this.save(entity);
|
||||
}
|
||||
|
||||
@GenerateSql({ params: [DummyValue.UUID] })
|
||||
async getById(id: string): Promise<StackEntity | null> {
|
||||
return this.repository.findOne({
|
||||
where: {
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { BadRequestException } from '@nestjs/common';
|
||||
import { ReactionType } from 'src/dtos/activity.dto';
|
||||
import { IActivityRepository } from 'src/interfaces/activity.interface';
|
||||
import { ActivityService } from 'src/services/activity.service';
|
||||
import { IActivityRepository } from 'src/types';
|
||||
import { activityStub } from 'test/fixtures/activity.stub';
|
||||
import { authStub } from 'test/fixtures/auth.stub';
|
||||
import { IAccessRepositoryMock } from 'test/repositories/access.repository.mock';
|
||||
|
@ -5,15 +5,15 @@ import {
|
||||
ActivityResponseDto,
|
||||
ActivitySearchDto,
|
||||
ActivityStatisticsResponseDto,
|
||||
mapActivity,
|
||||
MaybeDuplicate,
|
||||
ReactionLevel,
|
||||
ReactionType,
|
||||
mapActivity,
|
||||
} from 'src/dtos/activity.dto';
|
||||
import { AuthDto } from 'src/dtos/auth.dto';
|
||||
import { ActivityEntity } from 'src/entities/activity.entity';
|
||||
import { Permission } from 'src/enum';
|
||||
import { BaseService } from 'src/services/base.service';
|
||||
import { ActivityItem } from 'src/types';
|
||||
|
||||
@Injectable()
|
||||
export class ActivityService extends BaseService {
|
||||
@ -43,7 +43,7 @@ export class ActivityService extends BaseService {
|
||||
albumId: dto.albumId,
|
||||
};
|
||||
|
||||
let activity: ActivityEntity | null = null;
|
||||
let activity: ActivityItem | undefined;
|
||||
let duplicate = false;
|
||||
|
||||
if (dto.type === ReactionType.LIKE) {
|
||||
|
@ -323,18 +323,16 @@ describe(AlbumService.name, () => {
|
||||
albumMock.update.mockResolvedValue(albumStub.sharedWithAdmin);
|
||||
userMock.get.mockResolvedValue(userStub.user2);
|
||||
albumUserMock.create.mockResolvedValue({
|
||||
userId: userStub.user2.id,
|
||||
user: userStub.user2,
|
||||
albumId: albumStub.sharedWithAdmin.id,
|
||||
album: albumStub.sharedWithAdmin,
|
||||
usersId: userStub.user2.id,
|
||||
albumsId: albumStub.sharedWithAdmin.id,
|
||||
role: AlbumUserRole.EDITOR,
|
||||
});
|
||||
await sut.addUsers(authStub.user1, albumStub.sharedWithAdmin.id, {
|
||||
albumUsers: [{ userId: authStub.user2.user.id }],
|
||||
});
|
||||
expect(albumUserMock.create).toHaveBeenCalledWith({
|
||||
userId: authStub.user2.user.id,
|
||||
albumId: albumStub.sharedWithAdmin.id,
|
||||
usersId: authStub.user2.user.id,
|
||||
albumsId: albumStub.sharedWithAdmin.id,
|
||||
});
|
||||
expect(eventMock.emit).toHaveBeenCalledWith('album.invite', {
|
||||
id: albumStub.sharedWithAdmin.id,
|
||||
@ -361,8 +359,8 @@ describe(AlbumService.name, () => {
|
||||
|
||||
expect(albumUserMock.delete).toHaveBeenCalledTimes(1);
|
||||
expect(albumUserMock.delete).toHaveBeenCalledWith({
|
||||
albumId: albumStub.sharedWithUser.id,
|
||||
userId: userStub.user1.id,
|
||||
albumsId: albumStub.sharedWithUser.id,
|
||||
usersId: userStub.user1.id,
|
||||
});
|
||||
expect(albumMock.getById).toHaveBeenCalledWith(albumStub.sharedWithUser.id, { withAssets: false });
|
||||
});
|
||||
@ -388,8 +386,8 @@ describe(AlbumService.name, () => {
|
||||
|
||||
expect(albumUserMock.delete).toHaveBeenCalledTimes(1);
|
||||
expect(albumUserMock.delete).toHaveBeenCalledWith({
|
||||
albumId: albumStub.sharedWithUser.id,
|
||||
userId: authStub.user1.user.id,
|
||||
albumsId: albumStub.sharedWithUser.id,
|
||||
usersId: authStub.user1.user.id,
|
||||
});
|
||||
});
|
||||
|
||||
@ -400,8 +398,8 @@ describe(AlbumService.name, () => {
|
||||
|
||||
expect(albumUserMock.delete).toHaveBeenCalledTimes(1);
|
||||
expect(albumUserMock.delete).toHaveBeenCalledWith({
|
||||
albumId: albumStub.sharedWithUser.id,
|
||||
userId: authStub.user1.user.id,
|
||||
albumsId: albumStub.sharedWithUser.id,
|
||||
usersId: authStub.user1.user.id,
|
||||
});
|
||||
});
|
||||
|
||||
@ -433,7 +431,7 @@ describe(AlbumService.name, () => {
|
||||
role: AlbumUserRole.EDITOR,
|
||||
});
|
||||
expect(albumUserMock.update).toHaveBeenCalledWith(
|
||||
{ albumId: albumStub.sharedWithAdmin.id, userId: userStub.admin.id },
|
||||
{ albumsId: albumStub.sharedWithAdmin.id, usersId: userStub.admin.id },
|
||||
{ role: AlbumUserRole.EDITOR },
|
||||
);
|
||||
});
|
||||
|
@ -229,7 +229,7 @@ export class AlbumService extends BaseService {
|
||||
throw new BadRequestException('User not found');
|
||||
}
|
||||
|
||||
await this.albumUserRepository.create({ userId, albumId: id, role });
|
||||
await this.albumUserRepository.create({ usersId: userId, albumsId: id, role });
|
||||
await this.eventRepository.emit('album.invite', { id, userId });
|
||||
}
|
||||
|
||||
@ -257,12 +257,12 @@ export class AlbumService extends BaseService {
|
||||
await this.requireAccess({ auth, permission: Permission.ALBUM_SHARE, ids: [id] });
|
||||
}
|
||||
|
||||
await this.albumUserRepository.delete({ albumId: id, userId });
|
||||
await this.albumUserRepository.delete({ albumsId: id, usersId: userId });
|
||||
}
|
||||
|
||||
async updateUser(auth: AuthDto, id: string, userId: string, dto: Partial<AlbumUserEntity>): Promise<void> {
|
||||
await this.requireAccess({ auth, permission: Permission.ALBUM_SHARE, ids: [id] });
|
||||
await this.albumUserRepository.update({ albumId: id, userId }, { role: dto.role });
|
||||
await this.albumUserRepository.update({ albumsId: id, usersId: userId }, { role: dto.role });
|
||||
}
|
||||
|
||||
private async findOrFail(id: string, options: AlbumInfoOptions) {
|
||||
|
@ -7,7 +7,6 @@ import { StorageCore } from 'src/cores/storage.core';
|
||||
import { Users } from 'src/db';
|
||||
import { UserEntity } from 'src/entities/user.entity';
|
||||
import { IAccessRepository } from 'src/interfaces/access.interface';
|
||||
import { IActivityRepository } from 'src/interfaces/activity.interface';
|
||||
import { IAlbumUserRepository } from 'src/interfaces/album-user.interface';
|
||||
import { IAlbumRepository } from 'src/interfaces/album.interface';
|
||||
import { IKeyRepository } from 'src/interfaces/api-key.interface';
|
||||
@ -45,6 +44,7 @@ import { ITrashRepository } from 'src/interfaces/trash.interface';
|
||||
import { IUserRepository } from 'src/interfaces/user.interface';
|
||||
import { IVersionHistoryRepository } from 'src/interfaces/version-history.interface';
|
||||
import { IViewRepository } from 'src/interfaces/view.interface';
|
||||
import { ActivityRepository } from 'src/repositories/activity.repository';
|
||||
import { AccessRequest, checkAccess, requireAccess } from 'src/utils/access';
|
||||
import { getConfig, updateConfig } from 'src/utils/config';
|
||||
|
||||
@ -54,7 +54,7 @@ export class BaseService {
|
||||
constructor(
|
||||
@Inject(ILoggerRepository) protected logger: ILoggerRepository,
|
||||
@Inject(IAccessRepository) protected accessRepository: IAccessRepository,
|
||||
@Inject(IActivityRepository) protected activityRepository: IActivityRepository,
|
||||
protected activityRepository: ActivityRepository,
|
||||
@Inject(IAuditRepository) protected auditRepository: IAuditRepository,
|
||||
@Inject(IAlbumRepository) protected albumRepository: IAlbumRepository,
|
||||
@Inject(IAlbumUserRepository) protected albumUserRepository: IAlbumUserRepository,
|
||||
|
@ -87,7 +87,7 @@ describe(LibraryService.name, () => {
|
||||
Promise.resolve(
|
||||
[libraryStub.externalLibraryWithImportPaths1, libraryStub.externalLibraryWithImportPaths2].find(
|
||||
(library) => library.id === id,
|
||||
) || null,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
@ -190,8 +190,6 @@ describe(LibraryService.name, () => {
|
||||
});
|
||||
|
||||
it("should fail when library can't be found", async () => {
|
||||
libraryMock.get.mockResolvedValue(null);
|
||||
|
||||
await expect(sut.handleQueueSyncFiles({ id: libraryStub.externalLibrary1.id })).resolves.toBe(JobStatus.SKIPPED);
|
||||
});
|
||||
|
||||
@ -242,8 +240,6 @@ describe(LibraryService.name, () => {
|
||||
});
|
||||
|
||||
it("should fail when library can't be found", async () => {
|
||||
libraryMock.get.mockResolvedValue(null);
|
||||
|
||||
await expect(sut.handleQueueSyncAssets({ id: libraryStub.externalLibrary1.id })).resolves.toBe(JobStatus.SKIPPED);
|
||||
});
|
||||
});
|
||||
@ -630,7 +626,6 @@ describe(LibraryService.name, () => {
|
||||
});
|
||||
|
||||
it('should throw an error when a library is not found', async () => {
|
||||
libraryMock.get.mockResolvedValue(null);
|
||||
await expect(sut.get(libraryStub.externalLibrary1.id)).rejects.toBeInstanceOf(BadRequestException);
|
||||
expect(libraryMock.get).toHaveBeenCalledWith(libraryStub.externalLibrary1.id);
|
||||
});
|
||||
@ -825,7 +820,10 @@ describe(LibraryService.name, () => {
|
||||
await expect(sut.update('library-id', { importPaths: [`${cwd}/foo/bar`] })).resolves.toEqual(
|
||||
mapLibrary(libraryStub.externalLibrary1),
|
||||
);
|
||||
expect(libraryMock.update).toHaveBeenCalledWith(expect.objectContaining({ id: 'library-id' }));
|
||||
expect(libraryMock.update).toHaveBeenCalledWith(
|
||||
'library-id',
|
||||
expect.objectContaining({ importPaths: [`${cwd}/foo/bar`] }),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@ -1015,7 +1013,7 @@ describe(LibraryService.name, () => {
|
||||
Promise.resolve(
|
||||
[libraryStub.externalLibraryWithImportPaths1, libraryStub.externalLibraryWithImportPaths2].find(
|
||||
(library) => library.id === id,
|
||||
) || null,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
|
@ -311,7 +311,7 @@ export class LibraryService extends BaseService {
|
||||
}
|
||||
}
|
||||
|
||||
const library = await this.libraryRepository.update({ id, ...dto });
|
||||
const library = await this.libraryRepository.update(id, dto);
|
||||
return mapLibrary(library);
|
||||
}
|
||||
|
||||
@ -571,7 +571,7 @@ export class LibraryService extends BaseService {
|
||||
this.logger.debug(`No non-excluded assets found in any import path for library ${library.id}`);
|
||||
}
|
||||
|
||||
await this.libraryRepository.update({ id: job.id, refreshedAt: new Date() });
|
||||
await this.libraryRepository.update(job.id, { refreshedAt: new Date() });
|
||||
|
||||
return JobStatus.SUCCESS;
|
||||
}
|
||||
|
@ -69,7 +69,17 @@ describe(MemoryService.name, () => {
|
||||
memoryAt: new Date(2024),
|
||||
}),
|
||||
).resolves.toMatchObject({ assets: [] });
|
||||
expect(memoryMock.create).toHaveBeenCalledWith(expect.objectContaining({ assets: [] }));
|
||||
expect(memoryMock.create).toHaveBeenCalledWith(
|
||||
{
|
||||
ownerId: 'admin_id',
|
||||
memoryAt: expect.any(Date),
|
||||
type: MemoryType.ON_THIS_DAY,
|
||||
isSaved: undefined,
|
||||
sendAt: undefined,
|
||||
data: { year: 2024 },
|
||||
},
|
||||
new Set(),
|
||||
);
|
||||
});
|
||||
|
||||
it('should create a memory', async () => {
|
||||
@ -80,14 +90,14 @@ describe(MemoryService.name, () => {
|
||||
type: MemoryType.ON_THIS_DAY,
|
||||
data: { year: 2024 },
|
||||
assetIds: ['asset1'],
|
||||
memoryAt: new Date(2024),
|
||||
memoryAt: new Date(2024, 0, 1),
|
||||
}),
|
||||
).resolves.toBeDefined();
|
||||
expect(memoryMock.create).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
ownerId: userStub.admin.id,
|
||||
assets: [{ id: 'asset1' }],
|
||||
}),
|
||||
new Set(['asset1']),
|
||||
);
|
||||
});
|
||||
|
||||
@ -115,12 +125,7 @@ describe(MemoryService.name, () => {
|
||||
accessMock.memory.checkOwnerAccess.mockResolvedValue(new Set(['memory1']));
|
||||
memoryMock.update.mockResolvedValue(memoryStub.memory1);
|
||||
await expect(sut.update(authStub.admin, 'memory1', { isSaved: true })).resolves.toBeDefined();
|
||||
expect(memoryMock.update).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
id: 'memory1',
|
||||
isSaved: true,
|
||||
}),
|
||||
);
|
||||
expect(memoryMock.update).toHaveBeenCalledWith('memory1', expect.objectContaining({ isSaved: true }));
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -2,7 +2,6 @@ import { BadRequestException, Injectable } from '@nestjs/common';
|
||||
import { BulkIdResponseDto, BulkIdsDto } from 'src/dtos/asset-ids.response.dto';
|
||||
import { AuthDto } from 'src/dtos/auth.dto';
|
||||
import { MemoryCreateDto, MemoryResponseDto, MemoryUpdateDto, mapMemory } from 'src/dtos/memory.dto';
|
||||
import { AssetEntity } from 'src/entities/asset.entity';
|
||||
import { Permission } from 'src/enum';
|
||||
import { BaseService } from 'src/services/base.service';
|
||||
import { addAssets, removeAssets } from 'src/utils/asset.util';
|
||||
@ -29,15 +28,17 @@ export class MemoryService extends BaseService {
|
||||
permission: Permission.ASSET_SHARE,
|
||||
ids: assetIds,
|
||||
});
|
||||
const memory = await this.memoryRepository.create({
|
||||
ownerId: auth.user.id,
|
||||
type: dto.type,
|
||||
data: dto.data,
|
||||
isSaved: dto.isSaved,
|
||||
memoryAt: dto.memoryAt,
|
||||
seenAt: dto.seenAt,
|
||||
assets: [...allowedAssetIds].map((id) => ({ id }) as AssetEntity),
|
||||
});
|
||||
const memory = await this.memoryRepository.create(
|
||||
{
|
||||
ownerId: auth.user.id,
|
||||
type: dto.type,
|
||||
data: dto.data,
|
||||
isSaved: dto.isSaved,
|
||||
memoryAt: dto.memoryAt,
|
||||
seenAt: dto.seenAt,
|
||||
},
|
||||
allowedAssetIds,
|
||||
);
|
||||
|
||||
return mapMemory(memory);
|
||||
}
|
||||
@ -45,8 +46,7 @@ export class MemoryService extends BaseService {
|
||||
async update(auth: AuthDto, id: string, dto: MemoryUpdateDto): Promise<MemoryResponseDto> {
|
||||
await this.requireAccess({ auth, permission: Permission.MEMORY_UPDATE, ids: [id] });
|
||||
|
||||
const memory = await this.memoryRepository.update({
|
||||
id,
|
||||
const memory = await this.memoryRepository.update(id, {
|
||||
isSaved: dto.isSaved,
|
||||
memoryAt: dto.memoryAt,
|
||||
seenAt: dto.seenAt,
|
||||
@ -68,7 +68,7 @@ export class MemoryService extends BaseService {
|
||||
|
||||
const hasSuccess = results.find(({ success }) => success);
|
||||
if (hasSuccess) {
|
||||
await this.memoryRepository.update({ id, updatedAt: new Date() });
|
||||
await this.memoryRepository.update(id, { updatedAt: new Date() });
|
||||
}
|
||||
|
||||
return results;
|
||||
@ -86,7 +86,7 @@ export class MemoryService extends BaseService {
|
||||
|
||||
const hasSuccess = results.find(({ success }) => success);
|
||||
if (hasSuccess) {
|
||||
await this.memoryRepository.update({ id, updatedAt: new Date() });
|
||||
await this.memoryRepository.update(id, { id, updatedAt: new Date() });
|
||||
}
|
||||
|
||||
return results;
|
||||
|
@ -37,7 +37,7 @@ describe(PartnerService.name, () => {
|
||||
|
||||
describe('create', () => {
|
||||
it('should create a new partner', async () => {
|
||||
partnerMock.get.mockResolvedValue(null);
|
||||
partnerMock.get.mockResolvedValue(void 0);
|
||||
partnerMock.create.mockResolvedValue(partnerStub.adminToUser1);
|
||||
|
||||
await expect(sut.create(authStub.admin, authStub.user1.user.id)).resolves.toBeDefined();
|
||||
@ -67,7 +67,7 @@ describe(PartnerService.name, () => {
|
||||
});
|
||||
|
||||
it('should throw an error when the partner does not exist', async () => {
|
||||
partnerMock.get.mockResolvedValue(null);
|
||||
partnerMock.get.mockResolvedValue(void 0);
|
||||
|
||||
await expect(sut.remove(authStub.admin, authStub.user1.user.id)).rejects.toBeInstanceOf(BadRequestException);
|
||||
|
||||
@ -87,11 +87,10 @@ describe(PartnerService.name, () => {
|
||||
partnerMock.update.mockResolvedValue(partnerStub.adminToUser1);
|
||||
|
||||
await expect(sut.update(authStub.admin, 'shared-by-id', { inTimeline: true })).resolves.toBeDefined();
|
||||
expect(partnerMock.update).toHaveBeenCalledWith({
|
||||
sharedById: 'shared-by-id',
|
||||
sharedWithId: authStub.admin.user.id,
|
||||
inTimeline: true,
|
||||
});
|
||||
expect(partnerMock.update).toHaveBeenCalledWith(
|
||||
{ sharedById: 'shared-by-id', sharedWithId: authStub.admin.user.id },
|
||||
{ inTimeline: true },
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -43,7 +43,7 @@ export class PartnerService extends BaseService {
|
||||
await this.requireAccess({ auth, permission: Permission.PARTNER_UPDATE, ids: [sharedById] });
|
||||
const partnerId: PartnerIds = { sharedById, sharedWithId: auth.user.id };
|
||||
|
||||
const entity = await this.partnerRepository.update({ ...partnerId, inTimeline: dto.inTimeline });
|
||||
const entity = await this.partnerRepository.update(partnerId, { inTimeline: dto.inTimeline });
|
||||
return this.mapPartner(entity, PartnerDirection.SharedWith);
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { UserEntity } from 'src/entities/user.entity';
|
||||
import { Permission } from 'src/enum';
|
||||
import { ActivityRepository } from 'src/repositories/activity.repository';
|
||||
|
||||
export type AuthApiKey = {
|
||||
id: string;
|
||||
@ -7,3 +8,11 @@ export type AuthApiKey = {
|
||||
user: UserEntity;
|
||||
permissions: Permission[];
|
||||
};
|
||||
|
||||
export type RepositoryInterface<T extends object> = Pick<T, keyof T>;
|
||||
|
||||
export type IActivityRepository = RepositoryInterface<ActivityRepository>;
|
||||
|
||||
export type ActivityItem =
|
||||
| Awaited<ReturnType<IActivityRepository['create']>>
|
||||
| Awaited<ReturnType<IActivityRepository['search']>>[0];
|
||||
|
32
server/test/fixtures/activity.stub.ts
vendored
32
server/test/fixtures/activity.stub.ts
vendored
@ -1,33 +1,39 @@
|
||||
import { ActivityEntity } from 'src/entities/activity.entity';
|
||||
import { ActivityItem } from 'src/types';
|
||||
import { albumStub } from 'test/fixtures/album.stub';
|
||||
import { assetStub } from 'test/fixtures/asset.stub';
|
||||
import { authStub } from 'test/fixtures/auth.stub';
|
||||
import { userStub } from 'test/fixtures/user.stub';
|
||||
|
||||
export const activityStub = {
|
||||
oneComment: Object.freeze<ActivityEntity>({
|
||||
oneComment: Object.freeze<ActivityItem>({
|
||||
id: 'activity-1',
|
||||
comment: 'comment',
|
||||
isLiked: false,
|
||||
userId: authStub.admin.user.id,
|
||||
user: userStub.admin,
|
||||
userId: 'admin_id',
|
||||
user: {
|
||||
id: 'admin_id',
|
||||
name: 'admin',
|
||||
email: 'admin@test.com',
|
||||
profileImagePath: '',
|
||||
profileChangedAt: new Date('2021-01-01'),
|
||||
},
|
||||
assetId: assetStub.image.id,
|
||||
asset: assetStub.image,
|
||||
albumId: albumStub.oneAsset.id,
|
||||
album: albumStub.oneAsset,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
}),
|
||||
liked: Object.freeze<ActivityEntity>({
|
||||
liked: Object.freeze<ActivityItem>({
|
||||
id: 'activity-2',
|
||||
comment: null,
|
||||
isLiked: true,
|
||||
userId: authStub.admin.user.id,
|
||||
user: userStub.admin,
|
||||
userId: 'admin_id',
|
||||
user: {
|
||||
id: 'admin_id',
|
||||
name: 'admin',
|
||||
email: 'admin@test.com',
|
||||
profileImagePath: '',
|
||||
profileChangedAt: new Date('2021-01-01'),
|
||||
},
|
||||
assetId: assetStub.image.id,
|
||||
asset: assetStub.image,
|
||||
albumId: albumStub.oneAsset.id,
|
||||
album: albumStub.oneAsset,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
}),
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { IActivityRepository } from 'src/interfaces/activity.interface';
|
||||
import { IActivityRepository } from 'src/types';
|
||||
import { Mocked, vitest } from 'vitest';
|
||||
|
||||
export const newActivityRepositoryMock = (): Mocked<IActivityRepository> => {
|
||||
|
@ -3,7 +3,9 @@ import { Writable } from 'node:stream';
|
||||
import { PNG } from 'pngjs';
|
||||
import { ImmichWorker } from 'src/enum';
|
||||
import { IMetadataRepository } from 'src/interfaces/metadata.interface';
|
||||
import { ActivityRepository } from 'src/repositories/activity.repository';
|
||||
import { BaseService } from 'src/services/base.service';
|
||||
import { IActivityRepository } from 'src/types';
|
||||
import { newAccessRepositoryMock } from 'test/repositories/access.repository.mock';
|
||||
import { newActivityRepositoryMock } from 'test/repositories/activity.repository.mock';
|
||||
import { newAlbumUserRepositoryMock } from 'test/repositories/album-user.repository.mock';
|
||||
@ -104,7 +106,7 @@ export const newTestService = <T extends BaseService>(
|
||||
const sut = new Service(
|
||||
loggerMock,
|
||||
accessMock,
|
||||
activityMock,
|
||||
activityMock as IActivityRepository as ActivityRepository,
|
||||
auditMock,
|
||||
albumMock,
|
||||
albumUserMock,
|
||||
|
@ -1,4 +1,4 @@
|
||||
FROM node:22.12.0-alpine3.20@sha256:96cc8323e25c8cc6ddcb8b965e135cfd57846e8003ec0d7bcec16c5fd5f6d39f
|
||||
FROM node:22.13.0-alpine3.20@sha256:db8dcb90326a0116375414e9a7c068a6b87a4422b7da37b5c6cd026f7c7835d3
|
||||
|
||||
RUN apk add --no-cache tini
|
||||
USER node
|
||||
|
@ -81,6 +81,7 @@ export default [
|
||||
'unicorn/prevent-abbreviations': 'off',
|
||||
'unicorn/no-nested-ternary': 'off',
|
||||
'unicorn/consistent-function-scoping': 'off',
|
||||
'unicorn/filename-case': 'off',
|
||||
'unicorn/prefer-top-level-await': 'off',
|
||||
'unicorn/import-style': 'off',
|
||||
'svelte/button-has-type': 'error',
|
||||
|
317
web/package-lock.json
generated
317
web/package-lock.json
generated
@ -11,6 +11,7 @@
|
||||
"dependencies": {
|
||||
"@formatjs/icu-messageformat-parser": "^2.9.8",
|
||||
"@immich/sdk": "file:../open-api/typescript-sdk",
|
||||
"@immich/ui": "^0.13.0",
|
||||
"@mapbox/mapbox-gl-rtl-text": "0.2.3",
|
||||
"@mdi/js": "^7.4.47",
|
||||
"@photo-sphere-viewer/core": "^5.11.5",
|
||||
@ -104,7 +105,6 @@
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz",
|
||||
"integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
@ -793,6 +793,31 @@
|
||||
"npm": ">=9.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@floating-ui/core": {
|
||||
"version": "1.6.9",
|
||||
"resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.9.tgz",
|
||||
"integrity": "sha512-uMXCuQ3BItDUbAMhIXw7UPXRfAlOAvZzdK9BWpE60MCn+Svt3aLn9jsPTi/WNGlRUu2uI0v5S7JiIUsbsvh3fw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@floating-ui/utils": "^0.2.9"
|
||||
}
|
||||
},
|
||||
"node_modules/@floating-ui/dom": {
|
||||
"version": "1.6.13",
|
||||
"resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.13.tgz",
|
||||
"integrity": "sha512-umqzocjDgNRGTuO7Q8CU32dkHkECqI8ZdMZ5Swb6QAM0t5rnlrN3lGo1hdpscRd3WS8T6DKYK4ephgIH9iRh3w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@floating-ui/core": "^1.6.0",
|
||||
"@floating-ui/utils": "^0.2.9"
|
||||
}
|
||||
},
|
||||
"node_modules/@floating-ui/utils": {
|
||||
"version": "0.2.9",
|
||||
"resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.9.tgz",
|
||||
"integrity": "sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@formatjs/ecma402-abstract": {
|
||||
"version": "2.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-2.3.2.tgz",
|
||||
@ -1279,11 +1304,34 @@
|
||||
"resolved": "../open-api/typescript-sdk",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@immich/ui": {
|
||||
"version": "0.13.0",
|
||||
"resolved": "https://registry.npmjs.org/@immich/ui/-/ui-0.13.0.tgz",
|
||||
"integrity": "sha512-kO6uDbO+UpwRdzDI4FSyXkB7UXNDcnMo86gyLfdjZj6on9fy5Eam9KpJlt/zvVDNAqyGQzrBmdQSQl6n+S1JuA==",
|
||||
"license": "GNU Affero General Public License version 3",
|
||||
"dependencies": {
|
||||
"@mdi/js": "^7.4.47",
|
||||
"bits-ui": "^1.0.0-next.46",
|
||||
"tailwind-merge": "^2.5.4",
|
||||
"tailwind-variants": "^0.3.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"svelte": "^5.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@internationalized/date": {
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@internationalized/date/-/date-3.6.0.tgz",
|
||||
"integrity": "sha512-+z6ti+CcJnRlLHok/emGEsWQhe7kfSmEW+/6qCzvKY67YPh7YOBfvc7+/+NXq+zJlbArg30tYpqLjNgcAYv2YQ==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@swc/helpers": "^0.5.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@isaacs/cliui": {
|
||||
"version": "8.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
|
||||
"integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"string-width": "^5.1.2",
|
||||
"string-width-cjs": "npm:string-width@^4.2.0",
|
||||
@ -1300,7 +1348,6 @@
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz",
|
||||
"integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
@ -1312,7 +1359,6 @@
|
||||
"version": "6.2.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz",
|
||||
"integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
@ -1323,14 +1369,12 @@
|
||||
"node_modules/@isaacs/cliui/node_modules/emoji-regex": {
|
||||
"version": "9.2.2",
|
||||
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
|
||||
"integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
|
||||
"dev": true
|
||||
"integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="
|
||||
},
|
||||
"node_modules/@isaacs/cliui/node_modules/string-width": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
|
||||
"integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"eastasianwidth": "^0.2.0",
|
||||
"emoji-regex": "^9.2.2",
|
||||
@ -1347,7 +1391,6 @@
|
||||
"version": "7.1.0",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
|
||||
"integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"ansi-regex": "^6.0.1"
|
||||
},
|
||||
@ -1362,7 +1405,6 @@
|
||||
"version": "8.1.0",
|
||||
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
|
||||
"integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"ansi-styles": "^6.1.0",
|
||||
"string-width": "^5.0.1",
|
||||
@ -1542,7 +1584,6 @@
|
||||
"version": "2.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
|
||||
"integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@nodelib/fs.stat": "2.0.5",
|
||||
"run-parallel": "^1.1.9"
|
||||
@ -1555,7 +1596,6 @@
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
|
||||
"integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 8"
|
||||
}
|
||||
@ -1564,7 +1604,6 @@
|
||||
"version": "1.2.8",
|
||||
"resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
|
||||
"integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@nodelib/fs.scandir": "2.1.5",
|
||||
"fastq": "^1.6.0"
|
||||
@ -1605,7 +1644,6 @@
|
||||
"version": "0.11.0",
|
||||
"resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
|
||||
"integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
@ -2017,6 +2055,15 @@
|
||||
"vite": "^5.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@swc/helpers": {
|
||||
"version": "0.5.15",
|
||||
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz",
|
||||
"integrity": "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"tslib": "^2.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@testing-library/dom": {
|
||||
"version": "10.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.2.0.tgz",
|
||||
@ -2826,7 +2873,6 @@
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
|
||||
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
@ -2846,14 +2892,12 @@
|
||||
"node_modules/any-promise": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz",
|
||||
"integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==",
|
||||
"dev": true
|
||||
"integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A=="
|
||||
},
|
||||
"node_modules/anymatch": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
|
||||
"integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"normalize-path": "^3.0.0",
|
||||
"picomatch": "^2.0.4"
|
||||
@ -2865,8 +2909,7 @@
|
||||
"node_modules/arg": {
|
||||
"version": "5.0.2",
|
||||
"resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz",
|
||||
"integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==",
|
||||
"dev": true
|
||||
"integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg=="
|
||||
},
|
||||
"node_modules/argparse": {
|
||||
"version": "2.0.1",
|
||||
@ -2967,18 +3010,40 @@
|
||||
"node_modules/balanced-match": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
|
||||
"dev": true
|
||||
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
|
||||
},
|
||||
"node_modules/binary-extensions": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
|
||||
"integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/bits-ui": {
|
||||
"version": "1.0.0-next.77",
|
||||
"resolved": "https://registry.npmjs.org/bits-ui/-/bits-ui-1.0.0-next.77.tgz",
|
||||
"integrity": "sha512-IV0AyVEvsRkXv4s/fl4iea5E9W2b9EBf98s9mRMKMc1xHxM9MmtM2r6MZMqftHQ/c+gHTIt3A9EKuTlh7uay8w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@floating-ui/core": "^1.6.4",
|
||||
"@floating-ui/dom": "^1.6.7",
|
||||
"@internationalized/date": "^3.5.6",
|
||||
"esm-env": "^1.1.2",
|
||||
"runed": "^0.22.0",
|
||||
"svelte-toolbelt": "^0.7.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18",
|
||||
"pnpm": ">=8.7.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/huntabyte"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"svelte": "^5.11.0"
|
||||
}
|
||||
},
|
||||
"node_modules/brace-expansion": {
|
||||
"version": "1.1.11",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
||||
@ -2993,7 +3058,6 @@
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
|
||||
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"fill-range": "^7.1.1"
|
||||
@ -3093,7 +3157,6 @@
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz",
|
||||
"integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
@ -3164,7 +3227,6 @@
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
|
||||
"integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"anymatch": "~3.1.2",
|
||||
@ -3189,7 +3251,6 @@
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
|
||||
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"is-glob": "^4.0.1"
|
||||
},
|
||||
@ -3350,7 +3411,6 @@
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz",
|
||||
"integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
@ -3388,7 +3448,6 @@
|
||||
"version": "7.0.6",
|
||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
|
||||
"integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"path-key": "^3.1.0",
|
||||
@ -3416,7 +3475,6 @@
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
|
||||
"integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"cssesc": "bin/cssesc"
|
||||
},
|
||||
@ -3580,14 +3638,12 @@
|
||||
"node_modules/didyoumean": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz",
|
||||
"integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==",
|
||||
"dev": true
|
||||
"integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw=="
|
||||
},
|
||||
"node_modules/dlv": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz",
|
||||
"integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==",
|
||||
"dev": true
|
||||
"integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA=="
|
||||
},
|
||||
"node_modules/dom-accessibility-api": {
|
||||
"version": "0.5.16",
|
||||
@ -3621,8 +3677,7 @@
|
||||
"node_modules/eastasianwidth": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
|
||||
"integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
|
||||
"dev": true
|
||||
"integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="
|
||||
},
|
||||
"node_modules/electron-to-chromium": {
|
||||
"version": "1.5.74",
|
||||
@ -3634,8 +3689,7 @@
|
||||
"node_modules/emoji-regex": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
|
||||
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
|
||||
"dev": true
|
||||
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
|
||||
},
|
||||
"node_modules/engine.io-client": {
|
||||
"version": "6.5.4",
|
||||
@ -4146,9 +4200,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/esm-env": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.2.1.tgz",
|
||||
"integrity": "sha512-U9JedYYjCnadUlXk7e1Kr+aENQhtUaoaV9+gZm1T8LC/YBAPJx3NSPIAurFOC0U5vrdSevnUJS2/wUVxGwPhng==",
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.2.2.tgz",
|
||||
"integrity": "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/esniff": {
|
||||
@ -4306,7 +4360,6 @@
|
||||
"version": "3.3.2",
|
||||
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz",
|
||||
"integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@nodelib/fs.stat": "^2.0.2",
|
||||
@ -4323,7 +4376,6 @@
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
|
||||
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"is-glob": "^4.0.1"
|
||||
},
|
||||
@ -4347,7 +4399,6 @@
|
||||
"version": "1.15.0",
|
||||
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz",
|
||||
"integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"reusify": "^1.0.4"
|
||||
}
|
||||
@ -4374,7 +4425,6 @@
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
|
||||
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"to-regex-range": "^5.0.1"
|
||||
@ -4424,7 +4474,6 @@
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.2.1.tgz",
|
||||
"integrity": "sha512-PXUUyLqrR2XCWICfv6ukppP96sdFwWbNEnfEMt7jNsISjMsvaLNinAHNDYyvkyU+SZG2BTSbT5NjG+vZslfGTA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"cross-spawn": "^7.0.0",
|
||||
"signal-exit": "^4.0.1"
|
||||
@ -4469,7 +4518,6 @@
|
||||
"version": "2.3.3",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
||||
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
@ -4482,8 +4530,7 @@
|
||||
"node_modules/function-bind": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
|
||||
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
|
||||
"dev": true
|
||||
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
|
||||
},
|
||||
"node_modules/geojson-vt": {
|
||||
"version": "3.2.1",
|
||||
@ -4527,7 +4574,6 @@
|
||||
"version": "10.4.5",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz",
|
||||
"integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"foreground-child": "^3.1.0",
|
||||
@ -4548,7 +4594,6 @@
|
||||
"version": "6.0.2",
|
||||
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
|
||||
"integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"is-glob": "^4.0.3"
|
||||
},
|
||||
@ -4560,7 +4605,6 @@
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
|
||||
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"balanced-match": "^1.0.0"
|
||||
@ -4570,7 +4614,6 @@
|
||||
"version": "9.0.5",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
|
||||
"integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"brace-expansion": "^2.0.1"
|
||||
@ -4666,7 +4709,6 @@
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
|
||||
"integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"function-bind": "^1.1.1"
|
||||
},
|
||||
@ -4852,6 +4894,12 @@
|
||||
"resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz",
|
||||
"integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew=="
|
||||
},
|
||||
"node_modules/inline-style-parser": {
|
||||
"version": "0.2.4",
|
||||
"resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.4.tgz",
|
||||
"integrity": "sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/internmap": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz",
|
||||
@ -4882,7 +4930,6 @@
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
|
||||
"integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"binary-extensions": "^2.0.0"
|
||||
},
|
||||
@ -4909,7 +4956,6 @@
|
||||
"version": "2.13.0",
|
||||
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.0.tgz",
|
||||
"integrity": "sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"has": "^1.0.3"
|
||||
},
|
||||
@ -4944,7 +4990,6 @@
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
|
||||
"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
@ -4953,7 +4998,6 @@
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
|
||||
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
@ -4962,7 +5006,6 @@
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
|
||||
"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"is-extglob": "^2.1.1"
|
||||
},
|
||||
@ -4974,7 +5017,6 @@
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
|
||||
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.12.0"
|
||||
@ -5113,7 +5155,6 @@
|
||||
"version": "3.4.3",
|
||||
"resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz",
|
||||
"integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@isaacs/cliui": "^8.0.2"
|
||||
},
|
||||
@ -5128,7 +5169,6 @@
|
||||
"version": "1.21.6",
|
||||
"resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.6.tgz",
|
||||
"integrity": "sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"jiti": "bin/jiti.js"
|
||||
@ -5303,8 +5343,7 @@
|
||||
"node_modules/lines-and-columns": {
|
||||
"version": "1.2.4",
|
||||
"resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
|
||||
"integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==",
|
||||
"dev": true
|
||||
"integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="
|
||||
},
|
||||
"node_modules/locate-character": {
|
||||
"version": "3.0.0",
|
||||
@ -5546,7 +5585,6 @@
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
|
||||
"integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 8"
|
||||
}
|
||||
@ -5555,7 +5593,6 @@
|
||||
"version": "4.0.8",
|
||||
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
|
||||
"integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"braces": "^3.0.3",
|
||||
@ -5623,7 +5660,6 @@
|
||||
"version": "7.1.2",
|
||||
"resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
|
||||
"integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=16 || 14 >=14.17"
|
||||
}
|
||||
@ -5660,7 +5696,6 @@
|
||||
"version": "2.7.0",
|
||||
"resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz",
|
||||
"integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"any-promise": "^1.0.0",
|
||||
"object-assign": "^4.0.1",
|
||||
@ -5671,7 +5706,6 @@
|
||||
"version": "3.3.8",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz",
|
||||
"integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
@ -5734,7 +5768,6 @@
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
|
||||
"integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
@ -5760,7 +5793,6 @@
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
@ -5769,7 +5801,6 @@
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz",
|
||||
"integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
@ -5850,8 +5881,7 @@
|
||||
"node_modules/package-json-from-dist": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.0.tgz",
|
||||
"integrity": "sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==",
|
||||
"dev": true
|
||||
"integrity": "sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw=="
|
||||
},
|
||||
"node_modules/parent-module": {
|
||||
"version": "1.0.1",
|
||||
@ -5910,7 +5940,6 @@
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
|
||||
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
@ -5918,14 +5947,12 @@
|
||||
"node_modules/path-parse": {
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
|
||||
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
|
||||
"dev": true
|
||||
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="
|
||||
},
|
||||
"node_modules/path-scurry": {
|
||||
"version": "1.11.1",
|
||||
"resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz",
|
||||
"integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"lru-cache": "^10.2.0",
|
||||
"minipass": "^5.0.0 || ^6.0.2 || ^7.0.0"
|
||||
@ -5940,8 +5967,7 @@
|
||||
"node_modules/path-scurry/node_modules/lru-cache": {
|
||||
"version": "10.4.3",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
|
||||
"integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
|
||||
"dev": true
|
||||
"integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="
|
||||
},
|
||||
"node_modules/pathe": {
|
||||
"version": "1.1.2",
|
||||
@ -5976,14 +6002,12 @@
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
||||
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/picomatch": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
|
||||
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=8.6"
|
||||
},
|
||||
@ -5995,7 +6019,6 @@
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
|
||||
"integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
@ -6004,7 +6027,6 @@
|
||||
"version": "4.0.6",
|
||||
"resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz",
|
||||
"integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
@ -6031,7 +6053,6 @@
|
||||
"version": "8.5.0",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.0.tgz",
|
||||
"integrity": "sha512-27VKOqrYfPncKA2NrFOVhP5MGAfHKLYn/Q0mz9cNQyRAKYi3VNHwYU2qKKqPCqgBmeeJ0uAFB56NumXZ5ZReXg==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
@ -6060,7 +6081,6 @@
|
||||
"version": "15.1.0",
|
||||
"resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz",
|
||||
"integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"postcss-value-parser": "^4.0.0",
|
||||
"read-cache": "^1.0.0",
|
||||
@ -6077,7 +6097,6 @@
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz",
|
||||
"integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"camelcase-css": "^2.0.1"
|
||||
},
|
||||
@ -6125,7 +6144,6 @@
|
||||
"version": "6.2.0",
|
||||
"resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz",
|
||||
"integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
@ -6194,7 +6212,6 @@
|
||||
"version": "6.1.2",
|
||||
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz",
|
||||
"integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"cssesc": "^3.0.0",
|
||||
@ -6207,8 +6224,7 @@
|
||||
"node_modules/postcss-value-parser": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
|
||||
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
|
||||
"dev": true
|
||||
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ=="
|
||||
},
|
||||
"node_modules/potpack": {
|
||||
"version": "2.0.0",
|
||||
@ -6341,7 +6357,6 @@
|
||||
"version": "1.2.3",
|
||||
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
|
||||
"integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
@ -6372,7 +6387,6 @@
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
|
||||
"integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"pify": "^2.3.0"
|
||||
}
|
||||
@ -6483,7 +6497,6 @@
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
|
||||
"integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"picomatch": "^2.2.1"
|
||||
},
|
||||
@ -6561,7 +6574,6 @@
|
||||
"version": "1.22.8",
|
||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz",
|
||||
"integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"is-core-module": "^2.13.0",
|
||||
@ -6587,7 +6599,6 @@
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
|
||||
"integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"iojs": ">=1.0.0",
|
||||
"node": ">=0.10.0"
|
||||
@ -6697,7 +6708,6 @@
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
|
||||
"integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
@ -6716,6 +6726,21 @@
|
||||
"queue-microtask": "^1.2.2"
|
||||
}
|
||||
},
|
||||
"node_modules/runed": {
|
||||
"version": "0.22.0",
|
||||
"resolved": "https://registry.npmjs.org/runed/-/runed-0.22.0.tgz",
|
||||
"integrity": "sha512-ZWVXWhOr0P5xdNgtviz6D1ivLUDWKLCbeC5SUEJ3zBkqLReVqWHenFxMNFeFaiC5bfxhFxyxzyzB+98uYFtwdA==",
|
||||
"funding": [
|
||||
"https://github.com/sponsors/huntabyte",
|
||||
"https://github.com/sponsors/tglide"
|
||||
],
|
||||
"dependencies": {
|
||||
"esm-env": "^1.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"svelte": "^5.7.0"
|
||||
}
|
||||
},
|
||||
"node_modules/rw": {
|
||||
"version": "1.3.3",
|
||||
"resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz",
|
||||
@ -6843,7 +6868,6 @@
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
|
||||
"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"shebang-regex": "^3.0.0"
|
||||
},
|
||||
@ -6855,7 +6879,6 @@
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
|
||||
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
@ -6870,7 +6893,6 @@
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
|
||||
"integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
},
|
||||
@ -6979,7 +7001,6 @@
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
|
||||
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
|
||||
"dev": true,
|
||||
"license": "BSD-3-Clause",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
@ -7078,7 +7099,6 @@
|
||||
"version": "4.2.3",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
|
||||
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"emoji-regex": "^8.0.0",
|
||||
"is-fullwidth-code-point": "^3.0.0",
|
||||
@ -7093,7 +7113,6 @@
|
||||
"version": "4.2.3",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
|
||||
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"emoji-regex": "^8.0.0",
|
||||
"is-fullwidth-code-point": "^3.0.0",
|
||||
@ -7107,7 +7126,6 @@
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
|
||||
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"ansi-regex": "^5.0.1"
|
||||
},
|
||||
@ -7120,7 +7138,6 @@
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
|
||||
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"ansi-regex": "^5.0.1"
|
||||
},
|
||||
@ -7152,11 +7169,19 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/style-to-object": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.8.tgz",
|
||||
"integrity": "sha512-xT47I/Eo0rwJmaXC4oilDGDWLohVhR6o/xAQcPQN8q6QBuZVL8qMYL85kLmST5cPjAorwvqIA4qXTRQoYHaL6g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"inline-style-parser": "0.2.4"
|
||||
}
|
||||
},
|
||||
"node_modules/sucrase": {
|
||||
"version": "3.35.0",
|
||||
"resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz",
|
||||
"integrity": "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jridgewell/gen-mapping": "^0.3.2",
|
||||
@ -7199,7 +7224,6 @@
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
|
||||
"integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
@ -7841,6 +7865,41 @@
|
||||
"svelte": "^3.0.0 || ^4.0.0 || ^5.0.0-next.1"
|
||||
}
|
||||
},
|
||||
"node_modules/svelte-toolbelt": {
|
||||
"version": "0.7.0",
|
||||
"resolved": "https://registry.npmjs.org/svelte-toolbelt/-/svelte-toolbelt-0.7.0.tgz",
|
||||
"integrity": "sha512-i/Tv4NwAWWqJnK5H0F8y/ubDnogDYlwwyzKhrspTUFzrFuGnYshqd2g4/R43ds841wmaFiSW/HsdsdWhPOlrAA==",
|
||||
"funding": [
|
||||
"https://github.com/sponsors/huntabyte"
|
||||
],
|
||||
"dependencies": {
|
||||
"clsx": "^2.1.1",
|
||||
"runed": "^0.20.0",
|
||||
"style-to-object": "^1.0.8"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18",
|
||||
"pnpm": ">=8.7.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"svelte": "^5.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/svelte-toolbelt/node_modules/runed": {
|
||||
"version": "0.20.0",
|
||||
"resolved": "https://registry.npmjs.org/runed/-/runed-0.20.0.tgz",
|
||||
"integrity": "sha512-YqPxaUdWL5nUXuSF+/v8a+NkVN8TGyEGbQwTA25fLY35MR/2bvZ1c6sCbudoo1kT4CAJPh4kUkcgGVxW127WKw==",
|
||||
"funding": [
|
||||
"https://github.com/sponsors/huntabyte",
|
||||
"https://github.com/sponsors/tglide"
|
||||
],
|
||||
"dependencies": {
|
||||
"esm-env": "^1.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"svelte": "^5.7.0"
|
||||
}
|
||||
},
|
||||
"node_modules/svelte/node_modules/aria-query": {
|
||||
"version": "5.3.2",
|
||||
"resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz",
|
||||
@ -7858,11 +7917,36 @@
|
||||
"optional": true,
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/tailwind-merge": {
|
||||
"version": "2.6.0",
|
||||
"resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.6.0.tgz",
|
||||
"integrity": "sha512-P+Vu1qXfzediirmHOC3xKGAYeZtPcV9g76X+xg2FD4tYgR71ewMA35Y3sCz3zhiN/dwefRpJX0yBcgwi1fXNQA==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/dcastil"
|
||||
}
|
||||
},
|
||||
"node_modules/tailwind-variants": {
|
||||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/tailwind-variants/-/tailwind-variants-0.3.0.tgz",
|
||||
"integrity": "sha512-ho2k5kn+LB1fT5XdNS3Clb96zieWxbStE9wNLK7D0AV64kdZMaYzAKo0fWl6fXLPY99ffF9oBJnIj5escEl/8A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"tailwind-merge": "^2.5.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.x",
|
||||
"pnpm": ">=7.x"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"tailwindcss": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/tailwindcss": {
|
||||
"version": "3.4.17",
|
||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.17.tgz",
|
||||
"integrity": "sha512-w33E2aCvSDP0tW9RZuNXadXlkHXqFzSkQew/aIa2i/Sj8fThxwovwlXHSPXTbAHwEIhBFXAedUhP2tueAKP8Og==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@alloc/quick-lru": "^5.2.0",
|
||||
@ -7900,7 +7984,6 @@
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz",
|
||||
"integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
@ -7913,7 +7996,6 @@
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.2.tgz",
|
||||
"integrity": "sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
@ -7949,7 +8031,6 @@
|
||||
"version": "2.6.1",
|
||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.6.1.tgz",
|
||||
"integrity": "sha512-7r0XPzioN/Q9kXBro/XPnA6kznR73DHq+GXh5ON7ZozRO6aMjbmiBuKste2wslTFkC5d1dw0GooOCepZXJ2SAg==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"bin": {
|
||||
"yaml": "bin.mjs"
|
||||
@ -8000,7 +8081,6 @@
|
||||
"version": "3.3.1",
|
||||
"resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz",
|
||||
"integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"any-promise": "^1.0.0"
|
||||
}
|
||||
@ -8009,7 +8089,6 @@
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz",
|
||||
"integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"thenify": ">= 3.1.0 < 4"
|
||||
},
|
||||
@ -8098,7 +8177,6 @@
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
|
||||
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"is-number": "^7.0.0"
|
||||
@ -8163,8 +8241,7 @@
|
||||
"node_modules/ts-interface-checker": {
|
||||
"version": "0.1.13",
|
||||
"resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz",
|
||||
"integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==",
|
||||
"dev": true
|
||||
"integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA=="
|
||||
},
|
||||
"node_modules/tslib": {
|
||||
"version": "2.8.1",
|
||||
@ -8308,8 +8385,7 @@
|
||||
"node_modules/util-deprecate": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
|
||||
"dev": true
|
||||
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="
|
||||
},
|
||||
"node_modules/validate-npm-package-license": {
|
||||
"version": "3.0.4",
|
||||
@ -8581,7 +8657,6 @@
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
||||
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"isexe": "^2.0.0"
|
||||
},
|
||||
@ -8635,7 +8710,6 @@
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
|
||||
"integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"ansi-styles": "^4.0.0",
|
||||
"string-width": "^4.1.0",
|
||||
@ -8652,7 +8726,6 @@
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
||||
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"color-convert": "^2.0.1"
|
||||
},
|
||||
@ -8667,7 +8740,6 @@
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"color-name": "~1.1.4"
|
||||
},
|
||||
@ -8678,8 +8750,7 @@
|
||||
"node_modules/wrap-ansi-cjs/node_modules/color-name": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
|
||||
"dev": true
|
||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
|
||||
},
|
||||
"node_modules/wrap-ansi/node_modules/ansi-styles": {
|
||||
"version": "4.3.0",
|
||||
|
@ -67,6 +67,7 @@
|
||||
"dependencies": {
|
||||
"@formatjs/icu-messageformat-parser": "^2.9.8",
|
||||
"@immich/sdk": "file:../open-api/typescript-sdk",
|
||||
"@immich/ui": "^0.13.0",
|
||||
"@mapbox/mapbox-gl-rtl-text": "0.2.3",
|
||||
"@mdi/js": "^7.4.47",
|
||||
"@photo-sphere-viewer/core": "^5.11.5",
|
||||
|
@ -22,6 +22,30 @@
|
||||
--immich-dark-success: 56 142 60;
|
||||
--immich-dark-warning: 245 124 0;
|
||||
}
|
||||
|
||||
:root {
|
||||
/* light */
|
||||
--immich-ui-primary: 66 80 175;
|
||||
--immich-ui-dark: 58 58 58;
|
||||
--immich-ui-light: 255 255 255;
|
||||
--immich-ui-success: 34 197 94;
|
||||
--immich-ui-danger: 180 0 0;
|
||||
--immich-ui-warning: 255 170 0;
|
||||
--immich-ui-info: 14 165 233;
|
||||
--immich-ui-default-border: 209 213 219;
|
||||
}
|
||||
|
||||
.dark {
|
||||
/* dark */
|
||||
--immich-ui-primary: 172 203 250;
|
||||
--immich-ui-light: 0 0 0;
|
||||
--immich-ui-dark: 229 231 235;
|
||||
/* --immich-success: 56 142 60; */
|
||||
--immich-ui-danger: 239 68 68;
|
||||
--immich-ui-warning: 255 170 0;
|
||||
--immich-ui-info: 14 165 233;
|
||||
--immich-ui-default-border: 55 65 81;
|
||||
}
|
||||
}
|
||||
|
||||
@font-face {
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user