Fix entity-based migrations that crash on missing columns

MusicVideosUpdate and AddEpisodicGenres migrations used TypeORM
entity classes which reference columns that don't exist at their
migration timestamp (e.g. trailer_old, later entity fields).
Rewrote both to use raw SQL INSERT/UPDATE statements.

Also bumped CACHEBUST to v3 to force backend image rebuild.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Dorian
2026-02-13 18:30:37 +00:00
parent eeffce4baa
commit 82a5c0a5cf
3 changed files with 36 additions and 115 deletions

View File

@@ -1,32 +1,17 @@
/* eslint-disable unicorn/no-array-method-this-argument */
import { Project } from 'src/projects/entities/project.entity';
import { Category } from 'src/projects/enums/category.enum';
import { MigrationInterface, QueryRunner } from 'typeorm';
export class MusicVideosUpdate1729096262567 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
// update all projects that have category music-video to have the category narrative and type music-video
const projects = await queryRunner.manager.find(Project, {
where: { category: 'music-video' as Category },
});
const updatedProjects = projects.map((project) => {
project.category = 'narrative';
project.type = 'music-video';
return project;
});
await queryRunner.manager.save(updatedProjects);
// Update all projects with category 'music-video' to category 'narrative' + type 'music-video'
await queryRunner.query(
`UPDATE "projects" SET "category" = 'narrative', "type" = 'music-video' WHERE "category" = 'music-video' AND "deleted_at" IS NULL`,
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
// update all projects that have category narrative and type music-video to have the category music-video
const projects = await queryRunner.manager.find(Project, {
where: { category: 'narrative' as Category, type: 'music-video' },
});
const updatedProjects = projects.map((project) => {
project.category = 'music-video' as Category;
project.type = 'film';
return project;
});
await queryRunner.manager.save(updatedProjects);
// Revert: projects with category 'narrative' and type 'music-video' back to category 'music-video', type 'film'
await queryRunner.query(
`UPDATE "projects" SET "category" = 'music-video', "type" = 'film' WHERE "category" = 'narrative' AND "type" = 'music-video' AND "deleted_at" IS NULL`,
);
}
}

View File

@@ -1,97 +1,33 @@
import { Genre } from 'src/genres/entities/genre.entity';
import { MigrationInterface, QueryRunner } from 'typeorm';
export class AddEpisodicGenres1730338365708 implements MigrationInterface {
episodicGenres = [
{
id: 'f49d5884-842b-4f8e-b64f-c3f9a4e335d2',
type: 'episodic',
name: 'Drama',
},
{
id: '3170f9cc-338a-4478-a14c-39bce63870d0',
type: 'episodic',
name: 'Comedy',
},
{
id: '6f1785ef-b59b-4032-acc1-705f0aece2e6',
type: 'episodic',
name: 'Action & Adventure',
},
{
id: '932a7b0e-b07b-4829-9e17-36b13805c516',
type: 'episodic',
name: 'Science Fiction & Fantasy',
},
{
id: '88ea9593-12c0-4308-8157-c8ed5cf85568',
type: 'episodic',
name: 'Mystery & Thriller',
},
{
id: '264f275e-87ec-4f91-9c64-28264d869375',
type: 'episodic',
name: 'Horror',
},
{
id: 'a0bf144a-9bb7-4152-b30f-6d52ad064cf4',
type: 'episodic',
name: 'Reality TV',
},
{
id: '5b6ad647-d49b-4d4b-84c9-8b84bb5ebb86',
type: 'episodic',
name: 'Documentary',
},
{
id: '57751942-f0ba-499a-b220-7985059bc194',
type: 'episodic',
name: 'Animated Series',
},
{
id: 'e90710dd-cac4-4997-923a-78b19d778876',
type: 'episodic',
name: "Children's Programming",
},
{
id: '62c3e54a-cf91-4a01-9eb3-f6e5ba70b74e',
type: 'episodic',
name: 'Variety & Talk Shows',
},
{
id: '03e341d5-0338-406b-9672-64a06cf2b831',
type: 'episodic',
name: 'News & Current Affairs',
},
{
id: '094777d5-5926-43bf-a384-60f800fb6010',
type: 'episodic',
name: 'Game Shows',
},
{
id: '7e32a2d4-9615-49c0-9238-d10f997c43d5',
type: 'episodic',
name: 'Sports',
},
{
id: 'b492be4d-0011-443a-9094-9b927e2650a5',
type: 'episodic',
name: 'Music Television',
},
];
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.manager.save(
queryRunner.manager.create<Genre, Record<string, unknown>>(
Genre,
this.episodicGenres,
),
);
// Insert episodic genres using raw SQL to avoid entity column mismatches
await queryRunner.query(`
INSERT INTO "genres" ("id", "type", "name")
VALUES
('f49d5884-842b-4f8e-b64f-c3f9a4e335d2', 'episodic', 'Drama'),
('3170f9cc-338a-4478-a14c-39bce63870d0', 'episodic', 'Comedy'),
('6f1785ef-b59b-4032-acc1-705f0aece2e6', 'episodic', 'Action & Adventure'),
('932a7b0e-b07b-4829-9e17-36b13805c516', 'episodic', 'Science Fiction & Fantasy'),
('88ea9593-12c0-4308-8157-c8ed5cf85568', 'episodic', 'Mystery & Thriller'),
('264f275e-87ec-4f91-9c64-28264d869375', 'episodic', 'Horror'),
('a0bf144a-9bb7-4152-b30f-6d52ad064cf4', 'episodic', 'Reality TV'),
('5b6ad647-d49b-4d4b-84c9-8b84bb5ebb86', 'episodic', 'Documentary'),
('57751942-f0ba-499a-b220-7985059bc194', 'episodic', 'Animated Series'),
('e90710dd-cac4-4997-923a-78b19d778876', 'episodic', 'Children''s Programming'),
('62c3e54a-cf91-4a01-9eb3-f6e5ba70b74e', 'episodic', 'Variety & Talk Shows'),
('03e341d5-0338-406b-9672-64a06cf2b831', 'episodic', 'News & Current Affairs'),
('094777d5-5926-43bf-a384-60f800fb6010', 'episodic', 'Game Shows'),
('7e32a2d4-9615-49c0-9238-d10f997c43d5', 'episodic', 'Sports'),
('b492be4d-0011-443a-9094-9b927e2650a5', 'episodic', 'Music Television')
ON CONFLICT ("id") DO NOTHING
`);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.manager.delete(Genre, {
type: 'episodic',
});
await queryRunner.query(
`DELETE FROM "genres" WHERE "type" = 'episodic'`,
);
}
}