@btst/stack 2.1.0 → 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api/index.cjs +9 -1
- package/dist/api/index.d.cts +4 -4
- package/dist/api/index.d.mts +4 -4
- package/dist/api/index.d.ts +4 -4
- package/dist/api/index.mjs +9 -1
- package/dist/client/index.d.cts +2 -2
- package/dist/client/index.d.mts +2 -2
- package/dist/client/index.d.ts +2 -2
- package/dist/index.d.cts +1 -1
- package/dist/index.d.mts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/packages/stack/src/plugins/ai-chat/api/getters.cjs +42 -0
- package/dist/packages/stack/src/plugins/ai-chat/api/getters.mjs +39 -0
- package/dist/packages/stack/src/plugins/ai-chat/api/plugin.cjs +5 -0
- package/dist/packages/stack/src/plugins/ai-chat/api/plugin.mjs +5 -0
- package/dist/packages/stack/src/plugins/blog/api/getters.cjs +131 -0
- package/dist/packages/stack/src/plugins/blog/api/getters.mjs +127 -0
- package/dist/packages/stack/src/plugins/blog/api/plugin.cjs +60 -107
- package/dist/packages/stack/src/plugins/blog/api/plugin.mjs +60 -107
- package/dist/packages/stack/src/plugins/blog/api/query-key-defs.cjs +18 -0
- package/dist/packages/stack/src/plugins/blog/api/query-key-defs.mjs +15 -0
- package/dist/packages/stack/src/plugins/blog/api/serializers.cjs +21 -0
- package/dist/packages/stack/src/plugins/blog/api/serializers.mjs +18 -0
- package/dist/packages/stack/src/plugins/blog/client/plugin.cjs +16 -1
- package/dist/packages/stack/src/plugins/blog/client/plugin.mjs +17 -2
- package/dist/packages/stack/src/plugins/cms/api/getters.cjs +156 -0
- package/dist/packages/stack/src/plugins/cms/api/getters.mjs +147 -0
- package/dist/packages/stack/src/plugins/cms/api/plugin.cjs +624 -617
- package/dist/packages/stack/src/plugins/cms/api/plugin.mjs +623 -616
- package/dist/packages/stack/src/plugins/cms/api/query-key-defs.cjs +29 -0
- package/dist/packages/stack/src/plugins/cms/api/query-key-defs.mjs +26 -0
- package/dist/packages/stack/src/plugins/cms/client/components/pages/content-editor-page.internal.cjs +1 -1
- package/dist/packages/stack/src/plugins/cms/client/components/pages/content-editor-page.internal.mjs +1 -1
- package/dist/packages/stack/src/plugins/cms/client/hooks/cms-hooks.cjs +6 -3
- package/dist/packages/stack/src/plugins/cms/client/hooks/cms-hooks.mjs +6 -3
- package/dist/packages/stack/src/plugins/cms/client/plugin.cjs +15 -0
- package/dist/packages/stack/src/plugins/cms/client/plugin.mjs +16 -1
- package/dist/packages/stack/src/plugins/form-builder/api/getters.cjs +120 -0
- package/dist/packages/stack/src/plugins/form-builder/api/getters.mjs +112 -0
- package/dist/packages/stack/src/plugins/form-builder/api/plugin.cjs +75 -86
- package/dist/packages/stack/src/plugins/form-builder/api/plugin.mjs +71 -82
- package/dist/packages/stack/src/plugins/form-builder/api/query-key-defs.cjs +37 -0
- package/dist/packages/stack/src/plugins/form-builder/api/query-key-defs.mjs +33 -0
- package/dist/packages/stack/src/plugins/form-builder/client/components/pages/submissions-page.internal.cjs +1 -1
- package/dist/packages/stack/src/plugins/form-builder/client/components/pages/submissions-page.internal.mjs +1 -1
- package/dist/packages/stack/src/plugins/form-builder/client/plugin.cjs +15 -0
- package/dist/packages/stack/src/plugins/form-builder/client/plugin.mjs +16 -1
- package/dist/packages/stack/src/plugins/kanban/api/getters.cjs +84 -0
- package/dist/packages/stack/src/plugins/kanban/api/getters.mjs +81 -0
- package/dist/packages/stack/src/plugins/kanban/api/plugin.cjs +37 -123
- package/dist/packages/stack/src/plugins/kanban/api/plugin.mjs +37 -123
- package/dist/packages/stack/src/plugins/kanban/api/query-key-defs.cjs +26 -0
- package/dist/packages/stack/src/plugins/kanban/api/query-key-defs.mjs +23 -0
- package/dist/packages/stack/src/plugins/kanban/api/serializers.cjs +30 -0
- package/dist/packages/stack/src/plugins/kanban/api/serializers.mjs +26 -0
- package/dist/packages/stack/src/plugins/kanban/client/plugin.cjs +11 -1
- package/dist/packages/stack/src/plugins/kanban/client/plugin.mjs +12 -2
- package/dist/packages/stack/src/plugins/utils.cjs +6 -0
- package/dist/packages/stack/src/plugins/utils.mjs +6 -1
- package/dist/plugins/ai-chat/api/index.cjs +3 -0
- package/dist/plugins/ai-chat/api/index.d.cts +27 -4
- package/dist/plugins/ai-chat/api/index.d.mts +27 -4
- package/dist/plugins/ai-chat/api/index.d.ts +27 -4
- package/dist/plugins/ai-chat/api/index.mjs +1 -0
- package/dist/plugins/ai-chat/client/hooks/index.d.cts +2 -2
- package/dist/plugins/ai-chat/client/hooks/index.d.mts +2 -2
- package/dist/plugins/ai-chat/client/hooks/index.d.ts +2 -2
- package/dist/plugins/ai-chat/query-keys.d.cts +9 -284
- package/dist/plugins/ai-chat/query-keys.d.mts +9 -284
- package/dist/plugins/ai-chat/query-keys.d.ts +9 -284
- package/dist/plugins/api/index.d.cts +4 -3
- package/dist/plugins/api/index.d.mts +4 -3
- package/dist/plugins/api/index.d.ts +4 -3
- package/dist/plugins/blog/api/index.cjs +9 -0
- package/dist/plugins/blog/api/index.d.cts +20 -4
- package/dist/plugins/blog/api/index.d.mts +20 -4
- package/dist/plugins/blog/api/index.d.ts +20 -4
- package/dist/plugins/blog/api/index.mjs +3 -0
- package/dist/plugins/blog/client/hooks/index.d.cts +5 -5
- package/dist/plugins/blog/client/hooks/index.d.mts +5 -5
- package/dist/plugins/blog/client/hooks/index.d.ts +5 -5
- package/dist/plugins/blog/client/index.d.cts +1 -1
- package/dist/plugins/blog/client/index.d.mts +1 -1
- package/dist/plugins/blog/client/index.d.ts +1 -1
- package/dist/plugins/blog/query-keys.cjs +13 -9
- package/dist/plugins/blog/query-keys.d.cts +8 -333
- package/dist/plugins/blog/query-keys.d.mts +8 -333
- package/dist/plugins/blog/query-keys.d.ts +8 -333
- package/dist/plugins/blog/query-keys.mjs +13 -9
- package/dist/plugins/client/index.cjs +1 -0
- package/dist/plugins/client/index.d.cts +10 -3
- package/dist/plugins/client/index.d.mts +10 -3
- package/dist/plugins/client/index.d.ts +10 -3
- package/dist/plugins/client/index.mjs +1 -1
- package/dist/plugins/cms/api/index.cjs +10 -0
- package/dist/plugins/cms/api/index.d.cts +7 -163
- package/dist/plugins/cms/api/index.d.mts +7 -163
- package/dist/plugins/cms/api/index.d.ts +7 -163
- package/dist/plugins/cms/api/index.mjs +2 -0
- package/dist/plugins/cms/client/hooks/index.d.cts +1 -1
- package/dist/plugins/cms/client/hooks/index.d.mts +1 -1
- package/dist/plugins/cms/client/hooks/index.d.ts +1 -1
- package/dist/plugins/cms/query-keys.cjs +2 -1
- package/dist/plugins/cms/query-keys.d.cts +6 -9
- package/dist/plugins/cms/query-keys.d.mts +6 -9
- package/dist/plugins/cms/query-keys.d.ts +6 -9
- package/dist/plugins/cms/query-keys.mjs +2 -1
- package/dist/plugins/form-builder/api/index.cjs +10 -0
- package/dist/plugins/form-builder/api/index.d.cts +7 -141
- package/dist/plugins/form-builder/api/index.d.mts +7 -141
- package/dist/plugins/form-builder/api/index.d.ts +7 -141
- package/dist/plugins/form-builder/api/index.mjs +2 -0
- package/dist/plugins/form-builder/client/components/index.d.cts +1 -1
- package/dist/plugins/form-builder/client/components/index.d.mts +1 -1
- package/dist/plugins/form-builder/client/components/index.d.ts +1 -1
- package/dist/plugins/form-builder/client/hooks/index.d.cts +1 -1
- package/dist/plugins/form-builder/client/hooks/index.d.mts +1 -1
- package/dist/plugins/form-builder/client/hooks/index.d.ts +1 -1
- package/dist/plugins/form-builder/query-keys.cjs +3 -2
- package/dist/plugins/form-builder/query-keys.d.cts +7 -6
- package/dist/plugins/form-builder/query-keys.d.mts +7 -6
- package/dist/plugins/form-builder/query-keys.d.ts +7 -6
- package/dist/plugins/form-builder/query-keys.mjs +3 -2
- package/dist/plugins/kanban/api/index.cjs +9 -0
- package/dist/plugins/kanban/api/index.d.cts +17 -395
- package/dist/plugins/kanban/api/index.d.mts +17 -395
- package/dist/plugins/kanban/api/index.d.ts +17 -395
- package/dist/plugins/kanban/api/index.mjs +3 -0
- package/dist/plugins/kanban/client/components/index.d.cts +1 -1
- package/dist/plugins/kanban/client/components/index.d.mts +1 -1
- package/dist/plugins/kanban/client/components/index.d.ts +1 -1
- package/dist/plugins/kanban/client/hooks/index.d.cts +1 -1
- package/dist/plugins/kanban/client/hooks/index.d.mts +1 -1
- package/dist/plugins/kanban/client/hooks/index.d.ts +1 -1
- package/dist/plugins/kanban/client/index.d.cts +1 -1
- package/dist/plugins/kanban/client/index.d.mts +1 -1
- package/dist/plugins/kanban/client/index.d.ts +1 -1
- package/dist/plugins/kanban/query-keys.cjs +6 -12
- package/dist/plugins/kanban/query-keys.d.cts +5 -16
- package/dist/plugins/kanban/query-keys.d.mts +5 -16
- package/dist/plugins/kanban/query-keys.d.ts +5 -16
- package/dist/plugins/kanban/query-keys.mjs +6 -12
- package/dist/plugins/open-api/api/index.d.cts +2 -2
- package/dist/plugins/open-api/api/index.d.mts +2 -2
- package/dist/plugins/open-api/api/index.d.ts +2 -2
- package/dist/plugins/route-docs/client/index.d.cts +1 -1
- package/dist/plugins/route-docs/client/index.d.mts +1 -1
- package/dist/plugins/route-docs/client/index.d.ts +1 -1
- package/dist/plugins/ui-builder/index.d.cts +1 -1
- package/dist/plugins/ui-builder/index.d.mts +1 -1
- package/dist/plugins/ui-builder/index.d.ts +1 -1
- package/dist/shared/{stack.BoA0xkJv.d.cts → stack.7n9Y_u7N.d.cts} +33 -7
- package/dist/shared/{stack.BoA0xkJv.d.mts → stack.7n9Y_u7N.d.mts} +33 -7
- package/dist/shared/{stack.BoA0xkJv.d.ts → stack.7n9Y_u7N.d.ts} +33 -7
- package/dist/shared/stack.B1EeBt1b.d.ts +297 -0
- package/dist/shared/stack.BIXEI6v_.d.mts +419 -0
- package/dist/shared/stack.BKfolAyK.d.ts +419 -0
- package/dist/shared/stack.BeSm90va.d.ts +289 -0
- package/dist/shared/stack.BpolpQpf.d.cts +445 -0
- package/dist/shared/stack.C5dtIncc.d.mts +293 -0
- package/dist/shared/stack.CIP6QS9l.d.ts +293 -0
- package/dist/shared/stack.CMh_EdxW.d.cts +289 -0
- package/dist/shared/stack.CP68pFEH.d.mts +297 -0
- package/dist/shared/{stack.BsXokfNh.d.mts → stack.CVDTkMoO.d.cts} +8 -2
- package/dist/shared/{stack.BsXokfNh.d.ts → stack.CVDTkMoO.d.mts} +8 -2
- package/dist/shared/{stack.BsXokfNh.d.cts → stack.CVDTkMoO.d.ts} +8 -2
- package/dist/shared/{stack.DKDMI-QO.d.mts → stack.DJaKVY7v.d.cts} +7 -1
- package/dist/shared/{stack.DKDMI-QO.d.ts → stack.DJaKVY7v.d.mts} +7 -1
- package/dist/shared/{stack.DKDMI-QO.d.cts → stack.DJaKVY7v.d.ts} +7 -1
- package/dist/shared/{stack.DzH_wcvr.d.mts → stack.DdI5W6MB.d.cts} +9 -3
- package/dist/shared/{stack.DzH_wcvr.d.ts → stack.DdI5W6MB.d.mts} +9 -3
- package/dist/shared/{stack.DzH_wcvr.d.cts → stack.DdI5W6MB.d.ts} +9 -3
- package/dist/shared/stack.Dg09R0oB.d.mts +289 -0
- package/dist/shared/stack.Dw0Ly2TM.d.cts +293 -0
- package/dist/shared/stack.IdtKDRka.d.cts +297 -0
- package/dist/shared/stack.TIBF2AOx.d.ts +445 -0
- package/dist/shared/stack.rTy7-wQU.d.mts +445 -0
- package/dist/shared/stack.snB1EDP7.d.cts +419 -0
- package/package.json +3 -3
- package/src/__tests__/stack-api.test.ts +118 -0
- package/src/api/index.ts +15 -1
- package/src/plugins/ai-chat/__tests__/getters.test.ts +109 -0
- package/src/plugins/ai-chat/api/getters.ts +71 -0
- package/src/plugins/ai-chat/api/index.ts +1 -0
- package/src/plugins/ai-chat/api/plugin.ts +8 -0
- package/src/plugins/api/index.ts +3 -1
- package/src/plugins/blog/__tests__/getters.test.ts +540 -0
- package/src/plugins/blog/api/getters.ts +243 -0
- package/src/plugins/blog/api/index.ts +9 -0
- package/src/plugins/blog/api/plugin.ts +98 -141
- package/src/plugins/blog/api/query-key-defs.ts +46 -0
- package/src/plugins/blog/api/serializers.ts +27 -0
- package/src/plugins/blog/client/plugin.tsx +21 -1
- package/src/plugins/blog/query-keys.ts +21 -20
- package/src/plugins/client/index.ts +1 -1
- package/src/plugins/cms/__tests__/getters.test.ts +206 -0
- package/src/plugins/cms/api/getters.ts +268 -0
- package/src/plugins/cms/api/index.ts +15 -1
- package/src/plugins/cms/api/plugin.ts +151 -150
- package/src/plugins/cms/api/query-key-defs.ts +53 -0
- package/src/plugins/cms/api/serializers.ts +12 -0
- package/src/plugins/cms/client/components/pages/content-editor-page.internal.tsx +1 -1
- package/src/plugins/cms/client/hooks/cms-hooks.tsx +3 -0
- package/src/plugins/cms/client/plugin.tsx +19 -0
- package/src/plugins/cms/query-keys.ts +2 -1
- package/src/plugins/cms/types.ts +1 -1
- package/src/plugins/form-builder/__tests__/getters.test.ts +159 -0
- package/src/plugins/form-builder/api/getters.ts +226 -0
- package/src/plugins/form-builder/api/index.ts +15 -1
- package/src/plugins/form-builder/api/plugin.ts +107 -109
- package/src/plugins/form-builder/api/query-key-defs.ts +79 -0
- package/src/plugins/form-builder/api/serializers.ts +12 -0
- package/src/plugins/form-builder/client/components/pages/submissions-page.internal.tsx +1 -1
- package/src/plugins/form-builder/client/plugin.tsx +19 -0
- package/src/plugins/form-builder/query-keys.ts +6 -2
- package/src/plugins/form-builder/types.ts +2 -2
- package/src/plugins/kanban/__tests__/getters.test.ts +172 -0
- package/src/plugins/kanban/api/getters.ts +149 -0
- package/src/plugins/kanban/api/index.ts +4 -0
- package/src/plugins/kanban/api/plugin.ts +65 -146
- package/src/plugins/kanban/api/query-key-defs.ts +54 -0
- package/src/plugins/kanban/api/serializers.ts +49 -0
- package/src/plugins/kanban/client/plugin.tsx +15 -1
- package/src/plugins/kanban/query-keys.ts +10 -14
- package/src/plugins/utils.ts +19 -0
- package/src/types.ts +44 -5
- package/dist/shared/{stack.CbuN2zVV.d.cts → stack.CBON0dWL.d.cts} +7 -7
- package/dist/shared/{stack.CbuN2zVV.d.mts → stack.CBON0dWL.d.mts} +7 -7
- package/dist/shared/{stack.CbuN2zVV.d.ts → stack.CBON0dWL.d.ts} +7 -7
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach } from "vitest";
|
|
2
|
+
import { createMemoryAdapter } from "@btst/adapter-memory";
|
|
3
|
+
import { defineDb } from "@btst/db";
|
|
4
|
+
import type { Adapter } from "@btst/db";
|
|
5
|
+
import { kanbanSchema } from "../db";
|
|
6
|
+
import { getAllBoards, getBoardById } from "../api/getters";
|
|
7
|
+
|
|
8
|
+
const createTestAdapter = (): Adapter => {
|
|
9
|
+
const db = defineDb({}).use(kanbanSchema);
|
|
10
|
+
return createMemoryAdapter(db)({});
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
async function createBoard(
|
|
14
|
+
adapter: Adapter,
|
|
15
|
+
name: string,
|
|
16
|
+
slug: string,
|
|
17
|
+
ownerId?: string,
|
|
18
|
+
): Promise<any> {
|
|
19
|
+
return adapter.create({
|
|
20
|
+
model: "kanbanBoard",
|
|
21
|
+
data: {
|
|
22
|
+
name,
|
|
23
|
+
slug,
|
|
24
|
+
...(ownerId ? { ownerId } : {}),
|
|
25
|
+
createdAt: new Date(),
|
|
26
|
+
updatedAt: new Date(),
|
|
27
|
+
},
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async function createColumn(
|
|
32
|
+
adapter: Adapter,
|
|
33
|
+
boardId: string,
|
|
34
|
+
title: string,
|
|
35
|
+
order: number,
|
|
36
|
+
): Promise<any> {
|
|
37
|
+
return adapter.create({
|
|
38
|
+
model: "kanbanColumn",
|
|
39
|
+
data: {
|
|
40
|
+
boardId,
|
|
41
|
+
title,
|
|
42
|
+
order,
|
|
43
|
+
createdAt: new Date(),
|
|
44
|
+
updatedAt: new Date(),
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async function createTask(
|
|
50
|
+
adapter: Adapter,
|
|
51
|
+
columnId: string,
|
|
52
|
+
title: string,
|
|
53
|
+
order: number,
|
|
54
|
+
): Promise<any> {
|
|
55
|
+
return adapter.create({
|
|
56
|
+
model: "kanbanTask",
|
|
57
|
+
data: {
|
|
58
|
+
columnId,
|
|
59
|
+
title,
|
|
60
|
+
priority: "MEDIUM",
|
|
61
|
+
order,
|
|
62
|
+
isArchived: false,
|
|
63
|
+
createdAt: new Date(),
|
|
64
|
+
updatedAt: new Date(),
|
|
65
|
+
},
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
describe("kanban getters", () => {
|
|
70
|
+
let adapter: Adapter;
|
|
71
|
+
|
|
72
|
+
beforeEach(() => {
|
|
73
|
+
adapter = createTestAdapter();
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
describe("getAllBoards", () => {
|
|
77
|
+
it("returns empty array when no boards exist", async () => {
|
|
78
|
+
const { items, total } = await getAllBoards(adapter);
|
|
79
|
+
expect(items).toEqual([]);
|
|
80
|
+
expect(total).toBe(0);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it("returns all boards with columns and tasks", async () => {
|
|
84
|
+
const board = (await createBoard(adapter, "My Board", "my-board")) as any;
|
|
85
|
+
const col = (await createColumn(adapter, board.id, "To Do", 0)) as any;
|
|
86
|
+
await createTask(adapter, col.id, "Task 1", 0);
|
|
87
|
+
|
|
88
|
+
const { items: boards, total } = await getAllBoards(adapter);
|
|
89
|
+
expect(boards).toHaveLength(1);
|
|
90
|
+
expect(total).toBe(1);
|
|
91
|
+
expect(boards[0]!.slug).toBe("my-board");
|
|
92
|
+
expect(boards[0]!.columns).toHaveLength(1);
|
|
93
|
+
expect(boards[0]!.columns[0]!.title).toBe("To Do");
|
|
94
|
+
expect(boards[0]!.columns[0]!.tasks).toHaveLength(1);
|
|
95
|
+
expect(boards[0]!.columns[0]!.tasks[0]!.title).toBe("Task 1");
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it("returns boards with empty columns array when no columns exist", async () => {
|
|
99
|
+
await createBoard(adapter, "Empty Board", "empty-board");
|
|
100
|
+
|
|
101
|
+
const { items: boards } = await getAllBoards(adapter);
|
|
102
|
+
expect(boards).toHaveLength(1);
|
|
103
|
+
expect(boards[0]!.columns).toEqual([]);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it("filters boards by slug", async () => {
|
|
107
|
+
await createBoard(adapter, "Board A", "board-a");
|
|
108
|
+
await createBoard(adapter, "Board B", "board-b");
|
|
109
|
+
|
|
110
|
+
const { items, total } = await getAllBoards(adapter, { slug: "board-a" });
|
|
111
|
+
expect(items).toHaveLength(1);
|
|
112
|
+
expect(total).toBe(1);
|
|
113
|
+
expect(items[0]!.slug).toBe("board-a");
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it("filters boards by ownerId", async () => {
|
|
117
|
+
await createBoard(adapter, "Alice Board", "alice-board", "user-alice");
|
|
118
|
+
await createBoard(adapter, "Bob Board", "bob-board", "user-bob");
|
|
119
|
+
|
|
120
|
+
const { items, total } = await getAllBoards(adapter, {
|
|
121
|
+
ownerId: "user-alice",
|
|
122
|
+
});
|
|
123
|
+
expect(items).toHaveLength(1);
|
|
124
|
+
expect(total).toBe(1);
|
|
125
|
+
expect(items[0]!.slug).toBe("alice-board");
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it("sorts columns by order", async () => {
|
|
129
|
+
const board = (await createBoard(adapter, "Board", "board")) as any;
|
|
130
|
+
// Create columns out of order
|
|
131
|
+
await createColumn(adapter, board.id, "Done", 2);
|
|
132
|
+
await createColumn(adapter, board.id, "To Do", 0);
|
|
133
|
+
await createColumn(adapter, board.id, "In Progress", 1);
|
|
134
|
+
|
|
135
|
+
const { items: boards } = await getAllBoards(adapter);
|
|
136
|
+
expect(boards[0]!.columns[0]!.title).toBe("To Do");
|
|
137
|
+
expect(boards[0]!.columns[1]!.title).toBe("In Progress");
|
|
138
|
+
expect(boards[0]!.columns[2]!.title).toBe("Done");
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
describe("getBoardById", () => {
|
|
143
|
+
it("returns null when board does not exist", async () => {
|
|
144
|
+
const board = await getBoardById(adapter, "nonexistent");
|
|
145
|
+
expect(board).toBeNull();
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it("returns the board with columns and tasks", async () => {
|
|
149
|
+
const board = (await createBoard(adapter, "My Board", "my-board")) as any;
|
|
150
|
+
const col1 = (await createColumn(adapter, board.id, "To Do", 0)) as any;
|
|
151
|
+
const col2 = (await createColumn(adapter, board.id, "Done", 1)) as any;
|
|
152
|
+
await createTask(adapter, col1.id, "Task A", 0);
|
|
153
|
+
await createTask(adapter, col1.id, "Task B", 1);
|
|
154
|
+
await createTask(adapter, col2.id, "Task C", 0);
|
|
155
|
+
|
|
156
|
+
const result = await getBoardById(adapter, board.id);
|
|
157
|
+
expect(result).not.toBeNull();
|
|
158
|
+
expect(result!.id).toBe(board.id);
|
|
159
|
+
expect(result!.columns).toHaveLength(2);
|
|
160
|
+
expect(result!.columns[0]!.tasks).toHaveLength(2);
|
|
161
|
+
expect(result!.columns[1]!.tasks).toHaveLength(1);
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
it("returns board with empty columns when no columns exist", async () => {
|
|
165
|
+
const board = (await createBoard(adapter, "Empty Board", "empty")) as any;
|
|
166
|
+
|
|
167
|
+
const result = await getBoardById(adapter, board.id);
|
|
168
|
+
expect(result).not.toBeNull();
|
|
169
|
+
expect(result!.columns).toEqual([]);
|
|
170
|
+
});
|
|
171
|
+
});
|
|
172
|
+
});
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import type { Adapter } from "@btst/db";
|
|
2
|
+
import type {
|
|
3
|
+
BoardWithKanbanColumn,
|
|
4
|
+
BoardWithColumns,
|
|
5
|
+
ColumnWithTasks,
|
|
6
|
+
Task,
|
|
7
|
+
} from "../types";
|
|
8
|
+
import type { z } from "zod";
|
|
9
|
+
import type { BoardListQuerySchema } from "../schemas";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Paginated result returned by {@link getAllBoards}.
|
|
13
|
+
*/
|
|
14
|
+
export interface BoardListResult {
|
|
15
|
+
items: BoardWithColumns[];
|
|
16
|
+
total: number;
|
|
17
|
+
limit?: number;
|
|
18
|
+
offset?: number;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Given a raw board record (with a `column` join), fetches tasks for every
|
|
23
|
+
* column in parallel and returns the sorted columns with their tasks attached.
|
|
24
|
+
* Strips the raw `column` join field from the returned board object.
|
|
25
|
+
*/
|
|
26
|
+
async function hydrateColumnsWithTasks(
|
|
27
|
+
adapter: Adapter,
|
|
28
|
+
board: BoardWithKanbanColumn,
|
|
29
|
+
): Promise<BoardWithColumns> {
|
|
30
|
+
const columnIds = (board.column || []).map((c) => c.id);
|
|
31
|
+
const tasksByColumn = new Map<string, Task[]>();
|
|
32
|
+
|
|
33
|
+
if (columnIds.length > 0) {
|
|
34
|
+
const taskResults = await Promise.all(
|
|
35
|
+
columnIds.map((columnId) =>
|
|
36
|
+
adapter.findMany<Task>({
|
|
37
|
+
model: "kanbanTask",
|
|
38
|
+
where: [
|
|
39
|
+
{ field: "columnId", value: columnId, operator: "eq" as const },
|
|
40
|
+
],
|
|
41
|
+
sortBy: { field: "order", direction: "asc" },
|
|
42
|
+
}),
|
|
43
|
+
),
|
|
44
|
+
);
|
|
45
|
+
for (let i = 0; i < columnIds.length; i++) {
|
|
46
|
+
const columnId = columnIds[i];
|
|
47
|
+
const tasks = taskResults[i];
|
|
48
|
+
if (columnId && tasks) {
|
|
49
|
+
tasksByColumn.set(columnId, tasks);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const columns: ColumnWithTasks[] = (board.column || [])
|
|
55
|
+
.sort((a, b) => a.order - b.order)
|
|
56
|
+
.map((col) => ({ ...col, tasks: tasksByColumn.get(col.id) || [] }));
|
|
57
|
+
|
|
58
|
+
const { column: _, ...boardWithoutJoin } = board;
|
|
59
|
+
return { ...boardWithoutJoin, columns };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Retrieve all boards matching optional filter criteria, with columns and tasks.
|
|
64
|
+
* Pure DB function - no hooks, no HTTP context. Safe for SSG and server-side use.
|
|
65
|
+
*
|
|
66
|
+
* @param adapter - The database adapter
|
|
67
|
+
* @param params - Optional filter/pagination parameters (same shape as the list API query)
|
|
68
|
+
*/
|
|
69
|
+
export async function getAllBoards(
|
|
70
|
+
adapter: Adapter,
|
|
71
|
+
params?: z.infer<typeof BoardListQuerySchema>,
|
|
72
|
+
): Promise<BoardListResult> {
|
|
73
|
+
const query = params ?? {};
|
|
74
|
+
|
|
75
|
+
const whereConditions: Array<{
|
|
76
|
+
field: string;
|
|
77
|
+
value: string;
|
|
78
|
+
operator: "eq";
|
|
79
|
+
}> = [];
|
|
80
|
+
|
|
81
|
+
if (query.slug) {
|
|
82
|
+
whereConditions.push({
|
|
83
|
+
field: "slug",
|
|
84
|
+
value: query.slug,
|
|
85
|
+
operator: "eq" as const,
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (query.ownerId) {
|
|
90
|
+
whereConditions.push({
|
|
91
|
+
field: "ownerId",
|
|
92
|
+
value: query.ownerId,
|
|
93
|
+
operator: "eq" as const,
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (query.organizationId) {
|
|
98
|
+
whereConditions.push({
|
|
99
|
+
field: "organizationId",
|
|
100
|
+
value: query.organizationId,
|
|
101
|
+
operator: "eq" as const,
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const where = whereConditions.length > 0 ? whereConditions : undefined;
|
|
106
|
+
|
|
107
|
+
const [boards, total] = await Promise.all([
|
|
108
|
+
adapter.findMany<BoardWithKanbanColumn>({
|
|
109
|
+
model: "kanbanBoard",
|
|
110
|
+
limit: query.limit ?? 50,
|
|
111
|
+
offset: query.offset ?? 0,
|
|
112
|
+
where,
|
|
113
|
+
sortBy: { field: "createdAt", direction: "desc" },
|
|
114
|
+
join: { kanbanColumn: true },
|
|
115
|
+
}),
|
|
116
|
+
adapter.count({ model: "kanbanBoard", where }),
|
|
117
|
+
]);
|
|
118
|
+
|
|
119
|
+
const items = await Promise.all(
|
|
120
|
+
boards.map((board) => hydrateColumnsWithTasks(adapter, board)),
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
return { items, total, limit: query.limit, offset: query.offset };
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Retrieve a single board by its ID, with all columns and tasks.
|
|
128
|
+
* Returns null if the board is not found.
|
|
129
|
+
* Pure DB function - no hooks, no HTTP context. Safe for SSG and server-side use.
|
|
130
|
+
*
|
|
131
|
+
* @param adapter - The database adapter
|
|
132
|
+
* @param id - The board ID
|
|
133
|
+
*/
|
|
134
|
+
export async function getBoardById(
|
|
135
|
+
adapter: Adapter,
|
|
136
|
+
id: string,
|
|
137
|
+
): Promise<BoardWithColumns | null> {
|
|
138
|
+
const board = await adapter.findOne<BoardWithKanbanColumn>({
|
|
139
|
+
model: "kanbanBoard",
|
|
140
|
+
where: [{ field: "id", value: id, operator: "eq" as const }],
|
|
141
|
+
join: { kanbanColumn: true },
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
if (!board) {
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return hydrateColumnsWithTasks(adapter, board);
|
|
149
|
+
}
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
export {
|
|
2
2
|
kanbanBackendPlugin,
|
|
3
3
|
type KanbanApiRouter,
|
|
4
|
+
type KanbanRouteKey,
|
|
4
5
|
type KanbanApiContext,
|
|
5
6
|
type KanbanBackendHooks,
|
|
6
7
|
} from "./plugin";
|
|
8
|
+
export { getAllBoards, getBoardById, type BoardListResult } from "./getters";
|
|
9
|
+
export { serializeBoard, serializeColumn, serializeTask } from "./serializers";
|
|
10
|
+
export { KANBAN_QUERY_KEYS } from "./query-key-defs";
|
|
@@ -5,7 +5,7 @@ import { z } from "zod";
|
|
|
5
5
|
import { kanbanSchema as dbSchema } from "../db";
|
|
6
6
|
import type {
|
|
7
7
|
Board,
|
|
8
|
-
|
|
8
|
+
BoardWithColumns,
|
|
9
9
|
Column,
|
|
10
10
|
ColumnWithTasks,
|
|
11
11
|
Task,
|
|
@@ -23,6 +23,55 @@ import {
|
|
|
23
23
|
updateColumnSchema,
|
|
24
24
|
updateTaskSchema,
|
|
25
25
|
} from "../schemas";
|
|
26
|
+
import { getAllBoards, getBoardById } from "./getters";
|
|
27
|
+
import { KANBAN_QUERY_KEYS } from "./query-key-defs";
|
|
28
|
+
import { serializeBoard } from "./serializers";
|
|
29
|
+
import type { QueryClient } from "@tanstack/react-query";
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Route keys for the Kanban plugin — matches the keys returned by
|
|
33
|
+
* `stackClient.router.getRoute(path).routeKey`.
|
|
34
|
+
*/
|
|
35
|
+
export type KanbanRouteKey = "boards" | "newBoard" | "board";
|
|
36
|
+
|
|
37
|
+
interface KanbanPrefetchForRoute {
|
|
38
|
+
(key: "boards" | "newBoard", qc: QueryClient): Promise<void>;
|
|
39
|
+
(key: "board", qc: QueryClient, params: { boardId: string }): Promise<void>;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function createKanbanPrefetchForRoute(
|
|
43
|
+
adapter: Adapter,
|
|
44
|
+
): KanbanPrefetchForRoute {
|
|
45
|
+
return async function prefetchForRoute(
|
|
46
|
+
key: KanbanRouteKey,
|
|
47
|
+
qc: QueryClient,
|
|
48
|
+
params?: Record<string, string>,
|
|
49
|
+
): Promise<void> {
|
|
50
|
+
switch (key) {
|
|
51
|
+
case "boards": {
|
|
52
|
+
const result = await getAllBoards(adapter, { limit: 50, offset: 0 });
|
|
53
|
+
qc.setQueryData(
|
|
54
|
+
KANBAN_QUERY_KEYS.boardsList({}),
|
|
55
|
+
result.items.map(serializeBoard),
|
|
56
|
+
);
|
|
57
|
+
break;
|
|
58
|
+
}
|
|
59
|
+
case "board": {
|
|
60
|
+
const boardId = params?.boardId ?? "";
|
|
61
|
+
if (boardId) {
|
|
62
|
+
const board = await getBoardById(adapter, boardId);
|
|
63
|
+
qc.setQueryData(
|
|
64
|
+
KANBAN_QUERY_KEYS.boardDetail(boardId),
|
|
65
|
+
board ? serializeBoard(board) : null,
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
break;
|
|
69
|
+
}
|
|
70
|
+
default:
|
|
71
|
+
break;
|
|
72
|
+
}
|
|
73
|
+
} as KanbanPrefetchForRoute;
|
|
74
|
+
}
|
|
26
75
|
|
|
27
76
|
/**
|
|
28
77
|
* Context passed to kanban API hooks
|
|
@@ -84,10 +133,12 @@ export interface KanbanBackendHooks {
|
|
|
84
133
|
) => Promise<boolean> | boolean;
|
|
85
134
|
|
|
86
135
|
/**
|
|
87
|
-
* Called after boards are listed successfully
|
|
136
|
+
* Called after boards are listed successfully.
|
|
137
|
+
* Receives the items array (same shape as `board[]`) for consistency
|
|
138
|
+
* with analogous hooks in other plugins (e.g. `onPostsRead`).
|
|
88
139
|
*/
|
|
89
140
|
onBoardsRead?: (
|
|
90
|
-
boards:
|
|
141
|
+
boards: BoardWithColumns[],
|
|
91
142
|
filter: z.infer<typeof BoardListQuerySchema>,
|
|
92
143
|
context: KanbanApiContext,
|
|
93
144
|
) => Promise<void> | void;
|
|
@@ -261,6 +312,13 @@ export const kanbanBackendPlugin = (hooks?: KanbanBackendHooks) =>
|
|
|
261
312
|
|
|
262
313
|
dbPlugin: dbSchema,
|
|
263
314
|
|
|
315
|
+
api: (adapter) => ({
|
|
316
|
+
getAllBoards: (params?: Parameters<typeof getAllBoards>[1]) =>
|
|
317
|
+
getAllBoards(adapter, params),
|
|
318
|
+
getBoardById: (id: string) => getBoardById(adapter, id),
|
|
319
|
+
prefetchForRoute: createKanbanPrefetchForRoute(adapter),
|
|
320
|
+
}),
|
|
321
|
+
|
|
264
322
|
routes: (adapter: Adapter) => {
|
|
265
323
|
// ============ Board Endpoints ============
|
|
266
324
|
|
|
@@ -284,100 +342,10 @@ export const kanbanBackendPlugin = (hooks?: KanbanBackendHooks) =>
|
|
|
284
342
|
}
|
|
285
343
|
}
|
|
286
344
|
|
|
287
|
-
const
|
|
288
|
-
|
|
289
|
-
if (query.slug) {
|
|
290
|
-
whereConditions.push({
|
|
291
|
-
field: "slug",
|
|
292
|
-
value: query.slug,
|
|
293
|
-
operator: "eq" as const,
|
|
294
|
-
});
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
if (query.ownerId) {
|
|
298
|
-
whereConditions.push({
|
|
299
|
-
field: "ownerId",
|
|
300
|
-
value: query.ownerId,
|
|
301
|
-
operator: "eq" as const,
|
|
302
|
-
});
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
if (query.organizationId) {
|
|
306
|
-
whereConditions.push({
|
|
307
|
-
field: "organizationId",
|
|
308
|
-
value: query.organizationId,
|
|
309
|
-
operator: "eq" as const,
|
|
310
|
-
});
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
const boards = await adapter.findMany<BoardWithKanbanColumn>({
|
|
314
|
-
model: "kanbanBoard",
|
|
315
|
-
limit: query.limit ?? 50,
|
|
316
|
-
offset: query.offset ?? 0,
|
|
317
|
-
where: whereConditions,
|
|
318
|
-
sortBy: {
|
|
319
|
-
field: "createdAt",
|
|
320
|
-
direction: "desc",
|
|
321
|
-
},
|
|
322
|
-
join: {
|
|
323
|
-
kanbanColumn: true,
|
|
324
|
-
},
|
|
325
|
-
});
|
|
326
|
-
|
|
327
|
-
// Get all column IDs to fetch tasks
|
|
328
|
-
// Note: adapter returns joined data under schema key name ("column"), not model name
|
|
329
|
-
const columnIds: string[] = [];
|
|
330
|
-
for (const board of boards) {
|
|
331
|
-
if (board.column) {
|
|
332
|
-
for (const col of board.column) {
|
|
333
|
-
columnIds.push(col.id);
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
// Fetch tasks for each column in parallel (avoids loading all tasks from DB)
|
|
339
|
-
const tasksByColumn = new Map<string, Task[]>();
|
|
340
|
-
if (columnIds.length > 0) {
|
|
341
|
-
const taskQueries = columnIds.map((columnId) =>
|
|
342
|
-
adapter.findMany<Task>({
|
|
343
|
-
model: "kanbanTask",
|
|
344
|
-
where: [
|
|
345
|
-
{
|
|
346
|
-
field: "columnId",
|
|
347
|
-
value: columnId,
|
|
348
|
-
operator: "eq" as const,
|
|
349
|
-
},
|
|
350
|
-
],
|
|
351
|
-
sortBy: { field: "order", direction: "asc" },
|
|
352
|
-
}),
|
|
353
|
-
);
|
|
354
|
-
const taskResults = await Promise.all(taskQueries);
|
|
355
|
-
for (let i = 0; i < columnIds.length; i++) {
|
|
356
|
-
const columnId = columnIds[i];
|
|
357
|
-
const tasks = taskResults[i];
|
|
358
|
-
if (columnId && tasks) {
|
|
359
|
-
tasksByColumn.set(columnId, tasks);
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
// Map boards with columns and tasks
|
|
365
|
-
const result = boards.map((board) => {
|
|
366
|
-
const columns = (board.column || [])
|
|
367
|
-
.sort((a, b) => a.order - b.order)
|
|
368
|
-
.map((col) => ({
|
|
369
|
-
...col,
|
|
370
|
-
tasks: tasksByColumn.get(col.id) || [],
|
|
371
|
-
}));
|
|
372
|
-
const { column: _, ...boardWithoutJoin } = board;
|
|
373
|
-
return {
|
|
374
|
-
...boardWithoutJoin,
|
|
375
|
-
columns,
|
|
376
|
-
};
|
|
377
|
-
});
|
|
345
|
+
const result = await getAllBoards(adapter, query);
|
|
378
346
|
|
|
379
347
|
if (hooks?.onBoardsRead) {
|
|
380
|
-
await hooks.onBoardsRead(result, query, context);
|
|
348
|
+
await hooks.onBoardsRead(result.items, query, context);
|
|
381
349
|
}
|
|
382
350
|
|
|
383
351
|
return result;
|
|
@@ -409,61 +377,12 @@ export const kanbanBackendPlugin = (hooks?: KanbanBackendHooks) =>
|
|
|
409
377
|
}
|
|
410
378
|
}
|
|
411
379
|
|
|
412
|
-
const
|
|
413
|
-
model: "kanbanBoard",
|
|
414
|
-
where: [
|
|
415
|
-
{ field: "id", value: params.id, operator: "eq" as const },
|
|
416
|
-
],
|
|
417
|
-
join: {
|
|
418
|
-
kanbanColumn: true,
|
|
419
|
-
},
|
|
420
|
-
});
|
|
380
|
+
const result = await getBoardById(adapter, params.id);
|
|
421
381
|
|
|
422
|
-
if (!
|
|
382
|
+
if (!result) {
|
|
423
383
|
throw ctx.error(404, { message: "Board not found" });
|
|
424
384
|
}
|
|
425
385
|
|
|
426
|
-
// Fetch tasks for each column in parallel (avoids loading all tasks from DB)
|
|
427
|
-
// Note: adapter returns joined data under schema key name ("column"), not model name
|
|
428
|
-
const columnIds = (board.column || []).map((c) => c.id);
|
|
429
|
-
const tasksByColumn = new Map<string, Task[]>();
|
|
430
|
-
if (columnIds.length > 0) {
|
|
431
|
-
const taskQueries = columnIds.map((columnId) =>
|
|
432
|
-
adapter.findMany<Task>({
|
|
433
|
-
model: "kanbanTask",
|
|
434
|
-
where: [
|
|
435
|
-
{
|
|
436
|
-
field: "columnId",
|
|
437
|
-
value: columnId,
|
|
438
|
-
operator: "eq" as const,
|
|
439
|
-
},
|
|
440
|
-
],
|
|
441
|
-
sortBy: { field: "order", direction: "asc" },
|
|
442
|
-
}),
|
|
443
|
-
);
|
|
444
|
-
const taskResults = await Promise.all(taskQueries);
|
|
445
|
-
for (let i = 0; i < columnIds.length; i++) {
|
|
446
|
-
const columnId = columnIds[i];
|
|
447
|
-
const tasks = taskResults[i];
|
|
448
|
-
if (columnId && tasks) {
|
|
449
|
-
tasksByColumn.set(columnId, tasks);
|
|
450
|
-
}
|
|
451
|
-
}
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
const columns = (board.column || [])
|
|
455
|
-
.sort((a, b) => a.order - b.order)
|
|
456
|
-
.map((col) => ({
|
|
457
|
-
...col,
|
|
458
|
-
tasks: tasksByColumn.get(col.id) || [],
|
|
459
|
-
}));
|
|
460
|
-
|
|
461
|
-
const { column: _, ...boardWithoutJoin } = board;
|
|
462
|
-
const result = {
|
|
463
|
-
...boardWithoutJoin,
|
|
464
|
-
columns,
|
|
465
|
-
};
|
|
466
|
-
|
|
467
386
|
if (hooks?.onBoardRead) {
|
|
468
387
|
await hooks.onBoardRead(result, context);
|
|
469
388
|
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Internal query key constants for the Kanban plugin.
|
|
3
|
+
* Shared between query-keys.ts (HTTP path) and prefetchForRoute (DB path)
|
|
4
|
+
* to prevent key drift between SSR loaders and SSG prefetching.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export interface BoardsListDiscriminator {
|
|
8
|
+
slug: string | undefined;
|
|
9
|
+
ownerId: string | undefined;
|
|
10
|
+
organizationId: string | undefined;
|
|
11
|
+
limit: number;
|
|
12
|
+
offset: number;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Builds the discriminator object for the boards list query key.
|
|
17
|
+
* Mirrors the inline object used in createBoardsQueries.list.
|
|
18
|
+
*/
|
|
19
|
+
export function boardsListDiscriminator(params?: {
|
|
20
|
+
slug?: string;
|
|
21
|
+
ownerId?: string;
|
|
22
|
+
organizationId?: string;
|
|
23
|
+
limit?: number;
|
|
24
|
+
offset?: number;
|
|
25
|
+
}): BoardsListDiscriminator {
|
|
26
|
+
return {
|
|
27
|
+
slug: params?.slug,
|
|
28
|
+
ownerId: params?.ownerId,
|
|
29
|
+
organizationId: params?.organizationId,
|
|
30
|
+
limit: params?.limit ?? 50,
|
|
31
|
+
offset: params?.offset ?? 0,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Full query key builders — use these with queryClient.setQueryData() */
|
|
36
|
+
export const KANBAN_QUERY_KEYS = {
|
|
37
|
+
/**
|
|
38
|
+
* Key for boards.list(params) query.
|
|
39
|
+
* Full key: ["boards", "list", { slug, ownerId, organizationId, limit, offset }]
|
|
40
|
+
*/
|
|
41
|
+
boardsList: (params?: {
|
|
42
|
+
slug?: string;
|
|
43
|
+
ownerId?: string;
|
|
44
|
+
organizationId?: string;
|
|
45
|
+
limit?: number;
|
|
46
|
+
offset?: number;
|
|
47
|
+
}) => ["boards", "list", boardsListDiscriminator(params)] as const,
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Key for boards.detail(boardId) query.
|
|
51
|
+
* Full key: ["boards", "detail", boardId]
|
|
52
|
+
*/
|
|
53
|
+
boardDetail: (boardId: string) => ["boards", "detail", boardId] as const,
|
|
54
|
+
};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
Task,
|
|
3
|
+
ColumnWithTasks,
|
|
4
|
+
BoardWithColumns,
|
|
5
|
+
SerializedTask,
|
|
6
|
+
SerializedColumn,
|
|
7
|
+
SerializedBoardWithColumns,
|
|
8
|
+
} from "../types";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Serialize a Task for SSR/SSG use (convert dates to strings).
|
|
12
|
+
* Pure function — no DB access, no hooks.
|
|
13
|
+
*/
|
|
14
|
+
export function serializeTask(task: Task): SerializedTask {
|
|
15
|
+
return {
|
|
16
|
+
...task,
|
|
17
|
+
completedAt: task.completedAt?.toISOString(),
|
|
18
|
+
createdAt: task.createdAt.toISOString(),
|
|
19
|
+
updatedAt: task.updatedAt.toISOString(),
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Serialize a Column (with its tasks) for SSR/SSG use (convert dates to strings).
|
|
25
|
+
* Pure function — no DB access, no hooks.
|
|
26
|
+
*/
|
|
27
|
+
export function serializeColumn(col: ColumnWithTasks): SerializedColumn {
|
|
28
|
+
return {
|
|
29
|
+
...col,
|
|
30
|
+
createdAt: col.createdAt.toISOString(),
|
|
31
|
+
updatedAt: col.updatedAt.toISOString(),
|
|
32
|
+
tasks: col.tasks.map(serializeTask),
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Serialize a Board (with columns and tasks) for SSR/SSG use (convert dates to strings).
|
|
38
|
+
* Pure function — no DB access, no hooks.
|
|
39
|
+
*/
|
|
40
|
+
export function serializeBoard(
|
|
41
|
+
board: BoardWithColumns,
|
|
42
|
+
): SerializedBoardWithColumns {
|
|
43
|
+
return {
|
|
44
|
+
...board,
|
|
45
|
+
createdAt: board.createdAt.toISOString(),
|
|
46
|
+
updatedAt: board.updatedAt.toISOString(),
|
|
47
|
+
columns: board.columns.map(serializeColumn),
|
|
48
|
+
};
|
|
49
|
+
}
|