mirror of
https://github.com/immich-app/immich.git
synced 2026-02-07 02:09:35 +03:00
Compare commits
1 Commits
main
...
renovate/t
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3db8400d74 |
@@ -997,11 +997,6 @@
|
||||
"editor_close_without_save_prompt": "The changes will not be saved",
|
||||
"editor_close_without_save_title": "Close editor?",
|
||||
"editor_confirm_reset_all_changes": "Are you sure you want to reset all changes?",
|
||||
"editor_discard_edits_confirm": "Discard edits",
|
||||
"editor_discard_edits_prompt": "You have unsaved edits. Are you sure you want to discard them?",
|
||||
"editor_discard_edits_title": "Discard edits?",
|
||||
"editor_edits_applied_error": "Failed to apply edits",
|
||||
"editor_edits_applied_success": "Edits applied successfully",
|
||||
"editor_flip_horizontal": "Flip horizontal",
|
||||
"editor_flip_vertical": "Flip vertical",
|
||||
"editor_orientation": "Orientation",
|
||||
|
||||
@@ -16,7 +16,7 @@ config_roots = [
|
||||
[tools]
|
||||
node = "24.13.0"
|
||||
flutter = "3.35.7"
|
||||
pnpm = "10.28.0"
|
||||
pnpm = "10.28.2"
|
||||
terragrunt = "0.98.0"
|
||||
opentofu = "1.11.4"
|
||||
java = "21.0.2"
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"version": "2.5.5",
|
||||
"description": "Monorepo for Immich",
|
||||
"private": true,
|
||||
"packageManager": "pnpm@10.28.0+sha512.05df71d1421f21399e053fde567cea34d446fa02c76571441bfc1c7956e98e363088982d940465fd34480d4d90a0668bc12362f8aa88000a64e83d0b0e47be48",
|
||||
"packageManager": "pnpm@10.28.2+sha512.41872f037ad22f7348e3b1debbaf7e867cfd448f2726d9cf74c08f19507c31d2c8e7a11525b983febc2df640b5438dee6023ebb1f84ed43cc2d654d2bc326264",
|
||||
"engines": {
|
||||
"pnpm": ">=10.0.0"
|
||||
}
|
||||
|
||||
3413
pnpm-lock.yaml
generated
3413
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -45,14 +45,14 @@
|
||||
"@nestjs/websockets": "^11.0.4",
|
||||
"@opentelemetry/api": "^1.9.0",
|
||||
"@opentelemetry/context-async-hooks": "^2.0.0",
|
||||
"@opentelemetry/exporter-prometheus": "^0.210.0",
|
||||
"@opentelemetry/instrumentation-http": "^0.210.0",
|
||||
"@opentelemetry/instrumentation-ioredis": "^0.58.0",
|
||||
"@opentelemetry/instrumentation-nestjs-core": "^0.56.0",
|
||||
"@opentelemetry/instrumentation-pg": "^0.62.0",
|
||||
"@opentelemetry/exporter-prometheus": "^0.211.0",
|
||||
"@opentelemetry/instrumentation-http": "^0.211.0",
|
||||
"@opentelemetry/instrumentation-ioredis": "^0.59.0",
|
||||
"@opentelemetry/instrumentation-nestjs-core": "^0.57.0",
|
||||
"@opentelemetry/instrumentation-pg": "^0.63.0",
|
||||
"@opentelemetry/resources": "^2.0.1",
|
||||
"@opentelemetry/sdk-metrics": "^2.0.1",
|
||||
"@opentelemetry/sdk-node": "^0.210.0",
|
||||
"@opentelemetry/sdk-node": "^0.211.0",
|
||||
"@opentelemetry/semantic-conventions": "^1.34.0",
|
||||
"@react-email/components": "^0.5.0",
|
||||
"@react-email/render": "^1.1.2",
|
||||
@@ -69,7 +69,7 @@
|
||||
"compression": "^1.8.0",
|
||||
"cookie": "^1.0.2",
|
||||
"cookie-parser": "^1.4.7",
|
||||
"cron": "4.3.5",
|
||||
"cron": "4.4.0",
|
||||
"exiftool-vendored": "^34.3.0",
|
||||
"express": "^5.1.0",
|
||||
"fast-glob": "^3.3.2",
|
||||
@@ -81,7 +81,7 @@
|
||||
"jose": "^5.10.0",
|
||||
"js-yaml": "^4.1.0",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"kysely": "0.28.2",
|
||||
"kysely": "0.28.11",
|
||||
"kysely-postgres-js": "^3.0.0",
|
||||
"lodash": "^4.17.21",
|
||||
"luxon": "^3.4.2",
|
||||
|
||||
@@ -58,7 +58,7 @@ select
|
||||
from
|
||||
(
|
||||
select
|
||||
"shared_link".*
|
||||
*
|
||||
from
|
||||
"shared_link"
|
||||
where
|
||||
@@ -243,7 +243,7 @@ select
|
||||
from
|
||||
(
|
||||
select
|
||||
"shared_link".*
|
||||
*
|
||||
from
|
||||
"shared_link"
|
||||
where
|
||||
@@ -316,7 +316,7 @@ select
|
||||
from
|
||||
(
|
||||
select
|
||||
"shared_link".*
|
||||
*
|
||||
from
|
||||
"shared_link"
|
||||
where
|
||||
|
||||
@@ -17,6 +17,8 @@ set
|
||||
where
|
||||
"userId" = $2
|
||||
and "albumId" = $3
|
||||
returning
|
||||
*
|
||||
|
||||
-- AlbumUserRepository.delete
|
||||
delete from "album_user"
|
||||
|
||||
@@ -25,13 +25,14 @@ export class AlbumUserRepository {
|
||||
}
|
||||
|
||||
@GenerateSql({ params: [{ userId: DummyValue.UUID, albumId: DummyValue.UUID }, { role: AlbumUserRole.Viewer }] })
|
||||
async update({ userId, albumId }: AlbumPermissionId, dto: Updateable<AlbumUserTable>) {
|
||||
await this.db
|
||||
update({ userId, albumId }: AlbumPermissionId, dto: Updateable<AlbumUserTable>) {
|
||||
return this.db
|
||||
.updateTable('album_user')
|
||||
.set(dto)
|
||||
.where('userId', '=', userId)
|
||||
.where('albumId', '=', albumId)
|
||||
.execute();
|
||||
.returningAll()
|
||||
.executeTakeFirstOrThrow();
|
||||
}
|
||||
|
||||
@GenerateSql({ params: [{ userId: DummyValue.UUID, albumId: DummyValue.UUID }] })
|
||||
|
||||
@@ -44,9 +44,9 @@ const withAlbumUsers = (eb: ExpressionBuilder<DB, 'album'>) => {
|
||||
};
|
||||
|
||||
const withSharedLink = (eb: ExpressionBuilder<DB, 'album'>) => {
|
||||
return jsonArrayFrom(
|
||||
eb.selectFrom('shared_link').selectAll('shared_link').whereRef('shared_link.albumId', '=', 'album.id'),
|
||||
).as('sharedLinks');
|
||||
return jsonArrayFrom(eb.selectFrom('shared_link').selectAll().whereRef('shared_link.albumId', '=', 'album.id')).as(
|
||||
'sharedLinks',
|
||||
);
|
||||
};
|
||||
|
||||
const withAssets = (eb: ExpressionBuilder<DB, 'album'>) => {
|
||||
@@ -283,7 +283,7 @@ export class AlbumRepository {
|
||||
|
||||
return tx
|
||||
.selectFrom('album')
|
||||
.selectAll('album')
|
||||
.selectAll()
|
||||
.where('id', '=', newAlbum.id)
|
||||
.select(withOwner)
|
||||
.select(withAssets)
|
||||
|
||||
@@ -260,7 +260,7 @@ export class SharedLinkRepository {
|
||||
.selectAll('asset')
|
||||
.innerJoinLateral(
|
||||
(eb) =>
|
||||
eb.selectFrom('asset_exif').whereRef('asset_exif.assetId', '=', 'asset.id').selectAll().as('exifInfo'),
|
||||
eb.selectFrom('asset_exif').whereRef('asset_exif.assetId', '=', 'asset.id').selectAll().as('exif'),
|
||||
(join) => join.onTrue(),
|
||||
)
|
||||
.as('assets'),
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,54 +0,0 @@
|
||||
import { Selectable } from 'kysely';
|
||||
import { AlbumUserRole } from 'src/enum';
|
||||
import { AlbumUserTable } from 'src/schema/tables/album-user.table';
|
||||
import { AlbumFactory } from 'test/factories/album.factory';
|
||||
import { build } from 'test/factories/builder.factory';
|
||||
import { AlbumUserLike, FactoryBuilder, UserLike } from 'test/factories/types';
|
||||
import { UserFactory } from 'test/factories/user.factory';
|
||||
import { newDate, newUuid, newUuidV7 } from 'test/small.factory';
|
||||
|
||||
export class AlbumUserFactory {
|
||||
#user!: UserFactory;
|
||||
|
||||
private constructor(private readonly value: Selectable<AlbumUserTable>) {
|
||||
value.userId ??= newUuid();
|
||||
this.#user = UserFactory.from({ id: value.userId });
|
||||
}
|
||||
|
||||
static create(dto: AlbumUserLike = {}) {
|
||||
return AlbumUserFactory.from(dto).build();
|
||||
}
|
||||
|
||||
static from(dto: AlbumUserLike = {}) {
|
||||
return new AlbumUserFactory({
|
||||
albumId: newUuid(),
|
||||
userId: newUuid(),
|
||||
role: AlbumUserRole.Editor,
|
||||
createId: newUuidV7(),
|
||||
createdAt: newDate(),
|
||||
updateId: newUuidV7(),
|
||||
updatedAt: newDate(),
|
||||
...dto,
|
||||
});
|
||||
}
|
||||
|
||||
album(dto: AlbumUserLike = {}, builder?: FactoryBuilder<AlbumFactory>) {
|
||||
const album = build(AlbumFactory.from(dto), builder);
|
||||
this.value.albumId = album.build().id;
|
||||
return this;
|
||||
}
|
||||
|
||||
user(dto: UserLike = {}, builder?: FactoryBuilder<UserFactory>) {
|
||||
const user = build(UserFactory.from(dto), builder);
|
||||
this.value.userId = user.build().id;
|
||||
this.#user = user;
|
||||
return this;
|
||||
}
|
||||
|
||||
build() {
|
||||
return {
|
||||
...this.value,
|
||||
user: this.#user.build(),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,87 +0,0 @@
|
||||
import { Selectable } from 'kysely';
|
||||
import { AssetOrder } from 'src/enum';
|
||||
import { AlbumTable } from 'src/schema/tables/album.table';
|
||||
import { SharedLinkTable } from 'src/schema/tables/shared-link.table';
|
||||
import { AlbumUserFactory } from 'test/factories/album-user.factory';
|
||||
import { AssetFactory } from 'test/factories/asset.factory';
|
||||
import { build } from 'test/factories/builder.factory';
|
||||
import { AlbumLike, AlbumUserLike, AssetLike, FactoryBuilder, UserLike } from 'test/factories/types';
|
||||
import { UserFactory } from 'test/factories/user.factory';
|
||||
import { newDate, newUuid, newUuidV7 } from 'test/small.factory';
|
||||
|
||||
export class AlbumFactory {
|
||||
#owner: UserFactory;
|
||||
#sharedLinks: Selectable<SharedLinkTable>[] = [];
|
||||
#albumUsers: AlbumUserFactory[] = [];
|
||||
#assets: AssetFactory[] = [];
|
||||
|
||||
private constructor(private readonly value: Selectable<AlbumTable>) {
|
||||
value.ownerId ??= newUuid();
|
||||
this.#owner = UserFactory.from({ id: value.ownerId });
|
||||
}
|
||||
|
||||
static create(dto: AlbumLike = {}) {
|
||||
return AlbumFactory.from(dto).build();
|
||||
}
|
||||
|
||||
static from(dto: AlbumLike = {}) {
|
||||
return new AlbumFactory({
|
||||
id: newUuid(),
|
||||
ownerId: newUuid(),
|
||||
albumName: 'My Album',
|
||||
albumThumbnailAssetId: null,
|
||||
createdAt: newDate(),
|
||||
deletedAt: null,
|
||||
description: 'Album description',
|
||||
isActivityEnabled: false,
|
||||
order: AssetOrder.Desc,
|
||||
updatedAt: newDate(),
|
||||
updateId: newUuidV7(),
|
||||
...dto,
|
||||
}).owner();
|
||||
}
|
||||
|
||||
owner(dto: UserLike = {}, builder?: FactoryBuilder<UserFactory>) {
|
||||
this.#owner = build(UserFactory.from(dto), builder);
|
||||
this.value.ownerId = this.#owner.build().id;
|
||||
return this;
|
||||
}
|
||||
|
||||
sharedLinks() {
|
||||
this.#sharedLinks = [];
|
||||
return this;
|
||||
}
|
||||
|
||||
albumUser(dto: AlbumUserLike = {}, builder?: FactoryBuilder<AlbumUserFactory>) {
|
||||
const albumUser = build(AlbumUserFactory.from(dto).album(this.value), builder);
|
||||
this.#albumUsers.push(albumUser);
|
||||
return this;
|
||||
}
|
||||
|
||||
asset(dto: AssetLike = {}, builder?: FactoryBuilder<AssetFactory>) {
|
||||
const asset = build(AssetFactory.from(dto), builder);
|
||||
|
||||
// use album owner by default
|
||||
if (!dto.ownerId) {
|
||||
asset.owner(this.#owner.build());
|
||||
}
|
||||
|
||||
if (!this.#assets) {
|
||||
this.#assets = [];
|
||||
}
|
||||
|
||||
this.#assets.push(asset);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
build() {
|
||||
return {
|
||||
...this.value,
|
||||
owner: this.#owner.build(),
|
||||
assets: this.#assets.map((asset) => asset.build()),
|
||||
albumUsers: this.#albumUsers.map((albumUser) => albumUser.build()),
|
||||
sharedLinks: this.#sharedLinks,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
import { Selectable } from 'kysely';
|
||||
import { AssetExifTable } from 'src/schema/tables/asset-exif.table';
|
||||
import { AssetExifLike } from 'test/factories/types';
|
||||
import { factory } from 'test/small.factory';
|
||||
|
||||
export class AssetExifFactory {
|
||||
private constructor(private readonly value: Selectable<AssetExifTable>) {}
|
||||
|
||||
static create(dto: AssetExifLike = {}) {
|
||||
return AssetExifFactory.from(dto).build();
|
||||
}
|
||||
|
||||
static from(dto: AssetExifLike = {}) {
|
||||
return new AssetExifFactory({
|
||||
updatedAt: factory.date(),
|
||||
updateId: factory.uuid(),
|
||||
assetId: factory.uuid(),
|
||||
autoStackId: null,
|
||||
bitsPerSample: null,
|
||||
city: 'Austin',
|
||||
colorspace: null,
|
||||
country: 'United States of America',
|
||||
dateTimeOriginal: factory.date(),
|
||||
description: '',
|
||||
exifImageHeight: 420,
|
||||
exifImageWidth: 42,
|
||||
exposureTime: null,
|
||||
fileSizeInByte: 69,
|
||||
fNumber: 1.7,
|
||||
focalLength: 4.38,
|
||||
fps: null,
|
||||
iso: 947,
|
||||
latitude: 30.267_334_570_570_195,
|
||||
longitude: -97.789_833_534_282_07,
|
||||
lensModel: null,
|
||||
livePhotoCID: null,
|
||||
make: 'Google',
|
||||
model: 'Pixel 7',
|
||||
modifyDate: factory.date(),
|
||||
orientation: '1',
|
||||
profileDescription: null,
|
||||
projectionType: null,
|
||||
rating: 4,
|
||||
lockedProperties: [],
|
||||
state: 'Texas',
|
||||
tags: ['parent/child'],
|
||||
timeZone: 'UTC-6',
|
||||
...dto,
|
||||
});
|
||||
}
|
||||
|
||||
build() {
|
||||
return { ...this.value };
|
||||
}
|
||||
}
|
||||
@@ -1,79 +0,0 @@
|
||||
import { Selectable } from 'kysely';
|
||||
import { AssetStatus, AssetType, AssetVisibility } from 'src/enum';
|
||||
import { AssetTable } from 'src/schema/tables/asset.table';
|
||||
import { AssetExifFactory } from 'test/factories/asset-exif.factory';
|
||||
import { build } from 'test/factories/builder.factory';
|
||||
import { AssetExifLike, AssetLike, FactoryBuilder, UserLike } from 'test/factories/types';
|
||||
import { UserFactory } from 'test/factories/user.factory';
|
||||
import { factory, newDate, newSha1, newUuid, newUuidV7 } from 'test/small.factory';
|
||||
|
||||
export class AssetFactory {
|
||||
#assetExif?: AssetExifFactory;
|
||||
#owner!: UserFactory;
|
||||
|
||||
private constructor(private readonly value: Selectable<AssetTable>) {
|
||||
value.ownerId ??= newUuid();
|
||||
this.#owner = UserFactory.from({ id: value.ownerId });
|
||||
}
|
||||
|
||||
static create(dto: AssetLike = {}) {
|
||||
return AssetFactory.from(dto).build();
|
||||
}
|
||||
|
||||
static from(dto: AssetLike = {}) {
|
||||
return new AssetFactory({
|
||||
id: factory.uuid(),
|
||||
createdAt: newDate(),
|
||||
updatedAt: newDate(),
|
||||
deletedAt: null,
|
||||
updateId: newUuidV7(),
|
||||
status: AssetStatus.Active,
|
||||
checksum: newSha1(),
|
||||
deviceAssetId: '',
|
||||
deviceId: '',
|
||||
duplicateId: null,
|
||||
duration: null,
|
||||
encodedVideoPath: null,
|
||||
fileCreatedAt: newDate(),
|
||||
fileModifiedAt: newDate(),
|
||||
isExternal: false,
|
||||
isFavorite: false,
|
||||
isOffline: false,
|
||||
libraryId: null,
|
||||
livePhotoVideoId: null,
|
||||
localDateTime: newDate(),
|
||||
originalFileName: 'IMG_123.jpg',
|
||||
originalPath: `/data/12/34/IMG_123.jpg`,
|
||||
ownerId: newUuid(),
|
||||
stackId: null,
|
||||
thumbhash: null,
|
||||
type: AssetType.Image,
|
||||
visibility: AssetVisibility.Timeline,
|
||||
width: null,
|
||||
height: null,
|
||||
isEdited: false,
|
||||
...dto,
|
||||
});
|
||||
}
|
||||
|
||||
owner(dto: UserLike = {}, builder?: FactoryBuilder<UserFactory>) {
|
||||
this.#owner = build(UserFactory.from(dto), builder);
|
||||
this.value.ownerId = this.#owner.build().id;
|
||||
return this;
|
||||
}
|
||||
|
||||
exif(dto: AssetExifLike = {}, builder?: FactoryBuilder<AssetExifFactory>) {
|
||||
this.#assetExif = build(AssetExifFactory.from(dto), builder);
|
||||
return this;
|
||||
}
|
||||
|
||||
build() {
|
||||
const exif = this.#assetExif?.build();
|
||||
|
||||
return {
|
||||
...this.value,
|
||||
exifInfo: exif as NonNullable<typeof exif>,
|
||||
owner: this.#owner.build(),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
import { AuthDto } from 'src/dtos/auth.dto';
|
||||
import { build } from 'test/factories/builder.factory';
|
||||
import { SharedLinkFactory } from 'test/factories/shared-link.factory';
|
||||
import { FactoryBuilder, SharedLinkLike, UserLike } from 'test/factories/types';
|
||||
import { UserFactory } from 'test/factories/user.factory';
|
||||
|
||||
export class AuthFactory {
|
||||
#user: UserFactory;
|
||||
#sharedLink?: SharedLinkFactory;
|
||||
|
||||
private constructor(user: UserFactory) {
|
||||
this.#user = user;
|
||||
}
|
||||
|
||||
static create(dto: UserLike = {}) {
|
||||
return AuthFactory.from(dto).build();
|
||||
}
|
||||
|
||||
static from(dto: UserLike = {}) {
|
||||
return new AuthFactory(UserFactory.from(dto));
|
||||
}
|
||||
|
||||
apiKey() {
|
||||
// TODO
|
||||
return this;
|
||||
}
|
||||
|
||||
sharedLink(dto: SharedLinkLike = {}, builder?: FactoryBuilder<SharedLinkFactory>) {
|
||||
this.#sharedLink = build(SharedLinkFactory.from(dto), builder);
|
||||
return this;
|
||||
}
|
||||
|
||||
build(): AuthDto {
|
||||
const { id, isAdmin, name, email, quotaUsageInBytes, quotaSizeInBytes } = this.#user.build();
|
||||
|
||||
return {
|
||||
user: {
|
||||
id,
|
||||
isAdmin,
|
||||
name,
|
||||
email,
|
||||
quotaUsageInBytes,
|
||||
quotaSizeInBytes,
|
||||
},
|
||||
sharedLink: this.#sharedLink?.build(),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
import { FactoryBuilder } from 'test/factories/types';
|
||||
|
||||
export const build = <T>(factory: T, builder?: FactoryBuilder<T>) => {
|
||||
return builder ? builder(factory) : factory;
|
||||
};
|
||||
@@ -1,63 +0,0 @@
|
||||
import { Selectable } from 'kysely';
|
||||
import { SharedLinkType } from 'src/enum';
|
||||
import { SharedLinkTable } from 'src/schema/tables/shared-link.table';
|
||||
import { AlbumFactory } from 'test/factories/album.factory';
|
||||
import { build } from 'test/factories/builder.factory';
|
||||
import { AlbumLike, FactoryBuilder, SharedLinkLike, UserLike } from 'test/factories/types';
|
||||
import { UserFactory } from 'test/factories/user.factory';
|
||||
import { factory, newDate, newUuid } from 'test/small.factory';
|
||||
|
||||
export class SharedLinkFactory {
|
||||
#owner: UserFactory;
|
||||
#album?: AlbumFactory;
|
||||
|
||||
private constructor(private readonly value: Selectable<SharedLinkTable>) {
|
||||
value.userId ??= newUuid();
|
||||
this.#owner = UserFactory.from({ id: value.userId });
|
||||
}
|
||||
|
||||
static create(dto: SharedLinkLike = {}) {
|
||||
return SharedLinkFactory.from(dto).build();
|
||||
}
|
||||
|
||||
static from(dto: SharedLinkLike = {}) {
|
||||
const type = dto.type ?? SharedLinkType.Individual;
|
||||
const albumId = (dto.albumId ?? type === SharedLinkType.Album) ? newUuid() : null;
|
||||
|
||||
return new SharedLinkFactory({
|
||||
id: factory.uuid(),
|
||||
description: 'Shared link description',
|
||||
userId: newUuid(),
|
||||
key: factory.buffer(),
|
||||
type,
|
||||
albumId,
|
||||
createdAt: newDate(),
|
||||
expiresAt: null,
|
||||
allowUpload: true,
|
||||
allowDownload: true,
|
||||
showExif: true,
|
||||
password: null,
|
||||
slug: null,
|
||||
...dto,
|
||||
});
|
||||
}
|
||||
|
||||
owner(dto: UserLike = {}, builder?: FactoryBuilder<UserFactory>): SharedLinkFactory {
|
||||
this.#owner = build(UserFactory.from(dto), builder);
|
||||
return this;
|
||||
}
|
||||
|
||||
album(dto: AlbumLike = {}, builder?: FactoryBuilder<AlbumFactory>) {
|
||||
this.#album = build(AlbumFactory.from(dto), builder);
|
||||
return this;
|
||||
}
|
||||
|
||||
build() {
|
||||
return {
|
||||
...this.value,
|
||||
owner: this.#owner.build(),
|
||||
album: this.#album?.build(),
|
||||
assets: [],
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
import { Selectable } from 'kysely';
|
||||
import { AlbumUserTable } from 'src/schema/tables/album-user.table';
|
||||
import { AlbumTable } from 'src/schema/tables/album.table';
|
||||
import { AssetExifTable } from 'src/schema/tables/asset-exif.table';
|
||||
import { AssetTable } from 'src/schema/tables/asset.table';
|
||||
import { SharedLinkTable } from 'src/schema/tables/shared-link.table';
|
||||
import { UserTable } from 'src/schema/tables/user.table';
|
||||
|
||||
export type FactoryBuilder<T, R extends T = T> = (builder: T) => R;
|
||||
|
||||
export type AssetLike = Partial<Selectable<AssetTable>>;
|
||||
export type AssetExifLike = Partial<Selectable<AssetExifTable>>;
|
||||
export type AlbumLike = Partial<Selectable<AlbumTable>>;
|
||||
export type AlbumUserLike = Partial<Selectable<AlbumUserTable>>;
|
||||
export type SharedLinkLike = Partial<Selectable<SharedLinkTable>>;
|
||||
export type UserLike = Partial<Selectable<UserTable>>;
|
||||
@@ -1,46 +0,0 @@
|
||||
import { Selectable } from 'kysely';
|
||||
import { UserStatus } from 'src/enum';
|
||||
import { UserMetadataTable } from 'src/schema/tables/user-metadata.table';
|
||||
import { UserTable } from 'src/schema/tables/user.table';
|
||||
import { UserLike } from 'test/factories/types';
|
||||
import { newDate, newUuid, newUuidV7 } from 'test/small.factory';
|
||||
|
||||
export class UserFactory {
|
||||
private constructor(private value: Selectable<UserTable>) {}
|
||||
|
||||
static create(dto: UserLike = {}) {
|
||||
return UserFactory.from(dto).build();
|
||||
}
|
||||
|
||||
static from(dto: UserLike = {}) {
|
||||
return new UserFactory({
|
||||
id: newUuid(),
|
||||
email: 'test@immich.cloud',
|
||||
password: '',
|
||||
pinCode: null,
|
||||
createdAt: newDate(),
|
||||
profileImagePath: '',
|
||||
isAdmin: false,
|
||||
shouldChangePassword: false,
|
||||
avatarColor: null,
|
||||
deletedAt: null,
|
||||
oauthId: '',
|
||||
updatedAt: newDate(),
|
||||
storageLabel: null,
|
||||
name: 'Test User',
|
||||
quotaSizeInBytes: null,
|
||||
quotaUsageInBytes: 0,
|
||||
status: UserStatus.Active,
|
||||
profileChangedAt: newDate(),
|
||||
updateId: newUuidV7(),
|
||||
...dto,
|
||||
});
|
||||
}
|
||||
|
||||
build() {
|
||||
return {
|
||||
...this.value,
|
||||
metadata: [] as UserMetadataTable[],
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
import {
|
||||
Activity,
|
||||
Album,
|
||||
ApiKey,
|
||||
AssetFace,
|
||||
AssetFile,
|
||||
@@ -24,7 +23,6 @@ import { AssetEditAction, AssetEditActionItem, MirrorAxis } from 'src/dtos/editi
|
||||
import { QueueStatisticsDto } from 'src/dtos/queue.dto';
|
||||
import {
|
||||
AssetFileType,
|
||||
AssetOrder,
|
||||
AssetStatus,
|
||||
AssetType,
|
||||
AssetVisibility,
|
||||
@@ -508,24 +506,6 @@ const personFactory = (person?: Partial<Person>): Person => ({
|
||||
...person,
|
||||
});
|
||||
|
||||
const albumFactory = (album?: Partial<Omit<Album, 'assets'>>) => ({
|
||||
albumName: 'My Album',
|
||||
albumThumbnailAssetId: null,
|
||||
albumUsers: [],
|
||||
assets: [],
|
||||
createdAt: newDate(),
|
||||
deletedAt: null,
|
||||
description: 'Album description',
|
||||
id: newUuid(),
|
||||
isActivityEnabled: false,
|
||||
order: AssetOrder.Desc,
|
||||
ownerId: newUuid(),
|
||||
sharedLinks: [],
|
||||
updatedAt: newDate(),
|
||||
updateId: newUuidV7(),
|
||||
...album,
|
||||
});
|
||||
|
||||
export const factory = {
|
||||
activity: activityFactory,
|
||||
apiKey: apiKeyFactory,
|
||||
@@ -552,7 +532,6 @@ export const factory = {
|
||||
person: personFactory,
|
||||
assetEdit: assetEditFactory,
|
||||
tag: tagFactory,
|
||||
album: albumFactory,
|
||||
uuid: newUuid,
|
||||
buffer: () => Buffer.from('this is a fake buffer'),
|
||||
date: newDate,
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
"@photo-sphere-viewer/settings-plugin": "^5.14.0",
|
||||
"@photo-sphere-viewer/video-plugin": "^5.14.0",
|
||||
"@types/geojson": "^7946.0.16",
|
||||
"@zoom-image/core": "^0.41.0",
|
||||
"@zoom-image/core": "^0.42.0",
|
||||
"@zoom-image/svelte": "^0.3.0",
|
||||
"dom-to-image": "^2.6.0",
|
||||
"fabric": "^6.5.4",
|
||||
@@ -98,7 +98,7 @@
|
||||
"prettier-plugin-sort-json": "^4.1.1",
|
||||
"prettier-plugin-svelte": "^3.3.3",
|
||||
"rollup-plugin-visualizer": "^6.0.0",
|
||||
"svelte": "5.48.0",
|
||||
"svelte": "5.49.1",
|
||||
"svelte-check": "^4.1.5",
|
||||
"svelte-eslint-parser": "^1.3.3",
|
||||
"tailwindcss": "^4.1.7",
|
||||
|
||||
@@ -2,7 +2,6 @@ import TransformTool from '$lib/components/asset-viewer/editor/transform-tool/tr
|
||||
import { transformManager } from '$lib/managers/edit/transform-manager.svelte';
|
||||
import { eventManager } from '$lib/managers/event-manager.svelte';
|
||||
import { waitForWebsocketEvent } from '$lib/stores/websocket';
|
||||
import { getFormatter } from '$lib/utils/i18n';
|
||||
import { editAsset, removeAssetEdits, type AssetEditsDto, type AssetResponseDto } from '@immich/sdk';
|
||||
import { ConfirmModal, modalManager, toastManager } from '@immich/ui';
|
||||
import { mdiCropRotate } from '@mdi/js';
|
||||
@@ -64,12 +63,10 @@ export class EditManager {
|
||||
|
||||
this.isShowingConfirmDialog = true;
|
||||
|
||||
const t = await getFormatter();
|
||||
|
||||
const confirmed = await modalManager.show(ConfirmModal, {
|
||||
title: t('editor_discard_edits_title'),
|
||||
prompt: t('editor_discard_edits_prompt'),
|
||||
confirmText: t('editor_discard_edits_confirm'),
|
||||
title: 'Discard Edits?',
|
||||
prompt: 'You have unsaved edits. Are you sure you want to discard them?',
|
||||
confirmText: 'Discard Edits',
|
||||
});
|
||||
|
||||
this.isShowingConfirmDialog = false;
|
||||
@@ -123,7 +120,6 @@ export class EditManager {
|
||||
}
|
||||
|
||||
const assetId = this.currentAsset.id;
|
||||
const t = await getFormatter();
|
||||
|
||||
try {
|
||||
// Setup the websocket listener before sending the edit request
|
||||
@@ -142,12 +138,12 @@ export class EditManager {
|
||||
|
||||
eventManager.emit('AssetEditsApplied', assetId);
|
||||
|
||||
toastManager.success(t('editor_edits_applied_success'));
|
||||
toastManager.success('Edits applied successfully');
|
||||
this.hasAppliedEdits = true;
|
||||
|
||||
return true;
|
||||
} catch {
|
||||
toastManager.danger(t('editor_edits_applied_error'));
|
||||
toastManager.danger('Failed to apply edits');
|
||||
return false;
|
||||
} finally {
|
||||
this.isApplyingEdits = false;
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
let { asset, onClose }: Props = $props();
|
||||
|
||||
let imgElement: HTMLDivElement | undefined = $state();
|
||||
let cropContainer: HTMLDivElement | undefined = $state();
|
||||
|
||||
onMount(() => {
|
||||
if (!imgElement) {
|
||||
@@ -51,16 +52,23 @@
|
||||
};
|
||||
|
||||
const onSubmit = async () => {
|
||||
if (!imgElement) {
|
||||
if (!cropContainer) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const imgElementHeight = imgElement.offsetHeight;
|
||||
const imgElementWidth = imgElement.offsetWidth;
|
||||
const blob = await domtoimage.toBlob(imgElement, {
|
||||
width: imgElementWidth,
|
||||
height: imgElementHeight,
|
||||
// Get the container dimensions (which is always square due to aspect-square class)
|
||||
const containerSize = cropContainer.offsetWidth;
|
||||
|
||||
// Capture the crop container which maintains 1:1 aspect ratio
|
||||
// Override border-radius and border to avoid transparent corners from rounded-full
|
||||
const blob = await domtoimage.toBlob(cropContainer, {
|
||||
width: containerSize,
|
||||
height: containerSize,
|
||||
style: {
|
||||
borderRadius: '0',
|
||||
border: 'none',
|
||||
},
|
||||
});
|
||||
|
||||
if (await hasTransparentPixels(blob)) {
|
||||
@@ -83,6 +91,7 @@
|
||||
<FormModal size="small" title={$t('set_profile_picture')} {onClose} {onSubmit}>
|
||||
<div class="flex place-items-center items-center justify-center">
|
||||
<div
|
||||
bind:this={cropContainer}
|
||||
class="relative flex aspect-square w-62.5 overflow-hidden rounded-full border-4 border-immich-primary bg-immich-dark-primary dark:border-immich-dark-primary dark:bg-immich-primary"
|
||||
>
|
||||
<PhotoViewer bind:element={imgElement} cursor={{ current: asset }} />
|
||||
|
||||
Reference in New Issue
Block a user