@stacksjs/defaults 0.70.353 → 0.70.355
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-dashboard/scripts/audit.ts +0 -1
- package/ai/skills/stacks-email/SKILL.md +35 -15
- package/app/Actions/Dashboard/Auth/MeAction.ts +8 -7
- 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/Commerce/CommercePosCheckoutAction.ts +1 -0
- package/app/Actions/Dashboard/Commerce/CommercePrintLogsAction.ts +3 -4
- package/app/Actions/Dashboard/Commerce/CommerceProductDetailAction.ts +1 -0
- package/app/Actions/Dashboard/Commerce/PaymentRefundAction.ts +1 -0
- package/app/Actions/Dashboard/Commerce/ProductUnitDefaultAction.ts +1 -0
- package/app/Actions/Dashboard/Commerce/TaxRateDefaultAction.ts +1 -0
- package/app/Actions/Dashboard/Commerce/tax-rate-records.ts +9 -0
- package/app/Actions/Dashboard/Content/author-input.ts +1 -1
- package/app/Actions/Dashboard/Content/page-input.ts +1 -1
- package/app/Actions/Dashboard/DashboardStatsAction.ts +0 -1
- package/app/Actions/Dashboard/Deployments/CreateDeployment.ts +3 -2
- package/app/Actions/Dashboard/Deployments/GetDeployment.ts +1 -0
- package/app/Actions/Dashboard/Deployments/UpdateDeployScript.ts +3 -2
- 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/LogIndexAction.ts +3 -4
- package/app/Actions/Dashboard/Infrastructure/ServerShowAction.ts +3 -6
- package/app/Actions/Dashboard/Jobs/JobIndexAction.ts +8 -15
- package/app/Actions/Dashboard/Jobs/JobRetryAction.ts +4 -3
- package/app/Actions/Dashboard/Jobs/JobShowAction.ts +3 -3
- package/app/Actions/Dashboard/Kanban/BoardDestroyAction.ts +5 -5
- package/app/Actions/Dashboard/Kanban/BoardShowAction.ts +9 -16
- package/app/Actions/Dashboard/Kanban/BoardStoreAction.ts +4 -3
- package/app/Actions/Dashboard/Kanban/BoardUpdateAction.ts +5 -5
- package/app/Actions/Dashboard/Kanban/BoardsIndexAction.ts +1 -2
- package/app/Actions/Dashboard/Kanban/BoardsReorderAction.ts +4 -3
- package/app/Actions/Dashboard/Kanban/CardAssigneesSyncAction.ts +11 -9
- package/app/Actions/Dashboard/Kanban/CardCommentDestroyAction.ts +10 -12
- package/app/Actions/Dashboard/Kanban/CardCommentStoreAction.ts +19 -43
- package/app/Actions/Dashboard/Kanban/CardCommentUpdateAction.ts +44 -0
- package/app/Actions/Dashboard/Kanban/CardDestroyAction.ts +5 -5
- package/app/Actions/Dashboard/Kanban/CardLabelsSyncAction.ts +6 -6
- package/app/Actions/Dashboard/Kanban/CardShowAction.ts +4 -4
- package/app/Actions/Dashboard/Kanban/CardStoreAction.ts +7 -6
- package/app/Actions/Dashboard/Kanban/CardUpdateAction.ts +5 -5
- package/app/Actions/Dashboard/Kanban/CardsReorderAction.ts +4 -3
- package/app/Actions/Dashboard/Kanban/ColumnDestroyAction.ts +5 -5
- package/app/Actions/Dashboard/Kanban/ColumnStoreAction.ts +4 -3
- package/app/Actions/Dashboard/Kanban/ColumnUpdateAction.ts +5 -5
- package/app/Actions/Dashboard/Kanban/ColumnsReorderAction.ts +4 -3
- package/app/Actions/Dashboard/Kanban/LabelDestroyAction.ts +4 -4
- package/app/Actions/Dashboard/Kanban/LabelStoreAction.ts +4 -3
- package/app/Actions/Dashboard/Kanban/LabelUpdateAction.ts +5 -5
- package/app/Actions/Dashboard/Kanban/UsersListAction.ts +1 -1
- 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/Models/ModelDestroyAction.ts +4 -3
- package/app/Actions/Dashboard/Models/ModelShowAction.ts +15 -18
- package/app/Actions/Dashboard/Models/ModelStoreAction.ts +8 -8
- package/app/Actions/Dashboard/Models/ModelUpdateAction.ts +9 -9
- package/app/Actions/Dashboard/Models/ModelsIndexAction.ts +4 -3
- package/app/Actions/Dashboard/Models/model-api.ts +27 -0
- package/app/Actions/Dashboard/Models/model-write.ts +2 -10
- package/app/Actions/Dashboard/Notifications/NotificationDeliveryHistoryAction.ts +3 -4
- package/app/Actions/Dashboard/Notifications/NotificationDeliveryIndexAction.ts +6 -4
- package/app/Actions/Dashboard/Queries/QueryShowAction.ts +1 -0
- package/app/Actions/Dashboard/Rbac/PermissionDestroyAction.ts +4 -4
- package/app/Actions/Dashboard/Rbac/PermissionStoreAction.ts +3 -2
- package/app/Actions/Dashboard/Rbac/RoleDestroyAction.ts +4 -4
- package/app/Actions/Dashboard/Rbac/RolePermissionsShowAction.ts +4 -4
- package/app/Actions/Dashboard/Rbac/RolePermissionsSyncAction.ts +4 -3
- package/app/Actions/Dashboard/Rbac/RoleStoreAction.ts +3 -2
- package/app/Actions/Dashboard/Rbac/UserRolesShowAction.ts +4 -5
- package/app/Actions/Dashboard/Rbac/UserRolesSyncAction.ts +4 -4
- package/app/Actions/Dashboard/Search/GlobalSearchAction.ts +1 -0
- package/app/Actions/Dashboard/Teams/TeamInviteAction.ts +3 -3
- package/app/Actions/Dashboard/Teams/TeamMemberUpdateAction.ts +2 -2
- package/app/Actions/Dashboard/dashboard-request.ts +13 -0
- package/app/Models/CardComment.ts +3 -4
- package/app/Models/commerce/TaxRate.ts +48 -0
- package/functions/dashboard-api.ts +51 -10
- package/functions/inbox.test.ts +2 -0
- package/functions/inbox.ts +27 -2
- 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 +23 -0
- package/resources/components/Dashboard/Commerce/CommerceTaxesDashboard.stx +4 -0
- package/resources/components/Dashboard/Commerce/CommerceTaxesTable.stx +9 -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/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/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/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 +24 -0
- package/views/dashboard/layouts/default.stx +19 -16
- package/views/dashboard/stores/ci.ts +13 -7
- package/views/dashboard/stores/kanban.ts +48 -11
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Action } from '@stacksjs/actions'
|
|
2
2
|
import { dashboard as dashboardConfig } from '@stacksjs/config'
|
|
3
3
|
import { getDashboardData } from '@stacksjs/github'
|
|
4
|
+
import { response } from '@stacksjs/router'
|
|
4
5
|
import { runCiFailureNotifier } from './failure-notifier'
|
|
5
6
|
import { runRunnerPressureMonitor } from './runner-pressure-monitor'
|
|
6
7
|
|
|
@@ -57,17 +58,7 @@ export default new Action({
|
|
|
57
58
|
// shape the page can render without leaking the env-var name to
|
|
58
59
|
// every browser viewing the dashboard.
|
|
59
60
|
console.error('[dashboard/ci] snapshot failed:', err)
|
|
60
|
-
return {
|
|
61
|
-
repos: [],
|
|
62
|
-
fetchedAt: new Date().toISOString(),
|
|
63
|
-
total: 0,
|
|
64
|
-
passing: 0,
|
|
65
|
-
failing: 0,
|
|
66
|
-
pending: 0,
|
|
67
|
-
noRuns: 0,
|
|
68
|
-
runners: {},
|
|
69
|
-
error: err instanceof Error ? err.message : 'unknown error',
|
|
70
|
-
}
|
|
61
|
+
return response.json({ message: 'CI status could not be loaded.' }, 502)
|
|
71
62
|
}
|
|
72
63
|
},
|
|
73
64
|
})
|
|
@@ -2,7 +2,8 @@ import type { RequestInstance } from '@stacksjs/types'
|
|
|
2
2
|
import { Action } from '@stacksjs/actions'
|
|
3
3
|
import { db } from '@stacksjs/database'
|
|
4
4
|
import { formatDate, PrintDevice } from '@stacksjs/orm'
|
|
5
|
-
import {
|
|
5
|
+
import { response } from '@stacksjs/router'
|
|
6
|
+
import { dashboardRequestValue } from '../dashboard-request'
|
|
6
7
|
import {
|
|
7
8
|
commerceEnum,
|
|
8
9
|
commerceIdentifier,
|
|
@@ -24,9 +25,7 @@ const SORTS = {
|
|
|
24
25
|
type PrintLogSort = keyof typeof SORTS
|
|
25
26
|
|
|
26
27
|
function queryValue(request: RequestInstance, key: string): string {
|
|
27
|
-
|
|
28
|
-
const value = query[key]
|
|
29
|
-
return String((Array.isArray(value) ? value[0] : value) || request.get(key) || '').trim()
|
|
28
|
+
return dashboardRequestValue(request, key)
|
|
30
29
|
}
|
|
31
30
|
|
|
32
31
|
function selectedSort(value: string): PrintLogSort {
|
|
@@ -19,6 +19,10 @@ export interface TaxRateRecord {
|
|
|
19
19
|
region: TaxRateRegion
|
|
20
20
|
status: 'active' | 'inactive'
|
|
21
21
|
isDefault: boolean
|
|
22
|
+
/** Stable identifier application code matches on, independent of `name`. */
|
|
23
|
+
code: string
|
|
24
|
+
/** Whether a qualifying exemption stops this component being charged. */
|
|
25
|
+
exemptible: boolean
|
|
22
26
|
createdAt: string
|
|
23
27
|
}
|
|
24
28
|
|
|
@@ -50,6 +54,11 @@ export function normalizeTaxRateRecord(record: any): TaxRateRecord {
|
|
|
50
54
|
]),
|
|
51
55
|
status: commerceEnum(commerceValue(record, 'status'), source, 'status', ['active', 'inactive']),
|
|
52
56
|
isDefault: commerceBoolean(commerceValue(record, 'is_default', 'isDefault'), source, 'is_default'),
|
|
57
|
+
// Optional, both of them: a rate created before these columns existed has
|
|
58
|
+
// neither, and a dashboard that throws on an older row is worse than one
|
|
59
|
+
// that shows it without a badge.
|
|
60
|
+
code: String(commerceValue(record, 'code') ?? ''),
|
|
61
|
+
exemptible: Boolean(commerceValue(record, 'exemptible') ?? false),
|
|
53
62
|
createdAt: commerceTimestamp(commerceValue(record, 'created_at', 'createdAt'), source),
|
|
54
63
|
}
|
|
55
64
|
}
|
|
@@ -9,7 +9,7 @@ export interface AuthorInput {
|
|
|
9
9
|
avatar: string
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
// eslint-disable-next-line
|
|
12
|
+
// eslint-disable-next-line
|
|
13
13
|
export function parseAuthorInput(inputRequest: RequestInstance): { data: AuthorInput } | { message: string } {
|
|
14
14
|
const data = {
|
|
15
15
|
name: str(inputRequest.get('name')).trim(),
|
|
@@ -10,7 +10,7 @@ export function parsePublished(value: unknown): boolean {
|
|
|
10
10
|
return value === true || value === 1 || value === '1' || value === 'true'
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
-
// eslint-disable-next-line
|
|
13
|
+
// eslint-disable-next-line
|
|
14
14
|
export function parsePageInput(inputRequest: RequestInstance): { data: PageInput } | { message: string } {
|
|
15
15
|
const data = {
|
|
16
16
|
title: str(inputRequest.get('title')).trim(),
|
|
@@ -58,7 +58,6 @@ export default new Action({
|
|
|
58
58
|
const { count: _count, ...metadata } = definition
|
|
59
59
|
return formatDashboardStat(metadata, results[index])
|
|
60
60
|
})
|
|
61
|
-
// eslint-disable-next-line pickier/no-unused-vars
|
|
62
61
|
const issues = results.flatMap((result, index) => result.status === 'rejected'
|
|
63
62
|
? [{
|
|
64
63
|
source: definitions[index].title,
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
import type { RequestInstance } from '@stacksjs/types'
|
|
1
2
|
import { Action } from '@stacksjs/actions'
|
|
2
3
|
import { join } from 'node:path'
|
|
3
4
|
import process from 'node:process'
|
|
4
5
|
import { Deployment } from '@stacksjs/orm'
|
|
5
|
-
import {
|
|
6
|
+
import { response } from '@stacksjs/router'
|
|
6
7
|
import { booleanValue, deploymentCommandArgs } from './deployment-input'
|
|
7
8
|
|
|
8
9
|
function gitValue(args: string[]): string {
|
|
@@ -22,7 +23,7 @@ export default new Action({
|
|
|
22
23
|
method: 'POST',
|
|
23
24
|
apiResponse: true,
|
|
24
25
|
|
|
25
|
-
async handle() {
|
|
26
|
+
async handle(request: RequestInstance) {
|
|
26
27
|
const dryRun = booleanValue(request.get('dryRun') || request.get('dry_run'))
|
|
27
28
|
const confirmed = booleanValue(request.get('confirmed'))
|
|
28
29
|
if (!dryRun && !confirmed) {
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
import type { RequestInstance } from '@stacksjs/types'
|
|
1
2
|
import { Action } from '@stacksjs/actions'
|
|
2
3
|
import { mkdir, rename, writeFile } from 'node:fs/promises'
|
|
3
4
|
import { dirname, join } from 'node:path'
|
|
4
5
|
import process from 'node:process'
|
|
5
|
-
import {
|
|
6
|
+
import { response } from '@stacksjs/router'
|
|
6
7
|
|
|
7
8
|
export default new Action({
|
|
8
9
|
name: 'UpdateDeployScript',
|
|
@@ -10,7 +11,7 @@ export default new Action({
|
|
|
10
11
|
method: 'PUT',
|
|
11
12
|
apiResponse: true,
|
|
12
13
|
|
|
13
|
-
async handle() {
|
|
14
|
+
async handle(request: RequestInstance) {
|
|
14
15
|
const content = String(request.get('content') || '')
|
|
15
16
|
if (!content.trim())
|
|
16
17
|
return response.json({ success: false, message: 'Deploy script content is required.' }, { status: 422 })
|
|
@@ -2,8 +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 { emailSDK } from '@stacksjs/email'
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
5
|
+
import { response } from '@stacksjs/router'
|
|
6
|
+
import { dashboardRequestValue } from '../dashboard-request'
|
|
7
|
+
import { dashboardMailbox, inboxActionError } from './inbox-request'
|
|
7
8
|
|
|
8
9
|
const RANGES = ['day', 'week', 'month', 'year'] as const
|
|
9
10
|
type ActivityRange = typeof RANGES[number]
|
|
@@ -34,9 +35,7 @@ interface Bucket {
|
|
|
34
35
|
}
|
|
35
36
|
|
|
36
37
|
function queryValue(request: RequestInstance, key: string): string {
|
|
37
|
-
|
|
38
|
-
const value = query[key]
|
|
39
|
-
return String((Array.isArray(value) ? value[0] : value) || request.get(key) || '').trim()
|
|
38
|
+
return dashboardRequestValue(request, key)
|
|
40
39
|
}
|
|
41
40
|
|
|
42
41
|
function startOfHour(date: Date): Date {
|
|
@@ -144,23 +143,36 @@ export default new Action({
|
|
|
144
143
|
const range = RANGES.includes(requestedRange as ActivityRange)
|
|
145
144
|
? requestedRange as ActivityRange
|
|
146
145
|
: 'week'
|
|
147
|
-
|
|
146
|
+
let mailbox: string
|
|
147
|
+
try {
|
|
148
|
+
mailbox = dashboardMailbox(request)
|
|
149
|
+
}
|
|
150
|
+
catch (error) {
|
|
151
|
+
return inboxActionError(error, 'Inbox activity could not be loaded.')
|
|
152
|
+
}
|
|
148
153
|
const buckets = buildBuckets(range, new Date())
|
|
149
154
|
const periodStart = buckets[0]!.start
|
|
150
155
|
const periodEnd = buckets.at(-1)!.end
|
|
151
156
|
const periodLength = periodEnd.getTime() - periodStart.getTime()
|
|
152
157
|
const previousStart = new Date(periodStart.getTime() - periodLength)
|
|
153
158
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
.
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
159
|
+
let inbox
|
|
160
|
+
let deliveryRows: DeliveryRow[]
|
|
161
|
+
try {
|
|
162
|
+
[inbox, deliveryRows] = await Promise.all([
|
|
163
|
+
emailSDK.getInbox(mailbox, { limit: 1000 }),
|
|
164
|
+
db
|
|
165
|
+
.selectFrom('notification_deliveries')
|
|
166
|
+
.select(['id', 'recipient', 'subject', 'status', 'sent_at', 'created_at'])
|
|
167
|
+
.where('channel', '=', 'email')
|
|
168
|
+
.where('created_at', '>=', previousStart.toISOString())
|
|
169
|
+
.orderBy('created_at', 'desc')
|
|
170
|
+
.execute() as Promise<DeliveryRow[]>,
|
|
171
|
+
])
|
|
172
|
+
}
|
|
173
|
+
catch (error) {
|
|
174
|
+
return inboxActionError(error, 'Inbox activity could not be loaded.')
|
|
175
|
+
}
|
|
164
176
|
|
|
165
177
|
const currentInbound = inbox.filter(email => inPeriod(email.date, periodStart, periodEnd))
|
|
166
178
|
const previousInbound = inbox.filter(email => inPeriod(email.date, previousStart, periodStart))
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { RequestInstance } from '@stacksjs/types'
|
|
2
|
+
import { Action } from '@stacksjs/actions'
|
|
3
|
+
import {
|
|
4
|
+
emailSDK,
|
|
5
|
+
inboxAttachmentContentDisposition,
|
|
6
|
+
inboxAttachmentContentType,
|
|
7
|
+
} from '@stacksjs/email'
|
|
8
|
+
import { response } from '@stacksjs/router'
|
|
9
|
+
import { dashboardMailbox, inboxActionError } from './inbox-request'
|
|
10
|
+
|
|
11
|
+
export default new Action({
|
|
12
|
+
name: 'InboxAttachmentDownloadAction',
|
|
13
|
+
description: 'Downloads one attachment from a guarded inbound email.',
|
|
14
|
+
method: 'GET',
|
|
15
|
+
apiResponse: false,
|
|
16
|
+
|
|
17
|
+
async handle(request: RequestInstance) {
|
|
18
|
+
const messageId = String(request.getParam('id') || '')
|
|
19
|
+
const attachmentId = String(request.getParam('attachmentId') || '')
|
|
20
|
+
|
|
21
|
+
if (!messageId || !attachmentId)
|
|
22
|
+
return response.json({ message: 'A message ID and attachment ID are required.' }, 422)
|
|
23
|
+
|
|
24
|
+
try {
|
|
25
|
+
const mailbox = dashboardMailbox(request)
|
|
26
|
+
const result = await emailSDK.getAttachment(mailbox, messageId, attachmentId)
|
|
27
|
+
if (!result)
|
|
28
|
+
return response.json({ message: 'Attachment not found.' }, 404)
|
|
29
|
+
|
|
30
|
+
return new Response(result.body as BodyInit, {
|
|
31
|
+
status: 200,
|
|
32
|
+
headers: {
|
|
33
|
+
'Cache-Control': 'private, no-store',
|
|
34
|
+
'Content-Disposition': inboxAttachmentContentDisposition(result.attachment.name),
|
|
35
|
+
'Content-Length': String(result.body.byteLength),
|
|
36
|
+
'Content-Type': inboxAttachmentContentType(result.contentType),
|
|
37
|
+
'X-Content-Type-Options': 'nosniff',
|
|
38
|
+
},
|
|
39
|
+
})
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
return inboxActionError(error, 'The attachment could not be downloaded.')
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
})
|
|
@@ -2,7 +2,7 @@ import type { RequestInstance } from '@stacksjs/types'
|
|
|
2
2
|
import { Action } from '@stacksjs/actions'
|
|
3
3
|
import { emailSDK } from '@stacksjs/email'
|
|
4
4
|
import { response } from '@stacksjs/router'
|
|
5
|
-
import {
|
|
5
|
+
import { dashboardMailbox, inboxActionError } from './inbox-request'
|
|
6
6
|
|
|
7
7
|
export default new Action({
|
|
8
8
|
name: 'InboxDeleteAction',
|
|
@@ -11,16 +11,21 @@ export default new Action({
|
|
|
11
11
|
apiResponse: true,
|
|
12
12
|
|
|
13
13
|
async handle(request: RequestInstance) {
|
|
14
|
-
const mailbox = String(request.get('mailbox') || defaultMailbox())
|
|
15
14
|
const messageId = String(request.getParam('id') || '')
|
|
16
15
|
|
|
17
16
|
if (!messageId)
|
|
18
17
|
return response.json({ message: 'A message ID is required.' }, 422)
|
|
19
18
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
19
|
+
try {
|
|
20
|
+
const mailbox = dashboardMailbox(request)
|
|
21
|
+
const success = await emailSDK.delete(mailbox, messageId)
|
|
22
|
+
if (!success)
|
|
23
|
+
return response.json({ message: 'Email not found or it could not be deleted.' }, 404)
|
|
23
24
|
|
|
24
|
-
|
|
25
|
+
return response.json({ success: true, mailbox, messageId })
|
|
26
|
+
}
|
|
27
|
+
catch (error) {
|
|
28
|
+
return inboxActionError(error, 'The email could not be deleted.')
|
|
29
|
+
}
|
|
25
30
|
},
|
|
26
31
|
})
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
+
import type { RequestInstance } from '@stacksjs/types'
|
|
1
2
|
import { Action } from '@stacksjs/actions'
|
|
2
3
|
import { emailSDK } from '@stacksjs/email'
|
|
3
|
-
import {
|
|
4
|
-
import { defaultMailbox } from './mail-preference'
|
|
4
|
+
import { dashboardMailbox, inboxActionError } from './inbox-request'
|
|
5
5
|
|
|
6
6
|
export interface InboxItem {
|
|
7
7
|
messageId: string
|
|
@@ -22,9 +22,9 @@ export default new Action({
|
|
|
22
22
|
method: 'GET',
|
|
23
23
|
apiResponse: true,
|
|
24
24
|
|
|
25
|
-
async handle(request:
|
|
25
|
+
async handle(request: RequestInstance) {
|
|
26
26
|
try {
|
|
27
|
-
const mailbox = request
|
|
27
|
+
const mailbox = dashboardMailbox(request)
|
|
28
28
|
const emails = await emailSDK.getInbox(mailbox, { limit: 1000 })
|
|
29
29
|
|
|
30
30
|
return {
|
|
@@ -34,9 +34,7 @@ export default new Action({
|
|
|
34
34
|
}
|
|
35
35
|
}
|
|
36
36
|
catch (err) {
|
|
37
|
-
return
|
|
38
|
-
message: err instanceof Error ? err.message : 'Inbox messages could not be loaded.',
|
|
39
|
-
}, 503)
|
|
37
|
+
return inboxActionError(err, 'Inbox messages could not be loaded.')
|
|
40
38
|
}
|
|
41
39
|
},
|
|
42
40
|
})
|
|
@@ -2,7 +2,7 @@ import type { RequestInstance } from '@stacksjs/types'
|
|
|
2
2
|
import { Action } from '@stacksjs/actions'
|
|
3
3
|
import { emailSDK } from '@stacksjs/email'
|
|
4
4
|
import { response } from '@stacksjs/router'
|
|
5
|
-
import {
|
|
5
|
+
import { dashboardMailbox, inboxActionError } from './inbox-request'
|
|
6
6
|
|
|
7
7
|
export default new Action({
|
|
8
8
|
name: 'InboxMarkReadAction',
|
|
@@ -11,16 +11,21 @@ export default new Action({
|
|
|
11
11
|
apiResponse: true,
|
|
12
12
|
|
|
13
13
|
async handle(request: RequestInstance) {
|
|
14
|
-
const mailbox = String(request.get('mailbox') || defaultMailbox())
|
|
15
14
|
const messageId = String(request.get('messageId') || '')
|
|
16
15
|
|
|
17
16
|
if (!messageId)
|
|
18
17
|
return response.json({ message: 'messageId is required.' }, 422)
|
|
19
18
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
19
|
+
try {
|
|
20
|
+
const mailbox = dashboardMailbox(request)
|
|
21
|
+
const success = await emailSDK.markAsRead(mailbox, messageId)
|
|
22
|
+
if (!success)
|
|
23
|
+
return response.json({ message: 'Email not found or its read state could not be updated.' }, 404)
|
|
23
24
|
|
|
24
|
-
|
|
25
|
+
return response.json({ success: true, mailbox, messageId })
|
|
26
|
+
}
|
|
27
|
+
catch (error) {
|
|
28
|
+
return inboxActionError(error, 'The email read state could not be updated.')
|
|
29
|
+
}
|
|
25
30
|
},
|
|
26
31
|
})
|
|
@@ -2,7 +2,7 @@ import type { RequestInstance } from '@stacksjs/types'
|
|
|
2
2
|
import { Action } from '@stacksjs/actions'
|
|
3
3
|
import { emailSDK } from '@stacksjs/email'
|
|
4
4
|
import { response } from '@stacksjs/router'
|
|
5
|
-
import {
|
|
5
|
+
import { dashboardMailbox, inboxActionError } from './inbox-request'
|
|
6
6
|
|
|
7
7
|
export default new Action({
|
|
8
8
|
name: 'InboxMarkUnreadAction',
|
|
@@ -11,16 +11,21 @@ export default new Action({
|
|
|
11
11
|
apiResponse: true,
|
|
12
12
|
|
|
13
13
|
async handle(request: RequestInstance) {
|
|
14
|
-
const mailbox = String(request.get('mailbox') || defaultMailbox())
|
|
15
14
|
const messageId = String(request.get('messageId') || '')
|
|
16
15
|
|
|
17
16
|
if (!messageId)
|
|
18
17
|
return response.json({ message: 'messageId is required.' }, 422)
|
|
19
18
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
19
|
+
try {
|
|
20
|
+
const mailbox = dashboardMailbox(request)
|
|
21
|
+
const success = await emailSDK.markAsUnread(mailbox, messageId)
|
|
22
|
+
if (!success)
|
|
23
|
+
return response.json({ message: 'Email not found or its read state could not be updated.' }, 404)
|
|
23
24
|
|
|
24
|
-
|
|
25
|
+
return response.json({ success: true, mailbox, messageId })
|
|
26
|
+
}
|
|
27
|
+
catch (error) {
|
|
28
|
+
return inboxActionError(error, 'The email read state could not be updated.')
|
|
29
|
+
}
|
|
25
30
|
},
|
|
26
31
|
})
|
|
@@ -2,7 +2,8 @@ import type { RequestInstance } from '@stacksjs/types'
|
|
|
2
2
|
import { Action } from '@stacksjs/actions'
|
|
3
3
|
import { MailPreference } from '@stacksjs/orm'
|
|
4
4
|
import { response } from '@stacksjs/router'
|
|
5
|
-
import {
|
|
5
|
+
import { dashboardMailbox, inboxActionError } from './inbox-request'
|
|
6
|
+
import { serializeMailPreference } from './mail-preference'
|
|
6
7
|
|
|
7
8
|
export default new Action({
|
|
8
9
|
name: 'InboxPreferenceShowAction',
|
|
@@ -11,8 +12,13 @@ export default new Action({
|
|
|
11
12
|
apiResponse: true,
|
|
12
13
|
|
|
13
14
|
async handle(request: RequestInstance) {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
try {
|
|
16
|
+
const mailbox = dashboardMailbox(request)
|
|
17
|
+
const record = await MailPreference.where('mailbox', '=', mailbox).first()
|
|
18
|
+
return response.json({ preference: serializeMailPreference(record, mailbox) })
|
|
19
|
+
}
|
|
20
|
+
catch (error) {
|
|
21
|
+
return inboxActionError(error, 'Mail settings could not be loaded.')
|
|
22
|
+
}
|
|
17
23
|
},
|
|
18
24
|
})
|
|
@@ -2,8 +2,8 @@ import type { RequestInstance } from '@stacksjs/types'
|
|
|
2
2
|
import { Action } from '@stacksjs/actions'
|
|
3
3
|
import { MailPreference } from '@stacksjs/orm'
|
|
4
4
|
import { response } from '@stacksjs/router'
|
|
5
|
+
import { dashboardMailbox, inboxActionError } from './inbox-request'
|
|
5
6
|
import {
|
|
6
|
-
defaultMailbox,
|
|
7
7
|
type MailPreferenceInput,
|
|
8
8
|
mailPreferenceAttributes,
|
|
9
9
|
normalizeStringList,
|
|
@@ -132,14 +132,13 @@ export default new Action({
|
|
|
132
132
|
apiResponse: true,
|
|
133
133
|
|
|
134
134
|
async handle(request: RequestInstance) {
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
.
|
|
141
|
-
|
|
142
|
-
return response.json({ message: 'The mail settings are invalid.', fields: { mailbox: 'Enter a valid mailbox.' } }, 422)
|
|
135
|
+
let mailbox: string
|
|
136
|
+
try {
|
|
137
|
+
mailbox = dashboardMailbox(request)
|
|
138
|
+
}
|
|
139
|
+
catch (error) {
|
|
140
|
+
return inboxActionError(error, 'Mail settings could not be updated.')
|
|
141
|
+
}
|
|
143
142
|
|
|
144
143
|
const { fields, input } = inputFromRequest(request, mailbox)
|
|
145
144
|
if (Object.keys(fields).length)
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
+
import type { RequestInstance } from '@stacksjs/types'
|
|
1
2
|
import { Action } from '@stacksjs/actions'
|
|
2
3
|
import { emailSDK } from '@stacksjs/email'
|
|
3
4
|
import { response } from '@stacksjs/router'
|
|
4
|
-
import {
|
|
5
|
+
import { dashboardMailbox, inboxActionError } from './inbox-request'
|
|
5
6
|
import { sanitizeInboxHtml } from './sanitize-inbox-html'
|
|
6
7
|
|
|
7
8
|
export default new Action({
|
|
@@ -10,10 +11,10 @@ export default new Action({
|
|
|
10
11
|
method: 'GET',
|
|
11
12
|
apiResponse: true,
|
|
12
13
|
|
|
13
|
-
async handle(request:
|
|
14
|
+
async handle(request: RequestInstance) {
|
|
14
15
|
try {
|
|
15
|
-
const mailbox = request
|
|
16
|
-
const messageId = request
|
|
16
|
+
const mailbox = dashboardMailbox(request)
|
|
17
|
+
const messageId = String(request.getParam('id') || '')
|
|
17
18
|
|
|
18
19
|
if (!messageId)
|
|
19
20
|
return response.json({ message: 'A message ID is required.' }, 422)
|
|
@@ -28,12 +29,11 @@ export default new Action({
|
|
|
28
29
|
html: sanitizeInboxHtml(email.html ?? ''),
|
|
29
30
|
text: email.text ?? '',
|
|
30
31
|
metadata: email.metadata,
|
|
32
|
+
attachments: email.attachments,
|
|
31
33
|
}
|
|
32
34
|
}
|
|
33
35
|
catch (err) {
|
|
34
|
-
return
|
|
35
|
-
message: err instanceof Error ? err.message : 'Email content could not be loaded.',
|
|
36
|
-
}, 503)
|
|
36
|
+
return inboxActionError(err, 'Email content could not be loaded.')
|
|
37
37
|
}
|
|
38
38
|
},
|
|
39
39
|
})
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
+
import type { RequestInstance } from '@stacksjs/types'
|
|
1
2
|
import { Action } from '@stacksjs/actions'
|
|
2
3
|
import { emailSDK } from '@stacksjs/email'
|
|
3
|
-
import {
|
|
4
|
-
import { defaultMailbox } from './mail-preference'
|
|
4
|
+
import { dashboardMailbox, inboxActionError } from './inbox-request'
|
|
5
5
|
|
|
6
6
|
export default new Action({
|
|
7
7
|
name: 'InboxStatsAction',
|
|
@@ -9,9 +9,9 @@ export default new Action({
|
|
|
9
9
|
method: 'GET',
|
|
10
10
|
apiResponse: true,
|
|
11
11
|
|
|
12
|
-
async handle(request:
|
|
12
|
+
async handle(request: RequestInstance) {
|
|
13
13
|
try {
|
|
14
|
-
const mailbox = request
|
|
14
|
+
const mailbox = dashboardMailbox(request)
|
|
15
15
|
const stats = await emailSDK.getInboxStats(mailbox)
|
|
16
16
|
|
|
17
17
|
return {
|
|
@@ -20,9 +20,7 @@ export default new Action({
|
|
|
20
20
|
}
|
|
21
21
|
}
|
|
22
22
|
catch (err) {
|
|
23
|
-
return
|
|
24
|
-
message: err instanceof Error ? err.message : 'Inbox statistics could not be loaded.',
|
|
25
|
-
}, 503)
|
|
23
|
+
return inboxActionError(err, 'Inbox statistics could not be loaded.')
|
|
26
24
|
}
|
|
27
25
|
},
|
|
28
26
|
})
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { RequestInstance } from '@stacksjs/types'
|
|
2
|
+
import { config } from '@stacksjs/config'
|
|
3
|
+
import { inboxMailboxPath, InvalidInboxMailboxError } from '@stacksjs/email'
|
|
4
|
+
import { response } from '@stacksjs/router'
|
|
5
|
+
import { defaultMailbox } from './mail-preference'
|
|
6
|
+
|
|
7
|
+
function configuredMailDomain(): string {
|
|
8
|
+
const email = (config as any)?.email || {}
|
|
9
|
+
const fromAddress = String(email.from?.address || '').trim()
|
|
10
|
+
const fromDomain = fromAddress.includes('@') ? fromAddress.split('@').at(-1) : ''
|
|
11
|
+
return String(email.domain || fromDomain || 'stacksjs.com').trim().toLowerCase()
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function dashboardMailbox(request: RequestInstance): string {
|
|
15
|
+
const value = request.get('mailbox')
|
|
16
|
+
if (value !== null && value !== undefined && typeof value !== 'string')
|
|
17
|
+
throw new InvalidInboxMailboxError()
|
|
18
|
+
|
|
19
|
+
return inboxMailboxPath(value?.trim() || defaultMailbox(), configuredMailDomain()).address
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function inboxActionError(error: unknown, fallback: string): Response {
|
|
23
|
+
if (error instanceof InvalidInboxMailboxError) {
|
|
24
|
+
return response.json({
|
|
25
|
+
message: 'Enter a mailbox on the configured email domain.',
|
|
26
|
+
fields: { mailbox: 'Enter a mailbox on the configured email domain.' },
|
|
27
|
+
}, 422)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return response.json({
|
|
31
|
+
message: fallback,
|
|
32
|
+
}, 503)
|
|
33
|
+
}
|
|
@@ -1,15 +1,14 @@
|
|
|
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 { dashboardRequestValue } from '../dashboard-request'
|
|
5
6
|
import { DASHBOARD_LOG_TYPES, normalizeDashboardLog, summarizeDashboardLogTypes } from './log-dashboard'
|
|
6
7
|
|
|
7
8
|
const RANGES = ['1', '7', '30', '90', 'all'] as const
|
|
8
9
|
|
|
9
10
|
function queryValue(request: RequestInstance, key: string): string {
|
|
10
|
-
|
|
11
|
-
const value = query[key]
|
|
12
|
-
return String((Array.isArray(value) ? value[0] : value) || request.get(key) || '').trim()
|
|
11
|
+
return dashboardRequestValue(request, key)
|
|
13
12
|
}
|
|
14
13
|
|
|
15
14
|
export default new Action({
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { RequestInstance } from '@stacksjs/types'
|
|
1
2
|
import { Action } from '@stacksjs/actions'
|
|
2
3
|
import { response } from '@stacksjs/router'
|
|
3
4
|
import { tsCloud } from '~/config/cloud'
|
|
@@ -9,12 +10,8 @@ export default new Action({
|
|
|
9
10
|
description: 'Returns one configured server or persisted deployment.',
|
|
10
11
|
method: 'GET',
|
|
11
12
|
apiResponse: true,
|
|
12
|
-
async handle(request) {
|
|
13
|
-
const identifier =
|
|
14
|
-
(request as any)?.params?.id
|
|
15
|
-
?? (request as any)?.param?.('id')
|
|
16
|
-
?? '',
|
|
17
|
-
)
|
|
13
|
+
async handle(request: RequestInstance) {
|
|
14
|
+
const identifier = request.getParam('id')
|
|
18
15
|
if (!identifier)
|
|
19
16
|
return response.json({ error: 'A server identifier is required.' }, 400)
|
|
20
17
|
|