83 lines
2.3 KiB
TypeScript
83 lines
2.3 KiB
TypeScript
import { db, activityFeed, comments, tasks } from "@project-e/db";
|
|||
|
|
import { and, asc, eq, isNull } from "drizzle-orm";
|
||
|
|
|
||
|
|
// Backfill legacy comments from activity_feed (entity_type='comment') into the
|
||
|
|
// new dedicated comments table. Comments were only ever created for tasks, so
|
||
|
|
// every row maps to entity_type='task' with no parent.
|
||
|
|
//
|
||
|
|
// Idempotent: rows that already exist in `comments` (same entity_id + content +
|
||
|
|
// created_at) are skipped, so this script is safe to re-run.
|
||
|
|
|
||
|
|
try {
|
||
|
|
const rows = await db
|
||
|
|
.select()
|
||
|
|
.from(activityFeed)
|
||
|
|
.where(eq(activityFeed.entityType, "comment"))
|
||
|
|
.orderBy(asc(activityFeed.createdAt));
|
||
|
|
|
||
|
|
let inserted = 0;
|
||
|
|
let alreadyPresent = 0;
|
||
|
|
let missingContent = 0;
|
||
|
|
let orphaned = 0;
|
||
|
|
|
||
|
|
for (const row of rows) {
|
||
|
|
const content = row.changes?.content;
|
||
|
|
if (typeof content !== "string" || content.trim() === "") {
|
||
|
|
missingContent++;
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Only backfill comments whose task still exists (skip soft-deleted/absent).
|
||
|
|
const [task] = await db
|
||
|
|
.select({ id: tasks.id })
|
||
|
|
.from(tasks)
|
||
|
|
.where(and(eq(tasks.id, row.entityId), isNull(tasks.deletedAt)))
|
||
|
|
.limit(1);
|
||
|
|
|
||
|
|
if (!task) {
|
||
|
|
orphaned++;
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Idempotency guard: skip if this comment was already migrated.
|
||
|
|
const [existing] = await db
|
||
|
|
.select({ id: comments.id })
|
||
|
|
.from(comments)
|
||
|
|
.where(and(
|
||
|
|
eq(comments.entityId, row.entityId),
|
||
|
|
eq(comments.content, content),
|
||
|
|
eq(comments.createdAt, row.createdAt),
|
||
|
|
))
|
||
|
|
.limit(1);
|
||
|
|
|
||
|
|
if (existing) {
|
||
|
|
alreadyPresent++;
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
await db.insert(comments).values({
|
||
|
|
entityType: "task",
|
||
|
|
entityId: row.entityId,
|
||
|
|
workspaceId: row.workspaceId,
|
||
|
|
author: row.actor,
|
||
|
|
content,
|
||
|
|
createdAt: row.createdAt,
|
||
|
|
parentId: null,
|
||
|
|
deletedAt: null,
|
||
|
|
});
|
||
|
|
|
||
|
|
inserted++;
|
||
|
|
}
|
||
|
|
|
||
|
|
console.log(`[backfill-comments] rows found: ${rows.length}`);
|
||
|
|
console.log(`[backfill-comments] inserted: ${inserted}`);
|
||
|
|
console.log(`[backfill-comments] skipped (already present): ${alreadyPresent}`);
|
||
|
|
console.log(`[backfill-comments] skipped (missing content): ${missingContent}`);
|
||
|
|
console.log(`[backfill-comments] skipped (orphaned entity): ${orphaned}`);
|
||
|
|
|
||
|
|
process.exit(0);
|
||
|
|
} catch (error) {
|
||
|
|
console.error("[backfill-comments] failed:", error);
|
||
|
|
process.exit(1);
|
||
|
|
}
|