From 82a5c0a5cfa14211a378c30b657290255e08fccf Mon Sep 17 00:00:00 2001 From: Dorian Date: Fri, 13 Feb 2026 18:30:37 +0000 Subject: [PATCH] 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 --- .../1729096262567-music-videos-update.ts | 31 ++--- .../1730338365708-add-episodic-genres.ts | 112 ++++-------------- docker-compose.yml | 8 +- 3 files changed, 36 insertions(+), 115 deletions(-) diff --git a/backend/src/database/migrations/1729096262567-music-videos-update.ts b/backend/src/database/migrations/1729096262567-music-videos-update.ts index d0fda8f..7d22a70 100644 --- a/backend/src/database/migrations/1729096262567-music-videos-update.ts +++ b/backend/src/database/migrations/1729096262567-music-videos-update.ts @@ -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 { - // 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 { - // 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`, + ); } } diff --git a/backend/src/database/migrations/1730338365708-add-episodic-genres.ts b/backend/src/database/migrations/1730338365708-add-episodic-genres.ts index 06cdd6d..468d5ec 100644 --- a/backend/src/database/migrations/1730338365708-add-episodic-genres.ts +++ b/backend/src/database/migrations/1730338365708-add-episodic-genres.ts @@ -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 { - await queryRunner.manager.save( - queryRunner.manager.create>( - 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 { - await queryRunner.manager.delete(Genre, { - type: 'episodic', - }); + await queryRunner.query( + `DELETE FROM "genres" WHERE "type" = 'episodic'`, + ); } } diff --git a/docker-compose.yml b/docker-compose.yml index f94e572..81f359f 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -44,12 +44,12 @@ services: # ── Backend API (NestJS) ──────────────────────────────────── api: - image: indeehub-api:v2 + image: indeehub-api:v3 build: context: ./backend dockerfile: Dockerfile args: - CACHEBUST: "2" + CACHEBUST: "3" restart: unless-stopped environment: # ── Core ───────────────────────────────────────────── @@ -177,12 +177,12 @@ services: # ── FFmpeg Transcoding Worker ─────────────────────────────── ffmpeg-worker: - image: indeehub-ffmpeg:v2 + image: indeehub-ffmpeg:v3 build: context: ./backend dockerfile: Dockerfile.ffmpeg args: - CACHEBUST: "2" + CACHEBUST: "3" restart: unless-stopped environment: ENVIRONMENT: production