immich/server/src/services/base.service.ts
Kang 02b29046b3
feat: ocr (#18836)
* feat: add OCR functionality and related configurations

* chore: update labeler configuration for machine learning files

* feat(i18n): enhance OCR model descriptions and add orientation classification and unwarping features

* chore: update Dockerfile to include ccache for improved build performance

* feat(ocr): enhance OCR model configuration with orientation classification and unwarping options, update PaddleOCR integration, and improve response structure

* refactor(ocr): remove OCR_CLEANUP job from enum and type definitions

* refactor(ocr): remove obsolete OCR entity and migration files, and update asset job status and schema to accommodate new OCR table structure

* refactor(ocr): update OCR schema and response structure to use individual coordinates instead of bounding box, and adjust related service and repository files

* feat: enhance OCR configuration and functionality

- Updated OCR settings to include minimum detection box score, minimum detection score, and minimum recognition score.
- Refactored PaddleOCRecognizer to utilize new scoring parameters.
- Introduced new database tables for asset OCR data and search functionality.
- Modified related services and repositories to support the new OCR features.
- Updated translations for improved clarity in settings UI.

* sql changes

* use rapidocr

* change dto

* update web

* update lock

* update api

* store positions as normalized floats

* match column order in db

* update admin ui settings descriptions

fix max resolution key

set min threshold to 0.1

fix bind

* apply config correctly, adjust defaults

* unnecessary model type

* unnecessary sources

* fix(ocr): switch RapidOCR lang type from LangDet to LangRec

* fix(ocr): expose lang_type (LangRec.CH) and font_path on OcrOptions for RapidOCR

* fix(ocr): make OCR text search case- and accent-insensitive using ILIKE + unaccent

* fix(ocr): add OCR search fields

* fix: Add OCR database migration and update ML prediction logic.

* trigrams are already case insensitive

* add tests

* format

* update migrations

* wrong uuid function

* linting

* maybe fix medium tests

* formatting

* fix weblate check

* openapi

* sql

* minor fixes

* maybe fix medium tests part 2

* passing medium tests

* format web

* readd sql

* format dart

* disabled in e2e

* chore: translation ordering

---------

Co-authored-by: mertalev <101130780+mertalev@users.noreply.github.com>
Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2025-10-27 14:09:55 +00:00

233 lines
9.3 KiB
TypeScript

import { BadRequestException, Injectable } from '@nestjs/common';
import { Insertable } from 'kysely';
import sanitize from 'sanitize-filename';
import { SystemConfig } from 'src/config';
import { SALT_ROUNDS } from 'src/constants';
import { StorageCore } from 'src/cores/storage.core';
import { UserAdmin } from 'src/database';
import { AccessRepository } from 'src/repositories/access.repository';
import { ActivityRepository } from 'src/repositories/activity.repository';
import { AlbumUserRepository } from 'src/repositories/album-user.repository';
import { AlbumRepository } from 'src/repositories/album.repository';
import { ApiKeyRepository } from 'src/repositories/api-key.repository';
import { AssetJobRepository } from 'src/repositories/asset-job.repository';
import { AssetRepository } from 'src/repositories/asset.repository';
import { AuditRepository } from 'src/repositories/audit.repository';
import { ConfigRepository } from 'src/repositories/config.repository';
import { CronRepository } from 'src/repositories/cron.repository';
import { CryptoRepository } from 'src/repositories/crypto.repository';
import { DatabaseRepository } from 'src/repositories/database.repository';
import { DownloadRepository } from 'src/repositories/download.repository';
import { DuplicateRepository } from 'src/repositories/duplicate.repository';
import { EmailRepository } from 'src/repositories/email.repository';
import { EventRepository } from 'src/repositories/event.repository';
import { JobRepository } from 'src/repositories/job.repository';
import { LibraryRepository } from 'src/repositories/library.repository';
import { LoggingRepository } from 'src/repositories/logging.repository';
import { MachineLearningRepository } from 'src/repositories/machine-learning.repository';
import { MapRepository } from 'src/repositories/map.repository';
import { MediaRepository } from 'src/repositories/media.repository';
import { MemoryRepository } from 'src/repositories/memory.repository';
import { MetadataRepository } from 'src/repositories/metadata.repository';
import { MoveRepository } from 'src/repositories/move.repository';
import { NotificationRepository } from 'src/repositories/notification.repository';
import { OAuthRepository } from 'src/repositories/oauth.repository';
import { OcrRepository } from 'src/repositories/ocr.repository';
import { PartnerRepository } from 'src/repositories/partner.repository';
import { PersonRepository } from 'src/repositories/person.repository';
import { ProcessRepository } from 'src/repositories/process.repository';
import { SearchRepository } from 'src/repositories/search.repository';
import { ServerInfoRepository } from 'src/repositories/server-info.repository';
import { SessionRepository } from 'src/repositories/session.repository';
import { SharedLinkAssetRepository } from 'src/repositories/shared-link-asset.repository';
import { SharedLinkRepository } from 'src/repositories/shared-link.repository';
import { StackRepository } from 'src/repositories/stack.repository';
import { StorageRepository } from 'src/repositories/storage.repository';
import { SyncCheckpointRepository } from 'src/repositories/sync-checkpoint.repository';
import { SyncRepository } from 'src/repositories/sync.repository';
import { SystemMetadataRepository } from 'src/repositories/system-metadata.repository';
import { TagRepository } from 'src/repositories/tag.repository';
import { TelemetryRepository } from 'src/repositories/telemetry.repository';
import { TrashRepository } from 'src/repositories/trash.repository';
import { UserRepository } from 'src/repositories/user.repository';
import { VersionHistoryRepository } from 'src/repositories/version-history.repository';
import { ViewRepository } from 'src/repositories/view-repository';
import { WebsocketRepository } from 'src/repositories/websocket.repository';
import { UserTable } from 'src/schema/tables/user.table';
import { AccessRequest, checkAccess, requireAccess } from 'src/utils/access';
import { getConfig, updateConfig } from 'src/utils/config';
export const BASE_SERVICE_DEPENDENCIES = [
LoggingRepository,
AccessRepository,
ActivityRepository,
AlbumRepository,
AlbumUserRepository,
ApiKeyRepository,
AssetRepository,
AssetJobRepository,
AuditRepository,
ConfigRepository,
CronRepository,
CryptoRepository,
DatabaseRepository,
DownloadRepository,
DuplicateRepository,
EmailRepository,
EventRepository,
JobRepository,
LibraryRepository,
MachineLearningRepository,
MapRepository,
MediaRepository,
MemoryRepository,
MetadataRepository,
MoveRepository,
NotificationRepository,
OAuthRepository,
OcrRepository,
PartnerRepository,
PersonRepository,
ProcessRepository,
SearchRepository,
ServerInfoRepository,
SessionRepository,
SharedLinkRepository,
SharedLinkAssetRepository,
StackRepository,
StorageRepository,
SyncRepository,
SyncCheckpointRepository,
SystemMetadataRepository,
TagRepository,
TelemetryRepository,
TrashRepository,
UserRepository,
VersionHistoryRepository,
ViewRepository,
];
@Injectable()
export class BaseService {
protected storageCore: StorageCore;
constructor(
protected logger: LoggingRepository,
protected accessRepository: AccessRepository,
protected activityRepository: ActivityRepository,
protected albumRepository: AlbumRepository,
protected albumUserRepository: AlbumUserRepository,
protected apiKeyRepository: ApiKeyRepository,
protected assetRepository: AssetRepository,
protected assetJobRepository: AssetJobRepository,
protected auditRepository: AuditRepository,
protected configRepository: ConfigRepository,
protected cronRepository: CronRepository,
protected cryptoRepository: CryptoRepository,
protected databaseRepository: DatabaseRepository,
protected downloadRepository: DownloadRepository,
protected duplicateRepository: DuplicateRepository,
protected emailRepository: EmailRepository,
protected eventRepository: EventRepository,
protected jobRepository: JobRepository,
protected libraryRepository: LibraryRepository,
protected machineLearningRepository: MachineLearningRepository,
protected mapRepository: MapRepository,
protected mediaRepository: MediaRepository,
protected memoryRepository: MemoryRepository,
protected metadataRepository: MetadataRepository,
protected moveRepository: MoveRepository,
protected notificationRepository: NotificationRepository,
protected oauthRepository: OAuthRepository,
protected ocrRepository: OcrRepository,
protected partnerRepository: PartnerRepository,
protected personRepository: PersonRepository,
protected processRepository: ProcessRepository,
protected searchRepository: SearchRepository,
protected serverInfoRepository: ServerInfoRepository,
protected sessionRepository: SessionRepository,
protected sharedLinkRepository: SharedLinkRepository,
protected sharedLinkAssetRepository: SharedLinkAssetRepository,
protected stackRepository: StackRepository,
protected storageRepository: StorageRepository,
protected syncRepository: SyncRepository,
protected syncCheckpointRepository: SyncCheckpointRepository,
protected systemMetadataRepository: SystemMetadataRepository,
protected tagRepository: TagRepository,
protected telemetryRepository: TelemetryRepository,
protected trashRepository: TrashRepository,
protected userRepository: UserRepository,
protected versionRepository: VersionHistoryRepository,
protected viewRepository: ViewRepository,
protected websocketRepository: WebsocketRepository,
) {
this.logger.setContext(this.constructor.name);
this.storageCore = StorageCore.create(
assetRepository,
configRepository,
cryptoRepository,
moveRepository,
personRepository,
storageRepository,
systemMetadataRepository,
this.logger,
);
}
get worker() {
return this.configRepository.getWorker();
}
private get configRepos() {
return {
configRepo: this.configRepository,
metadataRepo: this.systemMetadataRepository,
logger: this.logger,
};
}
getConfig(options: { withCache: boolean }) {
return getConfig(this.configRepos, options);
}
updateConfig(newConfig: SystemConfig) {
return updateConfig(this.configRepos, newConfig);
}
requireAccess(request: AccessRequest) {
return requireAccess(this.accessRepository, request);
}
checkAccess(request: AccessRequest) {
return checkAccess(this.accessRepository, request);
}
async createUser(dto: Insertable<UserTable> & { email: string }): Promise<UserAdmin> {
const exists = await this.userRepository.getByEmail(dto.email);
if (exists) {
throw new BadRequestException('User exists');
}
if (!dto.isAdmin) {
const localAdmin = await this.userRepository.getAdmin();
if (!localAdmin) {
throw new BadRequestException('The first registered account must the administrator.');
}
}
const payload: Insertable<UserTable> = { ...dto };
if (payload.password) {
payload.password = await this.cryptoRepository.hashBcrypt(payload.password, SALT_ROUNDS);
}
if (payload.storageLabel) {
payload.storageLabel = sanitize(payload.storageLabel.replaceAll('.', ''));
}
const user = await this.userRepository.create(payload);
await this.eventRepository.emit('UserCreate', user);
return user;
}
}