Compare commits
8 Commits
feat/job-i
...
v1.126.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
feba590de7 | ||
|
|
64f0333306 | ||
|
|
758bcd1e97 | ||
|
|
fb21950ad8 | ||
|
|
758449e9f0 | ||
|
|
d7d4d22fe0 | ||
|
|
03948a69e2 | ||
|
|
61b8eb85b5 |
6
cli/package-lock.json
generated
6
cli/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@immich/cli",
|
||||
"version": "2.2.48",
|
||||
"version": "2.2.49",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@immich/cli",
|
||||
"version": "2.2.48",
|
||||
"version": "2.2.49",
|
||||
"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.125.7",
|
||||
"version": "1.126.0",
|
||||
"dev": true,
|
||||
"license": "GNU Affero General Public License version 3",
|
||||
"dependencies": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@immich/cli",
|
||||
"version": "2.2.48",
|
||||
"version": "2.2.49",
|
||||
"description": "Command Line Interface (CLI) for Immich",
|
||||
"type": "module",
|
||||
"exports": "./dist/index.js",
|
||||
|
||||
@@ -103,7 +103,7 @@ services:
|
||||
command: ['./run.sh', '-disable-reporting']
|
||||
ports:
|
||||
- 3000:3000
|
||||
image: grafana/grafana:11.5.0-ubuntu@sha256:3c9e2b202eb933a22da5f2b5a22c98a665493f603b452263d9d6f242a87f60d7
|
||||
image: grafana/grafana:11.5.1-ubuntu@sha256:9a4ab78cec1a2ec7d1ca5dfd5aacec6412706a1bc9e971fc7184e2f6696a63f5
|
||||
volumes:
|
||||
- grafana-data:/var/lib/grafana
|
||||
|
||||
|
||||
4
docs/static/archived-versions.json
vendored
4
docs/static/archived-versions.json
vendored
@@ -1,4 +1,8 @@
|
||||
[
|
||||
{
|
||||
"label": "v1.126.0",
|
||||
"url": "https://v1.126.0.archive.immich.app"
|
||||
},
|
||||
{
|
||||
"label": "v1.125.7",
|
||||
"url": "https://v1.125.7.archive.immich.app"
|
||||
|
||||
8
e2e/package-lock.json
generated
8
e2e/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "immich-e2e",
|
||||
"version": "1.125.7",
|
||||
"version": "1.126.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "immich-e2e",
|
||||
"version": "1.125.7",
|
||||
"version": "1.126.0",
|
||||
"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.48",
|
||||
"version": "2.2.49",
|
||||
"dev": true,
|
||||
"license": "GNU Affero General Public License version 3",
|
||||
"dependencies": {
|
||||
@@ -92,7 +92,7 @@
|
||||
},
|
||||
"../open-api/typescript-sdk": {
|
||||
"name": "@immich/sdk",
|
||||
"version": "1.125.7",
|
||||
"version": "1.126.0",
|
||||
"dev": true,
|
||||
"license": "GNU Affero General Public License version 3",
|
||||
"dependencies": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "immich-e2e",
|
||||
"version": "1.125.7",
|
||||
"version": "1.126.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"type": "module",
|
||||
|
||||
@@ -150,6 +150,30 @@ describe('/shared-links', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('should filter on albumId', async () => {
|
||||
const { status, body } = await request(app)
|
||||
.get(`/shared-links?albumId=${album.id}`)
|
||||
.set('Authorization', `Bearer ${user1.accessToken}`);
|
||||
|
||||
expect(status).toBe(200);
|
||||
expect(body).toHaveLength(2);
|
||||
expect(body).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({ id: linkWithAlbum.id }),
|
||||
expect.objectContaining({ id: linkWithPassword.id }),
|
||||
]),
|
||||
);
|
||||
});
|
||||
|
||||
it('should find 0 albums', async () => {
|
||||
const { status, body } = await request(app)
|
||||
.get(`/shared-links?albumId=${uuidDto.notFound}`)
|
||||
.set('Authorization', `Bearer ${user1.accessToken}`);
|
||||
|
||||
expect(status).toBe(200);
|
||||
expect(body).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('should not get shared links created by other users', async () => {
|
||||
const { status, body } = await request(app)
|
||||
.get('/shared-links')
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[tool.poetry]
|
||||
name = "machine-learning"
|
||||
version = "1.125.7"
|
||||
version = "1.126.0"
|
||||
description = ""
|
||||
authors = ["Hau Tran <alex.tran1502@gmail.com>"]
|
||||
readme = "README.md"
|
||||
|
||||
@@ -35,8 +35,8 @@ platform :android do
|
||||
task: 'bundle',
|
||||
build_type: 'Release',
|
||||
properties: {
|
||||
"android.injected.version.code" => 182,
|
||||
"android.injected.version.name" => "1.125.7",
|
||||
"android.injected.version.code" => 183,
|
||||
"android.injected.version.name" => "1.126.0",
|
||||
}
|
||||
)
|
||||
upload_to_play_store(skip_upload_apk: true, skip_upload_images: true, skip_upload_screenshots: true, aab: '../build/app/outputs/bundle/release/app-release.aab')
|
||||
|
||||
@@ -19,7 +19,7 @@ platform :ios do
|
||||
desc "iOS Release"
|
||||
lane :release do
|
||||
increment_version_number(
|
||||
version_number: "1.125.7"
|
||||
version_number: "1.126.0"
|
||||
)
|
||||
increment_build_number(
|
||||
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:
|
||||
|
||||
- API version: 1.125.7
|
||||
- API version: 1.126.0
|
||||
- Generator version: 7.8.0
|
||||
- Build package: org.openapitools.codegen.languages.DartClientCodegen
|
||||
|
||||
|
||||
16
mobile/openapi/lib/api/shared_links_api.dart
generated
16
mobile/openapi/lib/api/shared_links_api.dart
generated
@@ -127,7 +127,10 @@ class SharedLinksApi {
|
||||
}
|
||||
|
||||
/// Performs an HTTP 'GET /shared-links' operation and returns the [Response].
|
||||
Future<Response> getAllSharedLinksWithHttpInfo() async {
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [String] albumId:
|
||||
Future<Response> getAllSharedLinksWithHttpInfo({ String? albumId, }) async {
|
||||
// ignore: prefer_const_declarations
|
||||
final path = r'/shared-links';
|
||||
|
||||
@@ -138,6 +141,10 @@ class SharedLinksApi {
|
||||
final headerParams = <String, String>{};
|
||||
final formParams = <String, String>{};
|
||||
|
||||
if (albumId != null) {
|
||||
queryParams.addAll(_queryParams('', 'albumId', albumId));
|
||||
}
|
||||
|
||||
const contentTypes = <String>[];
|
||||
|
||||
|
||||
@@ -152,8 +159,11 @@ class SharedLinksApi {
|
||||
);
|
||||
}
|
||||
|
||||
Future<List<SharedLinkResponseDto>?> getAllSharedLinks() async {
|
||||
final response = await getAllSharedLinksWithHttpInfo();
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [String] albumId:
|
||||
Future<List<SharedLinkResponseDto>?> getAllSharedLinks({ String? albumId, }) async {
|
||||
final response = await getAllSharedLinksWithHttpInfo( albumId: albumId, );
|
||||
if (response.statusCode >= HttpStatus.badRequest) {
|
||||
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ name: immich_mobile
|
||||
description: Immich - selfhosted backup media file on mobile phone
|
||||
|
||||
publish_to: 'none'
|
||||
version: 1.125.7+182
|
||||
version: 1.126.0+183
|
||||
|
||||
environment:
|
||||
sdk: '>=3.3.0 <4.0.0'
|
||||
|
||||
@@ -5230,7 +5230,17 @@
|
||||
"/shared-links": {
|
||||
"get": {
|
||||
"operationId": "getAllSharedLinks",
|
||||
"parameters": [],
|
||||
"parameters": [
|
||||
{
|
||||
"name": "albumId",
|
||||
"required": false,
|
||||
"in": "query",
|
||||
"schema": {
|
||||
"format": "uuid",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"content": {
|
||||
@@ -7458,7 +7468,7 @@
|
||||
"info": {
|
||||
"title": "Immich",
|
||||
"description": "Immich API",
|
||||
"version": "1.125.7",
|
||||
"version": "1.126.0",
|
||||
"contact": {}
|
||||
},
|
||||
"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",
|
||||
"version": "1.125.7",
|
||||
"version": "1.126.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@immich/sdk",
|
||||
"version": "1.125.7",
|
||||
"version": "1.126.0",
|
||||
"license": "GNU Affero General Public License version 3",
|
||||
"dependencies": {
|
||||
"@oazapfts/runtime": "^1.0.2"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@immich/sdk",
|
||||
"version": "1.125.7",
|
||||
"version": "1.126.0",
|
||||
"description": "Auto-generated TypeScript SDK for the Immich API",
|
||||
"type": "module",
|
||||
"main": "./build/index.js",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Immich
|
||||
* 1.125.7
|
||||
* 1.126.0
|
||||
* DO NOT MODIFY - This file has been generated using oazapfts.
|
||||
* See https://www.npmjs.com/package/oazapfts
|
||||
*/
|
||||
@@ -2762,11 +2762,15 @@ export function deleteSession({ id }: {
|
||||
method: "DELETE"
|
||||
}));
|
||||
}
|
||||
export function getAllSharedLinks(opts?: Oazapfts.RequestOpts) {
|
||||
export function getAllSharedLinks({ albumId }: {
|
||||
albumId?: string;
|
||||
}, opts?: Oazapfts.RequestOpts) {
|
||||
return oazapfts.ok(oazapfts.fetchJson<{
|
||||
status: 200;
|
||||
data: SharedLinkResponseDto[];
|
||||
}>("/shared-links", {
|
||||
}>(`/shared-links${QS.query(QS.explode({
|
||||
albumId
|
||||
}))}`, {
|
||||
...opts
|
||||
}));
|
||||
}
|
||||
|
||||
4
server/package-lock.json
generated
4
server/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "immich",
|
||||
"version": "1.125.7",
|
||||
"version": "1.126.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "immich",
|
||||
"version": "1.125.7",
|
||||
"version": "1.126.0",
|
||||
"license": "GNU Affero General Public License version 3",
|
||||
"dependencies": {
|
||||
"@nestjs/bullmq": "^11.0.1",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "immich",
|
||||
"version": "1.125.7",
|
||||
"version": "1.126.0",
|
||||
"description": "",
|
||||
"author": "",
|
||||
"private": true,
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
SharedLinkEditDto,
|
||||
SharedLinkPasswordDto,
|
||||
SharedLinkResponseDto,
|
||||
SharedLinkSearchDto,
|
||||
} from 'src/dtos/shared-link.dto';
|
||||
import { ImmichCookie, Permission } from 'src/enum';
|
||||
import { Auth, Authenticated, GetLoginDetails } from 'src/middleware/auth.guard';
|
||||
@@ -24,8 +25,8 @@ export class SharedLinkController {
|
||||
|
||||
@Get()
|
||||
@Authenticated({ permission: Permission.SHARED_LINK_READ })
|
||||
getAllSharedLinks(@Auth() auth: AuthDto): Promise<SharedLinkResponseDto[]> {
|
||||
return this.service.getAll(auth);
|
||||
getAllSharedLinks(@Auth() auth: AuthDto, @Query() dto: SharedLinkSearchDto): Promise<SharedLinkResponseDto[]> {
|
||||
return this.service.getAll(auth, dto);
|
||||
}
|
||||
|
||||
@Get('me')
|
||||
|
||||
@@ -9,8 +9,7 @@ import { ICryptoRepository } from 'src/interfaces/crypto.interface';
|
||||
import { IMoveRepository } from 'src/interfaces/move.interface';
|
||||
import { IPersonRepository } from 'src/interfaces/person.interface';
|
||||
import { IStorageRepository } from 'src/interfaces/storage.interface';
|
||||
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
|
||||
import { IConfigRepository, ILoggingRepository } from 'src/types';
|
||||
import { IConfigRepository, ILoggingRepository, ISystemMetadataRepository } from 'src/types';
|
||||
import { getAssetFiles } from 'src/utils/asset.util';
|
||||
import { getConfig } from 'src/utils/config';
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { SessionEntity } from 'src/entities/session.entity';
|
||||
import { SessionItem } from 'src/types';
|
||||
|
||||
export class SessionResponseDto {
|
||||
id!: string;
|
||||
@@ -9,7 +9,7 @@ export class SessionResponseDto {
|
||||
deviceOS!: string;
|
||||
}
|
||||
|
||||
export const mapSession = (entity: SessionEntity, currentId?: string): SessionResponseDto => ({
|
||||
export const mapSession = (entity: SessionItem, currentId?: string): SessionResponseDto => ({
|
||||
id: entity.id,
|
||||
createdAt: entity.createdAt.toISOString(),
|
||||
updatedAt: entity.updatedAt.toISOString(),
|
||||
|
||||
@@ -7,6 +7,11 @@ import { SharedLinkEntity } from 'src/entities/shared-link.entity';
|
||||
import { SharedLinkType } from 'src/enum';
|
||||
import { Optional, ValidateBoolean, ValidateDate, ValidateUUID } from 'src/validation';
|
||||
|
||||
export class SharedLinkSearchDto {
|
||||
@ValidateUUID({ optional: true })
|
||||
albumId?: string;
|
||||
}
|
||||
|
||||
export class SharedLinkCreateDto {
|
||||
@IsEnum(SharedLinkType)
|
||||
@ApiProperty({ enum: SharedLinkType, enumName: 'SharedLinkType' })
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
import { ChildProcessWithoutNullStreams, SpawnOptionsWithoutStdio } from 'node:child_process';
|
||||
import { Readable } from 'node:stream';
|
||||
|
||||
export interface ImmichReadStream {
|
||||
stream: Readable;
|
||||
type?: string;
|
||||
length?: number;
|
||||
}
|
||||
|
||||
export interface ImmichZipStream extends ImmichReadStream {
|
||||
addFile: (inputPath: string, filename: string) => void;
|
||||
finalize: () => Promise<void>;
|
||||
}
|
||||
|
||||
export interface DiskUsage {
|
||||
available: number;
|
||||
free: number;
|
||||
total: number;
|
||||
}
|
||||
|
||||
export const IProcessRepository = 'IProcessRepository';
|
||||
|
||||
export interface IProcessRepository {
|
||||
spawn(command: string, args?: readonly string[], options?: SpawnOptionsWithoutStdio): ChildProcessWithoutNullStreams;
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
import { Insertable, Updateable } from 'kysely';
|
||||
import { Sessions } from 'src/db';
|
||||
import { SessionEntity } from 'src/entities/session.entity';
|
||||
|
||||
export const ISessionRepository = 'ISessionRepository';
|
||||
|
||||
type E = SessionEntity;
|
||||
export type SessionSearchOptions = { updatedBefore: Date };
|
||||
|
||||
export interface ISessionRepository {
|
||||
search(options: SessionSearchOptions): Promise<SessionEntity[]>;
|
||||
create(dto: Insertable<Sessions>): Promise<SessionEntity>;
|
||||
update(id: string, dto: Updateable<Sessions>): Promise<SessionEntity>;
|
||||
delete(id: string): Promise<void>;
|
||||
getByToken(token: string): Promise<E | undefined>;
|
||||
getByUserId(userId: string): Promise<E[]>;
|
||||
}
|
||||
@@ -4,8 +4,13 @@ import { SharedLinkEntity } from 'src/entities/shared-link.entity';
|
||||
|
||||
export const ISharedLinkRepository = 'ISharedLinkRepository';
|
||||
|
||||
export type SharedLinkSearchOptions = {
|
||||
userId: string;
|
||||
albumId?: string;
|
||||
};
|
||||
|
||||
export interface ISharedLinkRepository {
|
||||
getAll(userId: string): Promise<SharedLinkEntity[]>;
|
||||
getAll(options: SharedLinkSearchOptions): Promise<SharedLinkEntity[]>;
|
||||
get(userId: string, id: string): Promise<SharedLinkEntity | undefined>;
|
||||
getByKey(key: Buffer): Promise<SharedLinkEntity | undefined>;
|
||||
create(entity: Insertable<SharedLinks> & { assetIds?: string[] }): Promise<SharedLinkEntity>;
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
import { SystemMetadata } from 'src/entities/system-metadata.entity';
|
||||
|
||||
export const ISystemMetadataRepository = 'ISystemMetadataRepository';
|
||||
|
||||
export interface ISystemMetadataRepository {
|
||||
get<T extends keyof SystemMetadata>(key: T): Promise<SystemMetadata[T] | null>;
|
||||
set<T extends keyof SystemMetadata>(key: T, value: SystemMetadata[T]): Promise<void>;
|
||||
delete<T extends keyof SystemMetadata>(key: T): Promise<void>;
|
||||
readFile(filename: string): Promise<string>;
|
||||
}
|
||||
@@ -9,13 +9,10 @@ import { IMachineLearningRepository } from 'src/interfaces/machine-learning.inte
|
||||
import { IMoveRepository } from 'src/interfaces/move.interface';
|
||||
import { IPartnerRepository } from 'src/interfaces/partner.interface';
|
||||
import { IPersonRepository } from 'src/interfaces/person.interface';
|
||||
import { IProcessRepository } from 'src/interfaces/process.interface';
|
||||
import { ISearchRepository } from 'src/interfaces/search.interface';
|
||||
import { ISessionRepository } from 'src/interfaces/session.interface';
|
||||
import { ISharedLinkRepository } from 'src/interfaces/shared-link.interface';
|
||||
import { IStackRepository } from 'src/interfaces/stack.interface';
|
||||
import { IStorageRepository } from 'src/interfaces/storage.interface';
|
||||
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
|
||||
import { ITagRepository } from 'src/interfaces/tag.interface';
|
||||
import { IUserRepository } from 'src/interfaces/user.interface';
|
||||
import { AccessRepository } from 'src/repositories/access.repository';
|
||||
@@ -73,7 +70,10 @@ export const repositories = [
|
||||
MetadataRepository,
|
||||
NotificationRepository,
|
||||
OAuthRepository,
|
||||
ProcessRepository,
|
||||
SessionRepository,
|
||||
ServerInfoRepository,
|
||||
SystemMetadataRepository,
|
||||
TelemetryRepository,
|
||||
TrashRepository,
|
||||
ViewRepository,
|
||||
@@ -92,13 +92,10 @@ export const providers = [
|
||||
{ provide: IMoveRepository, useClass: MoveRepository },
|
||||
{ provide: IPartnerRepository, useClass: PartnerRepository },
|
||||
{ provide: IPersonRepository, useClass: PersonRepository },
|
||||
{ provide: IProcessRepository, useClass: ProcessRepository },
|
||||
{ provide: ISearchRepository, useClass: SearchRepository },
|
||||
{ provide: ISessionRepository, useClass: SessionRepository },
|
||||
{ provide: ISharedLinkRepository, useClass: SharedLinkRepository },
|
||||
{ provide: IStackRepository, useClass: StackRepository },
|
||||
{ provide: IStorageRepository, useClass: StorageRepository },
|
||||
{ provide: ISystemMetadataRepository, useClass: SystemMetadataRepository },
|
||||
{ provide: ITagRepository, useClass: TagRepository },
|
||||
{ provide: IUserRepository, useClass: UserRepository },
|
||||
];
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { getName } from 'i18n-iso-countries';
|
||||
import { Expression, Kysely, sql, SqlBool } from 'kysely';
|
||||
import { InjectKysely } from 'nestjs-kysely';
|
||||
@@ -11,9 +11,9 @@ import { DB, GeodataPlaces, NaturalearthCountries } from 'src/db';
|
||||
import { DummyValue, GenerateSql } from 'src/decorators';
|
||||
import { NaturalEarthCountriesTempEntity } from 'src/entities/natural-earth-countries.entity';
|
||||
import { LogLevel, SystemMetadataKey } from 'src/enum';
|
||||
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
|
||||
import { ConfigRepository } from 'src/repositories/config.repository';
|
||||
import { LoggingRepository } from 'src/repositories/logging.repository';
|
||||
import { SystemMetadataRepository } from 'src/repositories/system-metadata.repository';
|
||||
|
||||
export interface MapMarkerSearchOptions {
|
||||
isArchived?: boolean;
|
||||
@@ -48,7 +48,7 @@ interface MapDB extends DB {
|
||||
export class MapRepository {
|
||||
constructor(
|
||||
private configRepository: ConfigRepository,
|
||||
@Inject(ISystemMetadataRepository) private metadataRepository: ISystemMetadataRepository,
|
||||
private metadataRepository: SystemMetadataRepository,
|
||||
private logger: LoggingRepository,
|
||||
@InjectKysely() private db: Kysely<MapDB>,
|
||||
) {
|
||||
|
||||
@@ -43,7 +43,12 @@ export class OAuthRepository {
|
||||
const params = client.callbackParams(url);
|
||||
try {
|
||||
const tokens = await client.callback(redirectUrl, params, { state: params.state });
|
||||
return await client.userinfo<OAuthProfile>(tokens.access_token || '');
|
||||
const profile = await client.userinfo<OAuthProfile>(tokens.access_token || '');
|
||||
if (!profile.sub) {
|
||||
throw new Error('Unexpected profile response, no `sub`');
|
||||
}
|
||||
|
||||
return profile;
|
||||
} catch (error: Error | any) {
|
||||
if (error.message.includes('unexpected JWT alg received')) {
|
||||
this.logger.warn(
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { ChildProcessWithoutNullStreams, spawn, SpawnOptionsWithoutStdio } from 'node:child_process';
|
||||
import { IProcessRepository } from 'src/interfaces/process.interface';
|
||||
import { LoggingRepository } from 'src/repositories/logging.repository';
|
||||
import { StorageRepository } from 'src/repositories/storage.repository';
|
||||
|
||||
@Injectable()
|
||||
export class ProcessRepository implements IProcessRepository {
|
||||
export class ProcessRepository {
|
||||
constructor(private logger: LoggingRepository) {
|
||||
this.logger.setContext(StorageRepository.name);
|
||||
this.logger.setContext(ProcessRepository.name);
|
||||
}
|
||||
|
||||
spawn(command: string, args: readonly string[], options?: SpawnOptionsWithoutStdio): ChildProcessWithoutNullStreams {
|
||||
|
||||
@@ -3,36 +3,37 @@ import { Insertable, Kysely, Updateable } from 'kysely';
|
||||
import { InjectKysely } from 'nestjs-kysely';
|
||||
import { DB, Sessions } from 'src/db';
|
||||
import { DummyValue, GenerateSql } from 'src/decorators';
|
||||
import { SessionEntity, withUser } from 'src/entities/session.entity';
|
||||
import { ISessionRepository, SessionSearchOptions } from 'src/interfaces/session.interface';
|
||||
import { withUser } from 'src/entities/session.entity';
|
||||
import { asUuid } from 'src/utils/database';
|
||||
|
||||
export type SessionSearchOptions = { updatedBefore: Date };
|
||||
|
||||
@Injectable()
|
||||
export class SessionRepository implements ISessionRepository {
|
||||
export class SessionRepository {
|
||||
constructor(@InjectKysely() private db: Kysely<DB>) {}
|
||||
|
||||
@GenerateSql({ params: [{ updatedBefore: DummyValue.DATE }] })
|
||||
search(options: SessionSearchOptions): Promise<SessionEntity[]> {
|
||||
search(options: SessionSearchOptions) {
|
||||
return this.db
|
||||
.selectFrom('sessions')
|
||||
.selectAll()
|
||||
.where('sessions.updatedAt', '<=', options.updatedBefore)
|
||||
.execute() as Promise<SessionEntity[]>;
|
||||
.execute();
|
||||
}
|
||||
|
||||
@GenerateSql({ params: [DummyValue.STRING] })
|
||||
getByToken(token: string): Promise<SessionEntity | undefined> {
|
||||
getByToken(token: string) {
|
||||
return this.db
|
||||
.selectFrom('sessions')
|
||||
.innerJoinLateral(withUser, (join) => join.onTrue())
|
||||
.selectAll('sessions')
|
||||
.select((eb) => eb.fn.toJson('user').as('user'))
|
||||
.where('sessions.token', '=', token)
|
||||
.executeTakeFirst() as Promise<SessionEntity | undefined>;
|
||||
.executeTakeFirst();
|
||||
}
|
||||
|
||||
@GenerateSql({ params: [DummyValue.UUID] })
|
||||
getByUserId(userId: string): Promise<SessionEntity[]> {
|
||||
getByUserId(userId: string) {
|
||||
return this.db
|
||||
.selectFrom('sessions')
|
||||
.innerJoinLateral(withUser, (join) => join.onTrue())
|
||||
@@ -41,30 +42,24 @@ export class SessionRepository implements ISessionRepository {
|
||||
.where('sessions.userId', '=', userId)
|
||||
.orderBy('sessions.updatedAt', 'desc')
|
||||
.orderBy('sessions.createdAt', 'desc')
|
||||
.execute() as unknown as Promise<SessionEntity[]>;
|
||||
.execute();
|
||||
}
|
||||
|
||||
async create(dto: Insertable<Sessions>): Promise<SessionEntity> {
|
||||
const { id, token, userId, createdAt, updatedAt, deviceType, deviceOS } = await this.db
|
||||
.insertInto('sessions')
|
||||
.values(dto)
|
||||
.returningAll()
|
||||
.executeTakeFirstOrThrow();
|
||||
|
||||
return { id, token, userId, createdAt, updatedAt, deviceType, deviceOS } as SessionEntity;
|
||||
create(dto: Insertable<Sessions>) {
|
||||
return this.db.insertInto('sessions').values(dto).returningAll().executeTakeFirstOrThrow();
|
||||
}
|
||||
|
||||
update(id: string, dto: Updateable<Sessions>): Promise<SessionEntity> {
|
||||
update(id: string, dto: Updateable<Sessions>) {
|
||||
return this.db
|
||||
.updateTable('sessions')
|
||||
.set(dto)
|
||||
.where('sessions.id', '=', asUuid(id))
|
||||
.returningAll()
|
||||
.executeTakeFirstOrThrow() as Promise<SessionEntity>;
|
||||
.executeTakeFirstOrThrow();
|
||||
}
|
||||
|
||||
@GenerateSql({ params: [DummyValue.UUID] })
|
||||
async delete(id: string): Promise<void> {
|
||||
async delete(id: string) {
|
||||
await this.db.deleteFrom('sessions').where('id', '=', asUuid(id)).execute();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import { DB, SharedLinks } from 'src/db';
|
||||
import { DummyValue, GenerateSql } from 'src/decorators';
|
||||
import { SharedLinkEntity } from 'src/entities/shared-link.entity';
|
||||
import { SharedLinkType } from 'src/enum';
|
||||
import { ISharedLinkRepository } from 'src/interfaces/shared-link.interface';
|
||||
import { ISharedLinkRepository, SharedLinkSearchOptions } from 'src/interfaces/shared-link.interface';
|
||||
|
||||
@Injectable()
|
||||
export class SharedLinkRepository implements ISharedLinkRepository {
|
||||
@@ -93,7 +93,7 @@ export class SharedLinkRepository implements ISharedLinkRepository {
|
||||
}
|
||||
|
||||
@GenerateSql({ params: [DummyValue.UUID] })
|
||||
getAll(userId: string): Promise<SharedLinkEntity[]> {
|
||||
getAll({ userId, albumId }: SharedLinkSearchOptions): Promise<SharedLinkEntity[]> {
|
||||
return this.db
|
||||
.selectFrom('shared_links')
|
||||
.selectAll('shared_links')
|
||||
@@ -149,6 +149,7 @@ export class SharedLinkRepository implements ISharedLinkRepository {
|
||||
)
|
||||
.select((eb) => eb.fn.toJson('album').as('album'))
|
||||
.where((eb) => eb.or([eb('shared_links.type', '=', SharedLinkType.INDIVIDUAL), eb('album.id', 'is not', null)]))
|
||||
.$if(!!albumId, (eb) => eb.where('shared_links.albumId', '=', albumId!))
|
||||
.orderBy('shared_links.createdAt', 'desc')
|
||||
.distinctOn(['shared_links.createdAt'])
|
||||
.execute() as unknown as Promise<SharedLinkEntity[]>;
|
||||
|
||||
@@ -5,12 +5,11 @@ import { readFile } from 'node:fs/promises';
|
||||
import { DB, SystemMetadata as DbSystemMetadata } from 'src/db';
|
||||
import { GenerateSql } from 'src/decorators';
|
||||
import { SystemMetadata } from 'src/entities/system-metadata.entity';
|
||||
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
|
||||
|
||||
type Upsert = Insertable<DbSystemMetadata>;
|
||||
|
||||
@Injectable()
|
||||
export class SystemMetadataRepository implements ISystemMetadataRepository {
|
||||
export class SystemMetadataRepository {
|
||||
constructor(@InjectKysely() private db: Kysely<DB>) {}
|
||||
|
||||
@GenerateSql({ params: ['metadata_key'] })
|
||||
|
||||
@@ -9,9 +9,9 @@ import { IEventRepository } from 'src/interfaces/event.interface';
|
||||
import { IJobRepository, JobName, JobStatus } from 'src/interfaces/job.interface';
|
||||
import { IPartnerRepository } from 'src/interfaces/partner.interface';
|
||||
import { IStackRepository } from 'src/interfaces/stack.interface';
|
||||
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
|
||||
import { IUserRepository } from 'src/interfaces/user.interface';
|
||||
import { AssetService } from 'src/services/asset.service';
|
||||
import { ISystemMetadataRepository } from 'src/types';
|
||||
import { assetStub } from 'test/fixtures/asset.stub';
|
||||
import { authStub } from 'test/fixtures/auth.stub';
|
||||
import { faceStub } from 'test/fixtures/face.stub';
|
||||
|
||||
@@ -5,12 +5,10 @@ import { UserEntity } from 'src/entities/user.entity';
|
||||
import { AuthType, Permission } from 'src/enum';
|
||||
import { ICryptoRepository } from 'src/interfaces/crypto.interface';
|
||||
import { IEventRepository } from 'src/interfaces/event.interface';
|
||||
import { ISessionRepository } from 'src/interfaces/session.interface';
|
||||
import { ISharedLinkRepository } from 'src/interfaces/shared-link.interface';
|
||||
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
|
||||
import { IUserRepository } from 'src/interfaces/user.interface';
|
||||
import { AuthService } from 'src/services/auth.service';
|
||||
import { IApiKeyRepository, IOAuthRepository } from 'src/types';
|
||||
import { IApiKeyRepository, IOAuthRepository, ISessionRepository, ISystemMetadataRepository } from 'src/types';
|
||||
import { keyStub } from 'test/fixtures/api-key.stub';
|
||||
import { authStub } from 'test/fixtures/auth.stub';
|
||||
import { sessionStub } from 'test/fixtures/session.stub';
|
||||
@@ -258,7 +256,7 @@ describe('AuthService', () => {
|
||||
|
||||
it('should validate using authorization header', async () => {
|
||||
userMock.get.mockResolvedValue(userStub.user1);
|
||||
sessionMock.getByToken.mockResolvedValue(sessionStub.valid);
|
||||
sessionMock.getByToken.mockResolvedValue(sessionStub.valid as any);
|
||||
await expect(
|
||||
sut.authenticate({
|
||||
headers: { authorization: 'Bearer auth_token' },
|
||||
@@ -363,7 +361,7 @@ describe('AuthService', () => {
|
||||
});
|
||||
|
||||
it('should return an auth dto', async () => {
|
||||
sessionMock.getByToken.mockResolvedValue(sessionStub.valid);
|
||||
sessionMock.getByToken.mockResolvedValue(sessionStub.valid as any);
|
||||
await expect(
|
||||
sut.authenticate({
|
||||
headers: { cookie: 'immich_access_token=auth_token' },
|
||||
@@ -377,7 +375,7 @@ describe('AuthService', () => {
|
||||
});
|
||||
|
||||
it('should throw if admin route and not an admin', async () => {
|
||||
sessionMock.getByToken.mockResolvedValue(sessionStub.valid);
|
||||
sessionMock.getByToken.mockResolvedValue(sessionStub.valid as any);
|
||||
await expect(
|
||||
sut.authenticate({
|
||||
headers: { cookie: 'immich_access_token=auth_token' },
|
||||
@@ -388,7 +386,7 @@ describe('AuthService', () => {
|
||||
});
|
||||
|
||||
it('should update when access time exceeds an hour', async () => {
|
||||
sessionMock.getByToken.mockResolvedValue(sessionStub.inactive);
|
||||
sessionMock.getByToken.mockResolvedValue(sessionStub.inactive as any);
|
||||
sessionMock.update.mockResolvedValue(sessionStub.valid);
|
||||
await expect(
|
||||
sut.authenticate({
|
||||
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
mapLoginResponse,
|
||||
} from 'src/dtos/auth.dto';
|
||||
import { UserAdminResponseDto, mapUserAdmin } from 'src/dtos/user.dto';
|
||||
import { SessionEntity } from 'src/entities/session.entity';
|
||||
import { UserEntity } from 'src/entities/user.entity';
|
||||
import { AuthType, ImmichCookie, ImmichHeader, ImmichQuery, Permission } from 'src/enum';
|
||||
import { OAuthProfile } from 'src/repositories/oauth.repository';
|
||||
@@ -338,7 +339,7 @@ export class AuthService extends BaseService {
|
||||
await this.sessionRepository.update(session.id, { id: session.id, updatedAt: new Date() });
|
||||
}
|
||||
|
||||
return { user: session.user, session };
|
||||
return { user: session.user as unknown as UserEntity, session: session as unknown as SessionEntity };
|
||||
}
|
||||
|
||||
throw new UnauthorizedException('Invalid user token');
|
||||
|
||||
@@ -4,11 +4,9 @@ import { StorageCore } from 'src/cores/storage.core';
|
||||
import { ImmichWorker, StorageFolder } from 'src/enum';
|
||||
import { IDatabaseRepository } from 'src/interfaces/database.interface';
|
||||
import { JobStatus } from 'src/interfaces/job.interface';
|
||||
import { IProcessRepository } from 'src/interfaces/process.interface';
|
||||
import { IStorageRepository } from 'src/interfaces/storage.interface';
|
||||
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
|
||||
import { BackupService } from 'src/services/backup.service';
|
||||
import { IConfigRepository, ICronRepository } from 'src/types';
|
||||
import { IConfigRepository, ICronRepository, IProcessRepository, ISystemMetadataRepository } from 'src/types';
|
||||
import { systemConfigStub } from 'test/fixtures/system-config.stub';
|
||||
import { mockSpawn, newTestService } from 'test/utils';
|
||||
import { describe, Mocked } from 'vitest';
|
||||
|
||||
@@ -17,13 +17,10 @@ import { IMachineLearningRepository } from 'src/interfaces/machine-learning.inte
|
||||
import { IMoveRepository } from 'src/interfaces/move.interface';
|
||||
import { IPartnerRepository } from 'src/interfaces/partner.interface';
|
||||
import { IPersonRepository } from 'src/interfaces/person.interface';
|
||||
import { IProcessRepository } from 'src/interfaces/process.interface';
|
||||
import { ISearchRepository } from 'src/interfaces/search.interface';
|
||||
import { ISessionRepository } from 'src/interfaces/session.interface';
|
||||
import { ISharedLinkRepository } from 'src/interfaces/shared-link.interface';
|
||||
import { IStackRepository } from 'src/interfaces/stack.interface';
|
||||
import { IStorageRepository } from 'src/interfaces/storage.interface';
|
||||
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
|
||||
import { ITagRepository } from 'src/interfaces/tag.interface';
|
||||
import { IUserRepository } from 'src/interfaces/user.interface';
|
||||
import { AccessRepository } from 'src/repositories/access.repository';
|
||||
@@ -40,7 +37,10 @@ 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 { ProcessRepository } from 'src/repositories/process.repository';
|
||||
import { ServerInfoRepository } from 'src/repositories/server-info.repository';
|
||||
import { SessionRepository } from 'src/repositories/session.repository';
|
||||
import { SystemMetadataRepository } from 'src/repositories/system-metadata.repository';
|
||||
import { TelemetryRepository } from 'src/repositories/telemetry.repository';
|
||||
import { TrashRepository } from 'src/repositories/trash.repository';
|
||||
import { VersionHistoryRepository } from 'src/repositories/version-history.repository';
|
||||
@@ -77,14 +77,14 @@ export class BaseService {
|
||||
protected oauthRepository: OAuthRepository,
|
||||
@Inject(IPartnerRepository) protected partnerRepository: IPartnerRepository,
|
||||
@Inject(IPersonRepository) protected personRepository: IPersonRepository,
|
||||
@Inject(IProcessRepository) protected processRepository: IProcessRepository,
|
||||
protected processRepository: ProcessRepository,
|
||||
@Inject(ISearchRepository) protected searchRepository: ISearchRepository,
|
||||
protected serverInfoRepository: ServerInfoRepository,
|
||||
@Inject(ISessionRepository) protected sessionRepository: ISessionRepository,
|
||||
protected sessionRepository: SessionRepository,
|
||||
@Inject(ISharedLinkRepository) protected sharedLinkRepository: ISharedLinkRepository,
|
||||
@Inject(IStackRepository) protected stackRepository: IStackRepository,
|
||||
@Inject(IStorageRepository) protected storageRepository: IStorageRepository,
|
||||
@Inject(ISystemMetadataRepository) protected systemMetadataRepository: ISystemMetadataRepository,
|
||||
protected systemMetadataRepository: SystemMetadataRepository,
|
||||
@Inject(ITagRepository) protected tagRepository: ITagRepository,
|
||||
protected telemetryRepository: TelemetryRepository,
|
||||
protected trashRepository: TrashRepository,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
|
||||
import { IUserRepository } from 'src/interfaces/user.interface';
|
||||
import { CliService } from 'src/services/cli.service';
|
||||
import { ISystemMetadataRepository } from 'src/types';
|
||||
import { userStub } from 'test/fixtures/user.stub';
|
||||
import { newTestService } from 'test/utils';
|
||||
import { Mocked, describe, it } from 'vitest';
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import { IAssetRepository, WithoutProperty } from 'src/interfaces/asset.interface';
|
||||
import { IJobRepository, JobName, JobStatus } from 'src/interfaces/job.interface';
|
||||
import { ISearchRepository } from 'src/interfaces/search.interface';
|
||||
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
|
||||
import { DuplicateService } from 'src/services/duplicate.service';
|
||||
import { SearchService } from 'src/services/search.service';
|
||||
import { ILoggingRepository } from 'src/types';
|
||||
import { ILoggingRepository, ISystemMetadataRepository } from 'src/types';
|
||||
import { assetStub } from 'test/fixtures/asset.stub';
|
||||
import { authStub } from 'test/fixtures/auth.stub';
|
||||
import { newTestService } from 'test/utils';
|
||||
|
||||
@@ -18,9 +18,8 @@ import { IJobRepository, JobCounts, JobName, JobStatus } from 'src/interfaces/jo
|
||||
import { IMoveRepository } from 'src/interfaces/move.interface';
|
||||
import { IPersonRepository } from 'src/interfaces/person.interface';
|
||||
import { IStorageRepository } from 'src/interfaces/storage.interface';
|
||||
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
|
||||
import { MediaService } from 'src/services/media.service';
|
||||
import { ILoggingRepository, IMediaRepository, RawImageInfo } from 'src/types';
|
||||
import { ILoggingRepository, IMediaRepository, ISystemMetadataRepository, RawImageInfo } from 'src/types';
|
||||
import { assetStub } from 'test/fixtures/asset.stub';
|
||||
import { faceStub } from 'test/fixtures/face.stub';
|
||||
import { probeStub } from 'test/fixtures/media.stub';
|
||||
|
||||
@@ -12,12 +12,17 @@ import { IEventRepository } from 'src/interfaces/event.interface';
|
||||
import { IJobRepository, JobName, JobStatus } from 'src/interfaces/job.interface';
|
||||
import { IPersonRepository } from 'src/interfaces/person.interface';
|
||||
import { IStorageRepository } from 'src/interfaces/storage.interface';
|
||||
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
|
||||
import { ITagRepository } from 'src/interfaces/tag.interface';
|
||||
import { IUserRepository } from 'src/interfaces/user.interface';
|
||||
import { ImmichTags } from 'src/repositories/metadata.repository';
|
||||
import { MetadataService } from 'src/services/metadata.service';
|
||||
import { IConfigRepository, IMapRepository, IMediaRepository, IMetadataRepository } from 'src/types';
|
||||
import {
|
||||
IConfigRepository,
|
||||
IMapRepository,
|
||||
IMediaRepository,
|
||||
IMetadataRepository,
|
||||
ISystemMetadataRepository,
|
||||
} from 'src/types';
|
||||
import { assetStub } from 'test/fixtures/asset.stub';
|
||||
import { fileStub } from 'test/fixtures/file.stub';
|
||||
import { probeStub } from 'test/fixtures/media.stub';
|
||||
|
||||
@@ -8,11 +8,10 @@ import { IAlbumRepository } from 'src/interfaces/album.interface';
|
||||
import { IAssetRepository } from 'src/interfaces/asset.interface';
|
||||
import { IEventRepository } from 'src/interfaces/event.interface';
|
||||
import { IJobRepository, INotifyAlbumUpdateJob, JobName, JobStatus } from 'src/interfaces/job.interface';
|
||||
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
|
||||
import { IUserRepository } from 'src/interfaces/user.interface';
|
||||
import { EmailTemplate } from 'src/repositories/notification.repository';
|
||||
import { NotificationService } from 'src/services/notification.service';
|
||||
import { INotificationRepository } from 'src/types';
|
||||
import { INotificationRepository, ISystemMetadataRepository } from 'src/types';
|
||||
import { albumStub } from 'test/fixtures/album.stub';
|
||||
import { assetStub } from 'test/fixtures/asset.stub';
|
||||
import { userStub } from 'test/fixtures/user.stub';
|
||||
|
||||
@@ -10,9 +10,8 @@ import { DetectedFaces, IMachineLearningRepository } from 'src/interfaces/machin
|
||||
import { IPersonRepository } from 'src/interfaces/person.interface';
|
||||
import { FaceSearchResult, ISearchRepository } from 'src/interfaces/search.interface';
|
||||
import { IStorageRepository } from 'src/interfaces/storage.interface';
|
||||
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
|
||||
import { PersonService } from 'src/services/person.service';
|
||||
import { IMediaRepository } from 'src/types';
|
||||
import { IMediaRepository, ISystemMetadataRepository } from 'src/types';
|
||||
import { ImmichFileResponse } from 'src/utils/file';
|
||||
import { assetStub } from 'test/fixtures/asset.stub';
|
||||
import { authStub } from 'test/fixtures/auth.stub';
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { SystemMetadataKey } from 'src/enum';
|
||||
import { IStorageRepository } from 'src/interfaces/storage.interface';
|
||||
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
|
||||
import { IUserRepository } from 'src/interfaces/user.interface';
|
||||
import { ServerService } from 'src/services/server.service';
|
||||
import { ISystemMetadataRepository } from 'src/types';
|
||||
import { newTestService } from 'test/utils';
|
||||
import { Mocked } from 'vitest';
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { UserEntity } from 'src/entities/user.entity';
|
||||
import { JobStatus } from 'src/interfaces/job.interface';
|
||||
import { ISessionRepository } from 'src/interfaces/session.interface';
|
||||
import { SessionService } from 'src/services/session.service';
|
||||
import { ISessionRepository } from 'src/types';
|
||||
import { authStub } from 'test/fixtures/auth.stub';
|
||||
import { sessionStub } from 'test/fixtures/session.stub';
|
||||
import { IAccessRepositoryMock } from 'test/repositories/access.repository.mock';
|
||||
@@ -38,7 +37,6 @@ describe('SessionService', () => {
|
||||
deviceType: '',
|
||||
id: '123',
|
||||
token: '420',
|
||||
user: {} as UserEntity,
|
||||
userId: '42',
|
||||
},
|
||||
]);
|
||||
@@ -50,7 +48,7 @@ describe('SessionService', () => {
|
||||
|
||||
describe('getAll', () => {
|
||||
it('should get the devices', async () => {
|
||||
sessionMock.getByUserId.mockResolvedValue([sessionStub.valid, sessionStub.inactive]);
|
||||
sessionMock.getByUserId.mockResolvedValue([sessionStub.valid as any, sessionStub.inactive]);
|
||||
await expect(sut.getAll(authStub.user1)).resolves.toEqual([
|
||||
{
|
||||
createdAt: '2021-01-01T00:00:00.000Z',
|
||||
@@ -76,7 +74,7 @@ describe('SessionService', () => {
|
||||
|
||||
describe('logoutDevices', () => {
|
||||
it('should logout all devices', async () => {
|
||||
sessionMock.getByUserId.mockResolvedValue([sessionStub.inactive, sessionStub.valid]);
|
||||
sessionMock.getByUserId.mockResolvedValue([sessionStub.inactive, sessionStub.valid] as any[]);
|
||||
|
||||
await sut.deleteAll(authStub.user1);
|
||||
|
||||
|
||||
@@ -29,11 +29,11 @@ describe(SharedLinkService.name, () => {
|
||||
describe('getAll', () => {
|
||||
it('should return all shared links for a user', async () => {
|
||||
sharedLinkMock.getAll.mockResolvedValue([sharedLinkStub.expired, sharedLinkStub.valid]);
|
||||
await expect(sut.getAll(authStub.user1)).resolves.toEqual([
|
||||
await expect(sut.getAll(authStub.user1, {})).resolves.toEqual([
|
||||
sharedLinkResponseStub.expired,
|
||||
sharedLinkResponseStub.valid,
|
||||
]);
|
||||
expect(sharedLinkMock.getAll).toHaveBeenCalledWith(authStub.user1.user.id);
|
||||
expect(sharedLinkMock.getAll).toHaveBeenCalledWith({ userId: authStub.user1.user.id });
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
SharedLinkEditDto,
|
||||
SharedLinkPasswordDto,
|
||||
SharedLinkResponseDto,
|
||||
SharedLinkSearchDto,
|
||||
} from 'src/dtos/shared-link.dto';
|
||||
import { SharedLinkEntity } from 'src/entities/shared-link.entity';
|
||||
import { Permission, SharedLinkType } from 'src/enum';
|
||||
@@ -17,8 +18,10 @@ import { getExternalDomain, OpenGraphTags } from 'src/utils/misc';
|
||||
|
||||
@Injectable()
|
||||
export class SharedLinkService extends BaseService {
|
||||
async getAll(auth: AuthDto): Promise<SharedLinkResponseDto[]> {
|
||||
return this.sharedLinkRepository.getAll(auth.user.id).then((links) => links.map((link) => mapSharedLink(link)));
|
||||
async getAll(auth: AuthDto, { albumId }: SharedLinkSearchDto): Promise<SharedLinkResponseDto[]> {
|
||||
return this.sharedLinkRepository
|
||||
.getAll({ userId: auth.user.id, albumId })
|
||||
.then((links) => links.map((link) => mapSharedLink(link)));
|
||||
}
|
||||
|
||||
async getMine(auth: AuthDto, dto: SharedLinkPasswordDto): Promise<SharedLinkResponseDto> {
|
||||
|
||||
@@ -5,9 +5,8 @@ import { IDatabaseRepository } from 'src/interfaces/database.interface';
|
||||
import { IJobRepository, JobName, JobStatus } from 'src/interfaces/job.interface';
|
||||
import { IMachineLearningRepository } from 'src/interfaces/machine-learning.interface';
|
||||
import { ISearchRepository } from 'src/interfaces/search.interface';
|
||||
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
|
||||
import { SmartInfoService } from 'src/services/smart-info.service';
|
||||
import { IConfigRepository } from 'src/types';
|
||||
import { IConfigRepository, ISystemMetadataRepository } from 'src/types';
|
||||
import { getCLIPModelInfo } from 'src/utils/misc';
|
||||
import { assetStub } from 'test/fixtures/asset.stub';
|
||||
import { systemConfigStub } from 'test/fixtures/system-config.stub';
|
||||
|
||||
@@ -8,9 +8,9 @@ import { ICryptoRepository } from 'src/interfaces/crypto.interface';
|
||||
import { JobStatus } from 'src/interfaces/job.interface';
|
||||
import { IMoveRepository } from 'src/interfaces/move.interface';
|
||||
import { IStorageRepository } from 'src/interfaces/storage.interface';
|
||||
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
|
||||
import { IUserRepository } from 'src/interfaces/user.interface';
|
||||
import { StorageTemplateService } from 'src/services/storage-template.service';
|
||||
import { ISystemMetadataRepository } from 'src/types';
|
||||
import { albumStub } from 'test/fixtures/album.stub';
|
||||
import { assetStub } from 'test/fixtures/asset.stub';
|
||||
import { userStub } from 'test/fixtures/user.stub';
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { SystemMetadataKey } from 'src/enum';
|
||||
import { IStorageRepository } from 'src/interfaces/storage.interface';
|
||||
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
|
||||
import { StorageService } from 'src/services/storage.service';
|
||||
import { IConfigRepository, ILoggingRepository } from 'src/types';
|
||||
import { IConfigRepository, ILoggingRepository, ISystemMetadataRepository } from 'src/types';
|
||||
import { ImmichStartupError } from 'src/utils/misc';
|
||||
import { mockEnvData } from 'test/repositories/config.repository.mock';
|
||||
import { newTestService } from 'test/utils';
|
||||
|
||||
@@ -14,9 +14,8 @@ import {
|
||||
} from 'src/enum';
|
||||
import { IEventRepository } from 'src/interfaces/event.interface';
|
||||
import { QueueName } from 'src/interfaces/job.interface';
|
||||
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
|
||||
import { SystemConfigService } from 'src/services/system-config.service';
|
||||
import { DeepPartial, IConfigRepository, ILoggingRepository } from 'src/types';
|
||||
import { DeepPartial, IConfigRepository, ILoggingRepository, ISystemMetadataRepository } from 'src/types';
|
||||
import { mockEnvData } from 'test/repositories/config.repository.mock';
|
||||
import { newTestService } from 'test/utils';
|
||||
import { Mocked } from 'vitest';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { SystemMetadataKey } from 'src/enum';
|
||||
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
|
||||
import { SystemMetadataService } from 'src/services/system-metadata.service';
|
||||
import { ISystemMetadataRepository } from 'src/types';
|
||||
import { newTestService } from 'test/utils';
|
||||
import { Mocked } from 'vitest';
|
||||
|
||||
|
||||
@@ -4,9 +4,9 @@ import { CacheControl, UserMetadataKey } from 'src/enum';
|
||||
import { IAlbumRepository } from 'src/interfaces/album.interface';
|
||||
import { IJobRepository, JobName } from 'src/interfaces/job.interface';
|
||||
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 { ISystemMetadataRepository } from 'src/types';
|
||||
import { ImmichFileResponse } from 'src/utils/file';
|
||||
import { authStub } from 'test/fixtures/auth.stub';
|
||||
import { systemConfigStub } from 'test/fixtures/system-config.stub';
|
||||
|
||||
@@ -4,9 +4,14 @@ import { serverVersion } from 'src/constants';
|
||||
import { ImmichEnvironment, SystemMetadataKey } from 'src/enum';
|
||||
import { IEventRepository } from 'src/interfaces/event.interface';
|
||||
import { IJobRepository, JobName, JobStatus } from 'src/interfaces/job.interface';
|
||||
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
|
||||
import { VersionService } from 'src/services/version.service';
|
||||
import { IConfigRepository, ILoggingRepository, IServerInfoRepository, IVersionHistoryRepository } from 'src/types';
|
||||
import {
|
||||
IConfigRepository,
|
||||
ILoggingRepository,
|
||||
IServerInfoRepository,
|
||||
ISystemMetadataRepository,
|
||||
IVersionHistoryRepository,
|
||||
} from 'src/types';
|
||||
import { mockEnvData } from 'test/repositories/config.repository.mock';
|
||||
import { newTestService } from 'test/utils';
|
||||
import { Mocked } from 'vitest';
|
||||
|
||||
@@ -14,7 +14,10 @@ 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 { ProcessRepository } from 'src/repositories/process.repository';
|
||||
import { ServerInfoRepository } from 'src/repositories/server-info.repository';
|
||||
import { SessionRepository } from 'src/repositories/session.repository';
|
||||
import { SystemMetadataRepository } from 'src/repositories/system-metadata.repository';
|
||||
import { MetricGroupRepository, TelemetryRepository } from 'src/repositories/telemetry.repository';
|
||||
import { TrashRepository } from 'src/repositories/trash.repository';
|
||||
import { VersionHistoryRepository } from 'src/repositories/version-history.repository';
|
||||
@@ -58,7 +61,10 @@ export type IMetadataRepository = RepositoryInterface<MetadataRepository>;
|
||||
export type IMetricGroupRepository = RepositoryInterface<MetricGroupRepository>;
|
||||
export type INotificationRepository = RepositoryInterface<NotificationRepository>;
|
||||
export type IOAuthRepository = RepositoryInterface<OAuthRepository>;
|
||||
export type IProcessRepository = RepositoryInterface<ProcessRepository>;
|
||||
export type ISessionRepository = RepositoryInterface<SessionRepository>;
|
||||
export type IServerInfoRepository = RepositoryInterface<ServerInfoRepository>;
|
||||
export type ISystemMetadataRepository = RepositoryInterface<SystemMetadataRepository>;
|
||||
export type ITelemetryRepository = RepositoryInterface<TelemetryRepository>;
|
||||
export type ITrashRepository = RepositoryInterface<TrashRepository>;
|
||||
export type IViewRepository = RepositoryInterface<ViewRepository>;
|
||||
@@ -77,6 +83,8 @@ export type MemoryItem =
|
||||
| Awaited<ReturnType<IMemoryRepository['create']>>
|
||||
| Awaited<ReturnType<IMemoryRepository['search']>>[0];
|
||||
|
||||
export type SessionItem = Awaited<ReturnType<ISessionRepository['getByUserId']>>[0];
|
||||
|
||||
export interface CropOptions {
|
||||
top: number;
|
||||
left: number;
|
||||
|
||||
@@ -7,8 +7,7 @@ import { SystemConfig, defaults } from 'src/config';
|
||||
import { SystemConfigDto } from 'src/dtos/system-config.dto';
|
||||
import { SystemMetadataKey } from 'src/enum';
|
||||
import { DatabaseLock } from 'src/interfaces/database.interface';
|
||||
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
|
||||
import { DeepPartial, IConfigRepository, ILoggingRepository } from 'src/types';
|
||||
import { DeepPartial, IConfigRepository, ILoggingRepository, ISystemMetadataRepository } from 'src/types';
|
||||
import { getKeysDeep, unsetDeep } from 'src/utils/misc';
|
||||
|
||||
export type SystemConfigValidator = (config: SystemConfig, newConfig: SystemConfig) => void | Promise<void>;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { IProcessRepository } from 'src/interfaces/process.interface';
|
||||
import { IProcessRepository } from 'src/types';
|
||||
import { Mocked, vitest } from 'vitest';
|
||||
|
||||
export const newProcessRepositoryMock = (): Mocked<IProcessRepository> => {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ISessionRepository } from 'src/interfaces/session.interface';
|
||||
import { ISessionRepository } from 'src/types';
|
||||
import { Mocked, vitest } from 'vitest';
|
||||
|
||||
export const newSessionRepositoryMock = (): Mocked<ISessionRepository> => {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
|
||||
import { ISystemMetadataRepository } from 'src/types';
|
||||
import { clearConfigCache } from 'src/utils/config';
|
||||
import { Mocked, vitest } from 'vitest';
|
||||
|
||||
|
||||
@@ -15,7 +15,10 @@ 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 { ProcessRepository } from 'src/repositories/process.repository';
|
||||
import { ServerInfoRepository } from 'src/repositories/server-info.repository';
|
||||
import { SessionRepository } from 'src/repositories/session.repository';
|
||||
import { SystemMetadataRepository } from 'src/repositories/system-metadata.repository';
|
||||
import { TelemetryRepository } from 'src/repositories/telemetry.repository';
|
||||
import { TrashRepository } from 'src/repositories/trash.repository';
|
||||
import { VersionHistoryRepository } from 'src/repositories/version-history.repository';
|
||||
@@ -35,7 +38,10 @@ import {
|
||||
IMetadataRepository,
|
||||
INotificationRepository,
|
||||
IOAuthRepository,
|
||||
IProcessRepository,
|
||||
IServerInfoRepository,
|
||||
ISessionRepository,
|
||||
ISystemMetadataRepository,
|
||||
ITrashRepository,
|
||||
IVersionHistoryRepository,
|
||||
IViewRepository,
|
||||
@@ -163,14 +169,14 @@ export const newTestService = <T extends BaseService>(
|
||||
oauthMock as IOAuthRepository as OAuthRepository,
|
||||
partnerMock,
|
||||
personMock,
|
||||
processMock,
|
||||
processMock as IProcessRepository as ProcessRepository,
|
||||
searchMock,
|
||||
serverInfoMock as IServerInfoRepository as ServerInfoRepository,
|
||||
sessionMock,
|
||||
sessionMock as ISessionRepository as SessionRepository,
|
||||
sharedLinkMock,
|
||||
stackMock,
|
||||
storageMock,
|
||||
systemMock,
|
||||
systemMock as ISystemMetadataRepository as SystemMetadataRepository,
|
||||
tagMock,
|
||||
telemetryMock as unknown as TelemetryRepository,
|
||||
trashMock as ITrashRepository as TrashRepository,
|
||||
|
||||
6
web/package-lock.json
generated
6
web/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "immich-web",
|
||||
"version": "1.125.7",
|
||||
"version": "1.126.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "immich-web",
|
||||
"version": "1.125.7",
|
||||
"version": "1.126.0",
|
||||
"license": "GNU Affero General Public License version 3",
|
||||
"dependencies": {
|
||||
"@formatjs/icu-messageformat-parser": "^2.9.8",
|
||||
@@ -77,7 +77,7 @@
|
||||
},
|
||||
"../open-api/typescript-sdk": {
|
||||
"name": "@immich/sdk",
|
||||
"version": "1.125.7",
|
||||
"version": "1.126.0",
|
||||
"license": "GNU Affero General Public License version 3",
|
||||
"dependencies": {
|
||||
"@oazapfts/runtime": "^1.0.2"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "immich-web",
|
||||
"version": "1.125.7",
|
||||
"version": "1.126.0",
|
||||
"license": "GNU Affero General Public License version 3",
|
||||
"scripts": {
|
||||
"dev": "vite dev --host 0.0.0.0 --port 3000",
|
||||
|
||||
40
web/src/lib/components/album-page/album-shared-link.svelte
Normal file
40
web/src/lib/components/album-page/album-shared-link.svelte
Normal file
@@ -0,0 +1,40 @@
|
||||
<script lang="ts">
|
||||
import SharedLinkCopy from '$lib/components/sharedlinks-page/actions/shared-link-copy.svelte';
|
||||
import { locale } from '$lib/stores/preferences.store';
|
||||
import type { AlbumResponseDto, SharedLinkResponseDto } from '@immich/sdk';
|
||||
import { Text } from '@immich/ui';
|
||||
import { DateTime } from 'luxon';
|
||||
import { t } from 'svelte-i18n';
|
||||
|
||||
type Props = {
|
||||
album: AlbumResponseDto;
|
||||
sharedLink: SharedLinkResponseDto;
|
||||
};
|
||||
|
||||
const { album, sharedLink }: Props = $props();
|
||||
</script>
|
||||
|
||||
<div class="flex justify-between items-center">
|
||||
<div class="flex flex-col gap-1">
|
||||
<Text size="small">{sharedLink.description || album.albumName}</Text>
|
||||
<Text size="tiny" color="muted"
|
||||
>{[
|
||||
DateTime.fromISO(sharedLink.createdAt).toLocaleString(
|
||||
{
|
||||
month: 'long',
|
||||
day: 'numeric',
|
||||
year: 'numeric',
|
||||
},
|
||||
{ locale: $locale },
|
||||
),
|
||||
sharedLink.allowUpload && $t('upload'),
|
||||
sharedLink.allowDownload && $t('download'),
|
||||
sharedLink.showMetadata && $t('exif'),
|
||||
sharedLink.password && $t('password'),
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(' • ')}</Text
|
||||
>
|
||||
</div>
|
||||
<SharedLinkCopy link={sharedLink} />
|
||||
</div>
|
||||
@@ -1,4 +1,5 @@
|
||||
<script lang="ts">
|
||||
import AlbumSharedLink from '$lib/components/album-page/album-shared-link.svelte';
|
||||
import Dropdown from '$lib/components/elements/dropdown.svelte';
|
||||
import Icon from '$lib/components/elements/icon.svelte';
|
||||
import FullScreenModal from '$lib/components/shared-components/full-screen-modal.svelte';
|
||||
@@ -12,11 +13,11 @@
|
||||
type SharedLinkResponseDto,
|
||||
type UserResponseDto,
|
||||
} from '@immich/sdk';
|
||||
import { mdiCheck, mdiEye, mdiLink, mdiPencil, mdiShareCircle } from '@mdi/js';
|
||||
import { Button, Link, Stack, Text } from '@immich/ui';
|
||||
import { mdiCheck, mdiEye, mdiLink, mdiPencil } from '@mdi/js';
|
||||
import { onMount } from 'svelte';
|
||||
import Button from '../elements/buttons/button.svelte';
|
||||
import UserAvatar from '../shared-components/user-avatar.svelte';
|
||||
import { t } from 'svelte-i18n';
|
||||
import UserAvatar from '../shared-components/user-avatar.svelte';
|
||||
|
||||
interface Props {
|
||||
album: AlbumResponseDto;
|
||||
@@ -38,7 +39,7 @@
|
||||
|
||||
let sharedLinks: SharedLinkResponseDto[] = $state([]);
|
||||
onMount(async () => {
|
||||
await getSharedLinks();
|
||||
sharedLinks = await getAllSharedLinks({ albumId: album.id });
|
||||
const data = await searchUsers();
|
||||
|
||||
// remove album owner
|
||||
@@ -50,11 +51,6 @@
|
||||
}
|
||||
});
|
||||
|
||||
const getSharedLinks = async () => {
|
||||
const data = await getAllSharedLinks();
|
||||
sharedLinks = data.filter((link) => link.album?.id === album.id);
|
||||
};
|
||||
|
||||
const handleToggle = (user: UserResponseDto) => {
|
||||
if (Object.keys(selectedUsers).includes(user.id)) {
|
||||
delete selectedUsers[user.id];
|
||||
@@ -72,10 +68,10 @@
|
||||
};
|
||||
</script>
|
||||
|
||||
<FullScreenModal title={$t('invite_to_album')} showLogo {onClose}>
|
||||
<FullScreenModal title={$t('share')} showLogo {onClose}>
|
||||
{#if Object.keys(selectedUsers).length > 0}
|
||||
<div class="mb-2 py-2 sticky">
|
||||
<p class="text-xs font-medium">{$t('selected').toUpperCase()}</p>
|
||||
<p class="text-xs font-medium">{$t('selected')}</p>
|
||||
<div class="my-2">
|
||||
{#each Object.values(selectedUsers) as { user }}
|
||||
{#key user.id}
|
||||
@@ -117,7 +113,7 @@
|
||||
|
||||
<div class="immich-scrollbar max-h-[500px] overflow-y-auto">
|
||||
{#if users.length > 0 && users.length !== Object.keys(selectedUsers).length}
|
||||
<p class="text-xs font-medium">{$t('suggestions').toUpperCase()}</p>
|
||||
<Text>{$t('users')}</Text>
|
||||
|
||||
<div class="my-2">
|
||||
{#each users as user}
|
||||
@@ -144,9 +140,9 @@
|
||||
{#if users.length > 0}
|
||||
<div class="py-3">
|
||||
<Button
|
||||
size="sm"
|
||||
fullwidth
|
||||
rounded="full"
|
||||
size="small"
|
||||
fullWidth
|
||||
shape="round"
|
||||
disabled={Object.keys(selectedUsers).length === 0}
|
||||
onclick={() =>
|
||||
onSelect(Object.values(selectedUsers).map(({ user, ...rest }) => ({ userId: user.id, ...rest })))}
|
||||
@@ -155,26 +151,22 @@
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<hr />
|
||||
<hr class="my-4" />
|
||||
|
||||
<div id="shared-buttons" class="mt-4 flex place-content-center place-items-center justify-around">
|
||||
<button
|
||||
type="button"
|
||||
class="flex flex-col place-content-center place-items-center gap-2 hover:cursor-pointer"
|
||||
onclick={onShare}
|
||||
>
|
||||
<Icon path={mdiLink} size={24} />
|
||||
<p class="text-sm">{$t('create_link')}</p>
|
||||
</button>
|
||||
<Stack gap={6}>
|
||||
{#if sharedLinks.length > 0}
|
||||
<div class="flex justify-between items-center">
|
||||
<Text>{$t('shared_links')}</Text>
|
||||
<Link href={AppRoute.SHARED_LINKS} class="text-sm">{$t('view_all')}</Link>
|
||||
</div>
|
||||
|
||||
{#if sharedLinks.length}
|
||||
<a
|
||||
href={AppRoute.SHARED_LINKS}
|
||||
class="flex flex-col place-content-center place-items-center gap-2 hover:cursor-pointer"
|
||||
>
|
||||
<Icon path={mdiShareCircle} size={24} />
|
||||
<p class="text-sm">{$t('view_links')}</p>
|
||||
</a>
|
||||
<Stack gap={4}>
|
||||
{#each sharedLinks as sharedLink}
|
||||
<AlbumSharedLink {album} {sharedLink} />
|
||||
{/each}
|
||||
</Stack>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<Button leadingIcon={mdiLink} size="small" shape="round" fullWidth onclick={onShare}>{$t('create_link')}</Button>
|
||||
</Stack>
|
||||
</FullScreenModal>
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
let sharedLink = $derived(sharedLinks.find(({ id }) => id === page.params.id));
|
||||
|
||||
const refresh = async () => {
|
||||
sharedLinks = await getAllSharedLinks();
|
||||
sharedLinks = await getAllSharedLinks({});
|
||||
};
|
||||
|
||||
onMount(async () => {
|
||||
@@ -97,7 +97,7 @@
|
||||
</div>
|
||||
{/snippet}
|
||||
|
||||
<div>
|
||||
<div class="w-full max-w-3xl m-auto">
|
||||
{#if sharedLinks.length === 0}
|
||||
<div
|
||||
class="flex place-content-center place-items-center rounded-lg bg-gray-100 dark:bg-immich-dark-gray dark:text-immich-gray p-12"
|
||||
|
||||
Reference in New Issue
Block a user