always set lane
This commit is contained in:
@@ -16,6 +16,7 @@
|
||||
"db:migrate": "drizzle-kit migrate",
|
||||
"check-types": "tsc --noEmit",
|
||||
"lint": "eslint .",
|
||||
"db:fix-lanes": "node --env-file=../../apps/web/.env --import=tsx src/scripts/fix-lanes.ts",
|
||||
"db:start": "docker compose up -d",
|
||||
"db:watch": "docker compose up",
|
||||
"db:stop": "docker compose stop",
|
||||
@@ -34,6 +35,7 @@
|
||||
"@zendegi/eslint-config": "workspace:*",
|
||||
"drizzle-kit": "^0.31.8",
|
||||
"eslint": "^9.17.0",
|
||||
"tsx": "^4.21.0",
|
||||
"typescript": "catalog:"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,6 +42,7 @@ export const timelineItem = pgTable("timeline_item", {
|
||||
start: timestamp("start", { withTimezone: true }).notNull().defaultNow(),
|
||||
// Allow null to denote a event without duration
|
||||
end: timestamp("end", { withTimezone: true }),
|
||||
lane: integer("lane").notNull().default(1),
|
||||
...timestamps,
|
||||
});
|
||||
|
||||
|
||||
88
packages/db/src/scripts/fix-lanes.ts
Normal file
88
packages/db/src/scripts/fix-lanes.ts
Normal file
@@ -0,0 +1,88 @@
|
||||
/**
|
||||
* One-time script to backfill lane values for existing timeline items.
|
||||
*
|
||||
* For each group: fetches items ordered by start, assigns lanes with
|
||||
* the greedy algorithm, and updates the DB.
|
||||
*
|
||||
* Run from repo root:
|
||||
* node --env-file=apps/web/.env --import=tsx packages/db/src/scripts/fix-lanes.ts
|
||||
*/
|
||||
import { eq } from "drizzle-orm";
|
||||
import { drizzle } from "drizzle-orm/node-postgres";
|
||||
import { timelineGroup, timelineItem } from "../schema/timeline";
|
||||
|
||||
const DATABASE_URL = process.env.DATABASE_URL;
|
||||
if (!DATABASE_URL) {
|
||||
throw new Error("DATABASE_URL is required");
|
||||
}
|
||||
|
||||
interface ItemForLane {
|
||||
id: string;
|
||||
start: Date;
|
||||
end: Date | null;
|
||||
lane: number;
|
||||
}
|
||||
|
||||
function assignLane(
|
||||
existing: ItemForLane[],
|
||||
newStart: Date,
|
||||
newEnd: Date | null
|
||||
): number {
|
||||
const end = newEnd ?? new Date(newStart.getTime() + 24 * 60 * 60 * 1000);
|
||||
|
||||
for (let lane = 1; lane <= 100; lane++) {
|
||||
const hasConflict = existing.some((item) => {
|
||||
if (item.lane !== lane) return false;
|
||||
const itemEnd =
|
||||
item.end ?? new Date(item.start.getTime() + 24 * 60 * 60 * 1000);
|
||||
return newStart < itemEnd && end > item.start;
|
||||
});
|
||||
if (!hasConflict) return lane;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const db = drizzle(DATABASE_URL!);
|
||||
|
||||
const groups = await db.select({ id: timelineGroup.id }).from(timelineGroup);
|
||||
console.log(`Found ${groups.length} groups`);
|
||||
|
||||
for (const group of groups) {
|
||||
const items = await db
|
||||
.select()
|
||||
.from(timelineItem)
|
||||
.where(eq(timelineItem.timelineGroupId, group.id))
|
||||
.orderBy(timelineItem.start);
|
||||
|
||||
if (items.length === 0) continue;
|
||||
|
||||
const assigned: ItemForLane[] = [];
|
||||
let updated = 0;
|
||||
|
||||
for (const item of items) {
|
||||
const lane = assignLane(assigned, item.start, item.end);
|
||||
assigned.push({ id: item.id, start: item.start, end: item.end, lane });
|
||||
|
||||
if (item.lane !== lane) {
|
||||
await db
|
||||
.update(timelineItem)
|
||||
.set({ lane })
|
||||
.where(eq(timelineItem.id, item.id));
|
||||
updated++;
|
||||
}
|
||||
}
|
||||
|
||||
console.log(
|
||||
`Group ${group.id}: ${items.length} items, ${updated} lanes updated`
|
||||
);
|
||||
}
|
||||
|
||||
console.log("Done");
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
main().catch((err) => {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
});
|
||||
Reference in New Issue
Block a user