mirror of
https://github.com/immich-app/immich.git
synced 2024-12-29 15:11:58 +00:00
Fix typeorm migrations (#297)
* fix: remove config parameter from typeorm cli and update config the config parameter is no longer supported since version 0.3 the config now needs to export a DataSource object to work with the 0.3 cli * fix: update all typeorm entities and migrations to be aligned with database structure * Fixed test-util import databaseConfig * Fixed column mismatch in raw query with new migration * Remove dist build directory when starting dev server Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
This commit is contained in:
parent
355038a91a
commit
e6d30d72fa
12 changed files with 128 additions and 17 deletions
6
Makefile
6
Makefile
|
@ -1,11 +1,11 @@
|
||||||
dev:
|
dev:
|
||||||
docker-compose -f ./docker/docker-compose.dev.yml up --remove-orphans
|
rm -rf ./server/dist && docker-compose -f ./docker/docker-compose.dev.yml up --remove-orphans
|
||||||
|
|
||||||
dev-update:
|
dev-update:
|
||||||
docker-compose -f ./docker/docker-compose.dev.yml up --build -V --remove-orphans
|
rm -rf ./server/dist && docker-compose -f ./docker/docker-compose.dev.yml up --build -V --remove-orphans
|
||||||
|
|
||||||
dev-scale:
|
dev-scale:
|
||||||
docker-compose -f ./docker/docker-compose.dev.yml up --build -V --scale immich-server=3 --remove-orphans
|
rm -rf ./server/dist && docker-compose -f ./docker/docker-compose.dev.yml up --build -V --scale immich-server=3 --remove-orphans
|
||||||
|
|
||||||
stage:
|
stage:
|
||||||
docker-compose -f ./docker/docker-compose.staging.yml up --build -V --remove-orphans
|
docker-compose -f ./docker/docker-compose.staging.yml up --build -V --remove-orphans
|
||||||
|
|
|
@ -405,7 +405,7 @@ export class AssetService {
|
||||||
(
|
(
|
||||||
TO_TSVECTOR('english', ARRAY_TO_STRING(si.tags, ',')) @@ PLAINTO_TSQUERY('english', $2) OR
|
TO_TSVECTOR('english', ARRAY_TO_STRING(si.tags, ',')) @@ PLAINTO_TSQUERY('english', $2) OR
|
||||||
TO_TSVECTOR('english', ARRAY_TO_STRING(si.objects, ',')) @@ PLAINTO_TSQUERY('english', $2) OR
|
TO_TSVECTOR('english', ARRAY_TO_STRING(si.objects, ',')) @@ PLAINTO_TSQUERY('english', $2) OR
|
||||||
e.exif_text_searchable_column @@ PLAINTO_TSQUERY('english', $2)
|
e."exifTextSearchableColumn" @@ PLAINTO_TSQUERY('english', $2)
|
||||||
);
|
);
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { CanActivate, ExecutionContext } from '@nestjs/common';
|
||||||
import { TestingModuleBuilder } from '@nestjs/testing';
|
import { TestingModuleBuilder } from '@nestjs/testing';
|
||||||
import { AuthUserDto } from '../src/decorators/auth-user.decorator';
|
import { AuthUserDto } from '../src/decorators/auth-user.decorator';
|
||||||
import { JwtAuthGuard } from '../src/modules/immich-jwt/guards/jwt-auth.guard';
|
import { JwtAuthGuard } from '../src/modules/immich-jwt/guards/jwt-auth.guard';
|
||||||
import databaseConfig from '@app/database/config/database.config';
|
import { databaseConfig } from '@app/database/config/database.config';
|
||||||
|
|
||||||
type CustomAuthCallback = () => AuthUserDto;
|
type CustomAuthCallback = () => AuthUserDto;
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { TypeOrmModuleOptions } from '@nestjs/typeorm';
|
|
||||||
import { PostgresConnectionOptions } from 'typeorm/driver/postgres/PostgresConnectionOptions';
|
import { PostgresConnectionOptions } from 'typeorm/driver/postgres/PostgresConnectionOptions';
|
||||||
|
import {DataSource} from "typeorm";
|
||||||
|
|
||||||
export const databaseConfig: PostgresConnectionOptions = {
|
export const databaseConfig: PostgresConnectionOptions = {
|
||||||
type: 'postgres',
|
type: 'postgres',
|
||||||
|
@ -14,4 +14,4 @@ export const databaseConfig: PostgresConnectionOptions = {
|
||||||
migrationsRun: true,
|
migrationsRun: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default databaseConfig;
|
export const dataSource = new DataSource(databaseConfig);
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import { Column, Entity, JoinColumn, ManyToOne, PrimaryGeneratedColumn, Unique } from 'typeorm';
|
import {Column, Entity, JoinColumn, ManyToOne, OneToOne, PrimaryGeneratedColumn, Unique} from 'typeorm';
|
||||||
import { AlbumEntity } from './album.entity';
|
import { AlbumEntity } from './album.entity';
|
||||||
import { AssetEntity } from './asset.entity';
|
import { AssetEntity } from './asset.entity';
|
||||||
|
|
||||||
@Entity('asset_album')
|
@Entity('asset_album')
|
||||||
@Unique('PK_unique_asset_in_album', ['albumId', 'assetId'])
|
@Unique('UQ_unique_asset_in_album', ['albumId', 'assetId'])
|
||||||
export class AssetAlbumEntity {
|
export class AssetAlbumEntity {
|
||||||
@PrimaryGeneratedColumn()
|
@PrimaryGeneratedColumn()
|
||||||
id!: string;
|
id!: string;
|
||||||
|
@ -12,6 +12,7 @@ export class AssetAlbumEntity {
|
||||||
albumId!: string;
|
albumId!: string;
|
||||||
|
|
||||||
@Column()
|
@Column()
|
||||||
|
@OneToOne(() => AssetEntity, (entity) => entity.id)
|
||||||
assetId!: string;
|
assetId!: string;
|
||||||
|
|
||||||
@ManyToOne(() => AlbumEntity, (album) => album.assets, {
|
@ManyToOne(() => AlbumEntity, (album) => album.assets, {
|
||||||
|
|
|
@ -26,10 +26,10 @@ export class AssetEntity {
|
||||||
@Column({ type: 'varchar', nullable: true })
|
@Column({ type: 'varchar', nullable: true })
|
||||||
resizePath!: string | null;
|
resizePath!: string | null;
|
||||||
|
|
||||||
@Column({ type: 'varchar', nullable: true })
|
@Column({ type: 'varchar', nullable: true, default: '' })
|
||||||
webpPath!: string | null;
|
webpPath!: string | null;
|
||||||
|
|
||||||
@Column({ type: 'varchar', nullable: true })
|
@Column({ type: 'varchar', nullable: true, default: '' })
|
||||||
encodedVideoPath!: string;
|
encodedVideoPath!: string;
|
||||||
|
|
||||||
@Column()
|
@Column()
|
||||||
|
|
|
@ -73,4 +73,19 @@ export class ExifEntity {
|
||||||
@OneToOne(() => AssetEntity, { onDelete: 'CASCADE', nullable: true })
|
@OneToOne(() => AssetEntity, { onDelete: 'CASCADE', nullable: true })
|
||||||
@JoinColumn({ name: 'assetId', referencedColumnName: 'id' })
|
@JoinColumn({ name: 'assetId', referencedColumnName: 'id' })
|
||||||
asset?: ExifEntity;
|
asset?: ExifEntity;
|
||||||
|
|
||||||
|
@Index("exif_text_searchable", { synchronize: false })
|
||||||
|
@Column({
|
||||||
|
type: 'tsvector',
|
||||||
|
generatedType: 'STORED',
|
||||||
|
asExpression: `TO_TSVECTOR('english',
|
||||||
|
COALESCE(make, '') || ' ' ||
|
||||||
|
COALESCE(model, '') || ' ' ||
|
||||||
|
COALESCE(orientation, '') || ' ' ||
|
||||||
|
COALESCE("lensModel", '') || ' ' ||
|
||||||
|
COALESCE("city", '') || ' ' ||
|
||||||
|
COALESCE("state", '') || ' ' ||
|
||||||
|
COALESCE("country", ''))`
|
||||||
|
})
|
||||||
|
exifTextSearchableColumn!: string
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,13 +5,13 @@ export class UserEntity {
|
||||||
@PrimaryGeneratedColumn('uuid')
|
@PrimaryGeneratedColumn('uuid')
|
||||||
id!: string;
|
id!: string;
|
||||||
|
|
||||||
@Column()
|
@Column({ default: '' })
|
||||||
firstName!: string;
|
firstName!: string;
|
||||||
|
|
||||||
@Column()
|
@Column({ default: '' })
|
||||||
lastName!: string;
|
lastName!: string;
|
||||||
|
|
||||||
@Column()
|
@Column({ default: false })
|
||||||
isAdmin!: boolean;
|
isAdmin!: boolean;
|
||||||
|
|
||||||
@Column()
|
@Column()
|
||||||
|
@ -23,10 +23,10 @@ export class UserEntity {
|
||||||
@Column({ select: false })
|
@Column({ select: false })
|
||||||
salt?: string;
|
salt?: string;
|
||||||
|
|
||||||
@Column()
|
@Column({ default: '' })
|
||||||
profileImagePath!: string;
|
profileImagePath!: string;
|
||||||
|
|
||||||
@Column()
|
@Column({ default: true })
|
||||||
shouldChangePassword!: boolean;
|
shouldChangePassword!: boolean;
|
||||||
|
|
||||||
@CreateDateColumn()
|
@CreateDateColumn()
|
||||||
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
import { MigrationInterface, QueryRunner } from "typeorm"
|
||||||
|
|
||||||
|
export class RenameAssetAlbumIdSequence1656888591977 implements MigrationInterface {
|
||||||
|
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(`alter sequence asset_shared_album_id_seq rename to asset_album_id_seq;`);
|
||||||
|
await queryRunner.query(`alter table asset_album alter column id set default nextval('asset_album_id_seq'::regclass);`);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(`alter sequence asset_album_id_seq rename to asset_shared_album_id_seq;`);
|
||||||
|
await queryRunner.query(`alter table asset_album alter column id set default nextval('asset_shared_album_id_seq'::regclass);`);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||||
|
|
||||||
|
export class DropExifTextSearchableColumns1656888918620 implements MigrationInterface {
|
||||||
|
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(`ALTER TABLE "exif" DROP COLUMN "exif_text_searchable_column"`);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(`
|
||||||
|
ALTER TABLE exif
|
||||||
|
DROP COLUMN IF EXISTS exif_text_searchable_column;
|
||||||
|
|
||||||
|
ALTER TABLE exif
|
||||||
|
ADD COLUMN IF NOT EXISTS exif_text_searchable_column tsvector
|
||||||
|
GENERATED ALWAYS AS (
|
||||||
|
TO_TSVECTOR('english',
|
||||||
|
COALESCE(make, '') || ' ' ||
|
||||||
|
COALESCE(model, '') || ' ' ||
|
||||||
|
COALESCE(orientation, '') || ' ' ||
|
||||||
|
COALESCE("lensModel", '') || ' ' ||
|
||||||
|
COALESCE("city", '') || ' ' ||
|
||||||
|
COALESCE("state", '') || ' ' ||
|
||||||
|
COALESCE("country", '')
|
||||||
|
)
|
||||||
|
) STORED;
|
||||||
|
|
||||||
|
CREATE INDEX exif_text_searchable_idx
|
||||||
|
ON exif
|
||||||
|
USING GIN (exif_text_searchable_column);
|
||||||
|
`);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,46 @@
|
||||||
|
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||||
|
|
||||||
|
export class MatchMigrationsWithTypeORMEntities1656889061566 implements MigrationInterface {
|
||||||
|
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(`ALTER TABLE "exif" ADD "exifTextSearchableColumn" tsvector GENERATED ALWAYS AS (TO_TSVECTOR('english',
|
||||||
|
COALESCE(make, '') || ' ' ||
|
||||||
|
COALESCE(model, '') || ' ' ||
|
||||||
|
COALESCE(orientation, '') || ' ' ||
|
||||||
|
COALESCE("lensModel", '') || ' ' ||
|
||||||
|
COALESCE("city", '') || ' ' ||
|
||||||
|
COALESCE("state", '') || ' ' ||
|
||||||
|
COALESCE("country", ''))) STORED`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "exif" ALTER COLUMN "exifTextSearchableColumn" SET NOT NULL`);
|
||||||
|
await queryRunner.query(`DELETE FROM "typeorm_metadata" WHERE "type" = $1 AND "name" = $2 AND "database" = $3 AND "schema" = $4 AND "table" = $5`, ["GENERATED_COLUMN","exifTextSearchableColumn","postgres","public","exif"]);
|
||||||
|
await queryRunner.query(`INSERT INTO "typeorm_metadata"("database", "schema", "table", "type", "name", "value") VALUES ($1, $2, $3, $4, $5, $6)`, ["postgres","public","exif","GENERATED_COLUMN","exifTextSearchableColumn","TO_TSVECTOR('english',\n COALESCE(make, '') || ' ' ||\n COALESCE(model, '') || ' ' ||\n COALESCE(orientation, '') || ' ' ||\n COALESCE(\"lensModel\", '') || ' ' ||\n COALESCE(\"city\", '') || ' ' ||\n COALESCE(\"state\", '') || ' ' ||\n COALESCE(\"country\", ''))"]);
|
||||||
|
await queryRunner.query(`ALTER TABLE "users" ALTER COLUMN "firstName" SET NOT NULL`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "users" ALTER COLUMN "lastName" SET NOT NULL`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "users" ALTER COLUMN "isAdmin" SET NOT NULL`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "users" ALTER COLUMN "profileImagePath" SET NOT NULL`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "users" ALTER COLUMN "shouldChangePassword" SET NOT NULL`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "asset_album" DROP CONSTRAINT "FK_a8b79a84996cef6ba6a3662825d"`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "asset_album" DROP CONSTRAINT "FK_64f2e7d68d1d1d8417acc844a4a"`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "asset_album" DROP CONSTRAINT "UQ_a1e2734a1ce361e7a26f6b28288"`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "asset_album" ADD CONSTRAINT "UQ_unique_asset_in_album" UNIQUE ("albumId", "assetId")`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "asset_album" ADD CONSTRAINT "FK_256a30a03a4a0aff0394051397d" FOREIGN KEY ("albumId") REFERENCES "albums"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "asset_album" ADD CONSTRAINT "FK_7ae4e03729895bf87e056d7b598" FOREIGN KEY ("assetId") REFERENCES "assets"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(`ALTER TABLE "users" ALTER COLUMN "shouldChangePassword" DROP NOT NULL`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "users" ALTER COLUMN "profileImagePath" DROP NOT NULL`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "users" ALTER COLUMN "isAdmin" DROP NOT NULL`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "users" ALTER COLUMN "lastName" DROP NOT NULL`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "users" ALTER COLUMN "firstName" DROP NOT NULL`);
|
||||||
|
await queryRunner.query(`DELETE FROM "typeorm_metadata" WHERE "type" = $1 AND "name" = $2 AND "database" = $3 AND "schema" = $4 AND "table" = $5`, ["GENERATED_COLUMN","exifTextSearchableColumn","immich","public","exif"]);
|
||||||
|
await queryRunner.query(`ALTER TABLE "exif" DROP COLUMN "exifTextSearchableColumn"`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "asset_album" DROP CONSTRAINT "FK_7ae4e03729895bf87e056d7b598"`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "asset_album" DROP CONSTRAINT "FK_256a30a03a4a0aff0394051397d"`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "asset_album" DROP CONSTRAINT "UQ_unique_asset_in_album"`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "asset_album" ADD CONSTRAINT "UQ_a1e2734a1ce361e7a26f6b28288" UNIQUE ("albumId", "assetId")`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "asset_album" ADD CONSTRAINT "FK_64f2e7d68d1d1d8417acc844a4a" FOREIGN KEY ("assetId") REFERENCES "assets"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "asset_album" ADD CONSTRAINT "FK_a8b79a84996cef6ba6a3662825d" FOREIGN KEY ("albumId") REFERENCES "albums"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -22,7 +22,7 @@
|
||||||
"test:cov": "jest --coverage",
|
"test:cov": "jest --coverage",
|
||||||
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
|
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
|
||||||
"test:e2e": "jest --config ./apps/immich/test/jest-e2e.json",
|
"test:e2e": "jest --config ./apps/immich/test/jest-e2e.json",
|
||||||
"typeorm": "node --require ts-node/register ./node_modules/typeorm/cli.js --config libs/database/src/config/database.config.ts"
|
"typeorm": "node --require ts-node/register ./node_modules/typeorm/cli.js"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@mapbox/mapbox-sdk": "^0.13.3",
|
"@mapbox/mapbox-sdk": "^0.13.3",
|
||||||
|
|
Loading…
Reference in a new issue