feat: schema diff sql tools (#17116)

This commit is contained in:
Jason Rasmussen 2025-03-28 10:40:09 -04:00 committed by GitHub
parent 3fde5a8328
commit 4b4bcd23f4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
132 changed files with 5837 additions and 1246 deletions

View file

@ -0,0 +1,56 @@
import {
Check,
Column,
ColumnIndex,
CreateDateColumn,
ForeignKeyColumn,
Index,
PrimaryGeneratedColumn,
Table,
UpdateDateColumn,
UpdateIdColumn,
} from 'src/sql-tools';
import { AlbumTable } from 'src/tables/album.table';
import { AssetTable } from 'src/tables/asset.table';
import { UserTable } from 'src/tables/user.table';
@Table('activity')
@Index({
name: 'IDX_activity_like',
columns: ['assetId', 'userId', 'albumId'],
unique: true,
where: '("isLiked" = true)',
})
@Check({
name: 'CHK_2ab1e70f113f450eb40c1e3ec8',
expression: `("comment" IS NULL AND "isLiked" = true) OR ("comment" IS NOT NULL AND "isLiked" = false)`,
})
export class ActivityTable {
@PrimaryGeneratedColumn()
id!: string;
@CreateDateColumn()
createdAt!: Date;
@UpdateDateColumn()
updatedAt!: Date;
@ColumnIndex('IDX_activity_update_id')
@UpdateIdColumn()
updateId!: string;
@Column({ type: 'text', default: null })
comment!: string | null;
@Column({ type: 'boolean', default: false })
isLiked!: boolean;
@ForeignKeyColumn(() => AssetTable, { onDelete: 'CASCADE', onUpdate: 'CASCADE', nullable: true })
assetId!: string | null;
@ForeignKeyColumn(() => UserTable, { onDelete: 'CASCADE', onUpdate: 'CASCADE' })
userId!: string;
@ForeignKeyColumn(() => AlbumTable, { onDelete: 'CASCADE', onUpdate: 'CASCADE' })
albumId!: string;
}

View file

@ -0,0 +1,27 @@
import { ColumnIndex, CreateDateColumn, ForeignKeyColumn, Table } from 'src/sql-tools';
import { AlbumTable } from 'src/tables/album.table';
import { AssetTable } from 'src/tables/asset.table';
@Table({ name: 'albums_assets_assets', primaryConstraintName: 'PK_c67bc36fa845fb7b18e0e398180' })
export class AlbumAssetTable {
@ForeignKeyColumn(() => AssetTable, {
onDelete: 'CASCADE',
onUpdate: 'CASCADE',
nullable: false,
primary: true,
})
@ColumnIndex()
assetsId!: string;
@ForeignKeyColumn(() => AlbumTable, {
onDelete: 'CASCADE',
onUpdate: 'CASCADE',
nullable: false,
primary: true,
})
@ColumnIndex()
albumsId!: string;
@CreateDateColumn()
createdAt!: Date;
}

View file

@ -0,0 +1,29 @@
import { AlbumUserRole } from 'src/enum';
import { Column, ForeignKeyColumn, Index, Table } from 'src/sql-tools';
import { AlbumTable } from 'src/tables/album.table';
import { UserTable } from 'src/tables/user.table';
@Table({ name: 'albums_shared_users_users', primaryConstraintName: 'PK_7df55657e0b2e8b626330a0ebc8' })
// Pre-existing indices from original album <--> user ManyToMany mapping
@Index({ name: 'IDX_427c350ad49bd3935a50baab73', columns: ['albumsId'] })
@Index({ name: 'IDX_f48513bf9bccefd6ff3ad30bd0', columns: ['usersId'] })
export class AlbumUserTable {
@ForeignKeyColumn(() => AlbumTable, {
onDelete: 'CASCADE',
onUpdate: 'CASCADE',
nullable: false,
primary: true,
})
albumsId!: string;
@ForeignKeyColumn(() => UserTable, {
onDelete: 'CASCADE',
onUpdate: 'CASCADE',
nullable: false,
primary: true,
})
usersId!: string;
@Column({ type: 'character varying', default: AlbumUserRole.EDITOR })
role!: AlbumUserRole;
}

View file

@ -0,0 +1,51 @@
import { AssetOrder } from 'src/enum';
import {
Column,
ColumnIndex,
CreateDateColumn,
DeleteDateColumn,
ForeignKeyColumn,
PrimaryGeneratedColumn,
Table,
UpdateDateColumn,
UpdateIdColumn,
} from 'src/sql-tools';
import { AssetTable } from 'src/tables/asset.table';
import { UserTable } from 'src/tables/user.table';
@Table({ name: 'albums', primaryConstraintName: 'PK_7f71c7b5bc7c87b8f94c9a93a00' })
export class AlbumTable {
@PrimaryGeneratedColumn()
id!: string;
@ForeignKeyColumn(() => UserTable, { onDelete: 'CASCADE', onUpdate: 'CASCADE', nullable: false })
ownerId!: string;
@Column({ default: 'Untitled Album' })
albumName!: string;
@Column({ type: 'text', default: '' })
description!: string;
@CreateDateColumn()
createdAt!: Date;
@UpdateDateColumn()
updatedAt!: Date;
@ColumnIndex('IDX_albums_update_id')
@UpdateIdColumn()
updateId?: string;
@DeleteDateColumn()
deletedAt!: Date | null;
@ForeignKeyColumn(() => AssetTable, { nullable: true, onDelete: 'SET NULL', onUpdate: 'CASCADE' })
albumThumbnailAssetId!: string;
@Column({ type: 'boolean', default: true })
isActivityEnabled!: boolean;
@Column({ default: AssetOrder.DESC })
order!: AssetOrder;
}

View file

@ -0,0 +1,40 @@
import { Permission } from 'src/enum';
import {
Column,
ColumnIndex,
CreateDateColumn,
ForeignKeyColumn,
PrimaryGeneratedColumn,
Table,
UpdateDateColumn,
UpdateIdColumn,
} from 'src/sql-tools';
import { UserTable } from 'src/tables/user.table';
@Table('api_keys')
export class APIKeyTable {
@PrimaryGeneratedColumn()
id!: string;
@Column()
name!: string;
@Column()
key!: string;
@Column({ array: true, type: 'character varying' })
permissions!: Permission[];
@CreateDateColumn()
createdAt!: Date;
@UpdateDateColumn()
updatedAt!: Date;
@ColumnIndex({ name: 'IDX_api_keys_update_id' })
@UpdateIdColumn()
updateId?: string;
@ForeignKeyColumn(() => UserTable, { onUpdate: 'CASCADE', onDelete: 'CASCADE' })
userId!: string;
}

View file

@ -0,0 +1,19 @@
import { Column, ColumnIndex, CreateDateColumn, PrimaryGeneratedColumn, Table } from 'src/sql-tools';
@Table('assets_audit')
export class AssetAuditTable {
@PrimaryGeneratedColumn({ type: 'v7' })
id!: string;
@ColumnIndex('IDX_assets_audit_asset_id')
@Column({ type: 'uuid' })
assetId!: string;
@ColumnIndex('IDX_assets_audit_owner_id')
@Column({ type: 'uuid' })
ownerId!: string;
@ColumnIndex('IDX_assets_audit_deleted_at')
@CreateDateColumn({ default: () => 'clock_timestamp()' })
deletedAt!: Date;
}

View file

@ -0,0 +1,42 @@
import { SourceType } from 'src/enum';
import { Column, DeleteDateColumn, ForeignKeyColumn, Index, PrimaryGeneratedColumn, Table } from 'src/sql-tools';
import { AssetTable } from 'src/tables/asset.table';
import { PersonTable } from 'src/tables/person.table';
@Table({ name: 'asset_faces' })
@Index({ name: 'IDX_asset_faces_assetId_personId', columns: ['assetId', 'personId'] })
@Index({ columns: ['personId', 'assetId'] })
export class AssetFaceTable {
@PrimaryGeneratedColumn()
id!: string;
@Column({ default: 0, type: 'integer' })
imageWidth!: number;
@Column({ default: 0, type: 'integer' })
imageHeight!: number;
@Column({ default: 0, type: 'integer' })
boundingBoxX1!: number;
@Column({ default: 0, type: 'integer' })
boundingBoxY1!: number;
@Column({ default: 0, type: 'integer' })
boundingBoxX2!: number;
@Column({ default: 0, type: 'integer' })
boundingBoxY2!: number;
@Column({ default: SourceType.MACHINE_LEARNING, enumName: 'sourcetype', enum: SourceType })
sourceType!: SourceType;
@ForeignKeyColumn(() => AssetTable, { onDelete: 'CASCADE', onUpdate: 'CASCADE' })
assetId!: string;
@ForeignKeyColumn(() => PersonTable, { onDelete: 'SET NULL', onUpdate: 'CASCADE', nullable: true })
personId!: string | null;
@DeleteDateColumn()
deletedAt!: Date | null;
}

View file

@ -0,0 +1,40 @@
import { AssetEntity } from 'src/entities/asset.entity';
import { AssetFileType } from 'src/enum';
import {
Column,
ColumnIndex,
CreateDateColumn,
ForeignKeyColumn,
PrimaryGeneratedColumn,
Table,
Unique,
UpdateDateColumn,
UpdateIdColumn,
} from 'src/sql-tools';
@Unique({ name: 'UQ_assetId_type', columns: ['assetId', 'type'] })
@Table('asset_files')
export class AssetFileTable {
@PrimaryGeneratedColumn()
id!: string;
@ColumnIndex('IDX_asset_files_assetId')
@ForeignKeyColumn(() => AssetEntity, { onDelete: 'CASCADE', onUpdate: 'CASCADE' })
assetId?: AssetEntity;
@CreateDateColumn()
createdAt!: Date;
@UpdateDateColumn()
updatedAt!: Date;
@ColumnIndex('IDX_asset_files_update_id')
@UpdateIdColumn()
updateId?: string;
@Column()
type!: AssetFileType;
@Column()
path!: string;
}

View file

@ -0,0 +1,23 @@
import { Column, ForeignKeyColumn, Table } from 'src/sql-tools';
import { AssetTable } from 'src/tables/asset.table';
@Table('asset_job_status')
export class AssetJobStatusTable {
@ForeignKeyColumn(() => AssetTable, { onDelete: 'CASCADE', onUpdate: 'CASCADE', primary: true })
assetId!: string;
@Column({ type: 'timestamp with time zone', nullable: true })
facesRecognizedAt!: Date | null;
@Column({ type: 'timestamp with time zone', nullable: true })
metadataExtractedAt!: Date | null;
@Column({ type: 'timestamp with time zone', nullable: true })
duplicatesDetectedAt!: Date | null;
@Column({ type: 'timestamp with time zone', nullable: true })
previewAt!: Date | null;
@Column({ type: 'timestamp with time zone', nullable: true })
thumbnailAt!: Date | null;
}

View file

@ -0,0 +1,138 @@
import { ASSET_CHECKSUM_CONSTRAINT } from 'src/entities/asset.entity';
import { AssetStatus, AssetType } from 'src/enum';
import {
Column,
ColumnIndex,
CreateDateColumn,
DeleteDateColumn,
ForeignKeyColumn,
Index,
PrimaryGeneratedColumn,
Table,
UpdateDateColumn,
UpdateIdColumn,
} from 'src/sql-tools';
import { LibraryTable } from 'src/tables/library.table';
import { StackTable } from 'src/tables/stack.table';
import { UserTable } from 'src/tables/user.table';
@Table('assets')
// Checksums must be unique per user and library
@Index({
name: ASSET_CHECKSUM_CONSTRAINT,
columns: ['ownerId', 'checksum'],
unique: true,
where: '("libraryId" IS NULL)',
})
@Index({
name: 'UQ_assets_owner_library_checksum' + '',
columns: ['ownerId', 'libraryId', 'checksum'],
unique: true,
where: '("libraryId" IS NOT NULL)',
})
@Index({ name: 'idx_local_date_time', expression: `(("localDateTime" AT TIME ZONE 'UTC'::text))::date` })
@Index({
name: 'idx_local_date_time_month',
expression: `(date_trunc('MONTH'::text, ("localDateTime" AT TIME ZONE 'UTC'::text)) AT TIME ZONE 'UTC'::text)`,
})
@Index({ name: 'IDX_originalPath_libraryId', columns: ['originalPath', 'libraryId'] })
@Index({ name: 'IDX_asset_id_stackId', columns: ['id', 'stackId'] })
@Index({
name: 'idx_originalFileName_trigram',
using: 'gin',
expression: 'f_unaccent(("originalFileName")::text)',
})
// For all assets, each originalpath must be unique per user and library
export class AssetTable {
@PrimaryGeneratedColumn()
id!: string;
@Column()
deviceAssetId!: string;
@ForeignKeyColumn(() => UserTable, { onDelete: 'CASCADE', onUpdate: 'CASCADE', nullable: false })
ownerId!: string;
@ForeignKeyColumn(() => LibraryTable, { onDelete: 'CASCADE', onUpdate: 'CASCADE', nullable: true })
libraryId?: string | null;
@Column()
deviceId!: string;
@Column()
type!: AssetType;
@Column({ type: 'enum', enum: AssetStatus, default: AssetStatus.ACTIVE })
status!: AssetStatus;
@Column()
originalPath!: string;
@Column({ type: 'bytea', nullable: true })
thumbhash!: Buffer | null;
@Column({ type: 'character varying', nullable: true, default: '' })
encodedVideoPath!: string | null;
@CreateDateColumn()
createdAt!: Date;
@UpdateDateColumn()
updatedAt!: Date;
@ColumnIndex('IDX_assets_update_id')
@UpdateIdColumn()
updateId?: string;
@DeleteDateColumn()
deletedAt!: Date | null;
@ColumnIndex('idx_asset_file_created_at')
@Column({ type: 'timestamp with time zone', default: null })
fileCreatedAt!: Date;
@Column({ type: 'timestamp with time zone', default: null })
localDateTime!: Date;
@Column({ type: 'timestamp with time zone', default: null })
fileModifiedAt!: Date;
@Column({ type: 'boolean', default: false })
isFavorite!: boolean;
@Column({ type: 'boolean', default: false })
isArchived!: boolean;
@Column({ type: 'boolean', default: false })
isExternal!: boolean;
@Column({ type: 'boolean', default: false })
isOffline!: boolean;
@Column({ type: 'bytea' })
@ColumnIndex()
checksum!: Buffer; // sha1 checksum
@Column({ type: 'character varying', nullable: true })
duration!: string | null;
@Column({ type: 'boolean', default: true })
isVisible!: boolean;
@ForeignKeyColumn(() => AssetTable, { nullable: true, onUpdate: 'CASCADE', onDelete: 'SET NULL' })
livePhotoVideoId!: string | null;
@Column()
@ColumnIndex()
originalFileName!: string;
@Column({ nullable: true })
sidecarPath!: string | null;
@ForeignKeyColumn(() => StackTable, { nullable: true, onDelete: 'SET NULL', onUpdate: 'CASCADE' })
stackId?: string | null;
@ColumnIndex('IDX_assets_duplicateId')
@Column({ type: 'uuid', nullable: true })
duplicateId!: string | null;
}

View file

@ -0,0 +1,24 @@
import { DatabaseAction, EntityType } from 'src/enum';
import { Column, CreateDateColumn, Index, PrimaryColumn, Table } from 'src/sql-tools';
@Table('audit')
@Index({ name: 'IDX_ownerId_createdAt', columns: ['ownerId', 'createdAt'] })
export class AuditTable {
@PrimaryColumn({ type: 'integer', default: 'increment', synchronize: false })
id!: number;
@Column()
entityType!: EntityType;
@Column({ type: 'uuid' })
entityId!: string;
@Column()
action!: DatabaseAction;
@Column({ type: 'uuid' })
ownerId!: string;
@CreateDateColumn()
createdAt!: Date;
}

View file

@ -0,0 +1,105 @@
import { Column, ColumnIndex, ForeignKeyColumn, Table, UpdateDateColumn, UpdateIdColumn } from 'src/sql-tools';
import { AssetTable } from 'src/tables/asset.table';
@Table('exif')
export class ExifTable {
@ForeignKeyColumn(() => AssetTable, { onDelete: 'CASCADE', primary: true })
assetId!: string;
@UpdateDateColumn({ default: () => 'clock_timestamp()' })
updatedAt?: Date;
@ColumnIndex('IDX_asset_exif_update_id')
@UpdateIdColumn()
updateId?: string;
/* General info */
@Column({ type: 'text', default: '' })
description!: string; // or caption
@Column({ type: 'integer', nullable: true })
exifImageWidth!: number | null;
@Column({ type: 'integer', nullable: true })
exifImageHeight!: number | null;
@Column({ type: 'bigint', nullable: true })
fileSizeInByte!: number | null;
@Column({ type: 'character varying', nullable: true })
orientation!: string | null;
@Column({ type: 'timestamp with time zone', nullable: true })
dateTimeOriginal!: Date | null;
@Column({ type: 'timestamp with time zone', nullable: true })
modifyDate!: Date | null;
@Column({ type: 'character varying', nullable: true })
timeZone!: string | null;
@Column({ type: 'double precision', nullable: true })
latitude!: number | null;
@Column({ type: 'double precision', nullable: true })
longitude!: number | null;
@Column({ type: 'character varying', nullable: true })
projectionType!: string | null;
@ColumnIndex('exif_city')
@Column({ type: 'character varying', nullable: true })
city!: string | null;
@ColumnIndex('IDX_live_photo_cid')
@Column({ type: 'character varying', nullable: true })
livePhotoCID!: string | null;
@ColumnIndex('IDX_auto_stack_id')
@Column({ type: 'character varying', nullable: true })
autoStackId!: string | null;
@Column({ type: 'character varying', nullable: true })
state!: string | null;
@Column({ type: 'character varying', nullable: true })
country!: string | null;
/* Image info */
@Column({ type: 'character varying', nullable: true })
make!: string | null;
@Column({ type: 'character varying', nullable: true })
model!: string | null;
@Column({ type: 'character varying', nullable: true })
lensModel!: string | null;
@Column({ type: 'double precision', nullable: true })
fNumber!: number | null;
@Column({ type: 'double precision', nullable: true })
focalLength!: number | null;
@Column({ type: 'integer', nullable: true })
iso!: number | null;
@Column({ type: 'character varying', nullable: true })
exposureTime!: string | null;
@Column({ type: 'character varying', nullable: true })
profileDescription!: string | null;
@Column({ type: 'character varying', nullable: true })
colorspace!: string | null;
@Column({ type: 'integer', nullable: true })
bitsPerSample!: number | null;
@Column({ type: 'integer', nullable: true })
rating!: number | null;
/* Video info */
@Column({ type: 'double precision', nullable: true })
fps?: number | null;
}

View file

@ -0,0 +1,16 @@
import { Column, ColumnIndex, ForeignKeyColumn, Table } from 'src/sql-tools';
import { AssetFaceTable } from 'src/tables/asset-face.table';
@Table({ name: 'face_search', primaryConstraintName: 'face_search_pkey' })
export class FaceSearchTable {
@ForeignKeyColumn(() => AssetFaceTable, {
onDelete: 'CASCADE',
primary: true,
constraintName: 'face_search_faceId_fkey',
})
faceId!: string;
@ColumnIndex({ name: 'face_index', synchronize: false })
@Column({ type: 'vector', array: true, length: 512, synchronize: false })
embedding!: string;
}

View file

@ -0,0 +1,73 @@
import { Column, PrimaryColumn, Table } from 'src/sql-tools';
@Table({ name: 'geodata_places', synchronize: false })
export class GeodataPlacesTable {
@PrimaryColumn({ type: 'integer' })
id!: number;
@Column({ type: 'character varying', length: 200 })
name!: string;
@Column({ type: 'double precision' })
longitude!: number;
@Column({ type: 'double precision' })
latitude!: number;
@Column({ type: 'character', length: 2 })
countryCode!: string;
@Column({ type: 'character varying', length: 20, nullable: true })
admin1Code!: string;
@Column({ type: 'character varying', length: 80, nullable: true })
admin2Code!: string;
@Column({ type: 'character varying', nullable: true })
admin1Name!: string;
@Column({ type: 'character varying', nullable: true })
admin2Name!: string;
@Column({ type: 'character varying', nullable: true })
alternateNames!: string;
@Column({ type: 'date' })
modificationDate!: Date;
}
@Table({ name: 'geodata_places_tmp', synchronize: false })
export class GeodataPlacesTempEntity {
@PrimaryColumn({ type: 'integer' })
id!: number;
@Column({ type: 'character varying', length: 200 })
name!: string;
@Column({ type: 'double precision' })
longitude!: number;
@Column({ type: 'double precision' })
latitude!: number;
@Column({ type: 'character', length: 2 })
countryCode!: string;
@Column({ type: 'character varying', length: 20, nullable: true })
admin1Code!: string;
@Column({ type: 'character varying', length: 80, nullable: true })
admin2Code!: string;
@Column({ type: 'character varying', nullable: true })
admin1Name!: string;
@Column({ type: 'character varying', nullable: true })
admin2Name!: string;
@Column({ type: 'character varying', nullable: true })
alternateNames!: string;
@Column({ type: 'date' })
modificationDate!: Date;
}

View file

@ -0,0 +1,70 @@
import { ActivityTable } from 'src/tables/activity.table';
import { AlbumAssetTable } from 'src/tables/album-asset.table';
import { AlbumUserTable } from 'src/tables/album-user.table';
import { AlbumTable } from 'src/tables/album.table';
import { APIKeyTable } from 'src/tables/api-key.table';
import { AssetAuditTable } from 'src/tables/asset-audit.table';
import { AssetFaceTable } from 'src/tables/asset-face.table';
import { AssetJobStatusTable } from 'src/tables/asset-job-status.table';
import { AssetTable } from 'src/tables/asset.table';
import { AuditTable } from 'src/tables/audit.table';
import { ExifTable } from 'src/tables/exif.table';
import { FaceSearchTable } from 'src/tables/face-search.table';
import { GeodataPlacesTable } from 'src/tables/geodata-places.table';
import { LibraryTable } from 'src/tables/library.table';
import { MemoryTable } from 'src/tables/memory.table';
import { MemoryAssetTable } from 'src/tables/memory_asset.table';
import { MoveTable } from 'src/tables/move.table';
import { NaturalEarthCountriesTable, NaturalEarthCountriesTempTable } from 'src/tables/natural-earth-countries.table';
import { PartnerAuditTable } from 'src/tables/partner-audit.table';
import { PartnerTable } from 'src/tables/partner.table';
import { PersonTable } from 'src/tables/person.table';
import { SessionTable } from 'src/tables/session.table';
import { SharedLinkAssetTable } from 'src/tables/shared-link-asset.table';
import { SharedLinkTable } from 'src/tables/shared-link.table';
import { SmartSearchTable } from 'src/tables/smart-search.table';
import { StackTable } from 'src/tables/stack.table';
import { SessionSyncCheckpointTable } from 'src/tables/sync-checkpoint.table';
import { SystemMetadataTable } from 'src/tables/system-metadata.table';
import { TagAssetTable } from 'src/tables/tag-asset.table';
import { UserAuditTable } from 'src/tables/user-audit.table';
import { UserMetadataTable } from 'src/tables/user-metadata.table';
import { UserTable } from 'src/tables/user.table';
import { VersionHistoryTable } from 'src/tables/version-history.table';
export const tables = [
ActivityTable,
AlbumAssetTable,
AlbumUserTable,
AlbumTable,
APIKeyTable,
AssetAuditTable,
AssetFaceTable,
AssetJobStatusTable,
AssetTable,
AuditTable,
ExifTable,
FaceSearchTable,
GeodataPlacesTable,
LibraryTable,
MemoryAssetTable,
MemoryTable,
MoveTable,
NaturalEarthCountriesTable,
NaturalEarthCountriesTempTable,
PartnerAuditTable,
PartnerTable,
PersonTable,
SessionTable,
SharedLinkAssetTable,
SharedLinkTable,
SmartSearchTable,
StackTable,
SessionSyncCheckpointTable,
SystemMetadataTable,
TagAssetTable,
UserAuditTable,
UserMetadataTable,
UserTable,
VersionHistoryTable,
];

View file

@ -0,0 +1,46 @@
import {
Column,
ColumnIndex,
CreateDateColumn,
DeleteDateColumn,
ForeignKeyColumn,
PrimaryGeneratedColumn,
Table,
UpdateDateColumn,
UpdateIdColumn,
} from 'src/sql-tools';
import { UserTable } from 'src/tables/user.table';
@Table('libraries')
export class LibraryTable {
@PrimaryGeneratedColumn()
id!: string;
@Column()
name!: string;
@ForeignKeyColumn(() => UserTable, { onDelete: 'CASCADE', onUpdate: 'CASCADE', nullable: false })
ownerId!: string;
@Column({ type: 'text', array: true })
importPaths!: string[];
@Column({ type: 'text', array: true })
exclusionPatterns!: string[];
@CreateDateColumn()
createdAt!: Date;
@UpdateDateColumn()
updatedAt!: Date;
@ColumnIndex('IDX_libraries_update_id')
@UpdateIdColumn()
updateId?: string;
@DeleteDateColumn()
deletedAt?: Date;
@Column({ type: 'timestamp with time zone', nullable: true })
refreshedAt!: Date | null;
}

View file

@ -0,0 +1,60 @@
import { MemoryType } from 'src/enum';
import {
Column,
ColumnIndex,
CreateDateColumn,
DeleteDateColumn,
ForeignKeyColumn,
PrimaryGeneratedColumn,
Table,
UpdateDateColumn,
UpdateIdColumn,
} from 'src/sql-tools';
import { UserTable } from 'src/tables/user.table';
import { MemoryData } from 'src/types';
@Table('memories')
export class MemoryTable<T extends MemoryType = MemoryType> {
@PrimaryGeneratedColumn()
id!: string;
@CreateDateColumn()
createdAt!: Date;
@UpdateDateColumn()
updatedAt!: Date;
@ColumnIndex('IDX_memories_update_id')
@UpdateIdColumn()
updateId?: string;
@DeleteDateColumn()
deletedAt?: Date;
@ForeignKeyColumn(() => UserTable, { onDelete: 'CASCADE', onUpdate: 'CASCADE', nullable: false })
ownerId!: string;
@Column()
type!: T;
@Column({ type: 'jsonb' })
data!: MemoryData[T];
/** unless set to true, will be automatically deleted in the future */
@Column({ type: 'boolean', default: false })
isSaved!: boolean;
/** memories are sorted in ascending order by this value */
@Column({ type: 'timestamp with time zone' })
memoryAt!: Date;
@Column({ type: 'timestamp with time zone', nullable: true })
showAt?: Date;
@Column({ type: 'timestamp with time zone', nullable: true })
hideAt?: Date;
/** when the user last viewed the memory */
@Column({ type: 'timestamp with time zone', nullable: true })
seenAt?: Date;
}

View file

@ -0,0 +1,14 @@
import { ColumnIndex, ForeignKeyColumn, Table } from 'src/sql-tools';
import { AssetTable } from 'src/tables/asset.table';
import { MemoryTable } from 'src/tables/memory.table';
@Table('memories_assets_assets')
export class MemoryAssetTable {
@ColumnIndex()
@ForeignKeyColumn(() => AssetTable, { onUpdate: 'CASCADE', onDelete: 'CASCADE', primary: true })
assetsId!: string;
@ColumnIndex()
@ForeignKeyColumn(() => MemoryTable, { onUpdate: 'CASCADE', onDelete: 'CASCADE', primary: true })
memoriesId!: string;
}

View file

@ -0,0 +1,24 @@
import { PathType } from 'src/enum';
import { Column, PrimaryGeneratedColumn, Table, Unique } from 'src/sql-tools';
@Table('move_history')
// path lock (per entity)
@Unique({ name: 'UQ_entityId_pathType', columns: ['entityId', 'pathType'] })
// new path lock (global)
@Unique({ name: 'UQ_newPath', columns: ['newPath'] })
export class MoveTable {
@PrimaryGeneratedColumn()
id!: string;
@Column({ type: 'uuid' })
entityId!: string;
@Column({ type: 'character varying' })
pathType!: PathType;
@Column({ type: 'character varying' })
oldPath!: string;
@Column({ type: 'character varying' })
newPath!: string;
}

View file

@ -0,0 +1,37 @@
import { Column, PrimaryColumn, PrimaryGeneratedColumn, Table } from 'src/sql-tools';
@Table({ name: 'naturalearth_countries', synchronize: false })
export class NaturalEarthCountriesTable {
@PrimaryColumn({ type: 'serial' })
id!: number;
@Column({ type: 'character varying', length: 50 })
admin!: string;
@Column({ type: 'character varying', length: 3 })
admin_a3!: string;
@Column({ type: 'character varying', length: 50 })
type!: string;
@Column({ type: 'polygon' })
coordinates!: string;
}
@Table({ name: 'naturalearth_countries_tmp', synchronize: false })
export class NaturalEarthCountriesTempTable {
@PrimaryGeneratedColumn()
id!: number;
@Column({ type: 'character varying', length: 50 })
admin!: string;
@Column({ type: 'character varying', length: 3 })
admin_a3!: string;
@Column({ type: 'character varying', length: 50 })
type!: string;
@Column({ type: 'polygon' })
coordinates!: string;
}

View file

@ -0,0 +1,19 @@
import { Column, ColumnIndex, CreateDateColumn, PrimaryGeneratedColumn, Table } from 'src/sql-tools';
@Table('partners_audit')
export class PartnerAuditTable {
@PrimaryGeneratedColumn({ type: 'v7' })
id!: string;
@ColumnIndex('IDX_partners_audit_shared_by_id')
@Column({ type: 'uuid' })
sharedById!: string;
@ColumnIndex('IDX_partners_audit_shared_with_id')
@Column({ type: 'uuid' })
sharedWithId!: string;
@ColumnIndex('IDX_partners_audit_deleted_at')
@CreateDateColumn({ default: () => 'clock_timestamp()' })
deletedAt!: Date;
}

View file

@ -0,0 +1,32 @@
import {
Column,
ColumnIndex,
CreateDateColumn,
ForeignKeyColumn,
Table,
UpdateDateColumn,
UpdateIdColumn,
} from 'src/sql-tools';
import { UserTable } from 'src/tables/user.table';
@Table('partners')
export class PartnerTable {
@ForeignKeyColumn(() => UserTable, { onDelete: 'CASCADE', primary: true })
sharedById!: string;
@ForeignKeyColumn(() => UserTable, { onDelete: 'CASCADE', primary: true })
sharedWithId!: string;
@CreateDateColumn()
createdAt!: Date;
@UpdateDateColumn()
updatedAt!: Date;
@ColumnIndex('IDX_partners_update_id')
@UpdateIdColumn()
updateId!: string;
@Column({ type: 'boolean', default: false })
inTimeline!: boolean;
}

View file

@ -0,0 +1,54 @@
import {
Check,
Column,
ColumnIndex,
CreateDateColumn,
ForeignKeyColumn,
PrimaryGeneratedColumn,
Table,
UpdateDateColumn,
UpdateIdColumn,
} from 'src/sql-tools';
import { AssetFaceTable } from 'src/tables/asset-face.table';
import { UserTable } from 'src/tables/user.table';
@Table('person')
@Check({ name: 'CHK_b0f82b0ed662bfc24fbb58bb45', expression: `"birthDate" <= CURRENT_DATE` })
export class PersonTable {
@PrimaryGeneratedColumn('uuid')
id!: string;
@CreateDateColumn()
createdAt!: Date;
@UpdateDateColumn()
updatedAt!: Date;
@ColumnIndex('IDX_person_update_id')
@UpdateIdColumn()
updateId!: string;
@ForeignKeyColumn(() => UserTable, { onDelete: 'CASCADE', onUpdate: 'CASCADE', nullable: false })
ownerId!: string;
@Column({ default: '' })
name!: string;
@Column({ type: 'date', nullable: true })
birthDate!: Date | string | null;
@Column({ default: '' })
thumbnailPath!: string;
@ForeignKeyColumn(() => AssetFaceTable, { onDelete: 'SET NULL', nullable: true })
faceAssetId!: string | null;
@Column({ type: 'boolean', default: false })
isHidden!: boolean;
@Column({ type: 'boolean', default: false })
isFavorite!: boolean;
@Column({ type: 'character varying', nullable: true, default: null })
color?: string | null;
}

View file

@ -0,0 +1,40 @@
import {
Column,
ColumnIndex,
CreateDateColumn,
ForeignKeyColumn,
PrimaryGeneratedColumn,
Table,
UpdateDateColumn,
UpdateIdColumn,
} from 'src/sql-tools';
import { UserTable } from 'src/tables/user.table';
@Table({ name: 'sessions', primaryConstraintName: 'PK_48cb6b5c20faa63157b3c1baf7f' })
export class SessionTable {
@PrimaryGeneratedColumn()
id!: string;
// TODO convert to byte[]
@Column()
token!: string;
@ForeignKeyColumn(() => UserTable, { onUpdate: 'CASCADE', onDelete: 'CASCADE' })
userId!: string;
@CreateDateColumn()
createdAt!: Date;
@UpdateDateColumn()
updatedAt!: Date;
@ColumnIndex('IDX_sessions_update_id')
@UpdateIdColumn()
updateId!: string;
@Column({ default: '' })
deviceType!: string;
@Column({ default: '' })
deviceOS!: string;
}

View file

@ -0,0 +1,14 @@
import { ColumnIndex, ForeignKeyColumn, Table } from 'src/sql-tools';
import { AssetTable } from 'src/tables/asset.table';
import { SharedLinkTable } from 'src/tables/shared-link.table';
@Table('shared_link__asset')
export class SharedLinkAssetTable {
@ColumnIndex()
@ForeignKeyColumn(() => AssetTable, { onUpdate: 'CASCADE', onDelete: 'CASCADE', primary: true })
assetsId!: string;
@ColumnIndex()
@ForeignKeyColumn(() => SharedLinkTable, { onUpdate: 'CASCADE', onDelete: 'CASCADE', primary: true })
sharedLinksId!: string;
}

View file

@ -0,0 +1,54 @@
import { SharedLinkType } from 'src/enum';
import {
Column,
ColumnIndex,
CreateDateColumn,
ForeignKeyColumn,
PrimaryGeneratedColumn,
Table,
Unique,
} from 'src/sql-tools';
import { AlbumTable } from 'src/tables/album.table';
import { UserTable } from 'src/tables/user.table';
@Table('shared_links')
@Unique({ name: 'UQ_sharedlink_key', columns: ['key'] })
export class SharedLinkTable {
@PrimaryGeneratedColumn()
id!: string;
@Column({ type: 'character varying', nullable: true })
description!: string | null;
@Column({ type: 'character varying', nullable: true })
password!: string | null;
@ForeignKeyColumn(() => UserTable, { onDelete: 'CASCADE', onUpdate: 'CASCADE' })
userId!: string;
@ColumnIndex('IDX_sharedlink_albumId')
@ForeignKeyColumn(() => AlbumTable, { nullable: true, onDelete: 'CASCADE', onUpdate: 'CASCADE' })
albumId!: string;
@ColumnIndex('IDX_sharedlink_key')
@Column({ type: 'bytea' })
key!: Buffer; // use to access the inidividual asset
@Column()
type!: SharedLinkType;
@CreateDateColumn()
createdAt!: Date;
@Column({ type: 'timestamp with time zone', nullable: true })
expiresAt!: Date | null;
@Column({ type: 'boolean', default: false })
allowUpload!: boolean;
@Column({ type: 'boolean', default: true })
allowDownload!: boolean;
@Column({ type: 'boolean', default: true })
showExif!: boolean;
}

View file

@ -0,0 +1,16 @@
import { Column, ColumnIndex, ForeignKeyColumn, Table } from 'src/sql-tools';
import { AssetTable } from 'src/tables/asset.table';
@Table({ name: 'smart_search', primaryConstraintName: 'smart_search_pkey' })
export class SmartSearchTable {
@ForeignKeyColumn(() => AssetTable, {
onDelete: 'CASCADE',
primary: true,
constraintName: 'smart_search_assetId_fkey',
})
assetId!: string;
@ColumnIndex({ name: 'clip_index', synchronize: false })
@Column({ type: 'vector', array: true, length: 512, synchronize: false })
embedding!: string;
}

View file

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

View file

@ -0,0 +1,34 @@
import { SyncEntityType } from 'src/enum';
import {
Column,
ColumnIndex,
CreateDateColumn,
ForeignKeyColumn,
PrimaryColumn,
Table,
UpdateDateColumn,
UpdateIdColumn,
} from 'src/sql-tools';
import { SessionTable } from 'src/tables/session.table';
@Table('session_sync_checkpoints')
export class SessionSyncCheckpointTable {
@ForeignKeyColumn(() => SessionTable, { onDelete: 'CASCADE', onUpdate: 'CASCADE', primary: true })
sessionId!: string;
@PrimaryColumn({ type: 'character varying' })
type!: SyncEntityType;
@CreateDateColumn()
createdAt!: Date;
@UpdateDateColumn()
updatedAt!: Date;
@ColumnIndex('IDX_session_sync_checkpoints_update_id')
@UpdateIdColumn()
updateId!: string;
@Column()
ack!: string;
}

View file

@ -0,0 +1,12 @@
import { SystemMetadataKey } from 'src/enum';
import { Column, PrimaryColumn, Table } from 'src/sql-tools';
import { SystemMetadata } from 'src/types';
@Table('system_metadata')
export class SystemMetadataTable<T extends keyof SystemMetadata = SystemMetadataKey> {
@PrimaryColumn({ type: 'character varying' })
key!: T;
@Column({ type: 'jsonb' })
value!: SystemMetadata[T];
}

View file

@ -0,0 +1,15 @@
import { ColumnIndex, ForeignKeyColumn, Index, Table } from 'src/sql-tools';
import { AssetTable } from 'src/tables/asset.table';
import { TagTable } from 'src/tables/tag.table';
@Index({ name: 'IDX_tag_asset_assetsId_tagsId', columns: ['assetsId', 'tagsId'] })
@Table('tag_asset')
export class TagAssetTable {
@ColumnIndex()
@ForeignKeyColumn(() => AssetTable, { onUpdate: 'CASCADE', onDelete: 'CASCADE', primary: true })
assetsId!: string;
@ColumnIndex()
@ForeignKeyColumn(() => TagTable, { onUpdate: 'CASCADE', onDelete: 'CASCADE', primary: true })
tagsId!: string;
}

View file

@ -0,0 +1,15 @@
import { ColumnIndex, ForeignKeyColumn, PrimaryColumn, Table } from 'src/sql-tools';
import { TagTable } from 'src/tables/tag.table';
@Table('tags_closure')
export class TagClosureTable {
@PrimaryColumn()
@ColumnIndex()
@ForeignKeyColumn(() => TagTable, { onDelete: 'CASCADE', onUpdate: 'NO ACTION' })
id_ancestor!: string;
@PrimaryColumn()
@ColumnIndex()
@ForeignKeyColumn(() => TagTable, { onDelete: 'CASCADE', onUpdate: 'NO ACTION' })
id_descendant!: string;
}

View file

@ -0,0 +1,41 @@
import {
Column,
ColumnIndex,
CreateDateColumn,
ForeignKeyColumn,
PrimaryGeneratedColumn,
Table,
Unique,
UpdateDateColumn,
UpdateIdColumn,
} from 'src/sql-tools';
import { UserTable } from 'src/tables/user.table';
@Table('tags')
@Unique({ columns: ['userId', 'value'] })
export class TagTable {
@PrimaryGeneratedColumn()
id!: string;
@Column()
value!: string;
@CreateDateColumn()
createdAt!: Date;
@UpdateDateColumn()
updatedAt!: Date;
@ColumnIndex('IDX_tags_update_id')
@UpdateIdColumn()
updateId!: string;
@Column({ type: 'character varying', nullable: true, default: null })
color!: string | null;
@ForeignKeyColumn(() => TagTable, { nullable: true, onDelete: 'CASCADE' })
parentId?: string;
@ForeignKeyColumn(() => UserTable, { onUpdate: 'CASCADE', onDelete: 'CASCADE' })
userId!: string;
}

View file

@ -0,0 +1,14 @@
import { Column, ColumnIndex, CreateDateColumn, PrimaryGeneratedColumn, Table } from 'src/sql-tools';
@Table('users_audit')
export class UserAuditTable {
@PrimaryGeneratedColumn({ type: 'v7' })
id!: string;
@Column({ type: 'uuid' })
userId!: string;
@ColumnIndex('IDX_users_audit_deleted_at')
@CreateDateColumn({ default: () => 'clock_timestamp()' })
deletedAt!: Date;
}

View file

@ -0,0 +1,16 @@
import { UserMetadata, UserMetadataItem } from 'src/entities/user-metadata.entity';
import { UserMetadataKey } from 'src/enum';
import { Column, ForeignKeyColumn, PrimaryColumn, Table } from 'src/sql-tools';
import { UserTable } from 'src/tables/user.table';
@Table('user_metadata')
export class UserMetadataTable<T extends keyof UserMetadata = UserMetadataKey> implements UserMetadataItem<T> {
@ForeignKeyColumn(() => UserTable, { onUpdate: 'CASCADE', onDelete: 'CASCADE', primary: true })
userId!: string;
@PrimaryColumn({ type: 'character varying' })
key!: T;
@Column({ type: 'jsonb' })
value!: UserMetadata[T];
}

View file

@ -0,0 +1,73 @@
import { ColumnType } from 'kysely';
import { UserStatus } from 'src/enum';
import {
Column,
ColumnIndex,
CreateDateColumn,
DeleteDateColumn,
Index,
PrimaryGeneratedColumn,
Table,
UpdateDateColumn,
UpdateIdColumn,
} 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')
@Index({ name: 'IDX_users_updated_at_asc_id_asc', columns: ['updatedAt', 'id'] })
export class UserTable {
@PrimaryGeneratedColumn()
id!: Generated<string>;
@Column({ default: '' })
name!: Generated<string>;
@Column({ type: 'boolean', default: false })
isAdmin!: Generated<boolean>;
@Column({ unique: true })
email!: string;
@Column({ unique: true, nullable: true, default: null })
storageLabel!: string | null;
@Column({ default: '' })
password!: Generated<string>;
@Column({ default: '' })
oauthId!: Generated<string>;
@Column({ default: '' })
profileImagePath!: Generated<string>;
@Column({ type: 'boolean', default: true })
shouldChangePassword!: Generated<boolean>;
@CreateDateColumn()
createdAt!: Generated<Timestamp>;
@UpdateDateColumn()
updatedAt!: Generated<Timestamp>;
@DeleteDateColumn()
deletedAt!: Timestamp | null;
@Column({ type: 'character varying', default: UserStatus.ACTIVE })
status!: Generated<UserStatus>;
@ColumnIndex({ name: 'IDX_users_update_id' })
@UpdateIdColumn()
updateId!: Generated<string>;
@Column({ type: 'bigint', nullable: true })
quotaSizeInBytes!: ColumnType<number> | null;
@Column({ type: 'bigint', default: 0 })
quotaUsageInBytes!: Generated<ColumnType<number>>;
@Column({ type: 'timestamp with time zone', default: () => 'now()' })
profileChangedAt!: Generated<Timestamp>;
}

View file

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