@stacksjs/defaults 0.70.355 → 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-email/SKILL.md +38 -1
- 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 +4 -7
- package/app/Actions/Dashboard/Buddy/BuddyChatAction.ts +2 -3
- 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 +75 -61
- package/app/Actions/Dashboard/Commerce/CommercePrintDevicesAction.ts +2 -4
- package/app/Actions/Dashboard/Commerce/CommercePrintLogsAction.ts +2 -4
- package/app/Actions/Dashboard/Commerce/CommerceProductDetailAction.ts +3 -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/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/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 +23 -19
- 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 +4 -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 +18 -5
- package/app/Actions/Dashboard/Email/CapturedMailIndexAction.ts +2 -4
- package/app/Actions/Dashboard/Email/CapturedMailShowAction.ts +3 -4
- package/app/Actions/Dashboard/Infrastructure/InsightsAction.ts +15 -14
- package/app/Actions/Dashboard/Infrastructure/LogIndexAction.ts +2 -8
- package/app/Actions/Dashboard/Jobs/JobIndexAction.ts +2 -4
- package/app/Actions/Dashboard/Jobs/JobShowAction.ts +13 -8
- package/app/Actions/Dashboard/Jobs/JobStatsAction.ts +2 -4
- package/app/Actions/Dashboard/Kanban/BoardDestroyAction.ts +2 -3
- package/app/Actions/Dashboard/Kanban/BoardShowAction.ts +2 -3
- package/app/Actions/Dashboard/Kanban/BoardStoreAction.ts +2 -3
- package/app/Actions/Dashboard/Kanban/BoardUpdateAction.ts +2 -3
- package/app/Actions/Dashboard/Kanban/BoardsIndexAction.ts +2 -3
- package/app/Actions/Dashboard/Kanban/BoardsReorderAction.ts +2 -3
- package/app/Actions/Dashboard/Kanban/CardAssigneesSyncAction.ts +2 -3
- package/app/Actions/Dashboard/Kanban/CardCommentDestroyAction.ts +2 -3
- package/app/Actions/Dashboard/Kanban/CardCommentStoreAction.ts +2 -3
- package/app/Actions/Dashboard/Kanban/CardCommentUpdateAction.ts +2 -3
- package/app/Actions/Dashboard/Kanban/CardDestroyAction.ts +2 -3
- package/app/Actions/Dashboard/Kanban/CardLabelsSyncAction.ts +2 -3
- package/app/Actions/Dashboard/Kanban/CardShowAction.ts +2 -3
- package/app/Actions/Dashboard/Kanban/CardStoreAction.ts +2 -3
- package/app/Actions/Dashboard/Kanban/CardUpdateAction.ts +2 -3
- package/app/Actions/Dashboard/Kanban/CardsReorderAction.ts +2 -3
- package/app/Actions/Dashboard/Kanban/ColumnDestroyAction.ts +2 -3
- package/app/Actions/Dashboard/Kanban/ColumnStoreAction.ts +2 -3
- package/app/Actions/Dashboard/Kanban/ColumnUpdateAction.ts +2 -3
- package/app/Actions/Dashboard/Kanban/ColumnsReorderAction.ts +2 -3
- package/app/Actions/Dashboard/Kanban/LabelDestroyAction.ts +2 -3
- package/app/Actions/Dashboard/Kanban/LabelStoreAction.ts +2 -3
- package/app/Actions/Dashboard/Kanban/LabelUpdateAction.ts +2 -3
- package/app/Actions/Dashboard/Kanban/UsersListAction.ts +2 -3
- 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 +7 -5
- package/app/Actions/Dashboard/Models/ModelShowAction.ts +82 -174
- package/app/Actions/Dashboard/Models/ModelStoreAction.ts +6 -4
- package/app/Actions/Dashboard/Models/ModelUpdateAction.ts +9 -7
- package/app/Actions/Dashboard/Models/ModelsIndexAction.ts +47 -17
- package/app/Actions/Dashboard/Models/model-write.ts +20 -5
- package/app/Actions/Dashboard/Notifications/NotificationDeliveryHistoryAction.ts +46 -36
- package/app/Actions/Dashboard/Notifications/NotificationDeliveryIndexAction.ts +14 -6
- 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 +2 -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 +2 -2
- package/app/Actions/Dashboard/Rbac/PermissionStoreAction.ts +2 -2
- package/app/Actions/Dashboard/Rbac/PermissionsIndexAction.ts +2 -3
- package/app/Actions/Dashboard/Rbac/RoleDestroyAction.ts +2 -2
- package/app/Actions/Dashboard/Rbac/RolePermissionsShowAction.ts +2 -2
- package/app/Actions/Dashboard/Rbac/RolePermissionsSyncAction.ts +2 -6
- package/app/Actions/Dashboard/Rbac/RoleStoreAction.ts +2 -2
- package/app/Actions/Dashboard/Rbac/RolesIndexAction.ts +4 -6
- package/app/Actions/Dashboard/Rbac/UserRolesShowAction.ts +2 -2
- package/app/Actions/Dashboard/Rbac/UserRolesSyncAction.ts +2 -9
- 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 +68 -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 +102 -71
- package/app/Actions/Dashboard/Teams/TeamMemberDestroyAction.ts +36 -17
- package/app/Actions/Dashboard/Teams/TeamMemberUpdateAction.ts +39 -26
- 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-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/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/data.ts +0 -1
- package/functions/jobs.ts +0 -1
- package/ide/vscode/package.json +1 -1
- package/package.json +1 -1
- package/resources/components/Dashboard/Data/DataRecords.stx +0 -4
- package/resources/components/Dashboard/Jobs/JobDetail.stx +3 -2
- package/resources/components/Dashboard/Models/ModelRecordsDashboard.stx +7 -13
- package/resources/components/Dashboard/Models/ModelsOverview.stx +1 -1
- package/views/dashboard/.discovered-models.json +30 -0
- package/views/dashboard/stores/auth.ts +15 -14
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
import { Action } from '@stacksjs/actions'
|
|
2
2
|
import { getAllRoles } from '@stacksjs/auth'
|
|
3
|
-
import {
|
|
3
|
+
import { rbacActionError } from './rbac-response'
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* `GET /api/dashboard/rbac/roles` (stacksjs/stacks#1845).
|
|
7
7
|
*
|
|
8
8
|
* Returns every role in the system. The dashboard's permissions page
|
|
9
9
|
* renders this as a list users can pick from when assigning roles.
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* yelling at the user.
|
|
10
|
+
* Operational store failures use a real 500 response so the client can
|
|
11
|
+
* distinguish an unavailable RBAC store from a valid empty role list.
|
|
13
12
|
*/
|
|
14
13
|
export default new Action({
|
|
15
14
|
name: 'Dashboard RBAC Roles Index',
|
|
@@ -30,8 +29,7 @@ export default new Action({
|
|
|
30
29
|
}
|
|
31
30
|
}
|
|
32
31
|
catch (err) {
|
|
33
|
-
|
|
34
|
-
return response.json({ roles: [], error: err instanceof Error ? err.message : 'unknown error' }, 500)
|
|
32
|
+
return rbacActionError(err, 'Roles could not be loaded.', 'RolesIndexAction')
|
|
35
33
|
}
|
|
36
34
|
},
|
|
37
35
|
})
|
|
@@ -3,6 +3,7 @@ import { Action } from '@stacksjs/actions'
|
|
|
3
3
|
import { getUserRoles } from '@stacksjs/auth'
|
|
4
4
|
import { User } from '@stacksjs/orm'
|
|
5
5
|
import { response } from '@stacksjs/router'
|
|
6
|
+
import { rbacActionError } from './rbac-response'
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* `GET /api/dashboard/rbac/users/:id/roles` (stacksjs/stacks#1845).
|
|
@@ -43,8 +44,7 @@ export default new Action({
|
|
|
43
44
|
}
|
|
44
45
|
}
|
|
45
46
|
catch (err) {
|
|
46
|
-
|
|
47
|
-
return response.json({ userId, roles: [], error: err instanceof Error ? err.message : 'unknown error' }, 500)
|
|
47
|
+
return rbacActionError(err, 'User roles could not be loaded.', 'UserRolesShowAction')
|
|
48
48
|
}
|
|
49
49
|
},
|
|
50
50
|
})
|
|
@@ -3,6 +3,7 @@ import { Action } from '@stacksjs/actions'
|
|
|
3
3
|
import { getUserRoles, syncRoles } from '@stacksjs/auth'
|
|
4
4
|
import { User } from '@stacksjs/orm'
|
|
5
5
|
import { response } from '@stacksjs/router'
|
|
6
|
+
import { rbacActionError } from './rbac-response'
|
|
6
7
|
|
|
7
8
|
interface SyncInput {
|
|
8
9
|
roles?: unknown
|
|
@@ -60,15 +61,7 @@ export default new Action({
|
|
|
60
61
|
}
|
|
61
62
|
}
|
|
62
63
|
catch (err) {
|
|
63
|
-
|
|
64
|
-
// The facade throws "Role 'X' not found" — surface as 400 so
|
|
65
|
-
// the UI can show "this role doesn't exist anymore" rather
|
|
66
|
-
// than a 500.
|
|
67
|
-
if (msg.includes('not found')) {
|
|
68
|
-
return response.json({ error: msg }, 400)
|
|
69
|
-
}
|
|
70
|
-
console.error('[dashboard/rbac] UserRolesSyncAction failed:', err)
|
|
71
|
-
return response.json({ error: msg }, 500)
|
|
64
|
+
return rbacActionError(err, 'User roles could not be updated.', 'UserRolesSyncAction')
|
|
72
65
|
}
|
|
73
66
|
},
|
|
74
67
|
})
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Action } from '@stacksjs/actions'
|
|
2
2
|
import { User } from '@stacksjs/orm'
|
|
3
|
-
import { response } from '@stacksjs/router'
|
|
4
3
|
import { safeGet } from '../../../../resources/functions/dashboard/data'
|
|
4
|
+
import { rbacActionError } from './rbac-response'
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* `GET /api/dashboard/rbac/users` (stacksjs/stacks#1845).
|
|
@@ -40,8 +40,7 @@ export default new Action({
|
|
|
40
40
|
}
|
|
41
41
|
}
|
|
42
42
|
catch (err) {
|
|
43
|
-
|
|
44
|
-
return response.json({ users: [], error: err instanceof Error ? err.message : 'unknown error' }, 500)
|
|
43
|
+
return rbacActionError(err, 'Users could not be loaded.', 'UsersListAction')
|
|
45
44
|
}
|
|
46
45
|
},
|
|
47
46
|
})
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { RbacEntityNotFoundError } from '@stacksjs/auth'
|
|
2
|
+
import { response } from '@stacksjs/router'
|
|
3
|
+
|
|
4
|
+
export function rbacActionError(error: unknown, fallback: string, action: string): Response {
|
|
5
|
+
if (error instanceof RbacEntityNotFoundError)
|
|
6
|
+
return response.json({ error: error.message }, 400)
|
|
7
|
+
|
|
8
|
+
console.error(`[dashboard/rbac] ${action} failed:`, error)
|
|
9
|
+
return response.json({ error: fallback }, 500)
|
|
10
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Action } from '@stacksjs/actions'
|
|
2
2
|
import { config } from '@stacksjs/config'
|
|
3
3
|
import { Websocket } from '@stacksjs/orm'
|
|
4
|
-
import {
|
|
4
|
+
import { dashboardOperationalError } from '../dashboard-response'
|
|
5
5
|
import { buildRealtimeStats } from './realtime-stats'
|
|
6
6
|
|
|
7
7
|
export default new Action({
|
|
@@ -36,9 +36,7 @@ export default new Action({
|
|
|
36
36
|
}
|
|
37
37
|
}
|
|
38
38
|
catch (error) {
|
|
39
|
-
return
|
|
40
|
-
message: error instanceof Error ? error.message : 'Realtime events could not be loaded.',
|
|
41
|
-
}, 503)
|
|
39
|
+
return dashboardOperationalError(error, 'Realtime events could not be loaded.', 'RealtimeStatsAction')
|
|
42
40
|
}
|
|
43
41
|
},
|
|
44
42
|
})
|
|
@@ -4,6 +4,7 @@ import { env } from '@stacksjs/env'
|
|
|
4
4
|
import { readdirSync } from 'node:fs'
|
|
5
5
|
import { basename, extname, join } from 'node:path'
|
|
6
6
|
import process from 'node:process'
|
|
7
|
+
import { dashboardOperationalError } from '../dashboard-response'
|
|
7
8
|
|
|
8
9
|
interface SearchableModel {
|
|
9
10
|
name: string
|
|
@@ -153,8 +154,15 @@ async function loadSearchableModels(): Promise<SearchableModel[]> {
|
|
|
153
154
|
}
|
|
154
155
|
|
|
155
156
|
function catalog(): Promise<SearchableModel[]> {
|
|
156
|
-
modelCatalog.promise
|
|
157
|
-
|
|
157
|
+
if (modelCatalog.promise)
|
|
158
|
+
return modelCatalog.promise
|
|
159
|
+
|
|
160
|
+
const loading = loadSearchableModels().catch((error) => {
|
|
161
|
+
modelCatalog.promise = null
|
|
162
|
+
throw error
|
|
163
|
+
})
|
|
164
|
+
modelCatalog.promise = loading
|
|
165
|
+
return loading
|
|
158
166
|
}
|
|
159
167
|
|
|
160
168
|
function rowValue(row: Record<string, unknown>, field: string): string {
|
|
@@ -185,67 +193,72 @@ export default new Action({
|
|
|
185
193
|
method: 'GET',
|
|
186
194
|
apiResponse: true,
|
|
187
195
|
async handle(request: RequestInstance) {
|
|
188
|
-
const q = String(request
|
|
196
|
+
const q = String(request.get('q') || '').trim().slice(0, MAX_QUERY_LENGTH)
|
|
189
197
|
if (!q) return { results: {} }
|
|
190
198
|
|
|
191
|
-
const { Database } = await import('bun:sqlite')
|
|
192
|
-
const db = new Database(env.DB_DATABASE_PATH || 'database/stacks.sqlite', { readonly: true })
|
|
193
|
-
const results: Record<string, SearchResult[]> = {}
|
|
194
|
-
const unavailable: SearchUnavailable[] = []
|
|
195
|
-
|
|
196
199
|
try {
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
200
|
+
const { Database } = await import('bun:sqlite')
|
|
201
|
+
const db = new Database(env.DB_DATABASE_PATH || 'database/stacks.sqlite', { readonly: true })
|
|
202
|
+
const results: Record<string, SearchResult[]> = {}
|
|
203
|
+
const unavailable: SearchUnavailable[] = []
|
|
204
|
+
|
|
205
|
+
try {
|
|
206
|
+
for (const model of await catalog()) {
|
|
207
|
+
if (Object.keys(results).length >= MAX_GROUPS) break
|
|
208
|
+
|
|
209
|
+
const columns = db.query(`PRAGMA table_info(${model.table})`).all() as Array<{ name: string }>
|
|
210
|
+
if (columns.length === 0) {
|
|
211
|
+
unavailable.push({
|
|
212
|
+
model: model.name,
|
|
213
|
+
reason: `Table ${model.table} has not been migrated.`,
|
|
214
|
+
})
|
|
215
|
+
continue
|
|
216
|
+
}
|
|
208
217
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
+
const available = new Set(columns.map(column => column.name))
|
|
219
|
+
const fields = model.fields.filter(field => available.has(field))
|
|
220
|
+
if (fields.length === 0) {
|
|
221
|
+
unavailable.push({
|
|
222
|
+
model: model.name,
|
|
223
|
+
reason: 'Declared useSearch fields are not present in the migrated table.',
|
|
224
|
+
})
|
|
225
|
+
continue
|
|
226
|
+
}
|
|
218
227
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
}
|
|
231
|
-
if (rows.length === 0) continue
|
|
232
|
-
|
|
233
|
-
results[model.name] = rows.map((row, index) => {
|
|
234
|
-
const title = resultTitle(model, row)
|
|
235
|
-
return {
|
|
236
|
-
id: (row.id as string | number | undefined) ?? index,
|
|
237
|
-
title,
|
|
238
|
-
subtitle: resultSubtitle(model, row, title),
|
|
239
|
-
href: `/models/${model.slug}?q=${encodeURIComponent(q)}`,
|
|
240
|
-
icon: model.icon,
|
|
228
|
+
const selected = [...new Set(['id', ...fields])].filter(field => available.has(field))
|
|
229
|
+
const where = fields.map(field => `${field} LIKE ? COLLATE NOCASE`).join(' OR ')
|
|
230
|
+
const bindings = fields.map(() => `%${q}%`)
|
|
231
|
+
let rows: Array<Record<string, unknown>>
|
|
232
|
+
try {
|
|
233
|
+
rows = db
|
|
234
|
+
.query(`SELECT ${selected.join(', ')} FROM ${model.table} WHERE ${where} LIMIT ?`)
|
|
235
|
+
.all(...bindings, RESULTS_PER_MODEL) as Array<Record<string, unknown>>
|
|
236
|
+
}
|
|
237
|
+
catch (error) {
|
|
238
|
+
throw new Error(`Could not search model ${model.name}: ${error instanceof Error ? error.message : String(error)}`)
|
|
241
239
|
}
|
|
242
|
-
|
|
240
|
+
if (rows.length === 0) continue
|
|
241
|
+
|
|
242
|
+
results[model.name] = rows.map((row, index) => {
|
|
243
|
+
const title = resultTitle(model, row)
|
|
244
|
+
return {
|
|
245
|
+
id: (row.id as string | number | undefined) ?? index,
|
|
246
|
+
title,
|
|
247
|
+
subtitle: resultSubtitle(model, row, title),
|
|
248
|
+
href: `/models/${model.slug}?q=${encodeURIComponent(q)}`,
|
|
249
|
+
icon: model.icon,
|
|
250
|
+
}
|
|
251
|
+
})
|
|
252
|
+
}
|
|
243
253
|
}
|
|
254
|
+
finally {
|
|
255
|
+
db.close()
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
return { results, unavailable }
|
|
244
259
|
}
|
|
245
|
-
|
|
246
|
-
|
|
260
|
+
catch (error) {
|
|
261
|
+
return dashboardOperationalError(error, 'Search results could not be loaded.', 'GlobalSearchAction')
|
|
247
262
|
}
|
|
248
|
-
|
|
249
|
-
return { results, unavailable }
|
|
250
263
|
},
|
|
251
264
|
})
|
|
@@ -2,7 +2,9 @@ import type { RequestInstance } from '@stacksjs/types'
|
|
|
2
2
|
import { Action } from '@stacksjs/actions'
|
|
3
3
|
import { db } from '@stacksjs/database'
|
|
4
4
|
import { response } from '@stacksjs/router'
|
|
5
|
+
import { teamOperationalError } from '../../Teams/team-response'
|
|
5
6
|
import {
|
|
7
|
+
changedRows,
|
|
6
8
|
generateInvitationToken,
|
|
7
9
|
hashInvitationToken,
|
|
8
10
|
parsePositiveId,
|
|
@@ -21,27 +23,43 @@ export default new Action({
|
|
|
21
23
|
if (!teamId || !invitationId)
|
|
22
24
|
return response.json({ message: 'Invalid team or invitation id.' }, 400)
|
|
23
25
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
26
|
+
let invitation
|
|
27
|
+
try {
|
|
28
|
+
invitation = await (db as any)
|
|
29
|
+
.selectFrom('team_invitations')
|
|
30
|
+
.where('id', '=', invitationId)
|
|
31
|
+
.where('team_id', '=', teamId)
|
|
32
|
+
.select(['id', 'status', 'token_hash'])
|
|
33
|
+
.executeTakeFirst()
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
return teamOperationalError(error, 'Invitation could not be loaded.', 'TeamInvitationDestroyAction.lookup', 500)
|
|
37
|
+
}
|
|
30
38
|
if (!invitation)
|
|
31
39
|
return response.json({ message: 'Invitation not found.' }, 404)
|
|
32
40
|
if (invitation.status !== 'pending')
|
|
33
41
|
return response.json({ message: 'Only pending invitations can be revoked.' }, 409)
|
|
34
42
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
43
|
+
try {
|
|
44
|
+
const result = await (db as any)
|
|
45
|
+
.updateTable('team_invitations')
|
|
46
|
+
.set({
|
|
47
|
+
status: 'revoked',
|
|
48
|
+
token_hash: hashInvitationToken(generateInvitationToken()),
|
|
49
|
+
pending_key: null,
|
|
50
|
+
updated_at: sqlTimestamp(),
|
|
51
|
+
})
|
|
52
|
+
.where('id', '=', invitationId)
|
|
53
|
+
.where('team_id', '=', teamId)
|
|
54
|
+
.where('token_hash', '=', String(invitation.token_hash))
|
|
55
|
+
.where('status', '=', 'pending')
|
|
56
|
+
.executeTakeFirst()
|
|
57
|
+
if (changedRows(result) !== 1)
|
|
58
|
+
return response.json({ message: 'The invitation changed before it could be revoked.' }, 409)
|
|
59
|
+
}
|
|
60
|
+
catch (error) {
|
|
61
|
+
return teamOperationalError(error, 'Invitation could not be revoked.', 'TeamInvitationDestroyAction.update', 500)
|
|
62
|
+
}
|
|
45
63
|
|
|
46
64
|
return response.noContent()
|
|
47
65
|
},
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import type { RequestInstance } from '@stacksjs/types'
|
|
2
2
|
import { Action } from '@stacksjs/actions'
|
|
3
3
|
import { db } from '@stacksjs/database'
|
|
4
|
+
import { isUniqueViolation } from '@stacksjs/orm'
|
|
4
5
|
import { response } from '@stacksjs/router'
|
|
6
|
+
import { teamOperationalError } from '../../Teams/team-response'
|
|
5
7
|
import { deliverTeamInvitation } from './team-invitation-delivery'
|
|
6
8
|
import {
|
|
9
|
+
changedRows,
|
|
7
10
|
generateInvitationToken,
|
|
8
11
|
hashInvitationToken,
|
|
9
12
|
invitationExpiresAt,
|
|
@@ -23,40 +26,59 @@ export default new Action({
|
|
|
23
26
|
if (!teamId || !invitationId)
|
|
24
27
|
return response.json({ message: 'Invalid team or invitation id.' }, 400)
|
|
25
28
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
'team_invitations.
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
29
|
+
let invitation
|
|
30
|
+
try {
|
|
31
|
+
invitation = await (db as any)
|
|
32
|
+
.selectFrom('team_invitations')
|
|
33
|
+
.innerJoin('teams', 'teams.id', '=', 'team_invitations.team_id')
|
|
34
|
+
.where('team_invitations.id', '=', invitationId)
|
|
35
|
+
.where('team_invitations.team_id', '=', teamId)
|
|
36
|
+
.select([
|
|
37
|
+
'team_invitations.id as id',
|
|
38
|
+
'team_invitations.email as email',
|
|
39
|
+
'team_invitations.role as role',
|
|
40
|
+
'team_invitations.status as status',
|
|
41
|
+
'team_invitations.token_hash as token_hash',
|
|
42
|
+
'teams.name as team_name',
|
|
43
|
+
])
|
|
44
|
+
.executeTakeFirst()
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
return teamOperationalError(error, 'Invitation could not be loaded.', 'TeamInvitationResendAction.lookup', 500)
|
|
48
|
+
}
|
|
39
49
|
if (!invitation)
|
|
40
50
|
return response.json({ message: 'Invitation not found.' }, 404)
|
|
41
51
|
if (!['pending', 'expired'].includes(String(invitation.status)))
|
|
42
52
|
return response.json({ message: 'Only pending or expired invitations can be resent.' }, 409)
|
|
43
53
|
|
|
44
54
|
const token = generateInvitationToken()
|
|
55
|
+
const tokenHash = hashInvitationToken(token)
|
|
45
56
|
const expiresAt = invitationExpiresAt()
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
57
|
+
try {
|
|
58
|
+
const result = await (db as any)
|
|
59
|
+
.updateTable('team_invitations')
|
|
60
|
+
.set({
|
|
61
|
+
token_hash: tokenHash,
|
|
62
|
+
pending_key: `${teamId}:${String(invitation.email).trim().toLowerCase()}`,
|
|
63
|
+
status: 'pending',
|
|
64
|
+
delivery_status: 'pending',
|
|
65
|
+
delivery_error: null,
|
|
66
|
+
expires_at: expiresAt,
|
|
67
|
+
delivered_at: null,
|
|
68
|
+
updated_at: sqlTimestamp(),
|
|
69
|
+
})
|
|
70
|
+
.where('id', '=', invitationId)
|
|
71
|
+
.where('team_id', '=', teamId)
|
|
72
|
+
.where('token_hash', '=', String(invitation.token_hash))
|
|
73
|
+
.executeTakeFirst()
|
|
74
|
+
if (changedRows(result) !== 1)
|
|
75
|
+
return response.json({ message: 'The invitation changed before it could be resent.' }, 409)
|
|
76
|
+
}
|
|
77
|
+
catch (error) {
|
|
78
|
+
if (isUniqueViolation(error))
|
|
79
|
+
return response.json({ message: 'Another pending invitation already exists for this email.' }, 409)
|
|
80
|
+
return teamOperationalError(error, 'Invitation could not be refreshed.', 'TeamInvitationResendAction.update', 500)
|
|
81
|
+
}
|
|
60
82
|
|
|
61
83
|
try {
|
|
62
84
|
await deliverTeamInvitation({
|
|
@@ -65,10 +87,11 @@ export default new Action({
|
|
|
65
87
|
teamName: String(invitation.team_name),
|
|
66
88
|
role: String(invitation.role),
|
|
67
89
|
token,
|
|
90
|
+
tokenHash,
|
|
68
91
|
})
|
|
69
92
|
}
|
|
70
|
-
catch {
|
|
71
|
-
return
|
|
93
|
+
catch (error) {
|
|
94
|
+
return teamOperationalError(error, 'The invitation was refreshed, but email delivery failed.', 'TeamInvitationResendAction.delivery', 502)
|
|
72
95
|
}
|
|
73
96
|
|
|
74
97
|
return {
|