fix: 드라이브 전환 마이그레이션 멱등·견고화 (운영 적용 실패 방지)

ADD COLUMN IF NOT EXISTS, project_attachments 는 제약 이름 비의존 DROP TABLE IF EXISTS ... CASCADE 로 변경. 과거 synchronize 로 만들어진 운영 DB나 부분 적용 상태에서도 실패하지 않도록 함

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-30 19:43:23 +09:00
parent 524c6f6782
commit 5267b759be
@@ -5,35 +5,35 @@ import { MigrationInterface, QueryRunner } from 'typeorm';
// → 연동 폴더 문서를 NCP 에서 받아 텍스트 추출·캐싱(AI 업무 추천 주입)
// - 프로젝트 직접 업로드(project_attachments) 폐기 → 테이블 제거(완전 대체)
// 운영(migrationsRun)에서만 실행되며, dev 는 synchronize 로 자동 반영된다.
//
// 멱등·견고하게 작성: 과거 synchronize 로 만들어진 운영 DB(제약 이름 상이)나
// 부분 적용 상태에서도 실패하지 않도록 IF [NOT] EXISTS / CASCADE 를 사용한다.
export class AddDriveFileExtractDropProjectAttachments1783000000000 implements MigrationInterface {
name = 'AddDriveFileExtractDropProjectAttachments1783000000000';
public async up(queryRunner: QueryRunner): Promise<void> {
// 1) drive_files AI 추출 캐시 컬럼
// 1) drive_files AI 추출 캐시 컬럼 (이미 있으면 건너뜀)
await queryRunner.query(
`ALTER TABLE "drive_files" ADD "extracted_text" text`,
`ALTER TABLE "drive_files" ADD COLUMN IF NOT EXISTS "extracted_text" text`,
);
await queryRunner.query(
`ALTER TABLE "drive_files" ADD "extract_status" character varying NOT NULL DEFAULT 'pending'`,
`ALTER TABLE "drive_files" ADD COLUMN IF NOT EXISTS "extract_status" character varying NOT NULL DEFAULT 'pending'`,
);
// 2) project_attachments 폐기 (드라이브 연동으로 대체)
// 제약 이름에 의존하지 않도록 CASCADE 로 테이블·FK 를 한 번에 제거.
await queryRunner.query(
`ALTER TABLE "project_attachments" DROP CONSTRAINT "FK_project_attachments_uploader"`,
`DROP TABLE IF EXISTS "project_attachments" CASCADE`,
);
await queryRunner.query(
`ALTER TABLE "project_attachments" DROP CONSTRAINT "FK_project_attachments_project"`,
);
await queryRunner.query(`DROP TABLE "project_attachments"`);
}
public async down(queryRunner: QueryRunner): Promise<void> {
// 1) project_attachments 복원 (AddProjectAttachments + ExtractedText 와 동일 스키마)
await queryRunner.query(
`CREATE TABLE "project_attachments" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "name" character varying NOT NULL, "stored_name" character varying NOT NULL, "size" integer NOT NULL, "mime" character varying NOT NULL, "kind" character varying NOT NULL, "extracted_text" text, "created_at" TIMESTAMP NOT NULL DEFAULT now(), "project_id" uuid, "uploader_id" uuid, CONSTRAINT "PK_project_attachments" PRIMARY KEY ("id"))`,
`CREATE TABLE IF NOT EXISTS "project_attachments" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "name" character varying NOT NULL, "stored_name" character varying NOT NULL, "size" integer NOT NULL, "mime" character varying NOT NULL, "kind" character varying NOT NULL, "extracted_text" text, "created_at" TIMESTAMP NOT NULL DEFAULT now(), "project_id" uuid, "uploader_id" uuid, CONSTRAINT "PK_project_attachments" PRIMARY KEY ("id"))`,
);
await queryRunner.query(
`CREATE INDEX "IDX_project_attachments_project" ON "project_attachments" ("project_id")`,
`CREATE INDEX IF NOT EXISTS "IDX_project_attachments_project" ON "project_attachments" ("project_id")`,
);
await queryRunner.query(
`ALTER TABLE "project_attachments" ADD CONSTRAINT "FK_project_attachments_project" FOREIGN KEY ("project_id") REFERENCES "projects"("id") ON DELETE CASCADE ON UPDATE NO ACTION`,
@@ -44,10 +44,10 @@ export class AddDriveFileExtractDropProjectAttachments1783000000000 implements M
// 2) drive_files 추출 캐시 컬럼 제거
await queryRunner.query(
`ALTER TABLE "drive_files" DROP COLUMN "extract_status"`,
`ALTER TABLE "drive_files" DROP COLUMN IF EXISTS "extract_status"`,
);
await queryRunner.query(
`ALTER TABLE "drive_files" DROP COLUMN "extracted_text"`,
`ALTER TABLE "drive_files" DROP COLUMN IF EXISTS "extracted_text"`,
);
}
}