Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 65796a0893 | |||
| 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"`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ import EmptyState from '@/components/common/EmptyState.vue'
|
||||
import DriveNameModal from '@/components/drive/DriveNameModal.vue'
|
||||
import { useDriveStore } from '@/stores/drive.store'
|
||||
import { useDialog } from '@/composables/useDialog'
|
||||
import { fileBadge, formatBytes, relativeTimeKo } from '@/shared/utils/format'
|
||||
import { fileBadgeOf, formatBytes, relativeTimeKo } from '@/shared/utils/format'
|
||||
import {
|
||||
DRIVE_ACCEPT,
|
||||
DRIVE_MAX_FILE_SIZE,
|
||||
@@ -409,8 +409,8 @@ watch(
|
||||
>
|
||||
<span
|
||||
class="dr-ico dr-badge"
|
||||
:class="fileBadge(f.kind).cls"
|
||||
>{{ fileBadge(f.kind).label }}</span>
|
||||
:class="fileBadgeOf(f.name).cls"
|
||||
>{{ fileBadgeOf(f.name).label }}</span>
|
||||
<span class="dr-main">
|
||||
<span class="dr-name">{{ f.name }}</span>
|
||||
<span class="dr-meta">{{ formatBytes(f.size) }} · {{ relativeTimeKo(f.updatedAt) }}</span>
|
||||
@@ -681,6 +681,18 @@ watch(
|
||||
.dr-badge.fi-doc {
|
||||
background: #3d8bf2;
|
||||
}
|
||||
.dr-badge.fi-xls {
|
||||
background: #1f9d57;
|
||||
}
|
||||
.dr-badge.fi-ppt {
|
||||
background: #e0823d;
|
||||
}
|
||||
.dr-badge.fi-txt {
|
||||
background: #5b6470;
|
||||
}
|
||||
.dr-badge.fi-media {
|
||||
background: #c0469d;
|
||||
}
|
||||
.dr-main {
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
|
||||
@@ -82,6 +82,31 @@ export function fileBadge(kind: FileKind): { label: string; cls: string } {
|
||||
return map[kind]
|
||||
}
|
||||
|
||||
/**
|
||||
* 확장자 기준 세분화 배지(라벨/색상) — 4종 kind 보다 정확히 표기.
|
||||
* 예: 엑셀 → XLS, 파워포인트 → PPT, 워드 → DOC. (드라이브 파일 행 등)
|
||||
*/
|
||||
export function fileBadgeOf(name: string): { label: string; cls: string } {
|
||||
const ext = name.split('.').pop()?.toLowerCase() ?? ''
|
||||
if (['png', 'jpg', 'jpeg', 'gif', 'webp', 'svg', 'bmp'].includes(ext))
|
||||
return { label: 'IMG', cls: 'fi-img' }
|
||||
if (ext === 'pdf') return { label: 'PDF', cls: 'fi-pdf' }
|
||||
if (['xls', 'xlsx'].includes(ext)) return { label: 'XLS', cls: 'fi-xls' }
|
||||
if (['doc', 'docx'].includes(ext)) return { label: 'DOC', cls: 'fi-doc' }
|
||||
if (['ppt', 'pptx'].includes(ext)) return { label: 'PPT', cls: 'fi-ppt' }
|
||||
if (['zip', 'rar', '7z', 'tar', 'gz'].includes(ext))
|
||||
return { label: 'ZIP', cls: 'fi-zip' }
|
||||
if (ext === 'csv') return { label: 'CSV', cls: 'fi-txt' }
|
||||
if (ext === 'json') return { label: 'JSON', cls: 'fi-txt' }
|
||||
if (['txt', 'md'].includes(ext))
|
||||
return { label: ext.toUpperCase(), cls: 'fi-txt' }
|
||||
if (['mp4', 'webm', 'mov', 'avi', 'mkv'].includes(ext))
|
||||
return { label: 'VID', cls: 'fi-media' }
|
||||
if (['mp3', 'wav', 'ogg', 'flac', 'm4a'].includes(ext))
|
||||
return { label: 'AUD', cls: 'fi-media' }
|
||||
return { label: 'FILE', cls: 'fi-doc' }
|
||||
}
|
||||
|
||||
/** 바이트 수를 사람이 읽기 쉬운 단위로 (예: 19293798 → "18.4 MB") */
|
||||
export function formatBytes(bytes: number): string {
|
||||
if (!Number.isFinite(bytes) || bytes <= 0) return '0 B'
|
||||
|
||||
Reference in New Issue
Block a user