// SPDX-License-Identifier: LicenseRef-ConusAI-Template-1.0 import { actorRef, projectRef } from "@conusai-kit/business" import { conflict, notFound, type ProjectScope } from "@conusai-kit/core" import type { ActivitiesReads, ActivitiesService } from "./ports.js" const keyOf = (scope: ProjectScope, operation: string, requestId: string) => `activities:${scope.projectId}:${operation}:${requestId}` const targetOf = (service: ActivitiesReads, scope: ProjectScope) => projectRef(service.bindingId, scope.projectId) /** An app project is a narrower boundary than its organization partition. */ export async function ownedTask(service: ActivitiesReads, scope: ProjectScope, taskId: string) { const task = await service.get(taskId) if (!task.targets.includes(targetOf(service, scope))) throw notFound() return task } function currentOrRetry(actual: string, requested: string) { // A future revision could become valid after a concurrent target move. A // prior revision must reach the service's idempotency check on a real retry. if (BigInt(requested) > BigInt(actual)) throw conflict("The task changed; reload before editing.") } export const listTasks = ( service: ActivitiesReads, scope: ProjectScope, input: { status?: "open" | "done" | "cancelled" | undefined; after?: string | undefined }, ) => service.list({ target: targetOf(service, scope), status: input.status ?? "open", ...(input.after ? { after: input.after } : {}), }) export const projectTimeline = (service: ActivitiesReads, scope: ProjectScope, after?: string) => service.timeline({ target: targetOf(service, scope), ...(after ? { after } : {}) }) export const createTask = ( service: ActivitiesService, scope: ProjectScope, input: { subject: string; body?: string | undefined; requestId: string }, ) => service.create({ subject: input.subject.trim(), body: input.body ?? "", target: targetOf(service, scope), participant: actorRef(service.bindingId, scope.actor.userId), key: keyOf(scope, "create", input.requestId), }) export async function updateTask( service: ActivitiesService, scope: ProjectScope, input: { taskId: string; subject: string; body: string; expectedVersion: string; requestId: string }, ) { const task = await ownedTask(service, scope, input.taskId) currentOrRetry(task.revision, input.expectedVersion) return service.update({ task, target: targetOf(service, scope), subject: input.subject.trim(), body: input.body, expectedVersion: input.expectedVersion, key: keyOf(scope, "update", input.requestId), }) } export async function changeTask( service: ActivitiesService, scope: ProjectScope, input: { taskId: string; expectedVersion: string; requestId: string }, state: "done" | "open", ) { const task = await ownedTask(service, scope, input.taskId) currentOrRetry(task.revision, input.expectedVersion) return service.change({ taskId: task.taskId, state, expectedVersion: input.expectedVersion, key: keyOf(scope, state, input.requestId), }) } export async function listComments( service: ActivitiesReads, scope: ProjectScope, input: { taskId: string; after?: string | undefined }, ) { await ownedTask(service, scope, input.taskId) const comments = await service.comments({ taskId: input.taskId, ...(input.after ? { after: input.after } : {}) }) await ownedTask(service, scope, input.taskId) return comments } export async function addComment( service: ActivitiesService, scope: ProjectScope, input: { taskId: string; body: string; expectedVersion: string; requestId: string }, ) { const task = await ownedTask(service, scope, input.taskId) currentOrRetry(task.revision, input.expectedVersion) return service.comment({ taskId: input.taskId, body: input.body.trim(), expectedVersion: input.expectedVersion, key: keyOf(scope, "comment", input.requestId), }) }