mirror of
https://github.com/immich-app/immich.git
synced 2026-05-18 03:10:24 +03:00
Merge branch 'main' into feat/search-filter-album/web
This commit is contained in:
+47
-49
@@ -1,26 +1,27 @@
|
||||
# dev build
|
||||
FROM ghcr.io/immich-app/base-server-dev:202507091427@sha256:733e510024e03bc2450608a1f3622f45bf287b70d89a06e60acbccea4bf4fd8c AS dev
|
||||
FROM ghcr.io/immich-app/base-server-dev:202507162011@sha256:85d4230c2208646bd6c528db41b2213d780b11b7a311397ca6a2aaba7cf697c8 AS dev
|
||||
|
||||
WORKDIR /usr/src/app
|
||||
COPY server/package.json server/package-lock.json ./
|
||||
COPY ./server/package* ./server/
|
||||
WORKDIR /usr/src/app/server
|
||||
RUN npm ci && \
|
||||
# exiftool-vendored.pl, sharp-linux-x64 and sharp-linux-arm64 are the only ones we need
|
||||
# they're marked as optional dependencies, so we need to copy them manually after pruning
|
||||
rm -rf node_modules/@img/sharp-libvips* && \
|
||||
rm -rf node_modules/@img/sharp-linuxmusl-x64
|
||||
ENV PATH="${PATH}:/usr/src/app/bin" \
|
||||
IMMICH_ENV=development \
|
||||
NVIDIA_DRIVER_CAPABILITIES=all \
|
||||
NVIDIA_VISIBLE_DEVICES=all
|
||||
ENTRYPOINT ["tini", "--", "/bin/sh"]
|
||||
# exiftool-vendored.pl, sharp-linux-x64 and sharp-linux-arm64 are the only ones we need
|
||||
# they're marked as optional dependencies, so we need to copy them manually after pruning
|
||||
rm -rf node_modules/@img/sharp-libvips* && \
|
||||
rm -rf node_modules/@img/sharp-linuxmusl-x64
|
||||
ENV PATH="${PATH}:/usr/src/app/server/bin" \
|
||||
IMMICH_ENV=development \
|
||||
NVIDIA_DRIVER_CAPABILITIES=all \
|
||||
NVIDIA_VISIBLE_DEVICES=all
|
||||
ENTRYPOINT ["tini", "--", "/bin/bash", "-c"]
|
||||
|
||||
FROM dev AS dev-container-server
|
||||
|
||||
RUN rm -rf /usr/src/app
|
||||
RUN apt-get update && \
|
||||
apt-get install sudo inetutils-ping openjdk-11-jre-headless \
|
||||
vim nano \
|
||||
-y --no-install-recommends --fix-missing
|
||||
apt-get install sudo inetutils-ping openjdk-11-jre-headless \
|
||||
vim nano \
|
||||
-y --no-install-recommends --fix-missing
|
||||
|
||||
RUN usermod -aG sudo node
|
||||
RUN echo "node ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers
|
||||
@@ -38,14 +39,14 @@ FROM dev-container-server AS dev-container-mobile
|
||||
USER root
|
||||
# Enable multiarch for arm64 if necessary
|
||||
RUN if [ "$(dpkg --print-architecture)" = "arm64" ]; then \
|
||||
dpkg --add-architecture amd64 && \
|
||||
apt-get update && \
|
||||
apt-get install -y --no-install-recommends \
|
||||
qemu-user-static \
|
||||
libc6:amd64 \
|
||||
libstdc++6:amd64 \
|
||||
libgcc1:amd64; \
|
||||
fi
|
||||
dpkg --add-architecture amd64 && \
|
||||
apt-get update && \
|
||||
apt-get install -y --no-install-recommends \
|
||||
qemu-user-static \
|
||||
libc6:amd64 \
|
||||
libstdc++6:amd64 \
|
||||
libgcc1:amd64; \
|
||||
fi
|
||||
|
||||
# Flutter SDK
|
||||
# https://flutter.dev/docs/development/tools/sdk/releases?tab=linux
|
||||
@@ -77,45 +78,42 @@ FROM dev AS prod
|
||||
COPY server .
|
||||
RUN npm run build
|
||||
RUN npm prune --omit=dev --omit=optional
|
||||
COPY --from=dev /usr/src/app/node_modules/@img ./node_modules/@img
|
||||
COPY --from=dev /usr/src/app/node_modules/exiftool-vendored.pl ./node_modules/exiftool-vendored.pl
|
||||
COPY --from=dev /usr/src/app/server/node_modules/@img ./node_modules/@img
|
||||
COPY --from=dev /usr/src/app/server/node_modules/exiftool-vendored.pl ./node_modules/exiftool-vendored.pl
|
||||
|
||||
# web build
|
||||
FROM node:22.16.0-alpine3.20@sha256:2289fb1fba0f4633b08ec47b94a89c7e20b829fc5679f9b7b298eaa2f1ed8b7e AS web
|
||||
|
||||
WORKDIR /usr/src/open-api/typescript-sdk
|
||||
COPY open-api/typescript-sdk/package*.json open-api/typescript-sdk/tsconfig*.json ./
|
||||
RUN npm ci
|
||||
COPY open-api/typescript-sdk/ ./
|
||||
RUN npm run build
|
||||
|
||||
WORKDIR /usr/src/app
|
||||
COPY web/package*.json web/svelte.config.js ./
|
||||
RUN npm ci
|
||||
COPY web ./
|
||||
COPY i18n ../i18n
|
||||
RUN npm run build
|
||||
COPY ./web ./web/
|
||||
COPY ./i18n ./i18n/
|
||||
COPY ./open-api/typescript-sdk ./open-api/typescript-sdk/
|
||||
|
||||
WORKDIR /usr/src/app/open-api/typescript-sdk
|
||||
RUN npm ci && npm run build
|
||||
|
||||
WORKDIR /usr/src/app/web
|
||||
RUN npm ci && npm run build
|
||||
|
||||
# prod build
|
||||
FROM ghcr.io/immich-app/base-server-prod:202507091427@sha256:ad10451acd8eda05a006a2586b6b0425cdaaca97c413fb4c1a10896712528bdc
|
||||
FROM ghcr.io/immich-app/base-server-prod:202507162011@sha256:636f3ddb6106628ef851d51c23f3fa2c6e4829390cc315b27b38c288c82b23a7
|
||||
|
||||
WORKDIR /usr/src/app
|
||||
ENV NODE_ENV=production \
|
||||
NVIDIA_DRIVER_CAPABILITIES=all \
|
||||
NVIDIA_VISIBLE_DEVICES=all
|
||||
COPY --from=prod /usr/src/app/node_modules ./node_modules
|
||||
COPY --from=prod /usr/src/app/dist ./dist
|
||||
COPY --from=prod /usr/src/app/bin ./bin
|
||||
COPY --from=web /usr/src/app/build /build/www
|
||||
COPY server/resources resources
|
||||
COPY server/package.json server/package-lock.json ./
|
||||
COPY server/start*.sh ./
|
||||
COPY "docker/scripts/get-cpus.sh" ./
|
||||
RUN npm install -g @immich/cli && npm cache clean --force
|
||||
NVIDIA_DRIVER_CAPABILITIES=all \
|
||||
NVIDIA_VISIBLE_DEVICES=all
|
||||
|
||||
COPY --from=prod /usr/src/app/server/node_modules ./server/node_modules
|
||||
COPY --from=prod /usr/src/app/server/dist ./server/dist
|
||||
COPY --from=prod /usr/src/app/server/bin ./server/bin
|
||||
COPY --from=web /usr/src/app/web/build /build/www
|
||||
COPY ./server/resources ./server/resources
|
||||
COPY ./server/package.json server/package-lock.json ./
|
||||
COPY LICENSE /licenses/LICENSE.txt
|
||||
COPY LICENSE /LICENSE
|
||||
ENV PATH="${PATH}:/usr/src/app/bin"
|
||||
|
||||
RUN npm install -g @immich/cli && npm cache clean --force
|
||||
ENV PATH="${PATH}:/usr/src/app/server/bin"
|
||||
|
||||
ARG BUILD_ID
|
||||
ARG BUILD_IMAGE
|
||||
@@ -134,7 +132,7 @@ ENV IMMICH_SOURCE_URL=https://github.com/immich-app/immich/commit/${BUILD_SOURCE
|
||||
|
||||
VOLUME /usr/src/app/upload
|
||||
EXPOSE 2283
|
||||
ENTRYPOINT ["tini", "--", "/bin/bash"]
|
||||
ENTRYPOINT ["tini", "--", "/bin/bash", "-c"]
|
||||
CMD ["start.sh"]
|
||||
|
||||
HEALTHCHECK CMD immich-healthcheck
|
||||
|
||||
Executable
+49
@@ -0,0 +1,49 @@
|
||||
#!/bin/sh
|
||||
|
||||
set -eu
|
||||
|
||||
LOG_LEVEL="${IMMICH_LOG_LEVEL:='info'}"
|
||||
|
||||
logDebug() {
|
||||
if [ "$LOG_LEVEL" = "debug" ] || [ "$LOG_LEVEL" = "verbose" ]; then
|
||||
echo "DEBUG: $1" >&2
|
||||
fi
|
||||
}
|
||||
|
||||
if [ -f /sys/fs/cgroup/cgroup.controllers ]; then
|
||||
logDebug "cgroup v2 detected."
|
||||
if [ -f /sys/fs/cgroup/cpu.max ]; then
|
||||
read -r quota period </sys/fs/cgroup/cpu.max
|
||||
if [ "$quota" = "max" ]; then
|
||||
logDebug "No CPU limits set."
|
||||
unset quota period
|
||||
fi
|
||||
else
|
||||
logDebug "/sys/fs/cgroup/cpu.max not found."
|
||||
fi
|
||||
else
|
||||
logDebug "cgroup v1 detected."
|
||||
|
||||
if [ -f /sys/fs/cgroup/cpu/cpu.cfs_quota_us ] && [ -f /sys/fs/cgroup/cpu/cpu.cfs_period_us ]; then
|
||||
quota=$(cat /sys/fs/cgroup/cpu/cpu.cfs_quota_us)
|
||||
period=$(cat /sys/fs/cgroup/cpu/cpu.cfs_period_us)
|
||||
|
||||
if [ "$quota" = "-1" ]; then
|
||||
logDebug "No CPU limits set."
|
||||
unset quota period
|
||||
fi
|
||||
else
|
||||
logDebug "/sys/fs/cgroup/cpu/cpu.cfs_quota_us or /sys/fs/cgroup/cpu/cpu.cfs_period_us not found."
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -n "${quota:-}" ] && [ -n "${period:-}" ]; then
|
||||
cpus=$((quota / period))
|
||||
if [ "$cpus" -eq 0 ]; then
|
||||
cpus=1
|
||||
fi
|
||||
else
|
||||
cpus=$(grep -c ^processor /proc/cpuinfo)
|
||||
fi
|
||||
|
||||
echo "$cpus"
|
||||
@@ -1,3 +1,3 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
/usr/src/app/start.sh immich-admin "$@"
|
||||
start.sh immich-admin "$@"
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
node /usr/src/app/node_modules/.bin/nest start --debug "0.0.0.0:9230" --watch -- "$@"
|
||||
if [ "$IMMICH_ENV" != "development" ]; then
|
||||
echo "This command can only be run in development environments"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cd /usr/src/app/server || exit 1
|
||||
npm exec nest start --debug "0.0.0.0:9230" --watch -- "$@"
|
||||
|
||||
Executable
+44
@@ -0,0 +1,44 @@
|
||||
#!/usr/bin/env bash
|
||||
echo "Initializing Immich $IMMICH_SOURCE_REF"
|
||||
|
||||
lib_path="/usr/lib/$(arch)-linux-gnu/libmimalloc.so.2"
|
||||
if [ -f "$lib_path" ]; then
|
||||
export LD_PRELOAD="$lib_path"
|
||||
else
|
||||
echo "skipping libmimalloc - path not found $lib_path"
|
||||
fi
|
||||
export LD_LIBRARY_PATH="$LD_LIBRARY_PATH:/usr/lib/jellyfin-ffmpeg/lib"
|
||||
SERVER_HOME=/usr/src/app/server
|
||||
|
||||
read_file_and_export() {
|
||||
if [ -n "${!1}" ]; then
|
||||
content="$(cat "${!1}")"
|
||||
export "$2"="${content}"
|
||||
unset "$1"
|
||||
fi
|
||||
}
|
||||
read_file_and_export "DB_URL_FILE" "DB_URL"
|
||||
read_file_and_export "DB_HOSTNAME_FILE" "DB_HOSTNAME"
|
||||
read_file_and_export "DB_DATABASE_NAME_FILE" "DB_DATABASE_NAME"
|
||||
read_file_and_export "DB_USERNAME_FILE" "DB_USERNAME"
|
||||
read_file_and_export "DB_PASSWORD_FILE" "DB_PASSWORD"
|
||||
read_file_and_export "REDIS_PASSWORD_FILE" "REDIS_PASSWORD"
|
||||
|
||||
if CPU_CORES="${CPU_CORES:=$(get-cpus.sh 2>/dev/null)}"; then
|
||||
echo "Detected CPU Cores: $CPU_CORES"
|
||||
if [ "$CPU_CORES" -gt 4 ]; then
|
||||
export UV_THREADPOOL_SIZE=$CPU_CORES
|
||||
fi
|
||||
else
|
||||
echo "skipping get-cpus.sh - not found in PATH or failed: using default UV_THREADPOOL_SIZE"
|
||||
fi
|
||||
|
||||
if [ -f "${SERVER_HOME}/dist/main.js" ]; then
|
||||
exec node "${SERVER_HOME}/dist/main.js" "$@"
|
||||
else
|
||||
echo "Error: ${SERVER_HOME}/dist/main.js not found"
|
||||
if [ "$IMMICH_ENV" = "development" ]; then
|
||||
echo "You may need to build the server first."
|
||||
fi
|
||||
exit 1
|
||||
fi
|
||||
Generated
+64
-3
@@ -2882,6 +2882,67 @@
|
||||
"@nestjs/core": "^11.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@nestjs/platform-express/node_modules/media-typer": {
|
||||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
|
||||
"integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/@nestjs/platform-express/node_modules/mime-db": {
|
||||
"version": "1.52.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
||||
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/@nestjs/platform-express/node_modules/mime-types": {
|
||||
"version": "2.1.35",
|
||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
|
||||
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"mime-db": "1.52.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/@nestjs/platform-express/node_modules/multer": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/multer/-/multer-2.0.1.tgz",
|
||||
"integrity": "sha512-Ug8bXeTIUlxurg8xLTEskKShvcKDZALo1THEX5E41pYCD2sCVub5/kIRIGqWNoqV6szyLyQKV6mD4QUrWE5GCQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"append-field": "^1.0.0",
|
||||
"busboy": "^1.6.0",
|
||||
"concat-stream": "^2.0.0",
|
||||
"mkdirp": "^0.5.6",
|
||||
"object-assign": "^4.1.1",
|
||||
"type-is": "^1.6.18",
|
||||
"xtend": "^4.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10.16.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@nestjs/platform-express/node_modules/type-is": {
|
||||
"version": "1.6.18",
|
||||
"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
|
||||
"integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"media-typer": "0.3.0",
|
||||
"mime-types": "~2.1.24"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/@nestjs/platform-socket.io": {
|
||||
"version": "11.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@nestjs/platform-socket.io/-/platform-socket.io-11.1.3.tgz",
|
||||
@@ -13595,9 +13656,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/multer": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/multer/-/multer-2.0.1.tgz",
|
||||
"integrity": "sha512-Ug8bXeTIUlxurg8xLTEskKShvcKDZALo1THEX5E41pYCD2sCVub5/kIRIGqWNoqV6szyLyQKV6mD4QUrWE5GCQ==",
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/multer/-/multer-2.0.2.tgz",
|
||||
"integrity": "sha512-u7f2xaZ/UG8oLXHvtF/oWTRvT44p9ecwBBqTwgJVq0+4BW1g8OW01TyMEGWBHbyMOYVHXslaut7qEQ1meATXgw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"append-field": "^1.0.0",
|
||||
|
||||
@@ -5,7 +5,7 @@ import { ScheduleModule, SchedulerRegistry } from '@nestjs/schedule';
|
||||
import { ClsModule } from 'nestjs-cls';
|
||||
import { KyselyModule } from 'nestjs-kysely';
|
||||
import { OpenTelemetryModule } from 'nestjs-otel';
|
||||
import { commands } from 'src/commands';
|
||||
import { commandsAndQuestions } from 'src/commands';
|
||||
import { IWorker } from 'src/constants';
|
||||
import { controllers } from 'src/controllers';
|
||||
import { ImmichWorker } from 'src/enum';
|
||||
@@ -85,19 +85,19 @@ class BaseModule implements OnModuleInit, OnModuleDestroy {
|
||||
@Module({
|
||||
imports: [...imports, ScheduleModule.forRoot()],
|
||||
controllers: [...controllers],
|
||||
providers: [...common, ...middleware, { provide: IWorker, useValue: ImmichWorker.API }],
|
||||
providers: [...common, ...middleware, { provide: IWorker, useValue: ImmichWorker.Api }],
|
||||
})
|
||||
export class ApiModule extends BaseModule {}
|
||||
|
||||
@Module({
|
||||
imports: [...imports],
|
||||
providers: [...common, { provide: IWorker, useValue: ImmichWorker.MICROSERVICES }, SchedulerRegistry],
|
||||
providers: [...common, { provide: IWorker, useValue: ImmichWorker.Microservices }, SchedulerRegistry],
|
||||
})
|
||||
export class MicroservicesModule extends BaseModule {}
|
||||
|
||||
@Module({
|
||||
imports: [...imports],
|
||||
providers: [...common, ...commands, SchedulerRegistry],
|
||||
providers: [...common, ...commandsAndQuestions, SchedulerRegistry],
|
||||
})
|
||||
export class ImmichAdminModule implements OnModuleDestroy {
|
||||
constructor(private service: CliService) {}
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
import { GrantAdminCommand, PromptEmailQuestion, RevokeAdminCommand } from 'src/commands/grant-admin';
|
||||
import { ListUsersCommand } from 'src/commands/list-users.command';
|
||||
import {
|
||||
ChangeMediaLocationCommand,
|
||||
PromptConfirmMoveQuestions,
|
||||
PromptMediaLocationQuestions,
|
||||
} from 'src/commands/media-location.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 { VersionCommand } from 'src/commands/version.command';
|
||||
|
||||
export const commands = [
|
||||
export const commandsAndQuestions = [
|
||||
ResetAdminPasswordCommand,
|
||||
PromptPasswordQuestions,
|
||||
PromptEmailQuestion,
|
||||
@@ -17,4 +22,7 @@ export const commands = [
|
||||
VersionCommand,
|
||||
GrantAdminCommand,
|
||||
RevokeAdminCommand,
|
||||
ChangeMediaLocationCommand,
|
||||
PromptMediaLocationQuestions,
|
||||
PromptConfirmMoveQuestions,
|
||||
];
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
import { Command, CommandRunner, InquirerService, Question, QuestionSet } from 'nest-commander';
|
||||
import { CliService } from 'src/services/cli.service';
|
||||
|
||||
@Command({
|
||||
name: 'change-media-location',
|
||||
description: 'Change database file paths to align with a new media location',
|
||||
})
|
||||
export class ChangeMediaLocationCommand extends CommandRunner {
|
||||
constructor(
|
||||
private service: CliService,
|
||||
private inquirer: InquirerService,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
private async showSamplePaths(hint?: string) {
|
||||
hint = hint ? ` (${hint})` : '';
|
||||
|
||||
const paths = await this.service.getSampleFilePaths();
|
||||
if (paths.length > 0) {
|
||||
let message = ` Examples from the database${hint}:\n`;
|
||||
for (const path of paths) {
|
||||
message += ` - ${path}\n`;
|
||||
}
|
||||
|
||||
console.log(`\n${message}`);
|
||||
}
|
||||
}
|
||||
|
||||
async run(): Promise<void> {
|
||||
try {
|
||||
await this.showSamplePaths();
|
||||
|
||||
const { oldValue, newValue } = await this.inquirer.ask<{ oldValue: string; newValue: string }>(
|
||||
'prompt-media-location',
|
||||
{},
|
||||
);
|
||||
|
||||
const success = await this.service.migrateFilePaths({
|
||||
oldValue,
|
||||
newValue,
|
||||
confirm: async ({ sourceFolder, targetFolder }) => {
|
||||
console.log(`
|
||||
Previous value: ${oldValue}
|
||||
Current value: ${newValue}
|
||||
|
||||
Changing from "${sourceFolder}/*" to "${targetFolder}/*"
|
||||
`);
|
||||
|
||||
const { value: confirmed } = await this.inquirer.ask<{ value: boolean }>('prompt-confirm-move', {});
|
||||
return confirmed;
|
||||
},
|
||||
});
|
||||
|
||||
const successMessage = `Matching database file paths were updated successfully! 🎉
|
||||
|
||||
You may now set IMMICH_MEDIA_LOCATION=${newValue} and restart!
|
||||
|
||||
(please remember to update applicable volume mounts e.g
|
||||
services:
|
||||
immich-server:
|
||||
...
|
||||
volumes:
|
||||
- \${UPLOAD_LOCATION}:/usr/src/app/upload
|
||||
...
|
||||
)`;
|
||||
|
||||
console.log(`\n ${success ? successMessage : 'No rows were updated'}\n`);
|
||||
|
||||
await this.showSamplePaths('after');
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
console.error('Unable to update database file paths.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const currentValue = process.env.IMMICH_MEDIA_LOCATION || '';
|
||||
|
||||
const makePrompt = (which: string) => {
|
||||
return `Enter the ${which} value of IMMICH_MEDIA_LOCATION:${currentValue ? ` [${currentValue}]` : ''}`;
|
||||
};
|
||||
|
||||
@QuestionSet({ name: 'prompt-media-location' })
|
||||
export class PromptMediaLocationQuestions {
|
||||
@Question({ message: makePrompt('previous'), name: 'oldValue' })
|
||||
oldValue(value: string) {
|
||||
return value || currentValue;
|
||||
}
|
||||
|
||||
@Question({ message: makePrompt('new'), name: 'newValue' })
|
||||
newValue(value: string) {
|
||||
return value || currentValue;
|
||||
}
|
||||
}
|
||||
|
||||
@QuestionSet({ name: 'prompt-confirm-move' })
|
||||
export class PromptConfirmMoveQuestions {
|
||||
@Question({
|
||||
message: 'Do you want to proceed? [Y/n]',
|
||||
name: 'value',
|
||||
})
|
||||
value(value: string): boolean {
|
||||
return ['yes', 'y'].includes((value || 'y').toLowerCase());
|
||||
}
|
||||
}
|
||||
+25
-25
@@ -8,7 +8,7 @@ import {
|
||||
OAuthTokenEndpointAuthMethod,
|
||||
QueueName,
|
||||
ToneMapping,
|
||||
TranscodeHWAccel,
|
||||
TranscodeHardwareAcceleration,
|
||||
TranscodePolicy,
|
||||
VideoCodec,
|
||||
VideoContainer,
|
||||
@@ -42,7 +42,7 @@ export interface SystemConfig {
|
||||
twoPass: boolean;
|
||||
preferredHwDevice: string;
|
||||
transcode: TranscodePolicy;
|
||||
accel: TranscodeHWAccel;
|
||||
accel: TranscodeHardwareAcceleration;
|
||||
accelDecode: boolean;
|
||||
tonemap: ToneMapping;
|
||||
};
|
||||
@@ -190,39 +190,39 @@ export const defaults = Object.freeze<SystemConfig>({
|
||||
preset: 'ultrafast',
|
||||
targetVideoCodec: VideoCodec.H264,
|
||||
acceptedVideoCodecs: [VideoCodec.H264],
|
||||
targetAudioCodec: AudioCodec.AAC,
|
||||
acceptedAudioCodecs: [AudioCodec.AAC, AudioCodec.MP3, AudioCodec.LIBOPUS, AudioCodec.PCMS16LE],
|
||||
acceptedContainers: [VideoContainer.MOV, VideoContainer.OGG, VideoContainer.WEBM],
|
||||
targetAudioCodec: AudioCodec.Aac,
|
||||
acceptedAudioCodecs: [AudioCodec.Aac, AudioCodec.Mp3, AudioCodec.LibOpus, AudioCodec.PcmS16le],
|
||||
acceptedContainers: [VideoContainer.Mov, VideoContainer.Ogg, VideoContainer.Webm],
|
||||
targetResolution: '720',
|
||||
maxBitrate: '0',
|
||||
bframes: -1,
|
||||
refs: 0,
|
||||
gopSize: 0,
|
||||
temporalAQ: false,
|
||||
cqMode: CQMode.AUTO,
|
||||
cqMode: CQMode.Auto,
|
||||
twoPass: false,
|
||||
preferredHwDevice: 'auto',
|
||||
transcode: TranscodePolicy.REQUIRED,
|
||||
tonemap: ToneMapping.HABLE,
|
||||
accel: TranscodeHWAccel.DISABLED,
|
||||
transcode: TranscodePolicy.Required,
|
||||
tonemap: ToneMapping.Hable,
|
||||
accel: TranscodeHardwareAcceleration.Disabled,
|
||||
accelDecode: false,
|
||||
},
|
||||
job: {
|
||||
[QueueName.BACKGROUND_TASK]: { concurrency: 5 },
|
||||
[QueueName.SMART_SEARCH]: { concurrency: 2 },
|
||||
[QueueName.METADATA_EXTRACTION]: { concurrency: 5 },
|
||||
[QueueName.FACE_DETECTION]: { concurrency: 2 },
|
||||
[QueueName.SEARCH]: { concurrency: 5 },
|
||||
[QueueName.SIDECAR]: { concurrency: 5 },
|
||||
[QueueName.LIBRARY]: { concurrency: 5 },
|
||||
[QueueName.MIGRATION]: { concurrency: 5 },
|
||||
[QueueName.THUMBNAIL_GENERATION]: { concurrency: 3 },
|
||||
[QueueName.VIDEO_CONVERSION]: { concurrency: 1 },
|
||||
[QueueName.NOTIFICATION]: { concurrency: 5 },
|
||||
[QueueName.BackgroundTask]: { concurrency: 5 },
|
||||
[QueueName.SmartSearch]: { concurrency: 2 },
|
||||
[QueueName.MetadataExtraction]: { concurrency: 5 },
|
||||
[QueueName.FaceDetection]: { concurrency: 2 },
|
||||
[QueueName.Search]: { concurrency: 5 },
|
||||
[QueueName.Sidecar]: { concurrency: 5 },
|
||||
[QueueName.Library]: { concurrency: 5 },
|
||||
[QueueName.Migration]: { concurrency: 5 },
|
||||
[QueueName.ThumbnailGeneration]: { concurrency: 3 },
|
||||
[QueueName.VideoConversion]: { concurrency: 1 },
|
||||
[QueueName.Notification]: { concurrency: 5 },
|
||||
},
|
||||
logging: {
|
||||
enabled: true,
|
||||
level: LogLevel.LOG,
|
||||
level: LogLevel.Log,
|
||||
},
|
||||
machineLearning: {
|
||||
enabled: process.env.IMMICH_MACHINE_LEARNING_ENABLED !== 'false',
|
||||
@@ -273,7 +273,7 @@ export const defaults = Object.freeze<SystemConfig>({
|
||||
storageLabelClaim: 'preferred_username',
|
||||
storageQuotaClaim: 'immich_quota',
|
||||
roleClaim: 'immich_role',
|
||||
tokenEndpointAuthMethod: OAuthTokenEndpointAuthMethod.CLIENT_SECRET_POST,
|
||||
tokenEndpointAuthMethod: OAuthTokenEndpointAuthMethod.ClientSecretPost,
|
||||
timeout: 30_000,
|
||||
},
|
||||
passwordLogin: {
|
||||
@@ -286,12 +286,12 @@ export const defaults = Object.freeze<SystemConfig>({
|
||||
},
|
||||
image: {
|
||||
thumbnail: {
|
||||
format: ImageFormat.WEBP,
|
||||
format: ImageFormat.Webp,
|
||||
size: 250,
|
||||
quality: 80,
|
||||
},
|
||||
preview: {
|
||||
format: ImageFormat.JPEG,
|
||||
format: ImageFormat.Jpeg,
|
||||
size: 1440,
|
||||
quality: 80,
|
||||
},
|
||||
@@ -299,7 +299,7 @@ export const defaults = Object.freeze<SystemConfig>({
|
||||
extractEmbedded: false,
|
||||
fullsize: {
|
||||
enabled: false,
|
||||
format: ImageFormat.JPEG,
|
||||
format: ImageFormat.Jpeg,
|
||||
quality: 80,
|
||||
},
|
||||
},
|
||||
|
||||
@@ -25,14 +25,14 @@ export const EXTENSION_NAMES: Record<DatabaseExtension, string> = {
|
||||
} as const;
|
||||
|
||||
export const VECTOR_EXTENSIONS = [
|
||||
DatabaseExtension.VECTORCHORD,
|
||||
DatabaseExtension.VECTORS,
|
||||
DatabaseExtension.VECTOR,
|
||||
DatabaseExtension.VectorChord,
|
||||
DatabaseExtension.Vectors,
|
||||
DatabaseExtension.Vector,
|
||||
] as const;
|
||||
|
||||
export const VECTOR_INDEX_TABLES = {
|
||||
[VectorIndex.CLIP]: 'smart_search',
|
||||
[VectorIndex.FACE]: 'face_search',
|
||||
[VectorIndex.Clip]: 'smart_search',
|
||||
[VectorIndex.Face]: 'face_search',
|
||||
} as const;
|
||||
|
||||
export const VECTORCHORD_LIST_SLACK_FACTOR = 1.2;
|
||||
@@ -47,7 +47,7 @@ export const serverVersion = new SemVer(version);
|
||||
export const AUDIT_LOG_MAX_DURATION = Duration.fromObject({ days: 100 });
|
||||
export const ONE_HOUR = Duration.fromObject({ hours: 1 });
|
||||
|
||||
export const APP_MEDIA_LOCATION = process.env.IMMICH_MEDIA_LOCATION || './upload';
|
||||
export const APP_MEDIA_LOCATION = process.env.IMMICH_MEDIA_LOCATION || '/usr/src/app/upload';
|
||||
|
||||
export const MACHINE_LEARNING_PING_TIMEOUT = Number(process.env.MACHINE_LEARNING_PING_TIMEOUT || 2000);
|
||||
export const MACHINE_LEARNING_AVAILABILITY_BACKOFF_TIME = Number(
|
||||
|
||||
@@ -20,13 +20,13 @@ export class ActivityController {
|
||||
constructor(private service: ActivityService) {}
|
||||
|
||||
@Get()
|
||||
@Authenticated({ permission: Permission.ACTIVITY_READ })
|
||||
@Authenticated({ permission: Permission.ActivityRead })
|
||||
getActivities(@Auth() auth: AuthDto, @Query() dto: ActivitySearchDto): Promise<ActivityResponseDto[]> {
|
||||
return this.service.getAll(auth, dto);
|
||||
}
|
||||
|
||||
@Post()
|
||||
@Authenticated({ permission: Permission.ACTIVITY_CREATE })
|
||||
@Authenticated({ permission: Permission.ActivityCreate })
|
||||
async createActivity(
|
||||
@Auth() auth: AuthDto,
|
||||
@Body() dto: ActivityCreateDto,
|
||||
@@ -40,14 +40,14 @@ export class ActivityController {
|
||||
}
|
||||
|
||||
@Get('statistics')
|
||||
@Authenticated({ permission: Permission.ACTIVITY_STATISTICS })
|
||||
@Authenticated({ permission: Permission.ActivityStatistics })
|
||||
getActivityStatistics(@Auth() auth: AuthDto, @Query() dto: ActivityDto): Promise<ActivityStatisticsResponseDto> {
|
||||
return this.service.getStatistics(auth, dto);
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
@HttpCode(HttpStatus.NO_CONTENT)
|
||||
@Authenticated({ permission: Permission.ACTIVITY_DELETE })
|
||||
@Authenticated({ permission: Permission.ActivityDelete })
|
||||
deleteActivity(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<void> {
|
||||
return this.service.delete(auth, id);
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ export class AlbumController {
|
||||
constructor(private service: AlbumService) {}
|
||||
|
||||
@Get()
|
||||
@Authenticated({ permission: Permission.ALBUM_READ })
|
||||
@Authenticated({ permission: Permission.AlbumRead })
|
||||
getAllAlbums(@Auth() auth: AuthDto, @Query() query: GetAlbumsDto): Promise<AlbumResponseDto[]> {
|
||||
return this.service.getAll(auth, query);
|
||||
}
|
||||
@@ -35,18 +35,18 @@ export class AlbumController {
|
||||
}
|
||||
|
||||
@Post()
|
||||
@Authenticated({ permission: Permission.ALBUM_CREATE })
|
||||
@Authenticated({ permission: Permission.AlbumCreate })
|
||||
createAlbum(@Auth() auth: AuthDto, @Body() dto: CreateAlbumDto): Promise<AlbumResponseDto> {
|
||||
return this.service.create(auth, dto);
|
||||
}
|
||||
|
||||
@Get('statistics')
|
||||
@Authenticated({ permission: Permission.ALBUM_STATISTICS })
|
||||
@Authenticated({ permission: Permission.AlbumStatistics })
|
||||
getAlbumStatistics(@Auth() auth: AuthDto): Promise<AlbumStatisticsResponseDto> {
|
||||
return this.service.getStatistics(auth);
|
||||
}
|
||||
|
||||
@Authenticated({ permission: Permission.ALBUM_READ, sharedLink: true })
|
||||
@Authenticated({ permission: Permission.AlbumRead, sharedLink: true })
|
||||
@Get(':id')
|
||||
getAlbumInfo(
|
||||
@Auth() auth: AuthDto,
|
||||
@@ -57,7 +57,7 @@ export class AlbumController {
|
||||
}
|
||||
|
||||
@Patch(':id')
|
||||
@Authenticated({ permission: Permission.ALBUM_UPDATE })
|
||||
@Authenticated({ permission: Permission.AlbumUpdate })
|
||||
updateAlbumInfo(
|
||||
@Auth() auth: AuthDto,
|
||||
@Param() { id }: UUIDParamDto,
|
||||
@@ -67,7 +67,7 @@ export class AlbumController {
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
@Authenticated({ permission: Permission.ALBUM_DELETE })
|
||||
@Authenticated({ permission: Permission.AlbumDelete })
|
||||
deleteAlbum(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto) {
|
||||
return this.service.delete(auth, id);
|
||||
}
|
||||
|
||||
@@ -55,7 +55,7 @@ describe(APIKeyController.name, () => {
|
||||
it('should require a valid uuid', async () => {
|
||||
const { status, body } = await request(ctx.getHttpServer())
|
||||
.put(`/api-keys/123`)
|
||||
.send({ name: 'new name', permissions: [Permission.ALL] });
|
||||
.send({ name: 'new name', permissions: [Permission.All] });
|
||||
expect(status).toBe(400);
|
||||
expect(body).toEqual(factory.responses.badRequest(['id must be a UUID']));
|
||||
});
|
||||
|
||||
@@ -13,25 +13,25 @@ export class APIKeyController {
|
||||
constructor(private service: ApiKeyService) {}
|
||||
|
||||
@Post()
|
||||
@Authenticated({ permission: Permission.API_KEY_CREATE })
|
||||
@Authenticated({ permission: Permission.ApiKeyCreate })
|
||||
createApiKey(@Auth() auth: AuthDto, @Body() dto: APIKeyCreateDto): Promise<APIKeyCreateResponseDto> {
|
||||
return this.service.create(auth, dto);
|
||||
}
|
||||
|
||||
@Get()
|
||||
@Authenticated({ permission: Permission.API_KEY_READ })
|
||||
@Authenticated({ permission: Permission.ApiKeyRead })
|
||||
getApiKeys(@Auth() auth: AuthDto): Promise<APIKeyResponseDto[]> {
|
||||
return this.service.getAll(auth);
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
@Authenticated({ permission: Permission.API_KEY_READ })
|
||||
@Authenticated({ permission: Permission.ApiKeyRead })
|
||||
getApiKey(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<APIKeyResponseDto> {
|
||||
return this.service.getById(auth, id);
|
||||
}
|
||||
|
||||
@Put(':id')
|
||||
@Authenticated({ permission: Permission.API_KEY_UPDATE })
|
||||
@Authenticated({ permission: Permission.ApiKeyUpdate })
|
||||
updateApiKey(
|
||||
@Auth() auth: AuthDto,
|
||||
@Param() { id }: UUIDParamDto,
|
||||
@@ -42,7 +42,7 @@ export class APIKeyController {
|
||||
|
||||
@Delete(':id')
|
||||
@HttpCode(HttpStatus.NO_CONTENT)
|
||||
@Authenticated({ permission: Permission.API_KEY_DELETE })
|
||||
@Authenticated({ permission: Permission.ApiKeyDelete })
|
||||
deleteApiKey(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<void> {
|
||||
return this.service.delete(auth, id);
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ import { ImmichFileResponse, sendFile } from 'src/utils/file';
|
||||
import { FileNotEmptyValidator, UUIDParamDto } from 'src/validation';
|
||||
|
||||
@ApiTags('Assets')
|
||||
@Controller(RouteKey.ASSET)
|
||||
@Controller(RouteKey.Asset)
|
||||
export class AssetMediaController {
|
||||
constructor(
|
||||
private logger: LoggingRepository,
|
||||
@@ -56,7 +56,7 @@ export class AssetMediaController {
|
||||
@UseInterceptors(AssetUploadInterceptor, FileUploadInterceptor)
|
||||
@ApiConsumes('multipart/form-data')
|
||||
@ApiHeader({
|
||||
name: ImmichHeader.CHECKSUM,
|
||||
name: ImmichHeader.Checksum,
|
||||
description: 'sha1 checksum that can be used for duplicate detection before the file is uploaded',
|
||||
required: false,
|
||||
})
|
||||
|
||||
@@ -19,7 +19,7 @@ import { AssetService } from 'src/services/asset.service';
|
||||
import { UUIDParamDto } from 'src/validation';
|
||||
|
||||
@ApiTags('Assets')
|
||||
@Controller(RouteKey.ASSET)
|
||||
@Controller(RouteKey.Asset)
|
||||
export class AssetController {
|
||||
constructor(private service: AssetService) {}
|
||||
|
||||
|
||||
@@ -36,9 +36,9 @@ export class AuthController {
|
||||
return respondWithCookie(res, body, {
|
||||
isSecure: loginDetails.isSecure,
|
||||
values: [
|
||||
{ key: ImmichCookie.ACCESS_TOKEN, value: body.accessToken },
|
||||
{ key: ImmichCookie.AUTH_TYPE, value: AuthType.PASSWORD },
|
||||
{ key: ImmichCookie.IS_AUTHENTICATED, value: 'true' },
|
||||
{ key: ImmichCookie.AccessToken, value: body.accessToken },
|
||||
{ key: ImmichCookie.AuthType, value: AuthType.Password },
|
||||
{ key: ImmichCookie.IsAuthenticated, value: 'true' },
|
||||
],
|
||||
});
|
||||
}
|
||||
@@ -70,13 +70,13 @@ export class AuthController {
|
||||
@Res({ passthrough: true }) res: Response,
|
||||
@Auth() auth: AuthDto,
|
||||
): Promise<LogoutResponseDto> {
|
||||
const authType = (request.cookies || {})[ImmichCookie.AUTH_TYPE];
|
||||
const authType = (request.cookies || {})[ImmichCookie.AuthType];
|
||||
|
||||
const body = await this.service.logout(auth, authType);
|
||||
return respondWithoutCookie(res, body, [
|
||||
ImmichCookie.ACCESS_TOKEN,
|
||||
ImmichCookie.AUTH_TYPE,
|
||||
ImmichCookie.IS_AUTHENTICATED,
|
||||
ImmichCookie.AccessToken,
|
||||
ImmichCookie.AuthType,
|
||||
ImmichCookie.IsAuthenticated,
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@@ -19,19 +19,19 @@ export class FaceController {
|
||||
constructor(private service: PersonService) {}
|
||||
|
||||
@Post()
|
||||
@Authenticated({ permission: Permission.FACE_CREATE })
|
||||
@Authenticated({ permission: Permission.FaceCreate })
|
||||
createFace(@Auth() auth: AuthDto, @Body() dto: AssetFaceCreateDto) {
|
||||
return this.service.createFace(auth, dto);
|
||||
}
|
||||
|
||||
@Get()
|
||||
@Authenticated({ permission: Permission.FACE_READ })
|
||||
@Authenticated({ permission: Permission.FaceRead })
|
||||
getFaces(@Auth() auth: AuthDto, @Query() dto: FaceDto): Promise<AssetFaceResponseDto[]> {
|
||||
return this.service.getFacesById(auth, dto);
|
||||
}
|
||||
|
||||
@Put(':id')
|
||||
@Authenticated({ permission: Permission.FACE_UPDATE })
|
||||
@Authenticated({ permission: Permission.FaceUpdate })
|
||||
reassignFacesById(
|
||||
@Auth() auth: AuthDto,
|
||||
@Param() { id }: UUIDParamDto,
|
||||
@@ -41,7 +41,7 @@ export class FaceController {
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
@Authenticated({ permission: Permission.FACE_DELETE })
|
||||
@Authenticated({ permission: Permission.FaceDelete })
|
||||
deleteFace(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto, @Body() dto: AssetFaceDeleteDto) {
|
||||
return this.service.deleteFace(auth, id, dto);
|
||||
}
|
||||
|
||||
@@ -19,32 +19,32 @@ export class LibraryController {
|
||||
constructor(private service: LibraryService) {}
|
||||
|
||||
@Get()
|
||||
@Authenticated({ permission: Permission.LIBRARY_READ, admin: true })
|
||||
@Authenticated({ permission: Permission.LibraryRead, admin: true })
|
||||
getAllLibraries(): Promise<LibraryResponseDto[]> {
|
||||
return this.service.getAll();
|
||||
}
|
||||
|
||||
@Post()
|
||||
@Authenticated({ permission: Permission.LIBRARY_CREATE, admin: true })
|
||||
@Authenticated({ permission: Permission.LibraryCreate, admin: true })
|
||||
createLibrary(@Body() dto: CreateLibraryDto): Promise<LibraryResponseDto> {
|
||||
return this.service.create(dto);
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
@Authenticated({ permission: Permission.LIBRARY_READ, admin: true })
|
||||
@Authenticated({ permission: Permission.LibraryRead, admin: true })
|
||||
getLibrary(@Param() { id }: UUIDParamDto): Promise<LibraryResponseDto> {
|
||||
return this.service.get(id);
|
||||
}
|
||||
|
||||
@Put(':id')
|
||||
@Authenticated({ permission: Permission.LIBRARY_UPDATE, admin: true })
|
||||
@Authenticated({ permission: Permission.LibraryUpdate, admin: true })
|
||||
updateLibrary(@Param() { id }: UUIDParamDto, @Body() dto: UpdateLibraryDto): Promise<LibraryResponseDto> {
|
||||
return this.service.update(id, dto);
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
@HttpCode(HttpStatus.NO_CONTENT)
|
||||
@Authenticated({ permission: Permission.LIBRARY_DELETE, admin: true })
|
||||
@Authenticated({ permission: Permission.LibraryDelete, admin: true })
|
||||
deleteLibrary(@Param() { id }: UUIDParamDto): Promise<void> {
|
||||
return this.service.delete(id);
|
||||
}
|
||||
@@ -58,14 +58,14 @@ export class LibraryController {
|
||||
}
|
||||
|
||||
@Get(':id/statistics')
|
||||
@Authenticated({ permission: Permission.LIBRARY_STATISTICS, admin: true })
|
||||
@Authenticated({ permission: Permission.LibraryStatistics, admin: true })
|
||||
getLibraryStatistics(@Param() { id }: UUIDParamDto): Promise<LibraryStatsResponseDto> {
|
||||
return this.service.getStatistics(id);
|
||||
}
|
||||
|
||||
@Post(':id/scan')
|
||||
@HttpCode(HttpStatus.NO_CONTENT)
|
||||
@Authenticated({ permission: Permission.LIBRARY_UPDATE, admin: true })
|
||||
@Authenticated({ permission: Permission.LibraryUpdate, admin: true })
|
||||
scanLibrary(@Param() { id }: UUIDParamDto) {
|
||||
return this.service.queueScan(id);
|
||||
}
|
||||
|
||||
@@ -20,31 +20,31 @@ export class MemoryController {
|
||||
constructor(private service: MemoryService) {}
|
||||
|
||||
@Get()
|
||||
@Authenticated({ permission: Permission.MEMORY_READ })
|
||||
@Authenticated({ permission: Permission.MemoryRead })
|
||||
searchMemories(@Auth() auth: AuthDto, @Query() dto: MemorySearchDto): Promise<MemoryResponseDto[]> {
|
||||
return this.service.search(auth, dto);
|
||||
}
|
||||
|
||||
@Post()
|
||||
@Authenticated({ permission: Permission.MEMORY_CREATE })
|
||||
@Authenticated({ permission: Permission.MemoryCreate })
|
||||
createMemory(@Auth() auth: AuthDto, @Body() dto: MemoryCreateDto): Promise<MemoryResponseDto> {
|
||||
return this.service.create(auth, dto);
|
||||
}
|
||||
|
||||
@Get('statistics')
|
||||
@Authenticated({ permission: Permission.MEMORY_READ })
|
||||
@Authenticated({ permission: Permission.MemoryRead })
|
||||
memoriesStatistics(@Auth() auth: AuthDto, @Query() dto: MemorySearchDto): Promise<MemoryStatisticsResponseDto> {
|
||||
return this.service.statistics(auth, dto);
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
@Authenticated({ permission: Permission.MEMORY_READ })
|
||||
@Authenticated({ permission: Permission.MemoryRead })
|
||||
getMemory(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<MemoryResponseDto> {
|
||||
return this.service.get(auth, id);
|
||||
}
|
||||
|
||||
@Put(':id')
|
||||
@Authenticated({ permission: Permission.MEMORY_UPDATE })
|
||||
@Authenticated({ permission: Permission.MemoryUpdate })
|
||||
updateMemory(
|
||||
@Auth() auth: AuthDto,
|
||||
@Param() { id }: UUIDParamDto,
|
||||
@@ -55,7 +55,7 @@ export class MemoryController {
|
||||
|
||||
@Delete(':id')
|
||||
@HttpCode(HttpStatus.NO_CONTENT)
|
||||
@Authenticated({ permission: Permission.MEMORY_DELETE })
|
||||
@Authenticated({ permission: Permission.MemoryDelete })
|
||||
deleteMemory(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<void> {
|
||||
return this.service.remove(auth, id);
|
||||
}
|
||||
|
||||
@@ -19,31 +19,31 @@ export class NotificationController {
|
||||
constructor(private service: NotificationService) {}
|
||||
|
||||
@Get()
|
||||
@Authenticated({ permission: Permission.NOTIFICATION_READ })
|
||||
@Authenticated({ permission: Permission.NotificationRead })
|
||||
getNotifications(@Auth() auth: AuthDto, @Query() dto: NotificationSearchDto): Promise<NotificationDto[]> {
|
||||
return this.service.search(auth, dto);
|
||||
}
|
||||
|
||||
@Put()
|
||||
@Authenticated({ permission: Permission.NOTIFICATION_UPDATE })
|
||||
@Authenticated({ permission: Permission.NotificationUpdate })
|
||||
updateNotifications(@Auth() auth: AuthDto, @Body() dto: NotificationUpdateAllDto): Promise<void> {
|
||||
return this.service.updateAll(auth, dto);
|
||||
}
|
||||
|
||||
@Delete()
|
||||
@Authenticated({ permission: Permission.NOTIFICATION_DELETE })
|
||||
@Authenticated({ permission: Permission.NotificationDelete })
|
||||
deleteNotifications(@Auth() auth: AuthDto, @Body() dto: NotificationDeleteAllDto): Promise<void> {
|
||||
return this.service.deleteAll(auth, dto);
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
@Authenticated({ permission: Permission.NOTIFICATION_READ })
|
||||
@Authenticated({ permission: Permission.NotificationRead })
|
||||
getNotification(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<NotificationDto> {
|
||||
return this.service.get(auth, id);
|
||||
}
|
||||
|
||||
@Put(':id')
|
||||
@Authenticated({ permission: Permission.NOTIFICATION_UPDATE })
|
||||
@Authenticated({ permission: Permission.NotificationUpdate })
|
||||
updateNotification(
|
||||
@Auth() auth: AuthDto,
|
||||
@Param() { id }: UUIDParamDto,
|
||||
@@ -53,7 +53,7 @@ export class NotificationController {
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
@Authenticated({ permission: Permission.NOTIFICATION_DELETE })
|
||||
@Authenticated({ permission: Permission.NotificationDelete })
|
||||
deleteNotification(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<void> {
|
||||
return this.service.delete(auth, id);
|
||||
}
|
||||
|
||||
@@ -41,8 +41,8 @@ export class OAuthController {
|
||||
{
|
||||
isSecure: loginDetails.isSecure,
|
||||
values: [
|
||||
{ key: ImmichCookie.OAUTH_STATE, value: state },
|
||||
{ key: ImmichCookie.OAUTH_CODE_VERIFIER, value: codeVerifier },
|
||||
{ key: ImmichCookie.OAuthState, value: state },
|
||||
{ key: ImmichCookie.OAuthCodeVerifier, value: codeVerifier },
|
||||
],
|
||||
},
|
||||
);
|
||||
@@ -56,14 +56,14 @@ export class OAuthController {
|
||||
@GetLoginDetails() loginDetails: LoginDetails,
|
||||
): Promise<LoginResponseDto> {
|
||||
const body = await this.service.callback(dto, request.headers, loginDetails);
|
||||
res.clearCookie(ImmichCookie.OAUTH_STATE);
|
||||
res.clearCookie(ImmichCookie.OAUTH_CODE_VERIFIER);
|
||||
res.clearCookie(ImmichCookie.OAuthState);
|
||||
res.clearCookie(ImmichCookie.OAuthCodeVerifier);
|
||||
return respondWithCookie(res, body, {
|
||||
isSecure: loginDetails.isSecure,
|
||||
values: [
|
||||
{ key: ImmichCookie.ACCESS_TOKEN, value: body.accessToken },
|
||||
{ key: ImmichCookie.AUTH_TYPE, value: AuthType.OAUTH },
|
||||
{ key: ImmichCookie.IS_AUTHENTICATED, value: 'true' },
|
||||
{ key: ImmichCookie.AccessToken, value: body.accessToken },
|
||||
{ key: ImmichCookie.AuthType, value: AuthType.OAuth },
|
||||
{ key: ImmichCookie.IsAuthenticated, value: 'true' },
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
@@ -13,19 +13,19 @@ export class PartnerController {
|
||||
constructor(private service: PartnerService) {}
|
||||
|
||||
@Get()
|
||||
@Authenticated({ permission: Permission.PARTNER_READ })
|
||||
@Authenticated({ permission: Permission.PartnerRead })
|
||||
getPartners(@Auth() auth: AuthDto, @Query() dto: PartnerSearchDto): Promise<PartnerResponseDto[]> {
|
||||
return this.service.search(auth, dto);
|
||||
}
|
||||
|
||||
@Post(':id')
|
||||
@Authenticated({ permission: Permission.PARTNER_CREATE })
|
||||
@Authenticated({ permission: Permission.PartnerCreate })
|
||||
createPartner(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<PartnerResponseDto> {
|
||||
return this.service.create(auth, id);
|
||||
}
|
||||
|
||||
@Put(':id')
|
||||
@Authenticated({ permission: Permission.PARTNER_UPDATE })
|
||||
@Authenticated({ permission: Permission.PartnerUpdate })
|
||||
updatePartner(
|
||||
@Auth() auth: AuthDto,
|
||||
@Param() { id }: UUIDParamDto,
|
||||
@@ -35,7 +35,7 @@ export class PartnerController {
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
@Authenticated({ permission: Permission.PARTNER_DELETE })
|
||||
@Authenticated({ permission: Permission.PartnerDelete })
|
||||
removePartner(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<void> {
|
||||
return this.service.remove(auth, id);
|
||||
}
|
||||
|
||||
@@ -45,38 +45,38 @@ export class PersonController {
|
||||
}
|
||||
|
||||
@Get()
|
||||
@Authenticated({ permission: Permission.PERSON_READ })
|
||||
@Authenticated({ permission: Permission.PersonRead })
|
||||
getAllPeople(@Auth() auth: AuthDto, @Query() options: PersonSearchDto): Promise<PeopleResponseDto> {
|
||||
return this.service.getAll(auth, options);
|
||||
}
|
||||
|
||||
@Post()
|
||||
@Authenticated({ permission: Permission.PERSON_CREATE })
|
||||
@Authenticated({ permission: Permission.PersonCreate })
|
||||
createPerson(@Auth() auth: AuthDto, @Body() dto: PersonCreateDto): Promise<PersonResponseDto> {
|
||||
return this.service.create(auth, dto);
|
||||
}
|
||||
|
||||
@Put()
|
||||
@Authenticated({ permission: Permission.PERSON_UPDATE })
|
||||
@Authenticated({ permission: Permission.PersonUpdate })
|
||||
updatePeople(@Auth() auth: AuthDto, @Body() dto: PeopleUpdateDto): Promise<BulkIdResponseDto[]> {
|
||||
return this.service.updateAll(auth, dto);
|
||||
}
|
||||
|
||||
@Delete()
|
||||
@HttpCode(HttpStatus.NO_CONTENT)
|
||||
@Authenticated({ permission: Permission.PERSON_DELETE })
|
||||
@Authenticated({ permission: Permission.PersonDelete })
|
||||
deletePeople(@Auth() auth: AuthDto, @Body() dto: BulkIdsDto): Promise<void> {
|
||||
return this.service.deleteAll(auth, dto);
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
@Authenticated({ permission: Permission.PERSON_READ })
|
||||
@Authenticated({ permission: Permission.PersonRead })
|
||||
getPerson(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<PersonResponseDto> {
|
||||
return this.service.getById(auth, id);
|
||||
}
|
||||
|
||||
@Put(':id')
|
||||
@Authenticated({ permission: Permission.PERSON_UPDATE })
|
||||
@Authenticated({ permission: Permission.PersonUpdate })
|
||||
updatePerson(
|
||||
@Auth() auth: AuthDto,
|
||||
@Param() { id }: UUIDParamDto,
|
||||
@@ -87,20 +87,20 @@ export class PersonController {
|
||||
|
||||
@Delete(':id')
|
||||
@HttpCode(HttpStatus.NO_CONTENT)
|
||||
@Authenticated({ permission: Permission.PERSON_DELETE })
|
||||
@Authenticated({ permission: Permission.PersonDelete })
|
||||
deletePerson(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<void> {
|
||||
return this.service.delete(auth, id);
|
||||
}
|
||||
|
||||
@Get(':id/statistics')
|
||||
@Authenticated({ permission: Permission.PERSON_STATISTICS })
|
||||
@Authenticated({ permission: Permission.PersonStatistics })
|
||||
getPersonStatistics(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<PersonStatisticsResponseDto> {
|
||||
return this.service.getStatistics(auth, id);
|
||||
}
|
||||
|
||||
@Get(':id/thumbnail')
|
||||
@FileResponse()
|
||||
@Authenticated({ permission: Permission.PERSON_READ })
|
||||
@Authenticated({ permission: Permission.PersonRead })
|
||||
async getPersonThumbnail(
|
||||
@Res() res: Response,
|
||||
@Next() next: NextFunction,
|
||||
@@ -111,7 +111,7 @@ export class PersonController {
|
||||
}
|
||||
|
||||
@Put(':id/reassign')
|
||||
@Authenticated({ permission: Permission.PERSON_REASSIGN })
|
||||
@Authenticated({ permission: Permission.PersonReassign })
|
||||
reassignFaces(
|
||||
@Auth() auth: AuthDto,
|
||||
@Param() { id }: UUIDParamDto,
|
||||
@@ -121,7 +121,7 @@ export class PersonController {
|
||||
}
|
||||
|
||||
@Post(':id/merge')
|
||||
@Authenticated({ permission: Permission.PERSON_MERGE })
|
||||
@Authenticated({ permission: Permission.PersonMerge })
|
||||
mergePerson(
|
||||
@Auth() auth: AuthDto,
|
||||
@Param() { id }: UUIDParamDto,
|
||||
|
||||
@@ -13,26 +13,26 @@ export class SessionController {
|
||||
constructor(private service: SessionService) {}
|
||||
|
||||
@Post()
|
||||
@Authenticated({ permission: Permission.SESSION_CREATE })
|
||||
@Authenticated({ permission: Permission.SessionCreate })
|
||||
createSession(@Auth() auth: AuthDto, @Body() dto: SessionCreateDto): Promise<SessionCreateResponseDto> {
|
||||
return this.service.create(auth, dto);
|
||||
}
|
||||
|
||||
@Get()
|
||||
@Authenticated({ permission: Permission.SESSION_READ })
|
||||
@Authenticated({ permission: Permission.SessionRead })
|
||||
getSessions(@Auth() auth: AuthDto): Promise<SessionResponseDto[]> {
|
||||
return this.service.getAll(auth);
|
||||
}
|
||||
|
||||
@Delete()
|
||||
@Authenticated({ permission: Permission.SESSION_DELETE })
|
||||
@Authenticated({ permission: Permission.SessionDelete })
|
||||
@HttpCode(HttpStatus.NO_CONTENT)
|
||||
deleteAllSessions(@Auth() auth: AuthDto): Promise<void> {
|
||||
return this.service.deleteAll(auth);
|
||||
}
|
||||
|
||||
@Put(':id')
|
||||
@Authenticated({ permission: Permission.SESSION_UPDATE })
|
||||
@Authenticated({ permission: Permission.SessionUpdate })
|
||||
updateSession(
|
||||
@Auth() auth: AuthDto,
|
||||
@Param() { id }: UUIDParamDto,
|
||||
@@ -42,14 +42,14 @@ export class SessionController {
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
@Authenticated({ permission: Permission.SESSION_DELETE })
|
||||
@Authenticated({ permission: Permission.SessionDelete })
|
||||
@HttpCode(HttpStatus.NO_CONTENT)
|
||||
deleteSession(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<void> {
|
||||
return this.service.delete(auth, id);
|
||||
}
|
||||
|
||||
@Post(':id/lock')
|
||||
@Authenticated({ permission: Permission.SESSION_LOCK })
|
||||
@Authenticated({ permission: Permission.SessionLock })
|
||||
@HttpCode(HttpStatus.NO_CONTENT)
|
||||
lockSession(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<void> {
|
||||
return this.service.lock(auth, id);
|
||||
|
||||
@@ -24,7 +24,7 @@ export class SharedLinkController {
|
||||
constructor(private service: SharedLinkService) {}
|
||||
|
||||
@Get()
|
||||
@Authenticated({ permission: Permission.SHARED_LINK_READ })
|
||||
@Authenticated({ permission: Permission.SharedLinkRead })
|
||||
getAllSharedLinks(@Auth() auth: AuthDto, @Query() dto: SharedLinkSearchDto): Promise<SharedLinkResponseDto[]> {
|
||||
return this.service.getAll(auth, dto);
|
||||
}
|
||||
@@ -38,31 +38,31 @@ export class SharedLinkController {
|
||||
@Res({ passthrough: true }) res: Response,
|
||||
@GetLoginDetails() loginDetails: LoginDetails,
|
||||
): Promise<SharedLinkResponseDto> {
|
||||
const sharedLinkToken = request.cookies?.[ImmichCookie.SHARED_LINK_TOKEN];
|
||||
const sharedLinkToken = request.cookies?.[ImmichCookie.SharedLinkToken];
|
||||
if (sharedLinkToken) {
|
||||
dto.token = sharedLinkToken;
|
||||
}
|
||||
const body = await this.service.getMine(auth, dto);
|
||||
return respondWithCookie(res, body, {
|
||||
isSecure: loginDetails.isSecure,
|
||||
values: body.token ? [{ key: ImmichCookie.SHARED_LINK_TOKEN, value: body.token }] : [],
|
||||
values: body.token ? [{ key: ImmichCookie.SharedLinkToken, value: body.token }] : [],
|
||||
});
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
@Authenticated({ permission: Permission.SHARED_LINK_READ })
|
||||
@Authenticated({ permission: Permission.SharedLinkRead })
|
||||
getSharedLinkById(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<SharedLinkResponseDto> {
|
||||
return this.service.get(auth, id);
|
||||
}
|
||||
|
||||
@Post()
|
||||
@Authenticated({ permission: Permission.SHARED_LINK_CREATE })
|
||||
@Authenticated({ permission: Permission.SharedLinkCreate })
|
||||
createSharedLink(@Auth() auth: AuthDto, @Body() dto: SharedLinkCreateDto) {
|
||||
return this.service.create(auth, dto);
|
||||
}
|
||||
|
||||
@Patch(':id')
|
||||
@Authenticated({ permission: Permission.SHARED_LINK_UPDATE })
|
||||
@Authenticated({ permission: Permission.SharedLinkUpdate })
|
||||
updateSharedLink(
|
||||
@Auth() auth: AuthDto,
|
||||
@Param() { id }: UUIDParamDto,
|
||||
@@ -72,7 +72,7 @@ export class SharedLinkController {
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
@Authenticated({ permission: Permission.SHARED_LINK_DELETE })
|
||||
@Authenticated({ permission: Permission.SharedLinkDelete })
|
||||
removeSharedLink(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<void> {
|
||||
return this.service.remove(auth, id);
|
||||
}
|
||||
|
||||
@@ -14,32 +14,32 @@ export class StackController {
|
||||
constructor(private service: StackService) {}
|
||||
|
||||
@Get()
|
||||
@Authenticated({ permission: Permission.STACK_READ })
|
||||
@Authenticated({ permission: Permission.StackRead })
|
||||
searchStacks(@Auth() auth: AuthDto, @Query() query: StackSearchDto): Promise<StackResponseDto[]> {
|
||||
return this.service.search(auth, query);
|
||||
}
|
||||
|
||||
@Post()
|
||||
@Authenticated({ permission: Permission.STACK_CREATE })
|
||||
@Authenticated({ permission: Permission.StackCreate })
|
||||
createStack(@Auth() auth: AuthDto, @Body() dto: StackCreateDto): Promise<StackResponseDto> {
|
||||
return this.service.create(auth, dto);
|
||||
}
|
||||
|
||||
@Delete()
|
||||
@Authenticated({ permission: Permission.STACK_DELETE })
|
||||
@Authenticated({ permission: Permission.StackDelete })
|
||||
@HttpCode(HttpStatus.NO_CONTENT)
|
||||
deleteStacks(@Auth() auth: AuthDto, @Body() dto: BulkIdsDto): Promise<void> {
|
||||
return this.service.deleteAll(auth, dto);
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
@Authenticated({ permission: Permission.STACK_READ })
|
||||
@Authenticated({ permission: Permission.StackRead })
|
||||
getStack(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<StackResponseDto> {
|
||||
return this.service.get(auth, id);
|
||||
}
|
||||
|
||||
@Put(':id')
|
||||
@Authenticated({ permission: Permission.STACK_UPDATE })
|
||||
@Authenticated({ permission: Permission.StackUpdate })
|
||||
updateStack(
|
||||
@Auth() auth: AuthDto,
|
||||
@Param() { id }: UUIDParamDto,
|
||||
@@ -50,7 +50,7 @@ export class StackController {
|
||||
|
||||
@Delete(':id')
|
||||
@HttpCode(HttpStatus.NO_CONTENT)
|
||||
@Authenticated({ permission: Permission.STACK_DELETE })
|
||||
@Authenticated({ permission: Permission.StackDelete })
|
||||
deleteStack(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<void> {
|
||||
return this.service.delete(auth, id);
|
||||
}
|
||||
|
||||
@@ -15,25 +15,25 @@ export class SystemConfigController {
|
||||
) {}
|
||||
|
||||
@Get()
|
||||
@Authenticated({ permission: Permission.SYSTEM_CONFIG_READ, admin: true })
|
||||
@Authenticated({ permission: Permission.SystemConfigRead, admin: true })
|
||||
getConfig(): Promise<SystemConfigDto> {
|
||||
return this.service.getSystemConfig();
|
||||
}
|
||||
|
||||
@Get('defaults')
|
||||
@Authenticated({ permission: Permission.SYSTEM_CONFIG_READ, admin: true })
|
||||
@Authenticated({ permission: Permission.SystemConfigRead, admin: true })
|
||||
getConfigDefaults(): SystemConfigDto {
|
||||
return this.service.getDefaults();
|
||||
}
|
||||
|
||||
@Put()
|
||||
@Authenticated({ permission: Permission.SYSTEM_CONFIG_UPDATE, admin: true })
|
||||
@Authenticated({ permission: Permission.SystemConfigUpdate, admin: true })
|
||||
updateConfig(@Body() dto: SystemConfigDto): Promise<SystemConfigDto> {
|
||||
return this.service.updateSystemConfig(dto);
|
||||
}
|
||||
|
||||
@Get('storage-template-options')
|
||||
@Authenticated({ permission: Permission.SYSTEM_CONFIG_READ, admin: true })
|
||||
@Authenticated({ permission: Permission.SystemConfigRead, admin: true })
|
||||
getStorageTemplateOptions(): SystemConfigTemplateStorageOptionDto {
|
||||
return this.storageTemplateService.getStorageTemplateOptions();
|
||||
}
|
||||
|
||||
@@ -15,26 +15,26 @@ export class SystemMetadataController {
|
||||
constructor(private service: SystemMetadataService) {}
|
||||
|
||||
@Get('admin-onboarding')
|
||||
@Authenticated({ permission: Permission.SYSTEM_METADATA_READ, admin: true })
|
||||
@Authenticated({ permission: Permission.SystemMetadataRead, admin: true })
|
||||
getAdminOnboarding(): Promise<AdminOnboardingUpdateDto> {
|
||||
return this.service.getAdminOnboarding();
|
||||
}
|
||||
|
||||
@Post('admin-onboarding')
|
||||
@HttpCode(HttpStatus.NO_CONTENT)
|
||||
@Authenticated({ permission: Permission.SYSTEM_METADATA_UPDATE, admin: true })
|
||||
@Authenticated({ permission: Permission.SystemMetadataUpdate, admin: true })
|
||||
updateAdminOnboarding(@Body() dto: AdminOnboardingUpdateDto): Promise<void> {
|
||||
return this.service.updateAdminOnboarding(dto);
|
||||
}
|
||||
|
||||
@Get('reverse-geocoding-state')
|
||||
@Authenticated({ permission: Permission.SYSTEM_METADATA_READ, admin: true })
|
||||
@Authenticated({ permission: Permission.SystemMetadataRead, admin: true })
|
||||
getReverseGeocodingState(): Promise<ReverseGeocodingStateResponseDto> {
|
||||
return this.service.getReverseGeocodingState();
|
||||
}
|
||||
|
||||
@Get('version-check-state')
|
||||
@Authenticated({ permission: Permission.SYSTEM_METADATA_READ, admin: true })
|
||||
@Authenticated({ permission: Permission.SystemMetadataRead, admin: true })
|
||||
getVersionCheckState(): Promise<VersionCheckStateResponseDto> {
|
||||
return this.service.getVersionCheckState();
|
||||
}
|
||||
|
||||
@@ -21,50 +21,50 @@ export class TagController {
|
||||
constructor(private service: TagService) {}
|
||||
|
||||
@Post()
|
||||
@Authenticated({ permission: Permission.TAG_CREATE })
|
||||
@Authenticated({ permission: Permission.TagCreate })
|
||||
createTag(@Auth() auth: AuthDto, @Body() dto: TagCreateDto): Promise<TagResponseDto> {
|
||||
return this.service.create(auth, dto);
|
||||
}
|
||||
|
||||
@Get()
|
||||
@Authenticated({ permission: Permission.TAG_READ })
|
||||
@Authenticated({ permission: Permission.TagRead })
|
||||
getAllTags(@Auth() auth: AuthDto): Promise<TagResponseDto[]> {
|
||||
return this.service.getAll(auth);
|
||||
}
|
||||
|
||||
@Put()
|
||||
@Authenticated({ permission: Permission.TAG_CREATE })
|
||||
@Authenticated({ permission: Permission.TagCreate })
|
||||
upsertTags(@Auth() auth: AuthDto, @Body() dto: TagUpsertDto): Promise<TagResponseDto[]> {
|
||||
return this.service.upsert(auth, dto);
|
||||
}
|
||||
|
||||
@Put('assets')
|
||||
@Authenticated({ permission: Permission.TAG_ASSET })
|
||||
@Authenticated({ permission: Permission.TagAsset })
|
||||
bulkTagAssets(@Auth() auth: AuthDto, @Body() dto: TagBulkAssetsDto): Promise<TagBulkAssetsResponseDto> {
|
||||
return this.service.bulkTagAssets(auth, dto);
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
@Authenticated({ permission: Permission.TAG_READ })
|
||||
@Authenticated({ permission: Permission.TagRead })
|
||||
getTagById(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<TagResponseDto> {
|
||||
return this.service.get(auth, id);
|
||||
}
|
||||
|
||||
@Put(':id')
|
||||
@Authenticated({ permission: Permission.TAG_UPDATE })
|
||||
@Authenticated({ permission: Permission.TagUpdate })
|
||||
updateTag(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto, @Body() dto: TagUpdateDto): Promise<TagResponseDto> {
|
||||
return this.service.update(auth, id, dto);
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
@HttpCode(HttpStatus.NO_CONTENT)
|
||||
@Authenticated({ permission: Permission.TAG_DELETE })
|
||||
@Authenticated({ permission: Permission.TagDelete })
|
||||
deleteTag(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<void> {
|
||||
return this.service.remove(auth, id);
|
||||
}
|
||||
|
||||
@Put(':id/assets')
|
||||
@Authenticated({ permission: Permission.TAG_ASSET })
|
||||
@Authenticated({ permission: Permission.TagAsset })
|
||||
tagAssets(
|
||||
@Auth() auth: AuthDto,
|
||||
@Param() { id }: UUIDParamDto,
|
||||
@@ -74,7 +74,7 @@ export class TagController {
|
||||
}
|
||||
|
||||
@Delete(':id/assets')
|
||||
@Authenticated({ permission: Permission.TAG_ASSET })
|
||||
@Authenticated({ permission: Permission.TagAsset })
|
||||
untagAssets(
|
||||
@Auth() auth: AuthDto,
|
||||
@Body() dto: BulkIdsDto,
|
||||
|
||||
@@ -12,13 +12,13 @@ export class TimelineController {
|
||||
constructor(private service: TimelineService) {}
|
||||
|
||||
@Get('buckets')
|
||||
@Authenticated({ permission: Permission.ASSET_READ, sharedLink: true })
|
||||
@Authenticated({ permission: Permission.AssetRead, sharedLink: true })
|
||||
getTimeBuckets(@Auth() auth: AuthDto, @Query() dto: TimeBucketDto) {
|
||||
return this.service.getTimeBuckets(auth, dto);
|
||||
}
|
||||
|
||||
@Get('bucket')
|
||||
@Authenticated({ permission: Permission.ASSET_READ, sharedLink: true })
|
||||
@Authenticated({ permission: Permission.AssetRead, sharedLink: true })
|
||||
@ApiOkResponse({ type: TimeBucketAssetResponseDto })
|
||||
@Header('Content-Type', 'application/json')
|
||||
getTimeBucket(@Auth() auth: AuthDto, @Query() dto: TimeBucketAssetDto) {
|
||||
|
||||
@@ -14,21 +14,21 @@ export class TrashController {
|
||||
|
||||
@Post('empty')
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@Authenticated({ permission: Permission.ASSET_DELETE })
|
||||
@Authenticated({ permission: Permission.AssetDelete })
|
||||
emptyTrash(@Auth() auth: AuthDto): Promise<TrashResponseDto> {
|
||||
return this.service.empty(auth);
|
||||
}
|
||||
|
||||
@Post('restore')
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@Authenticated({ permission: Permission.ASSET_DELETE })
|
||||
@Authenticated({ permission: Permission.AssetDelete })
|
||||
restoreTrash(@Auth() auth: AuthDto): Promise<TrashResponseDto> {
|
||||
return this.service.restore(auth);
|
||||
}
|
||||
|
||||
@Post('restore/assets')
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@Authenticated({ permission: Permission.ASSET_DELETE })
|
||||
@Authenticated({ permission: Permission.AssetDelete })
|
||||
restoreAssets(@Auth() auth: AuthDto, @Body() dto: BulkIdsDto): Promise<TrashResponseDto> {
|
||||
return this.service.restoreAssets(auth, dto);
|
||||
}
|
||||
|
||||
@@ -21,25 +21,25 @@ export class UserAdminController {
|
||||
constructor(private service: UserAdminService) {}
|
||||
|
||||
@Get()
|
||||
@Authenticated({ permission: Permission.ADMIN_USER_READ, admin: true })
|
||||
@Authenticated({ permission: Permission.AdminUserRead, admin: true })
|
||||
searchUsersAdmin(@Auth() auth: AuthDto, @Query() dto: UserAdminSearchDto): Promise<UserAdminResponseDto[]> {
|
||||
return this.service.search(auth, dto);
|
||||
}
|
||||
|
||||
@Post()
|
||||
@Authenticated({ permission: Permission.ADMIN_USER_CREATE, admin: true })
|
||||
@Authenticated({ permission: Permission.AdminUserCreate, admin: true })
|
||||
createUserAdmin(@Body() createUserDto: UserAdminCreateDto): Promise<UserAdminResponseDto> {
|
||||
return this.service.create(createUserDto);
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
@Authenticated({ permission: Permission.ADMIN_USER_READ, admin: true })
|
||||
@Authenticated({ permission: Permission.AdminUserRead, admin: true })
|
||||
getUserAdmin(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<UserAdminResponseDto> {
|
||||
return this.service.get(auth, id);
|
||||
}
|
||||
|
||||
@Put(':id')
|
||||
@Authenticated({ permission: Permission.ADMIN_USER_UPDATE, admin: true })
|
||||
@Authenticated({ permission: Permission.AdminUserUpdate, admin: true })
|
||||
updateUserAdmin(
|
||||
@Auth() auth: AuthDto,
|
||||
@Param() { id }: UUIDParamDto,
|
||||
@@ -49,7 +49,7 @@ export class UserAdminController {
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
@Authenticated({ permission: Permission.ADMIN_USER_DELETE, admin: true })
|
||||
@Authenticated({ permission: Permission.AdminUserDelete, admin: true })
|
||||
deleteUserAdmin(
|
||||
@Auth() auth: AuthDto,
|
||||
@Param() { id }: UUIDParamDto,
|
||||
@@ -59,7 +59,7 @@ export class UserAdminController {
|
||||
}
|
||||
|
||||
@Get(':id/statistics')
|
||||
@Authenticated({ permission: Permission.ADMIN_USER_READ, admin: true })
|
||||
@Authenticated({ permission: Permission.AdminUserRead, admin: true })
|
||||
getUserStatisticsAdmin(
|
||||
@Auth() auth: AuthDto,
|
||||
@Param() { id }: UUIDParamDto,
|
||||
@@ -69,13 +69,13 @@ export class UserAdminController {
|
||||
}
|
||||
|
||||
@Get(':id/preferences')
|
||||
@Authenticated({ permission: Permission.ADMIN_USER_READ, admin: true })
|
||||
@Authenticated({ permission: Permission.AdminUserRead, admin: true })
|
||||
getUserPreferencesAdmin(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<UserPreferencesResponseDto> {
|
||||
return this.service.getPreferences(auth, id);
|
||||
}
|
||||
|
||||
@Put(':id/preferences')
|
||||
@Authenticated({ permission: Permission.ADMIN_USER_UPDATE, admin: true })
|
||||
@Authenticated({ permission: Permission.AdminUserUpdate, admin: true })
|
||||
updateUserPreferencesAdmin(
|
||||
@Auth() auth: AuthDto,
|
||||
@Param() { id }: UUIDParamDto,
|
||||
@@ -85,7 +85,7 @@ export class UserAdminController {
|
||||
}
|
||||
|
||||
@Post(':id/restore')
|
||||
@Authenticated({ permission: Permission.ADMIN_USER_DELETE, admin: true })
|
||||
@Authenticated({ permission: Permission.AdminUserDelete, admin: true })
|
||||
@HttpCode(HttpStatus.OK)
|
||||
restoreUserAdmin(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<UserAdminResponseDto> {
|
||||
return this.service.restore(auth, id);
|
||||
|
||||
@@ -30,7 +30,7 @@ import { sendFile } from 'src/utils/file';
|
||||
import { UUIDParamDto } from 'src/validation';
|
||||
|
||||
@ApiTags('Users')
|
||||
@Controller(RouteKey.USER)
|
||||
@Controller(RouteKey.User)
|
||||
export class UserController {
|
||||
constructor(
|
||||
private service: UserService,
|
||||
|
||||
@@ -25,8 +25,8 @@ export interface MoveRequest {
|
||||
};
|
||||
}
|
||||
|
||||
export type GeneratedImageType = AssetPathType.PREVIEW | AssetPathType.THUMBNAIL | AssetPathType.FULLSIZE;
|
||||
export type GeneratedAssetType = GeneratedImageType | AssetPathType.ENCODED_VIDEO;
|
||||
export type GeneratedImageType = AssetPathType.Preview | AssetPathType.Thumbnail | AssetPathType.FullSize;
|
||||
export type GeneratedAssetType = GeneratedImageType | AssetPathType.EncodedVideo;
|
||||
|
||||
export type ThumbnailPathEntity = { id: string; ownerId: string };
|
||||
|
||||
@@ -79,7 +79,7 @@ export class StorageCore {
|
||||
}
|
||||
|
||||
static getLibraryFolder(user: { storageLabel: string | null; id: string }) {
|
||||
return join(StorageCore.getBaseFolder(StorageFolder.LIBRARY), user.storageLabel || user.id);
|
||||
return join(StorageCore.getBaseFolder(StorageFolder.Library), user.storageLabel || user.id);
|
||||
}
|
||||
|
||||
static getBaseFolder(folder: StorageFolder) {
|
||||
@@ -87,23 +87,23 @@ export class StorageCore {
|
||||
}
|
||||
|
||||
static getPersonThumbnailPath(person: ThumbnailPathEntity) {
|
||||
return StorageCore.getNestedPath(StorageFolder.THUMBNAILS, person.ownerId, `${person.id}.jpeg`);
|
||||
return StorageCore.getNestedPath(StorageFolder.Thumbnails, person.ownerId, `${person.id}.jpeg`);
|
||||
}
|
||||
|
||||
static getImagePath(asset: ThumbnailPathEntity, type: GeneratedImageType, format: 'jpeg' | 'webp') {
|
||||
return StorageCore.getNestedPath(StorageFolder.THUMBNAILS, asset.ownerId, `${asset.id}-${type}.${format}`);
|
||||
return StorageCore.getNestedPath(StorageFolder.Thumbnails, asset.ownerId, `${asset.id}-${type}.${format}`);
|
||||
}
|
||||
|
||||
static getEncodedVideoPath(asset: ThumbnailPathEntity) {
|
||||
return StorageCore.getNestedPath(StorageFolder.ENCODED_VIDEO, asset.ownerId, `${asset.id}.mp4`);
|
||||
return StorageCore.getNestedPath(StorageFolder.EncodedVideo, asset.ownerId, `${asset.id}.mp4`);
|
||||
}
|
||||
|
||||
static getAndroidMotionPath(asset: ThumbnailPathEntity, uuid: string) {
|
||||
return StorageCore.getNestedPath(StorageFolder.ENCODED_VIDEO, asset.ownerId, `${uuid}-MP.mp4`);
|
||||
return StorageCore.getNestedPath(StorageFolder.EncodedVideo, asset.ownerId, `${uuid}-MP.mp4`);
|
||||
}
|
||||
|
||||
static isAndroidMotionPath(originalPath: string) {
|
||||
return originalPath.startsWith(StorageCore.getBaseFolder(StorageFolder.ENCODED_VIDEO));
|
||||
return originalPath.startsWith(StorageCore.getBaseFolder(StorageFolder.EncodedVideo));
|
||||
}
|
||||
|
||||
static isImmichPath(path: string) {
|
||||
@@ -130,7 +130,7 @@ export class StorageCore {
|
||||
async moveAssetVideo(asset: StorageAsset) {
|
||||
return this.moveFile({
|
||||
entityId: asset.id,
|
||||
pathType: AssetPathType.ENCODED_VIDEO,
|
||||
pathType: AssetPathType.EncodedVideo,
|
||||
oldPath: asset.encodedVideoPath,
|
||||
newPath: StorageCore.getEncodedVideoPath(asset),
|
||||
});
|
||||
@@ -139,7 +139,7 @@ export class StorageCore {
|
||||
async movePersonFile(person: { id: string; ownerId: string; thumbnailPath: string }, pathType: PersonPathType) {
|
||||
const { id: entityId, thumbnailPath } = person;
|
||||
switch (pathType) {
|
||||
case PersonPathType.FACE: {
|
||||
case PersonPathType.Face: {
|
||||
await this.moveFile({
|
||||
entityId,
|
||||
pathType,
|
||||
@@ -188,7 +188,7 @@ export class StorageCore {
|
||||
move = await this.moveRepository.create({ entityId, pathType, oldPath, newPath });
|
||||
}
|
||||
|
||||
if (pathType === AssetPathType.ORIGINAL && !assetInfo) {
|
||||
if (pathType === AssetPathType.Original && !assetInfo) {
|
||||
this.logger.warn(`Unable to complete move. Missing asset info for ${entityId}`);
|
||||
return;
|
||||
}
|
||||
@@ -274,25 +274,25 @@ export class StorageCore {
|
||||
|
||||
private savePath(pathType: PathType, id: string, newPath: string) {
|
||||
switch (pathType) {
|
||||
case AssetPathType.ORIGINAL: {
|
||||
case AssetPathType.Original: {
|
||||
return this.assetRepository.update({ id, originalPath: newPath });
|
||||
}
|
||||
case AssetPathType.FULLSIZE: {
|
||||
return this.assetRepository.upsertFile({ assetId: id, type: AssetFileType.FULLSIZE, path: newPath });
|
||||
case AssetPathType.FullSize: {
|
||||
return this.assetRepository.upsertFile({ assetId: id, type: AssetFileType.FullSize, path: newPath });
|
||||
}
|
||||
case AssetPathType.PREVIEW: {
|
||||
return this.assetRepository.upsertFile({ assetId: id, type: AssetFileType.PREVIEW, path: newPath });
|
||||
case AssetPathType.Preview: {
|
||||
return this.assetRepository.upsertFile({ assetId: id, type: AssetFileType.Preview, path: newPath });
|
||||
}
|
||||
case AssetPathType.THUMBNAIL: {
|
||||
return this.assetRepository.upsertFile({ assetId: id, type: AssetFileType.THUMBNAIL, path: newPath });
|
||||
case AssetPathType.Thumbnail: {
|
||||
return this.assetRepository.upsertFile({ assetId: id, type: AssetFileType.Thumbnail, path: newPath });
|
||||
}
|
||||
case AssetPathType.ENCODED_VIDEO: {
|
||||
case AssetPathType.EncodedVideo: {
|
||||
return this.assetRepository.update({ id, encodedVideoPath: newPath });
|
||||
}
|
||||
case AssetPathType.SIDECAR: {
|
||||
case AssetPathType.Sidecar: {
|
||||
return this.assetRepository.update({ id, sidecarPath: newPath });
|
||||
}
|
||||
case PersonPathType.FACE: {
|
||||
case PersonPathType.Face: {
|
||||
return this.personRepository.update({ id, thumbnailPath: newPath });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -272,6 +272,8 @@ export type AssetFace = {
|
||||
personId: string | null;
|
||||
sourceType: SourceType;
|
||||
person?: Person | null;
|
||||
updatedAt: Date;
|
||||
updateId: string;
|
||||
};
|
||||
|
||||
const userColumns = ['id', 'name', 'email', 'avatarColor', 'profileImagePath', 'profileChangedAt'] as const;
|
||||
|
||||
@@ -131,7 +131,7 @@ export interface GenerateSqlQueries {
|
||||
}
|
||||
|
||||
export const Telemetry = (options: { enabled?: boolean }) =>
|
||||
SetMetadata(MetadataKey.TELEMETRY_ENABLED, options?.enabled ?? true);
|
||||
SetMetadata(MetadataKey.TelemetryEnabled, options?.enabled ?? true);
|
||||
|
||||
/** Decorator to enable versioning/tracking of generated Sql */
|
||||
export const GenerateSql = (...options: GenerateSqlQueries[]) => SetMetadata(GENERATE_SQL_KEY, options);
|
||||
@@ -145,13 +145,13 @@ export type EventConfig = {
|
||||
/** register events for these workers, defaults to all workers */
|
||||
workers?: ImmichWorker[];
|
||||
};
|
||||
export const OnEvent = (config: EventConfig) => SetMetadata(MetadataKey.EVENT_CONFIG, config);
|
||||
export const OnEvent = (config: EventConfig) => SetMetadata(MetadataKey.EventConfig, config);
|
||||
|
||||
export type JobConfig = {
|
||||
name: JobName;
|
||||
queue: QueueName;
|
||||
};
|
||||
export const OnJob = (config: JobConfig) => SetMetadata(MetadataKey.JOB_CONFIG, config);
|
||||
export const OnJob = (config: JobConfig) => SetMetadata(MetadataKey.JobConfig, config);
|
||||
|
||||
type LifecycleRelease = 'NEXT_RELEASE' | string;
|
||||
type LifecycleMetadata = {
|
||||
|
||||
@@ -18,7 +18,7 @@ export class AlbumUserAddDto {
|
||||
@ValidateUUID()
|
||||
userId!: string;
|
||||
|
||||
@ValidateEnum({ enum: AlbumUserRole, name: 'AlbumUserRole', default: AlbumUserRole.EDITOR })
|
||||
@ValidateEnum({ enum: AlbumUserRole, name: 'AlbumUserRole', default: AlbumUserRole.Editor })
|
||||
role?: AlbumUserRole;
|
||||
}
|
||||
|
||||
|
||||
@@ -205,7 +205,7 @@ export function mapAsset(entity: MapAsset, options: AssetMapOptions = {}): Asset
|
||||
localDateTime: entity.localDateTime,
|
||||
updatedAt: entity.updatedAt,
|
||||
isFavorite: options.auth?.user.id === entity.ownerId ? entity.isFavorite : false,
|
||||
isArchived: entity.visibility === AssetVisibility.ARCHIVE,
|
||||
isArchived: entity.visibility === AssetVisibility.Archive,
|
||||
isTrashed: !!entity.deletedAt,
|
||||
visibility: entity.visibility,
|
||||
duration: entity.duration ?? '0:00:00.00000',
|
||||
|
||||
@@ -126,8 +126,8 @@ export class AssetStatsResponseDto {
|
||||
|
||||
export const mapStats = (stats: AssetStats): AssetStatsResponseDto => {
|
||||
return {
|
||||
images: stats[AssetType.IMAGE],
|
||||
videos: stats[AssetType.VIDEO],
|
||||
images: stats[AssetType.Image],
|
||||
videos: stats[AssetType.Video],
|
||||
total: Object.values(stats).reduce((total, value) => total + value, 0),
|
||||
};
|
||||
};
|
||||
|
||||
@@ -45,7 +45,7 @@ export class LoginResponseDto {
|
||||
|
||||
export function mapLoginResponse(entity: UserAdmin, accessToken: string): LoginResponseDto {
|
||||
const onboardingMetadata = entity.metadata.find(
|
||||
(item): item is UserMetadataItem<UserMetadataKey.ONBOARDING> => item.key === UserMetadataKey.ONBOARDING,
|
||||
(item): item is UserMetadataItem<UserMetadataKey.Onboarding> => item.key === UserMetadataKey.Onboarding,
|
||||
)?.value;
|
||||
|
||||
return {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Transform, Type } from 'class-transformer';
|
||||
import { IsEnum, IsInt, IsString } from 'class-validator';
|
||||
import { IsEnum, IsInt, IsString, Matches } from 'class-validator';
|
||||
import { DatabaseSslMode, ImmichEnvironment, LogLevel } from 'src/enum';
|
||||
import { IsIPRange, Optional, ValidateBoolean } from 'src/validation';
|
||||
|
||||
@@ -48,6 +48,10 @@ export class EnvDto {
|
||||
@Optional()
|
||||
IMMICH_LOG_LEVEL?: LogLevel;
|
||||
|
||||
@Optional()
|
||||
@Matches(/^\//, { message: 'IMMICH_MEDIA_LOCATION must be an absolute path' })
|
||||
IMMICH_MEDIA_LOCATION?: string;
|
||||
|
||||
@IsInt()
|
||||
@Optional()
|
||||
@Type(() => Number)
|
||||
|
||||
+15
-15
@@ -50,47 +50,47 @@ export class JobStatusDto {
|
||||
|
||||
export class AllJobStatusResponseDto implements Record<QueueName, JobStatusDto> {
|
||||
@ApiProperty({ type: JobStatusDto })
|
||||
[QueueName.THUMBNAIL_GENERATION]!: JobStatusDto;
|
||||
[QueueName.ThumbnailGeneration]!: JobStatusDto;
|
||||
|
||||
@ApiProperty({ type: JobStatusDto })
|
||||
[QueueName.METADATA_EXTRACTION]!: JobStatusDto;
|
||||
[QueueName.MetadataExtraction]!: JobStatusDto;
|
||||
|
||||
@ApiProperty({ type: JobStatusDto })
|
||||
[QueueName.VIDEO_CONVERSION]!: JobStatusDto;
|
||||
[QueueName.VideoConversion]!: JobStatusDto;
|
||||
|
||||
@ApiProperty({ type: JobStatusDto })
|
||||
[QueueName.SMART_SEARCH]!: JobStatusDto;
|
||||
[QueueName.SmartSearch]!: JobStatusDto;
|
||||
|
||||
@ApiProperty({ type: JobStatusDto })
|
||||
[QueueName.STORAGE_TEMPLATE_MIGRATION]!: JobStatusDto;
|
||||
[QueueName.StorageTemplateMigration]!: JobStatusDto;
|
||||
|
||||
@ApiProperty({ type: JobStatusDto })
|
||||
[QueueName.MIGRATION]!: JobStatusDto;
|
||||
[QueueName.Migration]!: JobStatusDto;
|
||||
|
||||
@ApiProperty({ type: JobStatusDto })
|
||||
[QueueName.BACKGROUND_TASK]!: JobStatusDto;
|
||||
[QueueName.BackgroundTask]!: JobStatusDto;
|
||||
|
||||
@ApiProperty({ type: JobStatusDto })
|
||||
[QueueName.SEARCH]!: JobStatusDto;
|
||||
[QueueName.Search]!: JobStatusDto;
|
||||
|
||||
@ApiProperty({ type: JobStatusDto })
|
||||
[QueueName.DUPLICATE_DETECTION]!: JobStatusDto;
|
||||
[QueueName.DuplicateDetection]!: JobStatusDto;
|
||||
|
||||
@ApiProperty({ type: JobStatusDto })
|
||||
[QueueName.FACE_DETECTION]!: JobStatusDto;
|
||||
[QueueName.FaceDetection]!: JobStatusDto;
|
||||
|
||||
@ApiProperty({ type: JobStatusDto })
|
||||
[QueueName.FACIAL_RECOGNITION]!: JobStatusDto;
|
||||
[QueueName.FacialRecognition]!: JobStatusDto;
|
||||
|
||||
@ApiProperty({ type: JobStatusDto })
|
||||
[QueueName.SIDECAR]!: JobStatusDto;
|
||||
[QueueName.Sidecar]!: JobStatusDto;
|
||||
|
||||
@ApiProperty({ type: JobStatusDto })
|
||||
[QueueName.LIBRARY]!: JobStatusDto;
|
||||
[QueueName.Library]!: JobStatusDto;
|
||||
|
||||
@ApiProperty({ type: JobStatusDto })
|
||||
[QueueName.NOTIFICATION]!: JobStatusDto;
|
||||
[QueueName.Notification]!: JobStatusDto;
|
||||
|
||||
@ApiProperty({ type: JobStatusDto })
|
||||
[QueueName.BACKUP_DATABASE]!: JobStatusDto;
|
||||
[QueueName.BackupDatabase]!: JobStatusDto;
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ export class MemoryCreateDto extends MemoryBaseDto {
|
||||
@ValidateNested()
|
||||
@Type((options) => {
|
||||
switch (options?.object.type) {
|
||||
case MemoryType.ON_THIS_DAY: {
|
||||
case MemoryType.OnThisDay: {
|
||||
return OnThisDayDto;
|
||||
}
|
||||
|
||||
|
||||
@@ -170,7 +170,7 @@ export class MetadataSearchDto extends RandomSearchDto {
|
||||
@Optional()
|
||||
encodedVideoPath?: string;
|
||||
|
||||
@ValidateEnum({ enum: AssetOrder, name: 'AssetOrder', optional: true, default: AssetOrder.DESC })
|
||||
@ValidateEnum({ enum: AssetOrder, name: 'AssetOrder', optional: true, default: AssetOrder.Desc })
|
||||
order?: AssetOrder;
|
||||
|
||||
@IsInt()
|
||||
|
||||
@@ -245,7 +245,6 @@ export class SyncPersonV1 {
|
||||
ownerId!: string;
|
||||
name!: string;
|
||||
birthDate!: Date | null;
|
||||
thumbnailPath!: string;
|
||||
isHidden!: boolean;
|
||||
isFavorite!: boolean;
|
||||
color!: string | null;
|
||||
@@ -257,6 +256,25 @@ export class SyncPersonDeleteV1 {
|
||||
personId!: string;
|
||||
}
|
||||
|
||||
@ExtraModel()
|
||||
export class SyncAssetFaceV1 {
|
||||
id!: string;
|
||||
assetId!: string;
|
||||
personId!: string | null;
|
||||
imageWidth!: number;
|
||||
imageHeight!: number;
|
||||
boundingBoxX1!: number;
|
||||
boundingBoxY1!: number;
|
||||
boundingBoxX2!: number;
|
||||
boundingBoxY2!: number;
|
||||
sourceType!: string;
|
||||
}
|
||||
|
||||
@ExtraModel()
|
||||
export class SyncAssetFaceDeleteV1 {
|
||||
assetFaceId!: string;
|
||||
}
|
||||
|
||||
@ExtraModel()
|
||||
export class SyncUserMetadataV1 {
|
||||
userId!: string;
|
||||
@@ -312,6 +330,8 @@ export type SyncItem = {
|
||||
[SyncEntityType.PartnerStackV1]: SyncStackV1;
|
||||
[SyncEntityType.PersonV1]: SyncPersonV1;
|
||||
[SyncEntityType.PersonDeleteV1]: SyncPersonDeleteV1;
|
||||
[SyncEntityType.AssetFaceV1]: SyncAssetFaceV1;
|
||||
[SyncEntityType.AssetFaceDeleteV1]: SyncAssetFaceDeleteV1;
|
||||
[SyncEntityType.UserMetadataV1]: SyncUserMetadataV1;
|
||||
[SyncEntityType.UserMetadataDeleteV1]: SyncUserMetadataDeleteV1;
|
||||
[SyncEntityType.SyncAckV1]: SyncAckV1;
|
||||
|
||||
@@ -26,7 +26,7 @@ import {
|
||||
OAuthTokenEndpointAuthMethod,
|
||||
QueueName,
|
||||
ToneMapping,
|
||||
TranscodeHWAccel,
|
||||
TranscodeHardwareAcceleration,
|
||||
TranscodePolicy,
|
||||
VideoCodec,
|
||||
VideoContainer,
|
||||
@@ -136,8 +136,8 @@ export class SystemConfigFFmpegDto {
|
||||
@ValidateEnum({ enum: TranscodePolicy, name: 'TranscodePolicy' })
|
||||
transcode!: TranscodePolicy;
|
||||
|
||||
@ValidateEnum({ enum: TranscodeHWAccel, name: 'TranscodeHWAccel' })
|
||||
accel!: TranscodeHWAccel;
|
||||
@ValidateEnum({ enum: TranscodeHardwareAcceleration, name: 'TranscodeHWAccel' })
|
||||
accel!: TranscodeHardwareAcceleration;
|
||||
|
||||
@ValidateBoolean()
|
||||
accelDecode!: boolean;
|
||||
@@ -158,67 +158,67 @@ class SystemConfigJobDto implements Record<ConcurrentQueueName, JobSettingsDto>
|
||||
@ValidateNested()
|
||||
@IsObject()
|
||||
@Type(() => JobSettingsDto)
|
||||
[QueueName.THUMBNAIL_GENERATION]!: JobSettingsDto;
|
||||
[QueueName.ThumbnailGeneration]!: JobSettingsDto;
|
||||
|
||||
@ApiProperty({ type: JobSettingsDto })
|
||||
@ValidateNested()
|
||||
@IsObject()
|
||||
@Type(() => JobSettingsDto)
|
||||
[QueueName.METADATA_EXTRACTION]!: JobSettingsDto;
|
||||
[QueueName.MetadataExtraction]!: JobSettingsDto;
|
||||
|
||||
@ApiProperty({ type: JobSettingsDto })
|
||||
@ValidateNested()
|
||||
@IsObject()
|
||||
@Type(() => JobSettingsDto)
|
||||
[QueueName.VIDEO_CONVERSION]!: JobSettingsDto;
|
||||
[QueueName.VideoConversion]!: JobSettingsDto;
|
||||
|
||||
@ApiProperty({ type: JobSettingsDto })
|
||||
@ValidateNested()
|
||||
@IsObject()
|
||||
@Type(() => JobSettingsDto)
|
||||
[QueueName.SMART_SEARCH]!: JobSettingsDto;
|
||||
[QueueName.SmartSearch]!: JobSettingsDto;
|
||||
|
||||
@ApiProperty({ type: JobSettingsDto })
|
||||
@ValidateNested()
|
||||
@IsObject()
|
||||
@Type(() => JobSettingsDto)
|
||||
[QueueName.MIGRATION]!: JobSettingsDto;
|
||||
[QueueName.Migration]!: JobSettingsDto;
|
||||
|
||||
@ApiProperty({ type: JobSettingsDto })
|
||||
@ValidateNested()
|
||||
@IsObject()
|
||||
@Type(() => JobSettingsDto)
|
||||
[QueueName.BACKGROUND_TASK]!: JobSettingsDto;
|
||||
[QueueName.BackgroundTask]!: JobSettingsDto;
|
||||
|
||||
@ApiProperty({ type: JobSettingsDto })
|
||||
@ValidateNested()
|
||||
@IsObject()
|
||||
@Type(() => JobSettingsDto)
|
||||
[QueueName.SEARCH]!: JobSettingsDto;
|
||||
[QueueName.Search]!: JobSettingsDto;
|
||||
|
||||
@ApiProperty({ type: JobSettingsDto })
|
||||
@ValidateNested()
|
||||
@IsObject()
|
||||
@Type(() => JobSettingsDto)
|
||||
[QueueName.FACE_DETECTION]!: JobSettingsDto;
|
||||
[QueueName.FaceDetection]!: JobSettingsDto;
|
||||
|
||||
@ApiProperty({ type: JobSettingsDto })
|
||||
@ValidateNested()
|
||||
@IsObject()
|
||||
@Type(() => JobSettingsDto)
|
||||
[QueueName.SIDECAR]!: JobSettingsDto;
|
||||
[QueueName.Sidecar]!: JobSettingsDto;
|
||||
|
||||
@ApiProperty({ type: JobSettingsDto })
|
||||
@ValidateNested()
|
||||
@IsObject()
|
||||
@Type(() => JobSettingsDto)
|
||||
[QueueName.LIBRARY]!: JobSettingsDto;
|
||||
[QueueName.Library]!: JobSettingsDto;
|
||||
|
||||
@ApiProperty({ type: JobSettingsDto })
|
||||
@ValidateNested()
|
||||
@IsObject()
|
||||
@Type(() => JobSettingsDto)
|
||||
[QueueName.NOTIFICATION]!: JobSettingsDto;
|
||||
[QueueName.Notification]!: JobSettingsDto;
|
||||
}
|
||||
|
||||
class SystemConfigLibraryScanDto {
|
||||
|
||||
@@ -157,7 +157,7 @@ export class UserPreferencesUpdateDto {
|
||||
|
||||
class AlbumsResponse {
|
||||
@ValidateEnum({ enum: AssetOrder, name: 'AssetOrder' })
|
||||
defaultAssetOrder: AssetOrder = AssetOrder.DESC;
|
||||
defaultAssetOrder: AssetOrder = AssetOrder.Desc;
|
||||
}
|
||||
|
||||
class RatingsResponse {
|
||||
|
||||
@@ -171,7 +171,7 @@ export class UserAdminResponseDto extends UserResponseDto {
|
||||
export function mapUserAdmin(entity: UserAdmin): UserAdminResponseDto {
|
||||
const metadata = entity.metadata || [];
|
||||
const license = metadata.find(
|
||||
(item): item is UserMetadataItem<UserMetadataKey.LICENSE> => item.key === UserMetadataKey.LICENSE,
|
||||
(item): item is UserMetadataItem<UserMetadataKey.License> => item.key === UserMetadataKey.License,
|
||||
)?.value;
|
||||
return {
|
||||
...mapUser(entity),
|
||||
|
||||
+331
-349
@@ -1,402 +1,398 @@
|
||||
export enum AuthType {
|
||||
PASSWORD = 'password',
|
||||
OAUTH = 'oauth',
|
||||
Password = 'password',
|
||||
OAuth = 'oauth',
|
||||
}
|
||||
|
||||
export enum ImmichCookie {
|
||||
ACCESS_TOKEN = 'immich_access_token',
|
||||
AUTH_TYPE = 'immich_auth_type',
|
||||
IS_AUTHENTICATED = 'immich_is_authenticated',
|
||||
SHARED_LINK_TOKEN = 'immich_shared_link_token',
|
||||
OAUTH_STATE = 'immich_oauth_state',
|
||||
OAUTH_CODE_VERIFIER = 'immich_oauth_code_verifier',
|
||||
AccessToken = 'immich_access_token',
|
||||
AuthType = 'immich_auth_type',
|
||||
IsAuthenticated = 'immich_is_authenticated',
|
||||
SharedLinkToken = 'immich_shared_link_token',
|
||||
OAuthState = 'immich_oauth_state',
|
||||
OAuthCodeVerifier = 'immich_oauth_code_verifier',
|
||||
}
|
||||
|
||||
export enum ImmichHeader {
|
||||
API_KEY = 'x-api-key',
|
||||
USER_TOKEN = 'x-immich-user-token',
|
||||
SESSION_TOKEN = 'x-immich-session-token',
|
||||
SHARED_LINK_KEY = 'x-immich-share-key',
|
||||
CHECKSUM = 'x-immich-checksum',
|
||||
CID = 'x-immich-cid',
|
||||
ApiKey = 'x-api-key',
|
||||
UserToken = 'x-immich-user-token',
|
||||
SessionToken = 'x-immich-session-token',
|
||||
SharedLinkKey = 'x-immich-share-key',
|
||||
Checksum = 'x-immich-checksum',
|
||||
Cid = 'x-immich-cid',
|
||||
}
|
||||
|
||||
export enum ImmichQuery {
|
||||
SHARED_LINK_KEY = 'key',
|
||||
API_KEY = 'apiKey',
|
||||
SESSION_KEY = 'sessionKey',
|
||||
SharedLinkKey = 'key',
|
||||
ApiKey = 'apiKey',
|
||||
SessionKey = 'sessionKey',
|
||||
}
|
||||
|
||||
export enum AssetType {
|
||||
IMAGE = 'IMAGE',
|
||||
VIDEO = 'VIDEO',
|
||||
AUDIO = 'AUDIO',
|
||||
OTHER = 'OTHER',
|
||||
Image = 'IMAGE',
|
||||
Video = 'VIDEO',
|
||||
Audio = 'AUDIO',
|
||||
Other = 'OTHER',
|
||||
}
|
||||
|
||||
export enum AssetFileType {
|
||||
/**
|
||||
* An full/large-size image extracted/converted from RAW photos
|
||||
*/
|
||||
FULLSIZE = 'fullsize',
|
||||
PREVIEW = 'preview',
|
||||
THUMBNAIL = 'thumbnail',
|
||||
FullSize = 'fullsize',
|
||||
Preview = 'preview',
|
||||
Thumbnail = 'thumbnail',
|
||||
}
|
||||
|
||||
export enum AlbumUserRole {
|
||||
EDITOR = 'editor',
|
||||
VIEWER = 'viewer',
|
||||
Editor = 'editor',
|
||||
Viewer = 'viewer',
|
||||
}
|
||||
|
||||
export enum AssetOrder {
|
||||
ASC = 'asc',
|
||||
DESC = 'desc',
|
||||
Asc = 'asc',
|
||||
Desc = 'desc',
|
||||
}
|
||||
|
||||
export enum DatabaseAction {
|
||||
CREATE = 'CREATE',
|
||||
UPDATE = 'UPDATE',
|
||||
DELETE = 'DELETE',
|
||||
Create = 'CREATE',
|
||||
Update = 'UPDATE',
|
||||
Delete = 'DELETE',
|
||||
}
|
||||
|
||||
export enum EntityType {
|
||||
ASSET = 'ASSET',
|
||||
ALBUM = 'ALBUM',
|
||||
Asset = 'ASSET',
|
||||
Album = 'ALBUM',
|
||||
}
|
||||
|
||||
export enum MemoryType {
|
||||
/** pictures taken on this day X years ago */
|
||||
ON_THIS_DAY = 'on_this_day',
|
||||
OnThisDay = 'on_this_day',
|
||||
}
|
||||
|
||||
export enum Permission {
|
||||
ALL = 'all',
|
||||
All = 'all',
|
||||
|
||||
ACTIVITY_CREATE = 'activity.create',
|
||||
ACTIVITY_READ = 'activity.read',
|
||||
ACTIVITY_UPDATE = 'activity.update',
|
||||
ACTIVITY_DELETE = 'activity.delete',
|
||||
ACTIVITY_STATISTICS = 'activity.statistics',
|
||||
ActivityCreate = 'activity.create',
|
||||
ActivityRead = 'activity.read',
|
||||
ActivityUpdate = 'activity.update',
|
||||
ActivityDelete = 'activity.delete',
|
||||
ActivityStatistics = 'activity.statistics',
|
||||
|
||||
API_KEY_CREATE = 'apiKey.create',
|
||||
API_KEY_READ = 'apiKey.read',
|
||||
API_KEY_UPDATE = 'apiKey.update',
|
||||
API_KEY_DELETE = 'apiKey.delete',
|
||||
ApiKeyCreate = 'apiKey.create',
|
||||
ApiKeyRead = 'apiKey.read',
|
||||
ApiKeyUpdate = 'apiKey.update',
|
||||
ApiKeyDelete = 'apiKey.delete',
|
||||
|
||||
// ASSET_CREATE = 'asset.create',
|
||||
ASSET_READ = 'asset.read',
|
||||
ASSET_UPDATE = 'asset.update',
|
||||
ASSET_DELETE = 'asset.delete',
|
||||
ASSET_SHARE = 'asset.share',
|
||||
ASSET_VIEW = 'asset.view',
|
||||
ASSET_DOWNLOAD = 'asset.download',
|
||||
ASSET_UPLOAD = 'asset.upload',
|
||||
AssetRead = 'asset.read',
|
||||
AssetUpdate = 'asset.update',
|
||||
AssetDelete = 'asset.delete',
|
||||
AssetShare = 'asset.share',
|
||||
AssetView = 'asset.view',
|
||||
AssetDownload = 'asset.download',
|
||||
AssetUpload = 'asset.upload',
|
||||
|
||||
ALBUM_CREATE = 'album.create',
|
||||
ALBUM_READ = 'album.read',
|
||||
ALBUM_UPDATE = 'album.update',
|
||||
ALBUM_DELETE = 'album.delete',
|
||||
ALBUM_STATISTICS = 'album.statistics',
|
||||
AlbumCreate = 'album.create',
|
||||
AlbumRead = 'album.read',
|
||||
AlbumUpdate = 'album.update',
|
||||
AlbumDelete = 'album.delete',
|
||||
AlbumStatistics = 'album.statistics',
|
||||
|
||||
ALBUM_ADD_ASSET = 'album.addAsset',
|
||||
ALBUM_REMOVE_ASSET = 'album.removeAsset',
|
||||
ALBUM_SHARE = 'album.share',
|
||||
ALBUM_DOWNLOAD = 'album.download',
|
||||
AlbumAddAsset = 'album.addAsset',
|
||||
AlbumRemoveAsset = 'album.removeAsset',
|
||||
AlbumShare = 'album.share',
|
||||
AlbumDownload = 'album.download',
|
||||
|
||||
AUTH_DEVICE_DELETE = 'authDevice.delete',
|
||||
AuthDeviceDelete = 'authDevice.delete',
|
||||
|
||||
ARCHIVE_READ = 'archive.read',
|
||||
ArchiveRead = 'archive.read',
|
||||
|
||||
FACE_CREATE = 'face.create',
|
||||
FACE_READ = 'face.read',
|
||||
FACE_UPDATE = 'face.update',
|
||||
FACE_DELETE = 'face.delete',
|
||||
FaceCreate = 'face.create',
|
||||
FaceRead = 'face.read',
|
||||
FaceUpdate = 'face.update',
|
||||
FaceDelete = 'face.delete',
|
||||
|
||||
LIBRARY_CREATE = 'library.create',
|
||||
LIBRARY_READ = 'library.read',
|
||||
LIBRARY_UPDATE = 'library.update',
|
||||
LIBRARY_DELETE = 'library.delete',
|
||||
LIBRARY_STATISTICS = 'library.statistics',
|
||||
LibraryCreate = 'library.create',
|
||||
LibraryRead = 'library.read',
|
||||
LibraryUpdate = 'library.update',
|
||||
LibraryDelete = 'library.delete',
|
||||
LibraryStatistics = 'library.statistics',
|
||||
|
||||
TIMELINE_READ = 'timeline.read',
|
||||
TIMELINE_DOWNLOAD = 'timeline.download',
|
||||
TimelineRead = 'timeline.read',
|
||||
TimelineDownload = 'timeline.download',
|
||||
|
||||
MEMORY_CREATE = 'memory.create',
|
||||
MEMORY_READ = 'memory.read',
|
||||
MEMORY_UPDATE = 'memory.update',
|
||||
MEMORY_DELETE = 'memory.delete',
|
||||
MemoryCreate = 'memory.create',
|
||||
MemoryRead = 'memory.read',
|
||||
MemoryUpdate = 'memory.update',
|
||||
MemoryDelete = 'memory.delete',
|
||||
|
||||
NOTIFICATION_CREATE = 'notification.create',
|
||||
NOTIFICATION_READ = 'notification.read',
|
||||
NOTIFICATION_UPDATE = 'notification.update',
|
||||
NOTIFICATION_DELETE = 'notification.delete',
|
||||
NotificationCreate = 'notification.create',
|
||||
NotificationRead = 'notification.read',
|
||||
NotificationUpdate = 'notification.update',
|
||||
NotificationDelete = 'notification.delete',
|
||||
|
||||
PARTNER_CREATE = 'partner.create',
|
||||
PARTNER_READ = 'partner.read',
|
||||
PARTNER_UPDATE = 'partner.update',
|
||||
PARTNER_DELETE = 'partner.delete',
|
||||
PartnerCreate = 'partner.create',
|
||||
PartnerRead = 'partner.read',
|
||||
PartnerUpdate = 'partner.update',
|
||||
PartnerDelete = 'partner.delete',
|
||||
|
||||
PERSON_CREATE = 'person.create',
|
||||
PERSON_READ = 'person.read',
|
||||
PERSON_UPDATE = 'person.update',
|
||||
PERSON_DELETE = 'person.delete',
|
||||
PERSON_STATISTICS = 'person.statistics',
|
||||
PERSON_MERGE = 'person.merge',
|
||||
PERSON_REASSIGN = 'person.reassign',
|
||||
PersonCreate = 'person.create',
|
||||
PersonRead = 'person.read',
|
||||
PersonUpdate = 'person.update',
|
||||
PersonDelete = 'person.delete',
|
||||
PersonStatistics = 'person.statistics',
|
||||
PersonMerge = 'person.merge',
|
||||
PersonReassign = 'person.reassign',
|
||||
|
||||
SESSION_CREATE = 'session.create',
|
||||
SESSION_READ = 'session.read',
|
||||
SESSION_UPDATE = 'session.update',
|
||||
SESSION_DELETE = 'session.delete',
|
||||
SESSION_LOCK = 'session.lock',
|
||||
SessionCreate = 'session.create',
|
||||
SessionRead = 'session.read',
|
||||
SessionUpdate = 'session.update',
|
||||
SessionDelete = 'session.delete',
|
||||
SessionLock = 'session.lock',
|
||||
|
||||
SHARED_LINK_CREATE = 'sharedLink.create',
|
||||
SHARED_LINK_READ = 'sharedLink.read',
|
||||
SHARED_LINK_UPDATE = 'sharedLink.update',
|
||||
SHARED_LINK_DELETE = 'sharedLink.delete',
|
||||
SharedLinkCreate = 'sharedLink.create',
|
||||
SharedLinkRead = 'sharedLink.read',
|
||||
SharedLinkUpdate = 'sharedLink.update',
|
||||
SharedLinkDelete = 'sharedLink.delete',
|
||||
|
||||
STACK_CREATE = 'stack.create',
|
||||
STACK_READ = 'stack.read',
|
||||
STACK_UPDATE = 'stack.update',
|
||||
STACK_DELETE = 'stack.delete',
|
||||
StackCreate = 'stack.create',
|
||||
StackRead = 'stack.read',
|
||||
StackUpdate = 'stack.update',
|
||||
StackDelete = 'stack.delete',
|
||||
|
||||
SYSTEM_CONFIG_READ = 'systemConfig.read',
|
||||
SYSTEM_CONFIG_UPDATE = 'systemConfig.update',
|
||||
SystemConfigRead = 'systemConfig.read',
|
||||
SystemConfigUpdate = 'systemConfig.update',
|
||||
|
||||
SYSTEM_METADATA_READ = 'systemMetadata.read',
|
||||
SYSTEM_METADATA_UPDATE = 'systemMetadata.update',
|
||||
SystemMetadataRead = 'systemMetadata.read',
|
||||
SystemMetadataUpdate = 'systemMetadata.update',
|
||||
|
||||
TAG_CREATE = 'tag.create',
|
||||
TAG_READ = 'tag.read',
|
||||
TAG_UPDATE = 'tag.update',
|
||||
TAG_DELETE = 'tag.delete',
|
||||
TAG_ASSET = 'tag.asset',
|
||||
TagCreate = 'tag.create',
|
||||
TagRead = 'tag.read',
|
||||
TagUpdate = 'tag.update',
|
||||
TagDelete = 'tag.delete',
|
||||
TagAsset = 'tag.asset',
|
||||
|
||||
ADMIN_USER_CREATE = 'admin.user.create',
|
||||
ADMIN_USER_READ = 'admin.user.read',
|
||||
ADMIN_USER_UPDATE = 'admin.user.update',
|
||||
ADMIN_USER_DELETE = 'admin.user.delete',
|
||||
AdminUserCreate = 'admin.user.create',
|
||||
AdminUserRead = 'admin.user.read',
|
||||
AdminUserUpdate = 'admin.user.update',
|
||||
AdminUserDelete = 'admin.user.delete',
|
||||
}
|
||||
|
||||
export enum SharedLinkType {
|
||||
ALBUM = 'ALBUM',
|
||||
Album = 'ALBUM',
|
||||
|
||||
/**
|
||||
* Individual asset
|
||||
* or group of assets that are not in an album
|
||||
*/
|
||||
INDIVIDUAL = 'INDIVIDUAL',
|
||||
Individual = 'INDIVIDUAL',
|
||||
}
|
||||
|
||||
export enum StorageFolder {
|
||||
ENCODED_VIDEO = 'encoded-video',
|
||||
LIBRARY = 'library',
|
||||
UPLOAD = 'upload',
|
||||
PROFILE = 'profile',
|
||||
THUMBNAILS = 'thumbs',
|
||||
BACKUPS = 'backups',
|
||||
EncodedVideo = 'encoded-video',
|
||||
Library = 'library',
|
||||
Upload = 'upload',
|
||||
Profile = 'profile',
|
||||
Thumbnails = 'thumbs',
|
||||
Backups = 'backups',
|
||||
}
|
||||
|
||||
export enum SystemMetadataKey {
|
||||
REVERSE_GEOCODING_STATE = 'reverse-geocoding-state',
|
||||
FACIAL_RECOGNITION_STATE = 'facial-recognition-state',
|
||||
MEMORIES_STATE = 'memories-state',
|
||||
ADMIN_ONBOARDING = 'admin-onboarding',
|
||||
SYSTEM_CONFIG = 'system-config',
|
||||
SYSTEM_FLAGS = 'system-flags',
|
||||
VERSION_CHECK_STATE = 'version-check-state',
|
||||
LICENSE = 'license',
|
||||
MediaLocation = 'MediaLocation',
|
||||
ReverseGeocodingState = 'reverse-geocoding-state',
|
||||
FacialRecognitionState = 'facial-recognition-state',
|
||||
MemoriesState = 'memories-state',
|
||||
AdminOnboarding = 'admin-onboarding',
|
||||
SystemConfig = 'system-config',
|
||||
SystemFlags = 'system-flags',
|
||||
VersionCheckState = 'version-check-state',
|
||||
License = 'license',
|
||||
}
|
||||
|
||||
export enum UserMetadataKey {
|
||||
PREFERENCES = 'preferences',
|
||||
LICENSE = 'license',
|
||||
ONBOARDING = 'onboarding',
|
||||
Preferences = 'preferences',
|
||||
License = 'license',
|
||||
Onboarding = 'onboarding',
|
||||
}
|
||||
|
||||
export enum UserAvatarColor {
|
||||
PRIMARY = 'primary',
|
||||
PINK = 'pink',
|
||||
RED = 'red',
|
||||
YELLOW = 'yellow',
|
||||
BLUE = 'blue',
|
||||
GREEN = 'green',
|
||||
PURPLE = 'purple',
|
||||
ORANGE = 'orange',
|
||||
GRAY = 'gray',
|
||||
AMBER = 'amber',
|
||||
Primary = 'primary',
|
||||
Pink = 'pink',
|
||||
Red = 'red',
|
||||
Yellow = 'yellow',
|
||||
Blue = 'blue',
|
||||
Green = 'green',
|
||||
Purple = 'purple',
|
||||
Orange = 'orange',
|
||||
Gray = 'gray',
|
||||
Amber = 'amber',
|
||||
}
|
||||
|
||||
export enum UserStatus {
|
||||
ACTIVE = 'active',
|
||||
REMOVING = 'removing',
|
||||
DELETED = 'deleted',
|
||||
Active = 'active',
|
||||
Removing = 'removing',
|
||||
Deleted = 'deleted',
|
||||
}
|
||||
|
||||
export enum AssetStatus {
|
||||
ACTIVE = 'active',
|
||||
TRASHED = 'trashed',
|
||||
DELETED = 'deleted',
|
||||
Active = 'active',
|
||||
Trashed = 'trashed',
|
||||
Deleted = 'deleted',
|
||||
}
|
||||
|
||||
export enum SourceType {
|
||||
MACHINE_LEARNING = 'machine-learning',
|
||||
EXIF = 'exif',
|
||||
MANUAL = 'manual',
|
||||
MachineLearning = 'machine-learning',
|
||||
Exif = 'exif',
|
||||
Manual = 'manual',
|
||||
}
|
||||
|
||||
export enum ManualJobName {
|
||||
PERSON_CLEANUP = 'person-cleanup',
|
||||
TAG_CLEANUP = 'tag-cleanup',
|
||||
USER_CLEANUP = 'user-cleanup',
|
||||
MEMORY_CLEANUP = 'memory-cleanup',
|
||||
MEMORY_CREATE = 'memory-create',
|
||||
BACKUP_DATABASE = 'backup-database',
|
||||
PersonCleanup = 'person-cleanup',
|
||||
TagCleanup = 'tag-cleanup',
|
||||
UserCleanup = 'user-cleanup',
|
||||
MemoryCleanup = 'memory-cleanup',
|
||||
MemoryCreate = 'memory-create',
|
||||
BackupDatabase = 'backup-database',
|
||||
}
|
||||
|
||||
export enum AssetPathType {
|
||||
ORIGINAL = 'original',
|
||||
FULLSIZE = 'fullsize',
|
||||
PREVIEW = 'preview',
|
||||
THUMBNAIL = 'thumbnail',
|
||||
ENCODED_VIDEO = 'encoded_video',
|
||||
SIDECAR = 'sidecar',
|
||||
Original = 'original',
|
||||
FullSize = 'fullsize',
|
||||
Preview = 'preview',
|
||||
Thumbnail = 'thumbnail',
|
||||
EncodedVideo = 'encoded_video',
|
||||
Sidecar = 'sidecar',
|
||||
}
|
||||
|
||||
export enum PersonPathType {
|
||||
FACE = 'face',
|
||||
Face = 'face',
|
||||
}
|
||||
|
||||
export enum UserPathType {
|
||||
PROFILE = 'profile',
|
||||
Profile = 'profile',
|
||||
}
|
||||
|
||||
export type PathType = AssetPathType | PersonPathType | UserPathType;
|
||||
|
||||
export enum TranscodePolicy {
|
||||
ALL = 'all',
|
||||
OPTIMAL = 'optimal',
|
||||
BITRATE = 'bitrate',
|
||||
REQUIRED = 'required',
|
||||
DISABLED = 'disabled',
|
||||
All = 'all',
|
||||
Optimal = 'optimal',
|
||||
Bitrate = 'bitrate',
|
||||
Required = 'required',
|
||||
Disabled = 'disabled',
|
||||
}
|
||||
|
||||
export enum TranscodeTarget {
|
||||
NONE,
|
||||
AUDIO,
|
||||
VIDEO,
|
||||
ALL,
|
||||
None = 'NONE',
|
||||
Audio = 'AUDIO',
|
||||
Video = 'VIDEO',
|
||||
All = 'ALL',
|
||||
}
|
||||
|
||||
export enum VideoCodec {
|
||||
H264 = 'h264',
|
||||
HEVC = 'hevc',
|
||||
VP9 = 'vp9',
|
||||
AV1 = 'av1',
|
||||
Hevc = 'hevc',
|
||||
Vp9 = 'vp9',
|
||||
Av1 = 'av1',
|
||||
}
|
||||
|
||||
export enum AudioCodec {
|
||||
MP3 = 'mp3',
|
||||
AAC = 'aac',
|
||||
LIBOPUS = 'libopus',
|
||||
PCMS16LE = 'pcm_s16le',
|
||||
Mp3 = 'mp3',
|
||||
Aac = 'aac',
|
||||
LibOpus = 'libopus',
|
||||
PcmS16le = 'pcm_s16le',
|
||||
}
|
||||
|
||||
export enum VideoContainer {
|
||||
MOV = 'mov',
|
||||
MP4 = 'mp4',
|
||||
OGG = 'ogg',
|
||||
WEBM = 'webm',
|
||||
Mov = 'mov',
|
||||
Mp4 = 'mp4',
|
||||
Ogg = 'ogg',
|
||||
Webm = 'webm',
|
||||
}
|
||||
|
||||
export enum TranscodeHWAccel {
|
||||
NVENC = 'nvenc',
|
||||
QSV = 'qsv',
|
||||
VAAPI = 'vaapi',
|
||||
RKMPP = 'rkmpp',
|
||||
DISABLED = 'disabled',
|
||||
export enum TranscodeHardwareAcceleration {
|
||||
Nvenc = 'nvenc',
|
||||
Qsv = 'qsv',
|
||||
Vaapi = 'vaapi',
|
||||
Rkmpp = 'rkmpp',
|
||||
Disabled = 'disabled',
|
||||
}
|
||||
|
||||
export enum ToneMapping {
|
||||
HABLE = 'hable',
|
||||
MOBIUS = 'mobius',
|
||||
REINHARD = 'reinhard',
|
||||
DISABLED = 'disabled',
|
||||
Hable = 'hable',
|
||||
Mobius = 'mobius',
|
||||
Reinhard = 'reinhard',
|
||||
Disabled = 'disabled',
|
||||
}
|
||||
|
||||
export enum CQMode {
|
||||
AUTO = 'auto',
|
||||
CQP = 'cqp',
|
||||
ICQ = 'icq',
|
||||
Auto = 'auto',
|
||||
Cqp = 'cqp',
|
||||
Icq = 'icq',
|
||||
}
|
||||
|
||||
export enum Colorspace {
|
||||
SRGB = 'srgb',
|
||||
Srgb = 'srgb',
|
||||
P3 = 'p3',
|
||||
}
|
||||
|
||||
export enum ImageFormat {
|
||||
JPEG = 'jpeg',
|
||||
WEBP = 'webp',
|
||||
Jpeg = 'jpeg',
|
||||
Webp = 'webp',
|
||||
}
|
||||
|
||||
export enum RawExtractedFormat {
|
||||
JPEG = 'jpeg',
|
||||
JXL = 'jxl',
|
||||
Jpeg = 'jpeg',
|
||||
Jxl = 'jxl',
|
||||
}
|
||||
|
||||
export enum LogLevel {
|
||||
VERBOSE = 'verbose',
|
||||
DEBUG = 'debug',
|
||||
LOG = 'log',
|
||||
WARN = 'warn',
|
||||
ERROR = 'error',
|
||||
FATAL = 'fatal',
|
||||
Verbose = 'verbose',
|
||||
Debug = 'debug',
|
||||
Log = 'log',
|
||||
Warn = 'warn',
|
||||
Error = 'error',
|
||||
Fatal = 'fatal',
|
||||
}
|
||||
|
||||
export enum MetadataKey {
|
||||
AUTH_ROUTE = 'auth_route',
|
||||
ADMIN_ROUTE = 'admin_route',
|
||||
SHARED_ROUTE = 'shared_route',
|
||||
API_KEY_SECURITY = 'api_key',
|
||||
EVENT_CONFIG = 'event_config',
|
||||
JOB_CONFIG = 'job_config',
|
||||
TELEMETRY_ENABLED = 'telemetry_enabled',
|
||||
AuthRoute = 'auth_route',
|
||||
AdminRoute = 'admin_route',
|
||||
SharedRoute = 'shared_route',
|
||||
ApiKeySecurity = 'api_key',
|
||||
EventConfig = 'event_config',
|
||||
JobConfig = 'job_config',
|
||||
TelemetryEnabled = 'telemetry_enabled',
|
||||
}
|
||||
|
||||
export enum RouteKey {
|
||||
ASSET = 'assets',
|
||||
USER = 'users',
|
||||
Asset = 'assets',
|
||||
User = 'users',
|
||||
}
|
||||
|
||||
export enum CacheControl {
|
||||
PRIVATE_WITH_CACHE = 'private_with_cache',
|
||||
PRIVATE_WITHOUT_CACHE = 'private_without_cache',
|
||||
NONE = 'none',
|
||||
}
|
||||
|
||||
export enum PaginationMode {
|
||||
LIMIT_OFFSET = 'limit-offset',
|
||||
SKIP_TAKE = 'skip-take',
|
||||
PrivateWithCache = 'private_with_cache',
|
||||
PrivateWithoutCache = 'private_without_cache',
|
||||
None = 'none',
|
||||
}
|
||||
|
||||
export enum ImmichEnvironment {
|
||||
DEVELOPMENT = 'development',
|
||||
TESTING = 'testing',
|
||||
PRODUCTION = 'production',
|
||||
Development = 'development',
|
||||
Testing = 'testing',
|
||||
Production = 'production',
|
||||
}
|
||||
|
||||
export enum ImmichWorker {
|
||||
API = 'api',
|
||||
MICROSERVICES = 'microservices',
|
||||
Api = 'api',
|
||||
Microservices = 'microservices',
|
||||
}
|
||||
|
||||
export enum ImmichTelemetry {
|
||||
HOST = 'host',
|
||||
API = 'api',
|
||||
IO = 'io',
|
||||
REPO = 'repo',
|
||||
JOB = 'job',
|
||||
Host = 'host',
|
||||
Api = 'api',
|
||||
Io = 'io',
|
||||
Repo = 'repo',
|
||||
Job = 'job',
|
||||
}
|
||||
|
||||
export enum ExifOrientation {
|
||||
@@ -411,11 +407,11 @@ export enum ExifOrientation {
|
||||
}
|
||||
|
||||
export enum DatabaseExtension {
|
||||
CUBE = 'cube',
|
||||
EARTH_DISTANCE = 'earthdistance',
|
||||
VECTOR = 'vector',
|
||||
VECTORS = 'vectors',
|
||||
VECTORCHORD = 'vchord',
|
||||
Cube = 'cube',
|
||||
EarthDistance = 'earthdistance',
|
||||
Vector = 'vector',
|
||||
Vectors = 'vectors',
|
||||
VectorChord = 'vchord',
|
||||
}
|
||||
|
||||
export enum BootstrapEventPriority {
|
||||
@@ -428,135 +424,116 @@ export enum BootstrapEventPriority {
|
||||
}
|
||||
|
||||
export enum QueueName {
|
||||
THUMBNAIL_GENERATION = 'thumbnailGeneration',
|
||||
METADATA_EXTRACTION = 'metadataExtraction',
|
||||
VIDEO_CONVERSION = 'videoConversion',
|
||||
FACE_DETECTION = 'faceDetection',
|
||||
FACIAL_RECOGNITION = 'facialRecognition',
|
||||
SMART_SEARCH = 'smartSearch',
|
||||
DUPLICATE_DETECTION = 'duplicateDetection',
|
||||
BACKGROUND_TASK = 'backgroundTask',
|
||||
STORAGE_TEMPLATE_MIGRATION = 'storageTemplateMigration',
|
||||
MIGRATION = 'migration',
|
||||
SEARCH = 'search',
|
||||
SIDECAR = 'sidecar',
|
||||
LIBRARY = 'library',
|
||||
NOTIFICATION = 'notifications',
|
||||
BACKUP_DATABASE = 'backupDatabase',
|
||||
ThumbnailGeneration = 'thumbnailGeneration',
|
||||
MetadataExtraction = 'metadataExtraction',
|
||||
VideoConversion = 'videoConversion',
|
||||
FaceDetection = 'faceDetection',
|
||||
FacialRecognition = 'facialRecognition',
|
||||
SmartSearch = 'smartSearch',
|
||||
DuplicateDetection = 'duplicateDetection',
|
||||
BackgroundTask = 'backgroundTask',
|
||||
StorageTemplateMigration = 'storageTemplateMigration',
|
||||
Migration = 'migration',
|
||||
Search = 'search',
|
||||
Sidecar = 'sidecar',
|
||||
Library = 'library',
|
||||
Notification = 'notifications',
|
||||
BackupDatabase = 'backupDatabase',
|
||||
}
|
||||
|
||||
export enum JobName {
|
||||
//backups
|
||||
BACKUP_DATABASE = 'database-backup',
|
||||
AssetDelete = 'AssetDelete',
|
||||
AssetDeleteCheck = 'AssetDeleteCheck',
|
||||
AssetDetectFacesQueueAll = 'AssetDetectFacesQueueAll',
|
||||
AssetDetectFaces = 'AssetDetectFaces',
|
||||
AssetDetectDuplicatesQueueAll = 'AssetDetectDuplicatesQueueAll',
|
||||
AssetDetectDuplicates = 'AssetDetectDuplicates',
|
||||
AssetEncodeVideoQueueAll = 'AssetEncodeVideoQueueAll',
|
||||
AssetEncodeVideo = 'AssetEncodeVideo',
|
||||
AssetEmptyTrash = 'AssetEmptyTrash',
|
||||
AssetExtractMetadataQueueAll = 'AssetExtractMetadataQueueAll',
|
||||
AssetExtractMetadata = 'AssetExtractMetadata',
|
||||
AssetFileMigration = 'AssetFileMigration',
|
||||
AssetGenerateThumbnailsQueueAll = 'AssetGenerateThumbnailsQueueAll',
|
||||
AssetGenerateThumbnails = 'AssetGenerateThumbnails',
|
||||
|
||||
// conversion
|
||||
QUEUE_VIDEO_CONVERSION = 'queue-video-conversion',
|
||||
VIDEO_CONVERSION = 'video-conversion',
|
||||
AuditLogCleanup = 'AuditLogCleanup',
|
||||
|
||||
// thumbnails
|
||||
QUEUE_GENERATE_THUMBNAILS = 'queue-generate-thumbnails',
|
||||
GENERATE_THUMBNAILS = 'generate-thumbnails',
|
||||
GENERATE_PERSON_THUMBNAIL = 'generate-person-thumbnail',
|
||||
DatabaseBackup = 'DatabaseBackup',
|
||||
|
||||
// metadata
|
||||
QUEUE_METADATA_EXTRACTION = 'queue-metadata-extraction',
|
||||
METADATA_EXTRACTION = 'metadata-extraction',
|
||||
FacialRecognitionQueueAll = 'FacialRecognitionQueueAll',
|
||||
FacialRecognition = 'FacialRecognition',
|
||||
|
||||
// user
|
||||
USER_DELETION = 'user-deletion',
|
||||
USER_DELETE_CHECK = 'user-delete-check',
|
||||
USER_SYNC_USAGE = 'user-sync-usage',
|
||||
FileDelete = 'FileDelete',
|
||||
FileMigrationQueueAll = 'FileMigrationQueueAll',
|
||||
|
||||
// asset
|
||||
ASSET_DELETION = 'asset-deletion',
|
||||
ASSET_DELETION_CHECK = 'asset-deletion-check',
|
||||
LibraryDeleteCheck = 'LibraryDeleteCheck',
|
||||
LibraryDelete = 'LibraryDelete',
|
||||
LibraryRemoveAsset = 'LibraryRemoveAsset',
|
||||
LibrarySyncAssetsQueueAll = 'LibraryScanAssetsQueueAll',
|
||||
LibrarySyncAssets = 'LibrarySyncAssets',
|
||||
LibrarySyncFilesQueueAll = 'LibrarySyncFilesQueueAll',
|
||||
LibrarySyncFiles = 'LibrarySyncFiles',
|
||||
LibraryScanQueueAll = 'LibraryScanQueueAll',
|
||||
|
||||
// storage template
|
||||
STORAGE_TEMPLATE_MIGRATION = 'storage-template-migration',
|
||||
STORAGE_TEMPLATE_MIGRATION_SINGLE = 'storage-template-migration-single',
|
||||
MemoryCleanup = 'MemoryCleanup',
|
||||
MemoryGenerate = 'MemoryGenerate',
|
||||
|
||||
// tags
|
||||
TAG_CLEANUP = 'tag-cleanup',
|
||||
NotificationsCleanup = 'NotificationsCleanup',
|
||||
|
||||
// migration
|
||||
QUEUE_MIGRATION = 'queue-migration',
|
||||
MIGRATE_ASSET = 'migrate-asset',
|
||||
MIGRATE_PERSON = 'migrate-person',
|
||||
NotifyUserSignup = 'NotifyUserSignup',
|
||||
NotifyAlbumInvite = 'NotifyAlbumInvite',
|
||||
NotifyAlbumUpdate = 'NotifyAlbumUpdate',
|
||||
|
||||
// facial recognition
|
||||
PERSON_CLEANUP = 'person-cleanup',
|
||||
QUEUE_FACE_DETECTION = 'queue-face-detection',
|
||||
FACE_DETECTION = 'face-detection',
|
||||
QUEUE_FACIAL_RECOGNITION = 'queue-facial-recognition',
|
||||
FACIAL_RECOGNITION = 'facial-recognition',
|
||||
UserDelete = 'UserDelete',
|
||||
UserDeleteCheck = 'UserDeleteCheck',
|
||||
UserSyncUsage = 'UserSyncUsage',
|
||||
|
||||
// library management
|
||||
LIBRARY_QUEUE_SYNC_FILES = 'library-queue-sync-files',
|
||||
LIBRARY_QUEUE_SYNC_ASSETS = 'library-queue-sync-assets',
|
||||
LIBRARY_SYNC_FILES = 'library-sync-files',
|
||||
LIBRARY_SYNC_ASSETS = 'library-sync-assets',
|
||||
LIBRARY_ASSET_REMOVAL = 'handle-library-file-deletion',
|
||||
LIBRARY_DELETE = 'library-delete',
|
||||
LIBRARY_QUEUE_SCAN_ALL = 'library-queue-scan-all',
|
||||
LIBRARY_QUEUE_CLEANUP = 'library-queue-cleanup',
|
||||
PersonCleanup = 'PersonCleanup',
|
||||
PersonFileMigration = 'PersonFileMigration',
|
||||
PersonGenerateThumbnail = 'PersonGenerateThumbnail',
|
||||
|
||||
// cleanup
|
||||
DELETE_FILES = 'delete-files',
|
||||
CLEAN_OLD_AUDIT_LOGS = 'clean-old-audit-logs',
|
||||
CLEAN_OLD_SESSION_TOKENS = 'clean-old-session-tokens',
|
||||
SessionCleanup = 'SessionCleanup',
|
||||
|
||||
// memories
|
||||
MEMORIES_CLEANUP = 'memories-cleanup',
|
||||
MEMORIES_CREATE = 'memories-create',
|
||||
SendMail = 'SendMail',
|
||||
|
||||
// smart search
|
||||
QUEUE_SMART_SEARCH = 'queue-smart-search',
|
||||
SMART_SEARCH = 'smart-search',
|
||||
SidecarQueueAll = 'SidecarQueueAll',
|
||||
SidecarDiscovery = 'SidecarDiscovery',
|
||||
SidecarSync = 'SidecarSync',
|
||||
SidecarWrite = 'SidecarWrite',
|
||||
|
||||
QUEUE_TRASH_EMPTY = 'queue-trash-empty',
|
||||
SmartSearchQueueAll = 'SmartSearchQueueAll',
|
||||
SmartSearch = 'SmartSearch',
|
||||
|
||||
// duplicate detection
|
||||
QUEUE_DUPLICATE_DETECTION = 'queue-duplicate-detection',
|
||||
DUPLICATE_DETECTION = 'duplicate-detection',
|
||||
StorageTemplateMigration = 'StorageTemplateMigration',
|
||||
StorageTemplateMigrationSingle = 'StorageTemplateMigrationSingle',
|
||||
|
||||
// XMP sidecars
|
||||
QUEUE_SIDECAR = 'queue-sidecar',
|
||||
SIDECAR_DISCOVERY = 'sidecar-discovery',
|
||||
SIDECAR_SYNC = 'sidecar-sync',
|
||||
SIDECAR_WRITE = 'sidecar-write',
|
||||
TagCleanup = 'TagCleanup',
|
||||
|
||||
// Notification
|
||||
NOTIFY_SIGNUP = 'notify-signup',
|
||||
NOTIFY_ALBUM_INVITE = 'notify-album-invite',
|
||||
NOTIFY_ALBUM_UPDATE = 'notify-album-update',
|
||||
NOTIFICATIONS_CLEANUP = 'notifications-cleanup',
|
||||
SEND_EMAIL = 'notification-send-email',
|
||||
|
||||
// Version check
|
||||
VERSION_CHECK = 'version-check',
|
||||
VersionCheck = 'VersionCheck',
|
||||
}
|
||||
|
||||
export enum JobCommand {
|
||||
START = 'start',
|
||||
PAUSE = 'pause',
|
||||
RESUME = 'resume',
|
||||
EMPTY = 'empty',
|
||||
CLEAR_FAILED = 'clear-failed',
|
||||
Start = 'start',
|
||||
Pause = 'pause',
|
||||
Resume = 'resume',
|
||||
Empty = 'empty',
|
||||
ClearFailed = 'clear-failed',
|
||||
}
|
||||
|
||||
export enum JobStatus {
|
||||
SUCCESS = 'success',
|
||||
FAILED = 'failed',
|
||||
SKIPPED = 'skipped',
|
||||
Success = 'success',
|
||||
Failed = 'failed',
|
||||
Skipped = 'skipped',
|
||||
}
|
||||
|
||||
export enum QueueCleanType {
|
||||
FAILED = 'failed',
|
||||
Failed = 'failed',
|
||||
}
|
||||
|
||||
export enum VectorIndex {
|
||||
CLIP = 'clip_index',
|
||||
FACE = 'face_index',
|
||||
Clip = 'clip_index',
|
||||
Face = 'face_index',
|
||||
}
|
||||
|
||||
export enum DatabaseLock {
|
||||
@@ -568,6 +545,7 @@ export enum DatabaseLock {
|
||||
CLIPDimSize = 512,
|
||||
Library = 1337,
|
||||
NightlyJobs = 600,
|
||||
MediaLocation = 700,
|
||||
GetSystemConfig = 69,
|
||||
BackupDatabase = 42,
|
||||
MemoryCreation = 777,
|
||||
@@ -590,6 +568,7 @@ export enum SyncRequestType {
|
||||
StacksV1 = 'StacksV1',
|
||||
UsersV1 = 'UsersV1',
|
||||
PeopleV1 = 'PeopleV1',
|
||||
AssetFacesV1 = 'AssetFacesV1',
|
||||
UserMetadataV1 = 'UserMetadataV1',
|
||||
}
|
||||
|
||||
@@ -641,6 +620,9 @@ export enum SyncEntityType {
|
||||
PersonV1 = 'PersonV1',
|
||||
PersonDeleteV1 = 'PersonDeleteV1',
|
||||
|
||||
AssetFaceV1 = 'AssetFaceV1',
|
||||
AssetFaceDeleteV1 = 'AssetFaceDeleteV1',
|
||||
|
||||
UserMetadataV1 = 'UserMetadataV1',
|
||||
UserMetadataDeleteV1 = 'UserMetadataDeleteV1',
|
||||
|
||||
@@ -663,8 +645,8 @@ export enum NotificationType {
|
||||
}
|
||||
|
||||
export enum OAuthTokenEndpointAuthMethod {
|
||||
CLIENT_SECRET_POST = 'client_secret_post',
|
||||
CLIENT_SECRET_BASIC = 'client_secret_basic',
|
||||
ClientSecretPost = 'client_secret_post',
|
||||
ClientSecretBasic = 'client_secret_basic',
|
||||
}
|
||||
|
||||
export enum DatabaseSslMode {
|
||||
@@ -676,14 +658,14 @@ export enum DatabaseSslMode {
|
||||
}
|
||||
|
||||
export enum AssetVisibility {
|
||||
ARCHIVE = 'archive',
|
||||
TIMELINE = 'timeline',
|
||||
Archive = 'archive',
|
||||
Timeline = 'timeline',
|
||||
|
||||
/**
|
||||
* Video part of the LivePhotos and MotionPhotos
|
||||
*/
|
||||
HIDDEN = 'hidden',
|
||||
LOCKED = 'locked',
|
||||
Hidden = 'hidden',
|
||||
Locked = 'locked',
|
||||
}
|
||||
|
||||
export enum CronJob {
|
||||
|
||||
+10
-5
@@ -1,5 +1,6 @@
|
||||
import { CommandFactory } from 'nest-commander';
|
||||
import { ChildProcess, fork } from 'node:child_process';
|
||||
import { dirname, join } from 'node:path';
|
||||
import { Worker } from 'node:worker_threads';
|
||||
import { ImmichAdminModule } from 'src/app.module';
|
||||
import { ImmichWorker, LogLevel } from 'src/enum';
|
||||
@@ -20,7 +21,7 @@ const onExit = (name: string, exitCode: number | null) => {
|
||||
if (exitCode !== 0) {
|
||||
console.error(`${name} worker exited with code ${exitCode}`);
|
||||
|
||||
if (apiProcess && name !== ImmichWorker.API) {
|
||||
if (apiProcess && name !== ImmichWorker.Api) {
|
||||
console.error('Killing api process');
|
||||
apiProcess.kill('SIGTERM');
|
||||
apiProcess = undefined;
|
||||
@@ -33,14 +34,18 @@ const onExit = (name: string, exitCode: number | null) => {
|
||||
function bootstrapWorker(name: ImmichWorker) {
|
||||
console.log(`Starting ${name} worker`);
|
||||
|
||||
// eslint-disable-next-line unicorn/prefer-module
|
||||
const basePath = dirname(__filename);
|
||||
const workerFile = join(basePath, 'workers', `${name}.js`);
|
||||
|
||||
let worker: Worker | ChildProcess;
|
||||
if (name === ImmichWorker.API) {
|
||||
worker = fork(`./dist/workers/${name}.js`, [], {
|
||||
if (name === ImmichWorker.Api) {
|
||||
worker = fork(workerFile, [], {
|
||||
execArgv: process.execArgv.map((arg) => (arg.startsWith('--inspect') ? '--inspect=0.0.0.0:9231' : arg)),
|
||||
});
|
||||
apiProcess = worker;
|
||||
} else {
|
||||
worker = new Worker(`./dist/workers/${name}.js`);
|
||||
worker = new Worker(workerFile);
|
||||
}
|
||||
|
||||
worker.on('error', (error) => onError(name, error));
|
||||
@@ -50,7 +55,7 @@ function bootstrapWorker(name: ImmichWorker) {
|
||||
function bootstrap() {
|
||||
if (immichApp === 'immich-admin') {
|
||||
process.title = 'immich_admin_cli';
|
||||
process.env.IMMICH_LOG_LEVEL = LogLevel.WARN;
|
||||
process.env.IMMICH_LOG_LEVEL = LogLevel.Warn;
|
||||
return CommandFactory.run(ImmichAdminModule);
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ export class AssetUploadInterceptor implements NestInterceptor {
|
||||
const req = context.switchToHttp().getRequest<AuthenticatedRequest>();
|
||||
const res = context.switchToHttp().getResponse<Response<AssetMediaResponseDto>>();
|
||||
|
||||
const checksum = fromMaybeArray(req.headers[ImmichHeader.CHECKSUM]);
|
||||
const checksum = fromMaybeArray(req.headers[ImmichHeader.Checksum]);
|
||||
const response = await this.service.getUploadAssetIdByChecksum(req.user, checksum);
|
||||
if (response) {
|
||||
res.status(200);
|
||||
|
||||
@@ -23,12 +23,12 @@ export const Authenticated = (options?: AuthenticatedOptions): MethodDecorator =
|
||||
const decorators: MethodDecorator[] = [
|
||||
ApiBearerAuth(),
|
||||
ApiCookieAuth(),
|
||||
ApiSecurity(MetadataKey.API_KEY_SECURITY),
|
||||
SetMetadata(MetadataKey.AUTH_ROUTE, options || {}),
|
||||
ApiSecurity(MetadataKey.ApiKeySecurity),
|
||||
SetMetadata(MetadataKey.AuthRoute, options || {}),
|
||||
];
|
||||
|
||||
if ((options as SharedLinkRoute)?.sharedLink) {
|
||||
decorators.push(ApiQuery({ name: ImmichQuery.SHARED_LINK_KEY, type: String, required: false }));
|
||||
decorators.push(ApiQuery({ name: ImmichQuery.SharedLinkKey, type: String, required: false }));
|
||||
}
|
||||
|
||||
return applyDecorators(...decorators);
|
||||
@@ -76,7 +76,7 @@ export class AuthGuard implements CanActivate {
|
||||
async canActivate(context: ExecutionContext): Promise<boolean> {
|
||||
const targets = [context.getHandler()];
|
||||
|
||||
const options = this.reflector.getAllAndOverride<AuthenticatedOptions | undefined>(MetadataKey.AUTH_ROUTE, targets);
|
||||
const options = this.reflector.getAllAndOverride<AuthenticatedOptions | undefined>(MetadataKey.AuthRoute, targets);
|
||||
if (!options) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -154,11 +154,11 @@ export class FileUploadInterceptor implements NestInterceptor {
|
||||
|
||||
private getHandler(route: RouteKey) {
|
||||
switch (route) {
|
||||
case RouteKey.ASSET: {
|
||||
case RouteKey.Asset: {
|
||||
return this.handlers.assetUpload;
|
||||
}
|
||||
|
||||
case RouteKey.USER: {
|
||||
case RouteKey.User: {
|
||||
return this.handlers.userProfile;
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
export class AddFaceSearchRelation1718486162779 implements MigrationInterface {
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
const vectorExtension = await getVectorExtension(queryRunner);
|
||||
if (vectorExtension === DatabaseExtension.VECTORS) {
|
||||
if (vectorExtension === DatabaseExtension.Vectors) {
|
||||
await queryRunner.query(`SET search_path TO "$user", public, vectors`);
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@ export class AddFaceSearchRelation1718486162779 implements MigrationInterface {
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
const vectorExtension = await getVectorExtension(queryRunner);
|
||||
if (vectorExtension === DatabaseExtension.VECTORS) {
|
||||
if (vectorExtension === DatabaseExtension.Vectors) {
|
||||
await queryRunner.query(`SET search_path TO "$user", public, vectors`);
|
||||
}
|
||||
|
||||
|
||||
@@ -168,6 +168,30 @@ from
|
||||
where
|
||||
"livePhotoVideoId" = $1::uuid
|
||||
|
||||
-- AssetRepository.getFileSamples
|
||||
select
|
||||
"asset"."id",
|
||||
"asset"."originalPath",
|
||||
"asset"."sidecarPath",
|
||||
"asset"."encodedVideoPath",
|
||||
(
|
||||
select
|
||||
coalesce(json_agg(agg), '[]')
|
||||
from
|
||||
(
|
||||
select
|
||||
"path"
|
||||
from
|
||||
"asset_file"
|
||||
where
|
||||
"asset"."id" = "asset_file"."assetId"
|
||||
) as agg
|
||||
) as "files"
|
||||
from
|
||||
"asset"
|
||||
limit
|
||||
3
|
||||
|
||||
-- AssetRepository.getById
|
||||
select
|
||||
"asset".*
|
||||
|
||||
@@ -12,6 +12,17 @@ delete from "person"
|
||||
where
|
||||
"person"."id" in ($1)
|
||||
|
||||
-- PersonRepository.getFileSamples
|
||||
select
|
||||
"id",
|
||||
"thumbnailPath"
|
||||
from
|
||||
"person"
|
||||
where
|
||||
"thumbnailPath" != ''
|
||||
limit
|
||||
3
|
||||
|
||||
-- PersonRepository.getAllForUser
|
||||
select
|
||||
"person".*
|
||||
|
||||
@@ -409,6 +409,41 @@ where
|
||||
order by
|
||||
"updateId" asc
|
||||
|
||||
-- SyncRepository.assetFace.getDeletes
|
||||
select
|
||||
"asset_face_audit"."id",
|
||||
"assetFaceId"
|
||||
from
|
||||
"asset_face_audit"
|
||||
left join "asset" on "asset"."id" = "asset_face_audit"."assetId"
|
||||
where
|
||||
"asset"."ownerId" = $1
|
||||
and "asset_face_audit"."deletedAt" < now() - interval '1 millisecond'
|
||||
order by
|
||||
"asset_face_audit"."id" asc
|
||||
|
||||
-- SyncRepository.assetFace.getUpserts
|
||||
select
|
||||
"asset_face"."id",
|
||||
"assetId",
|
||||
"personId",
|
||||
"imageWidth",
|
||||
"imageHeight",
|
||||
"boundingBoxX1",
|
||||
"boundingBoxY1",
|
||||
"boundingBoxX2",
|
||||
"boundingBoxY2",
|
||||
"sourceType",
|
||||
"asset_face"."updateId"
|
||||
from
|
||||
"asset_face"
|
||||
left join "asset" on "asset"."id" = "asset_face"."assetId"
|
||||
where
|
||||
"asset_face"."updatedAt" < now() - interval '1 millisecond'
|
||||
and "asset"."ownerId" = $1
|
||||
order by
|
||||
"asset_face"."updateId" asc
|
||||
|
||||
-- SyncRepository.memory.getDeletes
|
||||
select
|
||||
"id",
|
||||
@@ -779,7 +814,6 @@ select
|
||||
"ownerId",
|
||||
"name",
|
||||
"birthDate",
|
||||
"thumbnailPath",
|
||||
"isHidden",
|
||||
"isFavorite",
|
||||
"color",
|
||||
|
||||
@@ -78,6 +78,17 @@ where
|
||||
"user"."isAdmin" = $1
|
||||
and "user"."deletedAt" is null
|
||||
|
||||
-- UserRepository.getFileSamples
|
||||
select
|
||||
"id",
|
||||
"profileImagePath"
|
||||
from
|
||||
"user"
|
||||
where
|
||||
"profileImagePath" != ''
|
||||
limit
|
||||
3
|
||||
|
||||
-- UserRepository.hasAdmin
|
||||
select
|
||||
"user"."id"
|
||||
|
||||
@@ -91,7 +91,7 @@ class AlbumAccess {
|
||||
}
|
||||
|
||||
const accessRole =
|
||||
access === AlbumUserRole.EDITOR ? [AlbumUserRole.EDITOR] : [AlbumUserRole.EDITOR, AlbumUserRole.VIEWER];
|
||||
access === AlbumUserRole.Editor ? [AlbumUserRole.Editor] : [AlbumUserRole.Editor, AlbumUserRole.Viewer];
|
||||
|
||||
return this.db
|
||||
.selectFrom('album')
|
||||
@@ -178,7 +178,7 @@ class AssetAccess {
|
||||
.select('asset.id')
|
||||
.where('asset.id', 'in', [...assetIds])
|
||||
.where('asset.ownerId', '=', userId)
|
||||
.$if(!hasElevatedPermission, (eb) => eb.where('asset.visibility', '!=', AssetVisibility.LOCKED))
|
||||
.$if(!hasElevatedPermission, (eb) => eb.where('asset.visibility', '!=', AssetVisibility.Locked))
|
||||
.execute()
|
||||
.then((assets) => new Set(assets.map((asset) => asset.id)));
|
||||
}
|
||||
@@ -200,8 +200,8 @@ class AssetAccess {
|
||||
.where('partner.sharedWithId', '=', userId)
|
||||
.where((eb) =>
|
||||
eb.or([
|
||||
eb('asset.visibility', '=', sql.lit(AssetVisibility.TIMELINE)),
|
||||
eb('asset.visibility', '=', sql.lit(AssetVisibility.HIDDEN)),
|
||||
eb('asset.visibility', '=', sql.lit(AssetVisibility.Timeline)),
|
||||
eb('asset.visibility', '=', sql.lit(AssetVisibility.Hidden)),
|
||||
]),
|
||||
)
|
||||
|
||||
|
||||
@@ -90,7 +90,7 @@ export class ActivityRepository {
|
||||
.where('activity.albumId', '=', albumId)
|
||||
.where(({ or, and, eb }) =>
|
||||
or([
|
||||
and([eb('asset.deletedAt', 'is', null), eb('asset.visibility', '!=', sql.lit(AssetVisibility.LOCKED))]),
|
||||
and([eb('asset.deletedAt', 'is', null), eb('asset.visibility', '!=', sql.lit(AssetVisibility.Locked))]),
|
||||
eb('asset.id', 'is', null),
|
||||
]),
|
||||
)
|
||||
|
||||
@@ -24,7 +24,7 @@ export class AlbumUserRepository {
|
||||
.executeTakeFirstOrThrow();
|
||||
}
|
||||
|
||||
@GenerateSql({ params: [{ usersId: DummyValue.UUID, albumsId: DummyValue.UUID }, { role: AlbumUserRole.VIEWER }] })
|
||||
@GenerateSql({ params: [{ usersId: DummyValue.UUID, albumsId: DummyValue.UUID }, { role: AlbumUserRole.Viewer }] })
|
||||
update({ usersId, albumsId }: AlbumPermissionId, dto: Updateable<AlbumUserTable>) {
|
||||
return this.db
|
||||
.updateTable('album_user')
|
||||
|
||||
@@ -62,7 +62,7 @@ export class AssetJobRepository {
|
||||
.select(['asset.id', 'asset.thumbhash'])
|
||||
.select(withFiles)
|
||||
.where('asset.deletedAt', 'is', null)
|
||||
.where('asset.visibility', '!=', AssetVisibility.HIDDEN)
|
||||
.where('asset.visibility', '!=', AssetVisibility.Hidden)
|
||||
.$if(!force, (qb) =>
|
||||
qb
|
||||
// If there aren't any entries, metadata extraction hasn't run yet which is required for thumbnails
|
||||
@@ -117,7 +117,7 @@ export class AssetJobRepository {
|
||||
.executeTakeFirst();
|
||||
}
|
||||
|
||||
@GenerateSql({ params: [DummyValue.UUID, AssetFileType.THUMBNAIL] })
|
||||
@GenerateSql({ params: [DummyValue.UUID, AssetFileType.Thumbnail] })
|
||||
getAlbumThumbnailFiles(id: string, fileType?: AssetFileType) {
|
||||
return this.db
|
||||
.selectFrom('asset_file')
|
||||
@@ -130,7 +130,7 @@ export class AssetJobRepository {
|
||||
private assetsWithPreviews() {
|
||||
return this.db
|
||||
.selectFrom('asset')
|
||||
.where('asset.visibility', '!=', AssetVisibility.HIDDEN)
|
||||
.where('asset.visibility', '!=', AssetVisibility.Hidden)
|
||||
.where('asset.deletedAt', 'is', null)
|
||||
.innerJoin('asset_job_status as job_status', 'assetId', 'asset.id')
|
||||
.where('job_status.previewAt', 'is not', null);
|
||||
@@ -167,7 +167,7 @@ export class AssetJobRepository {
|
||||
return this.db
|
||||
.selectFrom('asset')
|
||||
.select(['asset.id', 'asset.visibility'])
|
||||
.select((eb) => withFiles(eb, AssetFileType.PREVIEW))
|
||||
.select((eb) => withFiles(eb, AssetFileType.Preview))
|
||||
.where('asset.id', '=', id)
|
||||
.executeTakeFirst();
|
||||
}
|
||||
@@ -179,7 +179,7 @@ export class AssetJobRepository {
|
||||
.select(['asset.id', 'asset.visibility'])
|
||||
.$call(withExifInner)
|
||||
.select((eb) => withFaces(eb, true))
|
||||
.select((eb) => withFiles(eb, AssetFileType.PREVIEW))
|
||||
.select((eb) => withFiles(eb, AssetFileType.Preview))
|
||||
.where('asset.id', '=', id)
|
||||
.executeTakeFirst();
|
||||
}
|
||||
@@ -225,7 +225,7 @@ export class AssetJobRepository {
|
||||
.select(['stack.id', 'stack.primaryAssetId'])
|
||||
.select((eb) => eb.fn<Asset[]>('array_agg', [eb.table('stacked')]).as('assets'))
|
||||
.where('stacked.deletedAt', 'is not', null)
|
||||
.where('stacked.visibility', '=', AssetVisibility.TIMELINE)
|
||||
.where('stacked.visibility', '=', AssetVisibility.Timeline)
|
||||
.whereRef('stacked.stackId', '=', 'stack.id')
|
||||
.groupBy('stack.id')
|
||||
.as('stacked_assets'),
|
||||
@@ -241,11 +241,11 @@ export class AssetJobRepository {
|
||||
return this.db
|
||||
.selectFrom('asset')
|
||||
.select(['asset.id'])
|
||||
.where('asset.type', '=', AssetType.VIDEO)
|
||||
.where('asset.type', '=', AssetType.Video)
|
||||
.$if(!force, (qb) =>
|
||||
qb
|
||||
.where((eb) => eb.or([eb('asset.encodedVideoPath', 'is', null), eb('asset.encodedVideoPath', '=', '')]))
|
||||
.where('asset.visibility', '!=', AssetVisibility.HIDDEN),
|
||||
.where('asset.visibility', '!=', AssetVisibility.Hidden),
|
||||
)
|
||||
.where('asset.deletedAt', 'is', null)
|
||||
.stream();
|
||||
@@ -257,7 +257,7 @@ export class AssetJobRepository {
|
||||
.selectFrom('asset')
|
||||
.select(['asset.id', 'asset.ownerId', 'asset.originalPath', 'asset.encodedVideoPath'])
|
||||
.where('asset.id', '=', id)
|
||||
.where('asset.type', '=', AssetType.VIDEO)
|
||||
.where('asset.type', '=', AssetType.Video)
|
||||
.executeTakeFirst();
|
||||
}
|
||||
|
||||
@@ -327,7 +327,7 @@ export class AssetJobRepository {
|
||||
.$if(!force, (qb) =>
|
||||
qb.where((eb) => eb.or([eb('asset.sidecarPath', '=', ''), eb('asset.sidecarPath', 'is', null)])),
|
||||
)
|
||||
.where('asset.visibility', '!=', AssetVisibility.HIDDEN)
|
||||
.where('asset.visibility', '!=', AssetVisibility.Hidden)
|
||||
.stream();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { Insertable, Kysely, NotNull, Selectable, UpdateResult, Updateable, sql } from 'kysely';
|
||||
import { jsonArrayFrom } from 'kysely/helpers/postgres';
|
||||
import { isEmpty, isUndefined, omitBy } from 'lodash';
|
||||
import { InjectKysely } from 'nestjs-kysely';
|
||||
import { Stack } from 'src/database';
|
||||
@@ -230,13 +231,13 @@ export class AssetRepository {
|
||||
.where('asset_job_status.previewAt', 'is not', null)
|
||||
.where(sql`(asset."localDateTime" at time zone 'UTC')::date`, '=', sql`today.date`)
|
||||
.where('asset.ownerId', '=', anyUuid(ownerIds))
|
||||
.where('asset.visibility', '=', AssetVisibility.TIMELINE)
|
||||
.where('asset.visibility', '=', AssetVisibility.Timeline)
|
||||
.where((eb) =>
|
||||
eb.exists((qb) =>
|
||||
qb
|
||||
.selectFrom('asset_file')
|
||||
.whereRef('assetId', '=', 'asset.id')
|
||||
.where('asset_file.type', '=', AssetFileType.PREVIEW),
|
||||
.where('asset_file.type', '=', AssetFileType.Preview),
|
||||
),
|
||||
)
|
||||
.where('asset.deletedAt', 'is', null)
|
||||
@@ -318,7 +319,7 @@ export class AssetRepository {
|
||||
.select(['deviceAssetId'])
|
||||
.where('ownerId', '=', asUuid(ownerId))
|
||||
.where('deviceId', '=', deviceId)
|
||||
.where('visibility', '!=', AssetVisibility.HIDDEN)
|
||||
.where('visibility', '!=', AssetVisibility.Hidden)
|
||||
.where('deletedAt', 'is', null)
|
||||
.execute();
|
||||
|
||||
@@ -335,6 +336,23 @@ export class AssetRepository {
|
||||
return count;
|
||||
}
|
||||
|
||||
@GenerateSql()
|
||||
getFileSamples() {
|
||||
return this.db
|
||||
.selectFrom('asset')
|
||||
.select((eb) => [
|
||||
'asset.id',
|
||||
'asset.originalPath',
|
||||
'asset.sidecarPath',
|
||||
'asset.encodedVideoPath',
|
||||
jsonArrayFrom(eb.selectFrom('asset_file').select('path').whereRef('asset.id', '=', 'asset_file.assetId')).as(
|
||||
'files',
|
||||
),
|
||||
])
|
||||
.limit(sql.lit(3))
|
||||
.execute();
|
||||
}
|
||||
|
||||
@GenerateSql({ params: [DummyValue.UUID] })
|
||||
getById(id: string, { exifInfo, faces, files, library, owner, smartSearch, stack, tags }: GetByIdsRelations = {}) {
|
||||
return this.db
|
||||
@@ -363,7 +381,7 @@ export class AssetRepository {
|
||||
.whereRef('stacked.stackId', '=', 'stack.id')
|
||||
.whereRef('stacked.id', '!=', 'stack.primaryAssetId')
|
||||
.where('stacked.deletedAt', 'is', null)
|
||||
.where('stacked.visibility', '=', AssetVisibility.TIMELINE)
|
||||
.where('stacked.visibility', '=', AssetVisibility.Timeline)
|
||||
.groupBy('stack.id')
|
||||
.as('stacked_assets'),
|
||||
(join) => join.on('stack.id', 'is not', null),
|
||||
@@ -463,15 +481,15 @@ export class AssetRepository {
|
||||
getStatistics(ownerId: string, { visibility, isFavorite, isTrashed }: AssetStatsOptions): Promise<AssetStats> {
|
||||
return this.db
|
||||
.selectFrom('asset')
|
||||
.select((eb) => eb.fn.countAll<number>().filterWhere('type', '=', AssetType.AUDIO).as(AssetType.AUDIO))
|
||||
.select((eb) => eb.fn.countAll<number>().filterWhere('type', '=', AssetType.IMAGE).as(AssetType.IMAGE))
|
||||
.select((eb) => eb.fn.countAll<number>().filterWhere('type', '=', AssetType.VIDEO).as(AssetType.VIDEO))
|
||||
.select((eb) => eb.fn.countAll<number>().filterWhere('type', '=', AssetType.OTHER).as(AssetType.OTHER))
|
||||
.select((eb) => eb.fn.countAll<number>().filterWhere('type', '=', AssetType.Audio).as(AssetType.Audio))
|
||||
.select((eb) => eb.fn.countAll<number>().filterWhere('type', '=', AssetType.Image).as(AssetType.Image))
|
||||
.select((eb) => eb.fn.countAll<number>().filterWhere('type', '=', AssetType.Video).as(AssetType.Video))
|
||||
.select((eb) => eb.fn.countAll<number>().filterWhere('type', '=', AssetType.Other).as(AssetType.Other))
|
||||
.where('ownerId', '=', asUuid(ownerId))
|
||||
.$if(visibility === undefined, withDefaultVisibility)
|
||||
.$if(!!visibility, (qb) => qb.where('asset.visibility', '=', visibility!))
|
||||
.$if(isFavorite !== undefined, (qb) => qb.where('isFavorite', '=', isFavorite!))
|
||||
.$if(!!isTrashed, (qb) => qb.where('asset.status', '!=', AssetStatus.DELETED))
|
||||
.$if(!!isTrashed, (qb) => qb.where('asset.status', '!=', AssetStatus.Deleted))
|
||||
.where('deletedAt', isTrashed ? 'is not' : 'is', null)
|
||||
.executeTakeFirstOrThrow();
|
||||
}
|
||||
@@ -496,7 +514,7 @@ export class AssetRepository {
|
||||
qb
|
||||
.selectFrom('asset')
|
||||
.select(truncatedDate<Date>().as('timeBucket'))
|
||||
.$if(!!options.isTrashed, (qb) => qb.where('asset.status', '!=', AssetStatus.DELETED))
|
||||
.$if(!!options.isTrashed, (qb) => qb.where('asset.status', '!=', AssetStatus.Deleted))
|
||||
.where('asset.deletedAt', options.isTrashed ? 'is not' : 'is', null)
|
||||
.$if(options.visibility === undefined, withDefaultVisibility)
|
||||
.$if(!!options.visibility, (qb) => qb.where('asset.visibility', '=', options.visibility!))
|
||||
@@ -606,7 +624,7 @@ export class AssetRepository {
|
||||
.select(sql`array[stacked."stackId"::text, count('stacked')::text]`.as('stack'))
|
||||
.whereRef('stacked.stackId', '=', 'asset.stackId')
|
||||
.where('stacked.deletedAt', 'is', null)
|
||||
.where('stacked.visibility', '=', AssetVisibility.TIMELINE)
|
||||
.where('stacked.visibility', '=', AssetVisibility.Timeline)
|
||||
.groupBy('stacked.stackId')
|
||||
.as('stacked_assets'),
|
||||
(join) => join.onTrue(),
|
||||
@@ -617,7 +635,7 @@ export class AssetRepository {
|
||||
.$if(options.isDuplicate !== undefined, (qb) =>
|
||||
qb.where('asset.duplicateId', options.isDuplicate ? 'is not' : 'is', null),
|
||||
)
|
||||
.$if(!!options.isTrashed, (qb) => qb.where('asset.status', '!=', AssetStatus.DELETED))
|
||||
.$if(!!options.isTrashed, (qb) => qb.where('asset.status', '!=', AssetStatus.Deleted))
|
||||
.$if(!!options.tagId, (qb) => withTagId(qb, options.tagId!))
|
||||
.orderBy('asset.fileCreatedAt', options.order ?? 'desc'),
|
||||
)
|
||||
@@ -671,8 +689,8 @@ export class AssetRepository {
|
||||
.select(['assetId as data', 'asset_exif.city as value'])
|
||||
.$narrowType<{ value: NotNull }>()
|
||||
.where('ownerId', '=', asUuid(ownerId))
|
||||
.where('visibility', '=', AssetVisibility.TIMELINE)
|
||||
.where('type', '=', AssetType.IMAGE)
|
||||
.where('visibility', '=', AssetVisibility.Timeline)
|
||||
.where('type', '=', AssetType.Image)
|
||||
.where('deletedAt', 'is', null)
|
||||
.limit(maxFields)
|
||||
.execute();
|
||||
@@ -710,7 +728,7 @@ export class AssetRepository {
|
||||
)
|
||||
.select((eb) => eb.fn.toJson(eb.table('stacked_assets')).$castTo<Stack | null>().as('stack'))
|
||||
.where('asset.ownerId', '=', asUuid(ownerId))
|
||||
.where('asset.visibility', '!=', AssetVisibility.HIDDEN)
|
||||
.where('asset.visibility', '!=', AssetVisibility.Hidden)
|
||||
.where('asset.updatedAt', '<=', updatedUntil)
|
||||
.$if(!!lastId, (qb) => qb.where('asset.id', '>', lastId!))
|
||||
.orderBy('asset.id')
|
||||
@@ -738,7 +756,7 @@ export class AssetRepository {
|
||||
)
|
||||
.select((eb) => eb.fn.toJson(eb.table('stacked_assets').$castTo<Stack | null>()).as('stack'))
|
||||
.where('asset.ownerId', '=', anyUuid(options.userIds))
|
||||
.where('asset.visibility', '!=', AssetVisibility.HIDDEN)
|
||||
.where('asset.visibility', '!=', AssetVisibility.Hidden)
|
||||
.where('asset.updatedAt', '>', options.updatedAfter)
|
||||
.limit(options.limit)
|
||||
.execute();
|
||||
|
||||
@@ -18,7 +18,7 @@ export class AuditRepository {
|
||||
@GenerateSql({
|
||||
params: [
|
||||
DummyValue.DATE,
|
||||
{ action: DatabaseAction.CREATE, entityType: EntityType.ASSET, userIds: [DummyValue.UUID] },
|
||||
{ action: DatabaseAction.Create, entityType: EntityType.Asset, userIds: [DummyValue.UUID] },
|
||||
],
|
||||
})
|
||||
async getAfter(since: Date, options: AuditSearch): Promise<string[]> {
|
||||
|
||||
@@ -13,6 +13,7 @@ const resetEnv = () => {
|
||||
'IMMICH_WORKERS_EXCLUDE',
|
||||
'IMMICH_TRUSTED_PROXIES',
|
||||
'IMMICH_API_METRICS_PORT',
|
||||
'IMMICH_MEDIA_LOCATION',
|
||||
'IMMICH_MICROSERVICES_METRICS_PORT',
|
||||
'IMMICH_TELEMETRY_INCLUDE',
|
||||
'IMMICH_TELEMETRY_EXCLUDE',
|
||||
@@ -76,6 +77,13 @@ describe('getEnv', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('IMMICH_MEDIA_LOCATION', () => {
|
||||
it('should throw an error for relative paths', () => {
|
||||
process.env.IMMICH_MEDIA_LOCATION = './relative/path';
|
||||
expect(() => getEnv()).toThrowError('IMMICH_MEDIA_LOCATION must be an absolute path');
|
||||
});
|
||||
});
|
||||
|
||||
describe('database', () => {
|
||||
it('should use defaults', () => {
|
||||
const { database } = getEnv();
|
||||
@@ -95,7 +103,7 @@ describe('getEnv', () => {
|
||||
|
||||
it('should validate DB_SSL_MODE', () => {
|
||||
process.env.DB_SSL_MODE = 'invalid';
|
||||
expect(() => getEnv()).toThrowError('Invalid environment variables: DB_SSL_MODE');
|
||||
expect(() => getEnv()).toThrowError('DB_SSL_MODE must be one of the following values:');
|
||||
});
|
||||
|
||||
it('should accept a valid DB_SSL_MODE', () => {
|
||||
@@ -239,7 +247,7 @@ describe('getEnv', () => {
|
||||
|
||||
it('should reject invalid trusted proxies', () => {
|
||||
process.env.IMMICH_TRUSTED_PROXIES = '10.1';
|
||||
expect(() => getEnv()).toThrowError('Invalid environment variables: IMMICH_TRUSTED_PROXIES');
|
||||
expect(() => getEnv()).toThrow('IMMICH_TRUSTED_PROXIES must be an ip address, or ip address range');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -275,14 +283,14 @@ describe('getEnv', () => {
|
||||
process.env.IMMICH_TELEMETRY_EXCLUDE = 'job';
|
||||
const { telemetry } = getEnv();
|
||||
expect(telemetry.metrics).toEqual(
|
||||
new Set([ImmichTelemetry.API, ImmichTelemetry.HOST, ImmichTelemetry.IO, ImmichTelemetry.REPO]),
|
||||
new Set([ImmichTelemetry.Api, ImmichTelemetry.Host, ImmichTelemetry.Io, ImmichTelemetry.Repo]),
|
||||
);
|
||||
});
|
||||
|
||||
it('should run with specific telemetry metrics', () => {
|
||||
process.env.IMMICH_TELEMETRY_INCLUDE = 'io, host, api';
|
||||
const { telemetry } = getEnv();
|
||||
expect(telemetry.metrics).toEqual(new Set([ImmichTelemetry.API, ImmichTelemetry.HOST, ImmichTelemetry.IO]));
|
||||
expect(telemetry.metrics).toEqual(new Set([ImmichTelemetry.Api, ImmichTelemetry.Host, ImmichTelemetry.Io]));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -131,12 +131,14 @@ const getEnv = (): EnvData => {
|
||||
const dto = plainToInstance(EnvDto, process.env);
|
||||
const errors = validateSync(dto);
|
||||
if (errors.length > 0) {
|
||||
throw new Error(
|
||||
`Invalid environment variables: ${errors.map((error) => `${error.property}=${error.value}`).join(', ')}`,
|
||||
);
|
||||
const messages = [`Invalid environment variables: `];
|
||||
for (const error of errors) {
|
||||
messages.push(` - ${error.property}=${error.value} (${Object.values(error.constraints || {}).join(', ')})`);
|
||||
}
|
||||
throw new Error(messages.join('\n'));
|
||||
}
|
||||
|
||||
const includedWorkers = asSet(dto.IMMICH_WORKERS_INCLUDE, [ImmichWorker.API, ImmichWorker.MICROSERVICES]);
|
||||
const includedWorkers = asSet(dto.IMMICH_WORKERS_INCLUDE, [ImmichWorker.Api, ImmichWorker.Microservices]);
|
||||
const excludedWorkers = asSet(dto.IMMICH_WORKERS_EXCLUDE, []);
|
||||
const workers = [...setDifference(includedWorkers, excludedWorkers)];
|
||||
for (const worker of workers) {
|
||||
@@ -145,8 +147,8 @@ const getEnv = (): EnvData => {
|
||||
}
|
||||
}
|
||||
|
||||
const environment = dto.IMMICH_ENV || ImmichEnvironment.PRODUCTION;
|
||||
const isProd = environment === ImmichEnvironment.PRODUCTION;
|
||||
const environment = dto.IMMICH_ENV || ImmichEnvironment.Production;
|
||||
const isProd = environment === ImmichEnvironment.Production;
|
||||
const buildFolder = dto.IMMICH_BUILD_DATA || '/build';
|
||||
const folders = {
|
||||
geodata: join(buildFolder, 'geodata'),
|
||||
@@ -199,15 +201,15 @@ const getEnv = (): EnvData => {
|
||||
let vectorExtension: VectorExtension | undefined;
|
||||
switch (dto.DB_VECTOR_EXTENSION) {
|
||||
case 'pgvector': {
|
||||
vectorExtension = DatabaseExtension.VECTOR;
|
||||
vectorExtension = DatabaseExtension.Vector;
|
||||
break;
|
||||
}
|
||||
case 'pgvecto.rs': {
|
||||
vectorExtension = DatabaseExtension.VECTORS;
|
||||
vectorExtension = DatabaseExtension.Vectors;
|
||||
break;
|
||||
}
|
||||
case 'vectorchord': {
|
||||
vectorExtension = DatabaseExtension.VECTORCHORD;
|
||||
vectorExtension = DatabaseExtension.VectorChord;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -254,11 +256,11 @@ const getEnv = (): EnvData => {
|
||||
mount: true,
|
||||
generateId: true,
|
||||
setup: (cls, req: Request, res: Response) => {
|
||||
const headerValues = req.headers[ImmichHeader.CID];
|
||||
const headerValues = req.headers[ImmichHeader.Cid];
|
||||
const headerValue = Array.isArray(headerValues) ? headerValues[0] : headerValues;
|
||||
const cid = headerValue || cls.get(CLS_ID);
|
||||
cls.set(CLS_ID, cid);
|
||||
res.header(ImmichHeader.CID, cid);
|
||||
res.header(ImmichHeader.Cid, cid);
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -278,9 +280,9 @@ const getEnv = (): EnvData => {
|
||||
|
||||
otel: {
|
||||
metrics: {
|
||||
hostMetrics: telemetries.has(ImmichTelemetry.HOST),
|
||||
hostMetrics: telemetries.has(ImmichTelemetry.Host),
|
||||
apiMetrics: {
|
||||
enable: telemetries.has(ImmichTelemetry.API),
|
||||
enable: telemetries.has(ImmichTelemetry.Api),
|
||||
ignoreRoutes: excludePaths,
|
||||
},
|
||||
},
|
||||
@@ -335,7 +337,7 @@ export class ConfigRepository {
|
||||
}
|
||||
|
||||
isDev() {
|
||||
return this.getEnv().environment === ImmichEnvironment.DEVELOPMENT;
|
||||
return this.getEnv().environment === ImmichEnvironment.Development;
|
||||
}
|
||||
|
||||
getWorker() {
|
||||
|
||||
@@ -53,8 +53,8 @@ export async function getVectorExtension(runner: Kysely<DB> | QueryRunner): Prom
|
||||
}
|
||||
|
||||
export const probes: Record<VectorIndex, number> = {
|
||||
[VectorIndex.CLIP]: 1,
|
||||
[VectorIndex.FACE]: 1,
|
||||
[VectorIndex.Clip]: 1,
|
||||
[VectorIndex.Face]: 1,
|
||||
};
|
||||
|
||||
@Injectable()
|
||||
@@ -77,7 +77,7 @@ export class DatabaseRepository {
|
||||
return getVectorExtension(this.db);
|
||||
}
|
||||
|
||||
@GenerateSql({ params: [[DatabaseExtension.VECTORS]] })
|
||||
@GenerateSql({ params: [[DatabaseExtension.Vectors]] })
|
||||
async getExtensionVersions(extensions: readonly DatabaseExtension[]): Promise<ExtensionVersion[]> {
|
||||
const { rows } = await sql<ExtensionVersion>`
|
||||
SELECT name, default_version as "availableVersion", installed_version as "installedVersion"
|
||||
@@ -89,13 +89,13 @@ export class DatabaseRepository {
|
||||
|
||||
getExtensionVersionRange(extension: VectorExtension): string {
|
||||
switch (extension) {
|
||||
case DatabaseExtension.VECTORCHORD: {
|
||||
case DatabaseExtension.VectorChord: {
|
||||
return VECTORCHORD_VERSION_RANGE;
|
||||
}
|
||||
case DatabaseExtension.VECTORS: {
|
||||
case DatabaseExtension.Vectors: {
|
||||
return VECTORS_VERSION_RANGE;
|
||||
}
|
||||
case DatabaseExtension.VECTOR: {
|
||||
case DatabaseExtension.Vector: {
|
||||
return VECTOR_VERSION_RANGE;
|
||||
}
|
||||
default: {
|
||||
@@ -117,7 +117,7 @@ export class DatabaseRepository {
|
||||
async createExtension(extension: DatabaseExtension): Promise<void> {
|
||||
this.logger.log(`Creating ${EXTENSION_NAMES[extension]} extension`);
|
||||
await sql`CREATE EXTENSION IF NOT EXISTS ${sql.raw(extension)} CASCADE`.execute(this.db);
|
||||
if (extension === DatabaseExtension.VECTORCHORD) {
|
||||
if (extension === DatabaseExtension.VectorChord) {
|
||||
const dbName = sql.id(await this.getDatabaseName());
|
||||
await sql`ALTER DATABASE ${dbName} SET vchordrq.probes = 1`.execute(this.db);
|
||||
await sql`SET vchordrq.probes = 1`.execute(this.db);
|
||||
@@ -147,8 +147,8 @@ export class DatabaseRepository {
|
||||
}
|
||||
|
||||
await Promise.all([
|
||||
this.db.schema.dropIndex(VectorIndex.CLIP).ifExists().execute(),
|
||||
this.db.schema.dropIndex(VectorIndex.FACE).ifExists().execute(),
|
||||
this.db.schema.dropIndex(VectorIndex.Clip).ifExists().execute(),
|
||||
this.db.schema.dropIndex(VectorIndex.Face).ifExists().execute(),
|
||||
]);
|
||||
|
||||
await this.db.transaction().execute(async (tx) => {
|
||||
@@ -156,14 +156,14 @@ export class DatabaseRepository {
|
||||
|
||||
await sql`ALTER EXTENSION ${sql.raw(extension)} UPDATE TO ${sql.lit(targetVersion)}`.execute(tx);
|
||||
|
||||
if (extension === DatabaseExtension.VECTORS && (diff === 'major' || diff === 'minor')) {
|
||||
if (extension === DatabaseExtension.Vectors && (diff === 'major' || diff === 'minor')) {
|
||||
await sql`SELECT pgvectors_upgrade()`.execute(tx);
|
||||
restartRequired = true;
|
||||
}
|
||||
});
|
||||
|
||||
if (!restartRequired) {
|
||||
await Promise.all([this.reindexVectors(VectorIndex.CLIP), this.reindexVectors(VectorIndex.FACE)]);
|
||||
await Promise.all([this.reindexVectors(VectorIndex.Clip), this.reindexVectors(VectorIndex.Face)]);
|
||||
}
|
||||
|
||||
return { restartRequired };
|
||||
@@ -171,7 +171,7 @@ export class DatabaseRepository {
|
||||
|
||||
async prewarm(index: VectorIndex): Promise<void> {
|
||||
const vectorExtension = await getVectorExtension(this.db);
|
||||
if (vectorExtension !== DatabaseExtension.VECTORCHORD) {
|
||||
if (vectorExtension !== DatabaseExtension.VectorChord) {
|
||||
return;
|
||||
}
|
||||
this.logger.debug(`Prewarming ${index}`);
|
||||
@@ -196,19 +196,19 @@ export class DatabaseRepository {
|
||||
}
|
||||
|
||||
switch (vectorExtension) {
|
||||
case DatabaseExtension.VECTOR: {
|
||||
case DatabaseExtension.Vector: {
|
||||
if (!row.indexdef.toLowerCase().includes('using hnsw')) {
|
||||
promises.push(this.reindexVectors(indexName));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case DatabaseExtension.VECTORS: {
|
||||
case DatabaseExtension.Vectors: {
|
||||
if (!row.indexdef.toLowerCase().includes('using vectors')) {
|
||||
promises.push(this.reindexVectors(indexName));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case DatabaseExtension.VECTORCHORD: {
|
||||
case DatabaseExtension.VectorChord: {
|
||||
const matches = row.indexdef.match(/(?<=lists = \[)\d+/g);
|
||||
const lists = matches && matches.length > 0 ? Number(matches[0]) : 1;
|
||||
promises.push(
|
||||
@@ -264,7 +264,7 @@ export class DatabaseRepository {
|
||||
await sql`ALTER TABLE ${sql.raw(table)} ADD COLUMN embedding real[] NOT NULL`.execute(tx);
|
||||
}
|
||||
await sql`ALTER TABLE ${sql.raw(table)} ALTER COLUMN embedding SET DATA TYPE real[]`.execute(tx);
|
||||
const schema = vectorExtension === DatabaseExtension.VECTORS ? 'vectors.' : '';
|
||||
const schema = vectorExtension === DatabaseExtension.Vectors ? 'vectors.' : '';
|
||||
await sql`
|
||||
ALTER TABLE ${sql.raw(table)}
|
||||
ALTER COLUMN embedding
|
||||
@@ -329,11 +329,11 @@ export class DatabaseRepository {
|
||||
.alterColumn('embedding', (col) => col.setDataType(sql.raw(`vector(${dimSize})`)))
|
||||
.execute();
|
||||
await sql
|
||||
.raw(vectorIndexQuery({ vectorExtension, table: 'smart_search', indexName: VectorIndex.CLIP }))
|
||||
.raw(vectorIndexQuery({ vectorExtension, table: 'smart_search', indexName: VectorIndex.Clip }))
|
||||
.execute(trx);
|
||||
await trx.schema.alterTable('smart_search').dropConstraint('dim_size_constraint').ifExists().execute();
|
||||
});
|
||||
probes[VectorIndex.CLIP] = 1;
|
||||
probes[VectorIndex.Clip] = 1;
|
||||
|
||||
await sql`vacuum analyze ${sql.table('smart_search')}`.execute(this.db);
|
||||
}
|
||||
@@ -436,6 +436,39 @@ export class DatabaseRepository {
|
||||
this.logger.debug('Finished running kysely migrations');
|
||||
}
|
||||
|
||||
async migrateFilePaths(sourceFolder: string, targetFolder: string): Promise<void> {
|
||||
// escaping regex special characters with a backslash
|
||||
const sourceRegex = '^' + sourceFolder.replaceAll(/[-[\]{}()*+?.,\\^$|#\s]/g, String.raw`\$&`);
|
||||
const source = sql.raw(`'${sourceRegex}'`);
|
||||
const target = sql.lit(targetFolder);
|
||||
|
||||
await this.db.transaction().execute(async (tx) => {
|
||||
await tx
|
||||
.updateTable('asset')
|
||||
.set((eb) => ({
|
||||
originalPath: eb.fn('REGEXP_REPLACE', ['originalPath', source, target]),
|
||||
encodedVideoPath: eb.fn('REGEXP_REPLACE', ['encodedVideoPath', source, target]),
|
||||
sidecarPath: eb.fn('REGEXP_REPLACE', ['sidecarPath', source, target]),
|
||||
}))
|
||||
.execute();
|
||||
|
||||
await tx
|
||||
.updateTable('asset_file')
|
||||
.set((eb) => ({ path: eb.fn('REGEXP_REPLACE', ['path', source, target]) }))
|
||||
.execute();
|
||||
|
||||
await tx
|
||||
.updateTable('person')
|
||||
.set((eb) => ({ thumbnailPath: eb.fn('REGEXP_REPLACE', ['thumbnailPath', source, target]) }))
|
||||
.execute();
|
||||
|
||||
await tx
|
||||
.updateTable('user')
|
||||
.set((eb) => ({ profileImagePath: eb.fn('REGEXP_REPLACE', ['profileImagePath', source, target]) }))
|
||||
.execute();
|
||||
});
|
||||
}
|
||||
|
||||
async withLock<R>(lock: DatabaseLock, callback: () => Promise<R>): Promise<R> {
|
||||
let res;
|
||||
await this.asyncLock.acquire(DatabaseLock[lock], async () => {
|
||||
|
||||
@@ -34,7 +34,7 @@ export class DownloadRepository {
|
||||
downloadUserId(userId: string) {
|
||||
return builder(this.db)
|
||||
.where('asset.ownerId', '=', userId)
|
||||
.where('asset.visibility', '!=', AssetVisibility.HIDDEN)
|
||||
.where('asset.visibility', '!=', AssetVisibility.Hidden)
|
||||
.stream();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,14 +109,14 @@ export class DuplicateRepository {
|
||||
assetId: DummyValue.UUID,
|
||||
embedding: DummyValue.VECTOR,
|
||||
maxDistance: 0.6,
|
||||
type: AssetType.IMAGE,
|
||||
type: AssetType.Image,
|
||||
userIds: [DummyValue.UUID],
|
||||
},
|
||||
],
|
||||
})
|
||||
search({ assetId, embedding, maxDistance, type, userIds }: DuplicateSearch) {
|
||||
return this.db.transaction().execute(async (trx) => {
|
||||
await sql`set local vchordrq.probes = ${sql.lit(probes[VectorIndex.CLIP])}`.execute(trx);
|
||||
await sql`set local vchordrq.probes = ${sql.lit(probes[VectorIndex.Clip])}`.execute(trx);
|
||||
return await trx
|
||||
.with('cte', (qb) =>
|
||||
qb
|
||||
|
||||
@@ -166,7 +166,7 @@ export class EventRepository implements OnGatewayConnection, OnGatewayDisconnect
|
||||
continue;
|
||||
}
|
||||
|
||||
const event = reflector.get<EventConfig>(MetadataKey.EVENT_CONFIG, handler);
|
||||
const event = reflector.get<EventConfig>(MetadataKey.EventConfig, handler);
|
||||
if (!event) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ export class JobRepository {
|
||||
const instance = this.moduleRef.get<any>(Service);
|
||||
for (const methodName of getMethodNames(instance)) {
|
||||
const handler = instance[methodName];
|
||||
const config = reflector.get<JobConfig>(MetadataKey.JOB_CONFIG, handler);
|
||||
const config = reflector.get<JobConfig>(MetadataKey.JobConfig, handler);
|
||||
if (!config) {
|
||||
continue;
|
||||
}
|
||||
@@ -99,7 +99,7 @@ export class JobRepository {
|
||||
const item = this.handlers[name as JobName];
|
||||
if (!item) {
|
||||
this.logger.warn(`Skipping unknown job: "${name}"`);
|
||||
return JobStatus.SKIPPED;
|
||||
return JobStatus.Skipped;
|
||||
}
|
||||
|
||||
return item.handler(data);
|
||||
@@ -205,20 +205,20 @@ export class JobRepository {
|
||||
|
||||
private getJobOptions(item: JobItem): JobsOptions | null {
|
||||
switch (item.name) {
|
||||
case JobName.NOTIFY_ALBUM_UPDATE: {
|
||||
case JobName.NotifyAlbumUpdate: {
|
||||
return {
|
||||
jobId: `${item.data.id}/${item.data.recipientId}`,
|
||||
delay: item.data?.delay,
|
||||
};
|
||||
}
|
||||
case JobName.STORAGE_TEMPLATE_MIGRATION_SINGLE: {
|
||||
case JobName.StorageTemplateMigrationSingle: {
|
||||
return { jobId: item.data.id };
|
||||
}
|
||||
case JobName.GENERATE_PERSON_THUMBNAIL: {
|
||||
case JobName.PersonGenerateThumbnail: {
|
||||
return { priority: 1 };
|
||||
}
|
||||
case JobName.QUEUE_FACIAL_RECOGNITION: {
|
||||
return { jobId: JobName.QUEUE_FACIAL_RECOGNITION };
|
||||
case JobName.FacialRecognitionQueueAll: {
|
||||
return { jobId: JobName.FacialRecognitionQueueAll };
|
||||
}
|
||||
default: {
|
||||
return null;
|
||||
|
||||
@@ -79,7 +79,7 @@ export class LibraryRepository {
|
||||
eb.fn
|
||||
.countAll<number>()
|
||||
.filterWhere((eb) =>
|
||||
eb.and([eb('asset.type', '=', AssetType.IMAGE), eb('asset.visibility', '!=', AssetVisibility.HIDDEN)]),
|
||||
eb.and([eb('asset.type', '=', AssetType.Image), eb('asset.visibility', '!=', AssetVisibility.Hidden)]),
|
||||
)
|
||||
.as('photos'),
|
||||
)
|
||||
@@ -87,7 +87,7 @@ export class LibraryRepository {
|
||||
eb.fn
|
||||
.countAll<number>()
|
||||
.filterWhere((eb) =>
|
||||
eb.and([eb('asset.type', '=', AssetType.VIDEO), eb('asset.visibility', '!=', AssetVisibility.HIDDEN)]),
|
||||
eb.and([eb('asset.type', '=', AssetType.Video), eb('asset.visibility', '!=', AssetVisibility.Hidden)]),
|
||||
)
|
||||
.as('videos'),
|
||||
)
|
||||
|
||||
@@ -22,7 +22,7 @@ describe(LoggingRepository.name, () => {
|
||||
describe('formatContext', () => {
|
||||
it('should use colors', () => {
|
||||
sut = new LoggingRepository(clsMock, configMock);
|
||||
sut.setAppName(ImmichWorker.API);
|
||||
sut.setAppName(ImmichWorker.Api);
|
||||
|
||||
const logger = new MyConsoleLogger(clsMock, { color: true });
|
||||
|
||||
@@ -31,7 +31,7 @@ describe(LoggingRepository.name, () => {
|
||||
|
||||
it('should not use colors when color is false', () => {
|
||||
sut = new LoggingRepository(clsMock, configMock);
|
||||
sut.setAppName(ImmichWorker.API);
|
||||
sut.setAppName(ImmichWorker.Api);
|
||||
|
||||
const logger = new MyConsoleLogger(clsMock, { color: false });
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import { ConfigRepository } from 'src/repositories/config.repository';
|
||||
type LogDetails = any;
|
||||
type LogFunction = () => string;
|
||||
|
||||
const LOG_LEVELS = [LogLevel.VERBOSE, LogLevel.DEBUG, LogLevel.LOG, LogLevel.WARN, LogLevel.ERROR, LogLevel.FATAL];
|
||||
const LOG_LEVELS = [LogLevel.Verbose, LogLevel.Debug, LogLevel.Log, LogLevel.Warn, LogLevel.Error, LogLevel.Fatal];
|
||||
|
||||
enum LogColor {
|
||||
RED = 31,
|
||||
@@ -20,7 +20,7 @@ enum LogColor {
|
||||
}
|
||||
|
||||
let appName: string | undefined;
|
||||
let logLevels: LogLevel[] = [LogLevel.LOG, LogLevel.WARN, LogLevel.ERROR, LogLevel.FATAL];
|
||||
let logLevels: LogLevel[] = [LogLevel.Log, LogLevel.Warn, LogLevel.Error, LogLevel.Fatal];
|
||||
|
||||
export class MyConsoleLogger extends ConsoleLogger {
|
||||
private isColorEnabled: boolean;
|
||||
@@ -106,35 +106,35 @@ export class LoggingRepository {
|
||||
}
|
||||
|
||||
verbose(message: string, ...details: LogDetails) {
|
||||
this.handleMessage(LogLevel.VERBOSE, message, details);
|
||||
this.handleMessage(LogLevel.Verbose, message, details);
|
||||
}
|
||||
|
||||
verboseFn(message: LogFunction, ...details: LogDetails) {
|
||||
this.handleFunction(LogLevel.VERBOSE, message, details);
|
||||
this.handleFunction(LogLevel.Verbose, message, details);
|
||||
}
|
||||
|
||||
debug(message: string, ...details: LogDetails) {
|
||||
this.handleMessage(LogLevel.DEBUG, message, details);
|
||||
this.handleMessage(LogLevel.Debug, message, details);
|
||||
}
|
||||
|
||||
debugFn(message: LogFunction, ...details: LogDetails) {
|
||||
this.handleFunction(LogLevel.DEBUG, message, details);
|
||||
this.handleFunction(LogLevel.Debug, message, details);
|
||||
}
|
||||
|
||||
log(message: string, ...details: LogDetails) {
|
||||
this.handleMessage(LogLevel.LOG, message, details);
|
||||
this.handleMessage(LogLevel.Log, message, details);
|
||||
}
|
||||
|
||||
warn(message: string, ...details: LogDetails) {
|
||||
this.handleMessage(LogLevel.WARN, message, details);
|
||||
this.handleMessage(LogLevel.Warn, message, details);
|
||||
}
|
||||
|
||||
error(message: string | Error, ...details: LogDetails) {
|
||||
this.handleMessage(LogLevel.ERROR, message, details);
|
||||
this.handleMessage(LogLevel.Error, message, details);
|
||||
}
|
||||
|
||||
fatal(message: string, ...details: LogDetails) {
|
||||
this.handleMessage(LogLevel.FATAL, message, details);
|
||||
this.handleMessage(LogLevel.Fatal, message, details);
|
||||
}
|
||||
|
||||
private handleFunction(level: LogLevel, message: LogFunction, details: LogDetails[]) {
|
||||
@@ -145,32 +145,32 @@ export class LoggingRepository {
|
||||
|
||||
private handleMessage(level: LogLevel, message: string | Error, details: LogDetails[]) {
|
||||
switch (level) {
|
||||
case LogLevel.VERBOSE: {
|
||||
case LogLevel.Verbose: {
|
||||
this.logger.verbose(message, ...details);
|
||||
break;
|
||||
}
|
||||
|
||||
case LogLevel.DEBUG: {
|
||||
case LogLevel.Debug: {
|
||||
this.logger.debug(message, ...details);
|
||||
break;
|
||||
}
|
||||
|
||||
case LogLevel.LOG: {
|
||||
case LogLevel.Log: {
|
||||
this.logger.log(message, ...details);
|
||||
break;
|
||||
}
|
||||
|
||||
case LogLevel.WARN: {
|
||||
case LogLevel.Warn: {
|
||||
this.logger.warn(message, ...details);
|
||||
break;
|
||||
}
|
||||
|
||||
case LogLevel.ERROR: {
|
||||
case LogLevel.Error: {
|
||||
this.logger.error(message, ...details);
|
||||
break;
|
||||
}
|
||||
|
||||
case LogLevel.FATAL: {
|
||||
case LogLevel.Fatal: {
|
||||
this.logger.fatal(message, ...details);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -61,14 +61,14 @@ export class MapRepository {
|
||||
const geodataDate = await readFile(resourcePaths.geodata.dateFile, 'utf8');
|
||||
|
||||
// TODO move to service init
|
||||
const geocodingMetadata = await this.metadataRepository.get(SystemMetadataKey.REVERSE_GEOCODING_STATE);
|
||||
const geocodingMetadata = await this.metadataRepository.get(SystemMetadataKey.ReverseGeocodingState);
|
||||
if (geocodingMetadata?.lastUpdate === geodataDate) {
|
||||
return;
|
||||
}
|
||||
|
||||
await Promise.all([this.importGeodata(), this.importNaturalEarthCountries()]);
|
||||
|
||||
await this.metadataRepository.set(SystemMetadataKey.REVERSE_GEOCODING_STATE, {
|
||||
await this.metadataRepository.set(SystemMetadataKey.ReverseGeocodingState, {
|
||||
lastUpdate: geodataDate,
|
||||
lastImportFileName: citiesFile,
|
||||
});
|
||||
@@ -102,13 +102,13 @@ export class MapRepository {
|
||||
.$if(isArchived === true, (qb) =>
|
||||
qb.where((eb) =>
|
||||
eb.or([
|
||||
eb('asset.visibility', '=', AssetVisibility.TIMELINE),
|
||||
eb('asset.visibility', '=', AssetVisibility.ARCHIVE),
|
||||
eb('asset.visibility', '=', AssetVisibility.Timeline),
|
||||
eb('asset.visibility', '=', AssetVisibility.Archive),
|
||||
]),
|
||||
),
|
||||
)
|
||||
.$if(isArchived === false || isArchived === undefined, (qb) =>
|
||||
qb.where('asset.visibility', '=', AssetVisibility.TIMELINE),
|
||||
qb.where('asset.visibility', '=', AssetVisibility.Timeline),
|
||||
)
|
||||
.$if(isFavorite !== undefined, (q) => q.where('isFavorite', '=', isFavorite!))
|
||||
.$if(fileCreatedAfter !== undefined, (q) => q.where('fileCreatedAt', '>=', fileCreatedAfter!))
|
||||
|
||||
@@ -55,28 +55,28 @@ export class MediaRepository {
|
||||
async extract(input: string): Promise<ExtractResult | null> {
|
||||
try {
|
||||
const buffer = await exiftool.extractBinaryTagToBuffer('JpgFromRaw2', input);
|
||||
return { buffer, format: RawExtractedFormat.JPEG };
|
||||
return { buffer, format: RawExtractedFormat.Jpeg };
|
||||
} catch (error: any) {
|
||||
this.logger.debug('Could not extract JpgFromRaw2 buffer from image, trying JPEG from RAW next', error.message);
|
||||
}
|
||||
|
||||
try {
|
||||
const buffer = await exiftool.extractBinaryTagToBuffer('JpgFromRaw', input);
|
||||
return { buffer, format: RawExtractedFormat.JPEG };
|
||||
return { buffer, format: RawExtractedFormat.Jpeg };
|
||||
} catch (error: any) {
|
||||
this.logger.debug('Could not extract JPEG buffer from image, trying PreviewJXL next', error.message);
|
||||
}
|
||||
|
||||
try {
|
||||
const buffer = await exiftool.extractBinaryTagToBuffer('PreviewJXL', input);
|
||||
return { buffer, format: RawExtractedFormat.JXL };
|
||||
return { buffer, format: RawExtractedFormat.Jxl };
|
||||
} catch (error: any) {
|
||||
this.logger.debug('Could not extract PreviewJXL buffer from image, trying PreviewImage next', error.message);
|
||||
}
|
||||
|
||||
try {
|
||||
const buffer = await exiftool.extractBinaryTagToBuffer('PreviewImage', input);
|
||||
return { buffer, format: RawExtractedFormat.JPEG };
|
||||
return { buffer, format: RawExtractedFormat.Jpeg };
|
||||
} catch (error: any) {
|
||||
this.logger.debug('Could not extract preview buffer from image', error.message);
|
||||
return null;
|
||||
@@ -142,7 +142,7 @@ export class MediaRepository {
|
||||
limitInputPixels: false,
|
||||
raw: options.raw,
|
||||
})
|
||||
.pipelineColorspace(options.colorspace === Colorspace.SRGB ? 'srgb' : 'rgb16')
|
||||
.pipelineColorspace(options.colorspace === Colorspace.Srgb ? 'srgb' : 'rgb16')
|
||||
.withIccProfile(options.colorspace);
|
||||
|
||||
if (!options.raw) {
|
||||
@@ -267,7 +267,7 @@ export class MediaRepository {
|
||||
|
||||
const { frameCount, percentInterval } = options.progress;
|
||||
const frameInterval = Math.ceil(frameCount / (100 / percentInterval));
|
||||
if (this.logger.isLevelEnabled(LogLevel.DEBUG) && frameCount && frameInterval) {
|
||||
if (this.logger.isLevelEnabled(LogLevel.Debug) && frameCount && frameInterval) {
|
||||
let lastProgressFrame: number = 0;
|
||||
ffmpegCall.on('progress', (progress: ProgressEvent) => {
|
||||
if (progress.frames - lastProgressFrame < frameInterval) {
|
||||
|
||||
@@ -19,7 +19,7 @@ export class MemoryRepository implements IBulkAsset {
|
||||
.deleteFrom('memory_asset')
|
||||
.using('asset')
|
||||
.whereRef('memory_asset.assetsId', '=', 'asset.id')
|
||||
.where('asset.visibility', '!=', AssetVisibility.TIMELINE)
|
||||
.where('asset.visibility', '!=', AssetVisibility.Timeline)
|
||||
.execute();
|
||||
|
||||
return this.db
|
||||
@@ -67,7 +67,7 @@ export class MemoryRepository implements IBulkAsset {
|
||||
.innerJoin('memory_asset', 'asset.id', 'memory_asset.assetsId')
|
||||
.whereRef('memory_asset.memoriesId', '=', 'memory.id')
|
||||
.orderBy('asset.fileCreatedAt', 'asc')
|
||||
.where('asset.visibility', '=', sql.lit(AssetVisibility.TIMELINE))
|
||||
.where('asset.visibility', '=', sql.lit(AssetVisibility.Timeline))
|
||||
.where('asset.deletedAt', 'is', null),
|
||||
).as('assets'),
|
||||
)
|
||||
@@ -158,7 +158,7 @@ export class MemoryRepository implements IBulkAsset {
|
||||
.innerJoin('memory_asset', 'asset.id', 'memory_asset.assetsId')
|
||||
.whereRef('memory_asset.memoriesId', '=', 'memory.id')
|
||||
.orderBy('asset.fileCreatedAt', 'asc')
|
||||
.where('asset.visibility', '=', sql.lit(AssetVisibility.TIMELINE))
|
||||
.where('asset.visibility', '=', sql.lit(AssetVisibility.Timeline))
|
||||
.where('asset.deletedAt', 'is', null),
|
||||
).as('assets'),
|
||||
)
|
||||
|
||||
@@ -48,7 +48,7 @@ export class MoveRepository {
|
||||
eb.selectFrom('asset').select('id').whereRef('asset.id', '=', 'move_history.entityId'),
|
||||
),
|
||||
)
|
||||
.where('move_history.pathType', '=', sql.lit(AssetPathType.ORIGINAL))
|
||||
.where('move_history.pathType', '=', sql.lit(AssetPathType.Original))
|
||||
.execute();
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ export class MoveRepository {
|
||||
async cleanMoveHistorySingle(assetId: string): Promise<void> {
|
||||
await this.db
|
||||
.deleteFrom('move_history')
|
||||
.where('move_history.pathType', '=', sql.lit(AssetPathType.ORIGINAL))
|
||||
.where('move_history.pathType', '=', sql.lit(AssetPathType.Original))
|
||||
.where('entityId', '=', assetId)
|
||||
.execute();
|
||||
}
|
||||
|
||||
@@ -138,11 +138,11 @@ export class OAuthRepository {
|
||||
}
|
||||
|
||||
switch (tokenEndpointAuthMethod) {
|
||||
case OAuthTokenEndpointAuthMethod.CLIENT_SECRET_POST: {
|
||||
case OAuthTokenEndpointAuthMethod.ClientSecretPost: {
|
||||
return ClientSecretPost(clientSecret);
|
||||
}
|
||||
|
||||
case OAuthTokenEndpointAuthMethod.CLIENT_SECRET_BASIC: {
|
||||
case OAuthTokenEndpointAuthMethod.ClientSecretBasic: {
|
||||
return ClientSecretBasic(clientSecret);
|
||||
}
|
||||
|
||||
|
||||
@@ -142,6 +142,16 @@ export class PersonRepository {
|
||||
.stream();
|
||||
}
|
||||
|
||||
@GenerateSql()
|
||||
getFileSamples() {
|
||||
return this.db
|
||||
.selectFrom('person')
|
||||
.select(['id', 'thumbnailPath'])
|
||||
.where('thumbnailPath', '!=', sql.lit(''))
|
||||
.limit(sql.lit(3))
|
||||
.execute();
|
||||
}
|
||||
|
||||
@GenerateSql({ params: [{ take: 1, skip: 0 }, DummyValue.UUID] })
|
||||
async getAllForUser(pagination: PaginationOptions, userId: string, options?: PersonSearchOptions) {
|
||||
const items = await this.db
|
||||
@@ -151,7 +161,7 @@ export class PersonRepository {
|
||||
.innerJoin('asset', (join) =>
|
||||
join
|
||||
.onRef('asset_face.assetId', '=', 'asset.id')
|
||||
.on('asset.visibility', '=', sql.lit(AssetVisibility.TIMELINE))
|
||||
.on('asset.visibility', '=', sql.lit(AssetVisibility.Timeline))
|
||||
.on('asset.deletedAt', 'is', null),
|
||||
)
|
||||
.where('person.ownerId', '=', userId)
|
||||
@@ -276,7 +286,7 @@ export class PersonRepository {
|
||||
.selectFrom('asset_file')
|
||||
.select('asset_file.path')
|
||||
.whereRef('asset_file.assetId', '=', 'asset.id')
|
||||
.where('asset_file.type', '=', sql.lit(AssetFileType.PREVIEW))
|
||||
.where('asset_file.type', '=', sql.lit(AssetFileType.Preview))
|
||||
.as('previewPath'),
|
||||
)
|
||||
.where('person.id', '=', id)
|
||||
@@ -341,7 +351,7 @@ export class PersonRepository {
|
||||
join
|
||||
.onRef('asset.id', '=', 'asset_face.assetId')
|
||||
.on('asset_face.personId', '=', personId)
|
||||
.on('asset.visibility', '=', sql.lit(AssetVisibility.TIMELINE))
|
||||
.on('asset.visibility', '=', sql.lit(AssetVisibility.Timeline))
|
||||
.on('asset.deletedAt', 'is', null),
|
||||
)
|
||||
.select((eb) => eb.fn.count(eb.fn('distinct', ['asset.id'])).as('count'))
|
||||
@@ -369,7 +379,7 @@ export class PersonRepository {
|
||||
eb
|
||||
.selectFrom('asset')
|
||||
.whereRef('asset.id', '=', 'asset_face.assetId')
|
||||
.where('asset.visibility', '=', sql.lit(AssetVisibility.TIMELINE))
|
||||
.where('asset.visibility', '=', sql.lit(AssetVisibility.Timeline))
|
||||
.where('asset.deletedAt', 'is', null),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -256,7 +256,7 @@ export class SearchRepository {
|
||||
}
|
||||
|
||||
return this.db.transaction().execute(async (trx) => {
|
||||
await sql`set local vchordrq.probes = ${sql.lit(probes[VectorIndex.CLIP])}`.execute(trx);
|
||||
await sql`set local vchordrq.probes = ${sql.lit(probes[VectorIndex.Clip])}`.execute(trx);
|
||||
const items = await searchAssetBuilder(trx, options)
|
||||
.selectAll('asset')
|
||||
.innerJoin('smart_search', 'asset.id', 'smart_search.assetId')
|
||||
@@ -284,7 +284,7 @@ export class SearchRepository {
|
||||
}
|
||||
|
||||
return this.db.transaction().execute(async (trx) => {
|
||||
await sql`set local vchordrq.probes = ${sql.lit(probes[VectorIndex.FACE])}`.execute(trx);
|
||||
await sql`set local vchordrq.probes = ${sql.lit(probes[VectorIndex.Face])}`.execute(trx);
|
||||
return await trx
|
||||
.with('cte', (qb) =>
|
||||
qb
|
||||
@@ -351,8 +351,8 @@ export class SearchRepository {
|
||||
.select(['city', 'assetId'])
|
||||
.innerJoin('asset', 'asset.id', 'asset_exif.assetId')
|
||||
.where('asset.ownerId', '=', anyUuid(userIds))
|
||||
.where('asset.visibility', '=', AssetVisibility.TIMELINE)
|
||||
.where('asset.type', '=', AssetType.IMAGE)
|
||||
.where('asset.visibility', '=', AssetVisibility.Timeline)
|
||||
.where('asset.type', '=', AssetType.Image)
|
||||
.where('asset.deletedAt', 'is', null)
|
||||
.orderBy('city')
|
||||
.limit(1);
|
||||
@@ -367,8 +367,8 @@ export class SearchRepository {
|
||||
.select(['city', 'assetId'])
|
||||
.innerJoin('asset', 'asset.id', 'asset_exif.assetId')
|
||||
.where('asset.ownerId', '=', anyUuid(userIds))
|
||||
.where('asset.visibility', '=', AssetVisibility.TIMELINE)
|
||||
.where('asset.type', '=', AssetType.IMAGE)
|
||||
.where('asset.visibility', '=', AssetVisibility.Timeline)
|
||||
.where('asset.type', '=', AssetType.Image)
|
||||
.where('asset.deletedAt', 'is', null)
|
||||
.whereRef('asset_exif.city', '>', 'cte.city')
|
||||
.orderBy('city')
|
||||
@@ -450,7 +450,7 @@ export class SearchRepository {
|
||||
.distinctOn(field)
|
||||
.innerJoin('asset', 'asset.id', 'asset_exif.assetId')
|
||||
.where('ownerId', '=', anyUuid(userIds))
|
||||
.where('visibility', '=', AssetVisibility.TIMELINE)
|
||||
.where('visibility', '=', AssetVisibility.Timeline)
|
||||
.where('deletedAt', 'is', null)
|
||||
.where(field, 'is not', null);
|
||||
}
|
||||
|
||||
@@ -103,7 +103,7 @@ export class SharedLinkRepository {
|
||||
.select((eb) => eb.fn.toJson('album').$castTo<Album | null>().as('album'))
|
||||
.where('shared_link.id', '=', id)
|
||||
.where('shared_link.userId', '=', userId)
|
||||
.where((eb) => eb.or([eb('shared_link.type', '=', SharedLinkType.INDIVIDUAL), eb('album.id', 'is not', null)]))
|
||||
.where((eb) => eb.or([eb('shared_link.type', '=', SharedLinkType.Individual), eb('album.id', 'is not', null)]))
|
||||
.orderBy('shared_link.createdAt', 'desc')
|
||||
.executeTakeFirst();
|
||||
}
|
||||
@@ -165,7 +165,7 @@ export class SharedLinkRepository {
|
||||
(join) => join.onTrue(),
|
||||
)
|
||||
.select((eb) => eb.fn.toJson('album').$castTo<Album | null>().as('album'))
|
||||
.where((eb) => eb.or([eb('shared_link.type', '=', SharedLinkType.INDIVIDUAL), eb('album.id', 'is not', null)]))
|
||||
.where((eb) => eb.or([eb('shared_link.type', '=', SharedLinkType.Individual), eb('album.id', 'is not', null)]))
|
||||
.$if(!!albumId, (eb) => eb.where('shared_link.albumId', '=', albumId!))
|
||||
.orderBy('shared_link.createdAt', 'desc')
|
||||
.distinctOn(['shared_link.createdAt'])
|
||||
@@ -185,7 +185,7 @@ export class SharedLinkRepository {
|
||||
eb.selectFrom('user').select(columns.authUser).whereRef('user.id', '=', 'shared_link.userId'),
|
||||
).as('user'),
|
||||
])
|
||||
.where((eb) => eb.or([eb('shared_link.type', '=', SharedLinkType.INDIVIDUAL), eb('album.id', 'is not', null)]))
|
||||
.where((eb) => eb.or([eb('shared_link.type', '=', SharedLinkType.Individual), eb('album.id', 'is not', null)]))
|
||||
.executeTakeFirst();
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,8 @@ type AuditTables =
|
||||
| 'memory_asset_audit'
|
||||
| 'stack_audit'
|
||||
| 'person_audit'
|
||||
| 'user_metadata_audit';
|
||||
| 'user_metadata_audit'
|
||||
| 'asset_face_audit';
|
||||
type UpsertTables =
|
||||
| 'user'
|
||||
| 'partner'
|
||||
@@ -29,7 +30,8 @@ type UpsertTables =
|
||||
| 'memory_asset'
|
||||
| 'stack'
|
||||
| 'person'
|
||||
| 'user_metadata';
|
||||
| 'user_metadata'
|
||||
| 'asset_face';
|
||||
|
||||
@Injectable()
|
||||
export class SyncRepository {
|
||||
@@ -40,6 +42,7 @@ export class SyncRepository {
|
||||
albumUser: AlbumUserSync;
|
||||
asset: AssetSync;
|
||||
assetExif: AssetExifSync;
|
||||
assetFace: AssetFaceSync;
|
||||
memory: MemorySync;
|
||||
memoryToAsset: MemoryToAssetSync;
|
||||
partner: PartnerSync;
|
||||
@@ -59,6 +62,7 @@ export class SyncRepository {
|
||||
this.albumUser = new AlbumUserSync(this.db);
|
||||
this.asset = new AssetSync(this.db);
|
||||
this.assetExif = new AssetExifSync(this.db);
|
||||
this.assetFace = new AssetFaceSync(this.db);
|
||||
this.memory = new MemorySync(this.db);
|
||||
this.memoryToAsset = new MemoryToAssetSync(this.db);
|
||||
this.partner = new PartnerSync(this.db);
|
||||
@@ -385,7 +389,6 @@ class PersonSync extends BaseSync {
|
||||
'ownerId',
|
||||
'name',
|
||||
'birthDate',
|
||||
'thumbnailPath',
|
||||
'isHidden',
|
||||
'isFavorite',
|
||||
'color',
|
||||
@@ -398,6 +401,46 @@ class PersonSync extends BaseSync {
|
||||
}
|
||||
}
|
||||
|
||||
class AssetFaceSync extends BaseSync {
|
||||
@GenerateSql({ params: [DummyValue.UUID], stream: true })
|
||||
getDeletes(userId: string, ack?: SyncAck) {
|
||||
return this.db
|
||||
.selectFrom('asset_face_audit')
|
||||
.select(['asset_face_audit.id', 'assetFaceId'])
|
||||
.orderBy('asset_face_audit.id', 'asc')
|
||||
.leftJoin('asset', 'asset.id', 'asset_face_audit.assetId')
|
||||
.where('asset.ownerId', '=', userId)
|
||||
.where('asset_face_audit.deletedAt', '<', sql.raw<Date>("now() - interval '1 millisecond'"))
|
||||
.$if(!!ack, (qb) => qb.where('asset_face_audit.id', '>', ack!.updateId))
|
||||
.stream();
|
||||
}
|
||||
|
||||
@GenerateSql({ params: [DummyValue.UUID], stream: true })
|
||||
getUpserts(userId: string, ack?: SyncAck) {
|
||||
return this.db
|
||||
.selectFrom('asset_face')
|
||||
.select([
|
||||
'asset_face.id',
|
||||
'assetId',
|
||||
'personId',
|
||||
'imageWidth',
|
||||
'imageHeight',
|
||||
'boundingBoxX1',
|
||||
'boundingBoxY1',
|
||||
'boundingBoxX2',
|
||||
'boundingBoxY2',
|
||||
'sourceType',
|
||||
'asset_face.updateId',
|
||||
])
|
||||
.where('asset_face.updatedAt', '<', sql.raw<Date>("now() - interval '1 millisecond'"))
|
||||
.$if(!!ack, (qb) => qb.where('asset_face.updateId', '>', ack!.updateId))
|
||||
.orderBy('asset_face.updateId', 'asc')
|
||||
.leftJoin('asset', 'asset.id', 'asset_face.assetId')
|
||||
.where('asset.ownerId', '=', userId)
|
||||
.stream();
|
||||
}
|
||||
}
|
||||
|
||||
class AssetExifSync extends BaseSync {
|
||||
@GenerateSql({ params: [DummyValue.UUID], stream: true })
|
||||
getUpserts(userId: string, ack?: SyncAck) {
|
||||
|
||||
@@ -112,21 +112,21 @@ export class TelemetryRepository {
|
||||
const { telemetry } = this.configRepository.getEnv();
|
||||
const { metrics } = telemetry;
|
||||
|
||||
this.api = new MetricGroupRepository(metricService).configure({ enabled: metrics.has(ImmichTelemetry.API) });
|
||||
this.host = new MetricGroupRepository(metricService).configure({ enabled: metrics.has(ImmichTelemetry.HOST) });
|
||||
this.jobs = new MetricGroupRepository(metricService).configure({ enabled: metrics.has(ImmichTelemetry.JOB) });
|
||||
this.repo = new MetricGroupRepository(metricService).configure({ enabled: metrics.has(ImmichTelemetry.REPO) });
|
||||
this.api = new MetricGroupRepository(metricService).configure({ enabled: metrics.has(ImmichTelemetry.Api) });
|
||||
this.host = new MetricGroupRepository(metricService).configure({ enabled: metrics.has(ImmichTelemetry.Host) });
|
||||
this.jobs = new MetricGroupRepository(metricService).configure({ enabled: metrics.has(ImmichTelemetry.Job) });
|
||||
this.repo = new MetricGroupRepository(metricService).configure({ enabled: metrics.has(ImmichTelemetry.Repo) });
|
||||
}
|
||||
|
||||
setup({ repositories }: { repositories: ClassConstructor<unknown>[] }) {
|
||||
const { telemetry } = this.configRepository.getEnv();
|
||||
const { metrics } = telemetry;
|
||||
if (!metrics.has(ImmichTelemetry.REPO)) {
|
||||
if (!metrics.has(ImmichTelemetry.Repo)) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const Repository of repositories) {
|
||||
const isEnabled = this.reflect.get(MetadataKey.TELEMETRY_ENABLED, Repository) ?? true;
|
||||
const isEnabled = this.reflect.get(MetadataKey.TelemetryEnabled, Repository) ?? true;
|
||||
if (!isEnabled) {
|
||||
this.logger.debug(`Telemetry disabled for ${Repository.name}`);
|
||||
continue;
|
||||
|
||||
@@ -8,7 +8,7 @@ export class TrashRepository {
|
||||
constructor(@InjectKysely() private db: Kysely<DB>) {}
|
||||
|
||||
getDeletedIds(): AsyncIterableIterator<{ id: string }> {
|
||||
return this.db.selectFrom('asset').select(['id']).where('status', '=', AssetStatus.DELETED).stream();
|
||||
return this.db.selectFrom('asset').select(['id']).where('status', '=', AssetStatus.Deleted).stream();
|
||||
}
|
||||
|
||||
@GenerateSql({ params: [DummyValue.UUID] })
|
||||
@@ -16,8 +16,8 @@ export class TrashRepository {
|
||||
const { numUpdatedRows } = await this.db
|
||||
.updateTable('asset')
|
||||
.where('ownerId', '=', userId)
|
||||
.where('status', '=', AssetStatus.TRASHED)
|
||||
.set({ status: AssetStatus.ACTIVE, deletedAt: null })
|
||||
.where('status', '=', AssetStatus.Trashed)
|
||||
.set({ status: AssetStatus.Active, deletedAt: null })
|
||||
.executeTakeFirst();
|
||||
|
||||
return Number(numUpdatedRows);
|
||||
@@ -28,8 +28,8 @@ export class TrashRepository {
|
||||
const { numUpdatedRows } = await this.db
|
||||
.updateTable('asset')
|
||||
.where('ownerId', '=', userId)
|
||||
.where('status', '=', AssetStatus.TRASHED)
|
||||
.set({ status: AssetStatus.DELETED })
|
||||
.where('status', '=', AssetStatus.Trashed)
|
||||
.set({ status: AssetStatus.Deleted })
|
||||
.executeTakeFirst();
|
||||
|
||||
return Number(numUpdatedRows);
|
||||
@@ -43,9 +43,9 @@ export class TrashRepository {
|
||||
|
||||
const { numUpdatedRows } = await this.db
|
||||
.updateTable('asset')
|
||||
.where('status', '=', AssetStatus.TRASHED)
|
||||
.where('status', '=', AssetStatus.Trashed)
|
||||
.where('id', 'in', ids)
|
||||
.set({ status: AssetStatus.ACTIVE, deletedAt: null })
|
||||
.set({ status: AssetStatus.Active, deletedAt: null })
|
||||
.executeTakeFirst();
|
||||
|
||||
return Number(numUpdatedRows);
|
||||
|
||||
@@ -79,6 +79,16 @@ export class UserRepository {
|
||||
.executeTakeFirst();
|
||||
}
|
||||
|
||||
@GenerateSql()
|
||||
getFileSamples() {
|
||||
return this.db
|
||||
.selectFrom('user')
|
||||
.select(['id', 'profileImagePath'])
|
||||
.where('profileImagePath', '!=', sql.lit(''))
|
||||
.limit(sql.lit(3))
|
||||
.execute();
|
||||
}
|
||||
|
||||
@GenerateSql()
|
||||
async hasAdmin(): Promise<boolean> {
|
||||
const admin = await this.db
|
||||
@@ -187,7 +197,7 @@ export class UserRepository {
|
||||
restore(id: string) {
|
||||
return this.db
|
||||
.updateTable('user')
|
||||
.set({ status: UserStatus.ACTIVE, deletedAt: null })
|
||||
.set({ status: UserStatus.Active, deletedAt: null })
|
||||
.where('user.id', '=', asUuid(id))
|
||||
.returning(columns.userAdmin)
|
||||
.returning(withMetadata)
|
||||
@@ -229,8 +239,8 @@ export class UserRepository {
|
||||
.countAll<number>()
|
||||
.filterWhere((eb) =>
|
||||
eb.and([
|
||||
eb('asset.type', '=', sql.lit(AssetType.IMAGE)),
|
||||
eb('asset.visibility', '!=', sql.lit(AssetVisibility.HIDDEN)),
|
||||
eb('asset.type', '=', sql.lit(AssetType.Image)),
|
||||
eb('asset.visibility', '!=', sql.lit(AssetVisibility.Hidden)),
|
||||
]),
|
||||
)
|
||||
.as('photos'),
|
||||
@@ -238,8 +248,8 @@ export class UserRepository {
|
||||
.countAll<number>()
|
||||
.filterWhere((eb) =>
|
||||
eb.and([
|
||||
eb('asset.type', '=', sql.lit(AssetType.VIDEO)),
|
||||
eb('asset.visibility', '!=', sql.lit(AssetVisibility.HIDDEN)),
|
||||
eb('asset.type', '=', sql.lit(AssetType.Video)),
|
||||
eb('asset.visibility', '!=', sql.lit(AssetVisibility.Hidden)),
|
||||
]),
|
||||
)
|
||||
.as('videos'),
|
||||
@@ -254,7 +264,7 @@ export class UserRepository {
|
||||
eb.fn
|
||||
.sum<number>('asset_exif.fileSizeInByte')
|
||||
.filterWhere((eb) =>
|
||||
eb.and([eb('asset.libraryId', 'is', null), eb('asset.type', '=', sql.lit(AssetType.IMAGE))]),
|
||||
eb.and([eb('asset.libraryId', 'is', null), eb('asset.type', '=', sql.lit(AssetType.Image))]),
|
||||
),
|
||||
eb.lit(0),
|
||||
)
|
||||
@@ -264,7 +274,7 @@ export class UserRepository {
|
||||
eb.fn
|
||||
.sum<number>('asset_exif.fileSizeInByte')
|
||||
.filterWhere((eb) =>
|
||||
eb.and([eb('asset.libraryId', 'is', null), eb('asset.type', '=', sql.lit(AssetType.VIDEO))]),
|
||||
eb.and([eb('asset.libraryId', 'is', null), eb('asset.type', '=', sql.lit(AssetType.Video))]),
|
||||
),
|
||||
eb.lit(0),
|
||||
)
|
||||
|
||||
@@ -15,7 +15,7 @@ export class ViewRepository {
|
||||
.select((eb) => eb.fn<string>('substring', ['asset.originalPath', eb.val('^(.*/)[^/]*$')]).as('directoryPath'))
|
||||
.distinct()
|
||||
.where('ownerId', '=', asUuid(userId))
|
||||
.where('visibility', '=', AssetVisibility.TIMELINE)
|
||||
.where('visibility', '=', AssetVisibility.Timeline)
|
||||
.where('deletedAt', 'is', null)
|
||||
.where('fileCreatedAt', 'is not', null)
|
||||
.where('fileModifiedAt', 'is not', null)
|
||||
@@ -34,7 +34,7 @@ export class ViewRepository {
|
||||
.selectAll('asset')
|
||||
.$call(withExif)
|
||||
.where('ownerId', '=', asUuid(userId))
|
||||
.where('visibility', '=', AssetVisibility.TIMELINE)
|
||||
.where('visibility', '=', AssetVisibility.Timeline)
|
||||
.where('deletedAt', 'is', null)
|
||||
.where('fileCreatedAt', 'is not', null)
|
||||
.where('fileModifiedAt', 'is not', null)
|
||||
|
||||
@@ -229,3 +229,16 @@ export const user_metadata_audit = registerFunction({
|
||||
RETURN NULL;
|
||||
END`,
|
||||
});
|
||||
|
||||
export const asset_face_audit = registerFunction({
|
||||
name: 'asset_face_audit',
|
||||
returnType: 'TRIGGER',
|
||||
language: 'PLPGSQL',
|
||||
body: `
|
||||
BEGIN
|
||||
INSERT INTO asset_face_audit ("assetFaceId", "assetId")
|
||||
SELECT "id", "assetId"
|
||||
FROM OLD;
|
||||
RETURN NULL;
|
||||
END`,
|
||||
});
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
album_user_after_insert,
|
||||
album_user_delete_audit,
|
||||
asset_delete_audit,
|
||||
asset_face_audit,
|
||||
f_concat_ws,
|
||||
f_unaccent,
|
||||
immich_uuid_v7,
|
||||
@@ -27,6 +28,7 @@ import { AlbumTable } from 'src/schema/tables/album.table';
|
||||
import { ApiKeyTable } from 'src/schema/tables/api-key.table';
|
||||
import { AssetAuditTable } from 'src/schema/tables/asset-audit.table';
|
||||
import { AssetExifTable } from 'src/schema/tables/asset-exif.table';
|
||||
import { AssetFaceAuditTable } from 'src/schema/tables/asset-face-audit.table';
|
||||
import { AssetFaceTable } from 'src/schema/tables/asset-face.table';
|
||||
import { AssetFileTable } from 'src/schema/tables/asset-file.table';
|
||||
import { AssetJobStatusTable } from 'src/schema/tables/asset-job-status.table';
|
||||
@@ -78,6 +80,7 @@ export class ImmichDatabase {
|
||||
ApiKeyTable,
|
||||
AssetAuditTable,
|
||||
AssetFaceTable,
|
||||
AssetFaceAuditTable,
|
||||
AssetJobStatusTable,
|
||||
AssetTable,
|
||||
AssetFileTable,
|
||||
@@ -132,6 +135,7 @@ export class ImmichDatabase {
|
||||
stack_delete_audit,
|
||||
person_delete_audit,
|
||||
user_metadata_audit,
|
||||
asset_face_audit,
|
||||
];
|
||||
|
||||
enum = [assets_status_enum, asset_face_source_type, asset_visibility_enum];
|
||||
@@ -158,6 +162,7 @@ export interface DB {
|
||||
asset: AssetTable;
|
||||
asset_exif: AssetExifTable;
|
||||
asset_face: AssetFaceTable;
|
||||
asset_face_audit: AssetFaceAuditTable;
|
||||
asset_file: AssetFileTable;
|
||||
asset_job_status: AssetJobStatusTable;
|
||||
asset_audit: AssetAuditTable;
|
||||
|
||||
@@ -16,9 +16,7 @@ export async function up(db: Kysely<any>): Promise<void> {
|
||||
rows: [lastMigration],
|
||||
} = await lastMigrationSql.execute(db);
|
||||
if (lastMigration?.name !== 'AddMissingIndex1744910873956') {
|
||||
throw new Error(
|
||||
'Invalid upgrade path. For more information, see https://immich.app/errors#typeorm-upgrade',
|
||||
);
|
||||
throw new Error('Invalid upgrade path. For more information, see https://immich.app/errors#typeorm-upgrade');
|
||||
}
|
||||
logger.log('Database has up to date TypeORM migrations, skipping initial Kysely migration');
|
||||
return;
|
||||
@@ -108,152 +106,344 @@ export async function up(db: Kysely<any>): Promise<void> {
|
||||
RETURN NULL;
|
||||
END;
|
||||
$$;`.execute(db);
|
||||
if (vectorExtension === DatabaseExtension.VECTORS) {
|
||||
if (vectorExtension === DatabaseExtension.Vectors) {
|
||||
await sql`SET search_path TO "$user", public, vectors`.execute(db);
|
||||
}
|
||||
await sql`CREATE TYPE "assets_status_enum" AS ENUM ('active','trashed','deleted');`.execute(db);
|
||||
await sql`CREATE TYPE "sourcetype" AS ENUM ('machine-learning','exif','manual');`.execute(db);
|
||||
await sql`CREATE TABLE "users" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "email" character varying NOT NULL, "password" character varying NOT NULL DEFAULT '', "createdAt" timestamp with time zone NOT NULL DEFAULT now(), "profileImagePath" character varying NOT NULL DEFAULT '', "isAdmin" boolean NOT NULL DEFAULT false, "shouldChangePassword" boolean NOT NULL DEFAULT true, "deletedAt" timestamp with time zone, "oauthId" character varying NOT NULL DEFAULT '', "updatedAt" timestamp with time zone NOT NULL DEFAULT now(), "storageLabel" character varying, "name" character varying NOT NULL DEFAULT '', "quotaSizeInBytes" bigint, "quotaUsageInBytes" bigint NOT NULL DEFAULT 0, "status" character varying NOT NULL DEFAULT 'active', "profileChangedAt" timestamp with time zone NOT NULL DEFAULT now(), "updateId" uuid NOT NULL DEFAULT immich_uuid_v7());`.execute(db);
|
||||
await sql`CREATE TABLE "libraries" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "name" character varying NOT NULL, "ownerId" uuid NOT NULL, "importPaths" text[] NOT NULL, "exclusionPatterns" text[] NOT NULL, "createdAt" timestamp with time zone NOT NULL DEFAULT now(), "updatedAt" timestamp with time zone NOT NULL DEFAULT now(), "deletedAt" timestamp with time zone, "refreshedAt" timestamp with time zone, "updateId" uuid NOT NULL DEFAULT immich_uuid_v7());`.execute(db);
|
||||
await sql`CREATE TABLE "asset_stack" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "primaryAssetId" uuid NOT NULL, "ownerId" uuid NOT NULL);`.execute(db);
|
||||
await sql`CREATE TABLE "assets" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "deviceAssetId" character varying NOT NULL, "ownerId" uuid NOT NULL, "deviceId" character varying NOT NULL, "type" character varying NOT NULL, "originalPath" character varying NOT NULL, "fileCreatedAt" timestamp with time zone NOT NULL, "fileModifiedAt" timestamp with time zone NOT NULL, "isFavorite" boolean NOT NULL DEFAULT false, "duration" character varying, "encodedVideoPath" character varying DEFAULT '', "checksum" bytea NOT NULL, "isVisible" boolean NOT NULL DEFAULT true, "livePhotoVideoId" uuid, "updatedAt" timestamp with time zone NOT NULL DEFAULT now(), "createdAt" timestamp with time zone NOT NULL DEFAULT now(), "isArchived" boolean NOT NULL DEFAULT false, "originalFileName" character varying NOT NULL, "sidecarPath" character varying, "thumbhash" bytea, "isOffline" boolean NOT NULL DEFAULT false, "libraryId" uuid, "isExternal" boolean NOT NULL DEFAULT false, "deletedAt" timestamp with time zone, "localDateTime" timestamp with time zone NOT NULL, "stackId" uuid, "duplicateId" uuid, "status" assets_status_enum NOT NULL DEFAULT 'active', "updateId" uuid NOT NULL DEFAULT immich_uuid_v7());`.execute(db);
|
||||
await sql`CREATE TABLE "albums" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "ownerId" uuid NOT NULL, "albumName" character varying NOT NULL DEFAULT 'Untitled Album', "createdAt" timestamp with time zone NOT NULL DEFAULT now(), "albumThumbnailAssetId" uuid, "updatedAt" timestamp with time zone NOT NULL DEFAULT now(), "description" text NOT NULL DEFAULT '', "deletedAt" timestamp with time zone, "isActivityEnabled" boolean NOT NULL DEFAULT true, "order" character varying NOT NULL DEFAULT 'desc', "updateId" uuid NOT NULL DEFAULT immich_uuid_v7());`.execute(db);
|
||||
await sql`CREATE TABLE "users" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "email" character varying NOT NULL, "password" character varying NOT NULL DEFAULT '', "createdAt" timestamp with time zone NOT NULL DEFAULT now(), "profileImagePath" character varying NOT NULL DEFAULT '', "isAdmin" boolean NOT NULL DEFAULT false, "shouldChangePassword" boolean NOT NULL DEFAULT true, "deletedAt" timestamp with time zone, "oauthId" character varying NOT NULL DEFAULT '', "updatedAt" timestamp with time zone NOT NULL DEFAULT now(), "storageLabel" character varying, "name" character varying NOT NULL DEFAULT '', "quotaSizeInBytes" bigint, "quotaUsageInBytes" bigint NOT NULL DEFAULT 0, "status" character varying NOT NULL DEFAULT 'active', "profileChangedAt" timestamp with time zone NOT NULL DEFAULT now(), "updateId" uuid NOT NULL DEFAULT immich_uuid_v7());`.execute(
|
||||
db,
|
||||
);
|
||||
await sql`CREATE TABLE "libraries" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "name" character varying NOT NULL, "ownerId" uuid NOT NULL, "importPaths" text[] NOT NULL, "exclusionPatterns" text[] NOT NULL, "createdAt" timestamp with time zone NOT NULL DEFAULT now(), "updatedAt" timestamp with time zone NOT NULL DEFAULT now(), "deletedAt" timestamp with time zone, "refreshedAt" timestamp with time zone, "updateId" uuid NOT NULL DEFAULT immich_uuid_v7());`.execute(
|
||||
db,
|
||||
);
|
||||
await sql`CREATE TABLE "asset_stack" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "primaryAssetId" uuid NOT NULL, "ownerId" uuid NOT NULL);`.execute(
|
||||
db,
|
||||
);
|
||||
await sql`CREATE TABLE "assets" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "deviceAssetId" character varying NOT NULL, "ownerId" uuid NOT NULL, "deviceId" character varying NOT NULL, "type" character varying NOT NULL, "originalPath" character varying NOT NULL, "fileCreatedAt" timestamp with time zone NOT NULL, "fileModifiedAt" timestamp with time zone NOT NULL, "isFavorite" boolean NOT NULL DEFAULT false, "duration" character varying, "encodedVideoPath" character varying DEFAULT '', "checksum" bytea NOT NULL, "isVisible" boolean NOT NULL DEFAULT true, "livePhotoVideoId" uuid, "updatedAt" timestamp with time zone NOT NULL DEFAULT now(), "createdAt" timestamp with time zone NOT NULL DEFAULT now(), "isArchived" boolean NOT NULL DEFAULT false, "originalFileName" character varying NOT NULL, "sidecarPath" character varying, "thumbhash" bytea, "isOffline" boolean NOT NULL DEFAULT false, "libraryId" uuid, "isExternal" boolean NOT NULL DEFAULT false, "deletedAt" timestamp with time zone, "localDateTime" timestamp with time zone NOT NULL, "stackId" uuid, "duplicateId" uuid, "status" assets_status_enum NOT NULL DEFAULT 'active', "updateId" uuid NOT NULL DEFAULT immich_uuid_v7());`.execute(
|
||||
db,
|
||||
);
|
||||
await sql`CREATE TABLE "albums" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "ownerId" uuid NOT NULL, "albumName" character varying NOT NULL DEFAULT 'Untitled Album', "createdAt" timestamp with time zone NOT NULL DEFAULT now(), "albumThumbnailAssetId" uuid, "updatedAt" timestamp with time zone NOT NULL DEFAULT now(), "description" text NOT NULL DEFAULT '', "deletedAt" timestamp with time zone, "isActivityEnabled" boolean NOT NULL DEFAULT true, "order" character varying NOT NULL DEFAULT 'desc', "updateId" uuid NOT NULL DEFAULT immich_uuid_v7());`.execute(
|
||||
db,
|
||||
);
|
||||
await sql`COMMENT ON COLUMN "albums"."albumThumbnailAssetId" IS 'Asset ID to be used as thumbnail';`.execute(db);
|
||||
await sql`CREATE TABLE "activity" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" timestamp with time zone NOT NULL DEFAULT now(), "updatedAt" timestamp with time zone NOT NULL DEFAULT now(), "albumId" uuid NOT NULL, "userId" uuid NOT NULL, "assetId" uuid, "comment" text, "isLiked" boolean NOT NULL DEFAULT false, "updateId" uuid NOT NULL DEFAULT immich_uuid_v7());`.execute(db);
|
||||
await sql`CREATE TABLE "albums_assets_assets" ("albumsId" uuid NOT NULL, "assetsId" uuid NOT NULL, "createdAt" timestamp with time zone NOT NULL DEFAULT now());`.execute(db);
|
||||
await sql`CREATE TABLE "albums_shared_users_users" ("albumsId" uuid NOT NULL, "usersId" uuid NOT NULL, "role" character varying NOT NULL DEFAULT 'editor');`.execute(db);
|
||||
await sql`CREATE TABLE "api_keys" ("name" character varying NOT NULL, "key" character varying NOT NULL, "userId" uuid NOT NULL, "createdAt" timestamp with time zone NOT NULL DEFAULT now(), "updatedAt" timestamp with time zone NOT NULL DEFAULT now(), "id" uuid NOT NULL DEFAULT uuid_generate_v4(), "permissions" character varying[] NOT NULL, "updateId" uuid NOT NULL DEFAULT immich_uuid_v7());`.execute(db);
|
||||
await sql`CREATE TABLE "assets_audit" ("id" uuid NOT NULL DEFAULT immich_uuid_v7(), "assetId" uuid NOT NULL, "ownerId" uuid NOT NULL, "deletedAt" timestamp with time zone NOT NULL DEFAULT clock_timestamp());`.execute(db);
|
||||
await sql`CREATE TABLE "person" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" timestamp with time zone NOT NULL DEFAULT now(), "updatedAt" timestamp with time zone NOT NULL DEFAULT now(), "ownerId" uuid NOT NULL, "name" character varying NOT NULL DEFAULT '', "thumbnailPath" character varying NOT NULL DEFAULT '', "isHidden" boolean NOT NULL DEFAULT false, "birthDate" date, "faceAssetId" uuid, "isFavorite" boolean NOT NULL DEFAULT false, "color" character varying, "updateId" uuid NOT NULL DEFAULT immich_uuid_v7());`.execute(db);
|
||||
await sql`CREATE TABLE "asset_faces" ("assetId" uuid NOT NULL, "personId" uuid, "imageWidth" integer NOT NULL DEFAULT 0, "imageHeight" integer NOT NULL DEFAULT 0, "boundingBoxX1" integer NOT NULL DEFAULT 0, "boundingBoxY1" integer NOT NULL DEFAULT 0, "boundingBoxX2" integer NOT NULL DEFAULT 0, "boundingBoxY2" integer NOT NULL DEFAULT 0, "id" uuid NOT NULL DEFAULT uuid_generate_v4(), "sourceType" sourcetype NOT NULL DEFAULT 'machine-learning', "deletedAt" timestamp with time zone);`.execute(db);
|
||||
await sql`CREATE TABLE "asset_files" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "assetId" uuid NOT NULL, "createdAt" timestamp with time zone NOT NULL DEFAULT now(), "updatedAt" timestamp with time zone NOT NULL DEFAULT now(), "type" character varying NOT NULL, "path" character varying NOT NULL, "updateId" uuid NOT NULL DEFAULT immich_uuid_v7());`.execute(db);
|
||||
await sql`CREATE TABLE "asset_job_status" ("assetId" uuid NOT NULL, "facesRecognizedAt" timestamp with time zone, "metadataExtractedAt" timestamp with time zone, "duplicatesDetectedAt" timestamp with time zone, "previewAt" timestamp with time zone, "thumbnailAt" timestamp with time zone);`.execute(db);
|
||||
await sql`CREATE TABLE "audit" ("id" serial NOT NULL, "entityType" character varying NOT NULL, "entityId" uuid NOT NULL, "action" character varying NOT NULL, "ownerId" uuid NOT NULL, "createdAt" timestamp with time zone NOT NULL DEFAULT now());`.execute(db);
|
||||
await sql`CREATE TABLE "exif" ("assetId" uuid NOT NULL, "make" character varying, "model" character varying, "exifImageWidth" integer, "exifImageHeight" integer, "fileSizeInByte" bigint, "orientation" character varying, "dateTimeOriginal" timestamp with time zone, "modifyDate" timestamp with time zone, "lensModel" character varying, "fNumber" double precision, "focalLength" double precision, "iso" integer, "latitude" double precision, "longitude" double precision, "city" character varying, "state" character varying, "country" character varying, "description" text NOT NULL DEFAULT '', "fps" double precision, "exposureTime" character varying, "livePhotoCID" character varying, "timeZone" character varying, "projectionType" character varying, "profileDescription" character varying, "colorspace" character varying, "bitsPerSample" integer, "autoStackId" character varying, "rating" integer, "updatedAt" timestamp with time zone NOT NULL DEFAULT clock_timestamp(), "updateId" uuid NOT NULL DEFAULT immich_uuid_v7());`.execute(db);
|
||||
await sql`CREATE TABLE "activity" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" timestamp with time zone NOT NULL DEFAULT now(), "updatedAt" timestamp with time zone NOT NULL DEFAULT now(), "albumId" uuid NOT NULL, "userId" uuid NOT NULL, "assetId" uuid, "comment" text, "isLiked" boolean NOT NULL DEFAULT false, "updateId" uuid NOT NULL DEFAULT immich_uuid_v7());`.execute(
|
||||
db,
|
||||
);
|
||||
await sql`CREATE TABLE "albums_assets_assets" ("albumsId" uuid NOT NULL, "assetsId" uuid NOT NULL, "createdAt" timestamp with time zone NOT NULL DEFAULT now());`.execute(
|
||||
db,
|
||||
);
|
||||
await sql`CREATE TABLE "albums_shared_users_users" ("albumsId" uuid NOT NULL, "usersId" uuid NOT NULL, "role" character varying NOT NULL DEFAULT 'editor');`.execute(
|
||||
db,
|
||||
);
|
||||
await sql`CREATE TABLE "api_keys" ("name" character varying NOT NULL, "key" character varying NOT NULL, "userId" uuid NOT NULL, "createdAt" timestamp with time zone NOT NULL DEFAULT now(), "updatedAt" timestamp with time zone NOT NULL DEFAULT now(), "id" uuid NOT NULL DEFAULT uuid_generate_v4(), "permissions" character varying[] NOT NULL, "updateId" uuid NOT NULL DEFAULT immich_uuid_v7());`.execute(
|
||||
db,
|
||||
);
|
||||
await sql`CREATE TABLE "assets_audit" ("id" uuid NOT NULL DEFAULT immich_uuid_v7(), "assetId" uuid NOT NULL, "ownerId" uuid NOT NULL, "deletedAt" timestamp with time zone NOT NULL DEFAULT clock_timestamp());`.execute(
|
||||
db,
|
||||
);
|
||||
await sql`CREATE TABLE "person" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" timestamp with time zone NOT NULL DEFAULT now(), "updatedAt" timestamp with time zone NOT NULL DEFAULT now(), "ownerId" uuid NOT NULL, "name" character varying NOT NULL DEFAULT '', "thumbnailPath" character varying NOT NULL DEFAULT '', "isHidden" boolean NOT NULL DEFAULT false, "birthDate" date, "faceAssetId" uuid, "isFavorite" boolean NOT NULL DEFAULT false, "color" character varying, "updateId" uuid NOT NULL DEFAULT immich_uuid_v7());`.execute(
|
||||
db,
|
||||
);
|
||||
await sql`CREATE TABLE "asset_faces" ("assetId" uuid NOT NULL, "personId" uuid, "imageWidth" integer NOT NULL DEFAULT 0, "imageHeight" integer NOT NULL DEFAULT 0, "boundingBoxX1" integer NOT NULL DEFAULT 0, "boundingBoxY1" integer NOT NULL DEFAULT 0, "boundingBoxX2" integer NOT NULL DEFAULT 0, "boundingBoxY2" integer NOT NULL DEFAULT 0, "id" uuid NOT NULL DEFAULT uuid_generate_v4(), "sourceType" sourcetype NOT NULL DEFAULT 'machine-learning', "deletedAt" timestamp with time zone);`.execute(
|
||||
db,
|
||||
);
|
||||
await sql`CREATE TABLE "asset_files" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "assetId" uuid NOT NULL, "createdAt" timestamp with time zone NOT NULL DEFAULT now(), "updatedAt" timestamp with time zone NOT NULL DEFAULT now(), "type" character varying NOT NULL, "path" character varying NOT NULL, "updateId" uuid NOT NULL DEFAULT immich_uuid_v7());`.execute(
|
||||
db,
|
||||
);
|
||||
await sql`CREATE TABLE "asset_job_status" ("assetId" uuid NOT NULL, "facesRecognizedAt" timestamp with time zone, "metadataExtractedAt" timestamp with time zone, "duplicatesDetectedAt" timestamp with time zone, "previewAt" timestamp with time zone, "thumbnailAt" timestamp with time zone);`.execute(
|
||||
db,
|
||||
);
|
||||
await sql`CREATE TABLE "audit" ("id" serial NOT NULL, "entityType" character varying NOT NULL, "entityId" uuid NOT NULL, "action" character varying NOT NULL, "ownerId" uuid NOT NULL, "createdAt" timestamp with time zone NOT NULL DEFAULT now());`.execute(
|
||||
db,
|
||||
);
|
||||
await sql`CREATE TABLE "exif" ("assetId" uuid NOT NULL, "make" character varying, "model" character varying, "exifImageWidth" integer, "exifImageHeight" integer, "fileSizeInByte" bigint, "orientation" character varying, "dateTimeOriginal" timestamp with time zone, "modifyDate" timestamp with time zone, "lensModel" character varying, "fNumber" double precision, "focalLength" double precision, "iso" integer, "latitude" double precision, "longitude" double precision, "city" character varying, "state" character varying, "country" character varying, "description" text NOT NULL DEFAULT '', "fps" double precision, "exposureTime" character varying, "livePhotoCID" character varying, "timeZone" character varying, "projectionType" character varying, "profileDescription" character varying, "colorspace" character varying, "bitsPerSample" integer, "autoStackId" character varying, "rating" integer, "updatedAt" timestamp with time zone NOT NULL DEFAULT clock_timestamp(), "updateId" uuid NOT NULL DEFAULT immich_uuid_v7());`.execute(
|
||||
db,
|
||||
);
|
||||
await sql`CREATE TABLE "face_search" ("faceId" uuid NOT NULL, "embedding" vector(512) NOT NULL);`.execute(db);
|
||||
await sql`CREATE TABLE "geodata_places" ("id" integer NOT NULL, "name" character varying(200) NOT NULL, "longitude" double precision NOT NULL, "latitude" double precision NOT NULL, "countryCode" character(2) NOT NULL, "admin1Code" character varying(20), "admin2Code" character varying(80), "modificationDate" date NOT NULL, "admin1Name" character varying, "admin2Name" character varying, "alternateNames" character varying);`.execute(db);
|
||||
await sql`CREATE TABLE "memories" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" timestamp with time zone NOT NULL DEFAULT now(), "updatedAt" timestamp with time zone NOT NULL DEFAULT now(), "deletedAt" timestamp with time zone, "ownerId" uuid NOT NULL, "type" character varying NOT NULL, "data" jsonb NOT NULL, "isSaved" boolean NOT NULL DEFAULT false, "memoryAt" timestamp with time zone NOT NULL, "seenAt" timestamp with time zone, "showAt" timestamp with time zone, "hideAt" timestamp with time zone, "updateId" uuid NOT NULL DEFAULT immich_uuid_v7());`.execute(db);
|
||||
await sql`CREATE TABLE "geodata_places" ("id" integer NOT NULL, "name" character varying(200) NOT NULL, "longitude" double precision NOT NULL, "latitude" double precision NOT NULL, "countryCode" character(2) NOT NULL, "admin1Code" character varying(20), "admin2Code" character varying(80), "modificationDate" date NOT NULL, "admin1Name" character varying, "admin2Name" character varying, "alternateNames" character varying);`.execute(
|
||||
db,
|
||||
);
|
||||
await sql`CREATE TABLE "memories" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" timestamp with time zone NOT NULL DEFAULT now(), "updatedAt" timestamp with time zone NOT NULL DEFAULT now(), "deletedAt" timestamp with time zone, "ownerId" uuid NOT NULL, "type" character varying NOT NULL, "data" jsonb NOT NULL, "isSaved" boolean NOT NULL DEFAULT false, "memoryAt" timestamp with time zone NOT NULL, "seenAt" timestamp with time zone, "showAt" timestamp with time zone, "hideAt" timestamp with time zone, "updateId" uuid NOT NULL DEFAULT immich_uuid_v7());`.execute(
|
||||
db,
|
||||
);
|
||||
await sql`CREATE TABLE "memories_assets_assets" ("memoriesId" uuid NOT NULL, "assetsId" uuid NOT NULL);`.execute(db);
|
||||
await sql`CREATE TABLE "move_history" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "entityId" uuid NOT NULL, "pathType" character varying NOT NULL, "oldPath" character varying NOT NULL, "newPath" character varying NOT NULL);`.execute(db);
|
||||
await sql`CREATE TABLE "naturalearth_countries" ("id" integer NOT NULL GENERATED ALWAYS AS IDENTITY, "admin" character varying(50) NOT NULL, "admin_a3" character varying(3) NOT NULL, "type" character varying(50) NOT NULL, "coordinates" polygon NOT NULL);`.execute(db);
|
||||
await sql`CREATE TABLE "partners_audit" ("id" uuid NOT NULL DEFAULT immich_uuid_v7(), "sharedById" uuid NOT NULL, "sharedWithId" uuid NOT NULL, "deletedAt" timestamp with time zone NOT NULL DEFAULT clock_timestamp());`.execute(db);
|
||||
await sql`CREATE TABLE "partners" ("sharedById" uuid NOT NULL, "sharedWithId" uuid NOT NULL, "createdAt" timestamp with time zone NOT NULL DEFAULT now(), "updatedAt" timestamp with time zone NOT NULL DEFAULT now(), "inTimeline" boolean NOT NULL DEFAULT false, "updateId" uuid NOT NULL DEFAULT immich_uuid_v7());`.execute(db);
|
||||
await sql`CREATE TABLE "sessions" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "token" character varying NOT NULL, "createdAt" timestamp with time zone NOT NULL DEFAULT now(), "updatedAt" timestamp with time zone NOT NULL DEFAULT now(), "userId" uuid NOT NULL, "deviceType" character varying NOT NULL DEFAULT '', "deviceOS" character varying NOT NULL DEFAULT '', "updateId" uuid NOT NULL DEFAULT immich_uuid_v7());`.execute(db);
|
||||
await sql`CREATE TABLE "shared_links" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "description" character varying, "userId" uuid NOT NULL, "key" bytea NOT NULL, "type" character varying NOT NULL, "createdAt" timestamp with time zone NOT NULL DEFAULT now(), "expiresAt" timestamp with time zone, "allowUpload" boolean NOT NULL DEFAULT false, "albumId" uuid, "allowDownload" boolean NOT NULL DEFAULT true, "showExif" boolean NOT NULL DEFAULT true, "password" character varying);`.execute(db);
|
||||
await sql`CREATE TABLE "move_history" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "entityId" uuid NOT NULL, "pathType" character varying NOT NULL, "oldPath" character varying NOT NULL, "newPath" character varying NOT NULL);`.execute(
|
||||
db,
|
||||
);
|
||||
await sql`CREATE TABLE "naturalearth_countries" ("id" integer NOT NULL GENERATED ALWAYS AS IDENTITY, "admin" character varying(50) NOT NULL, "admin_a3" character varying(3) NOT NULL, "type" character varying(50) NOT NULL, "coordinates" polygon NOT NULL);`.execute(
|
||||
db,
|
||||
);
|
||||
await sql`CREATE TABLE "partners_audit" ("id" uuid NOT NULL DEFAULT immich_uuid_v7(), "sharedById" uuid NOT NULL, "sharedWithId" uuid NOT NULL, "deletedAt" timestamp with time zone NOT NULL DEFAULT clock_timestamp());`.execute(
|
||||
db,
|
||||
);
|
||||
await sql`CREATE TABLE "partners" ("sharedById" uuid NOT NULL, "sharedWithId" uuid NOT NULL, "createdAt" timestamp with time zone NOT NULL DEFAULT now(), "updatedAt" timestamp with time zone NOT NULL DEFAULT now(), "inTimeline" boolean NOT NULL DEFAULT false, "updateId" uuid NOT NULL DEFAULT immich_uuid_v7());`.execute(
|
||||
db,
|
||||
);
|
||||
await sql`CREATE TABLE "sessions" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "token" character varying NOT NULL, "createdAt" timestamp with time zone NOT NULL DEFAULT now(), "updatedAt" timestamp with time zone NOT NULL DEFAULT now(), "userId" uuid NOT NULL, "deviceType" character varying NOT NULL DEFAULT '', "deviceOS" character varying NOT NULL DEFAULT '', "updateId" uuid NOT NULL DEFAULT immich_uuid_v7());`.execute(
|
||||
db,
|
||||
);
|
||||
await sql`CREATE TABLE "shared_links" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "description" character varying, "userId" uuid NOT NULL, "key" bytea NOT NULL, "type" character varying NOT NULL, "createdAt" timestamp with time zone NOT NULL DEFAULT now(), "expiresAt" timestamp with time zone, "allowUpload" boolean NOT NULL DEFAULT false, "albumId" uuid, "allowDownload" boolean NOT NULL DEFAULT true, "showExif" boolean NOT NULL DEFAULT true, "password" character varying);`.execute(
|
||||
db,
|
||||
);
|
||||
await sql`CREATE TABLE "shared_link__asset" ("assetsId" uuid NOT NULL, "sharedLinksId" uuid NOT NULL);`.execute(db);
|
||||
await sql`CREATE TABLE "smart_search" ("assetId" uuid NOT NULL, "embedding" vector(512) NOT NULL);`.execute(db);
|
||||
await sql`ALTER TABLE "smart_search" ALTER COLUMN "embedding" SET STORAGE EXTERNAL;`.execute(db);
|
||||
await sql`CREATE TABLE "session_sync_checkpoints" ("sessionId" uuid NOT NULL, "type" character varying NOT NULL, "createdAt" timestamp with time zone NOT NULL DEFAULT now(), "updatedAt" timestamp with time zone NOT NULL DEFAULT now(), "ack" character varying NOT NULL, "updateId" uuid NOT NULL DEFAULT immich_uuid_v7());`.execute(db);
|
||||
await sql`CREATE TABLE "session_sync_checkpoints" ("sessionId" uuid NOT NULL, "type" character varying NOT NULL, "createdAt" timestamp with time zone NOT NULL DEFAULT now(), "updatedAt" timestamp with time zone NOT NULL DEFAULT now(), "ack" character varying NOT NULL, "updateId" uuid NOT NULL DEFAULT immich_uuid_v7());`.execute(
|
||||
db,
|
||||
);
|
||||
await sql`CREATE TABLE "system_metadata" ("key" character varying NOT NULL, "value" jsonb NOT NULL);`.execute(db);
|
||||
await sql`CREATE TABLE "tags" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "userId" uuid NOT NULL, "value" character varying NOT NULL, "createdAt" timestamp with time zone NOT NULL DEFAULT now(), "updatedAt" timestamp with time zone NOT NULL DEFAULT now(), "color" character varying, "parentId" uuid, "updateId" uuid NOT NULL DEFAULT immich_uuid_v7());`.execute(db);
|
||||
await sql`CREATE TABLE "tags" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "userId" uuid NOT NULL, "value" character varying NOT NULL, "createdAt" timestamp with time zone NOT NULL DEFAULT now(), "updatedAt" timestamp with time zone NOT NULL DEFAULT now(), "color" character varying, "parentId" uuid, "updateId" uuid NOT NULL DEFAULT immich_uuid_v7());`.execute(
|
||||
db,
|
||||
);
|
||||
await sql`CREATE TABLE "tag_asset" ("assetsId" uuid NOT NULL, "tagsId" uuid NOT NULL);`.execute(db);
|
||||
await sql`CREATE TABLE "tags_closure" ("id_ancestor" uuid NOT NULL, "id_descendant" uuid NOT NULL);`.execute(db);
|
||||
await sql`CREATE TABLE "users_audit" ("userId" uuid NOT NULL, "deletedAt" timestamp with time zone NOT NULL DEFAULT clock_timestamp(), "id" uuid NOT NULL DEFAULT immich_uuid_v7());`.execute(db);
|
||||
await sql`CREATE TABLE "user_metadata" ("userId" uuid NOT NULL, "key" character varying NOT NULL, "value" jsonb NOT NULL);`.execute(db);
|
||||
await sql`CREATE TABLE "version_history" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" timestamp with time zone NOT NULL DEFAULT now(), "version" character varying NOT NULL);`.execute(db);
|
||||
await sql`CREATE TABLE "users_audit" ("userId" uuid NOT NULL, "deletedAt" timestamp with time zone NOT NULL DEFAULT clock_timestamp(), "id" uuid NOT NULL DEFAULT immich_uuid_v7());`.execute(
|
||||
db,
|
||||
);
|
||||
await sql`CREATE TABLE "user_metadata" ("userId" uuid NOT NULL, "key" character varying NOT NULL, "value" jsonb NOT NULL);`.execute(
|
||||
db,
|
||||
);
|
||||
await sql`CREATE TABLE "version_history" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" timestamp with time zone NOT NULL DEFAULT now(), "version" character varying NOT NULL);`.execute(
|
||||
db,
|
||||
);
|
||||
await sql`ALTER TABLE "users" ADD CONSTRAINT "PK_a3ffb1c0c8416b9fc6f907b7433" PRIMARY KEY ("id");`.execute(db);
|
||||
await sql`ALTER TABLE "libraries" ADD CONSTRAINT "PK_505fedfcad00a09b3734b4223de" PRIMARY KEY ("id");`.execute(db);
|
||||
await sql`ALTER TABLE "asset_stack" ADD CONSTRAINT "PK_74a27e7fcbd5852463d0af3034b" PRIMARY KEY ("id");`.execute(db);
|
||||
await sql`ALTER TABLE "assets" ADD CONSTRAINT "PK_da96729a8b113377cfb6a62439c" PRIMARY KEY ("id");`.execute(db);
|
||||
await sql`ALTER TABLE "albums" ADD CONSTRAINT "PK_7f71c7b5bc7c87b8f94c9a93a00" PRIMARY KEY ("id");`.execute(db);
|
||||
await sql`ALTER TABLE "activity" ADD CONSTRAINT "PK_24625a1d6b1b089c8ae206fe467" PRIMARY KEY ("id");`.execute(db);
|
||||
await sql`ALTER TABLE "albums_assets_assets" ADD CONSTRAINT "PK_c67bc36fa845fb7b18e0e398180" PRIMARY KEY ("albumsId", "assetsId");`.execute(db);
|
||||
await sql`ALTER TABLE "albums_shared_users_users" ADD CONSTRAINT "PK_7df55657e0b2e8b626330a0ebc8" PRIMARY KEY ("albumsId", "usersId");`.execute(db);
|
||||
await sql`ALTER TABLE "albums_assets_assets" ADD CONSTRAINT "PK_c67bc36fa845fb7b18e0e398180" PRIMARY KEY ("albumsId", "assetsId");`.execute(
|
||||
db,
|
||||
);
|
||||
await sql`ALTER TABLE "albums_shared_users_users" ADD CONSTRAINT "PK_7df55657e0b2e8b626330a0ebc8" PRIMARY KEY ("albumsId", "usersId");`.execute(
|
||||
db,
|
||||
);
|
||||
await sql`ALTER TABLE "api_keys" ADD CONSTRAINT "PK_5c8a79801b44bd27b79228e1dad" PRIMARY KEY ("id");`.execute(db);
|
||||
await sql`ALTER TABLE "assets_audit" ADD CONSTRAINT "PK_99bd5c015f81a641927a32b4212" PRIMARY KEY ("id");`.execute(db);
|
||||
await sql`ALTER TABLE "person" ADD CONSTRAINT "PK_5fdaf670315c4b7e70cce85daa3" PRIMARY KEY ("id");`.execute(db);
|
||||
await sql`ALTER TABLE "asset_faces" ADD CONSTRAINT "PK_6df76ab2eb6f5b57b7c2f1fc684" PRIMARY KEY ("id");`.execute(db);
|
||||
await sql`ALTER TABLE "asset_files" ADD CONSTRAINT "PK_c41dc3e9ef5e1c57ca5a08a0004" PRIMARY KEY ("id");`.execute(db);
|
||||
await sql`ALTER TABLE "asset_job_status" ADD CONSTRAINT "PK_420bec36fc02813bddf5c8b73d4" PRIMARY KEY ("assetId");`.execute(db);
|
||||
await sql`ALTER TABLE "asset_job_status" ADD CONSTRAINT "PK_420bec36fc02813bddf5c8b73d4" PRIMARY KEY ("assetId");`.execute(
|
||||
db,
|
||||
);
|
||||
await sql`ALTER TABLE "audit" ADD CONSTRAINT "PK_1d3d120ddaf7bc9b1ed68ed463a" PRIMARY KEY ("id");`.execute(db);
|
||||
await sql`ALTER TABLE "exif" ADD CONSTRAINT "PK_c0117fdbc50b917ef9067740c44" PRIMARY KEY ("assetId");`.execute(db);
|
||||
await sql`ALTER TABLE "face_search" ADD CONSTRAINT "face_search_pkey" PRIMARY KEY ("faceId");`.execute(db);
|
||||
await sql`ALTER TABLE "geodata_places" ADD CONSTRAINT "PK_c29918988912ef4036f3d7fbff4" PRIMARY KEY ("id");`.execute(db);
|
||||
await sql`ALTER TABLE "geodata_places" ADD CONSTRAINT "PK_c29918988912ef4036f3d7fbff4" PRIMARY KEY ("id");`.execute(
|
||||
db,
|
||||
);
|
||||
await sql`ALTER TABLE "memories" ADD CONSTRAINT "PK_aaa0692d9496fe827b0568612f8" PRIMARY KEY ("id");`.execute(db);
|
||||
await sql`ALTER TABLE "memories_assets_assets" ADD CONSTRAINT "PK_fcaf7112a013d1703c011c6793d" PRIMARY KEY ("memoriesId", "assetsId");`.execute(db);
|
||||
await sql`ALTER TABLE "memories_assets_assets" ADD CONSTRAINT "PK_fcaf7112a013d1703c011c6793d" PRIMARY KEY ("memoriesId", "assetsId");`.execute(
|
||||
db,
|
||||
);
|
||||
await sql`ALTER TABLE "move_history" ADD CONSTRAINT "PK_af608f132233acf123f2949678d" PRIMARY KEY ("id");`.execute(db);
|
||||
await sql`ALTER TABLE "naturalearth_countries" ADD CONSTRAINT "PK_21a6d86d1ab5d841648212e5353" PRIMARY KEY ("id");`.execute(db);
|
||||
await sql`ALTER TABLE "partners_audit" ADD CONSTRAINT "PK_952b50217ff78198a7e380f0359" PRIMARY KEY ("id");`.execute(db);
|
||||
await sql`ALTER TABLE "partners" ADD CONSTRAINT "PK_f1cc8f73d16b367f426261a8736" PRIMARY KEY ("sharedById", "sharedWithId");`.execute(db);
|
||||
await sql`ALTER TABLE "naturalearth_countries" ADD CONSTRAINT "PK_21a6d86d1ab5d841648212e5353" PRIMARY KEY ("id");`.execute(
|
||||
db,
|
||||
);
|
||||
await sql`ALTER TABLE "partners_audit" ADD CONSTRAINT "PK_952b50217ff78198a7e380f0359" PRIMARY KEY ("id");`.execute(
|
||||
db,
|
||||
);
|
||||
await sql`ALTER TABLE "partners" ADD CONSTRAINT "PK_f1cc8f73d16b367f426261a8736" PRIMARY KEY ("sharedById", "sharedWithId");`.execute(
|
||||
db,
|
||||
);
|
||||
await sql`ALTER TABLE "sessions" ADD CONSTRAINT "PK_48cb6b5c20faa63157b3c1baf7f" PRIMARY KEY ("id");`.execute(db);
|
||||
await sql`ALTER TABLE "shared_links" ADD CONSTRAINT "PK_642e2b0f619e4876e5f90a43465" PRIMARY KEY ("id");`.execute(db);
|
||||
await sql`ALTER TABLE "shared_link__asset" ADD CONSTRAINT "PK_9b4f3687f9b31d1e311336b05e3" PRIMARY KEY ("assetsId", "sharedLinksId");`.execute(db);
|
||||
await sql`ALTER TABLE "shared_link__asset" ADD CONSTRAINT "PK_9b4f3687f9b31d1e311336b05e3" PRIMARY KEY ("assetsId", "sharedLinksId");`.execute(
|
||||
db,
|
||||
);
|
||||
await sql`ALTER TABLE "smart_search" ADD CONSTRAINT "smart_search_pkey" PRIMARY KEY ("assetId");`.execute(db);
|
||||
await sql`ALTER TABLE "session_sync_checkpoints" ADD CONSTRAINT "PK_b846ab547a702863ef7cd9412fb" PRIMARY KEY ("sessionId", "type");`.execute(db);
|
||||
await sql`ALTER TABLE "system_metadata" ADD CONSTRAINT "PK_fa94f6857470fb5b81ec6084465" PRIMARY KEY ("key");`.execute(db);
|
||||
await sql`ALTER TABLE "session_sync_checkpoints" ADD CONSTRAINT "PK_b846ab547a702863ef7cd9412fb" PRIMARY KEY ("sessionId", "type");`.execute(
|
||||
db,
|
||||
);
|
||||
await sql`ALTER TABLE "system_metadata" ADD CONSTRAINT "PK_fa94f6857470fb5b81ec6084465" PRIMARY KEY ("key");`.execute(
|
||||
db,
|
||||
);
|
||||
await sql`ALTER TABLE "tags" ADD CONSTRAINT "PK_e7dc17249a1148a1970748eda99" PRIMARY KEY ("id");`.execute(db);
|
||||
await sql`ALTER TABLE "tag_asset" ADD CONSTRAINT "PK_ef5346fe522b5fb3bc96454747e" PRIMARY KEY ("assetsId", "tagsId");`.execute(db);
|
||||
await sql`ALTER TABLE "tags_closure" ADD CONSTRAINT "PK_eab38eb12a3ec6df8376c95477c" PRIMARY KEY ("id_ancestor", "id_descendant");`.execute(db);
|
||||
await sql`ALTER TABLE "tag_asset" ADD CONSTRAINT "PK_ef5346fe522b5fb3bc96454747e" PRIMARY KEY ("assetsId", "tagsId");`.execute(
|
||||
db,
|
||||
);
|
||||
await sql`ALTER TABLE "tags_closure" ADD CONSTRAINT "PK_eab38eb12a3ec6df8376c95477c" PRIMARY KEY ("id_ancestor", "id_descendant");`.execute(
|
||||
db,
|
||||
);
|
||||
await sql`ALTER TABLE "users_audit" ADD CONSTRAINT "PK_e9b2bdfd90e7eb5961091175180" PRIMARY KEY ("id");`.execute(db);
|
||||
await sql`ALTER TABLE "user_metadata" ADD CONSTRAINT "PK_5931462150b3438cbc83277fe5a" PRIMARY KEY ("userId", "key");`.execute(db);
|
||||
await sql`ALTER TABLE "version_history" ADD CONSTRAINT "PK_5db259cbb09ce82c0d13cfd1b23" PRIMARY KEY ("id");`.execute(db);
|
||||
await sql`ALTER TABLE "libraries" ADD CONSTRAINT "FK_0f6fc2fb195f24d19b0fb0d57c1" FOREIGN KEY ("ownerId") REFERENCES "users" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute(db);
|
||||
await sql`ALTER TABLE "asset_stack" ADD CONSTRAINT "FK_91704e101438fd0653f582426dc" FOREIGN KEY ("primaryAssetId") REFERENCES "assets" ("id") ON UPDATE NO ACTION ON DELETE NO ACTION;`.execute(db);
|
||||
await sql`ALTER TABLE "asset_stack" ADD CONSTRAINT "FK_c05079e542fd74de3b5ecb5c1c8" FOREIGN KEY ("ownerId") REFERENCES "users" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute(db);
|
||||
await sql`ALTER TABLE "assets" ADD CONSTRAINT "FK_2c5ac0d6fb58b238fd2068de67d" FOREIGN KEY ("ownerId") REFERENCES "users" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute(db);
|
||||
await sql`ALTER TABLE "assets" ADD CONSTRAINT "FK_16294b83fa8c0149719a1f631ef" FOREIGN KEY ("livePhotoVideoId") REFERENCES "assets" ("id") ON UPDATE CASCADE ON DELETE SET NULL;`.execute(db);
|
||||
await sql`ALTER TABLE "assets" ADD CONSTRAINT "FK_9977c3c1de01c3d848039a6b90c" FOREIGN KEY ("libraryId") REFERENCES "libraries" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute(db);
|
||||
await sql`ALTER TABLE "assets" ADD CONSTRAINT "FK_f15d48fa3ea5e4bda05ca8ab207" FOREIGN KEY ("stackId") REFERENCES "asset_stack" ("id") ON UPDATE CASCADE ON DELETE SET NULL;`.execute(db);
|
||||
await sql`ALTER TABLE "albums" ADD CONSTRAINT "FK_b22c53f35ef20c28c21637c85f4" FOREIGN KEY ("ownerId") REFERENCES "users" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute(db);
|
||||
await sql`ALTER TABLE "albums" ADD CONSTRAINT "FK_05895aa505a670300d4816debce" FOREIGN KEY ("albumThumbnailAssetId") REFERENCES "assets" ("id") ON UPDATE CASCADE ON DELETE SET NULL;`.execute(db);
|
||||
await sql`ALTER TABLE "activity" ADD CONSTRAINT "FK_1af8519996fbfb3684b58df280b" FOREIGN KEY ("albumId") REFERENCES "albums" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute(db);
|
||||
await sql`ALTER TABLE "activity" ADD CONSTRAINT "FK_3571467bcbe021f66e2bdce96ea" FOREIGN KEY ("userId") REFERENCES "users" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute(db);
|
||||
await sql`ALTER TABLE "activity" ADD CONSTRAINT "FK_8091ea76b12338cb4428d33d782" FOREIGN KEY ("assetId") REFERENCES "assets" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute(db);
|
||||
await sql`ALTER TABLE "albums_assets_assets" ADD CONSTRAINT "FK_e590fa396c6898fcd4a50e40927" FOREIGN KEY ("albumsId") REFERENCES "albums" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute(db);
|
||||
await sql`ALTER TABLE "albums_assets_assets" ADD CONSTRAINT "FK_4bd1303d199f4e72ccdf998c621" FOREIGN KEY ("assetsId") REFERENCES "assets" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute(db);
|
||||
await sql`ALTER TABLE "albums_shared_users_users" ADD CONSTRAINT "FK_427c350ad49bd3935a50baab737" FOREIGN KEY ("albumsId") REFERENCES "albums" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute(db);
|
||||
await sql`ALTER TABLE "albums_shared_users_users" ADD CONSTRAINT "FK_f48513bf9bccefd6ff3ad30bd06" FOREIGN KEY ("usersId") REFERENCES "users" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute(db);
|
||||
await sql`ALTER TABLE "api_keys" ADD CONSTRAINT "FK_6c2e267ae764a9413b863a29342" FOREIGN KEY ("userId") REFERENCES "users" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute(db);
|
||||
await sql`ALTER TABLE "person" ADD CONSTRAINT "FK_5527cc99f530a547093f9e577b6" FOREIGN KEY ("ownerId") REFERENCES "users" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute(db);
|
||||
await sql`ALTER TABLE "person" ADD CONSTRAINT "FK_2bbabe31656b6778c6b87b61023" FOREIGN KEY ("faceAssetId") REFERENCES "asset_faces" ("id") ON UPDATE NO ACTION ON DELETE SET NULL;`.execute(db);
|
||||
await sql`ALTER TABLE "asset_faces" ADD CONSTRAINT "FK_02a43fd0b3c50fb6d7f0cb7282c" FOREIGN KEY ("assetId") REFERENCES "assets" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute(db);
|
||||
await sql`ALTER TABLE "asset_faces" ADD CONSTRAINT "FK_95ad7106dd7b484275443f580f9" FOREIGN KEY ("personId") REFERENCES "person" ("id") ON UPDATE CASCADE ON DELETE SET NULL;`.execute(db);
|
||||
await sql`ALTER TABLE "asset_files" ADD CONSTRAINT "FK_e3e103a5f1d8bc8402999286040" FOREIGN KEY ("assetId") REFERENCES "assets" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute(db);
|
||||
await sql`ALTER TABLE "asset_job_status" ADD CONSTRAINT "FK_420bec36fc02813bddf5c8b73d4" FOREIGN KEY ("assetId") REFERENCES "assets" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute(db);
|
||||
await sql`ALTER TABLE "exif" ADD CONSTRAINT "FK_c0117fdbc50b917ef9067740c44" FOREIGN KEY ("assetId") REFERENCES "assets" ("id") ON UPDATE NO ACTION ON DELETE CASCADE;`.execute(db);
|
||||
await sql`ALTER TABLE "face_search" ADD CONSTRAINT "face_search_faceId_fkey" FOREIGN KEY ("faceId") REFERENCES "asset_faces" ("id") ON UPDATE NO ACTION ON DELETE CASCADE;`.execute(db);
|
||||
await sql`ALTER TABLE "memories" ADD CONSTRAINT "FK_575842846f0c28fa5da46c99b19" FOREIGN KEY ("ownerId") REFERENCES "users" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute(db);
|
||||
await sql`ALTER TABLE "memories_assets_assets" ADD CONSTRAINT "FK_984e5c9ab1f04d34538cd32334e" FOREIGN KEY ("memoriesId") REFERENCES "memories" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute(db);
|
||||
await sql`ALTER TABLE "memories_assets_assets" ADD CONSTRAINT "FK_6942ecf52d75d4273de19d2c16f" FOREIGN KEY ("assetsId") REFERENCES "assets" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute(db);
|
||||
await sql`ALTER TABLE "partners" ADD CONSTRAINT "FK_7e077a8b70b3530138610ff5e04" FOREIGN KEY ("sharedById") REFERENCES "users" ("id") ON UPDATE NO ACTION ON DELETE CASCADE;`.execute(db);
|
||||
await sql`ALTER TABLE "partners" ADD CONSTRAINT "FK_d7e875c6c60e661723dbf372fd3" FOREIGN KEY ("sharedWithId") REFERENCES "users" ("id") ON UPDATE NO ACTION ON DELETE CASCADE;`.execute(db);
|
||||
await sql`ALTER TABLE "sessions" ADD CONSTRAINT "FK_57de40bc620f456c7311aa3a1e6" FOREIGN KEY ("userId") REFERENCES "users" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute(db);
|
||||
await sql`ALTER TABLE "shared_links" ADD CONSTRAINT "FK_66fe3837414c5a9f1c33ca49340" FOREIGN KEY ("userId") REFERENCES "users" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute(db);
|
||||
await sql`ALTER TABLE "shared_links" ADD CONSTRAINT "FK_0c6ce9058c29f07cdf7014eac66" FOREIGN KEY ("albumId") REFERENCES "albums" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute(db);
|
||||
await sql`ALTER TABLE "shared_link__asset" ADD CONSTRAINT "FK_5b7decce6c8d3db9593d6111a66" FOREIGN KEY ("assetsId") REFERENCES "assets" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute(db);
|
||||
await sql`ALTER TABLE "shared_link__asset" ADD CONSTRAINT "FK_c9fab4aa97ffd1b034f3d6581ab" FOREIGN KEY ("sharedLinksId") REFERENCES "shared_links" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute(db);
|
||||
await sql`ALTER TABLE "smart_search" ADD CONSTRAINT "smart_search_assetId_fkey" FOREIGN KEY ("assetId") REFERENCES "assets" ("id") ON UPDATE NO ACTION ON DELETE CASCADE;`.execute(db);
|
||||
await sql`ALTER TABLE "session_sync_checkpoints" ADD CONSTRAINT "FK_d8ddd9d687816cc490432b3d4bc" FOREIGN KEY ("sessionId") REFERENCES "sessions" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute(db);
|
||||
await sql`ALTER TABLE "tags" ADD CONSTRAINT "FK_92e67dc508c705dd66c94615576" FOREIGN KEY ("userId") REFERENCES "users" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute(db);
|
||||
await sql`ALTER TABLE "tags" ADD CONSTRAINT "FK_9f9590cc11561f1f48ff034ef99" FOREIGN KEY ("parentId") REFERENCES "tags" ("id") ON UPDATE NO ACTION ON DELETE CASCADE;`.execute(db);
|
||||
await sql`ALTER TABLE "tag_asset" ADD CONSTRAINT "FK_f8e8a9e893cb5c54907f1b798e9" FOREIGN KEY ("assetsId") REFERENCES "assets" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute(db);
|
||||
await sql`ALTER TABLE "tag_asset" ADD CONSTRAINT "FK_e99f31ea4cdf3a2c35c7287eb42" FOREIGN KEY ("tagsId") REFERENCES "tags" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute(db);
|
||||
await sql`ALTER TABLE "tags_closure" ADD CONSTRAINT "FK_15fbcbc67663c6bfc07b354c22c" FOREIGN KEY ("id_ancestor") REFERENCES "tags" ("id") ON UPDATE NO ACTION ON DELETE CASCADE;`.execute(db);
|
||||
await sql`ALTER TABLE "tags_closure" ADD CONSTRAINT "FK_b1a2a7ed45c29179b5ad51548a1" FOREIGN KEY ("id_descendant") REFERENCES "tags" ("id") ON UPDATE NO ACTION ON DELETE CASCADE;`.execute(db);
|
||||
await sql`ALTER TABLE "user_metadata" ADD CONSTRAINT "FK_6afb43681a21cf7815932bc38ac" FOREIGN KEY ("userId") REFERENCES "users" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute(db);
|
||||
await sql`ALTER TABLE "user_metadata" ADD CONSTRAINT "PK_5931462150b3438cbc83277fe5a" PRIMARY KEY ("userId", "key");`.execute(
|
||||
db,
|
||||
);
|
||||
await sql`ALTER TABLE "version_history" ADD CONSTRAINT "PK_5db259cbb09ce82c0d13cfd1b23" PRIMARY KEY ("id");`.execute(
|
||||
db,
|
||||
);
|
||||
await sql`ALTER TABLE "libraries" ADD CONSTRAINT "FK_0f6fc2fb195f24d19b0fb0d57c1" FOREIGN KEY ("ownerId") REFERENCES "users" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute(
|
||||
db,
|
||||
);
|
||||
await sql`ALTER TABLE "asset_stack" ADD CONSTRAINT "FK_91704e101438fd0653f582426dc" FOREIGN KEY ("primaryAssetId") REFERENCES "assets" ("id") ON UPDATE NO ACTION ON DELETE NO ACTION;`.execute(
|
||||
db,
|
||||
);
|
||||
await sql`ALTER TABLE "asset_stack" ADD CONSTRAINT "FK_c05079e542fd74de3b5ecb5c1c8" FOREIGN KEY ("ownerId") REFERENCES "users" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute(
|
||||
db,
|
||||
);
|
||||
await sql`ALTER TABLE "assets" ADD CONSTRAINT "FK_2c5ac0d6fb58b238fd2068de67d" FOREIGN KEY ("ownerId") REFERENCES "users" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute(
|
||||
db,
|
||||
);
|
||||
await sql`ALTER TABLE "assets" ADD CONSTRAINT "FK_16294b83fa8c0149719a1f631ef" FOREIGN KEY ("livePhotoVideoId") REFERENCES "assets" ("id") ON UPDATE CASCADE ON DELETE SET NULL;`.execute(
|
||||
db,
|
||||
);
|
||||
await sql`ALTER TABLE "assets" ADD CONSTRAINT "FK_9977c3c1de01c3d848039a6b90c" FOREIGN KEY ("libraryId") REFERENCES "libraries" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute(
|
||||
db,
|
||||
);
|
||||
await sql`ALTER TABLE "assets" ADD CONSTRAINT "FK_f15d48fa3ea5e4bda05ca8ab207" FOREIGN KEY ("stackId") REFERENCES "asset_stack" ("id") ON UPDATE CASCADE ON DELETE SET NULL;`.execute(
|
||||
db,
|
||||
);
|
||||
await sql`ALTER TABLE "albums" ADD CONSTRAINT "FK_b22c53f35ef20c28c21637c85f4" FOREIGN KEY ("ownerId") REFERENCES "users" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute(
|
||||
db,
|
||||
);
|
||||
await sql`ALTER TABLE "albums" ADD CONSTRAINT "FK_05895aa505a670300d4816debce" FOREIGN KEY ("albumThumbnailAssetId") REFERENCES "assets" ("id") ON UPDATE CASCADE ON DELETE SET NULL;`.execute(
|
||||
db,
|
||||
);
|
||||
await sql`ALTER TABLE "activity" ADD CONSTRAINT "FK_1af8519996fbfb3684b58df280b" FOREIGN KEY ("albumId") REFERENCES "albums" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute(
|
||||
db,
|
||||
);
|
||||
await sql`ALTER TABLE "activity" ADD CONSTRAINT "FK_3571467bcbe021f66e2bdce96ea" FOREIGN KEY ("userId") REFERENCES "users" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute(
|
||||
db,
|
||||
);
|
||||
await sql`ALTER TABLE "activity" ADD CONSTRAINT "FK_8091ea76b12338cb4428d33d782" FOREIGN KEY ("assetId") REFERENCES "assets" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute(
|
||||
db,
|
||||
);
|
||||
await sql`ALTER TABLE "albums_assets_assets" ADD CONSTRAINT "FK_e590fa396c6898fcd4a50e40927" FOREIGN KEY ("albumsId") REFERENCES "albums" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute(
|
||||
db,
|
||||
);
|
||||
await sql`ALTER TABLE "albums_assets_assets" ADD CONSTRAINT "FK_4bd1303d199f4e72ccdf998c621" FOREIGN KEY ("assetsId") REFERENCES "assets" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute(
|
||||
db,
|
||||
);
|
||||
await sql`ALTER TABLE "albums_shared_users_users" ADD CONSTRAINT "FK_427c350ad49bd3935a50baab737" FOREIGN KEY ("albumsId") REFERENCES "albums" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute(
|
||||
db,
|
||||
);
|
||||
await sql`ALTER TABLE "albums_shared_users_users" ADD CONSTRAINT "FK_f48513bf9bccefd6ff3ad30bd06" FOREIGN KEY ("usersId") REFERENCES "users" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute(
|
||||
db,
|
||||
);
|
||||
await sql`ALTER TABLE "api_keys" ADD CONSTRAINT "FK_6c2e267ae764a9413b863a29342" FOREIGN KEY ("userId") REFERENCES "users" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute(
|
||||
db,
|
||||
);
|
||||
await sql`ALTER TABLE "person" ADD CONSTRAINT "FK_5527cc99f530a547093f9e577b6" FOREIGN KEY ("ownerId") REFERENCES "users" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute(
|
||||
db,
|
||||
);
|
||||
await sql`ALTER TABLE "person" ADD CONSTRAINT "FK_2bbabe31656b6778c6b87b61023" FOREIGN KEY ("faceAssetId") REFERENCES "asset_faces" ("id") ON UPDATE NO ACTION ON DELETE SET NULL;`.execute(
|
||||
db,
|
||||
);
|
||||
await sql`ALTER TABLE "asset_faces" ADD CONSTRAINT "FK_02a43fd0b3c50fb6d7f0cb7282c" FOREIGN KEY ("assetId") REFERENCES "assets" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute(
|
||||
db,
|
||||
);
|
||||
await sql`ALTER TABLE "asset_faces" ADD CONSTRAINT "FK_95ad7106dd7b484275443f580f9" FOREIGN KEY ("personId") REFERENCES "person" ("id") ON UPDATE CASCADE ON DELETE SET NULL;`.execute(
|
||||
db,
|
||||
);
|
||||
await sql`ALTER TABLE "asset_files" ADD CONSTRAINT "FK_e3e103a5f1d8bc8402999286040" FOREIGN KEY ("assetId") REFERENCES "assets" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute(
|
||||
db,
|
||||
);
|
||||
await sql`ALTER TABLE "asset_job_status" ADD CONSTRAINT "FK_420bec36fc02813bddf5c8b73d4" FOREIGN KEY ("assetId") REFERENCES "assets" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute(
|
||||
db,
|
||||
);
|
||||
await sql`ALTER TABLE "exif" ADD CONSTRAINT "FK_c0117fdbc50b917ef9067740c44" FOREIGN KEY ("assetId") REFERENCES "assets" ("id") ON UPDATE NO ACTION ON DELETE CASCADE;`.execute(
|
||||
db,
|
||||
);
|
||||
await sql`ALTER TABLE "face_search" ADD CONSTRAINT "face_search_faceId_fkey" FOREIGN KEY ("faceId") REFERENCES "asset_faces" ("id") ON UPDATE NO ACTION ON DELETE CASCADE;`.execute(
|
||||
db,
|
||||
);
|
||||
await sql`ALTER TABLE "memories" ADD CONSTRAINT "FK_575842846f0c28fa5da46c99b19" FOREIGN KEY ("ownerId") REFERENCES "users" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute(
|
||||
db,
|
||||
);
|
||||
await sql`ALTER TABLE "memories_assets_assets" ADD CONSTRAINT "FK_984e5c9ab1f04d34538cd32334e" FOREIGN KEY ("memoriesId") REFERENCES "memories" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute(
|
||||
db,
|
||||
);
|
||||
await sql`ALTER TABLE "memories_assets_assets" ADD CONSTRAINT "FK_6942ecf52d75d4273de19d2c16f" FOREIGN KEY ("assetsId") REFERENCES "assets" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute(
|
||||
db,
|
||||
);
|
||||
await sql`ALTER TABLE "partners" ADD CONSTRAINT "FK_7e077a8b70b3530138610ff5e04" FOREIGN KEY ("sharedById") REFERENCES "users" ("id") ON UPDATE NO ACTION ON DELETE CASCADE;`.execute(
|
||||
db,
|
||||
);
|
||||
await sql`ALTER TABLE "partners" ADD CONSTRAINT "FK_d7e875c6c60e661723dbf372fd3" FOREIGN KEY ("sharedWithId") REFERENCES "users" ("id") ON UPDATE NO ACTION ON DELETE CASCADE;`.execute(
|
||||
db,
|
||||
);
|
||||
await sql`ALTER TABLE "sessions" ADD CONSTRAINT "FK_57de40bc620f456c7311aa3a1e6" FOREIGN KEY ("userId") REFERENCES "users" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute(
|
||||
db,
|
||||
);
|
||||
await sql`ALTER TABLE "shared_links" ADD CONSTRAINT "FK_66fe3837414c5a9f1c33ca49340" FOREIGN KEY ("userId") REFERENCES "users" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute(
|
||||
db,
|
||||
);
|
||||
await sql`ALTER TABLE "shared_links" ADD CONSTRAINT "FK_0c6ce9058c29f07cdf7014eac66" FOREIGN KEY ("albumId") REFERENCES "albums" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute(
|
||||
db,
|
||||
);
|
||||
await sql`ALTER TABLE "shared_link__asset" ADD CONSTRAINT "FK_5b7decce6c8d3db9593d6111a66" FOREIGN KEY ("assetsId") REFERENCES "assets" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute(
|
||||
db,
|
||||
);
|
||||
await sql`ALTER TABLE "shared_link__asset" ADD CONSTRAINT "FK_c9fab4aa97ffd1b034f3d6581ab" FOREIGN KEY ("sharedLinksId") REFERENCES "shared_links" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute(
|
||||
db,
|
||||
);
|
||||
await sql`ALTER TABLE "smart_search" ADD CONSTRAINT "smart_search_assetId_fkey" FOREIGN KEY ("assetId") REFERENCES "assets" ("id") ON UPDATE NO ACTION ON DELETE CASCADE;`.execute(
|
||||
db,
|
||||
);
|
||||
await sql`ALTER TABLE "session_sync_checkpoints" ADD CONSTRAINT "FK_d8ddd9d687816cc490432b3d4bc" FOREIGN KEY ("sessionId") REFERENCES "sessions" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute(
|
||||
db,
|
||||
);
|
||||
await sql`ALTER TABLE "tags" ADD CONSTRAINT "FK_92e67dc508c705dd66c94615576" FOREIGN KEY ("userId") REFERENCES "users" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute(
|
||||
db,
|
||||
);
|
||||
await sql`ALTER TABLE "tags" ADD CONSTRAINT "FK_9f9590cc11561f1f48ff034ef99" FOREIGN KEY ("parentId") REFERENCES "tags" ("id") ON UPDATE NO ACTION ON DELETE CASCADE;`.execute(
|
||||
db,
|
||||
);
|
||||
await sql`ALTER TABLE "tag_asset" ADD CONSTRAINT "FK_f8e8a9e893cb5c54907f1b798e9" FOREIGN KEY ("assetsId") REFERENCES "assets" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute(
|
||||
db,
|
||||
);
|
||||
await sql`ALTER TABLE "tag_asset" ADD CONSTRAINT "FK_e99f31ea4cdf3a2c35c7287eb42" FOREIGN KEY ("tagsId") REFERENCES "tags" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute(
|
||||
db,
|
||||
);
|
||||
await sql`ALTER TABLE "tags_closure" ADD CONSTRAINT "FK_15fbcbc67663c6bfc07b354c22c" FOREIGN KEY ("id_ancestor") REFERENCES "tags" ("id") ON UPDATE NO ACTION ON DELETE CASCADE;`.execute(
|
||||
db,
|
||||
);
|
||||
await sql`ALTER TABLE "tags_closure" ADD CONSTRAINT "FK_b1a2a7ed45c29179b5ad51548a1" FOREIGN KEY ("id_descendant") REFERENCES "tags" ("id") ON UPDATE NO ACTION ON DELETE CASCADE;`.execute(
|
||||
db,
|
||||
);
|
||||
await sql`ALTER TABLE "user_metadata" ADD CONSTRAINT "FK_6afb43681a21cf7815932bc38ac" FOREIGN KEY ("userId") REFERENCES "users" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute(
|
||||
db,
|
||||
);
|
||||
await sql`ALTER TABLE "users" ADD CONSTRAINT "UQ_97672ac88f789774dd47f7c8be3" UNIQUE ("email");`.execute(db);
|
||||
await sql`ALTER TABLE "users" ADD CONSTRAINT "UQ_b309cf34fa58137c416b32cea3a" UNIQUE ("storageLabel");`.execute(db);
|
||||
await sql`ALTER TABLE "asset_stack" ADD CONSTRAINT "REL_91704e101438fd0653f582426d" UNIQUE ("primaryAssetId");`.execute(db);
|
||||
await sql`ALTER TABLE "asset_stack" ADD CONSTRAINT "REL_91704e101438fd0653f582426d" UNIQUE ("primaryAssetId");`.execute(
|
||||
db,
|
||||
);
|
||||
await sql`ALTER TABLE "asset_files" ADD CONSTRAINT "UQ_assetId_type" UNIQUE ("assetId", "type");`.execute(db);
|
||||
await sql`ALTER TABLE "move_history" ADD CONSTRAINT "UQ_newPath" UNIQUE ("newPath");`.execute(db);
|
||||
await sql`ALTER TABLE "move_history" ADD CONSTRAINT "UQ_entityId_pathType" UNIQUE ("entityId", "pathType");`.execute(db);
|
||||
await sql`ALTER TABLE "move_history" ADD CONSTRAINT "UQ_entityId_pathType" UNIQUE ("entityId", "pathType");`.execute(
|
||||
db,
|
||||
);
|
||||
await sql`ALTER TABLE "shared_links" ADD CONSTRAINT "UQ_sharedlink_key" UNIQUE ("key");`.execute(db);
|
||||
await sql`ALTER TABLE "tags" ADD CONSTRAINT "UQ_79d6f16e52bb2c7130375246793" UNIQUE ("userId", "value");`.execute(db);
|
||||
await sql`ALTER TABLE "activity" ADD CONSTRAINT "CHK_2ab1e70f113f450eb40c1e3ec8" CHECK (("comment" IS NULL AND "isLiked" = true) OR ("comment" IS NOT NULL AND "isLiked" = false));`.execute(db);
|
||||
await sql`ALTER TABLE "person" ADD CONSTRAINT "CHK_b0f82b0ed662bfc24fbb58bb45" CHECK ("birthDate" <= CURRENT_DATE);`.execute(db);
|
||||
await sql`ALTER TABLE "activity" ADD CONSTRAINT "CHK_2ab1e70f113f450eb40c1e3ec8" CHECK (("comment" IS NULL AND "isLiked" = true) OR ("comment" IS NOT NULL AND "isLiked" = false));`.execute(
|
||||
db,
|
||||
);
|
||||
await sql`ALTER TABLE "person" ADD CONSTRAINT "CHK_b0f82b0ed662bfc24fbb58bb45" CHECK ("birthDate" <= CURRENT_DATE);`.execute(
|
||||
db,
|
||||
);
|
||||
await sql`CREATE INDEX "IDX_users_updated_at_asc_id_asc" ON "users" ("updatedAt", "id")`.execute(db);
|
||||
await sql`CREATE INDEX "IDX_users_update_id" ON "users" ("updateId")`.execute(db);
|
||||
await sql`CREATE INDEX "IDX_0f6fc2fb195f24d19b0fb0d57c" ON "libraries" ("ownerId")`.execute(db);
|
||||
await sql`CREATE INDEX "IDX_libraries_update_id" ON "libraries" ("updateId")`.execute(db);
|
||||
await sql`CREATE INDEX "IDX_91704e101438fd0653f582426d" ON "asset_stack" ("primaryAssetId")`.execute(db);
|
||||
await sql`CREATE INDEX "IDX_c05079e542fd74de3b5ecb5c1c" ON "asset_stack" ("ownerId")`.execute(db);
|
||||
await sql`CREATE INDEX "idx_originalfilename_trigram" ON "assets" USING gin (f_unaccent("originalFileName") gin_trgm_ops)`.execute(db);
|
||||
await sql`CREATE INDEX "idx_originalfilename_trigram" ON "assets" USING gin (f_unaccent("originalFileName") gin_trgm_ops)`.execute(
|
||||
db,
|
||||
);
|
||||
await sql`CREATE INDEX "IDX_asset_id_stackId" ON "assets" ("id", "stackId")`.execute(db);
|
||||
await sql`CREATE INDEX "IDX_originalPath_libraryId" ON "assets" ("originalPath", "libraryId")`.execute(db);
|
||||
await sql`CREATE INDEX "idx_local_date_time_month" ON "assets" ((date_trunc('MONTH'::text, ("localDateTime" AT TIME ZONE 'UTC'::text)) AT TIME ZONE 'UTC'::text))`.execute(db);
|
||||
await sql`CREATE INDEX "idx_local_date_time_month" ON "assets" ((date_trunc('MONTH'::text, ("localDateTime" AT TIME ZONE 'UTC'::text)) AT TIME ZONE 'UTC'::text))`.execute(
|
||||
db,
|
||||
);
|
||||
await sql`CREATE INDEX "idx_local_date_time" ON "assets" ((("localDateTime" at time zone 'UTC')::date))`.execute(db);
|
||||
await sql`CREATE UNIQUE INDEX "UQ_assets_owner_library_checksum" ON "assets" ("ownerId", "libraryId", "checksum") WHERE ("libraryId" IS NOT NULL)`.execute(db);
|
||||
await sql`CREATE UNIQUE INDEX "UQ_assets_owner_checksum" ON "assets" ("ownerId", "checksum") WHERE ("libraryId" IS NULL)`.execute(db);
|
||||
await sql`CREATE UNIQUE INDEX "UQ_assets_owner_library_checksum" ON "assets" ("ownerId", "libraryId", "checksum") WHERE ("libraryId" IS NOT NULL)`.execute(
|
||||
db,
|
||||
);
|
||||
await sql`CREATE UNIQUE INDEX "UQ_assets_owner_checksum" ON "assets" ("ownerId", "checksum") WHERE ("libraryId" IS NULL)`.execute(
|
||||
db,
|
||||
);
|
||||
await sql`CREATE INDEX "IDX_2c5ac0d6fb58b238fd2068de67" ON "assets" ("ownerId")`.execute(db);
|
||||
await sql`CREATE INDEX "idx_asset_file_created_at" ON "assets" ("fileCreatedAt")`.execute(db);
|
||||
await sql`CREATE INDEX "IDX_8d3efe36c0755849395e6ea866" ON "assets" ("checksum")`.execute(db);
|
||||
@@ -266,7 +456,9 @@ export async function up(db: Kysely<any>): Promise<void> {
|
||||
await sql`CREATE INDEX "IDX_b22c53f35ef20c28c21637c85f" ON "albums" ("ownerId")`.execute(db);
|
||||
await sql`CREATE INDEX "IDX_05895aa505a670300d4816debc" ON "albums" ("albumThumbnailAssetId")`.execute(db);
|
||||
await sql`CREATE INDEX "IDX_albums_update_id" ON "albums" ("updateId")`.execute(db);
|
||||
await sql`CREATE UNIQUE INDEX "IDX_activity_like" ON "activity" ("assetId", "userId", "albumId") WHERE ("isLiked" = true)`.execute(db);
|
||||
await sql`CREATE UNIQUE INDEX "IDX_activity_like" ON "activity" ("assetId", "userId", "albumId") WHERE ("isLiked" = true)`.execute(
|
||||
db,
|
||||
);
|
||||
await sql`CREATE INDEX "IDX_1af8519996fbfb3684b58df280" ON "activity" ("albumId")`.execute(db);
|
||||
await sql`CREATE INDEX "IDX_3571467bcbe021f66e2bdce96e" ON "activity" ("userId")`.execute(db);
|
||||
await sql`CREATE INDEX "IDX_8091ea76b12338cb4428d33d78" ON "activity" ("assetId")`.execute(db);
|
||||
@@ -295,11 +487,21 @@ export async function up(db: Kysely<any>): Promise<void> {
|
||||
await sql`CREATE INDEX "IDX_auto_stack_id" ON "exif" ("autoStackId")`.execute(db);
|
||||
await sql`CREATE INDEX "IDX_asset_exif_update_id" ON "exif" ("updateId")`.execute(db);
|
||||
await sql.raw(vectorIndexQuery({ vectorExtension, table: 'face_search', indexName: 'face_index' })).execute(db);
|
||||
await sql`CREATE INDEX "IDX_geodata_gist_earthcoord" ON "geodata_places" (ll_to_earth_public(latitude, longitude))`.execute(db);
|
||||
await sql`CREATE INDEX "idx_geodata_places_name" ON "geodata_places" USING gin (f_unaccent("name") gin_trgm_ops)`.execute(db);
|
||||
await sql`CREATE INDEX "idx_geodata_places_admin2_name" ON "geodata_places" USING gin (f_unaccent("admin2Name") gin_trgm_ops)`.execute(db);
|
||||
await sql`CREATE INDEX "idx_geodata_places_admin1_name" ON "geodata_places" USING gin (f_unaccent("admin1Name") gin_trgm_ops)`.execute(db);
|
||||
await sql`CREATE INDEX "idx_geodata_places_alternate_names" ON "geodata_places" USING gin (f_unaccent("alternateNames") gin_trgm_ops)`.execute(db);
|
||||
await sql`CREATE INDEX "IDX_geodata_gist_earthcoord" ON "geodata_places" (ll_to_earth_public(latitude, longitude))`.execute(
|
||||
db,
|
||||
);
|
||||
await sql`CREATE INDEX "idx_geodata_places_name" ON "geodata_places" USING gin (f_unaccent("name") gin_trgm_ops)`.execute(
|
||||
db,
|
||||
);
|
||||
await sql`CREATE INDEX "idx_geodata_places_admin2_name" ON "geodata_places" USING gin (f_unaccent("admin2Name") gin_trgm_ops)`.execute(
|
||||
db,
|
||||
);
|
||||
await sql`CREATE INDEX "idx_geodata_places_admin1_name" ON "geodata_places" USING gin (f_unaccent("admin1Name") gin_trgm_ops)`.execute(
|
||||
db,
|
||||
);
|
||||
await sql`CREATE INDEX "idx_geodata_places_alternate_names" ON "geodata_places" USING gin (f_unaccent("alternateNames") gin_trgm_ops)`.execute(
|
||||
db,
|
||||
);
|
||||
await sql`CREATE INDEX "IDX_575842846f0c28fa5da46c99b1" ON "memories" ("ownerId")`.execute(db);
|
||||
await sql`CREATE INDEX "IDX_memories_update_id" ON "memories" ("updateId")`.execute(db);
|
||||
await sql`CREATE INDEX "IDX_984e5c9ab1f04d34538cd32334" ON "memories_assets_assets" ("memoriesId")`.execute(db);
|
||||
@@ -319,7 +521,9 @@ export async function up(db: Kysely<any>): Promise<void> {
|
||||
await sql`CREATE INDEX "IDX_c9fab4aa97ffd1b034f3d6581a" ON "shared_link__asset" ("sharedLinksId")`.execute(db);
|
||||
await sql.raw(vectorIndexQuery({ vectorExtension, table: 'smart_search', indexName: 'clip_index' })).execute(db);
|
||||
await sql`CREATE INDEX "IDX_d8ddd9d687816cc490432b3d4b" ON "session_sync_checkpoints" ("sessionId")`.execute(db);
|
||||
await sql`CREATE INDEX "IDX_session_sync_checkpoints_update_id" ON "session_sync_checkpoints" ("updateId")`.execute(db);
|
||||
await sql`CREATE INDEX "IDX_session_sync_checkpoints_update_id" ON "session_sync_checkpoints" ("updateId")`.execute(
|
||||
db,
|
||||
);
|
||||
await sql`CREATE INDEX "IDX_92e67dc508c705dd66c9461557" ON "tags" ("userId")`.execute(db);
|
||||
await sql`CREATE INDEX "IDX_9f9590cc11561f1f48ff034ef9" ON "tags" ("parentId")`.execute(db);
|
||||
await sql`CREATE INDEX "IDX_tags_update_id" ON "tags" ("updateId")`.execute(db);
|
||||
@@ -407,5 +611,5 @@ export async function up(db: Kysely<any>): Promise<void> {
|
||||
}
|
||||
|
||||
export async function down(): Promise<void> {
|
||||
// not implemented
|
||||
// not implemented
|
||||
}
|
||||
|
||||
@@ -2,11 +2,11 @@ import { Kysely, sql } from 'kysely';
|
||||
import { UserMetadataKey } from 'src/enum';
|
||||
|
||||
export async function up(db: Kysely<any>): Promise<void> {
|
||||
await sql`INSERT INTO user_metadata SELECT id, ${UserMetadataKey.ONBOARDING}, '{"isOnboarded": true}' FROM users
|
||||
await sql`INSERT INTO user_metadata SELECT id, ${UserMetadataKey.Onboarding}, '{"isOnboarded": true}' FROM users
|
||||
ON CONFLICT ("userId", key) DO NOTHING
|
||||
`.execute(db);
|
||||
}
|
||||
|
||||
export async function down(db: Kysely<any>): Promise<void> {
|
||||
await sql`DELETE FROM user_metadata WHERE key = ${UserMetadataKey.ONBOARDING}`.execute(db);
|
||||
await sql`DELETE FROM user_metadata WHERE key = ${UserMetadataKey.Onboarding}`.execute(db);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
import { Kysely, sql } from 'kysely';
|
||||
import { LoggingRepository } from 'src/repositories/logging.repository';
|
||||
|
||||
const logger = LoggingRepository.create();
|
||||
logger.setContext('Migrations');
|
||||
|
||||
export async function up(db: Kysely<any>): Promise<void> {
|
||||
if (process.env.IMMICH_MEDIA_LOCATION) {
|
||||
// do not automatically convert paths for a custom location/setting
|
||||
return;
|
||||
}
|
||||
|
||||
// we construct paths using `path.join(mediaLocation, ...)`, which strips the leading './'
|
||||
const source = 'upload';
|
||||
const target = '/usr/src/app/upload';
|
||||
|
||||
logger.log(`Converting database file paths from relative to absolute (source=${source}/*, target=${target}/*)`);
|
||||
|
||||
// escaping regex special characters with a backslash
|
||||
const sourceRegex = '^' + source.replaceAll(/[-[\]{}()*+?.,\\^$|#\s]/g, String.raw`\$&`);
|
||||
|
||||
const items: Array<{ table: string; column: string }> = [
|
||||
{ table: 'asset', column: 'originalPath' },
|
||||
{ table: 'asset', column: 'encodedVideoPath' },
|
||||
{ table: 'asset', column: 'sidecarPath' },
|
||||
{ table: 'asset_file', column: 'path' },
|
||||
{ table: 'person', column: 'thumbnailPath' },
|
||||
{ table: 'user', column: 'profileImagePath' },
|
||||
];
|
||||
|
||||
for (const { table, column } of items) {
|
||||
const query = `UPDATE "${table}" SET "${column}" = REGEXP_REPLACE("${column}", '${sourceRegex}', '${target}') WHERE "${column}" IS NOT NULL`;
|
||||
await sql.raw(query).execute(db);
|
||||
}
|
||||
}
|
||||
|
||||
export async function down(): Promise<void> {
|
||||
// not supported
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
import { Kysely, sql } from 'kysely';
|
||||
|
||||
export async function up(db: Kysely<any>): Promise<void> {
|
||||
await sql`CREATE OR REPLACE FUNCTION asset_face_audit()
|
||||
RETURNS TRIGGER
|
||||
LANGUAGE PLPGSQL
|
||||
AS $$
|
||||
BEGIN
|
||||
INSERT INTO asset_face_audit ("assetFaceId", "assetId")
|
||||
SELECT "id", "assetId"
|
||||
FROM OLD;
|
||||
RETURN NULL;
|
||||
END
|
||||
$$;`.execute(db);
|
||||
await sql`CREATE TABLE "asset_face_audit" (
|
||||
"id" uuid NOT NULL DEFAULT immich_uuid_v7(),
|
||||
"assetFaceId" uuid NOT NULL,
|
||||
"assetId" uuid NOT NULL,
|
||||
"deletedAt" timestamp with time zone NOT NULL DEFAULT clock_timestamp(),
|
||||
CONSTRAINT "asset_face_audit_pkey" PRIMARY KEY ("id")
|
||||
);`.execute(db);
|
||||
await sql`CREATE INDEX "asset_face_audit_assetFaceId_idx" ON "asset_face_audit" ("assetFaceId");`.execute(db);
|
||||
await sql`CREATE INDEX "asset_face_audit_assetId_idx" ON "asset_face_audit" ("assetId");`.execute(db);
|
||||
await sql`CREATE INDEX "asset_face_audit_deletedAt_idx" ON "asset_face_audit" ("deletedAt");`.execute(db);
|
||||
await sql`ALTER TABLE "asset_face" ADD "updatedAt" timestamp with time zone NOT NULL DEFAULT now();`.execute(db);
|
||||
await sql`ALTER TABLE "asset_face" ADD "updateId" uuid NOT NULL DEFAULT immich_uuid_v7();`.execute(db);
|
||||
await sql`CREATE OR REPLACE TRIGGER "asset_face_audit"
|
||||
AFTER DELETE ON "asset_face"
|
||||
REFERENCING OLD TABLE AS "old"
|
||||
FOR EACH STATEMENT
|
||||
WHEN (pg_trigger_depth() = 0)
|
||||
EXECUTE FUNCTION asset_face_audit();`.execute(db);
|
||||
await sql`CREATE OR REPLACE TRIGGER "asset_face_updatedAt"
|
||||
BEFORE UPDATE ON "asset_face"
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION updated_at();`.execute(db);
|
||||
await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('function_asset_face_audit', '{"type":"function","name":"asset_face_audit","sql":"CREATE OR REPLACE FUNCTION asset_face_audit()\\n RETURNS TRIGGER\\n LANGUAGE PLPGSQL\\n AS $$\\n BEGIN\\n INSERT INTO asset_face_audit (\\"assetFaceId\\", \\"assetId\\")\\n SELECT \\"id\\", \\"assetId\\"\\n FROM OLD;\\n RETURN NULL;\\n END\\n $$;"}'::jsonb);`.execute(db);
|
||||
await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('trigger_asset_face_audit', '{"type":"trigger","name":"asset_face_audit","sql":"CREATE OR REPLACE TRIGGER \\"asset_face_audit\\"\\n AFTER DELETE ON \\"asset_face\\"\\n REFERENCING OLD TABLE AS \\"old\\"\\n FOR EACH STATEMENT\\n WHEN (pg_trigger_depth() = 0)\\n EXECUTE FUNCTION asset_face_audit();"}'::jsonb);`.execute(db);
|
||||
await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('trigger_asset_face_updatedAt', '{"type":"trigger","name":"asset_face_updatedAt","sql":"CREATE OR REPLACE TRIGGER \\"asset_face_updatedAt\\"\\n BEFORE UPDATE ON \\"asset_face\\"\\n FOR EACH ROW\\n EXECUTE FUNCTION updated_at();"}'::jsonb);`.execute(db);
|
||||
}
|
||||
|
||||
export async function down(db: Kysely<any>): Promise<void> {
|
||||
await sql`DROP TRIGGER "asset_face_audit" ON "asset_face";`.execute(db);
|
||||
await sql`DROP TRIGGER "asset_face_updatedAt" ON "asset_face";`.execute(db);
|
||||
await sql`ALTER TABLE "asset_face" DROP COLUMN "updatedAt";`.execute(db);
|
||||
await sql`ALTER TABLE "asset_face" DROP COLUMN "updateId";`.execute(db);
|
||||
await sql`DROP TABLE "asset_face_audit";`.execute(db);
|
||||
await sql`DROP FUNCTION asset_face_audit;`.execute(db);
|
||||
await sql`DELETE FROM "migration_overrides" WHERE "name" = 'function_asset_face_audit';`.execute(db);
|
||||
await sql`DELETE FROM "migration_overrides" WHERE "name" = 'trigger_asset_face_audit';`.execute(db);
|
||||
await sql`DELETE FROM "migration_overrides" WHERE "name" = 'trigger_asset_face_updatedAt';`.execute(db);
|
||||
}
|
||||
@@ -47,7 +47,7 @@ export class AlbumUserTable {
|
||||
})
|
||||
usersId!: string;
|
||||
|
||||
@Column({ type: 'character varying', default: AlbumUserRole.EDITOR })
|
||||
@Column({ type: 'character varying', default: AlbumUserRole.Editor })
|
||||
role!: Generated<AlbumUserRole>;
|
||||
|
||||
@CreateIdColumn({ index: true })
|
||||
|
||||
@@ -57,7 +57,7 @@ export class AlbumTable {
|
||||
@Column({ type: 'boolean', default: true })
|
||||
isActivityEnabled!: Generated<boolean>;
|
||||
|
||||
@Column({ default: AssetOrder.DESC })
|
||||
@Column({ default: AssetOrder.Desc })
|
||||
order!: Generated<AssetOrder>;
|
||||
|
||||
@UpdateIdColumn({ index: true })
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
import { PrimaryGeneratedUuidV7Column } from 'src/decorators';
|
||||
import { Column, CreateDateColumn, Generated, Table, Timestamp } from 'src/sql-tools';
|
||||
|
||||
@Table('asset_face_audit')
|
||||
export class AssetFaceAuditTable {
|
||||
@PrimaryGeneratedUuidV7Column()
|
||||
id!: Generated<string>;
|
||||
|
||||
@Column({ type: 'uuid', index: true })
|
||||
assetFaceId!: string;
|
||||
|
||||
@Column({ type: 'uuid', index: true })
|
||||
assetId!: string;
|
||||
|
||||
@CreateDateColumn({ default: () => 'clock_timestamp()', index: true })
|
||||
deletedAt!: Generated<Timestamp>;
|
||||
}
|
||||
@@ -1,8 +1,11 @@
|
||||
import { UpdatedAtTrigger, UpdateIdColumn } from 'src/decorators';
|
||||
import { SourceType } from 'src/enum';
|
||||
import { asset_face_source_type } from 'src/schema/enums';
|
||||
import { asset_face_audit } from 'src/schema/functions';
|
||||
import { AssetTable } from 'src/schema/tables/asset.table';
|
||||
import { PersonTable } from 'src/schema/tables/person.table';
|
||||
import {
|
||||
AfterDeleteTrigger,
|
||||
Column,
|
||||
DeleteDateColumn,
|
||||
ForeignKeyColumn,
|
||||
@@ -11,9 +14,17 @@ import {
|
||||
PrimaryGeneratedColumn,
|
||||
Table,
|
||||
Timestamp,
|
||||
UpdateDateColumn,
|
||||
} from 'src/sql-tools';
|
||||
|
||||
@Table({ name: 'asset_face' })
|
||||
@UpdatedAtTrigger('asset_face_updatedAt')
|
||||
@AfterDeleteTrigger({
|
||||
scope: 'statement',
|
||||
function: asset_face_audit,
|
||||
referencingOldTableAs: 'old',
|
||||
when: 'pg_trigger_depth() = 0',
|
||||
})
|
||||
// schemaFromDatabase does not preserve column order
|
||||
@Index({ name: 'asset_face_assetId_personId_idx', columns: ['assetId', 'personId'] })
|
||||
@Index({ columns: ['personId', 'assetId'] })
|
||||
@@ -56,9 +67,15 @@ export class AssetFaceTable {
|
||||
@Column({ default: 0, type: 'integer' })
|
||||
boundingBoxY2!: Generated<number>;
|
||||
|
||||
@Column({ default: SourceType.MACHINE_LEARNING, enum: asset_face_source_type })
|
||||
@Column({ default: SourceType.MachineLearning, enum: asset_face_source_type })
|
||||
sourceType!: Generated<SourceType>;
|
||||
|
||||
@DeleteDateColumn()
|
||||
deletedAt!: Timestamp | null;
|
||||
|
||||
@UpdateDateColumn()
|
||||
updatedAt!: Generated<Timestamp>;
|
||||
|
||||
@UpdateIdColumn()
|
||||
updateId!: Generated<string>;
|
||||
}
|
||||
|
||||
@@ -132,12 +132,12 @@ export class AssetTable {
|
||||
@Column({ type: 'uuid', nullable: true, index: true })
|
||||
duplicateId!: string | null;
|
||||
|
||||
@Column({ enum: assets_status_enum, default: AssetStatus.ACTIVE })
|
||||
@Column({ enum: assets_status_enum, default: AssetStatus.Active })
|
||||
status!: Generated<AssetStatus>;
|
||||
|
||||
@UpdateIdColumn({ index: true })
|
||||
updateId!: Generated<string>;
|
||||
|
||||
@Column({ enum: asset_visibility_enum, default: AssetVisibility.TIMELINE })
|
||||
@Column({ enum: asset_visibility_enum, default: AssetVisibility.Timeline })
|
||||
visibility!: Generated<AssetVisibility>;
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user