diff --git a/app/api/products/[id]/claude-context/route.ts b/app/api/products/[id]/claude-context/route.ts index 5387a0f..f5a5fd5 100644 --- a/app/api/products/[id]/claude-context/route.ts +++ b/app/api/products/[id]/claude-context/route.ts @@ -29,14 +29,26 @@ export async function GET( return Response.json({ error: 'Product niet gevonden' }, { status: 404 }) } - const [activeSprint, openTodos] = await Promise.all([ + const [activeSprint, openIdeas] = await Promise.all([ prisma.sprint.findFirst({ where: { product_id: id, status: 'ACTIVE' }, select: { id: true, sprint_goal: true, status: true }, }), - prisma.todo.findMany({ - where: { user_id: auth.userId, product_id: id, done: false, archived: false }, - select: { id: true, title: true, description: true, created_at: true }, + prisma.idea.findMany({ + where: { + user_id: auth.userId, + product_id: id, + archived: false, + status: { not: 'PLANNED' }, + }, + select: { + id: true, + code: true, + title: true, + description: true, + status: true, + created_at: true, + }, orderBy: { created_at: 'asc' }, take: 50, }), @@ -89,6 +101,6 @@ export async function GET( product, active_sprint: activeSprint, next_story: nextStoryPayload, - open_todos: openTodos, + open_ideas: openIdeas, }) } diff --git a/docs/api.md b/docs/api.md index 4065a47..c8796e4 100644 --- a/docs/api.md +++ b/docs/api.md @@ -96,7 +96,7 @@ curl -H "Authorization: Bearer $TOKEN" https://scrum4me.app/api/products ### `GET /api/products/:id/claude-context` -Bundled context voor Claude Code: product, actieve sprint, volgende story (met tasks) en open todos van de tokengebruiker — in één call. +Bundled context voor Claude Code: product, actieve sprint, volgende story (met tasks) en open ideas van de tokengebruiker — in één call. **Response (200):** ```json @@ -111,13 +111,13 @@ Bundled context voor Claude Code: product, actieve sprint, volgende story (met t "priority", "sort_order", "status" } ] } | null, - "open_todos": [ - { "id", "title", "description", "created_at" } + "open_ideas": [ + { "id", "code", "title", "description", "status", "created_at" } ] } ``` -`open_todos` is gelimiteerd op 50 items, gesorteerd op `created_at` asc. Demo-tokens kunnen dit endpoint lezen. +`open_ideas` bevat ideeën van de gebruiker voor dit product die niet gearchiveerd zijn en nog niet de status `PLANNED` hebben (= nog niet als PBI gepromoveerd). Gelimiteerd op 50 items, gesorteerd op `created_at` asc. Demo-tokens kunnen dit endpoint lezen. ```bash curl -H "Authorization: Bearer $TOKEN" \ diff --git a/docs/api/rest-contract.md b/docs/api/rest-contract.md index 2feddd4..758d437 100644 --- a/docs/api/rest-contract.md +++ b/docs/api/rest-contract.md @@ -105,7 +105,7 @@ curl -H "Authorization: Bearer $TOKEN" https://scrum4me.app/api/products ### `GET /api/products/:id/claude-context` -Bundled context voor Claude Code: product, actieve sprint, volgende story (met tasks) en open todos van de tokengebruiker — in één call. +Bundled context voor Claude Code: product, actieve sprint, volgende story (met tasks) en open ideas van de tokengebruiker — in één call. **Response (200):** ```json @@ -120,13 +120,13 @@ Bundled context voor Claude Code: product, actieve sprint, volgende story (met t "priority", "sort_order", "status" } ] } | null, - "open_todos": [ - { "id", "title", "description", "created_at" } + "open_ideas": [ + { "id", "code", "title", "description", "status", "created_at" } ] } ``` -`open_todos` is gelimiteerd op 50 items, gesorteerd op `created_at` asc. Demo-tokens kunnen dit endpoint lezen. +`open_ideas` bevat ideeën van de gebruiker voor dit product die niet gearchiveerd zijn en nog niet de status `PLANNED` hebben (= nog niet als PBI gepromoveerd). Gelimiteerd op 50 items, gesorteerd op `created_at` asc. Demo-tokens kunnen dit endpoint lezen. ```bash curl -H "Authorization: Bearer $TOKEN" \ diff --git a/docs/erd.svg b/docs/erd.svg index dc4f289..7b40b67 100644 --- a/docs/erd.svg +++ b/docs/erd.svg @@ -1 +1 @@ -

active_product

user

enum:role

user

user

product

enum:status

pbi

product

sprint

assignee

enum:status

story

enum:type

enum:status

product

enum:status

story

product

sprint

enum:status

enum:verify_required

user

product

task

idea

enum:kind

enum:status

claimed_by_token

enum:verify_result

user

token

product

user

user

product

user

product

pbi

enum:status

idea

enum:type

enum:status

idea

user

story

task

idea

product

asker

answerer

Role

PRODUCT_OWNER

PRODUCT_OWNER

SCRUM_MASTER

SCRUM_MASTER

DEVELOPER

DEVELOPER

ADMIN

ADMIN

StoryStatus

OPEN

OPEN

IN_SPRINT

IN_SPRINT

DONE

DONE

PbiStatus

READY

READY

BLOCKED

BLOCKED

DONE

DONE

ClaudeJobStatus

QUEUED

QUEUED

CLAIMED

CLAIMED

RUNNING

RUNNING

DONE

DONE

FAILED

FAILED

CANCELLED

CANCELLED

VerifyResult

ALIGNED

ALIGNED

PARTIAL

PARTIAL

EMPTY

EMPTY

DIVERGENT

DIVERGENT

VerifyRequired

ALIGNED

ALIGNED

ALIGNED_OR_PARTIAL

ALIGNED_OR_PARTIAL

ANY

ANY

TaskStatus

TO_DO

TO_DO

IN_PROGRESS

IN_PROGRESS

REVIEW

REVIEW

DONE

DONE

LogType

IMPLEMENTATION_PLAN

IMPLEMENTATION_PLAN

TEST_RESULT

TEST_RESULT

COMMIT

COMMIT

TestStatus

PASSED

PASSED

FAILED

FAILED

SprintStatus

ACTIVE

ACTIVE

COMPLETED

COMPLETED

IdeaStatus

DRAFT

DRAFT

GRILLING

GRILLING

GRILL_FAILED

GRILL_FAILED

GRILLED

GRILLED

PLANNING

PLANNING

PLAN_FAILED

PLAN_FAILED

PLAN_READY

PLAN_READY

PLANNED

PLANNED

ClaudeJobKind

TASK_IMPLEMENTATION

TASK_IMPLEMENTATION

IDEA_GRILL

IDEA_GRILL

IDEA_MAKE_PLAN

IDEA_MAKE_PLAN

PLAN_CHAT

PLAN_CHAT

IdeaLogType

DECISION

DECISION

NOTE

NOTE

GRILL_RESULT

GRILL_RESULT

PLAN_RESULT

PLAN_RESULT

STATUS_CHANGE

STATUS_CHANGE

JOB_EVENT

JOB_EVENT

UserQuestionStatus

pending

pending

answered

answered

users

String

id

🗝️

String

username

String

email

String

password_hash

Boolean

is_demo

String

bio

String

bio_detail

Boolean

must_reset_password

Bytes

avatar_data

Int

idea_code_counter

DateTime

created_at

DateTime

updated_at

user_roles

String

id

🗝️

Role

role

api_tokens

String

id

🗝️

String

token_hash

String

label

DateTime

created_at

DateTime

revoked_at

products

String

id

🗝️

String

name

String

code

String

description

String

repo_url

String

definition_of_done

Boolean

auto_pr

Boolean

archived

DateTime

created_at

DateTime

updated_at

pbis

String

id

🗝️

String

code

String

title

String

description

Int

priority

Float

sort_order

PbiStatus

status

String

pr_url

DateTime

pr_merged_at

DateTime

created_at

DateTime

updated_at

stories

String

id

🗝️

String

code

String

title

String

description

String

acceptance_criteria

Int

priority

Float

sort_order

StoryStatus

status

DateTime

created_at

DateTime

updated_at

story_logs

String

id

🗝️

LogType

type

String

content

TestStatus

status

String

commit_hash

String

commit_message

Json

metadata

DateTime

created_at

sprints

String

id

🗝️

String

sprint_goal

SprintStatus

status

DateTime

start_date

DateTime

end_date

DateTime

created_at

DateTime

completed_at

tasks

String

id

🗝️

String

code

String

title

String

description

String

implementation_plan

Int

priority

Float

sort_order

TaskStatus

status

Boolean

verify_only

VerifyRequired

verify_required

String

repo_url

DateTime

created_at

DateTime

updated_at

claude_jobs

String

id

🗝️

ClaudeJobKind

kind

ClaudeJobStatus

status

DateTime

claimed_at

DateTime

started_at

DateTime

finished_at

DateTime

pushed_at

VerifyResult

verify_result

String

plan_snapshot

String

branch

String

pr_url

String

summary

String

error

Int

retry_count

DateTime

created_at

DateTime

updated_at

claude_workers

String

id

🗝️

String

product_id

DateTime

started_at

DateTime

last_seen_at

product_members

String

id

🗝️

DateTime

created_at

todos

String

id

🗝️

String

title

String

description

Boolean

done

Boolean

archived

DateTime

created_at

DateTime

updated_at

ideas

String

id

🗝️

String

code

String

title

String

description

String

grill_md

String

plan_md

IdeaStatus

status

Boolean

archived

DateTime

created_at

DateTime

updated_at

idea_logs

String

id

🗝️

IdeaLogType

type

String

content

Json

metadata

DateTime

created_at

user_questions

String

id

🗝️

String

user_id

String

question

String

answer

UserQuestionStatus

status

DateTime

created_at

DateTime

updated_at

login_pairings

String

id

🗝️

String

secret_hash

String

desktop_token_hash

String

status

String

desktop_ua

String

desktop_ip

DateTime

created_at

DateTime

expires_at

DateTime

approved_at

DateTime

consumed_at

claude_questions

String

id

🗝️

String

question

Json

options

String

status

String

answer

DateTime

answered_at

DateTime

created_at

DateTime

expires_at

\ No newline at end of file +

active_product

user

enum:role

user

user

product

enum:status

pbi

product

sprint

assignee

enum:status

story

enum:type

enum:status

product

enum:status

story

product

sprint

enum:status

enum:verify_required

user

product

task

idea

enum:kind

enum:status

claimed_by_token

enum:verify_result

user

token

product

user

user

product

pbi

enum:status

idea

product

idea

enum:type

enum:status

idea

user

story

task

idea

product

asker

answerer

Role

PRODUCT_OWNER

PRODUCT_OWNER

SCRUM_MASTER

SCRUM_MASTER

DEVELOPER

DEVELOPER

ADMIN

ADMIN

StoryStatus

OPEN

OPEN

IN_SPRINT

IN_SPRINT

DONE

DONE

PbiStatus

READY

READY

BLOCKED

BLOCKED

DONE

DONE

ClaudeJobStatus

QUEUED

QUEUED

CLAIMED

CLAIMED

RUNNING

RUNNING

DONE

DONE

FAILED

FAILED

CANCELLED

CANCELLED

SKIPPED

SKIPPED

VerifyResult

ALIGNED

ALIGNED

PARTIAL

PARTIAL

EMPTY

EMPTY

DIVERGENT

DIVERGENT

VerifyRequired

ALIGNED

ALIGNED

ALIGNED_OR_PARTIAL

ALIGNED_OR_PARTIAL

ANY

ANY

TaskStatus

TO_DO

TO_DO

IN_PROGRESS

IN_PROGRESS

REVIEW

REVIEW

DONE

DONE

LogType

IMPLEMENTATION_PLAN

IMPLEMENTATION_PLAN

TEST_RESULT

TEST_RESULT

COMMIT

COMMIT

TestStatus

PASSED

PASSED

FAILED

FAILED

SprintStatus

ACTIVE

ACTIVE

COMPLETED

COMPLETED

IdeaStatus

DRAFT

DRAFT

GRILLING

GRILLING

GRILL_FAILED

GRILL_FAILED

GRILLED

GRILLED

PLANNING

PLANNING

PLAN_FAILED

PLAN_FAILED

PLAN_READY

PLAN_READY

PLANNED

PLANNED

ClaudeJobKind

TASK_IMPLEMENTATION

TASK_IMPLEMENTATION

IDEA_GRILL

IDEA_GRILL

IDEA_MAKE_PLAN

IDEA_MAKE_PLAN

PLAN_CHAT

PLAN_CHAT

IdeaLogType

DECISION

DECISION

NOTE

NOTE

GRILL_RESULT

GRILL_RESULT

PLAN_RESULT

PLAN_RESULT

STATUS_CHANGE

STATUS_CHANGE

JOB_EVENT

JOB_EVENT

UserQuestionStatus

pending

pending

answered

answered

users

String

id

🗝️

String

username

String

email

String

password_hash

Boolean

is_demo

String

bio

String

bio_detail

Boolean

must_reset_password

Bytes

avatar_data

Int

idea_code_counter

Int

min_quota_pct

DateTime

created_at

DateTime

updated_at

user_roles

String

id

🗝️

Role

role

api_tokens

String

id

🗝️

String

token_hash

String

label

DateTime

created_at

DateTime

revoked_at

products

String

id

🗝️

String

name

String

code

String

description

String

repo_url

String

definition_of_done

Boolean

auto_pr

Boolean

archived

DateTime

created_at

DateTime

updated_at

pbis

String

id

🗝️

String

code

String

title

String

description

Int

priority

Float

sort_order

PbiStatus

status

String

pr_url

DateTime

pr_merged_at

DateTime

created_at

DateTime

updated_at

stories

String

id

🗝️

String

code

String

title

String

description

String

acceptance_criteria

Int

priority

Float

sort_order

StoryStatus

status

DateTime

created_at

DateTime

updated_at

story_logs

String

id

🗝️

LogType

type

String

content

TestStatus

status

String

commit_hash

String

commit_message

Json

metadata

DateTime

created_at

sprints

String

id

🗝️

String

sprint_goal

SprintStatus

status

DateTime

start_date

DateTime

end_date

DateTime

created_at

DateTime

completed_at

tasks

String

id

🗝️

String

code

String

title

String

description

String

implementation_plan

Int

priority

Float

sort_order

TaskStatus

status

Boolean

verify_only

VerifyRequired

verify_required

String

repo_url

DateTime

created_at

DateTime

updated_at

claude_jobs

String

id

🗝️

ClaudeJobKind

kind

ClaudeJobStatus

status

DateTime

claimed_at

DateTime

started_at

DateTime

finished_at

DateTime

pushed_at

VerifyResult

verify_result

String

model_id

Int

input_tokens

Int

output_tokens

Int

cache_read_tokens

Int

cache_write_tokens

String

plan_snapshot

String

branch

String

pr_url

String

summary

String

error

Int

retry_count

DateTime

created_at

DateTime

updated_at

model_prices

String

id

🗝️

String

model_id

Decimal

input_price_per_1m

Decimal

output_price_per_1m

Decimal

cache_read_price_per_1m

Decimal

cache_write_price_per_1m

String

currency

DateTime

created_at

DateTime

updated_at

claude_workers

String

id

🗝️

String

product_id

DateTime

started_at

DateTime

last_seen_at

Int

last_quota_pct

DateTime

last_quota_check_at

product_members

String

id

🗝️

DateTime

created_at

ideas

String

id

🗝️

String

code

String

title

String

description

String

grill_md

String

plan_md

IdeaStatus

status

Boolean

archived

DateTime

created_at

DateTime

updated_at

idea_products

String

id

🗝️

DateTime

created_at

idea_logs

String

id

🗝️

IdeaLogType

type

String

content

Json

metadata

DateTime

created_at

user_questions

String

id

🗝️

String

user_id

String

question

String

answer

UserQuestionStatus

status

DateTime

created_at

DateTime

updated_at

login_pairings

String

id

🗝️

String

secret_hash

String

desktop_token_hash

String

status

String

desktop_ua

String

desktop_ip

DateTime

created_at

DateTime

expires_at

DateTime

approved_at

DateTime

consumed_at

claude_questions

String

id

🗝️

String

question

Json

options

String

status

String

answer

DateTime

answered_at

DateTime

created_at

DateTime

expires_at

\ No newline at end of file diff --git a/prisma/migrations/20260507000000_migrate_todos_to_ideas/migration.sql b/prisma/migrations/20260507000000_migrate_todos_to_ideas/migration.sql new file mode 100644 index 0000000..4d740ae --- /dev/null +++ b/prisma/migrations/20260507000000_migrate_todos_to_ideas/migration.sql @@ -0,0 +1,59 @@ +BEGIN; + +WITH active_todos AS ( + SELECT t.id, t.user_id, t.product_id, t.title, t.description, t.created_at + FROM todos t + WHERE t.done = false + AND t.archived = false + AND NOT EXISTS ( + SELECT 1 FROM ideas i + WHERE i.user_id = t.user_id + AND lower(trim(i.title)) = lower(trim(t.title)) + ) +), +user_base AS ( + SELECT ut.user_id, u.idea_code_counter AS base_counter + FROM (SELECT DISTINCT user_id FROM active_todos) ut + JOIN users u ON u.id = ut.user_id +), +ranked AS ( + SELECT + at.user_id, at.product_id, at.title, at.description, at.created_at, + ub.base_counter, + ROW_NUMBER() OVER (PARTITION BY at.user_id ORDER BY at.created_at, at.id) AS rn + FROM active_todos at + JOIN user_base ub ON ub.user_id = at.user_id +), +inserted AS ( + INSERT INTO ideas ( + id, user_id, product_id, code, title, description, + status, archived, created_at, updated_at + ) + SELECT + gen_random_uuid()::text, + user_id, + product_id, + 'IDEA-' || lpad((base_counter + rn)::text, 3, '0'), + title, + description, + 'DRAFT', + false, + created_at, + now() + FROM ranked + RETURNING user_id, + (regexp_replace(code, '^IDEA-0*', ''))::int AS used_counter +) +UPDATE users u +SET idea_code_counter = sub.max_counter +FROM ( + SELECT user_id, MAX(used_counter) AS max_counter + FROM inserted + GROUP BY user_id +) sub +WHERE u.id = sub.user_id + AND sub.max_counter > u.idea_code_counter; + +DROP TABLE todos; + +COMMIT; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 5082a97..61a1d2c 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -127,7 +127,6 @@ model User { roles UserRole[] api_tokens ApiToken[] products Product[] - todos Todo[] ideas Idea[] product_members ProductMember[] assigned_stories Story[] @relation("StoryAssignee") @@ -183,7 +182,6 @@ model Product { sprints Sprint[] stories Story[] tasks Task[] - todos Todo[] members ProductMember[] active_for_users User[] @relation("UserActiveProduct") claude_questions ClaudeQuestion[] @@ -403,24 +401,6 @@ model ProductMember { @@map("product_members") } -model Todo { - id String @id @default(cuid()) - user User @relation(fields: [user_id], references: [id], onDelete: Cascade) - user_id String - product Product? @relation(fields: [product_id], references: [id], onDelete: SetNull) - product_id String? - title String - description String? @db.VarChar(2000) - done Boolean @default(false) - archived Boolean @default(false) - created_at DateTime @default(now()) - updated_at DateTime @updatedAt - - @@index([user_id, done, archived]) - @@index([user_id, product_id]) - @@map("todos") -} - model Idea { id String @id @default(cuid()) user User @relation(fields: [user_id], references: [id], onDelete: Cascade)