@stacksjs/defaults 0.70.354 → 0.70.356

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 (214) hide show
  1. package/ai/skills/stacks-browse/SKILL.md +17 -2
  2. package/ai/skills/stacks-browse/scripts/browse.ts +207 -6
  3. package/ai/skills/stacks-email/SKILL.md +73 -16
  4. package/app/Actions/ContactAction.ts +2 -2
  5. package/app/Actions/Dashboard/Analytics/EventAnalyticsAction.ts +2 -3
  6. package/app/Actions/Dashboard/Analytics/MarketingAnalyticsAction.ts +2 -3
  7. package/app/Actions/Dashboard/Analytics/SalesAnalyticsAction.ts +2 -3
  8. package/app/Actions/Dashboard/Analytics/WebAnalyticsAction.ts +9 -3
  9. package/app/Actions/Dashboard/Auth/MeAction.ts +12 -14
  10. package/app/Actions/Dashboard/Buddy/BuddyChatAction.ts +2 -3
  11. package/app/Actions/Dashboard/Ci/RepoRunJobsAction.ts +10 -15
  12. package/app/Actions/Dashboard/Ci/RepoRunsAction.ts +12 -14
  13. package/app/Actions/Dashboard/Ci/RunnerHistoryAction.ts +11 -10
  14. package/app/Actions/Dashboard/Ci/StatusAction.ts +2 -11
  15. package/app/Actions/Dashboard/Cloud/CloudIndexAction.ts +8 -1
  16. package/app/Actions/Dashboard/Cloud/ServerlessIndexAction.ts +15 -8
  17. package/app/Actions/Dashboard/Commerce/CommerceCategoriesAction.ts +2 -4
  18. package/app/Actions/Dashboard/Commerce/CommerceCouponsAction.ts +2 -4
  19. package/app/Actions/Dashboard/Commerce/CommerceCustomersAction.ts +2 -4
  20. package/app/Actions/Dashboard/Commerce/CommerceDashboardAction.ts +2 -4
  21. package/app/Actions/Dashboard/Commerce/CommerceDeliveryAction.ts +2 -4
  22. package/app/Actions/Dashboard/Commerce/CommerceGiftCardsAction.ts +2 -4
  23. package/app/Actions/Dashboard/Commerce/CommerceOrdersAction.ts +2 -4
  24. package/app/Actions/Dashboard/Commerce/CommercePaymentsAction.ts +2 -4
  25. package/app/Actions/Dashboard/Commerce/CommercePosAction.ts +2 -4
  26. package/app/Actions/Dashboard/Commerce/CommercePosCheckoutAction.ts +76 -61
  27. package/app/Actions/Dashboard/Commerce/CommercePrintDevicesAction.ts +2 -4
  28. package/app/Actions/Dashboard/Commerce/CommercePrintLogsAction.ts +4 -7
  29. package/app/Actions/Dashboard/Commerce/CommerceProductDetailAction.ts +4 -4
  30. package/app/Actions/Dashboard/Commerce/CommerceProductsAction.ts +2 -4
  31. package/app/Actions/Dashboard/Commerce/CommerceTaxesAction.ts +2 -4
  32. package/app/Actions/Dashboard/Commerce/DeliveryRouteIndexAction.ts +2 -4
  33. package/app/Actions/Dashboard/Commerce/DigitalDeliveryIndexAction.ts +2 -4
  34. package/app/Actions/Dashboard/Commerce/DriverIndexAction.ts +2 -4
  35. package/app/Actions/Dashboard/Commerce/LicenseKeyIndexAction.ts +2 -4
  36. package/app/Actions/Dashboard/Commerce/LicenseKeyOptionsAction.ts +2 -4
  37. package/app/Actions/Dashboard/Commerce/ManufacturerIndexAction.ts +2 -4
  38. package/app/Actions/Dashboard/Commerce/PaymentRefundAction.ts +1 -0
  39. package/app/Actions/Dashboard/Commerce/ProductUnitDefaultAction.ts +1 -0
  40. package/app/Actions/Dashboard/Commerce/ProductUnitIndexAction.ts +2 -4
  41. package/app/Actions/Dashboard/Commerce/ProductVariantIndexAction.ts +2 -4
  42. package/app/Actions/Dashboard/Commerce/ProductWaitlistIndexAction.ts +2 -4
  43. package/app/Actions/Dashboard/Commerce/RestaurantWaitlistIndexAction.ts +2 -4
  44. package/app/Actions/Dashboard/Commerce/ReviewIndexAction.ts +2 -4
  45. package/app/Actions/Dashboard/Commerce/ShippingMethodIndexAction.ts +2 -4
  46. package/app/Actions/Dashboard/Commerce/ShippingRateIndexAction.ts +2 -4
  47. package/app/Actions/Dashboard/Commerce/ShippingZoneIndexAction.ts +2 -4
  48. package/app/Actions/Dashboard/Commerce/TaxRateDefaultAction.ts +1 -0
  49. package/app/Actions/Dashboard/DashboardActivityAction.ts +2 -5
  50. package/app/Actions/Dashboard/Data/ActivityIndexAction.ts +2 -5
  51. package/app/Actions/Dashboard/Data/SubscriberIndexAction.ts +2 -5
  52. package/app/Actions/Dashboard/Data/TeamIndexAction.ts +2 -5
  53. package/app/Actions/Dashboard/Data/UserIndexAction.ts +2 -5
  54. package/app/Actions/Dashboard/Deployments/CreateDeployment.ts +26 -21
  55. package/app/Actions/Dashboard/Deployments/GetAverageDeploymentTime.ts +8 -2
  56. package/app/Actions/Dashboard/Deployments/GetDeployScript.ts +3 -6
  57. package/app/Actions/Dashboard/Deployments/GetDeployment.ts +5 -5
  58. package/app/Actions/Dashboard/Deployments/GetDeploymentCount.ts +7 -1
  59. package/app/Actions/Dashboard/Deployments/GetDeploymentLiveTerminalOutput.ts +10 -9
  60. package/app/Actions/Dashboard/Deployments/GetDeployments.ts +2 -4
  61. package/app/Actions/Dashboard/Deployments/GetRecentDeployments.ts +8 -2
  62. package/app/Actions/Dashboard/Deployments/UpdateDeployScript.ts +21 -7
  63. package/app/Actions/Dashboard/Email/CapturedMailIndexAction.ts +2 -4
  64. package/app/Actions/Dashboard/Email/CapturedMailShowAction.ts +3 -4
  65. package/app/Actions/Dashboard/Email/InboxActivityAction.ts +28 -16
  66. package/app/Actions/Dashboard/Email/InboxAttachmentDownloadAction.ts +45 -0
  67. package/app/Actions/Dashboard/Email/InboxDeleteAction.ts +11 -6
  68. package/app/Actions/Dashboard/Email/InboxIndexAction.ts +5 -7
  69. package/app/Actions/Dashboard/Email/InboxMarkReadAction.ts +11 -6
  70. package/app/Actions/Dashboard/Email/InboxMarkUnreadAction.ts +11 -6
  71. package/app/Actions/Dashboard/Email/InboxPreferenceShowAction.ts +10 -4
  72. package/app/Actions/Dashboard/Email/InboxPreferenceUpdateAction.ts +8 -9
  73. package/app/Actions/Dashboard/Email/InboxShowAction.ts +7 -7
  74. package/app/Actions/Dashboard/Email/InboxStatsAction.ts +5 -7
  75. package/app/Actions/Dashboard/Email/inbox-request.ts +33 -0
  76. package/app/Actions/Dashboard/Infrastructure/InsightsAction.ts +15 -14
  77. package/app/Actions/Dashboard/Infrastructure/LogIndexAction.ts +4 -11
  78. package/app/Actions/Dashboard/Infrastructure/ServerShowAction.ts +3 -6
  79. package/app/Actions/Dashboard/Jobs/JobIndexAction.ts +9 -18
  80. package/app/Actions/Dashboard/Jobs/JobRetryAction.ts +4 -3
  81. package/app/Actions/Dashboard/Jobs/JobShowAction.ts +16 -11
  82. package/app/Actions/Dashboard/Jobs/JobStatsAction.ts +2 -4
  83. package/app/Actions/Dashboard/Kanban/BoardDestroyAction.ts +7 -8
  84. package/app/Actions/Dashboard/Kanban/BoardShowAction.ts +11 -19
  85. package/app/Actions/Dashboard/Kanban/BoardStoreAction.ts +6 -6
  86. package/app/Actions/Dashboard/Kanban/BoardUpdateAction.ts +7 -8
  87. package/app/Actions/Dashboard/Kanban/BoardsIndexAction.ts +3 -5
  88. package/app/Actions/Dashboard/Kanban/BoardsReorderAction.ts +6 -6
  89. package/app/Actions/Dashboard/Kanban/CardAssigneesSyncAction.ts +13 -12
  90. package/app/Actions/Dashboard/Kanban/CardCommentDestroyAction.ts +12 -15
  91. package/app/Actions/Dashboard/Kanban/CardCommentStoreAction.ts +21 -46
  92. package/app/Actions/Dashboard/Kanban/CardCommentUpdateAction.ts +43 -0
  93. package/app/Actions/Dashboard/Kanban/CardDestroyAction.ts +7 -8
  94. package/app/Actions/Dashboard/Kanban/CardLabelsSyncAction.ts +8 -9
  95. package/app/Actions/Dashboard/Kanban/CardShowAction.ts +6 -7
  96. package/app/Actions/Dashboard/Kanban/CardStoreAction.ts +9 -9
  97. package/app/Actions/Dashboard/Kanban/CardUpdateAction.ts +7 -8
  98. package/app/Actions/Dashboard/Kanban/CardsReorderAction.ts +6 -6
  99. package/app/Actions/Dashboard/Kanban/ColumnDestroyAction.ts +7 -8
  100. package/app/Actions/Dashboard/Kanban/ColumnStoreAction.ts +6 -6
  101. package/app/Actions/Dashboard/Kanban/ColumnUpdateAction.ts +7 -8
  102. package/app/Actions/Dashboard/Kanban/ColumnsReorderAction.ts +6 -6
  103. package/app/Actions/Dashboard/Kanban/LabelDestroyAction.ts +6 -7
  104. package/app/Actions/Dashboard/Kanban/LabelStoreAction.ts +6 -6
  105. package/app/Actions/Dashboard/Kanban/LabelUpdateAction.ts +7 -8
  106. package/app/Actions/Dashboard/Kanban/UsersListAction.ts +3 -4
  107. package/app/Actions/Dashboard/Kanban/kanban-comment.ts +14 -0
  108. package/app/Actions/Dashboard/Kanban/kanban-model.test.ts +36 -0
  109. package/app/Actions/Dashboard/Kanban/kanban-model.ts +14 -7
  110. package/app/Actions/Dashboard/Kanban/kanban-response.ts +5 -0
  111. package/app/Actions/Dashboard/Library/CreateComponent.ts +15 -6
  112. package/app/Actions/Dashboard/Library/CreateFunction.ts +15 -6
  113. package/app/Actions/Dashboard/Library/DependencyIndexAction.ts +88 -77
  114. package/app/Actions/Dashboard/Library/GetAverageReleaseTime.ts +21 -15
  115. package/app/Actions/Dashboard/Library/GetComponents.ts +14 -7
  116. package/app/Actions/Dashboard/Library/GetDownloadCount.ts +14 -5
  117. package/app/Actions/Dashboard/Library/GetFunctions.ts +12 -5
  118. package/app/Actions/Dashboard/Library/GetReleaseCount.ts +14 -5
  119. package/app/Actions/Dashboard/Library/GetReleases.ts +17 -10
  120. package/app/Actions/Dashboard/Library/PackageIndexAction.ts +14 -7
  121. package/app/Actions/Dashboard/Models/ModelDestroyAction.ts +11 -8
  122. package/app/Actions/Dashboard/Models/ModelShowAction.ts +97 -192
  123. package/app/Actions/Dashboard/Models/ModelStoreAction.ts +14 -12
  124. package/app/Actions/Dashboard/Models/ModelUpdateAction.ts +18 -16
  125. package/app/Actions/Dashboard/Models/ModelsIndexAction.ts +50 -19
  126. package/app/Actions/Dashboard/Models/model-api.ts +27 -0
  127. package/app/Actions/Dashboard/Models/model-write.ts +22 -15
  128. package/app/Actions/Dashboard/Notifications/NotificationDeliveryHistoryAction.ts +49 -40
  129. package/app/Actions/Dashboard/Notifications/NotificationDeliveryIndexAction.ts +20 -10
  130. package/app/Actions/Dashboard/Notifications/NotificationDeliveryOverviewAction.ts +96 -84
  131. package/app/Actions/Dashboard/Notifications/NotificationDeliveryRetryAction.ts +34 -21
  132. package/app/Actions/Dashboard/Queries/QueryDashboardAction.ts +2 -4
  133. package/app/Actions/Dashboard/Queries/QueryShowAction.ts +3 -3
  134. package/app/Actions/Dashboard/Queue/QueueStatsAction.ts +2 -4
  135. package/app/Actions/Dashboard/Queue/QueueWorkersAction.ts +2 -4
  136. package/app/Actions/Dashboard/Rbac/PermissionDestroyAction.ts +6 -6
  137. package/app/Actions/Dashboard/Rbac/PermissionStoreAction.ts +5 -4
  138. package/app/Actions/Dashboard/Rbac/PermissionsIndexAction.ts +2 -3
  139. package/app/Actions/Dashboard/Rbac/RoleDestroyAction.ts +6 -6
  140. package/app/Actions/Dashboard/Rbac/RolePermissionsShowAction.ts +6 -6
  141. package/app/Actions/Dashboard/Rbac/RolePermissionsSyncAction.ts +6 -9
  142. package/app/Actions/Dashboard/Rbac/RoleStoreAction.ts +5 -4
  143. package/app/Actions/Dashboard/Rbac/RolesIndexAction.ts +4 -6
  144. package/app/Actions/Dashboard/Rbac/UserRolesShowAction.ts +6 -7
  145. package/app/Actions/Dashboard/Rbac/UserRolesSyncAction.ts +6 -13
  146. package/app/Actions/Dashboard/Rbac/UsersListAction.ts +2 -3
  147. package/app/Actions/Dashboard/Rbac/rbac-response.ts +10 -0
  148. package/app/Actions/Dashboard/Realtime/RealtimeStatsAction.ts +2 -4
  149. package/app/Actions/Dashboard/Search/GlobalSearchAction.ts +69 -55
  150. package/app/Actions/Dashboard/Teams/TeamInvitationDestroyAction.ts +34 -16
  151. package/app/Actions/Dashboard/Teams/TeamInvitationResendAction.ts +52 -29
  152. package/app/Actions/Dashboard/Teams/TeamInviteAction.ts +104 -73
  153. package/app/Actions/Dashboard/Teams/TeamMemberDestroyAction.ts +36 -17
  154. package/app/Actions/Dashboard/Teams/TeamMemberUpdateAction.ts +41 -28
  155. package/app/Actions/Dashboard/Teams/TeamPeopleIndexAction.ts +71 -58
  156. package/app/Actions/Dashboard/Teams/team-invitation-delivery.ts +29 -7
  157. package/app/Actions/Dashboard/Teams/team-member-count.ts +4 -2
  158. package/app/Actions/Dashboard/Teams/team-records.test.ts +2 -0
  159. package/app/Actions/Dashboard/Teams/team-records.ts +2 -0
  160. package/app/Actions/Dashboard/dashboard-request.ts +13 -0
  161. package/app/Actions/Dashboard/dashboard-response.ts +16 -0
  162. package/app/Actions/SendWelcomeEmail.ts +1 -1
  163. package/app/Actions/Teams/AcceptInvitationAction.ts +74 -51
  164. package/app/Actions/Teams/ShowInvitationAction.ts +21 -14
  165. package/app/Actions/Teams/team-response.ts +23 -0
  166. package/app/Mail/OrderConfirmation.ts +1 -1
  167. package/app/Mail/SubscriptionConfirmation.ts +1 -1
  168. package/app/Mail/TeamInvitation.ts +1 -1
  169. package/app/Models/CardComment.ts +3 -4
  170. package/app/Models/EmailIdempotency.ts +64 -0
  171. package/app/Models/EmailSuppression.ts +52 -0
  172. package/app/Models/EmailWebhookEvent.ts +51 -0
  173. package/app/Models/TeamInvitation.ts +13 -0
  174. package/functions/dashboard-api.ts +51 -10
  175. package/functions/data.ts +0 -1
  176. package/functions/inbox.test.ts +2 -0
  177. package/functions/inbox.ts +27 -2
  178. package/functions/jobs.ts +0 -1
  179. package/functions/public-application-url.test.ts +13 -0
  180. package/functions/public-application-url.ts +27 -0
  181. package/ide/vscode/package.json +1 -1
  182. package/package.json +1 -1
  183. package/resources/components/Dashboard/Auth/AccessTokens.stx +15 -0
  184. package/resources/components/Dashboard/Ci/CiDashboard.stx +24 -6
  185. package/resources/components/Dashboard/Commerce/CommerceTaxDialog.stx +1 -1
  186. package/resources/components/Dashboard/Commerce/ProductWaitlistDashboard.stx +8 -7
  187. package/resources/components/Dashboard/Commerce/RestaurantWaitlistDashboard.stx +8 -7
  188. package/resources/components/Dashboard/Content/BlogDashboard.stx +14 -2
  189. package/resources/components/Dashboard/Content/PostsDashboard.stx +7 -1
  190. package/resources/components/Dashboard/Data/DataRecords.stx +0 -4
  191. package/resources/components/Dashboard/Deployments/DeploymentDetail.stx +16 -2
  192. package/resources/components/Dashboard/Email/EmailNavigation.stx +5 -5
  193. package/resources/components/Dashboard/Email/EmailSidebar.stx +1 -1
  194. package/resources/components/Dashboard/Email/InboxAttachmentList.stx +53 -0
  195. package/resources/components/Dashboard/Email/InboxDashboard.stx +5 -4
  196. package/resources/components/Dashboard/Email/InboxMessageDetail.stx +6 -4
  197. package/resources/components/Dashboard/Jobs/JobDetail.stx +3 -2
  198. package/resources/components/Dashboard/Kanban/KanbanBoardDashboard.stx +63 -6
  199. package/resources/components/Dashboard/Kanban/KanbanBoardsDashboard.stx +3 -6
  200. package/resources/components/Dashboard/Management/PermissionsDashboard.stx +4 -4
  201. package/resources/components/Dashboard/Models/ModelRecordsDashboard.stx +7 -13
  202. package/resources/components/Dashboard/Models/ModelsOverview.stx +1 -1
  203. package/resources/components/Dashboard/NotFoundDashboard.stx +2 -5
  204. package/resources/components/Dashboard/Queries/QueryDashboard.stx +6 -2
  205. package/resources/components/Dashboard/Settings/AppearanceSettingsDashboard.stx +4 -4
  206. package/resources/components/Dashboard/UI/EmptyState.stx +2 -2
  207. package/resources/components/Dashboard/UI/PageLayout.stx +3 -3
  208. package/resources/functions/dashboard/sidebar.ts +4 -4
  209. package/routes/dashboard-api.ts +8 -20
  210. package/views/dashboard/.discovered-models.json +54 -0
  211. package/views/dashboard/layouts/default.stx +19 -16
  212. package/views/dashboard/stores/auth.ts +15 -14
  213. package/views/dashboard/stores/ci.ts +13 -7
  214. package/views/dashboard/stores/kanban.ts +48 -11
