@stacksjs/defaults 0.75.94 → 0.75.96
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-dashboard/SKILL.md +42 -0
- package/ai/skills/stacks-models/SKILL.md +73 -0
- package/ide/vscode/package.json +1 -1
- package/package.json +3 -3
- package/resources/components/NativeAccountButton.stx +8 -3
- package/resources/components/NativeComponents.test.ts +26 -1
- package/routes/dashboard.ts +68 -27
|
@@ -311,6 +311,48 @@ review and must not be silently recorded.
|
|
|
311
311
|
- `/kanban` - model-backed board management
|
|
312
312
|
- `/ci`, `/buddy` - CI and Buddy workflows
|
|
313
313
|
|
|
314
|
+
## Who may reach a dashboard surface
|
|
315
|
+
|
|
316
|
+
Two axes, declared in different places.
|
|
317
|
+
|
|
318
|
+
**Route groups** are gated in `config/dashboard.ts`, keyed by the group's path
|
|
319
|
+
prefix:
|
|
320
|
+
|
|
321
|
+
```ts
|
|
322
|
+
access: {
|
|
323
|
+
'/api/commerce': ['admin', 'superadmin'],
|
|
324
|
+
'/dashboard/commerce': ['admin', 'superadmin'],
|
|
325
|
+
'/cms': ['admin', 'editor'],
|
|
326
|
+
}
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
Each entry appends `role:<the list>` after that group's `auth`, so any one of
|
|
330
|
+
the named roles admits. Every group ships `auth` and nothing more, so an
|
|
331
|
+
omitted group stays reachable by any signed-in user; naming it is the only way
|
|
332
|
+
to tighten it, since `defaults/routes/dashboard.ts` is framework-owned
|
|
333
|
+
(stacksjs/stacks#2883). Keyed by prefix, so gating `/api/commerce` does not
|
|
334
|
+
gate `/dashboard/commerce`: name both when you mean both. The prefixes are a
|
|
335
|
+
typed union, so a typo is a compile error rather than a gate that is quietly
|
|
336
|
+
absent.
|
|
337
|
+
|
|
338
|
+
**A model's own generated API** is gated on the model, not here: either
|
|
339
|
+
`dashboard: { roles: ['admin'] }`, which gates the sidebar row and every
|
|
340
|
+
generated route together, or `useApi.middleware` for per-ability control. See
|
|
341
|
+
`stacks-models`.
|
|
342
|
+
|
|
343
|
+
**The sidebar** filters itself. A row carrying roles (from a model's
|
|
344
|
+
`dashboard.roles` or a `nav` item's `roles`) is hidden client-side from viewers
|
|
345
|
+
holding none of them, permissively for unauthenticated viewers so the local dev
|
|
346
|
+
dashboard stays usable (#1843). That is presentation: it hides the link, not
|
|
347
|
+
the endpoint, so it is never the gate on its own.
|
|
348
|
+
|
|
349
|
+
**SSR pages** take the same middleware names as an API route, because the page
|
|
350
|
+
servers build their registry from `app/Middleware.ts`:
|
|
351
|
+
|
|
352
|
+
```stx
|
|
353
|
+
definePageMeta({ middleware: ['auth', 'role:admin'] })
|
|
354
|
+
```
|
|
355
|
+
|
|
314
356
|
## Dashboard Components (250+)
|
|
315
357
|
|
|
316
358
|
### Layout Components
|
|
@@ -132,6 +132,79 @@ shape, authorization boundary, or aggregation differs from generic CRUD. Do
|
|
|
132
132
|
not expose a sensitive model through unguarded generated routes just because a
|
|
133
133
|
separate dashboard endpoint is protected.
|
|
134
134
|
|
|
135
|
+
#### Who may reach the generated routes
|
|
136
|
+
|
|
137
|
+
Omit `middleware` and reads AND writes get `auth`: an undeclared read route is
|
|
138
|
+
how a customer list leaks (#2224). Four declaration forms, narrowest last:
|
|
139
|
+
|
|
140
|
+
```ts
|
|
141
|
+
middleware: ['auth'] // every ability
|
|
142
|
+
middleware: [] // every ability public; warns at boot
|
|
143
|
+
middleware: { read: [], write: ['auth'] } // public catalog, guarded writes
|
|
144
|
+
middleware: { write: ['auth'], destroy: ['auth', 'role:admin'] } // per ability
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
`read` covers `index` and `show`; `write` covers `store`, `update` and
|
|
148
|
+
`destroy`; an ability name overrides its side; and `destroy` covers bulk-delete
|
|
149
|
+
with the single delete. Use the per-ability form for the "fewer actions" half of
|
|
150
|
+
a tiered role - "may file an order, may not void one" is `store` versus
|
|
151
|
+
`destroy`, which the read/write split cannot express. Declaring one ability
|
|
152
|
+
leaves the other four on `auth`, so narrowing one route never widens another.
|
|
153
|
+
|
|
154
|
+
A model is also gated by the roles it declares for the dashboard:
|
|
155
|
+
|
|
156
|
+
```ts
|
|
157
|
+
dashboard: { roles: ['admin'] } // hides the sidebar row AND gates every generated route
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
That one declaration covers both surfaces (#2883). It appends `role:admin` to
|
|
161
|
+
the resolved middleware of each ability, unless that ability names a `role:`
|
|
162
|
+
itself (explicit wins) or was explicitly emptied (a deliberately public surface
|
|
163
|
+
is reported at boot, not quietly closed). `dashboard: { roles: [...], enforce:
|
|
164
|
+
false }` keeps the row hidden and the API as the middleware leaves it, for a row
|
|
165
|
+
hidden only to reduce clutter.
|
|
166
|
+
|
|
167
|
+
Row-level scoping is separate and already on: a model scopes rows by an
|
|
168
|
+
`ownership` config or a `team_id` column, and `security.api.rowScoping` defaults
|
|
169
|
+
to `'deny'`, which withholds store/update/destroy from a model that scopes
|
|
170
|
+
neither (#2375). A role says who may call the route; ownership says which rows
|
|
171
|
+
are theirs. A tiered app usually needs both.
|
|
172
|
+
|
|
173
|
+
#### Per-row rules: a policy
|
|
174
|
+
|
|
175
|
+
Middleware answers "may this caller use this endpoint". A policy answers "may
|
|
176
|
+
this caller do this to THIS row", which is the part a role cannot express. The
|
|
177
|
+
generated routes consult one when the model has one (#2883):
|
|
178
|
+
|
|
179
|
+
| Route | Ability | Subject |
|
|
180
|
+
|---|---|---|
|
|
181
|
+
| `index` | `viewAny` | none |
|
|
182
|
+
| `show` | `view` | the row |
|
|
183
|
+
| `store` | `create` | none |
|
|
184
|
+
| `update` | `update` | the existing row |
|
|
185
|
+
| `destroy`, bulk-delete | `delete` | the row, one call each |
|
|
186
|
+
|
|
187
|
+
```ts
|
|
188
|
+
// app/Policies/OrderPolicy.ts
|
|
189
|
+
export default class OrderPolicy {
|
|
190
|
+
viewAny(user) { return user !== null }
|
|
191
|
+
view(user, order) { return order.customer_id === user?.id }
|
|
192
|
+
delete(user, order) { return order.status === 'draft' && order.customer_id === user?.id }
|
|
193
|
+
}
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
Register it in `app/Gates.ts` under `policies`, or name it `<Model>Policy` under
|
|
197
|
+
`app/Policies/` and it is discovered by convention. A model with no policy is
|
|
198
|
+
unaffected, so adding one is opt-in per model. A refusal is a 403 carrying the
|
|
199
|
+
policy's own message; an ability the policy does not implement is a refusal too,
|
|
200
|
+
so a partial policy does not leave the rest open.
|
|
201
|
+
|
|
202
|
+
`Gate.inspectFor(model, ability, user, row)` is the same check from your own
|
|
203
|
+
code, keyed by model NAME rather than by instance - which is what a dashboard
|
|
204
|
+
action holding a query-builder row needs. It answers null for "no policy
|
|
205
|
+
registered", which is deliberately distinct from a denial; `Gate.allowsFor`
|
|
206
|
+
is the boolean form that reads null as allowed.
|
|
207
|
+
|
|
135
208
|
Generated store and update routes accept both spellings of every fillable
|
|
136
209
|
attribute and each foreign key implied by `belongsTo`. Declaring Product as a
|
|
137
210
|
belongs-to relation therefore accepts `productId` or `product_id` without
|
package/ide/vscode/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"publisher": "Stacks",
|
|
3
3
|
"name": "vscode-stacks",
|
|
4
4
|
"displayName": "Stacks",
|
|
5
|
-
"version": "0.75.
|
|
5
|
+
"version": "0.75.96",
|
|
6
6
|
"description": "Stacks, built into VS Code: stx templates, pickier linting and formatting, encrypted env files, the dev server preview and every buddy command. No other extensions required.",
|
|
7
7
|
"license": "MIT",
|
|
8
8
|
"funding": "https://github.com/sponsors/chrisbbreuer",
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@stacksjs/defaults",
|
|
3
3
|
"type": "module",
|
|
4
4
|
"sideEffects": false,
|
|
5
|
-
"version": "0.75.
|
|
5
|
+
"version": "0.75.96",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
8
8
|
"url": "git+https://github.com/stacksjs/stacks.git",
|
|
@@ -55,8 +55,8 @@
|
|
|
55
55
|
"dependencies": {
|
|
56
56
|
"@iconify-json/f7": "^1.2.2",
|
|
57
57
|
"@iconify-json/hugeicons": "^1.2.35",
|
|
58
|
-
"@stacksjs/mobile": "^0.75.
|
|
59
|
-
"@stacksjs/sanitizer": "^0.2.
|
|
58
|
+
"@stacksjs/mobile": "^0.75.96",
|
|
59
|
+
"@stacksjs/sanitizer": "^0.2.406",
|
|
60
60
|
"@xterm/addon-fit": "^0.11.0",
|
|
61
61
|
"@xterm/xterm": "^6.0.0",
|
|
62
62
|
"ts-qr-codes": "^0.1.8"
|
|
@@ -12,7 +12,7 @@ interface NativeAccountButtonProps {
|
|
|
12
12
|
label?: string
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
defineProps<NativeAccountButtonProps>()
|
|
16
16
|
</script>
|
|
17
17
|
|
|
18
18
|
<script client>
|
|
@@ -21,6 +21,11 @@ import { haptics } from '@stacksjs/mobile'
|
|
|
21
21
|
// The account at the top right of a tab's screen, as iOS apps put it: the
|
|
22
22
|
// tab bar keeps its tabs for the places the app is used from. Reactive, so it
|
|
23
23
|
// follows the signed-in person and the count as they change.
|
|
24
|
+
// Every prop through useReactiveProp, under names of its own: read as plain
|
|
25
|
+
// {{ label }}, a host's client signal of the same name (a pull-to-refresh's
|
|
26
|
+
// "Refreshing") was shown instead of the button's own.
|
|
27
|
+
const accountHref = useReactiveProp<string>('href', '')
|
|
28
|
+
const accountLabel = useReactiveProp<string>('label', 'Account')
|
|
24
29
|
const accountName = useReactiveProp<string>('name', '')
|
|
25
30
|
const accountImage = useReactiveProp<string>('image', '')
|
|
26
31
|
const accountBadge = useReactiveProp<string | number>('badge', '')
|
|
@@ -42,8 +47,8 @@ function tapAccount(): void {
|
|
|
42
47
|
}
|
|
43
48
|
</script>
|
|
44
49
|
|
|
45
|
-
<a href="
|
|
46
|
-
<span class="native-account-label">{{
|
|
50
|
+
<a :href="accountHref()" class="native-account-button" draggable="false" data-stx-link data-stx-prefetch="eager" @click="tapAccount()">
|
|
51
|
+
<span class="native-account-label">{{ accountLabel() }}<span :if="accountCount()">, {{ accountCount() }}</span></span>
|
|
47
52
|
<span aria-hidden="true" class="native-account-face" :style="accountPhoto()">
|
|
48
53
|
<span :if="!accountImage() && accountInitials()">{{ accountInitials() }}</span>
|
|
49
54
|
<i :if="!accountImage() && !accountInitials()" class="i-lucide-circle-user-round native-account-glyph"></i>
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { describe, expect, it } from 'bun:test'
|
|
1
|
+
import { describe, expect, it, spyOn } from 'bun:test'
|
|
2
2
|
import { readFileSync } from 'node:fs'
|
|
3
3
|
import { join } from 'node:path'
|
|
4
|
+
import { processDirectives } from '@stacksjs/stx'
|
|
4
5
|
|
|
5
6
|
const read = (name: string) => readFileSync(join(import.meta.dir, `${name}.stx`), 'utf8')
|
|
6
7
|
|
|
@@ -113,6 +114,30 @@ describe('Native components', () => {
|
|
|
113
114
|
expect(control).toContain("emit('change', value)")
|
|
114
115
|
})
|
|
115
116
|
|
|
117
|
+
it('renders its segments in the browser, from whatever the options prop holds', async () => {
|
|
118
|
+
// `options` is both the prop and the derived list the segments loop over.
|
|
119
|
+
// stx once took the prop's value in the render context for server data and
|
|
120
|
+
// turned the `:for` into a server `@foreach(options())`, which rendered an
|
|
121
|
+
// empty track (the workout player's rest picker).
|
|
122
|
+
const warn = spyOn(console, 'warn')
|
|
123
|
+
try {
|
|
124
|
+
const html = await processDirectives(
|
|
125
|
+
`<NativeSegmentedControl label="Rest" :options="[{ label: '30s', value: 30 }, { label: '60s', value: 60 }]" />`,
|
|
126
|
+
{},
|
|
127
|
+
join(import.meta.dir, 'page.stx'),
|
|
128
|
+
{ componentsDir: import.meta.dir, partialsDir: import.meta.dir } as Parameters<typeof processDirectives>[3],
|
|
129
|
+
new Set<string>(),
|
|
130
|
+
)
|
|
131
|
+
expect(html).toContain('class="native-segmented"')
|
|
132
|
+
expect(html).toContain(':for="(option, index) in options()"')
|
|
133
|
+
expect(html).not.toContain('Foreach Error')
|
|
134
|
+
expect(warn.mock.calls.some(call => String(call[0]).includes('is not iterable server-side'))).toBe(false)
|
|
135
|
+
}
|
|
136
|
+
finally {
|
|
137
|
+
warn.mockRestore()
|
|
138
|
+
}
|
|
139
|
+
})
|
|
140
|
+
|
|
116
141
|
it('steps the tab bar away for a screen that is a task of its own', () => {
|
|
117
142
|
const shell = read('NativeAppShell')
|
|
118
143
|
expect(shell).toContain('.native-app-shell:has([data-native-hide-tab-bar]) .native-app-tab-slot { display: none; }')
|
package/routes/dashboard.ts
CHANGED
|
@@ -16,7 +16,47 @@
|
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
18
|
import { isLocalDeployment } from '@stacksjs/env'
|
|
19
|
-
import {
|
|
19
|
+
import { projectPath } from '@stacksjs/path'
|
|
20
|
+
import { dashboardGroupMiddleware, describeUnknownDashboardGroups, resolveDashboardAccess, route } from '@stacksjs/router'
|
|
21
|
+
import { log } from '@stacksjs/logging/runtime'
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Which roles may reach each group below, from `config/dashboard.ts`
|
|
25
|
+
* (stacksjs/stacks#2883).
|
|
26
|
+
*
|
|
27
|
+
* Every group here shipped `middleware: 'auth'` and nothing more, so any
|
|
28
|
+
* signed-in user could reach `/api/commerce`, `/cms` or `/models` whatever the
|
|
29
|
+
* sidebar showed them - and an app could not tighten it, because this file is
|
|
30
|
+
* framework-owned and nothing attached middleware to a path.
|
|
31
|
+
*
|
|
32
|
+
* Read by path rather than through `@stacksjs/config`, matching how the ORM
|
|
33
|
+
* route generator reads `config/security.ts`: registration runs at import time,
|
|
34
|
+
* which is before `overridesReady` resolves, so the config package would hand
|
|
35
|
+
* back framework defaults here. A missing or malformed file resolves to no
|
|
36
|
+
* gating at all, which is exactly how this file behaved before.
|
|
37
|
+
*/
|
|
38
|
+
const dashboardAccess = await (async () => {
|
|
39
|
+
try {
|
|
40
|
+
const configured = (await import(projectPath('config/dashboard.ts'))).default?.access
|
|
41
|
+
return resolveDashboardAccess(configured)
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
return resolveDashboardAccess(undefined)
|
|
45
|
+
}
|
|
46
|
+
})()
|
|
47
|
+
|
|
48
|
+
{
|
|
49
|
+
// A typo'd prefix gates nothing, and would read as a gate that is quietly
|
|
50
|
+
// absent. Named once, not per group.
|
|
51
|
+
const report = describeUnknownDashboardGroups(dashboardAccess.unknown)
|
|
52
|
+
if (report)
|
|
53
|
+
log.warn(report)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** One group's middleware: its `auth` base, plus any role the app declared. */
|
|
57
|
+
function guarded(prefix: string, base?: Parameters<typeof dashboardGroupMiddleware>[2]) {
|
|
58
|
+
return dashboardGroupMiddleware(prefix, dashboardAccess, base)
|
|
59
|
+
}
|
|
20
60
|
|
|
21
61
|
// ============================================================================
|
|
22
62
|
// Email
|
|
@@ -138,9 +178,10 @@ route.get('/robots.txt', 'Actions/RobotsAction')
|
|
|
138
178
|
// a public AI endpoint (e.g. a homepage chat widget), it can register the
|
|
139
179
|
// route in `routes/api.ts` without the middleware — user routes load before
|
|
140
180
|
// framework routes, so the unauthenticated copy wins.
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
route.post('/
|
|
181
|
+
// Prefixed so `access` can name it; `/ai/ask` and `/ai/summary` as before.
|
|
182
|
+
route.group({ prefix: '/ai', middleware: guarded('/ai') }, () => {
|
|
183
|
+
route.post('/ask', 'Actions/AI/AskAction')
|
|
184
|
+
route.post('/summary', 'Actions/AI/SummaryAction')
|
|
144
185
|
})
|
|
145
186
|
|
|
146
187
|
// ============================================================================
|
|
@@ -152,7 +193,7 @@ route.group({ middleware: 'auth' }, () => {
|
|
|
152
193
|
// Always auth-gated.
|
|
153
194
|
// ============================================================================
|
|
154
195
|
|
|
155
|
-
route.group({ prefix: '/voide', middleware: '
|
|
196
|
+
route.group({ prefix: '/voide', middleware: guarded('/voide') }, () => {
|
|
156
197
|
route.get('/state', 'Actions/Buddy/BuddyStateAction')
|
|
157
198
|
route.post('/repo', 'Actions/Buddy/BuddyRepoOpenAction')
|
|
158
199
|
route.post('/repo/validate', 'Actions/Buddy/BuddyRepoValidateAction')
|
|
@@ -180,7 +221,7 @@ route.group({ prefix: '/voide', middleware: 'auth' }, () => {
|
|
|
180
221
|
// the newsletter signup lives at /api/email/subscribe — those stay open.
|
|
181
222
|
// ============================================================================
|
|
182
223
|
|
|
183
|
-
route.group({ prefix: '/dashboard', middleware: '
|
|
224
|
+
route.group({ prefix: '/dashboard', middleware: guarded('/dashboard') }, () => {
|
|
184
225
|
route.get('/home', 'Actions/Dashboard/DashboardHomeAction')
|
|
185
226
|
route.get('/stats', 'Actions/Dashboard/DashboardStatsAction')
|
|
186
227
|
route.get('/activity', 'Actions/Dashboard/DashboardActivityAction')
|
|
@@ -201,7 +242,7 @@ route.group({ prefix: '/dashboard', middleware: 'auth' }, () => {
|
|
|
201
242
|
// action must STILL scope to the authenticated user (derive the customer from
|
|
202
243
|
// `await request.user()`, never trust the `{id}` path param) to prevent an
|
|
203
244
|
// authenticated user from reaching another user's billing.
|
|
204
|
-
route.group({ prefix: '/payments', middleware: '
|
|
245
|
+
route.group({ prefix: '/payments', middleware: guarded('/payments') }, () => {
|
|
205
246
|
route.get('/fetch-customer/{id}', 'Actions/Payment/FetchPaymentCustomerAction')
|
|
206
247
|
route.get('/fetch-transaction-history/{id}', 'Actions/Payment/FetchTransactionHistoryAction')
|
|
207
248
|
route.get('/fetch-user-subscriptions/{id}', 'Actions/Payment/FetchUserSubscriptionsAction')
|
|
@@ -232,11 +273,11 @@ route.group({ prefix: '/payments', middleware: 'auth' }, () => {
|
|
|
232
273
|
// Auth-gated to match every other operational dashboard group — these expose
|
|
233
274
|
// internal job/queue and websocket state and were the only siblings missing
|
|
234
275
|
// `auth`, leaking infra telemetry to anonymous callers.
|
|
235
|
-
route.group({ prefix: '/queues', middleware: '
|
|
276
|
+
route.group({ prefix: '/queues', middleware: guarded('/queues') }, () => {
|
|
236
277
|
route.get('/', 'Actions/Queue/FetchQueuesAction')
|
|
237
278
|
})
|
|
238
279
|
|
|
239
|
-
route.group({ prefix: '/realtime', middleware: '
|
|
280
|
+
route.group({ prefix: '/realtime', middleware: guarded('/realtime') }, () => {
|
|
240
281
|
route.get('/websockets', 'Actions/Realtime/FetchWebsocketsAction')
|
|
241
282
|
route.get('/stats', 'Actions/Dashboard/Realtime/RealtimeStatsAction')
|
|
242
283
|
})
|
|
@@ -245,7 +286,7 @@ route.group({ prefix: '/realtime', middleware: 'auth' }, () => {
|
|
|
245
286
|
// Query Dashboard
|
|
246
287
|
// ============================================================================
|
|
247
288
|
|
|
248
|
-
route.group({ prefix: '/api/queries', middleware: '
|
|
289
|
+
route.group({ prefix: '/api/queries', middleware: guarded('/api/queries') }, () => {
|
|
249
290
|
route.get('/dashboard', 'Actions/Dashboard/Queries/QueryIndexAction')
|
|
250
291
|
route.get('/stats', 'Controllers/QueryController@getStats')
|
|
251
292
|
route.get('/recent', 'Controllers/QueryController@getRecentQueries')
|
|
@@ -260,7 +301,7 @@ route.group({ prefix: '/api/queries', middleware: 'auth' }, () => {
|
|
|
260
301
|
// Monitoring / Error Tracking
|
|
261
302
|
// ============================================================================
|
|
262
303
|
|
|
263
|
-
route.group({ prefix: '/api/monitoring', middleware: '
|
|
304
|
+
route.group({ prefix: '/api/monitoring', middleware: guarded('/api/monitoring') }, () => {
|
|
264
305
|
route.get('/errors', 'Actions/Monitoring/ErrorIndexAction')
|
|
265
306
|
route.get('/errors/stats', 'Actions/Monitoring/ErrorStatsAction')
|
|
266
307
|
route.get('/errors/timeline', 'Actions/Monitoring/ErrorTimelineAction')
|
|
@@ -280,7 +321,7 @@ route.group({ prefix: '/api/monitoring', middleware: 'auth' }, () => {
|
|
|
280
321
|
// is no longer a DB/CMS mirror. /cms below remains the authoring API.
|
|
281
322
|
// ============================================================================
|
|
282
323
|
|
|
283
|
-
route.group({ prefix: '/cms', middleware: '
|
|
324
|
+
route.group({ prefix: '/cms', middleware: guarded('/cms') }, () => {
|
|
284
325
|
route.get('/dashboard', 'Actions/Dashboard/Content/ContentDashboardAction')
|
|
285
326
|
route.get('/posts', 'Actions/Cms/PostIndexAction')
|
|
286
327
|
route.get('/posts/{id}', 'Actions/Cms/PostShowAction')
|
|
@@ -334,7 +375,7 @@ route.group({ prefix: '/cms', middleware: 'auth' }, () => {
|
|
|
334
375
|
// underlying actions, since user routes load before framework routes.
|
|
335
376
|
// ============================================================================
|
|
336
377
|
|
|
337
|
-
route.group({ prefix: '/api/commerce', middleware: '
|
|
378
|
+
route.group({ prefix: '/api/commerce', middleware: guarded('/api/commerce') }, () => {
|
|
338
379
|
route.get('/dashboard', 'Actions/Dashboard/Commerce/CommerceDashboardAction')
|
|
339
380
|
route.get('/pos', 'Actions/Dashboard/Commerce/PosIndexAction')
|
|
340
381
|
route.get('/products', 'Actions/Commerce/Product/ProductIndexAction')
|
|
@@ -509,7 +550,7 @@ route.group({ prefix: '/api/commerce', middleware: 'auth' }, () => {
|
|
|
509
550
|
// Analytics
|
|
510
551
|
// ============================================================================
|
|
511
552
|
|
|
512
|
-
route.group({ prefix: '/api/analytics', middleware: '
|
|
553
|
+
route.group({ prefix: '/api/analytics', middleware: guarded('/api/analytics') }, () => {
|
|
513
554
|
route.get('/sales', 'Actions/Dashboard/Analytics/SalesAnalyticsAction')
|
|
514
555
|
route.get('/web', 'Actions/Dashboard/Analytics/WebAnalyticsAction')
|
|
515
556
|
route.get('/blog', 'Actions/Dashboard/Analytics/BlogAnalyticsAction')
|
|
@@ -527,13 +568,13 @@ route.group({ prefix: '/api/analytics', middleware: 'auth' }, () => {
|
|
|
527
568
|
// Jobs & Queue
|
|
528
569
|
// ============================================================================
|
|
529
570
|
|
|
530
|
-
route.group({ prefix: '/jobs', middleware: '
|
|
571
|
+
route.group({ prefix: '/jobs', middleware: guarded('/jobs') }, () => {
|
|
531
572
|
route.get('/', 'Actions/Dashboard/Jobs/JobIndexAction')
|
|
532
573
|
route.get('/stats', 'Actions/Dashboard/Jobs/JobStatsAction')
|
|
533
574
|
route.post('/{id}/retry', 'Actions/Dashboard/Jobs/JobRetryAction')
|
|
534
575
|
})
|
|
535
576
|
|
|
536
|
-
route.group({ prefix: '/queue', middleware: '
|
|
577
|
+
route.group({ prefix: '/queue', middleware: guarded('/queue') }, () => {
|
|
537
578
|
route.get('/stats', 'Actions/Dashboard/Queue/QueueStatsAction')
|
|
538
579
|
route.get('/workers', 'Actions/Dashboard/Queue/QueueWorkersAction')
|
|
539
580
|
route.post('/retry-failed', 'Actions/Dashboard/Queue/QueueRetryFailedAction')
|
|
@@ -543,7 +584,7 @@ route.group({ prefix: '/queue', middleware: 'auth' }, () => {
|
|
|
543
584
|
// Releases
|
|
544
585
|
// ============================================================================
|
|
545
586
|
|
|
546
|
-
route.group({ prefix: '/releases', middleware: '
|
|
587
|
+
route.group({ prefix: '/releases', middleware: guarded('/releases') }, () => {
|
|
547
588
|
route.get('/', 'Actions/Dashboard/Releases/ReleaseIndexAction')
|
|
548
589
|
route.get('/stats', 'Actions/Dashboard/Releases/ReleaseIndexAction')
|
|
549
590
|
})
|
|
@@ -552,7 +593,7 @@ route.group({ prefix: '/releases', middleware: 'auth' }, () => {
|
|
|
552
593
|
// Settings
|
|
553
594
|
// ============================================================================
|
|
554
595
|
|
|
555
|
-
route.group({ prefix: '/api/settings', middleware: '
|
|
596
|
+
route.group({ prefix: '/api/settings', middleware: guarded('/api/settings') }, () => {
|
|
556
597
|
route.get('/mail', 'Actions/Dashboard/Settings/MailSettingsGetAction')
|
|
557
598
|
route.put('/mail', 'Actions/Dashboard/Settings/MailSettingsUpdateAction')
|
|
558
599
|
})
|
|
@@ -561,7 +602,7 @@ route.group({ prefix: '/api/settings', middleware: 'auth' }, () => {
|
|
|
561
602
|
// Data Management
|
|
562
603
|
// ============================================================================
|
|
563
604
|
|
|
564
|
-
route.group({ prefix: '/api/data', middleware: '
|
|
605
|
+
route.group({ prefix: '/api/data', middleware: guarded('/api/data') }, () => {
|
|
565
606
|
route.get('/subscribers', 'Actions/Dashboard/Data/SubscriberIndexAction')
|
|
566
607
|
route.get('/teams', 'Actions/Dashboard/Data/TeamIndexAction')
|
|
567
608
|
route.get('/users', 'Actions/Dashboard/Data/UserIndexAction')
|
|
@@ -572,7 +613,7 @@ route.group({ prefix: '/api/data', middleware: 'auth' }, () => {
|
|
|
572
613
|
// Infrastructure
|
|
573
614
|
// ============================================================================
|
|
574
615
|
|
|
575
|
-
route.group({ prefix: '/infrastructure', middleware: '
|
|
616
|
+
route.group({ prefix: '/infrastructure', middleware: guarded('/infrastructure') }, () => {
|
|
576
617
|
route.get('/servers', 'Actions/Dashboard/Infrastructure/ServerIndexAction')
|
|
577
618
|
route.get('/dns', 'Actions/Dashboard/Infrastructure/DnsIndexAction')
|
|
578
619
|
route.get('/environment', 'Actions/Dashboard/Infrastructure/EnvironmentIndexAction')
|
|
@@ -589,7 +630,7 @@ route.get('/api/serverless', 'Actions/Dashboard/Cloud/ServerlessIndexAction').mi
|
|
|
589
630
|
// Dashboard Views — Commerce
|
|
590
631
|
// ============================================================================
|
|
591
632
|
|
|
592
|
-
route.group({ prefix: '/dashboard/commerce', middleware: '
|
|
633
|
+
route.group({ prefix: '/dashboard/commerce', middleware: guarded('/dashboard/commerce') }, () => {
|
|
593
634
|
route.get('/customers', 'Actions/Dashboard/Commerce/CommerceCustomersAction')
|
|
594
635
|
route.get('/orders', 'Actions/Dashboard/Commerce/CommerceOrdersAction')
|
|
595
636
|
route.get('/products', 'Actions/Dashboard/Commerce/CommerceProductsAction')
|
|
@@ -605,7 +646,7 @@ route.group({ prefix: '/dashboard/commerce', middleware: 'auth' }, () => {
|
|
|
605
646
|
// Dashboard Views — CMS Content
|
|
606
647
|
// ============================================================================
|
|
607
648
|
|
|
608
|
-
route.group({ prefix: '/dashboard/cms', middleware: '
|
|
649
|
+
route.group({ prefix: '/dashboard/cms', middleware: guarded('/dashboard/cms') }, () => {
|
|
609
650
|
route.get('/posts', 'Actions/Dashboard/Content/PostIndexAction')
|
|
610
651
|
route.get('/pages', 'Actions/Dashboard/Content/PageIndexAction')
|
|
611
652
|
route.get('/categories', 'Actions/Dashboard/Content/CategoryIndexAction')
|
|
@@ -618,7 +659,7 @@ route.group({ prefix: '/dashboard/cms', middleware: 'auth' }, () => {
|
|
|
618
659
|
// Marketing
|
|
619
660
|
// ============================================================================
|
|
620
661
|
|
|
621
|
-
route.group({ prefix: '/api/marketing', middleware: '
|
|
662
|
+
route.group({ prefix: '/api/marketing', middleware: guarded('/api/marketing') }, () => {
|
|
622
663
|
route.get('/campaigns', 'Actions/Dashboard/Marketing/CampaignIndexAction')
|
|
623
664
|
route.get('/lists', 'Actions/Dashboard/Marketing/ListIndexAction')
|
|
624
665
|
route.get('/social-posts', 'Actions/Dashboard/Marketing/SocialPostIndexAction')
|
|
@@ -628,7 +669,7 @@ route.group({ prefix: '/api/marketing', middleware: 'auth' }, () => {
|
|
|
628
669
|
// Notifications
|
|
629
670
|
// ============================================================================
|
|
630
671
|
|
|
631
|
-
route.group({ prefix: '/api/notifications', middleware: '
|
|
672
|
+
route.group({ prefix: '/api/notifications', middleware: guarded('/api/notifications') }, () => {
|
|
632
673
|
route.get('/dashboard', 'Actions/Dashboard/Notifications/NotificationDeliveryOverviewAction')
|
|
633
674
|
route.get('/email', 'Actions/Dashboard/Notifications/NotificationDeliveryIndexAction')
|
|
634
675
|
route.get('/sms', 'Actions/Dashboard/Notifications/NotificationDeliveryIndexAction')
|
|
@@ -639,7 +680,7 @@ route.group({ prefix: '/api/notifications', middleware: 'auth' }, () => {
|
|
|
639
680
|
// Library & Packages
|
|
640
681
|
// ============================================================================
|
|
641
682
|
|
|
642
|
-
route.group({ prefix: '/library', middleware: '
|
|
683
|
+
route.group({ prefix: '/library', middleware: guarded('/library') }, () => {
|
|
643
684
|
route.get('/dependencies', 'Actions/Dashboard/Library/DependencyIndexAction')
|
|
644
685
|
route.get('/packages', 'Actions/Dashboard/Library/PackageIndexAction')
|
|
645
686
|
route.get('/functions', 'Actions/Dashboard/Library/GetFunctions')
|
|
@@ -658,7 +699,7 @@ route.group({ prefix: '/library', middleware: 'auth' }, () => {
|
|
|
658
699
|
// Deployments
|
|
659
700
|
// ============================================================================
|
|
660
701
|
|
|
661
|
-
route.group({ prefix: '/deployments', middleware: '
|
|
702
|
+
route.group({ prefix: '/deployments', middleware: guarded('/deployments') }, () => {
|
|
662
703
|
route.get('/', 'Actions/Dashboard/Deployments/GetDeployments')
|
|
663
704
|
route.get('/count', 'Actions/Dashboard/Deployments/GetDeploymentCount')
|
|
664
705
|
route.get('/recent', 'Actions/Dashboard/Deployments/GetRecentDeployments')
|
|
@@ -674,7 +715,7 @@ route.group({ prefix: '/deployments', middleware: 'auth' }, () => {
|
|
|
674
715
|
// Models
|
|
675
716
|
// ============================================================================
|
|
676
717
|
|
|
677
|
-
route.group({ prefix: '/models', middleware: '
|
|
718
|
+
route.group({ prefix: '/models', middleware: guarded('/models') }, () => {
|
|
678
719
|
route.get('/', 'Actions/Dashboard/Models/GetModels')
|
|
679
720
|
route.get('/user-count', 'Actions/Dashboard/Models/GetUserCount')
|
|
680
721
|
route.get('/subscriber-count', 'Actions/Dashboard/Models/GetSubscriberCount')
|