Compare commits

...

6 Commits

Author SHA1 Message Date
github-actions
7c15e11efc chore: version v1.116.1 2024-09-27 15:32:16 +00:00
Alex
03aa346020 fix(mobile): incorrect filename is retrieved during upload (#12990)
* fix(mobile): incorrect filename is retrieve during upload

* use the same convention to get local id

* revert previous change

* pr feedback
2024-09-27 22:28:31 +07:00
martin
3a37fc8bfd feat: no slideshow transition (#12989) 2024-09-27 15:05:07 +00:00
Jason Rasmussen
36ee72cd87 refactor(server): access env via repository (#12987) 2024-09-27 10:28:56 -04:00
Jason Rasmussen
12da250028 refactor: enums (#12988) 2024-09-27 10:28:42 -04:00
Ryan Ribeiro
5b282733fe chore(Brazilian README): fix broken image links and update translation (#12980) 2024-09-27 08:15:25 -04:00
80 changed files with 487 additions and 328 deletions

6
cli/package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "@immich/cli",
"version": "2.2.20",
"version": "2.2.21",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@immich/cli",
"version": "2.2.20",
"version": "2.2.21",
"license": "GNU Affero General Public License version 3",
"dependencies": {
"fast-glob": "^3.3.2",
@@ -52,7 +52,7 @@
},
"../open-api/typescript-sdk": {
"name": "@immich/sdk",
"version": "1.116.0",
"version": "1.116.1",
"dev": true,
"license": "GNU Affero General Public License version 3",
"dependencies": {

View File

@@ -1,6 +1,6 @@
{
"name": "@immich/cli",
"version": "2.2.20",
"version": "2.2.21",
"description": "Command Line Interface (CLI) for Immich",
"type": "module",
"exports": "./dist/index.js",

View File

@@ -1,4 +1,8 @@
[
{
"label": "v1.116.1",
"url": "https://v1.116.1.archive.immich.app"
},
{
"label": "v1.116.0",
"url": "https://v1.116.0.archive.immich.app"

8
e2e/package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "immich-e2e",
"version": "1.116.0",
"version": "1.116.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "immich-e2e",
"version": "1.116.0",
"version": "1.116.1",
"license": "GNU Affero General Public License version 3",
"devDependencies": {
"@eslint/eslintrc": "^3.1.0",
@@ -45,7 +45,7 @@
},
"../cli": {
"name": "@immich/cli",
"version": "2.2.20",
"version": "2.2.21",
"dev": true,
"license": "GNU Affero General Public License version 3",
"dependencies": {
@@ -92,7 +92,7 @@
},
"../open-api/typescript-sdk": {
"name": "@immich/sdk",
"version": "1.116.0",
"version": "1.116.1",
"dev": true,
"license": "GNU Affero General Public License version 3",
"dependencies": {

View File

@@ -1,6 +1,6 @@
{
"name": "immich-e2e",
"version": "1.116.0",
"version": "1.116.1",
"description": "",
"main": "index.js",
"type": "module",

View File

@@ -1,6 +1,6 @@
[tool.poetry]
name = "machine-learning"
version = "1.116.0"
version = "1.116.1"
description = ""
authors = ["Hau Tran <alex.tran1502@gmail.com>"]
readme = "README.md"

View File

@@ -35,8 +35,8 @@ platform :android do
task: 'bundle',
build_type: 'Release',
properties: {
"android.injected.version.code" => 160,
"android.injected.version.name" => "1.116.0",
"android.injected.version.code" => 161,
"android.injected.version.name" => "1.116.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')

View File

@@ -19,7 +19,7 @@ platform :ios do
desc "iOS Release"
lane :release do
increment_version_number(
version_number: "1.116.0"
version_number: "1.116.1"
)
increment_build_number(
build_number: latest_testflight_build_number + 1,

View File

@@ -4,4 +4,7 @@ abstract interface class IAssetMediaRepository {
Future<List<String>> deleteAll(List<String> ids);
Future<Asset?> get(String id);
/// Obtaining the correct original filename of the asset
Future<String?> getOriginalFilename(String id);
}

View File

@@ -43,4 +43,17 @@ class AssetMediaRepository implements IAssetMediaRepository {
asset.local = local;
return asset;
}
@override
Future<String?> getOriginalFilename(String id) async {
final entity = await AssetEntity.fromId(id);
if (entity == null) {
return null;
}
// titleAsync gets the correct original filename for some assets on iOS
// otherwise using the `entity.title` would return a random GUID
return await entity.titleAsync;
}
}

View File

@@ -15,6 +15,7 @@ import 'package:immich_mobile/models/backup/success_upload_asset.model.dart';
import 'package:immich_mobile/repositories/album.repository.dart';
import 'package:immich_mobile/repositories/album_api.repository.dart';
import 'package:immich_mobile/repositories/asset.repository.dart';
import 'package:immich_mobile/repositories/asset_media.repository.dart';
import 'package:immich_mobile/repositories/backup.repository.dart';
import 'package:immich_mobile/repositories/album_media.repository.dart';
import 'package:immich_mobile/repositories/file_media.repository.dart';
@@ -368,6 +369,7 @@ class BackgroundService {
BackupRepository backupAlbumRepository = BackupRepository(db);
AlbumMediaRepository albumMediaRepository = AlbumMediaRepository();
FileMediaRepository fileMediaRepository = FileMediaRepository();
AssetMediaRepository assetMediaRepository = AssetMediaRepository();
UserRepository userRepository = UserRepository(db);
UserApiRepository userApiRepository =
UserApiRepository(apiService.usersApi);
@@ -409,6 +411,7 @@ class BackgroundService {
albumService,
albumMediaRepository,
fileMediaRepository,
assetMediaRepository,
);
final selectedAlbums = backupService.selectedAlbumsQuery().findAllSync();

View File

@@ -12,6 +12,7 @@ import 'package:immich_mobile/entities/backup_album.entity.dart';
import 'package:immich_mobile/entities/duplicated_asset.entity.dart';
import 'package:immich_mobile/entities/store.entity.dart';
import 'package:immich_mobile/interfaces/album_media.interface.dart';
import 'package:immich_mobile/interfaces/asset_media.interface.dart';
import 'package:immich_mobile/interfaces/file_media.interface.dart';
import 'package:immich_mobile/models/backup/backup_candidate.model.dart';
import 'package:immich_mobile/models/backup/current_upload_asset.model.dart';
@@ -21,6 +22,7 @@ import 'package:immich_mobile/providers/api.provider.dart';
import 'package:immich_mobile/providers/app_settings.provider.dart';
import 'package:immich_mobile/providers/db.provider.dart';
import 'package:immich_mobile/repositories/album_media.repository.dart';
import 'package:immich_mobile/repositories/asset_media.repository.dart';
import 'package:immich_mobile/repositories/file_media.repository.dart';
import 'package:immich_mobile/services/album.service.dart';
import 'package:immich_mobile/services/api.service.dart';
@@ -40,6 +42,7 @@ final backupServiceProvider = Provider(
ref.watch(albumServiceProvider),
ref.watch(albumMediaRepositoryProvider),
ref.watch(fileMediaRepositoryProvider),
ref.watch(assetMediaRepositoryProvider),
),
);
@@ -52,6 +55,7 @@ class BackupService {
final AlbumService _albumService;
final IAlbumMediaRepository _albumMediaRepository;
final IFileMediaRepository _fileMediaRepository;
final IAssetMediaRepository _assetMediaRepository;
BackupService(
this._apiService,
@@ -60,6 +64,7 @@ class BackupService {
this._albumService,
this._albumMediaRepository,
this._fileMediaRepository,
this._assetMediaRepository,
);
Future<List<String>?> getDeviceBackupAsset() async {
@@ -329,7 +334,9 @@ class BackupService {
}
if (file != null) {
String originalFileName = asset.fileName;
String? originalFileName =
await _assetMediaRepository.getOriginalFilename(asset.localId!);
originalFileName ??= asset.fileName;
if (asset.local!.isLivePhoto) {
if (livePhotoFile == null) {

View File

@@ -3,7 +3,7 @@ Immich API
This Dart package is automatically generated by the [OpenAPI Generator](https://openapi-generator.tech) project:
- API version: 1.116.0
- API version: 1.116.1
- Generator version: 7.8.0
- Build package: org.openapitools.codegen.languages.DartClientCodegen

View File

@@ -2,7 +2,7 @@ name: immich_mobile
description: Immich - selfhosted backup media file on mobile phone
publish_to: 'none'
version: 1.116.0+160
version: 1.116.1+161
environment:
sdk: '>=3.3.0 <4.0.0'

View File

@@ -7409,7 +7409,7 @@
"info": {
"title": "Immich",
"description": "Immich API",
"version": "1.116.0",
"version": "1.116.1",
"contact": {}
},
"tags": [],

View File

@@ -1,12 +1,12 @@
{
"name": "@immich/sdk",
"version": "1.116.0",
"version": "1.116.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@immich/sdk",
"version": "1.116.0",
"version": "1.116.1",
"license": "GNU Affero General Public License version 3",
"dependencies": {
"@oazapfts/runtime": "^1.0.2"

View File

@@ -1,6 +1,6 @@
{
"name": "@immich/sdk",
"version": "1.116.0",
"version": "1.116.1",
"description": "Auto-generated TypeScript SDK for the Immich API",
"type": "module",
"main": "./build/index.js",

View File

@@ -1,6 +1,6 @@
/**
* Immich
* 1.116.0
* 1.116.1
* DO NOT MODIFY - This file has been generated using oazapfts.
* See https://www.npmjs.com/package/oazapfts
*/

View File

@@ -1,84 +1,90 @@
<p align="center">
<br/>
<p align="center">
<br/>
<a href="https://opensource.org/license/agpl-v3"><img src="https://img.shields.io/badge/License-AGPL_v3-blue.svg?color=3F51B5&style=for-the-badge&label=License&logoColor=000000&labelColor=ececec" alt="Licença: AGPLv3"></a>
<a href="https://discord.immich.app">
<img src="https://img.shields.io/discord/979116623879368755.svg?label=Discord&logo=Discord&style=for-the-badge&logoColor=000000&labelColor=ececec" alt="Discord"/>
</a>
<br/>
<br/>
<br/>
<br/>
</p>
<p align="center">
<img src="design/immich-logo.svg" width="150" title="Login com URL customizada">
<img src="../design/immich-logo-stacked-light.svg" width="150" title="Immich Logo">
</p>
<h3 align="center">Immich - Solução self-hosted de alta performance para backup de fotos e vídeos</h3>
<h3 align="center">Solução self-hosted de alta performance para backup de fotos e vídeos</h3>
<br/>
<a href="https://immich.app">
<img src="design/immich-screenshots.png" title="Captura de tela princial">
<img src="../design/immich-screenshots.png" title="Captura de tela princial">
</a>
<br/>
<p align="center">
<a href="../README.md">English</a>
<a href="README_ca_ES.md">Català</a>
<a href="README_es_ES.md">Español</a>
<a href="README_fr_FR.md">Français</a>
<a href="README_it_IT.md">Italiano</a>
<a href="README_ja_JP.md">日本語</a>
<a href="README_ko_KR.md">한국어</a>
<a href="README_de_DE.md">Deutsch</a>
<a href="README_nl_NL.md">Nederlands</a>
<a href="README_tr_TR.md">Türkçe</a>
<a href="README_zh_CN.md">中文</a>
<a href="README_ru_RU.md">Русский</a>
<a href="README_sv_SE.md">Svenska</a>
<a href="README_ar_JO.md">العربية</a>
<a href="../README.md">English</a>
<a href="README_ca_ES.md">Català</a>
<a href="README_es_ES.md">Español</a>
<a href="README_fr_FR.md">Français</a>
<a href="README_it_IT.md">Italiano</a>
<a href="README_ja_JP.md">日本語</a>
<a href="README_ko_KR.md">한국어</a>
<a href="README_de_DE.md">Deutsch</a>
<a href="README_nl_NL.md">Nederlands</a>
<a href="README_tr_TR.md">Türkçe</a>
<a href="README_zh_CN.md">中文</a>
<a href="README_ru_RU.md">Русский</a>
<a href="README_sv_SE.md">Svenska</a>
<a href="README_ar_JO.md">العربية</a>
<a href="README_vi_VN.md">Tiếng Việt</a>
</p>
## Avisos
- ⚠️ Este projeto está sob **desenvolvimento constante**.
- ⚠️ Podem ocorrer bugs e _breaking changes_ (alterações que quebram a compatibilidade com versões anteriores).
- ⚠️ **Não use esta solução como a única forma de fazer backup das suas fotos e vídeos.**
- ⚠️ Sempre siga o plano [3-2-1](https://www.backblaze.com/blog/the-3-2-1-backup-strategy/) de backup para as suas mídias preciosas!
- ⚠️ Podem ocorrer bugs e _breaking changes_ (alterações que quebram a
compatibilidade com versões anteriores).
- ⚠️ **Não use esta solução como a única forma de fazer backup das suas fotos e
vídeos.**
- ⚠️ Sempre siga o plano
[3-2-1](https://www.backblaze.com/blog/the-3-2-1-backup-strategy/) de backup
para as suas mídias preciosas!
## Conteúdo
> [!NOTE]
> Você pode encontrar a documentação principal, incluindo guias de instalação, em https://immich.app/.
- [Documentação Oficial](https://immich.app/docs)
- [Roadmap](https://github.com/orgs/immich-app/projects/1)
- [Demonstração](#demo)
- [Recursos](#features)
- [Introdução](https://immich.app/docs/overview/introduction)
## Links
- [Documentação](https://immich.app/docs)
- [Sobre](https://immich.app/docs/overview/introduction)
- [Instalação](https://immich.app/docs/install/requirements)
- [Roadmap](https://github.com/orgs/immich-app/projects/1)
- [Demonstração](#demonstração)
- [Funcionalidades](#funcionalidades)
- [Traduções](https://immich.app/docs/developer/translations)
- [Diretrizes de Contribuição](https://immich.app/docs/overview/support-the-project)
## Documentação
Você pode encontrar a documentação principal, incluindo guias de instalação, em https://immich.app/.
## Demonstração
Você pode acessar a demonstração web em https://demo.immich.app
Acesse a demonstração [aqui](https://demo.immich.app). A demonstração está
hospedada no Nível Gratuito da Oracle VM em Amsterdam com um processador 2.4Ghz
quad-core ARM64 e 24GB de RAM.
No aplicativo para dispositivos móveis, você pode usar `https://demo.immich.app/api` no campo `Server Endpoint URL`
No aplicativo para dispositivos móveis, você pode usar
`https://demo.immich.app/api` no campo `Server Endpoint URL`
```bash title="Credenciais de Demonstração"
Credenciais de Demonstração
email: demo@immich.app
senha: demo
```
### Credenciais de login
```
Especificações: Nível Gratuito da Oracle VM - Amsterdam - 2.4Ghz quad-core ARM64 CPU, 24GB RAM
```
| Email | Senha |
| --------------- | ----- |
| demo@immich.app | demo |
## Atividades
![Atividades](https://repobeats.axiom.co/api/embed/9e86d9dc3ddd137161f2f6d2e758d7863b1789cb.svg "Imagem de Analytics do Repobeats")
## Recursos
## Funcionalidades
| Recursos | Aplicativo Móvel | Web |
|:----------------------------------------------------|------------------|-----|
| Funcionalidades | Aplicativo Móvel | Web |
| :-------------------------------------------------- | ---------------- | --- |
| Fazer upload e visualizar fotos e vídeos | Sim | Sim |
| Backup automático ao abrir o aplicativo | Sim | N/A |
| Prevenir a duplicação de arquivos | Sim | Sim |
@@ -88,17 +94,17 @@ Especificações: Nível Gratuito da Oracle VM - Amsterdam - 2.4Ghz quad-core AR
| Criação de álbuns e álbuns compartilhados | Sim | Sim |
| Barra de rolagem arrastável | Sim | Sim |
| Suporta formatos RAW | Sim | Sim |
| Visualização de metadados (EXIF, map) | Sim | Sim |
| Pesquisar por metadados, objetos, rostos, and CLIP | Sim | Sim |
| Visualização de metadados (EXIF, mapa) | Sim | Sim |
| Pesquisar por metadados, objetos, rostos, e CLIP | Sim | Sim |
| Funções administrativas (gerenciamento de usuários) | Não | Sim |
| Backup em segundo plano | Sim | N/A |
| Virtual scroll | Sim | Sim |
| Rolagem virtual | Sim | Sim |
| Suporte OAuth | Sim | Sim |
| Chaves de API | N/A | Sim |
| Backup e visualização de LivePhoto/MotionPhoto | Sim | Sim |
| Backup e reprodução de LivePhoto/MotionPhoto | Sim | Sim |
| Visualização de imagens 360º | Não | Sim |
| Estrutura de armazenamento definida pelo usuário | Sim | Sim |
| Compartilhar com o público | Não | Sim |
| Compartilhar com o público | Sim | Sim |
| Arquivo e Favoritos | Sim | Sim |
| Mapa Global | Sim | Sim |
| Compartilhamento com parceiro | Sim | Sim |
@@ -108,6 +114,29 @@ Especificações: Nível Gratuito da Oracle VM - Amsterdam - 2.4Ghz quad-core AR
| Galeria em modo apenas leitura | Sim | Sim |
| Empilhamento de fotos | Sim | Sim |
## Traduções
Leia mais sobre as traduções
[aqui](https://immich.app/docs/developer/translations).
<a href="https://hosted.weblate.org/engage/immich/">
<img src="https://hosted.weblate.org/widget/immich/immich/multi-auto.svg" alt="Status da tradução" />
</a>
## Atividade do repositório
![Activities](https://repobeats.axiom.co/api/embed/9e86d9dc3ddd137161f2f6d2e758d7863b1789cb.svg "Imagem de análise de atividade Repobeats")
## Histórico de estrelas
<a href="https://star-history.com/#immich-app/immich&Date">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=immich-app/immich&type=Date&theme=dark" />
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=immich-app/immich&type=Date" />
<img alt="Gráfico de histórico de estrelas" src="https://api.star-history.com/svg?repos=immich-app/immich&type=Date" width="100%" />
</picture>
</a>
## Contribuidores
<a href="https://github.com/alextran1502/immich/graphs/contributors">

View File

@@ -1,12 +1,12 @@
{
"name": "immich",
"version": "1.116.0",
"version": "1.116.1",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "immich",
"version": "1.116.0",
"version": "1.116.1",
"license": "GNU Affero General Public License version 3",
"dependencies": {
"@nestjs/bullmq": "^10.0.1",

View File

@@ -1,6 +1,6 @@
{
"name": "immich",
"version": "1.116.0",
"version": "1.116.1",
"description": "",
"author": "",
"private": true,

View File

@@ -7,83 +7,20 @@ import { RedisOptions } from 'ioredis';
import Joi, { Root } from 'joi';
import { CLS_ID, ClsModuleOptions } from 'nestjs-cls';
import { ImmichHeader } from 'src/dtos/auth.dto';
import {
AudioCodec,
Colorspace,
CQMode,
ImageFormat,
LogLevel,
ToneMapping,
TranscodeHWAccel,
TranscodePolicy,
VideoCodec,
VideoContainer,
} from 'src/enum';
import { ConcurrentQueueName, QueueName } from 'src/interfaces/job.interface';
export enum TranscodePolicy {
ALL = 'all',
OPTIMAL = 'optimal',
BITRATE = 'bitrate',
REQUIRED = 'required',
DISABLED = 'disabled',
}
export enum TranscodeTarget {
NONE,
AUDIO,
VIDEO,
ALL,
}
export enum VideoCodec {
H264 = 'h264',
HEVC = 'hevc',
VP9 = 'vp9',
AV1 = 'av1',
}
export enum AudioCodec {
MP3 = 'mp3',
AAC = 'aac',
LIBOPUS = 'libopus',
}
export enum VideoContainer {
MOV = 'mov',
MP4 = 'mp4',
OGG = 'ogg',
WEBM = 'webm',
}
export enum TranscodeHWAccel {
NVENC = 'nvenc',
QSV = 'qsv',
VAAPI = 'vaapi',
RKMPP = 'rkmpp',
DISABLED = 'disabled',
}
export enum ToneMapping {
HABLE = 'hable',
MOBIUS = 'mobius',
REINHARD = 'reinhard',
DISABLED = 'disabled',
}
export enum CQMode {
AUTO = 'auto',
CQP = 'cqp',
ICQ = 'icq',
}
export enum Colorspace {
SRGB = 'srgb',
P3 = 'p3',
}
export enum ImageFormat {
JPEG = 'jpeg',
WEBP = 'webp',
}
export enum LogLevel {
VERBOSE = 'verbose',
DEBUG = 'debug',
LOG = 'log',
WARN = 'warn',
ERROR = 'error',
FATAL = 'fatal',
}
export interface SystemConfig {
ffmpeg: {
crf: number;

View File

@@ -54,11 +54,6 @@ export const resourcePaths = {
export const MOBILE_REDIRECT = 'app.immich:///oauth-callback';
export const LOGIN_URL = '/auth/login?autoLaunch=0';
export enum AuthType {
PASSWORD = 'password',
OAUTH = 'oauth',
}
export const excludePaths = ['/.well-known/immich', '/custom.css', '/favicon.ico'];
export const FACE_THUMBNAIL_SIZE = 250;

View File

@@ -33,16 +33,17 @@ import {
UploadFieldName,
} from 'src/dtos/asset-media.dto';
import { AuthDto, ImmichHeader } from 'src/dtos/auth.dto';
import { RouteKey } from 'src/enum';
import { ILoggerRepository } from 'src/interfaces/logger.interface';
import { AssetUploadInterceptor } from 'src/middleware/asset-upload.interceptor';
import { Auth, Authenticated, FileResponse } from 'src/middleware/auth.guard';
import { FileUploadInterceptor, Route, UploadFiles, getFiles } from 'src/middleware/file-upload.interceptor';
import { FileUploadInterceptor, UploadFiles, getFiles } from 'src/middleware/file-upload.interceptor';
import { AssetMediaService } from 'src/services/asset-media.service';
import { sendFile } from 'src/utils/file';
import { FileNotEmptyValidator, UUIDParamDto } from 'src/validation';
@ApiTags('Assets')
@Controller(Route.ASSET)
@Controller(RouteKey.ASSET)
export class AssetMediaController {
constructor(
@Inject(ILoggerRepository) private logger: ILoggerRepository,

View File

@@ -14,13 +14,13 @@ import {
} from 'src/dtos/asset.dto';
import { AuthDto } from 'src/dtos/auth.dto';
import { MemoryLaneDto } from 'src/dtos/search.dto';
import { RouteKey } from 'src/enum';
import { Auth, Authenticated } from 'src/middleware/auth.guard';
import { Route } from 'src/middleware/file-upload.interceptor';
import { AssetService } from 'src/services/asset.service';
import { UUIDParamDto } from 'src/validation';
@ApiTags('Assets')
@Controller(Route.ASSET)
@Controller(RouteKey.ASSET)
export class AssetController {
constructor(private service: AssetService) {}

View File

@@ -1,7 +1,6 @@
import { Body, Controller, HttpCode, HttpStatus, Post, Req, Res } from '@nestjs/common';
import { ApiTags } from '@nestjs/swagger';
import { Request, Response } from 'express';
import { AuthType } from 'src/constants';
import {
AuthDto,
ChangePasswordDto,
@@ -13,6 +12,7 @@ import {
ValidateAccessTokenResponseDto,
} from 'src/dtos/auth.dto';
import { UserAdminResponseDto } from 'src/dtos/user.dto';
import { AuthType } from 'src/enum';
import { Auth, Authenticated, GetLoginDetails } from 'src/middleware/auth.guard';
import { AuthService, LoginDetails } from 'src/services/auth.service';
import { respondWithCookie, respondWithoutCookie } from 'src/utils/response';

View File

@@ -1,7 +1,6 @@
import { Body, Controller, Get, HttpCode, HttpStatus, Post, Redirect, Req, Res } from '@nestjs/common';
import { ApiTags } from '@nestjs/swagger';
import { Request, Response } from 'express';
import { AuthType } from 'src/constants';
import {
AuthDto,
ImmichCookie,
@@ -11,6 +10,7 @@ import {
OAuthConfigDto,
} from 'src/dtos/auth.dto';
import { UserAdminResponseDto } from 'src/dtos/user.dto';
import { AuthType } from 'src/enum';
import { Auth, Authenticated, GetLoginDetails } from 'src/middleware/auth.guard';
import { AuthService, LoginDetails } from 'src/services/auth.service';
import { respondWithCookie } from 'src/utils/response';

View File

@@ -21,15 +21,16 @@ import { LicenseKeyDto, LicenseResponseDto } from 'src/dtos/license.dto';
import { UserPreferencesResponseDto, UserPreferencesUpdateDto } from 'src/dtos/user-preferences.dto';
import { CreateProfileImageDto, CreateProfileImageResponseDto } from 'src/dtos/user-profile.dto';
import { UserAdminResponseDto, UserResponseDto, UserUpdateMeDto } from 'src/dtos/user.dto';
import { RouteKey } from 'src/enum';
import { ILoggerRepository } from 'src/interfaces/logger.interface';
import { Auth, Authenticated, FileResponse } from 'src/middleware/auth.guard';
import { FileUploadInterceptor, Route } from 'src/middleware/file-upload.interceptor';
import { FileUploadInterceptor } from 'src/middleware/file-upload.interceptor';
import { UserService } from 'src/services/user.service';
import { sendFile } from 'src/utils/file';
import { UUIDParamDto } from 'src/validation';
@ApiTags('Users')
@Controller(Route.USER)
@Controller(RouteKey.USER)
export class UserController {
constructor(
private service: UserService,

View File

@@ -1,12 +1,10 @@
import { randomUUID } from 'node:crypto';
import { dirname, join, resolve } from 'node:path';
import { ImageFormat } from 'src/config';
import { APP_MEDIA_LOCATION } from 'src/constants';
import { SystemConfigCore } from 'src/cores/system-config.core';
import { AssetEntity } from 'src/entities/asset.entity';
import { AssetPathType, PathType, PersonPathType } from 'src/entities/move.entity';
import { PersonEntity } from 'src/entities/person.entity';
import { AssetFileType } from 'src/enum';
import { AssetFileType, AssetPathType, ImageFormat, PathType, PersonPathType, StorageFolder } from 'src/enum';
import { IAssetRepository } from 'src/interfaces/asset.interface';
import { ICryptoRepository } from 'src/interfaces/crypto.interface';
import { ILoggerRepository } from 'src/interfaces/logger.interface';
@@ -16,14 +14,6 @@ import { IStorageRepository } from 'src/interfaces/storage.interface';
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
import { getAssetFiles } from 'src/utils/asset.util';
export enum StorageFolder {
ENCODED_VIDEO = 'encoded-video',
LIBRARY = 'library',
UPLOAD = 'upload',
PROFILE = 'profile',
THUMBNAILS = 'thumbs',
}
export const THUMBNAIL_DIR = resolve(join(APP_MEDIA_LOCATION, StorageFolder.THUMBNAILS));
export const ENCODED_VIDEO_DIR = resolve(join(APP_MEDIA_LOCATION, StorageFolder.ENCODED_VIDEO));

View File

@@ -4,8 +4,8 @@ import { OnEventOptions } from '@nestjs/event-emitter/dist/interfaces';
import { ApiExtension, ApiOperation, ApiProperty, ApiTags } from '@nestjs/swagger';
import _ from 'lodash';
import { ADDED_IN_PREFIX, DEPRECATED_IN_PREFIX, LIFECYCLE_EXTENSION } from 'src/constants';
import { MetadataKey } from 'src/enum';
import { EmitEvent, ServerEvent } from 'src/interfaces/event.interface';
import { Metadata } from 'src/middleware/auth.guard';
import { setUnion } from 'src/utils/set';
// PostgreSQL uses a 16-bit integer to indicate the number of bound parameters. This means that the
@@ -141,7 +141,7 @@ export type EmitConfig = {
/** lower value has higher priority, defaults to 0 */
priority?: number;
};
export const OnEmit = (config: EmitConfig) => SetMetadata(Metadata.ON_EMIT_CONFIG, config);
export const OnEmit = (config: EmitConfig) => SetMetadata(MetadataKey.ON_EMIT_CONFIG, config);
type LifecycleRelease = 'NEXT_RELEASE' | string;
type LifecycleMetadata = {

View File

@@ -1,8 +1,7 @@
import { ApiProperty } from '@nestjs/swagger';
import { Type } from 'class-transformer';
import { IsArray, IsEnum, IsString, IsUUID, ValidateNested } from 'class-validator';
import { AssetPathType, PathType, PersonPathType, UserPathType } from 'src/entities/move.entity';
import { EntityType } from 'src/enum';
import { AssetPathType, EntityType, PathType, PersonPathType, UserPathType } from 'src/enum';
import { Optional, ValidateDate, ValidateUUID } from 'src/validation';
const PathEnum = Object.values({ ...AssetPathType, ...PersonPathType, ...UserPathType });

View File

@@ -18,20 +18,20 @@ import {
ValidatorConstraint,
ValidatorConstraintInterface,
} from 'class-validator';
import { SystemConfig } from 'src/config';
import { CLIPConfig, DuplicateDetectionConfig, FacialRecognitionConfig } from 'src/dtos/model-config.dto';
import {
AudioCodec,
CQMode,
Colorspace,
ImageFormat,
LogLevel,
SystemConfig,
ToneMapping,
TranscodeHWAccel,
TranscodePolicy,
VideoCodec,
VideoContainer,
} from 'src/config';
import { CLIPConfig, DuplicateDetectionConfig, FacialRecognitionConfig } from 'src/dtos/model-config.dto';
} from 'src/enum';
import { ConcurrentQueueName, QueueName } from 'src/interfaces/job.interface';
import { ValidateBoolean, validateCronExpression } from 'src/validation';

View File

@@ -1,3 +1,4 @@
import { PathType } from 'src/enum';
import { Column, Entity, PrimaryGeneratedColumn, Unique } from 'typeorm';
@Entity('move_history')
@@ -21,21 +22,3 @@ export class MoveEntity {
@Column({ type: 'varchar' })
newPath!: string;
}
export enum AssetPathType {
ORIGINAL = 'original',
PREVIEW = 'preview',
THUMBNAIL = 'thumbnail',
ENCODED_VIDEO = 'encoded_video',
SIDECAR = 'sidecar',
}
export enum PersonPathType {
FACE = 'face',
}
export enum UserPathType {
PROFILE = 'profile',
}
export type PathType = AssetPathType | PersonPathType | UserPathType;

View File

@@ -1,3 +1,8 @@
export enum AuthType {
PASSWORD = 'password',
OAUTH = 'oauth',
}
export enum AssetType {
IMAGE = 'IMAGE',
VIDEO = 'VIDEO',
@@ -148,6 +153,14 @@ export enum SharedLinkType {
INDIVIDUAL = 'INDIVIDUAL',
}
export enum StorageFolder {
ENCODED_VIDEO = 'encoded-video',
LIBRARY = 'library',
UPLOAD = 'upload',
PROFILE = 'profile',
THUMBNAILS = 'thumbs',
}
export enum SystemMetadataKey {
REVERSE_GEOCODING_STATE = 'reverse-geocoding-state',
FACIAL_RECOGNITION_STATE = 'facial-recognition-state',
@@ -198,3 +211,120 @@ export enum ManualJobName {
TAG_CLEANUP = 'tag-cleanup',
USER_CLEANUP = 'user-cleanup',
}
export enum AssetPathType {
ORIGINAL = 'original',
PREVIEW = 'preview',
THUMBNAIL = 'thumbnail',
ENCODED_VIDEO = 'encoded_video',
SIDECAR = 'sidecar',
}
export enum PersonPathType {
FACE = 'face',
}
export enum UserPathType {
PROFILE = 'profile',
}
export type PathType = AssetPathType | PersonPathType | UserPathType;
export enum TranscodePolicy {
ALL = 'all',
OPTIMAL = 'optimal',
BITRATE = 'bitrate',
REQUIRED = 'required',
DISABLED = 'disabled',
}
export enum TranscodeTarget {
NONE,
AUDIO,
VIDEO,
ALL,
}
export enum VideoCodec {
H264 = 'h264',
HEVC = 'hevc',
VP9 = 'vp9',
AV1 = 'av1',
}
export enum AudioCodec {
MP3 = 'mp3',
AAC = 'aac',
LIBOPUS = 'libopus',
}
export enum VideoContainer {
MOV = 'mov',
MP4 = 'mp4',
OGG = 'ogg',
WEBM = 'webm',
}
export enum TranscodeHWAccel {
NVENC = 'nvenc',
QSV = 'qsv',
VAAPI = 'vaapi',
RKMPP = 'rkmpp',
DISABLED = 'disabled',
}
export enum ToneMapping {
HABLE = 'hable',
MOBIUS = 'mobius',
REINHARD = 'reinhard',
DISABLED = 'disabled',
}
export enum CQMode {
AUTO = 'auto',
CQP = 'cqp',
ICQ = 'icq',
}
export enum Colorspace {
SRGB = 'srgb',
P3 = 'p3',
}
export enum ImageFormat {
JPEG = 'jpeg',
WEBP = 'webp',
}
export enum LogLevel {
VERBOSE = 'verbose',
DEBUG = 'debug',
LOG = 'log',
WARN = 'warn',
ERROR = 'error',
FATAL = 'fatal',
}
export enum MetadataKey {
AUTH_ROUTE = 'auth_route',
ADMIN_ROUTE = 'admin_route',
SHARED_ROUTE = 'shared_route',
API_KEY_SECURITY = 'api_key',
ON_EMIT_CONFIG = 'on_emit_config',
}
export enum RouteKey {
ASSET = 'assets',
USER = 'users',
}
export enum CacheControl {
PRIVATE_WITH_CACHE = 'private_with_cache',
PRIVATE_WITHOUT_CACHE = 'private_without_cache',
NONE = 'none',
}
export enum PaginationMode {
LIMIT_OFFSET = 'limit-offset',
SKIP_TAKE = 'skip-take',
}

View File

@@ -0,0 +1,14 @@
import { VectorExtension } from 'src/interfaces/database.interface';
export const IConfigRepository = 'IConfigRepository';
export interface EnvData {
database: {
skipMigrations: boolean;
vectorExtension: VectorExtension;
};
}
export interface IConfigRepository {
getEnv(): EnvData;
}

View File

@@ -1,4 +1,4 @@
import { LogLevel } from 'src/config';
import { LogLevel } from 'src/enum';
export const ILoggerRepository = 'ILoggerRepository';

View File

@@ -1,5 +1,5 @@
import { Writable } from 'node:stream';
import { ImageFormat, TranscodeTarget, VideoCodec } from 'src/config';
import { ImageFormat, TranscodeTarget, VideoCodec } from 'src/enum';
export const IMediaRepository = 'IMediaRepository';

View File

@@ -1,4 +1,5 @@
import { MoveEntity, PathType } from 'src/entities/move.entity';
import { MoveEntity } from 'src/entities/move.entity';
import { PathType } from 'src/enum';
export const IMoveRepository = 'IMoveRepository';

View File

@@ -2,7 +2,7 @@ import { CommandFactory } from 'nest-commander';
import { fork } from 'node:child_process';
import { Worker } from 'node:worker_threads';
import { ImmichAdminModule } from 'src/app.module';
import { LogLevel } from 'src/config';
import { LogLevel } from 'src/enum';
import { getWorkers } from 'src/utils/workers';
const immichApp = process.argv[2] || process.env.IMMICH_APP;

View File

@@ -11,19 +11,11 @@ import { Reflector } from '@nestjs/core';
import { ApiBearerAuth, ApiCookieAuth, ApiOkResponse, ApiQuery, ApiSecurity } from '@nestjs/swagger';
import { Request } from 'express';
import { AuthDto, ImmichQuery } from 'src/dtos/auth.dto';
import { Permission } from 'src/enum';
import { MetadataKey, Permission } from 'src/enum';
import { ILoggerRepository } from 'src/interfaces/logger.interface';
import { AuthService, LoginDetails } from 'src/services/auth.service';
import { UAParser } from 'ua-parser-js';
export enum Metadata {
AUTH_ROUTE = 'auth_route',
ADMIN_ROUTE = 'admin_route',
SHARED_ROUTE = 'shared_route',
API_KEY_SECURITY = 'api_key',
ON_EMIT_CONFIG = 'on_emit_config',
}
type AdminRoute = { admin?: true };
type SharedLinkRoute = { sharedLink?: true };
type AuthenticatedOptions = { permission?: Permission } & (AdminRoute | SharedLinkRoute);
@@ -32,8 +24,8 @@ export const Authenticated = (options?: AuthenticatedOptions): MethodDecorator =
const decorators: MethodDecorator[] = [
ApiBearerAuth(),
ApiCookieAuth(),
ApiSecurity(Metadata.API_KEY_SECURITY),
SetMetadata(Metadata.AUTH_ROUTE, options || {}),
ApiSecurity(MetadataKey.API_KEY_SECURITY),
SetMetadata(MetadataKey.AUTH_ROUTE, options || {}),
];
if ((options as SharedLinkRoute)?.sharedLink) {
@@ -85,7 +77,7 @@ export class AuthGuard implements CanActivate {
async canActivate(context: ExecutionContext): Promise<boolean> {
const targets = [context.getHandler()];
const options = this.reflector.getAllAndOverride<AuthenticatedOptions | undefined>(Metadata.AUTH_ROUTE, targets);
const options = this.reflector.getAllAndOverride<AuthenticatedOptions | undefined>(MetadataKey.AUTH_ROUTE, targets);
if (!options) {
return true;
}

View File

@@ -7,6 +7,7 @@ import multer, { StorageEngine, diskStorage } from 'multer';
import { createHash, randomUUID } from 'node:crypto';
import { Observable } from 'rxjs';
import { UploadFieldName } from 'src/dtos/asset-media.dto';
import { RouteKey } from 'src/enum';
import { ILoggerRepository } from 'src/interfaces/logger.interface';
import { AuthRequest } from 'src/middleware/auth.guard';
import { AssetMediaService, UploadFile } from 'src/services/asset-media.service';
@@ -28,11 +29,6 @@ export function getFiles(files: UploadFiles) {
};
}
export enum Route {
ASSET = 'assets',
USER = 'users',
}
export interface ImmichFile extends Express.Multer.File {
/** sha1 hash of file */
uuid: string;
@@ -115,7 +111,7 @@ export class FileUploadInterceptor implements NestInterceptor {
const context_ = context.switchToHttp();
const route = this.reflect.get<string>(PATH_METADATA, context.getClass());
const handler: RequestHandler | null = this.getHandler(route as Route);
const handler: RequestHandler | null = this.getHandler(route as RouteKey);
if (handler) {
await new Promise<void>((resolve, reject) => {
const next: NextFunction = (error) => (error ? reject(transformException(error)) : resolve());
@@ -176,13 +172,13 @@ export class FileUploadInterceptor implements NestInterceptor {
return false;
}
private getHandler(route: Route) {
private getHandler(route: RouteKey) {
switch (route) {
case Route.ASSET: {
case RouteKey.ASSET: {
return this.handlers.assetUpload;
}
case Route.USER: {
case RouteKey.USER: {
return this.handlers.userProfile;
}

View File

@@ -6,7 +6,7 @@ import { AssetJobStatusEntity } from 'src/entities/asset-job-status.entity';
import { AssetEntity } from 'src/entities/asset.entity';
import { ExifEntity } from 'src/entities/exif.entity';
import { SmartInfoEntity } from 'src/entities/smart-info.entity';
import { AssetFileType, AssetOrder, AssetStatus, AssetType } from 'src/enum';
import { AssetFileType, AssetOrder, AssetStatus, AssetType, PaginationMode } from 'src/enum';
import {
AssetBuilderOptions,
AssetCreate,
@@ -30,7 +30,7 @@ import {
import { AssetSearchOptions, SearchExploreItem } from 'src/interfaces/search.interface';
import { searchAssetBuilder } from 'src/utils/database';
import { Instrumentation } from 'src/utils/instrumentation';
import { Paginated, PaginationMode, PaginationOptions, paginate, paginatedBuilder } from 'src/utils/pagination';
import { Paginated, PaginationOptions, paginate, paginatedBuilder } from 'src/utils/pagination';
import {
Brackets,
FindOptionsOrder,

View File

@@ -0,0 +1,15 @@
import { Injectable } from '@nestjs/common';
import { getVectorExtension } from 'src/database.config';
import { EnvData, IConfigRepository } from 'src/interfaces/config.interface';
@Injectable()
export class ConfigRepository implements IConfigRepository {
getEnv(): EnvData {
return {
database: {
skipMigrations: process.env.DB_SKIP_MIGRATIONS === 'true',
vectorExtension: getVectorExtension(),
},
};
}
}

View File

@@ -5,6 +5,7 @@ import { IAlbumRepository } from 'src/interfaces/album.interface';
import { IKeyRepository } from 'src/interfaces/api-key.interface';
import { IAssetRepository } from 'src/interfaces/asset.interface';
import { IAuditRepository } from 'src/interfaces/audit.interface';
import { IConfigRepository } from 'src/interfaces/config.interface';
import { ICryptoRepository } from 'src/interfaces/crypto.interface';
import { IDatabaseRepository } from 'src/interfaces/database.interface';
import { IEventRepository } from 'src/interfaces/event.interface';
@@ -39,6 +40,7 @@ import { AlbumRepository } from 'src/repositories/album.repository';
import { ApiKeyRepository } from 'src/repositories/api-key.repository';
import { AssetRepository } from 'src/repositories/asset.repository';
import { AuditRepository } from 'src/repositories/audit.repository';
import { ConfigRepository } from 'src/repositories/config.repository';
import { CryptoRepository } from 'src/repositories/crypto.repository';
import { DatabaseRepository } from 'src/repositories/database.repository';
import { EventRepository } from 'src/repositories/event.repository';
@@ -74,6 +76,7 @@ export const repositories = [
{ provide: IAlbumUserRepository, useClass: AlbumUserRepository },
{ provide: IAssetRepository, useClass: AssetRepository },
{ provide: IAuditRepository, useClass: AuditRepository },
{ provide: IConfigRepository, useClass: ConfigRepository },
{ provide: ICryptoRepository, useClass: CryptoRepository },
{ provide: IDatabaseRepository, useClass: DatabaseRepository },
{ provide: IEventRepository, useClass: EventRepository },

View File

@@ -1,7 +1,7 @@
import { ConsoleLogger, Injectable, Scope } from '@nestjs/common';
import { isLogLevelEnabled } from '@nestjs/common/services/utils/is-log-level-enabled.util';
import { ClsService } from 'nestjs-cls';
import { LogLevel } from 'src/config';
import { LogLevel } from 'src/enum';
import { ILoggerRepository } from 'src/interfaces/logger.interface';
import { LogColor } from 'src/utils/logger';

View File

@@ -5,7 +5,7 @@ import fs from 'node:fs/promises';
import { Writable } from 'node:stream';
import { promisify } from 'node:util';
import sharp from 'sharp';
import { Colorspace } from 'src/config';
import { Colorspace } from 'src/enum';
import { ILoggerRepository } from 'src/interfaces/logger.interface';
import {
IMediaRepository,

View File

@@ -1,7 +1,8 @@
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { DummyValue, GenerateSql } from 'src/decorators';
import { MoveEntity, PathType } from 'src/entities/move.entity';
import { MoveEntity } from 'src/entities/move.entity';
import { PathType } from 'src/enum';
import { IMoveRepository, MoveCreate } from 'src/interfaces/move.interface';
import { Instrumentation } from 'src/utils/instrumentation';
import { Repository } from 'typeorm';

View File

@@ -6,7 +6,7 @@ import { AssetFaceEntity } from 'src/entities/asset-face.entity';
import { AssetJobStatusEntity } from 'src/entities/asset-job-status.entity';
import { AssetEntity } from 'src/entities/asset.entity';
import { PersonEntity } from 'src/entities/person.entity';
import { SourceType } from 'src/enum';
import { PaginationMode, SourceType } from 'src/enum';
import {
AssetFaceId,
DeleteAllFacesOptions,
@@ -19,7 +19,7 @@ import {
UpdateFacesData,
} from 'src/interfaces/person.interface';
import { Instrumentation } from 'src/utils/instrumentation';
import { Paginated, PaginationMode, PaginationOptions, paginate, paginatedBuilder } from 'src/utils/pagination';
import { Paginated, PaginationOptions, paginate, paginatedBuilder } from 'src/utils/pagination';
import { DataSource, FindManyOptions, FindOptionsRelations, FindOptionsSelect, In, Repository } from 'typeorm';
@Instrumentation()

View File

@@ -8,7 +8,7 @@ import { ExifEntity } from 'src/entities/exif.entity';
import { GeodataPlacesEntity } from 'src/entities/geodata-places.entity';
import { SmartInfoEntity } from 'src/entities/smart-info.entity';
import { SmartSearchEntity } from 'src/entities/smart-search.entity';
import { AssetType } from 'src/enum';
import { AssetType, PaginationMode } from 'src/enum';
import { DatabaseExtension } from 'src/interfaces/database.interface';
import { ILoggerRepository } from 'src/interfaces/logger.interface';
import {
@@ -23,7 +23,7 @@ import {
} from 'src/interfaces/search.interface';
import { asVector, searchAssetBuilder } from 'src/utils/database';
import { Instrumentation } from 'src/utils/instrumentation';
import { Paginated, PaginationMode, PaginationResult, paginatedBuilder } from 'src/utils/pagination';
import { Paginated, PaginationResult, paginatedBuilder } from 'src/utils/pagination';
import { isValidInteger } from 'src/validation';
import { Repository, SelectQueryBuilder } from 'typeorm';

View File

@@ -4,7 +4,7 @@ import { AssetMediaStatus, AssetRejectReason, AssetUploadAction } from 'src/dtos
import { AssetMediaCreateDto, AssetMediaReplaceDto, UploadFieldName } from 'src/dtos/asset-media.dto';
import { AssetFileEntity } from 'src/entities/asset-files.entity';
import { ASSET_CHECKSUM_CONSTRAINT, AssetEntity } from 'src/entities/asset.entity';
import { AssetStatus, AssetType } from 'src/enum';
import { AssetStatus, AssetType, CacheControl } from 'src/enum';
import { IAssetRepository } from 'src/interfaces/asset.interface';
import { IEventRepository } from 'src/interfaces/event.interface';
import { IJobRepository, JobName } from 'src/interfaces/job.interface';
@@ -12,7 +12,7 @@ import { ILoggerRepository } from 'src/interfaces/logger.interface';
import { IStorageRepository } from 'src/interfaces/storage.interface';
import { IUserRepository } from 'src/interfaces/user.interface';
import { AssetMediaService } from 'src/services/asset-media.service';
import { CacheControl, ImmichFileResponse } from 'src/utils/file';
import { ImmichFileResponse } from 'src/utils/file';
import { assetStub } from 'test/fixtures/asset.stub';
import { authStub } from 'test/fixtures/auth.stub';
import { fileStub } from 'test/fixtures/file.stub';

View File

@@ -7,7 +7,7 @@ import {
} from '@nestjs/common';
import { extname } from 'node:path';
import sanitize from 'sanitize-filename';
import { StorageCore, StorageFolder } from 'src/cores/storage.core';
import { StorageCore } from 'src/cores/storage.core';
import {
AssetBulkUploadCheckResponseDto,
AssetMediaResponseDto,
@@ -27,7 +27,7 @@ import {
} from 'src/dtos/asset-media.dto';
import { AuthDto } from 'src/dtos/auth.dto';
import { ASSET_CHECKSUM_CONSTRAINT, AssetEntity } from 'src/entities/asset.entity';
import { AssetStatus, AssetType, Permission } from 'src/enum';
import { AssetStatus, AssetType, CacheControl, Permission, StorageFolder } from 'src/enum';
import { IAccessRepository } from 'src/interfaces/access.interface';
import { IAssetRepository } from 'src/interfaces/asset.interface';
import { IEventRepository } from 'src/interfaces/event.interface';
@@ -37,7 +37,7 @@ import { IStorageRepository } from 'src/interfaces/storage.interface';
import { IUserRepository } from 'src/interfaces/user.interface';
import { requireAccess, requireUploadAccess } from 'src/utils/access';
import { getAssetFiles, onBeforeLink } from 'src/utils/asset.util';
import { CacheControl, ImmichFileResponse } from 'src/utils/file';
import { ImmichFileResponse } from 'src/utils/file';
import { mimeTypes } from 'src/utils/mime-types';
import { fromChecksum } from 'src/utils/request';
import { QueryFailedError } from 'typeorm';

View File

@@ -2,7 +2,7 @@ import { BadRequestException, Inject, Injectable } from '@nestjs/common';
import { DateTime } from 'luxon';
import { resolve } from 'node:path';
import { AUDIT_LOG_MAX_DURATION } from 'src/constants';
import { StorageCore, StorageFolder } from 'src/cores/storage.core';
import { StorageCore } from 'src/cores/storage.core';
import {
AuditDeletesDto,
AuditDeletesResponseDto,
@@ -12,8 +12,15 @@ import {
PathEntityType,
} from 'src/dtos/audit.dto';
import { AuthDto } from 'src/dtos/auth.dto';
import { AssetPathType, PersonPathType, UserPathType } from 'src/entities/move.entity';
import { AssetFileType, DatabaseAction, Permission } from 'src/enum';
import {
AssetFileType,
AssetPathType,
DatabaseAction,
Permission,
PersonPathType,
StorageFolder,
UserPathType,
} from 'src/enum';
import { IAccessRepository } from 'src/interfaces/access.interface';
import { IAssetRepository } from 'src/interfaces/asset.interface';
import { IAuditRepository } from 'src/interfaces/audit.interface';

View File

@@ -1,9 +1,9 @@
import { BadRequestException, ForbiddenException, UnauthorizedException } from '@nestjs/common';
import { Issuer, generators } from 'openid-client';
import { AuthType } from 'src/constants';
import { AuthDto, SignUpDto } from 'src/dtos/auth.dto';
import { UserMetadataEntity } from 'src/entities/user-metadata.entity';
import { UserEntity } from 'src/entities/user.entity';
import { AuthType } from 'src/enum';
import { IKeyRepository } from 'src/interfaces/api-key.interface';
import { ICryptoRepository } from 'src/interfaces/crypto.interface';
import { IEventRepository } from 'src/interfaces/event.interface';

View File

@@ -12,7 +12,7 @@ import { DateTime } from 'luxon';
import { IncomingHttpHeaders } from 'node:http';
import { Issuer, UserinfoResponse, custom, generators } from 'openid-client';
import { SystemConfig } from 'src/config';
import { AuthType, LOGIN_URL, MOBILE_REDIRECT, SALT_ROUNDS } from 'src/constants';
import { LOGIN_URL, MOBILE_REDIRECT, SALT_ROUNDS } from 'src/constants';
import { SystemConfigCore } from 'src/cores/system-config.core';
import { UserCore } from 'src/cores/user.core';
import {
@@ -31,7 +31,7 @@ import {
} from 'src/dtos/auth.dto';
import { UserAdminResponseDto, mapUserAdmin } from 'src/dtos/user.dto';
import { UserEntity } from 'src/entities/user.entity';
import { Permission } from 'src/enum';
import { AuthType, Permission } from 'src/enum';
import { IKeyRepository } from 'src/interfaces/api-key.interface';
import { ICryptoRepository } from 'src/interfaces/crypto.interface';
import { IEventRepository } from 'src/interfaces/event.interface';

View File

@@ -1,12 +1,20 @@
import { DatabaseExtension, EXTENSION_NAMES, IDatabaseRepository } from 'src/interfaces/database.interface';
import { IConfigRepository } from 'src/interfaces/config.interface';
import {
DatabaseExtension,
EXTENSION_NAMES,
IDatabaseRepository,
VectorExtension,
} from 'src/interfaces/database.interface';
import { ILoggerRepository } from 'src/interfaces/logger.interface';
import { DatabaseService } from 'src/services/database.service';
import { newConfigRepositoryMock } from 'test/repositories/config.repository.mock';
import { newDatabaseRepositoryMock } from 'test/repositories/database.repository.mock';
import { newLoggerRepositoryMock } from 'test/repositories/logger.repository.mock';
import { Mocked } from 'vitest';
describe(DatabaseService.name, () => {
let sut: DatabaseService;
let configMock: Mocked<IConfigRepository>;
let databaseMock: Mocked<IDatabaseRepository>;
let loggerMock: Mocked<ILoggerRepository>;
let extensionRange: string;
@@ -16,9 +24,11 @@ describe(DatabaseService.name, () => {
let versionAboveRange: string;
beforeEach(() => {
configMock = newConfigRepositoryMock();
databaseMock = newDatabaseRepositoryMock();
loggerMock = newLoggerRepositoryMock();
sut = new DatabaseService(databaseMock, loggerMock);
sut = new DatabaseService(configMock, databaseMock, loggerMock);
extensionRange = '0.2.x';
databaseMock.getExtensionVersionRange.mockReturnValue(extensionRange);
@@ -33,11 +43,6 @@ describe(DatabaseService.name, () => {
});
});
afterEach(() => {
delete process.env.DB_SKIP_MIGRATIONS;
delete process.env.DB_VECTOR_EXTENSION;
});
it('should work', () => {
expect(sut).toBeDefined();
});
@@ -50,12 +55,12 @@ describe(DatabaseService.name, () => {
expect(databaseMock.getPostgresVersion).toHaveBeenCalledTimes(1);
});
describe.each([
describe.each(<Array<{ extension: VectorExtension; extensionName: string }>>[
{ extension: DatabaseExtension.VECTOR, extensionName: EXTENSION_NAMES[DatabaseExtension.VECTOR] },
{ extension: DatabaseExtension.VECTORS, extensionName: EXTENSION_NAMES[DatabaseExtension.VECTORS] },
])('should work with $extensionName', ({ extension, extensionName }) => {
beforeEach(() => {
process.env.DB_VECTOR_EXTENSION = extensionName;
configMock.getEnv.mockReturnValue({ database: { skipMigrations: false, vectorExtension: extension } });
});
it(`should start up successfully with ${extension}`, async () => {
@@ -236,18 +241,28 @@ describe(DatabaseService.name, () => {
expect(databaseMock.runMigrations).toHaveBeenCalledTimes(1);
expect(loggerMock.fatal).not.toHaveBeenCalled();
});
});
it('should skip migrations if DB_SKIP_MIGRATIONS=true', async () => {
process.env.DB_SKIP_MIGRATIONS = 'true';
await expect(sut.onBootstrap()).resolves.toBeUndefined();
expect(databaseMock.runMigrations).not.toHaveBeenCalled();
it('should skip migrations if DB_SKIP_MIGRATIONS=true', async () => {
configMock.getEnv.mockReturnValue({
database: {
skipMigrations: true,
vectorExtension: DatabaseExtension.VECTORS,
},
});
await expect(sut.onBootstrap()).resolves.toBeUndefined();
expect(databaseMock.runMigrations).not.toHaveBeenCalled();
});
it(`should throw error if pgvector extension could not be created`, async () => {
process.env.DB_VECTOR_EXTENSION = 'pgvector';
configMock.getEnv.mockReturnValue({
database: {
skipMigrations: true,
vectorExtension: DatabaseExtension.VECTOR,
},
});
databaseMock.getExtensionVersion.mockResolvedValue({
installedVersion: null,
availableVersion: minVersionInRange,

View File

@@ -1,8 +1,8 @@
import { Inject, Injectable } from '@nestjs/common';
import { Duration } from 'luxon';
import semver from 'semver';
import { getVectorExtension } from 'src/database.config';
import { OnEmit } from 'src/decorators';
import { IConfigRepository } from 'src/interfaces/config.interface';
import {
DatabaseExtension,
DatabaseLock,
@@ -67,6 +67,7 @@ export class DatabaseService {
private reconnection?: NodeJS.Timeout;
constructor(
@Inject(IConfigRepository) private configRepository: IConfigRepository,
@Inject(IDatabaseRepository) private databaseRepository: IDatabaseRepository,
@Inject(ILoggerRepository) private logger: ILoggerRepository,
) {
@@ -85,7 +86,8 @@ export class DatabaseService {
}
await this.databaseRepository.withLock(DatabaseLock.Migrations, async () => {
const extension = getVectorExtension();
const envData = this.configRepository.getEnv();
const extension = envData.database.vectorExtension;
const name = EXTENSION_NAMES[extension];
const extensionRange = this.databaseRepository.getExtensionVersionRange(extension);
@@ -116,7 +118,8 @@ export class DatabaseService {
await this.checkReindexing();
if (process.env.DB_SKIP_MIGRATIONS !== 'true') {
const { database } = this.configRepository.getEnv();
if (!database.skipMigrations) {
await this.databaseRepository.runMigrations();
}
});

View File

@@ -1,5 +1,8 @@
import { Stats } from 'node:fs';
import { ExifEntity } from 'src/entities/exif.entity';
import {
AssetFileType,
AssetType,
AudioCodec,
Colorspace,
ImageFormat,
@@ -7,9 +10,7 @@ import {
TranscodeHWAccel,
TranscodePolicy,
VideoCodec,
} from 'src/config';
import { ExifEntity } from 'src/entities/exif.entity';
import { AssetFileType, AssetType } from 'src/enum';
} from 'src/enum';
import { IAssetRepository, WithoutProperty } from 'src/interfaces/asset.interface';
import { ICryptoRepository } from 'src/interfaces/crypto.interface';
import { IJobRepository, JobName, JobStatus } from 'src/interfaces/job.interface';

View File

@@ -1,21 +1,23 @@
import { Inject, Injectable, UnsupportedMediaTypeException } from '@nestjs/common';
import { dirname } from 'node:path';
import { GeneratedImageType, StorageCore } from 'src/cores/storage.core';
import { SystemConfigCore } from 'src/cores/system-config.core';
import { SystemConfigFFmpegDto } from 'src/dtos/system-config.dto';
import { AssetEntity } from 'src/entities/asset.entity';
import {
AssetFileType,
AssetPathType,
AssetType,
AudioCodec,
Colorspace,
ImageFormat,
StorageFolder,
TranscodeHWAccel,
TranscodePolicy,
TranscodeTarget,
VideoCodec,
VideoContainer,
} from 'src/config';
import { GeneratedImageType, StorageCore, StorageFolder } from 'src/cores/storage.core';
import { SystemConfigCore } from 'src/cores/system-config.core';
import { SystemConfigFFmpegDto } from 'src/dtos/system-config.dto';
import { AssetEntity } from 'src/entities/asset.entity';
import { AssetPathType } from 'src/entities/move.entity';
import { AssetFileType, AssetType } from 'src/enum';
} from 'src/enum';
import { IAssetRepository, WithoutProperty } from 'src/interfaces/asset.interface';
import { ICryptoRepository } from 'src/interfaces/crypto.interface';
import {

View File

@@ -1,9 +1,8 @@
import { BadRequestException, NotFoundException } from '@nestjs/common';
import { Colorspace } from 'src/config';
import { BulkIdErrorReason } from 'src/dtos/asset-ids.response.dto';
import { PersonResponseDto, mapFaces, mapPerson } from 'src/dtos/person.dto';
import { AssetFaceEntity } from 'src/entities/asset-face.entity';
import { SourceType, SystemMetadataKey } from 'src/enum';
import { CacheControl, Colorspace, SourceType, SystemMetadataKey } from 'src/enum';
import { IAssetRepository, WithoutProperty } from 'src/interfaces/asset.interface';
import { ICryptoRepository } from 'src/interfaces/crypto.interface';
import { IJobRepository, JobName, JobStatus } from 'src/interfaces/job.interface';
@@ -16,7 +15,7 @@ import { FaceSearchResult, ISearchRepository } from 'src/interfaces/search.inter
import { IStorageRepository } from 'src/interfaces/storage.interface';
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
import { PersonService } from 'src/services/person.service';
import { CacheControl, ImmichFileResponse } from 'src/utils/file';
import { ImmichFileResponse } from 'src/utils/file';
import { assetStub } from 'test/fixtures/asset.stub';
import { authStub } from 'test/fixtures/auth.stub';
import { faceStub } from 'test/fixtures/face.stub';

View File

@@ -1,5 +1,4 @@
import { BadRequestException, Inject, Injectable, NotFoundException } from '@nestjs/common';
import { ImageFormat } from 'src/config';
import { FACE_THUMBNAIL_SIZE } from 'src/constants';
import { StorageCore } from 'src/cores/storage.core';
import { SystemConfigCore } from 'src/cores/system-config.core';
@@ -23,9 +22,16 @@ import {
} from 'src/dtos/person.dto';
import { AssetFaceEntity } from 'src/entities/asset-face.entity';
import { AssetEntity } from 'src/entities/asset.entity';
import { PersonPathType } from 'src/entities/move.entity';
import { PersonEntity } from 'src/entities/person.entity';
import { AssetType, Permission, SourceType, SystemMetadataKey } from 'src/enum';
import {
AssetType,
CacheControl,
ImageFormat,
Permission,
PersonPathType,
SourceType,
SystemMetadataKey,
} from 'src/enum';
import { IAccessRepository } from 'src/interfaces/access.interface';
import { IAssetRepository, WithoutProperty } from 'src/interfaces/asset.interface';
import { ICryptoRepository } from 'src/interfaces/crypto.interface';
@@ -51,7 +57,7 @@ import { IStorageRepository } from 'src/interfaces/storage.interface';
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
import { checkAccess, requireAccess } from 'src/utils/access';
import { getAssetFiles } from 'src/utils/asset.util';
import { CacheControl, ImmichFileResponse } from 'src/utils/file';
import { ImmichFileResponse } from 'src/utils/file';
import { mimeTypes } from 'src/utils/mime-types';
import { isFaceImportEnabled, isFacialRecognitionEnabled } from 'src/utils/misc';
import { usePagination } from 'src/utils/pagination';

View File

@@ -1,7 +1,7 @@
import { BadRequestException, Inject, Injectable, NotFoundException } from '@nestjs/common';
import { getBuildMetadata, getServerLicensePublicKey } from 'src/config';
import { serverVersion } from 'src/constants';
import { StorageCore, StorageFolder } from 'src/cores/storage.core';
import { StorageCore } from 'src/cores/storage.core';
import { SystemConfigCore } from 'src/cores/system-config.core';
import { OnEmit } from 'src/decorators';
import { LicenseKeyDto, LicenseResponseDto } from 'src/dtos/license.dto';
@@ -15,7 +15,7 @@ import {
ServerStorageResponseDto,
UsageByUserDto,
} from 'src/dtos/server.dto';
import { SystemMetadataKey } from 'src/enum';
import { StorageFolder, SystemMetadataKey } from 'src/enum';
import { ICryptoRepository } from 'src/interfaces/crypto.interface';
import { ILoggerRepository } from 'src/interfaces/logger.interface';
import { IServerInfoRepository } from 'src/interfaces/server-info.interface';

View File

@@ -2,7 +2,7 @@ import { Stats } from 'node:fs';
import { SystemConfig, defaults } from 'src/config';
import { SystemConfigCore } from 'src/cores/system-config.core';
import { AssetEntity } from 'src/entities/asset.entity';
import { AssetPathType } from 'src/entities/move.entity';
import { AssetPathType } from 'src/enum';
import { IAlbumRepository } from 'src/interfaces/album.interface';
import { IAssetRepository } from 'src/interfaces/asset.interface';
import { ICryptoRepository } from 'src/interfaces/crypto.interface';

View File

@@ -13,12 +13,11 @@ import {
supportedWeekTokens,
supportedYearTokens,
} from 'src/constants';
import { StorageCore, StorageFolder } from 'src/cores/storage.core';
import { StorageCore } from 'src/cores/storage.core';
import { SystemConfigCore } from 'src/cores/system-config.core';
import { OnEmit } from 'src/decorators';
import { AssetEntity } from 'src/entities/asset.entity';
import { AssetPathType } from 'src/entities/move.entity';
import { AssetType } from 'src/enum';
import { AssetPathType, AssetType, StorageFolder } from 'src/enum';
import { IAlbumRepository } from 'src/interfaces/album.interface';
import { IAssetRepository } from 'src/interfaces/asset.interface';
import { ICryptoRepository } from 'src/interfaces/crypto.interface';

View File

@@ -1,8 +1,8 @@
import { Inject, Injectable } from '@nestjs/common';
import { join } from 'node:path';
import { StorageCore, StorageFolder } from 'src/cores/storage.core';
import { StorageCore } from 'src/cores/storage.core';
import { OnEmit } from 'src/decorators';
import { SystemMetadataKey } from 'src/enum';
import { StorageFolder, SystemMetadataKey } from 'src/enum';
import { DatabaseLock, IDatabaseRepository } from 'src/interfaces/database.interface';
import { IDeleteFilesJob, JobStatus } from 'src/interfaces/job.interface';
import { ILoggerRepository } from 'src/interfaces/logger.interface';

View File

@@ -1,19 +1,18 @@
import { BadRequestException } from '@nestjs/common';
import { defaults, SystemConfig } from 'src/config';
import {
AudioCodec,
CQMode,
Colorspace,
CQMode,
ImageFormat,
LogLevel,
SystemConfig,
SystemMetadataKey,
ToneMapping,
TranscodeHWAccel,
TranscodePolicy,
VideoCodec,
VideoContainer,
defaults,
} from 'src/config';
import { SystemMetadataKey } from 'src/enum';
} from 'src/enum';
import { IEventRepository, ServerEvent } from 'src/interfaces/event.interface';
import { QueueName } from 'src/interfaces/job.interface';
import { ILoggerRepository } from 'src/interfaces/logger.interface';

View File

@@ -1,7 +1,7 @@
import { BadRequestException, Inject, Injectable } from '@nestjs/common';
import { instanceToPlain } from 'class-transformer';
import _ from 'lodash';
import { LogLevel, SystemConfig, defaults } from 'src/config';
import { SystemConfig, defaults } from 'src/config';
import {
supportedDayTokens,
supportedHourTokens,
@@ -15,6 +15,7 @@ import {
import { SystemConfigCore } from 'src/cores/system-config.core';
import { OnEmit, OnServerEvent } from 'src/decorators';
import { SystemConfigDto, SystemConfigTemplateStorageOptionDto, mapConfig } from 'src/dtos/system-config.dto';
import { LogLevel } from 'src/enum';
import { ArgOf, ClientEvent, IEventRepository, ServerEvent } from 'src/interfaces/event.interface';
import { ILoggerRepository } from 'src/interfaces/logger.interface';
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';

View File

@@ -1,6 +1,6 @@
import { BadRequestException, InternalServerErrorException, NotFoundException } from '@nestjs/common';
import { UserEntity } from 'src/entities/user.entity';
import { UserMetadataKey } from 'src/enum';
import { CacheControl, UserMetadataKey } from 'src/enum';
import { IAlbumRepository } from 'src/interfaces/album.interface';
import { ICryptoRepository } from 'src/interfaces/crypto.interface';
import { IJobRepository, JobName } from 'src/interfaces/job.interface';
@@ -9,7 +9,7 @@ import { IStorageRepository } from 'src/interfaces/storage.interface';
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
import { IUserRepository } from 'src/interfaces/user.interface';
import { UserService } from 'src/services/user.service';
import { CacheControl, ImmichFileResponse } from 'src/utils/file';
import { ImmichFileResponse } from 'src/utils/file';
import { authStub } from 'test/fixtures/auth.stub';
import { systemConfigStub } from 'test/fixtures/system-config.stub';
import { userStub } from 'test/fixtures/user.stub';

View File

@@ -2,7 +2,7 @@ import { BadRequestException, Inject, Injectable, NotFoundException } from '@nes
import { DateTime } from 'luxon';
import { getClientLicensePublicKey, getServerLicensePublicKey } from 'src/config';
import { SALT_ROUNDS } from 'src/constants';
import { StorageCore, StorageFolder } from 'src/cores/storage.core';
import { StorageCore } from 'src/cores/storage.core';
import { SystemConfigCore } from 'src/cores/system-config.core';
import { AuthDto } from 'src/dtos/auth.dto';
import { LicenseKeyDto, LicenseResponseDto } from 'src/dtos/license.dto';
@@ -11,7 +11,7 @@ import { CreateProfileImageResponseDto } from 'src/dtos/user-profile.dto';
import { UserAdminResponseDto, UserResponseDto, UserUpdateMeDto, mapUser, mapUserAdmin } from 'src/dtos/user.dto';
import { UserMetadataEntity } from 'src/entities/user-metadata.entity';
import { UserEntity } from 'src/entities/user.entity';
import { UserMetadataKey } from 'src/enum';
import { CacheControl, StorageFolder, UserMetadataKey } from 'src/enum';
import { IAlbumRepository } from 'src/interfaces/album.interface';
import { ICryptoRepository } from 'src/interfaces/crypto.interface';
import { IEntityJob, IJobRepository, JobName, JobStatus } from 'src/interfaces/job.interface';
@@ -19,7 +19,7 @@ import { ILoggerRepository } from 'src/interfaces/logger.interface';
import { IStorageRepository } from 'src/interfaces/storage.interface';
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
import { IUserRepository, UserFindOptions } from 'src/interfaces/user.interface';
import { CacheControl, ImmichFileResponse } from 'src/utils/file';
import { ImmichFileResponse } from 'src/utils/file';
import { getPreferences, getPreferencesPartial, mergePreferences } from 'src/utils/preferences';
@Injectable()

View File

@@ -1,8 +1,8 @@
import { ModuleRef, Reflector } from '@nestjs/core';
import _ from 'lodash';
import { EmitConfig } from 'src/decorators';
import { MetadataKey } from 'src/enum';
import { EmitEvent, EmitHandler, IEventRepository } from 'src/interfaces/event.interface';
import { Metadata } from 'src/middleware/auth.guard';
import { services } from 'src/services';
type Item<T extends EmitEvent> = {
@@ -35,7 +35,7 @@ export const setupEventHandlers = (moduleRef: ModuleRef) => {
continue;
}
const options = reflector.get<EmitConfig>(Metadata.ON_EMIT_CONFIG, handler);
const options = reflector.get<EmitConfig>(MetadataKey.ON_EMIT_CONFIG, handler);
if (!options) {
continue;
}

View File

@@ -3,6 +3,7 @@ import { NextFunction, Response } from 'express';
import { access, constants } from 'node:fs/promises';
import { basename, extname, isAbsolute } from 'node:path';
import { promisify } from 'node:util';
import { CacheControl } from 'src/enum';
import { ILoggerRepository } from 'src/interfaces/logger.interface';
import { ImmichReadStream } from 'src/interfaces/storage.interface';
import { isConnectionAborted } from 'src/utils/misc';
@@ -19,12 +20,6 @@ export function getLivePhotoMotionFilename(stillName: string, motionName: string
return getFileNameWithoutExtension(stillName) + extname(motionName);
}
export enum CacheControl {
PRIVATE_WITH_CACHE = 'private_with_cache',
PRIVATE_WITHOUT_CACHE = 'private_without_cache',
NONE = 'none',
}
export class ImmichFileResponse {
public readonly path!: string;
public readonly contentType!: string;

View File

@@ -1,5 +1,5 @@
import { CQMode, ToneMapping, TranscodeHWAccel, TranscodeTarget, VideoCodec } from 'src/config';
import { SystemConfigFFmpegDto } from 'src/dtos/system-config.dto';
import { CQMode, ToneMapping, TranscodeHWAccel, TranscodeTarget, VideoCodec } from 'src/enum';
import {
AudioStreamInfo,
BitrateDistribution,

View File

@@ -13,8 +13,8 @@ import path from 'node:path';
import { SystemConfig } from 'src/config';
import { CLIP_MODEL_INFO, isDev, serverVersion } from 'src/constants';
import { ImmichCookie, ImmichHeader } from 'src/dtos/auth.dto';
import { MetadataKey } from 'src/enum';
import { ILoggerRepository } from 'src/interfaces/logger.interface';
import { Metadata } from 'src/middleware/auth.guard';
/**
* @returns a list of strings representing the keys of the object in dot notation
@@ -210,7 +210,7 @@ export const useSwagger = (app: INestApplication, force = false) => {
in: 'header',
name: ImmichHeader.API_KEY,
},
Metadata.API_KEY_SECURITY,
MetadataKey.API_KEY_SECURITY,
)
.addServer('/api')
.build();

View File

@@ -1,4 +1,5 @@
import _ from 'lodash';
import { PaginationMode } from 'src/enum';
import { FindManyOptions, ObjectLiteral, Repository, SelectQueryBuilder } from 'typeorm';
export interface PaginationOptions {
@@ -6,11 +7,6 @@ export interface PaginationOptions {
skip?: number;
}
export enum PaginationMode {
LIMIT_OFFSET = 'limit-offset',
SKIP_TAKE = 'skip-take',
}
export interface PaginatedBuilderOptions {
take: number;
skip?: number;

View File

@@ -0,0 +1,14 @@
import { IConfigRepository } from 'src/interfaces/config.interface';
import { DatabaseExtension } from 'src/interfaces/database.interface';
import { Mocked, vitest } from 'vitest';
export const newConfigRepositoryMock = (): Mocked<IConfigRepository> => {
return {
getEnv: vitest.fn().mockReturnValue({
database: {
skipMigration: false,
vectorExtension: DatabaseExtension.VECTORS,
},
}),
};
};

6
web/package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "immich-web",
"version": "1.116.0",
"version": "1.116.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "immich-web",
"version": "1.116.0",
"version": "1.116.1",
"license": "GNU Affero General Public License version 3",
"dependencies": {
"@formatjs/icu-messageformat-parser": "^2.7.8",
@@ -74,7 +74,7 @@
},
"../open-api/typescript-sdk": {
"name": "@immich/sdk",
"version": "1.116.0",
"version": "1.116.1",
"license": "GNU Affero General Public License version 3",
"dependencies": {
"@oazapfts/runtime": "^1.0.2"

View File

@@ -1,6 +1,6 @@
{
"name": "immich-web",
"version": "1.116.0",
"version": "1.116.1",
"license": "GNU Affero General Public License version 3",
"scripts": {
"dev": "vite dev --host 0.0.0.0 --port 3000",

View File

@@ -67,6 +67,7 @@
stopProgress: stopSlideshowProgress,
slideshowNavigation,
slideshowState,
slideshowTransition,
} = slideshowStore;
let appearsInAlbums: AlbumResponseDto[] = [];
@@ -82,13 +83,14 @@
let numberOfComments: number;
let fullscreenElement: Element;
let unsubscribes: (() => void)[] = [];
let selectedEditType: string = '';
let stack: StackResponseDto | null = null;
let zoomToggle = () => void 0;
let copyImage: () => Promise<void>;
$: isFullScreen = fullscreenElement !== null;
let stack: StackResponseDto | null = null;
const refreshStack = async () => {
if (isSharedLink()) {
return;
@@ -390,11 +392,9 @@
onAction?.(action);
};
let selectedEditType: string = '';
function handleUpdateSelectedEditType(type: string) {
const handleUpdateSelectedEditType = (type: string) => {
selectedEditType = type;
}
};
</script>
<svelte:document bind:fullscreenElement />
@@ -508,6 +508,7 @@
onNextAsset={() => navigateAsset('next')}
on:close={closeViewer}
{sharedLink}
haveFadeTransition={$slideshowState === SlideshowState.None || $slideshowTransition}
/>
{/if}
{:else}

View File

@@ -18,7 +18,7 @@
import SettingDropdown from './shared-components/settings/setting-dropdown.svelte';
import { t } from 'svelte-i18n';
const { slideshowDelay, showProgressBar, slideshowNavigation, slideshowLook } = slideshowStore;
const { slideshowDelay, showProgressBar, slideshowNavigation, slideshowLook, slideshowTransition } = slideshowStore;
export let onClose = () => {};
@@ -65,6 +65,7 @@
}}
/>
<SettingSwitch title={$t('show_progress_bar')} bind:checked={$showProgressBar} />
<SettingSwitch title={$t('show_slideshow_transition')} bind:checked={$slideshowTransition} />
<SettingInputField
inputType={SettingInputFieldType.NUMBER}
label={$t('duration')}

View File

@@ -1144,6 +1144,7 @@
"show_person_options": "Show person options",
"show_progress_bar": "Show Progress Bar",
"show_search_options": "Show search options",
"show_slideshow_transition": "Show slideshow transition",
"show_supporter_badge": "Supporter badge",
"show_supporter_badge_description": "Show a supporter badge",
"shuffle": "Shuffle",

View File

@@ -38,6 +38,7 @@ function createSlideshowStore() {
const showProgressBar = persisted<boolean>('slideshow-show-progressbar', true);
const slideshowDelay = persisted<number>('slideshow-delay', 5, {});
const slideshowTransition = persisted<boolean>('slideshow-transition', true);
return {
restartProgress: {
@@ -67,6 +68,7 @@ function createSlideshowStore() {
slideshowState,
slideshowDelay,
showProgressBar,
slideshowTransition,
};
}