diff --git a/.github/workflows/prepare-release.yml b/.github/workflows/prepare-release.yml index 000dbe2dd..9d50f6f8f 100644 --- a/.github/workflows/prepare-release.yml +++ b/.github/workflows/prepare-release.yml @@ -4,16 +4,16 @@ on: workflow_dispatch: inputs: serverBump: - description: "Bump server version" + description: 'Bump server version' required: true - default: "false" + default: 'false' type: choice options: - - "false" + - 'false' - minor - patch mobileBump: - description: "Bump mobile build number" + description: 'Bump mobile build number' required: false type: boolean @@ -46,8 +46,8 @@ jobs: with: author_name: Alex The Bot author_email: alex.tran1502@gmail.com - default_author: user_info - message: "Version ${{ env.IMMICH_VERSION }}" + default_author: user_info + message: 'Version ${{ env.IMMICH_VERSION }}' tag: ${{ env.IMMICH_VERSION }} push: true @@ -85,4 +85,5 @@ jobs: docker/example.env docker/hwaccel.ml.yml docker/hwaccel.transcoding.yml + docker/prometheus.yml *.apk diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 000000000..eae6e1a16 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,30 @@ +{ + "editor.formatOnSave": true, + "[javascript][typescript][css]": { + "editor.defaultFormatter": "esbenp.prettier-vscode", + "editor.tabSize": 2, + "editor.formatOnSave": true + }, + "[svelte]": { + "editor.defaultFormatter": "svelte.svelte-vscode", + "editor.tabSize": 2 + }, + "svelte.enable-ts-plugin": true, + "eslint.validate": [ + "javascript", + "svelte" + ], + "typescript.preferences.importModuleSpecifier": "non-relative", + "[dart]": { + "editor.formatOnSave": true, + "editor.selectionHighlight": false, + "editor.suggest.snippetsPreventQuickSuggestions": false, + "editor.suggestSelection": "first", + "editor.tabCompletion": "onlySnippets", + "editor.wordBasedSuggestions": "off", + "editor.defaultFormatter": "Dart-Code.dart-code" + }, + "cSpell.words": [ + "immich" + ], +} \ No newline at end of file diff --git a/cli/Dockerfile b/cli/Dockerfile index cb0383a00..2eab54831 100644 --- a/cli/Dockerfile +++ b/cli/Dockerfile @@ -1,4 +1,4 @@ -FROM node:20-alpine3.19@sha256:c0a3badbd8a0a760de903e00cedbca94588e609299820557e72cba2a53dbaa2c as core +FROM node:20-alpine3.19@sha256:bf77dc26e48ea95fca9d1aceb5acfa69d2e546b765ec2abfb502975f1a2d4def as core WORKDIR /usr/src/open-api/typescript-sdk COPY open-api/typescript-sdk/package*.json open-api/typescript-sdk/tsconfig*.json ./ diff --git a/cli/package-lock.json b/cli/package-lock.json index 69be80132..8646b6b37 100644 --- a/cli/package-lock.json +++ b/cli/package-lock.json @@ -47,7 +47,7 @@ }, "../open-api/typescript-sdk": { "name": "@immich/sdk", - "version": "1.98.2", + "version": "1.99.0", "dev": true, "license": "GNU Affero General Public License version 3", "dependencies": { diff --git a/docker/docker-compose.dev.yml b/docker/docker-compose.dev.yml index 2541a1a20..ac56a2e06 100644 --- a/docker/docker-compose.dev.yml +++ b/docker/docker-compose.dev.yml @@ -99,7 +99,7 @@ services: redis: container_name: immich_redis - image: redis:6.2-alpine@sha256:51d6c56749a4243096327e3fb964a48ed92254357108449cb6e23999c37773c5 + image: redis:6.2-alpine@sha256:fd3535746075ba01b73c3602c0704bc944dd064c0a4ac46341a4a351bec69db8 database: container_name: immich_postgres diff --git a/docker/docker-compose.prod.yml b/docker/docker-compose.prod.yml index 77effc15f..b797e9e8e 100644 --- a/docker/docker-compose.prod.yml +++ b/docker/docker-compose.prod.yml @@ -56,7 +56,7 @@ services: redis: container_name: immich_redis - image: redis:6.2-alpine@sha256:51d6c56749a4243096327e3fb964a48ed92254357108449cb6e23999c37773c5 + image: redis:6.2-alpine@sha256:fd3535746075ba01b73c3602c0704bc944dd064c0a4ac46341a4a351bec69db8 restart: always database: @@ -78,7 +78,7 @@ services: container_name: immich_prometheus ports: - 9090:9090 - image: prom/prometheus@sha256:bc1794e85c9e00293351b967efa267ce6af1c824ac875a9d0c7ac84700a8b53e + image: prom/prometheus@sha256:5ccad477d0057e62a7cd1981ffcc43785ac10c5a35522dc207466ff7e7ec845f volumes: - ./prometheus.yml:/etc/prometheus/prometheus.yml - prometheus-data:/prometheus diff --git a/docs/docs/features/monitoring.md b/docs/docs/features/monitoring.md new file mode 100644 index 000000000..7e001c992 --- /dev/null +++ b/docs/docs/features/monitoring.md @@ -0,0 +1,113 @@ +# Monitoring + +## Overview + +Immich provides a variety of performance metrics to allow for local monitoring and insights. This integration is primarily in the form of Prometheus metrics. However, exporting traces is also possible due to the use of OpenTelemetry instrumentation. + +:::note +This is an opt-in feature intended for you to monitor immich's performance. This data isn't sent anywhere beyond what you've configured. +::: + +## Prometheus + +Prometheus is a tool that collects metrics from a number of sources you configure. It operates in a "pull" strategy - that is, it periodically requests metrics from each defined source. This means that the source doesn't send anything until it's requested. It also means that the source -- immich, in this case -- has to expose an endpoint for Prometheus to target when it requests metrics. + +### Metrics + +These metrics come in a variety of forms: + +- Counters, which can only increase. Example: the number of times an endpoint has been called. +- Gauges, which can increase or decrease within a certain range. Example: CPU utilization. +- Histograms, where each observation is assigned to a certain number of "buckets". Example: response time, where each bucket is a number of milliseconds. This one is a bit more complicated. + - Buckets in this case are _cumulative_; that is, an observation is placed not only into the smallest bucket that contains it, but also to all buckets larger than this. For example, if a histogram has three buckets for 1ms, 5ms and 10ms, an observation of 3ms will be bucketed into both 5ms and 10ms. + +The metrics in immich are grouped into API (endpoint calls and response times), host (memory and CPU utilization), and IO (internal database queries, image processing, and so on). Each group of metrics can be enabled or disabled independently. + +### Configuration + +Immich will not expose an endpoint for metrics by default. To enable this endpoint, you can add the `IMMICH_METRICS=true` environmental variable to your `.env` file. Note that only the server and microservices containers currently use this variable. + +:::note +`IMMICH_METRICS` is equivalent to enabling the following three environmental variables: `IMMICH_API_METRICS`, `IMMICH_HOST_METRICS`, and `IMMICH_IO_METRICS`. If you would like to only expose certain kinds of metrics, you can set only those environmental variables to `true`. Explicitly setting the environmental variable for a metric group overrides `IMMICH_METRICS` for that group. +::: + +The next step is to configure a new or existing Prometheus instance to scrape this endpoint. The following steps assume that you do not have an existing Prometheus instance, but the steps will be similar either way. + +You can start by defining a Prometheus service in the Compose file: + +```yaml +immich-prometheus: + container_name: immich_prometheus + ports: + # this exposes the default port for Prometheus so you can interact with it + - 9090:9090 + image: prom/prometheus + volumes: + # the Prometheus configuration file - a barebones one is provided to get started + - ./prometheus.yml:/etc/prometheus/prometheus.yml + # a named volume defined in the bottom of the Compose file; it can also be a mounted folder + - prometheus-data:/prometheus +``` + +You will also need to add `prometheus-data` to the list of volumes in the bottom of the Compose file: + +```yaml +volumes: + model-cache: + prometheus-data: +``` + +The last piece is the [configuration file][prom-file]. This file defines (among other things) the sources Prometheus should target. Download it and place it in the same folder as the Compose file. + +:::tip +The provided file is just a starting point. There are a ton of ways to configure Prometheus, so feel free to experiment! +::: + +After bringing down the containers with `docker compose down` and back up with `docker compose up -d`, a Prometheus instance will now collect metrics from the immich server and microservices containers. Note that we didn't need to expose any new ports for these containers - the communication is handled in the internal Docker network. + +:::note +To see exactly what metrics are made available, you can additionally add `8081:8081` to the server container's ports and `8082:8081` to the microservices container's ports. Visiting the `/metrics` endpoint for these services will show the same raw data that Prometheus collects. +::: + +### Usage + +So after setting up Prometheus, how do you actually view the metrics? The simplest way is to use Prometheus directly. Visiting Prometheus will show you a web UI where you can search for and visualize metrics. You can also view the status of your data sources and configure settings, but this is beyond the scope of this guide. + +## Grafana + +For a dedicated tool with nice presentation, you can use Grafana instead. This connects to Prometheus (and possibly other sources) for sophisticated data visualization. + +Setting up Grafana is similar to Prometheus. You can add a service for it: + +```yaml +immich-grafana: + container_name: immich_grafana + command: ['./run.sh', '-disable-reporting'] # this is to disable Grafana's telemetry + ports: + - 3000:3000 + image: grafana/grafana + volumes: + # stores your pretty dashboards and panels + - grafana-data:/var/lib/grafana +``` + +And add another volume for it: + +```yaml +volumes: + model-cache: + prometheus-data: + grafana-data: +``` + +After bringing down the services and back up again, you can now visit Grafana to view your metrics. On the first login, enter `admin` for both username and password and update your password. You can then go to the settings and add a data source with `http://immich-prometheus:9090` to point Grafana to your Prometheus instance. + +### Usage + +You can make your first dashboard to get started. Don't forget to save it frequently, or you'll lose all your progress! + +You can then make a new panel, specifying Prometheus as the data source for it. + +-- TODO: add images and more details here + +[prom-file]: https://github.com/immich-app/immich/releases/latest/download/prometheus.yml diff --git a/e2e/docker-compose.yml b/e2e/docker-compose.yml index 7cda6a12c..2c22fa72a 100644 --- a/e2e/docker-compose.yml +++ b/e2e/docker-compose.yml @@ -36,7 +36,7 @@ services: <<: *server-common redis: - image: redis:6.2-alpine@sha256:51d6c56749a4243096327e3fb964a48ed92254357108449cb6e23999c37773c5 + image: redis:6.2-alpine@sha256:fd3535746075ba01b73c3602c0704bc944dd064c0a4ac46341a4a351bec69db8 database: image: tensorchord/pgvecto-rs:pg14-v0.2.0@sha256:90724186f0a3517cf6914295b5ab410db9ce23190a2d9d0b9dd6463e3fa298f0 diff --git a/e2e/package-lock.json b/e2e/package-lock.json index 1b6d8ad19..3f892fe2e 100644 --- a/e2e/package-lock.json +++ b/e2e/package-lock.json @@ -1,12 +1,12 @@ { "name": "immich-e2e", - "version": "1.98.2", + "version": "1.99.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "immich-e2e", - "version": "1.98.2", + "version": "1.99.0", "license": "GNU Affero General Public License version 3", "devDependencies": { "@immich/cli": "file:../cli", @@ -80,7 +80,7 @@ }, "../open-api/typescript-sdk": { "name": "@immich/sdk", - "version": "1.98.2", + "version": "1.99.0", "dev": true, "license": "GNU Affero General Public License version 3", "dependencies": { @@ -339,9 +339,9 @@ "dev": true }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.19.12.tgz", - "integrity": "sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz", + "integrity": "sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==", "cpu": [ "ppc64" ], @@ -355,9 +355,9 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.12.tgz", - "integrity": "sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.20.2.tgz", + "integrity": "sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==", "cpu": [ "arm" ], @@ -371,9 +371,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.12.tgz", - "integrity": "sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.20.2.tgz", + "integrity": "sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==", "cpu": [ "arm64" ], @@ -387,9 +387,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.12.tgz", - "integrity": "sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.20.2.tgz", + "integrity": "sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==", "cpu": [ "x64" ], @@ -403,9 +403,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.12.tgz", - "integrity": "sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.20.2.tgz", + "integrity": "sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==", "cpu": [ "arm64" ], @@ -419,9 +419,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.12.tgz", - "integrity": "sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.20.2.tgz", + "integrity": "sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==", "cpu": [ "x64" ], @@ -435,9 +435,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.12.tgz", - "integrity": "sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.2.tgz", + "integrity": "sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==", "cpu": [ "arm64" ], @@ -451,9 +451,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.12.tgz", - "integrity": "sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.20.2.tgz", + "integrity": "sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==", "cpu": [ "x64" ], @@ -467,9 +467,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.12.tgz", - "integrity": "sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.20.2.tgz", + "integrity": "sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==", "cpu": [ "arm" ], @@ -483,9 +483,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.12.tgz", - "integrity": "sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.20.2.tgz", + "integrity": "sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==", "cpu": [ "arm64" ], @@ -499,9 +499,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.12.tgz", - "integrity": "sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.20.2.tgz", + "integrity": "sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==", "cpu": [ "ia32" ], @@ -515,9 +515,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.12.tgz", - "integrity": "sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.20.2.tgz", + "integrity": "sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==", "cpu": [ "loong64" ], @@ -531,9 +531,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.12.tgz", - "integrity": "sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.20.2.tgz", + "integrity": "sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==", "cpu": [ "mips64el" ], @@ -547,9 +547,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.12.tgz", - "integrity": "sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.20.2.tgz", + "integrity": "sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==", "cpu": [ "ppc64" ], @@ -563,9 +563,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.12.tgz", - "integrity": "sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.20.2.tgz", + "integrity": "sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==", "cpu": [ "riscv64" ], @@ -579,9 +579,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.12.tgz", - "integrity": "sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.20.2.tgz", + "integrity": "sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==", "cpu": [ "s390x" ], @@ -595,9 +595,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.12.tgz", - "integrity": "sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.20.2.tgz", + "integrity": "sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==", "cpu": [ "x64" ], @@ -611,9 +611,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.12.tgz", - "integrity": "sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.20.2.tgz", + "integrity": "sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==", "cpu": [ "x64" ], @@ -627,9 +627,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.12.tgz", - "integrity": "sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.20.2.tgz", + "integrity": "sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==", "cpu": [ "x64" ], @@ -643,9 +643,9 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.12.tgz", - "integrity": "sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.20.2.tgz", + "integrity": "sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==", "cpu": [ "x64" ], @@ -659,9 +659,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.12.tgz", - "integrity": "sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.20.2.tgz", + "integrity": "sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==", "cpu": [ "arm64" ], @@ -675,9 +675,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.12.tgz", - "integrity": "sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.20.2.tgz", + "integrity": "sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==", "cpu": [ "ia32" ], @@ -691,9 +691,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.12.tgz", - "integrity": "sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.20.2.tgz", + "integrity": "sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==", "cpu": [ "x64" ], @@ -863,9 +863,9 @@ "dev": true }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.22", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.22.tgz", - "integrity": "sha512-Wf963MzWtA2sjrNt+g18IAln9lKnlRp+K2eH4jjIoF1wYeq3aMREpG09xhlhdzS0EjwU7qmUJYangWa+151vZw==", + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", "dev": true, "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", @@ -941,9 +941,9 @@ } }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.12.0.tgz", - "integrity": "sha512-+ac02NL/2TCKRrJu2wffk1kZ+RyqxVUlbjSagNgPm94frxtr+XDL12E5Ll1enWskLrtrZ2r8L3wED1orIibV/w==", + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.13.0.tgz", + "integrity": "sha512-5ZYPOuaAqEH/W3gYsRkxQATBW3Ii1MfaT4EQstTnLKViLi2gLSQmlmtTpGucNP3sXEpOiI5tdGhjdE111ekyEg==", "cpu": [ "arm" ], @@ -954,9 +954,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.12.0.tgz", - "integrity": "sha512-OBqcX2BMe6nvjQ0Nyp7cC90cnumt8PXmO7Dp3gfAju/6YwG0Tj74z1vKrfRz7qAv23nBcYM8BCbhrsWqO7PzQQ==", + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.13.0.tgz", + "integrity": "sha512-BSbaCmn8ZadK3UAQdlauSvtaJjhlDEjS5hEVVIN3A4bbl3X+otyf/kOJV08bYiRxfejP3DXFzO2jz3G20107+Q==", "cpu": [ "arm64" ], @@ -967,9 +967,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.12.0.tgz", - "integrity": "sha512-X64tZd8dRE/QTrBIEs63kaOBG0b5GVEd3ccoLtyf6IdXtHdh8h+I56C2yC3PtC9Ucnv0CpNFJLqKFVgCYe0lOQ==", + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.13.0.tgz", + "integrity": "sha512-Ovf2evVaP6sW5Ut0GHyUSOqA6tVKfrTHddtmxGQc1CTQa1Cw3/KMCDEEICZBbyppcwnhMwcDce9ZRxdWRpVd6g==", "cpu": [ "arm64" ], @@ -980,9 +980,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.12.0.tgz", - "integrity": "sha512-cc71KUZoVbUJmGP2cOuiZ9HSOP14AzBAThn3OU+9LcA1+IUqswJyR1cAJj3Mg55HbjZP6OLAIscbQsQLrpgTOg==", + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.13.0.tgz", + "integrity": "sha512-U+Jcxm89UTK592vZ2J9st9ajRv/hrwHdnvyuJpa5A2ngGSVHypigidkQJP+YiGL6JODiUeMzkqQzbCG3At81Gg==", "cpu": [ "x64" ], @@ -993,9 +993,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.12.0.tgz", - "integrity": "sha512-a6w/Y3hyyO6GlpKL2xJ4IOh/7d+APaqLYdMf86xnczU3nurFTaVN9s9jOXQg97BE4nYm/7Ga51rjec5nfRdrvA==", + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.13.0.tgz", + "integrity": "sha512-8wZidaUJUTIR5T4vRS22VkSMOVooG0F4N+JSwQXWSRiC6yfEsFMLTYRFHvby5mFFuExHa/yAp9juSphQQJAijQ==", "cpu": [ "arm" ], @@ -1006,9 +1006,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.12.0.tgz", - "integrity": "sha512-0fZBq27b+D7Ar5CQMofVN8sggOVhEtzFUwOwPppQt0k+VR+7UHMZZY4y+64WJ06XOhBTKXtQB/Sv0NwQMXyNAA==", + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.13.0.tgz", + "integrity": "sha512-Iu0Kno1vrD7zHQDxOmvweqLkAzjxEVqNhUIXBsZ8hu8Oak7/5VTPrxOEZXYC1nmrBVJp0ZcL2E7lSuuOVaE3+w==", "cpu": [ "arm64" ], @@ -1019,9 +1019,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.12.0.tgz", - "integrity": "sha512-eTvzUS3hhhlgeAv6bfigekzWZjaEX9xP9HhxB0Dvrdbkk5w/b+1Sxct2ZuDxNJKzsRStSq1EaEkVSEe7A7ipgQ==", + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.13.0.tgz", + "integrity": "sha512-C31QrW47llgVyrRjIwiOwsHFcaIwmkKi3PCroQY5aVq4H0A5v/vVVAtFsI1nfBngtoRpeREvZOkIhmRwUKkAdw==", "cpu": [ "arm64" ], @@ -1032,9 +1032,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.12.0.tgz", - "integrity": "sha512-ix+qAB9qmrCRiaO71VFfY8rkiAZJL8zQRXveS27HS+pKdjwUfEhqo2+YF2oI+H/22Xsiski+qqwIBxVewLK7sw==", + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.13.0.tgz", + "integrity": "sha512-Oq90dtMHvthFOPMl7pt7KmxzX7E71AfyIhh+cPhLY9oko97Zf2C9tt/XJD4RgxhaGeAraAXDtqxvKE1y/j35lA==", "cpu": [ "riscv64" ], @@ -1045,9 +1045,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.12.0.tgz", - "integrity": "sha512-TenQhZVOtw/3qKOPa7d+QgkeM6xY0LtwzR8OplmyL5LrgTWIXpTQg2Q2ycBf8jm+SFW2Wt/DTn1gf7nFp3ssVA==", + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.13.0.tgz", + "integrity": "sha512-yUD/8wMffnTKuiIsl6xU+4IA8UNhQ/f1sAnQebmE/lyQ8abjsVyDkyRkWop0kdMhKMprpNIhPmYlCxgHrPoXoA==", "cpu": [ "x64" ], @@ -1058,9 +1058,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.12.0.tgz", - "integrity": "sha512-LfFdRhNnW0zdMvdCb5FNuWlls2WbbSridJvxOvYWgSBOYZtgBfW9UGNJG//rwMqTX1xQE9BAodvMH9tAusKDUw==", + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.13.0.tgz", + "integrity": "sha512-9RyNqoFNdF0vu/qqX63fKotBh43fJQeYC98hCaf89DYQpv+xu0D8QFSOS0biA7cGuqJFOc1bJ+m2rhhsKcw1hw==", "cpu": [ "x64" ], @@ -1071,9 +1071,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.12.0.tgz", - "integrity": "sha512-JPDxovheWNp6d7AHCgsUlkuCKvtu3RB55iNEkaQcf0ttsDU/JZF+iQnYcQJSk/7PtT4mjjVG8N1kpwnI9SLYaw==", + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.13.0.tgz", + "integrity": "sha512-46ue8ymtm/5PUU6pCvjlic0z82qWkxv54GTJZgHrQUuZnVH+tvvSP0LsozIDsCBFO4VjJ13N68wqrKSeScUKdA==", "cpu": [ "arm64" ], @@ -1084,9 +1084,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.12.0.tgz", - "integrity": "sha512-fjtuvMWRGJn1oZacG8IPnzIV6GF2/XG+h71FKn76OYFqySXInJtseAqdprVTDTyqPxQOG9Exak5/E9Z3+EJ8ZA==", + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.13.0.tgz", + "integrity": "sha512-P5/MqLdLSlqxbeuJ3YDeX37srC8mCflSyTrUsgbU1c/U9j6l2g2GiIdYaGD9QjdMQPMSgYm7hgg0551wHyIluw==", "cpu": [ "ia32" ], @@ -1097,9 +1097,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.12.0.tgz", - "integrity": "sha512-ZYmr5mS2wd4Dew/JjT0Fqi2NPB/ZhZ2VvPp7SmvPZb4Y1CG/LRcS6tcRo2cYU7zLK5A7cdbhWnnWmUjoI4qapg==", + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.13.0.tgz", + "integrity": "sha512-UKXUQNbO3DOhzLRwHSpa0HnhhCgNODvfoPWv2FCXme8N/ANFfhIPMGuOT+QuKd16+B5yxZ0HdpNlqPvTMS1qfw==", "cpu": [ "x64" ], @@ -1158,9 +1158,9 @@ "dev": true }, "node_modules/@types/node": { - "version": "20.11.27", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.27.tgz", - "integrity": "sha512-qyUZfMnCg1KEz57r7pzFtSGt49f6RPkPBis3Vo4PbS7roQEDn22hiHzl/Lo1q4i4hDEgBJmBF/NTNg2XR0HbFg==", + "version": "20.11.28", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.28.tgz", + "integrity": "sha512-M/GPWVS2wLkSkNHVeLkrF2fD5Lx5UC4PxA0uZcKc6QqbIQUJyW1jVjueJYi1z8n0I5PxYrtpnPnWglE+y9A0KA==", "dev": true, "dependencies": { "undici-types": "~5.26.4" @@ -1497,9 +1497,9 @@ "dev": true }, "node_modules/@vitest/coverage-v8": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-1.3.1.tgz", - "integrity": "sha512-UuBnkSJUNE9rdHjDCPyJ4fYuMkoMtnghes1XohYa4At0MS3OQSAo97FrbwSLRshYsXThMZy1+ybD/byK5llyIg==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-1.4.0.tgz", + "integrity": "sha512-4hDGyH1SvKpgZnIByr9LhGgCEuF9DKM34IBLCC/fVfy24Z3+PZ+Ii9hsVBsHvY1umM1aGPEjceRkzxCfcQ10wg==", "dev": true, "dependencies": { "@ampproject/remapping": "^2.2.1", @@ -1507,12 +1507,13 @@ "debug": "^4.3.4", "istanbul-lib-coverage": "^3.2.2", "istanbul-lib-report": "^3.0.1", - "istanbul-lib-source-maps": "^4.0.1", + "istanbul-lib-source-maps": "^5.0.4", "istanbul-reports": "^3.1.6", "magic-string": "^0.30.5", "magicast": "^0.3.3", "picocolors": "^1.0.0", "std-env": "^3.5.0", + "strip-literal": "^2.0.0", "test-exclude": "^6.0.0", "v8-to-istanbul": "^9.2.0" }, @@ -1520,17 +1521,17 @@ "url": "https://opencollective.com/vitest" }, "peerDependencies": { - "vitest": "1.3.1" + "vitest": "1.4.0" } }, "node_modules/@vitest/expect": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-1.3.1.tgz", - "integrity": "sha512-xofQFwIzfdmLLlHa6ag0dPV8YsnKOCP1KdAeVVh34vSjN2dcUiXYCD9htu/9eM7t8Xln4v03U9HLxLpPlsXdZw==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-1.4.0.tgz", + "integrity": "sha512-Jths0sWCJZ8BxjKe+p+eKsoqev1/T8lYcrjavEaz8auEJ4jAVY0GwW3JKmdVU4mmNPLPHixh4GNXP7GFtAiDHA==", "dev": true, "dependencies": { - "@vitest/spy": "1.3.1", - "@vitest/utils": "1.3.1", + "@vitest/spy": "1.4.0", + "@vitest/utils": "1.4.0", "chai": "^4.3.10" }, "funding": { @@ -1538,12 +1539,12 @@ } }, "node_modules/@vitest/runner": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-1.3.1.tgz", - "integrity": "sha512-5FzF9c3jG/z5bgCnjr8j9LNq/9OxV2uEBAITOXfoe3rdZJTdO7jzThth7FXv/6b+kdY65tpRQB7WaKhNZwX+Kg==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-1.4.0.tgz", + "integrity": "sha512-EDYVSmesqlQ4RD2VvWo3hQgTJ7ZrFQ2VSJdfiJiArkCerDAGeyF1i6dHkmySqk573jLp6d/cfqCN+7wUB5tLgg==", "dev": true, "dependencies": { - "@vitest/utils": "1.3.1", + "@vitest/utils": "1.4.0", "p-limit": "^5.0.0", "pathe": "^1.1.1" }, @@ -1552,9 +1553,9 @@ } }, "node_modules/@vitest/snapshot": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-1.3.1.tgz", - "integrity": "sha512-EF++BZbt6RZmOlE3SuTPu/NfwBF6q4ABS37HHXzs2LUVPBLx2QoY/K0fKpRChSo8eLiuxcbCVfqKgx/dplCDuQ==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-1.4.0.tgz", + "integrity": "sha512-saAFnt5pPIA5qDGxOHxJ/XxhMFKkUSBJmVt5VgDsAqPTX6JP326r5C/c9UuCMPoXNzuudTPsYDZCoJ5ilpqG2A==", "dev": true, "dependencies": { "magic-string": "^0.30.5", @@ -1566,9 +1567,9 @@ } }, "node_modules/@vitest/spy": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-1.3.1.tgz", - "integrity": "sha512-xAcW+S099ylC9VLU7eZfdT9myV67Nor9w9zhf0mGCYJSO+zM2839tOeROTdikOi/8Qeusffvxb/MyBSOja1Uig==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-1.4.0.tgz", + "integrity": "sha512-Ywau/Qs1DzM/8Uc+yA77CwSegizMlcgTJuYGAi0jujOteJOUf1ujunHThYo243KG9nAyWT3L9ifPYZ5+As/+6Q==", "dev": true, "dependencies": { "tinyspy": "^2.2.0" @@ -1578,9 +1579,9 @@ } }, "node_modules/@vitest/utils": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-1.3.1.tgz", - "integrity": "sha512-d3Waie/299qqRyHTm2DjADeTaNdNSVsnwHPWrs20JMpjh6eiVq7ggggweO8rc4arhf6rRkWuHKwvxGvejUXZZQ==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-1.4.0.tgz", + "integrity": "sha512-mx3Yd1/6e2Vt/PUC98DcqTirtfxUyAZ32uK82r8rZzbtBeBo+nqgnjx/LvqQdWsrvNtm14VmurNgcf4nqY5gJg==", "dev": true, "dependencies": { "diff-sequences": "^29.6.3", @@ -2186,9 +2187,9 @@ } }, "node_modules/esbuild": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.12.tgz", - "integrity": "sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.2.tgz", + "integrity": "sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==", "dev": true, "hasInstallScript": true, "bin": { @@ -2198,29 +2199,29 @@ "node": ">=12" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.19.12", - "@esbuild/android-arm": "0.19.12", - "@esbuild/android-arm64": "0.19.12", - "@esbuild/android-x64": "0.19.12", - "@esbuild/darwin-arm64": "0.19.12", - "@esbuild/darwin-x64": "0.19.12", - "@esbuild/freebsd-arm64": "0.19.12", - "@esbuild/freebsd-x64": "0.19.12", - "@esbuild/linux-arm": "0.19.12", - "@esbuild/linux-arm64": "0.19.12", - "@esbuild/linux-ia32": "0.19.12", - "@esbuild/linux-loong64": "0.19.12", - "@esbuild/linux-mips64el": "0.19.12", - "@esbuild/linux-ppc64": "0.19.12", - "@esbuild/linux-riscv64": "0.19.12", - "@esbuild/linux-s390x": "0.19.12", - "@esbuild/linux-x64": "0.19.12", - "@esbuild/netbsd-x64": "0.19.12", - "@esbuild/openbsd-x64": "0.19.12", - "@esbuild/sunos-x64": "0.19.12", - "@esbuild/win32-arm64": "0.19.12", - "@esbuild/win32-ia32": "0.19.12", - "@esbuild/win32-x64": "0.19.12" + "@esbuild/aix-ppc64": "0.20.2", + "@esbuild/android-arm": "0.20.2", + "@esbuild/android-arm64": "0.20.2", + "@esbuild/android-x64": "0.20.2", + "@esbuild/darwin-arm64": "0.20.2", + "@esbuild/darwin-x64": "0.20.2", + "@esbuild/freebsd-arm64": "0.20.2", + "@esbuild/freebsd-x64": "0.20.2", + "@esbuild/linux-arm": "0.20.2", + "@esbuild/linux-arm64": "0.20.2", + "@esbuild/linux-ia32": "0.20.2", + "@esbuild/linux-loong64": "0.20.2", + "@esbuild/linux-mips64el": "0.20.2", + "@esbuild/linux-ppc64": "0.20.2", + "@esbuild/linux-riscv64": "0.20.2", + "@esbuild/linux-s390x": "0.20.2", + "@esbuild/linux-x64": "0.20.2", + "@esbuild/netbsd-x64": "0.20.2", + "@esbuild/openbsd-x64": "0.20.2", + "@esbuild/sunos-x64": "0.20.2", + "@esbuild/win32-arm64": "0.20.2", + "@esbuild/win32-ia32": "0.20.2", + "@esbuild/win32-x64": "0.20.2" } }, "node_modules/escalade": { @@ -3109,14 +3110,14 @@ } }, "node_modules/istanbul-lib-source-maps": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", - "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.4.tgz", + "integrity": "sha512-wHOoEsNJTVltaJp8eVkm8w+GVkVNHT2YDYo53YdzQEL2gWm1hBX5cGFR9hQJtuGLebidVX7et3+dmDZrmclduw==", "dev": true, "dependencies": { + "@jridgewell/trace-mapping": "^0.3.23", "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0", - "source-map": "^0.6.1" + "istanbul-lib-coverage": "^3.0.0" }, "engines": { "node": ">=10" @@ -3920,9 +3921,9 @@ } }, "node_modules/postcss": { - "version": "8.4.35", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.35.tgz", - "integrity": "sha512-u5U8qYpBCpN13BsiEB0CbR1Hhh4Gc0zLFuedrHJKMctHCHAGrMdG0PRM/KErzAL3CU6/eckEtmHNB3x6e3c0vA==", + "version": "8.4.37", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.37.tgz", + "integrity": "sha512-7iB/v/r7Woof0glKLH8b1SPHrsX7uhdO+Geb41QpF/+mWZHU3uxxSlN+UXGVit1PawOYDToO+AbZzhBzWRDwbQ==", "dev": true, "funding": [ { @@ -3941,7 +3942,7 @@ "dependencies": { "nanoid": "^3.3.7", "picocolors": "^1.0.0", - "source-map-js": "^1.0.2" + "source-map-js": "^1.2.0" }, "engines": { "node": "^10 || ^12 || >=14" @@ -4296,9 +4297,9 @@ } }, "node_modules/rollup": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.12.0.tgz", - "integrity": "sha512-wz66wn4t1OHIJw3+XU7mJJQV/2NAfw5OAk6G6Hoo3zcvz/XOfQ52Vgi+AN4Uxoxi0KBBwk2g8zPrTDA4btSB/Q==", + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.13.0.tgz", + "integrity": "sha512-3YegKemjoQnYKmsBlOHfMLVPPA5xLkQ8MHLLSw/fBrFaVkEayL51DilPpNNLq1exr98F2B1TzrV0FUlN3gWRPg==", "dev": true, "dependencies": { "@types/estree": "1.0.5" @@ -4311,19 +4312,19 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.12.0", - "@rollup/rollup-android-arm64": "4.12.0", - "@rollup/rollup-darwin-arm64": "4.12.0", - "@rollup/rollup-darwin-x64": "4.12.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.12.0", - "@rollup/rollup-linux-arm64-gnu": "4.12.0", - "@rollup/rollup-linux-arm64-musl": "4.12.0", - "@rollup/rollup-linux-riscv64-gnu": "4.12.0", - "@rollup/rollup-linux-x64-gnu": "4.12.0", - "@rollup/rollup-linux-x64-musl": "4.12.0", - "@rollup/rollup-win32-arm64-msvc": "4.12.0", - "@rollup/rollup-win32-ia32-msvc": "4.12.0", - "@rollup/rollup-win32-x64-msvc": "4.12.0", + "@rollup/rollup-android-arm-eabi": "4.13.0", + "@rollup/rollup-android-arm64": "4.13.0", + "@rollup/rollup-darwin-arm64": "4.13.0", + "@rollup/rollup-darwin-x64": "4.13.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.13.0", + "@rollup/rollup-linux-arm64-gnu": "4.13.0", + "@rollup/rollup-linux-arm64-musl": "4.13.0", + "@rollup/rollup-linux-riscv64-gnu": "4.13.0", + "@rollup/rollup-linux-x64-gnu": "4.13.0", + "@rollup/rollup-linux-x64-musl": "4.13.0", + "@rollup/rollup-win32-arm64-msvc": "4.13.0", + "@rollup/rollup-win32-ia32-msvc": "4.13.0", + "@rollup/rollup-win32-x64-msvc": "4.13.0", "fsevents": "~2.3.2" } }, @@ -4449,9 +4450,9 @@ } }, "node_modules/socket.io-client": { - "version": "4.7.4", - "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.7.4.tgz", - "integrity": "sha512-wh+OkeF0rAVCrABWQBaEjLfb7DVPotMbu0cgWgyR0v6eA4EoVnAwcIeIbcdTE3GT/H3kbdLl7OoH2+asoDRIIg==", + "version": "4.7.5", + "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.7.5.tgz", + "integrity": "sha512-sJ/tqHOCe7Z50JCBCXrsY3I2k03iOiUe+tj1OmKeD2lXPiGH/RUCdTZFoqVyN7l1MnpIzPrGtLcijffmeouNlQ==", "dev": true, "dependencies": { "@socket.io/component-emitter": "~3.1.0", @@ -4476,19 +4477,10 @@ "node": ">=10.0.0" } }, - "node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/source-map-js": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", - "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", + "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", "dev": true, "engines": { "node": ">=0.10.0" @@ -4886,14 +4878,14 @@ } }, "node_modules/vite": { - "version": "5.1.4", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.1.4.tgz", - "integrity": "sha512-n+MPqzq+d9nMVTKyewqw6kSt+R3CkvF9QAKY8obiQn8g1fwTscKxyfaYnC632HtBXAQGc1Yjomphwn1dtwGAHg==", + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.2.2.tgz", + "integrity": "sha512-FWZbz0oSdLq5snUI0b6sULbz58iXFXdvkZfZWR/F0ZJuKTSPO7v72QPXt6KqYeMFb0yytNp6kZosxJ96Nr/wDQ==", "dev": true, "dependencies": { - "esbuild": "^0.19.3", - "postcss": "^8.4.35", - "rollup": "^4.2.0" + "esbuild": "^0.20.1", + "postcss": "^8.4.36", + "rollup": "^4.13.0" }, "bin": { "vite": "bin/vite.js" @@ -4941,9 +4933,9 @@ } }, "node_modules/vite-node": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-1.3.1.tgz", - "integrity": "sha512-azbRrqRxlWTJEVbzInZCTchx0X69M/XPTCz4H+TLvlTcR/xH/3hkRqhOakT41fMJCMzXTu4UvegkZiEoJAWvng==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-1.4.0.tgz", + "integrity": "sha512-VZDAseqjrHgNd4Kh8icYHWzTKSCZMhia7GyHfhtzLW33fZlG9SwsB6CEhgyVOWkJfJ2pFLrp/Gj1FSfAiqH9Lw==", "dev": true, "dependencies": { "cac": "^6.7.14", @@ -4977,16 +4969,16 @@ } }, "node_modules/vitest": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-1.3.1.tgz", - "integrity": "sha512-/1QJqXs8YbCrfv/GPQ05wAZf2eakUPLPa18vkJAKE7RXOKfVHqMZZ1WlTjiwl6Gcn65M5vpNUB6EFLnEdRdEXQ==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-1.4.0.tgz", + "integrity": "sha512-gujzn0g7fmwf83/WzrDTnncZt2UiXP41mHuFYFrdwaLRVQ6JYQEiME2IfEjU3vcFL3VKa75XhI3lFgn+hfVsQw==", "dev": true, "dependencies": { - "@vitest/expect": "1.3.1", - "@vitest/runner": "1.3.1", - "@vitest/snapshot": "1.3.1", - "@vitest/spy": "1.3.1", - "@vitest/utils": "1.3.1", + "@vitest/expect": "1.4.0", + "@vitest/runner": "1.4.0", + "@vitest/snapshot": "1.4.0", + "@vitest/spy": "1.4.0", + "@vitest/utils": "1.4.0", "acorn-walk": "^8.3.2", "chai": "^4.3.10", "debug": "^4.3.4", @@ -5000,7 +4992,7 @@ "tinybench": "^2.5.1", "tinypool": "^0.8.2", "vite": "^5.0.0", - "vite-node": "1.3.1", + "vite-node": "1.4.0", "why-is-node-running": "^2.2.2" }, "bin": { @@ -5015,8 +5007,8 @@ "peerDependencies": { "@edge-runtime/vm": "*", "@types/node": "^18.0.0 || >=20.0.0", - "@vitest/browser": "1.3.1", - "@vitest/ui": "1.3.1", + "@vitest/browser": "1.4.0", + "@vitest/ui": "1.4.0", "happy-dom": "*", "jsdom": "*" }, diff --git a/e2e/package.json b/e2e/package.json index 99d3a91cd..fef02501f 100644 --- a/e2e/package.json +++ b/e2e/package.json @@ -1,6 +1,6 @@ { "name": "immich-e2e", - "version": "1.98.2", + "version": "1.99.0", "description": "", "main": "index.js", "type": "module", diff --git a/e2e/src/api/specs/audit.e2e-spec.ts b/e2e/src/api/specs/audit.e2e-spec.ts index 2b551fd24..ec8c3799c 100644 --- a/e2e/src/api/specs/audit.e2e-spec.ts +++ b/e2e/src/api/specs/audit.e2e-spec.ts @@ -12,7 +12,8 @@ describe('/audit', () => { admin = await utils.adminSetup(); }); - describe('GET :/file-report', () => { + // TODO: Enable these tests again once #7436 is resolved as these were flaky + describe.skip('GET :/file-report', () => { it('excludes assets without issues from report', async () => { const [trashedAsset, archivedAsset] = await Promise.all([ utils.createAsset(admin.accessToken), diff --git a/machine-learning/pyproject.toml b/machine-learning/pyproject.toml index c0e549af5..6c55ada1c 100644 --- a/machine-learning/pyproject.toml +++ b/machine-learning/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "machine-learning" -version = "1.98.2" +version = "1.99.0" description = "" authors = ["Hau Tran "] readme = "README.md" diff --git a/mobile/android/fastlane/Fastfile b/mobile/android/fastlane/Fastfile index a11d60267..c046ee56a 100644 --- a/mobile/android/fastlane/Fastfile +++ b/mobile/android/fastlane/Fastfile @@ -35,8 +35,8 @@ platform :android do task: 'bundle', build_type: 'Release', properties: { - "android.injected.version.code" => 128, - "android.injected.version.name" => "1.98.2", + "android.injected.version.code" => 129, + "android.injected.version.name" => "1.99.0", } ) upload_to_play_store(skip_upload_apk: true, skip_upload_images: true, skip_upload_screenshots: true, aab: '../build/app/outputs/bundle/release/app-release.aab') diff --git a/mobile/android/fastlane/report.xml b/mobile/android/fastlane/report.xml index 0dcf80996..4afb67dd1 100644 --- a/mobile/android/fastlane/report.xml +++ b/mobile/android/fastlane/report.xml @@ -5,17 +5,17 @@ - + - + - + diff --git a/mobile/assets/i18n/en-US.json b/mobile/assets/i18n/en-US.json index 9da867386..3bebcb2b1 100644 --- a/mobile/assets/i18n/en-US.json +++ b/mobile/assets/i18n/en-US.json @@ -190,6 +190,7 @@ "exif_bottom_sheet_location": "LOCATION", "exif_bottom_sheet_location_add": "Add a location", "exif_bottom_sheet_people": "PEOPLE", + "exif_bottom_sheet_person_add_person": "Add name", "experimental_settings_new_asset_list_subtitle": "Work in progress", "experimental_settings_new_asset_list_title": "Enable experimental photo grid", "experimental_settings_subtitle": "Use at your own risk!", diff --git a/mobile/ios/Runner.xcodeproj/project.pbxproj b/mobile/ios/Runner.xcodeproj/project.pbxproj index 5f44646ec..d6056f3f1 100644 --- a/mobile/ios/Runner.xcodeproj/project.pbxproj +++ b/mobile/ios/Runner.xcodeproj/project.pbxproj @@ -383,7 +383,7 @@ CODE_SIGN_ENTITLEMENTS = Runner/RunnerProfile.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 144; + CURRENT_PROJECT_VERSION = 145; DEVELOPMENT_TEAM = 2F67MQ8R79; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; @@ -525,7 +525,7 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 144; + CURRENT_PROJECT_VERSION = 145; DEVELOPMENT_TEAM = 2F67MQ8R79; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; @@ -553,7 +553,7 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 144; + CURRENT_PROJECT_VERSION = 145; DEVELOPMENT_TEAM = 2F67MQ8R79; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; diff --git a/mobile/ios/Runner/Info.plist b/mobile/ios/Runner/Info.plist index a750a446b..9570af99d 100644 --- a/mobile/ios/Runner/Info.plist +++ b/mobile/ios/Runner/Info.plist @@ -55,11 +55,11 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.98.2 + 1.99.0 CFBundleSignature ???? CFBundleVersion - 144 + 145 FLTEnableImpeller ITSAppUsesNonExemptEncryption diff --git a/mobile/ios/fastlane/Fastfile b/mobile/ios/fastlane/Fastfile index 00cbeb8ac..a2ad017af 100644 --- a/mobile/ios/fastlane/Fastfile +++ b/mobile/ios/fastlane/Fastfile @@ -19,7 +19,7 @@ platform :ios do desc "iOS Beta" lane :beta do increment_version_number( - version_number: "1.98.2" + version_number: "1.99.0" ) increment_build_number( build_number: latest_testflight_build_number + 1, diff --git a/mobile/ios/fastlane/report.xml b/mobile/ios/fastlane/report.xml index f1e83434b..2b91e3faf 100644 --- a/mobile/ios/fastlane/report.xml +++ b/mobile/ios/fastlane/report.xml @@ -5,32 +5,32 @@ - + - + - + - + - + - + diff --git a/mobile/lib/modules/asset_viewer/ui/exif_sheet/exif_map.dart b/mobile/lib/modules/asset_viewer/ui/exif_sheet/exif_map.dart index 6c0050aee..69644e678 100644 --- a/mobile/lib/modules/asset_viewer/ui/exif_sheet/exif_map.dart +++ b/mobile/lib/modules/asset_viewer/ui/exif_sheet/exif_map.dart @@ -64,7 +64,7 @@ class ExifMap extends StatelessWidget { } return Padding( - padding: const EdgeInsets.symmetric(vertical: 16.0), + padding: const EdgeInsets.symmetric(vertical: 8.0), child: LayoutBuilder( builder: (context, constraints) { return MapThumbnail( diff --git a/mobile/lib/modules/search/ui/curated_people_row.dart b/mobile/lib/modules/search/ui/curated_people_row.dart index 049c1ce46..8af610ad1 100644 --- a/mobile/lib/modules/search/ui/curated_people_row.dart +++ b/mobile/lib/modules/search/ui/curated_people_row.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:easy_localization/easy_localization.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/modules/search/models/curated_content.dart'; import 'package:immich_mobile/modules/search/ui/thumbnail_with_info.dart'; @@ -23,7 +24,7 @@ class CuratedPeopleRow extends StatelessWidget { @override Widget build(BuildContext context) { - const imageSize = 80.0; + const imageSize = 70.0; // Guard empty [content] if (content.isEmpty) { @@ -82,11 +83,11 @@ class CuratedPeopleRow extends StatelessWidget { child: Padding( padding: const EdgeInsets.only(top: 8.0), child: Text( - "Add name", + "exif_bottom_sheet_person_add_person", style: context.textTheme.labelLarge?.copyWith( color: context.primaryColor, ), - ), + ).tr(), ), ) else diff --git a/mobile/openapi/README.md b/mobile/openapi/README.md index 938293568..3b605abfc 100644 --- a/mobile/openapi/README.md +++ b/mobile/openapi/README.md @@ -3,7 +3,7 @@ Immich API This Dart package is automatically generated by the [OpenAPI Generator](https://openapi-generator.tech) project: -- API version: 1.98.2 +- API version: 1.99.0 - Build package: org.openapitools.codegen.languages.DartClientCodegen ## Requirements diff --git a/mobile/pubspec.yaml b/mobile/pubspec.yaml index a566d1aa9..785d338da 100644 --- a/mobile/pubspec.yaml +++ b/mobile/pubspec.yaml @@ -2,7 +2,7 @@ name: immich_mobile description: Immich - selfhosted backup media file on mobile phone publish_to: 'none' -version: 1.98.2+128 +version: 1.99.0+129 isar_version: &isar_version 3.1.0+1 environment: diff --git a/open-api/immich-openapi-specs.json b/open-api/immich-openapi-specs.json index f50abdffc..d04b91aa8 100644 --- a/open-api/immich-openapi-specs.json +++ b/open-api/immich-openapi-specs.json @@ -6538,7 +6538,7 @@ "info": { "title": "Immich", "description": "Immich API", - "version": "1.98.2", + "version": "1.99.0", "contact": {} }, "tags": [], diff --git a/open-api/typescript-sdk/package-lock.json b/open-api/typescript-sdk/package-lock.json index 0f1926996..30b2d699b 100644 --- a/open-api/typescript-sdk/package-lock.json +++ b/open-api/typescript-sdk/package-lock.json @@ -1,12 +1,12 @@ { "name": "@immich/sdk", - "version": "1.98.2", + "version": "1.99.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@immich/sdk", - "version": "1.98.2", + "version": "1.99.0", "license": "GNU Affero General Public License version 3", "dependencies": { "@oazapfts/runtime": "^1.0.2" @@ -22,9 +22,9 @@ "integrity": "sha512-V33FjR6V+AkGRWYQW3XPm5BLn2loGl2ujSeja1TzdjjEn2zjGgl3ve0dcFf/jEwPZEOqQZl6YwIgIB/clXVqWw==" }, "node_modules/@types/node": { - "version": "20.11.25", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.25.tgz", - "integrity": "sha512-TBHyJxk2b7HceLVGFcpAUjsa5zIdsPWlR6XHfyGzd0SFu+/NFgQgMAl96MSDZgQDvJAvV6BKsFOrt6zIL09JDw==", + "version": "20.11.28", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.28.tgz", + "integrity": "sha512-M/GPWVS2wLkSkNHVeLkrF2fD5Lx5UC4PxA0uZcKc6QqbIQUJyW1jVjueJYi1z8n0I5PxYrtpnPnWglE+y9A0KA==", "dev": true, "dependencies": { "undici-types": "~5.26.4" diff --git a/open-api/typescript-sdk/package.json b/open-api/typescript-sdk/package.json index 4b04ee7c2..c9a7b930d 100644 --- a/open-api/typescript-sdk/package.json +++ b/open-api/typescript-sdk/package.json @@ -1,6 +1,6 @@ { "name": "@immich/sdk", - "version": "1.98.2", + "version": "1.99.0", "description": "Auto-generated TypeScript SDK for the Immich API", "type": "module", "main": "./build/index.js", diff --git a/open-api/typescript-sdk/src/fetch-client.ts b/open-api/typescript-sdk/src/fetch-client.ts index 00434aaba..883456403 100644 --- a/open-api/typescript-sdk/src/fetch-client.ts +++ b/open-api/typescript-sdk/src/fetch-client.ts @@ -1,6 +1,6 @@ /** * Immich - * 1.98.2 + * 1.99.0 * DO NOT MODIFY - This file has been generated using oazapfts. * See https://www.npmjs.com/package/oazapfts */ diff --git a/renovate.json b/renovate.json index 7cb136b1a..afa68011d 100644 --- a/renovate.json +++ b/renovate.json @@ -4,33 +4,28 @@ "minimumReleaseAge": "5 days", "packageRules": [ { - "matchFileNames": ["cli/**"], - "groupName": "@immich/cli", - "matchUpdateTypes": ["minor", "patch"], - "schedule": "on tuesday" - }, - { - "matchFileNames": ["docs/**"], - "groupName": "docs", - "matchUpdateTypes": ["minor", "patch"], - "schedule": "on tuesday" - }, - { - "matchFileNames": ["mobile/**"], - "groupName": "mobile", - "matchUpdateTypes": ["minor", "patch"], - "schedule": "on tuesday" - }, - { - "matchFileNames": ["server/**"], - "groupName": "server", + "matchFileNames": [ + "cli/**", + "docs/**", + "e2e/**", + "open-api/**", + "server/**", + "web/**" + ], + "groupName": "typescript-projects", "matchUpdateTypes": ["minor", "patch"], "excludePackagePrefixes": ["exiftool", "reflect-metadata"], "schedule": "on tuesday" }, { - "matchFileNames": ["open-api/**"], - "groupName": "open-api", + "matchFileNames": ["machine-learning/**"], + "groupName": "machine-learning", + "rangeStrategy": "in-range-only", + "schedule": "on tuesday" + }, + { + "matchFileNames": ["mobile/**"], + "groupName": "mobile", "matchUpdateTypes": ["minor", "patch"], "schedule": "on tuesday" }, @@ -45,18 +40,6 @@ "matchPackagePrefixes": ["@sveltejs"], "schedule": "on tuesday" }, - { - "matchFileNames": ["web/**"], - "groupName": "web", - "matchUpdateTypes": ["minor", "patch"], - "schedule": "on tuesday" - }, - { - "matchFileNames": ["machine-learning/**"], - "groupName": "machine-learning", - "rangeStrategy": "in-range-only", - "schedule": "on tuesday" - }, { "matchFileNames": [".github/**"], "groupName": "github-actions", @@ -81,9 +64,6 @@ } ], "ignorePaths": ["mobile/openapi/pubspec.yaml"], - "ignoreDeps": [ - "http", - "intl" - ], + "ignoreDeps": ["http", "intl"], "labels": ["dependencies", "renovate"] } diff --git a/server/.eslintrc.js b/server/.eslintrc.js index 3673add3c..75138ff23 100644 --- a/server/.eslintrc.js +++ b/server/.eslintrc.js @@ -33,5 +33,6 @@ module.exports = { '@typescript-eslint/require-await': 'error', curly: 2, 'prettier/prettier': 0, + 'no-restricted-imports': ['error', { patterns: [{ group: ['.*'], message: 'Relative imports are not allowed.' }] }], }, }; diff --git a/server/Dockerfile b/server/Dockerfile index 2c66c0af3..be453bba8 100644 --- a/server/Dockerfile +++ b/server/Dockerfile @@ -1,5 +1,5 @@ # dev build -FROM ghcr.io/immich-app/base-server-dev:20240312@sha256:3cb168dd87a2b412b25c512ec638a1e7f362e1d3eb8dd19a38d92d4a7c47999c as dev +FROM ghcr.io/immich-app/base-server-dev:20240319@sha256:9c9492d59b51a0c340ea3f61a1c9a483122b86b008c7fbe1759d7b858c5e6af5 as dev RUN apt-get install --no-install-recommends -yqq tini WORKDIR /usr/src/app @@ -24,7 +24,7 @@ RUN npm prune --omit=dev --omit=optional COPY --from=dev /usr/src/app/node_modules/@img ./node_modules/@img # web build -FROM node:iron-alpine3.18@sha256:a02826c7340c37a29179152723190bcc3044f933c925f3c2d78abb20f794de3f as web +FROM node:iron-alpine3.18@sha256:876514790dabd49fae7d9c4dfbba027954bd91d8e7d36da76334466533bc6b0c as web WORKDIR /usr/src/open-api/typescript-sdk COPY open-api/typescript-sdk/package*.json open-api/typescript-sdk/tsconfig*.json ./ @@ -40,7 +40,7 @@ RUN npm run build # prod build -FROM ghcr.io/immich-app/base-server-prod:20240312@sha256:8359fb1acc56580f2b4835e273293fdaa99d273b210892e1485fc6f1e47cf2bb +FROM ghcr.io/immich-app/base-server-prod:20240319@sha256:6bfcb2f2b84d3070be95ab09ba614e2ff3e832e566a283f516a276a8ae6a147b WORKDIR /usr/src/app ENV NODE_ENV=production \ diff --git a/server/e2e/client/asset-api.ts b/server/e2e/client/asset-api.ts index 63d439586..8f30e1f4a 100644 --- a/server/e2e/client/asset-api.ts +++ b/server/e2e/client/asset-api.ts @@ -1,4 +1,4 @@ -import { AssetResponseDto } from '@app/domain'; +import { AssetResponseDto } from 'src/domain/asset/response-dto/asset-response.dto'; import request from 'supertest'; export const assetApi = { diff --git a/server/e2e/client/auth-api.ts b/server/e2e/client/auth-api.ts index e89e6d057..46b21fb98 100644 --- a/server/e2e/client/auth-api.ts +++ b/server/e2e/client/auth-api.ts @@ -1,6 +1,7 @@ -import { LoginResponseDto, UserResponseDto } from '@app/domain'; -import { adminSignupStub, loginResponseStub, loginStub } from '@test'; +import { LoginResponseDto } from 'src/domain/auth/auth.dto'; +import { UserResponseDto } from 'src/domain/user/response-dto/user-response.dto'; import request from 'supertest'; +import { adminSignupStub, loginResponseStub, loginStub } from 'test/fixtures/auth.stub'; export const authApi = { adminSignUp: async (server: any) => { diff --git a/server/e2e/client/index.ts b/server/e2e/client/index.ts index b4aa2a141..41418ddcc 100644 --- a/server/e2e/client/index.ts +++ b/server/e2e/client/index.ts @@ -1,6 +1,6 @@ -import { assetApi } from './asset-api'; -import { authApi } from './auth-api'; -import { libraryApi } from './library-api'; +import { assetApi } from 'e2e/client/asset-api'; +import { authApi } from 'e2e/client/auth-api'; +import { libraryApi } from 'e2e/client/library-api'; export const api = { authApi, diff --git a/server/e2e/client/library-api.ts b/server/e2e/client/library-api.ts index 070683eb0..90b1b7451 100644 --- a/server/e2e/client/library-api.ts +++ b/server/e2e/client/library-api.ts @@ -1,4 +1,4 @@ -import { CreateLibraryDto, LibraryResponseDto, ScanLibraryDto } from '@app/domain'; +import { CreateLibraryDto, LibraryResponseDto, ScanLibraryDto } from 'src/domain/library/library.dto'; import request from 'supertest'; export const libraryApi = { diff --git a/server/e2e/jobs/jest-e2e.json b/server/e2e/jobs/jest-e2e.json index 333174c5a..b9a238d83 100644 --- a/server/e2e/jobs/jest-e2e.json +++ b/server/e2e/jobs/jest-e2e.json @@ -16,9 +16,7 @@ ], "coverageDirectory": "./coverage", "moduleNameMapper": { - "^@test(|/.*)$": "/test/$1", - "^@app/immich(|/.*)$": "/src/immich/$1", - "^@app/infra(|/.*)$": "/src/infra/$1", - "^@app/domain(|/.*)$": "/src/domain/$1" + "^test(|/.*)$": "/test/$1", + "^src(|/.*)$": "/src/$1" } } diff --git a/server/e2e/jobs/specs/library-watcher.e2e-spec.ts b/server/e2e/jobs/specs/library-watcher.e2e-spec.ts index 5f05d736b..a146bc856 100644 --- a/server/e2e/jobs/specs/library-watcher.e2e-spec.ts +++ b/server/e2e/jobs/specs/library-watcher.e2e-spec.ts @@ -1,15 +1,19 @@ -import { LibraryResponseDto, LibraryService, LoginResponseDto, StorageEventType } from '@app/domain'; -import { AssetType, LibraryType } from '@app/infra/entities'; +import { api } from 'e2e/client'; import fs from 'node:fs/promises'; import path from 'node:path'; +import { LoginResponseDto } from 'src/domain/auth/auth.dto'; +import { LibraryResponseDto } from 'src/domain/library/library.dto'; +import { LibraryService } from 'src/domain/library/library.service'; +import { AssetType } from 'src/infra/entities/asset.entity'; +import { LibraryType } from 'src/infra/entities/library.entity'; +import { StorageEventType } from 'src/interfaces/storage.repository'; import { IMMICH_TEST_ASSET_PATH, IMMICH_TEST_ASSET_TEMP_PATH, restoreTempFolder, testApp, waitForEvent, -} from '../../../src/test-utils/utils'; -import { api } from '../../client'; +} from 'src/test-utils/utils'; describe(`Library watcher (e2e)`, () => { let server: any; diff --git a/server/e2e/jobs/specs/library.e2e-spec.ts b/server/e2e/jobs/specs/library.e2e-spec.ts index b068ae9e8..e7089f002 100644 --- a/server/e2e/jobs/specs/library.e2e-spec.ts +++ b/server/e2e/jobs/specs/library.e2e-spec.ts @@ -1,17 +1,13 @@ -import { LoginResponseDto } from '@app/domain'; -import { LibraryController } from '@app/immich'; -import { LibraryType } from '@app/infra/entities'; -import { errorStub, uuidStub } from '@test/fixtures'; -import * as fs from 'node:fs'; +import { api } from 'e2e/client'; +import fs from 'node:fs'; +import { LibraryController } from 'src/controllers/library.controller'; +import { LoginResponseDto } from 'src/domain/auth/auth.dto'; +import { LibraryType } from 'src/infra/entities/library.entity'; +import { IMMICH_TEST_ASSET_PATH, IMMICH_TEST_ASSET_TEMP_PATH, restoreTempFolder, testApp } from 'src/test-utils/utils'; import request from 'supertest'; +import { errorStub } from 'test/fixtures/error.stub'; +import { uuidStub } from 'test/fixtures/uuid.stub'; import { utimes } from 'utimes'; -import { - IMMICH_TEST_ASSET_PATH, - IMMICH_TEST_ASSET_TEMP_PATH, - restoreTempFolder, - testApp, -} from '../../../src/test-utils/utils'; -import { api } from '../../client'; describe(`${LibraryController.name} (e2e)`, () => { let server: any; diff --git a/server/package-lock.json b/server/package-lock.json index 324e76a62..079b8db53 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -1,12 +1,12 @@ { "name": "immich", - "version": "1.98.2", + "version": "1.99.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "immich", - "version": "1.98.2", + "version": "1.99.0", "license": "GNU Affero General Public License version 3", "dependencies": { "@babel/runtime": "^7.22.11", @@ -22,7 +22,7 @@ "@nestjs/swagger": "^7.1.8", "@nestjs/typeorm": "^10.0.0", "@nestjs/websockets": "^10.2.2", - "@opentelemetry/auto-instrumentations-node": "^0.42.0", + "@opentelemetry/auto-instrumentations-node": "^0.43.0", "@opentelemetry/exporter-prometheus": "^0.49.0", "@opentelemetry/sdk-node": "^0.49.0", "@socket.io/postgres-adapter": "^0.3.1", @@ -81,7 +81,7 @@ "@types/mock-fs": "^4.13.1", "@types/multer": "^1.4.7", "@types/node": "^20.5.7", - "@types/sharp": "^0.31.1", + "@types/sharp": "^0.32.0", "@types/supertest": "^6.0.0", "@types/ua-parser-js": "^0.7.36", "@typescript-eslint/eslint-plugin": "^7.0.0", @@ -1719,9 +1719,12 @@ } }, "node_modules/@immich/cli": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/@immich/cli/-/cli-2.0.7.tgz", - "integrity": "sha512-36bpL7SCnbWuaHwuvVmV0iw2dgxX6umk3DhQ5rThJ6C9vOVZs8WY2zMU0voTATlEPoJkpwcQTOMLKFLTPL5OJw==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@immich/cli/-/cli-2.1.0.tgz", + "integrity": "sha512-3s/8+Js1dAwibzgaRtZ+bsAL9nOtvoEX/qMlOTgbgLf/lT96M88QScqhb+YrU2l3WBugtts6xW76XQTrWGXcmw==", + "dependencies": { + "lodash-es": "^4.17.21" + }, "bin": { "immich": "dist/index.js" }, @@ -2909,25 +2912,25 @@ } }, "node_modules/@opentelemetry/auto-instrumentations-node": { - "version": "0.42.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/auto-instrumentations-node/-/auto-instrumentations-node-0.42.0.tgz", - "integrity": "sha512-fxcB7My5QTVfX6kBH4r5OFduGSxdpROgyIu7CqClp1psFHfVaBMQd4lbK2u+39K5kbjzJT2OaUP8yQuAvKJqBg==", + "version": "0.43.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/auto-instrumentations-node/-/auto-instrumentations-node-0.43.0.tgz", + "integrity": "sha512-2WvHUSi/QVeVG8ObPD0Ls6WevfIbQjspxIQRuHaQFWXhmEwy/MsEcoQUjbNKXwO5516aS04GTydKEoRKsMwhdA==", "dependencies": { "@opentelemetry/instrumentation": "^0.49.1", "@opentelemetry/instrumentation-amqplib": "^0.35.0", "@opentelemetry/instrumentation-aws-lambda": "^0.39.0", - "@opentelemetry/instrumentation-aws-sdk": "^0.39.0", + "@opentelemetry/instrumentation-aws-sdk": "^0.39.1", "@opentelemetry/instrumentation-bunyan": "^0.36.0", "@opentelemetry/instrumentation-cassandra-driver": "^0.36.0", "@opentelemetry/instrumentation-connect": "^0.34.0", "@opentelemetry/instrumentation-cucumber": "^0.4.0", "@opentelemetry/instrumentation-dataloader": "^0.7.0", "@opentelemetry/instrumentation-dns": "^0.34.0", - "@opentelemetry/instrumentation-express": "^0.36.0", + "@opentelemetry/instrumentation-express": "^0.36.1", "@opentelemetry/instrumentation-fastify": "^0.34.0", "@opentelemetry/instrumentation-fs": "^0.10.0", "@opentelemetry/instrumentation-generic-pool": "^0.34.0", - "@opentelemetry/instrumentation-graphql": "^0.38.0", + "@opentelemetry/instrumentation-graphql": "^0.38.1", "@opentelemetry/instrumentation-grpc": "^0.49.1", "@opentelemetry/instrumentation-hapi": "^0.35.0", "@opentelemetry/instrumentation-http": "^0.49.1", @@ -2936,13 +2939,13 @@ "@opentelemetry/instrumentation-koa": "^0.38.0", "@opentelemetry/instrumentation-lru-memoizer": "^0.35.0", "@opentelemetry/instrumentation-memcached": "^0.34.0", - "@opentelemetry/instrumentation-mongodb": "^0.40.0", + "@opentelemetry/instrumentation-mongodb": "^0.41.0", "@opentelemetry/instrumentation-mongoose": "^0.36.0", "@opentelemetry/instrumentation-mysql": "^0.36.0", "@opentelemetry/instrumentation-mysql2": "^0.36.0", "@opentelemetry/instrumentation-nestjs-core": "^0.35.0", "@opentelemetry/instrumentation-net": "^0.34.0", - "@opentelemetry/instrumentation-pg": "^0.39.0", + "@opentelemetry/instrumentation-pg": "^0.39.1", "@opentelemetry/instrumentation-pino": "^0.36.0", "@opentelemetry/instrumentation-redis": "^0.37.0", "@opentelemetry/instrumentation-redis-4": "^0.37.0", @@ -3464,9 +3467,9 @@ } }, "node_modules/@opentelemetry/instrumentation-mongodb": { - "version": "0.40.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mongodb/-/instrumentation-mongodb-0.40.0.tgz", - "integrity": "sha512-ldlJUW/1UlnGtIWBt7fIUl+7+TGOKxIU+0Js5ukpXfQc07ENYFeck5TdbFjvYtF8GppPErnsZJiFiRdYm6Pv/Q==", + "version": "0.41.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mongodb/-/instrumentation-mongodb-0.41.0.tgz", + "integrity": "sha512-DlSH0oyEuTW5gprCUppb0Qe3pK3cpUUFW5eTmayWNyICI1LFunwtcrULTNv6UiThD/V5ykAf/GGGEa7KFAmkog==", "dependencies": { "@opentelemetry/instrumentation": "^0.49.1", "@opentelemetry/sdk-metrics": "^1.9.1", @@ -4644,9 +4647,9 @@ } }, "node_modules/@types/lodash": { - "version": "4.14.202", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.202.tgz", - "integrity": "sha512-OvlIYQK9tNneDlS0VN54LLd5uiPCBOp7gS5Z0f1mjoJYBrtStzgmJBxONW3U6OZqdtNzZPmn9BS/7WI7BFFcFQ==", + "version": "4.17.0", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.0.tgz", + "integrity": "sha512-t7dhREVv6dbNj0q17X12j7yDG4bD/DHYX7o5/DbDxobP0HnGPgpRz2Ej77aL7TZT3DSw13fqUTj8J4mMnqa7WA==", "dev": true }, "node_modules/@types/luxon": { @@ -4705,9 +4708,9 @@ } }, "node_modules/@types/node": { - "version": "20.11.25", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.25.tgz", - "integrity": "sha512-TBHyJxk2b7HceLVGFcpAUjsa5zIdsPWlR6XHfyGzd0SFu+/NFgQgMAl96MSDZgQDvJAvV6BKsFOrt6zIL09JDw==", + "version": "20.11.28", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.28.tgz", + "integrity": "sha512-M/GPWVS2wLkSkNHVeLkrF2fD5Lx5UC4PxA0uZcKc6QqbIQUJyW1jVjueJYi1z8n0I5PxYrtpnPnWglE+y9A0KA==", "dependencies": { "undici-types": "~5.26.4" } @@ -4838,12 +4841,13 @@ } }, "node_modules/@types/sharp": { - "version": "0.31.1", - "resolved": "https://registry.npmjs.org/@types/sharp/-/sharp-0.31.1.tgz", - "integrity": "sha512-5nWwamN9ZFHXaYEincMSuza8nNfOof8nmO+mcI+Agx1uMUk4/pQnNIcix+9rLPXzKrm1pS34+6WRDbDV0Jn7ag==", + "version": "0.32.0", + "resolved": "https://registry.npmjs.org/@types/sharp/-/sharp-0.32.0.tgz", + "integrity": "sha512-OOi3kL+FZDnPhVzsfD37J88FNeZh6gQsGcLc95NbeURRGvmSjeXiDcyWzF2o3yh/gQAUn2uhh/e+CPCa5nwAxw==", + "deprecated": "This is a stub types definition. sharp provides its own type definitions, so you do not need this installed.", "dev": true, "dependencies": { - "@types/node": "*" + "sharp": "*" } }, "node_modules/@types/shimmer": { @@ -4941,16 +4945,16 @@ "dev": true }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.1.1.tgz", - "integrity": "sha512-zioDz623d0RHNhvx0eesUmGfIjzrk18nSBC8xewepKXbBvN/7c1qImV7Hg8TI1URTxKax7/zxfxj3Uph8Chcuw==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.2.0.tgz", + "integrity": "sha512-mdekAHOqS9UjlmyF/LSs6AIEvfceV749GFxoBAjwAv0nkevfKHWQFDMcBZWUiIC5ft6ePWivXoS36aKQ0Cy3sw==", "dev": true, "dependencies": { "@eslint-community/regexpp": "^4.5.1", - "@typescript-eslint/scope-manager": "7.1.1", - "@typescript-eslint/type-utils": "7.1.1", - "@typescript-eslint/utils": "7.1.1", - "@typescript-eslint/visitor-keys": "7.1.1", + "@typescript-eslint/scope-manager": "7.2.0", + "@typescript-eslint/type-utils": "7.2.0", + "@typescript-eslint/utils": "7.2.0", + "@typescript-eslint/visitor-keys": "7.2.0", "debug": "^4.3.4", "graphemer": "^1.4.0", "ignore": "^5.2.4", @@ -4976,15 +4980,15 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.1.1.tgz", - "integrity": "sha512-ZWUFyL0z04R1nAEgr9e79YtV5LbafdOtN7yapNbn1ansMyaegl2D4bL7vHoJ4HPSc4CaLwuCVas8CVuneKzplQ==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.2.0.tgz", + "integrity": "sha512-5FKsVcHTk6TafQKQbuIVkXq58Fnbkd2wDL4LB7AURN7RUOu1utVP+G8+6u3ZhEroW3DF6hyo3ZEXxgKgp4KeCg==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "7.1.1", - "@typescript-eslint/types": "7.1.1", - "@typescript-eslint/typescript-estree": "7.1.1", - "@typescript-eslint/visitor-keys": "7.1.1", + "@typescript-eslint/scope-manager": "7.2.0", + "@typescript-eslint/types": "7.2.0", + "@typescript-eslint/typescript-estree": "7.2.0", + "@typescript-eslint/visitor-keys": "7.2.0", "debug": "^4.3.4" }, "engines": { @@ -5004,13 +5008,13 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.1.1.tgz", - "integrity": "sha512-cirZpA8bJMRb4WZ+rO6+mnOJrGFDd38WoXCEI57+CYBqta8Yc8aJym2i7vyqLL1vVYljgw0X27axkUXz32T8TA==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.2.0.tgz", + "integrity": "sha512-Qh976RbQM/fYtjx9hs4XkayYujB/aPwglw2choHmf3zBjB4qOywWSdt9+KLRdHubGcoSwBnXUH2sR3hkyaERRg==", "dev": true, "dependencies": { - "@typescript-eslint/types": "7.1.1", - "@typescript-eslint/visitor-keys": "7.1.1" + "@typescript-eslint/types": "7.2.0", + "@typescript-eslint/visitor-keys": "7.2.0" }, "engines": { "node": "^16.0.0 || >=18.0.0" @@ -5021,13 +5025,13 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.1.1.tgz", - "integrity": "sha512-5r4RKze6XHEEhlZnJtR3GYeCh1IueUHdbrukV2KSlLXaTjuSfeVF8mZUVPLovidCuZfbVjfhi4c0DNSa/Rdg5g==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.2.0.tgz", + "integrity": "sha512-xHi51adBHo9O9330J8GQYQwrKBqbIPJGZZVQTHHmy200hvkLZFWJIFtAG/7IYTWUyun6DE6w5InDReePJYJlJA==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "7.1.1", - "@typescript-eslint/utils": "7.1.1", + "@typescript-eslint/typescript-estree": "7.2.0", + "@typescript-eslint/utils": "7.2.0", "debug": "^4.3.4", "ts-api-utils": "^1.0.1" }, @@ -5048,9 +5052,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.1.1.tgz", - "integrity": "sha512-KhewzrlRMrgeKm1U9bh2z5aoL4s7K3tK5DwHDn8MHv0yQfWFz/0ZR6trrIHHa5CsF83j/GgHqzdbzCXJ3crx0Q==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.2.0.tgz", + "integrity": "sha512-XFtUHPI/abFhm4cbCDc5Ykc8npOKBSJePY3a3s+lwumt7XWJuzP5cZcfZ610MIPHjQjNsOLlYK8ASPaNG8UiyA==", "dev": true, "engines": { "node": "^16.0.0 || >=18.0.0" @@ -5061,13 +5065,13 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.1.1.tgz", - "integrity": "sha512-9ZOncVSfr+sMXVxxca2OJOPagRwT0u/UHikM2Rd6L/aB+kL/QAuTnsv6MeXtjzCJYb8PzrXarypSGIPx3Jemxw==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.2.0.tgz", + "integrity": "sha512-cyxS5WQQCoBwSakpMrvMXuMDEbhOo9bNHHrNcEWis6XHx6KF518tkF1wBvKIn/tpq5ZpUYK7Bdklu8qY0MsFIA==", "dev": true, "dependencies": { - "@typescript-eslint/types": "7.1.1", - "@typescript-eslint/visitor-keys": "7.1.1", + "@typescript-eslint/types": "7.2.0", + "@typescript-eslint/visitor-keys": "7.2.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -5113,17 +5117,17 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.1.1.tgz", - "integrity": "sha512-thOXM89xA03xAE0lW7alstvnyoBUbBX38YtY+zAUcpRPcq9EIhXPuJ0YTv948MbzmKh6e1AUszn5cBFK49Umqg==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.2.0.tgz", + "integrity": "sha512-YfHpnMAGb1Eekpm3XRK8hcMwGLGsnT6L+7b2XyRv6ouDuJU1tZir1GS2i0+VXRatMwSI1/UfcyPe53ADkU+IuA==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "@types/json-schema": "^7.0.12", "@types/semver": "^7.5.0", - "@typescript-eslint/scope-manager": "7.1.1", - "@typescript-eslint/types": "7.1.1", - "@typescript-eslint/typescript-estree": "7.1.1", + "@typescript-eslint/scope-manager": "7.2.0", + "@typescript-eslint/types": "7.2.0", + "@typescript-eslint/typescript-estree": "7.2.0", "semver": "^7.5.4" }, "engines": { @@ -5138,12 +5142,12 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.1.1.tgz", - "integrity": "sha512-yTdHDQxY7cSoCcAtiBzVzxleJhkGB9NncSIyMYe2+OGON1ZsP9zOPws/Pqgopa65jvknOjlk/w7ulPlZ78PiLQ==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.2.0.tgz", + "integrity": "sha512-c6EIQRHhcpl6+tO8EMR+kjkkV+ugUNXOmeASA1rlzkd8EPIriavpWoiEz1HR/VLhbVIdhqnV6E7JZm00cBDx2A==", "dev": true, "dependencies": { - "@typescript-eslint/types": "7.1.1", + "@typescript-eslint/types": "7.2.0", "eslint-visitor-keys": "^3.4.1" }, "engines": { @@ -5530,38 +5534,77 @@ "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==" }, "node_modules/archiver": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/archiver/-/archiver-7.0.0.tgz", - "integrity": "sha512-R9HM9egs8FfktSqUqyjlKmvF4U+CWNqm/2tlROV+lOFg79MLdT67ae1l3hU47pGy8twSXxHoiefMCh43w0BriQ==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/archiver/-/archiver-7.0.1.tgz", + "integrity": "sha512-ZcbTaIqJOfCc03QwD468Unz/5Ir8ATtvAHsK+FdXbDIbGfihqh9mrvdcYunQzqn4HrvWWaFyaxJhGZagaJJpPQ==", "dependencies": { - "archiver-utils": "^5.0.0", + "archiver-utils": "^5.0.2", "async": "^3.2.4", "buffer-crc32": "^1.0.0", "readable-stream": "^4.0.0", "readdir-glob": "^1.1.2", "tar-stream": "^3.0.0", - "zip-stream": "^6.0.0" + "zip-stream": "^6.0.1" }, "engines": { "node": ">= 14" } }, "node_modules/archiver-utils": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-5.0.1.tgz", - "integrity": "sha512-MMAoLdMvT/nckofX1tCLrf7uJce4jTNkiT6smA2u57AOImc1nce7mR3EDujxL5yv6/MnILuQH4sAsPtDS8kTvg==", + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-5.0.2.tgz", + "integrity": "sha512-wuLJMmIBQYCsGZgYLTy5FIB2pF6Lfb6cXMSF8Qywwk3t20zWnAi7zLcQFdKQmIB8wyZpY5ER38x08GbwtR2cLA==", "dependencies": { "glob": "^10.0.0", "graceful-fs": "^4.2.0", + "is-stream": "^2.0.1", "lazystream": "^1.0.0", "lodash": "^4.17.15", "normalize-path": "^3.0.0", - "readable-stream": "^3.6.0" + "readable-stream": "^4.0.0" }, "engines": { "node": ">= 14" } }, + "node_modules/archiver-utils/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/archiver-utils/node_modules/readable-stream": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.5.2.tgz", + "integrity": "sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==", + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, "node_modules/archiver/node_modules/buffer": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", @@ -6586,12 +6629,13 @@ "dev": true }, "node_modules/compress-commons": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-6.0.1.tgz", - "integrity": "sha512-l7occIJn8YwlCEbWUCrG6gPms9qnJTCZSaznCa5HaV+yJMH4kM8BDc7q9NyoQuoiB2O6jKgTcTeY462qw6MyHw==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-6.0.2.tgz", + "integrity": "sha512-6FqVXeETqWPoGcfzrXb37E50NP0LXT8kAMu5ooZayhWWdgEY4lBEEcbQNXtkuKQsGduxiIcI4gOTsxTmuq/bSg==", "dependencies": { "crc-32": "^1.2.0", "crc32-stream": "^6.0.0", + "is-stream": "^2.0.1", "normalize-path": "^3.0.0", "readable-stream": "^4.0.0" }, @@ -8369,9 +8413,9 @@ } }, "node_modules/geo-tz": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/geo-tz/-/geo-tz-8.0.1.tgz", - "integrity": "sha512-hpFbw3NKFOVy461NrWIt6Z6JQpGnMpYvNpvDunIrixbHsBPOnDcrfao0p+o/7gsMJnkhSYnTJ9DkyV2tXBLI8w==", + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/geo-tz/-/geo-tz-8.0.2.tgz", + "integrity": "sha512-NjEzJBzaMhO9C7lFZIsWDkVED7aLxcES3iEZOWJ97dhnDUGhEB8vhW7MaWR+2y4aWvtFV/VyuDi8Y0rUHvm4tw==", "dependencies": { "@turf/boolean-point-in-polygon": "^6.5.0", "@turf/helpers": "^6.5.0", @@ -10118,6 +10162,11 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, + "node_modules/lodash-es": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", + "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==" + }, "node_modules/lodash.camelcase": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", @@ -14473,12 +14522,12 @@ } }, "node_modules/zip-stream": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-6.0.0.tgz", - "integrity": "sha512-X0WFquRRDtL9HR9hc1OrabOP/VKJEX7gAr2geayt3b7dLgXgSXI6ucC4CphLQP/aQt2GyHIYgmXxtC+dVdghAQ==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-6.0.1.tgz", + "integrity": "sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA==", "dependencies": { "archiver-utils": "^5.0.0", - "compress-commons": "^6.0.0", + "compress-commons": "^6.0.2", "readable-stream": "^4.0.0" }, "engines": { @@ -15553,9 +15602,12 @@ "optional": true }, "@immich/cli": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/@immich/cli/-/cli-2.0.7.tgz", - "integrity": "sha512-36bpL7SCnbWuaHwuvVmV0iw2dgxX6umk3DhQ5rThJ6C9vOVZs8WY2zMU0voTATlEPoJkpwcQTOMLKFLTPL5OJw==" + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@immich/cli/-/cli-2.1.0.tgz", + "integrity": "sha512-3s/8+Js1dAwibzgaRtZ+bsAL9nOtvoEX/qMlOTgbgLf/lT96M88QScqhb+YrU2l3WBugtts6xW76XQTrWGXcmw==", + "requires": { + "lodash-es": "^4.17.21" + } }, "@ioredis/commands": { "version": "1.2.0", @@ -16357,25 +16409,25 @@ } }, "@opentelemetry/auto-instrumentations-node": { - "version": "0.42.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/auto-instrumentations-node/-/auto-instrumentations-node-0.42.0.tgz", - "integrity": "sha512-fxcB7My5QTVfX6kBH4r5OFduGSxdpROgyIu7CqClp1psFHfVaBMQd4lbK2u+39K5kbjzJT2OaUP8yQuAvKJqBg==", + "version": "0.43.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/auto-instrumentations-node/-/auto-instrumentations-node-0.43.0.tgz", + "integrity": "sha512-2WvHUSi/QVeVG8ObPD0Ls6WevfIbQjspxIQRuHaQFWXhmEwy/MsEcoQUjbNKXwO5516aS04GTydKEoRKsMwhdA==", "requires": { "@opentelemetry/instrumentation": "^0.49.1", "@opentelemetry/instrumentation-amqplib": "^0.35.0", "@opentelemetry/instrumentation-aws-lambda": "^0.39.0", - "@opentelemetry/instrumentation-aws-sdk": "^0.39.0", + "@opentelemetry/instrumentation-aws-sdk": "^0.39.1", "@opentelemetry/instrumentation-bunyan": "^0.36.0", "@opentelemetry/instrumentation-cassandra-driver": "^0.36.0", "@opentelemetry/instrumentation-connect": "^0.34.0", "@opentelemetry/instrumentation-cucumber": "^0.4.0", "@opentelemetry/instrumentation-dataloader": "^0.7.0", "@opentelemetry/instrumentation-dns": "^0.34.0", - "@opentelemetry/instrumentation-express": "^0.36.0", + "@opentelemetry/instrumentation-express": "^0.36.1", "@opentelemetry/instrumentation-fastify": "^0.34.0", "@opentelemetry/instrumentation-fs": "^0.10.0", "@opentelemetry/instrumentation-generic-pool": "^0.34.0", - "@opentelemetry/instrumentation-graphql": "^0.38.0", + "@opentelemetry/instrumentation-graphql": "^0.38.1", "@opentelemetry/instrumentation-grpc": "^0.49.1", "@opentelemetry/instrumentation-hapi": "^0.35.0", "@opentelemetry/instrumentation-http": "^0.49.1", @@ -16384,13 +16436,13 @@ "@opentelemetry/instrumentation-koa": "^0.38.0", "@opentelemetry/instrumentation-lru-memoizer": "^0.35.0", "@opentelemetry/instrumentation-memcached": "^0.34.0", - "@opentelemetry/instrumentation-mongodb": "^0.40.0", + "@opentelemetry/instrumentation-mongodb": "^0.41.0", "@opentelemetry/instrumentation-mongoose": "^0.36.0", "@opentelemetry/instrumentation-mysql": "^0.36.0", "@opentelemetry/instrumentation-mysql2": "^0.36.0", "@opentelemetry/instrumentation-nestjs-core": "^0.35.0", "@opentelemetry/instrumentation-net": "^0.34.0", - "@opentelemetry/instrumentation-pg": "^0.39.0", + "@opentelemetry/instrumentation-pg": "^0.39.1", "@opentelemetry/instrumentation-pino": "^0.36.0", "@opentelemetry/instrumentation-redis": "^0.37.0", "@opentelemetry/instrumentation-redis-4": "^0.37.0", @@ -16721,9 +16773,9 @@ } }, "@opentelemetry/instrumentation-mongodb": { - "version": "0.40.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mongodb/-/instrumentation-mongodb-0.40.0.tgz", - "integrity": "sha512-ldlJUW/1UlnGtIWBt7fIUl+7+TGOKxIU+0Js5ukpXfQc07ENYFeck5TdbFjvYtF8GppPErnsZJiFiRdYm6Pv/Q==", + "version": "0.41.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/instrumentation-mongodb/-/instrumentation-mongodb-0.41.0.tgz", + "integrity": "sha512-DlSH0oyEuTW5gprCUppb0Qe3pK3cpUUFW5eTmayWNyICI1LFunwtcrULTNv6UiThD/V5ykAf/GGGEa7KFAmkog==", "requires": { "@opentelemetry/instrumentation": "^0.49.1", "@opentelemetry/sdk-metrics": "^1.9.1", @@ -17668,9 +17720,9 @@ } }, "@types/lodash": { - "version": "4.14.202", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.202.tgz", - "integrity": "sha512-OvlIYQK9tNneDlS0VN54LLd5uiPCBOp7gS5Z0f1mjoJYBrtStzgmJBxONW3U6OZqdtNzZPmn9BS/7WI7BFFcFQ==", + "version": "4.17.0", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.0.tgz", + "integrity": "sha512-t7dhREVv6dbNj0q17X12j7yDG4bD/DHYX7o5/DbDxobP0HnGPgpRz2Ej77aL7TZT3DSw13fqUTj8J4mMnqa7WA==", "dev": true }, "@types/luxon": { @@ -17729,9 +17781,9 @@ } }, "@types/node": { - "version": "20.11.25", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.25.tgz", - "integrity": "sha512-TBHyJxk2b7HceLVGFcpAUjsa5zIdsPWlR6XHfyGzd0SFu+/NFgQgMAl96MSDZgQDvJAvV6BKsFOrt6zIL09JDw==", + "version": "20.11.28", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.28.tgz", + "integrity": "sha512-M/GPWVS2wLkSkNHVeLkrF2fD5Lx5UC4PxA0uZcKc6QqbIQUJyW1jVjueJYi1z8n0I5PxYrtpnPnWglE+y9A0KA==", "requires": { "undici-types": "~5.26.4" } @@ -17849,12 +17901,12 @@ } }, "@types/sharp": { - "version": "0.31.1", - "resolved": "https://registry.npmjs.org/@types/sharp/-/sharp-0.31.1.tgz", - "integrity": "sha512-5nWwamN9ZFHXaYEincMSuza8nNfOof8nmO+mcI+Agx1uMUk4/pQnNIcix+9rLPXzKrm1pS34+6WRDbDV0Jn7ag==", + "version": "0.32.0", + "resolved": "https://registry.npmjs.org/@types/sharp/-/sharp-0.32.0.tgz", + "integrity": "sha512-OOi3kL+FZDnPhVzsfD37J88FNeZh6gQsGcLc95NbeURRGvmSjeXiDcyWzF2o3yh/gQAUn2uhh/e+CPCa5nwAxw==", "dev": true, "requires": { - "@types/node": "*" + "sharp": "*" } }, "@types/shimmer": { @@ -17952,16 +18004,16 @@ "dev": true }, "@typescript-eslint/eslint-plugin": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.1.1.tgz", - "integrity": "sha512-zioDz623d0RHNhvx0eesUmGfIjzrk18nSBC8xewepKXbBvN/7c1qImV7Hg8TI1URTxKax7/zxfxj3Uph8Chcuw==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.2.0.tgz", + "integrity": "sha512-mdekAHOqS9UjlmyF/LSs6AIEvfceV749GFxoBAjwAv0nkevfKHWQFDMcBZWUiIC5ft6ePWivXoS36aKQ0Cy3sw==", "dev": true, "requires": { "@eslint-community/regexpp": "^4.5.1", - "@typescript-eslint/scope-manager": "7.1.1", - "@typescript-eslint/type-utils": "7.1.1", - "@typescript-eslint/utils": "7.1.1", - "@typescript-eslint/visitor-keys": "7.1.1", + "@typescript-eslint/scope-manager": "7.2.0", + "@typescript-eslint/type-utils": "7.2.0", + "@typescript-eslint/utils": "7.2.0", + "@typescript-eslint/visitor-keys": "7.2.0", "debug": "^4.3.4", "graphemer": "^1.4.0", "ignore": "^5.2.4", @@ -17971,54 +18023,54 @@ } }, "@typescript-eslint/parser": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.1.1.tgz", - "integrity": "sha512-ZWUFyL0z04R1nAEgr9e79YtV5LbafdOtN7yapNbn1ansMyaegl2D4bL7vHoJ4HPSc4CaLwuCVas8CVuneKzplQ==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.2.0.tgz", + "integrity": "sha512-5FKsVcHTk6TafQKQbuIVkXq58Fnbkd2wDL4LB7AURN7RUOu1utVP+G8+6u3ZhEroW3DF6hyo3ZEXxgKgp4KeCg==", "dev": true, "requires": { - "@typescript-eslint/scope-manager": "7.1.1", - "@typescript-eslint/types": "7.1.1", - "@typescript-eslint/typescript-estree": "7.1.1", - "@typescript-eslint/visitor-keys": "7.1.1", + "@typescript-eslint/scope-manager": "7.2.0", + "@typescript-eslint/types": "7.2.0", + "@typescript-eslint/typescript-estree": "7.2.0", + "@typescript-eslint/visitor-keys": "7.2.0", "debug": "^4.3.4" } }, "@typescript-eslint/scope-manager": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.1.1.tgz", - "integrity": "sha512-cirZpA8bJMRb4WZ+rO6+mnOJrGFDd38WoXCEI57+CYBqta8Yc8aJym2i7vyqLL1vVYljgw0X27axkUXz32T8TA==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.2.0.tgz", + "integrity": "sha512-Qh976RbQM/fYtjx9hs4XkayYujB/aPwglw2choHmf3zBjB4qOywWSdt9+KLRdHubGcoSwBnXUH2sR3hkyaERRg==", "dev": true, "requires": { - "@typescript-eslint/types": "7.1.1", - "@typescript-eslint/visitor-keys": "7.1.1" + "@typescript-eslint/types": "7.2.0", + "@typescript-eslint/visitor-keys": "7.2.0" } }, "@typescript-eslint/type-utils": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.1.1.tgz", - "integrity": "sha512-5r4RKze6XHEEhlZnJtR3GYeCh1IueUHdbrukV2KSlLXaTjuSfeVF8mZUVPLovidCuZfbVjfhi4c0DNSa/Rdg5g==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.2.0.tgz", + "integrity": "sha512-xHi51adBHo9O9330J8GQYQwrKBqbIPJGZZVQTHHmy200hvkLZFWJIFtAG/7IYTWUyun6DE6w5InDReePJYJlJA==", "dev": true, "requires": { - "@typescript-eslint/typescript-estree": "7.1.1", - "@typescript-eslint/utils": "7.1.1", + "@typescript-eslint/typescript-estree": "7.2.0", + "@typescript-eslint/utils": "7.2.0", "debug": "^4.3.4", "ts-api-utils": "^1.0.1" } }, "@typescript-eslint/types": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.1.1.tgz", - "integrity": "sha512-KhewzrlRMrgeKm1U9bh2z5aoL4s7K3tK5DwHDn8MHv0yQfWFz/0ZR6trrIHHa5CsF83j/GgHqzdbzCXJ3crx0Q==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.2.0.tgz", + "integrity": "sha512-XFtUHPI/abFhm4cbCDc5Ykc8npOKBSJePY3a3s+lwumt7XWJuzP5cZcfZ610MIPHjQjNsOLlYK8ASPaNG8UiyA==", "dev": true }, "@typescript-eslint/typescript-estree": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.1.1.tgz", - "integrity": "sha512-9ZOncVSfr+sMXVxxca2OJOPagRwT0u/UHikM2Rd6L/aB+kL/QAuTnsv6MeXtjzCJYb8PzrXarypSGIPx3Jemxw==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.2.0.tgz", + "integrity": "sha512-cyxS5WQQCoBwSakpMrvMXuMDEbhOo9bNHHrNcEWis6XHx6KF518tkF1wBvKIn/tpq5ZpUYK7Bdklu8qY0MsFIA==", "dev": true, "requires": { - "@typescript-eslint/types": "7.1.1", - "@typescript-eslint/visitor-keys": "7.1.1", + "@typescript-eslint/types": "7.2.0", + "@typescript-eslint/visitor-keys": "7.2.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -18048,27 +18100,27 @@ } }, "@typescript-eslint/utils": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.1.1.tgz", - "integrity": "sha512-thOXM89xA03xAE0lW7alstvnyoBUbBX38YtY+zAUcpRPcq9EIhXPuJ0YTv948MbzmKh6e1AUszn5cBFK49Umqg==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.2.0.tgz", + "integrity": "sha512-YfHpnMAGb1Eekpm3XRK8hcMwGLGsnT6L+7b2XyRv6ouDuJU1tZir1GS2i0+VXRatMwSI1/UfcyPe53ADkU+IuA==", "dev": true, "requires": { "@eslint-community/eslint-utils": "^4.4.0", "@types/json-schema": "^7.0.12", "@types/semver": "^7.5.0", - "@typescript-eslint/scope-manager": "7.1.1", - "@typescript-eslint/types": "7.1.1", - "@typescript-eslint/typescript-estree": "7.1.1", + "@typescript-eslint/scope-manager": "7.2.0", + "@typescript-eslint/types": "7.2.0", + "@typescript-eslint/typescript-estree": "7.2.0", "semver": "^7.5.4" } }, "@typescript-eslint/visitor-keys": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.1.1.tgz", - "integrity": "sha512-yTdHDQxY7cSoCcAtiBzVzxleJhkGB9NncSIyMYe2+OGON1ZsP9zOPws/Pqgopa65jvknOjlk/w7ulPlZ78PiLQ==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.2.0.tgz", + "integrity": "sha512-c6EIQRHhcpl6+tO8EMR+kjkkV+ugUNXOmeASA1rlzkd8EPIriavpWoiEz1HR/VLhbVIdhqnV6E7JZm00cBDx2A==", "dev": true, "requires": { - "@typescript-eslint/types": "7.1.1", + "@typescript-eslint/types": "7.2.0", "eslint-visitor-keys": "^3.4.1" } }, @@ -18382,17 +18434,17 @@ "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==" }, "archiver": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/archiver/-/archiver-7.0.0.tgz", - "integrity": "sha512-R9HM9egs8FfktSqUqyjlKmvF4U+CWNqm/2tlROV+lOFg79MLdT67ae1l3hU47pGy8twSXxHoiefMCh43w0BriQ==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/archiver/-/archiver-7.0.1.tgz", + "integrity": "sha512-ZcbTaIqJOfCc03QwD468Unz/5Ir8ATtvAHsK+FdXbDIbGfihqh9mrvdcYunQzqn4HrvWWaFyaxJhGZagaJJpPQ==", "requires": { - "archiver-utils": "^5.0.0", + "archiver-utils": "^5.0.2", "async": "^3.2.4", "buffer-crc32": "^1.0.0", "readable-stream": "^4.0.0", "readdir-glob": "^1.1.2", "tar-stream": "^3.0.0", - "zip-stream": "^6.0.0" + "zip-stream": "^6.0.1" }, "dependencies": { "buffer": { @@ -18424,16 +18476,40 @@ } }, "archiver-utils": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-5.0.1.tgz", - "integrity": "sha512-MMAoLdMvT/nckofX1tCLrf7uJce4jTNkiT6smA2u57AOImc1nce7mR3EDujxL5yv6/MnILuQH4sAsPtDS8kTvg==", + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-5.0.2.tgz", + "integrity": "sha512-wuLJMmIBQYCsGZgYLTy5FIB2pF6Lfb6cXMSF8Qywwk3t20zWnAi7zLcQFdKQmIB8wyZpY5ER38x08GbwtR2cLA==", "requires": { "glob": "^10.0.0", "graceful-fs": "^4.2.0", + "is-stream": "^2.0.1", "lazystream": "^1.0.0", "lodash": "^4.17.15", "normalize-path": "^3.0.0", - "readable-stream": "^3.6.0" + "readable-stream": "^4.0.0" + }, + "dependencies": { + "buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "requires": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "readable-stream": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.5.2.tgz", + "integrity": "sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==", + "requires": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + } + } } }, "are-we-there-yet": { @@ -19164,12 +19240,13 @@ "dev": true }, "compress-commons": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-6.0.1.tgz", - "integrity": "sha512-l7occIJn8YwlCEbWUCrG6gPms9qnJTCZSaznCa5HaV+yJMH4kM8BDc7q9NyoQuoiB2O6jKgTcTeY462qw6MyHw==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-6.0.2.tgz", + "integrity": "sha512-6FqVXeETqWPoGcfzrXb37E50NP0LXT8kAMu5ooZayhWWdgEY4lBEEcbQNXtkuKQsGduxiIcI4gOTsxTmuq/bSg==", "requires": { "crc-32": "^1.2.0", "crc32-stream": "^6.0.0", + "is-stream": "^2.0.1", "normalize-path": "^3.0.0", "readable-stream": "^4.0.0" }, @@ -20512,9 +20589,9 @@ "dev": true }, "geo-tz": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/geo-tz/-/geo-tz-8.0.1.tgz", - "integrity": "sha512-hpFbw3NKFOVy461NrWIt6Z6JQpGnMpYvNpvDunIrixbHsBPOnDcrfao0p+o/7gsMJnkhSYnTJ9DkyV2tXBLI8w==", + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/geo-tz/-/geo-tz-8.0.2.tgz", + "integrity": "sha512-NjEzJBzaMhO9C7lFZIsWDkVED7aLxcES3iEZOWJ97dhnDUGhEB8vhW7MaWR+2y4aWvtFV/VyuDi8Y0rUHvm4tw==", "requires": { "@turf/boolean-point-in-polygon": "^6.5.0", "@turf/helpers": "^6.5.0", @@ -21806,6 +21883,11 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, + "lodash-es": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", + "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==" + }, "lodash.camelcase": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", @@ -24991,12 +25073,12 @@ "dev": true }, "zip-stream": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-6.0.0.tgz", - "integrity": "sha512-X0WFquRRDtL9HR9hc1OrabOP/VKJEX7gAr2geayt3b7dLgXgSXI6ucC4CphLQP/aQt2GyHIYgmXxtC+dVdghAQ==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-6.0.1.tgz", + "integrity": "sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA==", "requires": { "archiver-utils": "^5.0.0", - "compress-commons": "^6.0.0", + "compress-commons": "^6.0.2", "readable-stream": "^4.0.0" }, "dependencies": { diff --git a/server/package.json b/server/package.json index 54f4be878..136a04a6f 100644 --- a/server/package.json +++ b/server/package.json @@ -1,6 +1,6 @@ { "name": "immich", - "version": "1.98.2", + "version": "1.99.0", "description": "", "author": "", "private": true, @@ -46,7 +46,7 @@ "@nestjs/swagger": "^7.1.8", "@nestjs/typeorm": "^10.0.0", "@nestjs/websockets": "^10.2.2", - "@opentelemetry/auto-instrumentations-node": "^0.42.0", + "@opentelemetry/auto-instrumentations-node": "^0.43.0", "@opentelemetry/exporter-prometheus": "^0.49.0", "@opentelemetry/sdk-node": "^0.49.0", "@socket.io/postgres-adapter": "^0.3.1", @@ -105,7 +105,7 @@ "@types/mock-fs": "^4.13.1", "@types/multer": "^1.4.7", "@types/node": "^20.5.7", - "@types/sharp": "^0.31.1", + "@types/sharp": "^0.32.0", "@types/supertest": "^6.0.0", "@types/ua-parser-js": "^0.7.36", "@typescript-eslint/eslint-plugin": "^7.0.0", @@ -154,16 +154,14 @@ "./src/domain/": { "branches": 75, "functions": 80, - "lines": 90, - "statements": 90 + "lines": 85, + "statements": 85 } }, "testEnvironment": "node", "moduleNameMapper": { - "^@test(|/.*)$": "/test/$1", - "^@app/immich(|/.*)$": "/src/immich/$1", - "^@app/infra(|/.*)$": "/src/infra/$1", - "^@app/domain(|/.*)$": "/src/domain/$1" + "^test(|/.*)$": "/test/$1", + "^src(|/.*)$": "/src/$1" }, "globalSetup": "/test/global-setup.js" }, diff --git a/server/src/immich-admin/commands/list-users.command.ts b/server/src/commands/list-users.command.ts similarity index 55% rename from server/src/immich-admin/commands/list-users.command.ts rename to server/src/commands/list-users.command.ts index 15ab0a240..eeda72d1c 100644 --- a/server/src/immich-admin/commands/list-users.command.ts +++ b/server/src/commands/list-users.command.ts @@ -1,6 +1,6 @@ -import { UserService } from '@app/domain'; import { Command, CommandRunner } from 'nest-commander'; -import { CLI_USER } from '../constants'; +import { UserService } from 'src/domain/user/user.service'; +import { UserEntity } from 'src/infra/entities/user.entity'; @Command({ name: 'list-users', @@ -13,7 +13,16 @@ export class ListUsersCommand extends CommandRunner { async run(): Promise { try { - const users = await this.userService.getAll(CLI_USER, true); + const users = await this.userService.getAll( + { + user: { + id: 'cli', + email: 'cli@immich.app', + isAdmin: true, + } as UserEntity, + }, + true, + ); console.dir(users); } catch (error) { console.error(error); diff --git a/server/src/immich-admin/commands/oauth-login.ts b/server/src/commands/oauth-login.ts similarity index 91% rename from server/src/immich-admin/commands/oauth-login.ts rename to server/src/commands/oauth-login.ts index 23747bf03..12562fae1 100644 --- a/server/src/immich-admin/commands/oauth-login.ts +++ b/server/src/commands/oauth-login.ts @@ -1,5 +1,5 @@ -import { SystemConfigService } from '@app/domain'; import { Command, CommandRunner } from 'nest-commander'; +import { SystemConfigService } from 'src/domain/system-config/system-config.service'; @Command({ name: 'enable-oauth-login', diff --git a/server/src/immich-admin/commands/password-login.ts b/server/src/commands/password-login.ts similarity index 92% rename from server/src/immich-admin/commands/password-login.ts rename to server/src/commands/password-login.ts index e6eea2c72..953a1293a 100644 --- a/server/src/immich-admin/commands/password-login.ts +++ b/server/src/commands/password-login.ts @@ -1,5 +1,5 @@ -import { SystemConfigService } from '@app/domain'; import { Command, CommandRunner } from 'nest-commander'; +import { SystemConfigService } from 'src/domain/system-config/system-config.service'; @Command({ name: 'enable-password-login', diff --git a/server/src/immich-admin/commands/reset-admin-password.command.ts b/server/src/commands/reset-admin-password.command.ts similarity index 90% rename from server/src/immich-admin/commands/reset-admin-password.command.ts rename to server/src/commands/reset-admin-password.command.ts index d19ddf433..ce0a897a8 100644 --- a/server/src/immich-admin/commands/reset-admin-password.command.ts +++ b/server/src/commands/reset-admin-password.command.ts @@ -1,5 +1,6 @@ -import { UserResponseDto, UserService } from '@app/domain'; import { Command, CommandRunner, InquirerService, Question, QuestionSet } from 'nest-commander'; +import { UserResponseDto } from 'src/domain/user/response-dto/user-response.dto'; +import { UserService } from 'src/domain/user/user.service'; @Command({ name: 'reset-admin-password', diff --git a/server/src/config.ts b/server/src/config.ts new file mode 100644 index 000000000..01f3f1a1c --- /dev/null +++ b/server/src/config.ts @@ -0,0 +1,73 @@ +import { RegisterQueueOptions } from '@nestjs/bullmq'; +import { ConfigModuleOptions } from '@nestjs/config'; +import { QueueOptions } from 'bullmq'; +import { RedisOptions } from 'ioredis'; +import Joi from 'joi'; +import { QueueName } from 'src/domain/job/job.constants'; +import { LogLevel } from 'src/infra/entities/system-config.entity'; + +const WHEN_DB_URL_SET = Joi.when('DB_URL', { + is: Joi.exist(), + then: Joi.string().optional(), + otherwise: Joi.string().required(), +}); + +export const immichAppConfig: ConfigModuleOptions = { + envFilePath: '.env', + isGlobal: true, + validationSchema: Joi.object({ + NODE_ENV: Joi.string().optional().valid('development', 'production', 'staging').default('development'), + LOG_LEVEL: Joi.string() + .optional() + .valid(...Object.values(LogLevel)), + + DB_USERNAME: WHEN_DB_URL_SET, + DB_PASSWORD: WHEN_DB_URL_SET, + DB_DATABASE_NAME: WHEN_DB_URL_SET, + DB_URL: Joi.string().optional(), + DB_VECTOR_EXTENSION: Joi.string().optional().valid('pgvector', 'pgvecto.rs').default('pgvecto.rs'), + + MACHINE_LEARNING_PORT: Joi.number().optional(), + MICROSERVICES_PORT: Joi.number().optional(), + IMMICH_METRICS_PORT: Joi.number().optional(), + + IMMICH_METRICS: Joi.boolean().optional().default(false), + IMMICH_HOST_METRICS: Joi.boolean().optional().default(false), + IMMICH_API_METRICS: Joi.boolean().optional().default(false), + IMMICH_IO_METRICS: Joi.boolean().optional().default(false), + }), +}; + +function parseRedisConfig(): RedisOptions { + const redisUrl = process.env.REDIS_URL; + if (redisUrl && redisUrl.startsWith('ioredis://')) { + try { + const decodedString = Buffer.from(redisUrl.slice(10), 'base64').toString(); + return JSON.parse(decodedString); + } catch (error) { + throw new Error(`Failed to decode redis options: ${error}`); + } + } + return { + host: process.env.REDIS_HOSTNAME || 'immich_redis', + port: Number.parseInt(process.env.REDIS_PORT || '6379'), + db: Number.parseInt(process.env.REDIS_DBINDEX || '0'), + username: process.env.REDIS_USERNAME || undefined, + password: process.env.REDIS_PASSWORD || undefined, + path: process.env.REDIS_SOCKET || undefined, + }; +} + +export const bullConfig: QueueOptions = { + prefix: 'immich_bull', + connection: parseRedisConfig(), + defaultJobOptions: { + attempts: 3, + removeOnComplete: true, + removeOnFail: false, + }, +}; + +export const bullQueues: RegisterQueueOptions[] = Object.values(QueueName).map((name) => ({ name })); + +export const excludePaths = ['/.well-known/immich', '/custom.css', '/favicon.ico']; diff --git a/server/src/immich/controllers/activity.controller.ts b/server/src/controllers/activity.controller.ts similarity index 67% rename from server/src/immich/controllers/activity.controller.ts rename to server/src/controllers/activity.controller.ts index 0808c7d4d..c405b0c6f 100644 --- a/server/src/immich/controllers/activity.controller.ts +++ b/server/src/controllers/activity.controller.ts @@ -1,17 +1,17 @@ -import { AuthDto } from '@app/domain'; -import { - ActivityDto, - ActivitySearchDto, - ActivityService, - ActivityCreateDto as CreateDto, - ActivityResponseDto as ResponseDto, - ActivityStatisticsResponseDto as StatsResponseDto, -} from '@app/domain/activity'; import { Body, Controller, Delete, Get, HttpCode, HttpStatus, Param, Post, Query, Res } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; import { Response } from 'express'; -import { Auth, Authenticated } from '../app.guard'; -import { UUIDParamDto } from './dto/uuid-param.dto'; +import { + ActivityCreateDto, + ActivityDto, + ActivityResponseDto, + ActivitySearchDto, + ActivityStatisticsResponseDto, +} from 'src/domain/activity/activity.dto'; +import { ActivityService } from 'src/domain/activity/activity.service'; +import { AuthDto } from 'src/domain/auth/auth.dto'; +import { Auth, Authenticated } from 'src/middleware/auth.guard'; +import { UUIDParamDto } from 'src/validation'; @ApiTags('Activity') @Controller('activity') @@ -20,21 +20,21 @@ export class ActivityController { constructor(private service: ActivityService) {} @Get() - getActivities(@Auth() auth: AuthDto, @Query() dto: ActivitySearchDto): Promise { + getActivities(@Auth() auth: AuthDto, @Query() dto: ActivitySearchDto): Promise { return this.service.getAll(auth, dto); } @Get('statistics') - getActivityStatistics(@Auth() auth: AuthDto, @Query() dto: ActivityDto): Promise { + getActivityStatistics(@Auth() auth: AuthDto, @Query() dto: ActivityDto): Promise { return this.service.getStatistics(auth, dto); } @Post() async createActivity( @Auth() auth: AuthDto, - @Body() dto: CreateDto, + @Body() dto: ActivityCreateDto, @Res({ passthrough: true }) res: Response, - ): Promise { + ): Promise { const { duplicate, value } = await this.service.create(auth, dto); if (duplicate) { res.status(HttpStatus.OK); diff --git a/server/src/immich/controllers/album.controller.ts b/server/src/controllers/album.controller.ts similarity index 69% rename from server/src/immich/controllers/album.controller.ts rename to server/src/controllers/album.controller.ts index ea1c5a428..18fd549b6 100644 --- a/server/src/immich/controllers/album.controller.ts +++ b/server/src/controllers/album.controller.ts @@ -1,21 +1,16 @@ -import { - AddUsersDto, - AlbumCountResponseDto, - AlbumInfoDto, - AlbumResponseDto, - AlbumService, - AuthDto, - BulkIdResponseDto, - BulkIdsDto, - CreateAlbumDto as CreateDto, - GetAlbumsDto, - UpdateAlbumDto as UpdateDto, -} from '@app/domain'; import { Body, Controller, Delete, Get, Param, Patch, Post, Put, Query } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; -import { ParseMeUUIDPipe } from '../api-v1/validation/parse-me-uuid-pipe'; -import { Auth, Authenticated, SharedLinkRoute } from '../app.guard'; -import { UUIDParamDto } from './dto/uuid-param.dto'; +import { AlbumCountResponseDto, AlbumResponseDto } from 'src/domain/album/album-response.dto'; +import { AlbumService } from 'src/domain/album/album.service'; +import { AddUsersDto } from 'src/domain/album/dto/album-add-users.dto'; +import { CreateAlbumDto } from 'src/domain/album/dto/album-create.dto'; +import { UpdateAlbumDto } from 'src/domain/album/dto/album-update.dto'; +import { AlbumInfoDto } from 'src/domain/album/dto/album.dto'; +import { GetAlbumsDto } from 'src/domain/album/dto/get-albums.dto'; +import { BulkIdResponseDto, BulkIdsDto } from 'src/domain/asset/response-dto/asset-ids-response.dto'; +import { AuthDto } from 'src/domain/auth/auth.dto'; +import { Auth, Authenticated, SharedLinkRoute } from 'src/middleware/auth.guard'; +import { ParseMeUUIDPipe, UUIDParamDto } from 'src/validation'; @ApiTags('Album') @Controller('album') @@ -34,7 +29,7 @@ export class AlbumController { } @Post() - createAlbum(@Auth() auth: AuthDto, @Body() dto: CreateDto): Promise { + createAlbum(@Auth() auth: AuthDto, @Body() dto: CreateAlbumDto): Promise { return this.service.create(auth, dto); } @@ -52,7 +47,7 @@ export class AlbumController { updateAlbumInfo( @Auth() auth: AuthDto, @Param() { id }: UUIDParamDto, - @Body() dto: UpdateDto, + @Body() dto: UpdateAlbumDto, ): Promise { return this.service.update(auth, id, dto); } diff --git a/server/src/immich/controllers/api-key.controller.ts b/server/src/controllers/api-key.controller.ts similarity index 81% rename from server/src/immich/controllers/api-key.controller.ts rename to server/src/controllers/api-key.controller.ts index 5b5072533..2d4f8db15 100644 --- a/server/src/immich/controllers/api-key.controller.ts +++ b/server/src/controllers/api-key.controller.ts @@ -1,15 +1,15 @@ +import { Body, Controller, Delete, Get, Param, Post, Put } from '@nestjs/common'; +import { ApiTags } from '@nestjs/swagger'; import { APIKeyCreateDto, APIKeyCreateResponseDto, APIKeyResponseDto, - APIKeyService, APIKeyUpdateDto, - AuthDto, -} from '@app/domain'; -import { Body, Controller, Delete, Get, Param, Post, Put } from '@nestjs/common'; -import { ApiTags } from '@nestjs/swagger'; -import { Auth, Authenticated } from '../app.guard'; -import { UUIDParamDto } from './dto/uuid-param.dto'; +} from 'src/domain/api-key/api-key.dto'; +import { APIKeyService } from 'src/domain/api-key/api-key.service'; +import { AuthDto } from 'src/domain/auth/auth.dto'; +import { Auth, Authenticated } from 'src/middleware/auth.guard'; +import { UUIDParamDto } from 'src/validation'; @ApiTags('API Key') @Controller('api-key') diff --git a/server/src/immich/controllers/app.controller.ts b/server/src/controllers/app.controller.ts similarity index 78% rename from server/src/immich/controllers/app.controller.ts rename to server/src/controllers/app.controller.ts index 68a61c34c..d4c7ea5b4 100644 --- a/server/src/immich/controllers/app.controller.ts +++ b/server/src/controllers/app.controller.ts @@ -1,7 +1,7 @@ -import { SystemConfigService } from '@app/domain'; import { Controller, Get, Header } from '@nestjs/common'; import { ApiExcludeEndpoint } from '@nestjs/swagger'; -import { PublicRoute } from '../app.guard'; +import { SystemConfigService } from 'src/domain/system-config/system-config.service'; +import { PublicRoute } from 'src/middleware/auth.guard'; @Controller() export class AppController { diff --git a/server/src/immich/controllers/asset.controller.ts b/server/src/controllers/asset.controller.ts similarity index 70% rename from server/src/immich/controllers/asset.controller.ts rename to server/src/controllers/asset.controller.ts index 39a36b175..a6db59815 100644 --- a/server/src/immich/controllers/asset.controller.ts +++ b/server/src/controllers/asset.controller.ts @@ -1,31 +1,28 @@ +import { Body, Controller, Delete, Get, HttpCode, HttpStatus, Param, Post, Put, Query } from '@nestjs/common'; +import { ApiOperation, ApiTags } from '@nestjs/swagger'; +import { AssetService } from 'src/domain/asset/asset.service'; +import { AssetJobsDto } from 'src/domain/asset/dto/asset-ids.dto'; +import { UpdateStackParentDto } from 'src/domain/asset/dto/asset-stack.dto'; +import { AssetStatsDto, AssetStatsResponseDto } from 'src/domain/asset/dto/asset-statistics.dto'; import { AssetBulkDeleteDto, AssetBulkUpdateDto, - AssetJobsDto, - AssetResponseDto, - AssetService, - AssetStatsDto, - AssetStatsResponseDto, - AuthDto, DeviceIdDto, - MapMarkerDto, - MapMarkerResponseDto, - MemoryLaneDto, - MemoryLaneResponseDto, - MetadataSearchDto, RandomAssetsDto, - SearchService, - TimeBucketAssetDto, - TimeBucketDto, - TimeBucketResponseDto, - UpdateAssetDto as UpdateDto, - UpdateStackParentDto, -} from '@app/domain'; -import { Body, Controller, Delete, Get, HttpCode, HttpStatus, Param, Post, Put, Query } from '@nestjs/common'; -import { ApiOperation, ApiTags } from '@nestjs/swagger'; -import { Auth, Authenticated, SharedLinkRoute } from '../app.guard'; -import { Route } from '../interceptors'; -import { UUIDParamDto } from './dto/uuid-param.dto'; + UpdateAssetDto, +} from 'src/domain/asset/dto/asset.dto'; +import { MapMarkerDto } from 'src/domain/asset/dto/map-marker.dto'; +import { MemoryLaneDto } from 'src/domain/asset/dto/memory-lane.dto'; +import { TimeBucketAssetDto, TimeBucketDto } from 'src/domain/asset/dto/time-bucket.dto'; +import { AssetResponseDto, MemoryLaneResponseDto } from 'src/domain/asset/response-dto/asset-response.dto'; +import { MapMarkerResponseDto } from 'src/domain/asset/response-dto/map-marker-response.dto'; +import { TimeBucketResponseDto } from 'src/domain/asset/response-dto/time-bucket-response.dto'; +import { AuthDto } from 'src/domain/auth/auth.dto'; +import { MetadataSearchDto } from 'src/domain/search/dto/search.dto'; +import { SearchService } from 'src/domain/search/search.service'; +import { Auth, Authenticated, SharedLinkRoute } from 'src/middleware/auth.guard'; +import { Route } from 'src/middleware/file-upload.interceptor'; +import { UUIDParamDto } from 'src/validation'; @ApiTags('Asset') @Controller('assets') @@ -120,7 +117,11 @@ export class AssetController { } @Put(':id') - updateAsset(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto, @Body() dto: UpdateDto): Promise { + updateAsset( + @Auth() auth: AuthDto, + @Param() { id }: UUIDParamDto, + @Body() dto: UpdateAssetDto, + ): Promise { return this.service.update(auth, id, dto); } } diff --git a/server/src/immich/controllers/audit.controller.ts b/server/src/controllers/audit.controller.ts similarity index 81% rename from server/src/immich/controllers/audit.controller.ts rename to server/src/controllers/audit.controller.ts index 09b707b8a..bb92f990e 100644 --- a/server/src/immich/controllers/audit.controller.ts +++ b/server/src/controllers/audit.controller.ts @@ -1,16 +1,16 @@ +import { Body, Controller, Get, Post, Query } from '@nestjs/common'; +import { ApiTags } from '@nestjs/swagger'; import { AuditDeletesDto, AuditDeletesResponseDto, - AuditService, - AuthDto, FileChecksumDto, FileChecksumResponseDto, FileReportDto, FileReportFixDto, -} from '@app/domain'; -import { Body, Controller, Get, Post, Query } from '@nestjs/common'; -import { ApiTags } from '@nestjs/swagger'; -import { AdminRoute, Auth, Authenticated } from '../app.guard'; +} from 'src/domain/audit/audit.dto'; +import { AuditService } from 'src/domain/audit/audit.service'; +import { AuthDto } from 'src/domain/auth/auth.dto'; +import { AdminRoute, Auth, Authenticated } from 'src/middleware/auth.guard'; @ApiTags('Audit') @Controller('audit') diff --git a/server/src/immich/controllers/auth.controller.ts b/server/src/controllers/auth.controller.ts similarity index 85% rename from server/src/immich/controllers/auth.controller.ts rename to server/src/controllers/auth.controller.ts index ac1fea2bc..941002fe6 100644 --- a/server/src/immich/controllers/auth.controller.ts +++ b/server/src/controllers/auth.controller.ts @@ -1,25 +1,21 @@ -import { - AuthDeviceResponseDto, - AuthDto, - AuthService, - ChangePasswordDto, - IMMICH_ACCESS_COOKIE, - IMMICH_AUTH_TYPE_COOKIE, - IMMICH_IS_AUTHENTICATED, - LoginCredentialDto, - LoginDetails, - LoginResponseDto, - LogoutResponseDto, - SignUpDto, - UserResponseDto, - ValidateAccessTokenResponseDto, - mapUser, -} from '@app/domain'; import { Body, Controller, Delete, Get, HttpCode, HttpStatus, Param, Post, Req, Res } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; import { Request, Response } from 'express'; -import { Auth, Authenticated, GetLoginDetails, PublicRoute } from '../app.guard'; -import { UUIDParamDto } from './dto/uuid-param.dto'; +import { IMMICH_ACCESS_COOKIE, IMMICH_AUTH_TYPE_COOKIE, IMMICH_IS_AUTHENTICATED } from 'src/domain/auth/auth.constant'; +import { + AuthDeviceResponseDto, + AuthDto, + ChangePasswordDto, + LoginCredentialDto, + LoginResponseDto, + LogoutResponseDto, + SignUpDto, + ValidateAccessTokenResponseDto, +} from 'src/domain/auth/auth.dto'; +import { AuthService, LoginDetails } from 'src/domain/auth/auth.service'; +import { UserResponseDto, mapUser } from 'src/domain/user/response-dto/user-response.dto'; +import { Auth, Authenticated, GetLoginDetails, PublicRoute } from 'src/middleware/auth.guard'; +import { UUIDParamDto } from 'src/validation'; @ApiTags('Authentication') @Controller('auth') diff --git a/server/src/immich/controllers/download.controller.ts b/server/src/controllers/download.controller.ts similarity index 73% rename from server/src/immich/controllers/download.controller.ts rename to server/src/controllers/download.controller.ts index 743797f74..0fb3520cf 100644 --- a/server/src/immich/controllers/download.controller.ts +++ b/server/src/controllers/download.controller.ts @@ -1,10 +1,13 @@ -import { AssetIdsDto, AuthDto, DownloadInfoDto, DownloadResponseDto, DownloadService } from '@app/domain'; import { Body, Controller, HttpCode, HttpStatus, Next, Param, Post, Res, StreamableFile } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; import { NextFunction, Response } from 'express'; -import { Auth, Authenticated, FileResponse, SharedLinkRoute } from '../app.guard'; -import { asStreamableFile, sendFile } from '../app.utils'; -import { UUIDParamDto } from './dto/uuid-param.dto'; +import { AssetIdsDto } from 'src/domain/asset/dto/asset-ids.dto'; +import { AuthDto } from 'src/domain/auth/auth.dto'; +import { DownloadInfoDto, DownloadResponseDto } from 'src/domain/download/download.dto'; +import { DownloadService } from 'src/domain/download/download.service'; +import { asStreamableFile, sendFile } from 'src/immich/app.utils'; +import { Auth, Authenticated, FileResponse, SharedLinkRoute } from 'src/middleware/auth.guard'; +import { UUIDParamDto } from 'src/validation'; @ApiTags('Download') @Controller('download') diff --git a/server/src/immich/controllers/face.controller.ts b/server/src/controllers/face.controller.ts similarity index 65% rename from server/src/immich/controllers/face.controller.ts rename to server/src/controllers/face.controller.ts index f4014713b..c7caba016 100644 --- a/server/src/immich/controllers/face.controller.ts +++ b/server/src/controllers/face.controller.ts @@ -1,8 +1,10 @@ -import { AssetFaceResponseDto, AuthDto, FaceDto, PersonResponseDto, PersonService } from '@app/domain'; import { Body, Controller, Get, Param, Put, Query } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; -import { Auth, Authenticated } from '../app.guard'; -import { UUIDParamDto } from './dto/uuid-param.dto'; +import { AuthDto } from 'src/domain/auth/auth.dto'; +import { AssetFaceResponseDto, FaceDto, PersonResponseDto } from 'src/domain/person/person.dto'; +import { PersonService } from 'src/domain/person/person.service'; +import { Auth, Authenticated } from 'src/middleware/auth.guard'; +import { UUIDParamDto } from 'src/validation'; @ApiTags('Face') @Controller('face') diff --git a/server/src/immich/controllers/job.controller.ts b/server/src/controllers/job.controller.ts similarity index 78% rename from server/src/immich/controllers/job.controller.ts rename to server/src/controllers/job.controller.ts index 413af44de..f0e9ed2be 100644 --- a/server/src/immich/controllers/job.controller.ts +++ b/server/src/controllers/job.controller.ts @@ -1,7 +1,8 @@ -import { AllJobStatusResponseDto, JobCommandDto, JobIdParamDto, JobService, JobStatusDto } from '@app/domain'; import { Body, Controller, Get, Param, Put } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; -import { Authenticated } from '../app.guard'; +import { AllJobStatusResponseDto, JobCommandDto, JobIdParamDto, JobStatusDto } from 'src/domain/job/job.dto'; +import { JobService } from 'src/domain/job/job.service'; +import { Authenticated } from 'src/middleware/auth.guard'; @ApiTags('Job') @Controller('jobs') diff --git a/server/src/immich/controllers/library.controller.ts b/server/src/controllers/library.controller.ts similarity index 74% rename from server/src/immich/controllers/library.controller.ts rename to server/src/controllers/library.controller.ts index 2b509645c..677773f2d 100644 --- a/server/src/immich/controllers/library.controller.ts +++ b/server/src/controllers/library.controller.ts @@ -1,18 +1,18 @@ -import { - CreateLibraryDto as CreateDto, - LibraryService, - LibraryStatsResponseDto, - LibraryResponseDto as ResponseDto, - ScanLibraryDto, - SearchLibraryDto, - UpdateLibraryDto as UpdateDto, - ValidateLibraryDto, - ValidateLibraryResponseDto, -} from '@app/domain'; import { Body, Controller, Delete, Get, HttpCode, HttpStatus, Param, Post, Put, Query } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; -import { AdminRoute, Authenticated } from '../app.guard'; -import { UUIDParamDto } from './dto/uuid-param.dto'; +import { + CreateLibraryDto, + LibraryResponseDto, + LibraryStatsResponseDto, + ScanLibraryDto, + SearchLibraryDto, + UpdateLibraryDto, + ValidateLibraryDto, + ValidateLibraryResponseDto, +} from 'src/domain/library/library.dto'; +import { LibraryService } from 'src/domain/library/library.service'; +import { AdminRoute, Authenticated } from 'src/middleware/auth.guard'; +import { UUIDParamDto } from 'src/validation'; @ApiTags('Library') @Controller('library') @@ -22,22 +22,22 @@ export class LibraryController { constructor(private service: LibraryService) {} @Get() - getAllLibraries(@Query() dto: SearchLibraryDto): Promise { + getAllLibraries(@Query() dto: SearchLibraryDto): Promise { return this.service.getAll(dto); } @Post() - createLibrary(@Body() dto: CreateDto): Promise { + createLibrary(@Body() dto: CreateLibraryDto): Promise { return this.service.create(dto); } @Put(':id') - updateLibrary(@Param() { id }: UUIDParamDto, @Body() dto: UpdateDto): Promise { + updateLibrary(@Param() { id }: UUIDParamDto, @Body() dto: UpdateLibraryDto): Promise { return this.service.update(id, dto); } @Get(':id') - getLibrary(@Param() { id }: UUIDParamDto): Promise { + getLibrary(@Param() { id }: UUIDParamDto): Promise { return this.service.get(id); } diff --git a/server/src/immich/controllers/oauth.controller.ts b/server/src/controllers/oauth.controller.ts similarity index 87% rename from server/src/immich/controllers/oauth.controller.ts rename to server/src/controllers/oauth.controller.ts index c7a5717af..48b3eafd5 100644 --- a/server/src/immich/controllers/oauth.controller.ts +++ b/server/src/controllers/oauth.controller.ts @@ -1,17 +1,16 @@ +import { Body, Controller, Get, HttpStatus, Post, Redirect, Req, Res } from '@nestjs/common'; +import { ApiTags } from '@nestjs/swagger'; +import { Request, Response } from 'express'; import { AuthDto, - AuthService, - LoginDetails, LoginResponseDto, OAuthAuthorizeResponseDto, OAuthCallbackDto, OAuthConfigDto, - UserResponseDto, -} from '@app/domain'; -import { Body, Controller, Get, HttpStatus, Post, Redirect, Req, Res } from '@nestjs/common'; -import { ApiTags } from '@nestjs/swagger'; -import { Request, Response } from 'express'; -import { Auth, Authenticated, GetLoginDetails, PublicRoute } from '../app.guard'; +} from 'src/domain/auth/auth.dto'; +import { AuthService, LoginDetails } from 'src/domain/auth/auth.service'; +import { UserResponseDto } from 'src/domain/user/response-dto/user-response.dto'; +import { Auth, Authenticated, GetLoginDetails, PublicRoute } from 'src/middleware/auth.guard'; @ApiTags('OAuth') @Controller('oauth') diff --git a/server/src/immich/controllers/partner.controller.ts b/server/src/controllers/partner.controller.ts similarity index 74% rename from server/src/immich/controllers/partner.controller.ts rename to server/src/controllers/partner.controller.ts index 65d95438d..8ca55e73c 100644 --- a/server/src/immich/controllers/partner.controller.ts +++ b/server/src/controllers/partner.controller.ts @@ -1,9 +1,11 @@ -import { AuthDto, PartnerDirection, PartnerService } from '@app/domain'; -import { PartnerResponseDto, UpdatePartnerDto } from '@app/domain/partner/partner.dto'; import { Body, Controller, Delete, Get, Param, Post, Put, Query } from '@nestjs/common'; import { ApiQuery, ApiTags } from '@nestjs/swagger'; -import { Auth, Authenticated } from '../app.guard'; -import { UUIDParamDto } from './dto/uuid-param.dto'; +import { AuthDto } from 'src/domain/auth/auth.dto'; +import { PartnerResponseDto, UpdatePartnerDto } from 'src/domain/partner/partner.dto'; +import { PartnerService } from 'src/domain/partner/partner.service'; +import { PartnerDirection } from 'src/interfaces/partner.repository'; +import { Auth, Authenticated } from 'src/middleware/auth.guard'; +import { UUIDParamDto } from 'src/validation'; @ApiTags('Partner') @Controller('partner') diff --git a/server/src/immich/controllers/person.controller.ts b/server/src/controllers/person.controller.ts similarity index 83% rename from server/src/immich/controllers/person.controller.ts rename to server/src/controllers/person.controller.ts index 3408aa6ec..bc955fd2c 100644 --- a/server/src/immich/controllers/person.controller.ts +++ b/server/src/controllers/person.controller.ts @@ -1,24 +1,24 @@ +import { Body, Controller, Get, Next, Param, Post, Put, Query, Res } from '@nestjs/common'; +import { ApiTags } from '@nestjs/swagger'; +import { NextFunction, Response } from 'express'; +import { BulkIdResponseDto } from 'src/domain/asset/response-dto/asset-ids-response.dto'; +import { AssetResponseDto } from 'src/domain/asset/response-dto/asset-response.dto'; +import { AuthDto } from 'src/domain/auth/auth.dto'; import { AssetFaceUpdateDto, - AssetResponseDto, - AuthDto, - BulkIdResponseDto, MergePersonDto, PeopleResponseDto, PeopleUpdateDto, PersonCreateDto, PersonResponseDto, PersonSearchDto, - PersonService, PersonStatisticsResponseDto, PersonUpdateDto, -} from '@app/domain'; -import { Body, Controller, Get, Next, Param, Post, Put, Query, Res } from '@nestjs/common'; -import { ApiTags } from '@nestjs/swagger'; -import { NextFunction, Response } from 'express'; -import { Auth, Authenticated, FileResponse } from '../app.guard'; -import { sendFile } from '../app.utils'; -import { UUIDParamDto } from './dto/uuid-param.dto'; +} from 'src/domain/person/person.dto'; +import { PersonService } from 'src/domain/person/person.service'; +import { sendFile } from 'src/immich/app.utils'; +import { Auth, Authenticated, FileResponse } from 'src/middleware/auth.guard'; +import { UUIDParamDto } from 'src/validation'; @ApiTags('Person') @Controller('person') diff --git a/server/src/immich/controllers/search.controller.ts b/server/src/controllers/search.controller.ts similarity index 73% rename from server/src/immich/controllers/search.controller.ts rename to server/src/controllers/search.controller.ts index a3527a66a..76fce1609 100644 --- a/server/src/immich/controllers/search.controller.ts +++ b/server/src/controllers/search.controller.ts @@ -1,21 +1,21 @@ -import { - AssetResponseDto, - AuthDto, - MetadataSearchDto, - PersonResponseDto, - PlacesResponseDto, - SearchDto, - SearchExploreResponseDto, - SearchPeopleDto, - SearchPlacesDto, - SearchResponseDto, - SearchService, - SmartSearchDto, -} from '@app/domain'; -import { SearchSuggestionRequestDto } from '@app/domain/search/dto/search-suggestion.dto'; import { Body, Controller, Get, HttpCode, HttpStatus, Post, Query } from '@nestjs/common'; import { ApiOperation, ApiTags } from '@nestjs/swagger'; -import { Auth, Authenticated } from '../app.guard'; +import { AssetResponseDto } from 'src/domain/asset/response-dto/asset-response.dto'; +import { AuthDto } from 'src/domain/auth/auth.dto'; +import { PersonResponseDto } from 'src/domain/person/person.dto'; +import { SearchSuggestionRequestDto } from 'src/domain/search/dto/search-suggestion.dto'; +import { + MetadataSearchDto, + PlacesResponseDto, + SearchDto, + SearchPeopleDto, + SearchPlacesDto, + SmartSearchDto, +} from 'src/domain/search/dto/search.dto'; +import { SearchExploreResponseDto } from 'src/domain/search/response-dto/search-explore.response.dto'; +import { SearchResponseDto } from 'src/domain/search/response-dto/search-response.dto'; +import { SearchService } from 'src/domain/search/search.service'; +import { Auth, Authenticated } from 'src/middleware/auth.guard'; @ApiTags('Search') @Controller('search') diff --git a/server/src/immich/controllers/server-info.controller.ts b/server/src/controllers/server-info.controller.ts similarity index 88% rename from server/src/immich/controllers/server-info.controller.ts rename to server/src/controllers/server-info.controller.ts index 4987a8984..aae617493 100644 --- a/server/src/immich/controllers/server-info.controller.ts +++ b/server/src/controllers/server-info.controller.ts @@ -1,17 +1,17 @@ +import { Controller, Get, HttpCode, HttpStatus, Post } from '@nestjs/common'; +import { ApiTags } from '@nestjs/swagger'; import { ServerConfigDto, ServerFeaturesDto, ServerInfoResponseDto, - ServerInfoService, ServerMediaTypesResponseDto, ServerPingResponse, ServerStatsResponseDto, ServerThemeDto, ServerVersionResponseDto, -} from '@app/domain'; -import { Controller, Get, HttpCode, HttpStatus, Post } from '@nestjs/common'; -import { ApiTags } from '@nestjs/swagger'; -import { AdminRoute, Authenticated, PublicRoute } from '../app.guard'; +} from 'src/domain/server-info/server-info.dto'; +import { ServerInfoService } from 'src/domain/server-info/server-info.service'; +import { AdminRoute, Authenticated, PublicRoute } from 'src/middleware/auth.guard'; @ApiTags('Server Info') @Controller('server-info') diff --git a/server/src/immich/controllers/shared-link.controller.ts b/server/src/controllers/shared-link.controller.ts similarity index 77% rename from server/src/immich/controllers/shared-link.controller.ts rename to server/src/controllers/shared-link.controller.ts index d265d018d..1e03a5c42 100644 --- a/server/src/immich/controllers/shared-link.controller.ts +++ b/server/src/controllers/shared-link.controller.ts @@ -1,19 +1,15 @@ -import { - AssetIdsDto, - AssetIdsResponseDto, - AuthDto, - IMMICH_SHARED_LINK_ACCESS_COOKIE, - SharedLinkCreateDto, - SharedLinkEditDto, - SharedLinkPasswordDto, - SharedLinkResponseDto, - SharedLinkService, -} from '@app/domain'; import { Body, Controller, Delete, Get, Param, Patch, Post, Put, Query, Req, Res } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; import { Request, Response } from 'express'; -import { Auth, Authenticated, SharedLinkRoute } from '../app.guard'; -import { UUIDParamDto } from './dto/uuid-param.dto'; +import { AssetIdsDto } from 'src/domain/asset/dto/asset-ids.dto'; +import { AssetIdsResponseDto } from 'src/domain/asset/response-dto/asset-ids-response.dto'; +import { IMMICH_SHARED_LINK_ACCESS_COOKIE } from 'src/domain/auth/auth.constant'; +import { AuthDto } from 'src/domain/auth/auth.dto'; +import { SharedLinkResponseDto } from 'src/domain/shared-link/shared-link-response.dto'; +import { SharedLinkCreateDto, SharedLinkEditDto, SharedLinkPasswordDto } from 'src/domain/shared-link/shared-link.dto'; +import { SharedLinkService } from 'src/domain/shared-link/shared-link.service'; +import { Auth, Authenticated, SharedLinkRoute } from 'src/middleware/auth.guard'; +import { UUIDParamDto } from 'src/validation'; @ApiTags('Shared Link') @Controller('shared-link') diff --git a/server/src/immich/controllers/system-config.controller.ts b/server/src/controllers/system-config.controller.ts similarity index 67% rename from server/src/immich/controllers/system-config.controller.ts rename to server/src/controllers/system-config.controller.ts index 73cf2c3c0..d10bccee0 100644 --- a/server/src/immich/controllers/system-config.controller.ts +++ b/server/src/controllers/system-config.controller.ts @@ -1,8 +1,10 @@ -import { SystemConfigDto, SystemConfigService, SystemConfigTemplateStorageOptionDto } from '@app/domain'; -import { MapThemeDto } from '@app/domain/system-config/system-config-map-theme.dto'; import { Body, Controller, Get, Put, Query } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; -import { AdminRoute, Authenticated } from '../app.guard'; +import { SystemConfigDto } from 'src/domain/system-config/dto/system-config.dto'; +import { SystemConfigTemplateStorageOptionDto } from 'src/domain/system-config/response-dto/system-config-template-storage-option.dto'; +import { MapThemeDto } from 'src/domain/system-config/system-config-map-theme.dto'; +import { SystemConfigService } from 'src/domain/system-config/system-config.service'; +import { AdminRoute, Authenticated } from 'src/middleware/auth.guard'; @ApiTags('System Config') @Controller('system-config') diff --git a/server/src/immich/controllers/tag.controller.ts b/server/src/controllers/tag.controller.ts similarity index 73% rename from server/src/immich/controllers/tag.controller.ts rename to server/src/controllers/tag.controller.ts index 0d0c563d4..e914e577e 100644 --- a/server/src/immich/controllers/tag.controller.ts +++ b/server/src/controllers/tag.controller.ts @@ -1,17 +1,14 @@ -import { - AssetIdsDto, - AssetIdsResponseDto, - AssetResponseDto, - AuthDto, - CreateTagDto, - TagResponseDto, - TagService, - UpdateTagDto, -} from '@app/domain'; import { Body, Controller, Delete, Get, Param, Patch, Post, Put } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; -import { Auth, Authenticated } from '../app.guard'; -import { UUIDParamDto } from './dto/uuid-param.dto'; +import { AssetIdsDto } from 'src/domain/asset/dto/asset-ids.dto'; +import { AssetIdsResponseDto } from 'src/domain/asset/response-dto/asset-ids-response.dto'; +import { AssetResponseDto } from 'src/domain/asset/response-dto/asset-response.dto'; +import { AuthDto } from 'src/domain/auth/auth.dto'; +import { TagResponseDto } from 'src/domain/tag/tag-response.dto'; +import { CreateTagDto, UpdateTagDto } from 'src/domain/tag/tag.dto'; +import { TagService } from 'src/domain/tag/tag.service'; +import { Auth, Authenticated } from 'src/middleware/auth.guard'; +import { UUIDParamDto } from 'src/validation'; @ApiTags('Tag') @Controller('tag') diff --git a/server/src/immich/controllers/trash.controller.ts b/server/src/controllers/trash.controller.ts similarity index 74% rename from server/src/immich/controllers/trash.controller.ts rename to server/src/controllers/trash.controller.ts index b61893817..2c0b0c946 100644 --- a/server/src/immich/controllers/trash.controller.ts +++ b/server/src/controllers/trash.controller.ts @@ -1,7 +1,9 @@ -import { AuthDto, BulkIdsDto, TrashService } from '@app/domain'; import { Body, Controller, HttpCode, HttpStatus, Post } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; -import { Auth, Authenticated } from '../app.guard'; +import { BulkIdsDto } from 'src/domain/asset/response-dto/asset-ids-response.dto'; +import { AuthDto } from 'src/domain/auth/auth.dto'; +import { TrashService } from 'src/domain/trash/trash.service'; +import { Auth, Authenticated } from 'src/middleware/auth.guard'; @ApiTags('Trash') @Controller('trash') diff --git a/server/src/immich/controllers/user.controller.ts b/server/src/controllers/user.controller.ts similarity index 69% rename from server/src/immich/controllers/user.controller.ts rename to server/src/controllers/user.controller.ts index 0b3828f5c..e573584ad 100644 --- a/server/src/immich/controllers/user.controller.ts +++ b/server/src/controllers/user.controller.ts @@ -1,13 +1,3 @@ -import { - AuthDto, - CreateUserDto as CreateDto, - CreateProfileImageDto, - CreateProfileImageResponseDto, - DeleteUserDto, - UpdateUserDto as UpdateDto, - UserResponseDto, - UserService, -} from '@app/domain'; import { Body, Controller, @@ -26,10 +16,18 @@ import { } from '@nestjs/common'; import { ApiBody, ApiConsumes, ApiTags } from '@nestjs/swagger'; import { NextFunction, Response } from 'express'; -import { AdminRoute, Auth, Authenticated, FileResponse } from '../app.guard'; -import { sendFile } from '../app.utils'; -import { FileUploadInterceptor, Route } from '../interceptors'; -import { UUIDParamDto } from './dto/uuid-param.dto'; +import { AuthDto } from 'src/domain/auth/auth.dto'; +import { CreateProfileImageDto } from 'src/domain/user/dto/create-profile-image.dto'; +import { CreateUserDto } from 'src/domain/user/dto/create-user.dto'; +import { DeleteUserDto } from 'src/domain/user/dto/delete-user.dto'; +import { UpdateUserDto } from 'src/domain/user/dto/update-user.dto'; +import { CreateProfileImageResponseDto } from 'src/domain/user/response-dto/create-profile-image-response.dto'; +import { UserResponseDto } from 'src/domain/user/response-dto/user-response.dto'; +import { UserService } from 'src/domain/user/user.service'; +import { sendFile } from 'src/immich/app.utils'; +import { AdminRoute, Auth, Authenticated, FileResponse } from 'src/middleware/auth.guard'; +import { FileUploadInterceptor, Route } from 'src/middleware/file-upload.interceptor'; +import { UUIDParamDto } from 'src/validation'; @ApiTags('User') @Controller(Route.USER) @@ -54,7 +52,7 @@ export class UserController { @AdminRoute() @Post() - createUser(@Body() createUserDto: CreateDto): Promise { + createUser(@Body() createUserDto: CreateUserDto): Promise { return this.service.create(createUserDto); } @@ -82,7 +80,7 @@ export class UserController { // TODO: replace with @Put(':id') @Put() - updateUser(@Auth() auth: AuthDto, @Body() updateUserDto: UpdateDto): Promise { + updateUser(@Auth() auth: AuthDto, @Body() updateUserDto: UpdateUserDto): Promise { return this.service.update(auth, updateUserDto); } diff --git a/server/src/domain/access/access.core.ts b/server/src/cores/access.core.ts similarity index 97% rename from server/src/domain/access/access.core.ts rename to server/src/cores/access.core.ts index 40b01de1d..389647524 100644 --- a/server/src/domain/access/access.core.ts +++ b/server/src/cores/access.core.ts @@ -1,8 +1,8 @@ import { BadRequestException, UnauthorizedException } from '@nestjs/common'; -import { SharedLinkEntity } from '../../infra/entities'; -import { AuthDto } from '../auth'; -import { setDifference, setIsEqual, setUnion } from '../domain.util'; -import { IAccessRepository } from '../repositories'; +import { AuthDto } from 'src/domain/auth/auth.dto'; +import { SharedLinkEntity } from 'src/infra/entities/shared-link.entity'; +import { IAccessRepository } from 'src/interfaces/access.repository'; +import { setDifference, setIsEqual, setUnion } from 'src/utils'; export enum Permission { ACTIVITY_CREATE = 'activity.create', diff --git a/server/src/domain/storage/storage.core.ts b/server/src/cores/storage.core.ts similarity index 92% rename from server/src/domain/storage/storage.core.ts rename to server/src/cores/storage.core.ts index 5cf65ad7c..39d9e12c9 100644 --- a/server/src/domain/storage/storage.core.ts +++ b/server/src/cores/storage.core.ts @@ -1,16 +1,16 @@ -import { SystemConfigCore } from '@app/domain/system-config'; -import { AssetEntity, AssetPathType, PathType, PersonEntity, PersonPathType } from '@app/infra/entities'; -import { ImmichLogger } from '@app/infra/logger'; import { dirname, join, resolve } from 'node:path'; -import { APP_MEDIA_LOCATION } from '../domain.constant'; -import { - IAssetRepository, - ICryptoRepository, - IMoveRepository, - IPersonRepository, - IStorageRepository, - ISystemConfigRepository, -} from '../repositories'; +import { SystemConfigCore } from 'src/cores/system-config.core'; +import { APP_MEDIA_LOCATION } from 'src/domain/domain.constant'; +import { AssetEntity } from 'src/infra/entities/asset.entity'; +import { AssetPathType, PathType, PersonPathType } from 'src/infra/entities/move.entity'; +import { PersonEntity } from 'src/infra/entities/person.entity'; +import { ImmichLogger } from 'src/infra/logger'; +import { IAssetRepository } from 'src/interfaces/asset.repository'; +import { ICryptoRepository } from 'src/interfaces/crypto.repository'; +import { IMoveRepository } from 'src/interfaces/move.repository'; +import { IPersonRepository } from 'src/interfaces/person.repository'; +import { IStorageRepository } from 'src/interfaces/storage.repository'; +import { ISystemConfigRepository } from 'src/interfaces/system-config.repository'; export enum StorageFolder { ENCODED_VIDEO = 'encoded-video', @@ -46,8 +46,8 @@ export class StorageCore { private moveRepository: IMoveRepository, private personRepository: IPersonRepository, private cryptoRepository: ICryptoRepository, - private systemConfigRepository: ISystemConfigRepository, private repository: IStorageRepository, + systemConfigRepository: ISystemConfigRepository, ) { this.configCore = SystemConfigCore.create(systemConfigRepository); } @@ -66,8 +66,8 @@ export class StorageCore { moveRepository, personRepository, cryptoRepository, - configRepository, repository, + configRepository, ); } diff --git a/server/src/domain/system-config/system-config.core.ts b/server/src/cores/system-config.core.ts similarity index 96% rename from server/src/domain/system-config/system-config.core.ts rename to server/src/cores/system-config.core.ts index 93a4937cb..1bfab773d 100644 --- a/server/src/domain/system-config/system-config.core.ts +++ b/server/src/cores/system-config.core.ts @@ -1,7 +1,16 @@ +import { BadRequestException, ForbiddenException, Injectable } from '@nestjs/common'; +import { CronExpression } from '@nestjs/schedule'; +import { plainToInstance } from 'class-transformer'; +import { validate } from 'class-validator'; +import { load as loadYaml } from 'js-yaml'; +import * as _ from 'lodash'; +import { Subject } from 'rxjs'; +import { QueueName } from 'src/domain/job/job.constants'; +import { SystemConfigDto } from 'src/domain/system-config/dto/system-config.dto'; import { AudioCodec, - Colorspace, CQMode, + Colorspace, LogLevel, SystemConfig, SystemConfigEntity, @@ -11,18 +20,9 @@ import { TranscodeHWAccel, TranscodePolicy, VideoCodec, -} from '@app/infra/entities'; -import { ImmichLogger } from '@app/infra/logger'; -import { BadRequestException, ForbiddenException, Injectable } from '@nestjs/common'; -import { CronExpression } from '@nestjs/schedule'; -import { plainToInstance } from 'class-transformer'; -import { validate } from 'class-validator'; -import { load as loadYaml } from 'js-yaml'; -import * as _ from 'lodash'; -import { Subject } from 'rxjs'; -import { QueueName } from '../job/job.constants'; -import { ISystemConfigRepository } from '../repositories'; -import { SystemConfigDto } from './dto'; +} from 'src/infra/entities/system-config.entity'; +import { ImmichLogger } from 'src/infra/logger'; +import { ISystemConfigRepository } from 'src/interfaces/system-config.repository'; export type SystemConfigValidator = (config: SystemConfig, newConfig: SystemConfig) => void | Promise; diff --git a/server/src/domain/user/user.core.ts b/server/src/cores/user.core.ts similarity index 88% rename from server/src/domain/user/user.core.ts rename to server/src/cores/user.core.ts index 6134e97ce..488c16036 100644 --- a/server/src/domain/user/user.core.ts +++ b/server/src/cores/user.core.ts @@ -1,8 +1,11 @@ -import { LibraryType, UserEntity } from '@app/infra/entities'; import { BadRequestException, ForbiddenException } from '@nestjs/common'; import sanitize from 'sanitize-filename'; -import { ICryptoRepository, ILibraryRepository, IUserRepository } from '../repositories'; -import { UserResponseDto } from './response-dto'; +import { UserResponseDto } from 'src/domain/user/response-dto/user-response.dto'; +import { LibraryType } from 'src/infra/entities/library.entity'; +import { UserEntity } from 'src/infra/entities/user.entity'; +import { ICryptoRepository } from 'src/interfaces/crypto.repository'; +import { ILibraryRepository } from 'src/interfaces/library.repository'; +import { IUserRepository } from 'src/interfaces/user.repository'; const SALT_ROUNDS = 10; diff --git a/server/src/decorators.ts b/server/src/decorators.ts new file mode 100644 index 000000000..06dc0bfdc --- /dev/null +++ b/server/src/decorators.ts @@ -0,0 +1,124 @@ +import { SetMetadata } from '@nestjs/common'; +import _ from 'lodash'; +import { setUnion } from 'src/utils'; + +// PostgreSQL uses a 16-bit integer to indicate the number of bound parameters. This means that the +// maximum number of parameters is 65535. Any query that tries to bind more than that (e.g. searching +// by a list of IDs) requires splitting the query into multiple chunks. +// We are rounding down this limit, as queries commonly include other filters and parameters. +export const DATABASE_PARAMETER_CHUNK_SIZE = 65_500; + +/** + * Chunks an array or set into smaller collections of the same type and specified size. + * + * @param collection The collection to chunk. + * @param size The size of each chunk. + */ +function chunks(collection: Array, size: number): Array>; +function chunks(collection: Set, size: number): Array>; +function chunks(collection: Array | Set, size: number): Array> | Array> { + if (collection instanceof Set) { + const result = []; + let chunk = new Set(); + for (const element of collection) { + chunk.add(element); + if (chunk.size === size) { + result.push(chunk); + chunk = new Set(); + } + } + if (chunk.size > 0) { + result.push(chunk); + } + return result; + } else { + return _.chunk(collection, size); + } +} + +/** + * Wraps a method that takes a collection of parameters and sequentially calls it with chunks of the collection, + * to overcome the maximum number of parameters allowed by the database driver. + * + * @param options.paramIndex The index of the function parameter to chunk. Defaults to 0. + * @param options.flatten Whether to flatten the results. Defaults to false. + */ +export function Chunked(options: { paramIndex?: number; mergeFn?: (results: any) => any } = {}): MethodDecorator { + return (target: any, propertyKey: string | symbol, descriptor: PropertyDescriptor) => { + const originalMethod = descriptor.value; + const parameterIndex = options.paramIndex ?? 0; + descriptor.value = async function (...arguments_: any[]) { + const argument = arguments_[parameterIndex]; + + // Early return if argument length is less than or equal to the chunk size. + if ( + (Array.isArray(argument) && argument.length <= DATABASE_PARAMETER_CHUNK_SIZE) || + (argument instanceof Set && argument.size <= DATABASE_PARAMETER_CHUNK_SIZE) + ) { + return await originalMethod.apply(this, arguments_); + } + + return Promise.all( + chunks(argument, DATABASE_PARAMETER_CHUNK_SIZE).map(async (chunk) => { + await Reflect.apply(originalMethod, this, [ + ...arguments_.slice(0, parameterIndex), + chunk, + ...arguments_.slice(parameterIndex + 1), + ]); + }), + ).then((results) => (options.mergeFn ? options.mergeFn(results) : results)); + }; + }; +} + +export function ChunkedArray(options?: { paramIndex?: number }): MethodDecorator { + return Chunked({ ...options, mergeFn: _.flatten }); +} + +export function ChunkedSet(options?: { paramIndex?: number }): MethodDecorator { + return Chunked({ ...options, mergeFn: setUnion }); +} + +// https://stackoverflow.com/a/74898678 +export function DecorateAll( + decorator: ( + target: any, + propertyKey: string, + descriptor: TypedPropertyDescriptor, + ) => TypedPropertyDescriptor | void, +) { + return (target: any) => { + const descriptors = Object.getOwnPropertyDescriptors(target.prototype); + for (const [propName, descriptor] of Object.entries(descriptors)) { + const isMethod = typeof descriptor.value == 'function' && propName !== 'constructor'; + if (!isMethod) { + continue; + } + decorator({ ...target, constructor: { ...target.constructor, name: target.name } as any }, propName, descriptor); + Object.defineProperty(target.prototype, propName, descriptor); + } + }; +} + +const UUID = '00000000-0000-4000-a000-000000000000'; + +export const DummyValue = { + UUID, + UUID_SET: new Set([UUID]), + PAGINATION: { take: 10, skip: 0 }, + EMAIL: 'user@immich.app', + STRING: 'abcdefghi', + BUFFER: Buffer.from('abcdefghi'), + DATE: new Date(), + TIME_BUCKET: '2024-01-01T00:00:00.000Z', +}; + +export const GENERATE_SQL_KEY = 'generate-sql-key'; + +export interface GenerateSqlQueries { + name?: string; + params: unknown[]; +} + +/** Decorator to enable versioning/tracking of generated Sql */ +export const GenerateSql = (...options: GenerateSqlQueries[]) => SetMetadata(GENERATE_SQL_KEY, options); diff --git a/server/src/domain/access/index.ts b/server/src/domain/access/index.ts deleted file mode 100644 index 80ae0c534..000000000 --- a/server/src/domain/access/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './access.core'; diff --git a/server/src/domain/activity/activity.dto.ts b/server/src/domain/activity/activity.dto.ts index a5a5bd3df..1bfbabd80 100644 --- a/server/src/domain/activity/activity.dto.ts +++ b/server/src/domain/activity/activity.dto.ts @@ -1,8 +1,8 @@ -import { ActivityEntity } from '@app/infra/entities'; import { ApiProperty } from '@nestjs/swagger'; import { IsEnum, IsNotEmpty, IsString, ValidateIf } from 'class-validator'; -import { Optional, ValidateUUID } from '../domain.util'; -import { UserDto, mapSimpleUser } from '../user/response-dto'; +import { UserDto, mapSimpleUser } from 'src/domain/user/response-dto/user-response.dto'; +import { ActivityEntity } from 'src/infra/entities/activity.entity'; +import { Optional, ValidateUUID } from 'src/validation'; export enum ReactionType { COMMENT = 'comment', diff --git a/server/src/domain/activity/activity.service.ts b/server/src/domain/activity/activity.service.ts index 69386f561..9ed8946f5 100644 --- a/server/src/domain/activity/activity.service.ts +++ b/server/src/domain/activity/activity.service.ts @@ -1,8 +1,5 @@ -import { ActivityEntity } from '@app/infra/entities'; import { Inject, Injectable } from '@nestjs/common'; -import { AccessCore, Permission } from '../access'; -import { AuthDto } from '../auth'; -import { IAccessRepository, IActivityRepository } from '../repositories'; +import { AccessCore, Permission } from 'src/cores/access.core'; import { ActivityCreateDto, ActivityDto, @@ -13,7 +10,11 @@ import { ReactionLevel, ReactionType, mapActivity, -} from './activity.dto'; +} from 'src/domain/activity/activity.dto'; +import { AuthDto } from 'src/domain/auth/auth.dto'; +import { ActivityEntity } from 'src/infra/entities/activity.entity'; +import { IAccessRepository } from 'src/interfaces/access.repository'; +import { IActivityRepository } from 'src/interfaces/activity.repository'; @Injectable() export class ActivityService { diff --git a/server/src/domain/activity/activity.spec.ts b/server/src/domain/activity/activity.spec.ts index 10a4c0725..467540be3 100644 --- a/server/src/domain/activity/activity.spec.ts +++ b/server/src/domain/activity/activity.spec.ts @@ -1,10 +1,11 @@ import { BadRequestException } from '@nestjs/common'; -import { authStub, IAccessRepositoryMock, newAccessRepositoryMock } from '@test'; -import { activityStub } from '@test/fixtures/activity.stub'; -import { newActivityRepositoryMock } from '@test/repositories/activity.repository.mock'; -import { IActivityRepository } from '../repositories'; -import { ReactionType } from './activity.dto'; -import { ActivityService } from './activity.service'; +import { ReactionType } from 'src/domain/activity/activity.dto'; +import { ActivityService } from 'src/domain/activity/activity.service'; +import { IActivityRepository } from 'src/interfaces/activity.repository'; +import { activityStub } from 'test/fixtures/activity.stub'; +import { authStub } from 'test/fixtures/auth.stub'; +import { IAccessRepositoryMock, newAccessRepositoryMock } from 'test/repositories/access.repository.mock'; +import { newActivityRepositoryMock } from 'test/repositories/activity.repository.mock'; describe(ActivityService.name, () => { let sut: ActivityService; diff --git a/server/src/domain/activity/index.ts b/server/src/domain/activity/index.ts deleted file mode 100644 index f0d954014..000000000 --- a/server/src/domain/activity/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './activity.dto'; -export * from './activity.service'; diff --git a/server/src/domain/album/album-response.dto.spec.ts b/server/src/domain/album/album-response.dto.spec.ts index c8485af65..568b416b4 100644 --- a/server/src/domain/album/album-response.dto.spec.ts +++ b/server/src/domain/album/album-response.dto.spec.ts @@ -1,5 +1,5 @@ -import { albumStub } from '@test'; -import { mapAlbum } from './album-response.dto'; +import { mapAlbum } from 'src/domain/album/album-response.dto'; +import { albumStub } from 'test/fixtures/album.stub'; describe('mapAlbum', () => { it('should set start and end dates', () => { diff --git a/server/src/domain/album/album-response.dto.ts b/server/src/domain/album/album-response.dto.ts index bcca1cd31..663a463f2 100644 --- a/server/src/domain/album/album-response.dto.ts +++ b/server/src/domain/album/album-response.dto.ts @@ -1,9 +1,9 @@ -import { AlbumEntity, AssetOrder } from '@app/infra/entities'; -import { Optional } from '@nestjs/common'; import { ApiProperty } from '@nestjs/swagger'; -import { AssetResponseDto, mapAsset } from '../asset'; -import { AuthDto } from '../auth/auth.dto'; -import { UserResponseDto, mapUser } from '../user'; +import { AssetResponseDto, mapAsset } from 'src/domain/asset/response-dto/asset-response.dto'; +import { AuthDto } from 'src/domain/auth/auth.dto'; +import { UserResponseDto, mapUser } from 'src/domain/user/response-dto/user-response.dto'; +import { AlbumEntity, AssetOrder } from 'src/infra/entities/album.entity'; +import { Optional } from 'src/validation'; export class AlbumResponseDto { id!: string; diff --git a/server/src/domain/album/album.service.spec.ts b/server/src/domain/album/album.service.spec.ts index fa0852d8c..5b3a91ecf 100644 --- a/server/src/domain/album/album.service.spec.ts +++ b/server/src/domain/album/album.service.spec.ts @@ -1,36 +1,32 @@ import { BadRequestException } from '@nestjs/common'; -import { - albumStub, - authStub, - IAccessRepositoryMock, - newAccessRepositoryMock, - newAlbumRepositoryMock, - newAssetRepositoryMock, - newJobRepositoryMock, - newUserRepositoryMock, - userStub, -} from '@test'; import _ from 'lodash'; -import { BulkIdErrorReason } from '../asset'; -import { IAlbumRepository, IAssetRepository, IJobRepository, IUserRepository } from '../repositories'; -import { AlbumService } from './album.service'; +import { AlbumService } from 'src/domain/album/album.service'; +import { BulkIdErrorReason } from 'src/domain/asset/response-dto/asset-ids-response.dto'; +import { IAlbumRepository } from 'src/interfaces/album.repository'; +import { IAssetRepository } from 'src/interfaces/asset.repository'; +import { IUserRepository } from 'src/interfaces/user.repository'; +import { albumStub } from 'test/fixtures/album.stub'; +import { authStub } from 'test/fixtures/auth.stub'; +import { userStub } from 'test/fixtures/user.stub'; +import { IAccessRepositoryMock, newAccessRepositoryMock } from 'test/repositories/access.repository.mock'; +import { newAlbumRepositoryMock } from 'test/repositories/album.repository.mock'; +import { newAssetRepositoryMock } from 'test/repositories/asset.repository.mock'; +import { newUserRepositoryMock } from 'test/repositories/user.repository.mock'; describe(AlbumService.name, () => { let sut: AlbumService; let accessMock: IAccessRepositoryMock; let albumMock: jest.Mocked; let assetMock: jest.Mocked; - let jobMock: jest.Mocked; let userMock: jest.Mocked; beforeEach(() => { accessMock = newAccessRepositoryMock(); albumMock = newAlbumRepositoryMock(); assetMock = newAssetRepositoryMock(); - jobMock = newJobRepositoryMock(); userMock = newUserRepositoryMock(); - sut = new AlbumService(accessMock, albumMock, assetMock, jobMock, userMock); + sut = new AlbumService(accessMock, albumMock, assetMock, userMock); }); it('should work', () => { diff --git a/server/src/domain/album/album.service.ts b/server/src/domain/album/album.service.ts index dc3d510d4..77a6c6c46 100644 --- a/server/src/domain/album/album.service.ts +++ b/server/src/domain/album/album.service.ts @@ -1,26 +1,27 @@ -import { AlbumEntity, AssetEntity, UserEntity } from '@app/infra/entities'; import { BadRequestException, Inject, Injectable } from '@nestjs/common'; -import { AccessCore, Permission } from '../access'; -import { BulkIdErrorReason, BulkIdResponseDto, BulkIdsDto } from '../asset'; -import { AuthDto } from '../auth'; -import { setUnion } from '../domain.util'; -import { - AlbumAssetCount, - AlbumInfoOptions, - IAccessRepository, - IAlbumRepository, - IAssetRepository, - IJobRepository, - IUserRepository, -} from '../repositories'; +import { AccessCore, Permission } from 'src/cores/access.core'; import { AlbumCountResponseDto, AlbumResponseDto, mapAlbum, mapAlbumWithAssets, mapAlbumWithoutAssets, -} from './album-response.dto'; -import { AddUsersDto, AlbumInfoDto, CreateAlbumDto, GetAlbumsDto, UpdateAlbumDto } from './dto'; +} from 'src/domain/album/album-response.dto'; +import { AddUsersDto } from 'src/domain/album/dto/album-add-users.dto'; +import { CreateAlbumDto } from 'src/domain/album/dto/album-create.dto'; +import { UpdateAlbumDto } from 'src/domain/album/dto/album-update.dto'; +import { AlbumInfoDto } from 'src/domain/album/dto/album.dto'; +import { GetAlbumsDto } from 'src/domain/album/dto/get-albums.dto'; +import { BulkIdErrorReason, BulkIdResponseDto, BulkIdsDto } from 'src/domain/asset/response-dto/asset-ids-response.dto'; +import { AuthDto } from 'src/domain/auth/auth.dto'; +import { AlbumEntity } from 'src/infra/entities/album.entity'; +import { AssetEntity } from 'src/infra/entities/asset.entity'; +import { UserEntity } from 'src/infra/entities/user.entity'; +import { IAccessRepository } from 'src/interfaces/access.repository'; +import { AlbumAssetCount, AlbumInfoOptions, IAlbumRepository } from 'src/interfaces/album.repository'; +import { IAssetRepository } from 'src/interfaces/asset.repository'; +import { IUserRepository } from 'src/interfaces/user.repository'; +import { setUnion } from 'src/utils'; @Injectable() export class AlbumService { @@ -29,7 +30,6 @@ export class AlbumService { @Inject(IAccessRepository) accessRepository: IAccessRepository, @Inject(IAlbumRepository) private albumRepository: IAlbumRepository, @Inject(IAssetRepository) private assetRepository: IAssetRepository, - @Inject(IJobRepository) private jobRepository: IJobRepository, @Inject(IUserRepository) private userRepository: IUserRepository, ) { this.access = AccessCore.create(accessRepository); diff --git a/server/src/domain/album/dto/album-add-users.dto.ts b/server/src/domain/album/dto/album-add-users.dto.ts index f238b9a05..1a6be4823 100644 --- a/server/src/domain/album/dto/album-add-users.dto.ts +++ b/server/src/domain/album/dto/album-add-users.dto.ts @@ -1,5 +1,5 @@ import { ArrayNotEmpty } from 'class-validator'; -import { ValidateUUID } from '../../domain.util'; +import { ValidateUUID } from 'src/validation'; export class AddUsersDto { @ValidateUUID({ each: true }) diff --git a/server/src/domain/album/dto/album-create.dto.ts b/server/src/domain/album/dto/album-create.dto.ts index bebbed20b..1b4a75332 100644 --- a/server/src/domain/album/dto/album-create.dto.ts +++ b/server/src/domain/album/dto/album-create.dto.ts @@ -1,6 +1,6 @@ import { ApiProperty } from '@nestjs/swagger'; import { IsString } from 'class-validator'; -import { Optional, ValidateUUID } from '../../domain.util'; +import { Optional, ValidateUUID } from 'src/validation'; export class CreateAlbumDto { @IsString() diff --git a/server/src/domain/album/dto/album-update.dto.ts b/server/src/domain/album/dto/album-update.dto.ts index 4f88cefbb..1f329cb3c 100644 --- a/server/src/domain/album/dto/album-update.dto.ts +++ b/server/src/domain/album/dto/album-update.dto.ts @@ -1,7 +1,7 @@ -import { AssetOrder } from '@app/infra/entities'; import { ApiProperty } from '@nestjs/swagger'; import { IsEnum, IsString } from 'class-validator'; -import { Optional, ValidateBoolean, ValidateUUID } from '../../domain.util'; +import { AssetOrder } from 'src/infra/entities/album.entity'; +import { Optional, ValidateBoolean, ValidateUUID } from 'src/validation'; export class UpdateAlbumDto { @Optional() diff --git a/server/src/domain/album/dto/album.dto.ts b/server/src/domain/album/dto/album.dto.ts index b7aad98b5..fe0eb0d5c 100644 --- a/server/src/domain/album/dto/album.dto.ts +++ b/server/src/domain/album/dto/album.dto.ts @@ -1,4 +1,4 @@ -import { ValidateBoolean } from '../../domain.util'; +import { ValidateBoolean } from 'src/validation'; export class AlbumInfoDto { @ValidateBoolean({ optional: true }) diff --git a/server/src/domain/album/dto/get-albums.dto.ts b/server/src/domain/album/dto/get-albums.dto.ts index 2628a3fc7..15e4f1cf2 100644 --- a/server/src/domain/album/dto/get-albums.dto.ts +++ b/server/src/domain/album/dto/get-albums.dto.ts @@ -1,4 +1,4 @@ -import { ValidateBoolean, ValidateUUID } from '../../domain.util'; +import { ValidateBoolean, ValidateUUID } from 'src/validation'; export class GetAlbumsDto { @ValidateBoolean({ optional: true }) diff --git a/server/src/domain/album/dto/index.ts b/server/src/domain/album/dto/index.ts deleted file mode 100644 index b1a4c2141..000000000 --- a/server/src/domain/album/dto/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -export * from './album-add-users.dto'; -export * from './album-create.dto'; -export * from './album-update.dto'; -export * from './album.dto'; -export * from './get-albums.dto'; diff --git a/server/src/domain/album/index.ts b/server/src/domain/album/index.ts deleted file mode 100644 index f06f6d33c..000000000 --- a/server/src/domain/album/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './album-response.dto'; -export * from './album.service'; -export * from './dto'; diff --git a/server/src/domain/api-key/api-key.dto.ts b/server/src/domain/api-key/api-key.dto.ts index c25ef5fd4..1f4f85521 100644 --- a/server/src/domain/api-key/api-key.dto.ts +++ b/server/src/domain/api-key/api-key.dto.ts @@ -1,5 +1,5 @@ import { IsNotEmpty, IsString } from 'class-validator'; -import { Optional } from '../domain.util'; +import { Optional } from 'src/validation'; export class APIKeyCreateDto { @IsString() @IsNotEmpty() diff --git a/server/src/domain/api-key/api-key.service.spec.ts b/server/src/domain/api-key/api-key.service.spec.ts index f3b291084..0400c13a2 100644 --- a/server/src/domain/api-key/api-key.service.spec.ts +++ b/server/src/domain/api-key/api-key.service.spec.ts @@ -1,7 +1,11 @@ import { BadRequestException } from '@nestjs/common'; -import { authStub, keyStub, newCryptoRepositoryMock, newKeyRepositoryMock } from '@test'; -import { ICryptoRepository, IKeyRepository } from '../repositories'; -import { APIKeyService } from './api-key.service'; +import { APIKeyService } from 'src/domain/api-key/api-key.service'; +import { IKeyRepository } from 'src/interfaces/api-key.repository'; +import { ICryptoRepository } from 'src/interfaces/crypto.repository'; +import { keyStub } from 'test/fixtures/api-key.stub'; +import { authStub } from 'test/fixtures/auth.stub'; +import { newKeyRepositoryMock } from 'test/repositories/api-key.repository.mock'; +import { newCryptoRepositoryMock } from 'test/repositories/crypto.repository.mock'; describe(APIKeyService.name, () => { let sut: APIKeyService; diff --git a/server/src/domain/api-key/api-key.service.ts b/server/src/domain/api-key/api-key.service.ts index 0eef1981c..c8e7c5ec3 100644 --- a/server/src/domain/api-key/api-key.service.ts +++ b/server/src/domain/api-key/api-key.service.ts @@ -1,8 +1,9 @@ -import { APIKeyEntity } from '@app/infra/entities'; import { BadRequestException, Inject, Injectable } from '@nestjs/common'; -import { AuthDto } from '../auth'; -import { ICryptoRepository, IKeyRepository } from '../repositories'; -import { APIKeyCreateDto, APIKeyCreateResponseDto, APIKeyResponseDto } from './api-key.dto'; +import { APIKeyCreateDto, APIKeyCreateResponseDto, APIKeyResponseDto } from 'src/domain/api-key/api-key.dto'; +import { AuthDto } from 'src/domain/auth/auth.dto'; +import { APIKeyEntity } from 'src/infra/entities/api-key.entity'; +import { IKeyRepository } from 'src/interfaces/api-key.repository'; +import { ICryptoRepository } from 'src/interfaces/crypto.repository'; @Injectable() export class APIKeyService { diff --git a/server/src/domain/api-key/index.ts b/server/src/domain/api-key/index.ts deleted file mode 100644 index 94076f2a3..000000000 --- a/server/src/domain/api-key/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './api-key.dto'; -export * from './api-key.service'; diff --git a/server/src/domain/asset/asset.service.spec.ts b/server/src/domain/asset/asset.service.spec.ts index 11d2ed00b..9b59f4092 100644 --- a/server/src/domain/asset/asset.service.spec.ts +++ b/server/src/domain/asset/asset.service.spec.ts @@ -1,42 +1,33 @@ -import { AssetEntity, AssetType } from '@app/infra/entities'; import { BadRequestException, UnauthorizedException } from '@nestjs/common'; -import { - IAccessRepositoryMock, - assetStackStub, - assetStub, - authStub, - faceStub, - newAccessRepositoryMock, - newAssetRepositoryMock, - newAssetStackRepositoryMock, - newCommunicationRepositoryMock, - newJobRepositoryMock, - newPartnerRepositoryMock, - newStorageRepositoryMock, - newSystemConfigRepositoryMock, - newUserRepositoryMock, - partnerStub, - userStub, -} from '@test'; import { when } from 'jest-when'; -import { JobName } from '../job'; -import { - AssetStats, - ClientEvent, - IAssetRepository, - IAssetStackRepository, - ICommunicationRepository, - IJobRepository, - IPartnerRepository, - IStorageRepository, - ISystemConfigRepository, - IUserRepository, - JobItem, - TimeBucketSize, -} from '../repositories'; -import { AssetService, UploadFieldName } from './asset.service'; -import { AssetJobName, AssetStatsResponseDto } from './dto'; -import { mapAsset } from './response-dto'; +import { AssetService, UploadFieldName } from 'src/domain/asset/asset.service'; +import { AssetJobName } from 'src/domain/asset/dto/asset-ids.dto'; +import { AssetStatsResponseDto } from 'src/domain/asset/dto/asset-statistics.dto'; +import { mapAsset } from 'src/domain/asset/response-dto/asset-response.dto'; +import { JobName } from 'src/domain/job/job.constants'; +import { AssetEntity, AssetType } from 'src/infra/entities/asset.entity'; +import { IAssetStackRepository } from 'src/interfaces/asset-stack.repository'; +import { AssetStats, IAssetRepository, TimeBucketSize } from 'src/interfaces/asset.repository'; +import { ClientEvent, ICommunicationRepository } from 'src/interfaces/communication.repository'; +import { IJobRepository, JobItem } from 'src/interfaces/job.repository'; +import { IPartnerRepository } from 'src/interfaces/partner.repository'; +import { IStorageRepository } from 'src/interfaces/storage.repository'; +import { ISystemConfigRepository } from 'src/interfaces/system-config.repository'; +import { IUserRepository } from 'src/interfaces/user.repository'; +import { assetStackStub, assetStub } from 'test/fixtures/asset.stub'; +import { authStub } from 'test/fixtures/auth.stub'; +import { faceStub } from 'test/fixtures/face.stub'; +import { partnerStub } from 'test/fixtures/partner.stub'; +import { userStub } from 'test/fixtures/user.stub'; +import { IAccessRepositoryMock, newAccessRepositoryMock } from 'test/repositories/access.repository.mock'; +import { newAssetStackRepositoryMock } from 'test/repositories/asset-stack.repository.mock'; +import { newAssetRepositoryMock } from 'test/repositories/asset.repository.mock'; +import { newCommunicationRepositoryMock } from 'test/repositories/communication.repository.mock'; +import { newJobRepositoryMock } from 'test/repositories/job.repository.mock'; +import { newPartnerRepositoryMock } from 'test/repositories/partner.repository.mock'; +import { newStorageRepositoryMock } from 'test/repositories/storage.repository.mock'; +import { newSystemConfigRepositoryMock } from 'test/repositories/system-config.repository.mock'; +import { newUserRepositoryMock } from 'test/repositories/user.repository.mock'; const stats: AssetStats = { [AssetType.IMAGE]: 10, diff --git a/server/src/domain/asset/asset.service.ts b/server/src/domain/asset/asset.service.ts index fbe4e91bd..2594233c4 100644 --- a/server/src/domain/asset/asset.service.ts +++ b/server/src/domain/asset/asset.service.ts @@ -1,54 +1,43 @@ -import { AssetEntity, LibraryType } from '@app/infra/entities'; -import { ImmichLogger } from '@app/infra/logger'; import { BadRequestException, Inject } from '@nestjs/common'; import _ from 'lodash'; import { DateTime, Duration } from 'luxon'; import { extname } from 'node:path'; import sanitize from 'sanitize-filename'; -import { AccessCore, Permission } from '../access'; -import { AuthDto } from '../auth'; -import { mimeTypes } from '../domain.constant'; -import { usePagination } from '../domain.util'; -import { IAssetDeletionJob, ISidecarWriteJob, JOBS_ASSET_PAGINATION_SIZE, JobName } from '../job'; -import { - ClientEvent, - IAccessRepository, - IAssetRepository, - IAssetStackRepository, - ICommunicationRepository, - IJobRepository, - IPartnerRepository, - IStorageRepository, - ISystemConfigRepository, - IUserRepository, - JobItem, - JobStatus, - TimeBucketOptions, -} from '../repositories'; -import { StorageCore, StorageFolder } from '../storage'; -import { SystemConfigCore } from '../system-config'; -import { - AssetBulkDeleteDto, - AssetBulkUpdateDto, - AssetJobName, - AssetJobsDto, - AssetStatsDto, - MapMarkerDto, - MemoryLaneDto, - TimeBucketAssetDto, - TimeBucketDto, - UpdateAssetDto, - UpdateStackParentDto, - mapStats, -} from './dto'; +import { AccessCore, Permission } from 'src/cores/access.core'; +import { StorageCore, StorageFolder } from 'src/cores/storage.core'; +import { SystemConfigCore } from 'src/cores/system-config.core'; +import { AssetJobName, AssetJobsDto } from 'src/domain/asset/dto/asset-ids.dto'; +import { UpdateStackParentDto } from 'src/domain/asset/dto/asset-stack.dto'; +import { AssetStatsDto, mapStats } from 'src/domain/asset/dto/asset-statistics.dto'; +import { AssetBulkDeleteDto, AssetBulkUpdateDto, UpdateAssetDto } from 'src/domain/asset/dto/asset.dto'; +import { MapMarkerDto } from 'src/domain/asset/dto/map-marker.dto'; +import { MemoryLaneDto } from 'src/domain/asset/dto/memory-lane.dto'; +import { TimeBucketAssetDto, TimeBucketDto } from 'src/domain/asset/dto/time-bucket.dto'; import { AssetResponseDto, - MapMarkerResponseDto, MemoryLaneResponseDto, SanitizedAssetResponseDto, - TimeBucketResponseDto, mapAsset, -} from './response-dto'; +} from 'src/domain/asset/response-dto/asset-response.dto'; +import { MapMarkerResponseDto } from 'src/domain/asset/response-dto/map-marker-response.dto'; +import { TimeBucketResponseDto } from 'src/domain/asset/response-dto/time-bucket-response.dto'; +import { AuthDto } from 'src/domain/auth/auth.dto'; +import { mimeTypes } from 'src/domain/domain.constant'; +import { JOBS_ASSET_PAGINATION_SIZE, JobName } from 'src/domain/job/job.constants'; +import { IAssetDeletionJob, ISidecarWriteJob } from 'src/domain/job/job.interface'; +import { AssetEntity } from 'src/infra/entities/asset.entity'; +import { LibraryType } from 'src/infra/entities/library.entity'; +import { ImmichLogger } from 'src/infra/logger'; +import { IAccessRepository } from 'src/interfaces/access.repository'; +import { IAssetStackRepository } from 'src/interfaces/asset-stack.repository'; +import { IAssetRepository, TimeBucketOptions } from 'src/interfaces/asset.repository'; +import { ClientEvent, ICommunicationRepository } from 'src/interfaces/communication.repository'; +import { IJobRepository, JobItem, JobStatus } from 'src/interfaces/job.repository'; +import { IPartnerRepository } from 'src/interfaces/partner.repository'; +import { IStorageRepository } from 'src/interfaces/storage.repository'; +import { ISystemConfigRepository } from 'src/interfaces/system-config.repository'; +import { IUserRepository } from 'src/interfaces/user.repository'; +import { usePagination } from 'src/utils'; export enum UploadFieldName { ASSET_DATA = 'assetData', diff --git a/server/src/domain/asset/dto/asset-ids.dto.ts b/server/src/domain/asset/dto/asset-ids.dto.ts index 5ee988bb4..ea875e85e 100644 --- a/server/src/domain/asset/dto/asset-ids.dto.ts +++ b/server/src/domain/asset/dto/asset-ids.dto.ts @@ -1,6 +1,6 @@ import { ApiProperty } from '@nestjs/swagger'; import { IsEnum } from 'class-validator'; -import { ValidateUUID } from '../../domain.util'; +import { ValidateUUID } from 'src/validation'; export class AssetIdsDto { @ValidateUUID({ each: true }) diff --git a/server/src/domain/asset/dto/asset-stack.dto.ts b/server/src/domain/asset/dto/asset-stack.dto.ts index 80dabdb34..3ff04ee5e 100644 --- a/server/src/domain/asset/dto/asset-stack.dto.ts +++ b/server/src/domain/asset/dto/asset-stack.dto.ts @@ -1,4 +1,4 @@ -import { ValidateUUID } from '../../domain.util'; +import { ValidateUUID } from 'src/validation'; export class UpdateStackParentDto { @ValidateUUID() diff --git a/server/src/domain/asset/dto/asset-statistics.dto.ts b/server/src/domain/asset/dto/asset-statistics.dto.ts index c313ccdf4..45bae1f5d 100644 --- a/server/src/domain/asset/dto/asset-statistics.dto.ts +++ b/server/src/domain/asset/dto/asset-statistics.dto.ts @@ -1,7 +1,7 @@ -import { AssetType } from '@app/infra/entities'; import { ApiProperty } from '@nestjs/swagger'; -import { ValidateBoolean } from '../../domain.util'; -import { AssetStats } from '../../repositories'; +import { AssetType } from 'src/infra/entities/asset.entity'; +import { AssetStats } from 'src/interfaces/asset.repository'; +import { ValidateBoolean } from 'src/validation'; export class AssetStatsDto { @ValidateBoolean({ optional: true }) diff --git a/server/src/domain/asset/dto/asset.dto.ts b/server/src/domain/asset/dto/asset.dto.ts index 2abe31d0a..a93a59ae3 100644 --- a/server/src/domain/asset/dto/asset.dto.ts +++ b/server/src/domain/asset/dto/asset.dto.ts @@ -9,8 +9,8 @@ import { IsString, ValidateIf, } from 'class-validator'; -import { Optional, ValidateBoolean, ValidateUUID } from '../../domain.util'; -import { BulkIdsDto } from '../response-dto'; +import { BulkIdsDto } from 'src/domain/asset/response-dto/asset-ids-response.dto'; +import { Optional, ValidateBoolean, ValidateUUID } from 'src/validation'; export class DeviceIdDto { @IsNotEmpty() diff --git a/server/src/domain/asset/dto/index.ts b/server/src/domain/asset/dto/index.ts deleted file mode 100644 index bc7a100b9..000000000 --- a/server/src/domain/asset/dto/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -export * from './asset-ids.dto'; -export * from './asset-stack.dto'; -export * from './asset-statistics.dto'; -export * from './asset.dto'; -export * from './map-marker.dto'; -export * from './memory-lane.dto'; -export * from './time-bucket.dto'; diff --git a/server/src/domain/asset/dto/map-marker.dto.ts b/server/src/domain/asset/dto/map-marker.dto.ts index 4fe6c16b8..158750e51 100644 --- a/server/src/domain/asset/dto/map-marker.dto.ts +++ b/server/src/domain/asset/dto/map-marker.dto.ts @@ -1,4 +1,4 @@ -import { ValidateBoolean, ValidateDate } from '../../domain.util'; +import { ValidateBoolean, ValidateDate } from 'src/validation'; export class MapMarkerDto { @ValidateBoolean({ optional: true }) diff --git a/server/src/domain/asset/dto/time-bucket.dto.ts b/server/src/domain/asset/dto/time-bucket.dto.ts index 7c5b9c212..7b2ee24c0 100644 --- a/server/src/domain/asset/dto/time-bucket.dto.ts +++ b/server/src/domain/asset/dto/time-bucket.dto.ts @@ -1,8 +1,8 @@ -import { AssetOrder } from '@app/infra/entities'; import { ApiProperty } from '@nestjs/swagger'; import { IsEnum, IsNotEmpty, IsString } from 'class-validator'; -import { Optional, ValidateBoolean, ValidateUUID } from '../../domain.util'; -import { TimeBucketSize } from '../../repositories'; +import { AssetOrder } from 'src/infra/entities/album.entity'; +import { TimeBucketSize } from 'src/interfaces/asset.repository'; +import { Optional, ValidateBoolean, ValidateUUID } from 'src/validation'; export class TimeBucketDto { @IsNotEmpty() diff --git a/server/src/domain/asset/index.ts b/server/src/domain/asset/index.ts deleted file mode 100644 index 71ad3c8c4..000000000 --- a/server/src/domain/asset/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './asset.service'; -export * from './dto'; -export * from './response-dto'; diff --git a/server/src/domain/asset/response-dto/asset-ids-response.dto.ts b/server/src/domain/asset/response-dto/asset-ids-response.dto.ts index 9bb6a5b36..fdc9942e3 100644 --- a/server/src/domain/asset/response-dto/asset-ids-response.dto.ts +++ b/server/src/domain/asset/response-dto/asset-ids-response.dto.ts @@ -1,4 +1,4 @@ -import { ValidateUUID } from '../../domain.util'; +import { ValidateUUID } from 'src/validation'; /** @deprecated Use `BulkIdResponseDto` instead */ export enum AssetIdErrorReason { diff --git a/server/src/domain/asset/response-dto/asset-response.dto.ts b/server/src/domain/asset/response-dto/asset-response.dto.ts index 2961a9dcc..a5c88b4cb 100644 --- a/server/src/domain/asset/response-dto/asset-response.dto.ts +++ b/server/src/domain/asset/response-dto/asset-response.dto.ts @@ -1,11 +1,12 @@ -import { AuthDto } from '@app/domain/auth/auth.dto'; -import { AssetEntity, AssetFaceEntity, AssetType } from '@app/infra/entities'; import { ApiProperty } from '@nestjs/swagger'; -import { PersonWithFacesResponseDto, mapFacesWithoutPerson, mapPerson } from '../../person/person.dto'; -import { TagResponseDto, mapTag } from '../../tag'; -import { UserResponseDto, mapUser } from '../../user/response-dto/user-response.dto'; -import { ExifResponseDto, mapExif } from './exif-response.dto'; -import { SmartInfoResponseDto, mapSmartInfo } from './smart-info-response.dto'; +import { ExifResponseDto, mapExif } from 'src/domain/asset/response-dto/exif-response.dto'; +import { SmartInfoResponseDto, mapSmartInfo } from 'src/domain/asset/response-dto/smart-info-response.dto'; +import { AuthDto } from 'src/domain/auth/auth.dto'; +import { PersonWithFacesResponseDto, mapFacesWithoutPerson, mapPerson } from 'src/domain/person/person.dto'; +import { TagResponseDto, mapTag } from 'src/domain/tag/tag-response.dto'; +import { UserResponseDto, mapUser } from 'src/domain/user/response-dto/user-response.dto'; +import { AssetFaceEntity } from 'src/infra/entities/asset-face.entity'; +import { AssetEntity, AssetType } from 'src/infra/entities/asset.entity'; export class SanitizedAssetResponseDto { id!: string; diff --git a/server/src/domain/asset/response-dto/exif-response.dto.ts b/server/src/domain/asset/response-dto/exif-response.dto.ts index f4d0226b4..fbef6cbbc 100644 --- a/server/src/domain/asset/response-dto/exif-response.dto.ts +++ b/server/src/domain/asset/response-dto/exif-response.dto.ts @@ -1,5 +1,5 @@ -import { ExifEntity } from '@app/infra/entities'; import { ApiProperty } from '@nestjs/swagger'; +import { ExifEntity } from 'src/infra/entities/exif.entity'; export class ExifResponseDto { make?: string | null = null; diff --git a/server/src/domain/asset/response-dto/index.ts b/server/src/domain/asset/response-dto/index.ts deleted file mode 100644 index 7ed99db13..000000000 --- a/server/src/domain/asset/response-dto/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -export * from './asset-ids-response.dto'; -export * from './asset-response.dto'; -export * from './exif-response.dto'; -export * from './map-marker-response.dto'; -export * from './smart-info-response.dto'; -export * from './time-bucket-response.dto'; diff --git a/server/src/domain/asset/response-dto/smart-info-response.dto.ts b/server/src/domain/asset/response-dto/smart-info-response.dto.ts index 72c336205..840fca8a7 100644 --- a/server/src/domain/asset/response-dto/smart-info-response.dto.ts +++ b/server/src/domain/asset/response-dto/smart-info-response.dto.ts @@ -1,4 +1,4 @@ -import { SmartInfoEntity } from '@app/infra/entities'; +import { SmartInfoEntity } from 'src/infra/entities/smart-info.entity'; export class SmartInfoResponseDto { tags?: string[] | null; diff --git a/server/src/domain/audit/audit.dto.ts b/server/src/domain/audit/audit.dto.ts index 0f3f04dab..1c34e9491 100644 --- a/server/src/domain/audit/audit.dto.ts +++ b/server/src/domain/audit/audit.dto.ts @@ -1,8 +1,9 @@ -import { AssetPathType, EntityType, PathType, PersonPathType, UserPathType } from '@app/infra/entities'; import { ApiProperty } from '@nestjs/swagger'; import { Type } from 'class-transformer'; import { IsArray, IsEnum, IsString, IsUUID, ValidateNested } from 'class-validator'; -import { Optional, ValidateDate, ValidateUUID } from '../domain.util'; +import { EntityType } from 'src/infra/entities/audit.entity'; +import { AssetPathType, PathType, PersonPathType, UserPathType } from 'src/infra/entities/move.entity'; +import { Optional, ValidateDate, ValidateUUID } from 'src/validation'; const PathEnum = Object.values({ ...AssetPathType, ...PersonPathType, ...UserPathType }); diff --git a/server/src/domain/audit/audit.service.spec.ts b/server/src/domain/audit/audit.service.spec.ts index 82c6cc699..aa110dfb2 100644 --- a/server/src/domain/audit/audit.service.spec.ts +++ b/server/src/domain/audit/audit.service.spec.ts @@ -1,26 +1,21 @@ -import { DatabaseAction, EntityType } from '@app/infra/entities'; -import { - IAccessRepositoryMock, - auditStub, - authStub, - newAccessRepositoryMock, - newAssetRepositoryMock, - newAuditRepositoryMock, - newCryptoRepositoryMock, - newPersonRepositoryMock, - newStorageRepositoryMock, - newUserRepositoryMock, -} from '@test'; -import { - IAssetRepository, - IAuditRepository, - ICryptoRepository, - IPersonRepository, - IStorageRepository, - IUserRepository, - JobStatus, -} from '../repositories'; -import { AuditService } from './audit.service'; +import { AuditService } from 'src/domain/audit/audit.service'; +import { DatabaseAction, EntityType } from 'src/infra/entities/audit.entity'; +import { IAssetRepository } from 'src/interfaces/asset.repository'; +import { IAuditRepository } from 'src/interfaces/audit.repository'; +import { ICryptoRepository } from 'src/interfaces/crypto.repository'; +import { JobStatus } from 'src/interfaces/job.repository'; +import { IPersonRepository } from 'src/interfaces/person.repository'; +import { IStorageRepository } from 'src/interfaces/storage.repository'; +import { IUserRepository } from 'src/interfaces/user.repository'; +import { auditStub } from 'test/fixtures/audit.stub'; +import { authStub } from 'test/fixtures/auth.stub'; +import { IAccessRepositoryMock, newAccessRepositoryMock } from 'test/repositories/access.repository.mock'; +import { newAssetRepositoryMock } from 'test/repositories/asset.repository.mock'; +import { newAuditRepositoryMock } from 'test/repositories/audit.repository.mock'; +import { newCryptoRepositoryMock } from 'test/repositories/crypto.repository.mock'; +import { newPersonRepositoryMock } from 'test/repositories/person.repository.mock'; +import { newStorageRepositoryMock } from 'test/repositories/storage.repository.mock'; +import { newUserRepositoryMock } from 'test/repositories/user.repository.mock'; describe(AuditService.name, () => { let sut: AuditService; diff --git a/server/src/domain/audit/audit.service.ts b/server/src/domain/audit/audit.service.ts index c96f36d74..cf8b7f303 100644 --- a/server/src/domain/audit/audit.service.ts +++ b/server/src/domain/audit/audit.service.ts @@ -1,24 +1,8 @@ -import { AssetPathType, DatabaseAction, PersonPathType, UserPathType } from '@app/infra/entities'; -import { ImmichLogger } from '@app/infra/logger'; import { BadRequestException, Inject, Injectable } from '@nestjs/common'; import { DateTime } from 'luxon'; import { resolve } from 'node:path'; -import { AccessCore, Permission } from '../access'; -import { AuthDto } from '../auth'; -import { AUDIT_LOG_MAX_DURATION } from '../domain.constant'; -import { usePagination } from '../domain.util'; -import { JOBS_ASSET_PAGINATION_SIZE } from '../job'; -import { - IAccessRepository, - IAssetRepository, - IAuditRepository, - ICryptoRepository, - IPersonRepository, - IStorageRepository, - IUserRepository, - JobStatus, -} from '../repositories'; -import { StorageCore, StorageFolder } from '../storage'; +import { AccessCore, Permission } from 'src/cores/access.core'; +import { StorageCore, StorageFolder } from 'src/cores/storage.core'; import { AuditDeletesDto, AuditDeletesResponseDto, @@ -26,7 +10,22 @@ import { FileChecksumResponseDto, FileReportItemDto, PathEntityType, -} from './audit.dto'; +} from 'src/domain/audit/audit.dto'; +import { AuthDto } from 'src/domain/auth/auth.dto'; +import { AUDIT_LOG_MAX_DURATION } from 'src/domain/domain.constant'; +import { JOBS_ASSET_PAGINATION_SIZE } from 'src/domain/job/job.constants'; +import { DatabaseAction } from 'src/infra/entities/audit.entity'; +import { AssetPathType, PersonPathType, UserPathType } from 'src/infra/entities/move.entity'; +import { ImmichLogger } from 'src/infra/logger'; +import { IAccessRepository } from 'src/interfaces/access.repository'; +import { IAssetRepository } from 'src/interfaces/asset.repository'; +import { IAuditRepository } from 'src/interfaces/audit.repository'; +import { ICryptoRepository } from 'src/interfaces/crypto.repository'; +import { JobStatus } from 'src/interfaces/job.repository'; +import { IPersonRepository } from 'src/interfaces/person.repository'; +import { IStorageRepository } from 'src/interfaces/storage.repository'; +import { IUserRepository } from 'src/interfaces/user.repository'; +import { usePagination } from 'src/utils'; @Injectable() export class AuditService { diff --git a/server/src/domain/audit/index.ts b/server/src/domain/audit/index.ts deleted file mode 100644 index febebf0f6..000000000 --- a/server/src/domain/audit/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './audit.dto'; -export * from './audit.service'; diff --git a/server/src/domain/auth/auth.dto.ts b/server/src/domain/auth/auth.dto.ts index 2f6f4b4b7..1694231ab 100644 --- a/server/src/domain/auth/auth.dto.ts +++ b/server/src/domain/auth/auth.dto.ts @@ -1,7 +1,10 @@ -import { APIKeyEntity, SharedLinkEntity, UserEntity, UserTokenEntity } from '@app/infra/entities'; import { ApiProperty } from '@nestjs/swagger'; import { Transform } from 'class-transformer'; import { IsEmail, IsNotEmpty, IsString, MinLength } from 'class-validator'; +import { APIKeyEntity } from 'src/infra/entities/api-key.entity'; +import { SharedLinkEntity } from 'src/infra/entities/shared-link.entity'; +import { UserTokenEntity } from 'src/infra/entities/user-token.entity'; +import { UserEntity } from 'src/infra/entities/user.entity'; export class AuthDto { user!: UserEntity; diff --git a/server/src/domain/auth/auth.service.spec.ts b/server/src/domain/auth/auth.service.spec.ts index 214b6748e..2cbc3cb6e 100644 --- a/server/src/domain/auth/auth.service.spec.ts +++ b/server/src/domain/auth/auth.service.spec.ts @@ -1,38 +1,32 @@ -import { UserEntity } from '@app/infra/entities'; import { BadRequestException, UnauthorizedException } from '@nestjs/common'; -import { - IAccessRepositoryMock, - authStub, - keyStub, - loginResponseStub, - newAccessRepositoryMock, - newCryptoRepositoryMock, - newKeyRepositoryMock, - newLibraryRepositoryMock, - newSharedLinkRepositoryMock, - newSystemConfigRepositoryMock, - newUserRepositoryMock, - newUserTokenRepositoryMock, - sharedLinkStub, - systemConfigStub, - userStub, - userTokenStub, -} from '@test'; import { IncomingHttpHeaders } from 'node:http'; import { Issuer, generators } from 'openid-client'; import { Socket } from 'socket.io'; -import { - ICryptoRepository, - IKeyRepository, - ILibraryRepository, - ISharedLinkRepository, - ISystemConfigRepository, - IUserRepository, - IUserTokenRepository, -} from '../repositories'; -import { AuthType } from './auth.constant'; -import { AuthDto, SignUpDto } from './auth.dto'; -import { AuthService } from './auth.service'; +import { AuthType } from 'src/domain/auth/auth.constant'; +import { AuthDto, SignUpDto } from 'src/domain/auth/auth.dto'; +import { AuthService } from 'src/domain/auth/auth.service'; +import { UserEntity } from 'src/infra/entities/user.entity'; +import { IKeyRepository } from 'src/interfaces/api-key.repository'; +import { ICryptoRepository } from 'src/interfaces/crypto.repository'; +import { ILibraryRepository } from 'src/interfaces/library.repository'; +import { ISharedLinkRepository } from 'src/interfaces/shared-link.repository'; +import { ISystemConfigRepository } from 'src/interfaces/system-config.repository'; +import { IUserTokenRepository } from 'src/interfaces/user-token.repository'; +import { IUserRepository } from 'src/interfaces/user.repository'; +import { keyStub } from 'test/fixtures/api-key.stub'; +import { authStub, loginResponseStub } from 'test/fixtures/auth.stub'; +import { sharedLinkStub } from 'test/fixtures/shared-link.stub'; +import { systemConfigStub } from 'test/fixtures/system-config.stub'; +import { userTokenStub } from 'test/fixtures/user-token.stub'; +import { userStub } from 'test/fixtures/user.stub'; +import { IAccessRepositoryMock, newAccessRepositoryMock } from 'test/repositories/access.repository.mock'; +import { newKeyRepositoryMock } from 'test/repositories/api-key.repository.mock'; +import { newCryptoRepositoryMock } from 'test/repositories/crypto.repository.mock'; +import { newLibraryRepositoryMock } from 'test/repositories/library.repository.mock'; +import { newSharedLinkRepositoryMock } from 'test/repositories/shared-link.repository.mock'; +import { newSystemConfigRepositoryMock } from 'test/repositories/system-config.repository.mock'; +import { newUserTokenRepositoryMock } from 'test/repositories/user-token.repository.mock'; +import { newUserRepositoryMock } from 'test/repositories/user.repository.mock'; // const token = Buffer.from('my-api-key', 'utf8').toString('base64'); diff --git a/server/src/domain/auth/auth.service.ts b/server/src/domain/auth/auth.service.ts index fe01ef39b..0f2709307 100644 --- a/server/src/domain/auth/auth.service.ts +++ b/server/src/domain/auth/auth.service.ts @@ -1,5 +1,3 @@ -import { SystemConfig, UserEntity } from '@app/infra/entities'; -import { ImmichLogger } from '@app/infra/logger'; import { BadRequestException, Inject, @@ -12,20 +10,9 @@ import cookieParser from 'cookie'; import { DateTime } from 'luxon'; import { IncomingHttpHeaders } from 'node:http'; import { ClientMetadata, Issuer, UserinfoResponse, custom, generators } from 'openid-client'; -import { AccessCore, Permission } from '../access'; -import { HumanReadableSize } from '../domain.util'; -import { - IAccessRepository, - ICryptoRepository, - IKeyRepository, - ILibraryRepository, - ISharedLinkRepository, - ISystemConfigRepository, - IUserRepository, - IUserTokenRepository, -} from '../repositories'; -import { SystemConfigCore } from '../system-config/system-config.core'; -import { UserCore, UserResponseDto, mapUser } from '../user'; +import { AccessCore, Permission } from 'src/cores/access.core'; +import { SystemConfigCore } from 'src/cores/system-config.core'; +import { UserCore } from 'src/cores/user.core'; import { AuthType, IMMICH_ACCESS_COOKIE, @@ -34,7 +21,7 @@ import { IMMICH_IS_AUTHENTICATED, LOGIN_URL, MOBILE_REDIRECT, -} from './auth.constant'; +} from 'src/domain/auth/auth.constant'; import { AuthDeviceResponseDto, AuthDto, @@ -48,7 +35,20 @@ import { SignUpDto, mapLoginResponse, mapUserToken, -} from './auth.dto'; +} from 'src/domain/auth/auth.dto'; +import { UserResponseDto, mapUser } from 'src/domain/user/response-dto/user-response.dto'; +import { SystemConfig } from 'src/infra/entities/system-config.entity'; +import { UserEntity } from 'src/infra/entities/user.entity'; +import { ImmichLogger } from 'src/infra/logger'; +import { IAccessRepository } from 'src/interfaces/access.repository'; +import { IKeyRepository } from 'src/interfaces/api-key.repository'; +import { ICryptoRepository } from 'src/interfaces/crypto.repository'; +import { ILibraryRepository } from 'src/interfaces/library.repository'; +import { ISharedLinkRepository } from 'src/interfaces/shared-link.repository'; +import { ISystemConfigRepository } from 'src/interfaces/system-config.repository'; +import { IUserTokenRepository } from 'src/interfaces/user-token.repository'; +import { IUserRepository } from 'src/interfaces/user.repository'; +import { HumanReadableSize } from 'src/utils'; export interface LoginDetails { isSecure: boolean; diff --git a/server/src/domain/auth/index.ts b/server/src/domain/auth/index.ts deleted file mode 100644 index 52e0463bc..000000000 --- a/server/src/domain/auth/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './auth.constant'; -export * from './auth.dto'; -export * from './auth.service'; diff --git a/server/src/domain/database/database.service.spec.ts b/server/src/domain/database/database.service.spec.ts index 14464c0cd..191c0221b 100644 --- a/server/src/domain/database/database.service.spec.ts +++ b/server/src/domain/database/database.service.spec.ts @@ -1,13 +1,8 @@ -import { - DatabaseExtension, - DatabaseService, - IDatabaseRepository, - VectorIndex, - Version, - VersionType, -} from '@app/domain'; -import { ImmichLogger } from '@app/infra/logger'; -import { newDatabaseRepositoryMock } from '@test'; +import { DatabaseService } from 'src/domain/database/database.service'; +import { Version, VersionType } from 'src/domain/domain.constant'; +import { ImmichLogger } from 'src/infra/logger'; +import { DatabaseExtension, IDatabaseRepository, VectorIndex } from 'src/interfaces/database.repository'; +import { newDatabaseRepositoryMock } from 'test/repositories/database.repository.mock'; describe(DatabaseService.name, () => { let sut: DatabaseService; diff --git a/server/src/domain/database/database.service.ts b/server/src/domain/database/database.service.ts index 946c6dac8..7bb16a4e1 100644 --- a/server/src/domain/database/database.service.ts +++ b/server/src/domain/database/database.service.ts @@ -1,6 +1,6 @@ -import { ImmichLogger } from '@app/infra/logger'; import { Inject, Injectable } from '@nestjs/common'; -import { Version, VersionType } from '../domain.constant'; +import { Version, VersionType } from 'src/domain/domain.constant'; +import { ImmichLogger } from 'src/infra/logger'; import { DatabaseExtension, DatabaseLock, @@ -8,7 +8,7 @@ import { VectorExtension, VectorIndex, extName, -} from '../repositories'; +} from 'src/interfaces/database.repository'; @Injectable() export class DatabaseService { diff --git a/server/src/domain/database/index.ts b/server/src/domain/database/index.ts deleted file mode 100644 index cd4e1d217..000000000 --- a/server/src/domain/database/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './database.service'; diff --git a/server/src/domain/domain.config.ts b/server/src/domain/domain.config.ts deleted file mode 100644 index b0471080d..000000000 --- a/server/src/domain/domain.config.ts +++ /dev/null @@ -1,36 +0,0 @@ -// TODO: remove nestjs references from domain -import { LogLevel } from '@app/infra/entities'; -import { ConfigModuleOptions } from '@nestjs/config'; -import Joi from 'joi'; - -const WHEN_DB_URL_SET = Joi.when('DB_URL', { - is: Joi.exist(), - then: Joi.string().optional(), - otherwise: Joi.string().required(), -}); - -export const immichAppConfig: ConfigModuleOptions = { - envFilePath: '.env', - isGlobal: true, - validationSchema: Joi.object({ - NODE_ENV: Joi.string().optional().valid('development', 'production', 'staging').default('development'), - LOG_LEVEL: Joi.string() - .optional() - .valid(...Object.values(LogLevel)), - - DB_USERNAME: WHEN_DB_URL_SET, - DB_PASSWORD: WHEN_DB_URL_SET, - DB_DATABASE_NAME: WHEN_DB_URL_SET, - DB_URL: Joi.string().optional(), - DB_VECTOR_EXTENSION: Joi.string().optional().valid('pgvector', 'pgvecto.rs').default('pgvecto.rs'), - - MACHINE_LEARNING_PORT: Joi.number().optional(), - MICROSERVICES_PORT: Joi.number().optional(), - IMMICH_METRICS_PORT: Joi.number().optional(), - - IMMICH_METRICS: Joi.boolean().optional().default(false), - IMMICH_HOST_METRICS: Joi.boolean().optional().default(false), - IMMICH_API_METRICS: Joi.boolean().optional().default(false), - IMMICH_IO_METRICS: Joi.boolean().optional().default(false), - }), -}; diff --git a/server/src/domain/domain.constant.spec.ts b/server/src/domain/domain.constant.spec.ts index 70944328e..e8a1c7b72 100644 --- a/server/src/domain/domain.constant.spec.ts +++ b/server/src/domain/domain.constant.spec.ts @@ -1,4 +1,4 @@ -import { Version, VersionType, mimeTypes } from './domain.constant'; +import { mimeTypes, Version, VersionType } from 'src/domain/domain.constant'; describe('mimeTypes', () => { for (const { mimetype, extension } of [ diff --git a/server/src/domain/domain.constant.ts b/server/src/domain/domain.constant.ts index 56b455855..13c8fa532 100644 --- a/server/src/domain/domain.constant.ts +++ b/server/src/domain/domain.constant.ts @@ -1,7 +1,7 @@ -import { AssetType } from '@app/infra/entities'; import { Duration } from 'luxon'; import { readFileSync } from 'node:fs'; import { extname, join } from 'node:path'; +import { AssetType } from 'src/infra/entities/asset.entity'; export const AUDIT_LOG_MAX_DURATION = Duration.fromObject({ days: 100 }); export const ONE_HOUR = Duration.fromObject({ hours: 1 }); diff --git a/server/src/domain/domain.module.ts b/server/src/domain/domain.module.ts index c3e62edb5..04d7c51f4 100644 --- a/server/src/domain/domain.module.ts +++ b/server/src/domain/domain.module.ts @@ -1,29 +1,29 @@ -import { ImmichLogger } from '@app/infra/logger'; import { Global, Module, Provider } from '@nestjs/common'; -import { ActivityService } from './activity'; -import { AlbumService } from './album'; -import { APIKeyService } from './api-key'; -import { AssetService } from './asset'; -import { AuditService } from './audit'; -import { AuthService } from './auth'; -import { DatabaseService } from './database'; -import { DownloadService } from './download'; -import { JobService } from './job'; -import { LibraryService } from './library'; -import { MediaService } from './media'; -import { MetadataService } from './metadata'; -import { PartnerService } from './partner'; -import { PersonService } from './person'; -import { SearchService } from './search'; -import { ServerInfoService } from './server-info'; -import { SharedLinkService } from './shared-link'; -import { SmartInfoService } from './smart-info'; -import { StorageService } from './storage'; -import { StorageTemplateService } from './storage-template'; -import { SystemConfigService } from './system-config'; -import { TagService } from './tag'; -import { TrashService } from './trash'; -import { UserService } from './user'; +import { ActivityService } from 'src/domain/activity/activity.service'; +import { AlbumService } from 'src/domain/album/album.service'; +import { APIKeyService } from 'src/domain/api-key/api-key.service'; +import { AssetService } from 'src/domain/asset/asset.service'; +import { AuditService } from 'src/domain/audit/audit.service'; +import { AuthService } from 'src/domain/auth/auth.service'; +import { DatabaseService } from 'src/domain/database/database.service'; +import { DownloadService } from 'src/domain/download/download.service'; +import { JobService } from 'src/domain/job/job.service'; +import { LibraryService } from 'src/domain/library/library.service'; +import { MediaService } from 'src/domain/media/media.service'; +import { MetadataService } from 'src/domain/metadata/metadata.service'; +import { PartnerService } from 'src/domain/partner/partner.service'; +import { PersonService } from 'src/domain/person/person.service'; +import { SearchService } from 'src/domain/search/search.service'; +import { ServerInfoService } from 'src/domain/server-info/server-info.service'; +import { SharedLinkService } from 'src/domain/shared-link/shared-link.service'; +import { SmartInfoService } from 'src/domain/smart-info/smart-info.service'; +import { StorageTemplateService } from 'src/domain/storage-template/storage-template.service'; +import { StorageService } from 'src/domain/storage/storage.service'; +import { SystemConfigService } from 'src/domain/system-config/system-config.service'; +import { TagService } from 'src/domain/tag/tag.service'; +import { TrashService } from 'src/domain/trash/trash.service'; +import { UserService } from 'src/domain/user/user.service'; +import { ImmichLogger } from 'src/infra/logger'; const providers: Provider[] = [ APIKeyService, diff --git a/server/src/domain/domain.util.ts b/server/src/domain/domain.util.ts deleted file mode 100644 index a079ff6bf..000000000 --- a/server/src/domain/domain.util.ts +++ /dev/null @@ -1,280 +0,0 @@ -import { ImmichLogger } from '@app/infra/logger'; -import { BadRequestException, applyDecorators } from '@nestjs/common'; -import { ApiProperty } from '@nestjs/swagger'; -import { Transform } from 'class-transformer'; -import { - IsArray, - IsBoolean, - IsDate, - IsNotEmpty, - IsOptional, - IsString, - IsUUID, - ValidateIf, - ValidationOptions, - isDateString, -} from 'class-validator'; -import { CronJob } from 'cron'; -import _ from 'lodash'; -import { basename, extname } from 'node:path'; -import sanitize from 'sanitize-filename'; - -export enum CacheControl { - PRIVATE_WITH_CACHE = 'private_with_cache', - PRIVATE_WITHOUT_CACHE = 'private_without_cache', - NONE = 'none', -} - -export class ImmichFileResponse { - public readonly path!: string; - public readonly contentType!: string; - public readonly cacheControl!: CacheControl; - - constructor(response: ImmichFileResponse) { - Object.assign(this, response); - } -} - -export interface OpenGraphTags { - title: string; - description: string; - imageUrl?: string; -} - -export const isConnectionAborted = (error: Error | any) => error.code === 'ECONNABORTED'; - -type UUIDOptions = { optional?: boolean; each?: boolean }; -export const ValidateUUID = (options?: UUIDOptions) => { - const { optional, each } = { optional: false, each: false, ...options }; - return applyDecorators( - IsUUID('4', { each }), - ApiProperty({ format: 'uuid' }), - optional ? Optional() : IsNotEmpty(), - each ? IsArray() : IsString(), - ); -}; - -type DateOptions = { optional?: boolean; nullable?: boolean; format?: 'date' | 'date-time' }; -export const ValidateDate = (options?: DateOptions) => { - const { optional, nullable, format } = { optional: false, nullable: false, format: 'date-time', ...options }; - - const decorators = [ - ApiProperty({ format }), - IsDate(), - optional ? Optional({ nullable: true }) : IsNotEmpty(), - Transform(({ key, value }) => { - if (value === null || value === undefined) { - return value; - } - - if (!isDateString(value)) { - throw new BadRequestException(`${key} must be a date string`); - } - - return new Date(value as string); - }), - ]; - - if (optional) { - decorators.push(Optional({ nullable })); - } - - return applyDecorators(...decorators); -}; - -type BooleanOptions = { optional?: boolean }; -export const ValidateBoolean = (options?: BooleanOptions) => { - const { optional } = { optional: false, ...options }; - const decorators = [ - // ApiProperty(), - IsBoolean(), - Transform(({ value }) => { - if (value == 'true') { - return true; - } else if (value == 'false') { - return false; - } - return value; - }), - ]; - - if (optional) { - decorators.push(Optional()); - } - - return applyDecorators(...decorators); -}; - -export function validateCronExpression(expression: string) { - try { - new CronJob(expression, () => {}); - } catch { - return false; - } - - return true; -} - -type IValue = { value: string }; - -export const toEmail = ({ value }: IValue) => value?.toLowerCase(); - -export const toSanitized = ({ value }: IValue) => sanitize((value || '').replaceAll('.', '')); - -export function getFileNameWithoutExtension(path: string): string { - return basename(path, extname(path)); -} - -export function getLivePhotoMotionFilename(stillName: string, motionName: string) { - return getFileNameWithoutExtension(stillName) + extname(motionName); -} - -const KiB = Math.pow(1024, 1); -const MiB = Math.pow(1024, 2); -const GiB = Math.pow(1024, 3); -const TiB = Math.pow(1024, 4); -const PiB = Math.pow(1024, 5); - -export const HumanReadableSize = { KiB, MiB, GiB, TiB, PiB }; - -export function asHumanReadable(bytes: number, precision = 1): string { - const units = ['B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB']; - - let magnitude = 0; - let remainder = bytes; - while (remainder >= 1024) { - if (magnitude + 1 < units.length) { - magnitude++; - remainder /= 1024; - } else { - break; - } - } - - return `${remainder.toFixed(magnitude == 0 ? 0 : precision)} ${units[magnitude]}`; -} - -export interface PaginationOptions { - take: number; - skip?: number; -} - -export enum PaginationMode { - LIMIT_OFFSET = 'limit-offset', - SKIP_TAKE = 'skip-take', -} - -export interface PaginatedBuilderOptions { - take: number; - skip?: number; - mode?: PaginationMode; -} - -export interface PaginationResult { - items: T[]; - hasNextPage: boolean; -} - -export type Paginated = Promise>; - -export async function* usePagination( - pageSize: number, - getNextPage: (pagination: PaginationOptions) => PaginationResult | Paginated, -) { - let hasNextPage = true; - - for (let skip = 0; hasNextPage; skip += pageSize) { - const result = await getNextPage({ take: pageSize, skip }); - hasNextPage = result.hasNextPage; - yield result.items; - } -} - -export interface OptionalOptions extends ValidationOptions { - nullable?: boolean; -} - -/** - * Checks if value is missing and if so, ignores all validators. - * - * @param validationOptions {@link OptionalOptions} - * - * @see IsOptional exported from `class-validator. - */ -// https://stackoverflow.com/a/71353929 -export function Optional({ nullable, ...validationOptions }: OptionalOptions = {}) { - if (nullable === true) { - return IsOptional(validationOptions); - } - - return ValidateIf((object: any, v: any) => v !== undefined, validationOptions); -} - -/** - * Chunks an array or set into smaller collections of the same type and specified size. - * - * @param collection The collection to chunk. - * @param size The size of each chunk. - */ -export function chunks(collection: Array, size: number): Array>; -export function chunks(collection: Set, size: number): Array>; -export function chunks(collection: Array | Set, size: number): Array> | Array> { - if (collection instanceof Set) { - const result = []; - let chunk = new Set(); - for (const element of collection) { - chunk.add(element); - if (chunk.size === size) { - result.push(chunk); - chunk = new Set(); - } - } - if (chunk.size > 0) { - result.push(chunk); - } - return result; - } else { - return _.chunk(collection, size); - } -} - -// NOTE: The following Set utils have been added here, to easily determine where they are used. -// They should be replaced with native Set operations, when they are added to the language. -// Proposal reference: https://github.com/tc39/proposal-set-methods - -export const setUnion = (...sets: Set[]): Set => { - const union = new Set(sets[0]); - for (const set of sets.slice(1)) { - for (const element of set) { - union.add(element); - } - } - return union; -}; - -export const setDifference = (setA: Set, ...sets: Set[]): Set => { - const difference = new Set(setA); - for (const set of sets) { - for (const element of set) { - difference.delete(element); - } - } - return difference; -}; - -export const setIsSuperset = (set: Set, subset: Set): boolean => { - for (const element of subset) { - if (!set.has(element)) { - return false; - } - } - return true; -}; - -export const setIsEqual = (setA: Set, setB: Set): boolean => { - return setA.size === setB.size && setIsSuperset(setA, setB); -}; - -export const handlePromiseError = (promise: Promise, logger: ImmichLogger): void => { - promise.catch((error: Error | any) => logger.error(`Promise error: ${error}`, error?.stack)); -}; diff --git a/server/src/domain/download/download.dto.ts b/server/src/domain/download/download.dto.ts index 3785a9d43..e6588a994 100644 --- a/server/src/domain/download/download.dto.ts +++ b/server/src/domain/download/download.dto.ts @@ -1,6 +1,6 @@ import { ApiProperty } from '@nestjs/swagger'; import { IsInt, IsPositive } from 'class-validator'; -import { Optional, ValidateUUID } from '../domain.util'; +import { Optional, ValidateUUID } from 'src/validation'; export class DownloadInfoDto { @ValidateUUID({ each: true, optional: true }) diff --git a/server/src/domain/download/download.service.spec.ts b/server/src/domain/download/download.service.spec.ts index 09161d8f6..6e1eafbec 100644 --- a/server/src/domain/download/download.service.spec.ts +++ b/server/src/domain/download/download.service.spec.ts @@ -1,18 +1,16 @@ import { BadRequestException } from '@nestjs/common'; -import { - IAccessRepositoryMock, - assetStub, - authStub, - newAccessRepositoryMock, - newAssetRepositoryMock, - newStorageRepositoryMock, -} from '@test'; import { when } from 'jest-when'; +import { DownloadResponseDto } from 'src/domain/download/download.dto'; +import { DownloadService } from 'src/domain/download/download.service'; +import { IAssetRepository } from 'src/interfaces/asset.repository'; +import { IStorageRepository } from 'src/interfaces/storage.repository'; +import { CacheControl, ImmichFileResponse } from 'src/utils'; +import { assetStub } from 'test/fixtures/asset.stub'; +import { authStub } from 'test/fixtures/auth.stub'; +import { IAccessRepositoryMock, newAccessRepositoryMock } from 'test/repositories/access.repository.mock'; +import { newAssetRepositoryMock } from 'test/repositories/asset.repository.mock'; +import { newStorageRepositoryMock } from 'test/repositories/storage.repository.mock'; import { Readable } from 'typeorm/platform/PlatformTools.js'; -import { CacheControl, ImmichFileResponse } from '../domain.util'; -import { IAssetRepository, IStorageRepository } from '../repositories'; -import { DownloadResponseDto } from './download.dto'; -import { DownloadService } from './download.service'; const downloadResponse: DownloadResponseDto = { totalSize: 105_000, diff --git a/server/src/domain/download/download.service.ts b/server/src/domain/download/download.service.ts index 1b4a19185..5c790548d 100644 --- a/server/src/domain/download/download.service.ts +++ b/server/src/domain/download/download.service.ts @@ -1,13 +1,15 @@ -import { AssetEntity } from '@app/infra/entities'; import { BadRequestException, Inject, Injectable } from '@nestjs/common'; import { parse } from 'node:path'; -import { AccessCore, Permission } from '../access'; -import { AssetIdsDto } from '../asset'; -import { AuthDto } from '../auth'; -import { mimeTypes } from '../domain.constant'; -import { CacheControl, HumanReadableSize, ImmichFileResponse, usePagination } from '../domain.util'; -import { IAccessRepository, IAssetRepository, IStorageRepository, ImmichReadStream } from '../repositories'; -import { DownloadArchiveInfo, DownloadInfoDto, DownloadResponseDto } from './download.dto'; +import { AccessCore, Permission } from 'src/cores/access.core'; +import { AssetIdsDto } from 'src/domain/asset/dto/asset-ids.dto'; +import { AuthDto } from 'src/domain/auth/auth.dto'; +import { mimeTypes } from 'src/domain/domain.constant'; +import { DownloadArchiveInfo, DownloadInfoDto, DownloadResponseDto } from 'src/domain/download/download.dto'; +import { AssetEntity } from 'src/infra/entities/asset.entity'; +import { IAccessRepository } from 'src/interfaces/access.repository'; +import { IAssetRepository } from 'src/interfaces/asset.repository'; +import { IStorageRepository, ImmichReadStream } from 'src/interfaces/storage.repository'; +import { CacheControl, HumanReadableSize, ImmichFileResponse, usePagination } from 'src/utils'; @Injectable() export class DownloadService { diff --git a/server/src/domain/download/index.ts b/server/src/domain/download/index.ts deleted file mode 100644 index ab5c91ec9..000000000 --- a/server/src/domain/download/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './download.dto'; -export * from './download.service'; diff --git a/server/src/domain/index.ts b/server/src/domain/index.ts deleted file mode 100644 index dce2fa696..000000000 --- a/server/src/domain/index.ts +++ /dev/null @@ -1,30 +0,0 @@ -export * from './access'; -export * from './activity'; -export * from './album'; -export * from './api-key'; -export * from './asset'; -export * from './audit'; -export * from './auth'; -export * from './database'; -export * from './domain.config'; -export * from './domain.constant'; -export * from './domain.module'; -export * from './domain.util'; -export * from './download'; -export * from './job'; -export * from './library'; -export * from './media'; -export * from './metadata'; -export * from './partner'; -export * from './person'; -export * from './repositories'; -export * from './search'; -export * from './server-info'; -export * from './shared-link'; -export * from './smart-info'; -export * from './storage'; -export * from './storage-template'; -export * from './system-config'; -export * from './tag'; -export * from './trash'; -export * from './user'; diff --git a/server/src/domain/job/index.ts b/server/src/domain/job/index.ts deleted file mode 100644 index 44f617f0c..000000000 --- a/server/src/domain/job/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export * from './job.constants'; -export * from './job.dto'; -export * from './job.interface'; -export * from './job.service'; diff --git a/server/src/domain/job/job.dto.ts b/server/src/domain/job/job.dto.ts index 87be1332f..fd463a9b0 100644 --- a/server/src/domain/job/job.dto.ts +++ b/server/src/domain/job/job.dto.ts @@ -1,7 +1,7 @@ import { ApiProperty } from '@nestjs/swagger'; import { IsEnum, IsNotEmpty } from 'class-validator'; -import { ValidateBoolean } from '../domain.util'; -import { JobCommand, QueueName } from './job.constants'; +import { JobCommand, QueueName } from 'src/domain/job/job.constants'; +import { ValidateBoolean } from 'src/validation'; export class JobIdParamDto { @IsNotEmpty() diff --git a/server/src/domain/job/job.service.spec.ts b/server/src/domain/job/job.service.spec.ts index c2133a623..1b46ec936 100644 --- a/server/src/domain/job/job.service.spec.ts +++ b/server/src/domain/job/job.service.spec.ts @@ -1,26 +1,19 @@ -import { SystemConfig, SystemConfigKey } from '@app/infra/entities'; import { BadRequestException } from '@nestjs/common'; -import { - assetStub, - newAssetRepositoryMock, - newCommunicationRepositoryMock, - newJobRepositoryMock, - newPersonRepositoryMock, - newSystemConfigRepositoryMock, -} from '@test'; -import { - IAssetRepository, - ICommunicationRepository, - IJobRepository, - IPersonRepository, - ISystemConfigRepository, - JobHandler, - JobItem, - JobStatus, -} from '../repositories'; -import { FeatureFlag, SystemConfigCore } from '../system-config/system-config.core'; -import { JobCommand, JobName, QueueName } from './job.constants'; -import { JobService } from './job.service'; +import { FeatureFlag, SystemConfigCore } from 'src/cores/system-config.core'; +import { JobCommand, JobName, QueueName } from 'src/domain/job/job.constants'; +import { JobService } from 'src/domain/job/job.service'; +import { SystemConfig, SystemConfigKey } from 'src/infra/entities/system-config.entity'; +import { IAssetRepository } from 'src/interfaces/asset.repository'; +import { ICommunicationRepository } from 'src/interfaces/communication.repository'; +import { IJobRepository, JobHandler, JobItem, JobStatus } from 'src/interfaces/job.repository'; +import { IPersonRepository } from 'src/interfaces/person.repository'; +import { ISystemConfigRepository } from 'src/interfaces/system-config.repository'; +import { assetStub } from 'test/fixtures/asset.stub'; +import { newAssetRepositoryMock } from 'test/repositories/asset.repository.mock'; +import { newCommunicationRepositoryMock } from 'test/repositories/communication.repository.mock'; +import { newJobRepositoryMock } from 'test/repositories/job.repository.mock'; +import { newPersonRepositoryMock } from 'test/repositories/person.repository.mock'; +import { newSystemConfigRepositoryMock } from 'test/repositories/system-config.repository.mock'; const makeMockHandlers = (status: JobStatus) => { const mock = jest.fn().mockResolvedValue(status); diff --git a/server/src/domain/job/job.service.ts b/server/src/domain/job/job.service.ts index e00636ad6..567603656 100644 --- a/server/src/domain/job/job.service.ts +++ b/server/src/domain/job/job.service.ts @@ -1,22 +1,15 @@ -import { AssetType } from '@app/infra/entities'; -import { ImmichLogger } from '@app/infra/logger'; import { BadRequestException, Inject, Injectable } from '@nestjs/common'; -import { mapAsset } from '../asset'; -import { - ClientEvent, - IAssetRepository, - ICommunicationRepository, - IJobRepository, - IPersonRepository, - ISystemConfigRepository, - JobHandler, - JobItem, - JobStatus, - QueueCleanType, -} from '../repositories'; -import { FeatureFlag, SystemConfigCore } from '../system-config/system-config.core'; -import { ConcurrentQueueName, JobCommand, JobName, QueueName } from './job.constants'; -import { AllJobStatusResponseDto, JobCommandDto, JobStatusDto } from './job.dto'; +import { FeatureFlag, SystemConfigCore } from 'src/cores/system-config.core'; +import { mapAsset } from 'src/domain/asset/response-dto/asset-response.dto'; +import { ConcurrentQueueName, JobCommand, JobName, QueueName } from 'src/domain/job/job.constants'; +import { AllJobStatusResponseDto, JobCommandDto, JobStatusDto } from 'src/domain/job/job.dto'; +import { AssetType } from 'src/infra/entities/asset.entity'; +import { ImmichLogger } from 'src/infra/logger'; +import { IAssetRepository } from 'src/interfaces/asset.repository'; +import { ClientEvent, ICommunicationRepository } from 'src/interfaces/communication.repository'; +import { IJobRepository, JobHandler, JobItem, JobStatus, QueueCleanType } from 'src/interfaces/job.repository'; +import { IPersonRepository } from 'src/interfaces/person.repository'; +import { ISystemConfigRepository } from 'src/interfaces/system-config.repository'; @Injectable() export class JobService { diff --git a/server/src/domain/library/index.ts b/server/src/domain/library/index.ts deleted file mode 100644 index da0d981f2..000000000 --- a/server/src/domain/library/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './library.dto'; -export * from './library.service'; diff --git a/server/src/domain/library/library.dto.ts b/server/src/domain/library/library.dto.ts index fcce02f87..f65779fb8 100644 --- a/server/src/domain/library/library.dto.ts +++ b/server/src/domain/library/library.dto.ts @@ -1,7 +1,7 @@ -import { LibraryEntity, LibraryType } from '@app/infra/entities'; import { ApiProperty } from '@nestjs/swagger'; import { ArrayMaxSize, ArrayUnique, IsEnum, IsNotEmpty, IsString } from 'class-validator'; -import { Optional, ValidateBoolean, ValidateUUID } from '../domain.util'; +import { LibraryEntity, LibraryType } from 'src/infra/entities/library.entity'; +import { Optional, ValidateBoolean, ValidateUUID } from 'src/validation'; export class CreateLibraryDto { @IsEnum(LibraryType) diff --git a/server/src/domain/library/library.service.spec.ts b/server/src/domain/library/library.service.spec.ts index 3266479f1..fe87aa50e 100644 --- a/server/src/domain/library/library.service.spec.ts +++ b/server/src/domain/library/library.service.spec.ts @@ -1,45 +1,39 @@ -import { AssetType, LibraryType, SystemConfig, SystemConfigKey, UserEntity } from '@app/infra/entities'; import { BadRequestException } from '@nestjs/common'; -import { - IAccessRepositoryMock, - assetStub, - authStub, - libraryStub, - makeMockWatcher, - newAccessRepositoryMock, - newAssetRepositoryMock, - newCryptoRepositoryMock, - newDatabaseRepositoryMock, - newJobRepositoryMock, - newLibraryRepositoryMock, - newStorageRepositoryMock, - newSystemConfigRepositoryMock, - systemConfigStub, - userStub, -} from '@test'; import { when } from 'jest-when'; import { R_OK } from 'node:constants'; import { Stats } from 'node:fs'; -import { ILibraryFileJob, ILibraryOfflineJob, ILibraryRefreshJob, JobName } from '../job'; -import { - IAssetRepository, - ICryptoRepository, - IDatabaseRepository, - IJobRepository, - ILibraryRepository, - IStorageRepository, - ISystemConfigRepository, - JobStatus, - StorageEventType, -} from '../repositories'; -import { SystemConfigCore } from '../system-config/system-config.core'; -import { mapLibrary } from './library.dto'; -import { LibraryService } from './library.service'; +import { SystemConfigCore } from 'src/cores/system-config.core'; +import { JobName } from 'src/domain/job/job.constants'; +import { ILibraryFileJob, ILibraryOfflineJob, ILibraryRefreshJob } from 'src/domain/job/job.interface'; +import { mapLibrary } from 'src/domain/library/library.dto'; +import { LibraryService } from 'src/domain/library/library.service'; +import { AssetType } from 'src/infra/entities/asset.entity'; +import { LibraryType } from 'src/infra/entities/library.entity'; +import { SystemConfig, SystemConfigKey } from 'src/infra/entities/system-config.entity'; +import { UserEntity } from 'src/infra/entities/user.entity'; +import { IAssetRepository } from 'src/interfaces/asset.repository'; +import { ICryptoRepository } from 'src/interfaces/crypto.repository'; +import { IDatabaseRepository } from 'src/interfaces/database.repository'; +import { IJobRepository, JobStatus } from 'src/interfaces/job.repository'; +import { ILibraryRepository } from 'src/interfaces/library.repository'; +import { IStorageRepository, StorageEventType } from 'src/interfaces/storage.repository'; +import { ISystemConfigRepository } from 'src/interfaces/system-config.repository'; +import { assetStub } from 'test/fixtures/asset.stub'; +import { authStub } from 'test/fixtures/auth.stub'; +import { libraryStub } from 'test/fixtures/library.stub'; +import { systemConfigStub } from 'test/fixtures/system-config.stub'; +import { userStub } from 'test/fixtures/user.stub'; +import { newAssetRepositoryMock } from 'test/repositories/asset.repository.mock'; +import { newCryptoRepositoryMock } from 'test/repositories/crypto.repository.mock'; +import { newDatabaseRepositoryMock } from 'test/repositories/database.repository.mock'; +import { newJobRepositoryMock } from 'test/repositories/job.repository.mock'; +import { newLibraryRepositoryMock } from 'test/repositories/library.repository.mock'; +import { makeMockWatcher, newStorageRepositoryMock } from 'test/repositories/storage.repository.mock'; +import { newSystemConfigRepositoryMock } from 'test/repositories/system-config.repository.mock'; describe(LibraryService.name, () => { let sut: LibraryService; - let accessMock: IAccessRepositoryMock; let assetMock: jest.Mocked; let configMock: jest.Mocked; let cryptoMock: jest.Mocked; @@ -49,7 +43,6 @@ describe(LibraryService.name, () => { let databaseMock: jest.Mocked; beforeEach(() => { - accessMock = newAccessRepositoryMock(); configMock = newSystemConfigRepositoryMock(); libraryMock = newLibraryRepositoryMock(); assetMock = newAssetRepositoryMock(); @@ -58,19 +51,7 @@ describe(LibraryService.name, () => { storageMock = newStorageRepositoryMock(); databaseMock = newDatabaseRepositoryMock(); - // Always validate owner access for library. - accessMock.library.checkOwnerAccess.mockImplementation((_, libraryIds) => Promise.resolve(libraryIds)); - - sut = new LibraryService( - accessMock, - assetMock, - configMock, - cryptoMock, - jobMock, - libraryMock, - storageMock, - databaseMock, - ); + sut = new LibraryService(assetMock, configMock, cryptoMock, jobMock, libraryMock, storageMock, databaseMock); databaseMock.tryLock.mockResolvedValue(true); }); diff --git a/server/src/domain/library/library.service.ts b/server/src/domain/library/library.service.ts index 1d4696e0b..14ede784b 100644 --- a/server/src/domain/library/library.service.ts +++ b/server/src/domain/library/library.service.ts @@ -1,5 +1,3 @@ -import { AssetEntity, AssetType, LibraryType } from '@app/infra/entities'; -import { ImmichLogger } from '@app/infra/logger'; import { BadRequestException, Inject, Injectable } from '@nestjs/common'; import { OnEvent } from '@nestjs/event-emitter'; import { R_OK } from 'node:constants'; @@ -7,36 +5,17 @@ import { EventEmitter } from 'node:events'; import { Stats } from 'node:fs'; import path, { basename, parse } from 'node:path'; import picomatch from 'picomatch'; -import { AccessCore } from '../access'; -import { mimeTypes } from '../domain.constant'; -import { handlePromiseError, usePagination, validateCronExpression } from '../domain.util'; +import { StorageCore } from 'src/cores/storage.core'; +import { SystemConfigCore } from 'src/cores/system-config.core'; +import { mimeTypes } from 'src/domain/domain.constant'; +import { JOBS_ASSET_PAGINATION_SIZE, JobName } from 'src/domain/job/job.constants'; import { IBaseJob, IEntityJob, ILibraryFileJob, ILibraryOfflineJob, ILibraryRefreshJob, - JOBS_ASSET_PAGINATION_SIZE, - JobName, -} from '../job'; -import { - DatabaseLock, - IAccessRepository, - IAssetRepository, - ICryptoRepository, - IDatabaseRepository, - IJobRepository, - ILibraryRepository, - IStorageRepository, - ISystemConfigRepository, - InternalEvent, - InternalEventMap, - JobStatus, - StorageEventType, - WithProperty, -} from '../repositories'; -import { StorageCore } from '../storage'; -import { SystemConfigCore } from '../system-config'; +} from 'src/domain/job/job.interface'; import { CreateLibraryDto, LibraryResponseDto, @@ -48,21 +27,32 @@ import { ValidateLibraryImportPathResponseDto, ValidateLibraryResponseDto, mapLibrary, -} from './library.dto'; +} from 'src/domain/library/library.dto'; +import { AssetEntity, AssetType } from 'src/infra/entities/asset.entity'; +import { LibraryType } from 'src/infra/entities/library.entity'; +import { ImmichLogger } from 'src/infra/logger'; +import { IAssetRepository, WithProperty } from 'src/interfaces/asset.repository'; +import { InternalEvent, InternalEventMap } from 'src/interfaces/communication.repository'; +import { ICryptoRepository } from 'src/interfaces/crypto.repository'; +import { DatabaseLock, IDatabaseRepository } from 'src/interfaces/database.repository'; +import { IJobRepository, JobStatus } from 'src/interfaces/job.repository'; +import { ILibraryRepository } from 'src/interfaces/library.repository'; +import { IStorageRepository, StorageEventType } from 'src/interfaces/storage.repository'; +import { ISystemConfigRepository } from 'src/interfaces/system-config.repository'; +import { handlePromiseError, usePagination } from 'src/utils'; +import { validateCronExpression } from 'src/validation'; const LIBRARY_SCAN_BATCH_SIZE = 1000; @Injectable() export class LibraryService extends EventEmitter { readonly logger = new ImmichLogger(LibraryService.name); - private access: AccessCore; private configCore: SystemConfigCore; private watchLibraries = false; private watchLock = false; private watchers: Record Promise> = {}; constructor( - @Inject(IAccessRepository) accessRepository: IAccessRepository, @Inject(IAssetRepository) private assetRepository: IAssetRepository, @Inject(ISystemConfigRepository) configRepository: ISystemConfigRepository, @Inject(ICryptoRepository) private cryptoRepository: ICryptoRepository, @@ -72,7 +62,6 @@ export class LibraryService extends EventEmitter { @Inject(IDatabaseRepository) private databaseRepository: IDatabaseRepository, ) { super(); - this.access = AccessCore.create(accessRepository); this.configCore = SystemConfigCore.create(configRepository); } diff --git a/server/src/domain/media/index.ts b/server/src/domain/media/index.ts deleted file mode 100644 index 83a31567b..000000000 --- a/server/src/domain/media/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './media.constant'; -export * from './media.service'; diff --git a/server/src/domain/media/media.service.spec.ts b/server/src/domain/media/media.service.spec.ts index 36d2cfdba..ad3ed3d22 100644 --- a/server/src/domain/media/media.service.spec.ts +++ b/server/src/domain/media/media.service.spec.ts @@ -1,43 +1,37 @@ +import { Stats } from 'node:fs'; +import { JobName } from 'src/domain/job/job.constants'; +import { MediaService } from 'src/domain/media/media.service'; +import { AssetType } from 'src/infra/entities/asset.entity'; +import { ExifEntity } from 'src/infra/entities/exif.entity'; import { - AssetType, AudioCodec, Colorspace, - ExifEntity, SystemConfigKey, ToneMapping, TranscodeHWAccel, TranscodePolicy, VideoCodec, -} from '@app/infra/entities'; -import { - assetStub, - faceStub, - newAssetRepositoryMock, - newCryptoRepositoryMock, - newJobRepositoryMock, - newMediaRepositoryMock, - newMoveRepositoryMock, - newPersonRepositoryMock, - newStorageRepositoryMock, - newSystemConfigRepositoryMock, - personStub, - probeStub, -} from '@test'; -import { Stats } from 'node:fs'; -import { JobName } from '../job'; -import { - IAssetRepository, - ICryptoRepository, - IJobRepository, - IMediaRepository, - IMoveRepository, - IPersonRepository, - IStorageRepository, - ISystemConfigRepository, - JobStatus, - WithoutProperty, -} from '../repositories'; -import { MediaService } from './media.service'; +} from 'src/infra/entities/system-config.entity'; +import { IAssetRepository, WithoutProperty } from 'src/interfaces/asset.repository'; +import { ICryptoRepository } from 'src/interfaces/crypto.repository'; +import { IJobRepository, JobStatus } from 'src/interfaces/job.repository'; +import { IMediaRepository } from 'src/interfaces/media.repository'; +import { IMoveRepository } from 'src/interfaces/move.repository'; +import { IPersonRepository } from 'src/interfaces/person.repository'; +import { IStorageRepository } from 'src/interfaces/storage.repository'; +import { ISystemConfigRepository } from 'src/interfaces/system-config.repository'; +import { assetStub } from 'test/fixtures/asset.stub'; +import { faceStub } from 'test/fixtures/face.stub'; +import { probeStub } from 'test/fixtures/media.stub'; +import { personStub } from 'test/fixtures/person.stub'; +import { newAssetRepositoryMock } from 'test/repositories/asset.repository.mock'; +import { newCryptoRepositoryMock } from 'test/repositories/crypto.repository.mock'; +import { newJobRepositoryMock } from 'test/repositories/job.repository.mock'; +import { newMediaRepositoryMock } from 'test/repositories/media.repository.mock'; +import { newMoveRepositoryMock } from 'test/repositories/move.repository.mock'; +import { newPersonRepositoryMock } from 'test/repositories/person.repository.mock'; +import { newStorageRepositoryMock } from 'test/repositories/storage.repository.mock'; +import { newSystemConfigRepositoryMock } from 'test/repositories/system-config.repository.mock'; describe(MediaService.name, () => { let sut: MediaService; diff --git a/server/src/domain/media/media.service.ts b/server/src/domain/media/media.service.ts index 31eafcbcf..6807e962a 100644 --- a/server/src/domain/media/media.service.ts +++ b/server/src/domain/media/media.service.ts @@ -1,37 +1,8 @@ -import { - AssetEntity, - AssetPathType, - AssetType, - AudioCodec, - Colorspace, - TranscodeHWAccel, - TranscodePolicy, - TranscodeTarget, - VideoCodec, -} from '@app/infra/entities'; -import { ImmichLogger } from '@app/infra/logger'; import { Inject, Injectable, UnsupportedMediaTypeException } from '@nestjs/common'; -import { usePagination } from '../domain.util'; -import { IBaseJob, IEntityJob, JOBS_ASSET_PAGINATION_SIZE, JobName, QueueName } from '../job'; -import { - AudioStreamInfo, - IAssetRepository, - ICryptoRepository, - IJobRepository, - IMediaRepository, - IMoveRepository, - IPersonRepository, - IStorageRepository, - ISystemConfigRepository, - JobItem, - JobStatus, - VideoCodecHWConfig, - VideoStreamInfo, - WithoutProperty, -} from '../repositories'; -import { StorageCore, StorageFolder } from '../storage'; -import { SystemConfigFFmpegDto } from '../system-config'; -import { SystemConfigCore } from '../system-config/system-config.core'; +import { StorageCore, StorageFolder } from 'src/cores/storage.core'; +import { SystemConfigCore } from 'src/cores/system-config.core'; +import { JOBS_ASSET_PAGINATION_SIZE, JobName, QueueName } from 'src/domain/job/job.constants'; +import { IBaseJob, IEntityJob } from 'src/domain/job/job.interface'; import { H264Config, HEVCConfig, @@ -41,7 +12,33 @@ import { ThumbnailConfig, VAAPIConfig, VP9Config, -} from './media.util'; +} from 'src/domain/media/media.util'; +import { SystemConfigFFmpegDto } from 'src/domain/system-config/dto/system-config-ffmpeg.dto'; +import { AssetEntity, AssetType } from 'src/infra/entities/asset.entity'; +import { AssetPathType } from 'src/infra/entities/move.entity'; +import { + AudioCodec, + Colorspace, + TranscodeHWAccel, + TranscodePolicy, + TranscodeTarget, + VideoCodec, +} from 'src/infra/entities/system-config.entity'; +import { ImmichLogger } from 'src/infra/logger'; +import { IAssetRepository, WithoutProperty } from 'src/interfaces/asset.repository'; +import { ICryptoRepository } from 'src/interfaces/crypto.repository'; +import { IJobRepository, JobItem, JobStatus } from 'src/interfaces/job.repository'; +import { + AudioStreamInfo, + IMediaRepository, + VideoCodecHWConfig, + VideoStreamInfo, +} from 'src/interfaces/media.repository'; +import { IMoveRepository } from 'src/interfaces/move.repository'; +import { IPersonRepository } from 'src/interfaces/person.repository'; +import { IStorageRepository } from 'src/interfaces/storage.repository'; +import { ISystemConfigRepository } from 'src/interfaces/system-config.repository'; +import { usePagination } from 'src/utils'; @Injectable() export class MediaService { @@ -58,7 +55,7 @@ export class MediaService { @Inject(IStorageRepository) private storageRepository: IStorageRepository, @Inject(ISystemConfigRepository) configRepository: ISystemConfigRepository, @Inject(IMoveRepository) moveRepository: IMoveRepository, - @Inject(ICryptoRepository) private cryptoRepository: ICryptoRepository, + @Inject(ICryptoRepository) cryptoRepository: ICryptoRepository, ) { this.configCore = SystemConfigCore.create(configRepository); this.storageCore = StorageCore.create( diff --git a/server/src/domain/media/media.util.ts b/server/src/domain/media/media.util.ts index 3acabb435..bc9fa4887 100644 --- a/server/src/domain/media/media.util.ts +++ b/server/src/domain/media/media.util.ts @@ -1,4 +1,11 @@ -import { CQMode, ToneMapping, TranscodeHWAccel, TranscodeTarget, VideoCodec } from '@app/infra/entities'; +import { SystemConfigFFmpegDto } from 'src/domain/system-config/dto/system-config-ffmpeg.dto'; +import { + CQMode, + ToneMapping, + TranscodeHWAccel, + TranscodeTarget, + VideoCodec, +} from 'src/infra/entities/system-config.entity'; import { AudioStreamInfo, BitrateDistribution, @@ -6,8 +13,8 @@ import { VideoCodecHWConfig, VideoCodecSWConfig, VideoStreamInfo, -} from '../repositories'; -import { SystemConfigFFmpegDto } from '../system-config/dto'; +} from 'src/interfaces/media.repository'; + class BaseConfig implements VideoCodecSWConfig { presets = ['veryslow', 'slower', 'slow', 'medium', 'fast', 'faster', 'veryfast', 'superfast', 'ultrafast']; constructor(protected config: SystemConfigFFmpegDto) {} diff --git a/server/src/domain/metadata/index.ts b/server/src/domain/metadata/index.ts deleted file mode 100644 index 92c69e450..000000000 --- a/server/src/domain/metadata/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './metadata.service'; diff --git a/server/src/domain/metadata/metadata.service.spec.ts b/server/src/domain/metadata/metadata.service.spec.ts index 69d31cbd5..f8483d6ef 100644 --- a/server/src/domain/metadata/metadata.service.spec.ts +++ b/server/src/domain/metadata/metadata.service.spec.ts @@ -1,46 +1,40 @@ -import { AssetType, ExifEntity, SystemConfigKey } from '@app/infra/entities'; -import { - assetStub, - fileStub, - newAlbumRepositoryMock, - newAssetRepositoryMock, - newCommunicationRepositoryMock, - newCryptoRepositoryMock, - newDatabaseRepositoryMock, - newJobRepositoryMock, - newMediaRepositoryMock, - newMetadataRepositoryMock, - newMoveRepositoryMock, - newPersonRepositoryMock, - newStorageRepositoryMock, - newSystemConfigRepositoryMock, - probeStub, -} from '@test'; import { BinaryField } from 'exiftool-vendored'; import { when } from 'jest-when'; import { randomBytes } from 'node:crypto'; import { Stats } from 'node:fs'; import { constants } from 'node:fs/promises'; -import { JobName } from '../job'; -import { - ClientEvent, - IAlbumRepository, - IAssetRepository, - ICommunicationRepository, - ICryptoRepository, - IDatabaseRepository, - IJobRepository, - IMediaRepository, - IMetadataRepository, - IMoveRepository, - IPersonRepository, - IStorageRepository, - ISystemConfigRepository, - ImmichTags, - JobStatus, - WithoutProperty, -} from '../repositories'; -import { MetadataService, Orientation } from './metadata.service'; +import { JobName } from 'src/domain/job/job.constants'; +import { MetadataService, Orientation } from 'src/domain/metadata/metadata.service'; +import { AssetType } from 'src/infra/entities/asset.entity'; +import { ExifEntity } from 'src/infra/entities/exif.entity'; +import { SystemConfigKey } from 'src/infra/entities/system-config.entity'; +import { IAlbumRepository } from 'src/interfaces/album.repository'; +import { IAssetRepository, WithoutProperty } from 'src/interfaces/asset.repository'; +import { ClientEvent, ICommunicationRepository } from 'src/interfaces/communication.repository'; +import { ICryptoRepository } from 'src/interfaces/crypto.repository'; +import { IDatabaseRepository } from 'src/interfaces/database.repository'; +import { IJobRepository, JobStatus } from 'src/interfaces/job.repository'; +import { IMediaRepository } from 'src/interfaces/media.repository'; +import { IMetadataRepository, ImmichTags } from 'src/interfaces/metadata.repository'; +import { IMoveRepository } from 'src/interfaces/move.repository'; +import { IPersonRepository } from 'src/interfaces/person.repository'; +import { IStorageRepository } from 'src/interfaces/storage.repository'; +import { ISystemConfigRepository } from 'src/interfaces/system-config.repository'; +import { assetStub } from 'test/fixtures/asset.stub'; +import { fileStub } from 'test/fixtures/file.stub'; +import { probeStub } from 'test/fixtures/media.stub'; +import { newAlbumRepositoryMock } from 'test/repositories/album.repository.mock'; +import { newAssetRepositoryMock } from 'test/repositories/asset.repository.mock'; +import { newCommunicationRepositoryMock } from 'test/repositories/communication.repository.mock'; +import { newCryptoRepositoryMock } from 'test/repositories/crypto.repository.mock'; +import { newDatabaseRepositoryMock } from 'test/repositories/database.repository.mock'; +import { newJobRepositoryMock } from 'test/repositories/job.repository.mock'; +import { newMediaRepositoryMock } from 'test/repositories/media.repository.mock'; +import { newMetadataRepositoryMock } from 'test/repositories/metadata.repository.mock'; +import { newMoveRepositoryMock } from 'test/repositories/move.repository.mock'; +import { newPersonRepositoryMock } from 'test/repositories/person.repository.mock'; +import { newStorageRepositoryMock } from 'test/repositories/storage.repository.mock'; +import { newSystemConfigRepositoryMock } from 'test/repositories/system-config.repository.mock'; describe(MetadataService.name, () => { let albumMock: jest.Mocked; diff --git a/server/src/domain/metadata/metadata.service.ts b/server/src/domain/metadata/metadata.service.ts index 75838330d..d7f98e245 100644 --- a/server/src/domain/metadata/metadata.service.ts +++ b/server/src/domain/metadata/metadata.service.ts @@ -1,5 +1,3 @@ -import { AssetEntity, AssetType, ExifEntity } from '@app/infra/entities'; -import { ImmichLogger } from '@app/infra/logger'; import { Inject, Injectable } from '@nestjs/common'; import { ExifDateTime, Tags } from 'exiftool-vendored'; import { firstDateTime } from 'exiftool-vendored/dist/FirstDateTime'; @@ -8,29 +6,26 @@ import { Duration } from 'luxon'; import { constants } from 'node:fs/promises'; import path from 'node:path'; import { Subscription } from 'rxjs'; -import { handlePromiseError, usePagination } from '../domain.util'; -import { IBaseJob, IEntityJob, ISidecarWriteJob, JOBS_ASSET_PAGINATION_SIZE, JobName, QueueName } from '../job'; -import { - ClientEvent, - DatabaseLock, - IAlbumRepository, - IAssetRepository, - ICommunicationRepository, - ICryptoRepository, - IDatabaseRepository, - IJobRepository, - IMediaRepository, - IMetadataRepository, - IMoveRepository, - IPersonRepository, - IStorageRepository, - ISystemConfigRepository, - ImmichTags, - JobStatus, - WithoutProperty, -} from '../repositories'; -import { StorageCore } from '../storage'; -import { FeatureFlag, SystemConfigCore } from '../system-config'; +import { StorageCore } from 'src/cores/storage.core'; +import { FeatureFlag, SystemConfigCore } from 'src/cores/system-config.core'; +import { JOBS_ASSET_PAGINATION_SIZE, JobName, QueueName } from 'src/domain/job/job.constants'; +import { IBaseJob, IEntityJob, ISidecarWriteJob } from 'src/domain/job/job.interface'; +import { AssetEntity, AssetType } from 'src/infra/entities/asset.entity'; +import { ExifEntity } from 'src/infra/entities/exif.entity'; +import { ImmichLogger } from 'src/infra/logger'; +import { IAlbumRepository } from 'src/interfaces/album.repository'; +import { IAssetRepository, WithoutProperty } from 'src/interfaces/asset.repository'; +import { ClientEvent, ICommunicationRepository } from 'src/interfaces/communication.repository'; +import { ICryptoRepository } from 'src/interfaces/crypto.repository'; +import { DatabaseLock, IDatabaseRepository } from 'src/interfaces/database.repository'; +import { IJobRepository, JobStatus } from 'src/interfaces/job.repository'; +import { IMediaRepository } from 'src/interfaces/media.repository'; +import { IMetadataRepository, ImmichTags } from 'src/interfaces/metadata.repository'; +import { IMoveRepository } from 'src/interfaces/move.repository'; +import { IPersonRepository } from 'src/interfaces/person.repository'; +import { IStorageRepository } from 'src/interfaces/storage.repository'; +import { ISystemConfigRepository } from 'src/interfaces/system-config.repository'; +import { handlePromiseError, usePagination } from 'src/utils'; /** look for a date from these tags (in order) */ const EXIF_DATE_TAGS: Array = [ diff --git a/server/src/domain/partner/index.ts b/server/src/domain/partner/index.ts deleted file mode 100644 index b25925e89..000000000 --- a/server/src/domain/partner/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './partner.dto'; -export * from './partner.service'; diff --git a/server/src/domain/partner/partner.dto.ts b/server/src/domain/partner/partner.dto.ts index 17afcad5d..c197d2079 100644 --- a/server/src/domain/partner/partner.dto.ts +++ b/server/src/domain/partner/partner.dto.ts @@ -1,5 +1,5 @@ import { IsNotEmpty } from 'class-validator'; -import { UserResponseDto } from '../user'; +import { UserResponseDto } from 'src/domain/user/response-dto/user-response.dto'; export class UpdatePartnerDto { @IsNotEmpty() diff --git a/server/src/domain/partner/partner.service.spec.ts b/server/src/domain/partner/partner.service.spec.ts index 6e9c10c5c..487818b3d 100644 --- a/server/src/domain/partner/partner.service.spec.ts +++ b/server/src/domain/partner/partner.service.spec.ts @@ -1,9 +1,12 @@ -import { UserAvatarColor } from '@app/infra/entities'; import { BadRequestException } from '@nestjs/common'; -import { authStub, newPartnerRepositoryMock, partnerStub } from '@test'; -import { IAccessRepository, IPartnerRepository, PartnerDirection } from '../repositories'; -import { PartnerResponseDto } from './partner.dto'; -import { PartnerService } from './partner.service'; +import { PartnerResponseDto } from 'src/domain/partner/partner.dto'; +import { PartnerService } from 'src/domain/partner/partner.service'; +import { UserAvatarColor } from 'src/infra/entities/user.entity'; +import { IAccessRepository } from 'src/interfaces/access.repository'; +import { IPartnerRepository, PartnerDirection } from 'src/interfaces/partner.repository'; +import { authStub } from 'test/fixtures/auth.stub'; +import { partnerStub } from 'test/fixtures/partner.stub'; +import { newPartnerRepositoryMock } from 'test/repositories/partner.repository.mock'; const responseDto = { admin: { diff --git a/server/src/domain/partner/partner.service.ts b/server/src/domain/partner/partner.service.ts index a3f9a9f3d..2528b44be 100644 --- a/server/src/domain/partner/partner.service.ts +++ b/server/src/domain/partner/partner.service.ts @@ -1,10 +1,11 @@ -import { PartnerEntity } from '@app/infra/entities'; import { BadRequestException, Inject, Injectable } from '@nestjs/common'; -import { AccessCore, Permission } from '../access'; -import { AuthDto } from '../auth'; -import { IAccessRepository, IPartnerRepository, PartnerDirection, PartnerIds } from '../repositories'; -import { mapUser } from '../user'; -import { PartnerResponseDto, UpdatePartnerDto } from './partner.dto'; +import { AccessCore, Permission } from 'src/cores/access.core'; +import { AuthDto } from 'src/domain/auth/auth.dto'; +import { PartnerResponseDto, UpdatePartnerDto } from 'src/domain/partner/partner.dto'; +import { mapUser } from 'src/domain/user/response-dto/user-response.dto'; +import { PartnerEntity } from 'src/infra/entities/partner.entity'; +import { IAccessRepository } from 'src/interfaces/access.repository'; +import { IPartnerRepository, PartnerDirection, PartnerIds } from 'src/interfaces/partner.repository'; @Injectable() export class PartnerService { diff --git a/server/src/domain/person/index.ts b/server/src/domain/person/index.ts deleted file mode 100644 index 14a960467..000000000 --- a/server/src/domain/person/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './person.dto'; -export * from './person.service'; diff --git a/server/src/domain/person/person.dto.ts b/server/src/domain/person/person.dto.ts index a00971c6b..f3699499a 100644 --- a/server/src/domain/person/person.dto.ts +++ b/server/src/domain/person/person.dto.ts @@ -1,9 +1,10 @@ -import { AssetFaceEntity, PersonEntity } from '@app/infra/entities'; import { ApiProperty } from '@nestjs/swagger'; import { Type } from 'class-transformer'; import { IsArray, IsNotEmpty, IsString, MaxDate, ValidateNested } from 'class-validator'; -import { AuthDto } from '../auth'; -import { Optional, ValidateBoolean, ValidateDate, ValidateUUID } from '../domain.util'; +import { AuthDto } from 'src/domain/auth/auth.dto'; +import { AssetFaceEntity } from 'src/infra/entities/asset-face.entity'; +import { PersonEntity } from 'src/infra/entities/person.entity'; +import { Optional, ValidateBoolean, ValidateDate, ValidateUUID } from 'src/validation'; export class PersonCreateDto { /** diff --git a/server/src/domain/person/person.service.spec.ts b/server/src/domain/person/person.service.spec.ts index 08b5875a5..042469d7e 100644 --- a/server/src/domain/person/person.service.spec.ts +++ b/server/src/domain/person/person.service.spec.ts @@ -1,44 +1,37 @@ -import { AssetFaceEntity, Colorspace, SystemConfigKey } from '@app/infra/entities'; import { BadRequestException, NotFoundException } from '@nestjs/common'; -import { - IAccessRepositoryMock, - assetStub, - authStub, - faceStub, - newAccessRepositoryMock, - newAssetRepositoryMock, - newCryptoRepositoryMock, - newJobRepositoryMock, - newMachineLearningRepositoryMock, - newMediaRepositoryMock, - newMoveRepositoryMock, - newPersonRepositoryMock, - newSearchRepositoryMock, - newStorageRepositoryMock, - newSystemConfigRepositoryMock, - personStub, -} from '@test'; +import { BulkIdErrorReason } from 'src/domain/asset/response-dto/asset-ids-response.dto'; +import { JobName } from 'src/domain/job/job.constants'; +import { PersonResponseDto, mapFaces, mapPerson } from 'src/domain/person/person.dto'; +import { PersonService } from 'src/domain/person/person.service'; +import { AssetFaceEntity } from 'src/infra/entities/asset-face.entity'; +import { Colorspace, SystemConfigKey } from 'src/infra/entities/system-config.entity'; +import { IAssetRepository, WithoutProperty } from 'src/interfaces/asset.repository'; +import { ICryptoRepository } from 'src/interfaces/crypto.repository'; +import { IJobRepository, JobStatus } from 'src/interfaces/job.repository'; +import { IMachineLearningRepository } from 'src/interfaces/machine-learning.repository'; +import { IMediaRepository } from 'src/interfaces/media.repository'; +import { IMoveRepository } from 'src/interfaces/move.repository'; +import { IPersonRepository } from 'src/interfaces/person.repository'; +import { FaceSearchResult, ISearchRepository } from 'src/interfaces/search.repository'; +import { IStorageRepository } from 'src/interfaces/storage.repository'; +import { ISystemConfigRepository } from 'src/interfaces/system-config.repository'; +import { CacheControl, ImmichFileResponse } from 'src/utils'; +import { assetStub } from 'test/fixtures/asset.stub'; +import { authStub } from 'test/fixtures/auth.stub'; +import { faceStub } from 'test/fixtures/face.stub'; +import { personStub } from 'test/fixtures/person.stub'; +import { IAccessRepositoryMock, newAccessRepositoryMock } from 'test/repositories/access.repository.mock'; +import { newAssetRepositoryMock } from 'test/repositories/asset.repository.mock'; +import { newCryptoRepositoryMock } from 'test/repositories/crypto.repository.mock'; +import { newJobRepositoryMock } from 'test/repositories/job.repository.mock'; +import { newMachineLearningRepositoryMock } from 'test/repositories/machine-learning.repository.mock'; +import { newMediaRepositoryMock } from 'test/repositories/media.repository.mock'; +import { newMoveRepositoryMock } from 'test/repositories/move.repository.mock'; +import { newPersonRepositoryMock } from 'test/repositories/person.repository.mock'; +import { newSearchRepositoryMock } from 'test/repositories/search.repository.mock'; +import { newStorageRepositoryMock } from 'test/repositories/storage.repository.mock'; +import { newSystemConfigRepositoryMock } from 'test/repositories/system-config.repository.mock'; import { IsNull } from 'typeorm'; -import { BulkIdErrorReason } from '../asset'; -import { CacheControl, ImmichFileResponse } from '../domain.util'; -import { JobName } from '../job'; -import { - FaceSearchResult, - IAssetRepository, - ICryptoRepository, - IJobRepository, - IMachineLearningRepository, - IMediaRepository, - IMoveRepository, - IPersonRepository, - ISearchRepository, - IStorageRepository, - ISystemConfigRepository, - JobStatus, - WithoutProperty, -} from '../repositories'; -import { PersonResponseDto, mapFaces, mapPerson } from './person.dto'; -import { PersonService } from './person.service'; const responseDto: PersonResponseDto = { id: 'person-1', diff --git a/server/src/domain/person/person.service.ts b/server/src/domain/person/person.service.ts index 1a2233f3c..9e56e4b18 100644 --- a/server/src/domain/person/person.service.ts +++ b/server/src/domain/person/person.service.ts @@ -1,35 +1,14 @@ -import { PersonEntity } from '@app/infra/entities'; -import { PersonPathType } from '@app/infra/entities/move.entity'; -import { ImmichLogger } from '@app/infra/logger'; import { BadRequestException, Inject, Injectable, NotFoundException } from '@nestjs/common'; -import { IsNull } from 'typeorm'; -import { AccessCore, Permission } from '../access'; -import { AssetResponseDto, BulkIdErrorReason, BulkIdResponseDto, mapAsset } from '../asset'; -import { AuthDto } from '../auth'; -import { mimeTypes } from '../domain.constant'; -import { CacheControl, ImmichFileResponse, usePagination } from '../domain.util'; -import { IBaseJob, IDeferrableJob, IEntityJob, JOBS_ASSET_PAGINATION_SIZE, JobName, QueueName } from '../job'; -import { FACE_THUMBNAIL_SIZE } from '../media'; -import { - CropOptions, - IAccessRepository, - IAssetRepository, - ICryptoRepository, - IJobRepository, - IMachineLearningRepository, - IMediaRepository, - IMoveRepository, - IPersonRepository, - ISearchRepository, - IStorageRepository, - ISystemConfigRepository, - JobItem, - JobStatus, - UpdateFacesData, - WithoutProperty, -} from '../repositories'; -import { StorageCore } from '../storage'; -import { SystemConfigCore } from '../system-config'; +import { AccessCore, Permission } from 'src/cores/access.core'; +import { StorageCore } from 'src/cores/storage.core'; +import { SystemConfigCore } from 'src/cores/system-config.core'; +import { BulkIdErrorReason, BulkIdResponseDto } from 'src/domain/asset/response-dto/asset-ids-response.dto'; +import { AssetResponseDto, mapAsset } from 'src/domain/asset/response-dto/asset-response.dto'; +import { AuthDto } from 'src/domain/auth/auth.dto'; +import { mimeTypes } from 'src/domain/domain.constant'; +import { JOBS_ASSET_PAGINATION_SIZE, JobName, QueueName } from 'src/domain/job/job.constants'; +import { IBaseJob, IDeferrableJob, IEntityJob } from 'src/domain/job/job.interface'; +import { FACE_THUMBNAIL_SIZE } from 'src/domain/media/media.constant'; import { AssetFaceResponseDto, AssetFaceUpdateDto, @@ -44,7 +23,23 @@ import { PersonUpdateDto, mapFaces, mapPerson, -} from './person.dto'; +} from 'src/domain/person/person.dto'; +import { PersonPathType } from 'src/infra/entities/move.entity'; +import { PersonEntity } from 'src/infra/entities/person.entity'; +import { ImmichLogger } from 'src/infra/logger'; +import { IAccessRepository } from 'src/interfaces/access.repository'; +import { IAssetRepository, WithoutProperty } from 'src/interfaces/asset.repository'; +import { ICryptoRepository } from 'src/interfaces/crypto.repository'; +import { IJobRepository, JobItem, JobStatus } from 'src/interfaces/job.repository'; +import { IMachineLearningRepository } from 'src/interfaces/machine-learning.repository'; +import { CropOptions, IMediaRepository } from 'src/interfaces/media.repository'; +import { IMoveRepository } from 'src/interfaces/move.repository'; +import { IPersonRepository, UpdateFacesData } from 'src/interfaces/person.repository'; +import { ISearchRepository } from 'src/interfaces/search.repository'; +import { IStorageRepository } from 'src/interfaces/storage.repository'; +import { ISystemConfigRepository } from 'src/interfaces/system-config.repository'; +import { CacheControl, ImmichFileResponse, usePagination } from 'src/utils'; +import { IsNull } from 'typeorm'; @Injectable() export class PersonService { @@ -64,7 +59,7 @@ export class PersonService { @Inject(IStorageRepository) private storageRepository: IStorageRepository, @Inject(IJobRepository) private jobRepository: IJobRepository, @Inject(ISearchRepository) private smartInfoRepository: ISearchRepository, - @Inject(ICryptoRepository) private cryptoRepository: ICryptoRepository, + @Inject(ICryptoRepository) cryptoRepository: ICryptoRepository, ) { this.access = AccessCore.create(accessRepository); this.configCore = SystemConfigCore.create(configRepository); diff --git a/server/src/domain/repositories/index.ts b/server/src/domain/repositories/index.ts deleted file mode 100644 index 636abd2be..000000000 --- a/server/src/domain/repositories/index.ts +++ /dev/null @@ -1,27 +0,0 @@ -export * from './access.repository'; -export * from './activity.repository'; -export * from './album.repository'; -export * from './api-key.repository'; -export * from './asset-stack.repository'; -export * from './asset.repository'; -export * from './audit.repository'; -export * from './communication.repository'; -export * from './crypto.repository'; -export * from './database.repository'; -export * from './job.repository'; -export * from './library.repository'; -export * from './machine-learning.repository'; -export * from './media.repository'; -export * from './metadata.repository'; -export * from './move.repository'; -export * from './partner.repository'; -export * from './person.repository'; -export * from './search.repository'; -export * from './server-info.repository'; -export * from './shared-link.repository'; -export * from './storage.repository'; -export * from './system-config.repository'; -export * from './system-metadata.repository'; -export * from './tag.repository'; -export * from './user-token.repository'; -export * from './user.repository'; diff --git a/server/src/domain/search/dto/index.ts b/server/src/domain/search/dto/index.ts deleted file mode 100644 index cd914d0ea..000000000 --- a/server/src/domain/search/dto/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './search.dto'; diff --git a/server/src/domain/search/dto/search-suggestion.dto.ts b/server/src/domain/search/dto/search-suggestion.dto.ts index 824a1066c..f702293d0 100644 --- a/server/src/domain/search/dto/search-suggestion.dto.ts +++ b/server/src/domain/search/dto/search-suggestion.dto.ts @@ -1,6 +1,6 @@ import { ApiProperty } from '@nestjs/swagger'; import { IsEnum, IsNotEmpty, IsString } from 'class-validator'; -import { Optional } from '../../domain.util'; +import { Optional } from 'src/validation'; export enum SearchSuggestionType { COUNTRY = 'country', diff --git a/server/src/domain/search/dto/search.dto.ts b/server/src/domain/search/dto/search.dto.ts index 1bc67266a..52090b945 100644 --- a/server/src/domain/search/dto/search.dto.ts +++ b/server/src/domain/search/dto/search.dto.ts @@ -1,8 +1,10 @@ -import { AssetOrder, AssetType, GeodataPlacesEntity } from '@app/infra/entities'; import { ApiProperty } from '@nestjs/swagger'; import { Type } from 'class-transformer'; import { IsEnum, IsInt, IsNotEmpty, IsString, Max, Min } from 'class-validator'; -import { Optional, ValidateBoolean, ValidateDate, ValidateUUID } from '../../domain.util'; +import { AssetOrder } from 'src/infra/entities/album.entity'; +import { AssetType } from 'src/infra/entities/asset.entity'; +import { GeodataPlacesEntity } from 'src/infra/entities/geodata-places.entity'; +import { Optional, ValidateBoolean, ValidateDate, ValidateUUID } from 'src/validation'; class BaseSearchDto { @ValidateUUID({ optional: true }) diff --git a/server/src/domain/search/index.ts b/server/src/domain/search/index.ts deleted file mode 100644 index 717439d3c..000000000 --- a/server/src/domain/search/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './dto'; -export * from './response-dto'; -export * from './search.service'; diff --git a/server/src/domain/search/response-dto/index.ts b/server/src/domain/search/response-dto/index.ts deleted file mode 100644 index f48856bca..000000000 --- a/server/src/domain/search/response-dto/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './search-explore.response.dto'; -export * from './search-response.dto'; diff --git a/server/src/domain/search/response-dto/search-explore.response.dto.ts b/server/src/domain/search/response-dto/search-explore.response.dto.ts index 37398d9de..33689b979 100644 --- a/server/src/domain/search/response-dto/search-explore.response.dto.ts +++ b/server/src/domain/search/response-dto/search-explore.response.dto.ts @@ -1,4 +1,4 @@ -import { AssetResponseDto } from '../../asset'; +import { AssetResponseDto } from 'src/domain/asset/response-dto/asset-response.dto'; class SearchExploreItem { value!: string; diff --git a/server/src/domain/search/response-dto/search-response.dto.ts b/server/src/domain/search/response-dto/search-response.dto.ts index 9dd65e7cc..53563a9ac 100644 --- a/server/src/domain/search/response-dto/search-response.dto.ts +++ b/server/src/domain/search/response-dto/search-response.dto.ts @@ -1,6 +1,6 @@ import { ApiProperty } from '@nestjs/swagger'; -import { AlbumResponseDto } from '../../album'; -import { AssetResponseDto } from '../../asset'; +import { AlbumResponseDto } from 'src/domain/album/album-response.dto'; +import { AssetResponseDto } from 'src/domain/asset/response-dto/asset-response.dto'; class SearchFacetCountResponseDto { @ApiProperty({ type: 'integer' }) diff --git a/server/src/domain/search/search.service.spec.ts b/server/src/domain/search/search.service.spec.ts index b6edf1ece..f9313d277 100644 --- a/server/src/domain/search/search.service.spec.ts +++ b/server/src/domain/search/search.service.spec.ts @@ -1,28 +1,24 @@ -import { SystemConfigKey } from '@app/infra/entities'; -import { - assetStub, - authStub, - newAssetRepositoryMock, - newMachineLearningRepositoryMock, - newMetadataRepositoryMock, - newPartnerRepositoryMock, - newPersonRepositoryMock, - newSearchRepositoryMock, - newSystemConfigRepositoryMock, - personStub, -} from '@test'; -import { mapAsset } from '../asset'; -import { - IAssetRepository, - IMachineLearningRepository, - IMetadataRepository, - IPartnerRepository, - IPersonRepository, - ISearchRepository, - ISystemConfigRepository, -} from '../repositories'; -import { SearchDto } from './dto'; -import { SearchService } from './search.service'; +import { mapAsset } from 'src/domain/asset/response-dto/asset-response.dto'; +import { SearchDto } from 'src/domain/search/dto/search.dto'; +import { SearchService } from 'src/domain/search/search.service'; +import { SystemConfigKey } from 'src/infra/entities/system-config.entity'; +import { IAssetRepository } from 'src/interfaces/asset.repository'; +import { IMachineLearningRepository } from 'src/interfaces/machine-learning.repository'; +import { IMetadataRepository } from 'src/interfaces/metadata.repository'; +import { IPartnerRepository } from 'src/interfaces/partner.repository'; +import { IPersonRepository } from 'src/interfaces/person.repository'; +import { ISearchRepository } from 'src/interfaces/search.repository'; +import { ISystemConfigRepository } from 'src/interfaces/system-config.repository'; +import { assetStub } from 'test/fixtures/asset.stub'; +import { authStub } from 'test/fixtures/auth.stub'; +import { personStub } from 'test/fixtures/person.stub'; +import { newAssetRepositoryMock } from 'test/repositories/asset.repository.mock'; +import { newMachineLearningRepositoryMock } from 'test/repositories/machine-learning.repository.mock'; +import { newMetadataRepositoryMock } from 'test/repositories/metadata.repository.mock'; +import { newPartnerRepositoryMock } from 'test/repositories/partner.repository.mock'; +import { newPersonRepositoryMock } from 'test/repositories/person.repository.mock'; +import { newSearchRepositoryMock } from 'test/repositories/search.repository.mock'; +import { newSystemConfigRepositoryMock } from 'test/repositories/system-config.repository.mock'; jest.useFakeTimers(); diff --git a/server/src/domain/search/search.service.ts b/server/src/domain/search/search.service.ts index 4b15dfd51..9778c6d3d 100644 --- a/server/src/domain/search/search.service.ts +++ b/server/src/domain/search/search.service.ts @@ -1,20 +1,9 @@ -import { AssetEntity, AssetOrder } from '@app/infra/entities'; import { Inject, Injectable } from '@nestjs/common'; -import { AssetResponseDto, mapAsset } from '../asset'; -import { AuthDto } from '../auth'; -import { PersonResponseDto } from '../person'; -import { - IAssetRepository, - IMachineLearningRepository, - IMetadataRepository, - IPartnerRepository, - IPersonRepository, - ISearchRepository, - ISystemConfigRepository, - SearchExploreItem, - SearchStrategy, -} from '../repositories'; -import { FeatureFlag, SystemConfigCore } from '../system-config'; +import { FeatureFlag, SystemConfigCore } from 'src/cores/system-config.core'; +import { AssetResponseDto, mapAsset } from 'src/domain/asset/response-dto/asset-response.dto'; +import { AuthDto } from 'src/domain/auth/auth.dto'; +import { PersonResponseDto } from 'src/domain/person/person.dto'; +import { SearchSuggestionRequestDto, SearchSuggestionType } from 'src/domain/search/dto/search-suggestion.dto'; import { MetadataSearchDto, PlacesResponseDto, @@ -23,9 +12,17 @@ import { SearchPlacesDto, SmartSearchDto, mapPlaces, -} from './dto'; -import { SearchSuggestionRequestDto, SearchSuggestionType } from './dto/search-suggestion.dto'; -import { SearchResponseDto } from './response-dto'; +} from 'src/domain/search/dto/search.dto'; +import { SearchResponseDto } from 'src/domain/search/response-dto/search-response.dto'; +import { AssetOrder } from 'src/infra/entities/album.entity'; +import { AssetEntity } from 'src/infra/entities/asset.entity'; +import { IAssetRepository } from 'src/interfaces/asset.repository'; +import { IMachineLearningRepository } from 'src/interfaces/machine-learning.repository'; +import { IMetadataRepository } from 'src/interfaces/metadata.repository'; +import { IPartnerRepository } from 'src/interfaces/partner.repository'; +import { IPersonRepository } from 'src/interfaces/person.repository'; +import { ISearchRepository, SearchExploreItem, SearchStrategy } from 'src/interfaces/search.repository'; +import { ISystemConfigRepository } from 'src/interfaces/system-config.repository'; @Injectable() export class SearchService { diff --git a/server/src/domain/server-info/index.ts b/server/src/domain/server-info/index.ts deleted file mode 100644 index 74a46a52b..000000000 --- a/server/src/domain/server-info/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './server-info.dto'; -export * from './server-info.service'; diff --git a/server/src/domain/server-info/server-info.dto.ts b/server/src/domain/server-info/server-info.dto.ts index 99d4f1566..0cbfbe773 100644 --- a/server/src/domain/server-info/server-info.dto.ts +++ b/server/src/domain/server-info/server-info.dto.ts @@ -1,7 +1,8 @@ -import { FeatureFlags, IVersion, type VersionType } from '@app/domain'; import { ApiProperty, ApiResponseProperty } from '@nestjs/swagger'; import type { DateTime } from 'luxon'; -import { SystemConfigThemeDto } from '../system-config/dto/system-config-theme.dto'; +import { FeatureFlags } from 'src/cores/system-config.core'; +import { IVersion, VersionType } from 'src/domain/domain.constant'; +import { SystemConfigThemeDto } from 'src/domain/system-config/dto/system-config-theme.dto'; export class ServerPingResponse { @ApiResponseProperty({ type: String, example: 'pong' }) diff --git a/server/src/domain/server-info/server-info.service.spec.ts b/server/src/domain/server-info/server-info.service.spec.ts index 8c90f8107..05f05cc8f 100644 --- a/server/src/domain/server-info/server-info.service.spec.ts +++ b/server/src/domain/server-info/server-info.service.spec.ts @@ -1,22 +1,18 @@ -import { SystemMetadataKey } from '@app/infra/entities'; -import { - newCommunicationRepositoryMock, - newServerInfoRepositoryMock, - newStorageRepositoryMock, - newSystemConfigRepositoryMock, - newSystemMetadataRepositoryMock, - newUserRepositoryMock, -} from '@test'; -import { serverVersion } from '../domain.constant'; -import { - ICommunicationRepository, - IServerInfoRepository, - IStorageRepository, - ISystemConfigRepository, - ISystemMetadataRepository, - IUserRepository, -} from '../repositories'; -import { ServerInfoService } from './server-info.service'; +import { serverVersion } from 'src/domain/domain.constant'; +import { ServerInfoService } from 'src/domain/server-info/server-info.service'; +import { SystemMetadataKey } from 'src/infra/entities/system-metadata.entity'; +import { ICommunicationRepository } from 'src/interfaces/communication.repository'; +import { IServerInfoRepository } from 'src/interfaces/server-info.repository'; +import { IStorageRepository } from 'src/interfaces/storage.repository'; +import { ISystemConfigRepository } from 'src/interfaces/system-config.repository'; +import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.repository'; +import { IUserRepository } from 'src/interfaces/user.repository'; +import { newCommunicationRepositoryMock } from 'test/repositories/communication.repository.mock'; +import { newStorageRepositoryMock } from 'test/repositories/storage.repository.mock'; +import { newSystemConfigRepositoryMock } from 'test/repositories/system-config.repository.mock'; +import { newServerInfoRepositoryMock } from 'test/repositories/system-info.repository.mock'; +import { newSystemMetadataRepositoryMock } from 'test/repositories/system-metadata.repository.mock'; +import { newUserRepositoryMock } from 'test/repositories/user.repository.mock'; describe(ServerInfoService.name, () => { let sut: ServerInfoService; diff --git a/server/src/domain/server-info/server-info.service.ts b/server/src/domain/server-info/server-info.service.ts index 04b3c4b6e..533bb42d9 100644 --- a/server/src/domain/server-info/server-info.service.ts +++ b/server/src/domain/server-info/server-info.service.ts @@ -1,21 +1,8 @@ -import { SystemMetadataKey } from '@app/infra/entities'; -import { ImmichLogger } from '@app/infra/logger'; import { Inject, Injectable } from '@nestjs/common'; import { DateTime } from 'luxon'; -import { Version, isDev, mimeTypes, serverVersion } from '../domain.constant'; -import { asHumanReadable } from '../domain.util'; -import { - ClientEvent, - ICommunicationRepository, - IServerInfoRepository, - IStorageRepository, - ISystemConfigRepository, - ISystemMetadataRepository, - IUserRepository, - UserStatsQueryResponse, -} from '../repositories'; -import { StorageCore, StorageFolder } from '../storage'; -import { SystemConfigCore } from '../system-config'; +import { StorageCore, StorageFolder } from 'src/cores/storage.core'; +import { SystemConfigCore } from 'src/cores/system-config.core'; +import { Version, isDev, mimeTypes, serverVersion } from 'src/domain/domain.constant'; import { ServerConfigDto, ServerFeaturesDto, @@ -24,7 +11,16 @@ import { ServerPingResponse, ServerStatsResponseDto, UsageByUserDto, -} from './server-info.dto'; +} from 'src/domain/server-info/server-info.dto'; +import { SystemMetadataKey } from 'src/infra/entities/system-metadata.entity'; +import { ImmichLogger } from 'src/infra/logger'; +import { ClientEvent, ICommunicationRepository } from 'src/interfaces/communication.repository'; +import { IServerInfoRepository } from 'src/interfaces/server-info.repository'; +import { IStorageRepository } from 'src/interfaces/storage.repository'; +import { ISystemConfigRepository } from 'src/interfaces/system-config.repository'; +import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.repository'; +import { IUserRepository, UserStatsQueryResponse } from 'src/interfaces/user.repository'; +import { asHumanReadable } from 'src/utils'; @Injectable() export class ServerInfoService { diff --git a/server/src/domain/shared-link/index.ts b/server/src/domain/shared-link/index.ts deleted file mode 100644 index 0b4720850..000000000 --- a/server/src/domain/shared-link/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './shared-link-response.dto'; -export * from './shared-link.dto'; -export * from './shared-link.service'; diff --git a/server/src/domain/shared-link/shared-link-response.dto.ts b/server/src/domain/shared-link/shared-link-response.dto.ts index b16a578f4..034b45f1c 100644 --- a/server/src/domain/shared-link/shared-link-response.dto.ts +++ b/server/src/domain/shared-link/shared-link-response.dto.ts @@ -1,8 +1,8 @@ -import { SharedLinkEntity, SharedLinkType } from '@app/infra/entities'; import { ApiProperty } from '@nestjs/swagger'; import _ from 'lodash'; -import { AlbumResponseDto, mapAlbumWithoutAssets } from '../album'; -import { AssetResponseDto, mapAsset } from '../asset'; +import { AlbumResponseDto, mapAlbumWithoutAssets } from 'src/domain/album/album-response.dto'; +import { AssetResponseDto, mapAsset } from 'src/domain/asset/response-dto/asset-response.dto'; +import { SharedLinkEntity, SharedLinkType } from 'src/infra/entities/shared-link.entity'; export class SharedLinkResponseDto { id!: string; diff --git a/server/src/domain/shared-link/shared-link.dto.ts b/server/src/domain/shared-link/shared-link.dto.ts index 550ed70ea..7ef9a7733 100644 --- a/server/src/domain/shared-link/shared-link.dto.ts +++ b/server/src/domain/shared-link/shared-link.dto.ts @@ -1,7 +1,7 @@ -import { SharedLinkType } from '@app/infra/entities'; import { ApiProperty } from '@nestjs/swagger'; import { IsEnum, IsString } from 'class-validator'; -import { Optional, ValidateBoolean, ValidateDate, ValidateUUID } from '../domain.util'; +import { SharedLinkType } from 'src/infra/entities/shared-link.entity'; +import { Optional, ValidateBoolean, ValidateDate, ValidateUUID } from 'src/validation'; export class SharedLinkCreateDto { @IsEnum(SharedLinkType) diff --git a/server/src/domain/shared-link/shared-link.service.spec.ts b/server/src/domain/shared-link/shared-link.service.spec.ts index f0d0715a3..3b15138f4 100644 --- a/server/src/domain/shared-link/shared-link.service.spec.ts +++ b/server/src/domain/shared-link/shared-link.service.spec.ts @@ -1,20 +1,17 @@ -import { SharedLinkType } from '@app/infra/entities'; import { BadRequestException, ForbiddenException, UnauthorizedException } from '@nestjs/common'; -import { - IAccessRepositoryMock, - albumStub, - assetStub, - authStub, - newAccessRepositoryMock, - newCryptoRepositoryMock, - newSharedLinkRepositoryMock, - sharedLinkResponseStub, - sharedLinkStub, -} from '@test'; import _ from 'lodash'; -import { AssetIdErrorReason } from '../asset'; -import { ICryptoRepository, ISharedLinkRepository } from '../repositories'; -import { SharedLinkService } from './shared-link.service'; +import { AssetIdErrorReason } from 'src/domain/asset/response-dto/asset-ids-response.dto'; +import { SharedLinkService } from 'src/domain/shared-link/shared-link.service'; +import { SharedLinkType } from 'src/infra/entities/shared-link.entity'; +import { ICryptoRepository } from 'src/interfaces/crypto.repository'; +import { ISharedLinkRepository } from 'src/interfaces/shared-link.repository'; +import { albumStub } from 'test/fixtures/album.stub'; +import { assetStub } from 'test/fixtures/asset.stub'; +import { authStub } from 'test/fixtures/auth.stub'; +import { sharedLinkResponseStub, sharedLinkStub } from 'test/fixtures/shared-link.stub'; +import { IAccessRepositoryMock, newAccessRepositoryMock } from 'test/repositories/access.repository.mock'; +import { newCryptoRepositoryMock } from 'test/repositories/crypto.repository.mock'; +import { newSharedLinkRepositoryMock } from 'test/repositories/shared-link.repository.mock'; describe(SharedLinkService.name, () => { let sut: SharedLinkService; diff --git a/server/src/domain/shared-link/shared-link.service.ts b/server/src/domain/shared-link/shared-link.service.ts index 54e6f6052..34619cc5c 100644 --- a/server/src/domain/shared-link/shared-link.service.ts +++ b/server/src/domain/shared-link/shared-link.service.ts @@ -1,12 +1,20 @@ -import { AssetEntity, SharedLinkEntity, SharedLinkType } from '@app/infra/entities'; import { BadRequestException, ForbiddenException, Inject, Injectable, UnauthorizedException } from '@nestjs/common'; -import { AccessCore, Permission } from '../access'; -import { AssetIdErrorReason, AssetIdsDto, AssetIdsResponseDto } from '../asset'; -import { AuthDto } from '../auth'; -import { OpenGraphTags } from '../domain.util'; -import { IAccessRepository, ICryptoRepository, ISharedLinkRepository } from '../repositories'; -import { SharedLinkResponseDto, mapSharedLink, mapSharedLinkWithoutMetadata } from './shared-link-response.dto'; -import { SharedLinkCreateDto, SharedLinkEditDto, SharedLinkPasswordDto } from './shared-link.dto'; +import { AccessCore, Permission } from 'src/cores/access.core'; +import { AssetIdsDto } from 'src/domain/asset/dto/asset-ids.dto'; +import { AssetIdErrorReason, AssetIdsResponseDto } from 'src/domain/asset/response-dto/asset-ids-response.dto'; +import { AuthDto } from 'src/domain/auth/auth.dto'; +import { + SharedLinkResponseDto, + mapSharedLink, + mapSharedLinkWithoutMetadata, +} from 'src/domain/shared-link/shared-link-response.dto'; +import { SharedLinkCreateDto, SharedLinkEditDto, SharedLinkPasswordDto } from 'src/domain/shared-link/shared-link.dto'; +import { AssetEntity } from 'src/infra/entities/asset.entity'; +import { SharedLinkEntity, SharedLinkType } from 'src/infra/entities/shared-link.entity'; +import { IAccessRepository } from 'src/interfaces/access.repository'; +import { ICryptoRepository } from 'src/interfaces/crypto.repository'; +import { ISharedLinkRepository } from 'src/interfaces/shared-link.repository'; +import { OpenGraphTags } from 'src/utils'; @Injectable() export class SharedLinkService { diff --git a/server/src/domain/smart-info/dto/index.ts b/server/src/domain/smart-info/dto/index.ts deleted file mode 100644 index aa672a787..000000000 --- a/server/src/domain/smart-info/dto/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './model-config.dto'; diff --git a/server/src/domain/smart-info/dto/model-config.dto.ts b/server/src/domain/smart-info/dto/model-config.dto.ts index b9e27669f..b63ec7414 100644 --- a/server/src/domain/smart-info/dto/model-config.dto.ts +++ b/server/src/domain/smart-info/dto/model-config.dto.ts @@ -1,8 +1,8 @@ import { ApiProperty } from '@nestjs/swagger'; import { Type } from 'class-transformer'; import { IsEnum, IsNotEmpty, IsNumber, IsString, Max, Min } from 'class-validator'; -import { Optional, ValidateBoolean } from '../../domain.util'; -import { CLIPMode, ModelType } from '../../repositories'; +import { CLIPMode, ModelType } from 'src/interfaces/machine-learning.repository'; +import { Optional, ValidateBoolean } from 'src/validation'; export class ModelConfig { @ValidateBoolean() diff --git a/server/src/domain/smart-info/index.ts b/server/src/domain/smart-info/index.ts deleted file mode 100644 index a0cbeecf4..000000000 --- a/server/src/domain/smart-info/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './dto'; -export * from './smart-info.service'; diff --git a/server/src/domain/smart-info/smart-info.service.spec.ts b/server/src/domain/smart-info/smart-info.service.spec.ts index 712c2b6a7..fec43eb08 100644 --- a/server/src/domain/smart-info/smart-info.service.spec.ts +++ b/server/src/domain/smart-info/smart-info.service.spec.ts @@ -1,25 +1,21 @@ -import { AssetEntity, SystemConfigKey } from '@app/infra/entities'; -import { - assetStub, - newAssetRepositoryMock, - newDatabaseRepositoryMock, - newJobRepositoryMock, - newMachineLearningRepositoryMock, - newSearchRepositoryMock, - newSystemConfigRepositoryMock, -} from '@test'; -import { JobName } from '../job'; -import { - IAssetRepository, - IDatabaseRepository, - IJobRepository, - IMachineLearningRepository, - ISearchRepository, - ISystemConfigRepository, - WithoutProperty, -} from '../repositories'; -import { cleanModelName, getCLIPModelInfo } from './smart-info.constant'; -import { SmartInfoService } from './smart-info.service'; +import { JobName } from 'src/domain/job/job.constants'; +import { cleanModelName, getCLIPModelInfo } from 'src/domain/smart-info/smart-info.constant'; +import { SmartInfoService } from 'src/domain/smart-info/smart-info.service'; +import { AssetEntity } from 'src/infra/entities/asset.entity'; +import { SystemConfigKey } from 'src/infra/entities/system-config.entity'; +import { IAssetRepository, WithoutProperty } from 'src/interfaces/asset.repository'; +import { IDatabaseRepository } from 'src/interfaces/database.repository'; +import { IJobRepository } from 'src/interfaces/job.repository'; +import { IMachineLearningRepository } from 'src/interfaces/machine-learning.repository'; +import { ISearchRepository } from 'src/interfaces/search.repository'; +import { ISystemConfigRepository } from 'src/interfaces/system-config.repository'; +import { assetStub } from 'test/fixtures/asset.stub'; +import { newAssetRepositoryMock } from 'test/repositories/asset.repository.mock'; +import { newDatabaseRepositoryMock } from 'test/repositories/database.repository.mock'; +import { newJobRepositoryMock } from 'test/repositories/job.repository.mock'; +import { newMachineLearningRepositoryMock } from 'test/repositories/machine-learning.repository.mock'; +import { newSearchRepositoryMock } from 'test/repositories/search.repository.mock'; +import { newSystemConfigRepositoryMock } from 'test/repositories/system-config.repository.mock'; const asset = { id: 'asset-1', diff --git a/server/src/domain/smart-info/smart-info.service.ts b/server/src/domain/smart-info/smart-info.service.ts index b7dd1a91f..a7d470008 100644 --- a/server/src/domain/smart-info/smart-info.service.ts +++ b/server/src/domain/smart-info/smart-info.service.ts @@ -1,19 +1,15 @@ -import { ImmichLogger } from '@app/infra/logger'; import { Inject, Injectable } from '@nestjs/common'; -import { usePagination } from '../domain.util'; -import { IBaseJob, IEntityJob, JOBS_ASSET_PAGINATION_SIZE, JobName, QueueName } from '../job'; -import { - DatabaseLock, - IAssetRepository, - IDatabaseRepository, - IJobRepository, - IMachineLearningRepository, - ISearchRepository, - ISystemConfigRepository, - JobStatus, - WithoutProperty, -} from '../repositories'; -import { SystemConfigCore } from '../system-config'; +import { SystemConfigCore } from 'src/cores/system-config.core'; +import { JOBS_ASSET_PAGINATION_SIZE, JobName, QueueName } from 'src/domain/job/job.constants'; +import { IBaseJob, IEntityJob } from 'src/domain/job/job.interface'; +import { ImmichLogger } from 'src/infra/logger'; +import { IAssetRepository, WithoutProperty } from 'src/interfaces/asset.repository'; +import { DatabaseLock, IDatabaseRepository } from 'src/interfaces/database.repository'; +import { IJobRepository, JobStatus } from 'src/interfaces/job.repository'; +import { IMachineLearningRepository } from 'src/interfaces/machine-learning.repository'; +import { ISearchRepository } from 'src/interfaces/search.repository'; +import { ISystemConfigRepository } from 'src/interfaces/system-config.repository'; +import { usePagination } from 'src/utils'; @Injectable() export class SmartInfoService { diff --git a/server/src/domain/storage-template/index.ts b/server/src/domain/storage-template/index.ts deleted file mode 100644 index f90e36389..000000000 --- a/server/src/domain/storage-template/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './storage-template.service'; diff --git a/server/src/domain/storage-template/storage-template.service.spec.ts b/server/src/domain/storage-template/storage-template.service.spec.ts index a81e27c8f..c73dfbdcf 100644 --- a/server/src/domain/storage-template/storage-template.service.spec.ts +++ b/server/src/domain/storage-template/storage-template.service.spec.ts @@ -1,34 +1,30 @@ -import { - IAlbumRepository, - IAssetRepository, - ICryptoRepository, - IDatabaseRepository, - IMoveRepository, - IPersonRepository, - IStorageRepository, - ISystemConfigRepository, - IUserRepository, - JobStatus, - StorageTemplateService, - defaults, -} from '@app/domain'; -import { AssetPathType, SystemConfig, SystemConfigKey } from '@app/infra/entities'; -import { - assetStub, - newAlbumRepositoryMock, - newAssetRepositoryMock, - newCryptoRepositoryMock, - newDatabaseRepositoryMock, - newMoveRepositoryMock, - newPersonRepositoryMock, - newStorageRepositoryMock, - newSystemConfigRepositoryMock, - newUserRepositoryMock, - userStub, -} from '@test'; import { when } from 'jest-when'; import { Stats } from 'node:fs'; -import { SystemConfigCore } from '../system-config'; +import { SystemConfigCore, defaults } from 'src/cores/system-config.core'; +import { StorageTemplateService } from 'src/domain/storage-template/storage-template.service'; +import { AssetPathType } from 'src/infra/entities/move.entity'; +import { SystemConfig, SystemConfigKey } from 'src/infra/entities/system-config.entity'; +import { IAlbumRepository } from 'src/interfaces/album.repository'; +import { IAssetRepository } from 'src/interfaces/asset.repository'; +import { ICryptoRepository } from 'src/interfaces/crypto.repository'; +import { IDatabaseRepository } from 'src/interfaces/database.repository'; +import { JobStatus } from 'src/interfaces/job.repository'; +import { IMoveRepository } from 'src/interfaces/move.repository'; +import { IPersonRepository } from 'src/interfaces/person.repository'; +import { IStorageRepository } from 'src/interfaces/storage.repository'; +import { ISystemConfigRepository } from 'src/interfaces/system-config.repository'; +import { IUserRepository } from 'src/interfaces/user.repository'; +import { assetStub } from 'test/fixtures/asset.stub'; +import { userStub } from 'test/fixtures/user.stub'; +import { newAlbumRepositoryMock } from 'test/repositories/album.repository.mock'; +import { newAssetRepositoryMock } from 'test/repositories/asset.repository.mock'; +import { newCryptoRepositoryMock } from 'test/repositories/crypto.repository.mock'; +import { newDatabaseRepositoryMock } from 'test/repositories/database.repository.mock'; +import { newMoveRepositoryMock } from 'test/repositories/move.repository.mock'; +import { newPersonRepositoryMock } from 'test/repositories/person.repository.mock'; +import { newStorageRepositoryMock } from 'test/repositories/storage.repository.mock'; +import { newSystemConfigRepositoryMock } from 'test/repositories/system-config.repository.mock'; +import { newUserRepositoryMock } from 'test/repositories/user.repository.mock'; describe(StorageTemplateService.name, () => { let sut: StorageTemplateService; diff --git a/server/src/domain/storage-template/storage-template.service.ts b/server/src/domain/storage-template/storage-template.service.ts index ffdbfbefb..480f97368 100644 --- a/server/src/domain/storage-template/storage-template.service.ts +++ b/server/src/domain/storage-template/storage-template.service.ts @@ -1,29 +1,13 @@ -import { AssetEntity, AssetPathType, AssetType, SystemConfig } from '@app/infra/entities'; -import { ImmichLogger } from '@app/infra/logger'; import { Inject, Injectable } from '@nestjs/common'; import { OnEvent } from '@nestjs/event-emitter'; import handlebar from 'handlebars'; -import * as luxon from 'luxon'; +import { DateTime } from 'luxon'; import path from 'node:path'; import sanitize from 'sanitize-filename'; -import { getLivePhotoMotionFilename, usePagination } from '../domain.util'; -import { IEntityJob, JOBS_ASSET_PAGINATION_SIZE } from '../job'; -import { - DatabaseLock, - IAlbumRepository, - IAssetRepository, - ICryptoRepository, - IDatabaseRepository, - IMoveRepository, - IPersonRepository, - IStorageRepository, - ISystemConfigRepository, - IUserRepository, - InternalEvent, - InternalEventMap, - JobStatus, -} from '../repositories'; -import { StorageCore, StorageFolder } from '../storage'; +import { StorageCore, StorageFolder } from 'src/cores/storage.core'; +import { SystemConfigCore } from 'src/cores/system-config.core'; +import { JOBS_ASSET_PAGINATION_SIZE } from 'src/domain/job/job.constants'; +import { IEntityJob } from 'src/domain/job/job.interface'; import { supportedDayTokens, supportedHourTokens, @@ -32,8 +16,23 @@ import { supportedSecondTokens, supportedWeekTokens, supportedYearTokens, -} from '../system-config'; -import { SystemConfigCore } from '../system-config/system-config.core'; +} from 'src/domain/system-config/system-config.constants'; +import { AssetEntity, AssetType } from 'src/infra/entities/asset.entity'; +import { AssetPathType } from 'src/infra/entities/move.entity'; +import { SystemConfig } from 'src/infra/entities/system-config.entity'; +import { ImmichLogger } from 'src/infra/logger'; +import { IAlbumRepository } from 'src/interfaces/album.repository'; +import { IAssetRepository } from 'src/interfaces/asset.repository'; +import { InternalEvent, InternalEventMap } from 'src/interfaces/communication.repository'; +import { ICryptoRepository } from 'src/interfaces/crypto.repository'; +import { DatabaseLock, IDatabaseRepository } from 'src/interfaces/database.repository'; +import { JobStatus } from 'src/interfaces/job.repository'; +import { IMoveRepository } from 'src/interfaces/move.repository'; +import { IPersonRepository } from 'src/interfaces/person.repository'; +import { IStorageRepository } from 'src/interfaces/storage.repository'; +import { ISystemConfigRepository } from 'src/interfaces/system-config.repository'; +import { IUserRepository } from 'src/interfaces/user.repository'; +import { getLivePhotoMotionFilename, usePagination } from 'src/utils'; export interface MoveAssetMetadata { storageLabel: string | null; @@ -312,7 +311,7 @@ export class StorageTemplateService { const systemTimeZone = Intl.DateTimeFormat().resolvedOptions().timeZone; const zone = asset.exifInfo?.timeZone || systemTimeZone; - const dt = luxon.DateTime.fromJSDate(asset.fileCreatedAt, { zone }); + const dt = DateTime.fromJSDate(asset.fileCreatedAt, { zone }); const dateTokens = [ ...supportedYearTokens, diff --git a/server/src/domain/storage/index.ts b/server/src/domain/storage/index.ts deleted file mode 100644 index bdea086bd..000000000 --- a/server/src/domain/storage/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './storage.core'; -export * from './storage.service'; diff --git a/server/src/domain/storage/storage.service.spec.ts b/server/src/domain/storage/storage.service.spec.ts index 785891086..2791c9630 100644 --- a/server/src/domain/storage/storage.service.spec.ts +++ b/server/src/domain/storage/storage.service.spec.ts @@ -1,6 +1,6 @@ -import { newStorageRepositoryMock } from '@test'; -import { IStorageRepository } from '../repositories'; -import { StorageService } from './storage.service'; +import { StorageService } from 'src/domain/storage/storage.service'; +import { IStorageRepository } from 'src/interfaces/storage.repository'; +import { newStorageRepositoryMock } from 'test/repositories/storage.repository.mock'; describe(StorageService.name, () => { let sut: StorageService; diff --git a/server/src/domain/storage/storage.service.ts b/server/src/domain/storage/storage.service.ts index 95c311881..9df3dd778 100644 --- a/server/src/domain/storage/storage.service.ts +++ b/server/src/domain/storage/storage.service.ts @@ -1,8 +1,9 @@ -import { ImmichLogger } from '@app/infra/logger'; import { Inject, Injectable } from '@nestjs/common'; -import { IDeleteFilesJob } from '../job'; -import { IStorageRepository, JobStatus } from '../repositories'; -import { StorageCore, StorageFolder } from './storage.core'; +import { StorageCore, StorageFolder } from 'src/cores/storage.core'; +import { IDeleteFilesJob } from 'src/domain/job/job.interface'; +import { ImmichLogger } from 'src/infra/logger'; +import { JobStatus } from 'src/interfaces/job.repository'; +import { IStorageRepository } from 'src/interfaces/storage.repository'; @Injectable() export class StorageService { diff --git a/server/src/domain/system-config/dto/index.ts b/server/src/domain/system-config/dto/index.ts deleted file mode 100644 index 652e34cc5..000000000 --- a/server/src/domain/system-config/dto/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -export * from './system-config-ffmpeg.dto'; -export * from './system-config-library.dto'; -export * from './system-config-oauth.dto'; -export * from './system-config-password-login.dto'; -export * from './system-config-storage-template.dto'; -export * from './system-config-thumbnail.dto'; -export * from './system-config-trash.dto'; -export * from './system-config.dto'; diff --git a/server/src/domain/system-config/dto/system-config-ffmpeg.dto.ts b/server/src/domain/system-config/dto/system-config-ffmpeg.dto.ts index 3a219888f..16f81b37c 100644 --- a/server/src/domain/system-config/dto/system-config-ffmpeg.dto.ts +++ b/server/src/domain/system-config/dto/system-config-ffmpeg.dto.ts @@ -1,8 +1,15 @@ -import { AudioCodec, CQMode, ToneMapping, TranscodeHWAccel, TranscodePolicy, VideoCodec } from '@app/infra/entities'; import { ApiProperty } from '@nestjs/swagger'; import { Type } from 'class-transformer'; import { IsEnum, IsInt, IsString, Max, Min } from 'class-validator'; -import { ValidateBoolean } from '../../domain.util'; +import { + AudioCodec, + CQMode, + ToneMapping, + TranscodeHWAccel, + TranscodePolicy, + VideoCodec, +} from 'src/infra/entities/system-config.entity'; +import { ValidateBoolean } from 'src/validation'; export class SystemConfigFFmpegDto { @IsInt() diff --git a/server/src/domain/system-config/dto/system-config-job.dto.ts b/server/src/domain/system-config/dto/system-config-job.dto.ts index 3307811d7..2769da327 100644 --- a/server/src/domain/system-config/dto/system-config-job.dto.ts +++ b/server/src/domain/system-config/dto/system-config-job.dto.ts @@ -1,7 +1,7 @@ import { ApiProperty } from '@nestjs/swagger'; import { Type } from 'class-transformer'; import { IsInt, IsObject, IsPositive, ValidateNested } from 'class-validator'; -import { ConcurrentQueueName, QueueName } from '../../job'; +import { ConcurrentQueueName, QueueName } from 'src/domain/job/job.constants'; export class JobSettingsDto { @IsInt() diff --git a/server/src/domain/system-config/dto/system-config-library.dto.ts b/server/src/domain/system-config/dto/system-config-library.dto.ts index 85ab62634..8c7501ae4 100644 --- a/server/src/domain/system-config/dto/system-config-library.dto.ts +++ b/server/src/domain/system-config/dto/system-config-library.dto.ts @@ -9,7 +9,7 @@ import { ValidatorConstraint, ValidatorConstraintInterface, } from 'class-validator'; -import { ValidateBoolean, validateCronExpression } from '../../domain.util'; +import { ValidateBoolean, validateCronExpression } from 'src/validation'; const isEnabled = (config: SystemConfigLibraryScanDto) => config.enabled; diff --git a/server/src/domain/system-config/dto/system-config-logging.dto.ts b/server/src/domain/system-config/dto/system-config-logging.dto.ts index 09f78fc86..f41b568a2 100644 --- a/server/src/domain/system-config/dto/system-config-logging.dto.ts +++ b/server/src/domain/system-config/dto/system-config-logging.dto.ts @@ -1,7 +1,7 @@ -import { LogLevel } from '@app/infra/entities'; import { ApiProperty } from '@nestjs/swagger'; import { IsEnum } from 'class-validator'; -import { ValidateBoolean } from '../../domain.util'; +import { LogLevel } from 'src/infra/entities/system-config.entity'; +import { ValidateBoolean } from 'src/validation'; export class SystemConfigLoggingDto { @ValidateBoolean() diff --git a/server/src/domain/system-config/dto/system-config-machine-learning.dto.ts b/server/src/domain/system-config/dto/system-config-machine-learning.dto.ts index 435e68826..058585992 100644 --- a/server/src/domain/system-config/dto/system-config-machine-learning.dto.ts +++ b/server/src/domain/system-config/dto/system-config-machine-learning.dto.ts @@ -1,7 +1,7 @@ import { Type } from 'class-transformer'; import { IsObject, IsUrl, ValidateIf, ValidateNested } from 'class-validator'; -import { ValidateBoolean } from '../../domain.util'; -import { CLIPConfig, RecognitionConfig } from '../../smart-info/dto/model-config.dto'; +import { CLIPConfig, RecognitionConfig } from 'src/domain/smart-info/dto/model-config.dto'; +import { ValidateBoolean } from 'src/validation'; export class SystemConfigMachineLearningDto { @ValidateBoolean() diff --git a/server/src/domain/system-config/dto/system-config-map.dto.ts b/server/src/domain/system-config/dto/system-config-map.dto.ts index 9e21e2d5d..9ec0abfa4 100644 --- a/server/src/domain/system-config/dto/system-config-map.dto.ts +++ b/server/src/domain/system-config/dto/system-config-map.dto.ts @@ -1,5 +1,5 @@ import { IsString } from 'class-validator'; -import { ValidateBoolean } from '../../domain.util'; +import { ValidateBoolean } from 'src/validation'; export class SystemConfigMapDto { @ValidateBoolean() diff --git a/server/src/domain/system-config/dto/system-config-new-version-check.dto.ts b/server/src/domain/system-config/dto/system-config-new-version-check.dto.ts index 379f5643d..7d5c5134f 100644 --- a/server/src/domain/system-config/dto/system-config-new-version-check.dto.ts +++ b/server/src/domain/system-config/dto/system-config-new-version-check.dto.ts @@ -1,4 +1,4 @@ -import { ValidateBoolean } from '../../domain.util'; +import { ValidateBoolean } from 'src/validation'; export class SystemConfigNewVersionCheckDto { @ValidateBoolean() diff --git a/server/src/domain/system-config/dto/system-config-oauth.dto.ts b/server/src/domain/system-config/dto/system-config-oauth.dto.ts index 99779bdfe..9c7fc5f40 100644 --- a/server/src/domain/system-config/dto/system-config-oauth.dto.ts +++ b/server/src/domain/system-config/dto/system-config-oauth.dto.ts @@ -1,5 +1,5 @@ import { IsNotEmpty, IsNumber, IsString, IsUrl, Min, ValidateIf } from 'class-validator'; -import { ValidateBoolean } from '../../domain.util'; +import { ValidateBoolean } from 'src/validation'; const isEnabled = (config: SystemConfigOAuthDto) => config.enabled; const isOverrideEnabled = (config: SystemConfigOAuthDto) => config.mobileOverrideEnabled; diff --git a/server/src/domain/system-config/dto/system-config-password-login.dto.ts b/server/src/domain/system-config/dto/system-config-password-login.dto.ts index 279bcc5a6..8d49a7002 100644 --- a/server/src/domain/system-config/dto/system-config-password-login.dto.ts +++ b/server/src/domain/system-config/dto/system-config-password-login.dto.ts @@ -1,4 +1,4 @@ -import { ValidateBoolean } from '../../domain.util'; +import { ValidateBoolean } from 'src/validation'; export class SystemConfigPasswordLoginDto { @ValidateBoolean() diff --git a/server/src/domain/system-config/dto/system-config-reverse-geocoding.dto.ts b/server/src/domain/system-config/dto/system-config-reverse-geocoding.dto.ts index 11e0ae289..8ff286601 100644 --- a/server/src/domain/system-config/dto/system-config-reverse-geocoding.dto.ts +++ b/server/src/domain/system-config/dto/system-config-reverse-geocoding.dto.ts @@ -1,4 +1,4 @@ -import { ValidateBoolean } from '../../domain.util'; +import { ValidateBoolean } from 'src/validation'; export class SystemConfigReverseGeocodingDto { @ValidateBoolean() diff --git a/server/src/domain/system-config/dto/system-config-storage-template.dto.ts b/server/src/domain/system-config/dto/system-config-storage-template.dto.ts index 615fd8521..77204b46e 100644 --- a/server/src/domain/system-config/dto/system-config-storage-template.dto.ts +++ b/server/src/domain/system-config/dto/system-config-storage-template.dto.ts @@ -1,5 +1,5 @@ import { IsNotEmpty, IsString } from 'class-validator'; -import { ValidateBoolean } from '../../domain.util'; +import { ValidateBoolean } from 'src/validation'; export class SystemConfigStorageTemplateDto { @ValidateBoolean() diff --git a/server/src/domain/system-config/dto/system-config-thumbnail.dto.ts b/server/src/domain/system-config/dto/system-config-thumbnail.dto.ts index c389ef77a..2f156894f 100644 --- a/server/src/domain/system-config/dto/system-config-thumbnail.dto.ts +++ b/server/src/domain/system-config/dto/system-config-thumbnail.dto.ts @@ -1,7 +1,7 @@ -import { Colorspace } from '@app/infra/entities'; import { ApiProperty } from '@nestjs/swagger'; import { Type } from 'class-transformer'; import { IsEnum, IsInt, Max, Min } from 'class-validator'; +import { Colorspace } from 'src/infra/entities/system-config.entity'; export class SystemConfigThumbnailDto { @IsInt() diff --git a/server/src/domain/system-config/dto/system-config-trash.dto.ts b/server/src/domain/system-config/dto/system-config-trash.dto.ts index 482410703..a9e5483eb 100644 --- a/server/src/domain/system-config/dto/system-config-trash.dto.ts +++ b/server/src/domain/system-config/dto/system-config-trash.dto.ts @@ -1,7 +1,7 @@ import { ApiProperty } from '@nestjs/swagger'; import { Type } from 'class-transformer'; import { IsInt, Min } from 'class-validator'; -import { ValidateBoolean } from '../../domain.util'; +import { ValidateBoolean } from 'src/validation'; export class SystemConfigTrashDto { @ValidateBoolean() diff --git a/server/src/domain/system-config/dto/system-config.dto.ts b/server/src/domain/system-config/dto/system-config.dto.ts index 4906e293e..4f97975bc 100644 --- a/server/src/domain/system-config/dto/system-config.dto.ts +++ b/server/src/domain/system-config/dto/system-config.dto.ts @@ -1,22 +1,22 @@ -import { SystemConfig } from '@app/infra/entities'; import { Type } from 'class-transformer'; import { IsObject, ValidateNested } from 'class-validator'; -import { SystemConfigFFmpegDto } from './system-config-ffmpeg.dto'; -import { SystemConfigJobDto } from './system-config-job.dto'; -import { SystemConfigLibraryDto } from './system-config-library.dto'; -import { SystemConfigLoggingDto } from './system-config-logging.dto'; -import { SystemConfigMachineLearningDto } from './system-config-machine-learning.dto'; -import { SystemConfigMapDto } from './system-config-map.dto'; -import { SystemConfigNewVersionCheckDto } from './system-config-new-version-check.dto'; -import { SystemConfigOAuthDto } from './system-config-oauth.dto'; -import { SystemConfigPasswordLoginDto } from './system-config-password-login.dto'; -import { SystemConfigReverseGeocodingDto } from './system-config-reverse-geocoding.dto'; -import { SystemConfigServerDto } from './system-config-server.dto'; -import { SystemConfigStorageTemplateDto } from './system-config-storage-template.dto'; -import { SystemConfigThemeDto } from './system-config-theme.dto'; -import { SystemConfigThumbnailDto } from './system-config-thumbnail.dto'; -import { SystemConfigTrashDto } from './system-config-trash.dto'; -import { SystemConfigUserDto } from './system-config-user.dto'; +import { SystemConfigFFmpegDto } from 'src/domain/system-config/dto/system-config-ffmpeg.dto'; +import { SystemConfigJobDto } from 'src/domain/system-config/dto/system-config-job.dto'; +import { SystemConfigLibraryDto } from 'src/domain/system-config/dto/system-config-library.dto'; +import { SystemConfigLoggingDto } from 'src/domain/system-config/dto/system-config-logging.dto'; +import { SystemConfigMachineLearningDto } from 'src/domain/system-config/dto/system-config-machine-learning.dto'; +import { SystemConfigMapDto } from 'src/domain/system-config/dto/system-config-map.dto'; +import { SystemConfigNewVersionCheckDto } from 'src/domain/system-config/dto/system-config-new-version-check.dto'; +import { SystemConfigOAuthDto } from 'src/domain/system-config/dto/system-config-oauth.dto'; +import { SystemConfigPasswordLoginDto } from 'src/domain/system-config/dto/system-config-password-login.dto'; +import { SystemConfigReverseGeocodingDto } from 'src/domain/system-config/dto/system-config-reverse-geocoding.dto'; +import { SystemConfigServerDto } from 'src/domain/system-config/dto/system-config-server.dto'; +import { SystemConfigStorageTemplateDto } from 'src/domain/system-config/dto/system-config-storage-template.dto'; +import { SystemConfigThemeDto } from 'src/domain/system-config/dto/system-config-theme.dto'; +import { SystemConfigThumbnailDto } from 'src/domain/system-config/dto/system-config-thumbnail.dto'; +import { SystemConfigTrashDto } from 'src/domain/system-config/dto/system-config-trash.dto'; +import { SystemConfigUserDto } from 'src/domain/system-config/dto/system-config-user.dto'; +import { SystemConfig } from 'src/infra/entities/system-config.entity'; export class SystemConfigDto implements SystemConfig { @Type(() => SystemConfigFFmpegDto) diff --git a/server/src/domain/system-config/index.ts b/server/src/domain/system-config/index.ts deleted file mode 100644 index fb71613dd..000000000 --- a/server/src/domain/system-config/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -export * from './dto'; -export * from './response-dto'; -export * from './system-config.constants'; -export * from './system-config.core'; -export * from './system-config.service'; diff --git a/server/src/domain/system-config/response-dto/index.ts b/server/src/domain/system-config/response-dto/index.ts deleted file mode 100644 index 9cb60bece..000000000 --- a/server/src/domain/system-config/response-dto/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './system-config-template-storage-option.dto'; diff --git a/server/src/domain/system-config/system-config.service.spec.ts b/server/src/domain/system-config/system-config.service.spec.ts index fd9c16463..94db09bab 100644 --- a/server/src/domain/system-config/system-config.service.spec.ts +++ b/server/src/domain/system-config/system-config.service.spec.ts @@ -1,7 +1,11 @@ +import { BadRequestException } from '@nestjs/common'; +import { defaults } from 'src/cores/system-config.core'; +import { QueueName } from 'src/domain/job/job.constants'; +import { SystemConfigService } from 'src/domain/system-config/system-config.service'; import { AudioCodec, - Colorspace, CQMode, + Colorspace, LogLevel, SystemConfig, SystemConfigEntity, @@ -10,14 +14,13 @@ import { TranscodeHWAccel, TranscodePolicy, VideoCodec, -} from '@app/infra/entities'; -import { ImmichLogger } from '@app/infra/logger'; -import { BadRequestException } from '@nestjs/common'; -import { newCommunicationRepositoryMock, newSystemConfigRepositoryMock } from '@test'; -import { QueueName } from '../job'; -import { ICommunicationRepository, ISearchRepository, ISystemConfigRepository, ServerEvent } from '../repositories'; -import { defaults } from './system-config.core'; -import { SystemConfigService } from './system-config.service'; +} from 'src/infra/entities/system-config.entity'; +import { ImmichLogger } from 'src/infra/logger'; +import { ICommunicationRepository, ServerEvent } from 'src/interfaces/communication.repository'; +import { ISearchRepository } from 'src/interfaces/search.repository'; +import { ISystemConfigRepository } from 'src/interfaces/system-config.repository'; +import { newCommunicationRepositoryMock } from 'test/repositories/communication.repository.mock'; +import { newSystemConfigRepositoryMock } from 'test/repositories/system-config.repository.mock'; const updates: SystemConfigEntity[] = [ { key: SystemConfigKey.FFMPEG_CRF, value: 30 }, diff --git a/server/src/domain/system-config/system-config.service.ts b/server/src/domain/system-config/system-config.service.ts index 7e68cf0b9..bd2c30805 100644 --- a/server/src/domain/system-config/system-config.service.ts +++ b/server/src/domain/system-config/system-config.service.ts @@ -1,20 +1,10 @@ -import { LogLevel, SystemConfig } from '@app/infra/entities'; -import { ImmichLogger } from '@app/infra/logger'; import { BadRequestException, Inject, Injectable } from '@nestjs/common'; import { OnEvent } from '@nestjs/event-emitter'; import { instanceToPlain } from 'class-transformer'; import _ from 'lodash'; -import { - ClientEvent, - ICommunicationRepository, - ISearchRepository, - ISystemConfigRepository, - InternalEvent, - InternalEventMap, - ServerEvent, -} from '../repositories'; -import { SystemConfigDto, mapConfig } from './dto/system-config.dto'; -import { SystemConfigTemplateStorageOptionDto } from './response-dto/system-config-template-storage-option.dto'; +import { SystemConfigCore } from 'src/cores/system-config.core'; +import { SystemConfigDto, mapConfig } from 'src/domain/system-config/dto/system-config.dto'; +import { SystemConfigTemplateStorageOptionDto } from 'src/domain/system-config/response-dto/system-config-template-storage-option.dto'; import { supportedDayTokens, supportedHourTokens, @@ -24,8 +14,18 @@ import { supportedSecondTokens, supportedWeekTokens, supportedYearTokens, -} from './system-config.constants'; -import { SystemConfigCore } from './system-config.core'; +} from 'src/domain/system-config/system-config.constants'; +import { LogLevel, SystemConfig } from 'src/infra/entities/system-config.entity'; +import { ImmichLogger } from 'src/infra/logger'; +import { + ClientEvent, + ICommunicationRepository, + InternalEvent, + InternalEventMap, + ServerEvent, +} from 'src/interfaces/communication.repository'; +import { ISearchRepository } from 'src/interfaces/search.repository'; +import { ISystemConfigRepository } from 'src/interfaces/system-config.repository'; @Injectable() export class SystemConfigService { diff --git a/server/src/domain/tag/index.ts b/server/src/domain/tag/index.ts deleted file mode 100644 index 38e9b389f..000000000 --- a/server/src/domain/tag/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './tag-response.dto'; -export * from './tag.dto'; -export * from './tag.service'; diff --git a/server/src/domain/tag/tag-response.dto.ts b/server/src/domain/tag/tag-response.dto.ts index a533b15c9..04ee9540b 100644 --- a/server/src/domain/tag/tag-response.dto.ts +++ b/server/src/domain/tag/tag-response.dto.ts @@ -1,5 +1,5 @@ -import { TagEntity, TagType } from '@app/infra/entities'; import { ApiProperty } from '@nestjs/swagger'; +import { TagEntity, TagType } from 'src/infra/entities/tag.entity'; export class TagResponseDto { id!: string; diff --git a/server/src/domain/tag/tag.dto.ts b/server/src/domain/tag/tag.dto.ts index 900aac9bd..322f40acf 100644 --- a/server/src/domain/tag/tag.dto.ts +++ b/server/src/domain/tag/tag.dto.ts @@ -1,7 +1,7 @@ -import { TagType } from '@app/infra/entities'; import { ApiProperty } from '@nestjs/swagger'; import { IsEnum, IsNotEmpty, IsString } from 'class-validator'; -import { Optional } from '../domain.util'; +import { TagType } from 'src/infra/entities/tag.entity'; +import { Optional } from 'src/validation'; export class CreateTagDto { @IsString() diff --git a/server/src/domain/tag/tag.service.spec.ts b/server/src/domain/tag/tag.service.spec.ts index e987beb6a..012a745ee 100644 --- a/server/src/domain/tag/tag.service.spec.ts +++ b/server/src/domain/tag/tag.service.spec.ts @@ -1,10 +1,13 @@ -import { TagType } from '@app/infra/entities'; import { BadRequestException } from '@nestjs/common'; -import { assetStub, authStub, newTagRepositoryMock, tagResponseStub, tagStub } from '@test'; import { when } from 'jest-when'; -import { AssetIdErrorReason } from '../asset'; -import { ITagRepository } from '../repositories'; -import { TagService } from './tag.service'; +import { AssetIdErrorReason } from 'src/domain/asset/response-dto/asset-ids-response.dto'; +import { TagService } from 'src/domain/tag/tag.service'; +import { TagType } from 'src/infra/entities/tag.entity'; +import { ITagRepository } from 'src/interfaces/tag.repository'; +import { assetStub } from 'test/fixtures/asset.stub'; +import { authStub } from 'test/fixtures/auth.stub'; +import { tagResponseStub, tagStub } from 'test/fixtures/tag.stub'; +import { newTagRepositoryMock } from 'test/repositories/tag.repository.mock'; describe(TagService.name, () => { let sut: TagService; diff --git a/server/src/domain/tag/tag.service.ts b/server/src/domain/tag/tag.service.ts index 38f1de1bc..b04e251f7 100644 --- a/server/src/domain/tag/tag.service.ts +++ b/server/src/domain/tag/tag.service.ts @@ -1,9 +1,11 @@ import { BadRequestException, Inject, Injectable } from '@nestjs/common'; -import { AssetIdErrorReason, AssetIdsDto, AssetIdsResponseDto, AssetResponseDto, mapAsset } from '../asset'; -import { AuthDto } from '../auth'; -import { ITagRepository } from '../repositories'; -import { TagResponseDto, mapTag } from './tag-response.dto'; -import { CreateTagDto, UpdateTagDto } from './tag.dto'; +import { AssetIdsDto } from 'src/domain/asset/dto/asset-ids.dto'; +import { AssetIdErrorReason, AssetIdsResponseDto } from 'src/domain/asset/response-dto/asset-ids-response.dto'; +import { AssetResponseDto, mapAsset } from 'src/domain/asset/response-dto/asset-response.dto'; +import { AuthDto } from 'src/domain/auth/auth.dto'; +import { TagResponseDto, mapTag } from 'src/domain/tag/tag-response.dto'; +import { CreateTagDto, UpdateTagDto } from 'src/domain/tag/tag.dto'; +import { ITagRepository } from 'src/interfaces/tag.repository'; @Injectable() export class TagService { diff --git a/server/src/domain/trash/index.ts b/server/src/domain/trash/index.ts deleted file mode 100644 index 3cd00e191..000000000 --- a/server/src/domain/trash/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './trash.service'; diff --git a/server/src/domain/trash/trash.service.spec.ts b/server/src/domain/trash/trash.service.spec.ts index 81f4186e8..021c8e526 100644 --- a/server/src/domain/trash/trash.service.spec.ts +++ b/server/src/domain/trash/trash.service.spec.ts @@ -1,16 +1,15 @@ import { BadRequestException } from '@nestjs/common'; -import { - IAccessRepositoryMock, - assetStub, - authStub, - newAccessRepositoryMock, - newAssetRepositoryMock, - newCommunicationRepositoryMock, - newJobRepositoryMock, -} from '@test'; -import { JobName } from '..'; -import { ClientEvent, IAssetRepository, ICommunicationRepository, IJobRepository } from '../repositories'; -import { TrashService } from './trash.service'; +import { JobName } from 'src/domain/job/job.constants'; +import { TrashService } from 'src/domain/trash/trash.service'; +import { IAssetRepository } from 'src/interfaces/asset.repository'; +import { ClientEvent, ICommunicationRepository } from 'src/interfaces/communication.repository'; +import { IJobRepository } from 'src/interfaces/job.repository'; +import { assetStub } from 'test/fixtures/asset.stub'; +import { authStub } from 'test/fixtures/auth.stub'; +import { IAccessRepositoryMock, newAccessRepositoryMock } from 'test/repositories/access.repository.mock'; +import { newAssetRepositoryMock } from 'test/repositories/asset.repository.mock'; +import { newCommunicationRepositoryMock } from 'test/repositories/communication.repository.mock'; +import { newJobRepositoryMock } from 'test/repositories/job.repository.mock'; describe(TrashService.name, () => { let sut: TrashService; diff --git a/server/src/domain/trash/trash.service.ts b/server/src/domain/trash/trash.service.ts index 30fd6843e..2043b4248 100644 --- a/server/src/domain/trash/trash.service.ts +++ b/server/src/domain/trash/trash.service.ts @@ -1,17 +1,14 @@ import { Inject } from '@nestjs/common'; import { DateTime } from 'luxon'; -import { AccessCore, Permission } from '../access'; -import { BulkIdsDto } from '../asset'; -import { AuthDto } from '../auth'; -import { usePagination } from '../domain.util'; -import { JOBS_ASSET_PAGINATION_SIZE, JobName } from '../job'; -import { - ClientEvent, - IAccessRepository, - IAssetRepository, - ICommunicationRepository, - IJobRepository, -} from '../repositories'; +import { AccessCore, Permission } from 'src/cores/access.core'; +import { BulkIdsDto } from 'src/domain/asset/response-dto/asset-ids-response.dto'; +import { AuthDto } from 'src/domain/auth/auth.dto'; +import { JOBS_ASSET_PAGINATION_SIZE, JobName } from 'src/domain/job/job.constants'; +import { IAccessRepository } from 'src/interfaces/access.repository'; +import { IAssetRepository } from 'src/interfaces/asset.repository'; +import { ClientEvent, ICommunicationRepository } from 'src/interfaces/communication.repository'; +import { IJobRepository } from 'src/interfaces/job.repository'; +import { usePagination } from 'src/utils'; export class TrashService { private access: AccessCore; diff --git a/server/src/domain/user/dto/create-profile-image.dto.ts b/server/src/domain/user/dto/create-profile-image.dto.ts index c7a1dc68b..37a7d1340 100644 --- a/server/src/domain/user/dto/create-profile-image.dto.ts +++ b/server/src/domain/user/dto/create-profile-image.dto.ts @@ -1,5 +1,5 @@ import { ApiProperty } from '@nestjs/swagger'; -import { UploadFieldName } from '../../asset/asset.service'; +import { UploadFieldName } from 'src/domain/asset/asset.service'; export class CreateProfileImageDto { @ApiProperty({ type: 'string', format: 'binary' }) diff --git a/server/src/domain/user/dto/create-user.dto.spec.ts b/server/src/domain/user/dto/create-user.dto.spec.ts index 4e571d38a..28abc44ad 100644 --- a/server/src/domain/user/dto/create-user.dto.spec.ts +++ b/server/src/domain/user/dto/create-user.dto.spec.ts @@ -1,6 +1,6 @@ import { plainToInstance } from 'class-transformer'; import { validate } from 'class-validator'; -import { CreateAdminDto, CreateUserDto, CreateUserOAuthDto } from './create-user.dto'; +import { CreateAdminDto, CreateUserDto, CreateUserOAuthDto } from 'src/domain/user/dto/create-user.dto'; describe('create user DTO', () => { it('validates the email', async () => { diff --git a/server/src/domain/user/dto/create-user.dto.ts b/server/src/domain/user/dto/create-user.dto.ts index f0cc7938c..7861c58c2 100644 --- a/server/src/domain/user/dto/create-user.dto.ts +++ b/server/src/domain/user/dto/create-user.dto.ts @@ -1,7 +1,7 @@ import { ApiProperty } from '@nestjs/swagger'; import { Transform } from 'class-transformer'; import { IsEmail, IsNotEmpty, IsNumber, IsPositive, IsString } from 'class-validator'; -import { Optional, ValidateBoolean, toEmail, toSanitized } from '../../domain.util'; +import { Optional, ValidateBoolean, toEmail, toSanitized } from 'src/validation'; export class CreateUserDto { @IsEmail({ require_tld: false }) diff --git a/server/src/domain/user/dto/delete-user.dto.ts b/server/src/domain/user/dto/delete-user.dto.ts index 88f55f4af..aa41e18aa 100644 --- a/server/src/domain/user/dto/delete-user.dto.ts +++ b/server/src/domain/user/dto/delete-user.dto.ts @@ -1,4 +1,4 @@ -import { ValidateBoolean } from '../../domain.util'; +import { ValidateBoolean } from 'src/validation'; export class DeleteUserDto { @ValidateBoolean({ optional: true }) diff --git a/server/src/domain/user/dto/index.ts b/server/src/domain/user/dto/index.ts deleted file mode 100644 index 2d166de36..000000000 --- a/server/src/domain/user/dto/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export * from './create-profile-image.dto'; -export * from './create-user.dto'; -export * from './delete-user.dto'; -export * from './update-user.dto'; diff --git a/server/src/domain/user/dto/update-user.dto.spec.ts b/server/src/domain/user/dto/update-user.dto.spec.ts index 8e9013f29..0ad407be3 100644 --- a/server/src/domain/user/dto/update-user.dto.spec.ts +++ b/server/src/domain/user/dto/update-user.dto.spec.ts @@ -1,6 +1,6 @@ import { plainToInstance } from 'class-transformer'; import { validate } from 'class-validator'; -import { UpdateUserDto } from './update-user.dto'; +import { UpdateUserDto } from 'src/domain/user/dto/update-user.dto'; describe('update user DTO', () => { it('should allow emails without a tld', async () => { diff --git a/server/src/domain/user/dto/update-user.dto.ts b/server/src/domain/user/dto/update-user.dto.ts index e8cce2214..c7c47c4cf 100644 --- a/server/src/domain/user/dto/update-user.dto.ts +++ b/server/src/domain/user/dto/update-user.dto.ts @@ -1,8 +1,8 @@ -import { UserAvatarColor } from '@app/infra/entities'; import { ApiProperty } from '@nestjs/swagger'; import { Transform } from 'class-transformer'; import { IsEmail, IsEnum, IsNotEmpty, IsNumber, IsPositive, IsString, IsUUID } from 'class-validator'; -import { Optional, ValidateBoolean, toEmail, toSanitized } from '../../domain.util'; +import { UserAvatarColor } from 'src/infra/entities/user.entity'; +import { Optional, ValidateBoolean, toEmail, toSanitized } from 'src/validation'; export class UpdateUserDto { @Optional() diff --git a/server/src/domain/user/index.ts b/server/src/domain/user/index.ts deleted file mode 100644 index 724859197..000000000 --- a/server/src/domain/user/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export * from './dto'; -export * from './response-dto'; -export * from './user.core'; -export * from './user.service'; diff --git a/server/src/domain/user/response-dto/index.ts b/server/src/domain/user/response-dto/index.ts deleted file mode 100644 index 8c550a4ff..000000000 --- a/server/src/domain/user/response-dto/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './create-profile-image-response.dto'; -export * from './user-response.dto'; diff --git a/server/src/domain/user/response-dto/user-response.dto.ts b/server/src/domain/user/response-dto/user-response.dto.ts index bd437ea34..b2a5d12cf 100644 --- a/server/src/domain/user/response-dto/user-response.dto.ts +++ b/server/src/domain/user/response-dto/user-response.dto.ts @@ -1,6 +1,6 @@ -import { UserAvatarColor, UserEntity, UserStatus } from '@app/infra/entities'; import { ApiProperty } from '@nestjs/swagger'; import { IsEnum } from 'class-validator'; +import { UserAvatarColor, UserEntity, UserStatus } from 'src/infra/entities/user.entity'; export const getRandomAvatarColor = (user: UserEntity): UserAvatarColor => { const values = Object.values(UserAvatarColor); diff --git a/server/src/domain/user/user.service.spec.ts b/server/src/domain/user/user.service.spec.ts index d0e56e4cd..c187e71cc 100644 --- a/server/src/domain/user/user.service.spec.ts +++ b/server/src/domain/user/user.service.spec.ts @@ -1,37 +1,33 @@ -import { UserEntity, UserStatus } from '@app/infra/entities'; import { BadRequestException, ForbiddenException, InternalServerErrorException, NotFoundException, } from '@nestjs/common'; -import { - authStub, - newAlbumRepositoryMock, - newCryptoRepositoryMock, - newJobRepositoryMock, - newLibraryRepositoryMock, - newStorageRepositoryMock, - newSystemConfigRepositoryMock, - newUserRepositoryMock, - systemConfigStub, - userStub, -} from '@test'; import { when } from 'jest-when'; -import { CacheControl, ImmichFileResponse } from '../domain.util'; -import { JobName } from '../job'; -import { - IAlbumRepository, - ICryptoRepository, - IJobRepository, - ILibraryRepository, - IStorageRepository, - ISystemConfigRepository, - IUserRepository, -} from '../repositories'; -import { UpdateUserDto } from './dto/update-user.dto'; -import { mapUser } from './response-dto'; -import { UserService } from './user.service'; +import { JobName } from 'src/domain/job/job.constants'; +import { UpdateUserDto } from 'src/domain/user/dto/update-user.dto'; +import { mapUser } from 'src/domain/user/response-dto/user-response.dto'; +import { UserService } from 'src/domain/user/user.service'; +import { UserEntity, UserStatus } from 'src/infra/entities/user.entity'; +import { IAlbumRepository } from 'src/interfaces/album.repository'; +import { ICryptoRepository } from 'src/interfaces/crypto.repository'; +import { IJobRepository } from 'src/interfaces/job.repository'; +import { ILibraryRepository } from 'src/interfaces/library.repository'; +import { IStorageRepository } from 'src/interfaces/storage.repository'; +import { ISystemConfigRepository } from 'src/interfaces/system-config.repository'; +import { IUserRepository } from 'src/interfaces/user.repository'; +import { CacheControl, ImmichFileResponse } from 'src/utils'; +import { authStub } from 'test/fixtures/auth.stub'; +import { systemConfigStub } from 'test/fixtures/system-config.stub'; +import { userStub } from 'test/fixtures/user.stub'; +import { newAlbumRepositoryMock } from 'test/repositories/album.repository.mock'; +import { newCryptoRepositoryMock } from 'test/repositories/crypto.repository.mock'; +import { newJobRepositoryMock } from 'test/repositories/job.repository.mock'; +import { newLibraryRepositoryMock } from 'test/repositories/library.repository.mock'; +import { newStorageRepositoryMock } from 'test/repositories/storage.repository.mock'; +import { newSystemConfigRepositoryMock } from 'test/repositories/system-config.repository.mock'; +import { newUserRepositoryMock } from 'test/repositories/user.repository.mock'; const makeDeletedAt = (daysAgo: number) => { const deletedAt = new Date(); diff --git a/server/src/domain/user/user.service.ts b/server/src/domain/user/user.service.ts index a1db1fb04..3b2bbf370 100644 --- a/server/src/domain/user/user.service.ts +++ b/server/src/domain/user/user.service.ts @@ -1,27 +1,30 @@ -import { UserEntity, UserStatus } from '@app/infra/entities'; -import { ImmichLogger } from '@app/infra/logger'; import { BadRequestException, ForbiddenException, Inject, Injectable, NotFoundException } from '@nestjs/common'; import { DateTime } from 'luxon'; import { randomBytes } from 'node:crypto'; -import { AuthDto } from '../auth'; -import { CacheControl, ImmichFileResponse } from '../domain.util'; -import { IEntityJob, JobName } from '../job'; +import { StorageCore, StorageFolder } from 'src/cores/storage.core'; +import { SystemConfigCore } from 'src/cores/system-config.core'; +import { UserCore } from 'src/cores/user.core'; +import { AuthDto } from 'src/domain/auth/auth.dto'; +import { JobName } from 'src/domain/job/job.constants'; +import { IEntityJob } from 'src/domain/job/job.interface'; +import { CreateUserDto } from 'src/domain/user/dto/create-user.dto'; +import { DeleteUserDto } from 'src/domain/user/dto/delete-user.dto'; +import { UpdateUserDto } from 'src/domain/user/dto/update-user.dto'; import { - IAlbumRepository, - ICryptoRepository, - IJobRepository, - ILibraryRepository, - IStorageRepository, - ISystemConfigRepository, - IUserRepository, - JobStatus, - UserFindOptions, -} from '../repositories'; -import { StorageCore, StorageFolder } from '../storage'; -import { SystemConfigCore } from '../system-config/system-config.core'; -import { CreateUserDto, DeleteUserDto, UpdateUserDto } from './dto'; -import { CreateProfileImageResponseDto, UserResponseDto, mapCreateProfileImageResponse, mapUser } from './response-dto'; -import { UserCore } from './user.core'; + CreateProfileImageResponseDto, + mapCreateProfileImageResponse, +} from 'src/domain/user/response-dto/create-profile-image-response.dto'; +import { UserResponseDto, mapUser } from 'src/domain/user/response-dto/user-response.dto'; +import { UserEntity, UserStatus } from 'src/infra/entities/user.entity'; +import { ImmichLogger } from 'src/infra/logger'; +import { IAlbumRepository } from 'src/interfaces/album.repository'; +import { ICryptoRepository } from 'src/interfaces/crypto.repository'; +import { IJobRepository, JobStatus } from 'src/interfaces/job.repository'; +import { ILibraryRepository } from 'src/interfaces/library.repository'; +import { IStorageRepository } from 'src/interfaces/storage.repository'; +import { ISystemConfigRepository } from 'src/interfaces/system-config.repository'; +import { IUserRepository, UserFindOptions } from 'src/interfaces/user.repository'; +import { CacheControl, ImmichFileResponse } from 'src/utils'; @Injectable() export class UserService { diff --git a/server/src/immich-admin/app.module.ts b/server/src/immich-admin/app.module.ts index b350aec83..b491fbb55 100644 --- a/server/src/immich-admin/app.module.ts +++ b/server/src/immich-admin/app.module.ts @@ -1,10 +1,10 @@ -import { DomainModule } from '@app/domain'; -import { InfraModule } from '@app/infra'; import { Module } from '@nestjs/common'; -import { ListUsersCommand } from './commands/list-users.command'; -import { DisableOAuthLogin, EnableOAuthLogin } from './commands/oauth-login'; -import { DisablePasswordLoginCommand, EnablePasswordLoginCommand } from './commands/password-login'; -import { PromptPasswordQuestions, ResetAdminPasswordCommand } from './commands/reset-admin-password.command'; +import { ListUsersCommand } from 'src/commands/list-users.command'; +import { DisableOAuthLogin, EnableOAuthLogin } from 'src/commands/oauth-login'; +import { DisablePasswordLoginCommand, EnablePasswordLoginCommand } from 'src/commands/password-login'; +import { PromptPasswordQuestions, ResetAdminPasswordCommand } from 'src/commands/reset-admin-password.command'; +import { DomainModule } from 'src/domain/domain.module'; +import { InfraModule } from 'src/infra/infra.module'; @Module({ imports: [InfraModule, DomainModule], diff --git a/server/src/immich-admin/constants.ts b/server/src/immich-admin/constants.ts deleted file mode 100644 index 44fbfdf77..000000000 --- a/server/src/immich-admin/constants.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { AuthDto } from '@app/domain'; -import { UserEntity } from '@app/infra/entities'; - -export const CLI_USER: AuthDto = { - user: { - id: 'cli', - email: 'cli@immich.app', - isAdmin: true, - } as UserEntity, -}; diff --git a/server/src/immich-admin/main.ts b/server/src/immich-admin/main.ts index f14aac20f..0b569dd9a 100755 --- a/server/src/immich-admin/main.ts +++ b/server/src/immich-admin/main.ts @@ -1,6 +1,6 @@ -import { LogLevel } from '@app/infra/entities'; import { CommandFactory } from 'nest-commander'; -import { AppModule } from './app.module'; +import { AppModule } from 'src/immich-admin/app.module'; +import { LogLevel } from 'src/infra/entities/system-config.entity'; export async function bootstrap() { process.env.LOG_LEVEL = LogLevel.WARN; diff --git a/server/src/immich/api-v1/asset/asset-repository.ts b/server/src/immich/api-v1/asset/asset-repository.ts index 18feb65dc..12a627875 100644 --- a/server/src/immich/api-v1/asset/asset-repository.ts +++ b/server/src/immich/api-v1/asset/asset-repository.ts @@ -1,14 +1,15 @@ -import { AssetEntity, ExifEntity } from '@app/infra/entities'; -import { OptionalBetween } from '@app/infra/infra.utils'; import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; +import { AssetSearchDto } from 'src/immich/api-v1/asset/dto/asset-search.dto'; +import { CheckExistingAssetsDto } from 'src/immich/api-v1/asset/dto/check-existing-assets.dto'; +import { SearchPropertiesDto } from 'src/immich/api-v1/asset/dto/search-properties.dto'; +import { CuratedLocationsResponseDto } from 'src/immich/api-v1/asset/response-dto/curated-locations-response.dto'; +import { CuratedObjectsResponseDto } from 'src/immich/api-v1/asset/response-dto/curated-objects-response.dto'; +import { AssetEntity } from 'src/infra/entities/asset.entity'; +import { ExifEntity } from 'src/infra/entities/exif.entity'; +import { OptionalBetween } from 'src/infra/infra.utils'; import { In } from 'typeorm/find-options/operator/In.js'; import { Repository } from 'typeorm/repository/Repository.js'; -import { AssetSearchDto } from './dto/asset-search.dto'; -import { CheckExistingAssetsDto } from './dto/check-existing-assets.dto'; -import { SearchPropertiesDto } from './dto/search-properties.dto'; -import { CuratedLocationsResponseDto } from './response-dto/curated-locations-response.dto'; -import { CuratedObjectsResponseDto } from './response-dto/curated-objects-response.dto'; export interface AssetCheck { id: string; checksum: Buffer; diff --git a/server/src/immich/api-v1/asset/asset.controller.ts b/server/src/immich/api-v1/asset/asset.controller.ts index 37b561490..d29f61fdd 100644 --- a/server/src/immich/api-v1/asset/asset.controller.ts +++ b/server/src/immich/api-v1/asset/asset.controller.ts @@ -1,4 +1,3 @@ -import { AssetResponseDto, AuthDto } from '@app/domain'; import { Body, Controller, @@ -16,23 +15,24 @@ import { } from '@nestjs/common'; import { ApiBody, ApiConsumes, ApiHeader, ApiTags } from '@nestjs/swagger'; import { NextFunction, Response } from 'express'; -import { Auth, Authenticated, FileResponse, SharedLinkRoute } from '../../app.guard'; -import { sendFile } from '../../app.utils'; -import { UUIDParamDto } from '../../controllers/dto/uuid-param.dto'; -import { FileUploadInterceptor, ImmichFile, Route, mapToUploadFile } from '../../interceptors'; -import FileNotEmptyValidator from '../validation/file-not-empty-validator'; -import { AssetService as AssetServiceV1 } from './asset.service'; -import { AssetBulkUploadCheckDto } from './dto/asset-check.dto'; -import { AssetSearchDto } from './dto/asset-search.dto'; -import { CheckExistingAssetsDto } from './dto/check-existing-assets.dto'; -import { CreateAssetDto } from './dto/create-asset.dto'; -import { GetAssetThumbnailDto } from './dto/get-asset-thumbnail.dto'; -import { ServeFileDto } from './dto/serve-file.dto'; -import { AssetBulkUploadCheckResponseDto } from './response-dto/asset-check-response.dto'; -import { AssetFileUploadResponseDto } from './response-dto/asset-file-upload-response.dto'; -import { CheckExistingAssetsResponseDto } from './response-dto/check-existing-assets-response.dto'; -import { CuratedLocationsResponseDto } from './response-dto/curated-locations-response.dto'; -import { CuratedObjectsResponseDto } from './response-dto/curated-objects-response.dto'; +import { AssetResponseDto } from 'src/domain/asset/response-dto/asset-response.dto'; +import { AuthDto } from 'src/domain/auth/auth.dto'; +import { AssetService as AssetServiceV1 } from 'src/immich/api-v1/asset/asset.service'; +import { AssetBulkUploadCheckDto } from 'src/immich/api-v1/asset/dto/asset-check.dto'; +import { AssetSearchDto } from 'src/immich/api-v1/asset/dto/asset-search.dto'; +import { CheckExistingAssetsDto } from 'src/immich/api-v1/asset/dto/check-existing-assets.dto'; +import { CreateAssetDto } from 'src/immich/api-v1/asset/dto/create-asset.dto'; +import { GetAssetThumbnailDto } from 'src/immich/api-v1/asset/dto/get-asset-thumbnail.dto'; +import { ServeFileDto } from 'src/immich/api-v1/asset/dto/serve-file.dto'; +import { AssetBulkUploadCheckResponseDto } from 'src/immich/api-v1/asset/response-dto/asset-check-response.dto'; +import { AssetFileUploadResponseDto } from 'src/immich/api-v1/asset/response-dto/asset-file-upload-response.dto'; +import { CheckExistingAssetsResponseDto } from 'src/immich/api-v1/asset/response-dto/check-existing-assets-response.dto'; +import { CuratedLocationsResponseDto } from 'src/immich/api-v1/asset/response-dto/curated-locations-response.dto'; +import { CuratedObjectsResponseDto } from 'src/immich/api-v1/asset/response-dto/curated-objects-response.dto'; +import { sendFile } from 'src/immich/app.utils'; +import { Auth, Authenticated, FileResponse, SharedLinkRoute } from 'src/middleware/auth.guard'; +import { FileUploadInterceptor, ImmichFile, Route, mapToUploadFile } from 'src/middleware/file-upload.interceptor'; +import { FileNotEmptyValidator, UUIDParamDto } from 'src/validation'; interface UploadFiles { assetData: ImmichFile[]; diff --git a/server/src/immich/api-v1/asset/asset.service.spec.ts b/server/src/immich/api-v1/asset/asset.service.spec.ts index 9f0aa371e..37f1a540f 100644 --- a/server/src/immich/api-v1/asset/asset.service.spec.ts +++ b/server/src/immich/api-v1/asset/asset.service.spec.ts @@ -1,30 +1,26 @@ -import { - IAssetRepository, - IJobRepository, - ILibraryRepository, - IStorageRepository, - IUserRepository, - JobName, -} from '@app/domain'; -import { ASSET_CHECKSUM_CONSTRAINT, AssetEntity, AssetType, ExifEntity } from '@app/infra/entities'; -import { - IAccessRepositoryMock, - assetStub, - authStub, - fileStub, - newAccessRepositoryMock, - newAssetRepositoryMock, - newJobRepositoryMock, - newLibraryRepositoryMock, - newStorageRepositoryMock, - newUserRepositoryMock, -} from '@test'; import { when } from 'jest-when'; +import { JobName } from 'src/domain/job/job.constants'; +import { IAssetRepositoryV1 } from 'src/immich/api-v1/asset/asset-repository'; +import { AssetService } from 'src/immich/api-v1/asset/asset.service'; +import { CreateAssetDto } from 'src/immich/api-v1/asset/dto/create-asset.dto'; +import { AssetRejectReason, AssetUploadAction } from 'src/immich/api-v1/asset/response-dto/asset-check-response.dto'; +import { ASSET_CHECKSUM_CONSTRAINT, AssetEntity, AssetType } from 'src/infra/entities/asset.entity'; +import { ExifEntity } from 'src/infra/entities/exif.entity'; +import { IAssetRepository } from 'src/interfaces/asset.repository'; +import { IJobRepository } from 'src/interfaces/job.repository'; +import { ILibraryRepository } from 'src/interfaces/library.repository'; +import { IStorageRepository } from 'src/interfaces/storage.repository'; +import { IUserRepository } from 'src/interfaces/user.repository'; +import { assetStub } from 'test/fixtures/asset.stub'; +import { authStub } from 'test/fixtures/auth.stub'; +import { fileStub } from 'test/fixtures/file.stub'; +import { IAccessRepositoryMock, newAccessRepositoryMock } from 'test/repositories/access.repository.mock'; +import { newAssetRepositoryMock } from 'test/repositories/asset.repository.mock'; +import { newJobRepositoryMock } from 'test/repositories/job.repository.mock'; +import { newLibraryRepositoryMock } from 'test/repositories/library.repository.mock'; +import { newStorageRepositoryMock } from 'test/repositories/storage.repository.mock'; +import { newUserRepositoryMock } from 'test/repositories/user.repository.mock'; import { QueryFailedError } from 'typeorm'; -import { IAssetRepositoryV1 } from './asset-repository'; -import { AssetService } from './asset.service'; -import { CreateAssetDto } from './dto/create-asset.dto'; -import { AssetRejectReason, AssetUploadAction } from './response-dto/asset-check-response.dto'; const _getCreateAssetDto = (): CreateAssetDto => { const createAssetDto = new CreateAssetDto(); diff --git a/server/src/immich/api-v1/asset/asset.service.ts b/server/src/immich/api-v1/asset/asset.service.ts index 821a7de82..879128077 100644 --- a/server/src/immich/api-v1/asset/asset.service.ts +++ b/server/src/immich/api-v1/asset/asset.service.ts @@ -1,24 +1,3 @@ -import { - AccessCore, - AssetResponseDto, - AuthDto, - CacheControl, - IAccessRepository, - IAssetRepository, - IJobRepository, - ILibraryRepository, - IStorageRepository, - IUserRepository, - ImmichFileResponse, - JobName, - Permission, - UploadFile, - getLivePhotoMotionFilename, - mapAsset, - mimeTypes, -} from '@app/domain'; -import { ASSET_CHECKSUM_CONSTRAINT, AssetEntity, AssetType, LibraryType } from '@app/infra/entities'; -import { ImmichLogger } from '@app/infra/logger'; import { BadRequestException, Inject, @@ -26,23 +5,39 @@ import { InternalServerErrorException, NotFoundException, } from '@nestjs/common'; -import { QueryFailedError } from 'typeorm'; -import { IAssetRepositoryV1 } from './asset-repository'; -import { AssetBulkUploadCheckDto } from './dto/asset-check.dto'; -import { AssetSearchDto } from './dto/asset-search.dto'; -import { CheckExistingAssetsDto } from './dto/check-existing-assets.dto'; -import { CreateAssetDto } from './dto/create-asset.dto'; -import { GetAssetThumbnailDto, GetAssetThumbnailFormatEnum } from './dto/get-asset-thumbnail.dto'; -import { ServeFileDto } from './dto/serve-file.dto'; +import { AccessCore, Permission } from 'src/cores/access.core'; +import { UploadFile } from 'src/domain/asset/asset.service'; +import { AssetResponseDto, mapAsset } from 'src/domain/asset/response-dto/asset-response.dto'; +import { AuthDto } from 'src/domain/auth/auth.dto'; +import { mimeTypes } from 'src/domain/domain.constant'; +import { JobName } from 'src/domain/job/job.constants'; +import { IAssetRepositoryV1 } from 'src/immich/api-v1/asset/asset-repository'; +import { AssetBulkUploadCheckDto } from 'src/immich/api-v1/asset/dto/asset-check.dto'; +import { AssetSearchDto } from 'src/immich/api-v1/asset/dto/asset-search.dto'; +import { CheckExistingAssetsDto } from 'src/immich/api-v1/asset/dto/check-existing-assets.dto'; +import { CreateAssetDto } from 'src/immich/api-v1/asset/dto/create-asset.dto'; +import { GetAssetThumbnailDto, GetAssetThumbnailFormatEnum } from 'src/immich/api-v1/asset/dto/get-asset-thumbnail.dto'; +import { ServeFileDto } from 'src/immich/api-v1/asset/dto/serve-file.dto'; import { AssetBulkUploadCheckResponseDto, AssetRejectReason, AssetUploadAction, -} from './response-dto/asset-check-response.dto'; -import { AssetFileUploadResponseDto } from './response-dto/asset-file-upload-response.dto'; -import { CheckExistingAssetsResponseDto } from './response-dto/check-existing-assets-response.dto'; -import { CuratedLocationsResponseDto } from './response-dto/curated-locations-response.dto'; -import { CuratedObjectsResponseDto } from './response-dto/curated-objects-response.dto'; +} from 'src/immich/api-v1/asset/response-dto/asset-check-response.dto'; +import { AssetFileUploadResponseDto } from 'src/immich/api-v1/asset/response-dto/asset-file-upload-response.dto'; +import { CheckExistingAssetsResponseDto } from 'src/immich/api-v1/asset/response-dto/check-existing-assets-response.dto'; +import { CuratedLocationsResponseDto } from 'src/immich/api-v1/asset/response-dto/curated-locations-response.dto'; +import { CuratedObjectsResponseDto } from 'src/immich/api-v1/asset/response-dto/curated-objects-response.dto'; +import { ASSET_CHECKSUM_CONSTRAINT, AssetEntity, AssetType } from 'src/infra/entities/asset.entity'; +import { LibraryType } from 'src/infra/entities/library.entity'; +import { ImmichLogger } from 'src/infra/logger'; +import { IAccessRepository } from 'src/interfaces/access.repository'; +import { IAssetRepository } from 'src/interfaces/asset.repository'; +import { IJobRepository } from 'src/interfaces/job.repository'; +import { ILibraryRepository } from 'src/interfaces/library.repository'; +import { IStorageRepository } from 'src/interfaces/storage.repository'; +import { IUserRepository } from 'src/interfaces/user.repository'; +import { CacheControl, ImmichFileResponse, getLivePhotoMotionFilename } from 'src/utils'; +import { QueryFailedError } from 'typeorm'; @Injectable() export class AssetService { diff --git a/server/src/immich/api-v1/asset/dto/asset-search.dto.ts b/server/src/immich/api-v1/asset/dto/asset-search.dto.ts index 719018488..97d0aa1fa 100644 --- a/server/src/immich/api-v1/asset/dto/asset-search.dto.ts +++ b/server/src/immich/api-v1/asset/dto/asset-search.dto.ts @@ -1,7 +1,7 @@ -import { Optional, ValidateBoolean, ValidateDate } from '@app/domain'; import { ApiProperty } from '@nestjs/swagger'; import { Type } from 'class-transformer'; import { IsInt, IsUUID } from 'class-validator'; +import { Optional, ValidateBoolean, ValidateDate } from 'src/validation'; export class AssetSearchDto { @ValidateBoolean({ optional: true }) diff --git a/server/src/immich/api-v1/asset/dto/check-existing-assets.dto.spec.ts b/server/src/immich/api-v1/asset/dto/check-existing-assets.dto.spec.ts index dd0eca03f..a634ba42e 100644 --- a/server/src/immich/api-v1/asset/dto/check-existing-assets.dto.spec.ts +++ b/server/src/immich/api-v1/asset/dto/check-existing-assets.dto.spec.ts @@ -1,6 +1,6 @@ import { plainToInstance } from 'class-transformer'; import { validateSync } from 'class-validator'; -import { CheckExistingAssetsDto } from './check-existing-assets.dto'; +import { CheckExistingAssetsDto } from 'src/immich/api-v1/asset/dto/check-existing-assets.dto'; describe('CheckExistingAssetsDto', () => { it('should fail with an empty list', () => { diff --git a/server/src/immich/api-v1/asset/dto/create-asset.dto.ts b/server/src/immich/api-v1/asset/dto/create-asset.dto.ts index 1b140d69f..d16a9c05c 100644 --- a/server/src/immich/api-v1/asset/dto/create-asset.dto.ts +++ b/server/src/immich/api-v1/asset/dto/create-asset.dto.ts @@ -1,6 +1,7 @@ -import { Optional, UploadFieldName, ValidateBoolean, ValidateDate, ValidateUUID } from '@app/domain'; import { ApiProperty } from '@nestjs/swagger'; import { IsNotEmpty, IsString } from 'class-validator'; +import { UploadFieldName } from 'src/domain/asset/asset.service'; +import { Optional, ValidateBoolean, ValidateDate, ValidateUUID } from 'src/validation'; export class CreateAssetDto { @ValidateUUID({ optional: true }) diff --git a/server/src/immich/api-v1/asset/dto/get-asset-thumbnail.dto.ts b/server/src/immich/api-v1/asset/dto/get-asset-thumbnail.dto.ts index da5661e0d..6c709eb02 100644 --- a/server/src/immich/api-v1/asset/dto/get-asset-thumbnail.dto.ts +++ b/server/src/immich/api-v1/asset/dto/get-asset-thumbnail.dto.ts @@ -1,6 +1,6 @@ -import { Optional } from '@app/domain'; import { ApiProperty } from '@nestjs/swagger'; import { IsEnum } from 'class-validator'; +import { Optional } from 'src/validation'; export enum GetAssetThumbnailFormatEnum { JPEG = 'JPEG', diff --git a/server/src/immich/api-v1/asset/dto/serve-file.dto.ts b/server/src/immich/api-v1/asset/dto/serve-file.dto.ts index 72e228601..8b3147fc2 100644 --- a/server/src/immich/api-v1/asset/dto/serve-file.dto.ts +++ b/server/src/immich/api-v1/asset/dto/serve-file.dto.ts @@ -1,5 +1,5 @@ -import { ValidateBoolean } from '@app/domain'; import { ApiProperty } from '@nestjs/swagger'; +import { ValidateBoolean } from 'src/validation'; export class ServeFileDto { @ValidateBoolean({ optional: true }) diff --git a/server/src/immich/api-v1/validation/file-not-empty-validator.ts b/server/src/immich/api-v1/validation/file-not-empty-validator.ts deleted file mode 100644 index 21f93a952..000000000 --- a/server/src/immich/api-v1/validation/file-not-empty-validator.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { FileValidator, Injectable } from '@nestjs/common'; - -@Injectable() -export default class FileNotEmptyValidator extends FileValidator { - constructor(private requiredFields: string[]) { - super({}); - this.requiredFields = requiredFields; - } - - isValid(files?: any): boolean { - if (!files) { - return false; - } - - return this.requiredFields.every((field) => files[field]); - } - - buildErrorMessage(): string { - return `Field(s) ${this.requiredFields.join(', ')} should not be empty`; - } -} diff --git a/server/src/immich/api-v1/validation/parse-me-uuid-pipe.ts b/server/src/immich/api-v1/validation/parse-me-uuid-pipe.ts deleted file mode 100644 index 4329af011..000000000 --- a/server/src/immich/api-v1/validation/parse-me-uuid-pipe.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { ArgumentMetadata, Injectable, ParseUUIDPipe } from '@nestjs/common'; - -@Injectable() -export class ParseMeUUIDPipe extends ParseUUIDPipe { - async transform(value: string, metadata: ArgumentMetadata) { - if (value == 'me') { - return value; - } - return super.transform(value, metadata); - } -} diff --git a/server/src/immich/app.module.ts b/server/src/immich/app.module.ts index 662f45f7c..66edf2341 100644 --- a/server/src/immich/app.module.ts +++ b/server/src/immich/app.module.ts @@ -1,40 +1,39 @@ -import { DomainModule } from '@app/domain'; -import { InfraModule } from '@app/infra'; -import { AssetEntity, ExifEntity } from '@app/infra/entities'; import { Module, OnModuleInit, ValidationPipe } from '@nestjs/common'; import { APP_GUARD, APP_INTERCEPTOR, APP_PIPE } from '@nestjs/core'; import { ScheduleModule } from '@nestjs/schedule'; import { TypeOrmModule } from '@nestjs/typeorm'; -import { AssetRepositoryV1, IAssetRepositoryV1 } from './api-v1/asset/asset-repository'; -import { AssetController as AssetControllerV1 } from './api-v1/asset/asset.controller'; -import { AssetService } from './api-v1/asset/asset.service'; -import { AppGuard } from './app.guard'; -import { AppService } from './app.service'; -import { - APIKeyController, - ActivityController, - AlbumController, - AppController, - AssetController, - AssetsController, - AuditController, - AuthController, - DownloadController, - FaceController, - JobController, - LibraryController, - OAuthController, - PartnerController, - PersonController, - SearchController, - ServerInfoController, - SharedLinkController, - SystemConfigController, - TagController, - TrashController, - UserController, -} from './controllers'; -import { ErrorInterceptor, FileUploadInterceptor } from './interceptors'; +import { ActivityController } from 'src/controllers/activity.controller'; +import { AlbumController } from 'src/controllers/album.controller'; +import { APIKeyController } from 'src/controllers/api-key.controller'; +import { AppController } from 'src/controllers/app.controller'; +import { AssetController, AssetsController } from 'src/controllers/asset.controller'; +import { AuditController } from 'src/controllers/audit.controller'; +import { AuthController } from 'src/controllers/auth.controller'; +import { DownloadController } from 'src/controllers/download.controller'; +import { FaceController } from 'src/controllers/face.controller'; +import { JobController } from 'src/controllers/job.controller'; +import { LibraryController } from 'src/controllers/library.controller'; +import { OAuthController } from 'src/controllers/oauth.controller'; +import { PartnerController } from 'src/controllers/partner.controller'; +import { PersonController } from 'src/controllers/person.controller'; +import { SearchController } from 'src/controllers/search.controller'; +import { ServerInfoController } from 'src/controllers/server-info.controller'; +import { SharedLinkController } from 'src/controllers/shared-link.controller'; +import { SystemConfigController } from 'src/controllers/system-config.controller'; +import { TagController } from 'src/controllers/tag.controller'; +import { TrashController } from 'src/controllers/trash.controller'; +import { UserController } from 'src/controllers/user.controller'; +import { DomainModule } from 'src/domain/domain.module'; +import { AssetRepositoryV1, IAssetRepositoryV1 } from 'src/immich/api-v1/asset/asset-repository'; +import { AssetController as AssetControllerV1 } from 'src/immich/api-v1/asset/asset.controller'; +import { AssetService as AssetServiceV1 } from 'src/immich/api-v1/asset/asset.service'; +import { AppService } from 'src/immich/app.service'; +import { AssetEntity } from 'src/infra/entities/asset.entity'; +import { ExifEntity } from 'src/infra/entities/exif.entity'; +import { InfraModule } from 'src/infra/infra.module'; +import { AuthGuard } from 'src/middleware/auth.guard'; +import { ErrorInterceptor } from 'src/middleware/error.interceptor'; +import { FileUploadInterceptor } from 'src/middleware/file-upload.interceptor'; @Module({ imports: [ @@ -72,10 +71,10 @@ import { ErrorInterceptor, FileUploadInterceptor } from './interceptors'; providers: [ { provide: APP_PIPE, useValue: new ValidationPipe({ transform: true, whitelist: true }) }, { provide: APP_INTERCEPTOR, useClass: ErrorInterceptor }, - { provide: APP_GUARD, useClass: AppGuard }, + { provide: APP_GUARD, useClass: AuthGuard }, { provide: IAssetRepositoryV1, useClass: AssetRepositoryV1 }, AppService, - AssetService, + AssetServiceV1, FileUploadInterceptor, ], }) diff --git a/server/src/immich/app.service.ts b/server/src/immich/app.service.ts index adfb9d878..f7f1664be 100644 --- a/server/src/immich/app.service.ts +++ b/server/src/immich/app.service.ts @@ -1,21 +1,18 @@ -import { - AuthService, - DatabaseService, - JobService, - ONE_HOUR, - OpenGraphTags, - ServerInfoService, - SharedLinkService, - StorageService, - SystemConfigService, - WEB_ROOT, -} from '@app/domain'; -import { ImmichLogger } from '@app/infra/logger'; import { Injectable } from '@nestjs/common'; import { Cron, CronExpression, Interval } from '@nestjs/schedule'; import { NextFunction, Request, Response } from 'express'; import { readFileSync } from 'node:fs'; import { join } from 'node:path'; +import { AuthService } from 'src/domain/auth/auth.service'; +import { DatabaseService } from 'src/domain/database/database.service'; +import { ONE_HOUR, WEB_ROOT } from 'src/domain/domain.constant'; +import { JobService } from 'src/domain/job/job.service'; +import { ServerInfoService } from 'src/domain/server-info/server-info.service'; +import { SharedLinkService } from 'src/domain/shared-link/shared-link.service'; +import { StorageService } from 'src/domain/storage/storage.service'; +import { SystemConfigService } from 'src/domain/system-config/system-config.service'; +import { ImmichLogger } from 'src/infra/logger'; +import { OpenGraphTags } from 'src/utils'; const render = (index: string, meta: OpenGraphTags) => { const tags = ` diff --git a/server/src/immich/app.utils.ts b/server/src/immich/app.utils.ts index 26aca2a9b..f0e4ba661 100644 --- a/server/src/immich/app.utils.ts +++ b/server/src/immich/app.utils.ts @@ -1,14 +1,3 @@ -import { - CacheControl, - IMMICH_ACCESS_COOKIE, - IMMICH_API_KEY_HEADER, - IMMICH_API_KEY_NAME, - ImmichFileResponse, - ImmichReadStream, - isConnectionAborted, - serverVersion, -} from '@app/domain'; -import { ImmichLogger } from '@app/infra/logger'; import { HttpException, INestApplication, StreamableFile } from '@nestjs/common'; import { DocumentBuilder, @@ -24,7 +13,12 @@ import { writeFileSync } from 'node:fs'; import { access, constants } from 'node:fs/promises'; import path, { isAbsolute } from 'node:path'; import { promisify } from 'node:util'; -import { Metadata } from './app.guard'; +import { IMMICH_ACCESS_COOKIE, IMMICH_API_KEY_HEADER, IMMICH_API_KEY_NAME } from 'src/domain/auth/auth.constant'; +import { serverVersion } from 'src/domain/domain.constant'; +import { ImmichLogger } from 'src/infra/logger'; +import { ImmichReadStream } from 'src/interfaces/storage.repository'; +import { Metadata } from 'src/middleware/auth.guard'; +import { CacheControl, ImmichFileResponse, isConnectionAborted } from 'src/utils'; type SendFile = Parameters; type SendFileOptions = SendFile[1]; diff --git a/server/src/immich/controllers/dto/uuid-param.dto.ts b/server/src/immich/controllers/dto/uuid-param.dto.ts deleted file mode 100644 index 6e1b5a36c..000000000 --- a/server/src/immich/controllers/dto/uuid-param.dto.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { ApiProperty } from '@nestjs/swagger'; -import { IsNotEmpty, IsUUID } from 'class-validator'; - -export class UUIDParamDto { - @IsNotEmpty() - @IsUUID('4') - @ApiProperty({ format: 'uuid' }) - id!: string; -} diff --git a/server/src/immich/controllers/index.ts b/server/src/immich/controllers/index.ts deleted file mode 100644 index f4e473091..000000000 --- a/server/src/immich/controllers/index.ts +++ /dev/null @@ -1,21 +0,0 @@ -export * from './activity.controller'; -export * from './album.controller'; -export * from './api-key.controller'; -export * from './app.controller'; -export * from './asset.controller'; -export * from './audit.controller'; -export * from './auth.controller'; -export * from './download.controller'; -export * from './face.controller'; -export * from './job.controller'; -export * from './library.controller'; -export * from './oauth.controller'; -export * from './partner.controller'; -export * from './person.controller'; -export * from './search.controller'; -export * from './server-info.controller'; -export * from './shared-link.controller'; -export * from './system-config.controller'; -export * from './tag.controller'; -export * from './trash.controller'; -export * from './user.controller'; diff --git a/server/src/immich/index.ts b/server/src/immich/index.ts deleted file mode 100644 index c590fa83e..000000000 --- a/server/src/immich/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './app.module'; -export * from './controllers'; diff --git a/server/src/immich/interceptors/index.ts b/server/src/immich/interceptors/index.ts deleted file mode 100644 index 5811b3232..000000000 --- a/server/src/immich/interceptors/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './error.interceptor'; -export * from './file-upload.interceptor'; diff --git a/server/src/immich/main.ts b/server/src/immich/main.ts index 60e323e6a..310ac9302 100644 --- a/server/src/immich/main.ts +++ b/server/src/immich/main.ts @@ -1,16 +1,17 @@ -import { WEB_ROOT, envName, isDev, serverVersion } from '@app/domain'; -import { WebSocketAdapter, excludePaths } from '@app/infra'; -import { otelSDK } from '@app/infra/instrumentation'; -import { ImmichLogger } from '@app/infra/logger'; import { NestFactory } from '@nestjs/core'; import { NestExpressApplication } from '@nestjs/platform-express'; import { json } from 'body-parser'; import cookieParser from 'cookie-parser'; import { existsSync } from 'node:fs'; import sirv from 'sirv'; -import { AppModule } from './app.module'; -import { AppService } from './app.service'; -import { useSwagger } from './app.utils'; +import { excludePaths } from 'src/config'; +import { WEB_ROOT, envName, isDev, serverVersion } from 'src/domain/domain.constant'; +import { AppModule } from 'src/immich/app.module'; +import { AppService } from 'src/immich/app.service'; +import { useSwagger } from 'src/immich/app.utils'; +import { otelSDK } from 'src/infra/instrumentation'; +import { ImmichLogger } from 'src/infra/logger'; +import { WebSocketAdapter } from 'src/infra/websocket.adapter'; const logger = new ImmichLogger('ImmichServer'); const port = Number(process.env.SERVER_PORT) || 3001; diff --git a/server/src/infra/database.config.ts b/server/src/infra/database.config.ts index 773e79f8a..fd6286f1b 100644 --- a/server/src/infra/database.config.ts +++ b/server/src/infra/database.config.ts @@ -1,4 +1,4 @@ -import { DatabaseExtension } from '@app/domain/repositories/database.repository'; +import { DatabaseExtension } from 'src/interfaces/database.repository'; import { DataSource } from 'typeorm'; import { PostgresConnectionOptions } from 'typeorm/driver/postgres/PostgresConnectionOptions.js'; diff --git a/server/src/infra/entities/activity.entity.ts b/server/src/infra/entities/activity.entity.ts index 255a3a708..478595556 100644 --- a/server/src/infra/entities/activity.entity.ts +++ b/server/src/infra/entities/activity.entity.ts @@ -1,3 +1,6 @@ +import { AlbumEntity } from 'src/infra/entities/album.entity'; +import { AssetEntity } from 'src/infra/entities/asset.entity'; +import { UserEntity } from 'src/infra/entities/user.entity'; import { Check, Column, @@ -8,9 +11,6 @@ import { PrimaryGeneratedColumn, UpdateDateColumn, } from 'typeorm'; -import { AlbumEntity } from './album.entity'; -import { AssetEntity } from './asset.entity'; -import { UserEntity } from './user.entity'; @Entity('activity') @Index('IDX_activity_like', ['assetId', 'userId', 'albumId'], { unique: true, where: '("isLiked" = true)' }) diff --git a/server/src/infra/entities/album.entity.ts b/server/src/infra/entities/album.entity.ts index daa8fcbc3..ab15d401d 100644 --- a/server/src/infra/entities/album.entity.ts +++ b/server/src/infra/entities/album.entity.ts @@ -1,3 +1,6 @@ +import { AssetEntity } from 'src/infra/entities/asset.entity'; +import { SharedLinkEntity } from 'src/infra/entities/shared-link.entity'; +import { UserEntity } from 'src/infra/entities/user.entity'; import { Column, CreateDateColumn, @@ -10,9 +13,6 @@ import { PrimaryGeneratedColumn, UpdateDateColumn, } from 'typeorm'; -import { AssetEntity } from './asset.entity'; -import { SharedLinkEntity } from './shared-link.entity'; -import { UserEntity } from './user.entity'; // ran into issues when importing the enum from `asset.dto.ts` export enum AssetOrder { diff --git a/server/src/infra/entities/api-key.entity.ts b/server/src/infra/entities/api-key.entity.ts index 0c8252fe4..0d1893398 100644 --- a/server/src/infra/entities/api-key.entity.ts +++ b/server/src/infra/entities/api-key.entity.ts @@ -1,5 +1,5 @@ +import { UserEntity } from 'src/infra/entities/user.entity'; import { Column, CreateDateColumn, Entity, ManyToOne, PrimaryGeneratedColumn, UpdateDateColumn } from 'typeorm'; -import { UserEntity } from './user.entity'; @Entity('api_keys') export class APIKeyEntity { diff --git a/server/src/infra/entities/asset-face.entity.ts b/server/src/infra/entities/asset-face.entity.ts index 1561f67d0..afd2a263a 100644 --- a/server/src/infra/entities/asset-face.entity.ts +++ b/server/src/infra/entities/asset-face.entity.ts @@ -1,6 +1,6 @@ +import { AssetEntity } from 'src/infra/entities/asset.entity'; +import { PersonEntity } from 'src/infra/entities/person.entity'; import { Column, Entity, Index, ManyToOne, PrimaryGeneratedColumn } from 'typeorm'; -import { AssetEntity } from './asset.entity'; -import { PersonEntity } from './person.entity'; @Entity('asset_faces', { synchronize: false }) @Index('IDX_asset_faces_assetId_personId', ['assetId', 'personId']) diff --git a/server/src/infra/entities/asset-job-status.entity.ts b/server/src/infra/entities/asset-job-status.entity.ts index f1965fbdb..22d98e367 100644 --- a/server/src/infra/entities/asset-job-status.entity.ts +++ b/server/src/infra/entities/asset-job-status.entity.ts @@ -1,5 +1,5 @@ +import { AssetEntity } from 'src/infra/entities/asset.entity'; import { Column, Entity, JoinColumn, OneToOne, PrimaryColumn } from 'typeorm'; -import { AssetEntity } from './asset.entity'; @Entity('asset_job_status') export class AssetJobStatusEntity { diff --git a/server/src/infra/entities/asset-stack.entity.ts b/server/src/infra/entities/asset-stack.entity.ts index d005fc0a5..372fac1e4 100644 --- a/server/src/infra/entities/asset-stack.entity.ts +++ b/server/src/infra/entities/asset-stack.entity.ts @@ -1,5 +1,5 @@ +import { AssetEntity } from 'src/infra/entities/asset.entity'; import { Column, Entity, JoinColumn, OneToMany, OneToOne, PrimaryGeneratedColumn } from 'typeorm'; -import { AssetEntity } from './asset.entity'; @Entity('asset_stack') export class AssetStackEntity { diff --git a/server/src/infra/entities/asset.entity.ts b/server/src/infra/entities/asset.entity.ts index 78a961757..e82bbfb00 100644 --- a/server/src/infra/entities/asset.entity.ts +++ b/server/src/infra/entities/asset.entity.ts @@ -1,3 +1,14 @@ +import { AlbumEntity } from 'src/infra/entities/album.entity'; +import { AssetFaceEntity } from 'src/infra/entities/asset-face.entity'; +import { AssetJobStatusEntity } from 'src/infra/entities/asset-job-status.entity'; +import { AssetStackEntity } from 'src/infra/entities/asset-stack.entity'; +import { ExifEntity } from 'src/infra/entities/exif.entity'; +import { LibraryEntity } from 'src/infra/entities/library.entity'; +import { SharedLinkEntity } from 'src/infra/entities/shared-link.entity'; +import { SmartInfoEntity } from 'src/infra/entities/smart-info.entity'; +import { SmartSearchEntity } from 'src/infra/entities/smart-search.entity'; +import { TagEntity } from 'src/infra/entities/tag.entity'; +import { UserEntity } from 'src/infra/entities/user.entity'; import { Column, CreateDateColumn, @@ -13,17 +24,6 @@ import { PrimaryGeneratedColumn, UpdateDateColumn, } from 'typeorm'; -import { AlbumEntity } from './album.entity'; -import { AssetFaceEntity } from './asset-face.entity'; -import { AssetJobStatusEntity } from './asset-job-status.entity'; -import { AssetStackEntity } from './asset-stack.entity'; -import { ExifEntity } from './exif.entity'; -import { LibraryEntity } from './library.entity'; -import { SharedLinkEntity } from './shared-link.entity'; -import { SmartInfoEntity } from './smart-info.entity'; -import { SmartSearchEntity } from './smart-search.entity'; -import { TagEntity } from './tag.entity'; -import { UserEntity } from './user.entity'; export const ASSET_CHECKSUM_CONSTRAINT = 'UQ_assets_owner_library_checksum'; diff --git a/server/src/infra/entities/exif.entity.ts b/server/src/infra/entities/exif.entity.ts index 639e24a50..8ed645d11 100644 --- a/server/src/infra/entities/exif.entity.ts +++ b/server/src/infra/entities/exif.entity.ts @@ -1,7 +1,7 @@ +import { AssetEntity } from 'src/infra/entities/asset.entity'; import { Index, JoinColumn, OneToOne, PrimaryColumn } from 'typeorm'; import { Column } from 'typeorm/decorator/columns/Column.js'; import { Entity } from 'typeorm/decorator/entity/Entity.js'; -import { AssetEntity } from './asset.entity'; @Entity('exif') export class ExifEntity { diff --git a/server/src/infra/entities/index.ts b/server/src/infra/entities/index.ts index af620790e..47fc3c7f1 100644 --- a/server/src/infra/entities/index.ts +++ b/server/src/infra/entities/index.ts @@ -1,48 +1,25 @@ -import { ActivityEntity } from './activity.entity'; -import { AlbumEntity } from './album.entity'; -import { APIKeyEntity } from './api-key.entity'; -import { AssetFaceEntity } from './asset-face.entity'; -import { AssetJobStatusEntity } from './asset-job-status.entity'; -import { AssetStackEntity } from './asset-stack.entity'; -import { AssetEntity } from './asset.entity'; -import { AuditEntity } from './audit.entity'; -import { ExifEntity } from './exif.entity'; -import { GeodataPlacesEntity } from './geodata-places.entity'; -import { LibraryEntity } from './library.entity'; -import { MoveEntity } from './move.entity'; -import { PartnerEntity } from './partner.entity'; -import { PersonEntity } from './person.entity'; -import { SharedLinkEntity } from './shared-link.entity'; -import { SmartInfoEntity } from './smart-info.entity'; -import { SmartSearchEntity } from './smart-search.entity'; -import { SystemConfigEntity } from './system-config.entity'; -import { SystemMetadataEntity } from './system-metadata.entity'; -import { TagEntity } from './tag.entity'; -import { UserTokenEntity } from './user-token.entity'; -import { UserEntity } from './user.entity'; - -export * from './activity.entity'; -export * from './album.entity'; -export * from './api-key.entity'; -export * from './asset-face.entity'; -export * from './asset-job-status.entity'; -export * from './asset-stack.entity'; -export * from './asset.entity'; -export * from './audit.entity'; -export * from './exif.entity'; -export * from './geodata-places.entity'; -export * from './library.entity'; -export * from './move.entity'; -export * from './partner.entity'; -export * from './person.entity'; -export * from './shared-link.entity'; -export * from './smart-info.entity'; -export * from './smart-search.entity'; -export * from './system-config.entity'; -export * from './system-metadata.entity'; -export * from './tag.entity'; -export * from './user-token.entity'; -export * from './user.entity'; +import { ActivityEntity } from 'src/infra/entities/activity.entity'; +import { AlbumEntity } from 'src/infra/entities/album.entity'; +import { APIKeyEntity } from 'src/infra/entities/api-key.entity'; +import { AssetFaceEntity } from 'src/infra/entities/asset-face.entity'; +import { AssetJobStatusEntity } from 'src/infra/entities/asset-job-status.entity'; +import { AssetStackEntity } from 'src/infra/entities/asset-stack.entity'; +import { AssetEntity } from 'src/infra/entities/asset.entity'; +import { AuditEntity } from 'src/infra/entities/audit.entity'; +import { ExifEntity } from 'src/infra/entities/exif.entity'; +import { GeodataPlacesEntity } from 'src/infra/entities/geodata-places.entity'; +import { LibraryEntity } from 'src/infra/entities/library.entity'; +import { MoveEntity } from 'src/infra/entities/move.entity'; +import { PartnerEntity } from 'src/infra/entities/partner.entity'; +import { PersonEntity } from 'src/infra/entities/person.entity'; +import { SharedLinkEntity } from 'src/infra/entities/shared-link.entity'; +import { SmartInfoEntity } from 'src/infra/entities/smart-info.entity'; +import { SmartSearchEntity } from 'src/infra/entities/smart-search.entity'; +import { SystemConfigEntity } from 'src/infra/entities/system-config.entity'; +import { SystemMetadataEntity } from 'src/infra/entities/system-metadata.entity'; +import { TagEntity } from 'src/infra/entities/tag.entity'; +import { UserTokenEntity } from 'src/infra/entities/user-token.entity'; +import { UserEntity } from 'src/infra/entities/user.entity'; export const databaseEntities = [ ActivityEntity, diff --git a/server/src/infra/entities/library.entity.ts b/server/src/infra/entities/library.entity.ts index bf5f444ab..f68a38095 100644 --- a/server/src/infra/entities/library.entity.ts +++ b/server/src/infra/entities/library.entity.ts @@ -1,3 +1,5 @@ +import { AssetEntity } from 'src/infra/entities/asset.entity'; +import { UserEntity } from 'src/infra/entities/user.entity'; import { Column, CreateDateColumn, @@ -9,8 +11,6 @@ import { PrimaryGeneratedColumn, UpdateDateColumn, } from 'typeorm'; -import { AssetEntity } from './asset.entity'; -import { UserEntity } from './user.entity'; @Entity('libraries') export class LibraryEntity { diff --git a/server/src/infra/entities/partner.entity.ts b/server/src/infra/entities/partner.entity.ts index 35d32e4c9..abf0400da 100644 --- a/server/src/infra/entities/partner.entity.ts +++ b/server/src/infra/entities/partner.entity.ts @@ -1,7 +1,6 @@ +import { UserEntity } from 'src/infra/entities/user.entity'; import { Column, CreateDateColumn, Entity, JoinColumn, ManyToOne, PrimaryColumn, UpdateDateColumn } from 'typeorm'; -import { UserEntity } from './user.entity'; - @Entity('partners') export class PartnerEntity { @PrimaryColumn('uuid') diff --git a/server/src/infra/entities/person.entity.ts b/server/src/infra/entities/person.entity.ts index ecba45dd2..046ac9d40 100644 --- a/server/src/infra/entities/person.entity.ts +++ b/server/src/infra/entities/person.entity.ts @@ -1,3 +1,5 @@ +import { AssetFaceEntity } from 'src/infra/entities/asset-face.entity'; +import { UserEntity } from 'src/infra/entities/user.entity'; import { Check, Column, @@ -8,8 +10,6 @@ import { PrimaryGeneratedColumn, UpdateDateColumn, } from 'typeorm'; -import { AssetFaceEntity } from './asset-face.entity'; -import { UserEntity } from './user.entity'; @Entity('person') @Check(`"birthDate" <= CURRENT_DATE`) diff --git a/server/src/infra/entities/shared-link.entity.ts b/server/src/infra/entities/shared-link.entity.ts index e7cd19e53..7370146df 100644 --- a/server/src/infra/entities/shared-link.entity.ts +++ b/server/src/infra/entities/shared-link.entity.ts @@ -1,3 +1,6 @@ +import { AlbumEntity } from 'src/infra/entities/album.entity'; +import { AssetEntity } from 'src/infra/entities/asset.entity'; +import { UserEntity } from 'src/infra/entities/user.entity'; import { Column, CreateDateColumn, @@ -8,9 +11,6 @@ import { PrimaryGeneratedColumn, Unique, } from 'typeorm'; -import { AlbumEntity } from './album.entity'; -import { AssetEntity } from './asset.entity'; -import { UserEntity } from './user.entity'; @Entity('shared_links') @Unique('UQ_sharedlink_key', ['key']) diff --git a/server/src/infra/entities/smart-info.entity.ts b/server/src/infra/entities/smart-info.entity.ts index 2606de60e..5bd65e7fb 100644 --- a/server/src/infra/entities/smart-info.entity.ts +++ b/server/src/infra/entities/smart-info.entity.ts @@ -1,5 +1,5 @@ +import { AssetEntity } from 'src/infra/entities/asset.entity'; import { Column, Entity, JoinColumn, OneToOne, PrimaryColumn } from 'typeorm'; -import { AssetEntity } from './asset.entity'; @Entity('smart_info', { synchronize: false }) export class SmartInfoEntity { diff --git a/server/src/infra/entities/smart-search.entity.ts b/server/src/infra/entities/smart-search.entity.ts index 2b295ac90..bbd966fd4 100644 --- a/server/src/infra/entities/smart-search.entity.ts +++ b/server/src/infra/entities/smart-search.entity.ts @@ -1,5 +1,5 @@ +import { AssetEntity } from 'src/infra/entities/asset.entity'; import { Column, Entity, Index, JoinColumn, OneToOne, PrimaryColumn } from 'typeorm'; -import { AssetEntity } from './asset.entity'; @Entity('smart_search', { synchronize: false }) export class SmartSearchEntity { diff --git a/server/src/infra/entities/system-config.entity.ts b/server/src/infra/entities/system-config.entity.ts index 1ba219429..cf7474417 100644 --- a/server/src/infra/entities/system-config.entity.ts +++ b/server/src/infra/entities/system-config.entity.ts @@ -1,4 +1,4 @@ -import { ConcurrentQueueName } from '@app/domain'; +import { ConcurrentQueueName } from 'src/domain/job/job.constants'; import { Column, Entity, PrimaryColumn } from 'typeorm'; @Entity('system_config') diff --git a/server/src/infra/entities/tag.entity.ts b/server/src/infra/entities/tag.entity.ts index a364529db..b159dcb2e 100644 --- a/server/src/infra/entities/tag.entity.ts +++ b/server/src/infra/entities/tag.entity.ts @@ -1,6 +1,6 @@ +import { AssetEntity } from 'src/infra/entities/asset.entity'; +import { UserEntity } from 'src/infra/entities/user.entity'; import { Column, Entity, ManyToMany, ManyToOne, PrimaryGeneratedColumn, Unique } from 'typeorm'; -import { AssetEntity } from './asset.entity'; -import { UserEntity } from './user.entity'; @Entity('tags') @Unique('UQ_tag_name_userId', ['name', 'userId']) diff --git a/server/src/infra/entities/user-token.entity.ts b/server/src/infra/entities/user-token.entity.ts index a39e93a33..fd04c20dd 100644 --- a/server/src/infra/entities/user-token.entity.ts +++ b/server/src/infra/entities/user-token.entity.ts @@ -1,5 +1,5 @@ +import { UserEntity } from 'src/infra/entities/user.entity'; import { Column, CreateDateColumn, Entity, ManyToOne, PrimaryGeneratedColumn, UpdateDateColumn } from 'typeorm'; -import { UserEntity } from './user.entity'; @Entity('user_token') export class UserTokenEntity { diff --git a/server/src/infra/entities/user.entity.ts b/server/src/infra/entities/user.entity.ts index 20c057d79..888655d3b 100644 --- a/server/src/infra/entities/user.entity.ts +++ b/server/src/infra/entities/user.entity.ts @@ -1,3 +1,5 @@ +import { AssetEntity } from 'src/infra/entities/asset.entity'; +import { TagEntity } from 'src/infra/entities/tag.entity'; import { Column, CreateDateColumn, @@ -7,8 +9,6 @@ import { PrimaryGeneratedColumn, UpdateDateColumn, } from 'typeorm'; -import { AssetEntity } from './asset.entity'; -import { TagEntity } from './tag.entity'; export enum UserAvatarColor { PRIMARY = 'primary', diff --git a/server/src/infra/index.ts b/server/src/infra/index.ts deleted file mode 100644 index 6a218d81c..000000000 --- a/server/src/infra/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export * from './database.config'; -export * from './infra.config'; -export * from './infra.module'; -export * from './websocket.adapter'; diff --git a/server/src/infra/infra.config.ts b/server/src/infra/infra.config.ts deleted file mode 100644 index 9ea570953..000000000 --- a/server/src/infra/infra.config.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { QueueName } from '@app/domain'; -import { RegisterQueueOptions } from '@nestjs/bullmq'; -import { QueueOptions } from 'bullmq'; -import { RedisOptions } from 'ioredis'; - -function parseRedisConfig(): RedisOptions { - const redisUrl = process.env.REDIS_URL; - if (redisUrl && redisUrl.startsWith('ioredis://')) { - try { - const decodedString = Buffer.from(redisUrl.slice(10), 'base64').toString(); - return JSON.parse(decodedString); - } catch (error) { - throw new Error(`Failed to decode redis options: ${error}`); - } - } - return { - host: process.env.REDIS_HOSTNAME || 'immich_redis', - port: Number.parseInt(process.env.REDIS_PORT || '6379'), - db: Number.parseInt(process.env.REDIS_DBINDEX || '0'), - username: process.env.REDIS_USERNAME || undefined, - password: process.env.REDIS_PASSWORD || undefined, - path: process.env.REDIS_SOCKET || undefined, - }; -} - -export const bullConfig: QueueOptions = { - prefix: 'immich_bull', - connection: parseRedisConfig(), - defaultJobOptions: { - attempts: 3, - removeOnComplete: true, - removeOnFail: false, - }, -}; - -export const bullQueues: RegisterQueueOptions[] = Object.values(QueueName).map((name) => ({ name })); - -export const excludePaths = ['/.well-known/immich', '/custom.css', '/favicon.ico']; diff --git a/server/src/infra/infra.module.ts b/server/src/infra/infra.module.ts index df3773deb..9864a0191 100644 --- a/server/src/infra/infra.module.ts +++ b/server/src/infra/infra.module.ts @@ -1,33 +1,3 @@ -import { - IAccessRepository, - IActivityRepository, - IAlbumRepository, - IAssetRepository, - IAssetStackRepository, - IAuditRepository, - ICommunicationRepository, - ICryptoRepository, - IDatabaseRepository, - IJobRepository, - IKeyRepository, - ILibraryRepository, - IMachineLearningRepository, - IMediaRepository, - IMetadataRepository, - IMoveRepository, - IPartnerRepository, - IPersonRepository, - ISearchRepository, - IServerInfoRepository, - ISharedLinkRepository, - IStorageRepository, - ISystemConfigRepository, - ISystemMetadataRepository, - ITagRepository, - IUserRepository, - IUserTokenRepository, - immichAppConfig, -} from '@app/domain'; import { BullModule } from '@nestjs/bullmq'; import { Global, Module, Provider } from '@nestjs/common'; import { ConfigModule } from '@nestjs/config'; @@ -35,39 +5,64 @@ import { EventEmitterModule } from '@nestjs/event-emitter'; import { ScheduleModule, SchedulerRegistry } from '@nestjs/schedule'; import { TypeOrmModule } from '@nestjs/typeorm'; import { OpenTelemetryModule } from 'nestjs-otel'; -import { databaseConfig } from './database.config'; -import { databaseEntities } from './entities'; -import { bullConfig, bullQueues } from './infra.config'; -import { otelConfig } from './instrumentation'; -import { - AccessRepository, - ActivityRepository, - AlbumRepository, - ApiKeyRepository, - AssetRepository, - AssetStackRepository, - AuditRepository, - CommunicationRepository, - CryptoRepository, - DatabaseRepository, - FilesystemProvider, - JobRepository, - LibraryRepository, - MachineLearningRepository, - MediaRepository, - MetadataRepository, - MoveRepository, - PartnerRepository, - PersonRepository, - SearchRepository, - ServerInfoRepository, - SharedLinkRepository, - SystemConfigRepository, - SystemMetadataRepository, - TagRepository, - UserRepository, - UserTokenRepository, -} from './repositories'; +import { bullConfig, bullQueues, immichAppConfig } from 'src/config'; +import { databaseConfig } from 'src/infra/database.config'; +import { databaseEntities } from 'src/infra/entities'; +import { otelConfig } from 'src/infra/instrumentation'; +import { AccessRepository } from 'src/infra/repositories/access.repository'; +import { ActivityRepository } from 'src/infra/repositories/activity.repository'; +import { AlbumRepository } from 'src/infra/repositories/album.repository'; +import { ApiKeyRepository } from 'src/infra/repositories/api-key.repository'; +import { AssetStackRepository } from 'src/infra/repositories/asset-stack.repository'; +import { AssetRepository } from 'src/infra/repositories/asset.repository'; +import { AuditRepository } from 'src/infra/repositories/audit.repository'; +import { CommunicationRepository } from 'src/infra/repositories/communication.repository'; +import { CryptoRepository } from 'src/infra/repositories/crypto.repository'; +import { DatabaseRepository } from 'src/infra/repositories/database.repository'; +import { FilesystemProvider } from 'src/infra/repositories/filesystem.provider'; +import { JobRepository } from 'src/infra/repositories/job.repository'; +import { LibraryRepository } from 'src/infra/repositories/library.repository'; +import { MachineLearningRepository } from 'src/infra/repositories/machine-learning.repository'; +import { MediaRepository } from 'src/infra/repositories/media.repository'; +import { MetadataRepository } from 'src/infra/repositories/metadata.repository'; +import { MoveRepository } from 'src/infra/repositories/move.repository'; +import { PartnerRepository } from 'src/infra/repositories/partner.repository'; +import { PersonRepository } from 'src/infra/repositories/person.repository'; +import { SearchRepository } from 'src/infra/repositories/search.repository'; +import { ServerInfoRepository } from 'src/infra/repositories/server-info.repository'; +import { SharedLinkRepository } from 'src/infra/repositories/shared-link.repository'; +import { SystemConfigRepository } from 'src/infra/repositories/system-config.repository'; +import { SystemMetadataRepository } from 'src/infra/repositories/system-metadata.repository'; +import { TagRepository } from 'src/infra/repositories/tag.repository'; +import { UserTokenRepository } from 'src/infra/repositories/user-token.repository'; +import { UserRepository } from 'src/infra/repositories/user.repository'; +import { IAccessRepository } from 'src/interfaces/access.repository'; +import { IActivityRepository } from 'src/interfaces/activity.repository'; +import { IAlbumRepository } from 'src/interfaces/album.repository'; +import { IKeyRepository } from 'src/interfaces/api-key.repository'; +import { IAssetStackRepository } from 'src/interfaces/asset-stack.repository'; +import { IAssetRepository } from 'src/interfaces/asset.repository'; +import { IAuditRepository } from 'src/interfaces/audit.repository'; +import { ICommunicationRepository } from 'src/interfaces/communication.repository'; +import { ICryptoRepository } from 'src/interfaces/crypto.repository'; +import { IDatabaseRepository } from 'src/interfaces/database.repository'; +import { IJobRepository } from 'src/interfaces/job.repository'; +import { ILibraryRepository } from 'src/interfaces/library.repository'; +import { IMachineLearningRepository } from 'src/interfaces/machine-learning.repository'; +import { IMediaRepository } from 'src/interfaces/media.repository'; +import { IMetadataRepository } from 'src/interfaces/metadata.repository'; +import { IMoveRepository } from 'src/interfaces/move.repository'; +import { IPartnerRepository } from 'src/interfaces/partner.repository'; +import { IPersonRepository } from 'src/interfaces/person.repository'; +import { ISearchRepository } from 'src/interfaces/search.repository'; +import { IServerInfoRepository } from 'src/interfaces/server-info.repository'; +import { ISharedLinkRepository } from 'src/interfaces/shared-link.repository'; +import { IStorageRepository } from 'src/interfaces/storage.repository'; +import { ISystemConfigRepository } from 'src/interfaces/system-config.repository'; +import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.repository'; +import { ITagRepository } from 'src/interfaces/tag.repository'; +import { IUserTokenRepository } from 'src/interfaces/user-token.repository'; +import { IUserRepository } from 'src/interfaces/user.repository'; const providers: Provider[] = [ { provide: IActivityRepository, useClass: ActivityRepository }, diff --git a/server/src/infra/infra.util.ts b/server/src/infra/infra.util.ts deleted file mode 100644 index 585d058e0..000000000 --- a/server/src/infra/infra.util.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { SetMetadata } from '@nestjs/common'; - -export const GENERATE_SQL_KEY = 'generate-sql-key'; - -export interface GenerateSqlQueries { - name?: string; - params: unknown[]; -} - -/** Decorator to enable versioning/tracking of generated Sql */ -export const GenerateSql = (...options: GenerateSqlQueries[]) => SetMetadata(GENERATE_SQL_KEY, options); - -const UUID = '00000000-0000-4000-a000-000000000000'; - -export const DummyValue = { - UUID, - UUID_SET: new Set([UUID]), - PAGINATION: { take: 10, skip: 0 }, - EMAIL: 'user@immich.app', - STRING: 'abcdefghi', - BUFFER: Buffer.from('abcdefghi'), - DATE: new Date(), - TIME_BUCKET: '2024-01-01T00:00:00.000Z', -}; - -// PostgreSQL uses a 16-bit integer to indicate the number of bound parameters. This means that the -// maximum number of parameters is 65535. Any query that tries to bind more than that (e.g. searching -// by a list of IDs) requires splitting the query into multiple chunks. -// We are rounding down this limit, as queries commonly include other filters and parameters. -export const DATABASE_PARAMETER_CHUNK_SIZE = 65_500; diff --git a/server/src/infra/infra.utils.ts b/server/src/infra/infra.utils.ts index 247244108..36f1ef4bf 100644 --- a/server/src/infra/infra.utils.ts +++ b/server/src/infra/infra.utils.ts @@ -1,5 +1,7 @@ -import { AssetSearchBuilderOptions, Paginated, PaginationOptions } from '@app/domain'; import _ from 'lodash'; +import { AssetEntity } from 'src/infra/entities/asset.entity'; +import { AssetSearchBuilderOptions } from 'src/interfaces/search.repository'; +import { Paginated, PaginatedBuilderOptions, PaginationMode, PaginationOptions, PaginationResult } from 'src/utils'; import { Between, FindManyOptions, @@ -11,9 +13,6 @@ import { Repository, SelectQueryBuilder, } from 'typeorm'; -import { PaginatedBuilderOptions, PaginationMode, PaginationResult, chunks, setUnion } from '../domain/domain.util'; -import { AssetEntity } from './entities'; -import { DATABASE_PARAMETER_CHUNK_SIZE } from './infra.util'; /** * Allows optional values unlike the regular Between and uses MoreThanOrEqual @@ -29,11 +28,6 @@ export function OptionalBetween(from?: T, to?: T) { } } -export const isValidInteger = (value: number, options: { min?: number; max?: number }): value is number => { - const { min = Number.MIN_SAFE_INTEGER, max = Number.MAX_SAFE_INTEGER } = options; - return Number.isInteger(value) && value >= min && value <= max; -}; - function paginationHelper(items: Entity[], take: number): PaginationResult { const hasNextPage = items.length > take; items.splice(take); @@ -78,70 +72,6 @@ export async function paginatedBuilder( export const asVector = (embedding: number[], quote = false) => quote ? `'[${embedding.join(',')}]'` : `[${embedding.join(',')}]`; -/** - * Wraps a method that takes a collection of parameters and sequentially calls it with chunks of the collection, - * to overcome the maximum number of parameters allowed by the database driver. - * - * @param options.paramIndex The index of the function parameter to chunk. Defaults to 0. - * @param options.flatten Whether to flatten the results. Defaults to false. - */ -export function Chunked(options: { paramIndex?: number; mergeFn?: (results: any) => any } = {}): MethodDecorator { - return (target: any, propertyKey: string | symbol, descriptor: PropertyDescriptor) => { - const originalMethod = descriptor.value; - const parameterIndex = options.paramIndex ?? 0; - descriptor.value = async function (...arguments_: any[]) { - const argument = arguments_[parameterIndex]; - - // Early return if argument length is less than or equal to the chunk size. - if ( - (Array.isArray(argument) && argument.length <= DATABASE_PARAMETER_CHUNK_SIZE) || - (argument instanceof Set && argument.size <= DATABASE_PARAMETER_CHUNK_SIZE) - ) { - return await originalMethod.apply(this, arguments_); - } - - return Promise.all( - chunks(argument, DATABASE_PARAMETER_CHUNK_SIZE).map(async (chunk) => { - await Reflect.apply(originalMethod, this, [ - ...arguments_.slice(0, parameterIndex), - chunk, - ...arguments_.slice(parameterIndex + 1), - ]); - }), - ).then((results) => (options.mergeFn ? options.mergeFn(results) : results)); - }; - }; -} - -export function ChunkedArray(options?: { paramIndex?: number }): MethodDecorator { - return Chunked({ ...options, mergeFn: _.flatten }); -} - -export function ChunkedSet(options?: { paramIndex?: number }): MethodDecorator { - return Chunked({ ...options, mergeFn: setUnion }); -} - -// https://stackoverflow.com/a/74898678 -export function DecorateAll( - decorator: ( - target: any, - propertyKey: string, - descriptor: TypedPropertyDescriptor, - ) => TypedPropertyDescriptor | void, -) { - return (target: any) => { - const descriptors = Object.getOwnPropertyDescriptors(target.prototype); - for (const [propName, descriptor] of Object.entries(descriptors)) { - const isMethod = typeof descriptor.value == 'function' && propName !== 'constructor'; - if (!isMethod) { - continue; - } - decorator({ ...target, constructor: { ...target.constructor, name: target.name } as any }, propName, descriptor); - Object.defineProperty(target.prototype, propName, descriptor); - } - }; -} - export function searchAssetBuilder( builder: SelectQueryBuilder, options: AssetSearchBuilderOptions, diff --git a/server/src/infra/instrumentation.ts b/server/src/infra/instrumentation.ts index 130eaea3d..a30f0523a 100644 --- a/server/src/infra/instrumentation.ts +++ b/server/src/infra/instrumentation.ts @@ -1,4 +1,3 @@ -import { serverVersion } from '@app/domain/domain.constant'; import { Histogram, MetricOptions, ValueType, metrics } from '@opentelemetry/api'; import { AsyncLocalStorageContextManager } from '@opentelemetry/context-async-hooks'; import { PrometheusExporter } from '@opentelemetry/exporter-prometheus'; @@ -14,8 +13,9 @@ import { snakeCase, startCase } from 'lodash'; import { OpenTelemetryModuleOptions } from 'nestjs-otel/lib/interfaces'; import { copyMetadataFromFunctionToFunction } from 'nestjs-otel/lib/opentelemetry.utils'; import { performance } from 'node:perf_hooks'; -import { excludePaths } from './infra.config'; -import { DecorateAll } from './infra.utils'; +import { excludePaths } from 'src/config'; +import { DecorateAll } from 'src/decorators'; +import { serverVersion } from 'src/domain/domain.constant'; let metricsEnabled = process.env.IMMICH_METRICS === 'true'; const hostMetrics = diff --git a/server/src/infra/logger.ts b/server/src/infra/logger.ts index 8de149c40..dcc87a875 100644 --- a/server/src/infra/logger.ts +++ b/server/src/infra/logger.ts @@ -1,6 +1,6 @@ import { ConsoleLogger } from '@nestjs/common'; import { isLogLevelEnabled } from '@nestjs/common/services/utils/is-log-level-enabled.util'; -import { LogLevel } from './entities'; +import { LogLevel } from 'src/infra/entities/system-config.entity'; const LOG_LEVELS = [LogLevel.VERBOSE, LogLevel.DEBUG, LogLevel.LOG, LogLevel.WARN, LogLevel.ERROR, LogLevel.FATAL]; diff --git a/server/src/infra/migrations/1700713871511-UsePgVectors.ts b/server/src/infra/migrations/1700713871511-UsePgVectors.ts index 008d5eadc..46d5320ce 100644 --- a/server/src/infra/migrations/1700713871511-UsePgVectors.ts +++ b/server/src/infra/migrations/1700713871511-UsePgVectors.ts @@ -1,6 +1,6 @@ -import { getCLIPModelInfo } from '@app/domain/smart-info/smart-info.constant'; +import { getCLIPModelInfo } from 'src/domain/smart-info/smart-info.constant'; +import { vectorExt } from 'src/infra/database.config'; import { MigrationInterface, QueryRunner } from 'typeorm'; -import { vectorExt } from '@app/infra/database.config'; export class UsePgVectors1700713871511 implements MigrationInterface { name = 'UsePgVectors1700713871511'; @@ -14,12 +14,14 @@ export class UsePgVectors1700713871511 implements MigrationInterface { LIMIT 1`); const faceDimSize = faceDimQuery?.[0]?.['dimsize'] ?? 512; - const clipModelNameQuery = await queryRunner.query(`SELECT value FROM system_config WHERE key = 'machineLearning.clip.modelName'`); + const clipModelNameQuery = await queryRunner.query( + `SELECT value FROM system_config WHERE key = 'machineLearning.clip.modelName'`, + ); const clipModelName: string = clipModelNameQuery?.[0]?.['value'] ?? 'ViT-B-32__openai'; const clipDimSize = getCLIPModelInfo(clipModelName.replaceAll('"', '')).dimSize; await queryRunner.query(` - ALTER TABLE asset_faces + ALTER TABLE asset_faces ALTER COLUMN embedding SET NOT NULL, ALTER COLUMN embedding TYPE vector(${faceDimSize})`); @@ -37,7 +39,7 @@ export class UsePgVectors1700713871511 implements MigrationInterface { AND array_position(si."clipEmbedding", NULL) IS NULL`); await queryRunner.query(`ALTER TABLE smart_info DROP COLUMN IF EXISTS "clipEmbedding"`); - } + } public async down(queryRunner: QueryRunner): Promise { await queryRunner.query(`ALTER TABLE asset_faces ALTER COLUMN embedding TYPE real array`); diff --git a/server/src/infra/migrations/1700713994428-AddCLIPEmbeddingIndex.ts b/server/src/infra/migrations/1700713994428-AddCLIPEmbeddingIndex.ts index c3716cc19..b045ba62b 100644 --- a/server/src/infra/migrations/1700713994428-AddCLIPEmbeddingIndex.ts +++ b/server/src/infra/migrations/1700713994428-AddCLIPEmbeddingIndex.ts @@ -1,6 +1,6 @@ +import { vectorExt } from 'src/infra/database.config'; +import { DatabaseExtension } from 'src/interfaces/database.repository'; import { MigrationInterface, QueryRunner } from 'typeorm'; -import { vectorExt } from '../database.config'; -import { DatabaseExtension } from '@app/domain/repositories/database.repository'; export class AddCLIPEmbeddingIndex1700713994428 implements MigrationInterface { name = 'AddCLIPEmbeddingIndex1700713994428'; diff --git a/server/src/infra/migrations/1700714033632-AddFaceEmbeddingIndex.ts b/server/src/infra/migrations/1700714033632-AddFaceEmbeddingIndex.ts index 066303530..e77ce3b0b 100644 --- a/server/src/infra/migrations/1700714033632-AddFaceEmbeddingIndex.ts +++ b/server/src/infra/migrations/1700714033632-AddFaceEmbeddingIndex.ts @@ -1,6 +1,6 @@ +import { vectorExt } from 'src/infra/database.config'; +import { DatabaseExtension } from 'src/interfaces/database.repository'; import { MigrationInterface, QueryRunner } from 'typeorm'; -import { vectorExt } from '../database.config'; -import { DatabaseExtension } from '@app/domain/repositories/database.repository'; export class AddFaceEmbeddingIndex1700714033632 implements MigrationInterface { name = 'AddFaceEmbeddingIndex1700714033632'; diff --git a/server/src/infra/repositories/access.repository.ts b/server/src/infra/repositories/access.repository.ts index ad650bf0e..c6b7bc0fd 100644 --- a/server/src/infra/repositories/access.repository.ts +++ b/server/src/infra/repositories/access.repository.ts @@ -1,20 +1,17 @@ -import { IAccessRepository } from '@app/domain'; import { InjectRepository } from '@nestjs/typeorm'; +import { ChunkedSet, DummyValue, GenerateSql } from 'src/decorators'; +import { ActivityEntity } from 'src/infra/entities/activity.entity'; +import { AlbumEntity } from 'src/infra/entities/album.entity'; +import { AssetFaceEntity } from 'src/infra/entities/asset-face.entity'; +import { AssetEntity } from 'src/infra/entities/asset.entity'; +import { LibraryEntity } from 'src/infra/entities/library.entity'; +import { PartnerEntity } from 'src/infra/entities/partner.entity'; +import { PersonEntity } from 'src/infra/entities/person.entity'; +import { SharedLinkEntity } from 'src/infra/entities/shared-link.entity'; +import { UserTokenEntity } from 'src/infra/entities/user-token.entity'; +import { Instrumentation } from 'src/infra/instrumentation'; +import { IAccessRepository } from 'src/interfaces/access.repository'; import { Brackets, In, Repository } from 'typeorm'; -import { - ActivityEntity, - AlbumEntity, - AssetEntity, - AssetFaceEntity, - LibraryEntity, - PartnerEntity, - PersonEntity, - SharedLinkEntity, - UserTokenEntity, -} from '../entities'; -import { DummyValue, GenerateSql } from '../infra.util'; -import { ChunkedSet } from '../infra.utils'; -import { Instrumentation } from '../instrumentation'; type IActivityAccess = IAccessRepository['activity']; type IAlbumAccess = IAccessRepository['album']; diff --git a/server/src/infra/repositories/activity.repository.ts b/server/src/infra/repositories/activity.repository.ts index c546056db..4c1cec6d6 100644 --- a/server/src/infra/repositories/activity.repository.ts +++ b/server/src/infra/repositories/activity.repository.ts @@ -1,10 +1,10 @@ -import { IActivityRepository } from '@app/domain'; import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; +import { DummyValue, GenerateSql } from 'src/decorators'; +import { ActivityEntity } from 'src/infra/entities/activity.entity'; +import { Instrumentation } from 'src/infra/instrumentation'; +import { IActivityRepository } from 'src/interfaces/activity.repository'; import { IsNull, Repository } from 'typeorm'; -import { ActivityEntity } from '../entities/activity.entity'; -import { DummyValue, GenerateSql } from '../infra.util'; -import { Instrumentation } from '../instrumentation'; export interface ActivitySearch { albumId?: string; diff --git a/server/src/infra/repositories/album.repository.ts b/server/src/infra/repositories/album.repository.ts index 60ef6126c..ca4d4a913 100644 --- a/server/src/infra/repositories/album.repository.ts +++ b/server/src/infra/repositories/album.repository.ts @@ -1,14 +1,20 @@ -import { AlbumAsset, AlbumAssetCount, AlbumAssets, AlbumInfoOptions, IAlbumRepository } from '@app/domain'; import { Injectable } from '@nestjs/common'; import { InjectDataSource, InjectRepository } from '@nestjs/typeorm'; import _ from 'lodash'; +import { Chunked, ChunkedArray, DATABASE_PARAMETER_CHUNK_SIZE, DummyValue, GenerateSql } from 'src/decorators'; +import { dataSource } from 'src/infra/database.config'; +import { AlbumEntity } from 'src/infra/entities/album.entity'; +import { AssetEntity } from 'src/infra/entities/asset.entity'; +import { Instrumentation } from 'src/infra/instrumentation'; +import { + AlbumAsset, + AlbumAssetCount, + AlbumAssets, + AlbumInfoOptions, + IAlbumRepository, +} from 'src/interfaces/album.repository'; +import { setUnion } from 'src/utils'; import { DataSource, FindOptionsOrder, FindOptionsRelations, In, IsNull, Not, Repository } from 'typeorm'; -import { setUnion } from '../../domain/domain.util'; -import { dataSource } from '../database.config'; -import { AlbumEntity, AssetEntity } from '../entities'; -import { DATABASE_PARAMETER_CHUNK_SIZE, DummyValue, GenerateSql } from '../infra.util'; -import { Chunked, ChunkedArray } from '../infra.utils'; -import { Instrumentation } from '../instrumentation'; @Instrumentation() @Injectable() diff --git a/server/src/infra/repositories/api-key.repository.ts b/server/src/infra/repositories/api-key.repository.ts index 3cafc22eb..0add8512a 100644 --- a/server/src/infra/repositories/api-key.repository.ts +++ b/server/src/infra/repositories/api-key.repository.ts @@ -1,10 +1,10 @@ -import { IKeyRepository } from '@app/domain'; import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; +import { DummyValue, GenerateSql } from 'src/decorators'; +import { APIKeyEntity } from 'src/infra/entities/api-key.entity'; +import { Instrumentation } from 'src/infra/instrumentation'; +import { IKeyRepository } from 'src/interfaces/api-key.repository'; import { Repository } from 'typeorm'; -import { APIKeyEntity } from '../entities'; -import { DummyValue, GenerateSql } from '../infra.util'; -import { Instrumentation } from '../instrumentation'; @Instrumentation() @Injectable() diff --git a/server/src/infra/repositories/asset-stack.repository.ts b/server/src/infra/repositories/asset-stack.repository.ts index d769030bb..0f984f4ea 100644 --- a/server/src/infra/repositories/asset-stack.repository.ts +++ b/server/src/infra/repositories/asset-stack.repository.ts @@ -1,9 +1,9 @@ -import { IAssetStackRepository } from '@app/domain'; import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; +import { AssetStackEntity } from 'src/infra/entities/asset-stack.entity'; +import { Instrumentation } from 'src/infra/instrumentation'; +import { IAssetStackRepository } from 'src/interfaces/asset-stack.repository'; import { Repository } from 'typeorm'; -import { AssetStackEntity } from '../entities'; -import { Instrumentation } from '../instrumentation'; @Instrumentation() @Injectable() diff --git a/server/src/infra/repositories/asset.repository.ts b/server/src/infra/repositories/asset.repository.ts index 4dc295bc6..3abfb93d7 100644 --- a/server/src/infra/repositories/asset.repository.ts +++ b/server/src/infra/repositories/asset.repository.ts @@ -1,9 +1,20 @@ +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { DateTime } from 'luxon'; +import path from 'node:path'; +import { Chunked, ChunkedArray, DummyValue, GenerateSql } from 'src/decorators'; +import { AssetOrder } from 'src/infra/entities/album.entity'; +import { AssetJobStatusEntity } from 'src/infra/entities/asset-job-status.entity'; +import { AssetEntity, AssetType } from 'src/infra/entities/asset.entity'; +import { ExifEntity } from 'src/infra/entities/exif.entity'; +import { SmartInfoEntity } from 'src/infra/entities/smart-info.entity'; +import { OptionalBetween, paginate, paginatedBuilder, searchAssetBuilder } from 'src/infra/infra.utils'; +import { Instrumentation } from 'src/infra/instrumentation'; import { AssetBuilderOptions, AssetCreate, AssetExploreFieldOptions, AssetPathEntity, - AssetSearchOptions, AssetStats, AssetStatsOptions, AssetUpdateAllOptions, @@ -14,20 +25,14 @@ import { MapMarkerSearchOptions, MetadataSearchOptions, MonthDay, - Paginated, - PaginationMode, - PaginationOptions, - SearchExploreItem, TimeBucketItem, TimeBucketOptions, TimeBucketSize, WithProperty, WithoutProperty, -} from '@app/domain'; -import { Injectable } from '@nestjs/common'; -import { InjectRepository } from '@nestjs/typeorm'; -import { DateTime } from 'luxon'; -import path from 'node:path'; +} from 'src/interfaces/asset.repository'; +import { AssetSearchOptions, SearchExploreItem } from 'src/interfaces/search.repository'; +import { Paginated, PaginationMode, PaginationOptions } from 'src/utils'; import { Brackets, FindOptionsRelations, @@ -38,10 +43,6 @@ import { Not, Repository, } from 'typeorm'; -import { AssetEntity, AssetJobStatusEntity, AssetOrder, AssetType, ExifEntity, SmartInfoEntity } from '../entities'; -import { DummyValue, GenerateSql } from '../infra.util'; -import { Chunked, ChunkedArray, OptionalBetween, paginate, paginatedBuilder, searchAssetBuilder } from '../infra.utils'; -import { Instrumentation } from '../instrumentation'; const truncateMap: Record = { [TimeBucketSize.DAY]: 'day', diff --git a/server/src/infra/repositories/audit.repository.ts b/server/src/infra/repositories/audit.repository.ts index bc00cbe9a..bfd55336d 100644 --- a/server/src/infra/repositories/audit.repository.ts +++ b/server/src/infra/repositories/audit.repository.ts @@ -1,8 +1,8 @@ -import { AuditSearch, IAuditRepository } from '@app/domain'; import { InjectRepository } from '@nestjs/typeorm'; +import { AuditEntity } from 'src/infra/entities/audit.entity'; +import { Instrumentation } from 'src/infra/instrumentation'; +import { AuditSearch, IAuditRepository } from 'src/interfaces/audit.repository'; import { LessThan, MoreThan, Repository } from 'typeorm'; -import { AuditEntity } from '../entities'; -import { Instrumentation } from '../instrumentation'; @Instrumentation() export class AuditRepository implements IAuditRepository { diff --git a/server/src/infra/repositories/communication.repository.ts b/server/src/infra/repositories/communication.repository.ts index 6429b6e19..e92fe1387 100644 --- a/server/src/infra/repositories/communication.repository.ts +++ b/server/src/infra/repositories/communication.repository.ts @@ -1,13 +1,3 @@ -import { - AuthService, - ClientEvent, - ICommunicationRepository, - InternalEventMap, - OnConnectCallback, - OnServerEventCallback, - ServerEvent, -} from '@app/domain'; -import { ImmichLogger } from '@app/infra/logger'; import { EventEmitter2 } from '@nestjs/event-emitter'; import { OnGatewayConnection, @@ -17,7 +7,17 @@ import { WebSocketServer, } from '@nestjs/websockets'; import { Server, Socket } from 'socket.io'; -import { Instrumentation } from '../instrumentation'; +import { AuthService } from 'src/domain/auth/auth.service'; +import { Instrumentation } from 'src/infra/instrumentation'; +import { ImmichLogger } from 'src/infra/logger'; +import { + ClientEvent, + ICommunicationRepository, + InternalEventMap, + OnConnectCallback, + OnServerEventCallback, + ServerEvent, +} from 'src/interfaces/communication.repository'; @Instrumentation() @WebSocketGateway({ diff --git a/server/src/infra/repositories/crypto.repository.ts b/server/src/infra/repositories/crypto.repository.ts index f98fa9d87..121943af3 100644 --- a/server/src/infra/repositories/crypto.repository.ts +++ b/server/src/infra/repositories/crypto.repository.ts @@ -1,9 +1,9 @@ -import { ICryptoRepository } from '@app/domain'; import { Injectable } from '@nestjs/common'; import { compareSync, hash } from 'bcrypt'; import { createHash, randomBytes, randomUUID } from 'node:crypto'; import { createReadStream } from 'node:fs'; -import { Instrumentation } from '../instrumentation'; +import { Instrumentation } from 'src/infra/instrumentation'; +import { ICryptoRepository } from 'src/interfaces/crypto.repository'; @Instrumentation() @Injectable() diff --git a/server/src/infra/repositories/database.repository.ts b/server/src/infra/repositories/database.repository.ts index 8154e9122..60586edc5 100644 --- a/server/src/infra/repositories/database.repository.ts +++ b/server/src/infra/repositories/database.repository.ts @@ -1,3 +1,10 @@ +import { Injectable } from '@nestjs/common'; +import { InjectDataSource } from '@nestjs/typeorm'; +import AsyncLock from 'async-lock'; +import { Version, VersionType } from 'src/domain/domain.constant'; +import { vectorExt } from 'src/infra/database.config'; +import { Instrumentation } from 'src/infra/instrumentation'; +import { ImmichLogger } from 'src/infra/logger'; import { DatabaseExtension, DatabaseLock, @@ -5,18 +12,10 @@ import { VectorExtension, VectorIndex, VectorUpdateResult, - Version, - VersionType, extName, -} from '@app/domain'; -import { vectorExt } from '@app/infra/database.config'; -import { Injectable } from '@nestjs/common'; -import { InjectDataSource } from '@nestjs/typeorm'; -import AsyncLock from 'async-lock'; +} from 'src/interfaces/database.repository'; +import { isValidInteger } from 'src/validation'; import { DataSource, EntityManager, QueryRunner } from 'typeorm'; -import { isValidInteger } from '../infra.utils'; -import { Instrumentation } from '../instrumentation'; -import { ImmichLogger } from '../logger'; @Instrumentation() @Injectable() diff --git a/server/src/infra/repositories/filesystem.provider.spec.ts b/server/src/infra/repositories/filesystem.provider.spec.ts index 4c20b2a50..9b12187ed 100644 --- a/server/src/infra/repositories/filesystem.provider.spec.ts +++ b/server/src/infra/repositories/filesystem.provider.spec.ts @@ -1,6 +1,6 @@ -import { CrawlOptionsDto } from '@app/domain'; import mockfs from 'mock-fs'; -import { FilesystemProvider } from './filesystem.provider'; +import { CrawlOptionsDto } from 'src/domain/library/library.dto'; +import { FilesystemProvider } from 'src/infra/repositories/filesystem.provider'; interface Test { test: string; diff --git a/server/src/infra/repositories/filesystem.provider.ts b/server/src/infra/repositories/filesystem.provider.ts index c4f577ed2..2da49625e 100644 --- a/server/src/infra/repositories/filesystem.provider.ts +++ b/server/src/infra/repositories/filesystem.provider.ts @@ -1,21 +1,21 @@ -import { - CrawlOptionsDto, - DiskUsage, - IStorageRepository, - ImmichReadStream, - ImmichZipStream, - StorageEventType, - WatchEvents, - mimeTypes, -} from '@app/domain'; -import { ImmichLogger } from '@app/infra/logger'; import archiver from 'archiver'; import chokidar, { WatchOptions } from 'chokidar'; import { glob, globStream } from 'fast-glob'; import { constants, createReadStream, existsSync, mkdirSync } from 'node:fs'; import fs from 'node:fs/promises'; import path from 'node:path'; -import { Instrumentation } from '../instrumentation'; +import { mimeTypes } from 'src/domain/domain.constant'; +import { CrawlOptionsDto } from 'src/domain/library/library.dto'; +import { Instrumentation } from 'src/infra/instrumentation'; +import { ImmichLogger } from 'src/infra/logger'; +import { + DiskUsage, + IStorageRepository, + ImmichReadStream, + ImmichZipStream, + StorageEventType, + WatchEvents, +} from 'src/interfaces/storage.repository'; @Instrumentation() export class FilesystemProvider implements IStorageRepository { diff --git a/server/src/infra/repositories/index.ts b/server/src/infra/repositories/index.ts deleted file mode 100644 index d684f6b00..000000000 --- a/server/src/infra/repositories/index.ts +++ /dev/null @@ -1,27 +0,0 @@ -export * from './access.repository'; -export * from './activity.repository'; -export * from './album.repository'; -export * from './api-key.repository'; -export * from './asset-stack.repository'; -export * from './asset.repository'; -export * from './audit.repository'; -export * from './communication.repository'; -export * from './crypto.repository'; -export * from './database.repository'; -export * from './filesystem.provider'; -export * from './job.repository'; -export * from './library.repository'; -export * from './machine-learning.repository'; -export * from './media.repository'; -export * from './metadata.repository'; -export * from './move.repository'; -export * from './partner.repository'; -export * from './person.repository'; -export * from './search.repository'; -export * from './server-info.repository'; -export * from './shared-link.repository'; -export * from './system-config.repository'; -export * from './system-metadata.repository'; -export * from './tag.repository'; -export * from './user-token.repository'; -export * from './user.repository'; diff --git a/server/src/infra/repositories/job.repository.ts b/server/src/infra/repositories/job.repository.ts index 227967a07..a7637e0e6 100644 --- a/server/src/infra/repositories/job.repository.ts +++ b/server/src/infra/repositories/job.repository.ts @@ -1,14 +1,3 @@ -import { - IJobRepository, - JobCounts, - JobItem, - JobName, - JOBS_TO_QUEUE, - QueueCleanType, - QueueName, - QueueStatus, -} from '@app/domain'; -import { ImmichLogger } from '@app/infra/logger'; import { getQueueToken } from '@nestjs/bullmq'; import { Injectable } from '@nestjs/common'; import { ModuleRef } from '@nestjs/core'; @@ -16,8 +5,11 @@ import { SchedulerRegistry } from '@nestjs/schedule'; import { Job, JobsOptions, Processor, Queue, Worker, WorkerOptions } from 'bullmq'; import { CronJob, CronTime } from 'cron'; import { setTimeout } from 'node:timers/promises'; -import { bullConfig } from '../infra.config'; -import { Instrumentation } from '../instrumentation'; +import { bullConfig } from 'src/config'; +import { JOBS_TO_QUEUE, JobName, QueueName } from 'src/domain/job/job.constants'; +import { Instrumentation } from 'src/infra/instrumentation'; +import { ImmichLogger } from 'src/infra/logger'; +import { IJobRepository, JobCounts, JobItem, QueueCleanType, QueueStatus } from 'src/interfaces/job.repository'; @Instrumentation() @Injectable() diff --git a/server/src/infra/repositories/library.repository.ts b/server/src/infra/repositories/library.repository.ts index 1caacb534..ac398d236 100644 --- a/server/src/infra/repositories/library.repository.ts +++ b/server/src/infra/repositories/library.repository.ts @@ -1,11 +1,12 @@ -import { ILibraryRepository, LibraryStatsResponseDto } from '@app/domain'; import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; +import { DummyValue, GenerateSql } from 'src/decorators'; +import { LibraryStatsResponseDto } from 'src/domain/library/library.dto'; +import { LibraryEntity, LibraryType } from 'src/infra/entities/library.entity'; +import { Instrumentation } from 'src/infra/instrumentation'; +import { ILibraryRepository } from 'src/interfaces/library.repository'; import { IsNull, Not } from 'typeorm'; import { Repository } from 'typeorm/repository/Repository.js'; -import { LibraryEntity, LibraryType } from '../entities'; -import { DummyValue, GenerateSql } from '../infra.util'; -import { Instrumentation } from '../instrumentation'; @Instrumentation() @Injectable() diff --git a/server/src/infra/repositories/machine-learning.repository.ts b/server/src/infra/repositories/machine-learning.repository.ts index 767ca812b..46d16dcd7 100644 --- a/server/src/infra/repositories/machine-learning.repository.ts +++ b/server/src/infra/repositories/machine-learning.repository.ts @@ -1,17 +1,15 @@ +import { Injectable } from '@nestjs/common'; +import { readFile } from 'node:fs/promises'; +import { CLIPConfig, ModelConfig, RecognitionConfig } from 'src/domain/smart-info/dto/model-config.dto'; +import { Instrumentation } from 'src/infra/instrumentation'; import { - CLIPConfig, CLIPMode, DetectFaceResult, IMachineLearningRepository, - ModelConfig, ModelType, - RecognitionConfig, TextModelInput, VisionModelInput, -} from '@app/domain'; -import { Injectable } from '@nestjs/common'; -import { readFile } from 'node:fs/promises'; -import { Instrumentation } from '../instrumentation'; +} from 'src/interfaces/machine-learning.repository'; const errorPrefix = 'Machine learning request'; diff --git a/server/src/infra/repositories/media.repository.ts b/server/src/infra/repositories/media.repository.ts index 39cec03af..8c143d049 100644 --- a/server/src/infra/repositories/media.repository.ts +++ b/server/src/infra/repositories/media.repository.ts @@ -1,19 +1,19 @@ +import ffmpeg, { FfprobeData } from 'fluent-ffmpeg'; +import fs from 'node:fs/promises'; +import { Writable } from 'node:stream'; +import { promisify } from 'node:util'; +import sharp from 'sharp'; +import { Colorspace } from 'src/infra/entities/system-config.entity'; +import { Instrumentation } from 'src/infra/instrumentation'; +import { ImmichLogger } from 'src/infra/logger'; import { CropOptions, IMediaRepository, ResizeOptions, TranscodeOptions, VideoInfo, - handlePromiseError, -} from '@app/domain'; -import { Colorspace } from '@app/infra/entities'; -import { ImmichLogger } from '@app/infra/logger'; -import ffmpeg, { FfprobeData } from 'fluent-ffmpeg'; -import fs from 'node:fs/promises'; -import { Writable } from 'node:stream'; -import { promisify } from 'node:util'; -import sharp from 'sharp'; -import { Instrumentation } from '../instrumentation'; +} from 'src/interfaces/media.repository'; +import { handlePromiseError } from 'src/utils'; const probe = promisify(ffmpeg.ffprobe); sharp.concurrency(0); diff --git a/server/src/infra/repositories/metadata.repository.ts b/server/src/infra/repositories/metadata.repository.ts index bf9bb8a46..a5e82535c 100644 --- a/server/src/infra/repositories/metadata.repository.ts +++ b/server/src/infra/repositories/metadata.repository.ts @@ -1,29 +1,28 @@ +import { Inject } from '@nestjs/common'; +import { InjectDataSource, InjectRepository } from '@nestjs/typeorm'; +import { DefaultReadTaskOptions, Tags, exiftool } from 'exiftool-vendored'; +import geotz from 'geo-tz'; +import { getName } from 'i18n-iso-countries'; +import { createReadStream, existsSync } from 'node:fs'; +import { readFile } from 'node:fs/promises'; +import readLine from 'node:readline'; +import { DummyValue, GenerateSql } from 'src/decorators'; import { citiesFile, geodataAdmin1Path, geodataAdmin2Path, geodataCities500Path, geodataDatePath, - GeoPoint, - IMetadataRepository, - ImmichTags, - ISystemMetadataRepository, - ReverseGeocodeResult, -} from '@app/domain'; -import { ExifEntity, GeodataPlacesEntity, SystemMetadataKey } from '@app/infra/entities'; -import { ImmichLogger } from '@app/infra/logger'; -import { Inject } from '@nestjs/common'; -import { InjectDataSource, InjectRepository } from '@nestjs/typeorm'; -import { DefaultReadTaskOptions, exiftool, Tags } from 'exiftool-vendored'; -import * as geotz from 'geo-tz'; -import { getName } from 'i18n-iso-countries'; -import { createReadStream, existsSync } from 'node:fs'; -import { readFile } from 'node:fs/promises'; -import * as readLine from 'node:readline'; +} from 'src/domain/domain.constant'; +import { ExifEntity } from 'src/infra/entities/exif.entity'; +import { GeodataPlacesEntity } from 'src/infra/entities/geodata-places.entity'; +import { SystemMetadataKey } from 'src/infra/entities/system-metadata.entity'; +import { Instrumentation } from 'src/infra/instrumentation'; +import { ImmichLogger } from 'src/infra/logger'; +import { GeoPoint, IMetadataRepository, ImmichTags, ReverseGeocodeResult } from 'src/interfaces/metadata.repository'; +import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.repository'; import { DataSource, QueryRunner, Repository } from 'typeorm'; import { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity.js'; -import { DummyValue, GenerateSql } from '../infra.util'; -import { Instrumentation } from '../instrumentation'; @Instrumentation() export class MetadataRepository implements IMetadataRepository { diff --git a/server/src/infra/repositories/move.repository.ts b/server/src/infra/repositories/move.repository.ts index 205c67ad6..7f7595125 100644 --- a/server/src/infra/repositories/move.repository.ts +++ b/server/src/infra/repositories/move.repository.ts @@ -1,10 +1,10 @@ -import { IMoveRepository, MoveCreate } from '@app/domain'; import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; +import { DummyValue, GenerateSql } from 'src/decorators'; +import { MoveEntity, PathType } from 'src/infra/entities/move.entity'; +import { Instrumentation } from 'src/infra/instrumentation'; +import { IMoveRepository, MoveCreate } from 'src/interfaces/move.repository'; import { Repository } from 'typeorm'; -import { MoveEntity, PathType } from '../entities'; -import { DummyValue, GenerateSql } from '../infra.util'; -import { Instrumentation } from '../instrumentation'; @Instrumentation() @Injectable() diff --git a/server/src/infra/repositories/partner.repository.ts b/server/src/infra/repositories/partner.repository.ts index eb07902dc..f8b9866ca 100644 --- a/server/src/infra/repositories/partner.repository.ts +++ b/server/src/infra/repositories/partner.repository.ts @@ -1,9 +1,9 @@ -import { IPartnerRepository, PartnerIds } from '@app/domain'; import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; +import { PartnerEntity } from 'src/infra/entities/partner.entity'; +import { Instrumentation } from 'src/infra/instrumentation'; +import { IPartnerRepository, PartnerIds } from 'src/interfaces/partner.repository'; import { DeepPartial, Repository } from 'typeorm'; -import { PartnerEntity } from '../entities'; -import { Instrumentation } from '../instrumentation'; @Instrumentation() @Injectable() diff --git a/server/src/infra/repositories/person.repository.ts b/server/src/infra/repositories/person.repository.ts index 562a56fb6..5096032da 100644 --- a/server/src/infra/repositories/person.repository.ts +++ b/server/src/infra/repositories/person.repository.ts @@ -1,21 +1,22 @@ +import { InjectRepository } from '@nestjs/typeorm'; +import _ from 'lodash'; +import { ChunkedArray, DummyValue, GenerateSql } from 'src/decorators'; +import { AssetFaceEntity } from 'src/infra/entities/asset-face.entity'; +import { AssetEntity } from 'src/infra/entities/asset.entity'; +import { PersonEntity } from 'src/infra/entities/person.entity'; +import { asVector, paginate } from 'src/infra/infra.utils'; +import { Instrumentation } from 'src/infra/instrumentation'; import { AssetFaceId, IPersonRepository, - Paginated, - PaginationOptions, PeopleStatistics, PersonNameSearchOptions, PersonSearchOptions, PersonStatistics, UpdateFacesData, -} from '@app/domain'; -import { InjectRepository } from '@nestjs/typeorm'; -import _ from 'lodash'; +} from 'src/interfaces/person.repository'; +import { Paginated, PaginationOptions } from 'src/utils'; import { FindManyOptions, FindOptionsRelations, FindOptionsSelect, In, Repository } from 'typeorm'; -import { AssetEntity, AssetFaceEntity, PersonEntity } from '../entities'; -import { DummyValue, GenerateSql } from '../infra.util'; -import { ChunkedArray, asVector, paginate } from '../infra.utils'; -import { Instrumentation } from '../instrumentation'; @Instrumentation() export class PersonRepository implements IPersonRepository { diff --git a/server/src/infra/repositories/search.repository.ts b/server/src/infra/repositories/search.repository.ts index 0e29506d1..83bbef667 100644 --- a/server/src/infra/repositories/search.repository.ts +++ b/server/src/infra/repositories/search.repository.ts @@ -1,33 +1,29 @@ +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { DummyValue, GenerateSql } from 'src/decorators'; +import { getCLIPModelInfo } from 'src/domain/smart-info/smart-info.constant'; +import { vectorExt } from 'src/infra/database.config'; +import { AssetFaceEntity } from 'src/infra/entities/asset-face.entity'; +import { AssetEntity, AssetType } from 'src/infra/entities/asset.entity'; +import { GeodataPlacesEntity } from 'src/infra/entities/geodata-places.entity'; +import { SmartInfoEntity } from 'src/infra/entities/smart-info.entity'; +import { SmartSearchEntity } from 'src/infra/entities/smart-search.entity'; +import { asVector, paginatedBuilder, searchAssetBuilder } from 'src/infra/infra.utils'; +import { Instrumentation } from 'src/infra/instrumentation'; +import { ImmichLogger } from 'src/infra/logger'; +import { DatabaseExtension } from 'src/interfaces/database.repository'; import { AssetSearchOptions, - DatabaseExtension, Embedding, FaceEmbeddingSearch, FaceSearchResult, ISearchRepository, - Paginated, - PaginationMode, - PaginationResult, SearchPaginationOptions, SmartSearchOptions, -} from '@app/domain'; -import { getCLIPModelInfo } from '@app/domain/smart-info/smart-info.constant'; -import { - AssetEntity, - AssetFaceEntity, - AssetType, - GeodataPlacesEntity, - SmartInfoEntity, - SmartSearchEntity, -} from '@app/infra/entities'; -import { ImmichLogger } from '@app/infra/logger'; -import { Injectable } from '@nestjs/common'; -import { InjectRepository } from '@nestjs/typeorm'; +} from 'src/interfaces/search.repository'; +import { Paginated, PaginationMode, PaginationResult } from 'src/utils'; +import { isValidInteger } from 'src/validation'; import { Repository, SelectQueryBuilder } from 'typeorm'; -import { vectorExt } from '../database.config'; -import { DummyValue, GenerateSql } from '../infra.util'; -import { asVector, isValidInteger, paginatedBuilder, searchAssetBuilder } from '../infra.utils'; -import { Instrumentation } from '../instrumentation'; @Instrumentation() @Injectable() @@ -335,7 +331,7 @@ WITH RECURSIVE cte AS ( ) UNION ALL - + SELECT l.city, l."assetId" FROM cte c , LATERAL ( diff --git a/server/src/infra/repositories/server-info.repository.ts b/server/src/infra/repositories/server-info.repository.ts index bd56a58dd..fcef0a396 100644 --- a/server/src/infra/repositories/server-info.repository.ts +++ b/server/src/infra/repositories/server-info.repository.ts @@ -1,6 +1,6 @@ -import { GitHubRelease, IServerInfoRepository } from '@app/domain'; import { Injectable } from '@nestjs/common'; -import { Instrumentation } from '../instrumentation'; +import { Instrumentation } from 'src/infra/instrumentation'; +import { GitHubRelease, IServerInfoRepository } from 'src/interfaces/server-info.repository'; @Instrumentation() @Injectable() diff --git a/server/src/infra/repositories/shared-link.repository.ts b/server/src/infra/repositories/shared-link.repository.ts index 5e796a772..93aacf1f2 100644 --- a/server/src/infra/repositories/shared-link.repository.ts +++ b/server/src/infra/repositories/shared-link.repository.ts @@ -1,10 +1,10 @@ -import { ISharedLinkRepository } from '@app/domain'; import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; +import { DummyValue, GenerateSql } from 'src/decorators'; +import { SharedLinkEntity } from 'src/infra/entities/shared-link.entity'; +import { Instrumentation } from 'src/infra/instrumentation'; +import { ISharedLinkRepository } from 'src/interfaces/shared-link.repository'; import { Repository } from 'typeorm'; -import { SharedLinkEntity } from '../entities'; -import { DummyValue, GenerateSql } from '../infra.util'; -import { Instrumentation } from '../instrumentation'; @Instrumentation() @Injectable() diff --git a/server/src/infra/repositories/system-config.repository.ts b/server/src/infra/repositories/system-config.repository.ts index 598333d9f..ba3ee0aa0 100644 --- a/server/src/infra/repositories/system-config.repository.ts +++ b/server/src/infra/repositories/system-config.repository.ts @@ -1,11 +1,10 @@ -import { ISystemConfigRepository } from '@app/domain'; import { InjectRepository } from '@nestjs/typeorm'; import { readFile } from 'node:fs/promises'; +import { Chunked, DummyValue, GenerateSql } from 'src/decorators'; +import { SystemConfigEntity } from 'src/infra/entities/system-config.entity'; +import { Instrumentation } from 'src/infra/instrumentation'; +import { ISystemConfigRepository } from 'src/interfaces/system-config.repository'; import { In, Repository } from 'typeorm'; -import { SystemConfigEntity } from '../entities'; -import { DummyValue, GenerateSql } from '../infra.util'; -import { Chunked } from '../infra.utils'; -import { Instrumentation } from '../instrumentation'; @Instrumentation() export class SystemConfigRepository implements ISystemConfigRepository { diff --git a/server/src/infra/repositories/system-metadata.repository.ts b/server/src/infra/repositories/system-metadata.repository.ts index 5b99cd1dd..12c72c090 100644 --- a/server/src/infra/repositories/system-metadata.repository.ts +++ b/server/src/infra/repositories/system-metadata.repository.ts @@ -1,8 +1,8 @@ -import { ISystemMetadataRepository } from '@app/domain/repositories/system-metadata.repository'; import { InjectRepository } from '@nestjs/typeorm'; +import { SystemMetadata, SystemMetadataEntity } from 'src/infra/entities/system-metadata.entity'; +import { Instrumentation } from 'src/infra/instrumentation'; +import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.repository'; import { Repository } from 'typeorm'; -import { SystemMetadata, SystemMetadataEntity } from '../entities'; -import { Instrumentation } from '../instrumentation'; @Instrumentation() export class SystemMetadataRepository implements ISystemMetadataRepository { diff --git a/server/src/infra/repositories/tag.repository.ts b/server/src/infra/repositories/tag.repository.ts index 3ac5afd0e..c44b0763a 100644 --- a/server/src/infra/repositories/tag.repository.ts +++ b/server/src/infra/repositories/tag.repository.ts @@ -1,9 +1,10 @@ -import { ITagRepository } from '@app/domain'; -import { AssetEntity, TagEntity } from '@app/infra/entities'; import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; +import { AssetEntity } from 'src/infra/entities/asset.entity'; +import { TagEntity } from 'src/infra/entities/tag.entity'; +import { Instrumentation } from 'src/infra/instrumentation'; +import { ITagRepository } from 'src/interfaces/tag.repository'; import { Repository } from 'typeorm'; -import { Instrumentation } from '../instrumentation'; @Instrumentation() @Injectable() diff --git a/server/src/infra/repositories/user-token.repository.ts b/server/src/infra/repositories/user-token.repository.ts index 60eccb2e5..19dcafc1e 100644 --- a/server/src/infra/repositories/user-token.repository.ts +++ b/server/src/infra/repositories/user-token.repository.ts @@ -1,10 +1,10 @@ -import { IUserTokenRepository } from '@app/domain'; import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; +import { DummyValue, GenerateSql } from 'src/decorators'; +import { UserTokenEntity } from 'src/infra/entities/user-token.entity'; +import { Instrumentation } from 'src/infra/instrumentation'; +import { IUserTokenRepository } from 'src/interfaces/user-token.repository'; import { Repository } from 'typeorm'; -import { UserTokenEntity } from '../entities'; -import { DummyValue, GenerateSql } from '../infra.util'; -import { Instrumentation } from '../instrumentation'; @Instrumentation() @Injectable() diff --git a/server/src/infra/repositories/user.repository.ts b/server/src/infra/repositories/user.repository.ts index 865a9c8cb..743293766 100644 --- a/server/src/infra/repositories/user.repository.ts +++ b/server/src/infra/repositories/user.repository.ts @@ -1,10 +1,16 @@ -import { IUserRepository, UserFindOptions, UserListFilter, UserStatsQueryResponse } from '@app/domain'; import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; +import { DummyValue, GenerateSql } from 'src/decorators'; +import { AssetEntity } from 'src/infra/entities/asset.entity'; +import { UserEntity } from 'src/infra/entities/user.entity'; +import { Instrumentation } from 'src/infra/instrumentation'; +import { + IUserRepository, + UserFindOptions, + UserListFilter, + UserStatsQueryResponse, +} from 'src/interfaces/user.repository'; import { IsNull, Not, Repository } from 'typeorm'; -import { AssetEntity, UserEntity } from '../entities'; -import { DummyValue, GenerateSql } from '../infra.util'; -import { Instrumentation } from '../instrumentation'; @Instrumentation() @Injectable() diff --git a/server/src/infra/sql-generator/index.ts b/server/src/infra/sql-generator/index.ts index 0b10c018c..a117568d8 100644 --- a/server/src/infra/sql-generator/index.ts +++ b/server/src/infra/sql-generator/index.ts @@ -1,33 +1,31 @@ #!/usr/bin/env node -import { ISystemConfigRepository } from '@app/domain'; import { INestApplication } from '@nestjs/common'; import { Reflector } from '@nestjs/core'; import { Test } from '@nestjs/testing'; import { TypeOrmModule } from '@nestjs/typeorm'; import { mkdir, rm, writeFile } from 'node:fs/promises'; import { join } from 'node:path'; -import { databaseConfig } from '../database.config'; -import { databaseEntities } from '../entities'; -import { GENERATE_SQL_KEY, GenerateSqlQueries } from '../infra.util'; -import { - AccessRepository, - AlbumRepository, - ApiKeyRepository, - AssetRepository, - AuditRepository, - LibraryRepository, - MoveRepository, - PartnerRepository, - PersonRepository, - SearchRepository, - SharedLinkRepository, - SystemConfigRepository, - SystemMetadataRepository, - TagRepository, - UserRepository, - UserTokenRepository, -} from '../repositories'; -import { SqlLogger } from './sql.logger'; +import { GENERATE_SQL_KEY, GenerateSqlQueries } from 'src/decorators'; +import { databaseConfig } from 'src/infra/database.config'; +import { databaseEntities } from 'src/infra/entities'; +import { AccessRepository } from 'src/infra/repositories/access.repository'; +import { AlbumRepository } from 'src/infra/repositories/album.repository'; +import { ApiKeyRepository } from 'src/infra/repositories/api-key.repository'; +import { AssetRepository } from 'src/infra/repositories/asset.repository'; +import { AuditRepository } from 'src/infra/repositories/audit.repository'; +import { LibraryRepository } from 'src/infra/repositories/library.repository'; +import { MoveRepository } from 'src/infra/repositories/move.repository'; +import { PartnerRepository } from 'src/infra/repositories/partner.repository'; +import { PersonRepository } from 'src/infra/repositories/person.repository'; +import { SearchRepository } from 'src/infra/repositories/search.repository'; +import { SharedLinkRepository } from 'src/infra/repositories/shared-link.repository'; +import { SystemConfigRepository } from 'src/infra/repositories/system-config.repository'; +import { SystemMetadataRepository } from 'src/infra/repositories/system-metadata.repository'; +import { TagRepository } from 'src/infra/repositories/tag.repository'; +import { UserTokenRepository } from 'src/infra/repositories/user-token.repository'; +import { UserRepository } from 'src/infra/repositories/user.repository'; +import { SqlLogger } from 'src/infra/sql-generator/sql.logger'; +import { ISystemConfigRepository } from 'src/interfaces/system-config.repository'; const reflector = new Reflector(); const repositories = [ diff --git a/server/src/infra/subscribers/audit.subscriber.ts b/server/src/infra/subscribers/audit.subscriber.ts index 896f9ae5e..ddfc5d0d7 100644 --- a/server/src/infra/subscribers/audit.subscriber.ts +++ b/server/src/infra/subscribers/audit.subscriber.ts @@ -1,5 +1,7 @@ +import { AlbumEntity } from 'src/infra/entities/album.entity'; +import { AssetEntity } from 'src/infra/entities/asset.entity'; +import { AuditEntity, DatabaseAction, EntityType } from 'src/infra/entities/audit.entity'; import { EntitySubscriberInterface, EventSubscriber, RemoveEvent } from 'typeorm'; -import { AlbumEntity, AssetEntity, AuditEntity, DatabaseAction, EntityType } from '../entities'; @EventSubscriber() export class AuditSubscriber implements EntitySubscriberInterface { diff --git a/server/src/domain/repositories/access.repository.ts b/server/src/interfaces/access.repository.ts similarity index 100% rename from server/src/domain/repositories/access.repository.ts rename to server/src/interfaces/access.repository.ts diff --git a/server/src/domain/repositories/activity.repository.ts b/server/src/interfaces/activity.repository.ts similarity index 70% rename from server/src/domain/repositories/activity.repository.ts rename to server/src/interfaces/activity.repository.ts index 6f5476a28..39eb5da6b 100644 --- a/server/src/domain/repositories/activity.repository.ts +++ b/server/src/interfaces/activity.repository.ts @@ -1,5 +1,5 @@ -import { ActivityEntity } from '@app/infra/entities/activity.entity'; -import { ActivitySearch } from '@app/infra/repositories'; +import { ActivityEntity } from 'src/infra/entities/activity.entity'; +import { ActivitySearch } from 'src/infra/repositories/activity.repository'; export const IActivityRepository = 'IActivityRepository'; diff --git a/server/src/domain/repositories/album.repository.ts b/server/src/interfaces/album.repository.ts similarity index 96% rename from server/src/domain/repositories/album.repository.ts rename to server/src/interfaces/album.repository.ts index eb4d4bf3d..213309412 100644 --- a/server/src/domain/repositories/album.repository.ts +++ b/server/src/interfaces/album.repository.ts @@ -1,4 +1,4 @@ -import { AlbumEntity } from '@app/infra/entities'; +import { AlbumEntity } from 'src/infra/entities/album.entity'; export const IAlbumRepository = 'IAlbumRepository'; diff --git a/server/src/domain/repositories/api-key.repository.ts b/server/src/interfaces/api-key.repository.ts similarity index 89% rename from server/src/domain/repositories/api-key.repository.ts rename to server/src/interfaces/api-key.repository.ts index 60f26f235..ff6d97dd5 100644 --- a/server/src/domain/repositories/api-key.repository.ts +++ b/server/src/interfaces/api-key.repository.ts @@ -1,4 +1,4 @@ -import { APIKeyEntity } from '@app/infra/entities'; +import { APIKeyEntity } from 'src/infra/entities/api-key.entity'; export const IKeyRepository = 'IKeyRepository'; diff --git a/server/src/domain/repositories/asset-stack.repository.ts b/server/src/interfaces/asset-stack.repository.ts similarity index 83% rename from server/src/domain/repositories/asset-stack.repository.ts rename to server/src/interfaces/asset-stack.repository.ts index 66201ea3a..32c45b0d1 100644 --- a/server/src/domain/repositories/asset-stack.repository.ts +++ b/server/src/interfaces/asset-stack.repository.ts @@ -1,4 +1,4 @@ -import { AssetStackEntity } from '@app/infra/entities/asset-stack.entity'; +import { AssetStackEntity } from 'src/infra/entities/asset-stack.entity'; export const IAssetStackRepository = 'IAssetStackRepository'; diff --git a/server/src/domain/repositories/asset.repository.ts b/server/src/interfaces/asset.repository.ts similarity index 91% rename from server/src/domain/repositories/asset.repository.ts rename to server/src/interfaces/asset.repository.ts index 7ef9b8943..c0b393ff0 100644 --- a/server/src/domain/repositories/asset.repository.ts +++ b/server/src/interfaces/asset.repository.ts @@ -1,7 +1,11 @@ -import { AssetSearchOptions, ReverseGeocodeResult, SearchExploreItem } from '@app/domain'; -import { AssetEntity, AssetJobStatusEntity, AssetOrder, AssetType, ExifEntity } from '@app/infra/entities'; +import { AssetOrder } from 'src/infra/entities/album.entity'; +import { AssetJobStatusEntity } from 'src/infra/entities/asset-job-status.entity'; +import { AssetEntity, AssetType } from 'src/infra/entities/asset.entity'; +import { ExifEntity } from 'src/infra/entities/exif.entity'; +import { ReverseGeocodeResult } from 'src/interfaces/metadata.repository'; +import { AssetSearchOptions, SearchExploreItem } from 'src/interfaces/search.repository'; +import { Paginated, PaginationOptions } from 'src/utils'; import { FindOptionsRelations, FindOptionsSelect } from 'typeorm'; -import { Paginated, PaginationOptions } from '../domain.util'; export type AssetStats = Record; diff --git a/server/src/domain/repositories/audit.repository.ts b/server/src/interfaces/audit.repository.ts similarity index 77% rename from server/src/domain/repositories/audit.repository.ts rename to server/src/interfaces/audit.repository.ts index 774ab1e42..36112bfb8 100644 --- a/server/src/domain/repositories/audit.repository.ts +++ b/server/src/interfaces/audit.repository.ts @@ -1,4 +1,4 @@ -import { AuditEntity, DatabaseAction, EntityType } from '@app/infra/entities'; +import { AuditEntity, DatabaseAction, EntityType } from 'src/infra/entities/audit.entity'; export const IAuditRepository = 'IAuditRepository'; diff --git a/server/src/domain/repositories/communication.repository.ts b/server/src/interfaces/communication.repository.ts similarity index 89% rename from server/src/domain/repositories/communication.repository.ts rename to server/src/interfaces/communication.repository.ts index 3efbbcb5e..38315a9bb 100644 --- a/server/src/domain/repositories/communication.repository.ts +++ b/server/src/interfaces/communication.repository.ts @@ -1,5 +1,6 @@ -import { AssetResponseDto, ReleaseNotification, ServerVersionResponseDto } from '@app/domain'; -import { SystemConfig } from '@app/infra/entities'; +import { AssetResponseDto } from 'src/domain/asset/response-dto/asset-response.dto'; +import { ReleaseNotification, ServerVersionResponseDto } from 'src/domain/server-info/server-info.dto'; +import { SystemConfig } from 'src/infra/entities/system-config.entity'; export const ICommunicationRepository = 'ICommunicationRepository'; diff --git a/server/src/domain/repositories/crypto.repository.ts b/server/src/interfaces/crypto.repository.ts similarity index 100% rename from server/src/domain/repositories/crypto.repository.ts rename to server/src/interfaces/crypto.repository.ts diff --git a/server/src/domain/repositories/database.repository.ts b/server/src/interfaces/database.repository.ts similarity index 96% rename from server/src/domain/repositories/database.repository.ts rename to server/src/interfaces/database.repository.ts index 55911e7ce..e87775c3e 100644 --- a/server/src/domain/repositories/database.repository.ts +++ b/server/src/interfaces/database.repository.ts @@ -1,4 +1,4 @@ -import { Version } from '../domain.constant'; +import { Version } from 'src/domain/domain.constant'; export enum DatabaseExtension { CUBE = 'cube', diff --git a/server/src/domain/repositories/job.repository.ts b/server/src/interfaces/job.repository.ts similarity index 97% rename from server/src/domain/repositories/job.repository.ts rename to server/src/interfaces/job.repository.ts index 40a26f53d..84b39bbad 100644 --- a/server/src/domain/repositories/job.repository.ts +++ b/server/src/interfaces/job.repository.ts @@ -1,5 +1,4 @@ -import { JobName, QueueName } from '../job/job.constants'; - +import { JobName, QueueName } from 'src/domain/job/job.constants'; import { IAssetDeletionJob, IBaseJob, @@ -9,7 +8,7 @@ import { ILibraryFileJob, ILibraryRefreshJob, ISidecarWriteJob, -} from '../job/job.interface'; +} from 'src/domain/job/job.interface'; export interface JobCounts { active: number; diff --git a/server/src/domain/repositories/library.repository.ts b/server/src/interfaces/library.repository.ts similarity index 84% rename from server/src/domain/repositories/library.repository.ts rename to server/src/interfaces/library.repository.ts index 395373bcc..39b94f87c 100644 --- a/server/src/domain/repositories/library.repository.ts +++ b/server/src/interfaces/library.repository.ts @@ -1,5 +1,5 @@ -import { LibraryEntity, LibraryType } from '@app/infra/entities'; -import { LibraryStatsResponseDto } from '../library/library.dto'; +import { LibraryStatsResponseDto } from 'src/domain/library/library.dto'; +import { LibraryEntity, LibraryType } from 'src/infra/entities/library.entity'; export const ILibraryRepository = 'ILibraryRepository'; diff --git a/server/src/domain/repositories/machine-learning.repository.ts b/server/src/interfaces/machine-learning.repository.ts similarity index 90% rename from server/src/domain/repositories/machine-learning.repository.ts rename to server/src/interfaces/machine-learning.repository.ts index f327a7a70..d11e2e8f7 100644 --- a/server/src/domain/repositories/machine-learning.repository.ts +++ b/server/src/interfaces/machine-learning.repository.ts @@ -1,4 +1,4 @@ -import { CLIPConfig, RecognitionConfig } from '../smart-info/dto'; +import { CLIPConfig, RecognitionConfig } from 'src/domain/smart-info/dto/model-config.dto'; export const IMachineLearningRepository = 'IMachineLearningRepository'; diff --git a/server/src/domain/repositories/media.repository.ts b/server/src/interfaces/media.repository.ts similarity index 95% rename from server/src/domain/repositories/media.repository.ts rename to server/src/interfaces/media.repository.ts index ed6f88449..09bcd7eef 100644 --- a/server/src/domain/repositories/media.repository.ts +++ b/server/src/interfaces/media.repository.ts @@ -1,5 +1,5 @@ -import { TranscodeTarget, VideoCodec } from '@app/infra/entities'; import { Writable } from 'node:stream'; +import { TranscodeTarget, VideoCodec } from 'src/infra/entities/system-config.entity'; export const IMediaRepository = 'IMediaRepository'; diff --git a/server/src/domain/repositories/metadata.repository.ts b/server/src/interfaces/metadata.repository.ts similarity index 100% rename from server/src/domain/repositories/metadata.repository.ts rename to server/src/interfaces/metadata.repository.ts diff --git a/server/src/domain/repositories/move.repository.ts b/server/src/interfaces/move.repository.ts similarity index 86% rename from server/src/domain/repositories/move.repository.ts rename to server/src/interfaces/move.repository.ts index 20caa117f..eb191021f 100644 --- a/server/src/domain/repositories/move.repository.ts +++ b/server/src/interfaces/move.repository.ts @@ -1,4 +1,4 @@ -import { MoveEntity, PathType } from '@app/infra/entities'; +import { MoveEntity, PathType } from 'src/infra/entities/move.entity'; export const IMoveRepository = 'IMoveRepository'; diff --git a/server/src/domain/repositories/partner.repository.ts b/server/src/interfaces/partner.repository.ts similarity index 89% rename from server/src/domain/repositories/partner.repository.ts rename to server/src/interfaces/partner.repository.ts index f0409b67a..5606fc495 100644 --- a/server/src/domain/repositories/partner.repository.ts +++ b/server/src/interfaces/partner.repository.ts @@ -1,4 +1,4 @@ -import { PartnerEntity } from '@app/infra/entities'; +import { PartnerEntity } from 'src/infra/entities/partner.entity'; export interface PartnerIds { sharedById: string; diff --git a/server/src/domain/repositories/person.repository.ts b/server/src/interfaces/person.repository.ts similarity index 89% rename from server/src/domain/repositories/person.repository.ts rename to server/src/interfaces/person.repository.ts index 85c11fe92..f00fce44e 100644 --- a/server/src/domain/repositories/person.repository.ts +++ b/server/src/interfaces/person.repository.ts @@ -1,6 +1,8 @@ -import { AssetEntity, AssetFaceEntity, PersonEntity } from '@app/infra/entities'; +import { AssetFaceEntity } from 'src/infra/entities/asset-face.entity'; +import { AssetEntity } from 'src/infra/entities/asset.entity'; +import { PersonEntity } from 'src/infra/entities/person.entity'; +import { Paginated, PaginationOptions } from 'src/utils'; import { FindManyOptions, FindOptionsRelations, FindOptionsSelect } from 'typeorm'; -import { Paginated, PaginationOptions } from '../domain.util'; export const IPersonRepository = 'IPersonRepository'; diff --git a/server/src/domain/repositories/search.repository.ts b/server/src/interfaces/search.repository.ts similarity index 92% rename from server/src/domain/repositories/search.repository.ts rename to server/src/interfaces/search.repository.ts index bd4face86..a54147f1f 100644 --- a/server/src/domain/repositories/search.repository.ts +++ b/server/src/interfaces/search.repository.ts @@ -1,5 +1,8 @@ -import { AssetEntity, AssetFaceEntity, AssetType, GeodataPlacesEntity, SmartInfoEntity } from '@app/infra/entities'; -import { Paginated } from '../domain.util'; +import { AssetFaceEntity } from 'src/infra/entities/asset-face.entity'; +import { AssetEntity, AssetType } from 'src/infra/entities/asset.entity'; +import { GeodataPlacesEntity } from 'src/infra/entities/geodata-places.entity'; +import { SmartInfoEntity } from 'src/infra/entities/smart-info.entity'; +import { Paginated } from 'src/utils'; export const ISearchRepository = 'ISearchRepository'; diff --git a/server/src/domain/repositories/server-info.repository.ts b/server/src/interfaces/server-info.repository.ts similarity index 100% rename from server/src/domain/repositories/server-info.repository.ts rename to server/src/interfaces/server-info.repository.ts diff --git a/server/src/domain/repositories/shared-link.repository.ts b/server/src/interfaces/shared-link.repository.ts similarity index 86% rename from server/src/domain/repositories/shared-link.repository.ts rename to server/src/interfaces/shared-link.repository.ts index 0f0255d0a..0d5e8eca7 100644 --- a/server/src/domain/repositories/shared-link.repository.ts +++ b/server/src/interfaces/shared-link.repository.ts @@ -1,4 +1,4 @@ -import { SharedLinkEntity } from '@app/infra/entities'; +import { SharedLinkEntity } from 'src/infra/entities/shared-link.entity'; export const ISharedLinkRepository = 'ISharedLinkRepository'; diff --git a/server/src/domain/repositories/storage.repository.ts b/server/src/interfaces/storage.repository.ts similarity index 96% rename from server/src/domain/repositories/storage.repository.ts rename to server/src/interfaces/storage.repository.ts index a052596c0..505e535b0 100644 --- a/server/src/domain/repositories/storage.repository.ts +++ b/server/src/interfaces/storage.repository.ts @@ -2,7 +2,7 @@ import { WatchOptions } from 'chokidar'; import { Stats } from 'node:fs'; import { FileReadOptions } from 'node:fs/promises'; import { Readable } from 'node:stream'; -import { CrawlOptionsDto } from '../library'; +import { CrawlOptionsDto } from 'src/domain/library/library.dto'; export interface ImmichReadStream { stream: Readable; diff --git a/server/src/domain/repositories/system-config.repository.ts b/server/src/interfaces/system-config.repository.ts similarity index 82% rename from server/src/domain/repositories/system-config.repository.ts rename to server/src/interfaces/system-config.repository.ts index d154f6eff..86b311485 100644 --- a/server/src/domain/repositories/system-config.repository.ts +++ b/server/src/interfaces/system-config.repository.ts @@ -1,4 +1,4 @@ -import { SystemConfigEntity } from '@app/infra/entities'; +import { SystemConfigEntity } from 'src/infra/entities/system-config.entity'; export const ISystemConfigRepository = 'ISystemConfigRepository'; diff --git a/server/src/domain/repositories/system-metadata.repository.ts b/server/src/interfaces/system-metadata.repository.ts similarity index 79% rename from server/src/domain/repositories/system-metadata.repository.ts rename to server/src/interfaces/system-metadata.repository.ts index 4d571953b..d28713226 100644 --- a/server/src/domain/repositories/system-metadata.repository.ts +++ b/server/src/interfaces/system-metadata.repository.ts @@ -1,4 +1,4 @@ -import { SystemMetadata } from '@app/infra/entities'; +import { SystemMetadata } from 'src/infra/entities/system-metadata.entity'; export const ISystemMetadataRepository = 'ISystemMetadataRepository'; diff --git a/server/src/domain/repositories/tag.repository.ts b/server/src/interfaces/tag.repository.ts similarity index 85% rename from server/src/domain/repositories/tag.repository.ts rename to server/src/interfaces/tag.repository.ts index 4e6f583b4..47c3f40fa 100644 --- a/server/src/domain/repositories/tag.repository.ts +++ b/server/src/interfaces/tag.repository.ts @@ -1,4 +1,5 @@ -import { AssetEntity, TagEntity } from '@app/infra/entities'; +import { AssetEntity } from 'src/infra/entities/asset.entity'; +import { TagEntity } from 'src/infra/entities/tag.entity'; export const ITagRepository = 'ITagRepository'; diff --git a/server/src/domain/repositories/user-token.repository.ts b/server/src/interfaces/user-token.repository.ts similarity index 84% rename from server/src/domain/repositories/user-token.repository.ts rename to server/src/interfaces/user-token.repository.ts index 713b3f1ef..2d342a2fc 100644 --- a/server/src/domain/repositories/user-token.repository.ts +++ b/server/src/interfaces/user-token.repository.ts @@ -1,4 +1,4 @@ -import { UserTokenEntity } from '@app/infra/entities'; +import { UserTokenEntity } from 'src/infra/entities/user-token.entity'; export const IUserTokenRepository = 'IUserTokenRepository'; diff --git a/server/src/domain/repositories/user.repository.ts b/server/src/interfaces/user.repository.ts similarity index 95% rename from server/src/domain/repositories/user.repository.ts rename to server/src/interfaces/user.repository.ts index efd950318..be5ef165d 100644 --- a/server/src/domain/repositories/user.repository.ts +++ b/server/src/interfaces/user.repository.ts @@ -1,4 +1,4 @@ -import { UserEntity } from '@app/infra/entities'; +import { UserEntity } from 'src/infra/entities/user.entity'; export interface UserListFilter { withDeleted?: boolean; diff --git a/server/src/main.ts b/server/src/main.ts index 198b0f087..d59875133 100644 --- a/server/src/main.ts +++ b/server/src/main.ts @@ -1,6 +1,6 @@ -import { bootstrap as admin } from './immich-admin/main'; -import { bootstrap as server } from './immich/main'; -import { bootstrap as microservices } from './microservices/main'; +import { bootstrap as admin } from 'src/immich-admin/main'; +import { bootstrap as server } from 'src/immich/main'; +import { bootstrap as microservices } from 'src/microservices/main'; const immichApp = process.argv[2] || process.env.IMMICH_APP; diff --git a/server/src/microservices/app.service.ts b/server/src/microservices/app.service.ts index 8d17bcafa..9d83e0c3b 100644 --- a/server/src/microservices/app.service.ts +++ b/server/src/microservices/app.service.ts @@ -1,22 +1,20 @@ -import { - AssetService, - AuditService, - DatabaseService, - IDeleteFilesJob, - JobName, - JobService, - LibraryService, - MediaService, - MetadataService, - PersonService, - SmartInfoService, - StorageService, - StorageTemplateService, - SystemConfigService, - UserService, -} from '@app/domain'; -import { otelSDK } from '@app/infra/instrumentation'; import { Injectable } from '@nestjs/common'; +import { AssetService } from 'src/domain/asset/asset.service'; +import { AuditService } from 'src/domain/audit/audit.service'; +import { DatabaseService } from 'src/domain/database/database.service'; +import { JobName } from 'src/domain/job/job.constants'; +import { IDeleteFilesJob } from 'src/domain/job/job.interface'; +import { JobService } from 'src/domain/job/job.service'; +import { LibraryService } from 'src/domain/library/library.service'; +import { MediaService } from 'src/domain/media/media.service'; +import { MetadataService } from 'src/domain/metadata/metadata.service'; +import { PersonService } from 'src/domain/person/person.service'; +import { SmartInfoService } from 'src/domain/smart-info/smart-info.service'; +import { StorageTemplateService } from 'src/domain/storage-template/storage-template.service'; +import { StorageService } from 'src/domain/storage/storage.service'; +import { SystemConfigService } from 'src/domain/system-config/system-config.service'; +import { UserService } from 'src/domain/user/user.service'; +import { otelSDK } from 'src/infra/instrumentation'; @Injectable() export class AppService { diff --git a/server/src/microservices/main.ts b/server/src/microservices/main.ts index f7dc64f57..01949dd02 100644 --- a/server/src/microservices/main.ts +++ b/server/src/microservices/main.ts @@ -1,9 +1,9 @@ -import { envName, serverVersion } from '@app/domain'; -import { WebSocketAdapter } from '@app/infra'; -import { otelSDK } from '@app/infra/instrumentation'; -import { ImmichLogger } from '@app/infra/logger'; import { NestFactory } from '@nestjs/core'; -import { MicroservicesModule } from './microservices.module'; +import { envName, serverVersion } from 'src/domain/domain.constant'; +import { otelSDK } from 'src/infra/instrumentation'; +import { ImmichLogger } from 'src/infra/logger'; +import { WebSocketAdapter } from 'src/infra/websocket.adapter'; +import { MicroservicesModule } from 'src/microservices/microservices.module'; const logger = new ImmichLogger('ImmichMicroservice'); const port = Number(process.env.MICROSERVICES_PORT) || 3002; diff --git a/server/src/microservices/microservices.module.ts b/server/src/microservices/microservices.module.ts index 4768d965f..6fc9f0890 100644 --- a/server/src/microservices/microservices.module.ts +++ b/server/src/microservices/microservices.module.ts @@ -1,7 +1,7 @@ -import { DomainModule } from '@app/domain'; -import { InfraModule } from '@app/infra'; import { Module, OnModuleInit } from '@nestjs/common'; -import { AppService } from './app.service'; +import { DomainModule } from 'src/domain/domain.module'; +import { InfraModule } from 'src/infra/infra.module'; +import { AppService } from 'src/microservices/app.service'; @Module({ imports: [InfraModule, DomainModule], diff --git a/server/src/microservices/utils/exif/coordinates.ts b/server/src/microservices/utils/exif/coordinates.ts deleted file mode 100644 index 03aeb17f0..000000000 --- a/server/src/microservices/utils/exif/coordinates.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { isNumberInRange } from '../numbers'; - -export function parseLatitude(input: string | number | null): number | null { - if (input === null) { - return null; - } - const latitude = typeof input === 'string' ? Number.parseFloat(input) : input; - - if (isNumberInRange(latitude, -90, 90)) { - return latitude; - } - return null; -} - -export function parseLongitude(input: string | number | null): number | null { - if (input === null) { - return null; - } - - const longitude = typeof input === 'string' ? Number.parseFloat(input) : input; - - if (isNumberInRange(longitude, -180, 180)) { - return longitude; - } - return null; -} diff --git a/server/src/microservices/utils/numbers.spec.ts b/server/src/microservices/utils/numbers.spec.ts deleted file mode 100644 index 47f95b8aa..000000000 --- a/server/src/microservices/utils/numbers.spec.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { isDecimalNumber, isNumberInRange, toNumberOrNull } from './numbers'; - -describe('checks if a number is a decimal number', () => { - it('returns false for non-decimal numbers', () => { - expect(isDecimalNumber(Number.NaN)).toBe(false); - expect(isDecimalNumber(Number.POSITIVE_INFINITY)).toBe(false); - expect(isDecimalNumber(Number.NEGATIVE_INFINITY)).toBe(false); - }); - - it('returns true for decimal numbers', () => { - expect(isDecimalNumber(0)).toBe(true); - expect(isDecimalNumber(-0)).toBe(true); - expect(isDecimalNumber(10.123_45)).toBe(true); - expect(isDecimalNumber(Number.MAX_VALUE)).toBe(true); - expect(isDecimalNumber(Number.MIN_VALUE)).toBe(true); - }); -}); - -describe('checks if a number is within a range', () => { - it('returns false for numbers outside the range', () => { - expect(isNumberInRange(0, 10, 10)).toBe(false); - expect(isNumberInRange(0.01, 10, 10)).toBe(false); - expect(isNumberInRange(50.1, 0, 50)).toBe(false); - }); - - it('returns true for numbers inside the range', () => { - expect(isNumberInRange(0, 0, 50)).toBe(true); - expect(isNumberInRange(50, 0, 50)).toBe(true); - expect(isNumberInRange(-50.123_45, -50.123_45, 0)).toBe(true); - }); -}); - -describe('converts input to a number or null', () => { - it('returns null for invalid inputs', () => { - expect(toNumberOrNull(null)).toBeNull(); - // eslint-disable-next-line unicorn/no-useless-undefined - expect(toNumberOrNull(undefined)).toBeNull(); - expect(toNumberOrNull('')).toBeNull(); - expect(toNumberOrNull(Number.NaN)).toBeNull(); - }); - - it('returns a number for valid inputs', () => { - expect(toNumberOrNull(0)).toBeCloseTo(0); - expect(toNumberOrNull('0')).toBeCloseTo(0); - expect(toNumberOrNull('-123.45')).toBeCloseTo(-123.45); - }); -}); diff --git a/server/src/microservices/utils/numbers.ts b/server/src/microservices/utils/numbers.ts deleted file mode 100644 index cd6e81d2a..000000000 --- a/server/src/microservices/utils/numbers.ts +++ /dev/null @@ -1,19 +0,0 @@ -export function isDecimalNumber(number_: number): boolean { - return !Number.isNaN(number_) && Number.isFinite(number_); -} - -/** - * Check if `num` is a valid number and is between `start` and `end` (inclusive) - */ -export function isNumberInRange(number_: number, start: number, end: number): boolean { - return isDecimalNumber(number_) && number_ >= start && number_ <= end; -} - -export function toNumberOrNull(input: number | string | null | undefined): number | null { - if (input === null || input === undefined) { - return null; - } - - const number_ = typeof input === 'string' ? Number.parseFloat(input) : input; - return isDecimalNumber(number_) ? number_ : null; -} diff --git a/server/src/immich/app.guard.ts b/server/src/middleware/auth.guard.ts similarity index 90% rename from server/src/immich/app.guard.ts rename to server/src/middleware/auth.guard.ts index bd07d107b..070bf15e8 100644 --- a/server/src/immich/app.guard.ts +++ b/server/src/middleware/auth.guard.ts @@ -1,5 +1,3 @@ -import { AuthDto, AuthService, IMMICH_API_KEY_NAME, LoginDetails } from '@app/domain'; -import { ImmichLogger } from '@app/infra/logger'; import { CanActivate, ExecutionContext, @@ -11,6 +9,10 @@ import { import { Reflector } from '@nestjs/core'; import { ApiBearerAuth, ApiCookieAuth, ApiOkResponse, ApiQuery, ApiSecurity } from '@nestjs/swagger'; import { Request } from 'express'; +import { IMMICH_API_KEY_NAME } from 'src/domain/auth/auth.constant'; +import { AuthDto } from 'src/domain/auth/auth.dto'; +import { AuthService, LoginDetails } from 'src/domain/auth/auth.service'; +import { ImmichLogger } from 'src/infra/logger'; import { UAParser } from 'ua-parser-js'; export enum Metadata { @@ -76,8 +78,8 @@ export interface AuthRequest extends Request { } @Injectable() -export class AppGuard implements CanActivate { - private logger = new ImmichLogger(AppGuard.name); +export class AuthGuard implements CanActivate { + private logger = new ImmichLogger(AuthGuard.name); constructor( private reflector: Reflector, diff --git a/server/src/immich/interceptors/error.interceptor.ts b/server/src/middleware/error.interceptor.ts similarity index 85% rename from server/src/immich/interceptors/error.interceptor.ts rename to server/src/middleware/error.interceptor.ts index 5fabdbe55..70c129922 100644 --- a/server/src/immich/interceptors/error.interceptor.ts +++ b/server/src/middleware/error.interceptor.ts @@ -1,4 +1,3 @@ -import { ImmichLogger } from '@app/infra/logger'; import { CallHandler, ExecutionContext, @@ -8,8 +7,9 @@ import { NestInterceptor, } from '@nestjs/common'; import { Observable, catchError, throwError } from 'rxjs'; -import { isConnectionAborted } from '../../domain'; -import { routeToErrorMessage } from '../app.utils'; +import { routeToErrorMessage } from 'src/immich/app.utils'; +import { ImmichLogger } from 'src/infra/logger'; +import { isConnectionAborted } from 'src/utils'; @Injectable() export class ErrorInterceptor implements NestInterceptor { diff --git a/server/src/immich/interceptors/file-upload.interceptor.ts b/server/src/middleware/file-upload.interceptor.ts similarity index 96% rename from server/src/immich/interceptors/file-upload.interceptor.ts rename to server/src/middleware/file-upload.interceptor.ts index a698dc8a6..a7598f99d 100644 --- a/server/src/immich/interceptors/file-upload.interceptor.ts +++ b/server/src/middleware/file-upload.interceptor.ts @@ -1,5 +1,3 @@ -import { AssetService, UploadFieldName, UploadFile } from '@app/domain'; -import { ImmichLogger } from '@app/infra/logger'; import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from '@nestjs/common'; import { PATH_METADATA } from '@nestjs/common/constants'; import { Reflector } from '@nestjs/core'; @@ -8,7 +6,9 @@ import { NextFunction, RequestHandler } from 'express'; import multer, { StorageEngine, diskStorage } from 'multer'; import { createHash, randomUUID } from 'node:crypto'; import { Observable } from 'rxjs'; -import { AuthRequest } from '../app.guard'; +import { AssetService, UploadFieldName, UploadFile } from 'src/domain/asset/asset.service'; +import { ImmichLogger } from 'src/infra/logger'; +import { AuthRequest } from 'src/middleware/auth.guard'; export enum Route { ASSET = 'asset', diff --git a/server/src/test-utils/utils.ts b/server/src/test-utils/utils.ts index cf9822295..b9e74f767 100644 --- a/server/src/test-utils/utils.ts +++ b/server/src/test-utils/utils.ts @@ -1,18 +1,22 @@ -import { IJobRepository, IMediaRepository, JobItem, JobItemHandler, QueueName, StorageEventType } from '@app/domain'; -import { AppModule } from '@app/immich'; -import { InfraModule, InfraTestModule, dataSource } from '@app/infra'; -import { MediaRepository } from '@app/infra/repositories'; import { INestApplication } from '@nestjs/common'; import { Test } from '@nestjs/testing'; import { DateTime } from 'luxon'; -import * as fs from 'node:fs'; +import fs from 'node:fs'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; import { EventEmitter } from 'node:stream'; import { Server } from 'node:tls'; +import { QueueName } from 'src/domain/job/job.constants'; +import { AppModule } from 'src/immich/app.module'; +import { AppService } from 'src/immich/app.service'; +import { dataSource } from 'src/infra/database.config'; +import { InfraModule, InfraTestModule } from 'src/infra/infra.module'; +import { MediaRepository } from 'src/infra/repositories/media.repository'; +import { IJobRepository, JobItem, JobItemHandler } from 'src/interfaces/job.repository'; +import { IMediaRepository } from 'src/interfaces/media.repository'; +import { StorageEventType } from 'src/interfaces/storage.repository'; +import { AppService as MicroAppService } from 'src/microservices/app.service'; import { EntityTarget, ObjectLiteral } from 'typeorm'; -import { AppService } from '../immich/app.service'; -import { AppService as MicroAppService } from '../microservices/app.service'; export const IMMICH_TEST_ASSET_PATH = process.env.IMMICH_TEST_ASSET_PATH as string; export const IMMICH_TEST_ASSET_TEMP_PATH = join(tmpdir(), 'immich'); diff --git a/server/src/microservices/utils/exif/coordinates.spec.ts b/server/src/utils.spec.ts similarity index 53% rename from server/src/microservices/utils/exif/coordinates.spec.ts rename to server/src/utils.spec.ts index b9644fb49..c5ae5f6e5 100644 --- a/server/src/microservices/utils/exif/coordinates.spec.ts +++ b/server/src/utils.spec.ts @@ -1,4 +1,50 @@ -import { parseLatitude, parseLongitude } from './coordinates'; +import { isDecimalNumber, isNumberInRange, parseLatitude, parseLongitude, toNumberOrNull } from 'src/utils'; + +describe('checks if a number is a decimal number', () => { + it('returns false for non-decimal numbers', () => { + expect(isDecimalNumber(Number.NaN)).toBe(false); + expect(isDecimalNumber(Number.POSITIVE_INFINITY)).toBe(false); + expect(isDecimalNumber(Number.NEGATIVE_INFINITY)).toBe(false); + }); + + it('returns true for decimal numbers', () => { + expect(isDecimalNumber(0)).toBe(true); + expect(isDecimalNumber(-0)).toBe(true); + expect(isDecimalNumber(10.123_45)).toBe(true); + expect(isDecimalNumber(Number.MAX_VALUE)).toBe(true); + expect(isDecimalNumber(Number.MIN_VALUE)).toBe(true); + }); +}); + +describe('checks if a number is within a range', () => { + it('returns false for numbers outside the range', () => { + expect(isNumberInRange(0, 10, 10)).toBe(false); + expect(isNumberInRange(0.01, 10, 10)).toBe(false); + expect(isNumberInRange(50.1, 0, 50)).toBe(false); + }); + + it('returns true for numbers inside the range', () => { + expect(isNumberInRange(0, 0, 50)).toBe(true); + expect(isNumberInRange(50, 0, 50)).toBe(true); + expect(isNumberInRange(-50.123_45, -50.123_45, 0)).toBe(true); + }); +}); + +describe('converts input to a number or null', () => { + it('returns null for invalid inputs', () => { + expect(toNumberOrNull(null)).toBeNull(); + // eslint-disable-next-line unicorn/no-useless-undefined + expect(toNumberOrNull(undefined)).toBeNull(); + expect(toNumberOrNull('')).toBeNull(); + expect(toNumberOrNull(Number.NaN)).toBeNull(); + }); + + it('returns a number for valid inputs', () => { + expect(toNumberOrNull(0)).toBeCloseTo(0); + expect(toNumberOrNull('0')).toBeCloseTo(0); + expect(toNumberOrNull('-123.45')).toBeCloseTo(-123.45); + }); +}); describe('parsing latitude from string input', () => { it('returns null for invalid inputs', () => { diff --git a/server/src/utils.ts b/server/src/utils.ts new file mode 100644 index 000000000..863dde7ca --- /dev/null +++ b/server/src/utils.ts @@ -0,0 +1,181 @@ +import { basename, extname } from 'node:path'; +import { ImmichLogger } from 'src/infra/logger'; + +const KiB = Math.pow(1024, 1); +const MiB = Math.pow(1024, 2); +const GiB = Math.pow(1024, 3); +const TiB = Math.pow(1024, 4); +const PiB = Math.pow(1024, 5); + +export const HumanReadableSize = { KiB, MiB, GiB, TiB, PiB }; + +export function asHumanReadable(bytes: number, precision = 1): string { + const units = ['B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB']; + + let magnitude = 0; + let remainder = bytes; + while (remainder >= 1024) { + if (magnitude + 1 < units.length) { + magnitude++; + remainder /= 1024; + } else { + break; + } + } + + return `${remainder.toFixed(magnitude == 0 ? 0 : precision)} ${units[magnitude]}`; +} + +export const isConnectionAborted = (error: Error | any) => error.code === 'ECONNABORTED'; + +export function isDecimalNumber(number_: number): boolean { + return !Number.isNaN(number_) && Number.isFinite(number_); +} + +/** + * Check if `num` is a valid number and is between `start` and `end` (inclusive) + */ +export function isNumberInRange(number_: number, start: number, end: number): boolean { + return isDecimalNumber(number_) && number_ >= start && number_ <= end; +} + +export function toNumberOrNull(input: number | string | null | undefined): number | null { + if (input === null || input === undefined) { + return null; + } + + const number_ = typeof input === 'string' ? Number.parseFloat(input) : input; + return isDecimalNumber(number_) ? number_ : null; +} + +export function parseLatitude(input: string | number | null): number | null { + if (input === null) { + return null; + } + const latitude = typeof input === 'string' ? Number.parseFloat(input) : input; + + if (isNumberInRange(latitude, -90, 90)) { + return latitude; + } + return null; +} + +export function parseLongitude(input: string | number | null): number | null { + if (input === null) { + return null; + } + + const longitude = typeof input === 'string' ? Number.parseFloat(input) : input; + + if (isNumberInRange(longitude, -180, 180)) { + return longitude; + } + return null; +} + +// NOTE: The following Set utils have been added here, to easily determine where they are used. +// They should be replaced with native Set operations, when they are added to the language. +// Proposal reference: https://github.com/tc39/proposal-set-methods + +export const setUnion = (...sets: Set[]): Set => { + const union = new Set(sets[0]); + for (const set of sets.slice(1)) { + for (const element of set) { + union.add(element); + } + } + return union; +}; + +export const setDifference = (setA: Set, ...sets: Set[]): Set => { + const difference = new Set(setA); + for (const set of sets) { + for (const element of set) { + difference.delete(element); + } + } + return difference; +}; + +export const setIsSuperset = (set: Set, subset: Set): boolean => { + for (const element of subset) { + if (!set.has(element)) { + return false; + } + } + return true; +}; + +export const setIsEqual = (setA: Set, setB: Set): boolean => { + return setA.size === setB.size && setIsSuperset(setA, setB); +}; + +export const handlePromiseError = (promise: Promise, logger: ImmichLogger): void => { + promise.catch((error: Error | any) => logger.error(`Promise error: ${error}`, error?.stack)); +}; + +export enum CacheControl { + PRIVATE_WITH_CACHE = 'private_with_cache', + PRIVATE_WITHOUT_CACHE = 'private_without_cache', + NONE = 'none', +} + +export class ImmichFileResponse { + public readonly path!: string; + public readonly contentType!: string; + public readonly cacheControl!: CacheControl; + + constructor(response: ImmichFileResponse) { + Object.assign(this, response); + } +} + +export interface OpenGraphTags { + title: string; + description: string; + imageUrl?: string; +} + +export function getFileNameWithoutExtension(path: string): string { + return basename(path, extname(path)); +} + +export function getLivePhotoMotionFilename(stillName: string, motionName: string) { + return getFileNameWithoutExtension(stillName) + extname(motionName); +} + +export interface PaginationOptions { + take: number; + skip?: number; +} + +export enum PaginationMode { + LIMIT_OFFSET = 'limit-offset', + SKIP_TAKE = 'skip-take', +} + +export interface PaginatedBuilderOptions { + take: number; + skip?: number; + mode?: PaginationMode; +} + +export interface PaginationResult { + items: T[]; + hasNextPage: boolean; +} + +export type Paginated = Promise>; + +export async function* usePagination( + pageSize: number, + getNextPage: (pagination: PaginationOptions) => PaginationResult | Paginated, +) { + let hasNextPage = true; + + for (let skip = 0; hasNextPage; skip += pageSize) { + const result = await getNextPage({ take: pageSize, skip }); + hasNextPage = result.hasNextPage; + yield result.items; + } +} diff --git a/server/src/validation.ts b/server/src/validation.ts new file mode 100644 index 000000000..bc1dbae81 --- /dev/null +++ b/server/src/validation.ts @@ -0,0 +1,164 @@ +import { + ArgumentMetadata, + BadRequestException, + FileValidator, + Injectable, + ParseUUIDPipe, + applyDecorators, +} from '@nestjs/common'; +import { ApiProperty } from '@nestjs/swagger'; +import { Transform } from 'class-transformer'; +import { + IsArray, + IsBoolean, + IsDate, + IsNotEmpty, + IsOptional, + IsString, + IsUUID, + ValidateIf, + ValidationOptions, + isDateString, +} from 'class-validator'; +import { CronJob } from 'cron'; +import sanitize from 'sanitize-filename'; + +@Injectable() +export class ParseMeUUIDPipe extends ParseUUIDPipe { + async transform(value: string, metadata: ArgumentMetadata) { + if (value == 'me') { + return value; + } + return super.transform(value, metadata); + } +} + +@Injectable() +export class FileNotEmptyValidator extends FileValidator { + constructor(private requiredFields: string[]) { + super({}); + this.requiredFields = requiredFields; + } + + isValid(files?: any): boolean { + if (!files) { + return false; + } + + return this.requiredFields.every((field) => files[field]); + } + + buildErrorMessage(): string { + return `Field(s) ${this.requiredFields.join(', ')} should not be empty`; + } +} + +export class UUIDParamDto { + @IsNotEmpty() + @IsUUID('4') + @ApiProperty({ format: 'uuid' }) + id!: string; +} + +export interface OptionalOptions extends ValidationOptions { + nullable?: boolean; +} + +/** + * Checks if value is missing and if so, ignores all validators. + * + * @param validationOptions {@link OptionalOptions} + * + * @see IsOptional exported from `class-validator. + */ +// https://stackoverflow.com/a/71353929 +export function Optional({ nullable, ...validationOptions }: OptionalOptions = {}) { + if (nullable === true) { + return IsOptional(validationOptions); + } + + return ValidateIf((object: any, v: any) => v !== undefined, validationOptions); +} + +type UUIDOptions = { optional?: boolean; each?: boolean }; +export const ValidateUUID = (options?: UUIDOptions) => { + const { optional, each } = { optional: false, each: false, ...options }; + return applyDecorators( + IsUUID('4', { each }), + ApiProperty({ format: 'uuid' }), + optional ? Optional() : IsNotEmpty(), + each ? IsArray() : IsString(), + ); +}; + +type DateOptions = { optional?: boolean; nullable?: boolean; format?: 'date' | 'date-time' }; +export const ValidateDate = (options?: DateOptions) => { + const { optional, nullable, format } = { optional: false, nullable: false, format: 'date-time', ...options }; + + const decorators = [ + ApiProperty({ format }), + IsDate(), + optional ? Optional({ nullable: true }) : IsNotEmpty(), + Transform(({ key, value }) => { + if (value === null || value === undefined) { + return value; + } + + if (!isDateString(value)) { + throw new BadRequestException(`${key} must be a date string`); + } + + return new Date(value as string); + }), + ]; + + if (optional) { + decorators.push(Optional({ nullable })); + } + + return applyDecorators(...decorators); +}; + +type BooleanOptions = { optional?: boolean }; +export const ValidateBoolean = (options?: BooleanOptions) => { + const { optional } = { optional: false, ...options }; + const decorators = [ + // ApiProperty(), + IsBoolean(), + Transform(({ value }) => { + if (value == 'true') { + return true; + } else if (value == 'false') { + return false; + } + return value; + }), + ]; + + if (optional) { + decorators.push(Optional()); + } + + return applyDecorators(...decorators); +}; + +export function validateCronExpression(expression: string) { + try { + new CronJob(expression, () => {}); + } catch { + return false; + } + + return true; +} + +type IValue = { value: string }; + +export const toEmail = ({ value }: IValue) => value?.toLowerCase(); + +export const toSanitized = ({ value }: IValue) => sanitize((value || '').replaceAll('.', '')); + +export const isValidInteger = (value: number, options: { min?: number; max?: number }): value is number => { + const { min = Number.MIN_SAFE_INTEGER, max = Number.MAX_SAFE_INTEGER } = options; + return Number.isInteger(value) && value >= min && value <= max; +}; diff --git a/server/test/fixtures/activity.stub.ts b/server/test/fixtures/activity.stub.ts index 7a3aae5ff..ae33699a0 100644 --- a/server/test/fixtures/activity.stub.ts +++ b/server/test/fixtures/activity.stub.ts @@ -1,8 +1,8 @@ -import { ActivityEntity } from '@app/infra/entities'; -import { albumStub } from './album.stub'; -import { assetStub } from './asset.stub'; -import { authStub } from './auth.stub'; -import { userStub } from './user.stub'; +import { ActivityEntity } from 'src/infra/entities/activity.entity'; +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({ diff --git a/server/test/fixtures/album.stub.ts b/server/test/fixtures/album.stub.ts index bfb6acb6d..18be7d4ff 100644 --- a/server/test/fixtures/album.stub.ts +++ b/server/test/fixtures/album.stub.ts @@ -1,7 +1,7 @@ -import { AlbumEntity, AssetOrder } from '@app/infra/entities'; -import { assetStub } from './asset.stub'; -import { authStub } from './auth.stub'; -import { userStub } from './user.stub'; +import { AlbumEntity, AssetOrder } from 'src/infra/entities/album.entity'; +import { assetStub } from 'test/fixtures/asset.stub'; +import { authStub } from 'test/fixtures/auth.stub'; +import { userStub } from 'test/fixtures/user.stub'; export const albumStub = { empty: Object.freeze({ diff --git a/server/test/fixtures/api-key.stub.ts b/server/test/fixtures/api-key.stub.ts index de1b8dc17..bc6f85c65 100644 --- a/server/test/fixtures/api-key.stub.ts +++ b/server/test/fixtures/api-key.stub.ts @@ -1,6 +1,6 @@ -import { APIKeyEntity } from '@app/infra/entities'; -import { authStub } from './auth.stub'; -import { userStub } from './user.stub'; +import { APIKeyEntity } from 'src/infra/entities/api-key.entity'; +import { authStub } from 'test/fixtures/auth.stub'; +import { userStub } from 'test/fixtures/user.stub'; export const keyStub = { admin: Object.freeze({ diff --git a/server/test/fixtures/asset.stub.ts b/server/test/fixtures/asset.stub.ts index d72a295d4..d2daeaaff 100644 --- a/server/test/fixtures/asset.stub.ts +++ b/server/test/fixtures/asset.stub.ts @@ -1,8 +1,10 @@ -import { AssetEntity, AssetStackEntity, AssetType, ExifEntity } from '@app/infra/entities'; -import { authStub } from './auth.stub'; -import { fileStub } from './file.stub'; -import { libraryStub } from './library.stub'; -import { userStub } from './user.stub'; +import { AssetStackEntity } from 'src/infra/entities/asset-stack.entity'; +import { AssetEntity, AssetType } from 'src/infra/entities/asset.entity'; +import { ExifEntity } from 'src/infra/entities/exif.entity'; +import { authStub } from 'test/fixtures/auth.stub'; +import { fileStub } from 'test/fixtures/file.stub'; +import { libraryStub } from 'test/fixtures/library.stub'; +import { userStub } from 'test/fixtures/user.stub'; export const assetStackStub = (stackId: string, assets: AssetEntity[]): AssetStackEntity => { return { diff --git a/server/test/fixtures/audit.stub.ts b/server/test/fixtures/audit.stub.ts index ab1ca98b9..2c7ef6782 100644 --- a/server/test/fixtures/audit.stub.ts +++ b/server/test/fixtures/audit.stub.ts @@ -1,5 +1,5 @@ -import { AuditEntity, DatabaseAction, EntityType } from '@app/infra/entities'; -import { authStub } from './auth.stub'; +import { AuditEntity, DatabaseAction, EntityType } from 'src/infra/entities/audit.entity'; +import { authStub } from 'test/fixtures/auth.stub'; export const auditStub = { create: Object.freeze({ diff --git a/server/test/fixtures/auth.stub.ts b/server/test/fixtures/auth.stub.ts index 79993a4da..f05285fd6 100644 --- a/server/test/fixtures/auth.stub.ts +++ b/server/test/fixtures/auth.stub.ts @@ -1,5 +1,7 @@ -import { AuthDto } from '@app/domain'; -import { SharedLinkEntity, UserEntity, UserTokenEntity } from '../../src/infra/entities'; +import { AuthDto } from 'src/domain/auth/auth.dto'; +import { SharedLinkEntity } from 'src/infra/entities/shared-link.entity'; +import { UserTokenEntity } from 'src/infra/entities/user-token.entity'; +import { UserEntity } from 'src/infra/entities/user.entity'; export const adminSignupStub = { name: 'Immich Admin', diff --git a/server/test/fixtures/face.stub.ts b/server/test/fixtures/face.stub.ts index 0b988d59a..58a017927 100644 --- a/server/test/fixtures/face.stub.ts +++ b/server/test/fixtures/face.stub.ts @@ -1,6 +1,6 @@ -import { AssetFaceEntity } from '@app/infra/entities'; -import { assetStub } from './asset.stub'; -import { personStub } from './person.stub'; +import { AssetFaceEntity } from 'src/infra/entities/asset-face.entity'; +import { assetStub } from 'test/fixtures/asset.stub'; +import { personStub } from 'test/fixtures/person.stub'; type NonNullableProperty = { [P in keyof T]: NonNullable }; diff --git a/server/test/fixtures/index.ts b/server/test/fixtures/index.ts deleted file mode 100644 index 2217c9b1f..000000000 --- a/server/test/fixtures/index.ts +++ /dev/null @@ -1,18 +0,0 @@ -export * from './album.stub'; -export * from './api-key.stub'; -export * from './asset.stub'; -export * from './audit.stub'; -export * from './auth.stub'; -export * from './error.stub'; -export * from './face.stub'; -export * from './file.stub'; -export * from './library.stub'; -export * from './media.stub'; -export * from './partner.stub'; -export * from './person.stub'; -export * from './shared-link.stub'; -export * from './system-config.stub'; -export * from './tag.stub'; -export * from './user-token.stub'; -export * from './user.stub'; -export * from './uuid.stub'; diff --git a/server/test/fixtures/library.stub.ts b/server/test/fixtures/library.stub.ts index db7687f28..0c5296595 100644 --- a/server/test/fixtures/library.stub.ts +++ b/server/test/fixtures/library.stub.ts @@ -1,7 +1,8 @@ -import { APP_MEDIA_LOCATION, THUMBNAIL_DIR } from '@app/domain'; -import { LibraryEntity, LibraryType } from '@app/infra/entities'; import { join } from 'node:path'; -import { userStub } from './user.stub'; +import { THUMBNAIL_DIR } from 'src/cores/storage.core'; +import { APP_MEDIA_LOCATION } from 'src/domain/domain.constant'; +import { LibraryEntity, LibraryType } from 'src/infra/entities/library.entity'; +import { userStub } from 'test/fixtures/user.stub'; export const libraryStub = { uploadLibrary1: Object.freeze({ diff --git a/server/test/fixtures/media.stub.ts b/server/test/fixtures/media.stub.ts index ad9a4baf7..1be792c25 100644 --- a/server/test/fixtures/media.stub.ts +++ b/server/test/fixtures/media.stub.ts @@ -1,4 +1,4 @@ -import { AudioStreamInfo, VideoFormat, VideoInfo, VideoStreamInfo } from '@app/domain'; +import { AudioStreamInfo, VideoFormat, VideoInfo, VideoStreamInfo } from 'src/interfaces/media.repository'; const probeStubDefaultFormat: VideoFormat = { formatName: 'mov,mp4,m4a,3gp,3g2,mj2', diff --git a/server/test/fixtures/partner.stub.ts b/server/test/fixtures/partner.stub.ts index 05c1c67d6..1277e2e21 100644 --- a/server/test/fixtures/partner.stub.ts +++ b/server/test/fixtures/partner.stub.ts @@ -1,5 +1,5 @@ -import { PartnerEntity } from '@app/infra/entities'; -import { userStub } from './user.stub'; +import { PartnerEntity } from 'src/infra/entities/partner.entity'; +import { userStub } from 'test/fixtures/user.stub'; export const partnerStub = { adminToUser1: Object.freeze({ diff --git a/server/test/fixtures/person.stub.ts b/server/test/fixtures/person.stub.ts index ad83d6800..a573d72e3 100644 --- a/server/test/fixtures/person.stub.ts +++ b/server/test/fixtures/person.stub.ts @@ -1,5 +1,5 @@ -import { PersonEntity } from '@app/infra/entities'; -import { userStub } from './user.stub'; +import { PersonEntity } from 'src/infra/entities/person.entity'; +import { userStub } from 'test/fixtures/user.stub'; export const personStub = { noName: Object.freeze({ diff --git a/server/test/fixtures/shared-link.stub.ts b/server/test/fixtures/shared-link.stub.ts index 109f05190..d3b3d5151 100644 --- a/server/test/fixtures/shared-link.stub.ts +++ b/server/test/fixtures/shared-link.stub.ts @@ -1,9 +1,16 @@ -import { AlbumResponseDto, AssetResponseDto, ExifResponseDto, mapUser, SharedLinkResponseDto } from '@app/domain'; -import { AssetOrder, AssetType, SharedLinkEntity, SharedLinkType, UserEntity } from '@app/infra/entities'; -import { assetStub } from './asset.stub'; -import { authStub } from './auth.stub'; -import { libraryStub } from './library.stub'; -import { userStub } from './user.stub'; +import { AlbumResponseDto } from 'src/domain/album/album-response.dto'; +import { AssetResponseDto } from 'src/domain/asset/response-dto/asset-response.dto'; +import { ExifResponseDto } from 'src/domain/asset/response-dto/exif-response.dto'; +import { SharedLinkResponseDto } from 'src/domain/shared-link/shared-link-response.dto'; +import { mapUser } from 'src/domain/user/response-dto/user-response.dto'; +import { AssetOrder } from 'src/infra/entities/album.entity'; +import { AssetType } from 'src/infra/entities/asset.entity'; +import { SharedLinkEntity, SharedLinkType } from 'src/infra/entities/shared-link.entity'; +import { UserEntity } from 'src/infra/entities/user.entity'; +import { assetStub } from 'test/fixtures/asset.stub'; +import { authStub } from 'test/fixtures/auth.stub'; +import { libraryStub } from 'test/fixtures/library.stub'; +import { userStub } from 'test/fixtures/user.stub'; const today = new Date(); const tomorrow = new Date(); diff --git a/server/test/fixtures/system-config.stub.ts b/server/test/fixtures/system-config.stub.ts index 9f9f02144..2de539848 100644 --- a/server/test/fixtures/system-config.stub.ts +++ b/server/test/fixtures/system-config.stub.ts @@ -1,4 +1,4 @@ -import { SystemConfigEntity, SystemConfigKey } from '@app/infra/entities'; +import { SystemConfigEntity, SystemConfigKey } from 'src/infra/entities/system-config.entity'; export const systemConfigStub: Record = { defaults: [], diff --git a/server/test/fixtures/tag.stub.ts b/server/test/fixtures/tag.stub.ts index cffae0032..a48005528 100644 --- a/server/test/fixtures/tag.stub.ts +++ b/server/test/fixtures/tag.stub.ts @@ -1,6 +1,6 @@ -import { TagResponseDto } from '@app/domain'; -import { TagEntity, TagType } from '@app/infra/entities'; -import { userStub } from './user.stub'; +import { TagResponseDto } from 'src/domain/tag/tag-response.dto'; +import { TagEntity, TagType } from 'src/infra/entities/tag.entity'; +import { userStub } from 'test/fixtures/user.stub'; export const tagStub = { tag1: Object.freeze({ diff --git a/server/test/fixtures/user-token.stub.ts b/server/test/fixtures/user-token.stub.ts index 975318e21..de42d1d32 100644 --- a/server/test/fixtures/user-token.stub.ts +++ b/server/test/fixtures/user-token.stub.ts @@ -1,5 +1,5 @@ -import { UserTokenEntity } from '@app/infra/entities'; -import { userStub } from './user.stub'; +import { UserTokenEntity } from 'src/infra/entities/user-token.entity'; +import { userStub } from 'test/fixtures/user.stub'; export const userTokenStub = { userToken: Object.freeze({ diff --git a/server/test/fixtures/user.stub.ts b/server/test/fixtures/user.stub.ts index e0d9113c6..7809583c7 100644 --- a/server/test/fixtures/user.stub.ts +++ b/server/test/fixtures/user.stub.ts @@ -1,5 +1,5 @@ -import { UserAvatarColor, UserEntity } from '@app/infra/entities'; -import { authStub } from './auth.stub'; +import { UserAvatarColor, UserEntity } from 'src/infra/entities/user.entity'; +import { authStub } from 'test/fixtures/auth.stub'; export const userDto = { user1: { diff --git a/server/test/index.ts b/server/test/index.ts deleted file mode 100644 index 784eeeb35..000000000 --- a/server/test/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './fixtures'; -export * from './repositories'; diff --git a/server/test/repositories/access.repository.mock.ts b/server/test/repositories/access.repository.mock.ts index e10dd7d9a..8d2233cf6 100644 --- a/server/test/repositories/access.repository.mock.ts +++ b/server/test/repositories/access.repository.mock.ts @@ -1,4 +1,5 @@ -import { AccessCore, IAccessRepository } from '@app/domain'; +import { AccessCore } from 'src/cores/access.core'; +import { IAccessRepository } from 'src/interfaces/access.repository'; export interface IAccessRepositoryMock { activity: jest.Mocked; diff --git a/server/test/repositories/activity.repository.mock.ts b/server/test/repositories/activity.repository.mock.ts index 349fa4636..84cf7e124 100644 --- a/server/test/repositories/activity.repository.mock.ts +++ b/server/test/repositories/activity.repository.mock.ts @@ -1,4 +1,4 @@ -import { IActivityRepository } from '@app/domain'; +import { IActivityRepository } from 'src/interfaces/activity.repository'; export const newActivityRepositoryMock = (): jest.Mocked => { return { diff --git a/server/test/repositories/album.repository.mock.ts b/server/test/repositories/album.repository.mock.ts index 36c3afb29..b50fe6750 100644 --- a/server/test/repositories/album.repository.mock.ts +++ b/server/test/repositories/album.repository.mock.ts @@ -1,4 +1,4 @@ -import { IAlbumRepository } from '@app/domain'; +import { IAlbumRepository } from 'src/interfaces/album.repository'; export const newAlbumRepositoryMock = (): jest.Mocked => { return { diff --git a/server/test/repositories/api-key.repository.mock.ts b/server/test/repositories/api-key.repository.mock.ts index 5688978e7..d74a41028 100644 --- a/server/test/repositories/api-key.repository.mock.ts +++ b/server/test/repositories/api-key.repository.mock.ts @@ -1,4 +1,4 @@ -import { IKeyRepository } from '@app/domain'; +import { IKeyRepository } from 'src/interfaces/api-key.repository'; export const newKeyRepositoryMock = (): jest.Mocked => { return { diff --git a/server/test/repositories/asset-stack.repository.mock.ts b/server/test/repositories/asset-stack.repository.mock.ts index d87f0316f..cf38d045f 100644 --- a/server/test/repositories/asset-stack.repository.mock.ts +++ b/server/test/repositories/asset-stack.repository.mock.ts @@ -1,4 +1,4 @@ -import { IAssetStackRepository } from '@app/domain'; +import { IAssetStackRepository } from 'src/interfaces/asset-stack.repository'; export const newAssetStackRepositoryMock = (): jest.Mocked => { return { diff --git a/server/test/repositories/asset.repository.mock.ts b/server/test/repositories/asset.repository.mock.ts index b9451f34f..c72d00414 100644 --- a/server/test/repositories/asset.repository.mock.ts +++ b/server/test/repositories/asset.repository.mock.ts @@ -1,4 +1,4 @@ -import { IAssetRepository } from '@app/domain'; +import { IAssetRepository } from 'src/interfaces/asset.repository'; export const newAssetRepositoryMock = (): jest.Mocked => { return { diff --git a/server/test/repositories/audit.repository.mock.ts b/server/test/repositories/audit.repository.mock.ts index bd1a4b815..654f67794 100644 --- a/server/test/repositories/audit.repository.mock.ts +++ b/server/test/repositories/audit.repository.mock.ts @@ -1,4 +1,4 @@ -import { IAuditRepository } from '@app/domain'; +import { IAuditRepository } from 'src/interfaces/audit.repository'; export const newAuditRepositoryMock = (): jest.Mocked => { return { diff --git a/server/test/repositories/communication.repository.mock.ts b/server/test/repositories/communication.repository.mock.ts index e98e0a68f..3d4eaeb37 100644 --- a/server/test/repositories/communication.repository.mock.ts +++ b/server/test/repositories/communication.repository.mock.ts @@ -1,4 +1,4 @@ -import { ICommunicationRepository } from '@app/domain'; +import { ICommunicationRepository } from 'src/interfaces/communication.repository'; export const newCommunicationRepositoryMock = (): jest.Mocked => { return { diff --git a/server/test/repositories/crypto.repository.mock.ts b/server/test/repositories/crypto.repository.mock.ts index 52f438453..6cf4adffd 100644 --- a/server/test/repositories/crypto.repository.mock.ts +++ b/server/test/repositories/crypto.repository.mock.ts @@ -1,4 +1,4 @@ -import { ICryptoRepository } from '@app/domain'; +import { ICryptoRepository } from 'src/interfaces/crypto.repository'; export const newCryptoRepositoryMock = (): jest.Mocked => { return { diff --git a/server/test/repositories/database.repository.mock.ts b/server/test/repositories/database.repository.mock.ts index 19e2df17a..82abe16a8 100644 --- a/server/test/repositories/database.repository.mock.ts +++ b/server/test/repositories/database.repository.mock.ts @@ -1,4 +1,5 @@ -import { IDatabaseRepository, Version } from '@app/domain'; +import { Version } from 'src/domain/domain.constant'; +import { IDatabaseRepository } from 'src/interfaces/database.repository'; export const newDatabaseRepositoryMock = (): jest.Mocked => { return { diff --git a/server/test/repositories/index.ts b/server/test/repositories/index.ts deleted file mode 100644 index 90fd1326b..000000000 --- a/server/test/repositories/index.ts +++ /dev/null @@ -1,26 +0,0 @@ -export * from './access.repository.mock'; -export * from './album.repository.mock'; -export * from './api-key.repository.mock'; -export * from './asset-stack.repository.mock'; -export * from './asset.repository.mock'; -export * from './audit.repository.mock'; -export * from './communication.repository.mock'; -export * from './crypto.repository.mock'; -export * from './database.repository.mock'; -export * from './job.repository.mock'; -export * from './library.repository.mock'; -export * from './machine-learning.repository.mock'; -export * from './media.repository.mock'; -export * from './metadata.repository.mock'; -export * from './move.repository.mock'; -export * from './partner.repository.mock'; -export * from './person.repository.mock'; -export * from './search.repository.mock'; -export * from './shared-link.repository.mock'; -export * from './storage.repository.mock'; -export * from './system-config.repository.mock'; -export * from './system-info.repository.mock'; -export * from './system-metadata.repository.mock'; -export * from './tag.repository.mock'; -export * from './user-token.repository.mock'; -export * from './user.repository.mock'; diff --git a/server/test/repositories/job.repository.mock.ts b/server/test/repositories/job.repository.mock.ts index 5967c3ce2..851da70da 100644 --- a/server/test/repositories/job.repository.mock.ts +++ b/server/test/repositories/job.repository.mock.ts @@ -1,4 +1,4 @@ -import { IJobRepository } from '@app/domain'; +import { IJobRepository } from 'src/interfaces/job.repository'; export const newJobRepositoryMock = (): jest.Mocked => { return { diff --git a/server/test/repositories/library.repository.mock.ts b/server/test/repositories/library.repository.mock.ts index 740f4c483..b3c62d681 100644 --- a/server/test/repositories/library.repository.mock.ts +++ b/server/test/repositories/library.repository.mock.ts @@ -1,4 +1,4 @@ -import { ILibraryRepository } from '@app/domain'; +import { ILibraryRepository } from 'src/interfaces/library.repository'; export const newLibraryRepositoryMock = (): jest.Mocked => { return { diff --git a/server/test/repositories/machine-learning.repository.mock.ts b/server/test/repositories/machine-learning.repository.mock.ts index 3538b7893..50da10cb4 100644 --- a/server/test/repositories/machine-learning.repository.mock.ts +++ b/server/test/repositories/machine-learning.repository.mock.ts @@ -1,4 +1,4 @@ -import { IMachineLearningRepository } from '@app/domain'; +import { IMachineLearningRepository } from 'src/interfaces/machine-learning.repository'; export const newMachineLearningRepositoryMock = (): jest.Mocked => { return { diff --git a/server/test/repositories/media.repository.mock.ts b/server/test/repositories/media.repository.mock.ts index 74c4a5d7a..d1806c636 100644 --- a/server/test/repositories/media.repository.mock.ts +++ b/server/test/repositories/media.repository.mock.ts @@ -1,4 +1,4 @@ -import { IMediaRepository } from '@app/domain'; +import { IMediaRepository } from 'src/interfaces/media.repository'; export const newMediaRepositoryMock = (): jest.Mocked => { return { diff --git a/server/test/repositories/metadata.repository.mock.ts b/server/test/repositories/metadata.repository.mock.ts index e47120ac9..a5f13daf5 100644 --- a/server/test/repositories/metadata.repository.mock.ts +++ b/server/test/repositories/metadata.repository.mock.ts @@ -1,4 +1,4 @@ -import { IMetadataRepository } from '@app/domain'; +import { IMetadataRepository } from 'src/interfaces/metadata.repository'; export const newMetadataRepositoryMock = (): jest.Mocked => { return { diff --git a/server/test/repositories/move.repository.mock.ts b/server/test/repositories/move.repository.mock.ts index e14b0640b..265799ab6 100644 --- a/server/test/repositories/move.repository.mock.ts +++ b/server/test/repositories/move.repository.mock.ts @@ -1,4 +1,4 @@ -import { IMoveRepository } from '@app/domain'; +import { IMoveRepository } from 'src/interfaces/move.repository'; export const newMoveRepositoryMock = (): jest.Mocked => { return { diff --git a/server/test/repositories/partner.repository.mock.ts b/server/test/repositories/partner.repository.mock.ts index 1e839ae4f..74c42ba8d 100644 --- a/server/test/repositories/partner.repository.mock.ts +++ b/server/test/repositories/partner.repository.mock.ts @@ -1,4 +1,4 @@ -import { IPartnerRepository } from '@app/domain'; +import { IPartnerRepository } from 'src/interfaces/partner.repository'; export const newPartnerRepositoryMock = (): jest.Mocked => { return { diff --git a/server/test/repositories/person.repository.mock.ts b/server/test/repositories/person.repository.mock.ts index 2a1ccdfe5..b4b817535 100644 --- a/server/test/repositories/person.repository.mock.ts +++ b/server/test/repositories/person.repository.mock.ts @@ -1,4 +1,4 @@ -import { IPersonRepository } from '@app/domain'; +import { IPersonRepository } from 'src/interfaces/person.repository'; export const newPersonRepositoryMock = (): jest.Mocked => { return { diff --git a/server/test/repositories/search.repository.mock.ts b/server/test/repositories/search.repository.mock.ts index 7b428f0cc..eb6c13ea4 100644 --- a/server/test/repositories/search.repository.mock.ts +++ b/server/test/repositories/search.repository.mock.ts @@ -1,4 +1,4 @@ -import { ISearchRepository } from '@app/domain'; +import { ISearchRepository } from 'src/interfaces/search.repository'; export const newSearchRepositoryMock = (): jest.Mocked => { return { diff --git a/server/test/repositories/shared-link.repository.mock.ts b/server/test/repositories/shared-link.repository.mock.ts index fb34b0ad7..ea084c1ed 100644 --- a/server/test/repositories/shared-link.repository.mock.ts +++ b/server/test/repositories/shared-link.repository.mock.ts @@ -1,4 +1,4 @@ -import { ISharedLinkRepository } from '@app/domain'; +import { ISharedLinkRepository } from 'src/interfaces/shared-link.repository'; export const newSharedLinkRepositoryMock = (): jest.Mocked => { return { diff --git a/server/test/repositories/storage.repository.mock.ts b/server/test/repositories/storage.repository.mock.ts index a8ffbf410..72b87aed1 100644 --- a/server/test/repositories/storage.repository.mock.ts +++ b/server/test/repositories/storage.repository.mock.ts @@ -1,5 +1,6 @@ -import { IStorageRepository, StorageCore, StorageEventType, WatchEvents } from '@app/domain'; import { WatchOptions } from 'chokidar'; +import { StorageCore } from 'src/cores/storage.core'; +import { IStorageRepository, StorageEventType, WatchEvents } from 'src/interfaces/storage.repository'; interface MockWatcherOptions { items?: Array<{ event: 'change' | 'add' | 'unlink' | 'error'; value: string }>; diff --git a/server/test/repositories/system-config.repository.mock.ts b/server/test/repositories/system-config.repository.mock.ts index 3be69f267..89ce6a050 100644 --- a/server/test/repositories/system-config.repository.mock.ts +++ b/server/test/repositories/system-config.repository.mock.ts @@ -1,4 +1,5 @@ -import { ISystemConfigRepository, SystemConfigCore } from '@app/domain'; +import { SystemConfigCore } from 'src/cores/system-config.core'; +import { ISystemConfigRepository } from 'src/interfaces/system-config.repository'; export const newSystemConfigRepositoryMock = (reset = true): jest.Mocked => { if (reset) { diff --git a/server/test/repositories/system-info.repository.mock.ts b/server/test/repositories/system-info.repository.mock.ts index 14c52a6b7..93ec9c7dd 100644 --- a/server/test/repositories/system-info.repository.mock.ts +++ b/server/test/repositories/system-info.repository.mock.ts @@ -1,4 +1,4 @@ -import { IServerInfoRepository } from '@app/domain'; +import { IServerInfoRepository } from 'src/interfaces/server-info.repository'; export const newServerInfoRepositoryMock = (): jest.Mocked => { return { diff --git a/server/test/repositories/system-metadata.repository.mock.ts b/server/test/repositories/system-metadata.repository.mock.ts index fc4207da6..f93278aaa 100644 --- a/server/test/repositories/system-metadata.repository.mock.ts +++ b/server/test/repositories/system-metadata.repository.mock.ts @@ -1,4 +1,4 @@ -import { ISystemMetadataRepository } from '@app/domain'; +import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.repository'; export const newSystemMetadataRepositoryMock = (): jest.Mocked => { return { diff --git a/server/test/repositories/tag.repository.mock.ts b/server/test/repositories/tag.repository.mock.ts index cb1e05a09..9f5283664 100644 --- a/server/test/repositories/tag.repository.mock.ts +++ b/server/test/repositories/tag.repository.mock.ts @@ -1,4 +1,4 @@ -import { ITagRepository } from '@app/domain'; +import { ITagRepository } from 'src/interfaces/tag.repository'; export const newTagRepositoryMock = (): jest.Mocked => { return { diff --git a/server/test/repositories/user-token.repository.mock.ts b/server/test/repositories/user-token.repository.mock.ts index 9d1bacf1c..23de88d35 100644 --- a/server/test/repositories/user-token.repository.mock.ts +++ b/server/test/repositories/user-token.repository.mock.ts @@ -1,4 +1,4 @@ -import { IUserTokenRepository } from '@app/domain'; +import { IUserTokenRepository } from 'src/interfaces/user-token.repository'; export const newUserTokenRepositoryMock = (): jest.Mocked => { return { diff --git a/server/test/repositories/user.repository.mock.ts b/server/test/repositories/user.repository.mock.ts index 402b90ead..4db0b16af 100644 --- a/server/test/repositories/user.repository.mock.ts +++ b/server/test/repositories/user.repository.mock.ts @@ -1,4 +1,5 @@ -import { IUserRepository, UserCore } from '@app/domain'; +import { UserCore } from 'src/cores/user.core'; +import { IUserRepository } from 'src/interfaces/user.repository'; export const newUserRepositoryMock = (reset = true): jest.Mocked => { if (reset) { diff --git a/server/tsconfig.json b/server/tsconfig.json index 6d89fe708..ce3edda39 100644 --- a/server/tsconfig.json +++ b/server/tsconfig.json @@ -17,16 +17,10 @@ "esModuleInterop": true, "preserveWatchOutput": true, "baseUrl": "./", - "paths": { - "@test": ["test"], - "@test/*": ["test/*"], - "@app/immich": ["src/immich"], - "@app/immich/*": ["src/immich/*"], - "@app/infra": ["src/infra"], - "@app/infra/*": ["src/infra/*"], - "@app/domain": ["src/domain"], - "@app/domain/*": ["src/domain/*"], - }, }, - "exclude": ["dist", "node_modules", "upload"], -} + "exclude": [ + "dist", + "node_modules", + "upload" + ], +} \ No newline at end of file diff --git a/web/.eslintignore b/web/.eslintignore index 38972655f..f944e33c4 100644 --- a/web/.eslintignore +++ b/web/.eslintignore @@ -11,3 +11,4 @@ node_modules pnpm-lock.yaml package-lock.json yarn.lock +svelte.config.js diff --git a/web/Dockerfile b/web/Dockerfile index 27d206e92..422ad9746 100644 --- a/web/Dockerfile +++ b/web/Dockerfile @@ -1,4 +1,4 @@ -FROM node:iron-alpine3.18@sha256:a02826c7340c37a29179152723190bcc3044f933c925f3c2d78abb20f794de3f +FROM node:iron-alpine3.18@sha256:876514790dabd49fae7d9c4dfbba027954bd91d8e7d36da76334466533bc6b0c RUN apk add --no-cache tini USER node diff --git a/web/package-lock.json b/web/package-lock.json index 899ec9156..85413cbc9 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -1,12 +1,12 @@ { "name": "immich-web", - "version": "1.98.2", + "version": "1.99.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "immich-web", - "version": "1.98.2", + "version": "1.99.0", "license": "GNU Affero General Public License version 3", "dependencies": { "@immich/sdk": "file:../open-api/typescript-sdk", @@ -63,7 +63,7 @@ }, "../open-api/typescript-sdk": { "name": "@immich/sdk", - "version": "1.98.2", + "version": "1.99.0", "license": "GNU Affero General Public License version 3", "dependencies": { "@oazapfts/runtime": "^1.0.2" @@ -1456,9 +1456,9 @@ "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==" }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.19", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.19.tgz", - "integrity": "sha512-kf37QtfW+Hwx/buWGMPcR60iF9ziHa6r/CZJIHbmcm4+0qrXiVdxegAH0F6yddEVQ7zdkjcGCgCzUu+BcbhQxw==", + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" @@ -1814,9 +1814,9 @@ } }, "node_modules/@sveltejs/enhanced-img": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/@sveltejs/enhanced-img/-/enhanced-img-0.1.8.tgz", - "integrity": "sha512-0cLVR9KiO0/t3VVm64OM7bPHTkdaT2aaz1rwoAhao+EBXR3vMvLoYXLHvz8o9/552PSV8G844RkH7qkGc3YAiQ==", + "version": "0.1.9", + "resolved": "https://registry.npmjs.org/@sveltejs/enhanced-img/-/enhanced-img-0.1.9.tgz", + "integrity": "sha512-gUgaiG88P6moWcxZx4YrzMhAlw1TgggKRp7n9gdfCREDeXHysCd1l9GpQR3sh109SM3rNlkiaAzt+iPLT0aG1w==", "dev": true, "dependencies": { "magic-string": "^0.30.5", @@ -1825,9 +1825,9 @@ } }, "node_modules/@sveltejs/kit": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.5.2.tgz", - "integrity": "sha512-1Pm2lsBYURQsjnLyZa+jw75eVD4gYHxGRwPyFe4DAmB3FjTVR8vRNWGeuDLGFcKMh/B1ij6FTUrc9GrerogCng==", + "version": "2.5.4", + "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.5.4.tgz", + "integrity": "sha512-eDxK2d4EGzk99QsZNoPXe7jlzA5EGqfcCpUwZ912bhnalsZ2ZsG5wGRthkydupVjYyqdmzEanVKFhLxU2vkPSQ==", "dev": true, "hasInstallScript": true, "dependencies": { @@ -2264,16 +2264,16 @@ } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.1.1.tgz", - "integrity": "sha512-zioDz623d0RHNhvx0eesUmGfIjzrk18nSBC8xewepKXbBvN/7c1qImV7Hg8TI1URTxKax7/zxfxj3Uph8Chcuw==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.2.0.tgz", + "integrity": "sha512-mdekAHOqS9UjlmyF/LSs6AIEvfceV749GFxoBAjwAv0nkevfKHWQFDMcBZWUiIC5ft6ePWivXoS36aKQ0Cy3sw==", "dev": true, "dependencies": { "@eslint-community/regexpp": "^4.5.1", - "@typescript-eslint/scope-manager": "7.1.1", - "@typescript-eslint/type-utils": "7.1.1", - "@typescript-eslint/utils": "7.1.1", - "@typescript-eslint/visitor-keys": "7.1.1", + "@typescript-eslint/scope-manager": "7.2.0", + "@typescript-eslint/type-utils": "7.2.0", + "@typescript-eslint/utils": "7.2.0", + "@typescript-eslint/visitor-keys": "7.2.0", "debug": "^4.3.4", "graphemer": "^1.4.0", "ignore": "^5.2.4", @@ -2332,15 +2332,15 @@ "dev": true }, "node_modules/@typescript-eslint/parser": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.1.1.tgz", - "integrity": "sha512-ZWUFyL0z04R1nAEgr9e79YtV5LbafdOtN7yapNbn1ansMyaegl2D4bL7vHoJ4HPSc4CaLwuCVas8CVuneKzplQ==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.2.0.tgz", + "integrity": "sha512-5FKsVcHTk6TafQKQbuIVkXq58Fnbkd2wDL4LB7AURN7RUOu1utVP+G8+6u3ZhEroW3DF6hyo3ZEXxgKgp4KeCg==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "7.1.1", - "@typescript-eslint/types": "7.1.1", - "@typescript-eslint/typescript-estree": "7.1.1", - "@typescript-eslint/visitor-keys": "7.1.1", + "@typescript-eslint/scope-manager": "7.2.0", + "@typescript-eslint/types": "7.2.0", + "@typescript-eslint/typescript-estree": "7.2.0", + "@typescript-eslint/visitor-keys": "7.2.0", "debug": "^4.3.4" }, "engines": { @@ -2360,13 +2360,13 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.1.1.tgz", - "integrity": "sha512-cirZpA8bJMRb4WZ+rO6+mnOJrGFDd38WoXCEI57+CYBqta8Yc8aJym2i7vyqLL1vVYljgw0X27axkUXz32T8TA==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.2.0.tgz", + "integrity": "sha512-Qh976RbQM/fYtjx9hs4XkayYujB/aPwglw2choHmf3zBjB4qOywWSdt9+KLRdHubGcoSwBnXUH2sR3hkyaERRg==", "dev": true, "dependencies": { - "@typescript-eslint/types": "7.1.1", - "@typescript-eslint/visitor-keys": "7.1.1" + "@typescript-eslint/types": "7.2.0", + "@typescript-eslint/visitor-keys": "7.2.0" }, "engines": { "node": "^16.0.0 || >=18.0.0" @@ -2377,13 +2377,13 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.1.1.tgz", - "integrity": "sha512-5r4RKze6XHEEhlZnJtR3GYeCh1IueUHdbrukV2KSlLXaTjuSfeVF8mZUVPLovidCuZfbVjfhi4c0DNSa/Rdg5g==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.2.0.tgz", + "integrity": "sha512-xHi51adBHo9O9330J8GQYQwrKBqbIPJGZZVQTHHmy200hvkLZFWJIFtAG/7IYTWUyun6DE6w5InDReePJYJlJA==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "7.1.1", - "@typescript-eslint/utils": "7.1.1", + "@typescript-eslint/typescript-estree": "7.2.0", + "@typescript-eslint/utils": "7.2.0", "debug": "^4.3.4", "ts-api-utils": "^1.0.1" }, @@ -2404,9 +2404,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.1.1.tgz", - "integrity": "sha512-KhewzrlRMrgeKm1U9bh2z5aoL4s7K3tK5DwHDn8MHv0yQfWFz/0ZR6trrIHHa5CsF83j/GgHqzdbzCXJ3crx0Q==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.2.0.tgz", + "integrity": "sha512-XFtUHPI/abFhm4cbCDc5Ykc8npOKBSJePY3a3s+lwumt7XWJuzP5cZcfZ610MIPHjQjNsOLlYK8ASPaNG8UiyA==", "dev": true, "engines": { "node": "^16.0.0 || >=18.0.0" @@ -2417,13 +2417,13 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.1.1.tgz", - "integrity": "sha512-9ZOncVSfr+sMXVxxca2OJOPagRwT0u/UHikM2Rd6L/aB+kL/QAuTnsv6MeXtjzCJYb8PzrXarypSGIPx3Jemxw==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.2.0.tgz", + "integrity": "sha512-cyxS5WQQCoBwSakpMrvMXuMDEbhOo9bNHHrNcEWis6XHx6KF518tkF1wBvKIn/tpq5ZpUYK7Bdklu8qY0MsFIA==", "dev": true, "dependencies": { - "@typescript-eslint/types": "7.1.1", - "@typescript-eslint/visitor-keys": "7.1.1", + "@typescript-eslint/types": "7.2.0", + "@typescript-eslint/visitor-keys": "7.2.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -2502,17 +2502,17 @@ "dev": true }, "node_modules/@typescript-eslint/utils": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.1.1.tgz", - "integrity": "sha512-thOXM89xA03xAE0lW7alstvnyoBUbBX38YtY+zAUcpRPcq9EIhXPuJ0YTv948MbzmKh6e1AUszn5cBFK49Umqg==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.2.0.tgz", + "integrity": "sha512-YfHpnMAGb1Eekpm3XRK8hcMwGLGsnT6L+7b2XyRv6ouDuJU1tZir1GS2i0+VXRatMwSI1/UfcyPe53ADkU+IuA==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "@types/json-schema": "^7.0.12", "@types/semver": "^7.5.0", - "@typescript-eslint/scope-manager": "7.1.1", - "@typescript-eslint/types": "7.1.1", - "@typescript-eslint/typescript-estree": "7.1.1", + "@typescript-eslint/scope-manager": "7.2.0", + "@typescript-eslint/types": "7.2.0", + "@typescript-eslint/typescript-estree": "7.2.0", "semver": "^7.5.4" }, "engines": { @@ -2560,12 +2560,12 @@ "dev": true }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.1.1.tgz", - "integrity": "sha512-yTdHDQxY7cSoCcAtiBzVzxleJhkGB9NncSIyMYe2+OGON1ZsP9zOPws/Pqgopa65jvknOjlk/w7ulPlZ78PiLQ==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.2.0.tgz", + "integrity": "sha512-c6EIQRHhcpl6+tO8EMR+kjkkV+ugUNXOmeASA1rlzkd8EPIriavpWoiEz1HR/VLhbVIdhqnV6E7JZm00cBDx2A==", "dev": true, "dependencies": { - "@typescript-eslint/types": "7.1.1", + "@typescript-eslint/types": "7.2.0", "eslint-visitor-keys": "^3.4.1" }, "engines": { @@ -2583,14 +2583,14 @@ "dev": true }, "node_modules/@vitest/browser": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@vitest/browser/-/browser-1.3.1.tgz", - "integrity": "sha512-pRof8G8nqRWwg3ouyIctyhfIVk5jXgF056uF//sqdi37+pVtDz9kBI/RMu0xlc8tgCyJ2aEMfbgJZPUydlEVaQ==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@vitest/browser/-/browser-1.4.0.tgz", + "integrity": "sha512-kC44DzuqPZZrqe2P7SX2a3zHDAt919WtpkUMAxzv9eP5uPfVXtpk2Ipms2NXJGY5190aJc1uY+ambfJ3rwDJRA==", "dev": true, "optional": true, "peer": true, "dependencies": { - "@vitest/utils": "1.3.1", + "@vitest/utils": "1.4.0", "magic-string": "^0.30.5", "sirv": "^2.0.4" }, @@ -2599,7 +2599,7 @@ }, "peerDependencies": { "playwright": "*", - "vitest": "1.3.1", + "vitest": "1.4.0", "webdriverio": "*" }, "peerDependenciesMeta": { @@ -2615,9 +2615,9 @@ } }, "node_modules/@vitest/coverage-v8": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-1.3.1.tgz", - "integrity": "sha512-UuBnkSJUNE9rdHjDCPyJ4fYuMkoMtnghes1XohYa4At0MS3OQSAo97FrbwSLRshYsXThMZy1+ybD/byK5llyIg==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-1.4.0.tgz", + "integrity": "sha512-4hDGyH1SvKpgZnIByr9LhGgCEuF9DKM34IBLCC/fVfy24Z3+PZ+Ii9hsVBsHvY1umM1aGPEjceRkzxCfcQ10wg==", "dev": true, "dependencies": { "@ampproject/remapping": "^2.2.1", @@ -2625,12 +2625,13 @@ "debug": "^4.3.4", "istanbul-lib-coverage": "^3.2.2", "istanbul-lib-report": "^3.0.1", - "istanbul-lib-source-maps": "^4.0.1", + "istanbul-lib-source-maps": "^5.0.4", "istanbul-reports": "^3.1.6", "magic-string": "^0.30.5", "magicast": "^0.3.3", "picocolors": "^1.0.0", "std-env": "^3.5.0", + "strip-literal": "^2.0.0", "test-exclude": "^6.0.0", "v8-to-istanbul": "^9.2.0" }, @@ -2638,17 +2639,17 @@ "url": "https://opencollective.com/vitest" }, "peerDependencies": { - "vitest": "1.3.1" + "vitest": "1.4.0" } }, "node_modules/@vitest/expect": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-1.3.1.tgz", - "integrity": "sha512-xofQFwIzfdmLLlHa6ag0dPV8YsnKOCP1KdAeVVh34vSjN2dcUiXYCD9htu/9eM7t8Xln4v03U9HLxLpPlsXdZw==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-1.4.0.tgz", + "integrity": "sha512-Jths0sWCJZ8BxjKe+p+eKsoqev1/T8lYcrjavEaz8auEJ4jAVY0GwW3JKmdVU4mmNPLPHixh4GNXP7GFtAiDHA==", "dev": true, "dependencies": { - "@vitest/spy": "1.3.1", - "@vitest/utils": "1.3.1", + "@vitest/spy": "1.4.0", + "@vitest/utils": "1.4.0", "chai": "^4.3.10" }, "funding": { @@ -2656,12 +2657,12 @@ } }, "node_modules/@vitest/runner": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-1.3.1.tgz", - "integrity": "sha512-5FzF9c3jG/z5bgCnjr8j9LNq/9OxV2uEBAITOXfoe3rdZJTdO7jzThth7FXv/6b+kdY65tpRQB7WaKhNZwX+Kg==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-1.4.0.tgz", + "integrity": "sha512-EDYVSmesqlQ4RD2VvWo3hQgTJ7ZrFQ2VSJdfiJiArkCerDAGeyF1i6dHkmySqk573jLp6d/cfqCN+7wUB5tLgg==", "dev": true, "dependencies": { - "@vitest/utils": "1.3.1", + "@vitest/utils": "1.4.0", "p-limit": "^5.0.0", "pathe": "^1.1.1" }, @@ -2697,9 +2698,9 @@ } }, "node_modules/@vitest/snapshot": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-1.3.1.tgz", - "integrity": "sha512-EF++BZbt6RZmOlE3SuTPu/NfwBF6q4ABS37HHXzs2LUVPBLx2QoY/K0fKpRChSo8eLiuxcbCVfqKgx/dplCDuQ==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-1.4.0.tgz", + "integrity": "sha512-saAFnt5pPIA5qDGxOHxJ/XxhMFKkUSBJmVt5VgDsAqPTX6JP326r5C/c9UuCMPoXNzuudTPsYDZCoJ5ilpqG2A==", "dev": true, "dependencies": { "magic-string": "^0.30.5", @@ -2743,9 +2744,9 @@ "dev": true }, "node_modules/@vitest/spy": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-1.3.1.tgz", - "integrity": "sha512-xAcW+S099ylC9VLU7eZfdT9myV67Nor9w9zhf0mGCYJSO+zM2839tOeROTdikOi/8Qeusffvxb/MyBSOja1Uig==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-1.4.0.tgz", + "integrity": "sha512-Ywau/Qs1DzM/8Uc+yA77CwSegizMlcgTJuYGAi0jujOteJOUf1ujunHThYo243KG9nAyWT3L9ifPYZ5+As/+6Q==", "dev": true, "dependencies": { "tinyspy": "^2.2.0" @@ -2755,9 +2756,9 @@ } }, "node_modules/@vitest/utils": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-1.3.1.tgz", - "integrity": "sha512-d3Waie/299qqRyHTm2DjADeTaNdNSVsnwHPWrs20JMpjh6eiVq7ggggweO8rc4arhf6rRkWuHKwvxGvejUXZZQ==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-1.4.0.tgz", + "integrity": "sha512-mx3Yd1/6e2Vt/PUC98DcqTirtfxUyAZ32uK82r8rZzbtBeBo+nqgnjx/LvqQdWsrvNtm14VmurNgcf4nqY5gJg==", "dev": true, "dependencies": { "diff-sequences": "^29.6.3", @@ -5649,14 +5650,14 @@ } }, "node_modules/istanbul-lib-source-maps": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", - "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.4.tgz", + "integrity": "sha512-wHOoEsNJTVltaJp8eVkm8w+GVkVNHT2YDYo53YdzQEL2gWm1hBX5cGFR9hQJtuGLebidVX7et3+dmDZrmclduw==", "dev": true, "dependencies": { + "@jridgewell/trace-mapping": "^0.3.23", "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0", - "source-map": "^0.6.1" + "istanbul-lib-coverage": "^3.0.0" }, "engines": { "node": ">=10" @@ -7671,9 +7672,9 @@ } }, "node_modules/socket.io-client": { - "version": "4.7.4", - "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.7.4.tgz", - "integrity": "sha512-wh+OkeF0rAVCrABWQBaEjLfb7DVPotMbu0cgWgyR0v6eA4EoVnAwcIeIbcdTE3GT/H3kbdLl7OoH2+asoDRIIg==", + "version": "4.7.5", + "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.7.5.tgz", + "integrity": "sha512-sJ/tqHOCe7Z50JCBCXrsY3I2k03iOiUe+tj1OmKeD2lXPiGH/RUCdTZFoqVyN7l1MnpIzPrGtLcijffmeouNlQ==", "dependencies": { "@socket.io/component-emitter": "~3.1.0", "debug": "~4.3.2", @@ -8038,9 +8039,9 @@ } }, "node_modules/svelte-check": { - "version": "3.6.6", - "resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-3.6.6.tgz", - "integrity": "sha512-b9q9rOHOMYF3U8XllK7LmXTq1LeWQ98waGfEJzrFutViadkNl1tgdEtxIQ8yuPx+VQ4l7YrknYol+0lfZocaZw==", + "version": "3.6.7", + "resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-3.6.7.tgz", + "integrity": "sha512-tKEjemK9FYCySAseCaIt+ps5o0XRvLC7ECjyJXXtO7vOQhR9E6JavgoUbGP1PCulD2OTcB/fi9RjV3nyF1AROw==", "dev": true, "dependencies": { "@jridgewell/trace-mapping": "^0.3.17", @@ -8670,9 +8671,9 @@ } }, "node_modules/vite": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.1.5.tgz", - "integrity": "sha512-BdN1xh0Of/oQafhU+FvopafUp6WaYenLU/NFoL5WyJL++GxkNfieKzBhM24H3HVsPQrlAqB7iJYTHabzaRed5Q==", + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.1.6.tgz", + "integrity": "sha512-yYIAZs9nVfRJ/AiOLCA91zzhjsHUgMjB+EigzFb6W2XTLO8JixBCKCjvhKZaye+NKYHCrkv3Oh50dH9EdLU2RA==", "dev": true, "dependencies": { "esbuild": "^0.19.3", @@ -8738,9 +8739,9 @@ } }, "node_modules/vite-node": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-1.3.1.tgz", - "integrity": "sha512-azbRrqRxlWTJEVbzInZCTchx0X69M/XPTCz4H+TLvlTcR/xH/3hkRqhOakT41fMJCMzXTu4UvegkZiEoJAWvng==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-1.4.0.tgz", + "integrity": "sha512-VZDAseqjrHgNd4Kh8icYHWzTKSCZMhia7GyHfhtzLW33fZlG9SwsB6CEhgyVOWkJfJ2pFLrp/Gj1FSfAiqH9Lw==", "dev": true, "dependencies": { "cac": "^6.7.14", @@ -8774,16 +8775,16 @@ } }, "node_modules/vitest": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-1.3.1.tgz", - "integrity": "sha512-/1QJqXs8YbCrfv/GPQ05wAZf2eakUPLPa18vkJAKE7RXOKfVHqMZZ1WlTjiwl6Gcn65M5vpNUB6EFLnEdRdEXQ==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-1.4.0.tgz", + "integrity": "sha512-gujzn0g7fmwf83/WzrDTnncZt2UiXP41mHuFYFrdwaLRVQ6JYQEiME2IfEjU3vcFL3VKa75XhI3lFgn+hfVsQw==", "dev": true, "dependencies": { - "@vitest/expect": "1.3.1", - "@vitest/runner": "1.3.1", - "@vitest/snapshot": "1.3.1", - "@vitest/spy": "1.3.1", - "@vitest/utils": "1.3.1", + "@vitest/expect": "1.4.0", + "@vitest/runner": "1.4.0", + "@vitest/snapshot": "1.4.0", + "@vitest/spy": "1.4.0", + "@vitest/utils": "1.4.0", "acorn-walk": "^8.3.2", "chai": "^4.3.10", "debug": "^4.3.4", @@ -8797,7 +8798,7 @@ "tinybench": "^2.5.1", "tinypool": "^0.8.2", "vite": "^5.0.0", - "vite-node": "1.3.1", + "vite-node": "1.4.0", "why-is-node-running": "^2.2.2" }, "bin": { @@ -8812,8 +8813,8 @@ "peerDependencies": { "@edge-runtime/vm": "*", "@types/node": "^18.0.0 || >=20.0.0", - "@vitest/browser": "1.3.1", - "@vitest/ui": "1.3.1", + "@vitest/browser": "1.4.0", + "@vitest/ui": "1.4.0", "happy-dom": "*", "jsdom": "*" }, diff --git a/web/package.json b/web/package.json index 4714b09b0..d151faaa9 100644 --- a/web/package.json +++ b/web/package.json @@ -1,6 +1,6 @@ { "name": "immich-web", - "version": "1.98.2", + "version": "1.99.0", "license": "GNU Affero General Public License version 3", "scripts": { "dev": "vite dev --host 0.0.0.0 --port 3000", diff --git a/web/src/lib/components/photos-page/asset-grid.svelte b/web/src/lib/components/photos-page/asset-grid.svelte index c21439d24..5892442df 100644 --- a/web/src/lib/components/photos-page/asset-grid.svelte +++ b/web/src/lib/components/photos-page/asset-grid.svelte @@ -207,7 +207,7 @@ return; } - if (matchesShortcut(event, { key: 'Shift' })) { + if (matchesShortcut(event, { key: 'Shift', shift: true })) { event.preventDefault(); shiftKeyIsDown = true; } @@ -218,7 +218,7 @@ return; } - if (matchesShortcut(event, { key: 'Shift' })) { + if (matchesShortcut(event, { key: 'Shift', shift: false })) { event.preventDefault(); shiftKeyIsDown = false; } diff --git a/web/src/lib/stores/asset.store.spec.ts b/web/src/lib/stores/asset.store.spec.ts index d97692ef6..55f3fe1ff 100644 --- a/web/src/lib/stores/asset.store.spec.ts +++ b/web/src/lib/stores/asset.store.spec.ts @@ -1,4 +1,5 @@ import { sdkMock } from '$lib/__mocks__/sdk.mock'; +import { AbortError } from '$lib/utils'; import { TimeBucketSize, type AssetResponseDto } from '@immich/sdk'; import { assetFactory } from '@test-data/factories/asset-factory'; import { AssetStore, BucketPosition } from './assets.store'; @@ -62,7 +63,15 @@ describe('AssetStore', () => { { count: 1, timeBucket: '2024-01-03T00:00:00.000Z' }, { count: 3, timeBucket: '2024-01-01T00:00:00.000Z' }, ]); - sdkMock.getTimeBucket.mockImplementation(({ timeBucket }) => Promise.resolve(bucketAssets[timeBucket])); + sdkMock.getTimeBucket.mockImplementation(async ({ timeBucket }, { signal } = {}) => { + // Allow request to be aborted + await new Promise((resolve) => setTimeout(resolve, 0)); + if (signal?.aborted) { + throw new AbortError(); + } + + return bucketAssets[timeBucket]; + }); await assetStore.init({ width: 0, height: 0 }); }); @@ -87,17 +96,39 @@ describe('AssetStore', () => { }); it('cancels bucket loading', async () => { - const abortSpy = vi.spyOn(AbortController.prototype, 'abort'); - const loadPromise = assetStore.loadBucket('2024-01-01T00:00:00.000Z', BucketPosition.Unknown); - const bucket = assetStore.getBucketByDate('2024-01-01T00:00:00.000Z'); - expect(bucket).not.toBeNull(); + const loadPromise = assetStore.loadBucket(bucket!.bucketDate, BucketPosition.Unknown); + const abortSpy = vi.spyOn(bucket!.cancelToken!, 'abort'); assetStore.cancelBucket(bucket!); expect(abortSpy).toBeCalledTimes(1); + await loadPromise; expect(assetStore.getBucketByDate('2024-01-01T00:00:00.000Z')?.assets.length).toEqual(0); }); + + it('prevents loading buckets multiple times', async () => { + await Promise.all([ + assetStore.loadBucket('2024-01-01T00:00:00.000Z', BucketPosition.Unknown), + assetStore.loadBucket('2024-01-01T00:00:00.000Z', BucketPosition.Unknown), + ]); + expect(sdkMock.getTimeBucket).toBeCalledTimes(1); + + await assetStore.loadBucket('2024-01-01T00:00:00.000Z', BucketPosition.Unknown); + expect(sdkMock.getTimeBucket).toBeCalledTimes(1); + }); + + it('allows loading a canceled bucket', async () => { + const bucket = assetStore.getBucketByDate('2024-01-01T00:00:00.000Z'); + const loadPromise = assetStore.loadBucket(bucket!.bucketDate, BucketPosition.Unknown); + + assetStore.cancelBucket(bucket!); + await loadPromise; + expect(bucket?.assets.length).toEqual(0); + + await assetStore.loadBucket(bucket!.bucketDate, BucketPosition.Unknown); + expect(bucket!.assets.length).toEqual(3); + }); }); describe('addAssets', () => { diff --git a/web/src/lib/stores/assets.store.ts b/web/src/lib/stores/assets.store.ts index 30fc06085..45b26c1a1 100644 --- a/web/src/lib/stores/assets.store.ts +++ b/web/src/lib/stores/assets.store.ts @@ -229,21 +229,21 @@ export class AssetStore { } async loadBucket(bucketDate: string, position: BucketPosition): Promise { + const bucket = this.getBucketByDate(bucketDate); + if (!bucket) { + return; + } + + bucket.position = position; + + if (bucket.cancelToken || bucket.assets.length > 0) { + this.emit(false); + return; + } + + bucket.cancelToken = new AbortController(); + try { - const bucket = this.getBucketByDate(bucketDate); - if (!bucket) { - return; - } - - bucket.position = position; - - if (bucket.assets.length > 0) { - this.emit(false); - return; - } - - bucket.cancelToken = new AbortController(); - const assets = await getTimeBucket( { ...this.options, @@ -278,6 +278,8 @@ export class AssetStore { this.emit(true); } catch (error) { handleError(error, 'Failed to load assets'); + } finally { + bucket.cancelToken = null; } } diff --git a/web/src/lib/utils.ts b/web/src/lib/utils.ts index 4b1892ae8..2cebba630 100644 --- a/web/src/lib/utils.ts +++ b/web/src/lib/utils.ts @@ -27,7 +27,7 @@ interface UploadRequestOptions { onUploadProgress?: (event: ProgressEvent) => void; } -class AbortError extends Error { +export class AbortError extends Error { name = 'AbortError'; } diff --git a/web/src/routes/(user)/albums/[albumId]/+page.svelte b/web/src/routes/(user)/albums/[albumId]/+page.svelte index 0d171d5a4..2f74a7872 100644 --- a/web/src/routes/(user)/albums/[albumId]/+page.svelte +++ b/web/src/routes/(user)/albums/[albumId]/+page.svelte @@ -148,7 +148,7 @@ backUrl = url || AppRoute.ALBUMS; - if (backUrl === AppRoute.SHARING && album.sharedUsers.length === 0) { + if (backUrl === AppRoute.SHARING && album.sharedUsers.length === 0 && !album.hasSharedLink) { isCreatingSharedAlbum = true; } }); diff --git a/web/src/routes/(user)/people/[personId]/+page.svelte b/web/src/routes/(user)/people/[personId]/+page.svelte index d532018a4..ae99b0762 100644 --- a/web/src/routes/(user)/people/[personId]/+page.svelte +++ b/web/src/routes/(user)/people/[personId]/+page.svelte @@ -434,7 +434,7 @@ {/if} -
+
{#key refreshAssetGrid} ; $: searchQuery = $page.url.searchParams.get(QueryParameter.QUERY); - $: terms = ((): SearchTerms => { - return searchQuery ? JSON.parse(searchQuery) : {}; - })(); + let terms: SearchTerms; + $: terms = searchQuery ? JSON.parse(searchQuery) : {}; - $: terms, handlePromiseError(onSearchQueryUpdate()); + $: if (terms && $featureFlags.loaded) { + handlePromiseError(onSearchQueryUpdate()); + } async function onSearchQueryUpdate() { nextPage = 1; @@ -124,18 +127,23 @@ ...terms, }; - const { albums, assets } = - 'query' in searchDto - ? await searchSmart({ smartSearchDto: searchDto }) - : await searchMetadata({ metadataSearchDto: searchDto }); + try { + const { albums, assets } = + 'query' in searchDto && $featureFlags.smartSearch + ? await searchSmart({ smartSearchDto: searchDto }) + : await searchMetadata({ metadataSearchDto: searchDto }); - searchResultAlbums.push(...albums.items); - searchResultAssets.push(...assets.items); - searchResultAlbums = searchResultAlbums; - searchResultAssets = searchResultAssets; + searchResultAlbums.push(...albums.items); + searchResultAssets.push(...assets.items); + searchResultAlbums = searchResultAlbums; + searchResultAssets = searchResultAssets; - nextPage = assets.nextPage ? Number(assets.nextPage) : null; - isLoading = false; + nextPage = assets.nextPage ? Number(assets.nextPage) : null; + } catch (error) { + handleError(error, 'Loading search results failed'); + } finally { + isLoading = false; + } }; function getHumanReadableDate(dateString: string) {