@btst/stack 1.11.0 → 1.12.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.
Files changed (163) hide show
  1. package/dist/node_modules/.pnpm/@dnd-kit_core@6.3.1_react-dom@19.2.0_react@19.2.0__react@19.2.0/node_modules/@dnd-kit/core/dist/core.esm.cjs +1 -1
  2. package/dist/node_modules/.pnpm/@dnd-kit_core@6.3.1_react-dom@19.2.0_react@19.2.0__react@19.2.0/node_modules/@dnd-kit/core/dist/core.esm.mjs +1 -1
  3. package/dist/node_modules/.pnpm/@dnd-kit_sortable@10.0.0_@dnd-kit_core@6.3.1_react-dom@19.2.0_react@19.2.0__react@19.2.0__react@19.2.0/node_modules/@dnd-kit/sortable/dist/sortable.esm.cjs +77 -0
  4. package/dist/node_modules/.pnpm/@dnd-kit_sortable@10.0.0_@dnd-kit_core@6.3.1_react-dom@19.2.0_react@19.2.0__react@19.2.0__react@19.2.0/node_modules/@dnd-kit/sortable/dist/sortable.esm.mjs +79 -3
  5. package/dist/node_modules/.pnpm/@radix-ui_react-avatar@1.1.11_@types_react-dom@19.2.3_@types_react@19.2.6__@types_react_850cfbef1935a6e49a6ad6c93c7ca70d/node_modules/@radix-ui/react-avatar/dist/index.cjs +140 -0
  6. package/dist/node_modules/.pnpm/@radix-ui_react-avatar@1.1.11_@types_react-dom@19.2.3_@types_react@19.2.6__@types_react_850cfbef1935a6e49a6ad6c93c7ca70d/node_modules/@radix-ui/react-avatar/dist/index.mjs +119 -0
  7. package/dist/node_modules/.pnpm/@radix-ui_react-context@1.1.3_@types_react@19.2.6_react@19.2.0/node_modules/@radix-ui/react-context/dist/index.cjs +80 -0
  8. package/dist/node_modules/.pnpm/@radix-ui_react-context@1.1.3_@types_react@19.2.6_react@19.2.0/node_modules/@radix-ui/react-context/dist/index.mjs +64 -0
  9. package/dist/node_modules/.pnpm/@radix-ui_react-use-is-hydrated@0.1.0_@types_react@19.2.6_react@19.2.0/node_modules/@radix-ui/react-use-is-hydrated/dist/index.cjs +18 -0
  10. package/dist/node_modules/.pnpm/@radix-ui_react-use-is-hydrated@0.1.0_@types_react@19.2.6_react@19.2.0/node_modules/@radix-ui/react-use-is-hydrated/dist/index.mjs +16 -0
  11. package/dist/packages/better-stack/src/plugins/kanban/api/plugin.cjs +846 -0
  12. package/dist/packages/better-stack/src/plugins/kanban/api/plugin.mjs +844 -0
  13. package/dist/packages/better-stack/src/plugins/kanban/client/components/forms/board-form.cjs +85 -0
  14. package/dist/packages/better-stack/src/plugins/kanban/client/components/forms/board-form.mjs +83 -0
  15. package/dist/packages/better-stack/src/plugins/kanban/client/components/forms/column-form.cjs +72 -0
  16. package/dist/packages/better-stack/src/plugins/kanban/client/components/forms/column-form.mjs +70 -0
  17. package/dist/packages/better-stack/src/plugins/kanban/client/components/forms/task-form.cjs +200 -0
  18. package/dist/packages/better-stack/src/plugins/kanban/client/components/forms/task-form.mjs +198 -0
  19. package/dist/packages/better-stack/src/plugins/kanban/client/components/loading/board-skeleton.cjs +47 -0
  20. package/dist/packages/better-stack/src/plugins/kanban/client/components/loading/board-skeleton.mjs +45 -0
  21. package/dist/packages/better-stack/src/plugins/kanban/client/components/loading/boards-list-skeleton.cjs +30 -0
  22. package/dist/packages/better-stack/src/plugins/kanban/client/components/loading/boards-list-skeleton.mjs +28 -0
  23. package/dist/packages/better-stack/src/plugins/kanban/client/components/pages/404-page.cjs +27 -0
  24. package/dist/packages/better-stack/src/plugins/kanban/client/components/pages/404-page.mjs +25 -0
  25. package/dist/packages/better-stack/src/plugins/kanban/client/components/pages/board-page.cjs +31 -0
  26. package/dist/packages/better-stack/src/plugins/kanban/client/components/pages/board-page.internal.cjs +458 -0
  27. package/dist/packages/better-stack/src/plugins/kanban/client/components/pages/board-page.internal.mjs +456 -0
  28. package/dist/packages/better-stack/src/plugins/kanban/client/components/pages/board-page.mjs +29 -0
  29. package/dist/packages/better-stack/src/plugins/kanban/client/components/pages/boards-list-page.cjs +30 -0
  30. package/dist/packages/better-stack/src/plugins/kanban/client/components/pages/boards-list-page.internal.cjs +72 -0
  31. package/dist/packages/better-stack/src/plugins/kanban/client/components/pages/boards-list-page.internal.mjs +70 -0
  32. package/dist/packages/better-stack/src/plugins/kanban/client/components/pages/boards-list-page.mjs +28 -0
  33. package/dist/packages/better-stack/src/plugins/kanban/client/components/pages/new-board-page.cjs +30 -0
  34. package/dist/packages/better-stack/src/plugins/kanban/client/components/pages/new-board-page.internal.cjs +51 -0
  35. package/dist/packages/better-stack/src/plugins/kanban/client/components/pages/new-board-page.internal.mjs +49 -0
  36. package/dist/packages/better-stack/src/plugins/kanban/client/components/pages/new-board-page.mjs +28 -0
  37. package/dist/packages/better-stack/src/plugins/kanban/client/components/shared/column-content.cjs +76 -0
  38. package/dist/packages/better-stack/src/plugins/kanban/client/components/shared/column-content.mjs +74 -0
  39. package/dist/packages/better-stack/src/plugins/kanban/client/components/shared/default-error.cjs +27 -0
  40. package/dist/packages/better-stack/src/plugins/kanban/client/components/shared/default-error.mjs +25 -0
  41. package/dist/packages/better-stack/src/plugins/kanban/client/components/shared/empty-state.cjs +32 -0
  42. package/dist/packages/better-stack/src/plugins/kanban/client/components/shared/empty-state.mjs +30 -0
  43. package/dist/packages/better-stack/src/plugins/kanban/client/components/shared/kanban-board.cjs +78 -0
  44. package/dist/packages/better-stack/src/plugins/kanban/client/components/shared/kanban-board.mjs +76 -0
  45. package/dist/packages/better-stack/src/plugins/kanban/client/components/shared/page-wrapper.cjs +15 -0
  46. package/dist/packages/better-stack/src/plugins/kanban/client/components/shared/page-wrapper.mjs +13 -0
  47. package/dist/packages/better-stack/src/plugins/kanban/client/components/shared/task-card.cjs +68 -0
  48. package/dist/packages/better-stack/src/plugins/kanban/client/components/shared/task-card.mjs +66 -0
  49. package/dist/packages/better-stack/src/plugins/kanban/client/components/shared/user-avatar.cjs +32 -0
  50. package/dist/packages/better-stack/src/plugins/kanban/client/components/shared/user-avatar.mjs +30 -0
  51. package/dist/packages/better-stack/src/plugins/kanban/client/hooks/kanban-hooks.cjs +391 -0
  52. package/dist/packages/better-stack/src/plugins/kanban/client/hooks/kanban-hooks.mjs +381 -0
  53. package/dist/packages/better-stack/src/plugins/kanban/client/plugin.cjs +290 -0
  54. package/dist/packages/better-stack/src/plugins/kanban/client/plugin.mjs +288 -0
  55. package/dist/packages/better-stack/src/plugins/kanban/db.cjs +125 -0
  56. package/dist/packages/better-stack/src/plugins/kanban/db.mjs +123 -0
  57. package/dist/packages/better-stack/src/plugins/kanban/schemas.cjs +117 -0
  58. package/dist/packages/better-stack/src/plugins/kanban/schemas.mjs +102 -0
  59. package/dist/packages/better-stack/src/plugins/kanban/utils.cjs +49 -0
  60. package/dist/packages/better-stack/src/plugins/kanban/utils.mjs +45 -0
  61. package/dist/packages/ui/src/components/avatar.cjs +58 -0
  62. package/dist/packages/ui/src/components/avatar.mjs +54 -0
  63. package/dist/packages/ui/src/components/command.cjs +3 -3
  64. package/dist/packages/ui/src/components/command.mjs +3 -3
  65. package/dist/packages/ui/src/components/form-builder/index.mjs +2 -2
  66. package/dist/packages/ui/src/components/kanban.cjs +835 -0
  67. package/dist/packages/ui/src/components/kanban.mjs +805 -0
  68. package/dist/packages/ui/src/components/popover.cjs +8 -3
  69. package/dist/packages/ui/src/components/popover.mjs +9 -4
  70. package/dist/packages/ui/src/components/search-select.cjs +75 -0
  71. package/dist/packages/ui/src/components/search-select.mjs +73 -0
  72. package/dist/packages/ui/src/lib/compose-refs.cjs +56 -0
  73. package/dist/packages/ui/src/lib/compose-refs.mjs +39 -0
  74. package/dist/plugins/blog/api/index.d.cts +1 -1
  75. package/dist/plugins/blog/api/index.d.mts +1 -1
  76. package/dist/plugins/blog/api/index.d.ts +1 -1
  77. package/dist/plugins/blog/client/hooks/index.d.cts +2 -2
  78. package/dist/plugins/blog/client/hooks/index.d.mts +2 -2
  79. package/dist/plugins/blog/client/hooks/index.d.ts +2 -2
  80. package/dist/plugins/blog/client/index.d.cts +1 -1
  81. package/dist/plugins/blog/client/index.d.mts +1 -1
  82. package/dist/plugins/blog/client/index.d.ts +1 -1
  83. package/dist/plugins/blog/query-keys.d.cts +2 -2
  84. package/dist/plugins/blog/query-keys.d.mts +2 -2
  85. package/dist/plugins/blog/query-keys.d.ts +2 -2
  86. package/dist/plugins/kanban/api/index.cjs +7 -0
  87. package/dist/plugins/kanban/api/index.d.cts +403 -0
  88. package/dist/plugins/kanban/api/index.d.mts +403 -0
  89. package/dist/plugins/kanban/api/index.d.ts +403 -0
  90. package/dist/plugins/kanban/api/index.mjs +1 -0
  91. package/dist/plugins/kanban/client/components/index.cjs +35 -0
  92. package/dist/plugins/kanban/client/components/index.d.cts +102 -0
  93. package/dist/plugins/kanban/client/components/index.d.mts +102 -0
  94. package/dist/plugins/kanban/client/components/index.d.ts +102 -0
  95. package/dist/plugins/kanban/client/components/index.mjs +15 -0
  96. package/dist/plugins/kanban/client/hooks/index.cjs +15 -0
  97. package/dist/plugins/kanban/client/hooks/index.d.cts +143 -0
  98. package/dist/plugins/kanban/client/hooks/index.d.mts +143 -0
  99. package/dist/plugins/kanban/client/hooks/index.d.ts +143 -0
  100. package/dist/plugins/kanban/client/hooks/index.mjs +1 -0
  101. package/dist/plugins/kanban/client/index.cjs +7 -0
  102. package/dist/plugins/kanban/client/index.d.cts +196 -0
  103. package/dist/plugins/kanban/client/index.d.mts +196 -0
  104. package/dist/plugins/kanban/client/index.d.ts +196 -0
  105. package/dist/plugins/kanban/client/index.mjs +1 -0
  106. package/dist/plugins/kanban/client.css +68 -0
  107. package/dist/plugins/kanban/query-keys.cjs +105 -0
  108. package/dist/plugins/kanban/query-keys.d.cts +59 -0
  109. package/dist/plugins/kanban/query-keys.d.mts +59 -0
  110. package/dist/plugins/kanban/query-keys.d.ts +59 -0
  111. package/dist/plugins/kanban/query-keys.mjs +103 -0
  112. package/dist/plugins/kanban/style.css +7 -0
  113. package/dist/plugins/ui-builder/style.css +6 -0
  114. package/dist/shared/stack.DKDMI-QO.d.cts +70 -0
  115. package/dist/shared/stack.DKDMI-QO.d.mts +70 -0
  116. package/dist/shared/stack.DKDMI-QO.d.ts +70 -0
  117. package/dist/shared/stack.FeaWkglm.d.cts +190 -0
  118. package/dist/shared/stack.FeaWkglm.d.mts +190 -0
  119. package/dist/shared/stack.FeaWkglm.d.ts +190 -0
  120. package/package.json +56 -2
  121. package/src/plugins/kanban/api/index.ts +6 -0
  122. package/src/plugins/kanban/api/plugin.ts +1245 -0
  123. package/src/plugins/kanban/client/components/forms/board-form.tsx +108 -0
  124. package/src/plugins/kanban/client/components/forms/column-form.tsx +97 -0
  125. package/src/plugins/kanban/client/components/forms/task-form.tsx +274 -0
  126. package/src/plugins/kanban/client/components/index.tsx +21 -0
  127. package/src/plugins/kanban/client/components/loading/board-skeleton.tsx +49 -0
  128. package/src/plugins/kanban/client/components/loading/boards-list-skeleton.tsx +34 -0
  129. package/src/plugins/kanban/client/components/loading/index.tsx +2 -0
  130. package/src/plugins/kanban/client/components/pages/404-page.tsx +28 -0
  131. package/src/plugins/kanban/client/components/pages/board-page.internal.tsx +575 -0
  132. package/src/plugins/kanban/client/components/pages/board-page.tsx +31 -0
  133. package/src/plugins/kanban/client/components/pages/boards-list-page.internal.tsx +101 -0
  134. package/src/plugins/kanban/client/components/pages/boards-list-page.tsx +26 -0
  135. package/src/plugins/kanban/client/components/pages/new-board-page.internal.tsx +65 -0
  136. package/src/plugins/kanban/client/components/pages/new-board-page.tsx +26 -0
  137. package/src/plugins/kanban/client/components/shared/column-content.tsx +108 -0
  138. package/src/plugins/kanban/client/components/shared/default-error.tsx +32 -0
  139. package/src/plugins/kanban/client/components/shared/empty-state.tsx +37 -0
  140. package/src/plugins/kanban/client/components/shared/kanban-board.tsx +87 -0
  141. package/src/plugins/kanban/client/components/shared/page-wrapper.tsx +20 -0
  142. package/src/plugins/kanban/client/components/shared/task-card.tsx +79 -0
  143. package/src/plugins/kanban/client/components/shared/user-avatar.tsx +63 -0
  144. package/src/plugins/kanban/client/hooks/index.tsx +11 -0
  145. package/src/plugins/kanban/client/hooks/kanban-hooks.tsx +560 -0
  146. package/src/plugins/kanban/client/index.ts +8 -0
  147. package/src/plugins/kanban/client/localization/index.ts +28 -0
  148. package/src/plugins/kanban/client/localization/kanban-common.ts +69 -0
  149. package/src/plugins/kanban/client/localization/kanban-forms.ts +70 -0
  150. package/src/plugins/kanban/client/localization/kanban-list.ts +36 -0
  151. package/src/plugins/kanban/client/overrides.ts +145 -0
  152. package/src/plugins/kanban/client/plugin.tsx +463 -0
  153. package/src/plugins/kanban/client.css +68 -0
  154. package/src/plugins/kanban/db.ts +125 -0
  155. package/src/plugins/kanban/query-keys.ts +154 -0
  156. package/src/plugins/kanban/schemas.ts +143 -0
  157. package/src/plugins/kanban/style.css +7 -0
  158. package/src/plugins/kanban/types.ts +106 -0
  159. package/src/plugins/kanban/utils.ts +107 -0
  160. package/src/plugins/ui-builder/style.css +6 -0
  161. package/dist/shared/{stack.DLhzx1-D.d.cts → stack.CcI4sYJP.d.cts} +1 -1
  162. package/dist/shared/{stack.DLhzx1-D.d.mts → stack.CcI4sYJP.d.mts} +1 -1
  163. package/dist/shared/{stack.DLhzx1-D.d.ts → stack.CcI4sYJP.d.ts} +1 -1
