rspace-online/modules/rtasks/schemas.ts

171 lines
3.6 KiB
TypeScript

/**
* rTasks Automerge document schemas.
*
* Granularity: one Automerge document per board/workspace.
* DocId format: {space}:tasks:boards:{boardId}
*/
import type { DocSchema } from '../../shared/local-first/document';
// ── Document types ──
export interface ClickUpTaskMeta {
taskId: string; // ClickUp task ID
listId: string; // ClickUp list ID
url: string; // Direct link to ClickUp task
lastSyncedAt: number;
syncStatus: 'synced' | 'pending-push' | 'conflict' | 'push-failed';
contentHash: string; // SHA of title+desc+status+priority at last sync
remoteHash?: string; // ClickUp state hash at last pull
}
export interface TaskItem {
id: string;
spaceId: string;
title: string;
description: string;
status: string;
priority: string | null;
labels: string[];
assigneeId: string | null;
createdBy: string | null;
sortOrder: number;
createdAt: number;
updatedAt: number;
clickup?: ClickUpTaskMeta;
}
export interface ClickUpBoardMeta {
listId: string; // ClickUp list this board mirrors
listName: string;
workspaceId: string; // ClickUp team ID
webhookId?: string; // For teardown on disconnect
syncEnabled: boolean;
statusMap: Record<string, string>; // rTasks → ClickUp
reverseStatusMap: Record<string, string>; // ClickUp → rTasks
}
export interface BoardMeta {
id: string;
name: string;
slug: string;
description: string;
icon: string | null;
ownerDid: string | null;
statuses: string[];
labels: string[];
createdAt: number;
updatedAt: number;
clickup?: ClickUpBoardMeta;
}
export interface BoardDoc {
meta: {
module: string;
collection: string;
version: number;
spaceSlug: string;
createdAt: number;
};
board: BoardMeta;
tasks: Record<string, TaskItem>;
}
// ── Schema registration ──
export const boardSchema: DocSchema<BoardDoc> = {
module: 'tasks',
collection: 'boards',
version: 1,
init: (): BoardDoc => ({
meta: {
module: 'tasks',
collection: 'boards',
version: 1,
spaceSlug: '',
createdAt: Date.now(),
},
board: {
id: '',
name: 'Untitled Board',
slug: '',
description: '',
icon: null,
ownerDid: null,
statuses: ['TODO', 'IN_PROGRESS', 'DONE'],
labels: [],
createdAt: Date.now(),
updatedAt: Date.now(),
},
tasks: {},
}),
};
// ── Helpers ──
export function boardDocId(space: string, boardId: string) {
return `${space}:tasks:boards:${boardId}` as const;
}
// ── ClickUp connection doc (one per space) ──
export interface ClickUpConnectionDoc {
meta: {
module: string;
collection: string;
version: number;
spaceSlug: string;
createdAt: number;
};
clickup?: {
accessToken: string; // OAuth token or personal API token
teamId: string;
teamName: string;
connectedAt: number;
webhookSecret: string; // HMAC secret for webhook validation
};
}
export const clickupConnectionSchema: DocSchema<ClickUpConnectionDoc> = {
module: 'tasks',
collection: 'clickup-connection',
version: 1,
init: (): ClickUpConnectionDoc => ({
meta: {
module: 'tasks',
collection: 'clickup-connection',
version: 1,
spaceSlug: '',
createdAt: Date.now(),
},
}),
};
export function clickupConnectionDocId(space: string) {
return `${space}:tasks:clickup-connection` as const;
}
export function createTaskItem(
id: string,
spaceId: string,
title: string,
opts: Partial<TaskItem> = {},
): TaskItem {
const now = Date.now();
return {
id,
spaceId,
title,
description: '',
status: 'TODO',
priority: null,
labels: [],
assigneeId: null,
createdBy: null,
sortOrder: 0,
createdAt: now,
updatedAt: now,
...opts,
};
}