mirror of
https://github.com/immich-app/immich.git
synced 2025-07-09 03:04:16 -04:00
Merge branch 'main' into rknn-toolkit-lite2
This commit is contained in:
commit
f9387d8478
9
.github/workflows/prepare-release.yml
vendored
9
.github/workflows/prepare-release.yml
vendored
@ -68,10 +68,17 @@ jobs:
|
|||||||
needs: build_mobile
|
needs: build_mobile
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
|
- name: Generate a token
|
||||||
|
id: generate-token
|
||||||
|
uses: actions/create-github-app-token@v1
|
||||||
|
with:
|
||||||
|
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
||||||
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.ORG_RELEASE_TOKEN }}
|
token: ${{ steps.generate-token.outputs.token }}
|
||||||
|
|
||||||
- name: Download APK
|
- name: Download APK
|
||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@v4
|
||||||
|
6
cli/package-lock.json
generated
6
cli/package-lock.json
generated
@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "@immich/cli",
|
"name": "@immich/cli",
|
||||||
"version": "2.2.40",
|
"version": "2.2.42",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "@immich/cli",
|
"name": "@immich/cli",
|
||||||
"version": "2.2.40",
|
"version": "2.2.42",
|
||||||
"license": "GNU Affero General Public License version 3",
|
"license": "GNU Affero General Public License version 3",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"fast-glob": "^3.3.2",
|
"fast-glob": "^3.3.2",
|
||||||
@ -52,7 +52,7 @@
|
|||||||
},
|
},
|
||||||
"../open-api/typescript-sdk": {
|
"../open-api/typescript-sdk": {
|
||||||
"name": "@immich/sdk",
|
"name": "@immich/sdk",
|
||||||
"version": "1.124.2",
|
"version": "1.125.1",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "GNU Affero General Public License version 3",
|
"license": "GNU Affero General Public License version 3",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@immich/cli",
|
"name": "@immich/cli",
|
||||||
"version": "2.2.40",
|
"version": "2.2.42",
|
||||||
"description": "Command Line Interface (CLI) for Immich",
|
"description": "Command Line Interface (CLI) for Immich",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"exports": "./dist/index.js",
|
"exports": "./dist/index.js",
|
||||||
|
8
docs/static/archived-versions.json
vendored
8
docs/static/archived-versions.json
vendored
@ -1,4 +1,12 @@
|
|||||||
[
|
[
|
||||||
|
{
|
||||||
|
"label": "v1.125.1",
|
||||||
|
"url": "https://v1.125.1.archive.immich.app"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "v1.125.0",
|
||||||
|
"url": "https://v1.125.0.archive.immich.app"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"label": "v1.124.2",
|
"label": "v1.124.2",
|
||||||
"url": "https://v1.124.2.archive.immich.app"
|
"url": "https://v1.124.2.archive.immich.app"
|
||||||
|
8
e2e/package-lock.json
generated
8
e2e/package-lock.json
generated
@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "immich-e2e",
|
"name": "immich-e2e",
|
||||||
"version": "1.124.2",
|
"version": "1.125.1",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "immich-e2e",
|
"name": "immich-e2e",
|
||||||
"version": "1.124.2",
|
"version": "1.125.1",
|
||||||
"license": "GNU Affero General Public License version 3",
|
"license": "GNU Affero General Public License version 3",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/eslintrc": "^3.1.0",
|
"@eslint/eslintrc": "^3.1.0",
|
||||||
@ -45,7 +45,7 @@
|
|||||||
},
|
},
|
||||||
"../cli": {
|
"../cli": {
|
||||||
"name": "@immich/cli",
|
"name": "@immich/cli",
|
||||||
"version": "2.2.40",
|
"version": "2.2.42",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "GNU Affero General Public License version 3",
|
"license": "GNU Affero General Public License version 3",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@ -92,7 +92,7 @@
|
|||||||
},
|
},
|
||||||
"../open-api/typescript-sdk": {
|
"../open-api/typescript-sdk": {
|
||||||
"name": "@immich/sdk",
|
"name": "@immich/sdk",
|
||||||
"version": "1.124.2",
|
"version": "1.125.1",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "GNU Affero General Public License version 3",
|
"license": "GNU Affero General Public License version 3",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "immich-e2e",
|
"name": "immich-e2e",
|
||||||
"version": "1.124.2",
|
"version": "1.125.1",
|
||||||
"description": "",
|
"description": "",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
@ -172,4 +172,31 @@ describe('/stacks', () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('GET /stacks/:id', () => {
|
||||||
|
it('should include exifInfo in stack assets', async () => {
|
||||||
|
const [asset1, asset2] = await Promise.all([
|
||||||
|
utils.createAsset(user1.accessToken),
|
||||||
|
utils.createAsset(user1.accessToken),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const stack = await utils.createStack(user1.accessToken, [asset1.id, asset2.id]);
|
||||||
|
|
||||||
|
const { status, body } = await request(app)
|
||||||
|
.get(`/stacks/${stack.id}`)
|
||||||
|
.set('Authorization', `Bearer ${user1.accessToken}`);
|
||||||
|
|
||||||
|
expect(status).toBe(200);
|
||||||
|
expect(body).toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
id: stack.id,
|
||||||
|
primaryAssetId: asset1.id,
|
||||||
|
assets: expect.arrayContaining([
|
||||||
|
expect.objectContaining({ id: asset1.id, exifInfo: expect.any(Object) }),
|
||||||
|
expect.objectContaining({ id: asset2.id, exifInfo: expect.any(Object) }),
|
||||||
|
]),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -822,6 +822,7 @@
|
|||||||
"latest_version": "Latest Version",
|
"latest_version": "Latest Version",
|
||||||
"latitude": "Latitude",
|
"latitude": "Latitude",
|
||||||
"leave": "Leave",
|
"leave": "Leave",
|
||||||
|
"lens_model": "Lens model",
|
||||||
"let_others_respond": "Let others respond",
|
"let_others_respond": "Let others respond",
|
||||||
"level": "Level",
|
"level": "Level",
|
||||||
"library": "Library",
|
"library": "Library",
|
||||||
@ -1113,6 +1114,7 @@
|
|||||||
"search_camera_model": "Search camera model...",
|
"search_camera_model": "Search camera model...",
|
||||||
"search_city": "Search city...",
|
"search_city": "Search city...",
|
||||||
"search_country": "Search country...",
|
"search_country": "Search country...",
|
||||||
|
"search_for": "Search for",
|
||||||
"search_for_existing_person": "Search for existing person",
|
"search_for_existing_person": "Search for existing person",
|
||||||
"search_no_people": "No people",
|
"search_no_people": "No people",
|
||||||
"search_no_people_named": "No people named \"{name}\"",
|
"search_no_people_named": "No people named \"{name}\"",
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
[tool.poetry]
|
[tool.poetry]
|
||||||
name = "machine-learning"
|
name = "machine-learning"
|
||||||
version = "1.124.2"
|
version = "1.125.1"
|
||||||
description = ""
|
description = ""
|
||||||
authors = ["Hau Tran <alex.tran1502@gmail.com>"]
|
authors = ["Hau Tran <alex.tran1502@gmail.com>"]
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
|
@ -35,8 +35,8 @@ platform :android do
|
|||||||
task: 'bundle',
|
task: 'bundle',
|
||||||
build_type: 'Release',
|
build_type: 'Release',
|
||||||
properties: {
|
properties: {
|
||||||
"android.injected.version.code" => 175,
|
"android.injected.version.code" => 177,
|
||||||
"android.injected.version.name" => "1.124.2",
|
"android.injected.version.name" => "1.125.1",
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
upload_to_play_store(skip_upload_apk: true, skip_upload_images: true, skip_upload_screenshots: true, aab: '../build/app/outputs/bundle/release/app-release.aab')
|
upload_to_play_store(skip_upload_apk: true, skip_upload_images: true, skip_upload_screenshots: true, aab: '../build/app/outputs/bundle/release/app-release.aab')
|
||||||
|
@ -19,7 +19,7 @@ platform :ios do
|
|||||||
desc "iOS Release"
|
desc "iOS Release"
|
||||||
lane :release do
|
lane :release do
|
||||||
increment_version_number(
|
increment_version_number(
|
||||||
version_number: "1.124.2"
|
version_number: "1.125.1"
|
||||||
)
|
)
|
||||||
increment_build_number(
|
increment_build_number(
|
||||||
build_number: latest_testflight_build_number + 1,
|
build_number: latest_testflight_build_number + 1,
|
||||||
|
2
mobile/openapi/README.md
generated
2
mobile/openapi/README.md
generated
@ -3,7 +3,7 @@ Immich API
|
|||||||
|
|
||||||
This Dart package is automatically generated by the [OpenAPI Generator](https://openapi-generator.tech) project:
|
This Dart package is automatically generated by the [OpenAPI Generator](https://openapi-generator.tech) project:
|
||||||
|
|
||||||
- API version: 1.124.2
|
- API version: 1.125.1
|
||||||
- Generator version: 7.8.0
|
- Generator version: 7.8.0
|
||||||
- Build package: org.openapitools.codegen.languages.DartClientCodegen
|
- Build package: org.openapitools.codegen.languages.DartClientCodegen
|
||||||
|
|
||||||
|
@ -2,7 +2,7 @@ name: immich_mobile
|
|||||||
description: Immich - selfhosted backup media file on mobile phone
|
description: Immich - selfhosted backup media file on mobile phone
|
||||||
|
|
||||||
publish_to: 'none'
|
publish_to: 'none'
|
||||||
version: 1.124.2+175
|
version: 1.125.1+177
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: '>=3.3.0 <4.0.0'
|
sdk: '>=3.3.0 <4.0.0'
|
||||||
|
@ -7454,7 +7454,7 @@
|
|||||||
"info": {
|
"info": {
|
||||||
"title": "Immich",
|
"title": "Immich",
|
||||||
"description": "Immich API",
|
"description": "Immich API",
|
||||||
"version": "1.124.2",
|
"version": "1.125.1",
|
||||||
"contact": {}
|
"contact": {}
|
||||||
},
|
},
|
||||||
"tags": [],
|
"tags": [],
|
||||||
|
4
open-api/typescript-sdk/package-lock.json
generated
4
open-api/typescript-sdk/package-lock.json
generated
@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "@immich/sdk",
|
"name": "@immich/sdk",
|
||||||
"version": "1.124.2",
|
"version": "1.125.1",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "@immich/sdk",
|
"name": "@immich/sdk",
|
||||||
"version": "1.124.2",
|
"version": "1.125.1",
|
||||||
"license": "GNU Affero General Public License version 3",
|
"license": "GNU Affero General Public License version 3",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@oazapfts/runtime": "^1.0.2"
|
"@oazapfts/runtime": "^1.0.2"
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@immich/sdk",
|
"name": "@immich/sdk",
|
||||||
"version": "1.124.2",
|
"version": "1.125.1",
|
||||||
"description": "Auto-generated TypeScript SDK for the Immich API",
|
"description": "Auto-generated TypeScript SDK for the Immich API",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "./build/index.js",
|
"main": "./build/index.js",
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
/**
|
/**
|
||||||
* Immich
|
* Immich
|
||||||
* 1.124.2
|
* 1.125.1
|
||||||
* DO NOT MODIFY - This file has been generated using oazapfts.
|
* DO NOT MODIFY - This file has been generated using oazapfts.
|
||||||
* See https://www.npmjs.com/package/oazapfts
|
* See https://www.npmjs.com/package/oazapfts
|
||||||
*/
|
*/
|
||||||
|
4
server/package-lock.json
generated
4
server/package-lock.json
generated
@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "immich",
|
"name": "immich",
|
||||||
"version": "1.124.2",
|
"version": "1.125.1",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "immich",
|
"name": "immich",
|
||||||
"version": "1.124.2",
|
"version": "1.125.1",
|
||||||
"license": "GNU Affero General Public License version 3",
|
"license": "GNU Affero General Public License version 3",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nestjs/bullmq": "^11.0.0",
|
"@nestjs/bullmq": "^11.0.0",
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "immich",
|
"name": "immich",
|
||||||
"version": "1.124.2",
|
"version": "1.125.1",
|
||||||
"description": "",
|
"description": "",
|
||||||
"author": "",
|
"author": "",
|
||||||
"private": true,
|
"private": true,
|
||||||
|
@ -13,7 +13,6 @@ import { entities } from 'src/entities';
|
|||||||
import { ImmichWorker } from 'src/enum';
|
import { ImmichWorker } from 'src/enum';
|
||||||
import { IEventRepository } from 'src/interfaces/event.interface';
|
import { IEventRepository } from 'src/interfaces/event.interface';
|
||||||
import { IJobRepository } from 'src/interfaces/job.interface';
|
import { IJobRepository } from 'src/interfaces/job.interface';
|
||||||
import { ITelemetryRepository } from 'src/interfaces/telemetry.interface';
|
|
||||||
import { AuthGuard } from 'src/middleware/auth.guard';
|
import { AuthGuard } from 'src/middleware/auth.guard';
|
||||||
import { ErrorInterceptor } from 'src/middleware/error.interceptor';
|
import { ErrorInterceptor } from 'src/middleware/error.interceptor';
|
||||||
import { FileUploadInterceptor } from 'src/middleware/file-upload.interceptor';
|
import { FileUploadInterceptor } from 'src/middleware/file-upload.interceptor';
|
||||||
@ -22,7 +21,7 @@ import { LoggingInterceptor } from 'src/middleware/logging.interceptor';
|
|||||||
import { providers, repositories } from 'src/repositories';
|
import { providers, repositories } from 'src/repositories';
|
||||||
import { ConfigRepository } from 'src/repositories/config.repository';
|
import { ConfigRepository } from 'src/repositories/config.repository';
|
||||||
import { LoggingRepository } from 'src/repositories/logging.repository';
|
import { LoggingRepository } from 'src/repositories/logging.repository';
|
||||||
import { teardownTelemetry } from 'src/repositories/telemetry.repository';
|
import { teardownTelemetry, TelemetryRepository } from 'src/repositories/telemetry.repository';
|
||||||
import { services } from 'src/services';
|
import { services } from 'src/services';
|
||||||
import { CliService } from 'src/services/cli.service';
|
import { CliService } from 'src/services/cli.service';
|
||||||
import { DatabaseService } from 'src/services/database.service';
|
import { DatabaseService } from 'src/services/database.service';
|
||||||
@ -67,7 +66,7 @@ class BaseModule implements OnModuleInit, OnModuleDestroy {
|
|||||||
logger: LoggingRepository,
|
logger: LoggingRepository,
|
||||||
@Inject(IEventRepository) private eventRepository: IEventRepository,
|
@Inject(IEventRepository) private eventRepository: IEventRepository,
|
||||||
@Inject(IJobRepository) private jobRepository: IJobRepository,
|
@Inject(IJobRepository) private jobRepository: IJobRepository,
|
||||||
@Inject(ITelemetryRepository) private telemetryRepository: ITelemetryRepository,
|
private telemetryRepository: TelemetryRepository,
|
||||||
) {
|
) {
|
||||||
logger.setAppName(this.worker);
|
logger.setAppName(this.worker);
|
||||||
}
|
}
|
||||||
|
@ -3,8 +3,8 @@ import { ApiTags } from '@nestjs/swagger';
|
|||||||
import { AuthDto } from 'src/dtos/auth.dto';
|
import { AuthDto } from 'src/dtos/auth.dto';
|
||||||
import { TemplateDto, TemplateResponseDto, TestEmailResponseDto } from 'src/dtos/notification.dto';
|
import { TemplateDto, TemplateResponseDto, TestEmailResponseDto } from 'src/dtos/notification.dto';
|
||||||
import { SystemConfigSmtpDto } from 'src/dtos/system-config.dto';
|
import { SystemConfigSmtpDto } from 'src/dtos/system-config.dto';
|
||||||
import { EmailTemplate } from 'src/interfaces/notification.interface';
|
|
||||||
import { Auth, Authenticated } from 'src/middleware/auth.guard';
|
import { Auth, Authenticated } from 'src/middleware/auth.guard';
|
||||||
|
import { EmailTemplate } from 'src/repositories/notification.repository';
|
||||||
import { NotificationService } from 'src/services/notification.service';
|
import { NotificationService } from 'src/services/notification.service';
|
||||||
|
|
||||||
@ApiTags('Notifications')
|
@ApiTags('Notifications')
|
||||||
|
@ -2,7 +2,7 @@ import { Img, Link, Section, Text } from '@react-email/components';
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { ImmichButton } from 'src/emails/components/button.component';
|
import { ImmichButton } from 'src/emails/components/button.component';
|
||||||
import ImmichLayout from 'src/emails/components/immich.layout';
|
import ImmichLayout from 'src/emails/components/immich.layout';
|
||||||
import { AlbumInviteEmailProps } from 'src/interfaces/notification.interface';
|
import { AlbumInviteEmailProps } from 'src/repositories/notification.repository';
|
||||||
import { replaceTemplateTags } from 'src/utils/replace-template-tags';
|
import { replaceTemplateTags } from 'src/utils/replace-template-tags';
|
||||||
|
|
||||||
export const AlbumInviteEmail = ({
|
export const AlbumInviteEmail = ({
|
||||||
|
@ -2,7 +2,7 @@ import { Img, Link, Section, Text } from '@react-email/components';
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { ImmichButton } from 'src/emails/components/button.component';
|
import { ImmichButton } from 'src/emails/components/button.component';
|
||||||
import ImmichLayout from 'src/emails/components/immich.layout';
|
import ImmichLayout from 'src/emails/components/immich.layout';
|
||||||
import { AlbumUpdateEmailProps } from 'src/interfaces/notification.interface';
|
import { AlbumUpdateEmailProps } from 'src/repositories/notification.repository';
|
||||||
import { replaceTemplateTags } from 'src/utils/replace-template-tags';
|
import { replaceTemplateTags } from 'src/utils/replace-template-tags';
|
||||||
|
|
||||||
export const AlbumUpdateEmail = ({
|
export const AlbumUpdateEmail = ({
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { Link, Row, Text } from '@react-email/components';
|
import { Link, Row, Text } from '@react-email/components';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import ImmichLayout from 'src/emails/components/immich.layout';
|
import ImmichLayout from 'src/emails/components/immich.layout';
|
||||||
import { TestEmailProps } from 'src/interfaces/notification.interface';
|
import { TestEmailProps } from 'src/repositories/notification.repository';
|
||||||
|
|
||||||
export const TestEmail = ({ baseUrl, displayName }: TestEmailProps) => (
|
export const TestEmail = ({ baseUrl, displayName }: TestEmailProps) => (
|
||||||
<ImmichLayout preview="This is a test email from Immich.">
|
<ImmichLayout preview="This is a test email from Immich.">
|
||||||
|
@ -2,7 +2,7 @@ import { Link, Section, Text } from '@react-email/components';
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { ImmichButton } from 'src/emails/components/button.component';
|
import { ImmichButton } from 'src/emails/components/button.component';
|
||||||
import ImmichLayout from 'src/emails/components/immich.layout';
|
import ImmichLayout from 'src/emails/components/immich.layout';
|
||||||
import { WelcomeEmailProps } from 'src/interfaces/notification.interface';
|
import { WelcomeEmailProps } from 'src/repositories/notification.repository';
|
||||||
import { replaceTemplateTags } from 'src/utils/replace-template-tags';
|
import { replaceTemplateTags } from 'src/utils/replace-template-tags';
|
||||||
|
|
||||||
export const WelcomeEmail = ({ baseUrl, displayName, username, password, customTemplate }: WelcomeEmailProps) => {
|
export const WelcomeEmail = ({ baseUrl, displayName, username, password, customTemplate }: WelcomeEmailProps) => {
|
||||||
|
@ -1,18 +0,0 @@
|
|||||||
import { Insertable, Selectable, Updateable } from 'kysely';
|
|
||||||
import { AlbumsSharedUsersUsers } from 'src/db';
|
|
||||||
|
|
||||||
export const IAlbumUserRepository = 'IAlbumUserRepository';
|
|
||||||
|
|
||||||
export type AlbumPermissionId = {
|
|
||||||
albumsId: string;
|
|
||||||
usersId: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export interface IAlbumUserRepository {
|
|
||||||
create(albumUser: Insertable<AlbumsSharedUsersUsers>): Promise<Selectable<AlbumsSharedUsersUsers>>;
|
|
||||||
update(
|
|
||||||
id: AlbumPermissionId,
|
|
||||||
albumPermission: Updateable<AlbumsSharedUsersUsers>,
|
|
||||||
): Promise<Selectable<AlbumsSharedUsersUsers>>;
|
|
||||||
delete(id: AlbumPermissionId): Promise<void>;
|
|
||||||
}
|
|
@ -1,20 +0,0 @@
|
|||||||
export const ICronRepository = 'ICronRepository';
|
|
||||||
|
|
||||||
type CronBase = {
|
|
||||||
name: string;
|
|
||||||
start?: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type CronCreate = CronBase & {
|
|
||||||
expression: string;
|
|
||||||
onTick: () => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type CronUpdate = CronBase & {
|
|
||||||
expression?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export interface ICronRepository {
|
|
||||||
create(cron: CronCreate): void;
|
|
||||||
update(cron: CronUpdate): void;
|
|
||||||
}
|
|
@ -1,5 +1,5 @@
|
|||||||
import { ClassConstructor } from 'class-transformer';
|
import { ClassConstructor } from 'class-transformer';
|
||||||
import { EmailImageAttachment } from 'src/interfaces/notification.interface';
|
import { EmailImageAttachment } from 'src/repositories/notification.repository';
|
||||||
|
|
||||||
export enum QueueName {
|
export enum QueueName {
|
||||||
THUMBNAIL_GENERATION = 'thumbnailGeneration',
|
THUMBNAIL_GENERATION = 'thumbnailGeneration',
|
||||||
|
@ -1,31 +0,0 @@
|
|||||||
export const IMapRepository = 'IMapRepository';
|
|
||||||
|
|
||||||
export interface MapMarkerSearchOptions {
|
|
||||||
isArchived?: boolean;
|
|
||||||
isFavorite?: boolean;
|
|
||||||
fileCreatedBefore?: Date;
|
|
||||||
fileCreatedAfter?: Date;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface GeoPoint {
|
|
||||||
latitude: number;
|
|
||||||
longitude: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ReverseGeocodeResult {
|
|
||||||
country: string | null;
|
|
||||||
state: string | null;
|
|
||||||
city: string | null;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface MapMarker extends ReverseGeocodeResult {
|
|
||||||
id: string;
|
|
||||||
lat: number;
|
|
||||||
lon: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IMapRepository {
|
|
||||||
init(): Promise<void>;
|
|
||||||
reverseGeocode(point: GeoPoint): Promise<ReverseGeocodeResult>;
|
|
||||||
getMapMarkers(ownerIds: string[], albumIds: string[], options?: MapMarkerSearchOptions): Promise<MapMarker[]>;
|
|
||||||
}
|
|
@ -1,71 +0,0 @@
|
|||||||
import { BinaryField, Tags } from 'exiftool-vendored';
|
|
||||||
|
|
||||||
export const IMetadataRepository = 'IMetadataRepository';
|
|
||||||
|
|
||||||
export interface ExifDuration {
|
|
||||||
Value: number;
|
|
||||||
Scale?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
type StringOrNumber = string | number;
|
|
||||||
|
|
||||||
type TagsWithWrongTypes =
|
|
||||||
| 'FocalLength'
|
|
||||||
| 'Duration'
|
|
||||||
| 'Description'
|
|
||||||
| 'ImageDescription'
|
|
||||||
| 'RegionInfo'
|
|
||||||
| 'TagsList'
|
|
||||||
| 'Keywords'
|
|
||||||
| 'HierarchicalSubject'
|
|
||||||
| 'ISO';
|
|
||||||
export interface ImmichTags extends Omit<Tags, TagsWithWrongTypes> {
|
|
||||||
ContentIdentifier?: string;
|
|
||||||
MotionPhoto?: number;
|
|
||||||
MotionPhotoVersion?: number;
|
|
||||||
MotionPhotoPresentationTimestampUs?: number;
|
|
||||||
MediaGroupUUID?: string;
|
|
||||||
ImagePixelDepth?: string;
|
|
||||||
FocalLength?: number;
|
|
||||||
Duration?: number | string | ExifDuration;
|
|
||||||
EmbeddedVideoType?: string;
|
|
||||||
EmbeddedVideoFile?: BinaryField;
|
|
||||||
MotionPhotoVideo?: BinaryField;
|
|
||||||
TagsList?: StringOrNumber[];
|
|
||||||
HierarchicalSubject?: StringOrNumber[];
|
|
||||||
Keywords?: StringOrNumber | StringOrNumber[];
|
|
||||||
ISO?: number | number[];
|
|
||||||
|
|
||||||
// Type is wrong, can also be number.
|
|
||||||
Description?: StringOrNumber;
|
|
||||||
ImageDescription?: StringOrNumber;
|
|
||||||
|
|
||||||
// Extended properties for image regions, such as faces
|
|
||||||
RegionInfo?: {
|
|
||||||
AppliedToDimensions: {
|
|
||||||
W: number;
|
|
||||||
H: number;
|
|
||||||
Unit: string;
|
|
||||||
};
|
|
||||||
RegionList: {
|
|
||||||
Area: {
|
|
||||||
// (X,Y) // center of the rectangle
|
|
||||||
X: number;
|
|
||||||
Y: number;
|
|
||||||
W: number;
|
|
||||||
H: number;
|
|
||||||
Unit: string;
|
|
||||||
};
|
|
||||||
Rotation?: number;
|
|
||||||
Type?: string;
|
|
||||||
Name?: string;
|
|
||||||
}[];
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IMetadataRepository {
|
|
||||||
teardown(): Promise<void>;
|
|
||||||
readTags(path: string): Promise<ImmichTags>;
|
|
||||||
writeTags(path: string, tags: Partial<Tags>): Promise<void>;
|
|
||||||
extractBinaryTag(tagName: string, path: string): Promise<Buffer>;
|
|
||||||
}
|
|
@ -1,101 +0,0 @@
|
|||||||
export const INotificationRepository = 'INotificationRepository';
|
|
||||||
|
|
||||||
export type EmailImageAttachment = {
|
|
||||||
filename: string;
|
|
||||||
path: string;
|
|
||||||
cid: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type SendEmailOptions = {
|
|
||||||
from: string;
|
|
||||||
to: string;
|
|
||||||
replyTo?: string;
|
|
||||||
subject: string;
|
|
||||||
html: string;
|
|
||||||
text: string;
|
|
||||||
imageAttachments?: EmailImageAttachment[];
|
|
||||||
smtp: SmtpOptions;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type SmtpOptions = {
|
|
||||||
host: string;
|
|
||||||
port?: number;
|
|
||||||
username?: string;
|
|
||||||
password?: string;
|
|
||||||
ignoreCert?: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
export enum EmailTemplate {
|
|
||||||
TEST_EMAIL = 'test',
|
|
||||||
|
|
||||||
// AUTH
|
|
||||||
WELCOME = 'welcome',
|
|
||||||
RESET_PASSWORD = 'reset-password',
|
|
||||||
|
|
||||||
// ALBUM
|
|
||||||
ALBUM_INVITE = 'album-invite',
|
|
||||||
ALBUM_UPDATE = 'album-update',
|
|
||||||
}
|
|
||||||
|
|
||||||
interface BaseEmailProps {
|
|
||||||
baseUrl: string;
|
|
||||||
customTemplate?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface TestEmailProps extends BaseEmailProps {
|
|
||||||
displayName: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface WelcomeEmailProps extends BaseEmailProps {
|
|
||||||
displayName: string;
|
|
||||||
username: string;
|
|
||||||
password?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface AlbumInviteEmailProps extends BaseEmailProps {
|
|
||||||
albumName: string;
|
|
||||||
albumId: string;
|
|
||||||
senderName: string;
|
|
||||||
recipientName: string;
|
|
||||||
cid?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface AlbumUpdateEmailProps extends BaseEmailProps {
|
|
||||||
albumName: string;
|
|
||||||
albumId: string;
|
|
||||||
recipientName: string;
|
|
||||||
cid?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type EmailRenderRequest =
|
|
||||||
| {
|
|
||||||
template: EmailTemplate.TEST_EMAIL;
|
|
||||||
data: TestEmailProps;
|
|
||||||
customTemplate: string;
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
template: EmailTemplate.WELCOME;
|
|
||||||
data: WelcomeEmailProps;
|
|
||||||
customTemplate: string;
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
template: EmailTemplate.ALBUM_INVITE;
|
|
||||||
data: AlbumInviteEmailProps;
|
|
||||||
customTemplate: string;
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
template: EmailTemplate.ALBUM_UPDATE;
|
|
||||||
data: AlbumUpdateEmailProps;
|
|
||||||
customTemplate: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type SendEmailResponse = {
|
|
||||||
messageId: string;
|
|
||||||
response: any;
|
|
||||||
};
|
|
||||||
|
|
||||||
export interface INotificationRepository {
|
|
||||||
renderEmail(request: EmailRenderRequest): Promise<{ html: string; text: string }>;
|
|
||||||
sendEmail(options: SendEmailOptions): Promise<SendEmailResponse>;
|
|
||||||
verifySmtp(options: SmtpOptions): Promise<true>;
|
|
||||||
}
|
|
@ -1,22 +0,0 @@
|
|||||||
import { UserinfoResponse } from 'openid-client';
|
|
||||||
|
|
||||||
export const IOAuthRepository = 'IOAuthRepository';
|
|
||||||
|
|
||||||
export type OAuthConfig = {
|
|
||||||
clientId: string;
|
|
||||||
clientSecret: string;
|
|
||||||
issuerUrl: string;
|
|
||||||
mobileOverrideEnabled: boolean;
|
|
||||||
mobileRedirectUri: string;
|
|
||||||
profileSigningAlgorithm: string;
|
|
||||||
scope: string;
|
|
||||||
signingAlgorithm: string;
|
|
||||||
};
|
|
||||||
export type OAuthProfile = UserinfoResponse;
|
|
||||||
|
|
||||||
export interface IOAuthRepository {
|
|
||||||
init(): void;
|
|
||||||
authorize(config: OAuthConfig, redirectUrl: string): Promise<string>;
|
|
||||||
getLogoutEndpoint(config: OAuthConfig): Promise<string | undefined>;
|
|
||||||
getProfile(config: OAuthConfig, url: string, redirectUrl: string): Promise<OAuthProfile>;
|
|
||||||
}
|
|
@ -1,24 +0,0 @@
|
|||||||
export interface GitHubRelease {
|
|
||||||
id: number;
|
|
||||||
url: string;
|
|
||||||
tag_name: string;
|
|
||||||
name: string;
|
|
||||||
created_at: string;
|
|
||||||
published_at: string;
|
|
||||||
body: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ServerBuildVersions {
|
|
||||||
nodejs: string;
|
|
||||||
ffmpeg: string;
|
|
||||||
libvips: string;
|
|
||||||
exiftool: string;
|
|
||||||
imagemagick: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const IServerInfoRepository = 'IServerInfoRepository';
|
|
||||||
|
|
||||||
export interface IServerInfoRepository {
|
|
||||||
getGitHubRelease(): Promise<GitHubRelease>;
|
|
||||||
getBuildVersions(): Promise<ServerBuildVersions>;
|
|
||||||
}
|
|
@ -1,23 +0,0 @@
|
|||||||
import { MetricOptions } from '@opentelemetry/api';
|
|
||||||
import { ClassConstructor } from 'class-transformer';
|
|
||||||
|
|
||||||
export const ITelemetryRepository = 'ITelemetryRepository';
|
|
||||||
|
|
||||||
export interface MetricGroupOptions {
|
|
||||||
enabled: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IMetricGroupRepository {
|
|
||||||
addToCounter(name: string, value: number, options?: MetricOptions): void;
|
|
||||||
addToGauge(name: string, value: number, options?: MetricOptions): void;
|
|
||||||
addToHistogram(name: string, value: number, options?: MetricOptions): void;
|
|
||||||
configure(options: MetricGroupOptions): this;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ITelemetryRepository {
|
|
||||||
setup(options: { repositories: ClassConstructor<unknown>[] }): void;
|
|
||||||
api: IMetricGroupRepository;
|
|
||||||
host: IMetricGroupRepository;
|
|
||||||
jobs: IMetricGroupRepository;
|
|
||||||
repo: IMetricGroupRepository;
|
|
||||||
}
|
|
@ -1,8 +0,0 @@
|
|||||||
export const ITrashRepository = 'ITrashRepository';
|
|
||||||
|
|
||||||
export interface ITrashRepository {
|
|
||||||
empty(userId: string): Promise<number>;
|
|
||||||
restore(userId: string): Promise<number>;
|
|
||||||
restoreAll(assetIds: string[]): Promise<number>;
|
|
||||||
getDeletedIds(): AsyncIterableIterator<{ id: string }>;
|
|
||||||
}
|
|
@ -1,9 +0,0 @@
|
|||||||
import { VersionHistoryEntity } from 'src/entities/version-history.entity';
|
|
||||||
|
|
||||||
export const IVersionHistoryRepository = 'IVersionHistoryRepository';
|
|
||||||
|
|
||||||
export interface IVersionHistoryRepository {
|
|
||||||
create(version: Omit<VersionHistoryEntity, 'id' | 'createdAt'>): Promise<VersionHistoryEntity>;
|
|
||||||
getAll(): Promise<VersionHistoryEntity[]>;
|
|
||||||
getLatest(): Promise<VersionHistoryEntity | undefined>;
|
|
||||||
}
|
|
102
server/src/migrations/1737672307560-AddUpdatedAtTriggers.ts
Normal file
102
server/src/migrations/1737672307560-AddUpdatedAtTriggers.ts
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||||
|
|
||||||
|
export class AddUpdatedAtTriggers1737672307560 implements MigrationInterface {
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(`
|
||||||
|
create function updated_at()
|
||||||
|
returns trigger as $$
|
||||||
|
begin
|
||||||
|
new."updatedAt" = now();
|
||||||
|
return new;
|
||||||
|
end;
|
||||||
|
$$ language 'plpgsql'`);
|
||||||
|
|
||||||
|
await queryRunner.query(`
|
||||||
|
create trigger activity_updated_at
|
||||||
|
before update on activity
|
||||||
|
for each row execute procedure updated_at()
|
||||||
|
`);
|
||||||
|
|
||||||
|
await queryRunner.query(`
|
||||||
|
create trigger albums_updated_at
|
||||||
|
before update on albums
|
||||||
|
for each row execute procedure updated_at()
|
||||||
|
`);
|
||||||
|
|
||||||
|
await queryRunner.query(`
|
||||||
|
create trigger api_keys_updated_at
|
||||||
|
before update on api_keys
|
||||||
|
for each row execute procedure updated_at()
|
||||||
|
`);
|
||||||
|
|
||||||
|
await queryRunner.query(`
|
||||||
|
create trigger asset_files_updated_at
|
||||||
|
before update on asset_files
|
||||||
|
for each row execute procedure updated_at()
|
||||||
|
`);
|
||||||
|
|
||||||
|
await queryRunner.query(`
|
||||||
|
create trigger assets_updated_at
|
||||||
|
before update on assets
|
||||||
|
for each row execute procedure updated_at()
|
||||||
|
`);
|
||||||
|
|
||||||
|
await queryRunner.query(`
|
||||||
|
create trigger libraries_updated_at
|
||||||
|
before update on libraries
|
||||||
|
for each row execute procedure updated_at()
|
||||||
|
`);
|
||||||
|
|
||||||
|
await queryRunner.query(`
|
||||||
|
create trigger memories_updated_at
|
||||||
|
before update on memories
|
||||||
|
for each row execute procedure updated_at()
|
||||||
|
`);
|
||||||
|
|
||||||
|
await queryRunner.query(`
|
||||||
|
create trigger partners_updated_at
|
||||||
|
before update on partners
|
||||||
|
for each row execute procedure updated_at()
|
||||||
|
`);
|
||||||
|
|
||||||
|
await queryRunner.query(`
|
||||||
|
create trigger person_updated_at
|
||||||
|
before update on person
|
||||||
|
for each row execute procedure updated_at()
|
||||||
|
`);
|
||||||
|
|
||||||
|
await queryRunner.query(`
|
||||||
|
create trigger sessions_updated_at
|
||||||
|
before update on sessions
|
||||||
|
for each row execute procedure updated_at()
|
||||||
|
`);
|
||||||
|
|
||||||
|
await queryRunner.query(`
|
||||||
|
create trigger tags_updated_at
|
||||||
|
before update on tags
|
||||||
|
for each row execute procedure updated_at()
|
||||||
|
`);
|
||||||
|
|
||||||
|
await queryRunner.query(`
|
||||||
|
create trigger users_updated_at
|
||||||
|
before update on users
|
||||||
|
for each row execute procedure updated_at()
|
||||||
|
`);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(`drop trigger activity_updated_at on activity`);
|
||||||
|
await queryRunner.query(`drop trigger albums_updated_at on albums`);
|
||||||
|
await queryRunner.query(`drop trigger api_keys_updated_at on api_keys`);
|
||||||
|
await queryRunner.query(`drop trigger asset_files_updated_at on asset_files`);
|
||||||
|
await queryRunner.query(`drop trigger assets_updated_at on assets`);
|
||||||
|
await queryRunner.query(`drop trigger libraries_updated_at on libraries`);
|
||||||
|
await queryRunner.query(`drop trigger memories_updated_at on memories`);
|
||||||
|
await queryRunner.query(`drop trigger partners_updated_at on partners`);
|
||||||
|
await queryRunner.query(`drop trigger person_updated_at on person`);
|
||||||
|
await queryRunner.query(`drop trigger sessions_updated_at on sessions`);
|
||||||
|
await queryRunner.query(`drop trigger tags_updated_at on tags`);
|
||||||
|
await queryRunner.query(`drop trigger users_updated_at on users`);
|
||||||
|
await queryRunner.query(`drop function updated_at_trigger`);
|
||||||
|
}
|
||||||
|
}
|
@ -9,9 +9,18 @@ select
|
|||||||
from
|
from
|
||||||
(
|
(
|
||||||
select
|
select
|
||||||
*
|
"assets".*,
|
||||||
|
to_json("exifInfo") as "exifInfo"
|
||||||
from
|
from
|
||||||
"assets"
|
"assets"
|
||||||
|
inner join lateral (
|
||||||
|
select
|
||||||
|
"exif".*
|
||||||
|
from
|
||||||
|
"exif"
|
||||||
|
where
|
||||||
|
"exif"."assetId" = "assets"."id"
|
||||||
|
) as "exifInfo" on true
|
||||||
where
|
where
|
||||||
"assets"."deletedAt" is null
|
"assets"."deletedAt" is null
|
||||||
and "assets"."stackId" = "asset_stack"."id"
|
and "assets"."stackId" = "asset_stack"."id"
|
||||||
@ -31,7 +40,7 @@ select
|
|||||||
from
|
from
|
||||||
(
|
(
|
||||||
select
|
select
|
||||||
*,
|
"assets".*,
|
||||||
(
|
(
|
||||||
select
|
select
|
||||||
coalesce(json_agg(agg), '[]')
|
coalesce(json_agg(agg), '[]')
|
||||||
@ -45,9 +54,18 @@ select
|
|||||||
where
|
where
|
||||||
"tag_asset"."assetsId" = "assets"."id"
|
"tag_asset"."assetsId" = "assets"."id"
|
||||||
) as agg
|
) as agg
|
||||||
) as "tags"
|
) as "tags",
|
||||||
|
to_json("exifInfo") as "exifInfo"
|
||||||
from
|
from
|
||||||
"assets"
|
"assets"
|
||||||
|
inner join lateral (
|
||||||
|
select
|
||||||
|
"exif".*
|
||||||
|
from
|
||||||
|
"exif"
|
||||||
|
where
|
||||||
|
"exif"."assetId" = "assets"."id"
|
||||||
|
) as "exifInfo" on true
|
||||||
where
|
where
|
||||||
"assets"."deletedAt" is null
|
"assets"."deletedAt" is null
|
||||||
and "assets"."stackId" = "asset_stack"."id"
|
and "assets"."stackId" = "asset_stack"."id"
|
||||||
@ -67,7 +85,7 @@ select
|
|||||||
from
|
from
|
||||||
(
|
(
|
||||||
select
|
select
|
||||||
*,
|
"assets".*,
|
||||||
(
|
(
|
||||||
select
|
select
|
||||||
coalesce(json_agg(agg), '[]')
|
coalesce(json_agg(agg), '[]')
|
||||||
@ -81,9 +99,18 @@ select
|
|||||||
where
|
where
|
||||||
"tag_asset"."assetsId" = "assets"."id"
|
"tag_asset"."assetsId" = "assets"."id"
|
||||||
) as agg
|
) as agg
|
||||||
) as "tags"
|
) as "tags",
|
||||||
|
to_json("exifInfo") as "exifInfo"
|
||||||
from
|
from
|
||||||
"assets"
|
"assets"
|
||||||
|
inner join lateral (
|
||||||
|
select
|
||||||
|
"exif".*
|
||||||
|
from
|
||||||
|
"exif"
|
||||||
|
where
|
||||||
|
"exif"."assetId" = "assets"."id"
|
||||||
|
) as "exifInfo" on true
|
||||||
where
|
where
|
||||||
"assets"."deletedAt" is null
|
"assets"."deletedAt" is null
|
||||||
and "assets"."stackId" = "asset_stack"."id"
|
and "assets"."stackId" = "asset_stack"."id"
|
||||||
|
@ -4,10 +4,14 @@ import { InjectKysely } from 'nestjs-kysely';
|
|||||||
import { AlbumsSharedUsersUsers, DB } from 'src/db';
|
import { AlbumsSharedUsersUsers, DB } from 'src/db';
|
||||||
import { DummyValue, GenerateSql } from 'src/decorators';
|
import { DummyValue, GenerateSql } from 'src/decorators';
|
||||||
import { AlbumUserRole } from 'src/enum';
|
import { AlbumUserRole } from 'src/enum';
|
||||||
import { AlbumPermissionId, IAlbumUserRepository } from 'src/interfaces/album-user.interface';
|
|
||||||
|
export type AlbumPermissionId = {
|
||||||
|
albumsId: string;
|
||||||
|
usersId: string;
|
||||||
|
};
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class AlbumUserRepository implements IAlbumUserRepository {
|
export class AlbumUserRepository {
|
||||||
constructor(@InjectKysely() private db: Kysely<DB>) {}
|
constructor(@InjectKysely() private db: Kysely<DB>) {}
|
||||||
|
|
||||||
@GenerateSql({ params: [{ usersId: DummyValue.UUID, albumsId: DummyValue.UUID }] })
|
@GenerateSql({ params: [{ usersId: DummyValue.UUID, albumsId: DummyValue.UUID }] })
|
||||||
@ -16,10 +20,7 @@ export class AlbumUserRepository implements IAlbumUserRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@GenerateSql({ params: [{ usersId: DummyValue.UUID, albumsId: DummyValue.UUID }, { role: AlbumUserRole.VIEWER }] })
|
@GenerateSql({ params: [{ usersId: DummyValue.UUID, albumsId: DummyValue.UUID }, { role: AlbumUserRole.VIEWER }] })
|
||||||
update(
|
update({ usersId, albumsId }: AlbumPermissionId, dto: Updateable<AlbumsSharedUsersUsers>) {
|
||||||
{ usersId, albumsId }: AlbumPermissionId,
|
|
||||||
dto: Updateable<AlbumsSharedUsersUsers>,
|
|
||||||
): Promise<Selectable<AlbumsSharedUsersUsers>> {
|
|
||||||
return this.db
|
return this.db
|
||||||
.updateTable('albums_shared_users_users')
|
.updateTable('albums_shared_users_users')
|
||||||
.set(dto)
|
.set(dto)
|
||||||
|
@ -43,8 +43,8 @@ import {
|
|||||||
WithProperty,
|
WithProperty,
|
||||||
WithoutProperty,
|
WithoutProperty,
|
||||||
} from 'src/interfaces/asset.interface';
|
} from 'src/interfaces/asset.interface';
|
||||||
import { MapMarker, MapMarkerSearchOptions } from 'src/interfaces/map.interface';
|
|
||||||
import { AssetSearchOptions, SearchExploreItem, SearchExploreItemSet } from 'src/interfaces/search.interface';
|
import { AssetSearchOptions, SearchExploreItem, SearchExploreItemSet } from 'src/interfaces/search.interface';
|
||||||
|
import { MapMarker, MapMarkerSearchOptions } from 'src/repositories/map.repository';
|
||||||
import { anyUuid, asUuid, mapUpsertColumns } from 'src/utils/database';
|
import { anyUuid, asUuid, mapUpsertColumns } from 'src/utils/database';
|
||||||
import { Paginated, PaginationOptions, paginationHelper } from 'src/utils/pagination';
|
import { Paginated, PaginationOptions, paginationHelper } from 'src/utils/pagination';
|
||||||
|
|
||||||
@ -605,10 +605,10 @@ export class AssetRepository implements IAssetRepository {
|
|||||||
.where((eb) => eb.or([eb('assets.stackId', 'is', null), eb(eb.table('asset_stack'), 'is not', null)])),
|
.where((eb) => eb.or([eb('assets.stackId', 'is', null), eb(eb.table('asset_stack'), 'is not', null)])),
|
||||||
)
|
)
|
||||||
.$if(!!options.userIds, (qb) => qb.where('assets.ownerId', '=', anyUuid(options.userIds!)))
|
.$if(!!options.userIds, (qb) => qb.where('assets.ownerId', '=', anyUuid(options.userIds!)))
|
||||||
.$if(!!options.isArchived, (qb) => qb.where('assets.isArchived', '=', options.isArchived!))
|
.$if(options.isArchived !== undefined, (qb) => qb.where('assets.isArchived', '=', options.isArchived!))
|
||||||
.$if(!!options.isFavorite, (qb) => qb.where('assets.isFavorite', '=', options.isFavorite!))
|
.$if(options.isFavorite !== undefined, (qb) => qb.where('assets.isFavorite', '=', options.isFavorite!))
|
||||||
.$if(!!options.assetType, (qb) => qb.where('assets.type', '=', options.assetType!))
|
.$if(!!options.assetType, (qb) => qb.where('assets.type', '=', options.assetType!))
|
||||||
.$if(!!options.isDuplicate, (qb) =>
|
.$if(options.isDuplicate !== undefined, (qb) =>
|
||||||
qb.where('assets.duplicateId', options.isDuplicate ? 'is not' : 'is', null),
|
qb.where('assets.duplicateId', options.isDuplicate ? 'is not' : 'is', null),
|
||||||
)
|
)
|
||||||
.$if(!!options.tagId, (qb) => withTagId(qb, options.tagId!)),
|
.$if(!!options.tagId, (qb) => withTagId(qb, options.tagId!)),
|
||||||
@ -622,7 +622,7 @@ export class AssetRepository implements IAssetRepository {
|
|||||||
*/
|
*/
|
||||||
.select((eb) => eb.fn.countAll().as('count'))
|
.select((eb) => eb.fn.countAll().as('count'))
|
||||||
.groupBy('timeBucket')
|
.groupBy('timeBucket')
|
||||||
.orderBy('timeBucket', 'desc')
|
.orderBy('timeBucket', options.order ?? 'desc')
|
||||||
.execute() as any as Promise<TimeBucketItem[]>
|
.execute() as any as Promise<TimeBucketItem[]>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -666,7 +666,7 @@ export class AssetRepository implements IAssetRepository {
|
|||||||
.where('assets.deletedAt', options.isTrashed ? 'is not' : 'is', null)
|
.where('assets.deletedAt', options.isTrashed ? 'is not' : 'is', null)
|
||||||
.where('assets.isVisible', '=', true)
|
.where('assets.isVisible', '=', true)
|
||||||
.where(truncatedDate(options.size), '=', timeBucket.replace(/^[+-]/, ''))
|
.where(truncatedDate(options.size), '=', timeBucket.replace(/^[+-]/, ''))
|
||||||
.orderBy('assets.localDateTime', 'desc')
|
.orderBy('assets.localDateTime', options.order ?? 'desc')
|
||||||
.execute() as any as Promise<AssetEntity[]>;
|
.execute() as any as Promise<AssetEntity[]>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,11 +1,24 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { SchedulerRegistry } from '@nestjs/schedule';
|
import { SchedulerRegistry } from '@nestjs/schedule';
|
||||||
import { CronJob, CronTime } from 'cron';
|
import { CronJob, CronTime } from 'cron';
|
||||||
import { CronCreate, CronUpdate, ICronRepository } from 'src/interfaces/cron.interface';
|
|
||||||
import { LoggingRepository } from 'src/repositories/logging.repository';
|
import { LoggingRepository } from 'src/repositories/logging.repository';
|
||||||
|
|
||||||
|
type CronBase = {
|
||||||
|
name: string;
|
||||||
|
start?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type CronCreate = CronBase & {
|
||||||
|
expression: string;
|
||||||
|
onTick: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type CronUpdate = CronBase & {
|
||||||
|
expression?: string;
|
||||||
|
};
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class CronRepository implements ICronRepository {
|
export class CronRepository {
|
||||||
constructor(
|
constructor(
|
||||||
private schedulerRegistry: SchedulerRegistry,
|
private schedulerRegistry: SchedulerRegistry,
|
||||||
private logger: LoggingRepository,
|
private logger: LoggingRepository,
|
||||||
|
@ -1,33 +1,23 @@
|
|||||||
import { IAlbumUserRepository } from 'src/interfaces/album-user.interface';
|
|
||||||
import { IAlbumRepository } from 'src/interfaces/album.interface';
|
import { IAlbumRepository } from 'src/interfaces/album.interface';
|
||||||
import { IAssetRepository } from 'src/interfaces/asset.interface';
|
import { IAssetRepository } from 'src/interfaces/asset.interface';
|
||||||
import { ICronRepository } from 'src/interfaces/cron.interface';
|
|
||||||
import { ICryptoRepository } from 'src/interfaces/crypto.interface';
|
import { ICryptoRepository } from 'src/interfaces/crypto.interface';
|
||||||
import { IDatabaseRepository } from 'src/interfaces/database.interface';
|
import { IDatabaseRepository } from 'src/interfaces/database.interface';
|
||||||
import { IEventRepository } from 'src/interfaces/event.interface';
|
import { IEventRepository } from 'src/interfaces/event.interface';
|
||||||
import { IJobRepository } from 'src/interfaces/job.interface';
|
import { IJobRepository } from 'src/interfaces/job.interface';
|
||||||
import { ILibraryRepository } from 'src/interfaces/library.interface';
|
import { ILibraryRepository } from 'src/interfaces/library.interface';
|
||||||
import { IMachineLearningRepository } from 'src/interfaces/machine-learning.interface';
|
import { IMachineLearningRepository } from 'src/interfaces/machine-learning.interface';
|
||||||
import { IMapRepository } from 'src/interfaces/map.interface';
|
|
||||||
import { IMetadataRepository } from 'src/interfaces/metadata.interface';
|
|
||||||
import { IMoveRepository } from 'src/interfaces/move.interface';
|
import { IMoveRepository } from 'src/interfaces/move.interface';
|
||||||
import { INotificationRepository } from 'src/interfaces/notification.interface';
|
|
||||||
import { IOAuthRepository } from 'src/interfaces/oauth.interface';
|
|
||||||
import { IPartnerRepository } from 'src/interfaces/partner.interface';
|
import { IPartnerRepository } from 'src/interfaces/partner.interface';
|
||||||
import { IPersonRepository } from 'src/interfaces/person.interface';
|
import { IPersonRepository } from 'src/interfaces/person.interface';
|
||||||
import { IProcessRepository } from 'src/interfaces/process.interface';
|
import { IProcessRepository } from 'src/interfaces/process.interface';
|
||||||
import { ISearchRepository } from 'src/interfaces/search.interface';
|
import { ISearchRepository } from 'src/interfaces/search.interface';
|
||||||
import { IServerInfoRepository } from 'src/interfaces/server-info.interface';
|
|
||||||
import { ISessionRepository } from 'src/interfaces/session.interface';
|
import { ISessionRepository } from 'src/interfaces/session.interface';
|
||||||
import { ISharedLinkRepository } from 'src/interfaces/shared-link.interface';
|
import { ISharedLinkRepository } from 'src/interfaces/shared-link.interface';
|
||||||
import { IStackRepository } from 'src/interfaces/stack.interface';
|
import { IStackRepository } from 'src/interfaces/stack.interface';
|
||||||
import { IStorageRepository } from 'src/interfaces/storage.interface';
|
import { IStorageRepository } from 'src/interfaces/storage.interface';
|
||||||
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
|
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
|
||||||
import { ITagRepository } from 'src/interfaces/tag.interface';
|
import { ITagRepository } from 'src/interfaces/tag.interface';
|
||||||
import { ITelemetryRepository } from 'src/interfaces/telemetry.interface';
|
|
||||||
import { ITrashRepository } from 'src/interfaces/trash.interface';
|
|
||||||
import { IUserRepository } from 'src/interfaces/user.interface';
|
import { IUserRepository } from 'src/interfaces/user.interface';
|
||||||
import { IVersionHistoryRepository } from 'src/interfaces/version-history.interface';
|
|
||||||
import { AccessRepository } from 'src/repositories/access.repository';
|
import { AccessRepository } from 'src/repositories/access.repository';
|
||||||
import { ActivityRepository } from 'src/repositories/activity.repository';
|
import { ActivityRepository } from 'src/repositories/activity.repository';
|
||||||
import { AlbumUserRepository } from 'src/repositories/album-user.repository';
|
import { AlbumUserRepository } from 'src/repositories/album-user.repository';
|
||||||
@ -71,44 +61,44 @@ import { ViewRepository } from 'src/repositories/view-repository';
|
|||||||
export const repositories = [
|
export const repositories = [
|
||||||
AccessRepository,
|
AccessRepository,
|
||||||
ActivityRepository,
|
ActivityRepository,
|
||||||
|
AlbumUserRepository,
|
||||||
AuditRepository,
|
AuditRepository,
|
||||||
ApiKeyRepository,
|
ApiKeyRepository,
|
||||||
ConfigRepository,
|
ConfigRepository,
|
||||||
|
CronRepository,
|
||||||
LoggingRepository,
|
LoggingRepository,
|
||||||
|
MapRepository,
|
||||||
MediaRepository,
|
MediaRepository,
|
||||||
MemoryRepository,
|
MemoryRepository,
|
||||||
|
MetadataRepository,
|
||||||
|
NotificationRepository,
|
||||||
|
OAuthRepository,
|
||||||
|
ServerInfoRepository,
|
||||||
|
TelemetryRepository,
|
||||||
|
TrashRepository,
|
||||||
ViewRepository,
|
ViewRepository,
|
||||||
|
VersionHistoryRepository,
|
||||||
];
|
];
|
||||||
|
|
||||||
export const providers = [
|
export const providers = [
|
||||||
{ provide: IAlbumRepository, useClass: AlbumRepository },
|
{ provide: IAlbumRepository, useClass: AlbumRepository },
|
||||||
{ provide: IAlbumUserRepository, useClass: AlbumUserRepository },
|
|
||||||
{ provide: IAssetRepository, useClass: AssetRepository },
|
{ provide: IAssetRepository, useClass: AssetRepository },
|
||||||
{ provide: ICronRepository, useClass: CronRepository },
|
|
||||||
{ provide: ICryptoRepository, useClass: CryptoRepository },
|
{ provide: ICryptoRepository, useClass: CryptoRepository },
|
||||||
{ provide: IDatabaseRepository, useClass: DatabaseRepository },
|
{ provide: IDatabaseRepository, useClass: DatabaseRepository },
|
||||||
{ provide: IEventRepository, useClass: EventRepository },
|
{ provide: IEventRepository, useClass: EventRepository },
|
||||||
{ provide: IJobRepository, useClass: JobRepository },
|
{ provide: IJobRepository, useClass: JobRepository },
|
||||||
{ provide: ILibraryRepository, useClass: LibraryRepository },
|
{ provide: ILibraryRepository, useClass: LibraryRepository },
|
||||||
{ provide: IMachineLearningRepository, useClass: MachineLearningRepository },
|
{ provide: IMachineLearningRepository, useClass: MachineLearningRepository },
|
||||||
{ provide: IMapRepository, useClass: MapRepository },
|
|
||||||
{ provide: IMetadataRepository, useClass: MetadataRepository },
|
|
||||||
{ provide: IMoveRepository, useClass: MoveRepository },
|
{ provide: IMoveRepository, useClass: MoveRepository },
|
||||||
{ provide: INotificationRepository, useClass: NotificationRepository },
|
|
||||||
{ provide: IOAuthRepository, useClass: OAuthRepository },
|
|
||||||
{ provide: IPartnerRepository, useClass: PartnerRepository },
|
{ provide: IPartnerRepository, useClass: PartnerRepository },
|
||||||
{ provide: IPersonRepository, useClass: PersonRepository },
|
{ provide: IPersonRepository, useClass: PersonRepository },
|
||||||
{ provide: IProcessRepository, useClass: ProcessRepository },
|
{ provide: IProcessRepository, useClass: ProcessRepository },
|
||||||
{ provide: ISearchRepository, useClass: SearchRepository },
|
{ provide: ISearchRepository, useClass: SearchRepository },
|
||||||
{ provide: IServerInfoRepository, useClass: ServerInfoRepository },
|
|
||||||
{ provide: ISessionRepository, useClass: SessionRepository },
|
{ provide: ISessionRepository, useClass: SessionRepository },
|
||||||
{ provide: ISharedLinkRepository, useClass: SharedLinkRepository },
|
{ provide: ISharedLinkRepository, useClass: SharedLinkRepository },
|
||||||
{ provide: IStackRepository, useClass: StackRepository },
|
{ provide: IStackRepository, useClass: StackRepository },
|
||||||
{ provide: IStorageRepository, useClass: StorageRepository },
|
{ provide: IStorageRepository, useClass: StorageRepository },
|
||||||
{ provide: ISystemMetadataRepository, useClass: SystemMetadataRepository },
|
{ provide: ISystemMetadataRepository, useClass: SystemMetadataRepository },
|
||||||
{ provide: ITagRepository, useClass: TagRepository },
|
{ provide: ITagRepository, useClass: TagRepository },
|
||||||
{ provide: ITelemetryRepository, useClass: TelemetryRepository },
|
|
||||||
{ provide: ITrashRepository, useClass: TrashRepository },
|
|
||||||
{ provide: IUserRepository, useClass: UserRepository },
|
{ provide: IUserRepository, useClass: UserRepository },
|
||||||
{ provide: IVersionHistoryRepository, useClass: VersionHistoryRepository },
|
|
||||||
];
|
];
|
||||||
|
@ -11,24 +11,41 @@ import { DB, GeodataPlaces, NaturalearthCountries } from 'src/db';
|
|||||||
import { AssetEntity, withExif } from 'src/entities/asset.entity';
|
import { AssetEntity, withExif } from 'src/entities/asset.entity';
|
||||||
import { NaturalEarthCountriesTempEntity } from 'src/entities/natural-earth-countries.entity';
|
import { NaturalEarthCountriesTempEntity } from 'src/entities/natural-earth-countries.entity';
|
||||||
import { LogLevel, SystemMetadataKey } from 'src/enum';
|
import { LogLevel, SystemMetadataKey } from 'src/enum';
|
||||||
import {
|
|
||||||
GeoPoint,
|
|
||||||
IMapRepository,
|
|
||||||
MapMarker,
|
|
||||||
MapMarkerSearchOptions,
|
|
||||||
ReverseGeocodeResult,
|
|
||||||
} from 'src/interfaces/map.interface';
|
|
||||||
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
|
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
|
||||||
import { ConfigRepository } from 'src/repositories/config.repository';
|
import { ConfigRepository } from 'src/repositories/config.repository';
|
||||||
import { LoggingRepository } from 'src/repositories/logging.repository';
|
import { LoggingRepository } from 'src/repositories/logging.repository';
|
||||||
|
|
||||||
|
export interface MapMarkerSearchOptions {
|
||||||
|
isArchived?: boolean;
|
||||||
|
isFavorite?: boolean;
|
||||||
|
fileCreatedBefore?: Date;
|
||||||
|
fileCreatedAfter?: Date;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GeoPoint {
|
||||||
|
latitude: number;
|
||||||
|
longitude: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ReverseGeocodeResult {
|
||||||
|
country: string | null;
|
||||||
|
state: string | null;
|
||||||
|
city: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MapMarker extends ReverseGeocodeResult {
|
||||||
|
id: string;
|
||||||
|
lat: number;
|
||||||
|
lon: number;
|
||||||
|
}
|
||||||
|
|
||||||
interface MapDB extends DB {
|
interface MapDB extends DB {
|
||||||
geodata_places_tmp: GeodataPlaces;
|
geodata_places_tmp: GeodataPlaces;
|
||||||
naturalearth_countries_tmp: NaturalearthCountries;
|
naturalearth_countries_tmp: NaturalearthCountries;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class MapRepository implements IMapRepository {
|
export class MapRepository {
|
||||||
constructor(
|
constructor(
|
||||||
private configRepository: ConfigRepository,
|
private configRepository: ConfigRepository,
|
||||||
@Inject(ISystemMetadataRepository) private metadataRepository: ISystemMetadataRepository,
|
@Inject(ISystemMetadataRepository) private metadataRepository: ISystemMetadataRepository,
|
||||||
|
@ -1,11 +1,72 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { DefaultReadTaskOptions, ExifTool, Tags } from 'exiftool-vendored';
|
import { BinaryField, DefaultReadTaskOptions, ExifTool, Tags } from 'exiftool-vendored';
|
||||||
import geotz from 'geo-tz';
|
import geotz from 'geo-tz';
|
||||||
import { IMetadataRepository, ImmichTags } from 'src/interfaces/metadata.interface';
|
|
||||||
import { LoggingRepository } from 'src/repositories/logging.repository';
|
import { LoggingRepository } from 'src/repositories/logging.repository';
|
||||||
|
|
||||||
|
interface ExifDuration {
|
||||||
|
Value: number;
|
||||||
|
Scale?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
type StringOrNumber = string | number;
|
||||||
|
|
||||||
|
type TagsWithWrongTypes =
|
||||||
|
| 'FocalLength'
|
||||||
|
| 'Duration'
|
||||||
|
| 'Description'
|
||||||
|
| 'ImageDescription'
|
||||||
|
| 'RegionInfo'
|
||||||
|
| 'TagsList'
|
||||||
|
| 'Keywords'
|
||||||
|
| 'HierarchicalSubject'
|
||||||
|
| 'ISO';
|
||||||
|
|
||||||
|
export interface ImmichTags extends Omit<Tags, TagsWithWrongTypes> {
|
||||||
|
ContentIdentifier?: string;
|
||||||
|
MotionPhoto?: number;
|
||||||
|
MotionPhotoVersion?: number;
|
||||||
|
MotionPhotoPresentationTimestampUs?: number;
|
||||||
|
MediaGroupUUID?: string;
|
||||||
|
ImagePixelDepth?: string;
|
||||||
|
FocalLength?: number;
|
||||||
|
Duration?: number | string | ExifDuration;
|
||||||
|
EmbeddedVideoType?: string;
|
||||||
|
EmbeddedVideoFile?: BinaryField;
|
||||||
|
MotionPhotoVideo?: BinaryField;
|
||||||
|
TagsList?: StringOrNumber[];
|
||||||
|
HierarchicalSubject?: StringOrNumber[];
|
||||||
|
Keywords?: StringOrNumber | StringOrNumber[];
|
||||||
|
ISO?: number | number[];
|
||||||
|
|
||||||
|
// Type is wrong, can also be number.
|
||||||
|
Description?: StringOrNumber;
|
||||||
|
ImageDescription?: StringOrNumber;
|
||||||
|
|
||||||
|
// Extended properties for image regions, such as faces
|
||||||
|
RegionInfo?: {
|
||||||
|
AppliedToDimensions: {
|
||||||
|
W: number;
|
||||||
|
H: number;
|
||||||
|
Unit: string;
|
||||||
|
};
|
||||||
|
RegionList: {
|
||||||
|
Area: {
|
||||||
|
// (X,Y) // center of the rectangle
|
||||||
|
X: number;
|
||||||
|
Y: number;
|
||||||
|
W: number;
|
||||||
|
H: number;
|
||||||
|
Unit: string;
|
||||||
|
};
|
||||||
|
Rotation?: number;
|
||||||
|
Type?: string;
|
||||||
|
Name?: string;
|
||||||
|
}[];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class MetadataRepository implements IMetadataRepository {
|
export class MetadataRepository {
|
||||||
private exiftool = new ExifTool({
|
private exiftool = new ExifTool({
|
||||||
defaultVideosToUTC: true,
|
defaultVideosToUTC: true,
|
||||||
backfillTimezones: true,
|
backfillTimezones: true,
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import { EmailRenderRequest, EmailTemplate } from 'src/interfaces/notification.interface';
|
|
||||||
import { LoggingRepository } from 'src/repositories/logging.repository';
|
import { LoggingRepository } from 'src/repositories/logging.repository';
|
||||||
import { NotificationRepository } from 'src/repositories/notification.repository';
|
import { EmailRenderRequest, EmailTemplate, NotificationRepository } from 'src/repositories/notification.repository';
|
||||||
import { ILoggingRepository } from 'src/types';
|
import { ILoggingRepository } from 'src/types';
|
||||||
import { newLoggingRepositoryMock } from 'test/repositories/logger.repository.mock';
|
import { newLoggingRepositoryMock } from 'test/repositories/logger.repository.mock';
|
||||||
import { Mocked } from 'vitest';
|
import { Mocked } from 'vitest';
|
||||||
|
@ -6,18 +6,104 @@ import { AlbumInviteEmail } from 'src/emails/album-invite.email';
|
|||||||
import { AlbumUpdateEmail } from 'src/emails/album-update.email';
|
import { AlbumUpdateEmail } from 'src/emails/album-update.email';
|
||||||
import { TestEmail } from 'src/emails/test.email';
|
import { TestEmail } from 'src/emails/test.email';
|
||||||
import { WelcomeEmail } from 'src/emails/welcome.email';
|
import { WelcomeEmail } from 'src/emails/welcome.email';
|
||||||
import {
|
|
||||||
EmailRenderRequest,
|
|
||||||
EmailTemplate,
|
|
||||||
INotificationRepository,
|
|
||||||
SendEmailOptions,
|
|
||||||
SendEmailResponse,
|
|
||||||
SmtpOptions,
|
|
||||||
} from 'src/interfaces/notification.interface';
|
|
||||||
import { LoggingRepository } from 'src/repositories/logging.repository';
|
import { LoggingRepository } from 'src/repositories/logging.repository';
|
||||||
|
|
||||||
|
export type EmailImageAttachment = {
|
||||||
|
filename: string;
|
||||||
|
path: string;
|
||||||
|
cid: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type SendEmailOptions = {
|
||||||
|
from: string;
|
||||||
|
to: string;
|
||||||
|
replyTo?: string;
|
||||||
|
subject: string;
|
||||||
|
html: string;
|
||||||
|
text: string;
|
||||||
|
imageAttachments?: EmailImageAttachment[];
|
||||||
|
smtp: SmtpOptions;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type SmtpOptions = {
|
||||||
|
host: string;
|
||||||
|
port?: number;
|
||||||
|
username?: string;
|
||||||
|
password?: string;
|
||||||
|
ignoreCert?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export enum EmailTemplate {
|
||||||
|
TEST_EMAIL = 'test',
|
||||||
|
|
||||||
|
// AUTH
|
||||||
|
WELCOME = 'welcome',
|
||||||
|
RESET_PASSWORD = 'reset-password',
|
||||||
|
|
||||||
|
// ALBUM
|
||||||
|
ALBUM_INVITE = 'album-invite',
|
||||||
|
ALBUM_UPDATE = 'album-update',
|
||||||
|
}
|
||||||
|
|
||||||
|
interface BaseEmailProps {
|
||||||
|
baseUrl: string;
|
||||||
|
customTemplate?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TestEmailProps extends BaseEmailProps {
|
||||||
|
displayName: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface WelcomeEmailProps extends BaseEmailProps {
|
||||||
|
displayName: string;
|
||||||
|
username: string;
|
||||||
|
password?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AlbumInviteEmailProps extends BaseEmailProps {
|
||||||
|
albumName: string;
|
||||||
|
albumId: string;
|
||||||
|
senderName: string;
|
||||||
|
recipientName: string;
|
||||||
|
cid?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AlbumUpdateEmailProps extends BaseEmailProps {
|
||||||
|
albumName: string;
|
||||||
|
albumId: string;
|
||||||
|
recipientName: string;
|
||||||
|
cid?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type EmailRenderRequest =
|
||||||
|
| {
|
||||||
|
template: EmailTemplate.TEST_EMAIL;
|
||||||
|
data: TestEmailProps;
|
||||||
|
customTemplate: string;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
template: EmailTemplate.WELCOME;
|
||||||
|
data: WelcomeEmailProps;
|
||||||
|
customTemplate: string;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
template: EmailTemplate.ALBUM_INVITE;
|
||||||
|
data: AlbumInviteEmailProps;
|
||||||
|
customTemplate: string;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
template: EmailTemplate.ALBUM_UPDATE;
|
||||||
|
data: AlbumUpdateEmailProps;
|
||||||
|
customTemplate: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type SendEmailResponse = {
|
||||||
|
messageId: string;
|
||||||
|
response: any;
|
||||||
|
};
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class NotificationRepository implements INotificationRepository {
|
export class NotificationRepository {
|
||||||
constructor(private logger: LoggingRepository) {
|
constructor(private logger: LoggingRepository) {
|
||||||
this.logger.setContext(NotificationRepository.name);
|
this.logger.setContext(NotificationRepository.name);
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,21 @@
|
|||||||
import { Injectable, InternalServerErrorException } from '@nestjs/common';
|
import { Injectable, InternalServerErrorException } from '@nestjs/common';
|
||||||
import { custom, generators, Issuer } from 'openid-client';
|
import { custom, generators, Issuer, UserinfoResponse } from 'openid-client';
|
||||||
import { IOAuthRepository, OAuthConfig, OAuthProfile } from 'src/interfaces/oauth.interface';
|
|
||||||
import { LoggingRepository } from 'src/repositories/logging.repository';
|
import { LoggingRepository } from 'src/repositories/logging.repository';
|
||||||
|
|
||||||
|
export type OAuthConfig = {
|
||||||
|
clientId: string;
|
||||||
|
clientSecret: string;
|
||||||
|
issuerUrl: string;
|
||||||
|
mobileOverrideEnabled: boolean;
|
||||||
|
mobileRedirectUri: string;
|
||||||
|
profileSigningAlgorithm: string;
|
||||||
|
scope: string;
|
||||||
|
signingAlgorithm: string;
|
||||||
|
};
|
||||||
|
export type OAuthProfile = UserinfoResponse;
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class OAuthRepository implements IOAuthRepository {
|
export class OAuthRepository {
|
||||||
constructor(private logger: LoggingRepository) {
|
constructor(private logger: LoggingRepository) {
|
||||||
this.logger.setContext(OAuthRepository.name);
|
this.logger.setContext(OAuthRepository.name);
|
||||||
}
|
}
|
||||||
|
@ -133,10 +133,6 @@ export class PersonRepository implements IPersonRepository {
|
|||||||
)
|
)
|
||||||
.where('person.ownerId', '=', userId)
|
.where('person.ownerId', '=', userId)
|
||||||
.orderBy('person.isHidden', 'asc')
|
.orderBy('person.isHidden', 'asc')
|
||||||
.orderBy(sql`NULLIF(person.name, '') is null`, 'asc')
|
|
||||||
.orderBy((eb) => eb.fn.count('asset_faces.assetId'), 'desc')
|
|
||||||
.orderBy(sql`NULLIF(person.name, '')`, sql`asc nulls last`)
|
|
||||||
.orderBy('person.createdAt')
|
|
||||||
.having((eb) =>
|
.having((eb) =>
|
||||||
eb.or([
|
eb.or([
|
||||||
eb('person.name', '!=', ''),
|
eb('person.name', '!=', ''),
|
||||||
@ -161,6 +157,13 @@ export class PersonRepository implements IPersonRepository {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
.$if(!options?.closestFaceAssetId, (qb) =>
|
||||||
|
qb
|
||||||
|
.orderBy(sql`NULLIF(person.name, '') is null`, 'asc')
|
||||||
|
.orderBy((eb) => eb.fn.count('asset_faces.assetId'), 'desc')
|
||||||
|
.orderBy(sql`NULLIF(person.name, '')`, sql`asc nulls last`)
|
||||||
|
.orderBy('person.createdAt'),
|
||||||
|
)
|
||||||
.$if(!options?.withHidden, (qb) => qb.where('person.isHidden', '=', false))
|
.$if(!options?.withHidden, (qb) => qb.where('person.isHidden', '=', false))
|
||||||
.offset(pagination.skip ?? 0)
|
.offset(pagination.skip ?? 0)
|
||||||
.limit(pagination.take + 1)
|
.limit(pagination.take + 1)
|
||||||
|
@ -4,10 +4,27 @@ import { exec as execCallback } from 'node:child_process';
|
|||||||
import { readFile } from 'node:fs/promises';
|
import { readFile } from 'node:fs/promises';
|
||||||
import { promisify } from 'node:util';
|
import { promisify } from 'node:util';
|
||||||
import sharp from 'sharp';
|
import sharp from 'sharp';
|
||||||
import { GitHubRelease, IServerInfoRepository, ServerBuildVersions } from 'src/interfaces/server-info.interface';
|
|
||||||
import { ConfigRepository } from 'src/repositories/config.repository';
|
import { ConfigRepository } from 'src/repositories/config.repository';
|
||||||
import { LoggingRepository } from 'src/repositories/logging.repository';
|
import { LoggingRepository } from 'src/repositories/logging.repository';
|
||||||
|
|
||||||
|
export interface GitHubRelease {
|
||||||
|
id: number;
|
||||||
|
url: string;
|
||||||
|
tag_name: string;
|
||||||
|
name: string;
|
||||||
|
created_at: string;
|
||||||
|
published_at: string;
|
||||||
|
body: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ServerBuildVersions {
|
||||||
|
nodejs: string;
|
||||||
|
ffmpeg: string;
|
||||||
|
libvips: string;
|
||||||
|
exiftool: string;
|
||||||
|
imagemagick: string;
|
||||||
|
}
|
||||||
|
|
||||||
const exec = promisify(execCallback);
|
const exec = promisify(execCallback);
|
||||||
const maybeFirstLine = async (command: string): Promise<string> => {
|
const maybeFirstLine = async (command: string): Promise<string> => {
|
||||||
try {
|
try {
|
||||||
@ -34,7 +51,7 @@ const getLockfileVersion = (name: string, lockfile?: BuildLockfile) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ServerInfoRepository implements IServerInfoRepository {
|
export class ServerInfoRepository {
|
||||||
constructor(
|
constructor(
|
||||||
private configRepository: ConfigRepository,
|
private configRepository: ConfigRepository,
|
||||||
private logger: LoggingRepository,
|
private logger: LoggingRepository,
|
||||||
|
@ -12,7 +12,11 @@ const withAssets = (eb: ExpressionBuilder<DB, 'asset_stack'>, withTags = false)
|
|||||||
return jsonArrayFrom(
|
return jsonArrayFrom(
|
||||||
eb
|
eb
|
||||||
.selectFrom('assets')
|
.selectFrom('assets')
|
||||||
.selectAll()
|
.selectAll('assets')
|
||||||
|
.innerJoinLateral(
|
||||||
|
(eb) => eb.selectFrom('exif').selectAll('exif').whereRef('exif.assetId', '=', 'assets.id').as('exifInfo'),
|
||||||
|
(join) => join.onTrue(),
|
||||||
|
)
|
||||||
.$if(withTags, (eb) =>
|
.$if(withTags, (eb) =>
|
||||||
eb.select((eb) =>
|
eb.select((eb) =>
|
||||||
jsonArrayFrom(
|
jsonArrayFrom(
|
||||||
@ -24,6 +28,7 @@ const withAssets = (eb: ExpressionBuilder<DB, 'asset_stack'>, withTags = false)
|
|||||||
).as('tags'),
|
).as('tags'),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
.select((eb) => eb.fn.toJson('exifInfo').as('exifInfo'))
|
||||||
.where('assets.deletedAt', 'is', null)
|
.where('assets.deletedAt', 'is', null)
|
||||||
.whereRef('assets.stackId', '=', 'asset_stack.id'),
|
.whereRef('assets.stackId', '=', 'asset_stack.id'),
|
||||||
).as('assets');
|
).as('assets');
|
||||||
|
@ -15,11 +15,12 @@ import { MetricService } from 'nestjs-otel';
|
|||||||
import { copyMetadataFromFunctionToFunction } from 'nestjs-otel/lib/opentelemetry.utils';
|
import { copyMetadataFromFunctionToFunction } from 'nestjs-otel/lib/opentelemetry.utils';
|
||||||
import { serverVersion } from 'src/constants';
|
import { serverVersion } from 'src/constants';
|
||||||
import { ImmichTelemetry, MetadataKey } from 'src/enum';
|
import { ImmichTelemetry, MetadataKey } from 'src/enum';
|
||||||
import { IMetricGroupRepository, ITelemetryRepository, MetricGroupOptions } from 'src/interfaces/telemetry.interface';
|
|
||||||
import { ConfigRepository } from 'src/repositories/config.repository';
|
import { ConfigRepository } from 'src/repositories/config.repository';
|
||||||
import { LoggingRepository } from 'src/repositories/logging.repository';
|
import { LoggingRepository } from 'src/repositories/logging.repository';
|
||||||
|
|
||||||
class MetricGroupRepository implements IMetricGroupRepository {
|
type MetricGroupOptions = { enabled: boolean };
|
||||||
|
|
||||||
|
export class MetricGroupRepository {
|
||||||
private enabled = false;
|
private enabled = false;
|
||||||
|
|
||||||
constructor(private metricService: MetricService) {}
|
constructor(private metricService: MetricService) {}
|
||||||
@ -86,7 +87,7 @@ export const teardownTelemetry = async () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class TelemetryRepository implements ITelemetryRepository {
|
export class TelemetryRepository {
|
||||||
api: MetricGroupRepository;
|
api: MetricGroupRepository;
|
||||||
host: MetricGroupRepository;
|
host: MetricGroupRepository;
|
||||||
jobs: MetricGroupRepository;
|
jobs: MetricGroupRepository;
|
||||||
|
@ -3,9 +3,8 @@ import { InjectKysely } from 'nestjs-kysely';
|
|||||||
import { DB } from 'src/db';
|
import { DB } from 'src/db';
|
||||||
import { DummyValue, GenerateSql } from 'src/decorators';
|
import { DummyValue, GenerateSql } from 'src/decorators';
|
||||||
import { AssetStatus } from 'src/enum';
|
import { AssetStatus } from 'src/enum';
|
||||||
import { ITrashRepository } from 'src/interfaces/trash.interface';
|
|
||||||
|
|
||||||
export class TrashRepository implements ITrashRepository {
|
export class TrashRepository {
|
||||||
constructor(@InjectKysely() private db: Kysely<DB>) {}
|
constructor(@InjectKysely() private db: Kysely<DB>) {}
|
||||||
|
|
||||||
getDeletedIds(): AsyncIterableIterator<{ id: string }> {
|
getDeletedIds(): AsyncIterableIterator<{ id: string }> {
|
||||||
|
@ -3,25 +3,23 @@ import { Insertable, Kysely } from 'kysely';
|
|||||||
import { InjectKysely } from 'nestjs-kysely';
|
import { InjectKysely } from 'nestjs-kysely';
|
||||||
import { DB, VersionHistory } from 'src/db';
|
import { DB, VersionHistory } from 'src/db';
|
||||||
import { GenerateSql } from 'src/decorators';
|
import { GenerateSql } from 'src/decorators';
|
||||||
import { VersionHistoryEntity } from 'src/entities/version-history.entity';
|
|
||||||
import { IVersionHistoryRepository } from 'src/interfaces/version-history.interface';
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class VersionHistoryRepository implements IVersionHistoryRepository {
|
export class VersionHistoryRepository {
|
||||||
constructor(@InjectKysely() private db: Kysely<DB>) {}
|
constructor(@InjectKysely() private db: Kysely<DB>) {}
|
||||||
|
|
||||||
@GenerateSql()
|
@GenerateSql()
|
||||||
getAll(): Promise<VersionHistoryEntity[]> {
|
getAll() {
|
||||||
return this.db.selectFrom('version_history').selectAll().orderBy('createdAt', 'desc').execute();
|
return this.db.selectFrom('version_history').selectAll().orderBy('createdAt', 'desc').execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
@GenerateSql()
|
@GenerateSql()
|
||||||
getLatest(): Promise<VersionHistoryEntity | undefined> {
|
getLatest() {
|
||||||
return this.db.selectFrom('version_history').selectAll().orderBy('createdAt', 'desc').executeTakeFirst();
|
return this.db.selectFrom('version_history').selectAll().orderBy('createdAt', 'desc').executeTakeFirst();
|
||||||
}
|
}
|
||||||
|
|
||||||
@GenerateSql({ params: [{ version: 'v1.123.0' }] })
|
@GenerateSql({ params: [{ version: 'v1.123.0' }] })
|
||||||
create(version: Insertable<VersionHistory>): Promise<VersionHistoryEntity> {
|
create(version: Insertable<VersionHistory>) {
|
||||||
return this.db.insertInto('version_history').values(version).returningAll().executeTakeFirstOrThrow();
|
return this.db.insertInto('version_history').values(version).returningAll().executeTakeFirstOrThrow();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,11 +2,11 @@ import { BadRequestException } from '@nestjs/common';
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import { BulkIdErrorReason } from 'src/dtos/asset-ids.response.dto';
|
import { BulkIdErrorReason } from 'src/dtos/asset-ids.response.dto';
|
||||||
import { AlbumUserRole } from 'src/enum';
|
import { AlbumUserRole } from 'src/enum';
|
||||||
import { IAlbumUserRepository } from 'src/interfaces/album-user.interface';
|
|
||||||
import { IAlbumRepository } from 'src/interfaces/album.interface';
|
import { IAlbumRepository } from 'src/interfaces/album.interface';
|
||||||
import { IEventRepository } from 'src/interfaces/event.interface';
|
import { IEventRepository } from 'src/interfaces/event.interface';
|
||||||
import { IUserRepository } from 'src/interfaces/user.interface';
|
import { IUserRepository } from 'src/interfaces/user.interface';
|
||||||
import { AlbumService } from 'src/services/album.service';
|
import { AlbumService } from 'src/services/album.service';
|
||||||
|
import { IAlbumUserRepository } from 'src/types';
|
||||||
import { albumStub } from 'test/fixtures/album.stub';
|
import { albumStub } from 'test/fixtures/album.stub';
|
||||||
import { authStub } from 'test/fixtures/auth.stub';
|
import { authStub } from 'test/fixtures/auth.stub';
|
||||||
import { userStub } from 'test/fixtures/user.stub';
|
import { userStub } from 'test/fixtures/user.stub';
|
||||||
|
@ -5,13 +5,12 @@ import { UserEntity } from 'src/entities/user.entity';
|
|||||||
import { AuthType, Permission } from 'src/enum';
|
import { AuthType, Permission } from 'src/enum';
|
||||||
import { ICryptoRepository } from 'src/interfaces/crypto.interface';
|
import { ICryptoRepository } from 'src/interfaces/crypto.interface';
|
||||||
import { IEventRepository } from 'src/interfaces/event.interface';
|
import { IEventRepository } from 'src/interfaces/event.interface';
|
||||||
import { IOAuthRepository } from 'src/interfaces/oauth.interface';
|
|
||||||
import { ISessionRepository } from 'src/interfaces/session.interface';
|
import { ISessionRepository } from 'src/interfaces/session.interface';
|
||||||
import { ISharedLinkRepository } from 'src/interfaces/shared-link.interface';
|
import { ISharedLinkRepository } from 'src/interfaces/shared-link.interface';
|
||||||
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
|
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
|
||||||
import { IUserRepository } from 'src/interfaces/user.interface';
|
import { IUserRepository } from 'src/interfaces/user.interface';
|
||||||
import { AuthService } from 'src/services/auth.service';
|
import { AuthService } from 'src/services/auth.service';
|
||||||
import { IApiKeyRepository } from 'src/types';
|
import { IApiKeyRepository, IOAuthRepository } from 'src/types';
|
||||||
import { keyStub } from 'test/fixtures/api-key.stub';
|
import { keyStub } from 'test/fixtures/api-key.stub';
|
||||||
import { authStub } from 'test/fixtures/auth.stub';
|
import { authStub } from 'test/fixtures/auth.stub';
|
||||||
import { sessionStub } from 'test/fixtures/session.stub';
|
import { sessionStub } from 'test/fixtures/session.stub';
|
||||||
|
@ -19,7 +19,7 @@ import {
|
|||||||
import { UserAdminResponseDto, mapUserAdmin } from 'src/dtos/user.dto';
|
import { UserAdminResponseDto, mapUserAdmin } from 'src/dtos/user.dto';
|
||||||
import { UserEntity } from 'src/entities/user.entity';
|
import { UserEntity } from 'src/entities/user.entity';
|
||||||
import { AuthType, ImmichCookie, ImmichHeader, ImmichQuery, Permission } from 'src/enum';
|
import { AuthType, ImmichCookie, ImmichHeader, ImmichQuery, Permission } from 'src/enum';
|
||||||
import { OAuthProfile } from 'src/interfaces/oauth.interface';
|
import { OAuthProfile } from 'src/repositories/oauth.repository';
|
||||||
import { BaseService } from 'src/services/base.service';
|
import { BaseService } from 'src/services/base.service';
|
||||||
import { AuthApiKey } from 'src/types';
|
import { AuthApiKey } from 'src/types';
|
||||||
import { isGranted } from 'src/utils/access';
|
import { isGranted } from 'src/utils/access';
|
||||||
|
@ -2,14 +2,13 @@ import { PassThrough } from 'node:stream';
|
|||||||
import { defaults, SystemConfig } from 'src/config';
|
import { defaults, SystemConfig } from 'src/config';
|
||||||
import { StorageCore } from 'src/cores/storage.core';
|
import { StorageCore } from 'src/cores/storage.core';
|
||||||
import { ImmichWorker, StorageFolder } from 'src/enum';
|
import { ImmichWorker, StorageFolder } from 'src/enum';
|
||||||
import { ICronRepository } from 'src/interfaces/cron.interface';
|
|
||||||
import { IDatabaseRepository } from 'src/interfaces/database.interface';
|
import { IDatabaseRepository } from 'src/interfaces/database.interface';
|
||||||
import { JobStatus } from 'src/interfaces/job.interface';
|
import { JobStatus } from 'src/interfaces/job.interface';
|
||||||
import { IProcessRepository } from 'src/interfaces/process.interface';
|
import { IProcessRepository } from 'src/interfaces/process.interface';
|
||||||
import { IStorageRepository } from 'src/interfaces/storage.interface';
|
import { IStorageRepository } from 'src/interfaces/storage.interface';
|
||||||
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
|
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
|
||||||
import { BackupService } from 'src/services/backup.service';
|
import { BackupService } from 'src/services/backup.service';
|
||||||
import { IConfigRepository } from 'src/types';
|
import { IConfigRepository, ICronRepository } from 'src/types';
|
||||||
import { systemConfigStub } from 'test/fixtures/system-config.stub';
|
import { systemConfigStub } from 'test/fixtures/system-config.stub';
|
||||||
import { mockSpawn, newTestService } from 'test/utils';
|
import { mockSpawn, newTestService } from 'test/utils';
|
||||||
import { describe, Mocked } from 'vitest';
|
import { describe, Mocked } from 'vitest';
|
||||||
|
@ -6,44 +6,44 @@ import { SALT_ROUNDS } from 'src/constants';
|
|||||||
import { StorageCore } from 'src/cores/storage.core';
|
import { StorageCore } from 'src/cores/storage.core';
|
||||||
import { Users } from 'src/db';
|
import { Users } from 'src/db';
|
||||||
import { UserEntity } from 'src/entities/user.entity';
|
import { UserEntity } from 'src/entities/user.entity';
|
||||||
import { IAlbumUserRepository } from 'src/interfaces/album-user.interface';
|
|
||||||
import { IAlbumRepository } from 'src/interfaces/album.interface';
|
import { IAlbumRepository } from 'src/interfaces/album.interface';
|
||||||
import { IAssetRepository } from 'src/interfaces/asset.interface';
|
import { IAssetRepository } from 'src/interfaces/asset.interface';
|
||||||
import { ICronRepository } from 'src/interfaces/cron.interface';
|
|
||||||
import { ICryptoRepository } from 'src/interfaces/crypto.interface';
|
import { ICryptoRepository } from 'src/interfaces/crypto.interface';
|
||||||
import { IDatabaseRepository } from 'src/interfaces/database.interface';
|
import { IDatabaseRepository } from 'src/interfaces/database.interface';
|
||||||
import { IEventRepository } from 'src/interfaces/event.interface';
|
import { IEventRepository } from 'src/interfaces/event.interface';
|
||||||
import { IJobRepository } from 'src/interfaces/job.interface';
|
import { IJobRepository } from 'src/interfaces/job.interface';
|
||||||
import { ILibraryRepository } from 'src/interfaces/library.interface';
|
import { ILibraryRepository } from 'src/interfaces/library.interface';
|
||||||
import { IMachineLearningRepository } from 'src/interfaces/machine-learning.interface';
|
import { IMachineLearningRepository } from 'src/interfaces/machine-learning.interface';
|
||||||
import { IMapRepository } from 'src/interfaces/map.interface';
|
|
||||||
import { IMetadataRepository } from 'src/interfaces/metadata.interface';
|
|
||||||
import { IMoveRepository } from 'src/interfaces/move.interface';
|
import { IMoveRepository } from 'src/interfaces/move.interface';
|
||||||
import { INotificationRepository } from 'src/interfaces/notification.interface';
|
|
||||||
import { IOAuthRepository } from 'src/interfaces/oauth.interface';
|
|
||||||
import { IPartnerRepository } from 'src/interfaces/partner.interface';
|
import { IPartnerRepository } from 'src/interfaces/partner.interface';
|
||||||
import { IPersonRepository } from 'src/interfaces/person.interface';
|
import { IPersonRepository } from 'src/interfaces/person.interface';
|
||||||
import { IProcessRepository } from 'src/interfaces/process.interface';
|
import { IProcessRepository } from 'src/interfaces/process.interface';
|
||||||
import { ISearchRepository } from 'src/interfaces/search.interface';
|
import { ISearchRepository } from 'src/interfaces/search.interface';
|
||||||
import { IServerInfoRepository } from 'src/interfaces/server-info.interface';
|
|
||||||
import { ISessionRepository } from 'src/interfaces/session.interface';
|
import { ISessionRepository } from 'src/interfaces/session.interface';
|
||||||
import { ISharedLinkRepository } from 'src/interfaces/shared-link.interface';
|
import { ISharedLinkRepository } from 'src/interfaces/shared-link.interface';
|
||||||
import { IStackRepository } from 'src/interfaces/stack.interface';
|
import { IStackRepository } from 'src/interfaces/stack.interface';
|
||||||
import { IStorageRepository } from 'src/interfaces/storage.interface';
|
import { IStorageRepository } from 'src/interfaces/storage.interface';
|
||||||
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
|
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
|
||||||
import { ITagRepository } from 'src/interfaces/tag.interface';
|
import { ITagRepository } from 'src/interfaces/tag.interface';
|
||||||
import { ITelemetryRepository } from 'src/interfaces/telemetry.interface';
|
|
||||||
import { ITrashRepository } from 'src/interfaces/trash.interface';
|
|
||||||
import { IUserRepository } from 'src/interfaces/user.interface';
|
import { IUserRepository } from 'src/interfaces/user.interface';
|
||||||
import { IVersionHistoryRepository } from 'src/interfaces/version-history.interface';
|
|
||||||
import { AccessRepository } from 'src/repositories/access.repository';
|
import { AccessRepository } from 'src/repositories/access.repository';
|
||||||
import { ActivityRepository } from 'src/repositories/activity.repository';
|
import { ActivityRepository } from 'src/repositories/activity.repository';
|
||||||
|
import { AlbumUserRepository } from 'src/repositories/album-user.repository';
|
||||||
import { ApiKeyRepository } from 'src/repositories/api-key.repository';
|
import { ApiKeyRepository } from 'src/repositories/api-key.repository';
|
||||||
import { AuditRepository } from 'src/repositories/audit.repository';
|
import { AuditRepository } from 'src/repositories/audit.repository';
|
||||||
import { ConfigRepository } from 'src/repositories/config.repository';
|
import { ConfigRepository } from 'src/repositories/config.repository';
|
||||||
|
import { CronRepository } from 'src/repositories/cron.repository';
|
||||||
import { LoggingRepository } from 'src/repositories/logging.repository';
|
import { LoggingRepository } from 'src/repositories/logging.repository';
|
||||||
|
import { MapRepository } from 'src/repositories/map.repository';
|
||||||
import { MediaRepository } from 'src/repositories/media.repository';
|
import { MediaRepository } from 'src/repositories/media.repository';
|
||||||
import { MemoryRepository } from 'src/repositories/memory.repository';
|
import { MemoryRepository } from 'src/repositories/memory.repository';
|
||||||
|
import { MetadataRepository } from 'src/repositories/metadata.repository';
|
||||||
|
import { NotificationRepository } from 'src/repositories/notification.repository';
|
||||||
|
import { OAuthRepository } from 'src/repositories/oauth.repository';
|
||||||
|
import { ServerInfoRepository } from 'src/repositories/server-info.repository';
|
||||||
|
import { TelemetryRepository } from 'src/repositories/telemetry.repository';
|
||||||
|
import { TrashRepository } from 'src/repositories/trash.repository';
|
||||||
|
import { VersionHistoryRepository } from 'src/repositories/version-history.repository';
|
||||||
import { ViewRepository } from 'src/repositories/view-repository';
|
import { ViewRepository } from 'src/repositories/view-repository';
|
||||||
import { AccessRequest, checkAccess, requireAccess } from 'src/utils/access';
|
import { AccessRequest, checkAccess, requireAccess } from 'src/utils/access';
|
||||||
import { getConfig, updateConfig } from 'src/utils/config';
|
import { getConfig, updateConfig } from 'src/utils/config';
|
||||||
@ -57,10 +57,10 @@ export class BaseService {
|
|||||||
protected activityRepository: ActivityRepository,
|
protected activityRepository: ActivityRepository,
|
||||||
protected auditRepository: AuditRepository,
|
protected auditRepository: AuditRepository,
|
||||||
@Inject(IAlbumRepository) protected albumRepository: IAlbumRepository,
|
@Inject(IAlbumRepository) protected albumRepository: IAlbumRepository,
|
||||||
@Inject(IAlbumUserRepository) protected albumUserRepository: IAlbumUserRepository,
|
protected albumUserRepository: AlbumUserRepository,
|
||||||
@Inject(IAssetRepository) protected assetRepository: IAssetRepository,
|
@Inject(IAssetRepository) protected assetRepository: IAssetRepository,
|
||||||
protected configRepository: ConfigRepository,
|
protected configRepository: ConfigRepository,
|
||||||
@Inject(ICronRepository) protected cronRepository: ICronRepository,
|
protected cronRepository: CronRepository,
|
||||||
@Inject(ICryptoRepository) protected cryptoRepository: ICryptoRepository,
|
@Inject(ICryptoRepository) protected cryptoRepository: ICryptoRepository,
|
||||||
@Inject(IDatabaseRepository) protected databaseRepository: IDatabaseRepository,
|
@Inject(IDatabaseRepository) protected databaseRepository: IDatabaseRepository,
|
||||||
@Inject(IEventRepository) protected eventRepository: IEventRepository,
|
@Inject(IEventRepository) protected eventRepository: IEventRepository,
|
||||||
@ -68,28 +68,28 @@ export class BaseService {
|
|||||||
protected keyRepository: ApiKeyRepository,
|
protected keyRepository: ApiKeyRepository,
|
||||||
@Inject(ILibraryRepository) protected libraryRepository: ILibraryRepository,
|
@Inject(ILibraryRepository) protected libraryRepository: ILibraryRepository,
|
||||||
@Inject(IMachineLearningRepository) protected machineLearningRepository: IMachineLearningRepository,
|
@Inject(IMachineLearningRepository) protected machineLearningRepository: IMachineLearningRepository,
|
||||||
@Inject(IMapRepository) protected mapRepository: IMapRepository,
|
protected mapRepository: MapRepository,
|
||||||
protected mediaRepository: MediaRepository,
|
protected mediaRepository: MediaRepository,
|
||||||
protected memoryRepository: MemoryRepository,
|
protected memoryRepository: MemoryRepository,
|
||||||
@Inject(IMetadataRepository) protected metadataRepository: IMetadataRepository,
|
protected metadataRepository: MetadataRepository,
|
||||||
@Inject(IMoveRepository) protected moveRepository: IMoveRepository,
|
@Inject(IMoveRepository) protected moveRepository: IMoveRepository,
|
||||||
@Inject(INotificationRepository) protected notificationRepository: INotificationRepository,
|
protected notificationRepository: NotificationRepository,
|
||||||
@Inject(IOAuthRepository) protected oauthRepository: IOAuthRepository,
|
protected oauthRepository: OAuthRepository,
|
||||||
@Inject(IPartnerRepository) protected partnerRepository: IPartnerRepository,
|
@Inject(IPartnerRepository) protected partnerRepository: IPartnerRepository,
|
||||||
@Inject(IPersonRepository) protected personRepository: IPersonRepository,
|
@Inject(IPersonRepository) protected personRepository: IPersonRepository,
|
||||||
@Inject(IProcessRepository) protected processRepository: IProcessRepository,
|
@Inject(IProcessRepository) protected processRepository: IProcessRepository,
|
||||||
@Inject(ISearchRepository) protected searchRepository: ISearchRepository,
|
@Inject(ISearchRepository) protected searchRepository: ISearchRepository,
|
||||||
@Inject(IServerInfoRepository) protected serverInfoRepository: IServerInfoRepository,
|
protected serverInfoRepository: ServerInfoRepository,
|
||||||
@Inject(ISessionRepository) protected sessionRepository: ISessionRepository,
|
@Inject(ISessionRepository) protected sessionRepository: ISessionRepository,
|
||||||
@Inject(ISharedLinkRepository) protected sharedLinkRepository: ISharedLinkRepository,
|
@Inject(ISharedLinkRepository) protected sharedLinkRepository: ISharedLinkRepository,
|
||||||
@Inject(IStackRepository) protected stackRepository: IStackRepository,
|
@Inject(IStackRepository) protected stackRepository: IStackRepository,
|
||||||
@Inject(IStorageRepository) protected storageRepository: IStorageRepository,
|
@Inject(IStorageRepository) protected storageRepository: IStorageRepository,
|
||||||
@Inject(ISystemMetadataRepository) protected systemMetadataRepository: ISystemMetadataRepository,
|
@Inject(ISystemMetadataRepository) protected systemMetadataRepository: ISystemMetadataRepository,
|
||||||
@Inject(ITagRepository) protected tagRepository: ITagRepository,
|
@Inject(ITagRepository) protected tagRepository: ITagRepository,
|
||||||
@Inject(ITelemetryRepository) protected telemetryRepository: ITelemetryRepository,
|
protected telemetryRepository: TelemetryRepository,
|
||||||
@Inject(ITrashRepository) protected trashRepository: ITrashRepository,
|
protected trashRepository: TrashRepository,
|
||||||
@Inject(IUserRepository) protected userRepository: IUserRepository,
|
@Inject(IUserRepository) protected userRepository: IUserRepository,
|
||||||
@Inject(IVersionHistoryRepository) protected versionRepository: IVersionHistoryRepository,
|
protected versionRepository: VersionHistoryRepository,
|
||||||
protected viewRepository: ViewRepository,
|
protected viewRepository: ViewRepository,
|
||||||
) {
|
) {
|
||||||
this.logger.setContext(this.constructor.name);
|
this.logger.setContext(this.constructor.name);
|
||||||
|
@ -3,10 +3,10 @@ import { defaults, SystemConfig } from 'src/config';
|
|||||||
import { ImmichWorker } from 'src/enum';
|
import { ImmichWorker } from 'src/enum';
|
||||||
import { IAssetRepository } from 'src/interfaces/asset.interface';
|
import { IAssetRepository } from 'src/interfaces/asset.interface';
|
||||||
import { IJobRepository, JobCommand, JobItem, JobName, JobStatus, QueueName } from 'src/interfaces/job.interface';
|
import { IJobRepository, JobCommand, JobItem, JobName, JobStatus, QueueName } from 'src/interfaces/job.interface';
|
||||||
import { ITelemetryRepository } from 'src/interfaces/telemetry.interface';
|
|
||||||
import { JobService } from 'src/services/job.service';
|
import { JobService } from 'src/services/job.service';
|
||||||
import { IConfigRepository, ILoggingRepository } from 'src/types';
|
import { IConfigRepository, ILoggingRepository } from 'src/types';
|
||||||
import { assetStub } from 'test/fixtures/asset.stub';
|
import { assetStub } from 'test/fixtures/asset.stub';
|
||||||
|
import { ITelemetryRepositoryMock } from 'test/repositories/telemetry.repository.mock';
|
||||||
import { newTestService } from 'test/utils';
|
import { newTestService } from 'test/utils';
|
||||||
import { Mocked } from 'vitest';
|
import { Mocked } from 'vitest';
|
||||||
|
|
||||||
@ -16,7 +16,7 @@ describe(JobService.name, () => {
|
|||||||
let configMock: Mocked<IConfigRepository>;
|
let configMock: Mocked<IConfigRepository>;
|
||||||
let jobMock: Mocked<IJobRepository>;
|
let jobMock: Mocked<IJobRepository>;
|
||||||
let loggerMock: Mocked<ILoggingRepository>;
|
let loggerMock: Mocked<ILoggingRepository>;
|
||||||
let telemetryMock: Mocked<ITelemetryRepository>;
|
let telemetryMock: ITelemetryRepositoryMock;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
({ sut, assetMock, configMock, jobMock, loggerMock, telemetryMock } = newTestService(JobService, {}));
|
({ sut, assetMock, configMock, jobMock, loggerMock, telemetryMock } = newTestService(JobService, {}));
|
||||||
|
@ -5,7 +5,6 @@ import { mapLibrary } from 'src/dtos/library.dto';
|
|||||||
import { UserEntity } from 'src/entities/user.entity';
|
import { UserEntity } from 'src/entities/user.entity';
|
||||||
import { AssetType, ImmichWorker } from 'src/enum';
|
import { AssetType, ImmichWorker } from 'src/enum';
|
||||||
import { IAssetRepository } from 'src/interfaces/asset.interface';
|
import { IAssetRepository } from 'src/interfaces/asset.interface';
|
||||||
import { ICronRepository } from 'src/interfaces/cron.interface';
|
|
||||||
import { IDatabaseRepository } from 'src/interfaces/database.interface';
|
import { IDatabaseRepository } from 'src/interfaces/database.interface';
|
||||||
import {
|
import {
|
||||||
IJobRepository,
|
IJobRepository,
|
||||||
@ -18,7 +17,7 @@ import {
|
|||||||
import { ILibraryRepository } from 'src/interfaces/library.interface';
|
import { ILibraryRepository } from 'src/interfaces/library.interface';
|
||||||
import { IStorageRepository } from 'src/interfaces/storage.interface';
|
import { IStorageRepository } from 'src/interfaces/storage.interface';
|
||||||
import { LibraryService } from 'src/services/library.service';
|
import { LibraryService } from 'src/services/library.service';
|
||||||
import { IConfigRepository } from 'src/types';
|
import { IConfigRepository, ICronRepository } from 'src/types';
|
||||||
import { assetStub } from 'test/fixtures/asset.stub';
|
import { assetStub } from 'test/fixtures/asset.stub';
|
||||||
import { authStub } from 'test/fixtures/auth.stub';
|
import { authStub } from 'test/fixtures/auth.stub';
|
||||||
import { libraryStub } from 'test/fixtures/library.stub';
|
import { libraryStub } from 'test/fixtures/library.stub';
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { IAlbumRepository } from 'src/interfaces/album.interface';
|
import { IAlbumRepository } from 'src/interfaces/album.interface';
|
||||||
import { IMapRepository } from 'src/interfaces/map.interface';
|
|
||||||
import { IPartnerRepository } from 'src/interfaces/partner.interface';
|
import { IPartnerRepository } from 'src/interfaces/partner.interface';
|
||||||
import { MapService } from 'src/services/map.service';
|
import { MapService } from 'src/services/map.service';
|
||||||
|
import { IMapRepository } from 'src/types';
|
||||||
import { albumStub } from 'test/fixtures/album.stub';
|
import { albumStub } from 'test/fixtures/album.stub';
|
||||||
import { assetStub } from 'test/fixtures/asset.stub';
|
import { assetStub } from 'test/fixtures/asset.stub';
|
||||||
import { authStub } from 'test/fixtures/auth.stub';
|
import { authStub } from 'test/fixtures/auth.stub';
|
||||||
|
@ -10,15 +10,14 @@ import { IAssetRepository, WithoutProperty } from 'src/interfaces/asset.interfac
|
|||||||
import { ICryptoRepository } from 'src/interfaces/crypto.interface';
|
import { ICryptoRepository } from 'src/interfaces/crypto.interface';
|
||||||
import { IEventRepository } from 'src/interfaces/event.interface';
|
import { IEventRepository } from 'src/interfaces/event.interface';
|
||||||
import { IJobRepository, JobName, JobStatus } from 'src/interfaces/job.interface';
|
import { IJobRepository, JobName, JobStatus } from 'src/interfaces/job.interface';
|
||||||
import { IMapRepository } from 'src/interfaces/map.interface';
|
|
||||||
import { IMetadataRepository, ImmichTags } from 'src/interfaces/metadata.interface';
|
|
||||||
import { IPersonRepository } from 'src/interfaces/person.interface';
|
import { IPersonRepository } from 'src/interfaces/person.interface';
|
||||||
import { IStorageRepository } from 'src/interfaces/storage.interface';
|
import { IStorageRepository } from 'src/interfaces/storage.interface';
|
||||||
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
|
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
|
||||||
import { ITagRepository } from 'src/interfaces/tag.interface';
|
import { ITagRepository } from 'src/interfaces/tag.interface';
|
||||||
import { IUserRepository } from 'src/interfaces/user.interface';
|
import { IUserRepository } from 'src/interfaces/user.interface';
|
||||||
|
import { ImmichTags } from 'src/repositories/metadata.repository';
|
||||||
import { MetadataService } from 'src/services/metadata.service';
|
import { MetadataService } from 'src/services/metadata.service';
|
||||||
import { IConfigRepository, IMediaRepository } from 'src/types';
|
import { IConfigRepository, IMapRepository, IMediaRepository, IMetadataRepository } from 'src/types';
|
||||||
import { assetStub } from 'test/fixtures/asset.stub';
|
import { assetStub } from 'test/fixtures/asset.stub';
|
||||||
import { fileStub } from 'test/fixtures/file.stub';
|
import { fileStub } from 'test/fixtures/file.stub';
|
||||||
import { probeStub } from 'test/fixtures/media.stub';
|
import { probeStub } from 'test/fixtures/media.stub';
|
||||||
|
@ -18,8 +18,8 @@ import { WithoutProperty } from 'src/interfaces/asset.interface';
|
|||||||
import { DatabaseLock } from 'src/interfaces/database.interface';
|
import { DatabaseLock } from 'src/interfaces/database.interface';
|
||||||
import { ArgOf } from 'src/interfaces/event.interface';
|
import { ArgOf } from 'src/interfaces/event.interface';
|
||||||
import { JobName, JobOf, JOBS_ASSET_PAGINATION_SIZE, JobStatus, QueueName } from 'src/interfaces/job.interface';
|
import { JobName, JobOf, JOBS_ASSET_PAGINATION_SIZE, JobStatus, QueueName } from 'src/interfaces/job.interface';
|
||||||
import { ReverseGeocodeResult } from 'src/interfaces/map.interface';
|
import { ReverseGeocodeResult } from 'src/repositories/map.repository';
|
||||||
import { ImmichTags } from 'src/interfaces/metadata.interface';
|
import { ImmichTags } from 'src/repositories/metadata.repository';
|
||||||
import { BaseService } from 'src/services/base.service';
|
import { BaseService } from 'src/services/base.service';
|
||||||
import { isFaceImportEnabled } from 'src/utils/misc';
|
import { isFaceImportEnabled } from 'src/utils/misc';
|
||||||
import { usePagination } from 'src/utils/pagination';
|
import { usePagination } from 'src/utils/pagination';
|
||||||
|
@ -8,10 +8,11 @@ import { IAlbumRepository } from 'src/interfaces/album.interface';
|
|||||||
import { IAssetRepository } from 'src/interfaces/asset.interface';
|
import { IAssetRepository } from 'src/interfaces/asset.interface';
|
||||||
import { IEventRepository } from 'src/interfaces/event.interface';
|
import { IEventRepository } from 'src/interfaces/event.interface';
|
||||||
import { IJobRepository, INotifyAlbumUpdateJob, JobName, JobStatus } from 'src/interfaces/job.interface';
|
import { IJobRepository, INotifyAlbumUpdateJob, JobName, JobStatus } from 'src/interfaces/job.interface';
|
||||||
import { EmailTemplate, INotificationRepository } from 'src/interfaces/notification.interface';
|
|
||||||
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
|
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
|
||||||
import { IUserRepository } from 'src/interfaces/user.interface';
|
import { IUserRepository } from 'src/interfaces/user.interface';
|
||||||
|
import { EmailTemplate } from 'src/repositories/notification.repository';
|
||||||
import { NotificationService } from 'src/services/notification.service';
|
import { NotificationService } from 'src/services/notification.service';
|
||||||
|
import { INotificationRepository } from 'src/types';
|
||||||
import { albumStub } from 'test/fixtures/album.stub';
|
import { albumStub } from 'test/fixtures/album.stub';
|
||||||
import { assetStub } from 'test/fixtures/asset.stub';
|
import { assetStub } from 'test/fixtures/asset.stub';
|
||||||
import { userStub } from 'test/fixtures/user.stub';
|
import { userStub } from 'test/fixtures/user.stub';
|
||||||
|
@ -12,7 +12,7 @@ import {
|
|||||||
JobStatus,
|
JobStatus,
|
||||||
QueueName,
|
QueueName,
|
||||||
} from 'src/interfaces/job.interface';
|
} from 'src/interfaces/job.interface';
|
||||||
import { EmailImageAttachment, EmailTemplate } from 'src/interfaces/notification.interface';
|
import { EmailImageAttachment, EmailTemplate } from 'src/repositories/notification.repository';
|
||||||
import { BaseService } from 'src/services/base.service';
|
import { BaseService } from 'src/services/base.service';
|
||||||
import { getAssetFiles } from 'src/utils/asset.util';
|
import { getAssetFiles } from 'src/utils/asset.util';
|
||||||
import { getFilenameExtension } from 'src/utils/file';
|
import { getFilenameExtension } from 'src/utils/file';
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { BadRequestException } from '@nestjs/common';
|
import { BadRequestException } from '@nestjs/common';
|
||||||
import { IJobRepository, JobName, JobStatus } from 'src/interfaces/job.interface';
|
import { IJobRepository, JobName, JobStatus } from 'src/interfaces/job.interface';
|
||||||
import { ITrashRepository } from 'src/interfaces/trash.interface';
|
|
||||||
import { TrashService } from 'src/services/trash.service';
|
import { TrashService } from 'src/services/trash.service';
|
||||||
|
import { ITrashRepository } from 'src/types';
|
||||||
import { authStub } from 'test/fixtures/auth.stub';
|
import { authStub } from 'test/fixtures/auth.stub';
|
||||||
import { IAccessRepositoryMock } from 'test/repositories/access.repository.mock';
|
import { IAccessRepositoryMock } from 'test/repositories/access.repository.mock';
|
||||||
import { newTestService } from 'test/utils';
|
import { newTestService } from 'test/utils';
|
||||||
|
@ -4,11 +4,9 @@ import { serverVersion } from 'src/constants';
|
|||||||
import { ImmichEnvironment, SystemMetadataKey } from 'src/enum';
|
import { ImmichEnvironment, SystemMetadataKey } from 'src/enum';
|
||||||
import { IEventRepository } from 'src/interfaces/event.interface';
|
import { IEventRepository } from 'src/interfaces/event.interface';
|
||||||
import { IJobRepository, JobName, JobStatus } from 'src/interfaces/job.interface';
|
import { IJobRepository, JobName, JobStatus } from 'src/interfaces/job.interface';
|
||||||
import { IServerInfoRepository } from 'src/interfaces/server-info.interface';
|
|
||||||
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
|
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
|
||||||
import { IVersionHistoryRepository } from 'src/interfaces/version-history.interface';
|
|
||||||
import { VersionService } from 'src/services/version.service';
|
import { VersionService } from 'src/services/version.service';
|
||||||
import { IConfigRepository, ILoggingRepository } from 'src/types';
|
import { IConfigRepository, ILoggingRepository, IServerInfoRepository, IVersionHistoryRepository } from 'src/types';
|
||||||
import { mockEnvData } from 'test/repositories/config.repository.mock';
|
import { mockEnvData } from 'test/repositories/config.repository.mock';
|
||||||
import { newTestService } from 'test/utils';
|
import { newTestService } from 'test/utils';
|
||||||
import { Mocked } from 'vitest';
|
import { Mocked } from 'vitest';
|
||||||
|
@ -2,12 +2,22 @@ import { UserEntity } from 'src/entities/user.entity';
|
|||||||
import { ExifOrientation, ImageFormat, Permission, TranscodeTarget, VideoCodec } from 'src/enum';
|
import { ExifOrientation, ImageFormat, Permission, TranscodeTarget, VideoCodec } from 'src/enum';
|
||||||
import { AccessRepository } from 'src/repositories/access.repository';
|
import { AccessRepository } from 'src/repositories/access.repository';
|
||||||
import { ActivityRepository } from 'src/repositories/activity.repository';
|
import { ActivityRepository } from 'src/repositories/activity.repository';
|
||||||
|
import { AlbumUserRepository } from 'src/repositories/album-user.repository';
|
||||||
import { ApiKeyRepository } from 'src/repositories/api-key.repository';
|
import { ApiKeyRepository } from 'src/repositories/api-key.repository';
|
||||||
import { AuditRepository } from 'src/repositories/audit.repository';
|
import { AuditRepository } from 'src/repositories/audit.repository';
|
||||||
import { ConfigRepository } from 'src/repositories/config.repository';
|
import { ConfigRepository } from 'src/repositories/config.repository';
|
||||||
|
import { CronRepository } from 'src/repositories/cron.repository';
|
||||||
import { LoggingRepository } from 'src/repositories/logging.repository';
|
import { LoggingRepository } from 'src/repositories/logging.repository';
|
||||||
|
import { MapRepository } from 'src/repositories/map.repository';
|
||||||
import { MediaRepository } from 'src/repositories/media.repository';
|
import { MediaRepository } from 'src/repositories/media.repository';
|
||||||
import { MemoryRepository } from 'src/repositories/memory.repository';
|
import { MemoryRepository } from 'src/repositories/memory.repository';
|
||||||
|
import { MetadataRepository } from 'src/repositories/metadata.repository';
|
||||||
|
import { NotificationRepository } from 'src/repositories/notification.repository';
|
||||||
|
import { OAuthRepository } from 'src/repositories/oauth.repository';
|
||||||
|
import { ServerInfoRepository } from 'src/repositories/server-info.repository';
|
||||||
|
import { MetricGroupRepository, TelemetryRepository } from 'src/repositories/telemetry.repository';
|
||||||
|
import { TrashRepository } from 'src/repositories/trash.repository';
|
||||||
|
import { VersionHistoryRepository } from 'src/repositories/version-history.repository';
|
||||||
import { ViewRepository } from 'src/repositories/view-repository';
|
import { ViewRepository } from 'src/repositories/view-repository';
|
||||||
|
|
||||||
export type DeepPartial<T> = T extends object ? { [K in keyof T]?: DeepPartial<T[K]> } : T;
|
export type DeepPartial<T> = T extends object ? { [K in keyof T]?: DeepPartial<T[K]> } : T;
|
||||||
@ -23,9 +33,11 @@ export type RepositoryInterface<T extends object> = Pick<T, keyof T>;
|
|||||||
|
|
||||||
export type IActivityRepository = RepositoryInterface<ActivityRepository>;
|
export type IActivityRepository = RepositoryInterface<ActivityRepository>;
|
||||||
export type IAccessRepository = { [K in keyof AccessRepository]: RepositoryInterface<AccessRepository[K]> };
|
export type IAccessRepository = { [K in keyof AccessRepository]: RepositoryInterface<AccessRepository[K]> };
|
||||||
|
export type IAlbumUserRepository = RepositoryInterface<AlbumUserRepository>;
|
||||||
export type IApiKeyRepository = RepositoryInterface<ApiKeyRepository>;
|
export type IApiKeyRepository = RepositoryInterface<ApiKeyRepository>;
|
||||||
export type IAuditRepository = RepositoryInterface<AuditRepository>;
|
export type IAuditRepository = RepositoryInterface<AuditRepository>;
|
||||||
export type IConfigRepository = RepositoryInterface<ConfigRepository>;
|
export type IConfigRepository = RepositoryInterface<ConfigRepository>;
|
||||||
|
export type ICronRepository = RepositoryInterface<CronRepository>;
|
||||||
export type ILoggingRepository = Pick<
|
export type ILoggingRepository = Pick<
|
||||||
LoggingRepository,
|
LoggingRepository,
|
||||||
| 'verbose'
|
| 'verbose'
|
||||||
@ -39,9 +51,18 @@ export type ILoggingRepository = Pick<
|
|||||||
| 'setContext'
|
| 'setContext'
|
||||||
| 'setAppName'
|
| 'setAppName'
|
||||||
>;
|
>;
|
||||||
|
export type IMapRepository = RepositoryInterface<MapRepository>;
|
||||||
export type IMediaRepository = RepositoryInterface<MediaRepository>;
|
export type IMediaRepository = RepositoryInterface<MediaRepository>;
|
||||||
export type IMemoryRepository = RepositoryInterface<MemoryRepository>;
|
export type IMemoryRepository = RepositoryInterface<MemoryRepository>;
|
||||||
|
export type IMetadataRepository = RepositoryInterface<MetadataRepository>;
|
||||||
|
export type IMetricGroupRepository = RepositoryInterface<MetricGroupRepository>;
|
||||||
|
export type INotificationRepository = RepositoryInterface<NotificationRepository>;
|
||||||
|
export type IOAuthRepository = RepositoryInterface<OAuthRepository>;
|
||||||
|
export type IServerInfoRepository = RepositoryInterface<ServerInfoRepository>;
|
||||||
|
export type ITelemetryRepository = RepositoryInterface<TelemetryRepository>;
|
||||||
|
export type ITrashRepository = RepositoryInterface<TrashRepository>;
|
||||||
export type IViewRepository = RepositoryInterface<ViewRepository>;
|
export type IViewRepository = RepositoryInterface<ViewRepository>;
|
||||||
|
export type IVersionHistoryRepository = RepositoryInterface<VersionHistoryRepository>;
|
||||||
|
|
||||||
export type ActivityItem =
|
export type ActivityItem =
|
||||||
| Awaited<ReturnType<IActivityRepository['create']>>
|
| Awaited<ReturnType<IActivityRepository['create']>>
|
||||||
|
2
server/test/fixtures/metadata.stub.ts
vendored
2
server/test/fixtures/metadata.stub.ts
vendored
@ -1,4 +1,4 @@
|
|||||||
import { ImmichTags } from 'src/interfaces/metadata.interface';
|
import { ImmichTags } from 'src/repositories/metadata.repository';
|
||||||
import { personStub } from 'test/fixtures/person.stub';
|
import { personStub } from 'test/fixtures/person.stub';
|
||||||
|
|
||||||
export const metadataStub = {
|
export const metadataStub = {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { IAlbumUserRepository } from 'src/interfaces/album-user.interface';
|
import { IAlbumUserRepository } from 'src/types';
|
||||||
import { Mocked } from 'vitest';
|
import { Mocked } from 'vitest';
|
||||||
|
|
||||||
export const newAlbumUserRepositoryMock = (): Mocked<IAlbumUserRepository> => {
|
export const newAlbumUserRepositoryMock = (): Mocked<IAlbumUserRepository> => {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { ICronRepository } from 'src/interfaces/cron.interface';
|
import { ICronRepository } from 'src/types';
|
||||||
import { Mocked, vitest } from 'vitest';
|
import { Mocked, vitest } from 'vitest';
|
||||||
|
|
||||||
export const newCronRepositoryMock = (): Mocked<ICronRepository> => {
|
export const newCronRepositoryMock = (): Mocked<ICronRepository> => {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { IMapRepository } from 'src/interfaces/map.interface';
|
import { IMapRepository } from 'src/types';
|
||||||
import { Mocked } from 'vitest';
|
import { Mocked } from 'vitest';
|
||||||
|
|
||||||
export const newMapRepositoryMock = (): Mocked<IMapRepository> => {
|
export const newMapRepositoryMock = (): Mocked<IMapRepository> => {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { IMetadataRepository } from 'src/interfaces/metadata.interface';
|
import { IMetadataRepository } from 'src/types';
|
||||||
import { Mocked, vitest } from 'vitest';
|
import { Mocked, vitest } from 'vitest';
|
||||||
|
|
||||||
export const newMetadataRepositoryMock = (): Mocked<IMetadataRepository> => {
|
export const newMetadataRepositoryMock = (): Mocked<IMetadataRepository> => {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { INotificationRepository } from 'src/interfaces/notification.interface';
|
import { INotificationRepository } from 'src/types';
|
||||||
import { Mocked } from 'vitest';
|
import { Mocked } from 'vitest';
|
||||||
|
|
||||||
export const newNotificationRepositoryMock = (): Mocked<INotificationRepository> => {
|
export const newNotificationRepositoryMock = (): Mocked<INotificationRepository> => {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { IOAuthRepository } from 'src/interfaces/oauth.interface';
|
import { IOAuthRepository } from 'src/types';
|
||||||
import { Mocked } from 'vitest';
|
import { Mocked } from 'vitest';
|
||||||
|
|
||||||
export const newOAuthRepositoryMock = (): Mocked<IOAuthRepository> => {
|
export const newOAuthRepositoryMock = (): Mocked<IOAuthRepository> => {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { IServerInfoRepository } from 'src/interfaces/server-info.interface';
|
import { IServerInfoRepository } from 'src/types';
|
||||||
import { Mocked, vitest } from 'vitest';
|
import { Mocked, vitest } from 'vitest';
|
||||||
|
|
||||||
export const newServerInfoRepositoryMock = (): Mocked<IServerInfoRepository> => {
|
export const newServerInfoRepositoryMock = (): Mocked<IServerInfoRepository> => {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { ITelemetryRepository } from 'src/interfaces/telemetry.interface';
|
import { ITelemetryRepository, RepositoryInterface } from 'src/types';
|
||||||
import { Mocked, vitest } from 'vitest';
|
import { Mocked, vitest } from 'vitest';
|
||||||
|
|
||||||
const newMetricGroupMock = () => {
|
const newMetricGroupMock = () => {
|
||||||
@ -10,7 +10,11 @@ const newMetricGroupMock = () => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const newTelemetryRepositoryMock = (): Mocked<ITelemetryRepository> => {
|
export type ITelemetryRepositoryMock = {
|
||||||
|
[K in keyof ITelemetryRepository]: Mocked<RepositoryInterface<ITelemetryRepository[K]>>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const newTelemetryRepositoryMock = (): ITelemetryRepositoryMock => {
|
||||||
return {
|
return {
|
||||||
setup: vitest.fn(),
|
setup: vitest.fn(),
|
||||||
api: newMetricGroupMock(),
|
api: newMetricGroupMock(),
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { ITrashRepository } from 'src/interfaces/trash.interface';
|
import { ITrashRepository } from 'src/types';
|
||||||
import { Mocked, vitest } from 'vitest';
|
import { Mocked, vitest } from 'vitest';
|
||||||
|
|
||||||
export const newTrashRepositoryMock = (): Mocked<ITrashRepository> => {
|
export const newTrashRepositoryMock = (): Mocked<ITrashRepository> => {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { IVersionHistoryRepository } from 'src/interfaces/version-history.interface';
|
import { IVersionHistoryRepository } from 'src/types';
|
||||||
import { Mocked, vitest } from 'vitest';
|
import { Mocked, vitest } from 'vitest';
|
||||||
|
|
||||||
export const newVersionHistoryRepositoryMock = (): Mocked<IVersionHistoryRepository> => {
|
export const newVersionHistoryRepositoryMock = (): Mocked<IVersionHistoryRepository> => {
|
||||||
|
@ -2,24 +2,42 @@ import { ChildProcessWithoutNullStreams } from 'node:child_process';
|
|||||||
import { Writable } from 'node:stream';
|
import { Writable } from 'node:stream';
|
||||||
import { PNG } from 'pngjs';
|
import { PNG } from 'pngjs';
|
||||||
import { ImmichWorker } from 'src/enum';
|
import { ImmichWorker } from 'src/enum';
|
||||||
import { IMetadataRepository } from 'src/interfaces/metadata.interface';
|
|
||||||
import { AccessRepository } from 'src/repositories/access.repository';
|
import { AccessRepository } from 'src/repositories/access.repository';
|
||||||
import { ActivityRepository } from 'src/repositories/activity.repository';
|
import { ActivityRepository } from 'src/repositories/activity.repository';
|
||||||
|
import { AlbumUserRepository } from 'src/repositories/album-user.repository';
|
||||||
import { ApiKeyRepository } from 'src/repositories/api-key.repository';
|
import { ApiKeyRepository } from 'src/repositories/api-key.repository';
|
||||||
import { AuditRepository } from 'src/repositories/audit.repository';
|
import { AuditRepository } from 'src/repositories/audit.repository';
|
||||||
|
import { CronRepository } from 'src/repositories/cron.repository';
|
||||||
import { LoggingRepository } from 'src/repositories/logging.repository';
|
import { LoggingRepository } from 'src/repositories/logging.repository';
|
||||||
|
import { MapRepository } from 'src/repositories/map.repository';
|
||||||
import { MediaRepository } from 'src/repositories/media.repository';
|
import { MediaRepository } from 'src/repositories/media.repository';
|
||||||
import { MemoryRepository } from 'src/repositories/memory.repository';
|
import { MemoryRepository } from 'src/repositories/memory.repository';
|
||||||
|
import { MetadataRepository } from 'src/repositories/metadata.repository';
|
||||||
|
import { NotificationRepository } from 'src/repositories/notification.repository';
|
||||||
|
import { OAuthRepository } from 'src/repositories/oauth.repository';
|
||||||
|
import { ServerInfoRepository } from 'src/repositories/server-info.repository';
|
||||||
|
import { TelemetryRepository } from 'src/repositories/telemetry.repository';
|
||||||
|
import { TrashRepository } from 'src/repositories/trash.repository';
|
||||||
|
import { VersionHistoryRepository } from 'src/repositories/version-history.repository';
|
||||||
import { ViewRepository } from 'src/repositories/view-repository';
|
import { ViewRepository } from 'src/repositories/view-repository';
|
||||||
import { BaseService } from 'src/services/base.service';
|
import { BaseService } from 'src/services/base.service';
|
||||||
import {
|
import {
|
||||||
IAccessRepository,
|
IAccessRepository,
|
||||||
IActivityRepository,
|
IActivityRepository,
|
||||||
|
IAlbumUserRepository,
|
||||||
IApiKeyRepository,
|
IApiKeyRepository,
|
||||||
IAuditRepository,
|
IAuditRepository,
|
||||||
|
ICronRepository,
|
||||||
ILoggingRepository,
|
ILoggingRepository,
|
||||||
|
IMapRepository,
|
||||||
IMediaRepository,
|
IMediaRepository,
|
||||||
IMemoryRepository,
|
IMemoryRepository,
|
||||||
|
IMetadataRepository,
|
||||||
|
INotificationRepository,
|
||||||
|
IOAuthRepository,
|
||||||
|
IServerInfoRepository,
|
||||||
|
ITrashRepository,
|
||||||
|
IVersionHistoryRepository,
|
||||||
IViewRepository,
|
IViewRepository,
|
||||||
} from 'src/types';
|
} from 'src/types';
|
||||||
import { newAccessRepositoryMock } from 'test/repositories/access.repository.mock';
|
import { newAccessRepositoryMock } from 'test/repositories/access.repository.mock';
|
||||||
@ -66,7 +84,7 @@ import { Mocked, vitest } from 'vitest';
|
|||||||
|
|
||||||
type Overrides = {
|
type Overrides = {
|
||||||
worker?: ImmichWorker;
|
worker?: ImmichWorker;
|
||||||
metadataRepository?: IMetadataRepository;
|
metadataRepository?: MetadataRepository;
|
||||||
};
|
};
|
||||||
type BaseServiceArgs = ConstructorParameters<typeof BaseService>;
|
type BaseServiceArgs = ConstructorParameters<typeof BaseService>;
|
||||||
type Constructor<Type, Args extends Array<any>> = {
|
type Constructor<Type, Args extends Array<any>> = {
|
||||||
@ -125,10 +143,10 @@ export const newTestService = <T extends BaseService>(
|
|||||||
activityMock as IActivityRepository as ActivityRepository,
|
activityMock as IActivityRepository as ActivityRepository,
|
||||||
auditMock as IAuditRepository as AuditRepository,
|
auditMock as IAuditRepository as AuditRepository,
|
||||||
albumMock,
|
albumMock,
|
||||||
albumUserMock,
|
albumUserMock as IAlbumUserRepository as AlbumUserRepository,
|
||||||
assetMock,
|
assetMock,
|
||||||
configMock,
|
configMock,
|
||||||
cronMock,
|
cronMock as ICronRepository as CronRepository,
|
||||||
cryptoMock,
|
cryptoMock,
|
||||||
databaseMock,
|
databaseMock,
|
||||||
eventMock,
|
eventMock,
|
||||||
@ -136,28 +154,28 @@ export const newTestService = <T extends BaseService>(
|
|||||||
keyMock as IApiKeyRepository as ApiKeyRepository,
|
keyMock as IApiKeyRepository as ApiKeyRepository,
|
||||||
libraryMock,
|
libraryMock,
|
||||||
machineLearningMock,
|
machineLearningMock,
|
||||||
mapMock,
|
mapMock as IMapRepository as MapRepository,
|
||||||
mediaMock as IMediaRepository as MediaRepository,
|
mediaMock as IMediaRepository as MediaRepository,
|
||||||
memoryMock as IMemoryRepository as MemoryRepository,
|
memoryMock as IMemoryRepository as MemoryRepository,
|
||||||
metadataMock,
|
metadataMock as IMetadataRepository as MetadataRepository,
|
||||||
moveMock,
|
moveMock,
|
||||||
notificationMock,
|
notificationMock as INotificationRepository as NotificationRepository,
|
||||||
oauthMock,
|
oauthMock as IOAuthRepository as OAuthRepository,
|
||||||
partnerMock,
|
partnerMock,
|
||||||
personMock,
|
personMock,
|
||||||
processMock,
|
processMock,
|
||||||
searchMock,
|
searchMock,
|
||||||
serverInfoMock,
|
serverInfoMock as IServerInfoRepository as ServerInfoRepository,
|
||||||
sessionMock,
|
sessionMock,
|
||||||
sharedLinkMock,
|
sharedLinkMock,
|
||||||
stackMock,
|
stackMock,
|
||||||
storageMock,
|
storageMock,
|
||||||
systemMock,
|
systemMock,
|
||||||
tagMock,
|
tagMock,
|
||||||
telemetryMock,
|
telemetryMock as unknown as TelemetryRepository,
|
||||||
trashMock,
|
trashMock as ITrashRepository as TrashRepository,
|
||||||
userMock,
|
userMock,
|
||||||
versionHistoryMock,
|
versionHistoryMock as IVersionHistoryRepository as VersionHistoryRepository,
|
||||||
viewMock as IViewRepository as ViewRepository,
|
viewMock as IViewRepository as ViewRepository,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
8
web/package-lock.json
generated
8
web/package-lock.json
generated
@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "immich-web",
|
"name": "immich-web",
|
||||||
"version": "1.124.2",
|
"version": "1.125.1",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "immich-web",
|
"name": "immich-web",
|
||||||
"version": "1.124.2",
|
"version": "1.125.1",
|
||||||
"license": "GNU Affero General Public License version 3",
|
"license": "GNU Affero General Public License version 3",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@formatjs/icu-messageformat-parser": "^2.9.8",
|
"@formatjs/icu-messageformat-parser": "^2.9.8",
|
||||||
@ -75,13 +75,13 @@
|
|||||||
},
|
},
|
||||||
"../open-api/typescript-sdk": {
|
"../open-api/typescript-sdk": {
|
||||||
"name": "@immich/sdk",
|
"name": "@immich/sdk",
|
||||||
"version": "1.124.2",
|
"version": "1.125.1",
|
||||||
"license": "GNU Affero General Public License version 3",
|
"license": "GNU Affero General Public License version 3",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@oazapfts/runtime": "^1.0.2"
|
"@oazapfts/runtime": "^1.0.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^22.10.7",
|
"@types/node": "^22.10.9",
|
||||||
"typescript": "^5.3.3"
|
"typescript": "^5.3.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "immich-web",
|
"name": "immich-web",
|
||||||
"version": "1.124.2",
|
"version": "1.125.1",
|
||||||
"license": "GNU Affero General Public License version 3",
|
"license": "GNU Affero General Public License version 3",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite dev --host 0.0.0.0 --port 3000",
|
"dev": "vite dev --host 0.0.0.0 --port 3000",
|
||||||
|
@ -45,6 +45,7 @@
|
|||||||
import UserAvatar from '../shared-components/user-avatar.svelte';
|
import UserAvatar from '../shared-components/user-avatar.svelte';
|
||||||
import AlbumListItemDetails from './album-list-item-details.svelte';
|
import AlbumListItemDetails from './album-list-item-details.svelte';
|
||||||
import Portal from '$lib/components/shared-components/portal/portal.svelte';
|
import Portal from '$lib/components/shared-components/portal/portal.svelte';
|
||||||
|
import { getMetadataSearchQuery } from '$lib/utils/metadata-search';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
asset: AssetResponseDto;
|
asset: AssetResponseDto;
|
||||||
@ -410,7 +411,36 @@
|
|||||||
<div><Icon path={mdiCameraIris} size="24" /></div>
|
<div><Icon path={mdiCameraIris} size="24" /></div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<p>{asset.exifInfo.make || ''} {asset.exifInfo.model || ''}</p>
|
{#if asset.exifInfo?.make || asset.exifInfo?.model}
|
||||||
|
<p>
|
||||||
|
<a
|
||||||
|
href="{AppRoute.SEARCH}?{getMetadataSearchQuery({
|
||||||
|
...(asset.exifInfo?.make ? { make: asset.exifInfo.make } : {}),
|
||||||
|
...(asset.exifInfo?.model ? { model: asset.exifInfo.model } : {}),
|
||||||
|
})}"
|
||||||
|
title="{$t('search_for')} {asset.exifInfo.make || ''} {asset.exifInfo.model || ''}"
|
||||||
|
class="hover:dark:text-immich-dark-primary hover:text-immich-primary"
|
||||||
|
>
|
||||||
|
{asset.exifInfo.make || ''}
|
||||||
|
{asset.exifInfo.model || ''}
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if asset.exifInfo?.lensModel}
|
||||||
|
<div class="flex gap-2 text-sm">
|
||||||
|
<p>
|
||||||
|
<a
|
||||||
|
href="{AppRoute.SEARCH}?{getMetadataSearchQuery({ lensModel: asset.exifInfo.lensModel })}"
|
||||||
|
title="{$t('search_for')} {asset.exifInfo.lensModel}"
|
||||||
|
class="hover:dark:text-immich-dark-primary hover:text-immich-primary line-clamp-1"
|
||||||
|
>
|
||||||
|
{asset.exifInfo.lensModel}
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
<div class="flex gap-2 text-sm">
|
<div class="flex gap-2 text-sm">
|
||||||
{#if asset.exifInfo?.fNumber}
|
{#if asset.exifInfo?.fNumber}
|
||||||
<p>ƒ/{asset.exifInfo.fNumber.toLocaleString($locale)}</p>
|
<p>ƒ/{asset.exifInfo.fNumber.toLocaleString($locale)}</p>
|
||||||
|
@ -10,7 +10,12 @@
|
|||||||
let { title, children }: Props = $props();
|
let { title, children }: Props = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<section class="min-w-screen flex min-h-screen items-center justify-center">
|
<section class="min-w-screen flex min-h-dvh items-center justify-center relative">
|
||||||
|
<div class="absolute -z-10 w-[99%] h-[99%] flex place-items-center place-content-center">
|
||||||
|
<img src="/immich-logo-no-bg.svg" class="w-[750px] mb-2 antialiased -z-10" alt="Immich logo" />
|
||||||
|
<div class="w-full h-[99%] absolute left-0 top-0 backdrop-blur-3xl bg-immich-bg/80 dark:bg-immich-dark-bg/20"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<Card color="secondary" class="w-full max-w-lg border m-2">
|
<Card color="secondary" class="w-full max-w-lg border m-2">
|
||||||
<CardHeader class="mt-6">
|
<CardHeader class="mt-6">
|
||||||
<VStack>
|
<VStack>
|
||||||
@ -18,7 +23,8 @@
|
|||||||
<Heading size="large" class="font-semibold" color="primary">{title}</Heading>
|
<Heading size="large" class="font-semibold" color="primary">{title}</Heading>
|
||||||
</VStack>
|
</VStack>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardBody>
|
|
||||||
|
<CardBody class="p-8">
|
||||||
{@render children?.()}
|
{@render children?.()}
|
||||||
</CardBody>
|
</CardBody>
|
||||||
</Card>
|
</Card>
|
||||||
|
@ -192,6 +192,7 @@
|
|||||||
state: $t('state'),
|
state: $t('state'),
|
||||||
make: $t('camera_brand'),
|
make: $t('camera_brand'),
|
||||||
model: $t('camera_model'),
|
model: $t('camera_model'),
|
||||||
|
lensModel: $t('lens_model'),
|
||||||
personIds: $t('people'),
|
personIds: $t('people'),
|
||||||
originalFileName: $t('file_name'),
|
originalFileName: $t('file_name'),
|
||||||
};
|
};
|
||||||
|
@ -34,29 +34,25 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<AuthPageLayout title={data.meta.title}>
|
<AuthPageLayout title={data.meta.title}>
|
||||||
<div class="m-4">
|
<form onsubmit={onSubmit} method="post" class="flex flex-col gap-4">
|
||||||
<Alert color="primary" size="small">
|
<Alert color="primary" size="small" class="mb-2">
|
||||||
<Stack gap={4}>
|
<Stack gap={4}>
|
||||||
<Text>{$t('hi_user', { values: { name: $user.name, email: $user.email } })}</Text>
|
<Text>{$t('hi_user', { values: { name: $user.name, email: $user.email } })}</Text>
|
||||||
<Text>{$t('change_password_description')}</Text>
|
<Text>{$t('change_password_description')}</Text>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Alert>
|
</Alert>
|
||||||
</div>
|
|
||||||
|
|
||||||
<form onsubmit={onSubmit} method="post" class="mx-4 mt-6">
|
<Field label={$t('new_password')} required>
|
||||||
<Stack gap={4} class="mt-4">
|
<PasswordInput bind:value={password} autocomplete="new-password" />
|
||||||
<Field label={$t('new_password')} required>
|
</Field>
|
||||||
<PasswordInput bind:value={password} autocomplete="new-password" />
|
|
||||||
</Field>
|
|
||||||
|
|
||||||
<Field label={$t('confirm_password')} required>
|
<Field label={$t('confirm_password')} required>
|
||||||
<PasswordInput bind:value={passwordConfirm} autocomplete="new-password" />
|
<PasswordInput bind:value={passwordConfirm} autocomplete="new-password" />
|
||||||
<HelperText color="danger">{errorMessage}</HelperText>
|
<HelperText color="danger">{errorMessage}</HelperText>
|
||||||
</Field>
|
</Field>
|
||||||
|
|
||||||
<div class="my-5 flex w-full">
|
<Button class="mt-2" type="submit" size="large" shape="round" fullWidth disabled={!valid}
|
||||||
<Button type="submit" size="large" shape="round" fullWidth disabled={!valid}>{$t('to_change_password')}</Button>
|
>{$t('to_change_password')}</Button
|
||||||
</div>
|
>
|
||||||
</Stack>
|
|
||||||
</form>
|
</form>
|
||||||
</AuthPageLayout>
|
</AuthPageLayout>
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
import { oauth } from '$lib/utils';
|
import { oauth } from '$lib/utils';
|
||||||
import { getServerErrorMessage, handleError } from '$lib/utils/handle-error';
|
import { getServerErrorMessage, handleError } from '$lib/utils/handle-error';
|
||||||
import { login } from '@immich/sdk';
|
import { login } from '@immich/sdk';
|
||||||
import { Alert, Button, Field, Input, PasswordInput } from '@immich/ui';
|
import { Alert, Button, Field, Input, PasswordInput, Stack } from '@immich/ui';
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import { t } from 'svelte-i18n';
|
import { t } from 'svelte-i18n';
|
||||||
import type { PageData } from './$types';
|
import type { PageData } from './$types';
|
||||||
@ -101,43 +101,43 @@
|
|||||||
|
|
||||||
{#if $featureFlags.loaded}
|
{#if $featureFlags.loaded}
|
||||||
<AuthPageLayout title={data.meta.title}>
|
<AuthPageLayout title={data.meta.title}>
|
||||||
{#if $serverConfig.loginPageMessage}
|
<Stack gap={4}>
|
||||||
<Alert color="primary">
|
{#if $serverConfig.loginPageMessage}
|
||||||
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
|
<Alert color="primary" class="mb-6">
|
||||||
{@html $serverConfig.loginPageMessage}
|
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
|
||||||
</Alert>
|
{@html $serverConfig.loginPageMessage}
|
||||||
{/if}
|
</Alert>
|
||||||
|
|
||||||
{#if !oauthLoading && $featureFlags.passwordLogin}
|
|
||||||
<form {onsubmit} class="mt-5 flex flex-col gap-5 text-dark mx-4">
|
|
||||||
{#if errorMessage}
|
|
||||||
<Alert color="danger" title={errorMessage} closable />
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
<Field label={$t('email')}>
|
|
||||||
<Input name="email" type="email" autocomplete="email" bind:value={email} />
|
|
||||||
</Field>
|
|
||||||
|
|
||||||
<Field label={$t('password')}>
|
|
||||||
<PasswordInput bind:value={password} autocomplete="current-password" />
|
|
||||||
</Field>
|
|
||||||
|
|
||||||
<Button type="submit" size="large" shape="round" fullWidth {loading} class="mt-6">{$t('to_login')}</Button>
|
|
||||||
</form>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
{#if $featureFlags.oauth}
|
|
||||||
{#if $featureFlags.passwordLogin}
|
|
||||||
<div class="inline-flex w-full items-center justify-center mt-4">
|
|
||||||
<hr class="my-4 h-px w-3/4 border-0 bg-gray-200 dark:bg-gray-600" />
|
|
||||||
<span
|
|
||||||
class="absolute left-1/2 -translate-x-1/2 bg-gray-50 px-3 font-medium text-gray-900 dark:bg-neutral-900 dark:text-white"
|
|
||||||
>
|
|
||||||
{$t('or').toUpperCase()}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
{/if}
|
{/if}
|
||||||
<div class="my-5 flex flex-col gap-5 mx-4">
|
|
||||||
|
{#if !oauthLoading && $featureFlags.passwordLogin}
|
||||||
|
<form {onsubmit} class="flex flex-col gap-4">
|
||||||
|
{#if errorMessage}
|
||||||
|
<Alert color="danger" title={errorMessage} closable />
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<Field label={$t('email')}>
|
||||||
|
<Input name="email" type="email" autocomplete="email" bind:value={email} />
|
||||||
|
</Field>
|
||||||
|
|
||||||
|
<Field label={$t('password')}>
|
||||||
|
<PasswordInput bind:value={password} autocomplete="current-password" />
|
||||||
|
</Field>
|
||||||
|
|
||||||
|
<Button type="submit" size="large" shape="round" fullWidth {loading} class="mt-6">{$t('to_login')}</Button>
|
||||||
|
</form>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if $featureFlags.oauth}
|
||||||
|
{#if $featureFlags.passwordLogin}
|
||||||
|
<div class="inline-flex w-full items-center justify-center my-4">
|
||||||
|
<hr class="my-4 h-px w-3/4 border-0 bg-gray-200 dark:bg-gray-600" />
|
||||||
|
<span
|
||||||
|
class="absolute left-1/2 -translate-x-1/2 bg-gray-50 px-3 font-medium text-gray-900 dark:bg-neutral-900 dark:text-white"
|
||||||
|
>
|
||||||
|
{$t('or').toUpperCase()}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
{#if oauthError}
|
{#if oauthError}
|
||||||
<Alert color="danger" title={oauthError} closable />
|
<Alert color="danger" title={oauthError} closable />
|
||||||
{/if}
|
{/if}
|
||||||
@ -152,11 +152,11 @@
|
|||||||
>
|
>
|
||||||
{$serverConfig.oauthButtonText}
|
{$serverConfig.oauthButtonText}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
{/if}
|
||||||
{/if}
|
|
||||||
|
|
||||||
{#if !$featureFlags.passwordLogin && !$featureFlags.oauth}
|
{#if !$featureFlags.passwordLogin && !$featureFlags.oauth}
|
||||||
<Alert color="warning" title={$t('login_has_been_disabled')} />
|
<Alert color="warning" title={$t('login_has_been_disabled')} />
|
||||||
{/if}
|
{/if}
|
||||||
|
</Stack>
|
||||||
</AuthPageLayout>
|
</AuthPageLayout>
|
||||||
{/if}
|
{/if}
|
||||||
|
@ -47,13 +47,11 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<AuthPageLayout title={data.meta.title}>
|
<AuthPageLayout title={data.meta.title}>
|
||||||
<div class="mx-4 mt-4">
|
<form onsubmit={onSubmit} method="post" class="flex flex-col gap-4">
|
||||||
<Alert color="primary">
|
<Alert color="primary" class="mb-2">
|
||||||
<Text>{$t('admin.registration_description')}</Text>
|
<Text>{$t('admin.registration_description')}</Text>
|
||||||
</Alert>
|
</Alert>
|
||||||
</div>
|
|
||||||
|
|
||||||
<form onsubmit={onSubmit} method="post" class="mt-5 flex flex-col gap-5 text-dark p-4">
|
|
||||||
<Field label={$t('admin_email')} required>
|
<Field label={$t('admin_email')} required>
|
||||||
<Input bind:value={email} type="email" autocomplete="email" />
|
<Input bind:value={email} type="email" autocomplete="email" />
|
||||||
</Field>
|
</Field>
|
||||||
@ -74,8 +72,6 @@
|
|||||||
<Alert color="danger" title={errorMessage} size="medium" class="mt-4" />
|
<Alert color="danger" title={errorMessage} size="medium" class="mt-4" />
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<div class="my-5 flex w-full">
|
<Button class="mt-4" type="submit" size="giant" shape="round" fullWidth disabled={!valid}>{$t('sign_up')}</Button>
|
||||||
<Button type="submit" size="giant" shape="round" fullWidth disabled={!valid}>{$t('sign_up')}</Button>
|
|
||||||
</div>
|
|
||||||
</form>
|
</form>
|
||||||
</AuthPageLayout>
|
</AuthPageLayout>
|
||||||
|
29
web/static/immich-logo-no-bg.svg
Normal file
29
web/static/immich-logo-no-bg.svg
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Generator: Adobe Illustrator 28.3.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||||
|
<svg version="1.1" id="Flower" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
|
viewBox="0 0 792 792" style="enable-background:new 0 0 792 792;" xml:space="preserve">
|
||||||
|
<style type="text/css">
|
||||||
|
.st0{fill:#FA2921;}
|
||||||
|
.st1{fill:#ED79B5;}
|
||||||
|
.st2{fill:#FFB400;}
|
||||||
|
.st3{fill:#1E83F7;}
|
||||||
|
.st4{fill:#18C249;}
|
||||||
|
</style>
|
||||||
|
<g id="Flower_00000077325900055813483940000000694823054982625702_">
|
||||||
|
<path class="st0" d="M375.48,267.63c38.64,34.21,69.78,70.87,89.82,105.42c34.42-61.56,57.42-134.71,57.71-181.3
|
||||||
|
c0-0.33,0-0.63,0-0.91c0-68.94-68.77-95.77-128.01-95.77s-128.01,26.83-128.01,95.77c0,0.94,0,2.2,0,3.72
|
||||||
|
C300.01,209.24,339.15,235.47,375.48,267.63z"/>
|
||||||
|
<path class="st1" d="M164.7,455.63c24.15-26.87,61.2-55.99,103.01-80.61c44.48-26.18,88.97-44.47,128.02-52.84
|
||||||
|
c-47.91-51.76-110.37-96.24-154.6-110.91c-0.31-0.1-0.6-0.19-0.86-0.28c-65.57-21.3-112.34,35.81-130.64,92.15
|
||||||
|
c-18.3,56.34-14.04,130.04,51.53,151.34C162.05,454.77,163.25,455.16,164.7,455.63z"/>
|
||||||
|
<path class="st2" d="M681.07,302.19c-18.3-56.34-65.07-113.45-130.64-92.15c-0.9,0.29-2.1,0.68-3.54,1.15
|
||||||
|
c-3.75,35.93-16.6,81.27-35.96,125.76c-20.59,47.32-45.84,88.27-72.51,118c69.18,13.72,145.86,12.98,190.26-1.14
|
||||||
|
c0.31-0.1,0.6-0.2,0.86-0.28C695.11,432.22,699.37,358.52,681.07,302.19z"/>
|
||||||
|
<path class="st3" d="M336.54,510.71c-11.15-50.39-14.8-98.36-10.7-138.08c-64.03,29.57-125.63,75.23-153.26,112.76
|
||||||
|
c-0.19,0.26-0.37,0.51-0.53,0.73c-40.52,55.78-0.66,117.91,47.27,152.72c47.92,34.82,119.33,53.54,159.86-2.24
|
||||||
|
c0.56-0.76,1.3-1.78,2.19-3.01C363.28,602.32,347.02,558.08,336.54,510.71z"/>
|
||||||
|
<path class="st4" d="M617.57,482.52c-35.33,7.54-82.42,9.33-130.72,4.66c-51.37-4.96-98.11-16.32-134.63-32.5
|
||||||
|
c8.33,70.03,32.73,142.73,59.88,180.6c0.19,0.26,0.37,0.51,0.53,0.73c40.52,55.78,111.93,37.06,159.86,2.24
|
||||||
|
c47.92-34.82,87.79-96.95,47.27-152.72C619.2,484.77,618.46,483.75,617.57,482.52z"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 2.0 KiB |
Loading…
x
Reference in New Issue
Block a user