diff --git a/app/(dashboard)/actions.ts b/app/(dashboard)/actions.ts index 156dd5b..b0addfc 100644 --- a/app/(dashboard)/actions.ts +++ b/app/(dashboard)/actions.ts @@ -243,7 +243,6 @@ export async function createCodePlanAction(formData: FormData) { type, tags, targetAssetIds: [], - assigneeIds: [], deadline, specUrl, }, diff --git a/app/(dashboard)/tasks/tasks-client.tsx b/app/(dashboard)/tasks/tasks-client.tsx index 8d21556..cae8ee7 100644 --- a/app/(dashboard)/tasks/tasks-client.tsx +++ b/app/(dashboard)/tasks/tasks-client.tsx @@ -218,15 +218,9 @@ export function TasksClient({ return ( <> -
-
-

Tasks

-

Track and manage tasks across all code plans

-
- +
+

Tasks

+

Track and manage tasks across all code plans

+ { setStatusFilter(v as TaskStatus | 'all' | 'open'); setPage(0) }} className="w-full sm:w-auto"> Open diff --git a/app/api/mcp/[transport]/route.ts b/app/api/mcp/[transport]/route.ts index 0dd859e..9430834 100644 --- a/app/api/mcp/[transport]/route.ts +++ b/app/api/mcp/[transport]/route.ts @@ -432,7 +432,7 @@ const handler = createMcpHandler( async ({ workItemIds, ownerEmail, ...data }, extra) => { requireWrite(extra) const ownerId = ownerEmail ? await resolveAssigneeEmail(uid(extra), ownerEmail) : undefined - const plan = await createCodePlan({ ...data, ownerId, assigneeIds: [] }, uid(extra)) + const plan = await createCodePlan({ ...data, ownerId }, uid(extra)) for (const workItemId of workItemIds) await linkWorkItemToPlan(workItemId, plan.id) return json(plan) }, diff --git a/lib/db/migrations/postgres/0010_drop_plan_assignees.sql b/lib/db/migrations/postgres/0010_drop_plan_assignees.sql new file mode 100644 index 0000000..e68212a --- /dev/null +++ b/lib/db/migrations/postgres/0010_drop_plan_assignees.sql @@ -0,0 +1 @@ +DROP TABLE "code_plan_assignees"; diff --git a/lib/db/migrations/postgres/meta/_journal.json b/lib/db/migrations/postgres/meta/_journal.json index c615466..f56ca84 100644 --- a/lib/db/migrations/postgres/meta/_journal.json +++ b/lib/db/migrations/postgres/meta/_journal.json @@ -71,6 +71,13 @@ "when": 1783480000000, "tag": "0009_task_percent", "breakpoints": true + }, + { + "idx": 10, + "version": "7", + "when": 1784058835573, + "tag": "0010_drop_plan_assignees", + "breakpoints": true } ] } \ No newline at end of file diff --git a/lib/db/migrations/sqlite/0010_drop_plan_assignees.sql b/lib/db/migrations/sqlite/0010_drop_plan_assignees.sql new file mode 100644 index 0000000..21ceaf0 --- /dev/null +++ b/lib/db/migrations/sqlite/0010_drop_plan_assignees.sql @@ -0,0 +1 @@ +DROP TABLE `code_plan_assignees`; \ No newline at end of file diff --git a/lib/db/migrations/sqlite/meta/0010_snapshot.json b/lib/db/migrations/sqlite/meta/0010_snapshot.json new file mode 100644 index 0000000..d1cbc33 --- /dev/null +++ b/lib/db/migrations/sqlite/meta/0010_snapshot.json @@ -0,0 +1,1960 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "1289b0db-bfaa-41e4-8db3-cc14ec419502", + "prevId": "c0c08b3f-8fc6-4df5-ae3b-6c4b47dba93a", + "tables": { + "api_keys": { + "name": "api_keys", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "key_hash": { + "name": "key_hash", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "key_prefix": { + "name": "key_prefix", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'read'" + }, + "last_used_at": { + "name": "last_used_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "api_keys_key_hash_unique": { + "name": "api_keys_key_hash_unique", + "columns": [ + "key_hash" + ], + "isUnique": true + } + }, + "foreignKeys": { + "api_keys_user_id_users_id_fk": { + "name": "api_keys_user_id_users_id_fk", + "tableFrom": "api_keys", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "asset_dependencies": { + "name": "asset_dependencies", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "source_asset_id": { + "name": "source_asset_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "target_asset_id": { + "name": "target_asset_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "dependency_type": { + "name": "dependency_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "asset_dependencies_source_asset_id_assets_id_fk": { + "name": "asset_dependencies_source_asset_id_assets_id_fk", + "tableFrom": "asset_dependencies", + "tableTo": "assets", + "columnsFrom": [ + "source_asset_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "asset_dependencies_target_asset_id_assets_id_fk": { + "name": "asset_dependencies_target_asset_id_assets_id_fk", + "tableFrom": "asset_dependencies", + "tableTo": "assets", + "columnsFrom": [ + "target_asset_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "assets": { + "name": "assets", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "product_id": { + "name": "product_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "''" + }, + "tags": { + "name": "tags", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'[]'" + }, + "health": { + "name": "health", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'healthy'" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'active'" + }, + "tech_debt_score": { + "name": "tech_debt_score", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "repository_url": { + "name": "repository_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "repo_path": { + "name": "repo_path", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "documentation_url": { + "name": "documentation_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "metadata": { + "name": "metadata", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'{}'" + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "assets_product_id_products_id_fk": { + "name": "assets_product_id_products_id_fk", + "tableFrom": "assets", + "tableTo": "products", + "columnsFrom": [ + "product_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "code_plan_assets": { + "name": "code_plan_assets", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "code_plan_id": { + "name": "code_plan_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "asset_id": { + "name": "asset_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "branch": { + "name": "branch", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "pr_url": { + "name": "pr_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "pr_status": { + "name": "pr_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'none'" + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "code_plan_assets_plan_asset_idx": { + "name": "code_plan_assets_plan_asset_idx", + "columns": [ + "code_plan_id", + "asset_id" + ], + "isUnique": true + } + }, + "foreignKeys": { + "code_plan_assets_code_plan_id_code_plans_id_fk": { + "name": "code_plan_assets_code_plan_id_code_plans_id_fk", + "tableFrom": "code_plan_assets", + "tableTo": "code_plans", + "columnsFrom": [ + "code_plan_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "code_plan_assets_asset_id_assets_id_fk": { + "name": "code_plan_assets_asset_id_assets_id_fk", + "tableFrom": "code_plan_assets", + "tableTo": "assets", + "columnsFrom": [ + "asset_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "code_plans": { + "name": "code_plans", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "''" + }, + "product_id": { + "name": "product_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'draft'" + }, + "tags": { + "name": "tags", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'[]'" + }, + "start_date": { + "name": "start_date", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "end_date": { + "name": "end_date", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "deadline": { + "name": "deadline", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "creator_id": { + "name": "creator_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "owner_id": { + "name": "owner_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "spec_url": { + "name": "spec_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'native'" + }, + "connection_id": { + "name": "connection_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "external_key": { + "name": "external_key", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "external_url": { + "name": "external_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "external_data": { + "name": "external_data", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'{}'" + }, + "external_deleted": { + "name": "external_deleted", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "synced_at": { + "name": "synced_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "code_plans_connection_external_idx": { + "name": "code_plans_connection_external_idx", + "columns": [ + "connection_id", + "external_id" + ], + "isUnique": true + } + }, + "foreignKeys": { + "code_plans_product_id_products_id_fk": { + "name": "code_plans_product_id_products_id_fk", + "tableFrom": "code_plans", + "tableTo": "products", + "columnsFrom": [ + "product_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "code_plans_creator_id_users_id_fk": { + "name": "code_plans_creator_id_users_id_fk", + "tableFrom": "code_plans", + "tableTo": "users", + "columnsFrom": [ + "creator_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "code_plans_owner_id_users_id_fk": { + "name": "code_plans_owner_id_users_id_fk", + "tableFrom": "code_plans", + "tableTo": "users", + "columnsFrom": [ + "owner_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "code_plans_connection_id_integrations_id_fk": { + "name": "code_plans_connection_id_integrations_id_fk", + "tableFrom": "code_plans", + "tableTo": "integrations", + "columnsFrom": [ + "connection_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "email_verification_tokens": { + "name": "email_verification_tokens", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "new_email": { + "name": "new_email", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "email_verification_tokens_token_unique": { + "name": "email_verification_tokens_token_unique", + "columns": [ + "token" + ], + "isUnique": true + } + }, + "foreignKeys": { + "email_verification_tokens_user_id_users_id_fk": { + "name": "email_verification_tokens_user_id_users_id_fk", + "tableFrom": "email_verification_tokens", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "integrations": { + "name": "integrations", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "auth_ref": { + "name": "auth_ref", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "token_encrypted": { + "name": "token_encrypted", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "config": { + "name": "config", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'{}'" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'active'" + }, + "last_sync_at": { + "name": "last_sync_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "integrations_organization_id_organizations_id_fk": { + "name": "integrations_organization_id_organizations_id_fk", + "tableFrom": "integrations", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "organization_members": { + "name": "organization_members", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'viewer'" + }, + "invited_by": { + "name": "invited_by", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "joined_at": { + "name": "joined_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "organization_members_organization_id_organizations_id_fk": { + "name": "organization_members_organization_id_organizations_id_fk", + "tableFrom": "organization_members", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "organization_members_user_id_users_id_fk": { + "name": "organization_members_user_id_users_id_fk", + "tableFrom": "organization_members", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "organization_members_invited_by_users_id_fk": { + "name": "organization_members_invited_by_users_id_fk", + "tableFrom": "organization_members", + "tableTo": "users", + "columnsFrom": [ + "invited_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "organizations": { + "name": "organizations", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "owner_id": { + "name": "owner_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "billing_tier": { + "name": "billing_tier", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'free'" + }, + "product_limit": { + "name": "product_limit", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 1 + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "organizations_slug_unique": { + "name": "organizations_slug_unique", + "columns": [ + "slug" + ], + "isUnique": true + } + }, + "foreignKeys": { + "organizations_owner_id_users_id_fk": { + "name": "organizations_owner_id_users_id_fk", + "tableFrom": "organizations", + "tableTo": "users", + "columnsFrom": [ + "owner_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "products": { + "name": "products", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "''" + }, + "tags": { + "name": "tags", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'[]'" + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "creator_id": { + "name": "creator_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "products_slug_unique": { + "name": "products_slug_unique", + "columns": [ + "slug" + ], + "isUnique": true + } + }, + "foreignKeys": { + "products_organization_id_organizations_id_fk": { + "name": "products_organization_id_organizations_id_fk", + "tableFrom": "products", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "products_creator_id_users_id_fk": { + "name": "products_creator_id_users_id_fk", + "tableFrom": "products", + "tableTo": "users", + "columnsFrom": [ + "creator_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "sync_log": { + "name": "sync_log", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "connection_id": { + "name": "connection_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "entity_type": { + "name": "entity_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "entity_id": { + "name": "entity_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "event": { + "name": "event", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "actor_id": { + "name": "actor_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "payload": { + "name": "payload", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'{}'" + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "sync_log_org_created_idx": { + "name": "sync_log_org_created_idx", + "columns": [ + "organization_id", + "created_at" + ], + "isUnique": false + } + }, + "foreignKeys": { + "sync_log_organization_id_organizations_id_fk": { + "name": "sync_log_organization_id_organizations_id_fk", + "tableFrom": "sync_log", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "sync_log_connection_id_integrations_id_fk": { + "name": "sync_log_connection_id_integrations_id_fk", + "tableFrom": "sync_log", + "tableTo": "integrations", + "columnsFrom": [ + "connection_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "sync_log_actor_id_users_id_fk": { + "name": "sync_log_actor_id_users_id_fk", + "tableFrom": "sync_log", + "tableTo": "users", + "columnsFrom": [ + "actor_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "tasks": { + "name": "tasks", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "code_plan_id": { + "name": "code_plan_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "asset_id": { + "name": "asset_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "''" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'not_started'" + }, + "priority": { + "name": "priority", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'medium'" + }, + "tags": { + "name": "tags", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'[]'" + }, + "assignee_id": { + "name": "assignee_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "percent_complete": { + "name": "percent_complete", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "start_date": { + "name": "start_date", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "end_date": { + "name": "end_date", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "estimated_effort": { + "name": "estimated_effort", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "actual_effort": { + "name": "actual_effort", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'native'" + }, + "connection_id": { + "name": "connection_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "external_key": { + "name": "external_key", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "external_url": { + "name": "external_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "external_data": { + "name": "external_data", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'{}'" + }, + "external_deleted": { + "name": "external_deleted", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "synced_at": { + "name": "synced_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "tasks_connection_external_idx": { + "name": "tasks_connection_external_idx", + "columns": [ + "connection_id", + "external_id" + ], + "isUnique": true + } + }, + "foreignKeys": { + "tasks_code_plan_id_code_plans_id_fk": { + "name": "tasks_code_plan_id_code_plans_id_fk", + "tableFrom": "tasks", + "tableTo": "code_plans", + "columnsFrom": [ + "code_plan_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tasks_asset_id_assets_id_fk": { + "name": "tasks_asset_id_assets_id_fk", + "tableFrom": "tasks", + "tableTo": "assets", + "columnsFrom": [ + "asset_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tasks_assignee_id_users_id_fk": { + "name": "tasks_assignee_id_users_id_fk", + "tableFrom": "tasks", + "tableTo": "users", + "columnsFrom": [ + "assignee_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tasks_connection_id_integrations_id_fk": { + "name": "tasks_connection_id_integrations_id_fk", + "tableFrom": "tasks", + "tableTo": "integrations", + "columnsFrom": [ + "connection_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "users": { + "name": "users", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "avatar_url": { + "name": "avatar_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "billing_tier": { + "name": "billing_tier", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'free'" + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'viewer'" + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "feature_flags": { + "name": "feature_flags", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'{}'" + }, + "password_hash": { + "name": "password_hash", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "work_item_code_plans": { + "name": "work_item_code_plans", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "work_item_id": { + "name": "work_item_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "code_plan_id": { + "name": "code_plan_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "work_item_code_plans_item_plan_idx": { + "name": "work_item_code_plans_item_plan_idx", + "columns": [ + "work_item_id", + "code_plan_id" + ], + "isUnique": true + } + }, + "foreignKeys": { + "work_item_code_plans_work_item_id_work_items_id_fk": { + "name": "work_item_code_plans_work_item_id_work_items_id_fk", + "tableFrom": "work_item_code_plans", + "tableTo": "work_items", + "columnsFrom": [ + "work_item_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "work_item_code_plans_code_plan_id_code_plans_id_fk": { + "name": "work_item_code_plans_code_plan_id_code_plans_id_fk", + "tableFrom": "work_item_code_plans", + "tableTo": "code_plans", + "columnsFrom": [ + "code_plan_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "work_items": { + "name": "work_items", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "product_id": { + "name": "product_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "asset_id": { + "name": "asset_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "area": { + "name": "area", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "parent_id": { + "name": "parent_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "''" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'open'" + }, + "severity": { + "name": "severity", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'medium'" + }, + "tags": { + "name": "tags", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'[]'" + }, + "reporter_id": { + "name": "reporter_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "owner_id": { + "name": "owner_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "spec_url": { + "name": "spec_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'native'" + }, + "connection_id": { + "name": "connection_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "external_key": { + "name": "external_key", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "external_url": { + "name": "external_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "external_data": { + "name": "external_data", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'{}'" + }, + "external_deleted": { + "name": "external_deleted", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "synced_at": { + "name": "synced_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "work_items_connection_external_idx": { + "name": "work_items_connection_external_idx", + "columns": [ + "connection_id", + "external_id" + ], + "isUnique": true + }, + "work_items_product_idx": { + "name": "work_items_product_idx", + "columns": [ + "product_id" + ], + "isUnique": false + }, + "work_items_asset_idx": { + "name": "work_items_asset_idx", + "columns": [ + "asset_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "work_items_product_id_products_id_fk": { + "name": "work_items_product_id_products_id_fk", + "tableFrom": "work_items", + "tableTo": "products", + "columnsFrom": [ + "product_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "work_items_asset_id_assets_id_fk": { + "name": "work_items_asset_id_assets_id_fk", + "tableFrom": "work_items", + "tableTo": "assets", + "columnsFrom": [ + "asset_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "work_items_parent_id_work_items_id_fk": { + "name": "work_items_parent_id_work_items_id_fk", + "tableFrom": "work_items", + "tableTo": "work_items", + "columnsFrom": [ + "parent_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "work_items_reporter_id_users_id_fk": { + "name": "work_items_reporter_id_users_id_fk", + "tableFrom": "work_items", + "tableTo": "users", + "columnsFrom": [ + "reporter_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "work_items_owner_id_users_id_fk": { + "name": "work_items_owner_id_users_id_fk", + "tableFrom": "work_items", + "tableTo": "users", + "columnsFrom": [ + "owner_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "work_items_connection_id_integrations_id_fk": { + "name": "work_items_connection_id_integrations_id_fk", + "tableFrom": "work_items", + "tableTo": "integrations", + "columnsFrom": [ + "connection_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + } + }, + "views": {}, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "indexes": {} + } +} \ No newline at end of file diff --git a/lib/db/migrations/sqlite/meta/_journal.json b/lib/db/migrations/sqlite/meta/_journal.json index dc752aa..5a661bd 100644 --- a/lib/db/migrations/sqlite/meta/_journal.json +++ b/lib/db/migrations/sqlite/meta/_journal.json @@ -71,6 +71,13 @@ "when": 1783565221947, "tag": "0009_daffy_the_executioner", "breakpoints": true + }, + { + "idx": 10, + "version": "6", + "when": 1784058835573, + "tag": "0010_drop_plan_assignees", + "breakpoints": true } ] } \ No newline at end of file diff --git a/lib/db/mutations.ts b/lib/db/mutations.ts index 988b012..55256f0 100644 --- a/lib/db/mutations.ts +++ b/lib/db/mutations.ts @@ -6,7 +6,6 @@ import { integrations, codePlans, codePlanAssets, - codePlanAssignees, workItems, workItemCodePlans, tasks, @@ -120,11 +119,11 @@ type CreateCodePlanData = { deadline?: string specUrl?: string ownerId?: string | null - assigneeIds: string[] } -// Join tables are the source of truth for plan↔asset and plan↔assignee links -// (the deprecated array columns were dropped in v0.3.0). +// codePlanAssets is the source of truth for plan↔asset links (the deprecated +// array column was dropped in v0.3.0). Plan assignees aren't a stored link at +// all — see getCodePlan/getCodePlans, which derive them from task.assigneeId. async function syncPlanAssets(planId: string, assetIds: string[]) { const existing = await db @@ -147,15 +146,8 @@ async function syncPlanAssets(planId: string, assetIds: string[]) { } } -async function syncPlanAssignees(planId: string, userIds: string[]) { - await db.delete(codePlanAssignees).where(eq(codePlanAssignees.codePlanId, planId)) - if (userIds.length > 0) { - await db.insert(codePlanAssignees).values(userIds.map((userId) => ({ codePlanId: planId, userId }))) - } -} - export async function createCodePlan(data: CreateCodePlanData, userId: string) { - const { targetAssetIds, assigneeIds, ...columns } = data + const { targetAssetIds, ...columns } = data const [plan] = await db .insert(codePlans) .values({ @@ -165,7 +157,6 @@ export async function createCodePlan(data: CreateCodePlanData, userId: string) { }) .returning() await syncPlanAssets(plan.id, targetAssetIds) - await syncPlanAssignees(plan.id, assigneeIds) return plan } @@ -176,7 +167,7 @@ type UpdateCodePlanData = Partial< > export async function updateCodePlan(id: string, data: UpdateCodePlanData) { - const { targetAssetIds, assigneeIds, ...columns } = data + const { targetAssetIds, ...columns } = data const [plan] = await db .update(codePlans) .set({ ...columns, updatedAt: new Date() }) @@ -184,7 +175,6 @@ export async function updateCodePlan(id: string, data: UpdateCodePlanData) { .returning() if (!plan) return null if (targetAssetIds !== undefined) await syncPlanAssets(id, targetAssetIds) - if (assigneeIds !== undefined) await syncPlanAssignees(id, assigneeIds) return plan } diff --git a/lib/db/queries.ts b/lib/db/queries.ts index 7c05f0e..573b883 100644 --- a/lib/db/queries.ts +++ b/lib/db/queries.ts @@ -5,7 +5,6 @@ import { assetDependencies, codePlans, codePlanAssets, - codePlanAssignees, workItems, workItemCodePlans, tasks, @@ -316,16 +315,18 @@ export async function getCodePlans(userId: string, filters: PlanFilters = {}): P .orderBy(desc(codePlans.updatedAt)) const planIds = rows.map((r) => r.id) - const [assetLinks, assigneeLinks] = planIds.length + // Assignees are derived from who's actually assigned to the plan's tasks — + // not a separately-maintained list, so they can never drift from reality. + const [assetLinks, taskAssigneeLinks] = planIds.length ? await Promise.all([ db .select({ codePlanId: codePlanAssets.codePlanId, assetId: codePlanAssets.assetId }) .from(codePlanAssets) .where(inArray(codePlanAssets.codePlanId, planIds)), db - .select({ codePlanId: codePlanAssignees.codePlanId, userId: codePlanAssignees.userId }) - .from(codePlanAssignees) - .where(inArray(codePlanAssignees.codePlanId, planIds)), + .select({ codePlanId: tasks.codePlanId, assigneeId: tasks.assigneeId }) + .from(tasks) + .where(and(inArray(tasks.codePlanId, planIds), isNotNull(tasks.assigneeId))), ]) : [[], []] @@ -333,9 +334,12 @@ export async function getCodePlans(userId: string, filters: PlanFilters = {}): P for (const l of assetLinks) { assetsByPlan.set(l.codePlanId, [...(assetsByPlan.get(l.codePlanId) ?? []), l.assetId]) } - const assigneesByPlan = new Map() - for (const l of assigneeLinks) { - assigneesByPlan.set(l.codePlanId, [...(assigneesByPlan.get(l.codePlanId) ?? []), l.userId]) + const assigneesByPlan = new Map>() + for (const l of taskAssigneeLinks) { + if (!l.assigneeId) continue + const set = assigneesByPlan.get(l.codePlanId) ?? new Set() + set.add(l.assigneeId) + assigneesByPlan.set(l.codePlanId, set) } return rows.map((r) => ({ @@ -343,7 +347,7 @@ export async function getCodePlans(userId: string, filters: PlanFilters = {}): P ownerId: r.ownerId ?? undefined, specUrl: r.specUrl ?? undefined, targetAssetIds: assetsByPlan.get(r.id) ?? [], - assigneeIds: assigneesByPlan.get(r.id) ?? [], + assigneeIds: [...(assigneesByPlan.get(r.id) ?? [])], startDate: r.startDate ?? undefined, endDate: r.endDate ?? undefined, deadline: r.deadline ?? undefined, @@ -375,7 +379,7 @@ export async function getCodePlan(id: string, userId: string): Promise ({ id: r.assetId, name: r.assetName })) + // Assignees are derived from who's actually assigned to the plan's tasks — + // not a separately-maintained list, so they can never drift from reality. + const assigneeIds = [...new Set(planTasks.map((t) => t.assigneeId).filter((x): x is string => !!x))] + const resolvedAssignees = assigneeIds.length + ? await db.select({ id: users.id, name: users.name }).from(users).where(inArray(users.id, assigneeIds)) + : [] + const taskCount = planTasks.length const completedTaskCount = planTasks.filter((t) => t.status === 'done').length diff --git a/lib/db/schema.pg.ts b/lib/db/schema.pg.ts index 1910251..1cdc39a 100644 --- a/lib/db/schema.pg.ts +++ b/lib/db/schema.pg.ts @@ -175,14 +175,6 @@ export const codePlanAssets = pgTable('code_plan_assets', { uniqueIndex('code_plan_assets_plan_asset_idx').on(t.codePlanId, t.assetId), ]) -export const codePlanAssignees = pgTable('code_plan_assignees', { - codePlanId: uuid('code_plan_id').notNull().references(() => codePlans.id, { onDelete: 'cascade' }), - userId: uuid('user_id').notNull().references(() => users.id, { onDelete: 'cascade' }), - createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(), -}, (t) => [ - primaryKey({ columns: [t.codePlanId, t.userId] }), -]) - export const workItems = pgTable('work_items', { id: uuid('id').primaryKey().defaultRandom(), productId: uuid('product_id').notNull().references(() => products.id, { onDelete: 'cascade' }), diff --git a/lib/db/schema.sqlite.ts b/lib/db/schema.sqlite.ts index c207c8c..c1c34e5 100644 --- a/lib/db/schema.sqlite.ts +++ b/lib/db/schema.sqlite.ts @@ -169,14 +169,6 @@ export const codePlanAssets = sqliteTable('code_plan_assets', { uniqueIndex('code_plan_assets_plan_asset_idx').on(t.codePlanId, t.assetId), ]) -export const codePlanAssignees = sqliteTable('code_plan_assignees', { - codePlanId: text('code_plan_id').notNull().references(() => codePlans.id, { onDelete: 'cascade' }), - userId: text('user_id').notNull().references(() => users.id, { onDelete: 'cascade' }), - createdAt: integer('created_at', { mode: 'timestamp' }).notNull().$defaultFn(() => new Date()), -}, (t) => [ - primaryKey({ columns: [t.codePlanId, t.userId] }), -]) - export const workItems = sqliteTable('work_items', { id: text('id').primaryKey().$defaultFn(() => crypto.randomUUID()), productId: text('product_id').notNull().references(() => products.id, { onDelete: 'cascade' }), diff --git a/lib/db/schema.ts b/lib/db/schema.ts index d2232f9..10cffe8 100644 --- a/lib/db/schema.ts +++ b/lib/db/schema.ts @@ -20,7 +20,6 @@ export const { assetDependencies, codePlans, codePlanAssets, - codePlanAssignees, workItems, workItemCodePlans, tasks, diff --git a/lib/db/seed-demo.ts b/lib/db/seed-demo.ts index 3b66027..1ea7858 100644 --- a/lib/db/seed-demo.ts +++ b/lib/db/seed-demo.ts @@ -18,7 +18,6 @@ import { assets, codePlans, codePlanAssets, - codePlanAssignees, tasks, } from './schema' import { eq, inArray } from 'drizzle-orm' @@ -282,7 +281,7 @@ async function seed() { async function findOrCreatePlan( title: string, - values: typeof codePlans.$inferInsert & { targetAssetIds?: string[]; assigneeIds?: string[] }, + values: typeof codePlans.$inferInsert & { targetAssetIds?: string[] }, ) { const existing = await db.query.codePlans.findFirst({ where: (p, { eq }) => eq(p.title, title), @@ -291,16 +290,12 @@ async function seed() { console.log(` plan exists: ${title}`) return existing.id } - const { targetAssetIds: _t, assigneeIds: _a, ...columns } = values + const { targetAssetIds: _t, ...columns } = values const [p] = await db.insert(codePlans).values(columns).returning() const assetIds = (values.targetAssetIds ?? []) as string[] if (assetIds.length > 0) { await db.insert(codePlanAssets).values(assetIds.map((assetId) => ({ codePlanId: p.id, assetId }))) } - const assigneeIds = (values.assigneeIds ?? []) as string[] - if (assigneeIds.length > 0) { - await db.insert(codePlanAssignees).values(assigneeIds.map((userId) => ({ codePlanId: p.id, userId }))) - } console.log(` created plan: ${title}`) return p.id } @@ -312,7 +307,7 @@ async function seed() { tags: ['ai', 'llm', 'q2'], targetAssetIds: [webAppId, planEngineId], startDate: '2026-04-01', endDate: '2026-05-20', deadline: '2026-06-01', - creatorId: alexId, assigneeIds: [alexId, mikeId], + creatorId: alexId, }) const collabPlanId = await findOrCreatePlan('Real-time Collaboration', { @@ -322,7 +317,7 @@ async function seed() { tags: ['realtime', 'websockets', 'collab'], targetAssetIds: [webAppId, planEngineId, redisId], startDate: '2026-04-15', endDate: '2026-06-01', deadline: '2026-06-15', - creatorId: sarahId, assigneeIds: [sarahId, lisaId], + creatorId: sarahId, }) const uiLibPlanId = await findOrCreatePlan('Component Library v2', { @@ -332,7 +327,7 @@ async function seed() { tags: ['frontend', 'a11y', 'design-system'], targetAssetIds: [uiLibId], startDate: '2026-03-01', endDate: '2026-04-15', deadline: '2026-04-20', - creatorId: mikeId, assigneeIds: [mikeId], + creatorId: mikeId, }) const apiV2PlanId = await findOrCreatePlan('API Gateway v2', { @@ -342,7 +337,7 @@ async function seed() { tags: ['api', 'backend', 'performance'], targetAssetIds: [apiGatewayId, workerSvcId], startDate: '2026-03-20', endDate: '2026-05-10', deadline: '2026-05-20', - creatorId: sarahId, assigneeIds: [sarahId, lisaId], + creatorId: sarahId, }) const analyticsPlanId = await findOrCreatePlan('Analytics Pipeline Overhaul', { @@ -352,7 +347,7 @@ async function seed() { tags: ['analytics', 'performance', 'urgent'], targetAssetIds: [analyticsDbId], startDate: '2026-05-01', endDate: '2026-05-31', deadline: '2026-06-07', - creatorId: alexId, assigneeIds: [alexId, lisaId], + creatorId: alexId, }) const pushPlanId = await findOrCreatePlan('Push Notifications', { @@ -362,7 +357,7 @@ async function seed() { tags: ['mobile', 'notifications', 'feature'], targetAssetIds: [iosId, androidId, bffId], startDate: '2026-04-10', endDate: '2026-05-15', deadline: '2026-05-20', - creatorId: sarahId, assigneeIds: [sarahId], + creatorId: sarahId, }) const androidPlanId = await findOrCreatePlan('Android Performance Optimization', { @@ -372,7 +367,7 @@ async function seed() { tags: ['mobile', 'android', 'performance'], targetAssetIds: [androidId], startDate: '2026-05-10', endDate: '2026-06-05', deadline: '2026-06-10', - creatorId: lisaId, assigneeIds: [lisaId], + creatorId: lisaId, }) await findOrCreatePlan('SSO & OAuth Integration', { @@ -382,7 +377,7 @@ async function seed() { tags: ['auth', 'sso', 'oauth'], targetAssetIds: [authSvcId], startDate: '2026-01-20', endDate: '2026-03-10', deadline: '2026-03-15', - creatorId: alexId, assigneeIds: [alexId, sarahId], + creatorId: alexId, }) await findOrCreatePlan('Database Schema v2', { @@ -391,7 +386,7 @@ async function seed() { productId: apiId, type: 'refactor', status: 'draft', tags: ['database', 'maintenance'], targetAssetIds: [postgresId], - creatorId: alexId, assigneeIds: [], + creatorId: alexId, }) // ── Tasks ────────────────────────────────────────────────────────────────── diff --git a/package.json b/package.json index 7ddb45a..2fcdf5d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "codeplans", - "version": "0.3.21", + "version": "0.3.22", "description": "Manage and track coordinated changes across your software architecture.", "author": "Sai Prakash ", "homepage": "https://codeplans.ai", diff --git a/tests/helpers/db.ts b/tests/helpers/db.ts index a4ecc7e..43c4371 100644 --- a/tests/helpers/db.ts +++ b/tests/helpers/db.ts @@ -11,7 +11,6 @@ import { assets, codePlans, codePlanAssets, - codePlanAssignees, workItems, workItemCodePlans, tasks, @@ -33,7 +32,6 @@ export async function clearTables() { await d.delete(workItemCodePlans) await d.delete(workItems) await d.delete(codePlanAssets) - await d.delete(codePlanAssignees) await d.delete(tasks) await d.delete(codePlans) await d.delete(assets) @@ -224,14 +222,12 @@ export async function seedFixtures() { }, ]) - // 7b. Plan↔asset and plan↔assignee join rows (source of truth; arrays kept in step 7 - // to mirror the one-release rollback window) + // 7b. Plan↔asset join rows (source of truth for targeted assets). Plan + // assignees aren't a stored link — they're derived from task.assigneeId + // (task1 below, assigned to bob, is what makes planActive show bob). await d.insert(codePlanAssets).values([ { codePlanId: F.planActive, assetId: F.assetApi }, ]) - await d.insert(codePlanAssignees).values([ - { codePlanId: F.planActive, userId: F.bob }, - ]) // 8. Tasks await d.insert(tasks).values([ diff --git a/tests/lib/db/mutations.test.ts b/tests/lib/db/mutations.test.ts index b7bd1c2..066a2e7 100644 --- a/tests/lib/db/mutations.test.ts +++ b/tests/lib/db/mutations.test.ts @@ -178,7 +178,6 @@ describe('createCodePlan', () => { type: 'improvement', tags: ['perf'], targetAssetIds: [F.assetApi], - assigneeIds: [F.bob], }, F.alice, ) @@ -187,10 +186,11 @@ describe('createCodePlan', () => { expect(plan.status).toBe('draft') expect(plan.creatorId).toBe(F.alice) - // Links live in the join tables (array columns dropped in v0.3.0) + // Target assets live in the join table (array column dropped in v0.3.0). + // Assignees aren't a stored link at all — a fresh plan with no tasks has none. const detail = await getCodePlan(plan.id, F.alice) expect(detail!.targetAssetIds).toEqual([F.assetApi]) - expect(detail!.assigneeIds).toEqual([F.bob]) + expect(detail!.assignees).toEqual([]) }) }) @@ -205,12 +205,9 @@ describe('updateCodePlan', () => { const updated = await updateCodePlan(F.planActive, { title: 'Renamed Plan', tags: ['new-tag'], - assigneeIds: [], }) expect(updated!.title).toBe('Renamed Plan') expect(updated!.tags).toEqual(['new-tag']) - const detail = await getCodePlan(F.planActive, F.alice) - expect(detail!.assigneeIds).toEqual([]) }) it('returns null for non-existent plan', async () => { @@ -219,6 +216,29 @@ describe('updateCodePlan', () => { }) }) +describe('plan assignees (derived from task assignment)', () => { + it('reflects the unique set of assignees across the plan\'s tasks', async () => { + // Fixture: planActive already has task1 assigned to bob. + let detail = await getCodePlan(F.planActive, F.alice) + expect(detail!.assignees.map((a) => a.id)).toEqual([F.bob]) + + // Assigning task2 to carol adds her to the derived set. + await updateTask(F.task2, { assigneeId: F.carol }) + detail = await getCodePlan(F.planActive, F.alice) + expect(detail!.assignees.map((a) => a.id).sort()).toEqual([F.bob, F.carol].sort()) + + // Unassigning task1 removes bob once no task in the plan points to him. + await updateTask(F.task1, { assigneeId: null }) + detail = await getCodePlan(F.planActive, F.alice) + expect(detail!.assignees.map((a) => a.id)).toEqual([F.carol]) + }) + + it('a plan with no tasks has no assignees', async () => { + const detail = await getCodePlan(F.planDraft, F.alice) + expect(detail!.assignees).toEqual([]) + }) +}) + describe('deleteCodePlan', () => { it('deletes plan when userId is creator', async () => { const result = await deleteCodePlan(F.planDraft, F.alice)