refactor: database types (#19624)

This commit is contained in:
Jason Rasmussen 2025-06-30 13:19:16 -04:00 committed by GitHub
parent 09cbc5d3f4
commit e60bc3c304
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
99 changed files with 518 additions and 889 deletions

View File

@ -29,7 +29,6 @@
"migrations:run": "node ./dist/bin/migrations.js run",
"schema:drop": "node ./dist/bin/migrations.js query 'DROP schema public cascade; CREATE schema public;'",
"schema:reset": "npm run schema:drop && npm run migrations:run",
"kysely:codegen": "npx kysely-codegen --include-pattern=\"(public|vectors).*\" --dialect postgres --url postgres://postgres:postgres@localhost/immich --log-level debug --out-file=./src/db.d.ts",
"sync:open-api": "node ./dist/bin/sync-open-api.js",
"sync:sql": "node ./dist/bin/sync-sql.js",
"email:dev": "email dev -p 3050 --dir src/emails",

View File

@ -1,5 +1,4 @@
import { Selectable } from 'kysely';
import { Albums, Exif as DatabaseExif } from 'src/db';
import { MapAsset } from 'src/dtos/asset-response.dto';
import {
AlbumUserRole,
@ -13,6 +12,8 @@ import {
UserAvatarColor,
UserStatus,
} from 'src/enum';
import { AlbumTable } from 'src/schema/tables/album.table';
import { ExifTable } from 'src/schema/tables/exif.table';
import { UserMetadataItem } from 'src/types';
export type AuthUser = {
@ -193,7 +194,7 @@ export type SharedLink = {
userId: string;
};
export type Album = Selectable<Albums> & {
export type Album = Selectable<AlbumTable> & {
owner: User;
assets: MapAsset[];
};
@ -239,7 +240,7 @@ export type Session = {
pinExpiresAt: Date | null;
};
export type Exif = Omit<Selectable<DatabaseExif>, 'updatedAt' | 'updateId'>;
export type Exif = Omit<Selectable<ExifTable>, 'updatedAt' | 'updateId'>;
export type Person = {
createdAt: Date;

540
server/src/db.d.ts vendored
View File

@ -1,540 +0,0 @@
/**
* This file was generated by kysely-codegen.
* Please do not edit it manually.
*/
import type { ColumnType } from 'kysely';
import {
AlbumUserRole,
AssetFileType,
AssetOrder,
AssetStatus,
AssetType,
AssetVisibility,
MemoryType,
NotificationLevel,
NotificationType,
Permission,
SharedLinkType,
SourceType,
SyncEntityType,
} from 'src/enum';
import { MemoryAssetAuditTable } from 'src/schema/tables/memory-asset-audit.table';
import { MemoryAssetTable } from 'src/schema/tables/memory-asset.table';
import { MemoryAuditTable } from 'src/schema/tables/memory-audit.table';
import { UserTable } from 'src/schema/tables/user.table';
import { UserMetadataItem } from 'src/types';
export type ArrayType<T> = ArrayTypeImpl<T> extends (infer U)[] ? U[] : ArrayTypeImpl<T>;
export type ArrayTypeImpl<T> = T extends ColumnType<infer S, infer I, infer U> ? ColumnType<S[], I[], U[]> : T[];
export type Generated<T> =
T extends ColumnType<infer S, infer I, infer U> ? ColumnType<S, I | undefined, U> : ColumnType<T, T | undefined, T>;
export type Int8 = ColumnType<number>;
export type Json = JsonValue;
export type JsonArray = JsonValue[];
export type JsonObject = {
[x: string]: JsonValue | undefined;
};
export type JsonPrimitive = boolean | number | string | null;
export type JsonValue = JsonArray | JsonObject | JsonPrimitive;
export type Timestamp = ColumnType<Date, Date | string, Date | string>;
export interface Activity {
albumId: string;
assetId: string | null;
comment: string | null;
createdAt: Generated<Timestamp>;
id: Generated<string>;
isLiked: Generated<boolean>;
updatedAt: Generated<Timestamp>;
updateId: Generated<string>;
userId: string;
}
export interface Albums {
albumName: Generated<string>;
/**
* Asset ID to be used as thumbnail
*/
albumThumbnailAssetId: string | null;
createdAt: Generated<Timestamp>;
deletedAt: Timestamp | null;
description: Generated<string>;
id: Generated<string>;
isActivityEnabled: Generated<boolean>;
order: Generated<AssetOrder>;
ownerId: string;
updatedAt: Generated<Timestamp>;
updateId: Generated<string>;
}
export interface AlbumsAudit {
deletedAt: Generated<Timestamp>;
id: Generated<string>;
albumId: string;
userId: string;
}
export interface AlbumUsersAudit {
deletedAt: Generated<Timestamp>;
id: Generated<string>;
albumId: string;
userId: string;
}
export interface AlbumsAssetsAssets {
albumsId: string;
assetsId: string;
createdAt: Generated<Timestamp>;
updatedAt: Generated<Timestamp>;
updateId: Generated<string>;
}
export interface AlbumAssetsAudit {
deletedAt: Generated<Timestamp>;
id: Generated<string>;
albumId: string;
assetId: string;
}
export interface AlbumsSharedUsersUsers {
albumsId: string;
role: Generated<AlbumUserRole>;
usersId: string;
createId: Generated<string>;
createdAt: Generated<Timestamp>;
updateId: Generated<string>;
updatedAt: Generated<Timestamp>;
}
export interface ApiKeys {
createdAt: Generated<Timestamp>;
id: Generated<string>;
key: string;
name: string;
permissions: Permission[];
updatedAt: Generated<Timestamp>;
updateId: Generated<string>;
userId: string;
}
export interface AssetFaces {
assetId: string;
boundingBoxX1: Generated<number>;
boundingBoxX2: Generated<number>;
boundingBoxY1: Generated<number>;
boundingBoxY2: Generated<number>;
deletedAt: Timestamp | null;
id: Generated<string>;
imageHeight: Generated<number>;
imageWidth: Generated<number>;
personId: string | null;
sourceType: Generated<SourceType>;
}
export interface AssetFiles {
assetId: string;
createdAt: Generated<Timestamp>;
id: Generated<string>;
path: string;
type: AssetFileType;
updatedAt: Generated<Timestamp>;
updateId: Generated<string>;
}
export interface AssetJobStatus {
assetId: string;
duplicatesDetectedAt: Timestamp | null;
facesRecognizedAt: Timestamp | null;
metadataExtractedAt: Timestamp | null;
previewAt: Timestamp | null;
thumbnailAt: Timestamp | null;
}
export interface AssetsAudit {
deletedAt: Generated<Timestamp>;
id: Generated<string>;
assetId: string;
ownerId: string;
}
export interface Assets {
checksum: Buffer;
createdAt: Generated<Timestamp>;
deletedAt: Timestamp | null;
deviceAssetId: string;
deviceId: string;
duplicateId: string | null;
duration: string | null;
encodedVideoPath: Generated<string | null>;
fileCreatedAt: Timestamp;
fileModifiedAt: Timestamp;
id: Generated<string>;
isExternal: Generated<boolean>;
isFavorite: Generated<boolean>;
isOffline: Generated<boolean>;
visibility: Generated<AssetVisibility>;
libraryId: string | null;
livePhotoVideoId: string | null;
localDateTime: Timestamp;
originalFileName: string;
originalPath: string;
ownerId: string;
sidecarPath: string | null;
stackId: string | null;
status: Generated<AssetStatus>;
thumbhash: Buffer | null;
type: AssetType;
updatedAt: Generated<Timestamp>;
updateId: Generated<string>;
}
export interface AssetStack {
id: Generated<string>;
ownerId: string;
primaryAssetId: string;
}
export interface Audit {
action: string;
createdAt: Generated<Timestamp>;
entityId: string;
entityType: string;
id: Generated<number>;
ownerId: string;
}
export interface Exif {
assetId: string;
updateId: Generated<string>;
updatedAt: Generated<Timestamp>;
autoStackId: string | null;
bitsPerSample: number | null;
city: string | null;
colorspace: string | null;
country: string | null;
dateTimeOriginal: Timestamp | null;
description: Generated<string>;
exifImageHeight: number | null;
exifImageWidth: number | null;
exposureTime: string | null;
fileSizeInByte: Int8 | null;
fNumber: number | null;
focalLength: number | null;
fps: number | null;
iso: number | null;
latitude: number | null;
lensModel: string | null;
livePhotoCID: string | null;
longitude: number | null;
make: string | null;
model: string | null;
modifyDate: Timestamp | null;
orientation: string | null;
profileDescription: string | null;
projectionType: string | null;
rating: number | null;
state: string | null;
timeZone: string | null;
}
export interface FaceSearch {
embedding: string;
faceId: string;
}
export interface GeodataPlaces {
admin1Code: string | null;
admin1Name: string | null;
admin2Code: string | null;
admin2Name: string | null;
alternateNames: string | null;
countryCode: string;
id: number;
latitude: number;
longitude: number;
modificationDate: Timestamp;
name: string;
}
export interface Libraries {
createdAt: Generated<Timestamp>;
deletedAt: Timestamp | null;
exclusionPatterns: string[];
id: Generated<string>;
importPaths: string[];
name: string;
ownerId: string;
refreshedAt: Timestamp | null;
updatedAt: Generated<Timestamp>;
updateId: Generated<string>;
}
export interface Memories {
createdAt: Generated<Timestamp>;
data: object;
deletedAt: Timestamp | null;
hideAt: Timestamp | null;
id: Generated<string>;
isSaved: Generated<boolean>;
memoryAt: Timestamp;
ownerId: string;
seenAt: Timestamp | null;
showAt: Timestamp | null;
type: MemoryType;
updatedAt: Generated<Timestamp>;
updateId: Generated<string>;
}
export interface Notifications {
id: Generated<string>;
createdAt: Generated<Timestamp>;
updatedAt: Generated<Timestamp>;
deletedAt: Timestamp | null;
updateId: Generated<string>;
userId: string;
level: Generated<NotificationLevel>;
type: NotificationType;
title: string;
description: string | null;
data: any | null;
readAt: Timestamp | null;
}
export interface Migrations {
id: Generated<number>;
name: string;
timestamp: Int8;
}
export interface MoveHistory {
entityId: string;
id: Generated<string>;
newPath: string;
oldPath: string;
pathType: string;
}
export interface NaturalearthCountries {
admin: string;
admin_a3: string;
coordinates: string;
id: Generated<number>;
type: string;
}
export interface PartnersAudit {
deletedAt: Generated<Timestamp>;
id: Generated<string>;
sharedById: string;
sharedWithId: string;
}
export interface Partners {
createdAt: Generated<Timestamp>;
createId: Generated<string>;
inTimeline: Generated<boolean>;
sharedById: string;
sharedWithId: string;
updatedAt: Generated<Timestamp>;
updateId: Generated<string>;
}
export interface Person {
birthDate: Timestamp | null;
color: string | null;
createdAt: Generated<Timestamp>;
faceAssetId: string | null;
id: Generated<string>;
isFavorite: Generated<boolean>;
isHidden: Generated<boolean>;
name: Generated<string>;
ownerId: string;
thumbnailPath: Generated<string>;
updatedAt: Generated<Timestamp>;
updateId: Generated<string>;
}
export interface Sessions {
createdAt: Generated<Timestamp>;
deviceOS: Generated<string>;
deviceType: Generated<string>;
id: Generated<string>;
parentId: string | null;
expiresAt: Date | null;
token: string;
updatedAt: Generated<Timestamp>;
updateId: Generated<string>;
userId: string;
pinExpiresAt: Timestamp | null;
}
export interface SessionSyncCheckpoints {
ack: string;
createdAt: Generated<Timestamp>;
sessionId: string;
type: SyncEntityType;
updatedAt: Generated<Timestamp>;
updateId: Generated<string>;
}
export interface SharedLinkAsset {
assetsId: string;
sharedLinksId: string;
}
export interface SharedLinks {
albumId: string | null;
allowDownload: Generated<boolean>;
allowUpload: Generated<boolean>;
createdAt: Generated<Timestamp>;
description: string | null;
expiresAt: Timestamp | null;
id: Generated<string>;
key: Buffer;
password: string | null;
showExif: Generated<boolean>;
type: SharedLinkType;
userId: string;
}
export interface SmartSearch {
assetId: string;
embedding: string;
}
export interface SocketIoAttachments {
created_at: Generated<Timestamp | null>;
id: Generated<Int8>;
payload: Buffer | null;
}
export interface SystemConfig {
key: string;
value: string | null;
}
export interface SystemMetadata {
key: string;
value: Json;
}
export interface TagAsset {
assetsId: string;
tagsId: string;
}
export interface Tags {
color: string | null;
createdAt: Generated<Timestamp>;
id: Generated<string>;
parentId: string | null;
updatedAt: Generated<Timestamp>;
updateId: Generated<string>;
userId: string;
value: string;
}
export interface TagsClosure {
id_ancestor: string;
id_descendant: string;
}
export interface TypeormMetadata {
database: string | null;
name: string | null;
schema: string | null;
table: string | null;
type: string;
value: string | null;
}
export interface UserMetadata extends UserMetadataItem {
userId: string;
}
export interface UsersAudit {
id: Generated<string>;
userId: string;
deletedAt: Generated<Timestamp>;
}
export interface VectorsPgVectorIndexStat {
idx_growing: ArrayType<Int8> | null;
idx_indexing: boolean | null;
idx_options: string | null;
idx_sealed: ArrayType<Int8> | null;
idx_size: Int8 | null;
idx_status: string | null;
idx_tuples: Int8 | null;
idx_write: Int8 | null;
indexname: string | null;
indexrelid: number | null;
tablename: string | null;
tablerelid: number | null;
}
export interface VersionHistory {
createdAt: Generated<Timestamp>;
id: Generated<string>;
version: string;
}
export interface DB {
activity: Activity;
albums: Albums;
albums_audit: AlbumsAudit;
albums_assets_assets: AlbumsAssetsAssets;
album_assets_audit: AlbumAssetsAudit;
albums_shared_users_users: AlbumsSharedUsersUsers;
album_users_audit: AlbumUsersAudit;
api_keys: ApiKeys;
asset_faces: AssetFaces;
asset_files: AssetFiles;
asset_job_status: AssetJobStatus;
asset_stack: AssetStack;
assets: Assets;
assets_audit: AssetsAudit;
audit: Audit;
exif: Exif;
face_search: FaceSearch;
geodata_places: GeodataPlaces;
libraries: Libraries;
memories: Memories;
memories_audit: MemoryAuditTable;
memories_assets_assets: MemoryAssetTable;
memory_assets_audit: MemoryAssetAuditTable;
migrations: Migrations;
notifications: Notifications;
move_history: MoveHistory;
naturalearth_countries: NaturalearthCountries;
partners_audit: PartnersAudit;
partners: Partners;
person: Person;
sessions: Sessions;
session_sync_checkpoints: SessionSyncCheckpoints;
shared_link__asset: SharedLinkAsset;
shared_links: SharedLinks;
smart_search: SmartSearch;
socket_io_attachments: SocketIoAttachments;
system_config: SystemConfig;
system_metadata: SystemMetadata;
tag_asset: TagAsset;
tags: Tags;
tags_closure: TagsClosure;
typeorm_metadata: TypeormMetadata;
user_metadata: UserMetadata;
users: UserTable;
users_audit: UsersAudit;
'vectors.pg_vector_index_stat': VectorsPgVectorIndexStat;
version_history: VersionHistory;
}

View File

@ -4,10 +4,10 @@ import { IsArray, IsInt, IsNotEmpty, IsNumber, IsString, Max, Min, ValidateNeste
import { Selectable } from 'kysely';
import { DateTime } from 'luxon';
import { AssetFace, Person } from 'src/database';
import { AssetFaces } from 'src/db';
import { PropertyLifecycle } from 'src/decorators';
import { AuthDto } from 'src/dtos/auth.dto';
import { SourceType } from 'src/enum';
import { AssetFaceTable } from 'src/schema/tables/asset-face.table';
import { asDateString } from 'src/utils/date';
import {
IsDateStringFormat,
@ -232,7 +232,7 @@ export function mapPerson(person: Person): PersonResponseDto {
};
}
export function mapFacesWithoutPerson(face: Selectable<AssetFaces>): AssetFaceWithoutPersonResponseDto {
export function mapFacesWithoutPerson(face: Selectable<AssetFaceTable>): AssetFaceWithoutPersonResponseDto {
return {
id: face.id,
imageHeight: face.imageHeight,

View File

@ -1,9 +1,9 @@
import { Injectable } from '@nestjs/common';
import { Kysely, sql } from 'kysely';
import { InjectKysely } from 'nestjs-kysely';
import { DB } from 'src/db';
import { ChunkedSet, DummyValue, GenerateSql } from 'src/decorators';
import { AlbumUserRole, AssetVisibility } from 'src/enum';
import { DB } from 'src/schema';
import { asUuid } from 'src/utils/database';
class ActivityAccess {

View File

@ -3,9 +3,10 @@ import { Insertable, Kysely, NotNull, sql } from 'kysely';
import { jsonObjectFrom } from 'kysely/helpers/postgres';
import { InjectKysely } from 'nestjs-kysely';
import { columns } from 'src/database';
import { Activity, DB } from 'src/db';
import { DummyValue, GenerateSql } from 'src/decorators';
import { AssetVisibility } from 'src/enum';
import { DB } from 'src/schema';
import { ActivityTable } from 'src/schema/tables/activity.table';
import { asUuid } from 'src/utils/database';
export interface ActivitySearch {
@ -48,7 +49,7 @@ export class ActivityRepository {
}
@GenerateSql({ params: [{ albumId: DummyValue.UUID, userId: DummyValue.UUID }] })
async create(activity: Insertable<Activity>) {
async create(activity: Insertable<ActivityTable>) {
return this.db
.insertInto('activity')
.values(activity)

View File

@ -1,9 +1,10 @@
import { Injectable } from '@nestjs/common';
import { Insertable, Kysely, Updateable } from 'kysely';
import { InjectKysely } from 'nestjs-kysely';
import { AlbumsSharedUsersUsers, DB } from 'src/db';
import { DummyValue, GenerateSql } from 'src/decorators';
import { AlbumUserRole } from 'src/enum';
import { DB } from 'src/schema';
import { AlbumUserTable } from 'src/schema/tables/album-user.table';
export type AlbumPermissionId = {
albumsId: string;
@ -15,7 +16,7 @@ export class AlbumUserRepository {
constructor(@InjectKysely() private db: Kysely<DB>) {}
@GenerateSql({ params: [{ usersId: DummyValue.UUID, albumsId: DummyValue.UUID }] })
create(albumUser: Insertable<AlbumsSharedUsersUsers>) {
create(albumUser: Insertable<AlbumUserTable>) {
return this.db
.insertInto('albums_shared_users_users')
.values(albumUser)
@ -24,7 +25,7 @@ export class AlbumUserRepository {
}
@GenerateSql({ params: [{ usersId: DummyValue.UUID, albumsId: DummyValue.UUID }, { role: AlbumUserRole.VIEWER }] })
update({ usersId, albumsId }: AlbumPermissionId, dto: Updateable<AlbumsSharedUsersUsers>) {
update({ usersId, albumsId }: AlbumPermissionId, dto: Updateable<AlbumUserTable>) {
return this.db
.updateTable('albums_shared_users_users')
.set(dto)

View File

@ -3,9 +3,10 @@ import { ExpressionBuilder, Insertable, Kysely, NotNull, sql, Updateable } from
import { jsonArrayFrom, jsonObjectFrom } from 'kysely/helpers/postgres';
import { InjectKysely } from 'nestjs-kysely';
import { columns, Exif } from 'src/database';
import { Albums, DB } from 'src/db';
import { Chunked, ChunkedArray, ChunkedSet, DummyValue, GenerateSql } from 'src/decorators';
import { AlbumUserCreateDto } from 'src/dtos/album.dto';
import { DB } from 'src/schema';
import { AlbumTable } from 'src/schema/tables/album.table';
import { withDefaultVisibility } from 'src/utils/database';
export interface AlbumAssetCount {
@ -269,7 +270,7 @@ export class AlbumRepository {
await this.addAssets(this.db, albumId, assetIds);
}
create(album: Insertable<Albums>, assetIds: string[], albumUsers: AlbumUserCreateDto[]) {
create(album: Insertable<AlbumTable>, assetIds: string[], albumUsers: AlbumUserCreateDto[]) {
return this.db.transaction().execute(async (tx) => {
const newAlbum = await tx.insertInto('albums').values(album).returning('albums.id').executeTakeFirst();
@ -302,7 +303,7 @@ export class AlbumRepository {
});
}
update(id: string, album: Updateable<Albums>) {
update(id: string, album: Updateable<AlbumTable>) {
return this.db
.updateTable('albums')
.set(album)

View File

@ -3,19 +3,20 @@ import { Insertable, Kysely, Updateable } from 'kysely';
import { jsonObjectFrom } from 'kysely/helpers/postgres';
import { InjectKysely } from 'nestjs-kysely';
import { columns } from 'src/database';
import { ApiKeys, DB } from 'src/db';
import { DummyValue, GenerateSql } from 'src/decorators';
import { DB } from 'src/schema';
import { ApiKeyTable } from 'src/schema/tables/api-key.table';
import { asUuid } from 'src/utils/database';
@Injectable()
export class ApiKeyRepository {
constructor(@InjectKysely() private db: Kysely<DB>) {}
create(dto: Insertable<ApiKeys>) {
create(dto: Insertable<ApiKeyTable>) {
return this.db.insertInto('api_keys').values(dto).returning(columns.apiKey).executeTakeFirstOrThrow();
}
async update(userId: string, id: string, dto: Updateable<ApiKeys>) {
async update(userId: string, id: string, dto: Updateable<ApiKeyTable>) {
return this.db
.updateTable('api_keys')
.set(dto)

View File

@ -3,9 +3,9 @@ import { Kysely } from 'kysely';
import { jsonArrayFrom } from 'kysely/helpers/postgres';
import { InjectKysely } from 'nestjs-kysely';
import { Asset, columns } from 'src/database';
import { DB } from 'src/db';
import { DummyValue, GenerateSql } from 'src/decorators';
import { AssetFileType, AssetType, AssetVisibility } from 'src/enum';
import { DB } from 'src/schema';
import { StorageAsset } from 'src/types';
import {
anyUuid,

View File

@ -3,9 +3,13 @@ import { Insertable, Kysely, NotNull, Selectable, UpdateResult, Updateable, sql
import { isEmpty, isUndefined, omitBy } from 'lodash';
import { InjectKysely } from 'nestjs-kysely';
import { Stack } from 'src/database';
import { AssetFiles, AssetJobStatus, Assets, DB, Exif } from 'src/db';
import { Chunked, ChunkedArray, DummyValue, GenerateSql } from 'src/decorators';
import { AssetFileType, AssetOrder, AssetStatus, AssetType, AssetVisibility } from 'src/enum';
import { DB } from 'src/schema';
import { AssetFileTable } from 'src/schema/tables/asset-files.table';
import { AssetJobStatusTable } from 'src/schema/tables/asset-job-status.table';
import { AssetTable } from 'src/schema/tables/asset.table';
import { ExifTable } from 'src/schema/tables/exif.table';
import {
anyUuid,
asUuid,
@ -110,7 +114,7 @@ interface GetByIdsRelations {
export class AssetRepository {
constructor(@InjectKysely() private db: Kysely<DB>) {}
async upsertExif(exif: Insertable<Exif>): Promise<void> {
async upsertExif(exif: Insertable<ExifTable>): Promise<void> {
const value = { ...exif, assetId: asUuid(exif.assetId) };
await this.db
.insertInto('exif')
@ -157,7 +161,7 @@ export class AssetRepository {
@GenerateSql({ params: [[DummyValue.UUID], { model: DummyValue.STRING }] })
@Chunked()
async updateAllExif(ids: string[], options: Updateable<Exif>): Promise<void> {
async updateAllExif(ids: string[], options: Updateable<ExifTable>): Promise<void> {
if (ids.length === 0) {
return;
}
@ -165,7 +169,7 @@ export class AssetRepository {
await this.db.updateTable('exif').set(options).where('assetId', 'in', ids).execute();
}
async upsertJobStatus(...jobStatus: Insertable<AssetJobStatus>[]): Promise<void> {
async upsertJobStatus(...jobStatus: Insertable<AssetJobStatusTable>[]): Promise<void> {
if (jobStatus.length === 0) {
return;
}
@ -191,11 +195,11 @@ export class AssetRepository {
.execute();
}
create(asset: Insertable<Assets>) {
create(asset: Insertable<AssetTable>) {
return this.db.insertInto('assets').values(asset).returningAll().executeTakeFirstOrThrow();
}
createAll(assets: Insertable<Assets>[]) {
createAll(assets: Insertable<AssetTable>[]) {
return this.db.insertInto('assets').values(assets).returningAll().execute();
}
@ -375,18 +379,18 @@ export class AssetRepository {
@GenerateSql({ params: [[DummyValue.UUID], { deviceId: DummyValue.STRING }] })
@Chunked()
async updateAll(ids: string[], options: Updateable<Assets>): Promise<void> {
async updateAll(ids: string[], options: Updateable<AssetTable>): Promise<void> {
if (ids.length === 0) {
return;
}
await this.db.updateTable('assets').set(options).where('id', '=', anyUuid(ids)).execute();
}
async updateByLibraryId(libraryId: string, options: Updateable<Assets>): Promise<void> {
async updateByLibraryId(libraryId: string, options: Updateable<AssetTable>): Promise<void> {
await this.db.updateTable('assets').set(options).where('libraryId', '=', asUuid(libraryId)).execute();
}
async update(asset: Updateable<Assets> & { id: string }) {
async update(asset: Updateable<AssetTable> & { id: string }) {
const value = omitBy(asset, isUndefined);
delete value.id;
if (!isEmpty(value)) {
@ -742,7 +746,7 @@ export class AssetRepository {
.execute();
}
async upsertFile(file: Pick<Insertable<AssetFiles>, 'assetId' | 'path' | 'type'>): Promise<void> {
async upsertFile(file: Pick<Insertable<AssetFileTable>, 'assetId' | 'path' | 'type'>): Promise<void> {
const value = { ...file, assetId: asUuid(file.assetId) };
await this.db
.insertInto('asset_files')
@ -755,7 +759,7 @@ export class AssetRepository {
.execute();
}
async upsertFiles(files: Pick<Insertable<AssetFiles>, 'assetId' | 'path' | 'type'>[]): Promise<void> {
async upsertFiles(files: Pick<Insertable<AssetFileTable>, 'assetId' | 'path' | 'type'>[]): Promise<void> {
if (files.length === 0) {
return;
}
@ -772,7 +776,7 @@ export class AssetRepository {
.execute();
}
async deleteFiles(files: Pick<Selectable<AssetFiles>, 'id'>[]): Promise<void> {
async deleteFiles(files: Pick<Selectable<AssetFileTable>, 'id'>[]): Promise<void> {
if (files.length === 0) {
return;
}

View File

@ -1,9 +1,9 @@
import { Injectable } from '@nestjs/common';
import { Kysely } from 'kysely';
import { InjectKysely } from 'nestjs-kysely';
import { DB } from 'src/db';
import { DummyValue, GenerateSql } from 'src/decorators';
import { DatabaseAction, EntityType } from 'src/enum';
import { DB } from 'src/schema';
export interface AuditSearch {
action?: DatabaseAction;

View File

@ -15,11 +15,11 @@ import {
VECTORCHORD_VERSION_RANGE,
VECTORS_VERSION_RANGE,
} from 'src/constants';
import { DB } from 'src/db';
import { GenerateSql } from 'src/decorators';
import { DatabaseExtension, DatabaseLock, VectorIndex } from 'src/enum';
import { ConfigRepository } from 'src/repositories/config.repository';
import { LoggingRepository } from 'src/repositories/logging.repository';
import { DB } from 'src/schema';
import { ExtensionVersion, VectorExtension, VectorUpdateResult } from 'src/types';
import { vectorIndexQuery } from 'src/utils/database';
import { isValidInteger } from 'src/validation';

View File

@ -1,8 +1,8 @@
import { Injectable } from '@nestjs/common';
import { Kysely } from 'kysely';
import { InjectKysely } from 'nestjs-kysely';
import { DB } from 'src/db';
import { AssetVisibility } from 'src/enum';
import { DB } from 'src/schema';
import { anyUuid } from 'src/utils/database';
const builder = (db: Kysely<DB>) =>

View File

@ -1,11 +1,11 @@
import { Injectable } from '@nestjs/common';
import { Kysely, NotNull, sql } from 'kysely';
import { InjectKysely } from 'nestjs-kysely';
import { DB } from 'src/db';
import { Chunked, DummyValue, GenerateSql } from 'src/decorators';
import { MapAsset } from 'src/dtos/asset-response.dto';
import { AssetType, VectorIndex } from 'src/enum';
import { probes } from 'src/repositories/database.repository';
import { DB } from 'src/schema';
import { anyUuid, asUuid, withDefaultVisibility } from 'src/utils/database';
interface DuplicateSearch {

View File

@ -1,10 +1,11 @@
import { Injectable } from '@nestjs/common';
import { Insertable, Kysely, sql, Updateable } from 'kysely';
import { InjectKysely } from 'nestjs-kysely';
import { DB, Libraries } from 'src/db';
import { DummyValue, GenerateSql } from 'src/decorators';
import { LibraryStatsResponseDto } from 'src/dtos/library.dto';
import { AssetType, AssetVisibility } from 'src/enum';
import { DB } from 'src/schema';
import { LibraryTable } from 'src/schema/tables/library.table';
export enum AssetSyncResult {
DO_NOTHING,
@ -47,7 +48,7 @@ export class LibraryRepository {
.execute();
}
create(library: Insertable<Libraries>) {
create(library: Insertable<LibraryTable>) {
return this.db.insertInto('libraries').values(library).returningAll().executeTakeFirstOrThrow();
}
@ -59,7 +60,7 @@ export class LibraryRepository {
await this.db.updateTable('libraries').set({ deletedAt: new Date() }).where('libraries.id', '=', id).execute();
}
update(id: string, library: Updateable<Libraries>) {
update(id: string, library: Updateable<LibraryTable>) {
return this.db
.updateTable('libraries')
.set(library)

View File

@ -6,12 +6,14 @@ import { createReadStream, existsSync } from 'node:fs';
import { readFile } from 'node:fs/promises';
import readLine from 'node:readline';
import { citiesFile } from 'src/constants';
import { DB, GeodataPlaces, NaturalearthCountries } from 'src/db';
import { DummyValue, GenerateSql } from 'src/decorators';
import { AssetVisibility, SystemMetadataKey } from 'src/enum';
import { ConfigRepository } from 'src/repositories/config.repository';
import { LoggingRepository } from 'src/repositories/logging.repository';
import { SystemMetadataRepository } from 'src/repositories/system-metadata.repository';
import { DB } from 'src/schema';
import { GeodataPlacesTable } from 'src/schema/tables/geodata-places.table';
import { NaturalEarthCountriesTable } from 'src/schema/tables/natural-earth-countries.table';
export interface MapMarkerSearchOptions {
isArchived?: boolean;
@ -38,8 +40,8 @@ export interface MapMarker extends ReverseGeocodeResult {
}
interface MapDB extends DB {
geodata_places_tmp: GeodataPlaces;
naturalearth_countries_tmp: NaturalearthCountries;
geodata_places_tmp: GeodataPlacesTable;
naturalearth_countries_tmp: NaturalEarthCountriesTable;
}
@Injectable()
@ -193,11 +195,11 @@ export class MapRepository {
return;
}
const entities: Insertable<NaturalearthCountries>[] = [];
const entities: Insertable<NaturalEarthCountriesTable>[] = [];
for (const feature of geoJSONData.features) {
for (const entry of feature.geometry.coordinates) {
const coordinates: number[][][] = feature.geometry.type === 'MultiPolygon' ? entry[0] : entry;
const featureRecord: Insertable<NaturalearthCountries> = {
const featureRecord: Insertable<NaturalEarthCountriesTable> = {
admin: feature.properties.ADMIN,
admin_a3: feature.properties.ADM0_A3,
type: feature.properties.TYPE,

View File

@ -3,10 +3,11 @@ import { Insertable, Kysely, sql, Updateable } from 'kysely';
import { jsonArrayFrom } from 'kysely/helpers/postgres';
import { DateTime } from 'luxon';
import { InjectKysely } from 'nestjs-kysely';
import { DB, Memories } from 'src/db';
import { Chunked, ChunkedSet, DummyValue, GenerateSql } from 'src/decorators';
import { MemorySearchDto } from 'src/dtos/memory.dto';
import { AssetVisibility } from 'src/enum';
import { DB } from 'src/schema';
import { MemoryTable } from 'src/schema/tables/memory.table';
import { IBulkAsset } from 'src/types';
@Injectable()
@ -80,7 +81,7 @@ export class MemoryRepository implements IBulkAsset {
return this.getByIdBuilder(id).executeTakeFirst();
}
async create(memory: Insertable<Memories>, assetIds: Set<string>) {
async create(memory: Insertable<MemoryTable>, assetIds: Set<string>) {
const id = await this.db.transaction().execute(async (tx) => {
const { id } = await tx.insertInto('memories').values(memory).returning('id').executeTakeFirstOrThrow();
@ -96,7 +97,7 @@ export class MemoryRepository implements IBulkAsset {
}
@GenerateSql({ params: [DummyValue.UUID, { ownerId: DummyValue.UUID, isSaved: true }] })
async update(id: string, memory: Updateable<Memories>) {
async update(id: string, memory: Updateable<MemoryTable>) {
await this.db.updateTable('memories').set(memory).where('id', '=', id).execute();
return this.getByIdBuilder(id).executeTakeFirstOrThrow();
}

View File

@ -1,15 +1,16 @@
import { Injectable } from '@nestjs/common';
import { Insertable, Kysely, sql, Updateable } from 'kysely';
import { InjectKysely } from 'nestjs-kysely';
import { DB, MoveHistory } from 'src/db';
import { DummyValue, GenerateSql } from 'src/decorators';
import { AssetPathType, PathType } from 'src/enum';
import { DB } from 'src/schema';
import { MoveTable } from 'src/schema/tables/move.table';
@Injectable()
export class MoveRepository {
constructor(@InjectKysely() private db: Kysely<DB>) {}
create(entity: Insertable<MoveHistory>) {
create(entity: Insertable<MoveTable>) {
return this.db.insertInto('move_history').values(entity).returningAll().executeTakeFirstOrThrow();
}
@ -23,7 +24,7 @@ export class MoveRepository {
.executeTakeFirst();
}
update(id: string, entity: Updateable<MoveHistory>) {
update(id: string, entity: Updateable<MoveTable>) {
return this.db
.updateTable('move_history')
.set(entity)

View File

@ -2,9 +2,10 @@ import { Insertable, Kysely, Updateable } from 'kysely';
import { DateTime } from 'luxon';
import { InjectKysely } from 'nestjs-kysely';
import { columns } from 'src/database';
import { DB, Notifications } from 'src/db';
import { DummyValue, GenerateSql } from 'src/decorators';
import { NotificationSearchDto } from 'src/dtos/notification.dto';
import { DB } from 'src/schema';
import { NotificationTable } from 'src/schema/tables/notification.table';
export class NotificationRepository {
constructor(@InjectKysely() private db: Kysely<DB>) {}
@ -53,7 +54,7 @@ export class NotificationRepository {
.execute();
}
create(notification: Insertable<Notifications>) {
create(notification: Insertable<NotificationTable>) {
return this.db
.insertInto('notifications')
.values(notification)
@ -70,7 +71,7 @@ export class NotificationRepository {
.executeTakeFirst();
}
update(id: string, notification: Updateable<Notifications>) {
update(id: string, notification: Updateable<NotificationTable>) {
return this.db
.updateTable('notifications')
.set(notification)
@ -80,7 +81,7 @@ export class NotificationRepository {
.executeTakeFirstOrThrow();
}
async updateAll(ids: string[], notification: Updateable<Notifications>) {
async updateAll(ids: string[], notification: Updateable<NotificationTable>) {
await this.db.updateTable('notifications').set(notification).where('id', 'in', ids).execute();
}

View File

@ -3,8 +3,9 @@ import { ExpressionBuilder, Insertable, Kysely, NotNull, Updateable } from 'kyse
import { jsonObjectFrom } from 'kysely/helpers/postgres';
import { InjectKysely } from 'nestjs-kysely';
import { columns } from 'src/database';
import { DB, Partners } from 'src/db';
import { DummyValue, GenerateSql } from 'src/decorators';
import { DB } from 'src/schema';
import { PartnerTable } from 'src/schema/tables/partner.table';
export interface PartnerIds {
sharedById: string;
@ -47,7 +48,7 @@ export class PartnerRepository {
.executeTakeFirst();
}
create(values: Insertable<Partners>) {
create(values: Insertable<PartnerTable>) {
return this.db
.insertInto('partners')
.values(values)
@ -59,7 +60,7 @@ export class PartnerRepository {
}
@GenerateSql({ params: [{ sharedWithId: DummyValue.UUID, sharedById: DummyValue.UUID }, { inTimeline: true }] })
update({ sharedWithId, sharedById }: PartnerIds, values: Updateable<Partners>) {
update({ sharedWithId, sharedById }: PartnerIds, values: Updateable<PartnerTable>) {
return this.db
.updateTable('partners')
.set(values)

View File

@ -2,9 +2,12 @@ import { Injectable } from '@nestjs/common';
import { ExpressionBuilder, Insertable, Kysely, Selectable, sql, Updateable } from 'kysely';
import { jsonObjectFrom } from 'kysely/helpers/postgres';
import { InjectKysely } from 'nestjs-kysely';
import { AssetFaces, DB, FaceSearch, Person } from 'src/db';
import { Chunked, ChunkedArray, DummyValue, GenerateSql } from 'src/decorators';
import { AssetFileType, AssetVisibility, SourceType } from 'src/enum';
import { DB } from 'src/schema';
import { AssetFaceTable } from 'src/schema/tables/asset-face.table';
import { FaceSearchTable } from 'src/schema/tables/face-search.table';
import { PersonTable } from 'src/schema/tables/person.table';
import { removeUndefinedKeys } from 'src/utils/database';
import { paginationHelper, PaginationOptions } from 'src/utils/pagination';
@ -57,7 +60,7 @@ export interface GetAllFacesOptions {
export type UnassignFacesOptions = DeleteFacesOptions;
export type SelectFaceOptions = (keyof Selectable<AssetFaces>)[];
export type SelectFaceOptions = (keyof Selectable<AssetFaceTable>)[];
const withPerson = (eb: ExpressionBuilder<DB, 'asset_faces'>) => {
return jsonObjectFrom(
@ -378,11 +381,11 @@ export class PersonRepository {
.executeTakeFirstOrThrow();
}
create(person: Insertable<Person>) {
create(person: Insertable<PersonTable>) {
return this.db.insertInto('person').values(person).returningAll().executeTakeFirstOrThrow();
}
async createAll(people: Insertable<Person>[]): Promise<string[]> {
async createAll(people: Insertable<PersonTable>[]): Promise<string[]> {
if (people.length === 0) {
return [];
}
@ -393,9 +396,9 @@ export class PersonRepository {
@GenerateSql({ params: [[], [], [{ faceId: DummyValue.UUID, embedding: DummyValue.VECTOR }]] })
async refreshFaces(
facesToAdd: (Insertable<AssetFaces> & { assetId: string })[],
facesToAdd: (Insertable<AssetFaceTable> & { assetId: string })[],
faceIdsToRemove: string[],
embeddingsToAdd?: Insertable<FaceSearch>[],
embeddingsToAdd?: Insertable<FaceSearchTable>[],
): Promise<void> {
let query = this.db;
if (facesToAdd.length > 0) {
@ -415,7 +418,7 @@ export class PersonRepository {
await query.selectFrom(sql`(select 1)`.as('dummy')).execute();
}
async update(person: Updateable<Person> & { id: string }) {
async update(person: Updateable<PersonTable> & { id: string }) {
return this.db
.updateTable('person')
.set(person)
@ -424,7 +427,7 @@ export class PersonRepository {
.executeTakeFirstOrThrow();
}
async updateAll(people: Insertable<Person>[]): Promise<void> {
async updateAll(people: Insertable<PersonTable>[]): Promise<void> {
if (people.length === 0) {
return;
}
@ -496,7 +499,7 @@ export class PersonRepository {
return result?.latestDate;
}
async createAssetFace(face: Insertable<AssetFaces>): Promise<void> {
async createAssetFace(face: Insertable<AssetFaceTable>): Promise<void> {
await this.db.insertInto('asset_faces').values(face).execute();
}

View File

@ -2,11 +2,12 @@ import { Injectable } from '@nestjs/common';
import { Kysely, OrderByDirection, Selectable, sql } from 'kysely';
import { InjectKysely } from 'nestjs-kysely';
import { randomUUID } from 'node:crypto';
import { DB, Exif } from 'src/db';
import { DummyValue, GenerateSql } from 'src/decorators';
import { MapAsset } from 'src/dtos/asset-response.dto';
import { AssetStatus, AssetType, AssetVisibility, VectorIndex } from 'src/enum';
import { probes } from 'src/repositories/database.repository';
import { DB } from 'src/schema';
import { ExifTable } from 'src/schema/tables/exif.table';
import { anyUuid, searchAssetBuilder } from 'src/utils/database';
import { paginationHelper } from 'src/utils/pagination';
import { isValidInteger } from 'src/validation';
@ -385,7 +386,7 @@ export class SearchRepository {
.select((eb) =>
eb
.fn('to_jsonb', [eb.table('exif')])
.$castTo<Selectable<Exif>>()
.$castTo<Selectable<ExifTable>>()
.as('exifInfo'),
)
.orderBy('exif.city')

View File

@ -4,8 +4,9 @@ import { jsonObjectFrom } from 'kysely/helpers/postgres';
import { DateTime } from 'luxon';
import { InjectKysely } from 'nestjs-kysely';
import { columns } from 'src/database';
import { DB, Sessions } from 'src/db';
import { DummyValue, GenerateSql } from 'src/decorators';
import { DB } from 'src/schema';
import { SessionTable } from 'src/schema/tables/session.table';
import { asUuid } from 'src/utils/database';
export type SessionSearchOptions = { updatedBefore: Date };
@ -72,11 +73,11 @@ export class SessionRepository {
.execute();
}
create(dto: Insertable<Sessions>) {
create(dto: Insertable<SessionTable>) {
return this.db.insertInto('sessions').values(dto).returningAll().executeTakeFirstOrThrow();
}
update(id: string, dto: Updateable<Sessions>) {
update(id: string, dto: Updateable<SessionTable>) {
return this.db
.updateTable('sessions')
.set(dto)

View File

@ -4,10 +4,11 @@ import { jsonObjectFrom } from 'kysely/helpers/postgres';
import _ from 'lodash';
import { InjectKysely } from 'nestjs-kysely';
import { Album, columns } from 'src/database';
import { DB, SharedLinks } from 'src/db';
import { DummyValue, GenerateSql } from 'src/decorators';
import { MapAsset } from 'src/dtos/asset-response.dto';
import { SharedLinkType } from 'src/enum';
import { DB } from 'src/schema';
import { SharedLinkTable } from 'src/schema/tables/shared-link.table';
export type SharedLinkSearchOptions = {
userId: string;
@ -183,7 +184,7 @@ export class SharedLinkRepository {
.executeTakeFirst();
}
async create(entity: Insertable<SharedLinks> & { assetIds?: string[] }) {
async create(entity: Insertable<SharedLinkTable> & { assetIds?: string[] }) {
const { id } = await this.db
.insertInto('shared_links')
.values(_.omit(entity, 'assetIds'))
@ -200,7 +201,7 @@ export class SharedLinkRepository {
return this.getSharedLinks(id);
}
async update(entity: Updateable<SharedLinks> & { id: string; assetIds?: string[] }) {
async update(entity: Updateable<SharedLinkTable> & { id: string; assetIds?: string[] }) {
const { id } = await this.db
.updateTable('shared_links')
.set(_.omit(entity, 'assets', 'album', 'assetIds'))

View File

@ -3,8 +3,9 @@ import { ExpressionBuilder, Kysely, Updateable } from 'kysely';
import { jsonArrayFrom } from 'kysely/helpers/postgres';
import { InjectKysely } from 'nestjs-kysely';
import { columns } from 'src/database';
import { AssetStack, DB } from 'src/db';
import { DummyValue, GenerateSql } from 'src/decorators';
import { DB } from 'src/schema';
import { StackTable } from 'src/schema/tables/stack.table';
import { asUuid, withDefaultVisibility } from 'src/utils/database';
export interface StackSearch {
@ -130,7 +131,7 @@ export class StackRepository {
await this.db.deleteFrom('asset_stack').where('id', 'in', ids).execute();
}
update(id: string, entity: Updateable<AssetStack>) {
update(id: string, entity: Updateable<StackTable>) {
return this.db
.updateTable('asset_stack')
.set(entity)

View File

@ -1,9 +1,10 @@
import { Injectable } from '@nestjs/common';
import { Insertable, Kysely } from 'kysely';
import { InjectKysely } from 'nestjs-kysely';
import { DB, SessionSyncCheckpoints } from 'src/db';
import { DummyValue, GenerateSql } from 'src/decorators';
import { SyncEntityType } from 'src/enum';
import { DB } from 'src/schema';
import { SessionSyncCheckpointTable } from 'src/schema/tables/sync-checkpoint.table';
@Injectable()
export class SyncCheckpointRepository {
@ -18,7 +19,7 @@ export class SyncCheckpointRepository {
.execute();
}
upsertAll(items: Insertable<SessionSyncCheckpoints>[]) {
upsertAll(items: Insertable<SessionSyncCheckpointTable>[]) {
return this.db
.insertInto('session_sync_checkpoints')
.values(items)

View File

@ -2,8 +2,8 @@ import { Injectable } from '@nestjs/common';
import { Kysely, SelectQueryBuilder, sql } from 'kysely';
import { InjectKysely } from 'nestjs-kysely';
import { columns } from 'src/database';
import { DB } from 'src/db';
import { DummyValue, GenerateSql } from 'src/decorators';
import { DB } from 'src/schema';
import { SyncAck } from 'src/types';
type AuditTables =

View File

@ -2,11 +2,12 @@ import { Injectable } from '@nestjs/common';
import { Insertable, Kysely } from 'kysely';
import { InjectKysely } from 'nestjs-kysely';
import { readFile } from 'node:fs/promises';
import { DB, SystemMetadata as DbSystemMetadata } from 'src/db';
import { GenerateSql } from 'src/decorators';
import { DB } from 'src/schema';
import { SystemMetadataTable } from 'src/schema/tables/system-metadata.table';
import { SystemMetadata } from 'src/types';
type Upsert = Insertable<DbSystemMetadata>;
type Upsert = Insertable<SystemMetadataTable>;
@Injectable()
export class SystemMetadataRepository {

View File

@ -2,9 +2,11 @@ import { Injectable } from '@nestjs/common';
import { Insertable, Kysely, sql, Updateable } from 'kysely';
import { InjectKysely } from 'nestjs-kysely';
import { columns } from 'src/database';
import { DB, TagAsset, Tags } from 'src/db';
import { Chunked, ChunkedSet, DummyValue, GenerateSql } from 'src/decorators';
import { LoggingRepository } from 'src/repositories/logging.repository';
import { DB } from 'src/schema';
import { TagAssetTable } from 'src/schema/tables/tag-asset.table';
import { TagTable } from 'src/schema/tables/tag.table';
@Injectable()
export class TagRepository {
@ -72,12 +74,12 @@ export class TagRepository {
}
@GenerateSql({ params: [{ userId: DummyValue.UUID, color: DummyValue.STRING, value: DummyValue.STRING }] })
create(tag: Insertable<Tags>) {
create(tag: Insertable<TagTable>) {
return this.db.insertInto('tags').values(tag).returningAll().executeTakeFirstOrThrow();
}
@GenerateSql({ params: [DummyValue.UUID, { color: DummyValue.STRING }] })
update(id: string, dto: Updateable<Tags>) {
update(id: string, dto: Updateable<TagTable>) {
return this.db.updateTable('tags').set(dto).where('id', '=', id).returningAll().executeTakeFirstOrThrow();
}
@ -128,7 +130,7 @@ export class TagRepository {
@GenerateSql({ params: [[{ assetId: DummyValue.UUID, tagsIds: [DummyValue.UUID] }]] })
@Chunked()
upsertAssetIds(items: Insertable<TagAsset>[]) {
upsertAssetIds(items: Insertable<TagAssetTable>[]) {
if (items.length === 0) {
return Promise.resolve([]);
}

View File

@ -1,8 +1,8 @@
import { Kysely } from 'kysely';
import { InjectKysely } from 'nestjs-kysely';
import { DB } from 'src/db';
import { DummyValue, GenerateSql } from 'src/decorators';
import { AssetStatus } from 'src/enum';
import { DB } from 'src/schema';
export class TrashRepository {
constructor(@InjectKysely() private db: Kysely<DB>) {}

View File

@ -4,14 +4,15 @@ import { jsonArrayFrom } from 'kysely/helpers/postgres';
import { DateTime } from 'luxon';
import { InjectKysely } from 'nestjs-kysely';
import { columns } from 'src/database';
import { DB, UserMetadata as DbUserMetadata } from 'src/db';
import { DummyValue, GenerateSql } from 'src/decorators';
import { AssetType, AssetVisibility, UserStatus } from 'src/enum';
import { DB } from 'src/schema';
import { UserMetadataTable } from 'src/schema/tables/user-metadata.table';
import { UserTable } from 'src/schema/tables/user.table';
import { UserMetadata, UserMetadataItem } from 'src/types';
import { asUuid } from 'src/utils/database';
type Upsert = Insertable<DbUserMetadata>;
type Upsert = Insertable<UserMetadataTable>;
export interface UserListFilter {
id?: string;

View File

@ -1,8 +1,9 @@
import { Injectable } from '@nestjs/common';
import { Insertable, Kysely } from 'kysely';
import { InjectKysely } from 'nestjs-kysely';
import { DB, VersionHistory } from 'src/db';
import { GenerateSql } from 'src/decorators';
import { DB } from 'src/schema';
import { VersionHistoryTable } from 'src/schema/tables/version-history.table';
@Injectable()
export class VersionHistoryRepository {
@ -18,7 +19,7 @@ export class VersionHistoryRepository {
return this.db.selectFrom('version_history').selectAll().orderBy('createdAt', 'desc').executeTakeFirst();
}
create(version: Insertable<VersionHistory>) {
create(version: Insertable<VersionHistoryTable>) {
return this.db.insertInto('version_history').values(version).returningAll().executeTakeFirstOrThrow();
}
}

View File

@ -1,8 +1,8 @@
import { Kysely } from 'kysely';
import { InjectKysely } from 'nestjs-kysely';
import { DB } from 'src/db';
import { DummyValue, GenerateSql } from 'src/decorators';
import { AssetVisibility } from 'src/enum';
import { DB } from 'src/schema';
import { asUuid, withExif } from 'src/utils/database';
export class ViewRepository {

View File

@ -21,7 +21,7 @@ import { AlbumAuditTable } from 'src/schema/tables/album-audit.table';
import { AlbumUserAuditTable } from 'src/schema/tables/album-user-audit.table';
import { AlbumUserTable } from 'src/schema/tables/album-user.table';
import { AlbumTable } from 'src/schema/tables/album.table';
import { APIKeyTable } from 'src/schema/tables/api-key.table';
import { ApiKeyTable } from 'src/schema/tables/api-key.table';
import { AssetAuditTable } from 'src/schema/tables/asset-audit.table';
import { AssetFaceTable } from 'src/schema/tables/asset-face.table';
import { AssetFileTable } from 'src/schema/tables/asset-files.table';
@ -51,11 +51,12 @@ import { SessionSyncCheckpointTable } from 'src/schema/tables/sync-checkpoint.ta
import { SystemMetadataTable } from 'src/schema/tables/system-metadata.table';
import { TagAssetTable } from 'src/schema/tables/tag-asset.table';
import { TagClosureTable } from 'src/schema/tables/tag-closure.table';
import { TagTable } from 'src/schema/tables/tag.table';
import { UserAuditTable } from 'src/schema/tables/user-audit.table';
import { UserMetadataTable } from 'src/schema/tables/user-metadata.table';
import { UserTable } from 'src/schema/tables/user.table';
import { VersionHistoryTable } from 'src/schema/tables/version-history.table';
import { Database, Extensions } from 'src/sql-tools';
import { Database, Extensions, Generated, Int8 } from 'src/sql-tools';
@Extensions(['uuid-ossp', 'unaccent', 'cube', 'earthdistance', 'pg_trgm', 'plpgsql'])
@Database({ name: 'immich' })
@ -68,7 +69,7 @@ export class ImmichDatabase {
AlbumUserAuditTable,
AlbumUserTable,
AlbumTable,
APIKeyTable,
ApiKeyTable,
AssetAuditTable,
AssetFaceTable,
AssetJobStatusTable,
@ -96,6 +97,7 @@ export class ImmichDatabase {
StackTable,
SessionSyncCheckpointTable,
SystemMetadataTable,
TagTable,
TagAssetTable,
TagClosureTable,
UserAuditTable,
@ -122,3 +124,55 @@ export class ImmichDatabase {
enum = [assets_status_enum, asset_face_source_type, asset_visibility_enum];
}
export interface Migrations {
id: Generated<number>;
name: string;
timestamp: Int8;
}
export interface DB {
activity: ActivityTable;
albums: AlbumTable;
albums_audit: AlbumAuditTable;
albums_assets_assets: AlbumAssetTable;
album_assets_audit: AlbumAssetAuditTable;
albums_shared_users_users: AlbumUserTable;
album_users_audit: AlbumUserAuditTable;
api_keys: ApiKeyTable;
asset_faces: AssetFaceTable;
asset_files: AssetFileTable;
asset_job_status: AssetJobStatusTable;
asset_stack: StackTable;
assets: AssetTable;
assets_audit: AssetAuditTable;
audit: AuditTable;
exif: ExifTable;
face_search: FaceSearchTable;
geodata_places: GeodataPlacesTable;
libraries: LibraryTable;
memories: MemoryTable;
memories_audit: MemoryAuditTable;
memories_assets_assets: MemoryAssetTable;
memory_assets_audit: MemoryAssetAuditTable;
migrations: Migrations;
notifications: NotificationTable;
move_history: MoveTable;
naturalearth_countries: NaturalEarthCountriesTable;
partners_audit: PartnerAuditTable;
partners: PartnerTable;
person: PersonTable;
sessions: SessionTable;
session_sync_checkpoints: SessionSyncCheckpointTable;
shared_link__asset: SharedLinkAssetTable;
shared_links: SharedLinkTable;
smart_search: SmartSearchTable;
system_metadata: SystemMetadataTable;
tag_asset: TagAssetTable;
tags: TagTable;
tags_closure: TagClosureTable;
user_metadata: UserMetadataTable;
users: UserTable;
users_audit: UserAuditTable;
version_history: VersionHistoryTable;
}

View File

@ -7,9 +7,11 @@ import {
Column,
CreateDateColumn,
ForeignKeyColumn,
Generated,
Index,
PrimaryGeneratedColumn,
Table,
Timestamp,
UpdateDateColumn,
} from 'src/sql-tools';
@ -27,13 +29,13 @@ import {
})
export class ActivityTable {
@PrimaryGeneratedColumn()
id!: string;
id!: Generated<string>;
@CreateDateColumn()
createdAt!: Date;
createdAt!: Generated<Timestamp>;
@UpdateDateColumn()
updatedAt!: Date;
updatedAt!: Generated<Timestamp>;
@ForeignKeyColumn(() => AlbumTable, { onDelete: 'CASCADE', onUpdate: 'CASCADE' })
albumId!: string;
@ -48,8 +50,8 @@ export class ActivityTable {
comment!: string | null;
@Column({ type: 'boolean', default: false })
isLiked!: boolean;
isLiked!: Generated<boolean>;
@UpdateIdColumn({ indexName: 'IDX_activity_update_id' })
updateId!: string;
updateId!: Generated<string>;
}

View File

@ -1,11 +1,11 @@
import { PrimaryGeneratedUuidV7Column } from 'src/decorators';
import { AlbumTable } from 'src/schema/tables/album.table';
import { Column, CreateDateColumn, ForeignKeyColumn, Table } from 'src/sql-tools';
import { Column, CreateDateColumn, ForeignKeyColumn, Generated, Table, Timestamp } from 'src/sql-tools';
@Table('album_assets_audit')
export class AlbumAssetAuditTable {
@PrimaryGeneratedUuidV7Column()
id!: string;
id!: Generated<string>;
@ForeignKeyColumn(() => AlbumTable, {
type: 'uuid',
@ -19,5 +19,5 @@ export class AlbumAssetAuditTable {
assetId!: string;
@CreateDateColumn({ default: () => 'clock_timestamp()', indexName: 'IDX_album_assets_audit_deleted_at' })
deletedAt!: Date;
deletedAt!: Generated<Timestamp>;
}

View File

@ -2,7 +2,15 @@ import { UpdatedAtTrigger, UpdateIdColumn } from 'src/decorators';
import { album_assets_delete_audit } from 'src/schema/functions';
import { AlbumTable } from 'src/schema/tables/album.table';
import { AssetTable } from 'src/schema/tables/asset.table';
import { AfterDeleteTrigger, CreateDateColumn, ForeignKeyColumn, Table, UpdateDateColumn } from 'src/sql-tools';
import {
AfterDeleteTrigger,
CreateDateColumn,
ForeignKeyColumn,
Generated,
Table,
Timestamp,
UpdateDateColumn,
} from 'src/sql-tools';
@Table({ name: 'albums_assets_assets', primaryConstraintName: 'PK_c67bc36fa845fb7b18e0e398180' })
@UpdatedAtTrigger('album_assets_updated_at')
@ -21,11 +29,11 @@ export class AlbumAssetTable {
assetsId!: string;
@CreateDateColumn()
createdAt!: Date;
createdAt!: Generated<Timestamp>;
@UpdateDateColumn()
updatedAt!: Date;
updatedAt!: Generated<Timestamp>;
@UpdateIdColumn({ indexName: 'IDX_album_assets_update_id' })
updateId!: string;
updateId!: Generated<string>;
}

View File

@ -1,10 +1,10 @@
import { PrimaryGeneratedUuidV7Column } from 'src/decorators';
import { Column, CreateDateColumn, Table } from 'src/sql-tools';
import { Column, CreateDateColumn, Generated, Table, Timestamp } from 'src/sql-tools';
@Table('albums_audit')
export class AlbumAuditTable {
@PrimaryGeneratedUuidV7Column()
id!: string;
id!: Generated<string>;
@Column({ type: 'uuid', indexName: 'IDX_albums_audit_album_id' })
albumId!: string;
@ -13,5 +13,5 @@ export class AlbumAuditTable {
userId!: string;
@CreateDateColumn({ default: () => 'clock_timestamp()', indexName: 'IDX_albums_audit_deleted_at' })
deletedAt!: Date;
deletedAt!: Generated<Timestamp>;
}

View File

@ -1,10 +1,10 @@
import { PrimaryGeneratedUuidV7Column } from 'src/decorators';
import { Column, CreateDateColumn, Table } from 'src/sql-tools';
import { Column, CreateDateColumn, Generated, Table, Timestamp } from 'src/sql-tools';
@Table('album_users_audit')
export class AlbumUserAuditTable {
@PrimaryGeneratedUuidV7Column()
id!: string;
id!: Generated<string>;
@Column({ type: 'uuid', indexName: 'IDX_album_users_audit_album_id' })
albumId!: string;
@ -13,5 +13,5 @@ export class AlbumUserAuditTable {
userId!: string;
@CreateDateColumn({ default: () => 'clock_timestamp()', indexName: 'IDX_album_users_audit_deleted_at' })
deletedAt!: Date;
deletedAt!: Generated<Timestamp>;
}

View File

@ -9,8 +9,10 @@ import {
Column,
CreateDateColumn,
ForeignKeyColumn,
Generated,
Index,
Table,
Timestamp,
UpdateDateColumn,
} from 'src/sql-tools';
@ -50,17 +52,17 @@ export class AlbumUserTable {
usersId!: string;
@Column({ type: 'character varying', default: AlbumUserRole.EDITOR })
role!: AlbumUserRole;
role!: Generated<AlbumUserRole>;
@CreateIdColumn({ indexName: 'IDX_album_users_create_id' })
createId?: string;
createId!: Generated<string>;
@CreateDateColumn()
createdAt!: Date;
createdAt!: Generated<Timestamp>;
@UpdateIdColumn({ indexName: 'IDX_album_users_update_id' })
updateId?: string;
updateId!: Generated<string>;
@UpdateDateColumn()
updatedAt!: Date;
updatedAt!: Generated<Timestamp>;
}

View File

@ -9,8 +9,10 @@ import {
CreateDateColumn,
DeleteDateColumn,
ForeignKeyColumn,
Generated,
PrimaryGeneratedColumn,
Table,
Timestamp,
UpdateDateColumn,
} from 'src/sql-tools';
@ -25,16 +27,16 @@ import {
})
export class AlbumTable {
@PrimaryGeneratedColumn()
id!: string;
id!: Generated<string>;
@ForeignKeyColumn(() => UserTable, { onDelete: 'CASCADE', onUpdate: 'CASCADE', nullable: false })
ownerId!: string;
@Column({ default: 'Untitled Album' })
albumName!: string;
albumName!: Generated<string>;
@CreateDateColumn()
createdAt!: Date;
createdAt!: Generated<Timestamp>;
@ForeignKeyColumn(() => AssetTable, {
nullable: true,
@ -42,23 +44,23 @@ export class AlbumTable {
onUpdate: 'CASCADE',
comment: 'Asset ID to be used as thumbnail',
})
albumThumbnailAssetId!: string;
albumThumbnailAssetId!: string | null;
@UpdateDateColumn()
updatedAt!: Date;
updatedAt!: Generated<Timestamp>;
@Column({ type: 'text', default: '' })
description!: string;
description!: Generated<string>;
@DeleteDateColumn()
deletedAt!: Date | null;
deletedAt!: Timestamp | null;
@Column({ type: 'boolean', default: true })
isActivityEnabled!: boolean;
isActivityEnabled!: Generated<boolean>;
@Column({ default: AssetOrder.DESC })
order!: AssetOrder;
order!: Generated<AssetOrder>;
@UpdateIdColumn({ indexName: 'IDX_albums_update_id' })
updateId?: string;
updateId!: Generated<string>;
}

View File

@ -5,14 +5,19 @@ import {
Column,
CreateDateColumn,
ForeignKeyColumn,
Generated,
PrimaryGeneratedColumn,
Table,
Timestamp,
UpdateDateColumn,
} from 'src/sql-tools';
@Table('api_keys')
@UpdatedAtTrigger('api_keys_updated_at')
export class APIKeyTable {
export class ApiKeyTable {
@PrimaryGeneratedColumn()
id!: Generated<string>;
@Column()
name!: string;
@ -23,17 +28,14 @@ export class APIKeyTable {
userId!: string;
@CreateDateColumn()
createdAt!: Date;
createdAt!: Generated<Timestamp>;
@UpdateDateColumn()
updatedAt!: Date;
@PrimaryGeneratedColumn()
id!: string;
updatedAt!: Generated<Timestamp>;
@Column({ array: true, type: 'character varying' })
permissions!: Permission[];
@UpdateIdColumn({ indexName: 'IDX_api_keys_update_id' })
updateId?: string;
updateId!: Generated<string>;
}

View File

@ -1,10 +1,10 @@
import { PrimaryGeneratedUuidV7Column } from 'src/decorators';
import { Column, CreateDateColumn, Table } from 'src/sql-tools';
import { Column, CreateDateColumn, Generated, Table, Timestamp } from 'src/sql-tools';
@Table('assets_audit')
export class AssetAuditTable {
@PrimaryGeneratedUuidV7Column()
id!: string;
id!: Generated<string>;
@Column({ type: 'uuid', indexName: 'IDX_assets_audit_asset_id' })
assetId!: string;
@ -13,5 +13,5 @@ export class AssetAuditTable {
ownerId!: string;
@CreateDateColumn({ default: () => 'clock_timestamp()', indexName: 'IDX_assets_audit_deleted_at' })
deletedAt!: Date;
deletedAt!: Generated<Timestamp>;
}

View File

@ -2,12 +2,24 @@ import { SourceType } from 'src/enum';
import { asset_face_source_type } from 'src/schema/enums';
import { AssetTable } from 'src/schema/tables/asset.table';
import { PersonTable } from 'src/schema/tables/person.table';
import { Column, DeleteDateColumn, ForeignKeyColumn, Index, PrimaryGeneratedColumn, Table } from 'src/sql-tools';
import {
Column,
DeleteDateColumn,
ForeignKeyColumn,
Generated,
Index,
PrimaryGeneratedColumn,
Table,
Timestamp,
} from 'src/sql-tools';
@Table({ name: 'asset_faces' })
@Index({ name: 'IDX_asset_faces_assetId_personId', columns: ['assetId', 'personId'] })
@Index({ columns: ['personId', 'assetId'] })
export class AssetFaceTable {
@PrimaryGeneratedColumn()
id!: Generated<string>;
@ForeignKeyColumn(() => AssetTable, {
onDelete: 'CASCADE',
onUpdate: 'CASCADE',
@ -26,29 +38,26 @@ export class AssetFaceTable {
personId!: string | null;
@Column({ default: 0, type: 'integer' })
imageWidth!: number;
imageWidth!: Generated<number>;
@Column({ default: 0, type: 'integer' })
imageHeight!: number;
imageHeight!: Generated<number>;
@Column({ default: 0, type: 'integer' })
boundingBoxX1!: number;
boundingBoxX1!: Generated<number>;
@Column({ default: 0, type: 'integer' })
boundingBoxY1!: number;
boundingBoxY1!: Generated<number>;
@Column({ default: 0, type: 'integer' })
boundingBoxX2!: number;
boundingBoxX2!: Generated<number>;
@Column({ default: 0, type: 'integer' })
boundingBoxY2!: number;
@PrimaryGeneratedColumn()
id!: string;
boundingBoxY2!: Generated<number>;
@Column({ default: SourceType.MACHINE_LEARNING, enum: asset_face_source_type })
sourceType!: SourceType;
sourceType!: Generated<SourceType>;
@DeleteDateColumn()
deletedAt!: Date | null;
deletedAt!: Timestamp | null;
}

View File

@ -5,8 +5,10 @@ import {
Column,
CreateDateColumn,
ForeignKeyColumn,
Generated,
PrimaryGeneratedColumn,
Table,
Timestamp,
Unique,
UpdateDateColumn,
} from 'src/sql-tools';
@ -16,20 +18,20 @@ import {
@UpdatedAtTrigger('asset_files_updated_at')
export class AssetFileTable {
@PrimaryGeneratedColumn()
id!: string;
id!: Generated<string>;
@ForeignKeyColumn(() => AssetTable, {
onDelete: 'CASCADE',
onUpdate: 'CASCADE',
indexName: 'IDX_asset_files_assetId',
})
assetId?: string;
assetId!: string;
@CreateDateColumn()
createdAt!: Date;
createdAt!: Generated<Timestamp>;
@UpdateDateColumn()
updatedAt!: Date;
updatedAt!: Generated<Timestamp>;
@Column()
type!: AssetFileType;
@ -38,5 +40,5 @@ export class AssetFileTable {
path!: string;
@UpdateIdColumn({ indexName: 'IDX_asset_files_update_id' })
updateId?: string;
updateId!: Generated<string>;
}

View File

@ -1,5 +1,5 @@
import { AssetTable } from 'src/schema/tables/asset.table';
import { Column, ForeignKeyColumn, Table } from 'src/sql-tools';
import { Column, ForeignKeyColumn, Table, Timestamp } from 'src/sql-tools';
@Table('asset_job_status')
export class AssetJobStatusTable {
@ -7,17 +7,17 @@ export class AssetJobStatusTable {
assetId!: string;
@Column({ type: 'timestamp with time zone', nullable: true })
facesRecognizedAt!: Date | null;
facesRecognizedAt!: Timestamp | null;
@Column({ type: 'timestamp with time zone', nullable: true })
metadataExtractedAt!: Date | null;
metadataExtractedAt!: Timestamp | null;
@Column({ type: 'timestamp with time zone', nullable: true })
duplicatesDetectedAt!: Date | null;
duplicatesDetectedAt!: Timestamp | null;
@Column({ type: 'timestamp with time zone', nullable: true })
previewAt!: Date | null;
previewAt!: Timestamp | null;
@Column({ type: 'timestamp with time zone', nullable: true })
thumbnailAt!: Date | null;
thumbnailAt!: Timestamp | null;
}

View File

@ -11,9 +11,11 @@ import {
CreateDateColumn,
DeleteDateColumn,
ForeignKeyColumn,
Generated,
Index,
PrimaryGeneratedColumn,
Table,
Timestamp,
UpdateDateColumn,
} from 'src/sql-tools';
import { ASSET_CHECKSUM_CONSTRAINT } from 'src/utils/database';
@ -60,7 +62,7 @@ import { ASSET_CHECKSUM_CONSTRAINT } from 'src/utils/database';
// For all assets, each originalpath must be unique per user and library
export class AssetTable {
@PrimaryGeneratedColumn()
id!: string;
id!: Generated<string>;
@Column()
deviceAssetId!: string;
@ -78,13 +80,13 @@ export class AssetTable {
originalPath!: string;
@Column({ type: 'timestamp with time zone', indexName: 'idx_asset_file_created_at' })
fileCreatedAt!: Date;
fileCreatedAt!: Timestamp;
@Column({ type: 'timestamp with time zone' })
fileModifiedAt!: Date;
fileModifiedAt!: Timestamp;
@Column({ type: 'boolean', default: false })
isFavorite!: boolean;
isFavorite!: Generated<boolean>;
@Column({ type: 'character varying', nullable: true })
duration!: string | null;
@ -99,10 +101,10 @@ export class AssetTable {
livePhotoVideoId!: string | null;
@UpdateDateColumn()
updatedAt!: Date;
updatedAt!: Generated<Timestamp>;
@CreateDateColumn()
createdAt!: Date;
createdAt!: Generated<Timestamp>;
@Column({ index: true })
originalFileName!: string;
@ -114,32 +116,32 @@ export class AssetTable {
thumbhash!: Buffer | null;
@Column({ type: 'boolean', default: false })
isOffline!: boolean;
isOffline!: Generated<boolean>;
@ForeignKeyColumn(() => LibraryTable, { onDelete: 'CASCADE', onUpdate: 'CASCADE', nullable: true })
libraryId?: string | null;
libraryId!: string | null;
@Column({ type: 'boolean', default: false })
isExternal!: boolean;
isExternal!: Generated<boolean>;
@DeleteDateColumn()
deletedAt!: Date | null;
deletedAt!: Timestamp | null;
@Column({ type: 'timestamp with time zone' })
localDateTime!: Date;
localDateTime!: Timestamp;
@ForeignKeyColumn(() => StackTable, { nullable: true, onDelete: 'SET NULL', onUpdate: 'CASCADE' })
stackId?: string | null;
stackId!: string | null;
@Column({ type: 'uuid', nullable: true, indexName: 'IDX_assets_duplicateId' })
duplicateId!: string | null;
@Column({ enum: assets_status_enum, default: AssetStatus.ACTIVE })
status!: AssetStatus;
status!: Generated<AssetStatus>;
@UpdateIdColumn({ indexName: 'IDX_assets_update_id' })
updateId?: string;
updateId!: Generated<string>;
@Column({ enum: asset_visibility_enum, default: AssetVisibility.TIMELINE })
visibility!: AssetVisibility;
visibility!: Generated<AssetVisibility>;
}

View File

@ -1,11 +1,11 @@
import { DatabaseAction, EntityType } from 'src/enum';
import { Column, CreateDateColumn, Index, PrimaryColumn, Table } from 'src/sql-tools';
import { Column, CreateDateColumn, Generated, Index, PrimaryColumn, Table, Timestamp } from 'src/sql-tools';
@Table('audit')
@Index({ name: 'IDX_ownerId_createdAt', columns: ['ownerId', 'createdAt'] })
export class AuditTable {
@PrimaryColumn({ type: 'serial', synchronize: false })
id!: number;
id!: Generated<number>;
@Column()
entityType!: EntityType;
@ -20,5 +20,5 @@ export class AuditTable {
ownerId!: string;
@CreateDateColumn()
createdAt!: Date;
createdAt!: Generated<Timestamp>;
}

View File

@ -1,6 +1,6 @@
import { UpdatedAtTrigger, UpdateIdColumn } from 'src/decorators';
import { AssetTable } from 'src/schema/tables/asset.table';
import { Column, ForeignKeyColumn, Table, UpdateDateColumn } from 'src/sql-tools';
import { Column, ForeignKeyColumn, Generated, Int8, Table, Timestamp, UpdateDateColumn } from 'src/sql-tools';
@Table('exif')
@UpdatedAtTrigger('asset_exif_updated_at')
@ -21,16 +21,16 @@ export class ExifTable {
exifImageHeight!: number | null;
@Column({ type: 'bigint', nullable: true })
fileSizeInByte!: number | null;
fileSizeInByte!: Int8 | null;
@Column({ type: 'character varying', nullable: true })
orientation!: string | null;
@Column({ type: 'timestamp with time zone', nullable: true })
dateTimeOriginal!: Date | null;
dateTimeOriginal!: Timestamp | null;
@Column({ type: 'timestamp with time zone', nullable: true })
modifyDate!: Date | null;
modifyDate!: Timestamp | null;
@Column({ type: 'character varying', nullable: true })
lensModel!: string | null;
@ -60,10 +60,10 @@ export class ExifTable {
country!: string | null;
@Column({ type: 'text', default: '' })
description!: string; // or caption
description!: Generated<string>; // or caption
@Column({ type: 'double precision', nullable: true })
fps?: number | null;
fps!: number | null;
@Column({ type: 'character varying', nullable: true })
exposureTime!: string | null;
@ -93,8 +93,8 @@ export class ExifTable {
rating!: number | null;
@UpdateDateColumn({ default: () => 'clock_timestamp()' })
updatedAt?: Date;
updatedAt!: Generated<Date>;
@UpdateIdColumn({ indexName: 'IDX_asset_exif_update_id' })
updateId?: string;
updateId!: Generated<string>;
}

View File

@ -1,4 +1,4 @@
import { Column, Index, PrimaryColumn, Table } from 'src/sql-tools';
import { Column, Index, PrimaryColumn, Table, Timestamp } from 'src/sql-tools';
@Table({ name: 'geodata_places', synchronize: false })
@Index({
@ -47,20 +47,20 @@ export class GeodataPlacesTable {
countryCode!: string;
@Column({ type: 'character varying', length: 20, nullable: true })
admin1Code!: string;
admin1Code!: string | null;
@Column({ type: 'character varying', length: 80, nullable: true })
admin2Code!: string;
admin2Code!: string | null;
@Column({ type: 'date' })
modificationDate!: Date;
modificationDate!: Timestamp;
@Column({ type: 'character varying', nullable: true })
admin1Name!: string;
admin1Name!: string | null;
@Column({ type: 'character varying', nullable: true })
admin2Name!: string;
admin2Name!: string | null;
@Column({ type: 'character varying', nullable: true })
alternateNames!: string;
alternateNames!: string | null;
}

View File

@ -5,8 +5,10 @@ import {
CreateDateColumn,
DeleteDateColumn,
ForeignKeyColumn,
Generated,
PrimaryGeneratedColumn,
Table,
Timestamp,
UpdateDateColumn,
} from 'src/sql-tools';
@ -14,7 +16,7 @@ import {
@UpdatedAtTrigger('libraries_updated_at')
export class LibraryTable {
@PrimaryGeneratedColumn()
id!: string;
id!: Generated<string>;
@Column()
name!: string;
@ -29,17 +31,17 @@ export class LibraryTable {
exclusionPatterns!: string[];
@CreateDateColumn()
createdAt!: Date;
createdAt!: Generated<Timestamp>;
@UpdateDateColumn()
updatedAt!: Date;
updatedAt!: Generated<Date>;
@DeleteDateColumn()
deletedAt?: Date;
deletedAt!: Timestamp | null;
@Column({ type: 'timestamp with time zone', nullable: true })
refreshedAt!: Date | null;
refreshedAt!: Timestamp | null;
@UpdateIdColumn({ indexName: 'IDX_libraries_update_id' })
updateId?: string;
updateId!: Generated<string>;
}

View File

@ -1,13 +1,16 @@
import { ColumnType } from 'kysely';
import { UpdatedAtTrigger, UpdateIdColumn } from 'src/decorators';
import { memory_assets_delete_audit } from 'src/schema/functions';
import { AssetTable } from 'src/schema/tables/asset.table';
import { MemoryTable } from 'src/schema/tables/memory.table';
import { AfterDeleteTrigger, CreateDateColumn, ForeignKeyColumn, Table, UpdateDateColumn } from 'src/sql-tools';
type Timestamp = ColumnType<Date, Date | string, Date | string>;
type Generated<T> =
T extends ColumnType<infer S, infer I, infer U> ? ColumnType<S, I | undefined, U> : ColumnType<T, T | undefined, T>;
import {
AfterDeleteTrigger,
CreateDateColumn,
ForeignKeyColumn,
Generated,
Table,
Timestamp,
UpdateDateColumn,
} from 'src/sql-tools';
@Table('memories_assets_assets')
@UpdatedAtTrigger('memory_assets_updated_at')

View File

@ -1,10 +1,5 @@
import { ColumnType } from 'kysely';
import { PrimaryGeneratedUuidV7Column } from 'src/decorators';
import { Column, CreateDateColumn, Table } from 'src/sql-tools';
type Timestamp = ColumnType<Date, Date | string, Date | string>;
type Generated<T> =
T extends ColumnType<infer S, infer I, infer U> ? ColumnType<S, I | undefined, U> : ColumnType<T, T | undefined, T>;
import { Column, CreateDateColumn, Generated, Table, Timestamp } from 'src/sql-tools';
@Table('memories_audit')
export class MemoryAuditTable {
@ -18,5 +13,5 @@ export class MemoryAuditTable {
userId!: string;
@CreateDateColumn({ default: () => 'clock_timestamp()', indexName: 'IDX_memories_audit_deleted_at' })
deletedAt!: Timestamp;
deletedAt!: Generated<Timestamp>;
}

View File

@ -8,8 +8,10 @@ import {
CreateDateColumn,
DeleteDateColumn,
ForeignKeyColumn,
Generated,
PrimaryGeneratedColumn,
Table,
Timestamp,
UpdateDateColumn,
} from 'src/sql-tools';
@ -24,16 +26,16 @@ import {
})
export class MemoryTable {
@PrimaryGeneratedColumn()
id!: string;
id!: Generated<string>;
@CreateDateColumn()
createdAt!: Date;
createdAt!: Generated<Timestamp>;
@UpdateDateColumn()
updatedAt!: Date;
updatedAt!: Generated<Timestamp>;
@DeleteDateColumn()
deletedAt?: Date;
deletedAt!: Timestamp | null;
@ForeignKeyColumn(() => UserTable, { onDelete: 'CASCADE', onUpdate: 'CASCADE', nullable: false })
ownerId!: string;
@ -46,22 +48,22 @@ export class MemoryTable {
/** unless set to true, will be automatically deleted in the future */
@Column({ type: 'boolean', default: false })
isSaved!: boolean;
isSaved!: Generated<boolean>;
/** memories are sorted in ascending order by this value */
@Column({ type: 'timestamp with time zone' })
memoryAt!: Date;
memoryAt!: Timestamp;
/** when the user last viewed the memory */
@Column({ type: 'timestamp with time zone', nullable: true })
seenAt?: Date;
seenAt!: Timestamp | null;
@Column({ type: 'timestamp with time zone', nullable: true })
showAt?: Date;
showAt!: Timestamp | null;
@Column({ type: 'timestamp with time zone', nullable: true })
hideAt?: Date;
hideAt!: Timestamp | null;
@UpdateIdColumn({ indexName: 'IDX_memories_update_id' })
updateId?: string;
updateId!: Generated<string>;
}

View File

@ -1,5 +1,5 @@
import { PathType } from 'src/enum';
import { Column, PrimaryGeneratedColumn, Table, Unique } from 'src/sql-tools';
import { Column, Generated, PrimaryGeneratedColumn, Table, Unique } from 'src/sql-tools';
@Table('move_history')
// path lock (per entity)
@ -8,7 +8,7 @@ import { Column, PrimaryGeneratedColumn, Table, Unique } from 'src/sql-tools';
@Unique({ name: 'UQ_newPath', columns: ['newPath'] })
export class MoveTable {
@PrimaryGeneratedColumn()
id!: string;
id!: Generated<string>;
@Column({ type: 'uuid' })
entityId!: string;

View File

@ -1,9 +1,9 @@
import { Column, PrimaryGeneratedColumn, Table } from 'src/sql-tools';
import { Column, Generated, PrimaryGeneratedColumn, Table } from 'src/sql-tools';
@Table({ name: 'naturalearth_countries', primaryConstraintName: 'naturalearth_countries_pkey' })
export class NaturalEarthCountriesTable {
@PrimaryGeneratedColumn({ strategy: 'identity' })
id!: number;
id!: Generated<number>;
@Column({ type: 'character varying', length: 50 })
admin!: string;

View File

@ -6,8 +6,10 @@ import {
CreateDateColumn,
DeleteDateColumn,
ForeignKeyColumn,
Generated,
PrimaryGeneratedColumn,
Table,
Timestamp,
UpdateDateColumn,
} from 'src/sql-tools';
@ -15,28 +17,28 @@ import {
@UpdatedAtTrigger('notifications_updated_at')
export class NotificationTable {
@PrimaryGeneratedColumn()
id!: string;
id!: Generated<string>;
@CreateDateColumn()
createdAt!: Date;
createdAt!: Generated<Timestamp>;
@UpdateDateColumn()
updatedAt!: Date;
updatedAt!: Generated<Timestamp>;
@DeleteDateColumn()
deletedAt?: Date;
deletedAt!: Timestamp | null;
@UpdateIdColumn({ indexName: 'IDX_notifications_update_id' })
updateId?: string;
updateId!: Generated<string>;
@ForeignKeyColumn(() => UserTable, { onDelete: 'CASCADE', onUpdate: 'CASCADE', nullable: true })
userId!: string;
@Column({ default: NotificationLevel.Info })
level!: NotificationLevel;
level!: Generated<NotificationLevel>;
@Column({ default: NotificationLevel.Info })
type!: NotificationType;
type!: Generated<NotificationType>;
@Column({ type: 'jsonb', nullable: true })
data!: any | null;
@ -45,8 +47,8 @@ export class NotificationTable {
title!: string;
@Column({ type: 'text', nullable: true })
description!: string;
description!: string | null;
@Column({ type: 'timestamp with time zone', nullable: true })
readAt?: Date | null;
readAt!: Timestamp | null;
}

View File

@ -1,10 +1,10 @@
import { PrimaryGeneratedUuidV7Column } from 'src/decorators';
import { Column, CreateDateColumn, Table } from 'src/sql-tools';
import { Column, CreateDateColumn, Generated, Table, Timestamp } from 'src/sql-tools';
@Table('partners_audit')
export class PartnerAuditTable {
@PrimaryGeneratedUuidV7Column()
id!: string;
id!: Generated<string>;
@Column({ type: 'uuid', indexName: 'IDX_partners_audit_shared_by_id' })
sharedById!: string;
@ -13,5 +13,5 @@ export class PartnerAuditTable {
sharedWithId!: string;
@CreateDateColumn({ default: () => 'clock_timestamp()', indexName: 'IDX_partners_audit_deleted_at' })
deletedAt!: Date;
deletedAt!: Generated<Timestamp>;
}

View File

@ -1,7 +1,16 @@
import { CreateIdColumn, UpdatedAtTrigger, UpdateIdColumn } from 'src/decorators';
import { partners_delete_audit } from 'src/schema/functions';
import { UserTable } from 'src/schema/tables/user.table';
import { AfterDeleteTrigger, Column, CreateDateColumn, ForeignKeyColumn, Table, UpdateDateColumn } from 'src/sql-tools';
import {
AfterDeleteTrigger,
Column,
CreateDateColumn,
ForeignKeyColumn,
Generated,
Table,
Timestamp,
UpdateDateColumn,
} from 'src/sql-tools';
@Table('partners')
@UpdatedAtTrigger('partners_updated_at')
@ -25,17 +34,17 @@ export class PartnerTable {
sharedWithId!: string;
@CreateDateColumn()
createdAt!: Date;
createdAt!: Generated<Timestamp>;
@CreateIdColumn({ indexName: 'IDX_partners_create_id' })
createId!: string;
createId!: Generated<string>;
@UpdateDateColumn()
updatedAt!: Date;
updatedAt!: Generated<Timestamp>;
@Column({ type: 'boolean', default: false })
inTimeline!: boolean;
inTimeline!: Generated<boolean>;
@UpdateIdColumn({ indexName: 'IDX_partners_update_id' })
updateId!: string;
updateId!: Generated<string>;
}

View File

@ -6,8 +6,10 @@ import {
Column,
CreateDateColumn,
ForeignKeyColumn,
Generated,
PrimaryGeneratedColumn,
Table,
Timestamp,
UpdateDateColumn,
} from 'src/sql-tools';
@ -16,38 +18,38 @@ import {
@Check({ name: 'CHK_b0f82b0ed662bfc24fbb58bb45', expression: `"birthDate" <= CURRENT_DATE` })
export class PersonTable {
@PrimaryGeneratedColumn('uuid')
id!: string;
id!: Generated<string>;
@CreateDateColumn()
createdAt!: Date;
createdAt!: Generated<Timestamp>;
@UpdateDateColumn()
updatedAt!: Date;
updatedAt!: Generated<Timestamp>;
@ForeignKeyColumn(() => UserTable, { onDelete: 'CASCADE', onUpdate: 'CASCADE', nullable: false })
ownerId!: string;
@Column({ default: '' })
name!: string;
name!: Generated<string>;
@Column({ default: '' })
thumbnailPath!: string;
thumbnailPath!: Generated<string>;
@Column({ type: 'boolean', default: false })
isHidden!: boolean;
isHidden!: Generated<boolean>;
@Column({ type: 'date', nullable: true })
birthDate!: Date | string | null;
birthDate!: Timestamp | null;
@ForeignKeyColumn(() => AssetFaceTable, { onDelete: 'SET NULL', nullable: true })
faceAssetId!: string | null;
@Column({ type: 'boolean', default: false })
isFavorite!: boolean;
isFavorite!: Generated<boolean>;
@Column({ type: 'character varying', nullable: true, default: null })
color?: string | null;
color!: string | null;
@UpdateIdColumn({ indexName: 'IDX_person_update_id' })
updateId!: string;
updateId!: Generated<string>;
}

View File

@ -4,8 +4,10 @@ import {
Column,
CreateDateColumn,
ForeignKeyColumn,
Generated,
PrimaryGeneratedColumn,
Table,
Timestamp,
UpdateDateColumn,
} from 'src/sql-tools';
@ -13,20 +15,20 @@ import {
@UpdatedAtTrigger('sessions_updated_at')
export class SessionTable {
@PrimaryGeneratedColumn()
id!: string;
id!: Generated<string>;
// TODO convert to byte[]
@Column()
token!: string;
@CreateDateColumn()
createdAt!: Date;
createdAt!: Generated<Timestamp>;
@UpdateDateColumn()
updatedAt!: Date;
updatedAt!: Generated<Timestamp>;
@Column({ type: 'timestamp with time zone', nullable: true })
expiresAt!: Date | null;
expiresAt!: Timestamp | null;
@ForeignKeyColumn(() => UserTable, { onUpdate: 'CASCADE', onDelete: 'CASCADE' })
userId!: string;
@ -35,14 +37,14 @@ export class SessionTable {
parentId!: string | null;
@Column({ default: '' })
deviceType!: string;
deviceType!: Generated<string>;
@Column({ default: '' })
deviceOS!: string;
deviceOS!: Generated<string>;
@UpdateIdColumn({ indexName: 'IDX_sessions_update_id' })
updateId!: string;
updateId!: Generated<string>;
@Column({ type: 'timestamp with time zone', nullable: true })
pinExpiresAt!: Date | null;
pinExpiresAt!: Timestamp | null;
}

View File

@ -1,13 +1,22 @@
import { SharedLinkType } from 'src/enum';
import { AlbumTable } from 'src/schema/tables/album.table';
import { UserTable } from 'src/schema/tables/user.table';
import { Column, CreateDateColumn, ForeignKeyColumn, PrimaryGeneratedColumn, Table, Unique } from 'src/sql-tools';
import {
Column,
CreateDateColumn,
ForeignKeyColumn,
Generated,
PrimaryGeneratedColumn,
Table,
Timestamp,
Unique,
} from 'src/sql-tools';
@Table('shared_links')
@Unique({ name: 'UQ_sharedlink_key', columns: ['key'] })
export class SharedLinkTable {
@PrimaryGeneratedColumn()
id!: string;
id!: Generated<string>;
@Column({ type: 'character varying', nullable: true })
description!: string | null;
@ -22,10 +31,10 @@ export class SharedLinkTable {
type!: SharedLinkType;
@CreateDateColumn()
createdAt!: Date;
createdAt!: Generated<Timestamp>;
@Column({ type: 'timestamp with time zone', nullable: true })
expiresAt!: Date | null;
expiresAt!: Timestamp | null;
@Column({ type: 'boolean', default: false })
allowUpload!: boolean;
@ -36,13 +45,13 @@ export class SharedLinkTable {
onUpdate: 'CASCADE',
indexName: 'IDX_sharedlink_albumId',
})
albumId!: string;
albumId!: string | null;
@Column({ type: 'boolean', default: true })
allowDownload!: boolean;
allowDownload!: Generated<boolean>;
@Column({ type: 'boolean', default: true })
showExif!: boolean;
showExif!: Generated<boolean>;
@Column({ type: 'character varying', nullable: true })
password!: string | null;

View File

@ -1,11 +1,11 @@
import { AssetTable } from 'src/schema/tables/asset.table';
import { UserTable } from 'src/schema/tables/user.table';
import { ForeignKeyColumn, PrimaryGeneratedColumn, Table } from 'src/sql-tools';
import { ForeignKeyColumn, Generated, PrimaryGeneratedColumn, Table } from 'src/sql-tools';
@Table('asset_stack')
export class StackTable {
@PrimaryGeneratedColumn()
id!: string;
id!: Generated<string>;
//TODO: Add constraint to ensure primary asset exists in the assets array
@ForeignKeyColumn(() => AssetTable, { nullable: false, unique: true })

View File

@ -1,7 +1,16 @@
import { UpdatedAtTrigger, UpdateIdColumn } from 'src/decorators';
import { SyncEntityType } from 'src/enum';
import { SessionTable } from 'src/schema/tables/session.table';
import { Column, CreateDateColumn, ForeignKeyColumn, PrimaryColumn, Table, UpdateDateColumn } from 'src/sql-tools';
import {
Column,
CreateDateColumn,
ForeignKeyColumn,
Generated,
PrimaryColumn,
Table,
Timestamp,
UpdateDateColumn,
} from 'src/sql-tools';
@Table('session_sync_checkpoints')
@UpdatedAtTrigger('session_sync_checkpoints_updated_at')
@ -13,14 +22,14 @@ export class SessionSyncCheckpointTable {
type!: SyncEntityType;
@CreateDateColumn()
createdAt!: Date;
createdAt!: Generated<Timestamp>;
@UpdateDateColumn()
updatedAt!: Date;
updatedAt!: Generated<Timestamp>;
@Column()
ack!: string;
@UpdateIdColumn({ indexName: 'IDX_session_sync_checkpoints_update_id' })
updateId!: string;
updateId!: Generated<string>;
}

View File

@ -4,8 +4,10 @@ import {
Column,
CreateDateColumn,
ForeignKeyColumn,
Generated,
PrimaryGeneratedColumn,
Table,
Timestamp,
Unique,
UpdateDateColumn,
} from 'src/sql-tools';
@ -15,7 +17,7 @@ import {
@Unique({ columns: ['userId', 'value'] })
export class TagTable {
@PrimaryGeneratedColumn()
id!: string;
id!: Generated<string>;
@ForeignKeyColumn(() => UserTable, {
onUpdate: 'CASCADE',
@ -29,17 +31,17 @@ export class TagTable {
value!: string;
@CreateDateColumn()
createdAt!: Date;
createdAt!: Generated<Timestamp>;
@UpdateDateColumn()
updatedAt!: Date;
updatedAt!: Generated<Timestamp>;
@Column({ type: 'character varying', nullable: true, default: null })
color!: string | null;
@ForeignKeyColumn(() => TagTable, { nullable: true, onDelete: 'CASCADE' })
parentId?: string;
parentId!: string | null;
@UpdateIdColumn({ indexName: 'IDX_tags_update_id' })
updateId!: string;
updateId!: Generated<string>;
}

View File

@ -1,14 +1,14 @@
import { PrimaryGeneratedUuidV7Column } from 'src/decorators';
import { Column, CreateDateColumn, Table } from 'src/sql-tools';
import { Column, CreateDateColumn, Generated, Table, Timestamp } from 'src/sql-tools';
@Table('users_audit')
export class UserAuditTable {
@PrimaryGeneratedUuidV7Column()
id!: Generated<string>;
@Column({ type: 'uuid' })
userId!: string;
@CreateDateColumn({ default: () => 'clock_timestamp()', indexName: 'IDX_users_audit_deleted_at' })
deletedAt!: Date;
@PrimaryGeneratedUuidV7Column()
id!: string;
deletedAt!: Generated<Timestamp>;
}

View File

@ -7,16 +7,14 @@ import {
Column,
CreateDateColumn,
DeleteDateColumn,
Generated,
Index,
PrimaryGeneratedColumn,
Table,
Timestamp,
UpdateDateColumn,
} from 'src/sql-tools';
type Timestamp = ColumnType<Date, Date | string, Date | string>;
type Generated<T> =
T extends ColumnType<infer S, infer I, infer U> ? ColumnType<S, I | undefined, U> : ColumnType<T, T | undefined, T>;
@Table('users')
@UpdatedAtTrigger('users_updated_at')
@AfterDeleteTrigger({

View File

@ -1,12 +1,12 @@
import { Column, CreateDateColumn, PrimaryGeneratedColumn, Table } from 'src/sql-tools';
import { Column, CreateDateColumn, Generated, PrimaryGeneratedColumn, Table, Timestamp } from 'src/sql-tools';
@Table('version_history')
export class VersionHistoryTable {
@PrimaryGeneratedColumn()
id!: string;
id!: Generated<string>;
@CreateDateColumn()
createdAt!: Date;
createdAt!: Generated<Timestamp>;
@Column()
version!: string;

View File

@ -6,7 +6,6 @@ import path, { basename, isAbsolute, parse } from 'node:path';
import picomatch from 'picomatch';
import { JOBS_LIBRARY_PAGINATION_SIZE } from 'src/constants';
import { StorageCore } from 'src/cores/storage.core';
import { Assets } from 'src/db';
import { OnEvent, OnJob } from 'src/decorators';
import {
CreateLibraryDto,
@ -21,6 +20,7 @@ import {
import { AssetStatus, AssetType, DatabaseLock, ImmichWorker, JobName, JobStatus, QueueName } from 'src/enum';
import { ArgOf } from 'src/repositories/event.repository';
import { AssetSyncResult } from 'src/repositories/library.repository';
import { AssetTable } from 'src/schema/tables/asset.table';
import { BaseService } from 'src/services/base.service';
import { JobOf } from 'src/types';
import { mimeTypes } from 'src/utils/mime-types';
@ -237,7 +237,7 @@ export class LibraryService extends BaseService {
return JobStatus.FAILED;
}
const assetImports: Insertable<Assets>[] = [];
const assetImports: Insertable<AssetTable>[] = [];
await Promise.all(
job.paths.map((path) =>
this.processEntity(path, library.ownerId, job.libraryId)

View File

@ -9,7 +9,6 @@ import path from 'node:path';
import { JOBS_ASSET_PAGINATION_SIZE } from 'src/constants';
import { StorageCore } from 'src/cores/storage.core';
import { Asset, AssetFace } from 'src/database';
import { AssetFaces, Exif, Person } from 'src/db';
import { OnEvent, OnJob } from 'src/decorators';
import {
AssetType,
@ -25,6 +24,9 @@ import {
import { ArgOf } from 'src/repositories/event.repository';
import { ReverseGeocodeResult } from 'src/repositories/map.repository';
import { ImmichTags } from 'src/repositories/metadata.repository';
import { AssetFaceTable } from 'src/schema/tables/asset-face.table';
import { ExifTable } from 'src/schema/tables/exif.table';
import { PersonTable } from 'src/schema/tables/person.table';
import { BaseService } from 'src/services/base.service';
import { JobItem, JobOf } from 'src/types';
import { isFaceImportEnabled } from 'src/utils/misc';
@ -162,7 +164,7 @@ export class MetadataService extends BaseService {
private async linkLivePhotos(
asset: { id: string; type: AssetType; ownerId: string; libraryId: string | null },
exifInfo: Insertable<Exif>,
exifInfo: Insertable<ExifTable>,
): Promise<void> {
if (!exifInfo.livePhotoCID) {
return;
@ -240,7 +242,7 @@ export class MetadataService extends BaseService {
}
}
const exifData: Insertable<Exif> = {
const exifData: Insertable<ExifTable> = {
assetId: asset.id,
// dates
@ -710,10 +712,10 @@ export class MetadataService extends BaseService {
return;
}
const facesToAdd: (Insertable<AssetFaces> & { assetId: string })[] = [];
const facesToAdd: (Insertable<AssetFaceTable> & { assetId: string })[] = [];
const existingNames = await this.personRepository.getDistinctNames(asset.ownerId, { withHidden: true });
const existingNameMap = new Map(existingNames.map(({ id, name }) => [name.toLowerCase(), id]));
const missing: (Insertable<Person> & { ownerId: string })[] = [];
const missing: (Insertable<PersonTable> & { ownerId: string })[] = [];
const missingWithFaceAsset: { id: string; ownerId: string; faceAssetId: string }[] = [];
const adjustedRegionInfo = this.orientRegionInfo(tags.RegionInfo, tags.Orientation);

View File

@ -2,7 +2,6 @@ import { BadRequestException, Injectable, NotFoundException } from '@nestjs/comm
import { Insertable, Updateable } from 'kysely';
import { JOBS_ASSET_PAGINATION_SIZE } from 'src/constants';
import { Person } from 'src/database';
import { AssetFaces, FaceSearch } from 'src/db';
import { Chunked, OnJob } from 'src/decorators';
import { BulkIdErrorReason, BulkIdResponseDto, BulkIdsDto } from 'src/dtos/asset-ids.response.dto';
import { AuthDto } from 'src/dtos/auth.dto';
@ -37,6 +36,8 @@ import {
} from 'src/enum';
import { BoundingBox } from 'src/repositories/machine-learning.repository';
import { UpdateFacesData } from 'src/repositories/person.repository';
import { AssetFaceTable } from 'src/schema/tables/asset-face.table';
import { FaceSearchTable } from 'src/schema/tables/face-search.table';
import { BaseService } from 'src/services/base.service';
import { JobItem, JobOf } from 'src/types';
import { ImmichFileResponse } from 'src/utils/file';
@ -317,8 +318,8 @@ export class PersonService extends BaseService {
);
this.logger.debug(`${faces.length} faces detected in ${previewFile.path}`);
const facesToAdd: (Insertable<AssetFaces> & { id: string })[] = [];
const embeddings: FaceSearch[] = [];
const facesToAdd: (Insertable<AssetFaceTable> & { id: string })[] = [];
const embeddings: FaceSearchTable[] = [];
const mlFaceIds = new Set<string>();
for (const face of asset.faces) {

View File

@ -3,7 +3,6 @@ import { Insertable } from 'kysely';
import { DateTime } from 'luxon';
import { Writable } from 'node:stream';
import { AUDIT_LOG_MAX_DURATION } from 'src/constants';
import { SessionSyncCheckpoints } from 'src/db';
import { AssetResponseDto, mapAsset } from 'src/dtos/asset-response.dto';
import { AuthDto } from 'src/dtos/auth.dto';
import {
@ -17,6 +16,7 @@ import {
SyncStreamDto,
} from 'src/dtos/sync.dto';
import { AssetVisibility, DatabaseAction, EntityType, Permission, SyncEntityType, SyncRequestType } from 'src/enum';
import { SessionSyncCheckpointTable } from 'src/schema/tables/sync-checkpoint.table';
import { BaseService } from 'src/services/base.service';
import { SyncAck } from 'src/types';
import { getMyPartnerIds } from 'src/utils/asset.util';
@ -90,7 +90,7 @@ export class SyncService extends BaseService {
return throwSessionRequired();
}
const checkpoints: Record<string, Insertable<SessionSyncCheckpoints>> = {};
const checkpoints: Record<string, Insertable<SessionSyncCheckpointTable>> = {};
for (const ack of dto.acks) {
const { type } = fromAck(ack);
// TODO proper ack validation via class validator

View File

@ -1,6 +1,5 @@
import { BadRequestException, Injectable } from '@nestjs/common';
import { Insertable } from 'kysely';
import { TagAsset } from 'src/db';
import { OnJob } from 'src/decorators';
import { BulkIdResponseDto, BulkIdsDto } from 'src/dtos/asset-ids.response.dto';
import { AuthDto } from 'src/dtos/auth.dto';
@ -14,6 +13,7 @@ import {
mapTag,
} from 'src/dtos/tag.dto';
import { JobName, JobStatus, Permission, QueueName } from 'src/enum';
import { TagAssetTable } from 'src/schema/tables/tag-asset.table';
import { BaseService } from 'src/services/base.service';
import { addAssets, removeAssets } from 'src/utils/asset.util';
import { upsertTags } from 'src/utils/tag';
@ -81,7 +81,7 @@ export class TagService extends BaseService {
this.checkAccess({ auth, permission: Permission.ASSET_UPDATE, ids: dto.assetIds }),
]);
const items: Insertable<TagAsset>[] = [];
const items: Insertable<TagAssetTable>[] = [];
for (const tagsId of tagIds) {
for (const assetsId of assetIds) {
items.push({ tagsId, assetsId });

View File

@ -1,4 +1,4 @@
import { Kysely } from 'kysely';
import { Kysely, ColumnType as KyselyColumnType } from 'kysely';
export type PostgresDB = {
pg_am: {
@ -476,3 +476,10 @@ export enum Reason {
MissingInSource = 'missing in source',
MissingInTarget = 'missing in target',
}
export type Timestamp = KyselyColumnType<Date, Date | string, Date | string>;
export type Generated<T> =
T extends KyselyColumnType<infer S, infer I, infer U>
? KyselyColumnType<S, I | undefined, U>
: KyselyColumnType<T, T | undefined, T>;
export type Int8 = KyselyColumnType<number>;

View File

@ -16,9 +16,9 @@ import { jsonArrayFrom, jsonObjectFrom } from 'kysely/helpers/postgres';
import { parse } from 'pg-connection-string';
import postgres, { Notice } from 'postgres';
import { columns, Exif, Person } from 'src/database';
import { DB } from 'src/db';
import { AssetFileType, AssetVisibility, DatabaseExtension, DatabaseSslMode } from 'src/enum';
import { AssetSearchBuilderOptions } from 'src/repositories/search.repository';
import { DB } from 'src/schema';
import { DatabaseConnectionParams, VectorExtension } from 'src/types';
type Ssl = 'require' | 'allow' | 'prefer' | 'verify-full' | boolean | object;

View File

@ -35,6 +35,7 @@ export const stackStub = (stackId: string, assets: (MapAsset & { exifInfo: Exif
primaryAssetId: assets[0].id,
createdAt: new Date('2023-02-23T05:06:29.716Z'),
updatedAt: new Date('2023-02-23T05:06:29.716Z'),
updateId: 'uuid-v7',
};
};

View File

@ -4,7 +4,6 @@ import { DateTime } from 'luxon';
import { createHash, randomBytes } from 'node:crypto';
import { Writable } from 'node:stream';
import { AssetFace } from 'src/database';
import { Albums, AssetJobStatus, Assets, DB, Exif, FaceSearch, Memories, Person, Sessions } from 'src/db';
import { AuthDto, LoginResponseDto } from 'src/dtos/auth.dto';
import { AlbumUserRole, AssetType, AssetVisibility, MemoryType, SourceType, SyncRequestType } from 'src/enum';
import { AccessRepository } from 'src/repositories/access.repository';
@ -32,6 +31,15 @@ import { SyncRepository } from 'src/repositories/sync.repository';
import { SystemMetadataRepository } from 'src/repositories/system-metadata.repository';
import { UserRepository } from 'src/repositories/user.repository';
import { VersionHistoryRepository } from 'src/repositories/version-history.repository';
import { DB } from 'src/schema';
import { AlbumTable } from 'src/schema/tables/album.table';
import { AssetJobStatusTable } from 'src/schema/tables/asset-job-status.table';
import { AssetTable } from 'src/schema/tables/asset.table';
import { ExifTable } from 'src/schema/tables/exif.table';
import { FaceSearchTable } from 'src/schema/tables/face-search.table';
import { MemoryTable } from 'src/schema/tables/memory.table';
import { PersonTable } from 'src/schema/tables/person.table';
import { SessionTable } from 'src/schema/tables/session.table';
import { UserTable } from 'src/schema/tables/user.table';
import { BASE_SERVICE_DEPENDENCIES, BaseService } from 'src/services/base.service';
import { SyncService } from 'src/services/sync.service';
@ -125,13 +133,13 @@ export class MediumTestContext<S extends BaseService = BaseService> {
return { partner, result };
}
async newAsset(dto: Partial<Insertable<Assets>> = {}) {
async newAsset(dto: Partial<Insertable<AssetTable>> = {}) {
const asset = mediumFactory.assetInsert(dto);
const result = await this.get(AssetRepository).create(asset);
return { asset, result };
}
async newMemory(dto: Partial<Insertable<Memories>> = {}) {
async newMemory(dto: Partial<Insertable<MemoryTable>> = {}) {
const memory = mediumFactory.memoryInsert(dto);
const result = await this.get(MemoryRepository).create(memory, new Set<string>());
return { memory, result };
@ -142,12 +150,12 @@ export class MediumTestContext<S extends BaseService = BaseService> {
return { memoryAsset: dto, result };
}
async newExif(dto: Insertable<Exif>) {
async newExif(dto: Insertable<ExifTable>) {
const result = await this.get(AssetRepository).upsertExif(dto);
return { result };
}
async newAlbum(dto: Insertable<Albums>) {
async newAlbum(dto: Insertable<AlbumTable>) {
const album = mediumFactory.albumInsert(dto);
const result = await this.get(AlbumRepository).create(album, [], []);
return { album, result };
@ -164,19 +172,19 @@ export class MediumTestContext<S extends BaseService = BaseService> {
return { albumUser: { albumId, userId, role }, result };
}
async newJobStatus(dto: Partial<Insertable<AssetJobStatus>> & { assetId: string }) {
async newJobStatus(dto: Partial<Insertable<AssetJobStatusTable>> & { assetId: string }) {
const jobStatus = mediumFactory.assetJobStatusInsert({ assetId: dto.assetId });
const result = await this.get(AssetRepository).upsertJobStatus(jobStatus);
return { jobStatus, result };
}
async newPerson(dto: Partial<Insertable<Person>> & { ownerId: string }) {
async newPerson(dto: Partial<Insertable<PersonTable>> & { ownerId: string }) {
const person = mediumFactory.personInsert(dto);
const result = await this.get(PersonRepository).create(person);
return { person, result };
}
async newSession(dto: Partial<Insertable<Sessions>> & { userId: string }) {
async newSession(dto: Partial<Insertable<SessionTable>> & { userId: string }) {
const session = mediumFactory.sessionInsert(dto);
const result = await this.get(SessionRepository).create(session);
return { session, result };
@ -338,10 +346,10 @@ const newMockRepository = <T>(key: ClassConstructor<T>) => {
}
};
const assetInsert = (asset: Partial<Insertable<Assets>> = {}) => {
const assetInsert = (asset: Partial<Insertable<AssetTable>> = {}) => {
const id = asset.id || newUuid();
const now = newDate();
const defaults: Insertable<Assets> = {
const defaults: Insertable<AssetTable> = {
deviceAssetId: '',
deviceId: '',
originalFileName: '',
@ -363,9 +371,9 @@ const assetInsert = (asset: Partial<Insertable<Assets>> = {}) => {
};
};
const albumInsert = (album: Partial<Insertable<Albums>> & { ownerId: string }) => {
const albumInsert = (album: Partial<Insertable<AlbumTable>> & { ownerId: string }) => {
const id = album.id || newUuid();
const defaults: Omit<Insertable<Albums>, 'ownerId'> = {
const defaults: Omit<Insertable<AlbumTable>, 'ownerId'> = {
albumName: 'Album',
};
@ -376,7 +384,7 @@ const albumInsert = (album: Partial<Insertable<Albums>> & { ownerId: string }) =
};
};
const faceInsert = (face: Partial<Insertable<FaceSearch>> & { faceId: string }) => {
const faceInsert = (face: Partial<Insertable<FaceSearchTable>> & { faceId: string }) => {
const defaults = {
faceId: face.faceId,
embedding: face.embedding || newEmbedding(),
@ -409,10 +417,10 @@ const assetFaceInsert = (assetFace: Partial<AssetFace> & { assetId: string }) =>
};
const assetJobStatusInsert = (
job: Partial<Insertable<AssetJobStatus>> & { assetId: string },
): Insertable<AssetJobStatus> => {
job: Partial<Insertable<AssetJobStatusTable>> & { assetId: string },
): Insertable<AssetJobStatusTable> => {
const date = DateTime.now().minus({ days: 15 }).toISO();
const defaults: Omit<Insertable<AssetJobStatus>, 'assetId'> = {
const defaults: Omit<Insertable<AssetJobStatusTable>, 'assetId'> = {
duplicatesDetectedAt: date,
facesRecognizedAt: date,
metadataExtractedAt: date,
@ -426,7 +434,7 @@ const assetJobStatusInsert = (
};
};
const personInsert = (person: Partial<Insertable<Person>> & { ownerId: string }) => {
const personInsert = (person: Partial<Insertable<PersonTable>> & { ownerId: string }) => {
const defaults = {
birthDate: person.birthDate || null,
color: person.color || null,
@ -449,8 +457,12 @@ const personInsert = (person: Partial<Insertable<Person>> & { ownerId: string })
const sha256 = (value: string) => createHash('sha256').update(value).digest('base64');
const sessionInsert = ({ id = newUuid(), userId, ...session }: Partial<Insertable<Sessions>> & { userId: string }) => {
const defaults: Insertable<Sessions> = {
const sessionInsert = ({
id = newUuid(),
userId,
...session
}: Partial<Insertable<SessionTable>> & { userId: string }) => {
const defaults: Insertable<SessionTable> = {
id,
userId,
token: sha256(id),
@ -478,11 +490,11 @@ const userInsert = (user: Partial<Insertable<UserTable>> = {}) => {
return { ...defaults, ...user, id };
};
const memoryInsert = (memory: Partial<Insertable<Memories>> = {}) => {
const memoryInsert = (memory: Partial<Insertable<MemoryTable>> = {}) => {
const id = memory.id || newUuid();
const date = newDate();
const defaults: Insertable<Memories> = {
const defaults: Insertable<MemoryTable> = {
id,
createdAt: date,
updatedAt: date,

View File

@ -1,8 +1,8 @@
import { Kysely } from 'kysely';
import { DB } from 'src/db';
import { ConfigRepository } from 'src/repositories/config.repository';
import { DatabaseRepository } from 'src/repositories/database.repository';
import { LoggingRepository } from 'src/repositories/logging.repository';
import { DB } from 'src/schema';
import { getKyselyConfig } from 'src/utils/database';
import { GenericContainer, Wait } from 'testcontainers';

View File

@ -1,7 +1,7 @@
import { Kysely } from 'kysely';
import { DB } from 'src/db';
import { AssetRepository } from 'src/repositories/asset.repository';
import { LoggingRepository } from 'src/repositories/logging.repository';
import { DB } from 'src/schema';
import { AssetService } from 'src/services/asset.service';
import { newMediumService } from 'test/medium.factory';
import { factory } from 'test/small.factory';

View File

@ -1,7 +1,6 @@
import { BadRequestException } from '@nestjs/common';
import { hash } from 'bcrypt';
import { Kysely } from 'kysely';
import { DB } from 'src/db';
import { AuthType } from 'src/enum';
import { AccessRepository } from 'src/repositories/access.repository';
import { ConfigRepository } from 'src/repositories/config.repository';
@ -13,6 +12,7 @@ import { SessionRepository } from 'src/repositories/session.repository';
import { StorageRepository } from 'src/repositories/storage.repository';
import { SystemMetadataRepository } from 'src/repositories/system-metadata.repository';
import { UserRepository } from 'src/repositories/user.repository';
import { DB } from 'src/schema';
import { AuthService } from 'src/services/auth.service';
import { mediumFactory, newMediumService } from 'test/medium.factory';
import { factory } from 'test/small.factory';

View File

@ -1,6 +1,5 @@
import { Kysely } from 'kysely';
import { DateTime } from 'luxon';
import { DB } from 'src/db';
import { AssetFileType, MemoryType } from 'src/enum';
import { AccessRepository } from 'src/repositories/access.repository';
import { AssetRepository } from 'src/repositories/asset.repository';
@ -10,6 +9,7 @@ import { MemoryRepository } from 'src/repositories/memory.repository';
import { PartnerRepository } from 'src/repositories/partner.repository';
import { SystemMetadataRepository } from 'src/repositories/system-metadata.repository';
import { UserRepository } from 'src/repositories/user.repository';
import { DB } from 'src/schema';
import { MemoryService } from 'src/services/memory.service';
import { newMediumService } from 'test/medium.factory';
import { factory } from 'test/small.factory';

View File

@ -1,10 +1,10 @@
import { Kysely } from 'kysely';
import { DB } from 'src/db';
import { AccessRepository } from 'src/repositories/access.repository';
import { DatabaseRepository } from 'src/repositories/database.repository';
import { LoggingRepository } from 'src/repositories/logging.repository';
import { PersonRepository } from 'src/repositories/person.repository';
import { StorageRepository } from 'src/repositories/storage.repository';
import { DB } from 'src/schema';
import { PersonService } from 'src/services/person.service';
import { newMediumService } from 'test/medium.factory';
import { factory } from 'test/small.factory';

View File

@ -1,6 +1,5 @@
import { Kysely } from 'kysely';
import { DateTime } from 'luxon';
import { DB } from 'src/db';
import { ImmichEnvironment, JobName, JobStatus } from 'src/enum';
import { ConfigRepository } from 'src/repositories/config.repository';
import { CryptoRepository } from 'src/repositories/crypto.repository';
@ -8,6 +7,7 @@ import { JobRepository } from 'src/repositories/job.repository';
import { LoggingRepository } from 'src/repositories/logging.repository';
import { SystemMetadataRepository } from 'src/repositories/system-metadata.repository';
import { UserRepository } from 'src/repositories/user.repository';
import { DB } from 'src/schema';
import { UserService } from 'src/services/user.service';
import { mediumFactory, newMediumService } from 'test/medium.factory';
import { factory } from 'test/small.factory';

View File

@ -1,11 +1,11 @@
import { Kysely } from 'kysely';
import { serverVersion } from 'src/constants';
import { DB } from 'src/db';
import { JobName } from 'src/enum';
import { DatabaseRepository } from 'src/repositories/database.repository';
import { JobRepository } from 'src/repositories/job.repository';
import { LoggingRepository } from 'src/repositories/logging.repository';
import { VersionHistoryRepository } from 'src/repositories/version-history.repository';
import { DB } from 'src/schema';
import { VersionService } from 'src/services/version.service';
import { newMediumService } from 'test/medium.factory';
import { getKyselyDB } from 'test/utils';

View File

@ -1,6 +1,6 @@
import { Kysely } from 'kysely';
import { DB } from 'src/db';
import { AlbumUserRole, SyncEntityType, SyncRequestType } from 'src/enum';
import { DB } from 'src/schema';
import { SyncTestContext } from 'test/medium.factory';
import { factory } from 'test/small.factory';
import { getKyselyDB, wait } from 'test/utils';

View File

@ -1,6 +1,6 @@
import { Kysely } from 'kysely';
import { DB } from 'src/db';
import { AlbumUserRole, SyncEntityType, SyncRequestType } from 'src/enum';
import { DB } from 'src/schema';
import { SyncTestContext } from 'test/medium.factory';
import { factory } from 'test/small.factory';
import { getKyselyDB, wait } from 'test/utils';

View File

@ -1,8 +1,8 @@
import { Kysely } from 'kysely';
import { DB } from 'src/db';
import { AlbumUserRole, SyncEntityType, SyncRequestType } from 'src/enum';
import { AlbumRepository } from 'src/repositories/album.repository';
import { AssetRepository } from 'src/repositories/asset.repository';
import { DB } from 'src/schema';
import { SyncTestContext } from 'test/medium.factory';
import { getKyselyDB, wait } from 'test/utils';

View File

@ -1,7 +1,7 @@
import { Kysely } from 'kysely';
import { DB } from 'src/db';
import { AlbumUserRole, SyncEntityType, SyncRequestType } from 'src/enum';
import { AlbumUserRepository } from 'src/repositories/album-user.repository';
import { DB } from 'src/schema';
import { SyncTestContext } from 'test/medium.factory';
import { getKyselyDB, wait } from 'test/utils';

View File

@ -1,8 +1,8 @@
import { Kysely } from 'kysely';
import { DB } from 'src/db';
import { AlbumUserRole, SyncEntityType, SyncRequestType } from 'src/enum';
import { AlbumUserRepository } from 'src/repositories/album-user.repository';
import { AlbumRepository } from 'src/repositories/album.repository';
import { DB } from 'src/schema';
import { SyncTestContext } from 'test/medium.factory';
import { getKyselyDB } from 'test/utils';

View File

@ -1,6 +1,6 @@
import { Kysely } from 'kysely';
import { DB } from 'src/db';
import { SyncEntityType, SyncRequestType } from 'src/enum';
import { DB } from 'src/schema';
import { SyncTestContext } from 'test/medium.factory';
import { factory } from 'test/small.factory';
import { getKyselyDB } from 'test/utils';

View File

@ -1,7 +1,7 @@
import { Kysely } from 'kysely';
import { DB } from 'src/db';
import { SyncEntityType, SyncRequestType } from 'src/enum';
import { AssetRepository } from 'src/repositories/asset.repository';
import { DB } from 'src/schema';
import { SyncTestContext } from 'test/medium.factory';
import { factory } from 'test/small.factory';
import { getKyselyDB } from 'test/utils';

View File

@ -1,7 +1,7 @@
import { Kysely } from 'kysely';
import { DB } from 'src/db';
import { SyncEntityType, SyncRequestType } from 'src/enum';
import { MemoryRepository } from 'src/repositories/memory.repository';
import { DB } from 'src/schema';
import { SyncTestContext } from 'test/medium.factory';
import { getKyselyDB } from 'test/utils';

View File

@ -1,7 +1,7 @@
import { Kysely } from 'kysely';
import { DB } from 'src/db';
import { SyncEntityType, SyncRequestType } from 'src/enum';
import { MemoryRepository } from 'src/repositories/memory.repository';
import { DB } from 'src/schema';
import { SyncTestContext } from 'test/medium.factory';
import { getKyselyDB } from 'test/utils';

View File

@ -1,6 +1,6 @@
import { Kysely } from 'kysely';
import { DB } from 'src/db';
import { SyncEntityType, SyncRequestType } from 'src/enum';
import { DB } from 'src/schema';
import { SyncTestContext } from 'test/medium.factory';
import { factory } from 'test/small.factory';
import { getKyselyDB, wait } from 'test/utils';

View File

@ -1,9 +1,9 @@
import { Kysely } from 'kysely';
import { DB } from 'src/db';
import { SyncEntityType, SyncRequestType } from 'src/enum';
import { AssetRepository } from 'src/repositories/asset.repository';
import { PartnerRepository } from 'src/repositories/partner.repository';
import { UserRepository } from 'src/repositories/user.repository';
import { DB } from 'src/schema';
import { SyncTestContext } from 'test/medium.factory';
import { factory } from 'test/small.factory';
import { getKyselyDB, wait } from 'test/utils';

View File

@ -1,8 +1,8 @@
import { Kysely } from 'kysely';
import { DB } from 'src/db';
import { SyncEntityType, SyncRequestType } from 'src/enum';
import { PartnerRepository } from 'src/repositories/partner.repository';
import { UserRepository } from 'src/repositories/user.repository';
import { DB } from 'src/schema';
import { SyncTestContext } from 'test/medium.factory';
import { getKyselyDB } from 'test/utils';

View File

@ -1,7 +1,7 @@
import { Kysely } from 'kysely';
import { DB } from 'src/db';
import { SyncEntityType, SyncRequestType } from 'src/enum';
import { UserRepository } from 'src/repositories/user.repository';
import { DB } from 'src/schema';
import { SyncTestContext } from 'test/medium.factory';
import { getKyselyDB } from 'test/utils';

View File

@ -7,7 +7,6 @@ import { ChildProcessWithoutNullStreams } from 'node:child_process';
import { Writable } from 'node:stream';
import { PNG } from 'pngjs';
import postgres from 'postgres';
import { DB } from 'src/db';
import { AssetUploadInterceptor } from 'src/middleware/asset-upload.interceptor';
import { AuthGuard } from 'src/middleware/auth.guard';
import { FileUploadInterceptor } from 'src/middleware/file-upload.interceptor';
@ -56,6 +55,7 @@ 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 { DB } from 'src/schema';
import { AuthService } from 'src/services/auth.service';
import { BaseService } from 'src/services/base.service';
import { RepositoryInterface } from 'src/types';