@@ -0,0 +1,846 @@
1
+ 'use strict';
2
+
3
+ const api = require('@btst/stack/plugins/api');
4
+ const db = require('../db.cjs');
5
+ const utils = require('../utils.cjs');
6
+ const schemas = require('../schemas.cjs');
7
+
8
+ const kanbanBackendPlugin = (hooks) => api.defineBackendPlugin({
9
+ name: "kanban",
10
+ dbPlugin: db.kanbanSchema,
11
+ routes: (adapter) => {
12
+ const listBoards = api.createEndpoint(
13
+ "/boards",
14
+ {
15
+ method: "GET",
16
+ query: schemas.BoardListQuerySchema
17
+ },
18
+ async (ctx) => {
19
+ const { query, headers } = ctx;
20
+ const context = { query, headers };
21
+ try {
22
+ if (hooks?.onBeforeListBoards) {
23
+ const canList = await hooks.onBeforeListBoards(query, context);
24
+ if (!canList) {
25
+ throw ctx.error(403, {
26
+ message: "Unauthorized: Cannot list boards"
27
+ });
28
+ }
29
+ }
30
+ const whereConditions = [];
31
+ if (query.slug) {
32
+ whereConditions.push({
33
+ field: "slug",
34
+ value: query.slug,
35
+ operator: "eq"
36
+ });
37
+ }
38
+ if (query.ownerId) {
39
+ whereConditions.push({
40
+ field: "ownerId",
41
+ value: query.ownerId,
42
+ operator: "eq"
43
+ });
44
+ }
45
+ if (query.organizationId) {
46
+ whereConditions.push({
47
+ field: "organizationId",
48
+ value: query.organizationId,
49
+ operator: "eq"
50
+ });
51
+ }
52
+ const boards = await adapter.findMany({
53
+ model: "kanbanBoard",
54
+ limit: query.limit ?? 50,
55
+ offset: query.offset ?? 0,
56
+ where: whereConditions,
57
+ sortBy: {
58
+ field: "createdAt",
59
+ direction: "desc"
60
+ },
61
+ join: {
62
+ kanbanColumn: true
63
+ }
64
+ });
65
+ const columnIds = [];
66
+ for (const board of boards) {
67
+ if (board.column) {
68
+ for (const col of board.column) {
69
+ columnIds.push(col.id);
70
+ }
71
+ }
72
+ }
73
+ const tasksByColumn = /* @__PURE__ */ new Map();
74
+ if (columnIds.length > 0) {
75
+ const taskQueries = columnIds.map(
76
+ (columnId) => adapter.findMany({
77
+ model: "kanbanTask",
78
+ where: [
79
+ {
80
+ field: "columnId",
81
+ value: columnId,
82
+ operator: "eq"
83
+ }
84
+ ],
85
+ sortBy: { field: "order", direction: "asc" }
86
+ })
87
+ );
88
+ const taskResults = await Promise.all(taskQueries);
89
+ for (let i = 0; i < columnIds.length; i++) {
90
+ const columnId = columnIds[i];
91
+ const tasks = taskResults[i];
92
+ if (columnId && tasks) {
93
+ tasksByColumn.set(columnId, tasks);
94
+ }
95
+ }
96
+ }
97
+ const result = boards.map((board) => {
98
+ const columns = (board.column || []).sort((a, b) => a.order - b.order).map((col) => ({
99
+ ...col,
100
+ tasks: tasksByColumn.get(col.id) || []
101
+ }));
102
+ const { column: _, ...boardWithoutJoin } = board;
103
+ return {
104
+ ...boardWithoutJoin,
105
+ columns
106
+ };
107
+ });
108
+ if (hooks?.onBoardsRead) {
109
+ await hooks.onBoardsRead(result, query, context);
110
+ }
111
+ return result;
112
+ } catch (error) {
113
+ if (hooks?.onListBoardsError) {
114
+ await hooks.onListBoardsError(error, context);
115
+ }
116
+ throw error;
117
+ }
118
+ }
119
+ );
120
+ const getBoard = api.createEndpoint(
121
+ "/boards/:id",
122
+ {
123
+ method: "GET"
124
+ },
125
+ async (ctx) => {
126
+ const { params, headers } = ctx;
127
+ const context = { params, headers };
128
+ try {
129
+ if (hooks?.onBeforeReadBoard) {
130
+ const canRead = await hooks.onBeforeReadBoard(params.id, context);
131
+ if (!canRead) {
132
+ throw ctx.error(403, {
133
+ message: "Unauthorized: Cannot read board"
134
+ });
135
+ }
136
+ }
137
+ const board = await adapter.findOne({
138
+ model: "kanbanBoard",
139
+ where: [
140
+ { field: "id", value: params.id, operator: "eq" }
141
+ ],
142
+ join: {
143
+ kanbanColumn: true
144
+ }
145
+ });
146
+ if (!board) {
147
+ throw ctx.error(404, { message: "Board not found" });
148
+ }
149
+ const columnIds = (board.column || []).map((c) => c.id);
150
+ const tasksByColumn = /* @__PURE__ */ new Map();
151
+ if (columnIds.length > 0) {
152
+ const taskQueries = columnIds.map(
153
+ (columnId) => adapter.findMany({
154
+ model: "kanbanTask",
155
+ where: [
156
+ {
157
+ field: "columnId",
158
+ value: columnId,
159
+ operator: "eq"
160
+ }
161
+ ],
162
+ sortBy: { field: "order", direction: "asc" }
163
+ })
164
+ );
165
+ const taskResults = await Promise.all(taskQueries);
166
+ for (let i = 0; i < columnIds.length; i++) {
167
+ const columnId = columnIds[i];
168
+ const tasks = taskResults[i];
169
+ if (columnId && tasks) {
170
+ tasksByColumn.set(columnId, tasks);
171
+ }
172
+ }
173
+ }
174
+ const columns = (board.column || []).sort((a, b) => a.order - b.order).map((col) => ({
175
+ ...col,
176
+ tasks: tasksByColumn.get(col.id) || []
177
+ }));
178
+ const { column: _, ...boardWithoutJoin } = board;
179
+ const result = {
180
+ ...boardWithoutJoin,
181
+ columns
182
+ };
183
+ if (hooks?.onBoardRead) {
184
+ await hooks.onBoardRead(result, context);
185
+ }
186
+ return result;
187
+ } catch (error) {
188
+ if (hooks?.onReadBoardError) {
189
+ await hooks.onReadBoardError(error, context);
190
+ }
191
+ throw error;
192
+ }
193
+ }
194
+ );
195
+ const createBoard = api.createEndpoint(
196
+ "/boards",
197
+ {
198
+ method: "POST",
199
+ body: schemas.createBoardSchema
200
+ },
201
+ async (ctx) => {
202
+ const context = {
203
+ body: ctx.body,
204
+ headers: ctx.headers
205
+ };
206
+ try {
207
+ if (hooks?.onBeforeCreateBoard) {
208
+ const canCreate = await hooks.onBeforeCreateBoard(
209
+ ctx.body,
210
+ context
211
+ );
212
+ if (!canCreate) {
213
+ throw ctx.error(403, {
214
+ message: "Unauthorized: Cannot create board"
215
+ });
216
+ }
217
+ }
218
+ const { ...boardData } = ctx.body;
219
+ const slug = utils.slugify(boardData.slug || boardData.name);
220
+ if (!slug) {
221
+ throw ctx.error(400, {
222
+ message: "Invalid slug: must contain at least one alphanumeric character"
223
+ });
224
+ }
225
+ let newBoard;
226
+ const createdColumns = [];
227
+ await adapter.transaction(async (tx) => {
228
+ newBoard = await tx.create({
229
+ model: "kanbanBoard",
230
+ data: {
231
+ ...boardData,
232
+ slug,
233
+ createdAt: /* @__PURE__ */ new Date(),
234
+ updatedAt: /* @__PURE__ */ new Date()
235
+ }
236
+ });
237
+ const defaultColumns = [
238
+ { title: "To Do", order: 0, boardId: newBoard.id },
239
+ { title: "In Progress", order: 1, boardId: newBoard.id },
240
+ { title: "Done", order: 2, boardId: newBoard.id }
241
+ ];
242
+ for (const colData of defaultColumns) {
243
+ const col = await tx.create({
244
+ model: "kanbanColumn",
245
+ data: {
246
+ ...colData,
247
+ createdAt: /* @__PURE__ */ new Date(),
248
+ updatedAt: /* @__PURE__ */ new Date()
249
+ }
250
+ });
251
+ createdColumns.push({ ...col, tasks: [] });
252
+ }
253
+ });
254
+ if (!newBoard) {
255
+ throw ctx.error(500, {
256
+ message: "Failed to create board"
257
+ });
258
+ }
259
+ const result = { ...newBoard, columns: createdColumns };
260
+ if (hooks?.onBoardCreated) {
261
+ await hooks.onBoardCreated(result, context);
262
+ }
263
+ return result;
264
+ } catch (error) {
265
+ if (hooks?.onCreateBoardError) {
266
+ await hooks.onCreateBoardError(error, context);
267
+ }
268
+ throw error;
269
+ }
270
+ }
271
+ );
272
+ const updateBoard = api.createEndpoint(
273
+ "/boards/:id",
274
+ {
275
+ method: "PUT",
276
+ body: schemas.updateBoardSchema.omit({ id: true })
277
+ },
278
+ async (ctx) => {
279
+ const context = {
280
+ body: ctx.body,
281
+ params: ctx.params,
282
+ headers: ctx.headers
283
+ };
284
+ try {
285
+ if (hooks?.onBeforeUpdateBoard) {
286
+ const canUpdate = await hooks.onBeforeUpdateBoard(
287
+ ctx.params.id,
288
+ { ...ctx.body, id: ctx.params.id },
289
+ context
290
+ );
291
+ if (!canUpdate) {
292
+ throw ctx.error(403, {
293
+ message: "Unauthorized: Cannot update board"
294
+ });
295
+ }
296
+ }
297
+ const { slug: rawSlug, ...restBoardData } = ctx.body;
298
+ const slugified = rawSlug ? utils.slugify(rawSlug) : void 0;
299
+ if (rawSlug && !slugified) {
300
+ throw ctx.error(400, {
301
+ message: "Invalid slug: must contain at least one alphanumeric character"
302
+ });
303
+ }
304
+ const boardData = {
305
+ ...restBoardData,
306
+ ...slugified ? { slug: slugified } : {}
307
+ };
308
+ const updated = await adapter.update({
309
+ model: "kanbanBoard",
310
+ where: [{ field: "id", value: ctx.params.id }],
311
+ update: {
312
+ ...boardData,
313
+ updatedAt: /* @__PURE__ */ new Date()
314
+ }
315
+ });
316
+ if (!updated) {
317
+ throw ctx.error(404, { message: "Board not found" });
318
+ }
319
+ if (hooks?.onBoardUpdated) {
320
+ await hooks.onBoardUpdated(updated, context);
321
+ }
322
+ return updated;
323
+ } catch (error) {
324
+ if (hooks?.onUpdateBoardError) {
325
+ await hooks.onUpdateBoardError(error, context);
326
+ }
327
+ throw error;
328
+ }
329
+ }
330
+ );
331
+ const deleteBoard = api.createEndpoint(
332
+ "/boards/:id",
333
+ {
334
+ method: "DELETE"
335
+ },
336
+ async (ctx) => {
337
+ const context = {
338
+ params: ctx.params,
339
+ headers: ctx.headers
340
+ };
341
+ try {
342
+ const existingBoard = await adapter.findOne({
343
+ model: "kanbanBoard",
344
+ where: [
345
+ { field: "id", value: ctx.params.id, operator: "eq" }
346
+ ]
347
+ });
348
+ if (!existingBoard) {
349
+ throw ctx.error(404, { message: "Board not found" });
350
+ }
351
+ if (hooks?.onBeforeDeleteBoard) {
352
+ const canDelete = await hooks.onBeforeDeleteBoard(
353
+ ctx.params.id,
354
+ context
355
+ );
356
+ if (!canDelete) {
357
+ throw ctx.error(403, {
358
+ message: "Unauthorized: Cannot delete board"
359
+ });
360
+ }
361
+ }
362
+ await adapter.delete({
363
+ model: "kanbanBoard",
364
+ where: [{ field: "id", value: ctx.params.id }]
365
+ });
366
+ if (hooks?.onBoardDeleted) {
367
+ await hooks.onBoardDeleted(ctx.params.id, context);
368
+ }
369
+ return { success: true };
370
+ } catch (error) {
371
+ if (hooks?.onDeleteBoardError) {
372
+ await hooks.onDeleteBoardError(error, context);
373
+ }
374
+ throw error;
375
+ }
376
+ }
377
+ );
378
+ const createColumn = api.createEndpoint(
379
+ "/columns",
380
+ {
381
+ method: "POST",
382
+ body: schemas.createColumnSchema
383
+ },
384
+ async (ctx) => {
385
+ const context = {
386
+ body: ctx.body,
387
+ headers: ctx.headers
388
+ };
389
+ try {
390
+ if (hooks?.onBeforeCreateColumn) {
391
+ const canCreate = await hooks.onBeforeCreateColumn(
392
+ ctx.body,
393
+ context
394
+ );
395
+ if (!canCreate) {
396
+ throw ctx.error(403, {
397
+ message: "Unauthorized: Cannot create column"
398
+ });
399
+ }
400
+ }
401
+ const existingColumns = await adapter.findMany({
402
+ model: "kanbanColumn",
403
+ where: [
404
+ {
405
+ field: "boardId",
406
+ value: ctx.body.boardId,
407
+ operator: "eq"
408
+ }
409
+ ]
410
+ });
411
+ const nextOrder = existingColumns.length > 0 ? Math.max(...existingColumns.map((c) => c.order)) + 1 : 0;
412
+ const newColumn = await adapter.create({
413
+ model: "kanbanColumn",
414
+ data: {
415
+ ...ctx.body,
416
+ order: ctx.body.order ?? nextOrder,
417
+ createdAt: /* @__PURE__ */ new Date(),
418
+ updatedAt: /* @__PURE__ */ new Date()
419
+ }
420
+ });
421
+ if (hooks?.onColumnCreated) {
422
+ await hooks.onColumnCreated(newColumn, context);
423
+ }
424
+ return newColumn;
425
+ } catch (error) {
426
+ throw error;
427
+ }
428
+ }
429
+ );
430
+ const updateColumn = api.createEndpoint(
431
+ "/columns/:id",
432
+ {
433
+ method: "PUT",
434
+ body: schemas.updateColumnSchema.omit({ id: true })
435
+ },
436
+ async (ctx) => {
437
+ const context = {
438
+ body: ctx.body,
439
+ params: ctx.params,
440
+ headers: ctx.headers
441
+ };
442
+ try {
443
+ if (hooks?.onBeforeUpdateColumn) {
444
+ const canUpdate = await hooks.onBeforeUpdateColumn(
445
+ ctx.params.id,
446
+ { ...ctx.body, id: ctx.params.id },
447
+ context
448
+ );
449
+ if (!canUpdate) {
450
+ throw ctx.error(403, {
451
+ message: "Unauthorized: Cannot update column"
452
+ });
453
+ }
454
+ }
455
+ const updated = await adapter.update({
456
+ model: "kanbanColumn",
457
+ where: [{ field: "id", value: ctx.params.id }],
458
+ update: {
459
+ ...ctx.body,
460
+ updatedAt: /* @__PURE__ */ new Date()
461
+ }
462
+ });
463
+ if (!updated) {
464
+ throw ctx.error(404, { message: "Column not found" });
465
+ }
466
+ if (hooks?.onColumnUpdated) {
467
+ await hooks.onColumnUpdated(updated, context);
468
+ }
469
+ return updated;
470
+ } catch (error) {
471
+ throw error;
472
+ }
473
+ }
474
+ );
475
+ const deleteColumn = api.createEndpoint(
476
+ "/columns/:id",
477
+ {
478
+ method: "DELETE"
479
+ },
480
+ async (ctx) => {
481
+ const context = {
482
+ params: ctx.params,
483
+ headers: ctx.headers
484
+ };
485
+ try {
486
+ const existingColumn = await adapter.findOne({
487
+ model: "kanbanColumn",
488
+ where: [
489
+ { field: "id", value: ctx.params.id, operator: "eq" }
490
+ ]
491
+ });
492
+ if (!existingColumn) {
493
+ throw ctx.error(404, { message: "Column not found" });
494
+ }
495
+ if (hooks?.onBeforeDeleteColumn) {
496
+ const canDelete = await hooks.onBeforeDeleteColumn(
497
+ ctx.params.id,
498
+ context
499
+ );
500
+ if (!canDelete) {
501
+ throw ctx.error(403, {
502
+ message: "Unauthorized: Cannot delete column"
503
+ });
504
+ }
505
+ }
506
+ await adapter.delete({
507
+ model: "kanbanColumn",
508
+ where: [{ field: "id", value: ctx.params.id }]
509
+ });
510
+ if (hooks?.onColumnDeleted) {
511
+ await hooks.onColumnDeleted(ctx.params.id, context);
512
+ }
513
+ return { success: true };
514
+ } catch (error) {
515
+ throw error;
516
+ }
517
+ }
518
+ );
519
+ const reorderColumns = api.createEndpoint(
520
+ "/columns/reorder",
521
+ {
522
+ method: "POST",
523
+ body: schemas.reorderColumnsSchema
524
+ },
525
+ async (ctx) => {
526
+ const { boardId, columnIds } = ctx.body;
527
+ const context = {
528
+ body: ctx.body,
529
+ headers: ctx.headers
530
+ };
531
+ if (hooks?.onBeforeUpdateColumn) {
532
+ for (let i = 0; i < columnIds.length; i++) {
533
+ const columnId = columnIds[i];
534
+ if (!columnId) continue;
535
+ const canUpdate = await hooks.onBeforeUpdateColumn(
536
+ columnId,
537
+ { id: columnId, order: i },
538
+ context
539
+ );
540
+ if (!canUpdate) {
541
+ throw ctx.error(403, {
542
+ message: "Unauthorized: Cannot reorder columns"
543
+ });
544
+ }
545
+ }
546
+ }
547
+ const updatedColumns = [];
548
+ await adapter.transaction(async (tx) => {
549
+ for (let i = 0; i < columnIds.length; i++) {
550
+ const columnId = columnIds[i];
551
+ if (!columnId) continue;
552
+ const updated = await tx.update({
553
+ model: "kanbanColumn",
554
+ where: [
555
+ { field: "id", value: columnId },
556
+ { field: "boardId", value: boardId, operator: "eq" }
557
+ ],
558
+ update: { order: i, updatedAt: /* @__PURE__ */ new Date() }
559
+ });
560
+ if (updated) {
561
+ updatedColumns.push(updated);
562
+ }
563
+ }
564
+ });
565
+ if (hooks?.onColumnUpdated) {
566
+ for (const column of updatedColumns) {
567
+ await hooks.onColumnUpdated(column, context);
568
+ }
569
+ }
570
+ return { success: true };
571
+ }
572
+ );
573
+ const createTask = api.createEndpoint(
574
+ "/tasks",
575
+ {
576
+ method: "POST",
577
+ body: schemas.createTaskSchema
578
+ },
579
+ async (ctx) => {
580
+ const context = {
581
+ body: ctx.body,
582
+ headers: ctx.headers
583
+ };
584
+ try {
585
+ if (hooks?.onBeforeCreateTask) {
586
+ const canCreate = await hooks.onBeforeCreateTask(
587
+ ctx.body,
588
+ context
589
+ );
590
+ if (!canCreate) {
591
+ throw ctx.error(403, {
592
+ message: "Unauthorized: Cannot create task"
593
+ });
594
+ }
595
+ }
596
+ const existingTasks = await adapter.findMany({
597
+ model: "kanbanTask",
598
+ where: [
599
+ {
600
+ field: "columnId",
601
+ value: ctx.body.columnId,
602
+ operator: "eq"
603
+ }
604
+ ]
605
+ });
606
+ const nextOrder = existingTasks.length > 0 ? Math.max(...existingTasks.map((t) => t.order)) + 1 : 0;
607
+ const taskData = {
608
+ title: ctx.body.title,
609
+ columnId: ctx.body.columnId,
610
+ description: ctx.body.description,
611
+ priority: ctx.body.priority || "MEDIUM",
612
+ order: ctx.body.order ?? nextOrder,
613
+ assigneeId: ctx.body.assigneeId ?? void 0,
614
+ isArchived: ctx.body.isArchived ?? false,
615
+ createdAt: /* @__PURE__ */ new Date(),
616
+ updatedAt: /* @__PURE__ */ new Date()
617
+ };
618
+ const newTask = await adapter.create({
619
+ model: "kanbanTask",
620
+ data: taskData
621
+ });
622
+ if (hooks?.onTaskCreated) {
623
+ await hooks.onTaskCreated(newTask, context);
624
+ }
625
+ return newTask;
626
+ } catch (error) {
627
+ throw error;
628
+ }
629
+ }
630
+ );
631
+ const updateTask = api.createEndpoint(
632
+ "/tasks/:id",
633
+ {
634
+ method: "PUT",
635
+ body: schemas.updateTaskSchema.omit({ id: true })
636
+ },
637
+ async (ctx) => {
638
+ const context = {
639
+ body: ctx.body,
640
+ params: ctx.params,
641
+ headers: ctx.headers
642
+ };
643
+ try {
644
+ if (hooks?.onBeforeUpdateTask) {
645
+ const canUpdate = await hooks.onBeforeUpdateTask(
646
+ ctx.params.id,
647
+ { ...ctx.body, id: ctx.params.id },
648
+ context
649
+ );
650
+ if (!canUpdate) {
651
+ throw ctx.error(403, {
652
+ message: "Unauthorized: Cannot update task"
653
+ });
654
+ }
655
+ }
656
+ const updated = await adapter.update({
657
+ model: "kanbanTask",
658
+ where: [{ field: "id", value: ctx.params.id }],
659
+ update: {
660
+ ...ctx.body,
661
+ updatedAt: /* @__PURE__ */ new Date()
662
+ }
663
+ });
664
+ if (!updated) {
665
+ throw ctx.error(404, { message: "Task not found" });
666
+ }
667
+ if (hooks?.onTaskUpdated) {
668
+ await hooks.onTaskUpdated(updated, context);
669
+ }
670
+ return updated;
671
+ } catch (error) {
672
+ throw error;
673
+ }
674
+ }
675
+ );
676
+ const deleteTask = api.createEndpoint(
677
+ "/tasks/:id",
678
+ {
679
+ method: "DELETE"
680
+ },
681
+ async (ctx) => {
682
+ const context = {
683
+ params: ctx.params,
684
+ headers: ctx.headers
685
+ };
686
+ try {
687
+ const existingTask = await adapter.findOne({
688
+ model: "kanbanTask",
689
+ where: [
690
+ { field: "id", value: ctx.params.id, operator: "eq" }
691
+ ]
692
+ });
693
+ if (!existingTask) {
694
+ throw ctx.error(404, { message: "Task not found" });
695
+ }
696
+ if (hooks?.onBeforeDeleteTask) {
697
+ const canDelete = await hooks.onBeforeDeleteTask(
698
+ ctx.params.id,
699
+ context
700
+ );
701
+ if (!canDelete) {
702
+ throw ctx.error(403, {
703
+ message: "Unauthorized: Cannot delete task"
704
+ });
705
+ }
706
+ }
707
+ await adapter.delete({
708
+ model: "kanbanTask",
709
+ where: [{ field: "id", value: ctx.params.id }]
710
+ });
711
+ if (hooks?.onTaskDeleted) {
712
+ await hooks.onTaskDeleted(ctx.params.id, context);
713
+ }
714
+ return { success: true };
715
+ } catch (error) {
716
+ throw error;
717
+ }
718
+ }
719
+ );
720
+ const moveTask = api.createEndpoint(
721
+ "/tasks/move",
722
+ {
723
+ method: "POST",
724
+ body: schemas.moveTaskSchema
725
+ },
726
+ async (ctx) => {
727
+ const { taskId, targetColumnId, targetOrder } = ctx.body;
728
+ const context = {
729
+ body: ctx.body,
730
+ headers: ctx.headers
731
+ };
732
+ const task = await adapter.findOne({
733
+ model: "kanbanTask",
734
+ where: [{ field: "id", value: taskId, operator: "eq" }]
735
+ });
736
+ if (!task) {
737
+ throw ctx.error(404, { message: "Task not found" });
738
+ }
739
+ if (hooks?.onBeforeUpdateTask) {
740
+ const canUpdate = await hooks.onBeforeUpdateTask(
741
+ taskId,
742
+ { id: taskId, columnId: targetColumnId, order: targetOrder },
743
+ context
744
+ );
745
+ if (!canUpdate) {
746
+ throw ctx.error(403, {
747
+ message: "Unauthorized: Cannot move task"
748
+ });
749
+ }
750
+ }
751
+ const updated = await adapter.update({
752
+ model: "kanbanTask",
753
+ where: [{ field: "id", value: taskId }],
754
+ update: {
755
+ columnId: targetColumnId,
756
+ order: targetOrder,
757
+ updatedAt: /* @__PURE__ */ new Date()
758
+ }
759
+ });
760
+ if (!updated) {
761
+ throw ctx.error(404, { message: "Task not found" });
762
+ }
763
+ if (hooks?.onTaskUpdated) {
764
+ await hooks.onTaskUpdated(updated, context);
765
+ }
766
+ return updated;
767
+ }
768
+ );
769
+ const reorderTasks = api.createEndpoint(
770
+ "/tasks/reorder",
771
+ {
772
+ method: "POST",
773
+ body: schemas.reorderTasksSchema
774
+ },
775
+ async (ctx) => {
776
+ const { columnId, taskIds } = ctx.body;
777
+ const context = {
778
+ body: ctx.body,
779
+ headers: ctx.headers
780
+ };
781
+ if (hooks?.onBeforeUpdateTask) {
782
+ for (let i = 0; i < taskIds.length; i++) {
783
+ const taskId = taskIds[i];
784
+ if (!taskId) continue;
785
+ const canUpdate = await hooks.onBeforeUpdateTask(
786
+ taskId,
787
+ { id: taskId, order: i },
788
+ context
789
+ );
790
+ if (!canUpdate) {
791
+ throw ctx.error(403, {
792
+ message: "Unauthorized: Cannot reorder tasks"
793
+ });
794
+ }
795
+ }
796
+ }
797
+ const updatedTasks = [];
798
+ await adapter.transaction(async (tx) => {
799
+ for (let i = 0; i < taskIds.length; i++) {
800
+ const taskId = taskIds[i];
801
+ if (!taskId) continue;
802
+ const updated = await tx.update({
803
+ model: "kanbanTask",
804
+ where: [
805
+ { field: "id", value: taskId },
806
+ {
807
+ field: "columnId",
808
+ value: columnId,
809
+ operator: "eq"
810
+ }
811
+ ],
812
+ update: { order: i, updatedAt: /* @__PURE__ */ new Date() }
813
+ });
814
+ if (updated) {
815
+ updatedTasks.push(updated);
816
+ }
817
+ }
818
+ });
819
+ if (hooks?.onTaskUpdated) {
820
+ for (const task of updatedTasks) {
821
+ await hooks.onTaskUpdated(task, context);
822
+ }
823
+ }
824
+ return { success: true };
825
+ }
826
+ );
827
+ return {
828
+ listBoards,
829
+ getBoard,
830
+ createBoard,
831
+ updateBoard,
832
+ deleteBoard,
833
+ createColumn,
834
+ updateColumn,
835
+ deleteColumn,
836
+ reorderColumns,
837
+ createTask,
838
+ updateTask,
839
+ deleteTask,
840
+ moveTask,
841
+ reorderTasks
842
+ };
843
+ }
844
+ });
845
+
846
+ exports.kanbanBackendPlugin = kanbanBackendPlugin;