Compare commits

..

1 Commits

Author SHA1 Message Date
Alex
94e315c845 open database 2024-09-04 08:58:30 -05:00
43 changed files with 121 additions and 206 deletions

View File

@@ -125,7 +125,7 @@ When `DB_URL` is defined, the `DB_HOSTNAME`, `DB_PORT`, `DB_USERNAME`, `DB_PASSW
All `REDIS_` variables must be provided to all Immich workers, including `api` and `microservices`.
`REDIS_URL` must start with `ioredis://` and then include a `base64` encoded JSON string for the configuration.
More info can be found in the upstream [ioredis] documentation.
More info can be found in the upstream [ioredis][redis-api] documentation.
When `REDIS_URL` or `REDIS_SOCKET` are defined, the `REDIS_HOSTNAME`, `REDIS_PORT`, `REDIS_USERNAME`, `REDIS_PASSWORD`, and `REDIS_DBINDEX` variables are ignored.
:::
@@ -226,4 +226,4 @@ to use use a Docker secret for the password in the Redis container.
[docker-secrets-example]: https://github.com/docker-library/redis/issues/46#issuecomment-335326234
[docker-secrets-docs]: https://github.com/docker-library/docs/tree/master/postgres#docker-secrets
[docker-secrets]: https://docs.docker.com/engine/swarm/secrets/
[ioredis]: https://ioredis.readthedocs.io/en/latest/README/#connect-to-redis
[redis-api]: https://docs.docker.com/engine/install/ubuntu/#install-using-the-repository

View File

@@ -13698,10 +13698,9 @@
}
},
"node_modules/prism-react-renderer": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/prism-react-renderer/-/prism-react-renderer-2.4.0.tgz",
"integrity": "sha512-327BsVCD/unU4CNLZTWVHyUHKnsqcvj2qbPlQ8MiBE2eq2rgctjigPA1Gp9HLF83kZ20zNN6jgizHJeEsyFYOw==",
"license": "MIT",
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/prism-react-renderer/-/prism-react-renderer-2.3.1.tgz",
"integrity": "sha512-Rdf+HzBLR7KYjzpJ1rSoxT9ioO85nZngQEoFIhL07XhtJHlCU3SOz0GJ6+qvMyQe0Se+BV3qpe6Yd/NmQF5Juw==",
"dependencies": {
"@types/prismjs": "^1.26.0",
"clsx": "^2.0.0"

View File

@@ -851,26 +851,4 @@ describe('/libraries', () => {
expect(existsSync(`${testAssetDir}/temp/directoryB/assetB.png`)).toBe(true);
});
});
describe('POST /search/metadata', () => {
it('should search by originalPath', async () => {
const directory = `some-61498-directory`;
const infix = 'me-61498-di';
utils.createImageFile(`${testAssetDir}/temp/${directory}/assetZ.jpg`);
await scan(admin.accessToken, library.id);
await utils.waitForWebsocketEvent({ event: 'assetUpload', total: 1 });
const { status, body } = await request(app)
.post('/search/metadata')
.send({ originalPath: infix })
.set('Authorization', `Bearer ${admin.accessToken}`);
expect(status).toBe(200);
expect(body.assets).toBeDefined();
expect(Array.isArray(body.assets.items)).toBe(true);
expect(body.assets.items).toHaveLength(1);
expect(body.assets.items[0]).toEqual(expect.objectContaining({ originalFileName: 'assetZ.jpg' }));
});
});
});

View File

