Compare commits
4 Commits
v1.135.0
...
fix/map-pa
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2c7a29b2f4 | ||
|
|
0a9a520ed2 | ||
|
|
de81006367 | ||
|
|
e0144b4ece |
3
mobile/openapi/lib/model/sync_entity_type.dart
generated
3
mobile/openapi/lib/model/sync_entity_type.dart
generated
@@ -38,6 +38,7 @@ class SyncEntityType {
|
|||||||
static const albumV1 = SyncEntityType._(r'AlbumV1');
|
static const albumV1 = SyncEntityType._(r'AlbumV1');
|
||||||
static const albumDeleteV1 = SyncEntityType._(r'AlbumDeleteV1');
|
static const albumDeleteV1 = SyncEntityType._(r'AlbumDeleteV1');
|
||||||
static const albumUserV1 = SyncEntityType._(r'AlbumUserV1');
|
static const albumUserV1 = SyncEntityType._(r'AlbumUserV1');
|
||||||
|
static const albumUserBackfillV1 = SyncEntityType._(r'AlbumUserBackfillV1');
|
||||||
static const albumUserDeleteV1 = SyncEntityType._(r'AlbumUserDeleteV1');
|
static const albumUserDeleteV1 = SyncEntityType._(r'AlbumUserDeleteV1');
|
||||||
static const syncAckV1 = SyncEntityType._(r'SyncAckV1');
|
static const syncAckV1 = SyncEntityType._(r'SyncAckV1');
|
||||||
|
|
||||||
@@ -58,6 +59,7 @@ class SyncEntityType {
|
|||||||
albumV1,
|
albumV1,
|
||||||
albumDeleteV1,
|
albumDeleteV1,
|
||||||
albumUserV1,
|
albumUserV1,
|
||||||
|
albumUserBackfillV1,
|
||||||
albumUserDeleteV1,
|
albumUserDeleteV1,
|
||||||
syncAckV1,
|
syncAckV1,
|
||||||
];
|
];
|
||||||
@@ -113,6 +115,7 @@ class SyncEntityTypeTypeTransformer {
|
|||||||
case r'AlbumV1': return SyncEntityType.albumV1;
|
case r'AlbumV1': return SyncEntityType.albumV1;
|
||||||
case r'AlbumDeleteV1': return SyncEntityType.albumDeleteV1;
|
case r'AlbumDeleteV1': return SyncEntityType.albumDeleteV1;
|
||||||
case r'AlbumUserV1': return SyncEntityType.albumUserV1;
|
case r'AlbumUserV1': return SyncEntityType.albumUserV1;
|
||||||
|
case r'AlbumUserBackfillV1': return SyncEntityType.albumUserBackfillV1;
|
||||||
case r'AlbumUserDeleteV1': return SyncEntityType.albumUserDeleteV1;
|
case r'AlbumUserDeleteV1': return SyncEntityType.albumUserDeleteV1;
|
||||||
case r'SyncAckV1': return SyncEntityType.syncAckV1;
|
case r'SyncAckV1': return SyncEntityType.syncAckV1;
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -13706,6 +13706,7 @@
|
|||||||
"AlbumV1",
|
"AlbumV1",
|
||||||
"AlbumDeleteV1",
|
"AlbumDeleteV1",
|
||||||
"AlbumUserV1",
|
"AlbumUserV1",
|
||||||
|
"AlbumUserBackfillV1",
|
||||||
"AlbumUserDeleteV1",
|
"AlbumUserDeleteV1",
|
||||||
"SyncAckV1"
|
"SyncAckV1"
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -4059,6 +4059,7 @@ export enum SyncEntityType {
|
|||||||
AlbumV1 = "AlbumV1",
|
AlbumV1 = "AlbumV1",
|
||||||
AlbumDeleteV1 = "AlbumDeleteV1",
|
AlbumDeleteV1 = "AlbumDeleteV1",
|
||||||
AlbumUserV1 = "AlbumUserV1",
|
AlbumUserV1 = "AlbumUserV1",
|
||||||
|
AlbumUserBackfillV1 = "AlbumUserBackfillV1",
|
||||||
AlbumUserDeleteV1 = "AlbumUserDeleteV1",
|
AlbumUserDeleteV1 = "AlbumUserDeleteV1",
|
||||||
SyncAckV1 = "SyncAckV1"
|
SyncAckV1 = "SyncAckV1"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -355,6 +355,12 @@ export const columns = {
|
|||||||
'updateId',
|
'updateId',
|
||||||
'duration',
|
'duration',
|
||||||
],
|
],
|
||||||
|
syncAlbumUser: [
|
||||||
|
'albums_shared_users_users.albumsId as albumId',
|
||||||
|
'albums_shared_users_users.usersId as userId',
|
||||||
|
'albums_shared_users_users.role',
|
||||||
|
'albums_shared_users_users.updateId',
|
||||||
|
],
|
||||||
stack: ['stack.id', 'stack.primaryAssetId', 'ownerId'],
|
stack: ['stack.id', 'stack.primaryAssetId', 'ownerId'],
|
||||||
syncAssetExif: [
|
syncAssetExif: [
|
||||||
'exif.assetId',
|
'exif.assetId',
|
||||||
|
|||||||
4
server/src/db.d.ts
vendored
4
server/src/db.d.ts
vendored
@@ -98,8 +98,10 @@ export interface AlbumsSharedUsersUsers {
|
|||||||
albumsId: string;
|
albumsId: string;
|
||||||
role: Generated<AlbumUserRole>;
|
role: Generated<AlbumUserRole>;
|
||||||
usersId: string;
|
usersId: string;
|
||||||
updatedAt: Generated<Timestamp>;
|
createId: Generated<string>;
|
||||||
|
createdAt: Generated<Timestamp>;
|
||||||
updateId: Generated<string>;
|
updateId: Generated<string>;
|
||||||
|
updatedAt: Generated<Timestamp>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ApiKeys {
|
export interface ApiKeys {
|
||||||
|
|||||||
@@ -161,6 +161,7 @@ export type SyncItem = {
|
|||||||
[SyncEntityType.AlbumV1]: SyncAlbumV1;
|
[SyncEntityType.AlbumV1]: SyncAlbumV1;
|
||||||
[SyncEntityType.AlbumDeleteV1]: SyncAlbumDeleteV1;
|
[SyncEntityType.AlbumDeleteV1]: SyncAlbumDeleteV1;
|
||||||
[SyncEntityType.AlbumUserV1]: SyncAlbumUserV1;
|
[SyncEntityType.AlbumUserV1]: SyncAlbumUserV1;
|
||||||
|
[SyncEntityType.AlbumUserBackfillV1]: SyncAlbumUserV1;
|
||||||
[SyncEntityType.AlbumUserDeleteV1]: SyncAlbumUserDeleteV1;
|
[SyncEntityType.AlbumUserDeleteV1]: SyncAlbumUserDeleteV1;
|
||||||
[SyncEntityType.SyncAckV1]: object;
|
[SyncEntityType.SyncAckV1]: object;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -603,6 +603,7 @@ export enum SyncEntityType {
|
|||||||
AlbumV1 = 'AlbumV1',
|
AlbumV1 = 'AlbumV1',
|
||||||
AlbumDeleteV1 = 'AlbumDeleteV1',
|
AlbumDeleteV1 = 'AlbumDeleteV1',
|
||||||
AlbumUserV1 = 'AlbumUserV1',
|
AlbumUserV1 = 'AlbumUserV1',
|
||||||
|
AlbumUserBackfillV1 = 'AlbumUserBackfillV1',
|
||||||
AlbumUserDeleteV1 = 'AlbumUserDeleteV1',
|
AlbumUserDeleteV1 = 'AlbumUserDeleteV1',
|
||||||
|
|
||||||
SyncAckV1 = 'SyncAckV1',
|
SyncAckV1 = 'SyncAckV1',
|
||||||
|
|||||||
@@ -394,6 +394,35 @@ where
|
|||||||
order by
|
order by
|
||||||
"id" asc
|
"id" asc
|
||||||
|
|
||||||
|
-- SyncRepository.getAlbumBackfill
|
||||||
|
select
|
||||||
|
"albumsId" as "id",
|
||||||
|
"createId"
|
||||||
|
from
|
||||||
|
"albums_shared_users_users"
|
||||||
|
where
|
||||||
|
"usersId" = $1
|
||||||
|
and "createId" >= $2
|
||||||
|
and "createdAt" < now() - interval '1 millisecond'
|
||||||
|
order by
|
||||||
|
"createId" asc
|
||||||
|
|
||||||
|
-- SyncRepository.getAlbumUsersBackfill
|
||||||
|
select
|
||||||
|
"albums_shared_users_users"."albumsId" as "albumId",
|
||||||
|
"albums_shared_users_users"."usersId" as "userId",
|
||||||
|
"albums_shared_users_users"."role",
|
||||||
|
"albums_shared_users_users"."updateId"
|
||||||
|
from
|
||||||
|
"albums_shared_users_users"
|
||||||
|
where
|
||||||
|
"albumsId" = $1
|
||||||
|
and "updatedAt" < now() - interval '1 millisecond'
|
||||||
|
and "updateId" < $2
|
||||||
|
and "updateId" >= $3
|
||||||
|
order by
|
||||||
|
"updateId" asc
|
||||||
|
|
||||||
-- SyncRepository.getAlbumUserUpserts
|
-- SyncRepository.getAlbumUserUpserts
|
||||||
select
|
select
|
||||||
"albums_shared_users_users"."albumsId" as "albumId",
|
"albums_shared_users_users"."albumsId" as "albumId",
|
||||||
|
|||||||
@@ -254,16 +254,36 @@ export class SyncRepository {
|
|||||||
.stream();
|
.stream();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID] })
|
||||||
|
getAlbumBackfill(userId: string, afterCreateId?: string) {
|
||||||
|
return this.db
|
||||||
|
.selectFrom('albums_shared_users_users')
|
||||||
|
.select(['albumsId as id', 'createId'])
|
||||||
|
.where('usersId', '=', userId)
|
||||||
|
.$if(!!afterCreateId, (qb) => qb.where('createId', '>=', afterCreateId!))
|
||||||
|
.where('createdAt', '<', sql.raw<Date>("now() - interval '1 millisecond'"))
|
||||||
|
.orderBy('createId', 'asc')
|
||||||
|
.execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
@GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID, DummyValue.UUID], stream: true })
|
||||||
|
getAlbumUsersBackfill(albumId: string, afterUpdateId: string | undefined, beforeUpdateId: string) {
|
||||||
|
return this.db
|
||||||
|
.selectFrom('albums_shared_users_users')
|
||||||
|
.select(columns.syncAlbumUser)
|
||||||
|
.where('albumsId', '=', albumId)
|
||||||
|
.where('updatedAt', '<', sql.raw<Date>("now() - interval '1 millisecond'"))
|
||||||
|
.where('updateId', '<', beforeUpdateId)
|
||||||
|
.$if(!!afterUpdateId, (eb) => eb.where('updateId', '>=', afterUpdateId!))
|
||||||
|
.orderBy('updateId', 'asc')
|
||||||
|
.stream();
|
||||||
|
}
|
||||||
|
|
||||||
@GenerateSql({ params: [DummyValue.UUID], stream: true })
|
@GenerateSql({ params: [DummyValue.UUID], stream: true })
|
||||||
getAlbumUserUpserts(userId: string, ack?: SyncAck) {
|
getAlbumUserUpserts(userId: string, ack?: SyncAck) {
|
||||||
return this.db
|
return this.db
|
||||||
.selectFrom('albums_shared_users_users')
|
.selectFrom('albums_shared_users_users')
|
||||||
.select([
|
.select(columns.syncAlbumUser)
|
||||||
'albums_shared_users_users.albumsId as albumId',
|
|
||||||
'albums_shared_users_users.usersId as userId',
|
|
||||||
'albums_shared_users_users.role',
|
|
||||||
'albums_shared_users_users.updateId',
|
|
||||||
])
|
|
||||||
.where('albums_shared_users_users.updatedAt', '<', sql.raw<Date>("now() - interval '1 millisecond'"))
|
.where('albums_shared_users_users.updatedAt', '<', sql.raw<Date>("now() - interval '1 millisecond'"))
|
||||||
.$if(!!ack, (qb) => qb.where('albums_shared_users_users.updateId', '>', ack!.updateId))
|
.$if(!!ack, (qb) => qb.where('albums_shared_users_users.updateId', '>', ack!.updateId))
|
||||||
.orderBy('albums_shared_users_users.updateId', 'asc')
|
.orderBy('albums_shared_users_users.updateId', 'asc')
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import { Kysely, sql } from 'kysely';
|
||||||
|
|
||||||
|
export async function up(db: Kysely<any>): Promise<void> {
|
||||||
|
await sql`ALTER TABLE "albums_shared_users_users" ADD "createId" uuid NOT NULL DEFAULT immich_uuid_v7();`.execute(db);
|
||||||
|
await sql`ALTER TABLE "albums_shared_users_users" ADD "createdAt" timestamp with time zone NOT NULL DEFAULT now();`.execute(db);
|
||||||
|
await sql`CREATE INDEX "IDX_album_users_create_id" ON "albums_shared_users_users" ("createId")`.execute(db);
|
||||||
|
await sql`CREATE INDEX "IDX_partners_create_id" ON "partners" ("createId")`.execute(db);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(db: Kysely<any>): Promise<void> {
|
||||||
|
await sql`DROP INDEX "IDX_partners_create_id";`.execute(db);
|
||||||
|
await sql`DROP INDEX "IDX_album_users_create_id";`.execute(db);
|
||||||
|
await sql`ALTER TABLE "albums_shared_users_users" DROP COLUMN "createId";`.execute(db);
|
||||||
|
await sql`ALTER TABLE "albums_shared_users_users" DROP COLUMN "createdAt";`.execute(db);
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { UpdatedAtTrigger, UpdateIdColumn } from 'src/decorators';
|
import { CreateIdColumn, UpdatedAtTrigger, UpdateIdColumn } from 'src/decorators';
|
||||||
import { AlbumUserRole } from 'src/enum';
|
import { AlbumUserRole } from 'src/enum';
|
||||||
import { album_user_after_insert, album_users_delete_audit } from 'src/schema/functions';
|
import { album_user_after_insert, album_users_delete_audit } from 'src/schema/functions';
|
||||||
import { AlbumTable } from 'src/schema/tables/album.table';
|
import { AlbumTable } from 'src/schema/tables/album.table';
|
||||||
@@ -7,6 +7,7 @@ import {
|
|||||||
AfterDeleteTrigger,
|
AfterDeleteTrigger,
|
||||||
AfterInsertTrigger,
|
AfterInsertTrigger,
|
||||||
Column,
|
Column,
|
||||||
|
CreateDateColumn,
|
||||||
ForeignKeyColumn,
|
ForeignKeyColumn,
|
||||||
Index,
|
Index,
|
||||||
Table,
|
Table,
|
||||||
@@ -51,6 +52,12 @@ export class AlbumUserTable {
|
|||||||
@Column({ type: 'character varying', default: AlbumUserRole.EDITOR })
|
@Column({ type: 'character varying', default: AlbumUserRole.EDITOR })
|
||||||
role!: AlbumUserRole;
|
role!: AlbumUserRole;
|
||||||
|
|
||||||
|
@CreateIdColumn({ indexName: 'IDX_album_users_create_id' })
|
||||||
|
createId?: string;
|
||||||
|
|
||||||
|
@CreateDateColumn()
|
||||||
|
createdAt!: Date;
|
||||||
|
|
||||||
@UpdateIdColumn({ indexName: 'IDX_album_users_update_id' })
|
@UpdateIdColumn({ indexName: 'IDX_album_users_update_id' })
|
||||||
updateId?: string;
|
updateId?: string;
|
||||||
|
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ export class PartnerTable {
|
|||||||
@CreateDateColumn()
|
@CreateDateColumn()
|
||||||
createdAt!: Date;
|
createdAt!: Date;
|
||||||
|
|
||||||
@CreateIdColumn()
|
@CreateIdColumn({ indexName: 'IDX_partners_create_id' })
|
||||||
createId!: string;
|
createId!: string;
|
||||||
|
|
||||||
@UpdateDateColumn()
|
@UpdateDateColumn()
|
||||||
|
|||||||
@@ -138,14 +138,14 @@ export class SyncService extends BaseService {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case SyncRequestType.PartnerAssetsV1: {
|
case SyncRequestType.AssetExifsV1: {
|
||||||
await this.syncPartnerAssetsV1(response, checkpointMap, auth, sessionId);
|
await this.syncAssetExifsV1(response, checkpointMap, auth);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case SyncRequestType.AssetExifsV1: {
|
case SyncRequestType.PartnerAssetsV1: {
|
||||||
await this.syncAssetExifsV1(response, checkpointMap, auth);
|
await this.syncPartnerAssetsV1(response, checkpointMap, auth, sessionId);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -160,7 +160,7 @@ export class SyncService extends BaseService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
case SyncRequestType.AlbumUsersV1: {
|
case SyncRequestType.AlbumUsersV1: {
|
||||||
await this.syncAlbumUsersV1(response, checkpointMap, auth);
|
await this.syncAlbumUsersV1(response, checkpointMap, auth, sessionId);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -330,18 +330,50 @@ export class SyncService extends BaseService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async syncAlbumUsersV1(response: Writable, checkpointMap: CheckpointMap, auth: AuthDto) {
|
private async syncAlbumUsersV1(response: Writable, checkpointMap: CheckpointMap, auth: AuthDto, sessionId: string) {
|
||||||
const deletes = this.syncRepository.getAlbumUserDeletes(
|
const backfillType = SyncEntityType.AlbumUserBackfillV1;
|
||||||
auth.user.id,
|
const upsertType = SyncEntityType.AlbumUserV1;
|
||||||
checkpointMap[SyncEntityType.AlbumUserDeleteV1],
|
const deleteType = SyncEntityType.AlbumUserDeleteV1;
|
||||||
);
|
|
||||||
|
const backfillCheckpoint = checkpointMap[backfillType];
|
||||||
|
const upsertCheckpoint = checkpointMap[upsertType];
|
||||||
|
|
||||||
|
const deletes = this.syncRepository.getAlbumUserDeletes(auth.user.id, checkpointMap[deleteType]);
|
||||||
|
|
||||||
for await (const { id, ...data } of deletes) {
|
for await (const { id, ...data } of deletes) {
|
||||||
send(response, { type: SyncEntityType.AlbumUserDeleteV1, ids: [id], data });
|
send(response, { type: deleteType, ids: [id], data });
|
||||||
}
|
}
|
||||||
|
|
||||||
const upserts = this.syncRepository.getAlbumUserUpserts(auth.user.id, checkpointMap[SyncEntityType.AlbumUserV1]);
|
const albums = await this.syncRepository.getAlbumBackfill(auth.user.id, backfillCheckpoint?.updateId);
|
||||||
|
|
||||||
|
if (upsertCheckpoint) {
|
||||||
|
const endId = upsertCheckpoint.updateId;
|
||||||
|
|
||||||
|
for (const album of albums) {
|
||||||
|
if (isEntityBackfillComplete(album, backfillCheckpoint)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const startId = getStartId(album, backfillCheckpoint);
|
||||||
|
const backfill = this.syncRepository.getAlbumUsersBackfill(album.id, startId, endId);
|
||||||
|
|
||||||
|
for await (const { updateId, ...data } of backfill) {
|
||||||
|
send(response, { type: backfillType, ids: [updateId], data });
|
||||||
|
}
|
||||||
|
|
||||||
|
sendEntityBackfillCompleteAck(response, backfillType, album.id);
|
||||||
|
}
|
||||||
|
} else if (albums.length > 0) {
|
||||||
|
await this.upsertBackfillCheckpoint({
|
||||||
|
type: backfillType,
|
||||||
|
sessionId,
|
||||||
|
createId: albums.at(-1)!.createId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const upserts = this.syncRepository.getAlbumUserUpserts(auth.user.id, checkpointMap[upsertType]);
|
||||||
for await (const { updateId, ...data } of upserts) {
|
for await (const { updateId, ...data } of upserts) {
|
||||||
send(response, { type: SyncEntityType.AlbumUserV1, ids: [updateId], data });
|
send(response, { type: upsertType, ids: [updateId], data });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
import { ColumnBaseOptions } from 'src/sql-tools/from-code/decorators/column.decorator';
|
import { ColumnBaseOptions } from 'src/sql-tools/from-code/decorators/column.decorator';
|
||||||
|
import { ForeignKeyAction } from 'src/sql-tools/from-code/decorators/foreign-key-constraint.decorator';
|
||||||
import { register } from 'src/sql-tools/from-code/register';
|
import { register } from 'src/sql-tools/from-code/register';
|
||||||
|
|
||||||
type Action = 'CASCADE' | 'SET NULL' | 'SET DEFAULT' | 'RESTRICT' | 'NO ACTION';
|
|
||||||
|
|
||||||
export type ForeignKeyColumnOptions = ColumnBaseOptions & {
|
export type ForeignKeyColumnOptions = ColumnBaseOptions & {
|
||||||
onUpdate?: Action;
|
onUpdate?: ForeignKeyAction;
|
||||||
onDelete?: Action;
|
onDelete?: ForeignKeyAction;
|
||||||
constraintName?: string;
|
constraintName?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import { register } from 'src/sql-tools/from-code/register';
|
||||||
|
|
||||||
|
export type ForeignKeyAction = 'CASCADE' | 'SET NULL' | 'SET DEFAULT' | 'RESTRICT' | 'NO ACTION';
|
||||||
|
|
||||||
|
export type ForeignKeyConstraintOptions = {
|
||||||
|
name?: string;
|
||||||
|
index?: boolean;
|
||||||
|
indexName?: string;
|
||||||
|
columns: string[];
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
|
||||||
|
referenceTable: () => Function;
|
||||||
|
referenceColumns?: string[];
|
||||||
|
onUpdate?: ForeignKeyAction;
|
||||||
|
onDelete?: ForeignKeyAction;
|
||||||
|
synchronize?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ForeignKeyConstraint = (options: ForeignKeyConstraintOptions): ClassDecorator => {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
|
||||||
|
return (target: Function) => {
|
||||||
|
register({ type: 'foreignKeyConstraint', item: { object: target, options } });
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -5,7 +5,8 @@ import { processConfigurationParameters } from 'src/sql-tools/from-code/processo
|
|||||||
import { processDatabases } from 'src/sql-tools/from-code/processors/database.processor';
|
import { processDatabases } from 'src/sql-tools/from-code/processors/database.processor';
|
||||||
import { processEnums } from 'src/sql-tools/from-code/processors/enum.processor';
|
import { processEnums } from 'src/sql-tools/from-code/processors/enum.processor';
|
||||||
import { processExtensions } from 'src/sql-tools/from-code/processors/extension.processor';
|
import { processExtensions } from 'src/sql-tools/from-code/processors/extension.processor';
|
||||||
import { processForeignKeyConstraints } from 'src/sql-tools/from-code/processors/foreign-key-constriant.processor';
|
import { processForeignKeyColumns } from 'src/sql-tools/from-code/processors/foreign-key-column.processor';
|
||||||
|
import { processForeignKeyConstraints } from 'src/sql-tools/from-code/processors/foreign-key-constraint.processor';
|
||||||
import { processFunctions } from 'src/sql-tools/from-code/processors/function.processor';
|
import { processFunctions } from 'src/sql-tools/from-code/processors/function.processor';
|
||||||
import { processIndexes } from 'src/sql-tools/from-code/processors/index.processor';
|
import { processIndexes } from 'src/sql-tools/from-code/processors/index.processor';
|
||||||
import { processPrimaryKeyConstraints } from 'src/sql-tools/from-code/processors/primary-key-contraint.processor';
|
import { processPrimaryKeyConstraints } from 'src/sql-tools/from-code/processors/primary-key-contraint.processor';
|
||||||
@@ -32,10 +33,11 @@ const processors: Processor[] = [
|
|||||||
processFunctions,
|
processFunctions,
|
||||||
processTables,
|
processTables,
|
||||||
processColumns,
|
processColumns,
|
||||||
|
processForeignKeyColumns,
|
||||||
|
processForeignKeyConstraints,
|
||||||
processUniqueConstraints,
|
processUniqueConstraints,
|
||||||
processCheckConstraints,
|
processCheckConstraints,
|
||||||
processPrimaryKeyConstraints,
|
processPrimaryKeyConstraints,
|
||||||
processForeignKeyConstraints,
|
|
||||||
processIndexes,
|
processIndexes,
|
||||||
processTriggers,
|
processTriggers,
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { ColumnOptions } from 'src/sql-tools/from-code/decorators/column.decorator';
|
import { ColumnOptions } from 'src/sql-tools/from-code/decorators/column.decorator';
|
||||||
import { onMissingTable, resolveTable } from 'src/sql-tools/from-code/processors/table.processor';
|
import { onMissingTable, resolveTable } from 'src/sql-tools/from-code/processors/table.processor';
|
||||||
import { Processor, SchemaBuilder } from 'src/sql-tools/from-code/processors/type';
|
import { Processor, SchemaBuilder } from 'src/sql-tools/from-code/processors/type';
|
||||||
import { asMetadataKey, fromColumnValue } from 'src/sql-tools/helpers';
|
import { addWarning, asMetadataKey, fromColumnValue } from 'src/sql-tools/helpers';
|
||||||
import { DatabaseColumn } from 'src/sql-tools/types';
|
import { DatabaseColumn } from 'src/sql-tools/types';
|
||||||
|
|
||||||
export const processColumns: Processor = (builder, items) => {
|
export const processColumns: Processor = (builder, items) => {
|
||||||
@@ -81,7 +81,7 @@ export const onMissingColumn = (
|
|||||||
propertyName?: symbol | string,
|
propertyName?: symbol | string,
|
||||||
) => {
|
) => {
|
||||||
const label = object.constructor.name + (propertyName ? '.' + String(propertyName) : '');
|
const label = object.constructor.name + (propertyName ? '.' + String(propertyName) : '');
|
||||||
builder.warnings.push(`[${context}] Unable to find column (${label})`);
|
addWarning(builder, context, `Unable to find column (${label})`);
|
||||||
};
|
};
|
||||||
|
|
||||||
const METADATA_KEY = asMetadataKey('table-metadata');
|
const METADATA_KEY = asMetadataKey('table-metadata');
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import { onMissingColumn, resolveColumn } from 'src/sql-tools/from-code/processors/column.processor';
|
import { onMissingColumn, resolveColumn } from 'src/sql-tools/from-code/processors/column.processor';
|
||||||
import { onMissingTable, resolveTable } from 'src/sql-tools/from-code/processors/table.processor';
|
import { onMissingTable, resolveTable } from 'src/sql-tools/from-code/processors/table.processor';
|
||||||
import { Processor } from 'src/sql-tools/from-code/processors/type';
|
import { Processor } from 'src/sql-tools/from-code/processors/type';
|
||||||
import { asKey } from 'src/sql-tools/helpers';
|
import { asForeignKeyConstraintName, asKey } from 'src/sql-tools/helpers';
|
||||||
import { DatabaseActionType, DatabaseConstraintType } from 'src/sql-tools/types';
|
import { DatabaseActionType, DatabaseConstraintType } from 'src/sql-tools/types';
|
||||||
|
|
||||||
export const processForeignKeyConstraints: Processor = (builder, items) => {
|
export const processForeignKeyColumns: Processor = (builder, items) => {
|
||||||
for (const {
|
for (const {
|
||||||
item: { object, propertyName, options, target },
|
item: { object, propertyName, options, target },
|
||||||
} of items.filter((item) => item.type === 'foreignKeyColumn')) {
|
} of items.filter((item) => item.type === 'foreignKeyColumn')) {
|
||||||
@@ -34,13 +34,16 @@ export const processForeignKeyConstraints: Processor = (builder, items) => {
|
|||||||
column.type = referenceColumns[0].type;
|
column.type = referenceColumns[0].type;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const referenceColumnNames = referenceColumns.map((column) => column.name);
|
||||||
|
const name = options.constraintName || asForeignKeyConstraintName(table.name, columnNames);
|
||||||
|
|
||||||
table.constraints.push({
|
table.constraints.push({
|
||||||
name: options.constraintName || asForeignKeyConstraintName(table.name, columnNames),
|
name,
|
||||||
tableName: table.name,
|
tableName: table.name,
|
||||||
columnNames,
|
columnNames,
|
||||||
type: DatabaseConstraintType.FOREIGN_KEY,
|
type: DatabaseConstraintType.FOREIGN_KEY,
|
||||||
referenceTableName: referenceTable.name,
|
referenceTableName: referenceTable.name,
|
||||||
referenceColumnNames: referenceColumns.map((column) => column.name),
|
referenceColumnNames,
|
||||||
onUpdate: options.onUpdate as DatabaseActionType,
|
onUpdate: options.onUpdate as DatabaseActionType,
|
||||||
onDelete: options.onDelete as DatabaseActionType,
|
onDelete: options.onDelete as DatabaseActionType,
|
||||||
synchronize: options.synchronize ?? true,
|
synchronize: options.synchronize ?? true,
|
||||||
@@ -58,5 +61,4 @@ export const processForeignKeyConstraints: Processor = (builder, items) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const asForeignKeyConstraintName = (table: string, columns: string[]) => asKey('FK_', table, columns);
|
|
||||||
const asRelationKeyConstraintName = (table: string, columns: string[]) => asKey('REL_', table, columns);
|
const asRelationKeyConstraintName = (table: string, columns: string[]) => asKey('REL_', table, columns);
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
import { onMissingTable, resolveTable } from 'src/sql-tools/from-code/processors/table.processor';
|
||||||
|
import { Processor } from 'src/sql-tools/from-code/processors/type';
|
||||||
|
import { addWarning, asForeignKeyConstraintName, asIndexName } from 'src/sql-tools/helpers';
|
||||||
|
import { DatabaseActionType, DatabaseConstraintType } from 'src/sql-tools/types';
|
||||||
|
|
||||||
|
export const processForeignKeyConstraints: Processor = (builder, items, config) => {
|
||||||
|
for (const {
|
||||||
|
item: { object, options },
|
||||||
|
} of items.filter((item) => item.type === 'foreignKeyConstraint')) {
|
||||||
|
const table = resolveTable(builder, object);
|
||||||
|
if (!table) {
|
||||||
|
onMissingTable(builder, '@ForeignKeyConstraint', { name: 'referenceTable' });
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const referenceTable = resolveTable(builder, options.referenceTable());
|
||||||
|
if (!referenceTable) {
|
||||||
|
const referenceTableName = options.referenceTable()?.name;
|
||||||
|
addWarning(
|
||||||
|
builder,
|
||||||
|
'@ForeignKeyConstraint.referenceTable',
|
||||||
|
`Unable to find table` + (referenceTableName ? ` (${referenceTableName})` : ''),
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let missingColumn = false;
|
||||||
|
|
||||||
|
for (const columnName of options.columns) {
|
||||||
|
if (!table.columns.some(({ name }) => name === columnName)) {
|
||||||
|
addWarning(
|
||||||
|
builder,
|
||||||
|
'@ForeignKeyConstraint.columns',
|
||||||
|
`Unable to find column (${table.metadata.object.name}.${columnName})`,
|
||||||
|
);
|
||||||
|
missingColumn = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const columnName of options.referenceColumns || []) {
|
||||||
|
if (!referenceTable.columns.some(({ name }) => name === columnName)) {
|
||||||
|
addWarning(
|
||||||
|
builder,
|
||||||
|
'@ForeignKeyConstraint.referenceColumns',
|
||||||
|
`Unable to find column (${referenceTable.metadata.object.name}.${columnName})`,
|
||||||
|
);
|
||||||
|
missingColumn = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (missingColumn) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const referenceColumns =
|
||||||
|
options.referenceColumns || referenceTable.columns.filter(({ primary }) => primary).map(({ name }) => name);
|
||||||
|
|
||||||
|
const name = options.name || asForeignKeyConstraintName(table.name, options.columns);
|
||||||
|
|
||||||
|
table.constraints.push({
|
||||||
|
type: DatabaseConstraintType.FOREIGN_KEY,
|
||||||
|
name,
|
||||||
|
tableName: table.name,
|
||||||
|
columnNames: options.columns,
|
||||||
|
referenceTableName: referenceTable.name,
|
||||||
|
referenceColumnNames: referenceColumns,
|
||||||
|
onUpdate: options.onUpdate as DatabaseActionType,
|
||||||
|
onDelete: options.onDelete as DatabaseActionType,
|
||||||
|
synchronize: options.synchronize ?? true,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (options.index === false) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.index || options.indexName || config.createForeignKeyIndexes) {
|
||||||
|
table.indexes.push({
|
||||||
|
name: options.indexName || asIndexName(table.name, options.columns),
|
||||||
|
tableName: table.name,
|
||||||
|
columnNames: options.columns,
|
||||||
|
unique: false,
|
||||||
|
synchronize: options.synchronize ?? true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import { onMissingColumn, resolveColumn } from 'src/sql-tools/from-code/processors/column.processor';
|
import { onMissingColumn, resolveColumn } from 'src/sql-tools/from-code/processors/column.processor';
|
||||||
import { onMissingTable, resolveTable } from 'src/sql-tools/from-code/processors/table.processor';
|
import { onMissingTable, resolveTable } from 'src/sql-tools/from-code/processors/table.processor';
|
||||||
import { Processor } from 'src/sql-tools/from-code/processors/type';
|
import { Processor } from 'src/sql-tools/from-code/processors/type';
|
||||||
import { asKey } from 'src/sql-tools/helpers';
|
import { asIndexName } from 'src/sql-tools/helpers';
|
||||||
|
|
||||||
export const processIndexes: Processor = (builder, items, config) => {
|
export const processIndexes: Processor = (builder, items, config) => {
|
||||||
for (const {
|
for (const {
|
||||||
@@ -75,16 +75,3 @@ export const processIndexes: Processor = (builder, items, config) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const asIndexName = (table: string, columns?: string[], where?: string) => {
|
|
||||||
const items: string[] = [];
|
|
||||||
for (const columnName of columns ?? []) {
|
|
||||||
items.push(columnName);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (where) {
|
|
||||||
items.push(where);
|
|
||||||
}
|
|
||||||
|
|
||||||
return asKey('IDX_', table, items);
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { TableOptions } from 'src/sql-tools/from-code/decorators/table.decorator';
|
import { TableOptions } from 'src/sql-tools/from-code/decorators/table.decorator';
|
||||||
import { Processor, SchemaBuilder } from 'src/sql-tools/from-code/processors/type';
|
import { Processor, SchemaBuilder } from 'src/sql-tools/from-code/processors/type';
|
||||||
import { asMetadataKey, asSnakeCase } from 'src/sql-tools/helpers';
|
import { addWarning, asMetadataKey, asSnakeCase } from 'src/sql-tools/helpers';
|
||||||
|
|
||||||
export const processTables: Processor = (builder, items) => {
|
export const processTables: Processor = (builder, items) => {
|
||||||
for (const {
|
for (const {
|
||||||
@@ -45,7 +45,7 @@ export const onMissingTable = (
|
|||||||
propertyName?: symbol | string,
|
propertyName?: symbol | string,
|
||||||
) => {
|
) => {
|
||||||
const label = object.constructor.name + (propertyName ? '.' + String(propertyName) : '');
|
const label = object.constructor.name + (propertyName ? '.' + String(propertyName) : '');
|
||||||
builder.warnings.push(`[${context}] Unable to find table (${label})`);
|
addWarning(builder, context, `Unable to find table (${label})`);
|
||||||
};
|
};
|
||||||
|
|
||||||
const METADATA_KEY = asMetadataKey('table-metadata');
|
const METADATA_KEY = asMetadataKey('table-metadata');
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { ConfigurationParameterOptions } from 'src/sql-tools/from-code/decorator
|
|||||||
import { DatabaseOptions } from 'src/sql-tools/from-code/decorators/database.decorator';
|
import { DatabaseOptions } from 'src/sql-tools/from-code/decorators/database.decorator';
|
||||||
import { ExtensionOptions } from 'src/sql-tools/from-code/decorators/extension.decorator';
|
import { ExtensionOptions } from 'src/sql-tools/from-code/decorators/extension.decorator';
|
||||||
import { ForeignKeyColumnOptions } from 'src/sql-tools/from-code/decorators/foreign-key-column.decorator';
|
import { ForeignKeyColumnOptions } from 'src/sql-tools/from-code/decorators/foreign-key-column.decorator';
|
||||||
|
import { ForeignKeyConstraintOptions } from 'src/sql-tools/from-code/decorators/foreign-key-constraint.decorator';
|
||||||
import { IndexOptions } from 'src/sql-tools/from-code/decorators/index.decorator';
|
import { IndexOptions } from 'src/sql-tools/from-code/decorators/index.decorator';
|
||||||
import { TableOptions } from 'src/sql-tools/from-code/decorators/table.decorator';
|
import { TableOptions } from 'src/sql-tools/from-code/decorators/table.decorator';
|
||||||
import { TriggerOptions } from 'src/sql-tools/from-code/decorators/trigger.decorator';
|
import { TriggerOptions } from 'src/sql-tools/from-code/decorators/trigger.decorator';
|
||||||
@@ -25,5 +26,6 @@ export type RegisterItem =
|
|||||||
| { type: 'trigger'; item: ClassBased<{ options: TriggerOptions }> }
|
| { type: 'trigger'; item: ClassBased<{ options: TriggerOptions }> }
|
||||||
| { type: 'extension'; item: ClassBased<{ options: ExtensionOptions }> }
|
| { type: 'extension'; item: ClassBased<{ options: ExtensionOptions }> }
|
||||||
| { type: 'configurationParameter'; item: ClassBased<{ options: ConfigurationParameterOptions }> }
|
| { type: 'configurationParameter'; item: ClassBased<{ options: ConfigurationParameterOptions }> }
|
||||||
| { type: 'foreignKeyColumn'; item: PropertyBased<{ options: ForeignKeyColumnOptions; target: () => object }> };
|
| { type: 'foreignKeyColumn'; item: PropertyBased<{ options: ForeignKeyColumnOptions; target: () => object }> }
|
||||||
|
| { type: 'foreignKeyConstraint'; item: ClassBased<{ options: ForeignKeyConstraintOptions }> };
|
||||||
export type RegisterItemType<T extends RegisterItem['type']> = Extract<RegisterItem, { type: T }>['item'];
|
export type RegisterItemType<T extends RegisterItem['type']> = Extract<RegisterItem, { type: T }>['item'];
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { createHash } from 'node:crypto';
|
import { createHash } from 'node:crypto';
|
||||||
import { ColumnValue } from 'src/sql-tools/from-code/decorators/column.decorator';
|
import { ColumnValue } from 'src/sql-tools/from-code/decorators/column.decorator';
|
||||||
|
import { SchemaBuilder } from 'src/sql-tools/from-code/processors/type';
|
||||||
import {
|
import {
|
||||||
Comparer,
|
Comparer,
|
||||||
DatabaseColumn,
|
DatabaseColumn,
|
||||||
@@ -211,3 +212,22 @@ export const asColumnComment = (tableName: string, columnName: string, comment:
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const asColumnList = (columns: string[]) => columns.map((column) => `"${column}"`).join(', ');
|
export const asColumnList = (columns: string[]) => columns.map((column) => `"${column}"`).join(', ');
|
||||||
|
|
||||||
|
export const asForeignKeyConstraintName = (table: string, columns: string[]) => asKey('FK_', table, [...columns]);
|
||||||
|
|
||||||
|
export const asIndexName = (table: string, columns?: string[], where?: string) => {
|
||||||
|
const items: string[] = [];
|
||||||
|
for (const columnName of columns ?? []) {
|
||||||
|
items.push(columnName);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (where) {
|
||||||
|
items.push(where);
|
||||||
|
}
|
||||||
|
|
||||||
|
return asKey('IDX_', table, items);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const addWarning = (builder: SchemaBuilder, context: string, message: string) => {
|
||||||
|
builder.warnings.push(`[${context}] ${message}`);
|
||||||
|
};
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ export * from 'src/sql-tools/from-code/decorators/delete-date-column.decorator';
|
|||||||
export * from 'src/sql-tools/from-code/decorators/extension.decorator';
|
export * from 'src/sql-tools/from-code/decorators/extension.decorator';
|
||||||
export * from 'src/sql-tools/from-code/decorators/extensions.decorator';
|
export * from 'src/sql-tools/from-code/decorators/extensions.decorator';
|
||||||
export * from 'src/sql-tools/from-code/decorators/foreign-key-column.decorator';
|
export * from 'src/sql-tools/from-code/decorators/foreign-key-column.decorator';
|
||||||
|
export * from 'src/sql-tools/from-code/decorators/foreign-key-constraint.decorator';
|
||||||
export * from 'src/sql-tools/from-code/decorators/generated-column.decorator';
|
export * from 'src/sql-tools/from-code/decorators/generated-column.decorator';
|
||||||
export * from 'src/sql-tools/from-code/decorators/index.decorator';
|
export * from 'src/sql-tools/from-code/decorators/index.decorator';
|
||||||
export * from 'src/sql-tools/from-code/decorators/primary-column.decorator';
|
export * from 'src/sql-tools/from-code/decorators/primary-column.decorator';
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { Kysely } from 'kysely';
|
|||||||
import { DB } from 'src/db';
|
import { DB } from 'src/db';
|
||||||
import { AlbumUserRole, SyncEntityType, SyncRequestType } from 'src/enum';
|
import { AlbumUserRole, SyncEntityType, SyncRequestType } from 'src/enum';
|
||||||
import { mediumFactory, newSyncAuthUser, newSyncTest } from 'test/medium.factory';
|
import { mediumFactory, newSyncAuthUser, newSyncTest } from 'test/medium.factory';
|
||||||
import { getKyselyDB } from 'test/utils';
|
import { getKyselyDB, wait } from 'test/utils';
|
||||||
|
|
||||||
let defaultDatabase: Kysely<DB>;
|
let defaultDatabase: Kysely<DB>;
|
||||||
|
|
||||||
@@ -265,5 +265,95 @@ describe(SyncRequestType.AlbumUsersV1, () => {
|
|||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should backfill album users when a user shares an album with you', async () => {
|
||||||
|
const { auth, sut, testSync, getRepository } = await setup();
|
||||||
|
|
||||||
|
const albumRepo = getRepository('album');
|
||||||
|
const albumUserRepo = getRepository('albumUser');
|
||||||
|
const userRepo = getRepository('user');
|
||||||
|
|
||||||
|
const user1 = mediumFactory.userInsert();
|
||||||
|
const user2 = mediumFactory.userInsert();
|
||||||
|
await userRepo.create(user1);
|
||||||
|
await userRepo.create(user2);
|
||||||
|
|
||||||
|
const album1 = mediumFactory.albumInsert({ ownerId: user1.id });
|
||||||
|
const album2 = mediumFactory.albumInsert({ ownerId: user1.id });
|
||||||
|
await albumRepo.create(album1, [], []);
|
||||||
|
await albumRepo.create(album2, [], []);
|
||||||
|
|
||||||
|
// backfill album user
|
||||||
|
await albumUserRepo.create({ albumsId: album1.id, usersId: user1.id, role: AlbumUserRole.EDITOR });
|
||||||
|
await wait(2);
|
||||||
|
// initial album user
|
||||||
|
await albumUserRepo.create({ albumsId: album2.id, usersId: auth.user.id, role: AlbumUserRole.EDITOR });
|
||||||
|
await wait(2);
|
||||||
|
// post checkpoint album user
|
||||||
|
await albumUserRepo.create({ albumsId: album1.id, usersId: user2.id, role: AlbumUserRole.EDITOR });
|
||||||
|
|
||||||
|
const response = await testSync(auth, [SyncRequestType.AlbumUsersV1]);
|
||||||
|
expect(response).toHaveLength(1);
|
||||||
|
expect(response).toEqual([
|
||||||
|
{
|
||||||
|
ack: expect.any(String),
|
||||||
|
data: expect.objectContaining({
|
||||||
|
albumId: album2.id,
|
||||||
|
role: AlbumUserRole.EDITOR,
|
||||||
|
userId: auth.user.id,
|
||||||
|
}),
|
||||||
|
type: SyncEntityType.AlbumUserV1,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
// ack initial user
|
||||||
|
const acks = response.map(({ ack }) => ack);
|
||||||
|
await sut.setAcks(auth, { acks });
|
||||||
|
|
||||||
|
// get access to the backfill album user
|
||||||
|
await albumUserRepo.create({ albumsId: album1.id, usersId: auth.user.id, role: AlbumUserRole.EDITOR });
|
||||||
|
|
||||||
|
// should backfill the album user
|
||||||
|
const backfillResponse = await testSync(auth, [SyncRequestType.AlbumUsersV1]);
|
||||||
|
expect(backfillResponse).toEqual([
|
||||||
|
{
|
||||||
|
ack: expect.any(String),
|
||||||
|
data: expect.objectContaining({
|
||||||
|
albumId: album1.id,
|
||||||
|
role: AlbumUserRole.EDITOR,
|
||||||
|
userId: user1.id,
|
||||||
|
}),
|
||||||
|
type: SyncEntityType.AlbumUserBackfillV1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ack: expect.stringContaining(SyncEntityType.AlbumUserBackfillV1),
|
||||||
|
data: {},
|
||||||
|
type: SyncEntityType.SyncAckV1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ack: expect.any(String),
|
||||||
|
data: expect.objectContaining({
|
||||||
|
albumId: album1.id,
|
||||||
|
role: AlbumUserRole.EDITOR,
|
||||||
|
userId: user2.id,
|
||||||
|
}),
|
||||||
|
type: SyncEntityType.AlbumUserV1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ack: expect.any(String),
|
||||||
|
data: expect.objectContaining({
|
||||||
|
albumId: album1.id,
|
||||||
|
role: AlbumUserRole.EDITOR,
|
||||||
|
userId: auth.user.id,
|
||||||
|
}),
|
||||||
|
type: SyncEntityType.AlbumUserV1,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
await sut.setAcks(auth, { acks: [backfillResponse[1].ack, backfillResponse.at(-1).ack] });
|
||||||
|
|
||||||
|
const finalResponse = await testSync(auth, [SyncRequestType.AlbumUsersV1]);
|
||||||
|
expect(finalResponse).toEqual([]);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -0,0 +1,124 @@
|
|||||||
|
import {
|
||||||
|
Column,
|
||||||
|
DatabaseConstraintType,
|
||||||
|
DatabaseSchema,
|
||||||
|
ForeignKeyConstraint,
|
||||||
|
PrimaryColumn,
|
||||||
|
Table,
|
||||||
|
} from 'src/sql-tools';
|
||||||
|
|
||||||
|
@Table()
|
||||||
|
export class Table1 {
|
||||||
|
@PrimaryColumn({ type: 'uuid' })
|
||||||
|
id1!: string;
|
||||||
|
|
||||||
|
@PrimaryColumn({ type: 'uuid' })
|
||||||
|
id2!: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Table()
|
||||||
|
@ForeignKeyConstraint({
|
||||||
|
columns: ['parentId1', 'parentId2'],
|
||||||
|
referenceTable: () => Table1,
|
||||||
|
referenceColumns: ['id2', 'id1'],
|
||||||
|
})
|
||||||
|
export class Table2 {
|
||||||
|
@Column({ type: 'uuid' })
|
||||||
|
parentId1!: string;
|
||||||
|
|
||||||
|
@Column({ type: 'uuid' })
|
||||||
|
parentId2!: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const description = 'should create a foreign key constraint to the target table';
|
||||||
|
export const schema: DatabaseSchema = {
|
||||||
|
name: 'postgres',
|
||||||
|
schemaName: 'public',
|
||||||
|
functions: [],
|
||||||
|
enums: [],
|
||||||
|
extensions: [],
|
||||||
|
parameters: [],
|
||||||
|
tables: [
|
||||||
|
{
|
||||||
|
name: 'table1',
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
name: 'id1',
|
||||||
|
tableName: 'table1',
|
||||||
|
type: 'uuid',
|
||||||
|
nullable: false,
|
||||||
|
isArray: false,
|
||||||
|
primary: true,
|
||||||
|
synchronize: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'id2',
|
||||||
|
tableName: 'table1',
|
||||||
|
type: 'uuid',
|
||||||
|
nullable: false,
|
||||||
|
isArray: false,
|
||||||
|
primary: true,
|
||||||
|
synchronize: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
indexes: [],
|
||||||
|
triggers: [],
|
||||||
|
constraints: [
|
||||||
|
{
|
||||||
|
type: DatabaseConstraintType.PRIMARY_KEY,
|
||||||
|
name: 'PK_e457e8b1301b7bc06ef78188ee4',
|
||||||
|
tableName: 'table1',
|
||||||
|
columnNames: ['id1', 'id2'],
|
||||||
|
synchronize: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
synchronize: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'table2',
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
name: 'parentId1',
|
||||||
|
tableName: 'table2',
|
||||||
|
type: 'uuid',
|
||||||
|
nullable: false,
|
||||||
|
isArray: false,
|
||||||
|
primary: false,
|
||||||
|
synchronize: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'parentId2',
|
||||||
|
tableName: 'table2',
|
||||||
|
type: 'uuid',
|
||||||
|
nullable: false,
|
||||||
|
isArray: false,
|
||||||
|
primary: false,
|
||||||
|
synchronize: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
indexes: [
|
||||||
|
{
|
||||||
|
name: 'IDX_aed36d04470eba20161aa8b1dc',
|
||||||
|
tableName: 'table2',
|
||||||
|
columnNames: ['parentId1', 'parentId2'],
|
||||||
|
unique: false,
|
||||||
|
synchronize: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
triggers: [],
|
||||||
|
constraints: [
|
||||||
|
{
|
||||||
|
type: DatabaseConstraintType.FOREIGN_KEY,
|
||||||
|
name: 'FK_aed36d04470eba20161aa8b1dc6',
|
||||||
|
tableName: 'table2',
|
||||||
|
columnNames: ['parentId1', 'parentId2'],
|
||||||
|
referenceColumnNames: ['id2', 'id1'],
|
||||||
|
referenceTableName: 'table1',
|
||||||
|
synchronize: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
synchronize: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
warnings: [],
|
||||||
|
};
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
import {
|
||||||
|
Column,
|
||||||
|
DatabaseConstraintType,
|
||||||
|
DatabaseSchema,
|
||||||
|
ForeignKeyConstraint,
|
||||||
|
PrimaryColumn,
|
||||||
|
Table,
|
||||||
|
} from 'src/sql-tools';
|
||||||
|
|
||||||
|
@Table()
|
||||||
|
export class Table1 {
|
||||||
|
@PrimaryColumn({ type: 'uuid' })
|
||||||
|
id!: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Table()
|
||||||
|
@ForeignKeyConstraint({ columns: ['parentId2'], referenceTable: () => Table1 })
|
||||||
|
export class Table2 {
|
||||||
|
@Column({ type: 'uuid' })
|
||||||
|
parentId!: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const description = 'should warn against missing column in foreign key constraint';
|
||||||
|
export const schema: DatabaseSchema = {
|
||||||
|
name: 'postgres',
|
||||||
|
schemaName: 'public',
|
||||||
|
functions: [],
|
||||||
|
enums: [],
|
||||||
|
extensions: [],
|
||||||
|
parameters: [],
|
||||||
|
tables: [
|
||||||
|
{
|
||||||
|
name: 'table1',
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
name: 'id',
|
||||||
|
tableName: 'table1',
|
||||||
|
type: 'uuid',
|
||||||
|
nullable: false,
|
||||||
|
isArray: false,
|
||||||
|
primary: true,
|
||||||
|
synchronize: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
indexes: [],
|
||||||
|
triggers: [],
|
||||||
|
constraints: [
|
||||||
|
{
|
||||||
|
type: DatabaseConstraintType.PRIMARY_KEY,
|
||||||
|
name: 'PK_b249cc64cf63b8a22557cdc8537',
|
||||||
|
tableName: 'table1',
|
||||||
|
columnNames: ['id'],
|
||||||
|
synchronize: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
synchronize: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'table2',
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
name: 'parentId',
|
||||||
|
tableName: 'table2',
|
||||||
|
type: 'uuid',
|
||||||
|
nullable: false,
|
||||||
|
isArray: false,
|
||||||
|
primary: false,
|
||||||
|
synchronize: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
indexes: [],
|
||||||
|
triggers: [],
|
||||||
|
constraints: [],
|
||||||
|
synchronize: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
warnings: ['[@ForeignKeyConstraint.columns] Unable to find column (Table2.parentId2)'],
|
||||||
|
};
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
import {
|
||||||
|
Column,
|
||||||
|
DatabaseConstraintType,
|
||||||
|
DatabaseSchema,
|
||||||
|
ForeignKeyConstraint,
|
||||||
|
PrimaryColumn,
|
||||||
|
Table,
|
||||||
|
} from 'src/sql-tools';
|
||||||
|
|
||||||
|
@Table()
|
||||||
|
export class Table1 {
|
||||||
|
@PrimaryColumn({ type: 'uuid' })
|
||||||
|
id!: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Table()
|
||||||
|
@ForeignKeyConstraint({ columns: ['parentId'], referenceTable: () => Table1, referenceColumns: ['foo'] })
|
||||||
|
export class Table2 {
|
||||||
|
@Column({ type: 'uuid' })
|
||||||
|
parentId!: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const description = 'should warn against missing reference column in foreign key constraint';
|
||||||
|
export const schema: DatabaseSchema = {
|
||||||
|
name: 'postgres',
|
||||||
|
schemaName: 'public',
|
||||||
|
functions: [],
|
||||||
|
enums: [],
|
||||||
|
extensions: [],
|
||||||
|
parameters: [],
|
||||||
|
tables: [
|
||||||
|
{
|
||||||
|
name: 'table1',
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
name: 'id',
|
||||||
|
tableName: 'table1',
|
||||||
|
type: 'uuid',
|
||||||
|
nullable: false,
|
||||||
|
isArray: false,
|
||||||
|
primary: true,
|
||||||
|
synchronize: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
indexes: [],
|
||||||
|
triggers: [],
|
||||||
|
constraints: [
|
||||||
|
{
|
||||||
|
type: DatabaseConstraintType.PRIMARY_KEY,
|
||||||
|
name: 'PK_b249cc64cf63b8a22557cdc8537',
|
||||||
|
tableName: 'table1',
|
||||||
|
columnNames: ['id'],
|
||||||
|
synchronize: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
synchronize: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'table2',
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
name: 'parentId',
|
||||||
|
tableName: 'table2',
|
||||||
|
type: 'uuid',
|
||||||
|
nullable: false,
|
||||||
|
isArray: false,
|
||||||
|
primary: false,
|
||||||
|
synchronize: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
indexes: [],
|
||||||
|
triggers: [],
|
||||||
|
constraints: [],
|
||||||
|
synchronize: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
warnings: ['[@ForeignKeyConstraint.referenceColumns] Unable to find column (Table1.foo)'],
|
||||||
|
};
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
import { Column, DatabaseSchema, ForeignKeyConstraint, Table } from 'src/sql-tools';
|
||||||
|
|
||||||
|
class Foo {}
|
||||||
|
|
||||||
|
@Table()
|
||||||
|
@ForeignKeyConstraint({
|
||||||
|
columns: ['parentId'],
|
||||||
|
referenceTable: () => Foo,
|
||||||
|
})
|
||||||
|
export class Table1 {
|
||||||
|
@Column()
|
||||||
|
parentId!: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const description = 'should warn against missing reference table';
|
||||||
|
export const schema: DatabaseSchema = {
|
||||||
|
name: 'postgres',
|
||||||
|
schemaName: 'public',
|
||||||
|
functions: [],
|
||||||
|
enums: [],
|
||||||
|
extensions: [],
|
||||||
|
parameters: [],
|
||||||
|
tables: [
|
||||||
|
{
|
||||||
|
name: 'table1',
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
name: 'parentId',
|
||||||
|
tableName: 'table1',
|
||||||
|
type: 'character varying',
|
||||||
|
nullable: false,
|
||||||
|
isArray: false,
|
||||||
|
primary: false,
|
||||||
|
synchronize: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
indexes: [],
|
||||||
|
triggers: [],
|
||||||
|
constraints: [],
|
||||||
|
synchronize: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
warnings: ['[@ForeignKeyConstraint.referenceTable] Unable to find table (Foo)'],
|
||||||
|
};
|
||||||
@@ -0,0 +1,120 @@
|
|||||||
|
import {
|
||||||
|
Column,
|
||||||
|
DatabaseConstraintType,
|
||||||
|
DatabaseSchema,
|
||||||
|
ForeignKeyConstraint,
|
||||||
|
PrimaryColumn,
|
||||||
|
Table,
|
||||||
|
} from 'src/sql-tools';
|
||||||
|
|
||||||
|
@Table()
|
||||||
|
export class Table1 {
|
||||||
|
@PrimaryColumn({ type: 'uuid' })
|
||||||
|
id1!: string;
|
||||||
|
|
||||||
|
@PrimaryColumn({ type: 'uuid' })
|
||||||
|
id2!: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Table()
|
||||||
|
@ForeignKeyConstraint({ columns: ['parentId1', 'parentId2'], referenceTable: () => Table1 })
|
||||||
|
export class Table2 {
|
||||||
|
@Column({ type: 'uuid' })
|
||||||
|
parentId1!: string;
|
||||||
|
|
||||||
|
@Column({ type: 'uuid' })
|
||||||
|
parentId2!: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const description = 'should create a foreign key constraint to the target table';
|
||||||
|
export const schema: DatabaseSchema = {
|
||||||
|
name: 'postgres',
|
||||||
|
schemaName: 'public',
|
||||||
|
functions: [],
|
||||||
|
enums: [],
|
||||||
|
extensions: [],
|
||||||
|
parameters: [],
|
||||||
|
tables: [
|
||||||
|
{
|
||||||
|
name: 'table1',
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
name: 'id1',
|
||||||
|
tableName: 'table1',
|
||||||
|
type: 'uuid',
|
||||||
|
nullable: false,
|
||||||
|
isArray: false,
|
||||||
|
primary: true,
|
||||||
|
synchronize: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'id2',
|
||||||
|
tableName: 'table1',
|
||||||
|
type: 'uuid',
|
||||||
|
nullable: false,
|
||||||
|
isArray: false,
|
||||||
|
primary: true,
|
||||||
|
synchronize: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
indexes: [],
|
||||||
|
triggers: [],
|
||||||
|
constraints: [
|
||||||
|
{
|
||||||
|
type: DatabaseConstraintType.PRIMARY_KEY,
|
||||||
|
name: 'PK_e457e8b1301b7bc06ef78188ee4',
|
||||||
|
tableName: 'table1',
|
||||||
|
columnNames: ['id1', 'id2'],
|
||||||
|
synchronize: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
synchronize: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'table2',
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
name: 'parentId1',
|
||||||
|
tableName: 'table2',
|
||||||
|
type: 'uuid',
|
||||||
|
nullable: false,
|
||||||
|
isArray: false,
|
||||||
|
primary: false,
|
||||||
|
synchronize: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'parentId2',
|
||||||
|
tableName: 'table2',
|
||||||
|
type: 'uuid',
|
||||||
|
nullable: false,
|
||||||
|
isArray: false,
|
||||||
|
primary: false,
|
||||||
|
synchronize: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
indexes: [
|
||||||
|
{
|
||||||
|
name: 'IDX_aed36d04470eba20161aa8b1dc',
|
||||||
|
tableName: 'table2',
|
||||||
|
columnNames: ['parentId1', 'parentId2'],
|
||||||
|
unique: false,
|
||||||
|
synchronize: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
triggers: [],
|
||||||
|
constraints: [
|
||||||
|
{
|
||||||
|
type: DatabaseConstraintType.FOREIGN_KEY,
|
||||||
|
name: 'FK_aed36d04470eba20161aa8b1dc6',
|
||||||
|
tableName: 'table2',
|
||||||
|
columnNames: ['parentId1', 'parentId2'],
|
||||||
|
referenceColumnNames: ['id1', 'id2'],
|
||||||
|
referenceTableName: 'table1',
|
||||||
|
synchronize: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
synchronize: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
warnings: [],
|
||||||
|
};
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
import {
|
||||||
|
Column,
|
||||||
|
DatabaseConstraintType,
|
||||||
|
DatabaseSchema,
|
||||||
|
ForeignKeyConstraint,
|
||||||
|
PrimaryColumn,
|
||||||
|
Table,
|
||||||
|
} from 'src/sql-tools';
|
||||||
|
|
||||||
|
@Table()
|
||||||
|
export class Table1 {
|
||||||
|
@PrimaryColumn({ type: 'uuid' })
|
||||||
|
id!: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Table()
|
||||||
|
@ForeignKeyConstraint({ columns: ['parentId'], referenceTable: () => Table1, index: false })
|
||||||
|
export class Table2 {
|
||||||
|
@Column({ type: 'uuid' })
|
||||||
|
parentId!: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const description = 'should create a foreign key constraint to the target table without an index';
|
||||||
|
export const schema: DatabaseSchema = {
|
||||||
|
name: 'postgres',
|
||||||
|
schemaName: 'public',
|
||||||
|
functions: [],
|
||||||
|
enums: [],
|
||||||
|
extensions: [],
|
||||||
|
parameters: [],
|
||||||
|
tables: [
|
||||||
|
{
|
||||||
|
name: 'table1',
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
name: 'id',
|
||||||
|
tableName: 'table1',
|
||||||
|
type: 'uuid',
|
||||||
|
nullable: false,
|
||||||
|
isArray: false,
|
||||||
|
primary: true,
|
||||||
|
synchronize: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
indexes: [],
|
||||||
|
triggers: [],
|
||||||
|
constraints: [
|
||||||
|
{
|
||||||
|
type: DatabaseConstraintType.PRIMARY_KEY,
|
||||||
|
name: 'PK_b249cc64cf63b8a22557cdc8537',
|
||||||
|
tableName: 'table1',
|
||||||
|
columnNames: ['id'],
|
||||||
|
synchronize: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
synchronize: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'table2',
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
name: 'parentId',
|
||||||
|
tableName: 'table2',
|
||||||
|
type: 'uuid',
|
||||||
|
nullable: false,
|
||||||
|
isArray: false,
|
||||||
|
primary: false,
|
||||||
|
synchronize: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
indexes: [],
|
||||||
|
triggers: [],
|
||||||
|
constraints: [
|
||||||
|
{
|
||||||
|
type: DatabaseConstraintType.FOREIGN_KEY,
|
||||||
|
name: 'FK_3fcca5cc563abf256fc346e3ff4',
|
||||||
|
tableName: 'table2',
|
||||||
|
columnNames: ['parentId'],
|
||||||
|
referenceColumnNames: ['id'],
|
||||||
|
referenceTableName: 'table1',
|
||||||
|
synchronize: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
synchronize: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
warnings: [],
|
||||||
|
};
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
import { Column, DatabaseConstraintType, DatabaseSchema, ForeignKeyConstraint, Table } from 'src/sql-tools';
|
||||||
|
|
||||||
|
@Table()
|
||||||
|
export class Table1 {
|
||||||
|
@Column()
|
||||||
|
foo!: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Table()
|
||||||
|
@ForeignKeyConstraint({
|
||||||
|
columns: ['bar'],
|
||||||
|
referenceTable: () => Table1,
|
||||||
|
referenceColumns: ['foo'],
|
||||||
|
})
|
||||||
|
export class Table2 {
|
||||||
|
@Column()
|
||||||
|
bar!: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const description = 'should create a foreign key constraint to the target table without a primary key';
|
||||||
|
export const schema: DatabaseSchema = {
|
||||||
|
name: 'postgres',
|
||||||
|
schemaName: 'public',
|
||||||
|
functions: [],
|
||||||
|
enums: [],
|
||||||
|
extensions: [],
|
||||||
|
parameters: [],
|
||||||
|
tables: [
|
||||||
|
{
|
||||||
|
name: 'table1',
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
name: 'foo',
|
||||||
|
tableName: 'table1',
|
||||||
|
type: 'character varying',
|
||||||
|
nullable: false,
|
||||||
|
isArray: false,
|
||||||
|
primary: false,
|
||||||
|
synchronize: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
indexes: [],
|
||||||
|
triggers: [],
|
||||||
|
constraints: [],
|
||||||
|
synchronize: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'table2',
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
name: 'bar',
|
||||||
|
tableName: 'table2',
|
||||||
|
type: 'character varying',
|
||||||
|
nullable: false,
|
||||||
|
isArray: false,
|
||||||
|
primary: false,
|
||||||
|
synchronize: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
indexes: [
|
||||||
|
{
|
||||||
|
name: 'IDX_7d9c784c98d12365d198d52e4e',
|
||||||
|
tableName: 'table2',
|
||||||
|
columnNames: ['bar'],
|
||||||
|
unique: false,
|
||||||
|
synchronize: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
triggers: [],
|
||||||
|
constraints: [
|
||||||
|
{
|
||||||
|
type: DatabaseConstraintType.FOREIGN_KEY,
|
||||||
|
name: 'FK_7d9c784c98d12365d198d52e4e6',
|
||||||
|
tableName: 'table2',
|
||||||
|
columnNames: ['bar'],
|
||||||
|
referenceTableName: 'table1',
|
||||||
|
referenceColumnNames: ['foo'],
|
||||||
|
synchronize: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
synchronize: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
warnings: [],
|
||||||
|
};
|
||||||
96
server/test/sql-tools/foreign-key-constraint.stub.ts
Normal file
96
server/test/sql-tools/foreign-key-constraint.stub.ts
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
import {
|
||||||
|
Column,
|
||||||
|
DatabaseConstraintType,
|
||||||
|
DatabaseSchema,
|
||||||
|
ForeignKeyConstraint,
|
||||||
|
PrimaryColumn,
|
||||||
|
Table,
|
||||||
|
} from 'src/sql-tools';
|
||||||
|
|
||||||
|
@Table()
|
||||||
|
export class Table1 {
|
||||||
|
@PrimaryColumn({ type: 'uuid' })
|
||||||
|
id!: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Table()
|
||||||
|
@ForeignKeyConstraint({ columns: ['parentId'], referenceTable: () => Table1 })
|
||||||
|
export class Table2 {
|
||||||
|
@Column({ type: 'uuid' })
|
||||||
|
parentId!: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const description = 'should create a foreign key constraint to the target table';
|
||||||
|
export const schema: DatabaseSchema = {
|
||||||
|
name: 'postgres',
|
||||||
|
schemaName: 'public',
|
||||||
|
functions: [],
|
||||||
|
enums: [],
|
||||||
|
extensions: [],
|
||||||
|
parameters: [],
|
||||||
|
tables: [
|
||||||
|
{
|
||||||
|
name: 'table1',
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
name: 'id',
|
||||||
|
tableName: 'table1',
|
||||||
|
type: 'uuid',
|
||||||
|
nullable: false,
|
||||||
|
isArray: false,
|
||||||
|
primary: true,
|
||||||
|
synchronize: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
indexes: [],
|
||||||
|
triggers: [],
|
||||||
|
constraints: [
|
||||||
|
{
|
||||||
|
type: DatabaseConstraintType.PRIMARY_KEY,
|
||||||
|
name: 'PK_b249cc64cf63b8a22557cdc8537',
|
||||||
|
tableName: 'table1',
|
||||||
|
columnNames: ['id'],
|
||||||
|
synchronize: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
synchronize: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'table2',
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
name: 'parentId',
|
||||||
|
tableName: 'table2',
|
||||||
|
type: 'uuid',
|
||||||
|
nullable: false,
|
||||||
|
isArray: false,
|
||||||
|
primary: false,
|
||||||
|
synchronize: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
indexes: [
|
||||||
|
{
|
||||||
|
name: 'IDX_3fcca5cc563abf256fc346e3ff',
|
||||||
|
tableName: 'table2',
|
||||||
|
columnNames: ['parentId'],
|
||||||
|
unique: false,
|
||||||
|
synchronize: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
triggers: [],
|
||||||
|
constraints: [
|
||||||
|
{
|
||||||
|
type: DatabaseConstraintType.FOREIGN_KEY,
|
||||||
|
name: 'FK_3fcca5cc563abf256fc346e3ff4',
|
||||||
|
tableName: 'table2',
|
||||||
|
columnNames: ['parentId'],
|
||||||
|
referenceColumnNames: ['id'],
|
||||||
|
referenceTableName: 'table1',
|
||||||
|
synchronize: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
synchronize: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
warnings: [],
|
||||||
|
};
|
||||||
@@ -22,7 +22,7 @@
|
|||||||
import { isEqual, omit } from 'lodash-es';
|
import { isEqual, omit } from 'lodash-es';
|
||||||
import { DateTime, Duration } from 'luxon';
|
import { DateTime, Duration } from 'luxon';
|
||||||
import maplibregl, { GlobeControl, type GeoJSONSource, type LngLatLike } from 'maplibre-gl';
|
import maplibregl, { GlobeControl, type GeoJSONSource, type LngLatLike } from 'maplibre-gl';
|
||||||
import { onDestroy, onMount } from 'svelte';
|
import { onDestroy, onMount, untrack } from 'svelte';
|
||||||
import { t } from 'svelte-i18n';
|
import { t } from 'svelte-i18n';
|
||||||
import {
|
import {
|
||||||
AttributionControl,
|
AttributionControl,
|
||||||
@@ -61,7 +61,7 @@
|
|||||||
mapMarkers = $bindable(),
|
mapMarkers = $bindable(),
|
||||||
showSettings = true,
|
showSettings = true,
|
||||||
zoom = undefined,
|
zoom = undefined,
|
||||||
center = $bindable(undefined),
|
center = undefined,
|
||||||
hash = false,
|
hash = false,
|
||||||
simplified = false,
|
simplified = false,
|
||||||
clickable = false,
|
clickable = false,
|
||||||
@@ -250,8 +250,14 @@
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let lastCenter: LngLatLike | undefined = undefined;
|
||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
|
if (!map || !center || isEqual(lastCenter, center)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
map?.jumpTo({ center, zoom });
|
map?.jumpTo({ center, zoom });
|
||||||
|
lastCenter = center;
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -171,7 +171,7 @@
|
|||||||
{#if sharedLinks.length > 0}
|
{#if sharedLinks.length > 0}
|
||||||
<div class="flex justify-between items-center">
|
<div class="flex justify-between items-center">
|
||||||
<Text>{$t('shared_links')}</Text>
|
<Text>{$t('shared_links')}</Text>
|
||||||
<Link href={AppRoute.SHARED_LINKS} class="text-sm">{$t('view_all')}</Link>
|
<Link href={AppRoute.SHARED_LINKS} onclick={() => onClose()} class="text-sm">{$t('view_all')}</Link>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Stack gap={4}>
|
<Stack gap={4}>
|
||||||
|
|||||||
Reference in New Issue
Block a user