@@ -1,7 +1,8 @@
1
+ import type { RequestInstance } from '@stacksjs/types'
1
2
  import { Action } from '@stacksjs/actions'
2
3
  import { Board } from '@stacksjs/orm'
3
4
  import { modelBoolean, modelNullableString, modelNumber, modelString, refreshModel } from './kanban-model'
4
- import { kanbanError } from './kanban-response'
5
+ import { kanbanActionError, kanbanError } from './kanban-response'
5
6
 
6
7
  interface BoardInput {
7
8
  name?: unknown
@@ -12,7 +13,7 @@ interface BoardInput {
12
13
  }
13
14
 
14
15
  /**
15
- * `PATCH /api/dashboard/kanban/boards/:id` (stacksjs/stacks#1846 Phase 2).
16
+ * `PATCH /api/dashboard/kanban/boards/:id`.
16
17
  *
17
18
  * Partial update — only the fields present in the body are written.
18
19
  * Omitting `archived` does NOT change the archive flag; passing
@@ -30,14 +31,13 @@ export default new Action({
30
31
  description: 'Partial update of a kanban board.',
31
32
  method: 'PATCH',
32
33
  apiResponse: true,
33
- async handle(request) {
34
- const rawId = (request as any)?.params?.id ?? (request as any)?.param?.('id') ?? null
35
- const id = Number(rawId)
34
+ async handle(request: RequestInstance<BoardInput>) {
35
+ const id = Number(request.getParam('id'))
36
36
  if (!Number.isFinite(id) || id <= 0) {
37
37
  return kanbanError('Invalid board id', 400)
38
38
  }
39
39
 
40
- const body = (request as any).jsonBody as BoardInput | undefined ?? {}
40
+ const body = request.all()
41
41
  const set: Record<string, unknown> = {}
42
42
 
43
43
  if (typeof body.name === 'string') {
@@ -83,8 +83,7 @@ export default new Action({
83
83
  }
84
84
  }
85
85
  catch (err) {
86
- console.error('[dashboard/kanban] BoardUpdateAction failed:', err)
87
- return kanbanError(err instanceof Error ? err.message : 'unknown error', 500)
86
+ return kanbanActionError(err, 'BoardUpdateAction')
88
87
  }
89
88
  },
90
89
  })
@@ -1,7 +1,7 @@
1
1
  import { Action } from '@stacksjs/actions'
2
2
  import { db } from '@stacksjs/database'
3
3
  import { modelBoolean } from './kanban-model'
4
- import { kanbanError } from './kanban-response'
4
+ import { kanbanActionError, kanbanError } from './kanban-response'
5
5
 
6
6
  interface BoardRow {
7
7
  id: number
@@ -30,8 +30,7 @@ interface BoardRow {
30
30
  * group (boards are a dev-mode surface per #1843). The action body
31
31
  * doesn't re-check the role; the middleware did.
32
32
  *
33
- * Phase 1 read-only. Phase 2 lands store / update / destroy + the
34
- * reorder endpoint that moves a board's position.
33
+ * Mutations use the sibling store, update, destroy, and reorder actions.
35
34
  */
36
35
  export default new Action({
37
36
  name: 'Kanban Boards Index',
@@ -71,8 +70,7 @@ export default new Action({
71
70
  return { boards }
72
71
  }
73
72
  catch (err) {
74
- console.error('[dashboard/kanban] BoardsIndexAction failed:', err)
75
- return kanbanError(err instanceof Error ? err.message : 'unknown error', 500)
73
+ return kanbanActionError(err, 'BoardsIndexAction')
76
74
  }
77
75
  },
78
76
  })
@@ -1,6 +1,7 @@
1
+ import type { RequestInstance } from '@stacksjs/types'
1
2
  import { Action } from '@stacksjs/actions'
2
3
  import { db } from '@stacksjs/database'
3
- import { kanbanError } from './kanban-response'
4
+ import { kanbanActionError, kanbanError } from './kanban-response'
4
5
 
5
6
  interface ReorderInput {
6
7
  /**
@@ -14,7 +15,7 @@ interface ReorderInput {
14
15
  }
15
16
 
16
17
  /**
17
- * `POST /api/dashboard/kanban/boards/reorder` (stacksjs/stacks#1846 Phase 2).
18
+ * `POST /api/dashboard/kanban/boards/reorder`.
18
19
  *
19
20
  * Rewrites the `position` column on every board in the submitted list
20
21
  * to match its index. Wrapped in a transaction so a partial failure
@@ -33,8 +34,8 @@ export default new Action({
33
34
  description: 'Bulk-rewrite `position` on a list of boards to match the submitted order.',
34
35
  method: 'POST',
35
36
  apiResponse: true,
36
- async handle(request) {
37
- const body = (request as any).jsonBody as ReorderInput | undefined ?? {}
37
+ async handle(request: RequestInstance<ReorderInput>) {
38
+ const body = request.all()
38
39
  if (!Array.isArray(body.order) || body.order.length === 0) {
39
40
  return kanbanError('`order` must be a non-empty array of board ids.', 400)
40
41
  }
@@ -66,8 +67,7 @@ export default new Action({
66
67
  return { reordered: ids.length }
67
68
  }
68
69
  catch (err) {
69
- console.error('[dashboard/kanban] BoardsReorderAction failed:', err)
70
- return kanbanError(err instanceof Error ? err.message : 'unknown error', 500)
70
+ return kanbanActionError(err, 'BoardsReorderAction')
71
71
  }
72
72
  },
73
73
  })
@@ -1,13 +1,14 @@
1
+ import type { RequestInstance } from '@stacksjs/types'
1
2
  import { Action } from '@stacksjs/actions'
2
3
  import { db } from '@stacksjs/database'
3
- import { kanbanError } from './kanban-response'
4
+ import { kanbanActionError, kanbanError } from './kanban-response'
4
5
 
5
6
  interface SyncInput {
6
7
  userIds?: unknown
7
8
  }
8
9
 
9
10
  /**
10
- * `POST /api/dashboard/kanban/cards/:id/assignees` (stacksjs/stacks#1846 Phase 3).
11
+ * `POST /api/dashboard/kanban/cards/:id/assignees`.
11
12
  *
12
13
  * Replaces the set of users assigned to a card. Sync semantics —
13
14
  * pass the new full list of user ids, the action diffs against
@@ -20,21 +21,20 @@ interface SyncInput {
20
21
  *
21
22
  * Cross-validation: each user id must exist. Doesn't validate
22
23
  * board-scoped membership — kanban users aren't (currently) team-
23
- * scoped; any platform user can be assigned to any card. Phase 4
24
- * could tighten this if board-membership becomes a concept.
24
+ * scoped; any platform user can be assigned to any card. A future
25
+ * board-membership feature can tighten this contract.
25
26
  */
26
27
  export default new Action({
27
28
  name: 'Kanban Card Assignees Sync',
28
29
  description: 'Replaces the set of users assigned to a card.',
29
30
  method: 'POST',
30
31
  apiResponse: true,
31
- async handle(request) {
32
- const rawId = (request as any)?.params?.id ?? (request as any)?.param?.('id') ?? null
33
- const cardId = Number(rawId)
32
+ async handle(request: RequestInstance<SyncInput>) {
33
+ const cardId = Number(request.getParam('id'))
34
34
  if (!Number.isFinite(cardId) || cardId <= 0)
35
35
  return kanbanError('Invalid card id', 400)
36
36
 
37
- const body = (request as any).jsonBody as SyncInput | undefined ?? {}
37
+ const body = request.all()
38
38
  if (!Array.isArray(body.userIds))
39
39
  return kanbanError('`userIds` must be an array of user ids (possibly empty).', 400)
40
40
 
@@ -63,8 +63,10 @@ export default new Action({
63
63
  return kanbanError('One or more user ids do not exist.', 400)
64
64
  }
65
65
 
66
- const requester = (request as any).user ?? (request as any)._authenticatedUser ?? null
67
- const assignedByUserId = requester && typeof requester.id === 'number' ? requester.id : null
66
+ const requester = await request.user()
67
+ const assignedByUserId = requester && Number.isInteger(Number(requester.id))
68
+ ? Number(requester.id)
69
+ : null
68
70
 
69
71
  await db.transaction(async (rawTrx) => {
70
72
  const qb = rawTrx as unknown as typeof db
@@ -92,8 +94,7 @@ export default new Action({
92
94
  return { cardId, assignees }
93
95
  }
94
96
  catch (err) {
95
- console.error('[dashboard/kanban] CardAssigneesSyncAction failed:', err)
96
- return kanbanError(err instanceof Error ? err.message : 'unknown error', 500)
97
+ return kanbanActionError(err, 'CardAssigneesSyncAction')
97
98
  }
98
99
  },
99
100
  })
@@ -1,34 +1,31 @@
1
+ import type { RequestInstance } from '@stacksjs/types'
1
2
  import { Action } from '@stacksjs/actions'
2
- import { db } from '@stacksjs/database'
3
- import { kanbanError } from './kanban-response'
3
+ import { CardComment } from '@stacksjs/orm'
4
+ import { kanbanActionError, kanbanError } from './kanban-response'
4
5
 
5
6
  /**
6
- * `DELETE /api/dashboard/kanban/comments/:id` (stacksjs/stacks#1846 Phase 3).
7
- *
8
- * Hard-deletes a card comment. No authorship check yet — Phase 4
9
- * could add "only the author or an admin can delete" semantics by
10
- * looking at `request.user.id` vs the comment's `user_id`. For now
11
- * the dashboard's role-gated visibility (only admin+dev see the
12
- * surface) is the gate.
7
+ * `DELETE /api/dashboard/kanban/comments/:id`.
8
+ * The dashboard route applies its local-development or admin guard.
13
9
  */
14
10
  export default new Action({
15
11
  name: 'Kanban Card Comment Destroy',
16
12
  description: 'Hard-deletes a single card comment.',
17
13
  method: 'DELETE',
18
14
  apiResponse: true,
19
- async handle(request) {
20
- const rawId = (request as any)?.params?.id ?? (request as any)?.param?.('id') ?? null
21
- const id = Number(rawId)
15
+ async handle(request: RequestInstance) {
16
+ const id = Number(request.getParam('id'))
22
17
  if (!Number.isFinite(id) || id <= 0)
23
18
  return kanbanError('Invalid comment id', 400)
24
19
 
25
20
  try {
26
- await db.deleteFrom('card_comments').where('id', '=', id).execute()
21
+ const comment = await CardComment.find(id)
22
+ if (!comment)
23
+ return kanbanError('Comment not found.', 404)
24
+ await comment.delete()
27
25
  return { deleted: true, id }
28
26
  }
29
27
  catch (err) {
30
- console.error('[dashboard/kanban] CardCommentDestroyAction failed:', err)
31
- return kanbanError(err instanceof Error ? err.message : 'unknown error', 500)
28
+ return kanbanActionError(err, 'CardCommentDestroyAction')
32
29
  }
33
30
  },
34
31
  })
@@ -1,83 +1,58 @@
1
+ import type { RequestInstance } from '@stacksjs/types'
1
2
  import { Action } from '@stacksjs/actions'
2
- import { db } from '@stacksjs/database'
3
- import { kanbanError } from './kanban-response'
3
+ import { Card, CardComment } from '@stacksjs/orm'
4
+ import { cardCommentResponse } from './kanban-comment'
5
+ import { refreshModel } from './kanban-model'
6
+ import { kanbanActionError, kanbanError } from './kanban-response'
4
7
 
5
8
  interface CommentInput {
6
9
  body?: unknown
7
10
  }
8
11
 
9
12
  /**
10
- * `POST /api/dashboard/kanban/cards/:id/comments` (stacksjs/stacks#1846 Phase 3).
13
+ * `POST /api/dashboard/kanban/cards/:id/comments`.
11
14
  *
12
15
  * Appends a comment to a card. Stamps `user_id` from the request's
13
16
  * authenticated user when available; leaves it null on the no-auth
14
17
  * dev dashboard so the surface stays usable locally.
15
18
  *
16
- * Returns the inserted row with denormalised author name/email for
17
- * the optimistic UI to render without a round-trip back through
18
- * `/cards/:id`.
19
+ * Writes through the model so generated API validation, timestamps, and
20
+ * observers stay aligned with the rest of the application.
19
21
  */
20
22
  export default new Action({
21
23
  name: 'Kanban Card Comment Store',
22
24
  description: 'Adds a comment to a card.',
23
25
  method: 'POST',
24
26
  apiResponse: true,
25
- async handle(request) {
26
- const rawId = (request as any)?.params?.id ?? (request as any)?.param?.('id') ?? null
27
- const cardId = Number(rawId)
27
+ async handle(request: RequestInstance<CommentInput>) {
28
+ const cardId = Number(request.getParam('id'))
28
29
  if (!Number.isFinite(cardId) || cardId <= 0)
29
30
  return kanbanError('Invalid card id', 400)
30
31
 
31
- const body = (request as any).jsonBody as CommentInput | undefined ?? {}
32
+ const body = request.all()
32
33
  const text = typeof body.body === 'string' ? body.body.trim() : ''
33
34
  if (!text || text.length > 10000)
34
35
  return kanbanError('`body` is required and must be 1-10000 characters.', 400)
35
36
 
36
37
  try {
37
- const cardRows = await db.unsafe('SELECT id FROM cards WHERE id = ? LIMIT 1', [cardId]).execute() as Array<{ id: number }>
38
- if (!cardRows?.length)
38
+ const card = await Card.find(cardId)
39
+ if (!card)
39
40
  return kanbanError('Card not found.', 404)
40
41
 
41
- const user = (request as any).user ?? (request as any)._authenticatedUser ?? null
42
- const userId = user && typeof user.id === 'number' ? user.id : null
43
-
44
- await db.insertInto('card_comments').values({
45
- card_id: cardId,
46
- user_id: userId,
42
+ const user = await request.user()
43
+ const userId = user && Number.isInteger(Number(user.id)) ? Number(user.id) : null
44
+ const comment = await refreshModel(await CardComment.create({
45
+ cardId,
46
+ userId,
47
47
  body: text,
48
- }).execute()
49
-
50
- // Pull the inserted row + author denormalisation. Ordered by id
51
- // DESC so the freshest matching row wins.
52
- const rows = await db.unsafe(
53
- `SELECT cc.id, cc.uuid, cc.user_id, cc.body, cc.created_at, cc.updated_at, u.name, u.email
54
- FROM card_comments cc
55
- LEFT JOIN users u ON u.id = cc.user_id
56
- WHERE cc.card_id = ? AND cc.body = ?
57
- ORDER BY cc.id DESC
58
- LIMIT 1`,
59
- [cardId, text],
60
- ).execute() as Array<{ id: number, uuid: string | null, user_id: number | null, body: string, created_at: string | null, updated_at: string | null, name: string | null, email: string | null }>
61
- const r = rows?.[0]
62
- if (!r)
63
- return kanbanError('Comment insert succeeded but follow-up read returned nothing.', 500)
48
+ }))
64
49
 
65
50
  return {
66
- comment: {
67
- id: r.id,
68
- uuid: r.uuid,
69
- userId: r.user_id,
70
- body: r.body,
71
- authorName: r.name,
72
- authorEmail: r.email,
73
- createdAt: r.created_at,
74
- updatedAt: r.updated_at,
75
- },
51
+ comment: cardCommentResponse(comment, user),
76
52
  }
77
53
  }
78
54
  catch (err) {
79
- console.error('[dashboard/kanban] CardCommentStoreAction failed:', err)
80
- return kanbanError(err instanceof Error ? err.message : 'unknown error', 500)
55
+ return kanbanActionError(err, 'CardCommentStoreAction')
81
56
  }
82
57
  },
83
58
  })
@@ -0,0 +1,43 @@
1
+ import type { RequestInstance } from '@stacksjs/types'
2
+ import { Action } from '@stacksjs/actions'
3
+ import { CardComment, User } from '@stacksjs/orm'
4
+ import { cardCommentResponse } from './kanban-comment'
5
+ import { modelNullableNumber, refreshModel } from './kanban-model'
6
+ import { kanbanActionError, kanbanError } from './kanban-response'
7
+
8
+ interface CommentInput {
9
+ body?: unknown
10
+ }
11
+
12
+ /** `PATCH /api/dashboard/kanban/comments/:id`. */
13
+ export default new Action({
14
+ name: 'Kanban Card Comment Update',
15
+ description: 'Edits the body of a card comment.',
16
+ method: 'PATCH',
17
+ apiResponse: true,
18
+ async handle(request: RequestInstance<CommentInput>) {
19
+ const id = Number(request.getParam('id'))
20
+ if (!Number.isFinite(id) || id <= 0)
21
+ return kanbanError('Invalid comment id', 400)
22
+
23
+ const input = request.all()
24
+ const body = typeof input.body === 'string' ? input.body.trim() : ''
25
+ if (!body || body.length > 10000)
26
+ return kanbanError('`body` is required and must be 1-10000 characters.', 400)
27
+
28
+ try {
29
+ const comment = await CardComment.find(id)
30
+ if (!comment)
31
+ return kanbanError('Comment not found.', 404)
32
+
33
+ const updated = await refreshModel(await comment.update({ body }))
34
+ const userId = modelNullableNumber(updated, 'userId', 'user_id')
35
+ const author = userId === null ? null : await User.find(userId)
36
+
37
+ return { comment: cardCommentResponse(updated, author) }
38
+ }
39
+ catch (error) {
40
+ return kanbanActionError(error, 'CardCommentUpdateAction')
41
+ }
42
+ },
43
+ })
@@ -1,9 +1,10 @@
1
+ import type { RequestInstance } from '@stacksjs/types'
1
2
  import { Action } from '@stacksjs/actions'
2
3
  import { db } from '@stacksjs/database'
3
- import { kanbanError } from './kanban-response'
4
+ import { kanbanActionError, kanbanError } from './kanban-response'
4
5
 
5
6
  /**
6
- * `DELETE /api/dashboard/kanban/cards/:id` (stacksjs/stacks#1846 Phase 2).
7
+ * `DELETE /api/dashboard/kanban/cards/:id`.
7
8
  *
8
9
  * Hard-deletes a card and clears its pivot rows in `card_labels` /
9
10
  * `card_assignees`. The columns and board are untouched — only the
@@ -18,9 +19,8 @@ export default new Action({
18
19
  description: 'Hard-deletes a card and its pivot rows.',
19
20
  method: 'DELETE',
20
21
  apiResponse: true,
21
- async handle(request) {
22
- const rawId = (request as any)?.params?.id ?? (request as any)?.param?.('id') ?? null
23
- const id = Number(rawId)
22
+ async handle(request: RequestInstance) {
23
+ const id = Number(request.getParam('id'))
24
24
  if (!Number.isFinite(id) || id <= 0) {
25
25
  return kanbanError('Invalid card id', 400)
26
26
  }
@@ -30,15 +30,14 @@ export default new Action({
30
30
  const qb = rawTrx as unknown as typeof db
31
31
  await qb.deleteFrom('card_labels').where('card_id', '=', id).execute()
32
32
  await qb.deleteFrom('card_assignees').where('card_id', '=', id).execute()
33
- // Card comments (Phase 3, stacksjs/stacks#1846).
33
+ // Card comments are card-scoped children.
34
34
  await qb.deleteFrom('card_comments').where('card_id', '=', id).execute()
35
35
  await qb.deleteFrom('cards').where('id', '=', id).execute()
36
36
  })
37
37
  return { deleted: true, id }
38
38
  }
39
39
  catch (err) {
40
- console.error('[dashboard/kanban] CardDestroyAction failed:', err)
41
- return kanbanError(err instanceof Error ? err.message : 'unknown error', 500)
40
+ return kanbanActionError(err, 'CardDestroyAction')
42
41
  }
43
42
  },
44
43
  })
@@ -1,13 +1,14 @@
1
+ import type { RequestInstance } from '@stacksjs/types'
1
2
  import { Action } from '@stacksjs/actions'
2
3
  import { db } from '@stacksjs/database'
3
- import { kanbanError } from './kanban-response'
4
+ import { kanbanActionError, kanbanError } from './kanban-response'
4
5
 
5
6
  interface SyncInput {
6
7
  labelIds?: unknown
7
8
  }
8
9
 
9
10
  /**
10
- * `POST /api/dashboard/kanban/cards/:id/labels` (stacksjs/stacks#1846 Phase 3).
11
+ * `POST /api/dashboard/kanban/cards/:id/labels`.
11
12
  *
12
13
  * Replaces the set of labels attached to a card. Sync semantics —
13
14
  * pass the new full list of label ids, the action diffs against
@@ -26,13 +27,12 @@ export default new Action({
26
27
  description: 'Replaces the set of labels attached to a card.',
27
28
  method: 'POST',
28
29
  apiResponse: true,
29
- async handle(request) {
30
- const rawId = (request as any)?.params?.id ?? (request as any)?.param?.('id') ?? null
31
- const cardId = Number(rawId)
30
+ async handle(request: RequestInstance<SyncInput>) {
31
+ const cardId = Number(request.getParam('id'))
32
32
  if (!Number.isFinite(cardId) || cardId <= 0)
33
33
  return kanbanError('Invalid card id', 400)
34
34
 
35
- const body = (request as any).jsonBody as SyncInput | undefined ?? {}
35
+ const body = request.all()
36
36
  if (!Array.isArray(body.labelIds))
37
37
  return kanbanError('`labelIds` must be an array of label ids (possibly empty).', 400)
38
38
 
@@ -56,7 +56,7 @@ export default new Action({
56
56
  return kanbanError('Card not found.', 404)
57
57
 
58
58
  // Every label must belong to the same board (labels are
59
- // board-scoped per Phase 1's `labels.board_id` design).
59
+ // board-scoped through `labels.board_id`).
60
60
  if (uniqueLabelIds.length > 0) {
61
61
  const placeholders = uniqueLabelIds.map(() => '?').join(',')
62
62
  const labelRows = await db.unsafe(
@@ -92,8 +92,7 @@ export default new Action({
92
92
  return { cardId, labels }
93
93
  }
94
94
  catch (err) {
95
- console.error('[dashboard/kanban] CardLabelsSyncAction failed:', err)
96
- return kanbanError(err instanceof Error ? err.message : 'unknown error', 500)
95
+ return kanbanActionError(err, 'CardLabelsSyncAction')
97
96
  }
98
97
  },
99
98
  })
@@ -1,7 +1,8 @@
1
+ import type { RequestInstance } from '@stacksjs/types'
1
2
  import { Action } from '@stacksjs/actions'
2
3
  import { db } from '@stacksjs/database'
3
4
  import { modelBoolean } from './kanban-model'
4
- import { kanbanError } from './kanban-response'
5
+ import { kanbanActionError, kanbanError } from './kanban-response'
5
6
 
6
7
  interface CardRow {
7
8
  id: number
@@ -20,7 +21,7 @@ interface CardRow {
20
21
  }
21
22
 
22
23
  /**
23
- * `GET /api/dashboard/kanban/cards/:id` (stacksjs/stacks#1846 Phase 3).
24
+ * `GET /api/dashboard/kanban/cards/:id`.
24
25
  *
25
26
  * Fetches a single card with everything the detail modal needs in one
26
27
  * round-trip: labels, assignees, comments. The board view's cards
@@ -39,9 +40,8 @@ export default new Action({
39
40
  description: 'Returns a single card with its labels, assignees, and comments thread.',
40
41
  method: 'GET',
41
42
  apiResponse: true,
42
- async handle(request) {
43
- const rawId = (request as any)?.params?.id ?? (request as any)?.param?.('id') ?? null
44
- const id = Number(rawId)
43
+ async handle(request: RequestInstance) {
44
+ const id = Number(request.getParam('id'))
45
45
  if (!Number.isFinite(id) || id <= 0) {
46
46
  return kanbanError('Invalid card id', 400)
47
47
  }
@@ -118,8 +118,7 @@ export default new Action({
118
118
  }
119
119
  }
120
120
  catch (err) {
121
- console.error('[dashboard/kanban] CardShowAction failed:', err)
122
- return kanbanError(err instanceof Error ? err.message : 'unknown error', 500)
121
+ return kanbanActionError(err, 'CardShowAction')
123
122
  }
124
123
  },
125
124
  })
@@ -1,7 +1,8 @@
1
+ import type { RequestInstance } from '@stacksjs/types'
1
2
  import { Action } from '@stacksjs/actions'
2
3
  import { db } from '@stacksjs/database'
3
4
  import { BoardColumn, Card } from '@stacksjs/orm'
4
- import { kanbanError } from './kanban-response'
5
+ import { kanbanActionError, kanbanError } from './kanban-response'
5
6
 
6
7
  interface CardInput {
7
8
  columnId?: unknown
@@ -11,7 +12,7 @@ interface CardInput {
11
12
  }
12
13
 
13
14
  /**
14
- * `POST /api/dashboard/kanban/cards` (stacksjs/stacks#1846 Phase 2).
15
+ * `POST /api/dashboard/kanban/cards`.
15
16
  *
16
17
  * Creates a card at the end of the target column. `boardId` is
17
18
  * resolved server-side from `column.board_id` (denormalised onto the
@@ -19,7 +20,7 @@ interface CardInput {
19
20
  * would invite drift if the column was moved between boards (which
20
21
  * isn't a feature, but still).
21
22
  *
22
- * Stamps `created_by_user_id` from `request.user` when the request is
23
+ * Stamps `created_by_user_id` from `request.user()` when the request is
23
24
  * authenticated; leaves it null for the no-auth dev dashboard so the
24
25
  * surface still works on localhost.
25
26
  */
@@ -28,8 +29,8 @@ export default new Action({
28
29
  description: 'Creates a new card in a column.',
29
30
  method: 'POST',
30
31
  apiResponse: true,
31
- async handle(request) {
32
- const body = (request as any).jsonBody as CardInput | undefined ?? {}
32
+ async handle(request: RequestInstance<CardInput>) {
33
+ const body = request.all()
33
34
 
34
35
  const columnId = Number(body.columnId)
35
36
  if (!Number.isFinite(columnId) || columnId <= 0) {
@@ -57,8 +58,8 @@ export default new Action({
57
58
  ).execute() as Array<{ m: number }>
58
59
  const nextPosition = (Number(maxRow?.[0]?.m ?? -1) + 1) || 0
59
60
 
60
- const user = (request as any).user ?? (request as any)._authenticatedUser ?? null
61
- const createdByUserId = user && typeof user.id === 'number' ? user.id : null
61
+ const user = await request.user()
62
+ const createdByUserId = user && Number.isInteger(Number(user.id)) ? Number(user.id) : null
62
63
 
63
64
  const card = await Card.create({
64
65
  columnId,
@@ -89,8 +90,7 @@ export default new Action({
89
90
  }
90
91
  }
91
92
  catch (err) {
92
- console.error('[dashboard/kanban] CardStoreAction failed:', err)
93
- return kanbanError(err instanceof Error ? err.message : 'unknown error', 500)
93
+ return kanbanActionError(err, 'CardStoreAction')
94
94
  }
95
95
  },
96
96
  })
@@ -1,7 +1,8 @@
1
+ import type { RequestInstance } from '@stacksjs/types'
1
2
  import { Action } from '@stacksjs/actions'
2
3
  import { Card } from '@stacksjs/orm'
3
4
  import { modelBoolean, modelNullableString, modelNumber, modelString, modelValue, refreshModel } from './kanban-model'
4
- import { kanbanError } from './kanban-response'
5
+ import { kanbanActionError, kanbanError } from './kanban-response'
5
6
 
6
7
  interface CardInput {
7
8
  title?: unknown
@@ -11,7 +12,7 @@ interface CardInput {
11
12
  }
12
13
 
13
14
  /**
14
- * `PATCH /api/dashboard/kanban/cards/:id` (stacksjs/stacks#1846 Phase 2).
15
+ * `PATCH /api/dashboard/kanban/cards/:id`.
15
16
  *
16
17
  * Partial update for the card body: title, description, due date,
17
18
  * archive flag. `position` and `columnId` are NOT updatable here —
@@ -25,14 +26,13 @@ export default new Action({
25
26
  description: 'Partial update of a card body (title, description, due date, archive).',
26
27
  method: 'PATCH',
27
28
  apiResponse: true,
28
- async handle(request) {
29
- const rawId = (request as any)?.params?.id ?? (request as any)?.param?.('id') ?? null
30
- const id = Number(rawId)
29
+ async handle(request: RequestInstance<CardInput>) {
30
+ const id = Number(request.getParam('id'))
31
31
  if (!Number.isFinite(id) || id <= 0) {
32
32
  return kanbanError('Invalid card id', 400)
33
33
  }
34
34
 
35
- const body = (request as any).jsonBody as CardInput | undefined ?? {}
35
+ const body = request.all()
36
36
  const set: Record<string, unknown> = {}
37
37
 
38
38
  if (typeof body.title === 'string') {
@@ -81,8 +81,7 @@ export default new Action({
81
81
  }
82
82
  }
83
83
  catch (err) {
84
- console.error('[dashboard/kanban] CardUpdateAction failed:', err)
85
- return kanbanError(err instanceof Error ? err.message : 'unknown error', 500)
84
+ return kanbanActionError(err, 'CardUpdateAction')
86
85
  }
87
86
  },
88
87
  })
@@ -1,6 +1,7 @@
1
+ import type { RequestInstance } from '@stacksjs/types'
1
2
  import { Action } from '@stacksjs/actions'
2
3
  import { db } from '@stacksjs/database'
3
- import { kanbanError } from './kanban-response'
4
+ import { kanbanActionError, kanbanError } from './kanban-response'
4
5
 
5
6
  interface ColumnReorder {
6
7
  columnId: number
@@ -31,7 +32,7 @@ interface ReorderInput {
31
32
  }
32
33
 
33
34
  /**
34
- * `POST /api/dashboard/kanban/cards/reorder` (stacksjs/stacks#1846 Phase 2).
35
+ * `POST /api/dashboard/kanban/cards/reorder`.
35
36
  *
36
37
  * The drag-and-drop hot path. Rewrites card positions and column
37
38
  * assignments across one or more columns of a single board in a single
@@ -47,8 +48,8 @@ export default new Action({
47
48
  description: 'Bulk-rewrite cards.{column_id, position} across one or more columns of the same board.',
48
49
  method: 'POST',
49
50
  apiResponse: true,
50
- async handle(request) {
51
- const body = (request as any).jsonBody as ReorderInput | undefined ?? {}
51
+ async handle(request: RequestInstance<ReorderInput>) {
52
+ const body = request.all()
52
53
 
53
54
  if (!Array.isArray(body.columns) || body.columns.length === 0) {
54
55
  return kanbanError('`columns` must be a non-empty array.', 400)
@@ -134,8 +135,7 @@ export default new Action({
134
135
  return { moved: allCardIds.size, boardId }
135
136
  }
136
137
  catch (err) {
137
- console.error('[dashboard/kanban] CardsReorderAction failed:', err)
138
- return kanbanError(err instanceof Error ? err.message : 'unknown error', 500)
138
+ return kanbanActionError(err, 'CardsReorderAction')
139
139
  }
140
140
  },
141
141
  })