feat(mobile): ocr support (#26523)
This commit is contained in:
+3603
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,128 @@
|
|||||||
|
class Ocr {
|
||||||
|
final String id;
|
||||||
|
final String assetId;
|
||||||
|
final double x1;
|
||||||
|
final double y1;
|
||||||
|
final double x2;
|
||||||
|
final double y2;
|
||||||
|
final double x3;
|
||||||
|
final double y3;
|
||||||
|
final double x4;
|
||||||
|
final double y4;
|
||||||
|
final double boxScore;
|
||||||
|
final double textScore;
|
||||||
|
final String text;
|
||||||
|
final bool isVisible;
|
||||||
|
|
||||||
|
const Ocr({
|
||||||
|
required this.id,
|
||||||
|
required this.assetId,
|
||||||
|
required this.x1,
|
||||||
|
required this.y1,
|
||||||
|
required this.x2,
|
||||||
|
required this.y2,
|
||||||
|
required this.x3,
|
||||||
|
required this.y3,
|
||||||
|
required this.x4,
|
||||||
|
required this.y4,
|
||||||
|
required this.boxScore,
|
||||||
|
required this.textScore,
|
||||||
|
required this.text,
|
||||||
|
required this.isVisible,
|
||||||
|
});
|
||||||
|
|
||||||
|
Ocr copyWith({
|
||||||
|
String? id,
|
||||||
|
String? assetId,
|
||||||
|
double? x1,
|
||||||
|
double? y1,
|
||||||
|
double? x2,
|
||||||
|
double? y2,
|
||||||
|
double? x3,
|
||||||
|
double? y3,
|
||||||
|
double? x4,
|
||||||
|
double? y4,
|
||||||
|
double? boxScore,
|
||||||
|
double? textScore,
|
||||||
|
String? text,
|
||||||
|
bool? isVisible,
|
||||||
|
}) {
|
||||||
|
return Ocr(
|
||||||
|
id: id ?? this.id,
|
||||||
|
assetId: assetId ?? this.assetId,
|
||||||
|
x1: x1 ?? this.x1,
|
||||||
|
y1: y1 ?? this.y1,
|
||||||
|
x2: x2 ?? this.x2,
|
||||||
|
y2: y2 ?? this.y2,
|
||||||
|
x3: x3 ?? this.x3,
|
||||||
|
y3: y3 ?? this.y3,
|
||||||
|
x4: x4 ?? this.x4,
|
||||||
|
y4: y4 ?? this.y4,
|
||||||
|
boxScore: boxScore ?? this.boxScore,
|
||||||
|
textScore: textScore ?? this.textScore,
|
||||||
|
text: text ?? this.text,
|
||||||
|
isVisible: isVisible ?? this.isVisible,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return '''Ocr {
|
||||||
|
id: $id,
|
||||||
|
assetId: $assetId,
|
||||||
|
x1: $x1,
|
||||||
|
y1: $y1,
|
||||||
|
x2: $x2,
|
||||||
|
y2: $y2,
|
||||||
|
x3: $x3,
|
||||||
|
y3: $y3,
|
||||||
|
x4: $x4,
|
||||||
|
y4: $y4,
|
||||||
|
boxScore: $boxScore,
|
||||||
|
textScore: $textScore,
|
||||||
|
text: $text,
|
||||||
|
isVisible: $isVisible
|
||||||
|
}''';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
if (identical(this, other)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return other is Ocr &&
|
||||||
|
other.id == id &&
|
||||||
|
other.assetId == assetId &&
|
||||||
|
other.x1 == x1 &&
|
||||||
|
other.y1 == y1 &&
|
||||||
|
other.x2 == x2 &&
|
||||||
|
other.y2 == y2 &&
|
||||||
|
other.x3 == x3 &&
|
||||||
|
other.y3 == y3 &&
|
||||||
|
other.x4 == x4 &&
|
||||||
|
other.y4 == y4 &&
|
||||||
|
other.boxScore == boxScore &&
|
||||||
|
other.textScore == textScore &&
|
||||||
|
other.text == text &&
|
||||||
|
other.isVisible == isVisible;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode {
|
||||||
|
return id.hashCode ^
|
||||||
|
assetId.hashCode ^
|
||||||
|
x1.hashCode ^
|
||||||
|
y1.hashCode ^
|
||||||
|
x2.hashCode ^
|
||||||
|
y2.hashCode ^
|
||||||
|
x3.hashCode ^
|
||||||
|
y3.hashCode ^
|
||||||
|
x4.hashCode ^
|
||||||
|
y4.hashCode ^
|
||||||
|
boxScore.hashCode ^
|
||||||
|
textScore.hashCode ^
|
||||||
|
text.hashCode ^
|
||||||
|
isVisible.hashCode;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import 'package:immich_mobile/domain/models/ocr.model.dart';
|
||||||
|
import 'package:immich_mobile/infrastructure/repositories/ocr.repository.dart';
|
||||||
|
|
||||||
|
class OcrService {
|
||||||
|
final OcrRepository _repository;
|
||||||
|
|
||||||
|
const OcrService(this._repository);
|
||||||
|
|
||||||
|
Future<List<Ocr>?> get(String assetId) {
|
||||||
|
return _repository.get(assetId);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -317,6 +317,10 @@ class SyncStreamService {
|
|||||||
return _syncStreamRepository.updateAssetFacesV2(data.cast());
|
return _syncStreamRepository.updateAssetFacesV2(data.cast());
|
||||||
case SyncEntityType.assetFaceDeleteV1:
|
case SyncEntityType.assetFaceDeleteV1:
|
||||||
return _syncStreamRepository.deleteAssetFacesV1(data.cast());
|
return _syncStreamRepository.deleteAssetFacesV1(data.cast());
|
||||||
|
case SyncEntityType.assetOcrV1:
|
||||||
|
return _syncStreamRepository.updateAssetOcrV1(data.cast());
|
||||||
|
case SyncEntityType.assetOcrDeleteV1:
|
||||||
|
return _syncStreamRepository.deleteAssetOcrV1(data.cast());
|
||||||
default:
|
default:
|
||||||
_logger.warning("Unknown sync data type: $type");
|
_logger.warning("Unknown sync data type: $type");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
import 'package:drift/drift.dart';
|
||||||
|
import 'package:immich_mobile/infrastructure/entities/remote_asset.entity.dart';
|
||||||
|
import 'package:immich_mobile/infrastructure/utils/drift_default.mixin.dart';
|
||||||
|
|
||||||
|
@TableIndex.sql('CREATE INDEX IF NOT EXISTS idx_asset_ocr_asset_id ON asset_ocr_entity (asset_id)')
|
||||||
|
class AssetOcrEntity extends Table with DriftDefaultsMixin {
|
||||||
|
const AssetOcrEntity();
|
||||||
|
|
||||||
|
TextColumn get id => text()();
|
||||||
|
|
||||||
|
TextColumn get assetId => text().references(RemoteAssetEntity, #id, onDelete: KeyAction.cascade)();
|
||||||
|
|
||||||
|
RealColumn get x1 => real()();
|
||||||
|
RealColumn get y1 => real()();
|
||||||
|
|
||||||
|
RealColumn get x2 => real()();
|
||||||
|
RealColumn get y2 => real()();
|
||||||
|
|
||||||
|
RealColumn get x3 => real()();
|
||||||
|
RealColumn get y3 => real()();
|
||||||
|
|
||||||
|
RealColumn get x4 => real()();
|
||||||
|
RealColumn get y4 => real()();
|
||||||
|
|
||||||
|
RealColumn get boxScore => real()();
|
||||||
|
RealColumn get textScore => real()();
|
||||||
|
|
||||||
|
TextColumn get recognizedText => text()();
|
||||||
|
|
||||||
|
BoolColumn get isVisible => boolean().withDefault(const Constant(true))();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Set<Column> get primaryKey => {id};
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -6,6 +6,7 @@ import 'package:drift_sqlite_async/drift_sqlite_async.dart';
|
|||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:immich_mobile/infrastructure/entities/asset_edit.entity.dart';
|
import 'package:immich_mobile/infrastructure/entities/asset_edit.entity.dart';
|
||||||
import 'package:immich_mobile/infrastructure/entities/asset_face.entity.dart';
|
import 'package:immich_mobile/infrastructure/entities/asset_face.entity.dart';
|
||||||
|
import 'package:immich_mobile/infrastructure/entities/asset_ocr.entity.dart';
|
||||||
import 'package:immich_mobile/infrastructure/entities/auth_user.entity.dart';
|
import 'package:immich_mobile/infrastructure/entities/auth_user.entity.dart';
|
||||||
import 'package:immich_mobile/infrastructure/entities/exif.entity.dart';
|
import 'package:immich_mobile/infrastructure/entities/exif.entity.dart';
|
||||||
import 'package:immich_mobile/infrastructure/entities/local_album.entity.dart';
|
import 'package:immich_mobile/infrastructure/entities/local_album.entity.dart';
|
||||||
@@ -62,6 +63,7 @@ import 'package:sqlite_async/sqlite_async.dart';
|
|||||||
TrashedLocalAssetEntity,
|
TrashedLocalAssetEntity,
|
||||||
AssetEditEntity,
|
AssetEditEntity,
|
||||||
SettingsEntity,
|
SettingsEntity,
|
||||||
|
AssetOcrEntity,
|
||||||
],
|
],
|
||||||
include: {'package:immich_mobile/infrastructure/entities/merged_asset.drift'},
|
include: {'package:immich_mobile/infrastructure/entities/merged_asset.drift'},
|
||||||
)
|
)
|
||||||
@@ -105,7 +107,7 @@ class Drift extends $Drift {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get schemaVersion => 28;
|
int get schemaVersion => 29;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
MigrationStrategy get migration => MigrationStrategy(
|
MigrationStrategy get migration => MigrationStrategy(
|
||||||
@@ -289,6 +291,10 @@ class Drift extends $Drift {
|
|||||||
from27To28: (m, v28) async {
|
from27To28: (m, v28) async {
|
||||||
await m.createIndex(v28.idxLocalAssetCreatedAt);
|
await m.createIndex(v28.idxLocalAssetCreatedAt);
|
||||||
},
|
},
|
||||||
|
from28To29: (m, v29) async {
|
||||||
|
await m.createTable(v29.assetOcrEntity);
|
||||||
|
await m.createIndex(v29.idxAssetOcrAssetId);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
+20
-4
@@ -45,9 +45,11 @@ import 'package:immich_mobile/infrastructure/entities/asset_edit.entity.drift.da
|
|||||||
as i21;
|
as i21;
|
||||||
import 'package:immich_mobile/infrastructure/entities/settings.entity.drift.dart'
|
import 'package:immich_mobile/infrastructure/entities/settings.entity.drift.dart'
|
||||||
as i22;
|
as i22;
|
||||||
import 'package:immich_mobile/infrastructure/entities/merged_asset.drift.dart'
|
import 'package:immich_mobile/infrastructure/entities/asset_ocr.entity.drift.dart'
|
||||||
as i23;
|
as i23;
|
||||||
import 'package:drift/internal/modular.dart' as i24;
|
import 'package:immich_mobile/infrastructure/entities/merged_asset.drift.dart'
|
||||||
|
as i24;
|
||||||
|
import 'package:drift/internal/modular.dart' as i25;
|
||||||
|
|
||||||
abstract class $Drift extends i0.GeneratedDatabase {
|
abstract class $Drift extends i0.GeneratedDatabase {
|
||||||
$Drift(i0.QueryExecutor e) : super(e);
|
$Drift(i0.QueryExecutor e) : super(e);
|
||||||
@@ -94,9 +96,12 @@ abstract class $Drift extends i0.GeneratedDatabase {
|
|||||||
late final i22.$SettingsEntityTable settingsEntity = i22.$SettingsEntityTable(
|
late final i22.$SettingsEntityTable settingsEntity = i22.$SettingsEntityTable(
|
||||||
this,
|
this,
|
||||||
);
|
);
|
||||||
i23.MergedAssetDrift get mergedAssetDrift => i24.ReadDatabaseContainer(
|
late final i23.$AssetOcrEntityTable assetOcrEntity = i23.$AssetOcrEntityTable(
|
||||||
this,
|
this,
|
||||||
).accessor<i23.MergedAssetDrift>(i23.MergedAssetDrift.new);
|
);
|
||||||
|
i24.MergedAssetDrift get mergedAssetDrift => i25.ReadDatabaseContainer(
|
||||||
|
this,
|
||||||
|
).accessor<i24.MergedAssetDrift>(i24.MergedAssetDrift.new);
|
||||||
@override
|
@override
|
||||||
Iterable<i0.TableInfo<i0.Table, Object?>> get allTables =>
|
Iterable<i0.TableInfo<i0.Table, Object?>> get allTables =>
|
||||||
allSchemaEntities.whereType<i0.TableInfo<i0.Table, Object?>>();
|
allSchemaEntities.whereType<i0.TableInfo<i0.Table, Object?>>();
|
||||||
@@ -134,6 +139,7 @@ abstract class $Drift extends i0.GeneratedDatabase {
|
|||||||
trashedLocalAssetEntity,
|
trashedLocalAssetEntity,
|
||||||
assetEditEntity,
|
assetEditEntity,
|
||||||
settingsEntity,
|
settingsEntity,
|
||||||
|
assetOcrEntity,
|
||||||
i10.idxPartnerSharedWithId,
|
i10.idxPartnerSharedWithId,
|
||||||
i11.idxLatLng,
|
i11.idxLatLng,
|
||||||
i11.idxRemoteExifCity,
|
i11.idxRemoteExifCity,
|
||||||
@@ -146,6 +152,7 @@ abstract class $Drift extends i0.GeneratedDatabase {
|
|||||||
i20.idxTrashedLocalAssetChecksum,
|
i20.idxTrashedLocalAssetChecksum,
|
||||||
i20.idxTrashedLocalAssetAlbum,
|
i20.idxTrashedLocalAssetAlbum,
|
||||||
i21.idxAssetEditAssetId,
|
i21.idxAssetEditAssetId,
|
||||||
|
i23.idxAssetOcrAssetId,
|
||||||
];
|
];
|
||||||
@override
|
@override
|
||||||
i0.StreamQueryUpdateRules
|
i0.StreamQueryUpdateRules
|
||||||
@@ -335,6 +342,13 @@ abstract class $Drift extends i0.GeneratedDatabase {
|
|||||||
),
|
),
|
||||||
result: [i0.TableUpdate('asset_edit_entity', kind: i0.UpdateKind.delete)],
|
result: [i0.TableUpdate('asset_edit_entity', kind: i0.UpdateKind.delete)],
|
||||||
),
|
),
|
||||||
|
i0.WritePropagation(
|
||||||
|
on: i0.TableUpdateQuery.onTableName(
|
||||||
|
'remote_asset_entity',
|
||||||
|
limitUpdateKind: i0.UpdateKind.delete,
|
||||||
|
),
|
||||||
|
result: [i0.TableUpdate('asset_ocr_entity', kind: i0.UpdateKind.delete)],
|
||||||
|
),
|
||||||
]);
|
]);
|
||||||
@override
|
@override
|
||||||
i0.DriftDatabaseOptions get options =>
|
i0.DriftDatabaseOptions get options =>
|
||||||
@@ -398,4 +412,6 @@ class $DriftManager {
|
|||||||
i21.$$AssetEditEntityTableTableManager(_db, _db.assetEditEntity);
|
i21.$$AssetEditEntityTableTableManager(_db, _db.assetEditEntity);
|
||||||
i22.$$SettingsEntityTableTableManager get settingsEntity =>
|
i22.$$SettingsEntityTableTableManager get settingsEntity =>
|
||||||
i22.$$SettingsEntityTableTableManager(_db, _db.settingsEntity);
|
i22.$$SettingsEntityTableTableManager(_db, _db.settingsEntity);
|
||||||
|
i23.$$AssetOcrEntityTableTableManager get assetOcrEntity =>
|
||||||
|
i23.$$AssetOcrEntityTableTableManager(_db, _db.assetOcrEntity);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14631,6 +14631,706 @@ final class Schema28 extends i0.VersionedSchema {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final class Schema29 extends i0.VersionedSchema {
|
||||||
|
Schema29({required super.database}) : super(version: 29);
|
||||||
|
@override
|
||||||
|
late final List<i1.DatabaseSchemaEntity> entities = [
|
||||||
|
userEntity,
|
||||||
|
remoteAssetEntity,
|
||||||
|
stackEntity,
|
||||||
|
localAssetEntity,
|
||||||
|
remoteAlbumEntity,
|
||||||
|
localAlbumEntity,
|
||||||
|
localAlbumAssetEntity,
|
||||||
|
idxLocalAlbumAssetAlbumAsset,
|
||||||
|
idxLocalAssetChecksum,
|
||||||
|
idxLocalAssetCloudId,
|
||||||
|
idxLocalAssetCreatedAt,
|
||||||
|
idxStackPrimaryAssetId,
|
||||||
|
uQRemoteAssetsOwnerChecksum,
|
||||||
|
uQRemoteAssetsOwnerLibraryChecksum,
|
||||||
|
idxRemoteAssetChecksum,
|
||||||
|
idxRemoteAssetStackId,
|
||||||
|
idxRemoteAssetOwnerVisibilityDeletedCreated,
|
||||||
|
authUserEntity,
|
||||||
|
userMetadataEntity,
|
||||||
|
partnerEntity,
|
||||||
|
remoteExifEntity,
|
||||||
|
remoteAlbumAssetEntity,
|
||||||
|
remoteAlbumUserEntity,
|
||||||
|
remoteAssetCloudIdEntity,
|
||||||
|
memoryEntity,
|
||||||
|
memoryAssetEntity,
|
||||||
|
personEntity,
|
||||||
|
assetFaceEntity,
|
||||||
|
storeEntity,
|
||||||
|
trashedLocalAssetEntity,
|
||||||
|
assetEditEntity,
|
||||||
|
settings,
|
||||||
|
assetOcrEntity,
|
||||||
|
idxPartnerSharedWithId,
|
||||||
|
idxLatLng,
|
||||||
|
idxRemoteExifCity,
|
||||||
|
idxRemoteAlbumAssetAlbumAsset,
|
||||||
|
idxRemoteAssetCloudId,
|
||||||
|
idxPersonOwnerId,
|
||||||
|
idxAssetFacePersonId,
|
||||||
|
idxAssetFaceAssetId,
|
||||||
|
idxAssetFaceVisiblePerson,
|
||||||
|
idxTrashedLocalAssetChecksum,
|
||||||
|
idxTrashedLocalAssetAlbum,
|
||||||
|
idxAssetEditAssetId,
|
||||||
|
idxAssetOcrAssetId,
|
||||||
|
];
|
||||||
|
late final Shape33 userEntity = Shape33(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'user_entity',
|
||||||
|
withoutRowId: true,
|
||||||
|
isStrict: true,
|
||||||
|
tableConstraints: ['PRIMARY KEY(id)'],
|
||||||
|
columns: [
|
||||||
|
_column_107,
|
||||||
|
_column_108,
|
||||||
|
_column_109,
|
||||||
|
_column_110,
|
||||||
|
_column_111,
|
||||||
|
_column_112,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape50 remoteAssetEntity = Shape50(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'remote_asset_entity',
|
||||||
|
withoutRowId: true,
|
||||||
|
isStrict: true,
|
||||||
|
tableConstraints: ['PRIMARY KEY(id)'],
|
||||||
|
columns: [
|
||||||
|
_column_108,
|
||||||
|
_column_113,
|
||||||
|
_column_114,
|
||||||
|
_column_115,
|
||||||
|
_column_116,
|
||||||
|
_column_117,
|
||||||
|
_column_118,
|
||||||
|
_column_107,
|
||||||
|
_column_119,
|
||||||
|
_column_120,
|
||||||
|
_column_121,
|
||||||
|
_column_122,
|
||||||
|
_column_123,
|
||||||
|
_column_124,
|
||||||
|
_column_212,
|
||||||
|
_column_125,
|
||||||
|
_column_126,
|
||||||
|
_column_127,
|
||||||
|
_column_128,
|
||||||
|
_column_129,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape35 stackEntity = Shape35(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'stack_entity',
|
||||||
|
withoutRowId: true,
|
||||||
|
isStrict: true,
|
||||||
|
tableConstraints: ['PRIMARY KEY(id)'],
|
||||||
|
columns: [
|
||||||
|
_column_107,
|
||||||
|
_column_114,
|
||||||
|
_column_115,
|
||||||
|
_column_121,
|
||||||
|
_column_130,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape36 localAssetEntity = Shape36(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'local_asset_entity',
|
||||||
|
withoutRowId: true,
|
||||||
|
isStrict: true,
|
||||||
|
tableConstraints: ['PRIMARY KEY(id)'],
|
||||||
|
columns: [
|
||||||
|
_column_108,
|
||||||
|
_column_113,
|
||||||
|
_column_114,
|
||||||
|
_column_115,
|
||||||
|
_column_116,
|
||||||
|
_column_117,
|
||||||
|
_column_118,
|
||||||
|
_column_107,
|
||||||
|
_column_131,
|
||||||
|
_column_120,
|
||||||
|
_column_132,
|
||||||
|
_column_133,
|
||||||
|
_column_134,
|
||||||
|
_column_135,
|
||||||
|
_column_136,
|
||||||
|
_column_137,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape48 remoteAlbumEntity = Shape48(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'remote_album_entity',
|
||||||
|
withoutRowId: true,
|
||||||
|
isStrict: true,
|
||||||
|
tableConstraints: ['PRIMARY KEY(id)'],
|
||||||
|
columns: [
|
||||||
|
_column_107,
|
||||||
|
_column_108,
|
||||||
|
_column_138,
|
||||||
|
_column_114,
|
||||||
|
_column_115,
|
||||||
|
_column_139,
|
||||||
|
_column_140,
|
||||||
|
_column_141,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape38 localAlbumEntity = Shape38(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'local_album_entity',
|
||||||
|
withoutRowId: true,
|
||||||
|
isStrict: true,
|
||||||
|
tableConstraints: ['PRIMARY KEY(id)'],
|
||||||
|
columns: [
|
||||||
|
_column_107,
|
||||||
|
_column_108,
|
||||||
|
_column_115,
|
||||||
|
_column_142,
|
||||||
|
_column_143,
|
||||||
|
_column_144,
|
||||||
|
_column_145,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape39 localAlbumAssetEntity = Shape39(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'local_album_asset_entity',
|
||||||
|
withoutRowId: true,
|
||||||
|
isStrict: true,
|
||||||
|
tableConstraints: ['PRIMARY KEY(asset_id, album_id)'],
|
||||||
|
columns: [_column_146, _column_147, _column_145],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
final i1.Index idxLocalAlbumAssetAlbumAsset = i1.Index(
|
||||||
|
'idx_local_album_asset_album_asset',
|
||||||
|
'CREATE INDEX IF NOT EXISTS idx_local_album_asset_album_asset ON local_album_asset_entity (album_id, asset_id)',
|
||||||
|
);
|
||||||
|
final i1.Index idxLocalAssetChecksum = i1.Index(
|
||||||
|
'idx_local_asset_checksum',
|
||||||
|
'CREATE INDEX IF NOT EXISTS idx_local_asset_checksum ON local_asset_entity (checksum)',
|
||||||
|
);
|
||||||
|
final i1.Index idxLocalAssetCloudId = i1.Index(
|
||||||
|
'idx_local_asset_cloud_id',
|
||||||
|
'CREATE INDEX IF NOT EXISTS idx_local_asset_cloud_id ON local_asset_entity (i_cloud_id)',
|
||||||
|
);
|
||||||
|
final i1.Index idxLocalAssetCreatedAt = i1.Index(
|
||||||
|
'idx_local_asset_created_at',
|
||||||
|
'CREATE INDEX IF NOT EXISTS idx_local_asset_created_at ON local_asset_entity (created_at)',
|
||||||
|
);
|
||||||
|
final i1.Index idxStackPrimaryAssetId = i1.Index(
|
||||||
|
'idx_stack_primary_asset_id',
|
||||||
|
'CREATE INDEX IF NOT EXISTS idx_stack_primary_asset_id ON stack_entity (primary_asset_id)',
|
||||||
|
);
|
||||||
|
final i1.Index uQRemoteAssetsOwnerChecksum = i1.Index(
|
||||||
|
'UQ_remote_assets_owner_checksum',
|
||||||
|
'CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_checksum ON remote_asset_entity (owner_id, checksum) WHERE(library_id IS NULL)',
|
||||||
|
);
|
||||||
|
final i1.Index uQRemoteAssetsOwnerLibraryChecksum = i1.Index(
|
||||||
|
'UQ_remote_assets_owner_library_checksum',
|
||||||
|
'CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_library_checksum ON remote_asset_entity (owner_id, library_id, checksum) WHERE(library_id IS NOT NULL)',
|
||||||
|
);
|
||||||
|
final i1.Index idxRemoteAssetChecksum = i1.Index(
|
||||||
|
'idx_remote_asset_checksum',
|
||||||
|
'CREATE INDEX IF NOT EXISTS idx_remote_asset_checksum ON remote_asset_entity (checksum)',
|
||||||
|
);
|
||||||
|
final i1.Index idxRemoteAssetStackId = i1.Index(
|
||||||
|
'idx_remote_asset_stack_id',
|
||||||
|
'CREATE INDEX IF NOT EXISTS idx_remote_asset_stack_id ON remote_asset_entity (stack_id)',
|
||||||
|
);
|
||||||
|
final i1.Index idxRemoteAssetOwnerVisibilityDeletedCreated = i1.Index(
|
||||||
|
'idx_remote_asset_owner_visibility_deleted_created',
|
||||||
|
'CREATE INDEX IF NOT EXISTS idx_remote_asset_owner_visibility_deleted_created ON remote_asset_entity (owner_id, visibility, deleted_at, created_at DESC)',
|
||||||
|
);
|
||||||
|
late final Shape40 authUserEntity = Shape40(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'auth_user_entity',
|
||||||
|
withoutRowId: true,
|
||||||
|
isStrict: true,
|
||||||
|
tableConstraints: ['PRIMARY KEY(id)'],
|
||||||
|
columns: [
|
||||||
|
_column_107,
|
||||||
|
_column_108,
|
||||||
|
_column_109,
|
||||||
|
_column_148,
|
||||||
|
_column_110,
|
||||||
|
_column_111,
|
||||||
|
_column_149,
|
||||||
|
_column_150,
|
||||||
|
_column_151,
|
||||||
|
_column_152,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape4 userMetadataEntity = Shape4(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'user_metadata_entity',
|
||||||
|
withoutRowId: true,
|
||||||
|
isStrict: true,
|
||||||
|
tableConstraints: ['PRIMARY KEY(user_id, "key")'],
|
||||||
|
columns: [_column_153, _column_154, _column_155],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape41 partnerEntity = Shape41(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'partner_entity',
|
||||||
|
withoutRowId: true,
|
||||||
|
isStrict: true,
|
||||||
|
tableConstraints: ['PRIMARY KEY(shared_by_id, shared_with_id)'],
|
||||||
|
columns: [_column_156, _column_157, _column_158],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape42 remoteExifEntity = Shape42(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'remote_exif_entity',
|
||||||
|
withoutRowId: true,
|
||||||
|
isStrict: true,
|
||||||
|
tableConstraints: ['PRIMARY KEY(asset_id)'],
|
||||||
|
columns: [
|
||||||
|
_column_159,
|
||||||
|
_column_160,
|
||||||
|
_column_161,
|
||||||
|
_column_162,
|
||||||
|
_column_163,
|
||||||
|
_column_164,
|
||||||
|
_column_117,
|
||||||
|
_column_116,
|
||||||
|
_column_165,
|
||||||
|
_column_166,
|
||||||
|
_column_167,
|
||||||
|
_column_168,
|
||||||
|
_column_135,
|
||||||
|
_column_136,
|
||||||
|
_column_169,
|
||||||
|
_column_170,
|
||||||
|
_column_171,
|
||||||
|
_column_172,
|
||||||
|
_column_173,
|
||||||
|
_column_174,
|
||||||
|
_column_175,
|
||||||
|
_column_176,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape7 remoteAlbumAssetEntity = Shape7(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'remote_album_asset_entity',
|
||||||
|
withoutRowId: true,
|
||||||
|
isStrict: true,
|
||||||
|
tableConstraints: ['PRIMARY KEY(asset_id, album_id)'],
|
||||||
|
columns: [_column_159, _column_177],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape10 remoteAlbumUserEntity = Shape10(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'remote_album_user_entity',
|
||||||
|
withoutRowId: true,
|
||||||
|
isStrict: true,
|
||||||
|
tableConstraints: ['PRIMARY KEY(album_id, user_id)'],
|
||||||
|
columns: [_column_177, _column_153, _column_178],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape43 remoteAssetCloudIdEntity = Shape43(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'remote_asset_cloud_id_entity',
|
||||||
|
withoutRowId: true,
|
||||||
|
isStrict: true,
|
||||||
|
tableConstraints: ['PRIMARY KEY(asset_id)'],
|
||||||
|
columns: [
|
||||||
|
_column_159,
|
||||||
|
_column_179,
|
||||||
|
_column_180,
|
||||||
|
_column_134,
|
||||||
|
_column_135,
|
||||||
|
_column_136,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape44 memoryEntity = Shape44(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'memory_entity',
|
||||||
|
withoutRowId: true,
|
||||||
|
isStrict: true,
|
||||||
|
tableConstraints: ['PRIMARY KEY(id)'],
|
||||||
|
columns: [
|
||||||
|
_column_107,
|
||||||
|
_column_114,
|
||||||
|
_column_115,
|
||||||
|
_column_124,
|
||||||
|
_column_121,
|
||||||
|
_column_113,
|
||||||
|
_column_181,
|
||||||
|
_column_182,
|
||||||
|
_column_183,
|
||||||
|
_column_184,
|
||||||
|
_column_185,
|
||||||
|
_column_186,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape12 memoryAssetEntity = Shape12(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'memory_asset_entity',
|
||||||
|
withoutRowId: true,
|
||||||
|
isStrict: true,
|
||||||
|
tableConstraints: ['PRIMARY KEY(asset_id, memory_id)'],
|
||||||
|
columns: [_column_159, _column_187],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape45 personEntity = Shape45(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'person_entity',
|
||||||
|
withoutRowId: true,
|
||||||
|
isStrict: true,
|
||||||
|
tableConstraints: ['PRIMARY KEY(id)'],
|
||||||
|
columns: [
|
||||||
|
_column_107,
|
||||||
|
_column_114,
|
||||||
|
_column_115,
|
||||||
|
_column_121,
|
||||||
|
_column_108,
|
||||||
|
_column_188,
|
||||||
|
_column_189,
|
||||||
|
_column_190,
|
||||||
|
_column_191,
|
||||||
|
_column_192,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape46 assetFaceEntity = Shape46(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'asset_face_entity',
|
||||||
|
withoutRowId: true,
|
||||||
|
isStrict: true,
|
||||||
|
tableConstraints: ['PRIMARY KEY(id)'],
|
||||||
|
columns: [
|
||||||
|
_column_107,
|
||||||
|
_column_159,
|
||||||
|
_column_193,
|
||||||
|
_column_194,
|
||||||
|
_column_195,
|
||||||
|
_column_196,
|
||||||
|
_column_197,
|
||||||
|
_column_198,
|
||||||
|
_column_199,
|
||||||
|
_column_200,
|
||||||
|
_column_201,
|
||||||
|
_column_124,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape18 storeEntity = Shape18(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'store_entity',
|
||||||
|
withoutRowId: true,
|
||||||
|
isStrict: true,
|
||||||
|
tableConstraints: ['PRIMARY KEY(id)'],
|
||||||
|
columns: [_column_202, _column_203, _column_204],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape47 trashedLocalAssetEntity = Shape47(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'trashed_local_asset_entity',
|
||||||
|
withoutRowId: true,
|
||||||
|
isStrict: true,
|
||||||
|
tableConstraints: ['PRIMARY KEY(id, album_id)'],
|
||||||
|
columns: [
|
||||||
|
_column_108,
|
||||||
|
_column_113,
|
||||||
|
_column_114,
|
||||||
|
_column_115,
|
||||||
|
_column_116,
|
||||||
|
_column_117,
|
||||||
|
_column_118,
|
||||||
|
_column_107,
|
||||||
|
_column_205,
|
||||||
|
_column_131,
|
||||||
|
_column_120,
|
||||||
|
_column_132,
|
||||||
|
_column_206,
|
||||||
|
_column_137,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape32 assetEditEntity = Shape32(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'asset_edit_entity',
|
||||||
|
withoutRowId: true,
|
||||||
|
isStrict: true,
|
||||||
|
tableConstraints: ['PRIMARY KEY(id)'],
|
||||||
|
columns: [
|
||||||
|
_column_107,
|
||||||
|
_column_159,
|
||||||
|
_column_207,
|
||||||
|
_column_208,
|
||||||
|
_column_209,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape49 settings = Shape49(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'settings',
|
||||||
|
withoutRowId: true,
|
||||||
|
isStrict: true,
|
||||||
|
tableConstraints: ['PRIMARY KEY("key")'],
|
||||||
|
columns: [_column_210, _column_211, _column_115],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
late final Shape51 assetOcrEntity = Shape51(
|
||||||
|
source: i0.VersionedTable(
|
||||||
|
entityName: 'asset_ocr_entity',
|
||||||
|
withoutRowId: true,
|
||||||
|
isStrict: true,
|
||||||
|
tableConstraints: ['PRIMARY KEY(id)'],
|
||||||
|
columns: [
|
||||||
|
_column_107,
|
||||||
|
_column_159,
|
||||||
|
_column_213,
|
||||||
|
_column_214,
|
||||||
|
_column_215,
|
||||||
|
_column_216,
|
||||||
|
_column_217,
|
||||||
|
_column_218,
|
||||||
|
_column_219,
|
||||||
|
_column_220,
|
||||||
|
_column_221,
|
||||||
|
_column_222,
|
||||||
|
_column_223,
|
||||||
|
_column_201,
|
||||||
|
],
|
||||||
|
attachedDatabase: database,
|
||||||
|
),
|
||||||
|
alias: null,
|
||||||
|
);
|
||||||
|
final i1.Index idxPartnerSharedWithId = i1.Index(
|
||||||
|
'idx_partner_shared_with_id',
|
||||||
|
'CREATE INDEX IF NOT EXISTS idx_partner_shared_with_id ON partner_entity (shared_with_id)',
|
||||||
|
);
|
||||||
|
final i1.Index idxLatLng = i1.Index(
|
||||||
|
'idx_lat_lng',
|
||||||
|
'CREATE INDEX IF NOT EXISTS idx_lat_lng ON remote_exif_entity (latitude, longitude)',
|
||||||
|
);
|
||||||
|
final i1.Index idxRemoteExifCity = i1.Index(
|
||||||
|
'idx_remote_exif_city',
|
||||||
|
'CREATE INDEX IF NOT EXISTS idx_remote_exif_city ON remote_exif_entity (city) WHERE city IS NOT NULL',
|
||||||
|
);
|
||||||
|
final i1.Index idxRemoteAlbumAssetAlbumAsset = i1.Index(
|
||||||
|
'idx_remote_album_asset_album_asset',
|
||||||
|
'CREATE INDEX IF NOT EXISTS idx_remote_album_asset_album_asset ON remote_album_asset_entity (album_id, asset_id)',
|
||||||
|
);
|
||||||
|
final i1.Index idxRemoteAssetCloudId = i1.Index(
|
||||||
|
'idx_remote_asset_cloud_id',
|
||||||
|
'CREATE INDEX IF NOT EXISTS idx_remote_asset_cloud_id ON remote_asset_cloud_id_entity (cloud_id)',
|
||||||
|
);
|
||||||
|
final i1.Index idxPersonOwnerId = i1.Index(
|
||||||
|
'idx_person_owner_id',
|
||||||
|
'CREATE INDEX IF NOT EXISTS idx_person_owner_id ON person_entity (owner_id)',
|
||||||
|
);
|
||||||
|
final i1.Index idxAssetFacePersonId = i1.Index(
|
||||||
|
'idx_asset_face_person_id',
|
||||||
|
'CREATE INDEX IF NOT EXISTS idx_asset_face_person_id ON asset_face_entity (person_id)',
|
||||||
|
);
|
||||||
|
final i1.Index idxAssetFaceAssetId = i1.Index(
|
||||||
|
'idx_asset_face_asset_id',
|
||||||
|
'CREATE INDEX IF NOT EXISTS idx_asset_face_asset_id ON asset_face_entity (asset_id)',
|
||||||
|
);
|
||||||
|
final i1.Index idxAssetFaceVisiblePerson = i1.Index(
|
||||||
|
'idx_asset_face_visible_person',
|
||||||
|
'CREATE INDEX IF NOT EXISTS idx_asset_face_visible_person ON asset_face_entity (person_id, asset_id) WHERE is_visible = 1 AND deleted_at IS NULL',
|
||||||
|
);
|
||||||
|
final i1.Index idxTrashedLocalAssetChecksum = i1.Index(
|
||||||
|
'idx_trashed_local_asset_checksum',
|
||||||
|
'CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_checksum ON trashed_local_asset_entity (checksum)',
|
||||||
|
);
|
||||||
|
final i1.Index idxTrashedLocalAssetAlbum = i1.Index(
|
||||||
|
'idx_trashed_local_asset_album',
|
||||||
|
'CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_album ON trashed_local_asset_entity (album_id)',
|
||||||
|
);
|
||||||
|
final i1.Index idxAssetEditAssetId = i1.Index(
|
||||||
|
'idx_asset_edit_asset_id',
|
||||||
|
'CREATE INDEX IF NOT EXISTS idx_asset_edit_asset_id ON asset_edit_entity (asset_id)',
|
||||||
|
);
|
||||||
|
final i1.Index idxAssetOcrAssetId = i1.Index(
|
||||||
|
'idx_asset_ocr_asset_id',
|
||||||
|
'CREATE INDEX IF NOT EXISTS idx_asset_ocr_asset_id ON asset_ocr_entity (asset_id)',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
class Shape51 extends i0.VersionedTable {
|
||||||
|
Shape51({required super.source, required super.alias}) : super.aliased();
|
||||||
|
i1.GeneratedColumn<String> get id =>
|
||||||
|
columnsByName['id']! as i1.GeneratedColumn<String>;
|
||||||
|
i1.GeneratedColumn<String> get assetId =>
|
||||||
|
columnsByName['asset_id']! as i1.GeneratedColumn<String>;
|
||||||
|
i1.GeneratedColumn<double> get x1 =>
|
||||||
|
columnsByName['x1']! as i1.GeneratedColumn<double>;
|
||||||
|
i1.GeneratedColumn<double> get y1 =>
|
||||||
|
columnsByName['y1']! as i1.GeneratedColumn<double>;
|
||||||
|
i1.GeneratedColumn<double> get x2 =>
|
||||||
|
columnsByName['x2']! as i1.GeneratedColumn<double>;
|
||||||
|
i1.GeneratedColumn<double> get y2 =>
|
||||||
|
columnsByName['y2']! as i1.GeneratedColumn<double>;
|
||||||
|
i1.GeneratedColumn<double> get x3 =>
|
||||||
|
columnsByName['x3']! as i1.GeneratedColumn<double>;
|
||||||
|
i1.GeneratedColumn<double> get y3 =>
|
||||||
|
columnsByName['y3']! as i1.GeneratedColumn<double>;
|
||||||
|
i1.GeneratedColumn<double> get x4 =>
|
||||||
|
columnsByName['x4']! as i1.GeneratedColumn<double>;
|
||||||
|
i1.GeneratedColumn<double> get y4 =>
|
||||||
|
columnsByName['y4']! as i1.GeneratedColumn<double>;
|
||||||
|
i1.GeneratedColumn<double> get boxScore =>
|
||||||
|
columnsByName['box_score']! as i1.GeneratedColumn<double>;
|
||||||
|
i1.GeneratedColumn<double> get textScore =>
|
||||||
|
columnsByName['text_score']! as i1.GeneratedColumn<double>;
|
||||||
|
i1.GeneratedColumn<String> get recognizedText =>
|
||||||
|
columnsByName['recognized_text']! as i1.GeneratedColumn<String>;
|
||||||
|
i1.GeneratedColumn<int> get isVisible =>
|
||||||
|
columnsByName['is_visible']! as i1.GeneratedColumn<int>;
|
||||||
|
}
|
||||||
|
|
||||||
|
i1.GeneratedColumn<double> _column_213(String aliasedName) =>
|
||||||
|
i1.GeneratedColumn<double>(
|
||||||
|
'x1',
|
||||||
|
aliasedName,
|
||||||
|
false,
|
||||||
|
type: i1.DriftSqlType.double,
|
||||||
|
$customConstraints: 'NOT NULL',
|
||||||
|
);
|
||||||
|
i1.GeneratedColumn<double> _column_214(String aliasedName) =>
|
||||||
|
i1.GeneratedColumn<double>(
|
||||||
|
'y1',
|
||||||
|
aliasedName,
|
||||||
|
false,
|
||||||
|
type: i1.DriftSqlType.double,
|
||||||
|
$customConstraints: 'NOT NULL',
|
||||||
|
);
|
||||||
|
i1.GeneratedColumn<double> _column_215(String aliasedName) =>
|
||||||
|
i1.GeneratedColumn<double>(
|
||||||
|
'x2',
|
||||||
|
aliasedName,
|
||||||
|
false,
|
||||||
|
type: i1.DriftSqlType.double,
|
||||||
|
$customConstraints: 'NOT NULL',
|
||||||
|
);
|
||||||
|
i1.GeneratedColumn<double> _column_216(String aliasedName) =>
|
||||||
|
i1.GeneratedColumn<double>(
|
||||||
|
'y2',
|
||||||
|
aliasedName,
|
||||||
|
false,
|
||||||
|
type: i1.DriftSqlType.double,
|
||||||
|
$customConstraints: 'NOT NULL',
|
||||||
|
);
|
||||||
|
i1.GeneratedColumn<double> _column_217(String aliasedName) =>
|
||||||
|
i1.GeneratedColumn<double>(
|
||||||
|
'x3',
|
||||||
|
aliasedName,
|
||||||
|
false,
|
||||||
|
type: i1.DriftSqlType.double,
|
||||||
|
$customConstraints: 'NOT NULL',
|
||||||
|
);
|
||||||
|
i1.GeneratedColumn<double> _column_218(String aliasedName) =>
|
||||||
|
i1.GeneratedColumn<double>(
|
||||||
|
'y3',
|
||||||
|
aliasedName,
|
||||||
|
false,
|
||||||
|
type: i1.DriftSqlType.double,
|
||||||
|
$customConstraints: 'NOT NULL',
|
||||||
|
);
|
||||||
|
i1.GeneratedColumn<double> _column_219(String aliasedName) =>
|
||||||
|
i1.GeneratedColumn<double>(
|
||||||
|
'x4',
|
||||||
|
aliasedName,
|
||||||
|
false,
|
||||||
|
type: i1.DriftSqlType.double,
|
||||||
|
$customConstraints: 'NOT NULL',
|
||||||
|
);
|
||||||
|
i1.GeneratedColumn<double> _column_220(String aliasedName) =>
|
||||||
|
i1.GeneratedColumn<double>(
|
||||||
|
'y4',
|
||||||
|
aliasedName,
|
||||||
|
false,
|
||||||
|
type: i1.DriftSqlType.double,
|
||||||
|
$customConstraints: 'NOT NULL',
|
||||||
|
);
|
||||||
|
i1.GeneratedColumn<double> _column_221(String aliasedName) =>
|
||||||
|
i1.GeneratedColumn<double>(
|
||||||
|
'box_score',
|
||||||
|
aliasedName,
|
||||||
|
false,
|
||||||
|
type: i1.DriftSqlType.double,
|
||||||
|
$customConstraints: 'NOT NULL',
|
||||||
|
);
|
||||||
|
i1.GeneratedColumn<double> _column_222(String aliasedName) =>
|
||||||
|
i1.GeneratedColumn<double>(
|
||||||
|
'text_score',
|
||||||
|
aliasedName,
|
||||||
|
false,
|
||||||
|
type: i1.DriftSqlType.double,
|
||||||
|
$customConstraints: 'NOT NULL',
|
||||||
|
);
|
||||||
|
i1.GeneratedColumn<String> _column_223(String aliasedName) =>
|
||||||
|
i1.GeneratedColumn<String>(
|
||||||
|
'recognized_text',
|
||||||
|
aliasedName,
|
||||||
|
false,
|
||||||
|
type: i1.DriftSqlType.string,
|
||||||
|
$customConstraints: 'NOT NULL',
|
||||||
|
);
|
||||||
i0.MigrationStepWithVersion migrationSteps({
|
i0.MigrationStepWithVersion migrationSteps({
|
||||||
required Future<void> Function(i1.Migrator m, Schema2 schema) from1To2,
|
required Future<void> Function(i1.Migrator m, Schema2 schema) from1To2,
|
||||||
required Future<void> Function(i1.Migrator m, Schema3 schema) from2To3,
|
required Future<void> Function(i1.Migrator m, Schema3 schema) from2To3,
|
||||||
@@ -14659,6 +15359,7 @@ i0.MigrationStepWithVersion migrationSteps({
|
|||||||
required Future<void> Function(i1.Migrator m, Schema26 schema) from25To26,
|
required Future<void> Function(i1.Migrator m, Schema26 schema) from25To26,
|
||||||
required Future<void> Function(i1.Migrator m, Schema27 schema) from26To27,
|
required Future<void> Function(i1.Migrator m, Schema27 schema) from26To27,
|
||||||
required Future<void> Function(i1.Migrator m, Schema28 schema) from27To28,
|
required Future<void> Function(i1.Migrator m, Schema28 schema) from27To28,
|
||||||
|
required Future<void> Function(i1.Migrator m, Schema29 schema) from28To29,
|
||||||
}) {
|
}) {
|
||||||
return (currentVersion, database) async {
|
return (currentVersion, database) async {
|
||||||
switch (currentVersion) {
|
switch (currentVersion) {
|
||||||
@@ -14797,6 +15498,11 @@ i0.MigrationStepWithVersion migrationSteps({
|
|||||||
final migrator = i1.Migrator(database, schema);
|
final migrator = i1.Migrator(database, schema);
|
||||||
await from27To28(migrator, schema);
|
await from27To28(migrator, schema);
|
||||||
return 28;
|
return 28;
|
||||||
|
case 28:
|
||||||
|
final schema = Schema29(database: database);
|
||||||
|
final migrator = i1.Migrator(database, schema);
|
||||||
|
await from28To29(migrator, schema);
|
||||||
|
return 29;
|
||||||
default:
|
default:
|
||||||
throw ArgumentError.value('Unknown migration from $currentVersion');
|
throw ArgumentError.value('Unknown migration from $currentVersion');
|
||||||
}
|
}
|
||||||
@@ -14831,6 +15537,7 @@ i1.OnUpgrade stepByStep({
|
|||||||
required Future<void> Function(i1.Migrator m, Schema26 schema) from25To26,
|
required Future<void> Function(i1.Migrator m, Schema26 schema) from25To26,
|
||||||
required Future<void> Function(i1.Migrator m, Schema27 schema) from26To27,
|
required Future<void> Function(i1.Migrator m, Schema27 schema) from26To27,
|
||||||
required Future<void> Function(i1.Migrator m, Schema28 schema) from27To28,
|
required Future<void> Function(i1.Migrator m, Schema28 schema) from27To28,
|
||||||
|
required Future<void> Function(i1.Migrator m, Schema29 schema) from28To29,
|
||||||
}) => i0.VersionedSchema.stepByStepHelper(
|
}) => i0.VersionedSchema.stepByStepHelper(
|
||||||
step: migrationSteps(
|
step: migrationSteps(
|
||||||
from1To2: from1To2,
|
from1To2: from1To2,
|
||||||
@@ -14860,5 +15567,6 @@ i1.OnUpgrade stepByStep({
|
|||||||
from25To26: from25To26,
|
from25To26: from25To26,
|
||||||
from26To27: from26To27,
|
from26To27: from26To27,
|
||||||
from27To28: from27To28,
|
from27To28: from27To28,
|
||||||
|
from28To29: from28To29,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
import 'package:immich_mobile/domain/models/ocr.model.dart';
|
||||||
|
import 'package:immich_mobile/infrastructure/entities/asset_ocr.entity.drift.dart';
|
||||||
|
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
|
||||||
|
import 'package:drift/drift.dart';
|
||||||
|
|
||||||
|
class OcrRepository extends DriftDatabaseRepository {
|
||||||
|
final Drift _db;
|
||||||
|
const OcrRepository(this._db) : super(_db);
|
||||||
|
|
||||||
|
Future<List<Ocr>> get(String assetId) async {
|
||||||
|
final query = _db.select(_db.assetOcrEntity)
|
||||||
|
..where((row) => row.assetId.equals(assetId) & row.isVisible.equals(true));
|
||||||
|
|
||||||
|
final result = await query.get();
|
||||||
|
return result.map((e) => e.toDto()).toList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension on AssetOcrEntityData {
|
||||||
|
Ocr toDto() {
|
||||||
|
return Ocr(
|
||||||
|
id: id,
|
||||||
|
assetId: assetId,
|
||||||
|
x1: x1,
|
||||||
|
y1: y1,
|
||||||
|
x2: x2,
|
||||||
|
y2: y2,
|
||||||
|
x3: x3,
|
||||||
|
y3: y3,
|
||||||
|
x4: x4,
|
||||||
|
y4: y4,
|
||||||
|
boxScore: boxScore,
|
||||||
|
textScore: textScore,
|
||||||
|
text: recognizedText,
|
||||||
|
isVisible: isVisible,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -74,6 +74,7 @@ class SyncApiRepository {
|
|||||||
serverVersion >= const SemVer(major: 2, minor: 6, patch: 0)
|
serverVersion >= const SemVer(major: 2, minor: 6, patch: 0)
|
||||||
? SyncRequestType.assetFacesV2
|
? SyncRequestType.assetFacesV2
|
||||||
: SyncRequestType.assetFacesV1,
|
: SyncRequestType.assetFacesV1,
|
||||||
|
if (serverVersion >= const SemVer(major: 3, minor: 0, patch: 0)) SyncRequestType.assetOcrV1,
|
||||||
],
|
],
|
||||||
).toJson(),
|
).toJson(),
|
||||||
);
|
);
|
||||||
@@ -204,6 +205,8 @@ const _kResponseMap = <SyncEntityType, Function(Object)>{
|
|||||||
SyncEntityType.assetFaceV1: SyncAssetFaceV1.fromJson,
|
SyncEntityType.assetFaceV1: SyncAssetFaceV1.fromJson,
|
||||||
SyncEntityType.assetFaceV2: SyncAssetFaceV2.fromJson,
|
SyncEntityType.assetFaceV2: SyncAssetFaceV2.fromJson,
|
||||||
SyncEntityType.assetFaceDeleteV1: SyncAssetFaceDeleteV1.fromJson,
|
SyncEntityType.assetFaceDeleteV1: SyncAssetFaceDeleteV1.fromJson,
|
||||||
|
SyncEntityType.assetOcrV1: SyncAssetOcrV1.fromJson,
|
||||||
|
SyncEntityType.assetOcrDeleteV1: SyncAssetOcrDeleteV1.fromJson,
|
||||||
SyncEntityType.syncCompleteV1: _SyncEmptyDto.fromJson,
|
SyncEntityType.syncCompleteV1: _SyncEmptyDto.fromJson,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import 'package:immich_mobile/domain/models/user_metadata.model.dart';
|
|||||||
import 'package:immich_mobile/extensions/string_extensions.dart';
|
import 'package:immich_mobile/extensions/string_extensions.dart';
|
||||||
import 'package:immich_mobile/infrastructure/entities/asset_edit.entity.drift.dart';
|
import 'package:immich_mobile/infrastructure/entities/asset_edit.entity.drift.dart';
|
||||||
import 'package:immich_mobile/infrastructure/entities/asset_face.entity.drift.dart';
|
import 'package:immich_mobile/infrastructure/entities/asset_face.entity.drift.dart';
|
||||||
|
import 'package:immich_mobile/infrastructure/entities/asset_ocr.entity.drift.dart';
|
||||||
import 'package:immich_mobile/infrastructure/entities/auth_user.entity.drift.dart';
|
import 'package:immich_mobile/infrastructure/entities/auth_user.entity.drift.dart';
|
||||||
import 'package:immich_mobile/infrastructure/entities/exif.entity.drift.dart';
|
import 'package:immich_mobile/infrastructure/entities/exif.entity.drift.dart';
|
||||||
import 'package:immich_mobile/infrastructure/entities/local_album.entity.drift.dart';
|
import 'package:immich_mobile/infrastructure/entities/local_album.entity.drift.dart';
|
||||||
@@ -69,6 +70,7 @@ class SyncStreamRepository extends DriftDatabaseRepository {
|
|||||||
await _db.userMetadataEntity.deleteAll();
|
await _db.userMetadataEntity.deleteAll();
|
||||||
await _db.remoteAssetCloudIdEntity.deleteAll();
|
await _db.remoteAssetCloudIdEntity.deleteAll();
|
||||||
await _db.assetEditEntity.deleteAll();
|
await _db.assetEditEntity.deleteAll();
|
||||||
|
await _db.assetOcrEntity.deleteAll();
|
||||||
});
|
});
|
||||||
} finally {
|
} finally {
|
||||||
// re-enable FK even if the transaction throws, otherwise the connection
|
// re-enable FK even if the transaction throws, otherwise the connection
|
||||||
@@ -848,6 +850,52 @@ class SyncStreamRepository extends DriftDatabaseRepository {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> updateAssetOcrV1(Iterable<SyncAssetOcrV1> data) async {
|
||||||
|
try {
|
||||||
|
await _db.batch((batch) {
|
||||||
|
for (final assetOcr in data) {
|
||||||
|
final companion = AssetOcrEntityCompanion(
|
||||||
|
assetId: Value(assetOcr.assetId),
|
||||||
|
recognizedText: Value(assetOcr.text),
|
||||||
|
x1: Value(assetOcr.x1),
|
||||||
|
y1: Value(assetOcr.y1),
|
||||||
|
x2: Value(assetOcr.x2),
|
||||||
|
y2: Value(assetOcr.y2),
|
||||||
|
x3: Value(assetOcr.x3),
|
||||||
|
y3: Value(assetOcr.y3),
|
||||||
|
x4: Value(assetOcr.x4),
|
||||||
|
y4: Value(assetOcr.y4),
|
||||||
|
boxScore: Value(assetOcr.boxScore),
|
||||||
|
textScore: Value(assetOcr.textScore),
|
||||||
|
isVisible: Value(assetOcr.isVisible),
|
||||||
|
);
|
||||||
|
|
||||||
|
batch.insert(
|
||||||
|
_db.assetOcrEntity,
|
||||||
|
companion.copyWith(id: Value(assetOcr.id)),
|
||||||
|
onConflict: DoUpdate((_) => companion),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (error, stack) {
|
||||||
|
_logger.severe('Error: updateAssetOcrV1', error, stack);
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> deleteAssetOcrV1(Iterable<SyncAssetOcrDeleteV1> data) async {
|
||||||
|
try {
|
||||||
|
await _db.batch((batch) {
|
||||||
|
for (final assetOcr in data) {
|
||||||
|
batch.deleteWhere(_db.assetOcrEntity, (row) => row.id.equals(assetOcr.id));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (error, stack) {
|
||||||
|
_logger.severe('Error: deleteAssetOcrV1', error, stack);
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> pruneAssets() async {
|
Future<void> pruneAssets() async {
|
||||||
try {
|
try {
|
||||||
await _db.transaction(() async {
|
await _db.transaction(() async {
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import 'package:immich_mobile/extensions/scroll_extensions.dart';
|
|||||||
import 'package:immich_mobile/presentation/widgets/asset_viewer/asset_details.widget.dart';
|
import 'package:immich_mobile/presentation/widgets/asset_viewer/asset_details.widget.dart';
|
||||||
import 'package:immich_mobile/presentation/widgets/asset_viewer/asset_stack.provider.dart';
|
import 'package:immich_mobile/presentation/widgets/asset_viewer/asset_stack.provider.dart';
|
||||||
import 'package:immich_mobile/presentation/widgets/asset_viewer/asset_stack.widget.dart';
|
import 'package:immich_mobile/presentation/widgets/asset_viewer/asset_stack.widget.dart';
|
||||||
|
import 'package:immich_mobile/presentation/widgets/asset_viewer/ocr_overlay.widget.dart';
|
||||||
import 'package:immich_mobile/presentation/widgets/asset_viewer/video_viewer.widget.dart';
|
import 'package:immich_mobile/presentation/widgets/asset_viewer/video_viewer.widget.dart';
|
||||||
import 'package:immich_mobile/presentation/widgets/images/image_provider.dart';
|
import 'package:immich_mobile/presentation/widgets/images/image_provider.dart';
|
||||||
import 'package:immich_mobile/presentation/widgets/images/thumbnail.widget.dart';
|
import 'package:immich_mobile/presentation/widgets/images/thumbnail.widget.dart';
|
||||||
@@ -394,6 +395,7 @@ class _AssetPageState extends ConsumerState<AssetPage> {
|
|||||||
final stackIndex = ref.watch(assetViewerProvider.select((s) => s.stackIndex));
|
final stackIndex = ref.watch(assetViewerProvider.select((s) => s.stackIndex));
|
||||||
final isPlayingMotionVideo = ref.watch(isPlayingMotionVideoProvider);
|
final isPlayingMotionVideo = ref.watch(isPlayingMotionVideoProvider);
|
||||||
final timelineOrigin = ref.read(timelineServiceProvider).origin;
|
final timelineOrigin = ref.read(timelineServiceProvider).origin;
|
||||||
|
final showingOcr = ref.watch(assetViewerProvider.select((s) => s.showingOcr));
|
||||||
|
|
||||||
final asset = _asset;
|
final asset = _asset;
|
||||||
if (asset == null) {
|
if (asset == null) {
|
||||||
@@ -446,6 +448,15 @@ class _AssetPageState extends ConsumerState<AssetPage> {
|
|||||||
localFilePath: viewIntentFilePath,
|
localFilePath: viewIntentFilePath,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
if (showingOcr && displayAsset.width != null && displayAsset.height != null)
|
||||||
|
Positioned.fill(
|
||||||
|
child: OcrOverlay(
|
||||||
|
asset: displayAsset,
|
||||||
|
imageSize: Size(displayAsset.width!.toDouble(), displayAsset.height!.toDouble()),
|
||||||
|
viewportSize: Size(viewportWidth, viewportHeight),
|
||||||
|
controller: _viewController,
|
||||||
|
),
|
||||||
|
),
|
||||||
IgnorePointer(
|
IgnorePointer(
|
||||||
ignoring: !_showingDetails,
|
ignoring: !_showingDetails,
|
||||||
child: Column(
|
child: Column(
|
||||||
|
|||||||
@@ -0,0 +1,343 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
import 'dart:math' as math;
|
||||||
|
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
||||||
|
import 'package:immich_mobile/domain/models/ocr.model.dart';
|
||||||
|
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||||
|
import 'package:immich_mobile/providers/infrastructure/ocr.provider.dart';
|
||||||
|
import 'package:immich_mobile/widgets/photo_view/photo_view.dart';
|
||||||
|
|
||||||
|
class OcrOverlay extends ConsumerStatefulWidget {
|
||||||
|
final BaseAsset asset;
|
||||||
|
final Size imageSize;
|
||||||
|
final Size viewportSize;
|
||||||
|
final PhotoViewControllerBase? controller;
|
||||||
|
|
||||||
|
const OcrOverlay({
|
||||||
|
super.key,
|
||||||
|
required this.asset,
|
||||||
|
required this.imageSize,
|
||||||
|
required this.viewportSize,
|
||||||
|
this.controller,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
ConsumerState<OcrOverlay> createState() => _OcrOverlayState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _OcrOverlayState extends ConsumerState<OcrOverlay> {
|
||||||
|
int? _selectedBoxIndex;
|
||||||
|
|
||||||
|
// Current transform read from the PhotoView controller.
|
||||||
|
// Null until the controller has emitted at least one real event or until
|
||||||
|
// we can seed a reliable value from controller.value on init.
|
||||||
|
PhotoViewControllerValue? _controllerValue;
|
||||||
|
StreamSubscription<PhotoViewControllerValue>? _controllerSub;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_attachController(widget.controller);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didUpdateWidget(OcrOverlay oldWidget) {
|
||||||
|
super.didUpdateWidget(oldWidget);
|
||||||
|
if (oldWidget.controller != widget.controller) {
|
||||||
|
_detachController();
|
||||||
|
_attachController(widget.controller);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_detachController();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _attachController(PhotoViewControllerBase? controller) {
|
||||||
|
if (controller == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Seed with the current value only when scaleBoundaries is already set.
|
||||||
|
// Before the image finishes loading, PhotoView uses childSize = outerSize
|
||||||
|
// (viewport) as a placeholder, which sets scale = 1.0. That placeholder
|
||||||
|
// is wrong for any image that doesn't exactly fill the viewport.
|
||||||
|
// Once scaleBoundaries is set the value is trustworthy (the image has rendered
|
||||||
|
// at least one frame and setScaleInvisibly has been called with the real
|
||||||
|
// initial/zoomed scale).
|
||||||
|
if (controller.scaleBoundaries != null) {
|
||||||
|
_controllerValue = controller.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
_controllerSub = controller.outputStateStream.listen((value) {
|
||||||
|
if (mounted) {
|
||||||
|
setState(() => _controllerValue = value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void _detachController() {
|
||||||
|
_controllerSub?.cancel();
|
||||||
|
_controllerSub = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
if (widget.asset is! RemoteAsset) {
|
||||||
|
return const SizedBox.shrink();
|
||||||
|
}
|
||||||
|
|
||||||
|
final ocrData = ref.watch(ocrAssetProvider((widget.asset as RemoteAsset).id));
|
||||||
|
|
||||||
|
return ocrData.when(
|
||||||
|
data: (data) {
|
||||||
|
if (data == null || data.isEmpty) {
|
||||||
|
return const SizedBox.shrink();
|
||||||
|
}
|
||||||
|
return _OcrBoxes(
|
||||||
|
ocrData: data,
|
||||||
|
controller: widget.controller,
|
||||||
|
imageSize: widget.imageSize,
|
||||||
|
viewportSize: widget.viewportSize,
|
||||||
|
controllerValue: _controllerValue,
|
||||||
|
selectedBoxIndex: _selectedBoxIndex,
|
||||||
|
onSelectionChanged: (index) => setState(() => _selectedBoxIndex = index),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
loading: () => const SizedBox.shrink(),
|
||||||
|
error: (_, __) => const SizedBox.shrink(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _OcrBoxes extends StatelessWidget {
|
||||||
|
final List<Ocr> ocrData;
|
||||||
|
final PhotoViewControllerBase? controller;
|
||||||
|
final Size imageSize;
|
||||||
|
final Size viewportSize;
|
||||||
|
final PhotoViewControllerValue? controllerValue;
|
||||||
|
final int? selectedBoxIndex;
|
||||||
|
final ValueChanged<int?> onSelectionChanged;
|
||||||
|
|
||||||
|
const _OcrBoxes({
|
||||||
|
required this.ocrData,
|
||||||
|
required this.controller,
|
||||||
|
required this.imageSize,
|
||||||
|
required this.viewportSize,
|
||||||
|
required this.controllerValue,
|
||||||
|
required this.selectedBoxIndex,
|
||||||
|
required this.onSelectionChanged,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
// Use the actual decoded image size from PhotoView's scaleBoundaries when
|
||||||
|
// available. The image provider may serve a downscaled preview (e.g. Immich
|
||||||
|
// serves a ~1440px preview for large originals), so the decoded dimensions
|
||||||
|
// can differ significantly from the stored asset dimensions. Using the wrong
|
||||||
|
// size would scale every coordinate by the ratio between the two resolutions.
|
||||||
|
final resolvedImageSize = controller?.scaleBoundaries?.childSize ?? imageSize;
|
||||||
|
|
||||||
|
final scale =
|
||||||
|
controllerValue?.scale ??
|
||||||
|
math.min(viewportSize.width / resolvedImageSize.width, viewportSize.height / resolvedImageSize.height);
|
||||||
|
final position = controllerValue?.position ?? Offset.zero;
|
||||||
|
|
||||||
|
final imageWidth = resolvedImageSize.width;
|
||||||
|
final imageHeight = resolvedImageSize.height;
|
||||||
|
final viewportWidth = viewportSize.width;
|
||||||
|
final viewportHeight = viewportSize.height;
|
||||||
|
|
||||||
|
// Image center in viewport space, accounting for pan
|
||||||
|
final cx = viewportWidth / 2 + position.dx;
|
||||||
|
final cy = viewportHeight / 2 + position.dy;
|
||||||
|
|
||||||
|
return GestureDetector(
|
||||||
|
behavior: HitTestBehavior.translucent,
|
||||||
|
onTap: () => onSelectionChanged(null),
|
||||||
|
child: ClipRect(
|
||||||
|
child: Stack(
|
||||||
|
children: [
|
||||||
|
// Fills the viewport so taps outside boxes deselect
|
||||||
|
SizedBox(width: viewportWidth, height: viewportHeight),
|
||||||
|
...ocrData.asMap().entries.map((entry) {
|
||||||
|
final index = entry.key;
|
||||||
|
final ocr = entry.value;
|
||||||
|
|
||||||
|
// Map normalized image coords (0–1) to viewport space
|
||||||
|
final x1 = cx + (ocr.x1 - 0.5) * imageWidth * scale;
|
||||||
|
final y1 = cy + (ocr.y1 - 0.5) * imageHeight * scale;
|
||||||
|
final x2 = cx + (ocr.x2 - 0.5) * imageWidth * scale;
|
||||||
|
final y2 = cy + (ocr.y2 - 0.5) * imageHeight * scale;
|
||||||
|
final x3 = cx + (ocr.x3 - 0.5) * imageWidth * scale;
|
||||||
|
final y3 = cy + (ocr.y3 - 0.5) * imageHeight * scale;
|
||||||
|
final x4 = cx + (ocr.x4 - 0.5) * imageWidth * scale;
|
||||||
|
final y4 = cy + (ocr.y4 - 0.5) * imageHeight * scale;
|
||||||
|
|
||||||
|
// Bounding rectangle for hit testing and Positioned placement
|
||||||
|
final minX = [x1, x2, x3, x4].reduce((a, b) => a < b ? a : b);
|
||||||
|
final maxX = [x1, x2, x3, x4].reduce((a, b) => a > b ? a : b);
|
||||||
|
final minY = [y1, y2, y3, y4].reduce((a, b) => a < b ? a : b);
|
||||||
|
final maxY = [y1, y2, y3, y4].reduce((a, b) => a > b ? a : b);
|
||||||
|
|
||||||
|
return _OcrBoxItem(
|
||||||
|
key: ValueKey(index),
|
||||||
|
ocr: ocr,
|
||||||
|
index: index,
|
||||||
|
isSelected: selectedBoxIndex == index,
|
||||||
|
points: [
|
||||||
|
Offset(x1 - minX, y1 - minY),
|
||||||
|
Offset(x2 - minX, y2 - minY),
|
||||||
|
Offset(x3 - minX, y3 - minY),
|
||||||
|
Offset(x4 - minX, y4 - minY),
|
||||||
|
],
|
||||||
|
left: minX,
|
||||||
|
top: minY,
|
||||||
|
width: maxX - minX,
|
||||||
|
height: maxY - minY,
|
||||||
|
angle: math.atan2(y2 - y1, x2 - x1),
|
||||||
|
labelDx: (minX + maxX) / 2 - minX,
|
||||||
|
labelDy: (minY + maxY) / 2 - minY,
|
||||||
|
onSelectionChanged: onSelectionChanged,
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _OcrBoxItem extends StatelessWidget {
|
||||||
|
final Ocr ocr;
|
||||||
|
final int index;
|
||||||
|
final bool isSelected;
|
||||||
|
final List<Offset> points;
|
||||||
|
final double left;
|
||||||
|
final double top;
|
||||||
|
final double width;
|
||||||
|
final double height;
|
||||||
|
final double angle;
|
||||||
|
final double labelDx;
|
||||||
|
final double labelDy;
|
||||||
|
final ValueChanged<int?> onSelectionChanged;
|
||||||
|
|
||||||
|
const _OcrBoxItem({
|
||||||
|
super.key,
|
||||||
|
required this.ocr,
|
||||||
|
required this.index,
|
||||||
|
required this.isSelected,
|
||||||
|
required this.points,
|
||||||
|
required this.left,
|
||||||
|
required this.top,
|
||||||
|
required this.width,
|
||||||
|
required this.height,
|
||||||
|
required this.angle,
|
||||||
|
required this.labelDx,
|
||||||
|
required this.labelDy,
|
||||||
|
required this.onSelectionChanged,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Positioned(
|
||||||
|
left: left,
|
||||||
|
top: top,
|
||||||
|
child: GestureDetector(
|
||||||
|
onTap: () => onSelectionChanged(isSelected ? null : index),
|
||||||
|
behavior: HitTestBehavior.translucent,
|
||||||
|
child: SizedBox(
|
||||||
|
width: width,
|
||||||
|
height: height,
|
||||||
|
child: Stack(
|
||||||
|
children: [
|
||||||
|
CustomPaint(
|
||||||
|
painter: _OcrBoxPainter(
|
||||||
|
points: points,
|
||||||
|
isSelected: isSelected,
|
||||||
|
colorScheme: context.themeData.colorScheme,
|
||||||
|
),
|
||||||
|
size: Size(width, height),
|
||||||
|
),
|
||||||
|
if (isSelected)
|
||||||
|
Positioned(
|
||||||
|
left: labelDx,
|
||||||
|
top: labelDy,
|
||||||
|
child: FractionalTranslation(
|
||||||
|
translation: const Offset(-0.5, -0.5),
|
||||||
|
child: Transform.rotate(
|
||||||
|
angle: angle,
|
||||||
|
alignment: Alignment.center,
|
||||||
|
child: Container(
|
||||||
|
margin: const EdgeInsets.all(2),
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 2),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.grey[800]?.withValues(alpha: 0.4),
|
||||||
|
borderRadius: const BorderRadius.all(Radius.circular(4)),
|
||||||
|
),
|
||||||
|
child: ConstrainedBox(
|
||||||
|
constraints: BoxConstraints(maxWidth: math.max(50, width), maxHeight: math.max(20, height)),
|
||||||
|
child: FittedBox(
|
||||||
|
fit: BoxFit.scaleDown,
|
||||||
|
child: SelectableText(
|
||||||
|
ocr.text,
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.white,
|
||||||
|
fontSize: math.max(12, height * 0.6),
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _OcrBoxPainter extends CustomPainter {
|
||||||
|
final List<Offset> points;
|
||||||
|
final bool isSelected;
|
||||||
|
final ColorScheme colorScheme;
|
||||||
|
|
||||||
|
const _OcrBoxPainter({required this.points, required this.isSelected, required this.colorScheme});
|
||||||
|
|
||||||
|
@override
|
||||||
|
void paint(Canvas canvas, Size size) {
|
||||||
|
final paint = Paint()
|
||||||
|
..color = isSelected ? colorScheme.primary : colorScheme.secondary
|
||||||
|
..style = PaintingStyle.stroke
|
||||||
|
..strokeWidth = 2.0;
|
||||||
|
|
||||||
|
final fillPaint = Paint()
|
||||||
|
..color = (isSelected ? colorScheme.primary : colorScheme.secondary).withValues(alpha: 0.1)
|
||||||
|
..style = PaintingStyle.fill;
|
||||||
|
|
||||||
|
final path = Path()
|
||||||
|
..moveTo(points[0].dx, points[0].dy)
|
||||||
|
..lineTo(points[1].dx, points[1].dy)
|
||||||
|
..lineTo(points[2].dx, points[2].dy)
|
||||||
|
..lineTo(points[3].dx, points[3].dy)
|
||||||
|
..close();
|
||||||
|
|
||||||
|
canvas.drawPath(path, fillPaint);
|
||||||
|
canvas.drawPath(path, paint);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool shouldRepaint(_OcrBoxPainter oldDelegate) {
|
||||||
|
return oldDelegate.isSelected != isSelected || !listEquals(oldDelegate.points, points);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,6 +13,7 @@ import 'package:immich_mobile/providers/activity.provider.dart';
|
|||||||
import 'package:immich_mobile/providers/asset_viewer/asset_viewer.provider.dart';
|
import 'package:immich_mobile/providers/asset_viewer/asset_viewer.provider.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/asset_viewer/asset.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/asset_viewer/asset.provider.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/current_album.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/current_album.provider.dart';
|
||||||
|
import 'package:immich_mobile/providers/infrastructure/ocr.provider.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/readonly_mode.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/readonly_mode.provider.dart';
|
||||||
import 'package:immich_mobile/providers/routes.provider.dart';
|
import 'package:immich_mobile/providers/routes.provider.dart';
|
||||||
import 'package:immich_mobile/providers/user.provider.dart';
|
import 'package:immich_mobile/providers/user.provider.dart';
|
||||||
@@ -35,6 +36,7 @@ class ViewerTopAppBar extends ConsumerWidget implements PreferredSizeWidget {
|
|||||||
final isOwner = asset is RemoteAsset && asset.ownerId == user?.id;
|
final isOwner = asset is RemoteAsset && asset.ownerId == user?.id;
|
||||||
final isInLockedView = ref.watch(inLockedViewProvider);
|
final isInLockedView = ref.watch(inLockedViewProvider);
|
||||||
final isReadonlyModeEnabled = ref.watch(readonlyModeProvider);
|
final isReadonlyModeEnabled = ref.watch(readonlyModeProvider);
|
||||||
|
final hasOcr = asset is RemoteAsset && ref.watch(ocrAssetProvider(asset.id)).valueOrNull?.isNotEmpty == true;
|
||||||
|
|
||||||
final showingDetails = ref.watch(assetViewerProvider.select((state) => state.showingDetails));
|
final showingDetails = ref.watch(assetViewerProvider.select((state) => state.showingDetails));
|
||||||
|
|
||||||
@@ -46,8 +48,15 @@ class ViewerTopAppBar extends ConsumerWidget implements PreferredSizeWidget {
|
|||||||
double opacity = ref.watch(assetViewerProvider.select((s) => s.backgroundOpacity)) * (showingControls ? 1 : 0);
|
double opacity = ref.watch(assetViewerProvider.select((s) => s.backgroundOpacity)) * (showingControls ? 1 : 0);
|
||||||
|
|
||||||
final originalTheme = context.themeData;
|
final originalTheme = context.themeData;
|
||||||
|
final showingOcr = ref.watch(assetViewerProvider.select((state) => state.showingOcr));
|
||||||
|
|
||||||
final actions = <Widget>[
|
final actions = <Widget>[
|
||||||
|
if (hasOcr)
|
||||||
|
IconButton(
|
||||||
|
icon: Icon(showingOcr ? Icons.text_fields : Icons.text_fields_outlined),
|
||||||
|
onPressed: ref.read(assetViewerProvider.notifier).toggleOcr,
|
||||||
|
color: showingOcr ? context.primaryColor : null,
|
||||||
|
),
|
||||||
if (asset.isMotionPhoto) const MotionPhotoActionButton(iconOnly: true),
|
if (asset.isMotionPhoto) const MotionPhotoActionButton(iconOnly: true),
|
||||||
if (album != null && album.isActivityEnabled && album.isShared)
|
if (album != null && album.isActivityEnabled && album.isShared)
|
||||||
IconButton(
|
IconButton(
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ class AssetViewerState {
|
|||||||
final bool showingDetails;
|
final bool showingDetails;
|
||||||
final bool showingControls;
|
final bool showingControls;
|
||||||
final bool isZoomed;
|
final bool isZoomed;
|
||||||
|
final bool showingOcr;
|
||||||
final BaseAsset? currentAsset;
|
final BaseAsset? currentAsset;
|
||||||
final int stackIndex;
|
final int stackIndex;
|
||||||
|
|
||||||
@@ -16,6 +17,7 @@ class AssetViewerState {
|
|||||||
this.showingDetails = false,
|
this.showingDetails = false,
|
||||||
this.showingControls = true,
|
this.showingControls = true,
|
||||||
this.isZoomed = false,
|
this.isZoomed = false,
|
||||||
|
this.showingOcr = false,
|
||||||
this.currentAsset,
|
this.currentAsset,
|
||||||
this.stackIndex = 0,
|
this.stackIndex = 0,
|
||||||
});
|
});
|
||||||
@@ -25,6 +27,7 @@ class AssetViewerState {
|
|||||||
bool? showingDetails,
|
bool? showingDetails,
|
||||||
bool? showingControls,
|
bool? showingControls,
|
||||||
bool? isZoomed,
|
bool? isZoomed,
|
||||||
|
bool? showingOcr,
|
||||||
BaseAsset? currentAsset,
|
BaseAsset? currentAsset,
|
||||||
int? stackIndex,
|
int? stackIndex,
|
||||||
}) {
|
}) {
|
||||||
@@ -33,6 +36,7 @@ class AssetViewerState {
|
|||||||
showingDetails: showingDetails ?? this.showingDetails,
|
showingDetails: showingDetails ?? this.showingDetails,
|
||||||
showingControls: showingControls ?? this.showingControls,
|
showingControls: showingControls ?? this.showingControls,
|
||||||
isZoomed: isZoomed ?? this.isZoomed,
|
isZoomed: isZoomed ?? this.isZoomed,
|
||||||
|
showingOcr: showingOcr ?? this.showingOcr,
|
||||||
currentAsset: currentAsset ?? this.currentAsset,
|
currentAsset: currentAsset ?? this.currentAsset,
|
||||||
stackIndex: stackIndex ?? this.stackIndex,
|
stackIndex: stackIndex ?? this.stackIndex,
|
||||||
);
|
);
|
||||||
@@ -40,7 +44,7 @@ class AssetViewerState {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return 'AssetViewerState(opacity: $backgroundOpacity, showingDetails: $showingDetails, controls: $showingControls, isZoomed: $isZoomed)';
|
return 'AssetViewerState(opacity: $backgroundOpacity, showingDetails: $showingDetails, controls: $showingControls, isZoomed: $isZoomed, showingOcr: $showingOcr)';
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -56,6 +60,7 @@ class AssetViewerState {
|
|||||||
other.showingDetails == showingDetails &&
|
other.showingDetails == showingDetails &&
|
||||||
other.showingControls == showingControls &&
|
other.showingControls == showingControls &&
|
||||||
other.isZoomed == isZoomed &&
|
other.isZoomed == isZoomed &&
|
||||||
|
other.showingOcr == showingOcr &&
|
||||||
other.currentAsset == currentAsset &&
|
other.currentAsset == currentAsset &&
|
||||||
other.stackIndex == stackIndex;
|
other.stackIndex == stackIndex;
|
||||||
}
|
}
|
||||||
@@ -66,6 +71,7 @@ class AssetViewerState {
|
|||||||
showingDetails.hashCode ^
|
showingDetails.hashCode ^
|
||||||
showingControls.hashCode ^
|
showingControls.hashCode ^
|
||||||
isZoomed.hashCode ^
|
isZoomed.hashCode ^
|
||||||
|
showingOcr.hashCode ^
|
||||||
currentAsset.hashCode ^
|
currentAsset.hashCode ^
|
||||||
stackIndex.hashCode;
|
stackIndex.hashCode;
|
||||||
}
|
}
|
||||||
@@ -90,7 +96,7 @@ class AssetViewerStateNotifier extends Notifier<AssetViewerState> {
|
|||||||
if (asset == state.currentAsset) {
|
if (asset == state.currentAsset) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
state = state.copyWith(currentAsset: asset, stackIndex: 0);
|
state = state.copyWith(currentAsset: asset, stackIndex: 0, showingOcr: false);
|
||||||
}
|
}
|
||||||
|
|
||||||
void setOpacity(double opacity) {
|
void setOpacity(double opacity) {
|
||||||
@@ -137,6 +143,10 @@ class AssetViewerStateNotifier extends Notifier<AssetViewerState> {
|
|||||||
}
|
}
|
||||||
state = state.copyWith(stackIndex: index);
|
state = state.copyWith(stackIndex: index);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void toggleOcr() {
|
||||||
|
state = state.copyWith(showingOcr: !state.showingOcr);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final assetViewerProvider = NotifierProvider<AssetViewerStateNotifier, AssetViewerState>(AssetViewerStateNotifier.new);
|
final assetViewerProvider = NotifierProvider<AssetViewerStateNotifier, AssetViewerState>(AssetViewerStateNotifier.new);
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:immich_mobile/domain/models/ocr.model.dart';
|
||||||
|
import 'package:immich_mobile/domain/services/ocr.service.dart';
|
||||||
|
import 'package:immich_mobile/infrastructure/repositories/ocr.repository.dart';
|
||||||
|
import 'package:immich_mobile/providers/infrastructure/db.provider.dart';
|
||||||
|
|
||||||
|
final ocrRepositoryProvider = Provider<OcrRepository>((ref) => OcrRepository(ref.watch(driftProvider)));
|
||||||
|
|
||||||
|
final ocrServiceProvider = Provider<OcrService>((ref) => OcrService(ref.watch(ocrRepositoryProvider)));
|
||||||
|
|
||||||
|
final ocrAssetProvider = FutureProvider.autoDispose.family<List<Ocr>?, String>((ref, assetId) async {
|
||||||
|
final service = ref.watch(ocrServiceProvider);
|
||||||
|
return service.get(assetId);
|
||||||
|
});
|
||||||
Generated
+2
@@ -586,6 +586,8 @@ Class | Method | HTTP request | Description
|
|||||||
- [SyncAssetFaceV2](doc//SyncAssetFaceV2.md)
|
- [SyncAssetFaceV2](doc//SyncAssetFaceV2.md)
|
||||||
- [SyncAssetMetadataDeleteV1](doc//SyncAssetMetadataDeleteV1.md)
|
- [SyncAssetMetadataDeleteV1](doc//SyncAssetMetadataDeleteV1.md)
|
||||||
- [SyncAssetMetadataV1](doc//SyncAssetMetadataV1.md)
|
- [SyncAssetMetadataV1](doc//SyncAssetMetadataV1.md)
|
||||||
|
- [SyncAssetOcrDeleteV1](doc//SyncAssetOcrDeleteV1.md)
|
||||||
|
- [SyncAssetOcrV1](doc//SyncAssetOcrV1.md)
|
||||||
- [SyncAssetV1](doc//SyncAssetV1.md)
|
- [SyncAssetV1](doc//SyncAssetV1.md)
|
||||||
- [SyncAssetV2](doc//SyncAssetV2.md)
|
- [SyncAssetV2](doc//SyncAssetV2.md)
|
||||||
- [SyncAuthUserV1](doc//SyncAuthUserV1.md)
|
- [SyncAuthUserV1](doc//SyncAuthUserV1.md)
|
||||||
|
|||||||
Generated
+2
@@ -326,6 +326,8 @@ part 'model/sync_asset_face_v1.dart';
|
|||||||
part 'model/sync_asset_face_v2.dart';
|
part 'model/sync_asset_face_v2.dart';
|
||||||
part 'model/sync_asset_metadata_delete_v1.dart';
|
part 'model/sync_asset_metadata_delete_v1.dart';
|
||||||
part 'model/sync_asset_metadata_v1.dart';
|
part 'model/sync_asset_metadata_v1.dart';
|
||||||
|
part 'model/sync_asset_ocr_delete_v1.dart';
|
||||||
|
part 'model/sync_asset_ocr_v1.dart';
|
||||||
part 'model/sync_asset_v1.dart';
|
part 'model/sync_asset_v1.dart';
|
||||||
part 'model/sync_asset_v2.dart';
|
part 'model/sync_asset_v2.dart';
|
||||||
part 'model/sync_auth_user_v1.dart';
|
part 'model/sync_auth_user_v1.dart';
|
||||||
|
|||||||
Generated
+4
@@ -697,6 +697,10 @@ class ApiClient {
|
|||||||
return SyncAssetMetadataDeleteV1.fromJson(value);
|
return SyncAssetMetadataDeleteV1.fromJson(value);
|
||||||
case 'SyncAssetMetadataV1':
|
case 'SyncAssetMetadataV1':
|
||||||
return SyncAssetMetadataV1.fromJson(value);
|
return SyncAssetMetadataV1.fromJson(value);
|
||||||
|
case 'SyncAssetOcrDeleteV1':
|
||||||
|
return SyncAssetOcrDeleteV1.fromJson(value);
|
||||||
|
case 'SyncAssetOcrV1':
|
||||||
|
return SyncAssetOcrV1.fromJson(value);
|
||||||
case 'SyncAssetV1':
|
case 'SyncAssetV1':
|
||||||
return SyncAssetV1.fromJson(value);
|
return SyncAssetV1.fromJson(value);
|
||||||
case 'SyncAssetV2':
|
case 'SyncAssetV2':
|
||||||
|
|||||||
+120
@@ -0,0 +1,120 @@
|
|||||||
|
//
|
||||||
|
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
||||||
|
//
|
||||||
|
// @dart=2.18
|
||||||
|
|
||||||
|
// ignore_for_file: unused_element, unused_import
|
||||||
|
// ignore_for_file: always_put_required_named_parameters_first
|
||||||
|
// ignore_for_file: constant_identifier_names
|
||||||
|
// ignore_for_file: lines_longer_than_80_chars
|
||||||
|
|
||||||
|
part of openapi.api;
|
||||||
|
|
||||||
|
class SyncAssetOcrDeleteV1 {
|
||||||
|
/// Returns a new [SyncAssetOcrDeleteV1] instance.
|
||||||
|
SyncAssetOcrDeleteV1({
|
||||||
|
required this.assetId,
|
||||||
|
required this.deletedAt,
|
||||||
|
required this.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
/// Original asset ID of the deleted OCR entry
|
||||||
|
String assetId;
|
||||||
|
|
||||||
|
/// Timestamp when the OCR entry was deleted
|
||||||
|
DateTime deletedAt;
|
||||||
|
|
||||||
|
/// Audit row ID of the deleted OCR entry
|
||||||
|
String id;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) => identical(this, other) || other is SyncAssetOcrDeleteV1 &&
|
||||||
|
other.assetId == assetId &&
|
||||||
|
other.deletedAt == deletedAt &&
|
||||||
|
other.id == id;
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode =>
|
||||||
|
// ignore: unnecessary_parenthesis
|
||||||
|
(assetId.hashCode) +
|
||||||
|
(deletedAt.hashCode) +
|
||||||
|
(id.hashCode);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => 'SyncAssetOcrDeleteV1[assetId=$assetId, deletedAt=$deletedAt, id=$id]';
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
final json = <String, dynamic>{};
|
||||||
|
json[r'assetId'] = this.assetId;
|
||||||
|
json[r'deletedAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')
|
||||||
|
? this.deletedAt.millisecondsSinceEpoch
|
||||||
|
: this.deletedAt.toUtc().toIso8601String();
|
||||||
|
json[r'id'] = this.id;
|
||||||
|
return json;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a new [SyncAssetOcrDeleteV1] instance and imports its values from
|
||||||
|
/// [value] if it's a [Map], null otherwise.
|
||||||
|
// ignore: prefer_constructors_over_static_methods
|
||||||
|
static SyncAssetOcrDeleteV1? fromJson(dynamic value) {
|
||||||
|
upgradeDto(value, "SyncAssetOcrDeleteV1");
|
||||||
|
if (value is Map) {
|
||||||
|
final json = value.cast<String, dynamic>();
|
||||||
|
|
||||||
|
return SyncAssetOcrDeleteV1(
|
||||||
|
assetId: mapValueOfType<String>(json, r'assetId')!,
|
||||||
|
deletedAt: mapDateTime(json, r'deletedAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')!,
|
||||||
|
id: mapValueOfType<String>(json, r'id')!,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
static List<SyncAssetOcrDeleteV1> listFromJson(dynamic json, {bool growable = false,}) {
|
||||||
|
final result = <SyncAssetOcrDeleteV1>[];
|
||||||
|
if (json is List && json.isNotEmpty) {
|
||||||
|
for (final row in json) {
|
||||||
|
final value = SyncAssetOcrDeleteV1.fromJson(row);
|
||||||
|
if (value != null) {
|
||||||
|
result.add(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result.toList(growable: growable);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Map<String, SyncAssetOcrDeleteV1> mapFromJson(dynamic json) {
|
||||||
|
final map = <String, SyncAssetOcrDeleteV1>{};
|
||||||
|
if (json is Map && json.isNotEmpty) {
|
||||||
|
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||||
|
for (final entry in json.entries) {
|
||||||
|
final value = SyncAssetOcrDeleteV1.fromJson(entry.value);
|
||||||
|
if (value != null) {
|
||||||
|
map[entry.key] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
// maps a json object with a list of SyncAssetOcrDeleteV1-objects as value to a dart map
|
||||||
|
static Map<String, List<SyncAssetOcrDeleteV1>> mapListFromJson(dynamic json, {bool growable = false,}) {
|
||||||
|
final map = <String, List<SyncAssetOcrDeleteV1>>{};
|
||||||
|
if (json is Map && json.isNotEmpty) {
|
||||||
|
// ignore: parameter_assignments
|
||||||
|
json = json.cast<String, dynamic>();
|
||||||
|
for (final entry in json.entries) {
|
||||||
|
map[entry.key] = SyncAssetOcrDeleteV1.listFromJson(entry.value, growable: growable,);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The list of required keys that must be present in a JSON.
|
||||||
|
static const requiredKeys = <String>{
|
||||||
|
'assetId',
|
||||||
|
'deletedAt',
|
||||||
|
'id',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
+217
@@ -0,0 +1,217 @@
|
|||||||
|
//
|
||||||
|
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
||||||
|
//
|
||||||
|
// @dart=2.18
|
||||||
|
|
||||||
|
// ignore_for_file: unused_element, unused_import
|
||||||
|
// ignore_for_file: always_put_required_named_parameters_first
|
||||||
|
// ignore_for_file: constant_identifier_names
|
||||||
|
// ignore_for_file: lines_longer_than_80_chars
|
||||||
|
|
||||||
|
part of openapi.api;
|
||||||
|
|
||||||
|
class SyncAssetOcrV1 {
|
||||||
|
/// Returns a new [SyncAssetOcrV1] instance.
|
||||||
|
SyncAssetOcrV1({
|
||||||
|
required this.assetId,
|
||||||
|
required this.boxScore,
|
||||||
|
required this.id,
|
||||||
|
required this.isVisible,
|
||||||
|
required this.text,
|
||||||
|
required this.textScore,
|
||||||
|
required this.x1,
|
||||||
|
required this.x2,
|
||||||
|
required this.x3,
|
||||||
|
required this.x4,
|
||||||
|
required this.y1,
|
||||||
|
required this.y2,
|
||||||
|
required this.y3,
|
||||||
|
required this.y4,
|
||||||
|
});
|
||||||
|
|
||||||
|
/// Asset ID
|
||||||
|
String assetId;
|
||||||
|
|
||||||
|
/// Confidence score of the bounding box
|
||||||
|
double boxScore;
|
||||||
|
|
||||||
|
/// OCR entry ID
|
||||||
|
String id;
|
||||||
|
|
||||||
|
/// Whether the OCR entry is visible
|
||||||
|
bool isVisible;
|
||||||
|
|
||||||
|
/// Recognized text content
|
||||||
|
String text;
|
||||||
|
|
||||||
|
/// Confidence score of the recognized text
|
||||||
|
double textScore;
|
||||||
|
|
||||||
|
/// Top-left X coordinate (normalized 0–1)
|
||||||
|
double x1;
|
||||||
|
|
||||||
|
/// Top-right X coordinate (normalized 0–1)
|
||||||
|
double x2;
|
||||||
|
|
||||||
|
/// Bottom-right X coordinate (normalized 0–1)
|
||||||
|
double x3;
|
||||||
|
|
||||||
|
/// Bottom-left X coordinate (normalized 0–1)
|
||||||
|
double x4;
|
||||||
|
|
||||||
|
/// Top-left Y coordinate (normalized 0–1)
|
||||||
|
double y1;
|
||||||
|
|
||||||
|
/// Top-right Y coordinate (normalized 0–1)
|
||||||
|
double y2;
|
||||||
|
|
||||||
|
/// Bottom-right Y coordinate (normalized 0–1)
|
||||||
|
double y3;
|
||||||
|
|
||||||
|
/// Bottom-left Y coordinate (normalized 0–1)
|
||||||
|
double y4;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) => identical(this, other) || other is SyncAssetOcrV1 &&
|
||||||
|
other.assetId == assetId &&
|
||||||
|
other.boxScore == boxScore &&
|
||||||
|
other.id == id &&
|
||||||
|
other.isVisible == isVisible &&
|
||||||
|
other.text == text &&
|
||||||
|
other.textScore == textScore &&
|
||||||
|
other.x1 == x1 &&
|
||||||
|
other.x2 == x2 &&
|
||||||
|
other.x3 == x3 &&
|
||||||
|
other.x4 == x4 &&
|
||||||
|
other.y1 == y1 &&
|
||||||
|
other.y2 == y2 &&
|
||||||
|
other.y3 == y3 &&
|
||||||
|
other.y4 == y4;
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode =>
|
||||||
|
// ignore: unnecessary_parenthesis
|
||||||
|
(assetId.hashCode) +
|
||||||
|
(boxScore.hashCode) +
|
||||||
|
(id.hashCode) +
|
||||||
|
(isVisible.hashCode) +
|
||||||
|
(text.hashCode) +
|
||||||
|
(textScore.hashCode) +
|
||||||
|
(x1.hashCode) +
|
||||||
|
(x2.hashCode) +
|
||||||
|
(x3.hashCode) +
|
||||||
|
(x4.hashCode) +
|
||||||
|
(y1.hashCode) +
|
||||||
|
(y2.hashCode) +
|
||||||
|
(y3.hashCode) +
|
||||||
|
(y4.hashCode);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => 'SyncAssetOcrV1[assetId=$assetId, boxScore=$boxScore, id=$id, isVisible=$isVisible, text=$text, textScore=$textScore, x1=$x1, x2=$x2, x3=$x3, x4=$x4, y1=$y1, y2=$y2, y3=$y3, y4=$y4]';
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
final json = <String, dynamic>{};
|
||||||
|
json[r'assetId'] = this.assetId;
|
||||||
|
json[r'boxScore'] = this.boxScore;
|
||||||
|
json[r'id'] = this.id;
|
||||||
|
json[r'isVisible'] = this.isVisible;
|
||||||
|
json[r'text'] = this.text;
|
||||||
|
json[r'textScore'] = this.textScore;
|
||||||
|
json[r'x1'] = this.x1;
|
||||||
|
json[r'x2'] = this.x2;
|
||||||
|
json[r'x3'] = this.x3;
|
||||||
|
json[r'x4'] = this.x4;
|
||||||
|
json[r'y1'] = this.y1;
|
||||||
|
json[r'y2'] = this.y2;
|
||||||
|
json[r'y3'] = this.y3;
|
||||||
|
json[r'y4'] = this.y4;
|
||||||
|
return json;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a new [SyncAssetOcrV1] instance and imports its values from
|
||||||
|
/// [value] if it's a [Map], null otherwise.
|
||||||
|
// ignore: prefer_constructors_over_static_methods
|
||||||
|
static SyncAssetOcrV1? fromJson(dynamic value) {
|
||||||
|
upgradeDto(value, "SyncAssetOcrV1");
|
||||||
|
if (value is Map) {
|
||||||
|
final json = value.cast<String, dynamic>();
|
||||||
|
|
||||||
|
return SyncAssetOcrV1(
|
||||||
|
assetId: mapValueOfType<String>(json, r'assetId')!,
|
||||||
|
boxScore: mapValueOfType<double>(json, r'boxScore')!,
|
||||||
|
id: mapValueOfType<String>(json, r'id')!,
|
||||||
|
isVisible: mapValueOfType<bool>(json, r'isVisible')!,
|
||||||
|
text: mapValueOfType<String>(json, r'text')!,
|
||||||
|
textScore: mapValueOfType<double>(json, r'textScore')!,
|
||||||
|
x1: mapValueOfType<double>(json, r'x1')!,
|
||||||
|
x2: mapValueOfType<double>(json, r'x2')!,
|
||||||
|
x3: mapValueOfType<double>(json, r'x3')!,
|
||||||
|
x4: mapValueOfType<double>(json, r'x4')!,
|
||||||
|
y1: mapValueOfType<double>(json, r'y1')!,
|
||||||
|
y2: mapValueOfType<double>(json, r'y2')!,
|
||||||
|
y3: mapValueOfType<double>(json, r'y3')!,
|
||||||
|
y4: mapValueOfType<double>(json, r'y4')!,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
static List<SyncAssetOcrV1> listFromJson(dynamic json, {bool growable = false,}) {
|
||||||
|
final result = <SyncAssetOcrV1>[];
|
||||||
|
if (json is List && json.isNotEmpty) {
|
||||||
|
for (final row in json) {
|
||||||
|
final value = SyncAssetOcrV1.fromJson(row);
|
||||||
|
if (value != null) {
|
||||||
|
result.add(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result.toList(growable: growable);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Map<String, SyncAssetOcrV1> mapFromJson(dynamic json) {
|
||||||
|
final map = <String, SyncAssetOcrV1>{};
|
||||||
|
if (json is Map && json.isNotEmpty) {
|
||||||
|
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||||
|
for (final entry in json.entries) {
|
||||||
|
final value = SyncAssetOcrV1.fromJson(entry.value);
|
||||||
|
if (value != null) {
|
||||||
|
map[entry.key] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
// maps a json object with a list of SyncAssetOcrV1-objects as value to a dart map
|
||||||
|
static Map<String, List<SyncAssetOcrV1>> mapListFromJson(dynamic json, {bool growable = false,}) {
|
||||||
|
final map = <String, List<SyncAssetOcrV1>>{};
|
||||||
|
if (json is Map && json.isNotEmpty) {
|
||||||
|
// ignore: parameter_assignments
|
||||||
|
json = json.cast<String, dynamic>();
|
||||||
|
for (final entry in json.entries) {
|
||||||
|
map[entry.key] = SyncAssetOcrV1.listFromJson(entry.value, growable: growable,);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The list of required keys that must be present in a JSON.
|
||||||
|
static const requiredKeys = <String>{
|
||||||
|
'assetId',
|
||||||
|
'boxScore',
|
||||||
|
'id',
|
||||||
|
'isVisible',
|
||||||
|
'text',
|
||||||
|
'textScore',
|
||||||
|
'x1',
|
||||||
|
'x2',
|
||||||
|
'x3',
|
||||||
|
'x4',
|
||||||
|
'y1',
|
||||||
|
'y2',
|
||||||
|
'y3',
|
||||||
|
'y4',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
+6
@@ -34,6 +34,8 @@ class SyncEntityType {
|
|||||||
static const assetEditDeleteV1 = SyncEntityType._(r'AssetEditDeleteV1');
|
static const assetEditDeleteV1 = SyncEntityType._(r'AssetEditDeleteV1');
|
||||||
static const assetMetadataV1 = SyncEntityType._(r'AssetMetadataV1');
|
static const assetMetadataV1 = SyncEntityType._(r'AssetMetadataV1');
|
||||||
static const assetMetadataDeleteV1 = SyncEntityType._(r'AssetMetadataDeleteV1');
|
static const assetMetadataDeleteV1 = SyncEntityType._(r'AssetMetadataDeleteV1');
|
||||||
|
static const assetOcrV1 = SyncEntityType._(r'AssetOcrV1');
|
||||||
|
static const assetOcrDeleteV1 = SyncEntityType._(r'AssetOcrDeleteV1');
|
||||||
static const partnerV1 = SyncEntityType._(r'PartnerV1');
|
static const partnerV1 = SyncEntityType._(r'PartnerV1');
|
||||||
static const partnerDeleteV1 = SyncEntityType._(r'PartnerDeleteV1');
|
static const partnerDeleteV1 = SyncEntityType._(r'PartnerDeleteV1');
|
||||||
static const partnerAssetV1 = SyncEntityType._(r'PartnerAssetV1');
|
static const partnerAssetV1 = SyncEntityType._(r'PartnerAssetV1');
|
||||||
@@ -94,6 +96,8 @@ class SyncEntityType {
|
|||||||
assetEditDeleteV1,
|
assetEditDeleteV1,
|
||||||
assetMetadataV1,
|
assetMetadataV1,
|
||||||
assetMetadataDeleteV1,
|
assetMetadataDeleteV1,
|
||||||
|
assetOcrV1,
|
||||||
|
assetOcrDeleteV1,
|
||||||
partnerV1,
|
partnerV1,
|
||||||
partnerDeleteV1,
|
partnerDeleteV1,
|
||||||
partnerAssetV1,
|
partnerAssetV1,
|
||||||
@@ -189,6 +193,8 @@ class SyncEntityTypeTypeTransformer {
|
|||||||
case r'AssetEditDeleteV1': return SyncEntityType.assetEditDeleteV1;
|
case r'AssetEditDeleteV1': return SyncEntityType.assetEditDeleteV1;
|
||||||
case r'AssetMetadataV1': return SyncEntityType.assetMetadataV1;
|
case r'AssetMetadataV1': return SyncEntityType.assetMetadataV1;
|
||||||
case r'AssetMetadataDeleteV1': return SyncEntityType.assetMetadataDeleteV1;
|
case r'AssetMetadataDeleteV1': return SyncEntityType.assetMetadataDeleteV1;
|
||||||
|
case r'AssetOcrV1': return SyncEntityType.assetOcrV1;
|
||||||
|
case r'AssetOcrDeleteV1': return SyncEntityType.assetOcrDeleteV1;
|
||||||
case r'PartnerV1': return SyncEntityType.partnerV1;
|
case r'PartnerV1': return SyncEntityType.partnerV1;
|
||||||
case r'PartnerDeleteV1': return SyncEntityType.partnerDeleteV1;
|
case r'PartnerDeleteV1': return SyncEntityType.partnerDeleteV1;
|
||||||
case r'PartnerAssetV1': return SyncEntityType.partnerAssetV1;
|
case r'PartnerAssetV1': return SyncEntityType.partnerAssetV1;
|
||||||
|
|||||||
+3
@@ -35,6 +35,7 @@ class SyncRequestType {
|
|||||||
static const assetExifsV1 = SyncRequestType._(r'AssetExifsV1');
|
static const assetExifsV1 = SyncRequestType._(r'AssetExifsV1');
|
||||||
static const assetEditsV1 = SyncRequestType._(r'AssetEditsV1');
|
static const assetEditsV1 = SyncRequestType._(r'AssetEditsV1');
|
||||||
static const assetMetadataV1 = SyncRequestType._(r'AssetMetadataV1');
|
static const assetMetadataV1 = SyncRequestType._(r'AssetMetadataV1');
|
||||||
|
static const assetOcrV1 = SyncRequestType._(r'AssetOcrV1');
|
||||||
static const authUsersV1 = SyncRequestType._(r'AuthUsersV1');
|
static const authUsersV1 = SyncRequestType._(r'AuthUsersV1');
|
||||||
static const memoriesV1 = SyncRequestType._(r'MemoriesV1');
|
static const memoriesV1 = SyncRequestType._(r'MemoriesV1');
|
||||||
static const memoryToAssetsV1 = SyncRequestType._(r'MemoryToAssetsV1');
|
static const memoryToAssetsV1 = SyncRequestType._(r'MemoryToAssetsV1');
|
||||||
@@ -64,6 +65,7 @@ class SyncRequestType {
|
|||||||
assetExifsV1,
|
assetExifsV1,
|
||||||
assetEditsV1,
|
assetEditsV1,
|
||||||
assetMetadataV1,
|
assetMetadataV1,
|
||||||
|
assetOcrV1,
|
||||||
authUsersV1,
|
authUsersV1,
|
||||||
memoriesV1,
|
memoriesV1,
|
||||||
memoryToAssetsV1,
|
memoryToAssetsV1,
|
||||||
@@ -128,6 +130,7 @@ class SyncRequestTypeTypeTransformer {
|
|||||||
case r'AssetExifsV1': return SyncRequestType.assetExifsV1;
|
case r'AssetExifsV1': return SyncRequestType.assetExifsV1;
|
||||||
case r'AssetEditsV1': return SyncRequestType.assetEditsV1;
|
case r'AssetEditsV1': return SyncRequestType.assetEditsV1;
|
||||||
case r'AssetMetadataV1': return SyncRequestType.assetMetadataV1;
|
case r'AssetMetadataV1': return SyncRequestType.assetMetadataV1;
|
||||||
|
case r'AssetOcrV1': return SyncRequestType.assetOcrV1;
|
||||||
case r'AuthUsersV1': return SyncRequestType.authUsersV1;
|
case r'AuthUsersV1': return SyncRequestType.authUsersV1;
|
||||||
case r'MemoriesV1': return SyncRequestType.memoriesV1;
|
case r'MemoriesV1': return SyncRequestType.memoriesV1;
|
||||||
case r'MemoryToAssetsV1': return SyncRequestType.memoryToAssetsV1;
|
case r'MemoryToAssetsV1': return SyncRequestType.memoryToAssetsV1;
|
||||||
|
|||||||
+4
@@ -32,6 +32,7 @@ import 'schema_v25.dart' as v25;
|
|||||||
import 'schema_v26.dart' as v26;
|
import 'schema_v26.dart' as v26;
|
||||||
import 'schema_v27.dart' as v27;
|
import 'schema_v27.dart' as v27;
|
||||||
import 'schema_v28.dart' as v28;
|
import 'schema_v28.dart' as v28;
|
||||||
|
import 'schema_v29.dart' as v29;
|
||||||
|
|
||||||
class GeneratedHelper implements SchemaInstantiationHelper {
|
class GeneratedHelper implements SchemaInstantiationHelper {
|
||||||
@override
|
@override
|
||||||
@@ -93,6 +94,8 @@ class GeneratedHelper implements SchemaInstantiationHelper {
|
|||||||
return v27.DatabaseAtV27(db);
|
return v27.DatabaseAtV27(db);
|
||||||
case 28:
|
case 28:
|
||||||
return v28.DatabaseAtV28(db);
|
return v28.DatabaseAtV28(db);
|
||||||
|
case 29:
|
||||||
|
return v29.DatabaseAtV29(db);
|
||||||
default:
|
default:
|
||||||
throw MissingSchemaException(version, versions);
|
throw MissingSchemaException(version, versions);
|
||||||
}
|
}
|
||||||
@@ -127,5 +130,6 @@ class GeneratedHelper implements SchemaInstantiationHelper {
|
|||||||
26,
|
26,
|
||||||
27,
|
27,
|
||||||
28,
|
28,
|
||||||
|
29,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
+10027
File diff suppressed because it is too large
Load Diff
@@ -23805,6 +23805,118 @@
|
|||||||
],
|
],
|
||||||
"type": "object"
|
"type": "object"
|
||||||
},
|
},
|
||||||
|
"SyncAssetOcrDeleteV1": {
|
||||||
|
"properties": {
|
||||||
|
"assetId": {
|
||||||
|
"description": "Original asset ID of the deleted OCR entry",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"deletedAt": {
|
||||||
|
"description": "Timestamp when the OCR entry was deleted",
|
||||||
|
"example": "2024-01-01T00:00:00.000Z",
|
||||||
|
"format": "date-time",
|
||||||
|
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"id": {
|
||||||
|
"description": "Audit row ID of the deleted OCR entry",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"assetId",
|
||||||
|
"deletedAt",
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"SyncAssetOcrV1": {
|
||||||
|
"properties": {
|
||||||
|
"assetId": {
|
||||||
|
"description": "Asset ID",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"boxScore": {
|
||||||
|
"description": "Confidence score of the bounding box",
|
||||||
|
"format": "double",
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"id": {
|
||||||
|
"description": "OCR entry ID",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"isVisible": {
|
||||||
|
"description": "Whether the OCR entry is visible",
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"text": {
|
||||||
|
"description": "Recognized text content",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"textScore": {
|
||||||
|
"description": "Confidence score of the recognized text",
|
||||||
|
"format": "double",
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"x1": {
|
||||||
|
"description": "Top-left X coordinate (normalized 0–1)",
|
||||||
|
"format": "double",
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"x2": {
|
||||||
|
"description": "Top-right X coordinate (normalized 0–1)",
|
||||||
|
"format": "double",
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"x3": {
|
||||||
|
"description": "Bottom-right X coordinate (normalized 0–1)",
|
||||||
|
"format": "double",
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"x4": {
|
||||||
|
"description": "Bottom-left X coordinate (normalized 0–1)",
|
||||||
|
"format": "double",
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"y1": {
|
||||||
|
"description": "Top-left Y coordinate (normalized 0–1)",
|
||||||
|
"format": "double",
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"y2": {
|
||||||
|
"description": "Top-right Y coordinate (normalized 0–1)",
|
||||||
|
"format": "double",
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"y3": {
|
||||||
|
"description": "Bottom-right Y coordinate (normalized 0–1)",
|
||||||
|
"format": "double",
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"y4": {
|
||||||
|
"description": "Bottom-left Y coordinate (normalized 0–1)",
|
||||||
|
"format": "double",
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"assetId",
|
||||||
|
"boxScore",
|
||||||
|
"id",
|
||||||
|
"isVisible",
|
||||||
|
"text",
|
||||||
|
"textScore",
|
||||||
|
"x1",
|
||||||
|
"x2",
|
||||||
|
"x3",
|
||||||
|
"x4",
|
||||||
|
"y1",
|
||||||
|
"y2",
|
||||||
|
"y3",
|
||||||
|
"y4"
|
||||||
|
],
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
"SyncAssetV1": {
|
"SyncAssetV1": {
|
||||||
"properties": {
|
"properties": {
|
||||||
"checksum": {
|
"checksum": {
|
||||||
@@ -24186,6 +24298,8 @@
|
|||||||
"AssetEditDeleteV1",
|
"AssetEditDeleteV1",
|
||||||
"AssetMetadataV1",
|
"AssetMetadataV1",
|
||||||
"AssetMetadataDeleteV1",
|
"AssetMetadataDeleteV1",
|
||||||
|
"AssetOcrV1",
|
||||||
|
"AssetOcrDeleteV1",
|
||||||
"PartnerV1",
|
"PartnerV1",
|
||||||
"PartnerDeleteV1",
|
"PartnerDeleteV1",
|
||||||
"PartnerAssetV1",
|
"PartnerAssetV1",
|
||||||
@@ -24508,6 +24622,7 @@
|
|||||||
"AssetExifsV1",
|
"AssetExifsV1",
|
||||||
"AssetEditsV1",
|
"AssetEditsV1",
|
||||||
"AssetMetadataV1",
|
"AssetMetadataV1",
|
||||||
|
"AssetOcrV1",
|
||||||
"AuthUsersV1",
|
"AuthUsersV1",
|
||||||
"MemoriesV1",
|
"MemoriesV1",
|
||||||
"MemoryToAssetsV1",
|
"MemoryToAssetsV1",
|
||||||
|
|||||||
@@ -3013,6 +3013,44 @@ export type SyncAssetMetadataV1 = {
|
|||||||
[key: string]: any;
|
[key: string]: any;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
export type SyncAssetOcrDeleteV1 = {
|
||||||
|
/** Original asset ID of the deleted OCR entry */
|
||||||
|
assetId: string;
|
||||||
|
/** Timestamp when the OCR entry was deleted */
|
||||||
|
deletedAt: string;
|
||||||
|
/** Audit row ID of the deleted OCR entry */
|
||||||
|
id: string;
|
||||||
|
};
|
||||||
|
export type SyncAssetOcrV1 = {
|
||||||
|
/** Asset ID */
|
||||||
|
assetId: string;
|
||||||
|
/** Confidence score of the bounding box */
|
||||||
|
boxScore: number;
|
||||||
|
/** OCR entry ID */
|
||||||
|
id: string;
|
||||||
|
/** Whether the OCR entry is visible */
|
||||||
|
isVisible: boolean;
|
||||||
|
/** Recognized text content */
|
||||||
|
text: string;
|
||||||
|
/** Confidence score of the recognized text */
|
||||||
|
textScore: number;
|
||||||
|
/** Top-left X coordinate (normalized 0–1) */
|
||||||
|
x1: number;
|
||||||
|
/** Top-right X coordinate (normalized 0–1) */
|
||||||
|
x2: number;
|
||||||
|
/** Bottom-right X coordinate (normalized 0–1) */
|
||||||
|
x3: number;
|
||||||
|
/** Bottom-left X coordinate (normalized 0–1) */
|
||||||
|
x4: number;
|
||||||
|
/** Top-left Y coordinate (normalized 0–1) */
|
||||||
|
y1: number;
|
||||||
|
/** Top-right Y coordinate (normalized 0–1) */
|
||||||
|
y2: number;
|
||||||
|
/** Bottom-right Y coordinate (normalized 0–1) */
|
||||||
|
y3: number;
|
||||||
|
/** Bottom-left Y coordinate (normalized 0–1) */
|
||||||
|
y4: number;
|
||||||
|
};
|
||||||
export type SyncAssetV1 = {
|
export type SyncAssetV1 = {
|
||||||
/** Checksum */
|
/** Checksum */
|
||||||
checksum: string;
|
checksum: string;
|
||||||
@@ -7336,6 +7374,8 @@ export enum SyncEntityType {
|
|||||||
AssetEditDeleteV1 = "AssetEditDeleteV1",
|
AssetEditDeleteV1 = "AssetEditDeleteV1",
|
||||||
AssetMetadataV1 = "AssetMetadataV1",
|
AssetMetadataV1 = "AssetMetadataV1",
|
||||||
AssetMetadataDeleteV1 = "AssetMetadataDeleteV1",
|
AssetMetadataDeleteV1 = "AssetMetadataDeleteV1",
|
||||||
|
AssetOcrV1 = "AssetOcrV1",
|
||||||
|
AssetOcrDeleteV1 = "AssetOcrDeleteV1",
|
||||||
PartnerV1 = "PartnerV1",
|
PartnerV1 = "PartnerV1",
|
||||||
PartnerDeleteV1 = "PartnerDeleteV1",
|
PartnerDeleteV1 = "PartnerDeleteV1",
|
||||||
PartnerAssetV1 = "PartnerAssetV1",
|
PartnerAssetV1 = "PartnerAssetV1",
|
||||||
@@ -7396,6 +7436,7 @@ export enum SyncRequestType {
|
|||||||
AssetExifsV1 = "AssetExifsV1",
|
AssetExifsV1 = "AssetExifsV1",
|
||||||
AssetEditsV1 = "AssetEditsV1",
|
AssetEditsV1 = "AssetEditsV1",
|
||||||
AssetMetadataV1 = "AssetMetadataV1",
|
AssetMetadataV1 = "AssetMetadataV1",
|
||||||
|
AssetOcrV1 = "AssetOcrV1",
|
||||||
AuthUsersV1 = "AuthUsersV1",
|
AuthUsersV1 = "AuthUsersV1",
|
||||||
MemoriesV1 = "MemoriesV1",
|
MemoriesV1 = "MemoriesV1",
|
||||||
MemoryToAssetsV1 = "MemoryToAssetsV1",
|
MemoryToAssetsV1 = "MemoryToAssetsV1",
|
||||||
|
|||||||
@@ -444,6 +444,23 @@ export const columns = {
|
|||||||
'asset_exif.rating',
|
'asset_exif.rating',
|
||||||
'asset_exif.fps',
|
'asset_exif.fps',
|
||||||
],
|
],
|
||||||
|
syncAssetOcr: [
|
||||||
|
'asset_ocr.id',
|
||||||
|
'asset_ocr.assetId',
|
||||||
|
'asset_ocr.x1',
|
||||||
|
'asset_ocr.y1',
|
||||||
|
'asset_ocr.x2',
|
||||||
|
'asset_ocr.y2',
|
||||||
|
'asset_ocr.x3',
|
||||||
|
'asset_ocr.y3',
|
||||||
|
'asset_ocr.x4',
|
||||||
|
'asset_ocr.y4',
|
||||||
|
'asset_ocr.text',
|
||||||
|
'asset_ocr.boxScore',
|
||||||
|
'asset_ocr.textScore',
|
||||||
|
'asset_ocr.updateId',
|
||||||
|
'asset_ocr.isVisible',
|
||||||
|
],
|
||||||
syncAssetEdit: [
|
syncAssetEdit: [
|
||||||
'asset_edit.id',
|
'asset_edit.id',
|
||||||
'asset_edit.assetId',
|
'asset_edit.assetId',
|
||||||
|
|||||||
@@ -401,6 +401,41 @@ class SyncMemoryDeleteV1 extends createZodDto(SyncMemoryDeleteV1Schema) {}
|
|||||||
class SyncMemoryAssetV1 extends createZodDto(SyncMemoryAssetV1Schema) {}
|
class SyncMemoryAssetV1 extends createZodDto(SyncMemoryAssetV1Schema) {}
|
||||||
@ExtraModel()
|
@ExtraModel()
|
||||||
class SyncMemoryAssetDeleteV1 extends createZodDto(SyncMemoryAssetDeleteV1Schema) {}
|
class SyncMemoryAssetDeleteV1 extends createZodDto(SyncMemoryAssetDeleteV1Schema) {}
|
||||||
|
|
||||||
|
const SyncAssetOcrV1Schema = z
|
||||||
|
.object({
|
||||||
|
id: z.string().describe('OCR entry ID'),
|
||||||
|
assetId: z.string().describe('Asset ID'),
|
||||||
|
|
||||||
|
x1: z.number().meta({ format: 'double' }).describe('Top-left X coordinate (normalized 0–1)'),
|
||||||
|
y1: z.number().meta({ format: 'double' }).describe('Top-left Y coordinate (normalized 0–1)'),
|
||||||
|
x2: z.number().meta({ format: 'double' }).describe('Top-right X coordinate (normalized 0–1)'),
|
||||||
|
y2: z.number().meta({ format: 'double' }).describe('Top-right Y coordinate (normalized 0–1)'),
|
||||||
|
x3: z.number().meta({ format: 'double' }).describe('Bottom-right X coordinate (normalized 0–1)'),
|
||||||
|
y3: z.number().meta({ format: 'double' }).describe('Bottom-right Y coordinate (normalized 0–1)'),
|
||||||
|
x4: z.number().meta({ format: 'double' }).describe('Bottom-left X coordinate (normalized 0–1)'),
|
||||||
|
y4: z.number().meta({ format: 'double' }).describe('Bottom-left Y coordinate (normalized 0–1)'),
|
||||||
|
|
||||||
|
boxScore: z.number().meta({ format: 'double' }).describe('Confidence score of the bounding box'),
|
||||||
|
textScore: z.number().meta({ format: 'double' }).describe('Confidence score of the recognized text'),
|
||||||
|
text: z.string().describe('Recognized text content'),
|
||||||
|
isVisible: z.boolean().describe('Whether the OCR entry is visible'),
|
||||||
|
})
|
||||||
|
.meta({ id: 'SyncAssetOcrV1' });
|
||||||
|
|
||||||
|
const SyncAssetOcrDeleteV1Schema = z
|
||||||
|
.object({
|
||||||
|
id: z.string().describe('Audit row ID of the deleted OCR entry'),
|
||||||
|
assetId: z.string().describe('Original asset ID of the deleted OCR entry'),
|
||||||
|
deletedAt: isoDatetimeToDate.describe('Timestamp when the OCR entry was deleted'),
|
||||||
|
})
|
||||||
|
.meta({ id: 'SyncAssetOcrDeleteV1' });
|
||||||
|
|
||||||
|
@ExtraModel()
|
||||||
|
class SyncAssetOcrV1 extends createZodDto(SyncAssetOcrV1Schema) {}
|
||||||
|
|
||||||
|
@ExtraModel()
|
||||||
|
class SyncAssetOcrDeleteV1 extends createZodDto(SyncAssetOcrDeleteV1Schema) {}
|
||||||
@ExtraModel()
|
@ExtraModel()
|
||||||
class SyncStackV1 extends createZodDto(SyncStackV1Schema) {}
|
class SyncStackV1 extends createZodDto(SyncStackV1Schema) {}
|
||||||
@ExtraModel()
|
@ExtraModel()
|
||||||
@@ -437,6 +472,8 @@ export type SyncItem = {
|
|||||||
[SyncEntityType.AssetMetadataV1]: SyncAssetMetadataV1;
|
[SyncEntityType.AssetMetadataV1]: SyncAssetMetadataV1;
|
||||||
[SyncEntityType.AssetMetadataDeleteV1]: SyncAssetMetadataDeleteV1;
|
[SyncEntityType.AssetMetadataDeleteV1]: SyncAssetMetadataDeleteV1;
|
||||||
[SyncEntityType.AssetExifV1]: SyncAssetExifV1;
|
[SyncEntityType.AssetExifV1]: SyncAssetExifV1;
|
||||||
|
[SyncEntityType.AssetOcrV1]: SyncAssetOcrV1;
|
||||||
|
[SyncEntityType.AssetOcrDeleteV1]: SyncAssetOcrDeleteV1;
|
||||||
[SyncEntityType.AssetEditV1]: SyncAssetEditV1;
|
[SyncEntityType.AssetEditV1]: SyncAssetEditV1;
|
||||||
[SyncEntityType.AssetEditDeleteV1]: SyncAssetEditDeleteV1;
|
[SyncEntityType.AssetEditDeleteV1]: SyncAssetEditDeleteV1;
|
||||||
[SyncEntityType.PartnerAssetV2]: SyncAssetV2;
|
[SyncEntityType.PartnerAssetV2]: SyncAssetV2;
|
||||||
|
|||||||
@@ -952,6 +952,7 @@ export enum SyncRequestType {
|
|||||||
AssetExifsV1 = 'AssetExifsV1',
|
AssetExifsV1 = 'AssetExifsV1',
|
||||||
AssetEditsV1 = 'AssetEditsV1',
|
AssetEditsV1 = 'AssetEditsV1',
|
||||||
AssetMetadataV1 = 'AssetMetadataV1',
|
AssetMetadataV1 = 'AssetMetadataV1',
|
||||||
|
AssetOcrV1 = 'AssetOcrV1',
|
||||||
AuthUsersV1 = 'AuthUsersV1',
|
AuthUsersV1 = 'AuthUsersV1',
|
||||||
MemoriesV1 = 'MemoriesV1',
|
MemoriesV1 = 'MemoriesV1',
|
||||||
MemoryToAssetsV1 = 'MemoryToAssetsV1',
|
MemoryToAssetsV1 = 'MemoryToAssetsV1',
|
||||||
@@ -990,6 +991,8 @@ export enum SyncEntityType {
|
|||||||
AssetEditDeleteV1 = 'AssetEditDeleteV1',
|
AssetEditDeleteV1 = 'AssetEditDeleteV1',
|
||||||
AssetMetadataV1 = 'AssetMetadataV1',
|
AssetMetadataV1 = 'AssetMetadataV1',
|
||||||
AssetMetadataDeleteV1 = 'AssetMetadataDeleteV1',
|
AssetMetadataDeleteV1 = 'AssetMetadataDeleteV1',
|
||||||
|
AssetOcrV1 = 'AssetOcrV1',
|
||||||
|
AssetOcrDeleteV1 = 'AssetOcrDeleteV1',
|
||||||
|
|
||||||
PartnerV1 = 'PartnerV1',
|
PartnerV1 = 'PartnerV1',
|
||||||
PartnerDeleteV1 = 'PartnerDeleteV1',
|
PartnerDeleteV1 = 'PartnerDeleteV1',
|
||||||
|
|||||||
@@ -579,6 +579,48 @@ where
|
|||||||
order by
|
order by
|
||||||
"asset_metadata"."updateId" asc
|
"asset_metadata"."updateId" asc
|
||||||
|
|
||||||
|
-- SyncRepository.assetOcr.getDeletes
|
||||||
|
select
|
||||||
|
"asset_ocr_audit"."id",
|
||||||
|
"asset_ocr_audit"."assetId",
|
||||||
|
"asset_ocr_audit"."deletedAt"
|
||||||
|
from
|
||||||
|
"asset_ocr_audit" as "asset_ocr_audit"
|
||||||
|
left join "asset" on "asset"."id" = "asset_ocr_audit"."assetId"
|
||||||
|
where
|
||||||
|
"asset_ocr_audit"."id" < $1
|
||||||
|
and "asset_ocr_audit"."id" > $2
|
||||||
|
and "asset"."ownerId" = $3
|
||||||
|
order by
|
||||||
|
"asset_ocr_audit"."id" asc
|
||||||
|
|
||||||
|
-- SyncRepository.assetOcr.getUpserts
|
||||||
|
select
|
||||||
|
"asset_ocr"."id",
|
||||||
|
"asset_ocr"."assetId",
|
||||||
|
"asset_ocr"."x1",
|
||||||
|
"asset_ocr"."y1",
|
||||||
|
"asset_ocr"."x2",
|
||||||
|
"asset_ocr"."y2",
|
||||||
|
"asset_ocr"."x3",
|
||||||
|
"asset_ocr"."y3",
|
||||||
|
"asset_ocr"."x4",
|
||||||
|
"asset_ocr"."y4",
|
||||||
|
"asset_ocr"."text",
|
||||||
|
"asset_ocr"."boxScore",
|
||||||
|
"asset_ocr"."textScore",
|
||||||
|
"asset_ocr"."updateId",
|
||||||
|
"asset_ocr"."isVisible"
|
||||||
|
from
|
||||||
|
"asset_ocr" as "asset_ocr"
|
||||||
|
inner join "asset" on "asset"."id" = "asset_ocr"."assetId"
|
||||||
|
where
|
||||||
|
"asset_ocr"."updateId" < $1
|
||||||
|
and "asset_ocr"."updateId" > $2
|
||||||
|
and "asset"."ownerId" = $3
|
||||||
|
order by
|
||||||
|
"asset_ocr"."updateId" asc
|
||||||
|
|
||||||
-- SyncRepository.authUser.getUpserts
|
-- SyncRepository.authUser.getUpserts
|
||||||
select
|
select
|
||||||
"id",
|
"id",
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ export class SyncRepository {
|
|||||||
assetEdit: AssetEditSync;
|
assetEdit: AssetEditSync;
|
||||||
assetFace: AssetFaceSync;
|
assetFace: AssetFaceSync;
|
||||||
assetMetadata: AssetMetadataSync;
|
assetMetadata: AssetMetadataSync;
|
||||||
|
assetOcr: AssetOcrSync;
|
||||||
authUser: AuthUserSync;
|
authUser: AuthUserSync;
|
||||||
memory: MemorySync;
|
memory: MemorySync;
|
||||||
memoryToAsset: MemoryToAssetSync;
|
memoryToAsset: MemoryToAssetSync;
|
||||||
@@ -79,6 +80,7 @@ export class SyncRepository {
|
|||||||
this.assetEdit = new AssetEditSync(this.db);
|
this.assetEdit = new AssetEditSync(this.db);
|
||||||
this.assetFace = new AssetFaceSync(this.db);
|
this.assetFace = new AssetFaceSync(this.db);
|
||||||
this.assetMetadata = new AssetMetadataSync(this.db);
|
this.assetMetadata = new AssetMetadataSync(this.db);
|
||||||
|
this.assetOcr = new AssetOcrSync(this.db);
|
||||||
this.authUser = new AuthUserSync(this.db);
|
this.authUser = new AuthUserSync(this.db);
|
||||||
this.memory = new MemorySync(this.db);
|
this.memory = new MemorySync(this.db);
|
||||||
this.memoryToAsset = new MemoryToAssetSync(this.db);
|
this.memoryToAsset = new MemoryToAssetSync(this.db);
|
||||||
@@ -769,3 +771,27 @@ class AssetMetadataSync extends BaseSync {
|
|||||||
.stream();
|
.stream();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class AssetOcrSync extends BaseSync {
|
||||||
|
@GenerateSql({ params: [dummyQueryOptions, DummyValue.UUID], stream: true })
|
||||||
|
getDeletes(options: SyncQueryOptions, userId: string) {
|
||||||
|
return this.auditQuery('asset_ocr_audit', options)
|
||||||
|
.select(['asset_ocr_audit.id', 'asset_ocr_audit.assetId', 'asset_ocr_audit.deletedAt'])
|
||||||
|
.leftJoin('asset', 'asset.id', 'asset_ocr_audit.assetId')
|
||||||
|
.where('asset.ownerId', '=', userId)
|
||||||
|
.stream();
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanupAuditTable(daysAgo: number) {
|
||||||
|
return this.auditCleanup('asset_ocr_audit', daysAgo);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GenerateSql({ params: [dummyQueryOptions, DummyValue.UUID], stream: true })
|
||||||
|
getUpserts(options: SyncQueryOptions, userId: string) {
|
||||||
|
return this.upsertQuery('asset_ocr', options)
|
||||||
|
.select(columns.syncAssetOcr)
|
||||||
|
.innerJoin('asset', 'asset.id', 'asset_ocr.assetId')
|
||||||
|
.where('asset.ownerId', '=', userId)
|
||||||
|
.stream();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -287,3 +287,16 @@ export const asset_edit_audit = registerFunction({
|
|||||||
RETURN NULL;
|
RETURN NULL;
|
||||||
END`,
|
END`,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const asset_ocr_delete_audit = registerFunction({
|
||||||
|
name: 'asset_ocr_delete_audit',
|
||||||
|
returnType: 'TRIGGER',
|
||||||
|
language: 'PLPGSQL',
|
||||||
|
body: `
|
||||||
|
BEGIN
|
||||||
|
INSERT INTO asset_ocr_audit ("assetId")
|
||||||
|
SELECT "assetId"
|
||||||
|
FROM OLD;
|
||||||
|
RETURN NULL;
|
||||||
|
END`,
|
||||||
|
});
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import {
|
|||||||
asset_delete_audit,
|
asset_delete_audit,
|
||||||
asset_face_audit,
|
asset_face_audit,
|
||||||
asset_metadata_audit,
|
asset_metadata_audit,
|
||||||
|
asset_ocr_delete_audit,
|
||||||
f_concat_ws,
|
f_concat_ws,
|
||||||
f_unaccent,
|
f_unaccent,
|
||||||
immich_uuid_v7,
|
immich_uuid_v7,
|
||||||
@@ -43,6 +44,7 @@ import { AssetFileTable } from 'src/schema/tables/asset-file.table';
|
|||||||
import { AssetJobStatusTable } from 'src/schema/tables/asset-job-status.table';
|
import { AssetJobStatusTable } from 'src/schema/tables/asset-job-status.table';
|
||||||
import { AssetMetadataAuditTable } from 'src/schema/tables/asset-metadata-audit.table';
|
import { AssetMetadataAuditTable } from 'src/schema/tables/asset-metadata-audit.table';
|
||||||
import { AssetMetadataTable } from 'src/schema/tables/asset-metadata.table';
|
import { AssetMetadataTable } from 'src/schema/tables/asset-metadata.table';
|
||||||
|
import { AssetOcrAuditTable } from 'src/schema/tables/asset-ocr-audit.table';
|
||||||
import { AssetOcrTable } from 'src/schema/tables/asset-ocr.table';
|
import { AssetOcrTable } from 'src/schema/tables/asset-ocr.table';
|
||||||
import { AssetTable } from 'src/schema/tables/asset.table';
|
import { AssetTable } from 'src/schema/tables/asset.table';
|
||||||
import { FaceSearchTable } from 'src/schema/tables/face-search.table';
|
import { FaceSearchTable } from 'src/schema/tables/face-search.table';
|
||||||
@@ -107,6 +109,7 @@ export class ImmichDatabase {
|
|||||||
AssetMetadataAuditTable,
|
AssetMetadataAuditTable,
|
||||||
AssetJobStatusTable,
|
AssetJobStatusTable,
|
||||||
AssetOcrTable,
|
AssetOcrTable,
|
||||||
|
AssetOcrAuditTable,
|
||||||
AssetTable,
|
AssetTable,
|
||||||
AssetFileTable,
|
AssetFileTable,
|
||||||
AssetExifTable,
|
AssetExifTable,
|
||||||
@@ -168,6 +171,7 @@ export class ImmichDatabase {
|
|||||||
user_metadata_audit,
|
user_metadata_audit,
|
||||||
asset_metadata_audit,
|
asset_metadata_audit,
|
||||||
asset_face_audit,
|
asset_face_audit,
|
||||||
|
asset_ocr_delete_audit,
|
||||||
];
|
];
|
||||||
|
|
||||||
enum = [album_user_role_enum, assets_status_enum, asset_face_source_type, asset_visibility_enum];
|
enum = [album_user_role_enum, assets_status_enum, asset_face_source_type, asset_visibility_enum];
|
||||||
@@ -205,6 +209,7 @@ export interface DB {
|
|||||||
asset_metadata_audit: AssetMetadataAuditTable;
|
asset_metadata_audit: AssetMetadataAuditTable;
|
||||||
asset_job_status: AssetJobStatusTable;
|
asset_job_status: AssetJobStatusTable;
|
||||||
asset_ocr: AssetOcrTable;
|
asset_ocr: AssetOcrTable;
|
||||||
|
asset_ocr_audit: AssetOcrAuditTable;
|
||||||
asset_audio: AssetAudioTable;
|
asset_audio: AssetAudioTable;
|
||||||
asset_video: AssetVideoTable;
|
asset_video: AssetVideoTable;
|
||||||
asset_keyframe: AssetKeyframeTable;
|
asset_keyframe: AssetKeyframeTable;
|
||||||
|
|||||||
@@ -0,0 +1,58 @@
|
|||||||
|
import { Kysely, sql } from 'kysely';
|
||||||
|
|
||||||
|
export async function up(db: Kysely<any>): Promise<void> {
|
||||||
|
await sql`CREATE OR REPLACE FUNCTION asset_edit_delete()
|
||||||
|
RETURNS TRIGGER
|
||||||
|
LANGUAGE PLPGSQL
|
||||||
|
AS $$
|
||||||
|
BEGIN
|
||||||
|
UPDATE asset
|
||||||
|
SET "isEdited" = false
|
||||||
|
FROM deleted_edit
|
||||||
|
WHERE asset.id = deleted_edit."assetId" AND asset."isEdited"
|
||||||
|
AND NOT EXISTS (SELECT FROM asset_edit edit WHERE edit."assetId" = asset.id);
|
||||||
|
RETURN NULL;
|
||||||
|
END
|
||||||
|
$$;`.execute(db);
|
||||||
|
await sql`CREATE OR REPLACE FUNCTION asset_ocr_delete_audit()
|
||||||
|
RETURNS TRIGGER
|
||||||
|
LANGUAGE PLPGSQL
|
||||||
|
AS $$
|
||||||
|
BEGIN
|
||||||
|
INSERT INTO asset_ocr_audit ("assetId")
|
||||||
|
SELECT "assetId"
|
||||||
|
FROM OLD;
|
||||||
|
RETURN NULL;
|
||||||
|
END
|
||||||
|
$$;`.execute(db);
|
||||||
|
await sql`CREATE TABLE "asset_ocr_audit" (
|
||||||
|
"id" uuid NOT NULL DEFAULT immich_uuid_v7(),
|
||||||
|
"assetId" uuid NOT NULL,
|
||||||
|
"deletedAt" timestamp with time zone NOT NULL DEFAULT clock_timestamp(),
|
||||||
|
CONSTRAINT "asset_ocr_audit_pkey" PRIMARY KEY ("id")
|
||||||
|
);`.execute(db);
|
||||||
|
await sql`CREATE INDEX "asset_ocr_audit_assetId_idx" ON "asset_ocr_audit" ("assetId");`.execute(db);
|
||||||
|
await sql`CREATE INDEX "asset_ocr_audit_deletedAt_idx" ON "asset_ocr_audit" ("deletedAt");`.execute(db);
|
||||||
|
await sql`ALTER TABLE "asset_ocr" ADD "updateId" uuid NOT NULL DEFAULT immich_uuid_v7();`.execute(db);
|
||||||
|
await sql`CREATE INDEX "asset_ocr_updateId_idx" ON "asset_ocr" ("updateId");`.execute(db);
|
||||||
|
await sql`CREATE OR REPLACE TRIGGER "asset_ocr_delete_audit"
|
||||||
|
AFTER DELETE ON "asset_ocr"
|
||||||
|
REFERENCING OLD TABLE AS "old"
|
||||||
|
FOR EACH STATEMENT
|
||||||
|
WHEN (pg_trigger_depth() = 0)
|
||||||
|
EXECUTE FUNCTION asset_ocr_delete_audit();`.execute(db);
|
||||||
|
await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('function_asset_ocr_delete_audit', '{"type":"function","name":"asset_ocr_delete_audit","sql":"CREATE OR REPLACE FUNCTION asset_ocr_delete_audit()\\n RETURNS TRIGGER\\n LANGUAGE PLPGSQL\\n AS $$\\n BEGIN\\n INSERT INTO asset_ocr_audit (\\"assetId\\")\\n SELECT \\"assetId\\"\\n FROM OLD;\\n RETURN NULL;\\n END\\n $$;"}'::jsonb);`.execute(db);
|
||||||
|
await sql`INSERT INTO "migration_overrides" ("name", "value") VALUES ('trigger_asset_ocr_delete_audit', '{"type":"trigger","name":"asset_ocr_delete_audit","sql":"CREATE OR REPLACE TRIGGER \\"asset_ocr_delete_audit\\"\\n AFTER DELETE ON \\"asset_ocr\\"\\n REFERENCING OLD TABLE AS \\"old\\"\\n FOR EACH STATEMENT\\n WHEN (pg_trigger_depth() = 0)\\n EXECUTE FUNCTION asset_ocr_delete_audit();"}'::jsonb);`.execute(db);
|
||||||
|
await sql`UPDATE "migration_overrides" SET "value" = '{"type":"function","name":"asset_edit_delete","sql":"CREATE OR REPLACE FUNCTION asset_edit_delete()\\n RETURNS TRIGGER\\n LANGUAGE PLPGSQL\\n AS $$\\n BEGIN\\n UPDATE asset\\n SET \\"isEdited\\" = false\\n FROM deleted_edit\\n WHERE asset.id = deleted_edit.\\"assetId\\" AND asset.\\"isEdited\\"\\n AND NOT EXISTS (SELECT FROM asset_edit edit WHERE edit.\\"assetId\\" = asset.id);\\n RETURN NULL;\\n END\\n $$;"}'::jsonb WHERE "name" = 'function_asset_edit_delete';`.execute(db);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(db: Kysely<any>): Promise<void> {
|
||||||
|
await sql`DROP TRIGGER "asset_ocr_delete_audit" ON "asset_ocr";`.execute(db);
|
||||||
|
await sql`DROP INDEX "asset_ocr_updateId_idx";`.execute(db);
|
||||||
|
await sql`ALTER TABLE "asset_ocr" DROP COLUMN "updateId";`.execute(db);
|
||||||
|
await sql`DROP TABLE "asset_ocr_audit";`.execute(db);
|
||||||
|
await sql`DROP FUNCTION asset_ocr_delete_audit;`.execute(db);
|
||||||
|
await sql`UPDATE "migration_overrides" SET "value" = '{"sql":"CREATE OR REPLACE FUNCTION asset_edit_delete()\\n RETURNS TRIGGER\\n LANGUAGE PLPGSQL\\n AS $$\\n BEGIN\\n UPDATE asset\\n SET \\"isEdited\\" = false\\n FROM deleted_edit\\n WHERE asset.id = deleted_edit.\\"assetId\\" AND asset.\\"isEdited\\" \\n AND NOT EXISTS (SELECT FROM asset_edit edit WHERE edit.\\"assetId\\" = asset.id);\\n RETURN NULL;\\n END\\n $$;","name":"asset_edit_delete","type":"function"}'::jsonb WHERE "name" = 'function_asset_edit_delete';`.execute(db);
|
||||||
|
await sql`DELETE FROM "migration_overrides" WHERE "name" = 'function_asset_ocr_delete_audit';`.execute(db);
|
||||||
|
await sql`DELETE FROM "migration_overrides" WHERE "name" = 'trigger_asset_ocr_delete_audit';`.execute(db);
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import { Column, CreateDateColumn, Generated, Table } from '@immich/sql-tools';
|
||||||
|
import { PrimaryGeneratedUuidV7Column } from 'src/decorators';
|
||||||
|
|
||||||
|
@Table('asset_ocr_audit')
|
||||||
|
export class AssetOcrAuditTable {
|
||||||
|
@PrimaryGeneratedUuidV7Column()
|
||||||
|
id!: Generated<string>;
|
||||||
|
|
||||||
|
@Column({ type: 'uuid', index: true })
|
||||||
|
assetId!: string;
|
||||||
|
|
||||||
|
@CreateDateColumn({ default: () => 'clock_timestamp()', index: true })
|
||||||
|
deletedAt!: Date;
|
||||||
|
}
|
||||||
@@ -1,7 +1,22 @@
|
|||||||
import { Column, ForeignKeyColumn, Generated, PrimaryGeneratedColumn, Table } from '@immich/sql-tools';
|
import {
|
||||||
|
AfterDeleteTrigger,
|
||||||
|
Column,
|
||||||
|
ForeignKeyColumn,
|
||||||
|
Generated,
|
||||||
|
PrimaryGeneratedColumn,
|
||||||
|
Table,
|
||||||
|
} from '@immich/sql-tools';
|
||||||
|
import { UpdateIdColumn } from 'src/decorators';
|
||||||
|
import { asset_ocr_delete_audit } from 'src/schema/functions';
|
||||||
import { AssetTable } from 'src/schema/tables/asset.table';
|
import { AssetTable } from 'src/schema/tables/asset.table';
|
||||||
|
|
||||||
@Table('asset_ocr')
|
@Table('asset_ocr')
|
||||||
|
@AfterDeleteTrigger({
|
||||||
|
scope: 'statement',
|
||||||
|
function: asset_ocr_delete_audit,
|
||||||
|
referencingOldTableAs: 'old',
|
||||||
|
when: 'pg_trigger_depth() = 0',
|
||||||
|
})
|
||||||
export class AssetOcrTable {
|
export class AssetOcrTable {
|
||||||
@PrimaryGeneratedColumn()
|
@PrimaryGeneratedColumn()
|
||||||
id!: Generated<string>;
|
id!: Generated<string>;
|
||||||
@@ -45,4 +60,7 @@ export class AssetOcrTable {
|
|||||||
|
|
||||||
@Column({ type: 'boolean', default: true })
|
@Column({ type: 'boolean', default: true })
|
||||||
isVisible!: Generated<boolean>;
|
isVisible!: Generated<boolean>;
|
||||||
|
|
||||||
|
@UpdateIdColumn({ index: true })
|
||||||
|
updateId!: Generated<string>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -68,6 +68,7 @@ export const SYNC_TYPES_ORDER = [
|
|||||||
SyncRequestType.AlbumToAssetsV1,
|
SyncRequestType.AlbumToAssetsV1,
|
||||||
SyncRequestType.AssetExifsV1,
|
SyncRequestType.AssetExifsV1,
|
||||||
SyncRequestType.AlbumAssetExifsV1,
|
SyncRequestType.AlbumAssetExifsV1,
|
||||||
|
SyncRequestType.AssetOcrV1,
|
||||||
SyncRequestType.PartnerAssetExifsV1,
|
SyncRequestType.PartnerAssetExifsV1,
|
||||||
SyncRequestType.MemoriesV1,
|
SyncRequestType.MemoriesV1,
|
||||||
SyncRequestType.MemoryToAssetsV1,
|
SyncRequestType.MemoryToAssetsV1,
|
||||||
@@ -188,6 +189,7 @@ export class SyncService extends BaseService {
|
|||||||
[SyncRequestType.PeopleV1]: () => this.syncPeopleV1(options, response, checkpointMap),
|
[SyncRequestType.PeopleV1]: () => this.syncPeopleV1(options, response, checkpointMap),
|
||||||
[SyncRequestType.AssetFacesV2]: () => this.syncAssetFacesV2(options, response, checkpointMap),
|
[SyncRequestType.AssetFacesV2]: () => this.syncAssetFacesV2(options, response, checkpointMap),
|
||||||
[SyncRequestType.UserMetadataV1]: () => this.syncUserMetadataV1(options, response, checkpointMap),
|
[SyncRequestType.UserMetadataV1]: () => this.syncUserMetadataV1(options, response, checkpointMap),
|
||||||
|
[SyncRequestType.AssetOcrV1]: () => this.syncAssetOcrV1(options, response, checkpointMap, auth),
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
for (const type of SYNC_TYPES_ORDER.filter((type) => dto.types.includes(type))) {
|
for (const type of SYNC_TYPES_ORDER.filter((type) => dto.types.includes(type))) {
|
||||||
@@ -218,6 +220,7 @@ export class SyncService extends BaseService {
|
|||||||
await this.syncRepository.stack.cleanupAuditTable(pruneThreshold);
|
await this.syncRepository.stack.cleanupAuditTable(pruneThreshold);
|
||||||
await this.syncRepository.user.cleanupAuditTable(pruneThreshold);
|
await this.syncRepository.user.cleanupAuditTable(pruneThreshold);
|
||||||
await this.syncRepository.userMetadata.cleanupAuditTable(pruneThreshold);
|
await this.syncRepository.userMetadata.cleanupAuditTable(pruneThreshold);
|
||||||
|
await this.syncRepository.assetOcr.cleanupAuditTable(pruneThreshold);
|
||||||
}
|
}
|
||||||
|
|
||||||
private needsFullSync(checkpointMap: CheckpointMap) {
|
private needsFullSync(checkpointMap: CheckpointMap) {
|
||||||
@@ -888,6 +891,33 @@ export class SyncService extends BaseService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async syncAssetOcrV1(
|
||||||
|
options: SyncQueryOptions,
|
||||||
|
response: Writable,
|
||||||
|
checkpointMap: CheckpointMap,
|
||||||
|
auth: AuthDto,
|
||||||
|
) {
|
||||||
|
const deleteType = SyncEntityType.AssetOcrDeleteV1;
|
||||||
|
const deletes = this.syncRepository.assetOcr.getDeletes(
|
||||||
|
{ ...options, ack: checkpointMap[deleteType] },
|
||||||
|
auth.user.id,
|
||||||
|
);
|
||||||
|
|
||||||
|
for await (const row of deletes) {
|
||||||
|
send(response, { type: deleteType, ids: [row.id], data: row });
|
||||||
|
}
|
||||||
|
|
||||||
|
const upsertType = SyncEntityType.AssetOcrV1;
|
||||||
|
const upserts = this.syncRepository.assetOcr.getUpserts(
|
||||||
|
{ ...options, ack: checkpointMap[upsertType] },
|
||||||
|
auth.user.id,
|
||||||
|
);
|
||||||
|
|
||||||
|
for await (const { updateId, ...data } of upserts) {
|
||||||
|
send(response, { type: upsertType, ids: [updateId], data });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private async upsertBackfillCheckpoint(item: { type: SyncEntityType; sessionId: string; createId: string }) {
|
private async upsertBackfillCheckpoint(item: { type: SyncEntityType; sessionId: string; createId: string }) {
|
||||||
const { type, sessionId, createId } = item;
|
const { type, sessionId, createId } = item;
|
||||||
await this.syncCheckpointRepository.upsertAll([
|
await this.syncCheckpointRepository.upsertAll([
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ describe(OcrService.name, () => {
|
|||||||
assetId: asset.id,
|
assetId: asset.id,
|
||||||
boxScore: 0.99,
|
boxScore: 0.99,
|
||||||
id: expect.any(String),
|
id: expect.any(String),
|
||||||
|
updateId: expect.any(String),
|
||||||
text: 'Test OCR',
|
text: 'Test OCR',
|
||||||
textScore: 0.95,
|
textScore: 0.95,
|
||||||
isVisible: true,
|
isVisible: true,
|
||||||
@@ -105,6 +106,7 @@ describe(OcrService.name, () => {
|
|||||||
assetId: asset.id,
|
assetId: asset.id,
|
||||||
boxScore: 0.7,
|
boxScore: 0.7,
|
||||||
id: expect.any(String),
|
id: expect.any(String),
|
||||||
|
updateId: expect.any(String),
|
||||||
text: 'One',
|
text: 'One',
|
||||||
textScore: 0.9,
|
textScore: 0.9,
|
||||||
isVisible: true,
|
isVisible: true,
|
||||||
@@ -121,6 +123,7 @@ describe(OcrService.name, () => {
|
|||||||
assetId: asset.id,
|
assetId: asset.id,
|
||||||
boxScore: 0.67,
|
boxScore: 0.67,
|
||||||
id: expect.any(String),
|
id: expect.any(String),
|
||||||
|
updateId: expect.any(String),
|
||||||
text: 'Two',
|
text: 'Two',
|
||||||
textScore: 0.89,
|
textScore: 0.89,
|
||||||
isVisible: true,
|
isVisible: true,
|
||||||
@@ -137,6 +140,7 @@ describe(OcrService.name, () => {
|
|||||||
assetId: asset.id,
|
assetId: asset.id,
|
||||||
boxScore: 0.65,
|
boxScore: 0.65,
|
||||||
id: expect.any(String),
|
id: expect.any(String),
|
||||||
|
updateId: expect.any(String),
|
||||||
text: 'Three',
|
text: 'Three',
|
||||||
textScore: 0.88,
|
textScore: 0.88,
|
||||||
isVisible: true,
|
isVisible: true,
|
||||||
@@ -153,6 +157,7 @@ describe(OcrService.name, () => {
|
|||||||
assetId: asset.id,
|
assetId: asset.id,
|
||||||
boxScore: 0.62,
|
boxScore: 0.62,
|
||||||
id: expect.any(String),
|
id: expect.any(String),
|
||||||
|
updateId: expect.any(String),
|
||||||
text: 'Four',
|
text: 'Four',
|
||||||
textScore: 0.87,
|
textScore: 0.87,
|
||||||
isVisible: true,
|
isVisible: true,
|
||||||
@@ -169,6 +174,7 @@ describe(OcrService.name, () => {
|
|||||||
assetId: asset.id,
|
assetId: asset.id,
|
||||||
boxScore: 0.6,
|
boxScore: 0.6,
|
||||||
id: expect.any(String),
|
id: expect.any(String),
|
||||||
|
updateId: expect.any(String),
|
||||||
text: 'Five',
|
text: 'Five',
|
||||||
textScore: 0.86,
|
textScore: 0.86,
|
||||||
isVisible: true,
|
isVisible: true,
|
||||||
|
|||||||
@@ -0,0 +1,379 @@
|
|||||||
|
import { Kysely } from 'kysely';
|
||||||
|
import { SyncEntityType, SyncRequestType } from 'src/enum';
|
||||||
|
import { OcrRepository } from 'src/repositories/ocr.repository';
|
||||||
|
import { DB } from 'src/schema';
|
||||||
|
import { SyncTestContext } from 'test/medium.factory';
|
||||||
|
import { getKyselyDB } from 'test/utils';
|
||||||
|
|
||||||
|
let defaultDatabase: Kysely<DB>;
|
||||||
|
|
||||||
|
const setup = async (db?: Kysely<DB>) => {
|
||||||
|
const ctx = new SyncTestContext(db || defaultDatabase);
|
||||||
|
const { auth, user, session } = await ctx.newSyncAuthUser();
|
||||||
|
return { auth, user, session, ctx };
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
defaultDatabase = await getKyselyDB();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe(SyncEntityType.AssetOcrV1, () => {
|
||||||
|
it('should detect and sync new asset OCR', async () => {
|
||||||
|
const { auth, user, ctx } = await setup();
|
||||||
|
|
||||||
|
const ocrRepo = ctx.get(OcrRepository);
|
||||||
|
const { asset } = await ctx.newAsset({ ownerId: user.id });
|
||||||
|
await ocrRepo.upsert(
|
||||||
|
asset.id,
|
||||||
|
[
|
||||||
|
{
|
||||||
|
assetId: asset.id,
|
||||||
|
x1: 0.1,
|
||||||
|
y1: 0.2,
|
||||||
|
x2: 0.9,
|
||||||
|
y2: 0.2,
|
||||||
|
x3: 0.9,
|
||||||
|
y3: 0.8,
|
||||||
|
x4: 0.1,
|
||||||
|
y4: 0.8,
|
||||||
|
boxScore: 0.95,
|
||||||
|
textScore: 0.92,
|
||||||
|
text: 'Hello World',
|
||||||
|
isVisible: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'Hello World',
|
||||||
|
);
|
||||||
|
|
||||||
|
const response = await ctx.syncStream(auth, [SyncRequestType.AssetOcrV1]);
|
||||||
|
expect(response).toEqual([
|
||||||
|
{
|
||||||
|
ack: expect.any(String),
|
||||||
|
data: {
|
||||||
|
id: expect.any(String),
|
||||||
|
assetId: asset.id,
|
||||||
|
x1: 0.1,
|
||||||
|
y1: 0.2,
|
||||||
|
x2: 0.9,
|
||||||
|
y2: 0.2,
|
||||||
|
x3: 0.9,
|
||||||
|
y3: 0.8,
|
||||||
|
x4: 0.1,
|
||||||
|
y4: 0.8,
|
||||||
|
boxScore: 0.95,
|
||||||
|
textScore: 0.92,
|
||||||
|
text: 'Hello World',
|
||||||
|
isVisible: true,
|
||||||
|
},
|
||||||
|
type: 'AssetOcrV1',
|
||||||
|
},
|
||||||
|
expect.objectContaining({ type: SyncEntityType.SyncCompleteV1 }),
|
||||||
|
]);
|
||||||
|
|
||||||
|
await ctx.syncAckAll(auth, response);
|
||||||
|
await ctx.assertSyncIsComplete(auth, [SyncRequestType.AssetOcrV1]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should update asset OCR', async () => {
|
||||||
|
const { auth, user, ctx } = await setup();
|
||||||
|
|
||||||
|
const ocrRepo = ctx.get(OcrRepository);
|
||||||
|
const { asset } = await ctx.newAsset({ ownerId: user.id });
|
||||||
|
await ocrRepo.upsert(
|
||||||
|
asset.id,
|
||||||
|
[
|
||||||
|
{
|
||||||
|
assetId: asset.id,
|
||||||
|
x1: 0.1,
|
||||||
|
y1: 0.2,
|
||||||
|
x2: 0.9,
|
||||||
|
y2: 0.2,
|
||||||
|
x3: 0.9,
|
||||||
|
y3: 0.8,
|
||||||
|
x4: 0.1,
|
||||||
|
y4: 0.8,
|
||||||
|
boxScore: 0.95,
|
||||||
|
textScore: 0.92,
|
||||||
|
text: 'Hello World',
|
||||||
|
isVisible: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'Hello World',
|
||||||
|
);
|
||||||
|
|
||||||
|
const response = await ctx.syncStream(auth, [SyncRequestType.AssetOcrV1]);
|
||||||
|
expect(response).toEqual([
|
||||||
|
{
|
||||||
|
ack: expect.any(String),
|
||||||
|
data: {
|
||||||
|
id: expect.any(String),
|
||||||
|
assetId: asset.id,
|
||||||
|
x1: 0.1,
|
||||||
|
y1: 0.2,
|
||||||
|
x2: 0.9,
|
||||||
|
y2: 0.2,
|
||||||
|
x3: 0.9,
|
||||||
|
y3: 0.8,
|
||||||
|
x4: 0.1,
|
||||||
|
y4: 0.8,
|
||||||
|
boxScore: 0.95,
|
||||||
|
textScore: 0.92,
|
||||||
|
text: 'Hello World',
|
||||||
|
isVisible: true,
|
||||||
|
},
|
||||||
|
type: 'AssetOcrV1',
|
||||||
|
},
|
||||||
|
expect.objectContaining({ type: SyncEntityType.SyncCompleteV1 }),
|
||||||
|
]);
|
||||||
|
|
||||||
|
await ctx.syncAckAll(auth, response);
|
||||||
|
|
||||||
|
// Update OCR data (upsert deletes old entries first, then inserts new ones)
|
||||||
|
await ocrRepo.upsert(
|
||||||
|
asset.id,
|
||||||
|
[
|
||||||
|
{
|
||||||
|
assetId: asset.id,
|
||||||
|
x1: 0.15,
|
||||||
|
y1: 0.25,
|
||||||
|
x2: 0.85,
|
||||||
|
y2: 0.25,
|
||||||
|
x3: 0.85,
|
||||||
|
y3: 0.75,
|
||||||
|
x4: 0.15,
|
||||||
|
y4: 0.75,
|
||||||
|
boxScore: 0.98,
|
||||||
|
textScore: 0.96,
|
||||||
|
text: 'Updated Text',
|
||||||
|
isVisible: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'Updated Text',
|
||||||
|
);
|
||||||
|
|
||||||
|
const updatedResponse = await ctx.syncStream(auth, [SyncRequestType.AssetOcrV1]);
|
||||||
|
// upsert() deletes old entries first, so we expect both delete and upsert events
|
||||||
|
expect(updatedResponse).toEqual([
|
||||||
|
{
|
||||||
|
ack: expect.any(String),
|
||||||
|
data: {
|
||||||
|
id: expect.any(String),
|
||||||
|
assetId: asset.id,
|
||||||
|
deletedAt: expect.any(String),
|
||||||
|
},
|
||||||
|
type: 'AssetOcrDeleteV1',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ack: expect.any(String),
|
||||||
|
data: {
|
||||||
|
id: expect.any(String),
|
||||||
|
assetId: asset.id,
|
||||||
|
x1: 0.15,
|
||||||
|
y1: 0.25,
|
||||||
|
x2: 0.85,
|
||||||
|
y2: 0.25,
|
||||||
|
x3: 0.85,
|
||||||
|
y3: 0.75,
|
||||||
|
x4: 0.15,
|
||||||
|
y4: 0.75,
|
||||||
|
boxScore: 0.98,
|
||||||
|
textScore: 0.96,
|
||||||
|
text: 'Updated Text',
|
||||||
|
isVisible: true,
|
||||||
|
},
|
||||||
|
type: 'AssetOcrV1',
|
||||||
|
},
|
||||||
|
expect.objectContaining({ type: SyncEntityType.SyncCompleteV1 }),
|
||||||
|
]);
|
||||||
|
|
||||||
|
await ctx.syncAckAll(auth, updatedResponse);
|
||||||
|
await ctx.assertSyncIsComplete(auth, [SyncRequestType.AssetOcrV1]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should update asset OCR visibility flag', async () => {
|
||||||
|
const { auth, user, ctx } = await setup();
|
||||||
|
|
||||||
|
const ocrRepo = ctx.get(OcrRepository);
|
||||||
|
const { asset } = await ctx.newAsset({ ownerId: user.id });
|
||||||
|
await ocrRepo.upsert(
|
||||||
|
asset.id,
|
||||||
|
[
|
||||||
|
{
|
||||||
|
assetId: asset.id,
|
||||||
|
x1: 0.1,
|
||||||
|
y1: 0.2,
|
||||||
|
x2: 0.9,
|
||||||
|
y2: 0.2,
|
||||||
|
x3: 0.9,
|
||||||
|
y3: 0.8,
|
||||||
|
x4: 0.1,
|
||||||
|
y4: 0.8,
|
||||||
|
boxScore: 0.95,
|
||||||
|
textScore: 0.92,
|
||||||
|
text: 'Hello World',
|
||||||
|
isVisible: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'Hello World',
|
||||||
|
);
|
||||||
|
|
||||||
|
const response = await ctx.syncStream(auth, [SyncRequestType.AssetOcrV1]);
|
||||||
|
expect(response).toEqual([
|
||||||
|
{
|
||||||
|
ack: expect.any(String),
|
||||||
|
data: {
|
||||||
|
id: expect.any(String),
|
||||||
|
assetId: asset.id,
|
||||||
|
x1: 0.1,
|
||||||
|
y1: 0.2,
|
||||||
|
x2: 0.9,
|
||||||
|
y2: 0.2,
|
||||||
|
x3: 0.9,
|
||||||
|
y3: 0.8,
|
||||||
|
x4: 0.1,
|
||||||
|
y4: 0.8,
|
||||||
|
boxScore: 0.95,
|
||||||
|
textScore: 0.92,
|
||||||
|
text: 'Hello World',
|
||||||
|
isVisible: true,
|
||||||
|
},
|
||||||
|
type: 'AssetOcrV1',
|
||||||
|
},
|
||||||
|
expect.objectContaining({ type: SyncEntityType.SyncCompleteV1 }),
|
||||||
|
]);
|
||||||
|
|
||||||
|
await ctx.syncAckAll(auth, response);
|
||||||
|
|
||||||
|
await ocrRepo.upsert(
|
||||||
|
asset.id,
|
||||||
|
[
|
||||||
|
{
|
||||||
|
assetId: asset.id,
|
||||||
|
x1: 0.1,
|
||||||
|
y1: 0.2,
|
||||||
|
x2: 0.9,
|
||||||
|
y2: 0.2,
|
||||||
|
x3: 0.9,
|
||||||
|
y3: 0.8,
|
||||||
|
x4: 0.1,
|
||||||
|
y4: 0.8,
|
||||||
|
boxScore: 0.95,
|
||||||
|
textScore: 0.92,
|
||||||
|
text: 'Hello World',
|
||||||
|
isVisible: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'Hello World',
|
||||||
|
);
|
||||||
|
|
||||||
|
const updatedResponse = await ctx.syncStream(auth, [SyncRequestType.AssetOcrV1]);
|
||||||
|
expect(updatedResponse).toEqual([
|
||||||
|
{
|
||||||
|
ack: expect.any(String),
|
||||||
|
data: {
|
||||||
|
id: expect.any(String),
|
||||||
|
assetId: asset.id,
|
||||||
|
deletedAt: expect.any(String),
|
||||||
|
},
|
||||||
|
type: 'AssetOcrDeleteV1',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ack: expect.any(String),
|
||||||
|
data: {
|
||||||
|
id: expect.any(String),
|
||||||
|
assetId: asset.id,
|
||||||
|
x1: 0.1,
|
||||||
|
y1: 0.2,
|
||||||
|
x2: 0.9,
|
||||||
|
y2: 0.2,
|
||||||
|
x3: 0.9,
|
||||||
|
y3: 0.8,
|
||||||
|
x4: 0.1,
|
||||||
|
y4: 0.8,
|
||||||
|
boxScore: 0.95,
|
||||||
|
textScore: 0.92,
|
||||||
|
text: 'Hello World',
|
||||||
|
isVisible: false,
|
||||||
|
},
|
||||||
|
type: 'AssetOcrV1',
|
||||||
|
},
|
||||||
|
expect.objectContaining({ type: SyncEntityType.SyncCompleteV1 }),
|
||||||
|
]);
|
||||||
|
|
||||||
|
await ctx.syncAckAll(auth, updatedResponse);
|
||||||
|
await ctx.assertSyncIsComplete(auth, [SyncRequestType.AssetOcrV1]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe(SyncEntityType.AssetOcrDeleteV1, () => {
|
||||||
|
it('should delete and sync asset OCR', async () => {
|
||||||
|
const { auth, user, ctx } = await setup();
|
||||||
|
|
||||||
|
const ocrRepo = ctx.get(OcrRepository);
|
||||||
|
const { asset } = await ctx.newAsset({ ownerId: user.id });
|
||||||
|
await ocrRepo.upsert(
|
||||||
|
asset.id,
|
||||||
|
[
|
||||||
|
{
|
||||||
|
assetId: asset.id,
|
||||||
|
x1: 0.1,
|
||||||
|
y1: 0.2,
|
||||||
|
x2: 0.9,
|
||||||
|
y2: 0.2,
|
||||||
|
x3: 0.9,
|
||||||
|
y3: 0.8,
|
||||||
|
x4: 0.1,
|
||||||
|
y4: 0.8,
|
||||||
|
boxScore: 0.95,
|
||||||
|
textScore: 0.92,
|
||||||
|
text: 'Hello World',
|
||||||
|
isVisible: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'Hello World',
|
||||||
|
);
|
||||||
|
|
||||||
|
const response = await ctx.syncStream(auth, [SyncRequestType.AssetOcrV1]);
|
||||||
|
expect(response).toEqual([
|
||||||
|
{
|
||||||
|
ack: expect.any(String),
|
||||||
|
data: {
|
||||||
|
id: expect.any(String),
|
||||||
|
assetId: asset.id,
|
||||||
|
x1: 0.1,
|
||||||
|
y1: 0.2,
|
||||||
|
x2: 0.9,
|
||||||
|
y2: 0.2,
|
||||||
|
x3: 0.9,
|
||||||
|
y3: 0.8,
|
||||||
|
x4: 0.1,
|
||||||
|
y4: 0.8,
|
||||||
|
boxScore: 0.95,
|
||||||
|
textScore: 0.92,
|
||||||
|
text: 'Hello World',
|
||||||
|
isVisible: true,
|
||||||
|
},
|
||||||
|
type: 'AssetOcrV1',
|
||||||
|
},
|
||||||
|
expect.objectContaining({ type: SyncEntityType.SyncCompleteV1 }),
|
||||||
|
]);
|
||||||
|
|
||||||
|
await ctx.syncAckAll(auth, response);
|
||||||
|
|
||||||
|
// Delete OCR data
|
||||||
|
await ocrRepo.upsert(asset.id, [], '');
|
||||||
|
|
||||||
|
await expect(ctx.syncStream(auth, [SyncRequestType.AssetOcrV1])).resolves.toEqual([
|
||||||
|
{
|
||||||
|
ack: expect.any(String),
|
||||||
|
data: {
|
||||||
|
assetId: asset.id,
|
||||||
|
deletedAt: expect.any(String),
|
||||||
|
id: expect.any(String),
|
||||||
|
},
|
||||||
|
type: 'AssetOcrDeleteV1',
|
||||||
|
},
|
||||||
|
expect.objectContaining({ type: SyncEntityType.SyncCompleteV1 }),
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -201,6 +201,7 @@ const assetSidecarWriteFactory = () => {
|
|||||||
const assetOcrFactory = (
|
const assetOcrFactory = (
|
||||||
ocr: {
|
ocr: {
|
||||||
id?: string;
|
id?: string;
|
||||||
|
updateId?: string;
|
||||||
assetId?: string;
|
assetId?: string;
|
||||||
x1?: number;
|
x1?: number;
|
||||||
y1?: number;
|
y1?: number;
|
||||||
@@ -217,6 +218,7 @@ const assetOcrFactory = (
|
|||||||
} = {},
|
} = {},
|
||||||
) => ({
|
) => ({
|
||||||
id: newUuid(),
|
id: newUuid(),
|
||||||
|
updateId: newUuidV7(),
|
||||||
assetId: newUuid(),
|
assetId: newUuid(),
|
||||||
x1: 0.1,
|
x1: 0.1,
|
||||||
y1: 0.2,
|
y1: 0.2,
|
||||||
|
|||||||
Reference in New Issue
Block a user