feat: add project_goals many-to-many join table

Create project_goals join table with composite PK (project_id, goal_id),
backfill from existing projects.goal_id, and update the project service
to read/write through the join table. Shared types now include goalIds
and goals arrays on Project. Legacy goalId column is kept in sync.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Forgotten
2026-02-20 13:43:25 -06:00
parent 0cf33695d3
commit ef700c2391
10 changed files with 4252 additions and 15 deletions

View File

@@ -0,0 +1,18 @@
CREATE TABLE "project_goals" (
"project_id" uuid NOT NULL,
"goal_id" uuid NOT NULL,
"company_id" uuid NOT NULL,
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
"updated_at" timestamp with time zone DEFAULT now() NOT NULL,
CONSTRAINT "project_goals_project_id_goal_id_pk" PRIMARY KEY("project_id","goal_id")
);
--> statement-breakpoint
ALTER TABLE "project_goals" ADD CONSTRAINT "project_goals_project_id_projects_id_fk" FOREIGN KEY ("project_id") REFERENCES "public"."projects"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "project_goals" ADD CONSTRAINT "project_goals_goal_id_goals_id_fk" FOREIGN KEY ("goal_id") REFERENCES "public"."goals"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "project_goals" ADD CONSTRAINT "project_goals_company_id_companies_id_fk" FOREIGN KEY ("company_id") REFERENCES "public"."companies"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
CREATE INDEX "project_goals_project_idx" ON "project_goals" USING btree ("project_id");--> statement-breakpoint
CREATE INDEX "project_goals_goal_idx" ON "project_goals" USING btree ("goal_id");--> statement-breakpoint
CREATE INDEX "project_goals_company_idx" ON "project_goals" USING btree ("company_id");--> statement-breakpoint
INSERT INTO "project_goals" ("project_id", "goal_id", "company_id")
SELECT "id", "goal_id", "company_id" FROM "projects" WHERE "goal_id" IS NOT NULL
ON CONFLICT DO NOTHING;

File diff suppressed because it is too large Load Diff

View File

@@ -78,6 +78,13 @@
"when": 1771605173513,
"tag": "0010_stale_justin_hammer",
"breakpoints": true
},
{
"idx": 11,
"version": "7",
"when": 1771616419708,
"tag": "0011_windy_corsair",
"breakpoints": true
}
]
}

View File

@@ -6,6 +6,7 @@ export { agentRuntimeState } from "./agent_runtime_state.js";
export { agentTaskSessions } from "./agent_task_sessions.js";
export { agentWakeupRequests } from "./agent_wakeup_requests.js";
export { projects } from "./projects.js";
export { projectGoals } from "./project_goals.js";
export { goals } from "./goals.js";
export { issues } from "./issues.js";
export { issueApprovals } from "./issue_approvals.js";

View File

@@ -0,0 +1,21 @@
import { pgTable, uuid, timestamp, index, primaryKey } from "drizzle-orm/pg-core";
import { companies } from "./companies.js";
import { projects } from "./projects.js";
import { goals } from "./goals.js";
export const projectGoals = pgTable(
"project_goals",
{
projectId: uuid("project_id").notNull().references(() => projects.id, { onDelete: "cascade" }),
goalId: uuid("goal_id").notNull().references(() => goals.id, { onDelete: "cascade" }),
companyId: uuid("company_id").notNull().references(() => companies.id),
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
},
(table) => ({
pk: primaryKey({ columns: [table.projectId, table.goalId] }),
projectIdx: index("project_goals_project_idx").on(table.projectId),
goalIdx: index("project_goals_goal_idx").on(table.goalId),
companyIdx: index("project_goals_company_idx").on(table.companyId),
}),
);