@@ -288,6 +288,13 @@ describe('/search', () => {
should: 'should search by takenAfter (no results)',
deferred: () => ({ dto: { takenAfter: today.plus({ hour: 1 }).toJSDate() }, assets: [] }),
},
// {
// should: 'should search by originalPath',
// deferred: () => ({
// dto: { originalPath: asset1.originalPath },
// assets: [asset1],
// }),
// },
{
should: 'should search by originalFilename',
deferred: () => ({

View File

@@ -9,6 +9,7 @@ import 'package:flutter/services.dart';
import 'package:flutter_displaymode/flutter_displaymode.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/utils/sqlite.dart';
import 'package:timezone/data/latest.dart';
import 'package:immich_mobile/constants/locales.dart';
import 'package:immich_mobile/services/background.service.dart';
@@ -54,6 +55,7 @@ void main() async {
Future<void> initApp() async {
await EasyLocalization.ensureInitialized();
await openSqliteDatabase();
if (kReleaseMode && Platform.isAndroid) {
try {

View File

@@ -0,0 +1,22 @@
import 'package:path/path.dart';
import 'package:sqflite/sqflite.dart';
Future<void> openSqliteDatabase() async {
final database = openDatabase(
// Set the path to the database. Note: Using the `join` function from the
// `path` package is best practice to ensure the path is correctly
// constructed for each platform.
join(await getDatabasesPath(), 'immich_database.db'),
// When the database is first created, create a table to store dogs.
onCreate: (db, version) {
// Run the CREATE TABLE statement on the database.
return db.execute(
'CREATE TABLE dogs(id INTEGER PRIMARY KEY, name TEXT, age INTEGER)',
);
},
// Set the version. This executes the onCreate function and provides a
// path to perform database upgrades and downgrades.
version: 1,
);
}

View File

@@ -1454,7 +1454,7 @@ packages:
source: hosted
version: "7.0.0"
sqflite:
dependency: transitive
dependency: "direct main"
description:
name: sqflite
sha256: a43e5a27235518c03ca238e7b4732cf35eabe863a369ceba6cbefa537a66f16d

View File

@@ -38,7 +38,7 @@ dependencies:
share_plus: ^10.0.0
flutter_displaymode: ^0.6.0
scrollable_positioned_list: ^0.3.8
path: ^1.8.3
path: ^1.9.0
path_provider: ^2.1.2
collection: ^1.18.0
http_parser: ^4.0.2
@@ -67,6 +67,7 @@ dependencies:
image_picker: ^1.0.7 # only used to select user profile image from system gallery -> we can simply select an image from within immich?
logging: ^1.2.0
file_picker: ^8.0.0+1
sqflite: ^2.3.3+1
# This is uncommented in F-Droid build script
# Taken from https://github.com/Myzel394/locus/blob/445013d22ec1d759027d4303bd65b30c5c8588c8/pubspec.yaml#L105

View File

@@ -24,7 +24,7 @@
"@opentelemetry/context-async-hooks": "^1.24.0",
"@opentelemetry/exporter-prometheus": "^0.53.0",
"@opentelemetry/sdk-node": "^0.53.0",
"@react-email/components": "^0.0.24",
"@react-email/components": "^0.0.23",
"@socket.io/redis-adapter": "^8.3.0",
"archiver": "^7.0.0",
"async-lock": "^1.4.0",
@@ -5070,9 +5070,9 @@
}
},
"node_modules/@react-email/code-block": {
"version": "0.0.8",
"resolved": "https://registry.npmjs.org/@react-email/code-block/-/code-block-0.0.8.tgz",
"integrity": "sha512-WbuAEpTnB262i9C3SGPmmErgZ4iU5KIpqLUjr7uBJijqldLqZc5x39e8wPWaRdF7NLcShmrc/+G7GJgI1bdC5w==",
"version": "0.0.7",
"resolved": "https://registry.npmjs.org/@react-email/code-block/-/code-block-0.0.7.tgz",
"integrity": "sha512-3lYLwn9rK16I4JmTR/sTzAJMVHzUmmcT1PT27+TXnQyBCfpfDV+VockSg1qhsgCusA/u6j0C97BMsa96AWEbbw==",
"dependencies": {
"prismjs": "1.29.0"
},
@@ -5106,13 +5106,13 @@
}
},
"node_modules/@react-email/components": {
"version": "0.0.24",
"resolved": "https://registry.npmjs.org/@react-email/components/-/components-0.0.24.tgz",
"integrity": "sha512-/DNmfTREaT59UFdkHoIK3BewJ214LfRxmduiil3m7POj+gougkItANu1+BMmgbUATxjf7jH1WoBxo9x/rhFEFw==",
"version": "0.0.23",
"resolved": "https://registry.npmjs.org/@react-email/components/-/components-0.0.23.tgz",
"integrity": "sha512-RcBoffx2IZG6quLBXo5sj3fF47rKmmkiMhG1ZBua4nFjHYlmW8j1uUMyO5HNglxIF9E52NYq4sF7XeZRp9jYjg==",
"dependencies": {
"@react-email/body": "0.0.10",
"@react-email/button": "0.0.17",
"@react-email/code-block": "0.0.8",
"@react-email/code-block": "0.0.7",
"@react-email/code-inline": "0.0.4",
"@react-email/column": "0.0.12",
"@react-email/container": "0.0.14",
@@ -5125,7 +5125,7 @@
"@react-email/link": "0.0.10",
"@react-email/markdown": "0.0.12",
"@react-email/preview": "0.0.11",
"@react-email/render": "1.0.1",
"@react-email/render": "1.0.0",
"@react-email/row": "0.0.10",
"@react-email/section": "0.0.14",
"@react-email/tailwind": "0.1.0",
@@ -5249,9 +5249,9 @@
}
},
"node_modules/@react-email/render": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@react-email/render/-/render-1.0.1.tgz",
"integrity": "sha512-W3gTrcmLOVYnG80QuUp22ReIT/xfLsVJ+n7ghSlG2BITB8evNABn1AO2rGQoXuK84zKtDAlxCdm3hRyIpZdGSA==",
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@react-email/render/-/render-1.0.0.tgz",
"integrity": "sha512-seN2p3JRUSZhwIUiymh9N6ZfhRZ14ywOraQqAokY63DkDeHZW2pA2a6nWpNc/igfOcNyt09Wsoi1Aj0esxhdzw==",
"dependencies": {
"html-to-text": "9.0.5",
"js-beautify": "^1.14.11",
@@ -19280,9 +19280,9 @@
"requires": {}
},
"@react-email/code-block": {
"version": "0.0.8",
"resolved": "https://registry.npmjs.org/@react-email/code-block/-/code-block-0.0.8.tgz",
"integrity": "sha512-WbuAEpTnB262i9C3SGPmmErgZ4iU5KIpqLUjr7uBJijqldLqZc5x39e8wPWaRdF7NLcShmrc/+G7GJgI1bdC5w==",
"version": "0.0.7",
"resolved": "https://registry.npmjs.org/@react-email/code-block/-/code-block-0.0.7.tgz",
"integrity": "sha512-3lYLwn9rK16I4JmTR/sTzAJMVHzUmmcT1PT27+TXnQyBCfpfDV+VockSg1qhsgCusA/u6j0C97BMsa96AWEbbw==",
"requires": {
"prismjs": "1.29.0"
}
@@ -19300,13 +19300,13 @@
"requires": {}
},
"@react-email/components": {
"version": "0.0.24",
"resolved": "https://registry.npmjs.org/@react-email/components/-/components-0.0.24.tgz",
"integrity": "sha512-/DNmfTREaT59UFdkHoIK3BewJ214LfRxmduiil3m7POj+gougkItANu1+BMmgbUATxjf7jH1WoBxo9x/rhFEFw==",
"version": "0.0.23",
"resolved": "https://registry.npmjs.org/@react-email/components/-/components-0.0.23.tgz",
"integrity": "sha512-RcBoffx2IZG6quLBXo5sj3fF47rKmmkiMhG1ZBua4nFjHYlmW8j1uUMyO5HNglxIF9E52NYq4sF7XeZRp9jYjg==",
"requires": {
"@react-email/body": "0.0.10",
"@react-email/button": "0.0.17",
"@react-email/code-block": "0.0.8",
"@react-email/code-block": "0.0.7",
"@react-email/code-inline": "0.0.4",
"@react-email/column": "0.0.12",
"@react-email/container": "0.0.14",
@@ -19319,7 +19319,7 @@
"@react-email/link": "0.0.10",
"@react-email/markdown": "0.0.12",
"@react-email/preview": "0.0.11",
"@react-email/render": "1.0.1",
"@react-email/render": "1.0.0",
"@react-email/row": "0.0.10",
"@react-email/section": "0.0.14",
"@react-email/tailwind": "0.1.0",
@@ -19389,9 +19389,9 @@
"requires": {}
},
"@react-email/render": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@react-email/render/-/render-1.0.1.tgz",
"integrity": "sha512-W3gTrcmLOVYnG80QuUp22ReIT/xfLsVJ+n7ghSlG2BITB8evNABn1AO2rGQoXuK84zKtDAlxCdm3hRyIpZdGSA==",
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@react-email/render/-/render-1.0.0.tgz",
"integrity": "sha512-seN2p3JRUSZhwIUiymh9N6ZfhRZ14ywOraQqAokY63DkDeHZW2pA2a6nWpNc/igfOcNyt09Wsoi1Aj0esxhdzw==",
"requires": {
"html-to-text": "9.0.5",
"js-beautify": "^1.14.11",

View File

@@ -50,7 +50,7 @@
"@opentelemetry/context-async-hooks": "^1.24.0",
"@opentelemetry/exporter-prometheus": "^0.53.0",
"@opentelemetry/sdk-node": "^0.53.0",
"@react-email/components": "^0.0.24",
"@react-email/components": "^0.0.23",
"@socket.io/redis-adapter": "^8.3.0",
"archiver": "^7.0.0",
"async-lock": "^1.4.0",

View File

@@ -44,8 +44,7 @@ export const ASSET_CHECKSUM_CONSTRAINT = 'UQ_assets_owner_checksum';
@Index('IDX_originalPath_libraryId', ['originalPath', 'libraryId'])
@Index('IDX_asset_id_stackId', ['id', 'stackId'])
@Index('idx_originalFileName_trigram', { synchronize: false })
@Index('idx_originalPath_trigram', { synchronize: false })
// For all assets, each originalPath must be unique per user and library
// For all assets, each originalpath must be unique per user and library
export class AssetEntity {
@PrimaryGeneratedColumn('uuid')
id!: string;

View File

@@ -11,7 +11,7 @@ export class AddAssetChecksum1661881837496 implements MigrationInterface {
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`DROP INDEX "IDX_64c507300988dd1764f9a6530c"`);
await queryRunner.query(`DROP INDEX "public"."IDX_64c507300988dd1764f9a6530c"`);
await queryRunner.query(`ALTER TABLE "assets" DROP COLUMN "checksum"`);
}
}

View File

@@ -17,8 +17,8 @@ export class CreateTagsTable1670257571385 implements MigrationInterface {
await queryRunner.query(`ALTER TABLE "tag_asset" DROP CONSTRAINT "FK_e99f31ea4cdf3a2c35c7287eb42"`);
await queryRunner.query(`ALTER TABLE "tag_asset" DROP CONSTRAINT "FK_f8e8a9e893cb5c54907f1b798e9"`);
await queryRunner.query(`ALTER TABLE "tags" DROP CONSTRAINT "FK_92e67dc508c705dd66c94615576"`);
await queryRunner.query(`DROP INDEX "IDX_e99f31ea4cdf3a2c35c7287eb4"`);
await queryRunner.query(`DROP INDEX "IDX_f8e8a9e893cb5c54907f1b798e"`);
await queryRunner.query(`DROP INDEX "public"."IDX_e99f31ea4cdf3a2c35c7287eb4"`);
await queryRunner.query(`DROP INDEX "public"."IDX_f8e8a9e893cb5c54907f1b798e"`);
await queryRunner.query(`DROP TABLE "tag_asset"`);
await queryRunner.query(`DROP TABLE "tags"`);
}

View File

@@ -18,10 +18,10 @@ export class AddSharedLinkTable1673150490490 implements MigrationInterface {
await queryRunner.query(`ALTER TABLE "shared_link__asset" DROP CONSTRAINT "FK_c9fab4aa97ffd1b034f3d6581ab"`);
await queryRunner.query(`ALTER TABLE "shared_link__asset" DROP CONSTRAINT "FK_5b7decce6c8d3db9593d6111a66"`);
await queryRunner.query(`ALTER TABLE "shared_links" DROP CONSTRAINT "FK_0c6ce9058c29f07cdf7014eac66"`);
await queryRunner.query(`DROP INDEX "IDX_c9fab4aa97ffd1b034f3d6581a"`);
await queryRunner.query(`DROP INDEX "IDX_5b7decce6c8d3db9593d6111a6"`);
await queryRunner.query(`DROP INDEX "public"."IDX_c9fab4aa97ffd1b034f3d6581a"`);
await queryRunner.query(`DROP INDEX "public"."IDX_5b7decce6c8d3db9593d6111a6"`);
await queryRunner.query(`DROP TABLE "shared_link__asset"`);
await queryRunner.query(`DROP INDEX "IDX_sharedlink_key"`);
await queryRunner.query(`DROP INDEX "public"."IDX_sharedlink_key"`);
await queryRunner.query(`DROP TABLE "shared_links"`);
}

View File

@@ -44,10 +44,10 @@ export class FixAlbumEntityTypeORM1675812532822 implements MigrationInterface {
await queryRunner.query(`ALTER TABLE "asset_album" DROP CONSTRAINT "FK_4bd1303d199f4e72ccdf998c621"`);
await queryRunner.query(`ALTER TABLE "user_shared_album" DROP CONSTRAINT "FK_427c350ad49bd3935a50baab737"`);
await queryRunner.query(`ALTER TABLE "user_shared_album" DROP CONSTRAINT "FK_f48513bf9bccefd6ff3ad30bd06"`);
await queryRunner.query(`DROP INDEX "IDX_427c350ad49bd3935a50baab73"`);
await queryRunner.query(`DROP INDEX "IDX_f48513bf9bccefd6ff3ad30bd0"`);
await queryRunner.query(`DROP INDEX "IDX_e590fa396c6898fcd4a50e4092"`);
await queryRunner.query(`DROP INDEX "IDX_4bd1303d199f4e72ccdf998c62"`);
await queryRunner.query(`DROP INDEX "public"."IDX_427c350ad49bd3935a50baab73"`);
await queryRunner.query(`DROP INDEX "public"."IDX_f48513bf9bccefd6ff3ad30bd0"`);
await queryRunner.query(`DROP INDEX "public"."IDX_e590fa396c6898fcd4a50e4092"`);
await queryRunner.query(`DROP INDEX "public"."IDX_4bd1303d199f4e72ccdf998c62"`);
await queryRunner.query(`ALTER TABLE "albums" DROP CONSTRAINT "FK_b22c53f35ef20c28c21637c85f4"`);
await queryRunner.query(

View File

@@ -9,7 +9,7 @@ export class AppleContentIdentifier1676437878377 implements MigrationInterface {
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`DROP INDEX "IDX_live_photo_cid"`);
await queryRunner.query(`DROP INDEX "public"."IDX_live_photo_cid"`);
await queryRunner.query(`ALTER TABLE "exif" DROP COLUMN "livePhotoCID"`);
}
}

View File

@@ -6,7 +6,7 @@ export class ExifEntityDefinitionFixes1676848629119 implements MigrationInterfac
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "exif" ALTER COLUMN "description" SET NOT NULL`);
await queryRunner.query(`DROP INDEX "IDX_c0117fdbc50b917ef9067740c4"`);
await queryRunner.query(`DROP INDEX "public"."IDX_c0117fdbc50b917ef9067740c4"`);
await queryRunner.query(`ALTER TABLE "exif" DROP CONSTRAINT "PK_28663352d85078ad0046dafafaa"`);
await queryRunner.query(`ALTER TABLE "exif" DROP COLUMN "id"`);
await queryRunner.query(`ALTER TABLE "exif" DROP CONSTRAINT "FK_c0117fdbc50b917ef9067740c44"`);

View File

@@ -4,7 +4,7 @@ export class SmartInfoEntityDefinitionFixes1676852143506 implements MigrationInt
name = 'SmartInfoEntityDefinitionFixes1676852143506'
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`DROP INDEX "IDX_5e3753aadd956110bf3ec0244a"`);
await queryRunner.query(`DROP INDEX "public"."IDX_5e3753aadd956110bf3ec0244a"`);
await queryRunner.query(`ALTER TABLE "smart_info" DROP CONSTRAINT "PK_0beace66440e9713f5c40470e46"`);
await queryRunner.query(`ALTER TABLE "smart_info" DROP COLUMN "id"`);
await queryRunner.query(`ALTER TABLE "smart_info" DROP CONSTRAINT "FK_5e3753aadd956110bf3ec0244ac"`);

View File

@@ -8,7 +8,7 @@ export class AddIndexForAlbumInSharedLinkTable1677535643119 implements Migration
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`DROP INDEX "IDX_sharedlink_albumId"`);
await queryRunner.query(`DROP INDEX "public"."IDX_sharedlink_albumId"`);
}
}

View File

@@ -4,13 +4,13 @@ export class RequireChecksumNotNull1684328185099 implements MigrationInterface {
name = 'removeNotNullFromChecksumIndex1684328185099';
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`DROP INDEX "IDX_64c507300988dd1764f9a6530c"`);
await queryRunner.query(`DROP INDEX "public"."IDX_64c507300988dd1764f9a6530c"`);
await queryRunner.query(`ALTER TABLE "assets" ALTER COLUMN "checksum" SET NOT NULL`);
await queryRunner.query(`CREATE INDEX "IDX_8d3efe36c0755849395e6ea866" ON "assets" ("checksum") `);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`DROP INDEX "IDX_8d3efe36c0755849395e6ea866"`);
await queryRunner.query(`DROP INDEX "public"."IDX_8d3efe36c0755849395e6ea866"`);
await queryRunner.query(`ALTER TABLE "assets" ALTER COLUMN "checksum" DROP NOT NULL`);
await queryRunner.query(
`CREATE INDEX "IDX_64c507300988dd1764f9a6530c" ON "assets" ("checksum") WHERE ('checksum' IS NOT NULL)`,

View File

@@ -9,7 +9,7 @@ export class AddAuditTable1692804658140 implements MigrationInterface {
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`DROP INDEX "IDX_ownerId_createdAt"`);
await queryRunner.query(`DROP INDEX "public"."IDX_ownerId_createdAt"`);
await queryRunner.query(`DROP TABLE "audit"`);
}

View File

@@ -8,6 +8,6 @@ export class AddOriginalPathIndex1696888644031 implements MigrationInterface {
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`DROP INDEX "IDX_originalPath_libraryId"`);
await queryRunner.query(`DROP INDEX "public"."IDX_originalPath_libraryId"`);
}
}

View File

@@ -15,7 +15,7 @@ export class AddActivity1698693294632 implements MigrationInterface {
await queryRunner.query(`ALTER TABLE "activity" DROP CONSTRAINT "FK_1af8519996fbfb3684b58df280b"`);
await queryRunner.query(`ALTER TABLE "activity" DROP CONSTRAINT "FK_3571467bcbe021f66e2bdce96ea"`);
await queryRunner.query(`ALTER TABLE "activity" DROP CONSTRAINT "FK_8091ea76b12338cb4428d33d782"`);
await queryRunner.query(`DROP INDEX "IDX_activity_like"`);
await queryRunner.query(`DROP INDEX "public"."IDX_activity_like"`);
await queryRunner.query(`DROP TABLE "activity"`);
}

View File

@@ -9,8 +9,8 @@ export class AddAssetFaceIndicies1700752078178 implements MigrationInterface {
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`DROP INDEX "IDX_b463c8edb01364bf2beba08ef1"`);
await queryRunner.query(`DROP INDEX "IDX_bf339a24070dac7e71304ec530"`);
await queryRunner.query(`DROP INDEX "public"."IDX_b463c8edb01364bf2beba08ef1"`);
await queryRunner.query(`DROP INDEX "public"."IDX_bf339a24070dac7e71304ec530"`);
}
}

View File

@@ -8,7 +8,7 @@ export class AddExifCityIndex1701665867595 implements MigrationInterface {
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`DROP INDEX "exif_city"`);
await queryRunner.query(`DROP INDEX "public"."exif_city"`);
}
}

View File

@@ -9,7 +9,7 @@ export class AddAutoStackId1703035138085 implements MigrationInterface {
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`DROP INDEX "IDX_auto_stack_id"`);
await queryRunner.query(`DROP INDEX "public"."IDX_auto_stack_id"`);
await queryRunner.query(`ALTER TABLE "exif" DROP COLUMN "autoStackId"`);
}

View File

@@ -8,6 +8,6 @@ export class AddOriginalFileNameIndex1705306747072 implements MigrationInterface
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`DROP INDEX "IDX_4d66e76dada1ca180f67a205dc"`);
await queryRunner.query(`DROP INDEX "public"."IDX_4d66e76dada1ca180f67a205dc"`);
}
}

View File

@@ -41,7 +41,7 @@ export class CreateAssetStackTable1705197515600 implements MigrationInterface {
);
// update constraints
await queryRunner.query(`DROP INDEX "IDX_b463c8edb01364bf2beba08ef1"`);
await queryRunner.query(`DROP INDEX "public"."IDX_b463c8edb01364bf2beba08ef1"`);
await queryRunner.query(`ALTER TABLE "assets" DROP CONSTRAINT "FK_b463c8edb01364bf2beba08ef19"`);
await queryRunner.query(
`ALTER TABLE "assets" ADD CONSTRAINT "FK_f15d48fa3ea5e4bda05ca8ab207" FOREIGN KEY ("stackId") REFERENCES "asset_stack"("id") ON DELETE SET NULL ON UPDATE CASCADE`,

View File

@@ -17,8 +17,8 @@ export class AddMemoryTable1711637874206 implements MigrationInterface {
await queryRunner.query(`ALTER TABLE "memories_assets_assets" DROP CONSTRAINT "FK_6942ecf52d75d4273de19d2c16f"`);
await queryRunner.query(`ALTER TABLE "memories_assets_assets" DROP CONSTRAINT "FK_984e5c9ab1f04d34538cd32334e"`);
await queryRunner.query(`ALTER TABLE "memories" DROP CONSTRAINT "FK_575842846f0c28fa5da46c99b19"`);
await queryRunner.query(`DROP INDEX "IDX_6942ecf52d75d4273de19d2c16"`);
await queryRunner.query(`DROP INDEX "IDX_984e5c9ab1f04d34538cd32334"`);
await queryRunner.query(`DROP INDEX "public"."IDX_6942ecf52d75d4273de19d2c16"`);
await queryRunner.query(`DROP INDEX "public"."IDX_984e5c9ab1f04d34538cd32334"`);
await queryRunner.query(`DROP TABLE "memories_assets_assets"`);
await queryRunner.query(`DROP TABLE "memories"`);
}

View File

@@ -5,8 +5,8 @@ export class RemoveLibraryType1715804005643 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "assets" DROP CONSTRAINT "FK_9977c3c1de01c3d848039a6b90c"`);
await queryRunner.query(`DROP INDEX "UQ_assets_owner_library_checksum"`);
await queryRunner.query(`DROP INDEX "IDX_originalPath_libraryId"`);
await queryRunner.query(`DROP INDEX "public"."UQ_assets_owner_library_checksum"`);
await queryRunner.query(`DROP INDEX "public"."IDX_originalPath_libraryId"`);
await queryRunner.query(`ALTER TABLE "assets" ALTER COLUMN "libraryId" DROP NOT NULL`);
await queryRunner.query(`
UPDATE "assets"

View File

@@ -27,7 +27,7 @@ export class AddAssetFilesTable1724101822106 implements MigrationInterface {
await queryRunner.query(`UPDATE "assets" SET "thumbnailPath" = "asset_files".path FROM "asset_files" WHERE "assets".id = "asset_files".assetId AND "asset_files".type = 'thumbnail'`);
await queryRunner.query(`ALTER TABLE "asset_files" DROP CONSTRAINT "FK_e3e103a5f1d8bc8402999286040"`);
await queryRunner.query(`DROP INDEX "IDX_asset_files_assetId"`);
await queryRunner.query(`DROP INDEX "public"."IDX_asset_files_assetId"`);
await queryRunner.query(`DROP TABLE "asset_files"`);
}

View File

@@ -1,13 +0,0 @@
import { MigrationInterface, QueryRunner } from "typeorm";
export class AddAssetOriginalPathTrigramIndex1724231348454 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`CREATE INDEX idx_originalPath_trigram ON assets USING gin (f_unaccent("originalPath") gin_trgm_ops)`);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`DROP INDEX "idx_originalPath_trigram"`);
}
}

View File

@@ -47,8 +47,8 @@ export class NestedTagTable1724790460210 implements MigrationInterface {
await queryRunner.query(`ALTER TABLE "tags" ADD "name" character varying NOT NULL`);
await queryRunner.query(`ALTER TABLE "tags" ADD "type" character varying NOT NULL`);
await queryRunner.query(`ALTER TABLE "tags" ADD "renameTagId" uuid`);
await queryRunner.query(`DROP INDEX "IDX_b1a2a7ed45c29179b5ad51548a"`);
await queryRunner.query(`DROP INDEX "IDX_15fbcbc67663c6bfc07b354c22"`);
await queryRunner.query(`DROP INDEX "public"."IDX_b1a2a7ed45c29179b5ad51548a"`);
await queryRunner.query(`DROP INDEX "public"."IDX_15fbcbc67663c6bfc07b354c22"`);
await queryRunner.query(`DROP TABLE "tags_closure"`);
await queryRunner.query(`ALTER TABLE "tags" ADD CONSTRAINT "UQ_tag_name_userId" UNIQUE ("name", "userId")`);
await queryRunner.query(`ALTER TABLE "tags" ADD CONSTRAINT "FK_92e67dc508c705dd66c94615576" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`);

View File

@@ -1,13 +1,13 @@
import { MigrationInterface, QueryRunner } from 'typeorm';
import { MigrationInterface, QueryRunner } from "typeorm";
export class RemoveThumbailAtForMissingThumbnails1725327902980 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`UPDATE "asset_job_status" j SET "thumbnailAt" = NULL WHERE j."thumbnailAt" IS NOT NULL AND NOT EXISTS ( SELECT 1 FROM asset_files f WHERE j."assetId" = f."assetId" AND f."type" = 'thumbnail' AND f."path" IS NOT NULL )`,
);
}
public async down(): Promise<void> {
// do nothing
}
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`UPDATE "asset_job_status" j SET "thumbnailAt" = NULL WHERE j."thumbnailAt" IS NOT NULL AND NOT EXISTS ( SELECT 1 FROM public.asset_files f WHERE j."assetId" = f."assetId" AND f."type" = 'thumbnail' AND f."path" IS NOT NULL )`);
}
public async down(): Promise<void> {
// do nothing
}
}

View File

@@ -75,6 +75,7 @@ FROM
"asset"."fileCreatedAt" >= $1
AND "exifInfo"."lensModel" = $2
AND 1 = 1
AND 1 = 1
AND (
"asset"."isFavorite" = $3
AND "asset"."isArchived" = $4
@@ -168,6 +169,7 @@ WHERE
"asset"."fileCreatedAt" >= $1
AND "exifInfo"."lensModel" = $2
AND 1 = 1
AND 1 = 1
AND (
"asset"."isFavorite" = $3
AND "asset"."isArchived" = $4

View File

@@ -176,7 +176,7 @@ export class MediaService {
async handleGeneratePreview({ id }: IEntityJob): Promise<JobStatus> {
const [{ image }, [asset]] = await Promise.all([
this.configCore.getConfig({ withCache: true }),
this.assetRepository.getByIds([id], { exifInfo: true, files: true }),
this.assetRepository.getByIds([id], { exifInfo: true }),
]);
if (!asset) {
return JobStatus.FAILED;

View File

@@ -434,66 +434,6 @@ describe(MetadataService.name, () => {
});
});
it('should ignore Keywords when TagsList is present', async () => {
assetMock.getByIds.mockResolvedValue([assetStub.image]);
metadataMock.readTags.mockResolvedValue({ Keywords: 'Child', TagsList: ['Parent/Child'] });
tagMock.upsertValue.mockResolvedValue(tagStub.parent);
await sut.handleMetadataExtraction({ id: assetStub.image.id });
expect(tagMock.upsertValue).toHaveBeenNthCalledWith(1, { userId: 'user-id', value: 'Parent', parent: undefined });
expect(tagMock.upsertValue).toHaveBeenNthCalledWith(2, {
userId: 'user-id',
value: 'Parent/Child',
parent: tagStub.parent,
});
});
it('should extract hierarchy from HierarchicalSubject', async () => {
assetMock.getByIds.mockResolvedValue([assetStub.image]);
metadataMock.readTags.mockResolvedValue({ HierarchicalSubject: ['Parent|Child'] });
tagMock.upsertValue.mockResolvedValueOnce(tagStub.parent);
tagMock.upsertValue.mockResolvedValueOnce(tagStub.child);
await sut.handleMetadataExtraction({ id: assetStub.image.id });
expect(tagMock.upsertValue).toHaveBeenNthCalledWith(1, { userId: 'user-id', value: 'Parent', parent: undefined });
expect(tagMock.upsertValue).toHaveBeenNthCalledWith(2, {
userId: 'user-id',
value: 'Parent/Child',
parent: tagStub.parent,
});
});
it('should extract ignore / characters in a HierarchicalSubject tag', async () => {
assetMock.getByIds.mockResolvedValue([assetStub.image]);
metadataMock.readTags.mockResolvedValue({ HierarchicalSubject: ['Mom/Dad'] });
tagMock.upsertValue.mockResolvedValueOnce(tagStub.parent);
await sut.handleMetadataExtraction({ id: assetStub.image.id });
expect(tagMock.upsertValue).toHaveBeenCalledWith({
userId: 'user-id',
value: 'Mom|Dad',
parent: undefined,
});
});
it('should ignore HierarchicalSubject when TagsList is present', async () => {
assetMock.getByIds.mockResolvedValue([assetStub.image]);
metadataMock.readTags.mockResolvedValue({ HierarchicalSubject: ['Parent2|Child2'], TagsList: ['Parent/Child'] });
tagMock.upsertValue.mockResolvedValue(tagStub.parent);
await sut.handleMetadataExtraction({ id: assetStub.image.id });
expect(tagMock.upsertValue).toHaveBeenNthCalledWith(1, { userId: 'user-id', value: 'Parent', parent: undefined });
expect(tagMock.upsertValue).toHaveBeenNthCalledWith(2, {
userId: 'user-id',
value: 'Parent/Child',
parent: tagStub.parent,
});
});
it('should not apply motion photos if asset is video', async () => {
assetMock.getByIds.mockResolvedValue([{ ...assetStub.livePhotoMotionAsset, isVisible: true }]);
mediaMock.probe.mockResolvedValue(probeStub.matroskaContainer);

View File

@@ -355,17 +355,9 @@ export class MetadataService {
const tags: unknown[] = [];
if (exifTags.TagsList) {
tags.push(...exifTags.TagsList);
} else if (exifTags.HierarchicalSubject) {
tags.push(
exifTags.HierarchicalSubject.map((tag) =>
tag
// convert | to /
.replaceAll('/', '<PLACEHOLDER>')
.replaceAll('|', '/')
.replaceAll('<PLACEHOLDER>', '|'),
),
);
} else if (exifTags.Keywords) {
}
if (exifTags.Keywords) {
let keywords = exifTags.Keywords;
if (!Array.isArray(keywords)) {
keywords = [keywords];

View File

@@ -71,15 +71,8 @@ export function searchAssetBuilder(
builder.andWhere(`${builder.alias}.ownerId IN (:...userIds)`, { userIds: options.userIds });
}
if (options.encodedVideoPath) {
builder.andWhere({ encodedVideoPath: options.encodedVideoPath });
}
if (options.originalPath) {
builder.andWhere(`f_unaccent(${builder.alias}.originalPath) ILIKE f_unaccent(:originalPath)`, {
originalPath: `%${options.originalPath}%`,
});
}
const path = _.pick(options, ['encodedVideoPath', 'originalPath']);
builder.andWhere(_.omitBy(path, _.isUndefined));
if (options.originalFileName) {
builder.andWhere(`f_unaccent(${builder.alias}.originalFileName) ILIKE f_unaccent(:originalFileName)`, {

View File

@@ -15,7 +15,7 @@ export type ShortcutOptions<T = HTMLElement> = {
preventDefault?: boolean;
};
export const shouldIgnoreEvent = (event: KeyboardEvent | ClipboardEvent): boolean => {
export const shouldIgnoreShortcut = (event: KeyboardEvent): boolean => {
if (event.target === event.currentTarget) {
return false;
}
@@ -52,7 +52,7 @@ export const shortcuts = <T extends HTMLElement>(
options: ShortcutOptions<T>[],
): ActionReturn<ShortcutOptions<T>[]> => {
function onKeydown(event: KeyboardEvent) {
const ignoreShortcut = shouldIgnoreEvent(event);
const ignoreShortcut = shouldIgnoreShortcut(event);
for (const { shortcut, onShortcut, ignoreInputFields = true, preventDefault = true } of options) {
if (ignoreInputFields && ignoreShortcut) {
continue;

View File

@@ -1,12 +1,11 @@
<script lang="ts">
import { fade } from 'svelte/transition';
import ImmichLogo from './immich-logo.svelte';
import { page } from '$app/stores';
import { shouldIgnoreEvent } from '$lib/actions/shortcut';
import { dragAndDropFilesStore } from '$lib/stores/drag-and-drop-files.store';
import { fileUploadHandler } from '$lib/utils/file-uploader';
import { isAlbumsRoute, isSharedLinkRoute } from '$lib/utils/navigation';
import { t } from 'svelte-i18n';
import { fade } from 'svelte/transition';
import ImmichLogo from './immich-logo.svelte';
$: albumId = isAlbumsRoute($page.route?.id) ? $page.params.albumId : undefined;
$: isShare = isSharedLinkRoute($page.route?.id);
@@ -30,13 +29,7 @@
await handleDataTransfer(e.dataTransfer);
};
const onPaste = (event: ClipboardEvent) => {
if (shouldIgnoreEvent(event)) {
return;
}
return handleDataTransfer(event.clipboardData);
};
const onPaste = ({ clipboardData }: ClipboardEvent) => handleDataTransfer(clipboardData);
const handleDataTransfer = async (dataTransfer?: DataTransfer | null) => {
if (!dataTransfer) {

View File

@@ -3,7 +3,7 @@
import SideBarLink from '$lib/components/shared-components/side-bar/side-bar-link.svelte';
import SideBarSection from '$lib/components/shared-components/side-bar/side-bar-section.svelte';
import { AppRoute } from '$lib/constants';
import { mdiAccountMultipleOutline, mdiBookshelf, mdiCog, mdiServer, mdiSync } from '@mdi/js';
import { mdiAccountMultipleOutline, mdiBookshelf, mdiCog, mdiServer, mdiSync, mdiTools } from '@mdi/js';
import { t } from 'svelte-i18n';
</script>
@@ -14,6 +14,7 @@
<SideBarLink title={$t('settings')} routeId={AppRoute.ADMIN_SETTINGS} icon={mdiCog} />
<SideBarLink title={$t('external_libraries')} routeId={AppRoute.ADMIN_LIBRARY_MANAGEMENT} icon={mdiBookshelf} />
<SideBarLink title={$t('server_stats')} routeId={AppRoute.ADMIN_STATS} icon={mdiServer} />
<SideBarLink title={$t('repair')} routeId={AppRoute.ADMIN_REPAIR} icon={mdiTools} preloadData={false} />
</nav>
<BottomInfo />

View File

@@ -25,7 +25,6 @@
$: pathSegments = data.path ? data.path.split('/') : [];
$: tree = buildTree($foldersStore?.uniquePaths || []);
$: currentPath = $page.url.searchParams.get(QueryParameter.PATH) || '';
$: currentTreeItems = currentPath ? data.currentFolders : Object.keys(tree);
onMount(async () => {
await foldersStore.fetchUniquePaths();
@@ -64,7 +63,7 @@
<Breadcrumbs {pathSegments} icon={mdiFolderHome} title={$t('folders')} {getLink} />
<section class="mt-2">
<TreeItemThumbnails items={currentTreeItems} icon={mdiFolder} onClick={handleNavigation} />
<TreeItemThumbnails items={data.currentFolders} icon={mdiFolder} onClick={handleNavigation} />
<!-- Assets -->
{#if data.pathAssets && data.pathAssets.length > 0}