Skip to content

moooon

Motir

Vibe your whole project. Bring an idea — Motir's three AI layers plan it, track it, and ship it, end to end. You're looking at Motir, built in Motir.

  • Vibe Project
  • Open Source
  • AI Agent
  • AI Loop
1
requests
0
upvotes
145
planned
1,361
shipped

Motir · Work items

MOTIR-220Done

4.2.2 Backend — atomic bulk sprint-assign / move-to-backlog + create-into-sprint (composes the 4.1.4 single-issue primitives; bounded batch, one transaction)

Estimate: 22m · Depends on: 4.1.3, 4.1.4

The ONLY net-new backend in this Story: a thin composition layer over Story 4.1.4's single-issue primitives, so the backlog's multi-select + inline-create affordances are ATOMIC and bounded rather than N client round-trips. Strictly composes existing service methods + the 4-layer rules (CLAUDE.md); adds no new schema (the sprint + backlog_rank model is Story 4.1).

Why a backend subtask at all (vs. looping 4.1.4 in the client). Jira moves a multi-selection to a sprint in one operation; doing it as N sequential assignToSprint calls from the browser is slow at real-team scale and NON-ATOMIC — a mid-batch failure leaves half the selection moved. A bounded server transaction is the durable shape (completeness/scale axis), so the bulk move is one prisma.$transaction.

Methods (extend sprintsService / backlogService — wherever 4.1.4's association writes live; same module, one tx per method, DTO mapping, typed errors, the finding-#26 workspaceId gate on every read/write):

  • bulkAssignToSprint(itemIds: string[], sprintId: string) — assign every issue in ONE transaction, reusing 4.1.4 assignToSprint's same-project guard per item (reject the WHOLE batch with CrossProjectSprintAssignmentError if any member is cross-project — atomic, no partial move); records a 1.4.6 revision per moved item in the same tx. Bounded: cap the batch (e.g. ≤ a sane max) and reject oversize with a typed error — never an unbounded write.
  • bulkMoveToBacklog(itemIds: string[]) — null sprint_id for every issue in one tx (they reappear in backlog_rank order); same bounded + revision rules.
  • createBacklogIssue(projectId, { sprintId?, title, type, ... }) — create an issue, append its backlog_rank (4.1.4 create-time-rank path), and — when sprintId is given — assign it (same-project guard) ALL in one transaction; records the create + assignment revision. Reuses workItemsService.create; keep the new surface thin.
  • Empty-input guards (prodect-core-coverage-gate): an empty itemIds short-circuits (no-op, not an error) with a direct unit test so the branch-coverage gate stays green.

Routes (HTTP-only, one service call + error→status mapping each): POST /api/sprints/[id]/issues:bulk (bulk assign) / POST /api/projects/[id]/backlog:bulk-move (bulk to backlog) / POST /api/projects/[id]/backlog/issues (create into backlog or sprint) — or the closest existing route shape; no business logic in the route.

Acceptance criteria

  • bulkAssignToSprint + bulkMoveToBacklog move every issue in the batch in ONE transaction (a forced mid-batch failure rolls the whole batch back — no partial move); the same-project guard rejects the whole batch if any member is cross-project; the batch is bounded (oversize → typed error); each moved item records a 1.4.6 revision.
  • createBacklogIssue creates an issue, appends a backlog_rank, and optionally assigns it to a sprint (same-project guarded) in one transaction, recording the revision; it reuses workItemsService.create rather than re-implementing creation.
  • Methods own their transaction, return DTOs (mapped in lib/mappers/*), throw typed errors the routes map to status codes, and enforce the finding-#26 workspaceId gate; empty itemIds is a guarded no-op with a direct test.
  • Routes are HTTP-only one-service-call handlers; no new migration (the model is Story 4.1).
  • pnpm test:coverage keeps the new/changed service + route files ≥90% branch/fn/line (the CI coverage gate).

Context refs

  • Story 4.1.4 (assignToSprint / moveToBacklog / rankIssue + the create-time-rank path) + 4.1.3 (sprintsService + DTOs/errors) — the single-issue primitives this composes; do NOT re-implement them
  • lib/services/workItemsService.ts — the create path createBacklogIssue reuses; the 1.4.6 workItemRevisionsService audit write to reuse in the same tx
  • lib/services/boardsService.ts — the bulk/transaction + DTO-mapping shape to mirror; lib/mappers/*, lib/dto/*, lib/<domain>/errors.ts layout
  • motir-core/CLAUDE.md (4-layer: one-tx-per-method, repo single-ops, required-tx writes, HTTP-only routes) + prodect-core-coverage-gate (empty-input guard tests) + finding #26 (workspaceId gate)