@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.
- package/ai/skills/stacks-browse/SKILL.md +17 -2
- package/ai/skills/stacks-browse/scripts/browse.ts +207 -6
- package/ai/skills/stacks-email/SKILL.md +73 -16
- package/app/Actions/ContactAction.ts +2 -2
- package/app/Actions/Dashboard/Analytics/EventAnalyticsAction.ts +2 -3
- package/app/Actions/Dashboard/Analytics/MarketingAnalyticsAction.ts +2 -3
- package/app/Actions/Dashboard/Analytics/SalesAnalyticsAction.ts +2 -3
- package/app/Actions/Dashboard/Analytics/WebAnalyticsAction.ts +9 -3
- package/app/Actions/Dashboard/Auth/MeAction.ts +12 -14
- package/app/Actions/Dashboard/Buddy/BuddyChatAction.ts +2 -3
- package/app/Actions/Dashboard/Ci/RepoRunJobsAction.ts +10 -15
- package/app/Actions/Dashboard/Ci/RepoRunsAction.ts +12 -14
- package/app/Actions/Dashboard/Ci/RunnerHistoryAction.ts +11 -10
- package/app/Actions/Dashboard/Ci/StatusAction.ts +2 -11
- package/app/Actions/Dashboard/Cloud/CloudIndexAction.ts +8 -1
- package/app/Actions/Dashboard/Cloud/ServerlessIndexAction.ts +15 -8
- package/app/Actions/Dashboard/Commerce/CommerceCategoriesAction.ts +2 -4
- package/app/Actions/Dashboard/Commerce/CommerceCouponsAction.ts +2 -4
- package/app/Actions/Dashboard/Commerce/CommerceCustomersAction.ts +2 -4
- package/app/Actions/Dashboard/Commerce/CommerceDashboardAction.ts +2 -4
- package/app/Actions/Dashboard/Commerce/CommerceDeliveryAction.ts +2 -4
- package/app/Actions/Dashboard/Commerce/CommerceGiftCardsAction.ts +2 -4
- package/app/Actions/Dashboard/Commerce/CommerceOrdersAction.ts +2 -4
- package/app/Actions/Dashboard/Commerce/CommercePaymentsAction.ts +2 -4
- package/app/Actions/Dashboard/Commerce/CommercePosAction.ts +2 -4
- package/app/Actions/Dashboard/Commerce/CommercePosCheckoutAction.ts +76 -61
- package/app/Actions/Dashboard/Commerce/CommercePrintDevicesAction.ts +2 -4
- package/app/Actions/Dashboard/Commerce/CommercePrintLogsAction.ts +4 -7
- package/app/Actions/Dashboard/Commerce/CommerceProductDetailAction.ts +4 -4
- package/app/Actions/Dashboard/Commerce/CommerceProductsAction.ts +2 -4
- package/app/Actions/Dashboard/Commerce/CommerceTaxesAction.ts +2 -4
- package/app/Actions/Dashboard/Commerce/DeliveryRouteIndexAction.ts +2 -4
- package/app/Actions/Dashboard/Commerce/DigitalDeliveryIndexAction.ts +2 -4
- package/app/Actions/Dashboard/Commerce/DriverIndexAction.ts +2 -4
- package/app/Actions/Dashboard/Commerce/LicenseKeyIndexAction.ts +2 -4
- package/app/Actions/Dashboard/Commerce/LicenseKeyOptionsAction.ts +2 -4
- package/app/Actions/Dashboard/Commerce/ManufacturerIndexAction.ts +2 -4
- package/app/Actions/Dashboard/Commerce/PaymentRefundAction.ts +1 -0
- package/app/Actions/Dashboard/Commerce/ProductUnitDefaultAction.ts +1 -0
- package/app/Actions/Dashboard/Commerce/ProductUnitIndexAction.ts +2 -4
- package/app/Actions/Dashboard/Commerce/ProductVariantIndexAction.ts +2 -4
- package/app/Actions/Dashboard/Commerce/ProductWaitlistIndexAction.ts +2 -4
- package/app/Actions/Dashboard/Commerce/RestaurantWaitlistIndexAction.ts +2 -4
- package/app/Actions/Dashboard/Commerce/ReviewIndexAction.ts +2 -4
- package/app/Actions/Dashboard/Commerce/ShippingMethodIndexAction.ts +2 -4
- package/app/Actions/Dashboard/Commerce/ShippingRateIndexAction.ts +2 -4
- package/app/Actions/Dashboard/Commerce/ShippingZoneIndexAction.ts +2 -4
- package/app/Actions/Dashboard/Commerce/TaxRateDefaultAction.ts +1 -0
- package/app/Actions/Dashboard/DashboardActivityAction.ts +2 -5
- package/app/Actions/Dashboard/Data/ActivityIndexAction.ts +2 -5
- package/app/Actions/Dashboard/Data/SubscriberIndexAction.ts +2 -5
- package/app/Actions/Dashboard/Data/TeamIndexAction.ts +2 -5
- package/app/Actions/Dashboard/Data/UserIndexAction.ts +2 -5
- package/app/Actions/Dashboard/Deployments/CreateDeployment.ts +26 -21
- package/app/Actions/Dashboard/Deployments/GetAverageDeploymentTime.ts +8 -2
- package/app/Actions/Dashboard/Deployments/GetDeployScript.ts +3 -6
- package/app/Actions/Dashboard/Deployments/GetDeployment.ts +5 -5
- package/app/Actions/Dashboard/Deployments/GetDeploymentCount.ts +7 -1
- package/app/Actions/Dashboard/Deployments/GetDeploymentLiveTerminalOutput.ts +10 -9
- package/app/Actions/Dashboard/Deployments/GetDeployments.ts +2 -4
- package/app/Actions/Dashboard/Deployments/GetRecentDeployments.ts +8 -2
- package/app/Actions/Dashboard/Deployments/UpdateDeployScript.ts +21 -7
- package/app/Actions/Dashboard/Email/CapturedMailIndexAction.ts +2 -4
- package/app/Actions/Dashboard/Email/CapturedMailShowAction.ts +3 -4
- package/app/Actions/Dashboard/Email/InboxActivityAction.ts +28 -16
- package/app/Actions/Dashboard/Email/InboxAttachmentDownloadAction.ts +45 -0
- package/app/Actions/Dashboard/Email/InboxDeleteAction.ts +11 -6
- package/app/Actions/Dashboard/Email/InboxIndexAction.ts +5 -7
- package/app/Actions/Dashboard/Email/InboxMarkReadAction.ts +11 -6
- package/app/Actions/Dashboard/Email/InboxMarkUnreadAction.ts +11 -6
- package/app/Actions/Dashboard/Email/InboxPreferenceShowAction.ts +10 -4
- package/app/Actions/Dashboard/Email/InboxPreferenceUpdateAction.ts +8 -9
- package/app/Actions/Dashboard/Email/InboxShowAction.ts +7 -7
- package/app/Actions/Dashboard/Email/InboxStatsAction.ts +5 -7
- package/app/Actions/Dashboard/Email/inbox-request.ts +33 -0
- package/app/Actions/Dashboard/Infrastructure/InsightsAction.ts +15 -14
- package/app/Actions/Dashboard/Infrastructure/LogIndexAction.ts +4 -11
- package/app/Actions/Dashboard/Infrastructure/ServerShowAction.ts +3 -6
- package/app/Actions/Dashboard/Jobs/JobIndexAction.ts +9 -18
- package/app/Actions/Dashboard/Jobs/JobRetryAction.ts +4 -3
- package/app/Actions/Dashboard/Jobs/JobShowAction.ts +16 -11
- package/app/Actions/Dashboard/Jobs/JobStatsAction.ts +2 -4
- package/app/Actions/Dashboard/Kanban/BoardDestroyAction.ts +7 -8
- package/app/Actions/Dashboard/Kanban/BoardShowAction.ts +11 -19
- package/app/Actions/Dashboard/Kanban/BoardStoreAction.ts +6 -6
- package/app/Actions/Dashboard/Kanban/BoardUpdateAction.ts +7 -8
- package/app/Actions/Dashboard/Kanban/BoardsIndexAction.ts +3 -5
- package/app/Actions/Dashboard/Kanban/BoardsReorderAction.ts +6 -6
- package/app/Actions/Dashboard/Kanban/CardAssigneesSyncAction.ts +13 -12
- package/app/Actions/Dashboard/Kanban/CardCommentDestroyAction.ts +12 -15
- package/app/Actions/Dashboard/Kanban/CardCommentStoreAction.ts +21 -46
- package/app/Actions/Dashboard/Kanban/CardCommentUpdateAction.ts +43 -0
- package/app/Actions/Dashboard/Kanban/CardDestroyAction.ts +7 -8
- package/app/Actions/Dashboard/Kanban/CardLabelsSyncAction.ts +8 -9
- package/app/Actions/Dashboard/Kanban/CardShowAction.ts +6 -7
- package/app/Actions/Dashboard/Kanban/CardStoreAction.ts +9 -9
- package/app/Actions/Dashboard/Kanban/CardUpdateAction.ts +7 -8
- package/app/Actions/Dashboard/Kanban/CardsReorderAction.ts +6 -6
- package/app/Actions/Dashboard/Kanban/ColumnDestroyAction.ts +7 -8
- package/app/Actions/Dashboard/Kanban/ColumnStoreAction.ts +6 -6
- package/app/Actions/Dashboard/Kanban/ColumnUpdateAction.ts +7 -8
- package/app/Actions/Dashboard/Kanban/ColumnsReorderAction.ts +6 -6
- package/app/Actions/Dashboard/Kanban/LabelDestroyAction.ts +6 -7
- package/app/Actions/Dashboard/Kanban/LabelStoreAction.ts +6 -6
- package/app/Actions/Dashboard/Kanban/LabelUpdateAction.ts +7 -8
- package/app/Actions/Dashboard/Kanban/UsersListAction.ts +3 -4
- package/app/Actions/Dashboard/Kanban/kanban-comment.ts +14 -0
- package/app/Actions/Dashboard/Kanban/kanban-model.test.ts +36 -0
- package/app/Actions/Dashboard/Kanban/kanban-model.ts +14 -7
- package/app/Actions/Dashboard/Kanban/kanban-response.ts +5 -0
- package/app/Actions/Dashboard/Library/CreateComponent.ts +15 -6
- package/app/Actions/Dashboard/Library/CreateFunction.ts +15 -6
- package/app/Actions/Dashboard/Library/DependencyIndexAction.ts +88 -77
- package/app/Actions/Dashboard/Library/GetAverageReleaseTime.ts +21 -15
- package/app/Actions/Dashboard/Library/GetComponents.ts +14 -7
- package/app/Actions/Dashboard/Library/GetDownloadCount.ts +14 -5
- package/app/Actions/Dashboard/Library/GetFunctions.ts +12 -5
- package/app/Actions/Dashboard/Library/GetReleaseCount.ts +14 -5
- package/app/Actions/Dashboard/Library/GetReleases.ts +17 -10
- package/app/Actions/Dashboard/Library/PackageIndexAction.ts +14 -7
- package/app/Actions/Dashboard/Models/ModelDestroyAction.ts +11 -8
- package/app/Actions/Dashboard/Models/ModelShowAction.ts +97 -192
- package/app/Actions/Dashboard/Models/ModelStoreAction.ts +14 -12
- package/app/Actions/Dashboard/Models/ModelUpdateAction.ts +18 -16
- package/app/Actions/Dashboard/Models/ModelsIndexAction.ts +50 -19
- package/app/Actions/Dashboard/Models/model-api.ts +27 -0
- package/app/Actions/Dashboard/Models/model-write.ts +22 -15
- package/app/Actions/Dashboard/Notifications/NotificationDeliveryHistoryAction.ts +49 -40
- package/app/Actions/Dashboard/Notifications/NotificationDeliveryIndexAction.ts +20 -10
- package/app/Actions/Dashboard/Notifications/NotificationDeliveryOverviewAction.ts +96 -84
- package/app/Actions/Dashboard/Notifications/NotificationDeliveryRetryAction.ts +34 -21
- package/app/Actions/Dashboard/Queries/QueryDashboardAction.ts +2 -4
- package/app/Actions/Dashboard/Queries/QueryShowAction.ts +3 -3
- package/app/Actions/Dashboard/Queue/QueueStatsAction.ts +2 -4
- package/app/Actions/Dashboard/Queue/QueueWorkersAction.ts +2 -4
- package/app/Actions/Dashboard/Rbac/PermissionDestroyAction.ts +6 -6
- package/app/Actions/Dashboard/Rbac/PermissionStoreAction.ts +5 -4
- package/app/Actions/Dashboard/Rbac/PermissionsIndexAction.ts +2 -3
- package/app/Actions/Dashboard/Rbac/RoleDestroyAction.ts +6 -6
- package/app/Actions/Dashboard/Rbac/RolePermissionsShowAction.ts +6 -6
- package/app/Actions/Dashboard/Rbac/RolePermissionsSyncAction.ts +6 -9
- package/app/Actions/Dashboard/Rbac/RoleStoreAction.ts +5 -4
- package/app/Actions/Dashboard/Rbac/RolesIndexAction.ts +4 -6
- package/app/Actions/Dashboard/Rbac/UserRolesShowAction.ts +6 -7
- package/app/Actions/Dashboard/Rbac/UserRolesSyncAction.ts +6 -13
- package/app/Actions/Dashboard/Rbac/UsersListAction.ts +2 -3
- package/app/Actions/Dashboard/Rbac/rbac-response.ts +10 -0
- package/app/Actions/Dashboard/Realtime/RealtimeStatsAction.ts +2 -4
- package/app/Actions/Dashboard/Search/GlobalSearchAction.ts +69 -55
- package/app/Actions/Dashboard/Teams/TeamInvitationDestroyAction.ts +34 -16
- package/app/Actions/Dashboard/Teams/TeamInvitationResendAction.ts +52 -29
- package/app/Actions/Dashboard/Teams/TeamInviteAction.ts +104 -73
- package/app/Actions/Dashboard/Teams/TeamMemberDestroyAction.ts +36 -17
- package/app/Actions/Dashboard/Teams/TeamMemberUpdateAction.ts +41 -28
- package/app/Actions/Dashboard/Teams/TeamPeopleIndexAction.ts +71 -58
- package/app/Actions/Dashboard/Teams/team-invitation-delivery.ts +29 -7
- package/app/Actions/Dashboard/Teams/team-member-count.ts +4 -2
- package/app/Actions/Dashboard/Teams/team-records.test.ts +2 -0
- package/app/Actions/Dashboard/Teams/team-records.ts +2 -0
- package/app/Actions/Dashboard/dashboard-request.ts +13 -0
- package/app/Actions/Dashboard/dashboard-response.ts +16 -0
- package/app/Actions/SendWelcomeEmail.ts +1 -1
- package/app/Actions/Teams/AcceptInvitationAction.ts +74 -51
- package/app/Actions/Teams/ShowInvitationAction.ts +21 -14
- package/app/Actions/Teams/team-response.ts +23 -0
- package/app/Mail/OrderConfirmation.ts +1 -1
- package/app/Mail/SubscriptionConfirmation.ts +1 -1
- package/app/Mail/TeamInvitation.ts +1 -1
- package/app/Models/CardComment.ts +3 -4
- package/app/Models/EmailIdempotency.ts +64 -0
- package/app/Models/EmailSuppression.ts +52 -0
- package/app/Models/EmailWebhookEvent.ts +51 -0
- package/app/Models/TeamInvitation.ts +13 -0
- package/functions/dashboard-api.ts +51 -10
- package/functions/data.ts +0 -1
- package/functions/inbox.test.ts +2 -0
- package/functions/inbox.ts +27 -2
- package/functions/jobs.ts +0 -1
- package/functions/public-application-url.test.ts +13 -0
- package/functions/public-application-url.ts +27 -0
- package/ide/vscode/package.json +1 -1
- package/package.json +1 -1
- package/resources/components/Dashboard/Auth/AccessTokens.stx +15 -0
- package/resources/components/Dashboard/Ci/CiDashboard.stx +24 -6
- package/resources/components/Dashboard/Commerce/CommerceTaxDialog.stx +1 -1
- package/resources/components/Dashboard/Commerce/ProductWaitlistDashboard.stx +8 -7
- package/resources/components/Dashboard/Commerce/RestaurantWaitlistDashboard.stx +8 -7
- package/resources/components/Dashboard/Content/BlogDashboard.stx +14 -2
- package/resources/components/Dashboard/Content/PostsDashboard.stx +7 -1
- package/resources/components/Dashboard/Data/DataRecords.stx +0 -4
- package/resources/components/Dashboard/Deployments/DeploymentDetail.stx +16 -2
- package/resources/components/Dashboard/Email/EmailNavigation.stx +5 -5
- package/resources/components/Dashboard/Email/EmailSidebar.stx +1 -1
- package/resources/components/Dashboard/Email/InboxAttachmentList.stx +53 -0
- package/resources/components/Dashboard/Email/InboxDashboard.stx +5 -4
- package/resources/components/Dashboard/Email/InboxMessageDetail.stx +6 -4
- package/resources/components/Dashboard/Jobs/JobDetail.stx +3 -2
- package/resources/components/Dashboard/Kanban/KanbanBoardDashboard.stx +63 -6
- package/resources/components/Dashboard/Kanban/KanbanBoardsDashboard.stx +3 -6
- package/resources/components/Dashboard/Management/PermissionsDashboard.stx +4 -4
- package/resources/components/Dashboard/Models/ModelRecordsDashboard.stx +7 -13
- package/resources/components/Dashboard/Models/ModelsOverview.stx +1 -1
- package/resources/components/Dashboard/NotFoundDashboard.stx +2 -5
- package/resources/components/Dashboard/Queries/QueryDashboard.stx +6 -2
- package/resources/components/Dashboard/Settings/AppearanceSettingsDashboard.stx +4 -4
- package/resources/components/Dashboard/UI/EmptyState.stx +2 -2
- package/resources/components/Dashboard/UI/PageLayout.stx +3 -3
- package/resources/functions/dashboard/sidebar.ts +4 -4
- package/routes/dashboard-api.ts +8 -20
- package/views/dashboard/.discovered-models.json +54 -0
- package/views/dashboard/layouts/default.stx +19 -16
- package/views/dashboard/stores/auth.ts +15 -14
- package/views/dashboard/stores/ci.ts +13 -7
- package/views/dashboard/stores/kanban.ts +48 -11
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
* bypass every guarantee the ORM makes. Those rows stay read-only.
|
|
8
8
|
*/
|
|
9
9
|
import { loadModelIfExists } from '../../../../resources/functions/dashboard/data'
|
|
10
|
+
import { dashboardOperationalIssue } from '../dashboard-response'
|
|
11
|
+
import { modelApiConfiguration } from './model-api'
|
|
10
12
|
|
|
11
13
|
/** Never writable from a generic admin table, whatever the model declares. */
|
|
12
14
|
export const PROTECTED_COLUMNS: ReadonlySet<string> = new Set([
|
|
@@ -41,6 +43,11 @@ export interface ResolvedModel {
|
|
|
41
43
|
modelName: string
|
|
42
44
|
}
|
|
43
45
|
|
|
46
|
+
export interface ModelResolutionError {
|
|
47
|
+
error: string
|
|
48
|
+
status: 400 | 404 | 405 | 500
|
|
49
|
+
}
|
|
50
|
+
|
|
44
51
|
export type ModelWriteOperation = 'store' | 'update' | 'destroy'
|
|
45
52
|
|
|
46
53
|
export interface ModelWriteCapabilities {
|
|
@@ -121,17 +128,8 @@ function isProtectedField(name: string): boolean {
|
|
|
121
128
|
return PROTECTED_COLUMNS.has(name) || PROTECTED_COLUMNS.has(column)
|
|
122
129
|
}
|
|
123
130
|
|
|
124
|
-
function useApiRoutes(Model: any): string[] {
|
|
125
|
-
const useApi = Model?.traits?.useApi
|
|
126
|
-
if (!useApi)
|
|
127
|
-
return []
|
|
128
|
-
if (typeof useApi === 'object' && Array.isArray(useApi.routes))
|
|
129
|
-
return useApi.routes.map(String)
|
|
130
|
-
return ['index', 'store', 'show', 'update', 'destroy']
|
|
131
|
-
}
|
|
132
|
-
|
|
133
131
|
export function modelWriteCapabilities(Model: any): ModelWriteCapabilities {
|
|
134
|
-
const routes =
|
|
132
|
+
const routes = modelApiConfiguration(Model).routes
|
|
135
133
|
return {
|
|
136
134
|
create: routes.includes('store'),
|
|
137
135
|
update: routes.includes('update'),
|
|
@@ -293,19 +291,28 @@ export function prepareModelFields(
|
|
|
293
291
|
* injects (the same object the rest of the dashboard queries, so scopes,
|
|
294
292
|
* casts and observers all apply) over the path-map lookup.
|
|
295
293
|
*/
|
|
296
|
-
export async function resolveWritableModel(slug: string, operation?: ModelWriteOperation): Promise<ResolvedModel |
|
|
294
|
+
export async function resolveWritableModel(slug: string, operation?: ModelWriteOperation): Promise<ResolvedModel | ModelResolutionError> {
|
|
297
295
|
if (!isValidModelSlug(slug))
|
|
298
|
-
return { error: 'Model slug must be lowercase kebab-case.' }
|
|
296
|
+
return { error: 'Model slug must be lowercase kebab-case.', status: 400 }
|
|
299
297
|
|
|
300
298
|
const modelName = slugToPascal(slug)
|
|
301
299
|
const injected = (globalThis as Record<string, any>)[modelName]
|
|
302
|
-
|
|
300
|
+
let Model: any
|
|
301
|
+
try {
|
|
302
|
+
Model = (injected && typeof injected.where === 'function') ? injected : await loadModelIfExists(modelName)
|
|
303
|
+
}
|
|
304
|
+
catch (error) {
|
|
305
|
+
return {
|
|
306
|
+
error: dashboardOperationalIssue(error, `${modelName} could not be loaded.`, `resolveWritableModel.${operation || 'read'}`),
|
|
307
|
+
status: 500,
|
|
308
|
+
}
|
|
309
|
+
}
|
|
303
310
|
if (!Model || typeof Model.find !== 'function')
|
|
304
|
-
return { error: `No ORM model named ${modelName}. Rows from tables without a model are read-only
|
|
311
|
+
return { error: `No ORM model named ${modelName}. Rows from tables without a model are read-only.`, status: 404 }
|
|
305
312
|
if (operation) {
|
|
306
313
|
const capability = operation === 'store' ? 'create' : operation
|
|
307
314
|
if (!modelWriteCapabilities(Model)[capability])
|
|
308
|
-
return { error: `${modelName} does not declare the ${operation} route in its useApi trait
|
|
315
|
+
return { error: `${modelName} does not declare the ${operation} route in its useApi trait.`, status: 405 }
|
|
309
316
|
}
|
|
310
317
|
return { Model, modelName }
|
|
311
318
|
}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import type { RequestInstance } from '@stacksjs/types'
|
|
2
2
|
import { Action } from '@stacksjs/actions'
|
|
3
3
|
import { db } from '@stacksjs/database'
|
|
4
|
-
import {
|
|
4
|
+
import { response } from '@stacksjs/router'
|
|
5
|
+
import { dashboardOperationalError } from '../dashboard-response'
|
|
6
|
+
import { dashboardRequestValue } from '../dashboard-request'
|
|
5
7
|
import {
|
|
6
8
|
type NotificationDeliveryRow,
|
|
7
9
|
serializeNotificationDelivery,
|
|
@@ -16,9 +18,7 @@ type DeliveryStatus = typeof STATUSES[number]
|
|
|
16
18
|
type DeliverySort = typeof SORTS[number]
|
|
17
19
|
|
|
18
20
|
function queryValue(request: RequestInstance, key: string): string {
|
|
19
|
-
|
|
20
|
-
const value = query[key]
|
|
21
|
-
return String((Array.isArray(value) ? value[0] : value) || request.get(key) || '').trim()
|
|
21
|
+
return dashboardRequestValue(request, key)
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
function allowedValue<T extends string>(value: string, values: readonly T[]): T | null {
|
|
@@ -31,13 +31,15 @@ export default new Action({
|
|
|
31
31
|
method: 'GET',
|
|
32
32
|
apiResponse: true,
|
|
33
33
|
async handle(request: RequestInstance) {
|
|
34
|
-
const page = Math.max(1, Number.parseInt(queryValue(request, 'page') || '1', 10) || 1)
|
|
34
|
+
const page = Math.min(1_000_000, Math.max(1, Number.parseInt(queryValue(request, 'page') || '1', 10) || 1))
|
|
35
35
|
const perPage = Math.min(100, Math.max(1, Number.parseInt(queryValue(request, 'per_page') || '20', 10) || 20))
|
|
36
36
|
const channel = allowedValue(queryValue(request, 'channel').toLowerCase(), CHANNELS)
|
|
37
37
|
const status = allowedValue(queryValue(request, 'status').toLowerCase(), STATUSES)
|
|
38
38
|
const sort = allowedValue(queryValue(request, 'sort').toLowerCase(), SORTS) || 'sent_at'
|
|
39
39
|
const direction = queryValue(request, 'direction').toLowerCase() === 'asc' ? 'asc' : 'desc'
|
|
40
40
|
const search = queryValue(request, 'search')
|
|
41
|
+
if (search.length > 200)
|
|
42
|
+
return response.json({ message: 'Notification delivery search must be 200 characters or fewer.' }, 422)
|
|
41
43
|
const searchPattern = `%${search}%`
|
|
42
44
|
|
|
43
45
|
const applyFilters = <T extends {
|
|
@@ -59,42 +61,49 @@ export default new Action({
|
|
|
59
61
|
return filtered
|
|
60
62
|
}
|
|
61
63
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
64
|
+
try {
|
|
65
|
+
const countQuery = applyFilters(
|
|
66
|
+
db
|
|
67
|
+
.selectFrom('notification_deliveries')
|
|
68
|
+
.select(db.fn.count('id').as('count')),
|
|
69
|
+
)
|
|
70
|
+
const rowsQuery = applyFilters(
|
|
71
|
+
db
|
|
72
|
+
.selectFrom('notification_deliveries')
|
|
73
|
+
.selectAll(),
|
|
74
|
+
)
|
|
72
75
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
76
|
+
const [countRow, rows] = await Promise.all([
|
|
77
|
+
countQuery.executeTakeFirst() as Promise<{ count: number | string } | undefined>,
|
|
78
|
+
rowsQuery
|
|
79
|
+
.orderBy(sort as DeliverySort, direction)
|
|
80
|
+
.limit(perPage)
|
|
81
|
+
.offset((page - 1) * perPage)
|
|
82
|
+
.execute() as Promise<NotificationDeliveryRow[]>,
|
|
83
|
+
])
|
|
81
84
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
85
|
+
const total = Number(countRow?.count || 0)
|
|
86
|
+
if (!Number.isSafeInteger(total) || total < 0)
|
|
87
|
+
throw new TypeError('Notification delivery count must be a non-negative integer.')
|
|
88
|
+
return response.json({
|
|
89
|
+
deliveries: rows.map(serializeNotificationDelivery),
|
|
90
|
+
pagination: {
|
|
91
|
+
page,
|
|
92
|
+
per_page: perPage,
|
|
93
|
+
total,
|
|
94
|
+
total_pages: Math.max(1, Math.ceil(total / perPage)),
|
|
95
|
+
},
|
|
96
|
+
filters: {
|
|
97
|
+
channel: channel as DeliveryChannel | null,
|
|
98
|
+
status: status as DeliveryStatus | null,
|
|
99
|
+
search,
|
|
100
|
+
sort,
|
|
101
|
+
direction,
|
|
102
|
+
},
|
|
103
|
+
})
|
|
104
|
+
}
|
|
105
|
+
catch (error) {
|
|
106
|
+
return dashboardOperationalError(error, 'Notification delivery history could not be loaded.', 'NotificationDeliveryHistoryAction')
|
|
107
|
+
}
|
|
99
108
|
},
|
|
100
109
|
})
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import type { RequestInstance } from '@stacksjs/types'
|
|
2
2
|
import { Action } from '@stacksjs/actions'
|
|
3
3
|
import { NotificationDelivery } from '@stacksjs/orm'
|
|
4
|
-
import {
|
|
4
|
+
import { response } from '@stacksjs/router'
|
|
5
|
+
import { dashboardOperationalError } from '../dashboard-response'
|
|
6
|
+
import { dashboardRequestValue } from '../dashboard-request'
|
|
5
7
|
import { parseDeliveryMetadata } from './notification-delivery'
|
|
6
8
|
|
|
7
9
|
type DashboardDeliveryChannel = 'email' | 'sms'
|
|
@@ -29,15 +31,21 @@ export default new Action({
|
|
|
29
31
|
method: 'GET',
|
|
30
32
|
apiResponse: true,
|
|
31
33
|
async handle(request: RequestInstance) {
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
34
|
+
const channel = resolveDashboardDeliveryChannel(
|
|
35
|
+
request.url,
|
|
36
|
+
dashboardRequestValue(request, 'channel'),
|
|
37
|
+
)
|
|
35
38
|
if (!channel)
|
|
36
39
|
return response.json({ message: 'Channel must be email or sms.' }, 422)
|
|
37
40
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
+
try {
|
|
42
|
+
const records = await NotificationDelivery
|
|
43
|
+
.where('channel', '=', channel)
|
|
44
|
+
.orderByDesc('sent_at')
|
|
45
|
+
.orderByDesc('created_at')
|
|
46
|
+
.limit(500)
|
|
47
|
+
.get()
|
|
48
|
+
const deliveries = records.map(record => ({
|
|
41
49
|
id: Number(record.get('id')),
|
|
42
50
|
user_id: record.get('user_id') ? Number(record.get('user_id')) : null,
|
|
43
51
|
channel,
|
|
@@ -53,9 +61,11 @@ export default new Action({
|
|
|
53
61
|
sent_at: String(record.get('sent_at') || record.get('created_at') || ''),
|
|
54
62
|
created_at: String(record.get('created_at') || ''),
|
|
55
63
|
}))
|
|
56
|
-
.sort((a, b) => new Date(b.sent_at).getTime() - new Date(a.sent_at).getTime())
|
|
57
|
-
.slice(0, 500)
|
|
58
64
|
|
|
59
|
-
|
|
65
|
+
return response.json(deliveries)
|
|
66
|
+
}
|
|
67
|
+
catch (error) {
|
|
68
|
+
return dashboardOperationalError(error, 'Notification deliveries could not be loaded.', 'NotificationDeliveryIndexAction')
|
|
69
|
+
}
|
|
60
70
|
},
|
|
61
71
|
})
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Action } from '@stacksjs/actions'
|
|
2
2
|
import { db } from '@stacksjs/database'
|
|
3
3
|
import { response } from '@stacksjs/router'
|
|
4
|
+
import { dashboardOperationalError } from '../dashboard-response'
|
|
4
5
|
import {
|
|
5
6
|
type NotificationDeliveryRow,
|
|
6
7
|
serializeNotificationDelivery,
|
|
@@ -22,99 +23,110 @@ const DELIVERY_STATUSES = ['sent', 'delivered', 'failed', 'pending'] as const
|
|
|
22
23
|
|
|
23
24
|
function numericCount(value: number | string | undefined): number {
|
|
24
25
|
const count = Number(value || 0)
|
|
25
|
-
|
|
26
|
+
if (!Number.isSafeInteger(count) || count < 0)
|
|
27
|
+
throw new TypeError('Notification delivery count must be a non-negative integer.')
|
|
28
|
+
return count
|
|
26
29
|
}
|
|
27
30
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
.selectAll()
|
|
52
|
-
.orderBy('sent_at', 'desc')
|
|
53
|
-
.limit(8)
|
|
54
|
-
.execute() as Promise<NotificationDeliveryRow[]>,
|
|
55
|
-
])
|
|
31
|
+
async function notificationDeliveryOverview() {
|
|
32
|
+
const [totalRow, statusRows, channelRows, recentRows] = await Promise.all([
|
|
33
|
+
db
|
|
34
|
+
.selectFrom('notification_deliveries')
|
|
35
|
+
.select(db.fn.count('id').as('count'))
|
|
36
|
+
.executeTakeFirst() as Promise<CountRow | undefined>,
|
|
37
|
+
db
|
|
38
|
+
.selectFrom('notification_deliveries')
|
|
39
|
+
.select(['status', db.fn.count('id').as('count')])
|
|
40
|
+
.groupBy('status')
|
|
41
|
+
.execute() as Promise<StatusCountRow[]>,
|
|
42
|
+
db
|
|
43
|
+
.selectFrom('notification_deliveries')
|
|
44
|
+
.select(['channel', 'status', db.fn.count('id').as('count')])
|
|
45
|
+
.groupBy(['channel', 'status'])
|
|
46
|
+
.execute() as Promise<ChannelStatusCountRow[]>,
|
|
47
|
+
db
|
|
48
|
+
.selectFrom('notification_deliveries')
|
|
49
|
+
.selectAll()
|
|
50
|
+
.orderBy('sent_at', 'desc')
|
|
51
|
+
.limit(8)
|
|
52
|
+
.execute() as Promise<NotificationDeliveryRow[]>,
|
|
53
|
+
])
|
|
56
54
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
55
|
+
const statuses = Object.fromEntries(
|
|
56
|
+
DELIVERY_STATUSES.map(status => [status, 0]),
|
|
57
|
+
) as Record<typeof DELIVERY_STATUSES[number], number>
|
|
60
58
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
59
|
+
for (const row of statusRows) {
|
|
60
|
+
if (DELIVERY_STATUSES.includes(row.status as typeof DELIVERY_STATUSES[number]))
|
|
61
|
+
statuses[row.status as typeof DELIVERY_STATUSES[number]] = numericCount(row.count)
|
|
62
|
+
}
|
|
65
63
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
64
|
+
const channelMap = new Map<string, {
|
|
65
|
+
channel: string
|
|
66
|
+
total: number
|
|
67
|
+
sent: number
|
|
68
|
+
delivered: number
|
|
69
|
+
failed: number
|
|
70
|
+
pending: number
|
|
71
|
+
}>()
|
|
74
72
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
}
|
|
85
|
-
const count = numericCount(row.count)
|
|
86
|
-
entry.total += count
|
|
87
|
-
if (DELIVERY_STATUSES.includes(row.status as typeof DELIVERY_STATUSES[number]))
|
|
88
|
-
entry[row.status as typeof DELIVERY_STATUSES[number]] += count
|
|
89
|
-
channelMap.set(channel, entry)
|
|
73
|
+
for (const row of channelRows) {
|
|
74
|
+
const channel = String(row.channel || 'unknown')
|
|
75
|
+
const entry = channelMap.get(channel) || {
|
|
76
|
+
channel,
|
|
77
|
+
total: 0,
|
|
78
|
+
sent: 0,
|
|
79
|
+
delivered: 0,
|
|
80
|
+
failed: 0,
|
|
81
|
+
pending: 0,
|
|
90
82
|
}
|
|
83
|
+
const count = numericCount(row.count)
|
|
84
|
+
entry.total += count
|
|
85
|
+
if (DELIVERY_STATUSES.includes(row.status as typeof DELIVERY_STATUSES[number]))
|
|
86
|
+
entry[row.status as typeof DELIVERY_STATUSES[number]] += count
|
|
87
|
+
channelMap.set(channel, entry)
|
|
88
|
+
}
|
|
91
89
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
90
|
+
const channels = [...channelMap.values()]
|
|
91
|
+
.map(channel => ({
|
|
92
|
+
...channel,
|
|
93
|
+
success_rate: channel.total > 0
|
|
94
|
+
? Number((((channel.sent + channel.delivered) / channel.total) * 100).toFixed(1))
|
|
95
|
+
: 0,
|
|
96
|
+
}))
|
|
97
|
+
.sort((a, b) => b.total - a.total || a.channel.localeCompare(b.channel))
|
|
100
98
|
|
|
101
|
-
|
|
102
|
-
|
|
99
|
+
const total = numericCount(totalRow?.count)
|
|
100
|
+
const successful = statuses.sent + statuses.delivered
|
|
101
|
+
|
|
102
|
+
return {
|
|
103
|
+
stats: {
|
|
104
|
+
total,
|
|
105
|
+
successful,
|
|
106
|
+
failed: statuses.failed,
|
|
107
|
+
pending: statuses.pending,
|
|
108
|
+
success_rate: total > 0
|
|
109
|
+
? Number(((successful / total) * 100).toFixed(1))
|
|
110
|
+
: 0,
|
|
111
|
+
active_channels: channels.length,
|
|
112
|
+
},
|
|
113
|
+
statuses,
|
|
114
|
+
channels,
|
|
115
|
+
recent: recentRows.map(serializeNotificationDelivery),
|
|
116
|
+
}
|
|
117
|
+
}
|
|
103
118
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
channels,
|
|
117
|
-
recent: recentRows.map(serializeNotificationDelivery),
|
|
118
|
-
})
|
|
119
|
+
export default new Action({
|
|
120
|
+
name: 'Notification Delivery Overview',
|
|
121
|
+
description: 'Returns aggregate transport health and recent notification delivery attempts.',
|
|
122
|
+
method: 'GET',
|
|
123
|
+
apiResponse: true,
|
|
124
|
+
async handle() {
|
|
125
|
+
try {
|
|
126
|
+
return response.json(await notificationDeliveryOverview())
|
|
127
|
+
}
|
|
128
|
+
catch (error) {
|
|
129
|
+
return dashboardOperationalError(error, 'Notification delivery overview could not be loaded.', 'NotificationDeliveryOverviewAction')
|
|
130
|
+
}
|
|
119
131
|
},
|
|
120
132
|
})
|
|
@@ -4,6 +4,7 @@ import { Action } from '@stacksjs/actions'
|
|
|
4
4
|
import { notify } from '@stacksjs/notifications'
|
|
5
5
|
import { NotificationDelivery } from '@stacksjs/orm'
|
|
6
6
|
import { response } from '@stacksjs/router'
|
|
7
|
+
import { dashboardOperationalError } from '../dashboard-response'
|
|
7
8
|
import { parseDeliveryMetadata } from './notification-delivery'
|
|
8
9
|
|
|
9
10
|
type RetryChannel = 'email' | 'sms'
|
|
@@ -19,10 +20,16 @@ export default new Action({
|
|
|
19
20
|
apiResponse: true,
|
|
20
21
|
async handle(request: RequestInstance) {
|
|
21
22
|
const id = Number(request.getParam('id'))
|
|
22
|
-
if (!Number.
|
|
23
|
-
return response.json({ message: 'A valid delivery ID is required.' },
|
|
23
|
+
if (!Number.isSafeInteger(id) || id <= 0)
|
|
24
|
+
return response.json({ message: 'A valid delivery ID is required.' }, 400)
|
|
24
25
|
|
|
25
|
-
|
|
26
|
+
let delivery: Awaited<ReturnType<typeof NotificationDelivery.find>>
|
|
27
|
+
try {
|
|
28
|
+
delivery = await NotificationDelivery.find(id)
|
|
29
|
+
}
|
|
30
|
+
catch (error) {
|
|
31
|
+
return dashboardOperationalError(error, 'Notification delivery could not be loaded.', 'NotificationDeliveryRetryAction.lookup')
|
|
32
|
+
}
|
|
26
33
|
if (!delivery)
|
|
27
34
|
return response.json({ message: 'Notification delivery not found.' }, 404)
|
|
28
35
|
|
|
@@ -34,27 +41,33 @@ export default new Action({
|
|
|
34
41
|
if (!recipientValue)
|
|
35
42
|
return response.json({ message: 'The original delivery has no recipient.' }, 422)
|
|
36
43
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
delivery.get('
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
44
|
+
let results: Awaited<ReturnType<typeof notify>>
|
|
45
|
+
try {
|
|
46
|
+
const channels: NotificationChannel[] = [channel]
|
|
47
|
+
results = await notify(
|
|
48
|
+
{
|
|
49
|
+
userId: delivery.get('user_id') ? Number(delivery.get('user_id')) : undefined,
|
|
50
|
+
...(channel === 'email' ? { email: recipientValue } : { phone: recipientValue }),
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
subject: String(delivery.get('subject') || ''),
|
|
54
|
+
body: String(delivery.get('body') || ''),
|
|
55
|
+
data: parseDeliveryMetadata(
|
|
56
|
+
delivery.get('metadata'),
|
|
57
|
+
`notification delivery ${id} metadata`,
|
|
58
|
+
),
|
|
59
|
+
},
|
|
60
|
+
channels,
|
|
61
|
+
{ ignorePreferences: true },
|
|
62
|
+
)
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
return dashboardOperationalError(error, 'The notification retry could not be completed.', 'NotificationDeliveryRetryAction.provider', 502)
|
|
66
|
+
}
|
|
54
67
|
|
|
55
68
|
const result = results[0]
|
|
56
69
|
if (!result?.success)
|
|
57
|
-
return
|
|
70
|
+
return dashboardOperationalError(result?.error, 'The notification retry failed.', 'NotificationDeliveryRetryAction.result', 502)
|
|
58
71
|
|
|
59
72
|
return response.json({ success: true })
|
|
60
73
|
},
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Action } from '@stacksjs/actions'
|
|
2
2
|
import { config } from '@stacksjs/config'
|
|
3
3
|
import { db } from '@stacksjs/database'
|
|
4
|
-
import {
|
|
4
|
+
import { dashboardOperationalError } from '../dashboard-response'
|
|
5
5
|
import { dashboardQueryColumns, mapDashboardQueryLog, type QueryLogSourceRow } from './query-dashboard'
|
|
6
6
|
|
|
7
7
|
export default new Action({
|
|
@@ -27,9 +27,7 @@ export default new Action({
|
|
|
27
27
|
}
|
|
28
28
|
}
|
|
29
29
|
catch (error) {
|
|
30
|
-
return
|
|
31
|
-
message: error instanceof Error ? error.message : 'Query logs could not be loaded.',
|
|
32
|
-
}, 503)
|
|
30
|
+
return dashboardOperationalError(error, 'Query logs could not be loaded.', 'QueryDashboardAction')
|
|
33
31
|
}
|
|
34
32
|
},
|
|
35
33
|
})
|
|
@@ -1,6 +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 { response } from '@stacksjs/router'
|
|
5
|
+
import { dashboardOperationalError } from '../dashboard-response'
|
|
4
6
|
import { dashboardQueryColumns, mapDashboardQueryLog, type QueryLogSourceRow } from './query-dashboard'
|
|
5
7
|
|
|
6
8
|
export default new Action({
|
|
@@ -27,9 +29,7 @@ export default new Action({
|
|
|
27
29
|
return { query: mapDashboardQueryLog(row as QueryLogSourceRow) }
|
|
28
30
|
}
|
|
29
31
|
catch (error) {
|
|
30
|
-
return
|
|
31
|
-
message: error instanceof Error ? error.message : 'Query log could not be loaded.',
|
|
32
|
-
}, 503)
|
|
32
|
+
return dashboardOperationalError(error, 'Query log could not be loaded.', 'QueryShowAction')
|
|
33
33
|
}
|
|
34
34
|
},
|
|
35
35
|
})
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Action } from '@stacksjs/actions'
|
|
2
2
|
import { FailedJob, Job } from '@stacksjs/orm'
|
|
3
3
|
import { getGlobalMetrics } from '@stacksjs/queue'
|
|
4
|
-
import {
|
|
4
|
+
import { dashboardOperationalError } from '../dashboard-response'
|
|
5
5
|
|
|
6
6
|
interface QueueBucket {
|
|
7
7
|
pending: number
|
|
@@ -81,9 +81,7 @@ export default new Action({
|
|
|
81
81
|
return { queues, stats, queueConnected: true }
|
|
82
82
|
}
|
|
83
83
|
catch (error) {
|
|
84
|
-
return
|
|
85
|
-
message: error instanceof Error ? error.message : 'Queue statistics could not be loaded.',
|
|
86
|
-
}, 503)
|
|
84
|
+
return dashboardOperationalError(error, 'Queue statistics could not be loaded.', 'QueueStatsAction')
|
|
87
85
|
}
|
|
88
86
|
},
|
|
89
87
|
})
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Action } from '@stacksjs/actions'
|
|
2
2
|
import { getActiveJobCount, getWorkerTracker, isWorkerRunning } from '@stacksjs/queue'
|
|
3
|
-
import {
|
|
3
|
+
import { dashboardOperationalError } from '../dashboard-response'
|
|
4
4
|
|
|
5
5
|
export default new Action({
|
|
6
6
|
name: 'QueueWorkersAction',
|
|
@@ -41,9 +41,7 @@ export default new Action({
|
|
|
41
41
|
}
|
|
42
42
|
}
|
|
43
43
|
catch (error) {
|
|
44
|
-
return
|
|
45
|
-
message: error instanceof Error ? error.message : 'Queue workers could not be loaded.',
|
|
46
|
-
}, 503)
|
|
44
|
+
return dashboardOperationalError(error, 'Queue workers could not be loaded.', 'QueueWorkersAction')
|
|
47
45
|
}
|
|
48
46
|
},
|
|
49
47
|
})
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import type { RequestInstance } from '@stacksjs/types'
|
|
1
2
|
import { Action } from '@stacksjs/actions'
|
|
2
3
|
import { deletePermission } from '@stacksjs/auth'
|
|
3
4
|
import { response } from '@stacksjs/router'
|
|
5
|
+
import { rbacActionError } from './rbac-response'
|
|
4
6
|
|
|
5
7
|
/**
|
|
6
8
|
* `DELETE /api/dashboard/rbac/permissions/:name` (stacksjs/stacks#1845).
|
|
@@ -14,13 +16,12 @@ export default new Action({
|
|
|
14
16
|
description: 'Hard-delete a permission + its pivot rows.',
|
|
15
17
|
method: 'DELETE',
|
|
16
18
|
apiResponse: true,
|
|
17
|
-
async handle(request) {
|
|
18
|
-
const name =
|
|
19
|
+
async handle(request: RequestInstance) {
|
|
20
|
+
const name = request.getParam('name').trim()
|
|
19
21
|
if (!name) {
|
|
20
22
|
return response.json({ error: '`name` route param is required.' }, 400)
|
|
21
23
|
}
|
|
22
|
-
const
|
|
23
|
-
const guardName = (url.searchParams.get('guard') || 'web').trim()
|
|
24
|
+
const guardName = String(request.get('guard', 'web')).trim()
|
|
24
25
|
if (!guardName || guardName.length > 60) {
|
|
25
26
|
return response.json({ error: 'Invalid guard name.' }, 400)
|
|
26
27
|
}
|
|
@@ -30,8 +31,7 @@ export default new Action({
|
|
|
30
31
|
return { deleted: true, name, guardName }
|
|
31
32
|
}
|
|
32
33
|
catch (err) {
|
|
33
|
-
|
|
34
|
-
return response.json({ error: err instanceof Error ? err.message : 'unknown error' }, 500)
|
|
34
|
+
return rbacActionError(err, 'The permission could not be deleted.', 'PermissionDestroyAction')
|
|
35
35
|
}
|
|
36
36
|
},
|
|
37
37
|
})
|