mirror of
https://github.com/immich-app/immich.git
synced 2024-11-24 08:52:28 +02:00
feat(server): improve thumbnail relation and updating (#1897)
* feat(server): improve thumbnail relation and updating * improve query + update tests and migration * make sure uuids are valid in migration * fix unit test
This commit is contained in:
parent
2ac54ce4bd
commit
bdf35b6688
@ -1,4 +1,4 @@
|
|||||||
import { AlbumEntity, AssetEntity, UserEntity } from '@app/infra';
|
import { AlbumEntity, AssetEntity, dataSource, UserEntity } from '@app/infra';
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { InjectRepository } from '@nestjs/typeorm';
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
import { Repository, Not, IsNull, FindManyOptions } from 'typeorm';
|
import { Repository, Not, IsNull, FindManyOptions } from 'typeorm';
|
||||||
@ -22,6 +22,7 @@ export interface IAlbumRepository {
|
|||||||
removeAssets(album: AlbumEntity, removeAssets: RemoveAssetsDto): Promise<number>;
|
removeAssets(album: AlbumEntity, removeAssets: RemoveAssetsDto): Promise<number>;
|
||||||
addAssets(album: AlbumEntity, addAssetsDto: AddAssetsDto): Promise<AddAssetsResponseDto>;
|
addAssets(album: AlbumEntity, addAssetsDto: AddAssetsDto): Promise<AddAssetsResponseDto>;
|
||||||
updateAlbum(album: AlbumEntity, updateAlbumDto: UpdateAlbumDto): Promise<AlbumEntity>;
|
updateAlbum(album: AlbumEntity, updateAlbumDto: UpdateAlbumDto): Promise<AlbumEntity>;
|
||||||
|
updateThumbnails(): Promise<number | undefined>;
|
||||||
getListByAssetId(userId: string, assetId: string): Promise<AlbumEntity[]>;
|
getListByAssetId(userId: string, assetId: string): Promise<AlbumEntity[]>;
|
||||||
getCountByUserId(userId: string): Promise<AlbumCountResponseDto>;
|
getCountByUserId(userId: string): Promise<AlbumCountResponseDto>;
|
||||||
getSharedWithUserAlbumCount(userId: string, assetId: string): Promise<number>;
|
getSharedWithUserAlbumCount(userId: string, assetId: string): Promise<number>;
|
||||||
@ -34,6 +35,9 @@ export class AlbumRepository implements IAlbumRepository {
|
|||||||
constructor(
|
constructor(
|
||||||
@InjectRepository(AlbumEntity)
|
@InjectRepository(AlbumEntity)
|
||||||
private albumRepository: Repository<AlbumEntity>,
|
private albumRepository: Repository<AlbumEntity>,
|
||||||
|
|
||||||
|
@InjectRepository(AssetEntity)
|
||||||
|
private assetRepository: Repository<AssetEntity>,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async getPublicSharingList(ownerId: string): Promise<AlbumEntity[]> {
|
async getPublicSharingList(ownerId: string): Promise<AlbumEntity[]> {
|
||||||
@ -208,6 +212,48 @@ export class AlbumRepository implements IAlbumRepository {
|
|||||||
return this.albumRepository.save(album);
|
return this.albumRepository.save(album);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Makes sure all thumbnails for albums are updated by:
|
||||||
|
* - Removing thumbnails from albums without assets
|
||||||
|
* - Removing references of thumbnails to assets outside the album
|
||||||
|
* - Setting a thumbnail when none is set and the album contains assets
|
||||||
|
*
|
||||||
|
* @returns Amount of updated album thumbnails or undefined when unknown
|
||||||
|
*/
|
||||||
|
async updateThumbnails(): Promise<number | undefined> {
|
||||||
|
// Subquery for getting a new thumbnail.
|
||||||
|
const newThumbnail = this.assetRepository
|
||||||
|
.createQueryBuilder('assets')
|
||||||
|
.select('albums_assets2.assetsId')
|
||||||
|
.addFrom('albums_assets_assets', 'albums_assets2')
|
||||||
|
.where('albums_assets2.assetsId = assets.id')
|
||||||
|
.andWhere('albums_assets2.albumsId = "albums"."id"') // Reference to albums.id outside this query
|
||||||
|
.orderBy('assets.fileCreatedAt', 'DESC')
|
||||||
|
.limit(1);
|
||||||
|
|
||||||
|
// Using dataSource, because there is no direct access to albums_assets_assets.
|
||||||
|
const albumHasAssets = dataSource
|
||||||
|
.createQueryBuilder()
|
||||||
|
.select('1')
|
||||||
|
.from('albums_assets_assets', 'albums_assets')
|
||||||
|
.where('"albums"."id" = "albums_assets"."albumsId"');
|
||||||
|
|
||||||
|
const albumContainsThumbnail = albumHasAssets
|
||||||
|
.clone()
|
||||||
|
.andWhere('"albums"."albumThumbnailAssetId" = "albums_assets"."assetsId"');
|
||||||
|
|
||||||
|
const updateAlbums = this.albumRepository
|
||||||
|
.createQueryBuilder('albums')
|
||||||
|
.update(AlbumEntity)
|
||||||
|
.set({ albumThumbnailAssetId: () => `(${newThumbnail.getQuery()})` })
|
||||||
|
.where(`"albums"."albumThumbnailAssetId" IS NULL AND EXISTS (${albumHasAssets.getQuery()})`)
|
||||||
|
.orWhere(`"albums"."albumThumbnailAssetId" IS NOT NULL AND NOT EXISTS (${albumContainsThumbnail.getQuery()})`);
|
||||||
|
|
||||||
|
const result = await updateAlbums.execute();
|
||||||
|
|
||||||
|
return result.affected;
|
||||||
|
}
|
||||||
|
|
||||||
async getSharedWithUserAlbumCount(userId: string, assetId: string): Promise<number> {
|
async getSharedWithUserAlbumCount(userId: string, assetId: string): Promise<number> {
|
||||||
return this.albumRepository.count({
|
return this.albumRepository.count({
|
||||||
where: [
|
where: [
|
||||||
|
@ -2,7 +2,7 @@ import { Module } from '@nestjs/common';
|
|||||||
import { AlbumService } from './album.service';
|
import { AlbumService } from './album.service';
|
||||||
import { AlbumController } from './album.controller';
|
import { AlbumController } from './album.controller';
|
||||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||||
import { AlbumEntity } from '@app/infra';
|
import { AlbumEntity, AssetEntity } from '@app/infra';
|
||||||
import { AlbumRepository, IAlbumRepository } from './album-repository';
|
import { AlbumRepository, IAlbumRepository } from './album-repository';
|
||||||
import { DownloadModule } from '../../modules/download/download.module';
|
import { DownloadModule } from '../../modules/download/download.module';
|
||||||
|
|
||||||
@ -12,7 +12,7 @@ const ALBUM_REPOSITORY_PROVIDER = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [TypeOrmModule.forFeature([AlbumEntity]), DownloadModule],
|
imports: [TypeOrmModule.forFeature([AlbumEntity, AssetEntity]), DownloadModule],
|
||||||
controllers: [AlbumController],
|
controllers: [AlbumController],
|
||||||
providers: [AlbumService, ALBUM_REPOSITORY_PROVIDER],
|
providers: [AlbumService, ALBUM_REPOSITORY_PROVIDER],
|
||||||
exports: [ALBUM_REPOSITORY_PROVIDER],
|
exports: [ALBUM_REPOSITORY_PROVIDER],
|
||||||
|
@ -129,6 +129,7 @@ describe('Album service', () => {
|
|||||||
removeAssets: jest.fn(),
|
removeAssets: jest.fn(),
|
||||||
removeUser: jest.fn(),
|
removeUser: jest.fn(),
|
||||||
updateAlbum: jest.fn(),
|
updateAlbum: jest.fn(),
|
||||||
|
updateThumbnails: jest.fn(),
|
||||||
getListByAssetId: jest.fn(),
|
getListByAssetId: jest.fn(),
|
||||||
getCountByUserId: jest.fn(),
|
getCountByUserId: jest.fn(),
|
||||||
getSharedWithUserAlbumCount: jest.fn(),
|
getSharedWithUserAlbumCount: jest.fn(),
|
||||||
@ -502,59 +503,47 @@ describe('Album service', () => {
|
|||||||
it('updates the album thumbnail by listing all albums', async () => {
|
it('updates the album thumbnail by listing all albums', async () => {
|
||||||
const albumEntity = _getOwnedAlbum();
|
const albumEntity = _getOwnedAlbum();
|
||||||
const assetEntity = new AssetEntity();
|
const assetEntity = new AssetEntity();
|
||||||
const newThumbnailAssetId = 'e5e65c02-b889-4f3c-afe1-a39a96d578ed';
|
const newThumbnailAsset = new AssetEntity();
|
||||||
|
newThumbnailAsset.id = 'e5e65c02-b889-4f3c-afe1-a39a96d578ed';
|
||||||
|
|
||||||
albumEntity.albumThumbnailAssetId = 'nonexistent';
|
albumEntity.albumThumbnailAssetId = 'nonexistent';
|
||||||
assetEntity.id = newThumbnailAssetId;
|
assetEntity.id = newThumbnailAsset.id;
|
||||||
albumEntity.assets = [assetEntity];
|
albumEntity.assets = [assetEntity];
|
||||||
albumRepositoryMock.getList.mockImplementation(async () => [albumEntity]);
|
albumRepositoryMock.getList.mockImplementation(async () => [albumEntity]);
|
||||||
albumRepositoryMock.updateAlbum.mockImplementation(async () => ({
|
albumRepositoryMock.updateThumbnails.mockImplementation(async () => {
|
||||||
...albumEntity,
|
albumEntity.albumThumbnailAsset = newThumbnailAsset;
|
||||||
albumThumbnailAssetId: newThumbnailAssetId,
|
albumEntity.albumThumbnailAssetId = newThumbnailAsset.id;
|
||||||
}));
|
|
||||||
|
return 1;
|
||||||
|
});
|
||||||
|
|
||||||
const result = await sut.getAllAlbums(authUser, {});
|
const result = await sut.getAllAlbums(authUser, {});
|
||||||
|
|
||||||
expect(result).toHaveLength(1);
|
expect(result).toHaveLength(1);
|
||||||
expect(result[0].albumThumbnailAssetId).toEqual(newThumbnailAssetId);
|
expect(result[0].albumThumbnailAssetId).toEqual(newThumbnailAsset.id);
|
||||||
expect(albumRepositoryMock.getList).toHaveBeenCalledTimes(1);
|
expect(albumRepositoryMock.getList).toHaveBeenCalledTimes(1);
|
||||||
expect(albumRepositoryMock.updateAlbum).toHaveBeenCalledTimes(1);
|
expect(albumRepositoryMock.updateThumbnails).toHaveBeenCalledTimes(1);
|
||||||
expect(albumRepositoryMock.getList).toHaveBeenCalledWith(albumEntity.ownerId, {});
|
expect(albumRepositoryMock.getList).toHaveBeenCalledWith(albumEntity.ownerId, {});
|
||||||
expect(albumRepositoryMock.updateAlbum).toHaveBeenCalledWith(albumEntity, {
|
|
||||||
albumThumbnailAssetId: newThumbnailAssetId,
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('removes the thumbnail for an empty album', async () => {
|
it('removes the thumbnail for an empty album', async () => {
|
||||||
const albumEntity = _getOwnedAlbum();
|
const albumEntity = _getOwnedAlbum();
|
||||||
const newAlbumEntity = { ...albumEntity, albumThumbnailAssetId: null };
|
|
||||||
|
|
||||||
albumEntity.albumThumbnailAssetId = 'e5e65c02-b889-4f3c-afe1-a39a96d578ed';
|
albumEntity.albumThumbnailAssetId = 'e5e65c02-b889-4f3c-afe1-a39a96d578ed';
|
||||||
albumRepositoryMock.getList.mockImplementation(async () => [albumEntity]);
|
albumRepositoryMock.getList.mockImplementation(async () => [albumEntity]);
|
||||||
albumRepositoryMock.updateAlbum.mockImplementation(async () => newAlbumEntity);
|
albumRepositoryMock.updateThumbnails.mockImplementation(async () => {
|
||||||
|
albumEntity.albumThumbnailAsset = null;
|
||||||
|
albumEntity.albumThumbnailAssetId = null;
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
});
|
||||||
|
|
||||||
const result = await sut.getAllAlbums(authUser, {});
|
const result = await sut.getAllAlbums(authUser, {});
|
||||||
|
|
||||||
expect(result).toHaveLength(1);
|
expect(result).toHaveLength(1);
|
||||||
expect(result[0].albumThumbnailAssetId).toBeNull();
|
expect(result[0].albumThumbnailAssetId).toBeNull();
|
||||||
expect(albumRepositoryMock.getList).toHaveBeenCalledTimes(1);
|
expect(albumRepositoryMock.getList).toHaveBeenCalledTimes(1);
|
||||||
expect(albumRepositoryMock.updateAlbum).toHaveBeenCalledTimes(1);
|
expect(albumRepositoryMock.updateThumbnails).toHaveBeenCalledTimes(1);
|
||||||
expect(albumRepositoryMock.getList).toHaveBeenCalledWith(albumEntity.ownerId, {});
|
|
||||||
expect(albumRepositoryMock.updateAlbum).toHaveBeenCalledWith(newAlbumEntity, {
|
|
||||||
albumThumbnailAssetId: undefined,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('listing empty albums does not unnecessarily update the album', async () => {
|
|
||||||
const albumEntity = _getOwnedAlbum();
|
|
||||||
albumRepositoryMock.getList.mockImplementation(async () => [albumEntity]);
|
|
||||||
albumRepositoryMock.updateAlbum.mockImplementation(async () => albumEntity);
|
|
||||||
|
|
||||||
const result = await sut.getAllAlbums(authUser, {});
|
|
||||||
|
|
||||||
expect(result).toHaveLength(1);
|
|
||||||
expect(albumRepositoryMock.getList).toHaveBeenCalledTimes(1);
|
|
||||||
expect(albumRepositoryMock.updateAlbum).toHaveBeenCalledTimes(0);
|
|
||||||
expect(albumRepositoryMock.getList).toHaveBeenCalledWith(albumEntity.ownerId, {});
|
expect(albumRepositoryMock.getList).toHaveBeenCalledWith(albumEntity.ownerId, {});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -41,6 +41,8 @@ export class AlbumService {
|
|||||||
albumId: string;
|
albumId: string;
|
||||||
validateIsOwner?: boolean;
|
validateIsOwner?: boolean;
|
||||||
}): Promise<AlbumEntity> {
|
}): Promise<AlbumEntity> {
|
||||||
|
await this.albumRepository.updateThumbnails();
|
||||||
|
|
||||||
const album = await this.albumRepository.get(albumId);
|
const album = await this.albumRepository.get(albumId);
|
||||||
if (!album) {
|
if (!album) {
|
||||||
throw new NotFoundException('Album Not Found');
|
throw new NotFoundException('Album Not Found');
|
||||||
@ -67,6 +69,8 @@ export class AlbumService {
|
|||||||
* @returns All Shared Album And Its Members
|
* @returns All Shared Album And Its Members
|
||||||
*/
|
*/
|
||||||
async getAllAlbums(authUser: AuthUserDto, getAlbumsDto: GetAlbumsDto): Promise<AlbumResponseDto[]> {
|
async getAllAlbums(authUser: AuthUserDto, getAlbumsDto: GetAlbumsDto): Promise<AlbumResponseDto[]> {
|
||||||
|
await this.albumRepository.updateThumbnails();
|
||||||
|
|
||||||
let albums: AlbumEntity[];
|
let albums: AlbumEntity[];
|
||||||
if (typeof getAlbumsDto.assetId === 'string') {
|
if (typeof getAlbumsDto.assetId === 'string') {
|
||||||
albums = await this.albumRepository.getListByAssetId(authUser.id, getAlbumsDto.assetId);
|
albums = await this.albumRepository.getListByAssetId(authUser.id, getAlbumsDto.assetId);
|
||||||
@ -81,10 +85,6 @@ export class AlbumService {
|
|||||||
|
|
||||||
albums = _.uniqBy(albums, (album) => album.id);
|
albums = _.uniqBy(albums, (album) => album.id);
|
||||||
|
|
||||||
for (const album of albums) {
|
|
||||||
await this._checkValidThumbnail(album);
|
|
||||||
}
|
|
||||||
|
|
||||||
return albums.map((album) => mapAlbumExcludeAssetInfo(album));
|
return albums.map((album) => mapAlbumExcludeAssetInfo(album));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -131,10 +131,6 @@ export class AlbumService {
|
|||||||
const deletedCount = await this.albumRepository.removeAssets(album, removeAssetsDto);
|
const deletedCount = await this.albumRepository.removeAssets(album, removeAssetsDto);
|
||||||
const newAlbum = await this._getAlbum({ authUser, albumId });
|
const newAlbum = await this._getAlbum({ authUser, albumId });
|
||||||
|
|
||||||
if (newAlbum) {
|
|
||||||
await this._checkValidThumbnail(newAlbum);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (deletedCount !== removeAssetsDto.assetIds.length) {
|
if (deletedCount !== removeAssetsDto.assetIds.length) {
|
||||||
throw new BadRequestException('Some assets were not found in the album');
|
throw new BadRequestException('Some assets were not found in the album');
|
||||||
}
|
}
|
||||||
@ -191,24 +187,6 @@ export class AlbumService {
|
|||||||
return this.downloadService.downloadArchive(album.albumName, assets);
|
return this.downloadService.downloadArchive(album.albumName, assets);
|
||||||
}
|
}
|
||||||
|
|
||||||
async _checkValidThumbnail(album: AlbumEntity) {
|
|
||||||
const assets = album.assets || [];
|
|
||||||
|
|
||||||
// Check if the album's thumbnail is invalid by referencing
|
|
||||||
// an asset outside the album.
|
|
||||||
const invalid = assets.length > 0 && !assets.some((asset) => asset.id === album.albumThumbnailAssetId);
|
|
||||||
|
|
||||||
// Check if an empty album still has a thumbnail.
|
|
||||||
const isEmptyWithThumbnail = assets.length === 0 && album.albumThumbnailAssetId !== null;
|
|
||||||
|
|
||||||
if (invalid || isEmptyWithThumbnail) {
|
|
||||||
const albumThumbnailAssetId = assets[0]?.id;
|
|
||||||
|
|
||||||
album.albumThumbnailAssetId = albumThumbnailAssetId || null;
|
|
||||||
await this.albumRepository.updateAlbum(album, { albumThumbnailAssetId });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async createAlbumSharedLink(authUser: AuthUserDto, dto: CreateAlbumShareLinkDto): Promise<SharedLinkResponseDto> {
|
async createAlbumSharedLink(authUser: AuthUserDto, dto: CreateAlbumShareLinkDto): Promise<SharedLinkResponseDto> {
|
||||||
const album = await this._getAlbum({ authUser, albumId: dto.albumId });
|
const album = await this._getAlbum({ authUser, albumId: dto.albumId });
|
||||||
|
|
||||||
|
@ -163,6 +163,7 @@ export const albumStub = {
|
|||||||
ownerId: authStub.admin.id,
|
ownerId: authStub.admin.id,
|
||||||
owner: userEntityStub.admin,
|
owner: userEntityStub.admin,
|
||||||
assets: [],
|
assets: [],
|
||||||
|
albumThumbnailAsset: null,
|
||||||
albumThumbnailAssetId: null,
|
albumThumbnailAssetId: null,
|
||||||
createdAt: new Date().toISOString(),
|
createdAt: new Date().toISOString(),
|
||||||
updatedAt: new Date().toISOString(),
|
updatedAt: new Date().toISOString(),
|
||||||
@ -422,6 +423,7 @@ export const sharedLinkStub = {
|
|||||||
albumName: 'Test Album',
|
albumName: 'Test Album',
|
||||||
createdAt: today.toISOString(),
|
createdAt: today.toISOString(),
|
||||||
updatedAt: today.toISOString(),
|
updatedAt: today.toISOString(),
|
||||||
|
albumThumbnailAsset: null,
|
||||||
albumThumbnailAssetId: null,
|
albumThumbnailAssetId: null,
|
||||||
sharedUsers: [],
|
sharedUsers: [],
|
||||||
sharedLinks: [],
|
sharedLinks: [],
|
||||||
|
@ -33,7 +33,10 @@ export class AlbumEntity {
|
|||||||
@UpdateDateColumn({ type: 'timestamptz' })
|
@UpdateDateColumn({ type: 'timestamptz' })
|
||||||
updatedAt!: string;
|
updatedAt!: string;
|
||||||
|
|
||||||
@Column({ comment: 'Asset ID to be used as thumbnail', type: 'varchar', nullable: true })
|
@ManyToOne(() => AssetEntity, { nullable: true, onDelete: 'SET NULL', onUpdate: 'CASCADE' })
|
||||||
|
albumThumbnailAsset!: AssetEntity | null;
|
||||||
|
|
||||||
|
@Column({ comment: 'Asset ID to be used as thumbnail', nullable: true })
|
||||||
albumThumbnailAssetId!: string | null;
|
albumThumbnailAssetId!: string | null;
|
||||||
|
|
||||||
@ManyToMany(() => UserEntity)
|
@ManyToMany(() => UserEntity)
|
||||||
|
@ -0,0 +1,60 @@
|
|||||||
|
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||||
|
|
||||||
|
export class AlbumThumbnailRelation1677613712565 implements MigrationInterface {
|
||||||
|
name = 'AlbumThumbnailRelation1677613712565';
|
||||||
|
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
// Make sure all albums have a valid albumThumbnailAssetId UUID or NULL.
|
||||||
|
await queryRunner.query(`
|
||||||
|
UPDATE "albums"
|
||||||
|
SET
|
||||||
|
"albumThumbnailAssetId" = (
|
||||||
|
SELECT
|
||||||
|
"albums_assets2"."assetsId"
|
||||||
|
FROM
|
||||||
|
"assets" "assets",
|
||||||
|
"albums_assets_assets" "albums_assets2"
|
||||||
|
WHERE
|
||||||
|
"albums_assets2"."assetsId" = "assets"."id"
|
||||||
|
AND "albums_assets2"."albumsId" = "albums"."id"
|
||||||
|
ORDER BY
|
||||||
|
"assets"."fileCreatedAt" DESC
|
||||||
|
LIMIT 1
|
||||||
|
),
|
||||||
|
"updatedAt" = CURRENT_TIMESTAMP
|
||||||
|
WHERE
|
||||||
|
"albums"."albumThumbnailAssetId" IS NULL
|
||||||
|
AND EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM "albums_assets_assets" "albums_assets"
|
||||||
|
WHERE "albums"."id" = "albums_assets"."albumsId"
|
||||||
|
)
|
||||||
|
OR "albums"."albumThumbnailAssetId" IS NOT NULL
|
||||||
|
AND NOT EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM "albums_assets_assets" "albums_assets"
|
||||||
|
WHERE
|
||||||
|
"albums"."id" = "albums_assets"."albumsId"
|
||||||
|
AND "albums"."albumThumbnailAssetId" = "albums_assets"."assetsId"::varchar
|
||||||
|
)
|
||||||
|
`);
|
||||||
|
|
||||||
|
await queryRunner.query(`
|
||||||
|
ALTER TABLE "albums"
|
||||||
|
ALTER COLUMN "albumThumbnailAssetId"
|
||||||
|
TYPE uuid USING "albumThumbnailAssetId"::uuid
|
||||||
|
`);
|
||||||
|
|
||||||
|
await queryRunner.query(`
|
||||||
|
ALTER TABLE "albums" ADD CONSTRAINT "FK_05895aa505a670300d4816debce" FOREIGN KEY ("albumThumbnailAssetId") REFERENCES "assets"("id") ON DELETE SET NULL ON UPDATE CASCADE
|
||||||
|
`);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(`ALTER TABLE "albums" DROP CONSTRAINT "FK_05895aa505a670300d4816debce"`);
|
||||||
|
|
||||||
|
await queryRunner.query(`
|
||||||
|
ALTER TABLE "albums" ALTER COLUMN "albumThumbnailAssetId" TYPE varchar USING "albumThumbnailAssetId"::varchar
|
||||||
|
`);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user