Skip to content

Commit b70814c

Browse files
committed
feat: kysely migrations
1 parent d2bcf5d commit b70814c

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+267
-126
lines changed

server/src/bin/migrations.ts

+33-10
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ process.env.DB_URL = 'postgres://postgres:postgres@localhost:5432/immich';
44
import { writeFileSync } from 'node:fs';
55
import postgres from 'postgres';
66
import { ConfigRepository } from 'src/repositories/config.repository';
7+
import 'src/schema/tables';
78
import { DatabaseTable, schemaDiff, schemaFromDatabase, schemaFromDecorators } from 'src/sql-tools';
8-
import 'src/tables';
99

1010
const main = async () => {
1111
const command = process.argv[2];
@@ -54,9 +54,10 @@ const generate = async (name: string) => {
5454
};
5555

5656
const create = (name: string, up: string[], down: string[]) => {
57-
const { filename, code } = asMigration(name, up, down);
57+
const timestamp = Date.now();
58+
const filename = `${timestamp}-${name}.ts`;
5859
const fullPath = `./src/${filename}`;
59-
writeFileSync(fullPath, code);
60+
writeFileSync(fullPath, asMigration('kysely', { name, timestamp, up, down }));
6061
console.log(`Wrote ${fullPath}`);
6162
};
6263

@@ -79,14 +80,21 @@ const compare = async () => {
7980
return { up, down };
8081
};
8182

82-
const asMigration = (name: string, up: string[], down: string[]) => {
83-
const timestamp = Date.now();
83+
type MigrationProps = {
84+
name: string;
85+
timestamp: number;
86+
up: string[];
87+
down: string[];
88+
};
89+
90+
const asMigration = (type: 'kysely' | 'typeorm', options: MigrationProps) =>
91+
type === 'typeorm' ? asTypeOrmMigration(options) : asKyselyMigration(options);
8492

93+
const asTypeOrmMigration = ({ timestamp, name, up, down }: MigrationProps) => {
8594
const upSql = up.map((sql) => ` await queryRunner.query(\`${sql}\`);`).join('\n');
8695
const downSql = down.map((sql) => ` await queryRunner.query(\`${sql}\`);`).join('\n');
87-
return {
88-
filename: `${timestamp}-${name}.ts`,
89-
code: `import { MigrationInterface, QueryRunner } from 'typeorm';
96+
97+
return `import { MigrationInterface, QueryRunner } from 'typeorm';
9098
9199
export class ${name}${timestamp} implements MigrationInterface {
92100
public async up(queryRunner: QueryRunner): Promise<void> {
@@ -97,8 +105,23 @@ ${upSql}
97105
${downSql}
98106
}
99107
}
100-
`,
101-
};
108+
`;
109+
};
110+
111+
const asKyselyMigration = ({ up, down }: MigrationProps) => {
112+
const upSql = up.map((sql) => ` await sql\`${sql}\`.execute(db);`).join('\n');
113+
const downSql = down.map((sql) => ` await sql\`${sql}\`.execute(db);`).join('\n');
114+
115+
return `import { Kysely, sql } from 'kysely';
116+
117+
export async function up(db: Kysely<any>): Promise<void> {
118+
${upSql}
119+
}
120+
121+
export async function down(db: Kysely<any>): Promise<void> {
122+
${downSql}
123+
}
124+
`;
102125
};
103126

104127
main()

server/src/db.d.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
import type { ColumnType } from 'kysely';
77
import { AssetType, MemoryType, Permission, SyncEntityType } from 'src/enum';
8-
import { UserTable } from 'src/tables/user.table';
8+
import { UserTable } from 'src/schema/tables/user.table';
99
import { OnThisDayData } from 'src/types';
1010

1111
export type ArrayType<T> = ArrayTypeImpl<T> extends (infer U)[] ? U[] : ArrayTypeImpl<T>;

server/src/repositories/database.repository.ts

+43-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import { Injectable } from '@nestjs/common';
22
import AsyncLock from 'async-lock';
3-
import { Kysely, sql, Transaction } from 'kysely';
3+
import { FileMigrationProvider, Kysely, Migrator, sql, Transaction } from 'kysely';
44
import { InjectKysely } from 'nestjs-kysely';
5+
import { mkdir, readdir } from 'node:fs/promises';
6+
import { join } from 'node:path';
57
import semver from 'semver';
68
import { EXTENSION_NAMES, POSTGRES_VERSION_RANGE, VECTOR_VERSION_RANGE, VECTORS_VERSION_RANGE } from 'src/constants';
79
import { DB } from 'src/db';
@@ -200,9 +202,49 @@ export class DatabaseRepository {
200202

201203
this.logger.log('Running migrations, this may take a while');
202204

205+
this.logger.debug('Running typeorm migrations');
206+
203207
await dataSource.initialize();
204208
await dataSource.runMigrations(options);
205209
await dataSource.destroy();
210+
211+
this.logger.debug('Finished running typeorm migrations');
212+
213+
// eslint-disable-next-line unicorn/prefer-module
214+
const migrationFolder = join(__dirname, '..', 'schema/migrations');
215+
// TODO remove after we have at least one kysely migration
216+
await mkdir(migrationFolder, { recursive: true });
217+
218+
this.logger.debug('Running kysely migrations');
219+
const migrator = new Migrator({
220+
db: this.db,
221+
migrationLockTableName: 'kysely_migrations_lock',
222+
migrationTableName: 'kysely_migrations',
223+
provider: new FileMigrationProvider({
224+
fs: { readdir },
225+
path: { join },
226+
migrationFolder,
227+
}),
228+
});
229+
230+
const { error, results } = await migrator.migrateToLatest();
231+
232+
for (const result of results ?? []) {
233+
if (result.status === 'Success') {
234+
this.logger.log(`Migration "${result.migrationName}" succeeded`);
235+
}
236+
237+
if (result.status === 'Error') {
238+
this.logger.warn(`Migration "${result.migrationName}" failed`);
239+
}
240+
}
241+
242+
if (error) {
243+
this.logger.error(`Kysely migrations failed: ${error}`);
244+
throw error;
245+
}
246+
247+
this.logger.debug('Finished running kysely migrations');
206248
}
207249

208250
async withLock<R>(lock: DatabaseLock, callback: () => Promise<R>): Promise<R> {

server/src/repositories/user.repository.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { DummyValue, GenerateSql } from 'src/decorators';
88
import { UserMetadata, UserMetadataItem } from 'src/entities/user-metadata.entity';
99
import { UserEntity, withMetadata } from 'src/entities/user.entity';
1010
import { AssetType, UserStatus } from 'src/enum';
11-
import { UserTable } from 'src/tables/user.table';
11+
import { UserTable } from 'src/schema/tables/user.table';
1212
import { asUuid } from 'src/utils/database';
1313

1414
type Upsert = Insertable<DbUserMetadata>;

server/src/tables/activity.table.ts server/src/schema/tables/activity.table.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
import { AlbumTable } from 'src/schema/tables/album.table';
2+
import { AssetTable } from 'src/schema/tables/asset.table';
3+
import { UserTable } from 'src/schema/tables/user.table';
14
import {
25
Check,
36
Column,
@@ -10,9 +13,6 @@ import {
1013
UpdateDateColumn,
1114
UpdateIdColumn,
1215
} from 'src/sql-tools';
13-
import { AlbumTable } from 'src/tables/album.table';
14-
import { AssetTable } from 'src/tables/asset.table';
15-
import { UserTable } from 'src/tables/user.table';
1616

1717
@Table('activity')
1818
@Index({

server/src/tables/album-asset.table.ts server/src/schema/tables/album-asset.table.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1+
import { AlbumTable } from 'src/schema/tables/album.table';
2+
import { AssetTable } from 'src/schema/tables/asset.table';
13
import { ColumnIndex, CreateDateColumn, ForeignKeyColumn, Table } from 'src/sql-tools';
2-
import { AlbumTable } from 'src/tables/album.table';
3-
import { AssetTable } from 'src/tables/asset.table';
44

55
@Table({ name: 'albums_assets_assets', primaryConstraintName: 'PK_c67bc36fa845fb7b18e0e398180' })
66
export class AlbumAssetTable {

server/src/tables/album-user.table.ts server/src/schema/tables/album-user.table.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { AlbumUserRole } from 'src/enum';
2+
import { AlbumTable } from 'src/schema/tables/album.table';
3+
import { UserTable } from 'src/schema/tables/user.table';
24
import { Column, ForeignKeyColumn, Index, Table } from 'src/sql-tools';
3-
import { AlbumTable } from 'src/tables/album.table';
4-
import { UserTable } from 'src/tables/user.table';
55

66
@Table({ name: 'albums_shared_users_users', primaryConstraintName: 'PK_7df55657e0b2e8b626330a0ebc8' })
77
// Pre-existing indices from original album <--> user ManyToMany mapping

server/src/tables/album.table.ts server/src/schema/tables/album.table.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import { AssetOrder } from 'src/enum';
2+
import { AssetTable } from 'src/schema/tables/asset.table';
3+
import { UserTable } from 'src/schema/tables/user.table';
24
import {
35
Column,
46
ColumnIndex,
@@ -10,8 +12,6 @@ import {
1012
UpdateDateColumn,
1113
UpdateIdColumn,
1214
} from 'src/sql-tools';
13-
import { AssetTable } from 'src/tables/asset.table';
14-
import { UserTable } from 'src/tables/user.table';
1515

1616
@Table({ name: 'albums', primaryConstraintName: 'PK_7f71c7b5bc7c87b8f94c9a93a00' })
1717
export class AlbumTable {

server/src/tables/api-key.table.ts server/src/schema/tables/api-key.table.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { Permission } from 'src/enum';
2+
import { UserTable } from 'src/schema/tables/user.table';
23
import {
34
Column,
45
ColumnIndex,
@@ -9,7 +10,6 @@ import {
910
UpdateDateColumn,
1011
UpdateIdColumn,
1112
} from 'src/sql-tools';
12-
import { UserTable } from 'src/tables/user.table';
1313

1414
@Table('api_keys')
1515
export class APIKeyTable {

server/src/tables/asset-face.table.ts server/src/schema/tables/asset-face.table.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { SourceType } from 'src/enum';
2+
import { AssetTable } from 'src/schema/tables/asset.table';
3+
import { PersonTable } from 'src/schema/tables/person.table';
24
import { Column, DeleteDateColumn, ForeignKeyColumn, Index, PrimaryGeneratedColumn, Table } from 'src/sql-tools';
3-
import { AssetTable } from 'src/tables/asset.table';
4-
import { PersonTable } from 'src/tables/person.table';
55

66
@Table({ name: 'asset_faces' })
77
@Index({ name: 'IDX_asset_faces_assetId_personId', columns: ['assetId', 'personId'] })

server/src/tables/asset-job-status.table.ts server/src/schema/tables/asset-job-status.table.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1+
import { AssetTable } from 'src/schema/tables/asset.table';
12
import { Column, ForeignKeyColumn, Table } from 'src/sql-tools';
2-
import { AssetTable } from 'src/tables/asset.table';
33

44
@Table('asset_job_status')
55
export class AssetJobStatusTable {

server/src/tables/asset.table.ts server/src/schema/tables/asset.table.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
import { ASSET_CHECKSUM_CONSTRAINT } from 'src/entities/asset.entity';
22
import { AssetStatus, AssetType } from 'src/enum';
3+
import { LibraryTable } from 'src/schema/tables/library.table';
4+
import { StackTable } from 'src/schema/tables/stack.table';
5+
import { UserTable } from 'src/schema/tables/user.table';
36
import {
47
Column,
58
ColumnIndex,
@@ -12,9 +15,6 @@ import {
1215
UpdateDateColumn,
1316
UpdateIdColumn,
1417
} from 'src/sql-tools';
15-
import { LibraryTable } from 'src/tables/library.table';
16-
import { StackTable } from 'src/tables/stack.table';
17-
import { UserTable } from 'src/tables/user.table';
1818

1919
@Table('assets')
2020
// Checksums must be unique per user and library
File renamed without changes.

server/src/tables/exif.table.ts server/src/schema/tables/exif.table.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1+
import { AssetTable } from 'src/schema/tables/asset.table';
12
import { Column, ColumnIndex, ForeignKeyColumn, Table, UpdateDateColumn, UpdateIdColumn } from 'src/sql-tools';
2-
import { AssetTable } from 'src/tables/asset.table';
33

44
@Table('exif')
55
export class ExifTable {

server/src/tables/face-search.table.ts server/src/schema/tables/face-search.table.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1+
import { AssetFaceTable } from 'src/schema/tables/asset-face.table';
12
import { Column, ColumnIndex, ForeignKeyColumn, Table } from 'src/sql-tools';
2-
import { AssetFaceTable } from 'src/tables/asset-face.table';
33

44
@Table({ name: 'face_search', primaryConstraintName: 'face_search_pkey' })
55
export class FaceSearchTable {

server/src/schema/tables/index.ts

+73
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import { ActivityTable } from 'src/schema/tables/activity.table';
2+
import { AlbumAssetTable } from 'src/schema/tables/album-asset.table';
3+
import { AlbumUserTable } from 'src/schema/tables/album-user.table';
4+
import { AlbumTable } from 'src/schema/tables/album.table';
5+
import { APIKeyTable } from 'src/schema/tables/api-key.table';
6+
import { AssetAuditTable } from 'src/schema/tables/asset-audit.table';
7+
import { AssetFaceTable } from 'src/schema/tables/asset-face.table';
8+
import { AssetJobStatusTable } from 'src/schema/tables/asset-job-status.table';
9+
import { AssetTable } from 'src/schema/tables/asset.table';
10+
import { AuditTable } from 'src/schema/tables/audit.table';
11+
import { ExifTable } from 'src/schema/tables/exif.table';
12+
import { FaceSearchTable } from 'src/schema/tables/face-search.table';
13+
import { GeodataPlacesTable } from 'src/schema/tables/geodata-places.table';
14+
import { LibraryTable } from 'src/schema/tables/library.table';
15+
import { MemoryTable } from 'src/schema/tables/memory.table';
16+
import { MemoryAssetTable } from 'src/schema/tables/memory_asset.table';
17+
import { MoveTable } from 'src/schema/tables/move.table';
18+
import {
19+
NaturalEarthCountriesTable,
20+
NaturalEarthCountriesTempTable,
21+
} from 'src/schema/tables/natural-earth-countries.table';
22+
import { PartnerAuditTable } from 'src/schema/tables/partner-audit.table';
23+
import { PartnerTable } from 'src/schema/tables/partner.table';
24+
import { PersonTable } from 'src/schema/tables/person.table';
25+
import { SessionTable } from 'src/schema/tables/session.table';
26+
import { SharedLinkAssetTable } from 'src/schema/tables/shared-link-asset.table';
27+
import { SharedLinkTable } from 'src/schema/tables/shared-link.table';
28+
import { SmartSearchTable } from 'src/schema/tables/smart-search.table';
29+
import { StackTable } from 'src/schema/tables/stack.table';
30+
import { SessionSyncCheckpointTable } from 'src/schema/tables/sync-checkpoint.table';
31+
import { SystemMetadataTable } from 'src/schema/tables/system-metadata.table';
32+
import { TagAssetTable } from 'src/schema/tables/tag-asset.table';
33+
import { UserAuditTable } from 'src/schema/tables/user-audit.table';
34+
import { UserMetadataTable } from 'src/schema/tables/user-metadata.table';
35+
import { UserTable } from 'src/schema/tables/user.table';
36+
import { VersionHistoryTable } from 'src/schema/tables/version-history.table';
37+
38+
export const tables = [
39+
ActivityTable,
40+
AlbumAssetTable,
41+
AlbumUserTable,
42+
AlbumTable,
43+
APIKeyTable,
44+
AssetAuditTable,
45+
AssetFaceTable,
46+
AssetJobStatusTable,
47+
AssetTable,
48+
AuditTable,
49+
ExifTable,
50+
FaceSearchTable,
51+
GeodataPlacesTable,
52+
LibraryTable,
53+
MemoryAssetTable,
54+
MemoryTable,
55+
MoveTable,
56+
NaturalEarthCountriesTable,
57+
NaturalEarthCountriesTempTable,
58+
PartnerAuditTable,
59+
PartnerTable,
60+
PersonTable,
61+
SessionTable,
62+
SharedLinkAssetTable,
63+
SharedLinkTable,
64+
SmartSearchTable,
65+
StackTable,
66+
SessionSyncCheckpointTable,
67+
SystemMetadataTable,
68+
TagAssetTable,
69+
UserAuditTable,
70+
UserMetadataTable,
71+
UserTable,
72+
VersionHistoryTable,
73+
];

server/src/tables/library.table.ts server/src/schema/tables/library.table.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { UserTable } from 'src/schema/tables/user.table';
12
import {
23
Column,
34
ColumnIndex,
@@ -9,7 +10,6 @@ import {
910
UpdateDateColumn,
1011
UpdateIdColumn,
1112
} from 'src/sql-tools';
12-
import { UserTable } from 'src/tables/user.table';
1313

1414
@Table('libraries')
1515
export class LibraryTable {

server/src/tables/memory.table.ts server/src/schema/tables/memory.table.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { MemoryType } from 'src/enum';
2+
import { UserTable } from 'src/schema/tables/user.table';
23
import {
34
Column,
45
ColumnIndex,
@@ -10,7 +11,6 @@ import {
1011
UpdateDateColumn,
1112
UpdateIdColumn,
1213
} from 'src/sql-tools';
13-
import { UserTable } from 'src/tables/user.table';
1414
import { MemoryData } from 'src/types';
1515

1616
@Table('memories')

server/src/tables/memory_asset.table.ts server/src/schema/tables/memory_asset.table.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1+
import { AssetTable } from 'src/schema/tables/asset.table';
2+
import { MemoryTable } from 'src/schema/tables/memory.table';
13
import { ColumnIndex, ForeignKeyColumn, Table } from 'src/sql-tools';
2-
import { AssetTable } from 'src/tables/asset.table';
3-
import { MemoryTable } from 'src/tables/memory.table';
44

55
@Table('memories_assets_assets')
66
export class MemoryAssetTable {
File renamed without changes.

server/src/tables/partner.table.ts server/src/schema/tables/partner.table.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { UserTable } from 'src/schema/tables/user.table';
12
import {
23
Column,
34
ColumnIndex,
@@ -7,7 +8,6 @@ import {
78
UpdateDateColumn,
89
UpdateIdColumn,
910
} from 'src/sql-tools';
10-
import { UserTable } from 'src/tables/user.table';
1111

1212
@Table('partners')
1313
export class PartnerTable {

0 commit comments

Comments
 (0)