mirror of
https://github.com/immich-app/immich.git
synced 2025-05-24 01:12:58 -04:00
feat: kysely migrations (#17198)
This commit is contained in:
parent
6fa0cb534a
commit
55a3c30664
@ -4,8 +4,8 @@ process.env.DB_URL = 'postgres://postgres:postgres@localhost:5432/immich';
|
||||
import { writeFileSync } from 'node:fs';
|
||||
import postgres from 'postgres';
|
||||
import { ConfigRepository } from 'src/repositories/config.repository';
|
||||
import 'src/schema/tables';
|
||||
import { DatabaseTable, schemaDiff, schemaFromDatabase, schemaFromDecorators } from 'src/sql-tools';
|
||||
import 'src/tables';
|
||||
|
||||
const main = async () => {
|
||||
const command = process.argv[2];
|
||||
@ -54,9 +54,10 @@ const generate = async (name: string) => {
|
||||
};
|
||||
|
||||
const create = (name: string, up: string[], down: string[]) => {
|
||||
const { filename, code } = asMigration(name, up, down);
|
||||
const timestamp = Date.now();
|
||||
const filename = `${timestamp}-${name}.ts`;
|
||||
const fullPath = `./src/${filename}`;
|
||||
writeFileSync(fullPath, code);
|
||||
writeFileSync(fullPath, asMigration('kysely', { name, timestamp, up, down }));
|
||||
console.log(`Wrote ${fullPath}`);
|
||||
};
|
||||
|
||||
@ -79,14 +80,21 @@ const compare = async () => {
|
||||
return { up, down };
|
||||
};
|
||||
|
||||
const asMigration = (name: string, up: string[], down: string[]) => {
|
||||
const timestamp = Date.now();
|
||||
type MigrationProps = {
|
||||
name: string;
|
||||
timestamp: number;
|
||||
up: string[];
|
||||
down: string[];
|
||||
};
|
||||
|
||||
const asMigration = (type: 'kysely' | 'typeorm', options: MigrationProps) =>
|
||||
type === 'typeorm' ? asTypeOrmMigration(options) : asKyselyMigration(options);
|
||||
|
||||
const asTypeOrmMigration = ({ timestamp, name, up, down }: MigrationProps) => {
|
||||
const upSql = up.map((sql) => ` await queryRunner.query(\`${sql}\`);`).join('\n');
|
||||
const downSql = down.map((sql) => ` await queryRunner.query(\`${sql}\`);`).join('\n');
|
||||
return {
|
||||
filename: `${timestamp}-${name}.ts`,
|
||||
code: `import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
|
||||
return `import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
|
||||
export class ${name}${timestamp} implements MigrationInterface {
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
@ -97,8 +105,23 @@ ${upSql}
|
||||
${downSql}
|
||||
}
|
||||
}
|
||||
`,
|
||||
};
|
||||
`;
|
||||
};
|
||||
|
||||
const asKyselyMigration = ({ up, down }: MigrationProps) => {
|
||||
const upSql = up.map((sql) => ` await sql\`${sql}\`.execute(db);`).join('\n');
|
||||
const downSql = down.map((sql) => ` await sql\`${sql}\`.execute(db);`).join('\n');
|
||||
|
||||
return `import { Kysely, sql } from 'kysely';
|
||||
|
||||
export async function up(db: Kysely<any>): Promise<void> {
|
||||
${upSql}
|
||||
}
|
||||
|
||||
export async function down(db: Kysely<any>): Promise<void> {
|
||||
${downSql}
|
||||
}
|
||||
`;
|
||||
};
|
||||
|
||||
main()
|
||||
|
2
server/src/db.d.ts
vendored
2
server/src/db.d.ts
vendored
@ -5,7 +5,7 @@
|
||||
|
||||
import type { ColumnType } from 'kysely';
|
||||
import { AssetType, MemoryType, Permission, SyncEntityType } from 'src/enum';
|
||||
import { UserTable } from 'src/tables/user.table';
|
||||
import { UserTable } from 'src/schema/tables/user.table';
|
||||
import { OnThisDayData } from 'src/types';
|
||||
|
||||
export type ArrayType<T> = ArrayTypeImpl<T> extends (infer U)[] ? U[] : ArrayTypeImpl<T>;
|
||||
|
@ -1,7 +1,9 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import AsyncLock from 'async-lock';
|
||||
import { Kysely, sql, Transaction } from 'kysely';
|
||||
import { FileMigrationProvider, Kysely, Migrator, sql, Transaction } from 'kysely';
|
||||
import { InjectKysely } from 'nestjs-kysely';
|
||||
import { mkdir, readdir } from 'node:fs/promises';
|
||||
import { join } from 'node:path';
|
||||
import semver from 'semver';
|
||||
import { EXTENSION_NAMES, POSTGRES_VERSION_RANGE, VECTOR_VERSION_RANGE, VECTORS_VERSION_RANGE } from 'src/constants';
|
||||
import { DB } from 'src/db';
|
||||
@ -200,9 +202,49 @@ export class DatabaseRepository {
|
||||
|
||||
this.logger.log('Running migrations, this may take a while');
|
||||
|
||||
this.logger.debug('Running typeorm migrations');
|
||||
|
||||
await dataSource.initialize();
|
||||
await dataSource.runMigrations(options);
|
||||
await dataSource.destroy();
|
||||
|
||||
this.logger.debug('Finished running typeorm migrations');
|
||||
|
||||
// eslint-disable-next-line unicorn/prefer-module
|
||||
const migrationFolder = join(__dirname, '..', 'schema/migrations');
|
||||
// TODO remove after we have at least one kysely migration
|
||||
await mkdir(migrationFolder, { recursive: true });
|
||||
|
||||
this.logger.debug('Running kysely migrations');
|
||||
const migrator = new Migrator({
|
||||
db: this.db,
|
||||
migrationLockTableName: 'kysely_migrations_lock',
|
||||
migrationTableName: 'kysely_migrations',
|
||||
provider: new FileMigrationProvider({
|
||||
fs: { readdir },
|
||||
path: { join },
|
||||
migrationFolder,
|
||||
}),
|
||||
});
|
||||
|
||||
const { error, results } = await migrator.migrateToLatest();
|
||||
|
||||
for (const result of results ?? []) {
|
||||
if (result.status === 'Success') {
|
||||
this.logger.log(`Migration "${result.migrationName}" succeeded`);
|
||||
}
|
||||
|
||||
if (result.status === 'Error') {
|
||||
this.logger.warn(`Migration "${result.migrationName}" failed`);
|
||||
}
|
||||
}
|
||||
|
||||
if (error) {
|
||||
this.logger.error(`Kysely migrations failed: ${error}`);
|
||||
throw error;
|
||||
}
|
||||
|
||||
this.logger.debug('Finished running kysely migrations');
|
||||
}
|
||||
|
||||
async withLock<R>(lock: DatabaseLock, callback: () => Promise<R>): Promise<R> {
|
||||
|
@ -8,7 +8,7 @@ import { DummyValue, GenerateSql } from 'src/decorators';
|
||||
import { UserMetadata, UserMetadataItem } from 'src/entities/user-metadata.entity';
|
||||
import { UserEntity, withMetadata } from 'src/entities/user.entity';
|
||||
import { AssetType, UserStatus } from 'src/enum';
|
||||
import { UserTable } from 'src/tables/user.table';
|
||||
import { UserTable } from 'src/schema/tables/user.table';
|
||||
import { asUuid } from 'src/utils/database';
|
||||
|
||||
type Upsert = Insertable<DbUserMetadata>;
|
||||
|
@ -1,3 +1,6 @@
|
||||
import { AlbumTable } from 'src/schema/tables/album.table';
|
||||
import { AssetTable } from 'src/schema/tables/asset.table';
|
||||
import { UserTable } from 'src/schema/tables/user.table';
|
||||
import {
|
||||
Check,
|
||||
Column,
|
||||
@ -10,9 +13,6 @@ import {
|
||||
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({
|
@ -1,6 +1,6 @@
|
||||
import { AlbumTable } from 'src/schema/tables/album.table';
|
||||
import { AssetTable } from 'src/schema/tables/asset.table';
|
||||
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 {
|
@ -1,7 +1,7 @@
|
||||
import { AlbumUserRole } from 'src/enum';
|
||||
import { AlbumTable } from 'src/schema/tables/album.table';
|
||||
import { UserTable } from 'src/schema/tables/user.table';
|
||||
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
|
@ -1,4 +1,6 @@
|
||||
import { AssetOrder } from 'src/enum';
|
||||
import { AssetTable } from 'src/schema/tables/asset.table';
|
||||
import { UserTable } from 'src/schema/tables/user.table';
|
||||
import {
|
||||
Column,
|
||||
ColumnIndex,
|
||||
@ -10,8 +12,6 @@ import {
|
||||
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 {
|
@ -1,4 +1,5 @@
|
||||
import { Permission } from 'src/enum';
|
||||
import { UserTable } from 'src/schema/tables/user.table';
|
||||
import {
|
||||
Column,
|
||||
ColumnIndex,
|
||||
@ -9,7 +10,6 @@ import {
|
||||
UpdateDateColumn,
|
||||
UpdateIdColumn,
|
||||
} from 'src/sql-tools';
|
||||
import { UserTable } from 'src/tables/user.table';
|
||||
|
||||
@Table('api_keys')
|
||||
export class APIKeyTable {
|
@ -1,7 +1,7 @@
|
||||
import { SourceType } from 'src/enum';
|
||||
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 { 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'] })
|
@ -1,5 +1,5 @@
|
||||
import { AssetTable } from 'src/schema/tables/asset.table';
|
||||
import { Column, ForeignKeyColumn, Table } from 'src/sql-tools';
|
||||
import { AssetTable } from 'src/tables/asset.table';
|
||||
|
||||
@Table('asset_job_status')
|
||||
export class AssetJobStatusTable {
|
@ -1,5 +1,8 @@
|
||||
import { ASSET_CHECKSUM_CONSTRAINT } from 'src/entities/asset.entity';
|
||||
import { AssetStatus, AssetType } from 'src/enum';
|
||||
import { LibraryTable } from 'src/schema/tables/library.table';
|
||||
import { StackTable } from 'src/schema/tables/stack.table';
|
||||
import { UserTable } from 'src/schema/tables/user.table';
|
||||
import {
|
||||
Column,
|
||||
ColumnIndex,
|
||||
@ -12,9 +15,6 @@ import {
|
||||
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
|
@ -1,5 +1,5 @@
|
||||
import { AssetTable } from 'src/schema/tables/asset.table';
|
||||
import { Column, ColumnIndex, ForeignKeyColumn, Table, UpdateDateColumn, UpdateIdColumn } from 'src/sql-tools';
|
||||
import { AssetTable } from 'src/tables/asset.table';
|
||||
|
||||
@Table('exif')
|
||||
export class ExifTable {
|
@ -1,5 +1,5 @@
|
||||
import { AssetFaceTable } from 'src/schema/tables/asset-face.table';
|
||||
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 {
|
73
server/src/schema/tables/index.ts
Normal file
73
server/src/schema/tables/index.ts
Normal file
@ -0,0 +1,73 @@
|
||||
import { ActivityTable } from 'src/schema/tables/activity.table';
|
||||
import { AlbumAssetTable } from 'src/schema/tables/album-asset.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 { AssetAuditTable } from 'src/schema/tables/asset-audit.table';
|
||||
import { AssetFaceTable } from 'src/schema/tables/asset-face.table';
|
||||
import { AssetJobStatusTable } from 'src/schema/tables/asset-job-status.table';
|
||||
import { AssetTable } from 'src/schema/tables/asset.table';
|
||||
import { AuditTable } from 'src/schema/tables/audit.table';
|
||||
import { ExifTable } from 'src/schema/tables/exif.table';
|
||||
import { FaceSearchTable } from 'src/schema/tables/face-search.table';
|
||||
import { GeodataPlacesTable } from 'src/schema/tables/geodata-places.table';
|
||||
import { LibraryTable } from 'src/schema/tables/library.table';
|
||||
import { MemoryTable } from 'src/schema/tables/memory.table';
|
||||
import { MemoryAssetTable } from 'src/schema/tables/memory_asset.table';
|
||||
import { MoveTable } from 'src/schema/tables/move.table';
|
||||
import {
|
||||
NaturalEarthCountriesTable,
|
||||
NaturalEarthCountriesTempTable,
|
||||
} from 'src/schema/tables/natural-earth-countries.table';
|
||||
import { PartnerAuditTable } from 'src/schema/tables/partner-audit.table';
|
||||
import { PartnerTable } from 'src/schema/tables/partner.table';
|
||||
import { PersonTable } from 'src/schema/tables/person.table';
|
||||
import { SessionTable } from 'src/schema/tables/session.table';
|
||||
import { SharedLinkAssetTable } from 'src/schema/tables/shared-link-asset.table';
|
||||
import { SharedLinkTable } from 'src/schema/tables/shared-link.table';
|
||||
import { SmartSearchTable } from 'src/schema/tables/smart-search.table';
|
||||
import { StackTable } from 'src/schema/tables/stack.table';
|
||||
import { SessionSyncCheckpointTable } from 'src/schema/tables/sync-checkpoint.table';
|
||||
import { SystemMetadataTable } from 'src/schema/tables/system-metadata.table';
|
||||
import { TagAssetTable } from 'src/schema/tables/tag-asset.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';
|
||||
|
||||
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,
|
||||
];
|
@ -1,3 +1,4 @@
|
||||
import { UserTable } from 'src/schema/tables/user.table';
|
||||
import {
|
||||
Column,
|
||||
ColumnIndex,
|
||||
@ -9,7 +10,6 @@ import {
|
||||
UpdateDateColumn,
|
||||
UpdateIdColumn,
|
||||
} from 'src/sql-tools';
|
||||
import { UserTable } from 'src/tables/user.table';
|
||||
|
||||
@Table('libraries')
|
||||
export class LibraryTable {
|
@ -1,4 +1,5 @@
|
||||
import { MemoryType } from 'src/enum';
|
||||
import { UserTable } from 'src/schema/tables/user.table';
|
||||
import {
|
||||
Column,
|
||||
ColumnIndex,
|
||||
@ -10,7 +11,6 @@ import {
|
||||
UpdateDateColumn,
|
||||
UpdateIdColumn,
|
||||
} from 'src/sql-tools';
|
||||
import { UserTable } from 'src/tables/user.table';
|
||||
import { MemoryData } from 'src/types';
|
||||
|
||||
@Table('memories')
|
@ -1,6 +1,6 @@
|
||||
import { AssetTable } from 'src/schema/tables/asset.table';
|
||||
import { MemoryTable } from 'src/schema/tables/memory.table';
|
||||
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 {
|
@ -1,3 +1,4 @@
|
||||
import { UserTable } from 'src/schema/tables/user.table';
|
||||
import {
|
||||
Column,
|
||||
ColumnIndex,
|
||||
@ -7,7 +8,6 @@ import {
|
||||
UpdateDateColumn,
|
||||
UpdateIdColumn,
|
||||
} from 'src/sql-tools';
|
||||
import { UserTable } from 'src/tables/user.table';
|
||||
|
||||
@Table('partners')
|
||||
export class PartnerTable {
|
@ -1,3 +1,5 @@
|
||||
import { AssetFaceTable } from 'src/schema/tables/asset-face.table';
|
||||
import { UserTable } from 'src/schema/tables/user.table';
|
||||
import {
|
||||
Check,
|
||||
Column,
|
||||
@ -9,8 +11,6 @@ import {
|
||||
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` })
|
@ -1,3 +1,4 @@
|
||||
import { UserTable } from 'src/schema/tables/user.table';
|
||||
import {
|
||||
Column,
|
||||
ColumnIndex,
|
||||
@ -8,7 +9,6 @@ import {
|
||||
UpdateDateColumn,
|
||||
UpdateIdColumn,
|
||||
} from 'src/sql-tools';
|
||||
import { UserTable } from 'src/tables/user.table';
|
||||
|
||||
@Table({ name: 'sessions', primaryConstraintName: 'PK_48cb6b5c20faa63157b3c1baf7f' })
|
||||
export class SessionTable {
|
@ -1,6 +1,6 @@
|
||||
import { AssetTable } from 'src/schema/tables/asset.table';
|
||||
import { SharedLinkTable } from 'src/schema/tables/shared-link.table';
|
||||
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 {
|
@ -1,4 +1,6 @@
|
||||
import { SharedLinkType } from 'src/enum';
|
||||
import { AlbumTable } from 'src/schema/tables/album.table';
|
||||
import { UserTable } from 'src/schema/tables/user.table';
|
||||
import {
|
||||
Column,
|
||||
ColumnIndex,
|
||||
@ -8,8 +10,6 @@ import {
|
||||
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'] })
|
@ -1,5 +1,5 @@
|
||||
import { AssetTable } from 'src/schema/tables/asset.table';
|
||||
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 {
|
@ -1,6 +1,6 @@
|
||||
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 { AssetTable } from 'src/tables/asset.table';
|
||||
import { UserTable } from 'src/tables/user.table';
|
||||
|
||||
@Table('asset_stack')
|
||||
export class StackTable {
|
@ -1,4 +1,5 @@
|
||||
import { SyncEntityType } from 'src/enum';
|
||||
import { SessionTable } from 'src/schema/tables/session.table';
|
||||
import {
|
||||
Column,
|
||||
ColumnIndex,
|
||||
@ -9,7 +10,6 @@ import {
|
||||
UpdateDateColumn,
|
||||
UpdateIdColumn,
|
||||
} from 'src/sql-tools';
|
||||
import { SessionTable } from 'src/tables/session.table';
|
||||
|
||||
@Table('session_sync_checkpoints')
|
||||
export class SessionSyncCheckpointTable {
|
@ -1,6 +1,6 @@
|
||||
import { AssetTable } from 'src/schema/tables/asset.table';
|
||||
import { TagTable } from 'src/schema/tables/tag.table';
|
||||
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')
|
@ -1,5 +1,5 @@
|
||||
import { TagTable } from 'src/schema/tables/tag.table';
|
||||
import { ColumnIndex, ForeignKeyColumn, PrimaryColumn, Table } from 'src/sql-tools';
|
||||
import { TagTable } from 'src/tables/tag.table';
|
||||
|
||||
@Table('tags_closure')
|
||||
export class TagClosureTable {
|
@ -1,3 +1,4 @@
|
||||
import { UserTable } from 'src/schema/tables/user.table';
|
||||
import {
|
||||
Column,
|
||||
ColumnIndex,
|
||||
@ -9,7 +10,6 @@ import {
|
||||
UpdateDateColumn,
|
||||
UpdateIdColumn,
|
||||
} from 'src/sql-tools';
|
||||
import { UserTable } from 'src/tables/user.table';
|
||||
|
||||
@Table('tags')
|
||||
@Unique({ columns: ['userId', 'value'] })
|
@ -1,7 +1,7 @@
|
||||
import { UserMetadata, UserMetadataItem } from 'src/entities/user-metadata.entity';
|
||||
import { UserMetadataKey } from 'src/enum';
|
||||
import { UserTable } from 'src/schema/tables/user.table';
|
||||
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> {
|
@ -46,7 +46,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 { UserTable } from 'src/tables/user.table';
|
||||
import { UserTable } from 'src/schema/tables/user.table';
|
||||
import { AccessRequest, checkAccess, requireAccess } from 'src/utils/access';
|
||||
import { getConfig, updateConfig } from 'src/utils/config';
|
||||
|
||||
|
@ -1,70 +0,0 @@
|
||||
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,
|
||||
];
|
@ -35,7 +35,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 { UserTable } from 'src/tables/user.table';
|
||||
import { UserTable } from 'src/schema/tables/user.table';
|
||||
import { newTelemetryRepositoryMock } from 'test/repositories/telemetry.repository.mock';
|
||||
import { newUuid } from 'test/small.factory';
|
||||
import { automock } from 'test/utils';
|
||||
|
@ -1,8 +1,14 @@
|
||||
import { FileMigrationProvider, Kysely, Migrator } from 'kysely';
|
||||
import { PostgresJSDialect } from 'kysely-postgres-js';
|
||||
import { mkdir, readdir } from 'node:fs/promises';
|
||||
import { join } from 'node:path';
|
||||
import { parse } from 'pg-connection-string';
|
||||
import postgres, { Notice } from 'postgres';
|
||||
import { GenericContainer, Wait } from 'testcontainers';
|
||||
import { DataSource } from 'typeorm';
|
||||
|
||||
const globalSetup = async () => {
|
||||
const postgres = await new GenericContainer('tensorchord/pgvecto-rs:pg14-v0.2.0')
|
||||
const postgresContainer = await new GenericContainer('tensorchord/pgvecto-rs:pg14-v0.2.0')
|
||||
.withExposedPorts(5432)
|
||||
.withEnvironment({
|
||||
POSTGRES_PASSWORD: 'postgres',
|
||||
@ -29,7 +35,7 @@ const globalSetup = async () => {
|
||||
.withWaitStrategy(Wait.forAll([Wait.forLogMessage('database system is ready to accept connections', 2)]))
|
||||
.start();
|
||||
|
||||
const postgresPort = postgres.getMappedPort(5432);
|
||||
const postgresPort = postgresContainer.getMappedPort(5432);
|
||||
const postgresUrl = `postgres://postgres:postgres@localhost:${postgresPort}/immich`;
|
||||
process.env.IMMICH_TEST_POSTGRES_URL = postgresUrl;
|
||||
|
||||
@ -55,6 +61,73 @@ const globalSetup = async () => {
|
||||
await dataSource.initialize();
|
||||
await dataSource.runMigrations();
|
||||
await dataSource.destroy();
|
||||
|
||||
// for whatever reason, importing from test/utils causes vitest to crash
|
||||
// eslint-disable-next-line unicorn/prefer-module
|
||||
const migrationFolder = join(__dirname, '..', 'schema/migrations');
|
||||
// TODO remove after we have at least one kysely migration
|
||||
await mkdir(migrationFolder, { recursive: true });
|
||||
|
||||
const parsed = parse(process.env.IMMICH_TEST_POSTGRES_URL!);
|
||||
|
||||
const parsedOptions = {
|
||||
...parsed,
|
||||
ssl: false,
|
||||
host: parsed.host ?? undefined,
|
||||
port: parsed.port ? Number(parsed.port) : undefined,
|
||||
database: parsed.database ?? undefined,
|
||||
};
|
||||
|
||||
const driverOptions = {
|
||||
...parsedOptions,
|
||||
onnotice: (notice: Notice) => {
|
||||
if (notice['severity'] !== 'NOTICE') {
|
||||
console.warn('Postgres notice:', notice);
|
||||
}
|
||||
},
|
||||
max: 10,
|
||||
types: {
|
||||
date: {
|
||||
to: 1184,
|
||||
from: [1082, 1114, 1184],
|
||||
serialize: (x: Date | string) => (x instanceof Date ? x.toISOString() : x),
|
||||
parse: (x: string) => new Date(x),
|
||||
},
|
||||
bigint: {
|
||||
to: 20,
|
||||
from: [20],
|
||||
parse: (value: string) => Number.parseInt(value),
|
||||
serialize: (value: number) => value.toString(),
|
||||
},
|
||||
},
|
||||
connection: {
|
||||
TimeZone: 'UTC',
|
||||
},
|
||||
};
|
||||
|
||||
const db = new Kysely({
|
||||
dialect: new PostgresJSDialect({ postgres: postgres({ ...driverOptions, max: 1, database: 'postgres' }) }),
|
||||
});
|
||||
|
||||
// TODO just call `databaseRepository.migrate()` (probably have to wait until TypeOrm is gone)
|
||||
const migrator = new Migrator({
|
||||
db,
|
||||
migrationLockTableName: 'kysely_migrations_lock',
|
||||
migrationTableName: 'kysely_migrations',
|
||||
provider: new FileMigrationProvider({
|
||||
fs: { readdir },
|
||||
path: { join },
|
||||
migrationFolder,
|
||||
}),
|
||||
});
|
||||
|
||||
const { error } = await migrator.migrateToLatest();
|
||||
if (error) {
|
||||
console.error('Unable to run kysely migrations', error);
|
||||
throw error;
|
||||
}
|
||||
|
||||
await db.destroy();
|
||||
};
|
||||
|
||||
export default globalSetup;
|
||||
|
Loading…
x
Reference in New Issue
Block a user