@stacksjs/defaults 0.70.357 → 0.70.362
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 +10 -5
- package/ai/skills/stacks-browse/scripts/browse.ts +109 -13
- package/app/Actions/AI/AskAction.ts +12 -17
- package/app/Actions/AI/SummaryAction.ts +12 -17
- package/app/Actions/Dashboard/Actions/GetActions.ts +15 -9
- package/app/Actions/Dashboard/Analytics/EventStoreAction.ts +21 -9
- package/app/Actions/Dashboard/Commerce/ProductUnitDefaultAction.ts +12 -3
- package/app/Actions/Dashboard/Commerce/TaxRateDefaultAction.ts +12 -3
- package/app/Actions/Dashboard/DashboardHealthAction.ts +34 -14
- package/app/Actions/Dashboard/DashboardHomeAction.test.ts +1 -1
- package/app/Actions/Dashboard/DashboardHomeAction.ts +31 -4
- package/app/Actions/Dashboard/DashboardStatsAction.ts +6 -1
- package/app/Actions/Dashboard/Email/InboxSendAction.ts +14 -7
- package/app/Actions/Dashboard/Infrastructure/CommandIndexAction.ts +33 -27
- package/app/Actions/Dashboard/Infrastructure/EnvironmentIndexAction.ts +10 -4
- package/app/Actions/Dashboard/Infrastructure/EnvironmentUpdateAction.ts +8 -1
- package/app/Actions/Dashboard/Infrastructure/RequestIndexAction.ts +24 -18
- package/app/Actions/Dashboard/Infrastructure/ServerIndexAction.ts +15 -9
- package/app/Actions/Dashboard/Infrastructure/ServerShowAction.ts +9 -2
- package/app/Actions/Dashboard/Marketing/CampaignCancelAction.ts +23 -5
- package/app/Actions/Dashboard/Marketing/CampaignDestroyAction.ts +26 -17
- package/app/Actions/Dashboard/Marketing/CampaignIndexAction.ts +49 -43
- package/app/Actions/Dashboard/Marketing/CampaignScheduleAction.ts +31 -25
- package/app/Actions/Dashboard/Marketing/CampaignSendAction.ts +28 -22
- package/app/Actions/Dashboard/Marketing/CampaignStoreAction.ts +15 -10
- package/app/Actions/Dashboard/Marketing/CampaignUpdateAction.ts +19 -12
- package/app/Actions/Dashboard/Marketing/ListDestroyAction.ts +34 -25
- package/app/Actions/Dashboard/Marketing/ListIndexAction.ts +32 -26
- package/app/Actions/Dashboard/Marketing/ListStoreAction.ts +31 -18
- package/app/Actions/Dashboard/Marketing/ListUpdateAction.ts +35 -19
- package/app/Actions/Dashboard/Marketing/SocialPostDestroyAction.ts +15 -5
- package/app/Actions/Dashboard/Marketing/SocialPostIndexAction.ts +11 -5
- package/app/Actions/Dashboard/Marketing/SocialPostStoreAction.ts +19 -13
- package/app/Actions/Dashboard/Marketing/SocialPostUpdateAction.ts +22 -12
- package/app/Actions/Dashboard/Marketing/campaign-delivery.ts +34 -0
- package/app/Actions/Dashboard/Marketing/marketing-list-records.test.ts +10 -0
- package/app/Actions/Dashboard/Marketing/marketing-list-records.ts +10 -0
- package/app/Actions/Dashboard/Marketing/marketing-response.ts +28 -0
- package/app/Actions/Dashboard/Marketing/social-post-records.test.ts +13 -0
- package/app/Actions/Dashboard/Marketing/social-post-records.ts +22 -4
- package/app/Actions/Dashboard/Models/GetModels.ts +14 -7
- package/app/Actions/Dashboard/Models/GetSubscriberCount.ts +8 -1
- package/app/Actions/Dashboard/Models/GetUserCount.ts +8 -1
- package/app/Actions/Dashboard/Releases/ReleaseIndexAction.ts +11 -5
- package/app/Actions/Dashboard/Settings/MailSettingsGetAction.ts +10 -4
- package/app/Actions/Dashboard/Settings/MailSettingsUpdateAction.ts +8 -1
- package/app/Actions/LogAction.ts +11 -11
- package/ide/vscode/package.json +1 -1
- package/package.json +1 -1
- package/resources/components/Dashboard/Marketing/CampaignDeleteDialog.stx +11 -12
- package/resources/components/Dashboard/Marketing/MarketingListDeleteDialog.stx +15 -15
- package/resources/components/Dashboard/Marketing/SocialPostDeleteDialog.stx +11 -12
- package/resources/components/Dashboard/UI/ConfirmDialog.stx +51 -53
- package/resources/components/Dashboard/UI/Modal.stx +83 -29
- package/views/dashboard/stores/auth.ts +18 -2
|
@@ -2,7 +2,8 @@ import type { RequestInstance } from '@stacksjs/types'
|
|
|
2
2
|
import { Action } from '@stacksjs/actions'
|
|
3
3
|
import { SocialPost } from '@stacksjs/orm'
|
|
4
4
|
import { response } from '@stacksjs/router'
|
|
5
|
-
import {
|
|
5
|
+
import { marketingModelError, marketingRecordId } from './marketing-response'
|
|
6
|
+
import { socialPostWriteData, validateSocialPostWriteData } from './social-post-records'
|
|
6
7
|
|
|
7
8
|
export default new Action({
|
|
8
9
|
name: 'SocialPostUpdateAction',
|
|
@@ -11,18 +12,27 @@ export default new Action({
|
|
|
11
12
|
model: SocialPost,
|
|
12
13
|
|
|
13
14
|
async handle(request: RequestInstance) {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
if (!post)
|
|
18
|
-
return response.json({ message: 'Social post not found.' }, 404)
|
|
19
|
-
|
|
15
|
+
const id = marketingRecordId(request)
|
|
16
|
+
if (!id)
|
|
17
|
+
return response.json({ message: 'A valid social post id is required.' }, 400)
|
|
20
18
|
const data = socialPostWriteData(await request.all())
|
|
21
|
-
const
|
|
22
|
-
if (
|
|
23
|
-
return response.json({ message:
|
|
19
|
+
const validationError = validateSocialPostWriteData(data)
|
|
20
|
+
if (validationError)
|
|
21
|
+
return response.json({ message: validationError }, 422)
|
|
22
|
+
|
|
23
|
+
try {
|
|
24
|
+
const post = await SocialPost.find(id)
|
|
25
|
+
if (!post)
|
|
26
|
+
return response.json({ message: 'Social post not found.' }, 404)
|
|
24
27
|
|
|
25
|
-
|
|
26
|
-
|
|
28
|
+
await post.update({
|
|
29
|
+
...data,
|
|
30
|
+
platform: data.platform as Exclude<typeof data.platform, ''>,
|
|
31
|
+
})
|
|
32
|
+
return response.json({ id })
|
|
33
|
+
}
|
|
34
|
+
catch (error) {
|
|
35
|
+
return marketingModelError(error, 'Social post could not be updated.', 'SocialPostUpdateAction')
|
|
36
|
+
}
|
|
27
37
|
},
|
|
28
38
|
})
|
|
@@ -1,5 +1,39 @@
|
|
|
1
|
+
import { db } from '@stacksjs/database'
|
|
2
|
+
import { Campaign, EmailList } from '@stacksjs/orm'
|
|
3
|
+
|
|
1
4
|
export type CampaignDeliveryOperation = 'send' | 'schedule' | 'cancel'
|
|
2
5
|
|
|
6
|
+
export interface CampaignDeliveryContext {
|
|
7
|
+
campaign: any | null
|
|
8
|
+
activeMembers: number
|
|
9
|
+
listStatus: string
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export async function loadCampaignDeliveryContext(id: number): Promise<CampaignDeliveryContext> {
|
|
13
|
+
const campaign = await Campaign.find(id)
|
|
14
|
+
if (!campaign)
|
|
15
|
+
return { campaign: null, activeMembers: 0, listStatus: '' }
|
|
16
|
+
|
|
17
|
+
const listId = Number(campaign.get('email_list_id') || 0)
|
|
18
|
+
const [list, memberRow] = listId
|
|
19
|
+
? await Promise.all([
|
|
20
|
+
EmailList.find(listId),
|
|
21
|
+
db
|
|
22
|
+
.selectFrom('email_list_subscribers')
|
|
23
|
+
.select(db.fn.count('id').as('count'))
|
|
24
|
+
.where('email_list_id', '=', listId)
|
|
25
|
+
.where('status', '=', 'subscribed')
|
|
26
|
+
.executeTakeFirst(),
|
|
27
|
+
])
|
|
28
|
+
: [null, null]
|
|
29
|
+
|
|
30
|
+
return {
|
|
31
|
+
campaign,
|
|
32
|
+
activeMembers: Number(memberRow?.count || 0),
|
|
33
|
+
listStatus: String(list?.get('status') || ''),
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
3
37
|
function value(record: any, ...keys: string[]): unknown {
|
|
4
38
|
for (const key of keys) {
|
|
5
39
|
const result = typeof record?.get === 'function' ? record.get(key) : record?.[key]
|
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
marketingListWriteData,
|
|
4
4
|
normalizeMarketingLists,
|
|
5
5
|
slugifyMarketingList,
|
|
6
|
+
validateMarketingListWriteData,
|
|
6
7
|
} from './marketing-list-records'
|
|
7
8
|
|
|
8
9
|
describe('marketing list records', () => {
|
|
@@ -68,4 +69,13 @@ describe('marketing list records', () => {
|
|
|
68
69
|
doubleOptIn: 0,
|
|
69
70
|
})
|
|
70
71
|
})
|
|
72
|
+
|
|
73
|
+
test('rejects empty and oversized dashboard writes before model defaults apply', () => {
|
|
74
|
+
expect(validateMarketingListWriteData(marketingListWriteData({})))
|
|
75
|
+
.toBe('List name must be between 2 and 100 characters.')
|
|
76
|
+
expect(validateMarketingListWriteData(marketingListWriteData({
|
|
77
|
+
name: 'Product News',
|
|
78
|
+
description: 'x'.repeat(501),
|
|
79
|
+
}))).toBe('List description must be 500 characters or fewer.')
|
|
80
|
+
})
|
|
71
81
|
})
|
|
@@ -159,3 +159,13 @@ export function marketingListWriteData(input: Record<string, unknown>): {
|
|
|
159
159
|
doubleOptIn: boolean(input.doubleOptIn ?? input.double_opt_in) ? 1 : 0,
|
|
160
160
|
}
|
|
161
161
|
}
|
|
162
|
+
|
|
163
|
+
export function validateMarketingListWriteData(data: ReturnType<typeof marketingListWriteData>): string {
|
|
164
|
+
if (data.name.length < 2 || data.name.length > 100)
|
|
165
|
+
return 'List name must be between 2 and 100 characters.'
|
|
166
|
+
if (!data.slug || data.slug.length > 120)
|
|
167
|
+
return 'List slug must be between 1 and 120 characters.'
|
|
168
|
+
if (data.description.length > 500)
|
|
169
|
+
return 'List description must be 500 characters or fewer.'
|
|
170
|
+
return ''
|
|
171
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { RequestInstance } from '@stacksjs/types'
|
|
2
|
+
import { isUniqueViolation, ModelValidationError } from '@stacksjs/orm'
|
|
3
|
+
import { response } from '@stacksjs/router'
|
|
4
|
+
import { dashboardOperationalError } from '../dashboard-response'
|
|
5
|
+
|
|
6
|
+
export function marketingRecordId(request: RequestInstance): number | null {
|
|
7
|
+
const id = Number(request.getParam('id'))
|
|
8
|
+
return Number.isSafeInteger(id) && id > 0 ? id : null
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function marketingModelError(
|
|
12
|
+
error: unknown,
|
|
13
|
+
message: string,
|
|
14
|
+
action: string,
|
|
15
|
+
duplicateMessage?: string,
|
|
16
|
+
): Response {
|
|
17
|
+
if (error instanceof ModelValidationError) {
|
|
18
|
+
return response.json({
|
|
19
|
+
message: 'Validation failed.',
|
|
20
|
+
errors: error.errors,
|
|
21
|
+
}, 422)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (duplicateMessage && isUniqueViolation(error))
|
|
25
|
+
return response.json({ message: duplicateMessage }, 422)
|
|
26
|
+
|
|
27
|
+
return dashboardOperationalError(error, message, action, 500)
|
|
28
|
+
}
|
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
normalizeSocialPosts,
|
|
4
4
|
socialPostWriteData,
|
|
5
5
|
validateSocialPostSchedule,
|
|
6
|
+
validateSocialPostWriteData,
|
|
6
7
|
} from './social-post-records'
|
|
7
8
|
|
|
8
9
|
const now = new Date('2026-07-29T12:00:00.000Z')
|
|
@@ -68,4 +69,16 @@ describe('social post records', () => {
|
|
|
68
69
|
}, now)
|
|
69
70
|
expect(validateSocialPostSchedule(stale, now)).toBe('Schedule time must be in the future.')
|
|
70
71
|
})
|
|
72
|
+
|
|
73
|
+
test('rejects missing content, invalid platforms, and unsafe image URLs', () => {
|
|
74
|
+
expect(validateSocialPostWriteData(socialPostWriteData({})))
|
|
75
|
+
.toBe('Post content must be between 1 and 2000 characters.')
|
|
76
|
+
expect(validateSocialPostWriteData(socialPostWriteData({ content: 'Ready to publish' })))
|
|
77
|
+
.toBe('Choose a valid social platform.')
|
|
78
|
+
expect(validateSocialPostWriteData(socialPostWriteData({
|
|
79
|
+
content: 'Ready to publish',
|
|
80
|
+
platform: 'linkedin',
|
|
81
|
+
imageUrl: 'javascript:alert(1)',
|
|
82
|
+
}))).toBe('Image URL must use HTTP or HTTPS.')
|
|
83
|
+
})
|
|
71
84
|
})
|
|
@@ -55,11 +55,11 @@ function number(input: unknown): number {
|
|
|
55
55
|
return Number.isFinite(result) && result >= 0 ? result : 0
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
-
function platform(input: unknown): SocialPostPlatform {
|
|
58
|
+
function platform(input: unknown): SocialPostPlatform | '' {
|
|
59
59
|
const normalized = text(input).toLowerCase()
|
|
60
60
|
return ['twitter', 'facebook', 'instagram', 'linkedin', 'tiktok', 'youtube'].includes(normalized)
|
|
61
61
|
? normalized as SocialPostPlatform
|
|
62
|
-
: '
|
|
62
|
+
: ''
|
|
63
63
|
}
|
|
64
64
|
|
|
65
65
|
function status(input: unknown): SocialPostStatus {
|
|
@@ -89,7 +89,7 @@ export function normalizeSocialPosts(
|
|
|
89
89
|
return {
|
|
90
90
|
id: text(value(post, 'id')),
|
|
91
91
|
content: text(value(post, 'content')),
|
|
92
|
-
platform: platform(value(post, 'platform')),
|
|
92
|
+
platform: platform(value(post, 'platform')) || 'twitter',
|
|
93
93
|
status: status(value(post, 'status')),
|
|
94
94
|
scheduledAt: text(value(post, 'scheduled_at', 'scheduledAt')),
|
|
95
95
|
publishedAt: text(value(post, 'published_at', 'publishedAt')),
|
|
@@ -124,7 +124,7 @@ export function normalizeSocialPosts(
|
|
|
124
124
|
|
|
125
125
|
export function socialPostWriteData(input: Record<string, unknown>, now = new Date()): {
|
|
126
126
|
content: string
|
|
127
|
-
platform: SocialPostPlatform
|
|
127
|
+
platform: SocialPostPlatform | ''
|
|
128
128
|
status: SocialPostStatus
|
|
129
129
|
scheduled_at: string | null
|
|
130
130
|
published_at: string | null
|
|
@@ -161,3 +161,21 @@ export function validateSocialPostSchedule(data: ReturnType<typeof socialPostWri
|
|
|
161
161
|
return 'Schedule time must be in the future.'
|
|
162
162
|
return ''
|
|
163
163
|
}
|
|
164
|
+
|
|
165
|
+
export function validateSocialPostWriteData(data: ReturnType<typeof socialPostWriteData>): string {
|
|
166
|
+
if (data.content.length < 1 || data.content.length > 2000)
|
|
167
|
+
return 'Post content must be between 1 and 2000 characters.'
|
|
168
|
+
if (!data.platform)
|
|
169
|
+
return 'Choose a valid social platform.'
|
|
170
|
+
if (data.image_url) {
|
|
171
|
+
try {
|
|
172
|
+
const url = new URL(data.image_url)
|
|
173
|
+
if (!['http:', 'https:'].includes(url.protocol))
|
|
174
|
+
return 'Image URL must use HTTP or HTTPS.'
|
|
175
|
+
}
|
|
176
|
+
catch {
|
|
177
|
+
return 'Image URL must be a valid URL.'
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
return validateSocialPostSchedule(data)
|
|
181
|
+
}
|
|
@@ -3,15 +3,18 @@ import { Glob } from 'bun'
|
|
|
3
3
|
import { Action } from '@stacksjs/actions'
|
|
4
4
|
import { path } from '@stacksjs/path'
|
|
5
5
|
import { countRows } from '../../../../resources/functions/dashboard/data'
|
|
6
|
+
import { dashboardOperationalError } from '../dashboard-response'
|
|
6
7
|
|
|
7
8
|
export default new Action({
|
|
8
9
|
name: 'GetModels',
|
|
9
10
|
description: 'Gets the application models.',
|
|
11
|
+
method: 'GET',
|
|
10
12
|
|
|
11
13
|
async handle() {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
14
|
+
try {
|
|
15
|
+
const glob = new Glob('**/*.ts')
|
|
16
|
+
const scanOptions = { cwd: path.userModelsPath(), onlyFiles: true }
|
|
17
|
+
const displayModels: Model[] = []
|
|
15
18
|
|
|
16
19
|
for await (const file of glob.scan(scanOptions)) {
|
|
17
20
|
const model = (await import(path.userModelsPath(file))).default as Model
|
|
@@ -31,9 +34,13 @@ export default new Action({
|
|
|
31
34
|
displayModels.length = 8
|
|
32
35
|
displayModels.sort((a, b) => (a.name || '').localeCompare(b.name || ''))
|
|
33
36
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
37
|
+
return await Promise.all(displayModels.map(async model => ({
|
|
38
|
+
model: model.name,
|
|
39
|
+
total: await countRows(model),
|
|
40
|
+
})))
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
return dashboardOperationalError(error, 'Dashboard models could not be loaded.', 'GetModels')
|
|
44
|
+
}
|
|
38
45
|
},
|
|
39
46
|
})
|
|
@@ -1,12 +1,19 @@
|
|
|
1
1
|
import { Action } from '@stacksjs/actions'
|
|
2
2
|
import { Subscriber } from '@stacksjs/orm'
|
|
3
|
+
import { dashboardOperationalError } from '../dashboard-response'
|
|
3
4
|
|
|
4
5
|
export default new Action({
|
|
5
6
|
name: 'GetSubscriberCount',
|
|
6
7
|
description: 'Gets the total number of subscribers.',
|
|
8
|
+
method: 'GET',
|
|
7
9
|
apiResponse: true,
|
|
8
10
|
|
|
9
11
|
async handle() {
|
|
10
|
-
|
|
12
|
+
try {
|
|
13
|
+
return await Subscriber.count()
|
|
14
|
+
}
|
|
15
|
+
catch (error) {
|
|
16
|
+
return dashboardOperationalError(error, 'Subscriber total could not be loaded.', 'GetSubscriberCount')
|
|
17
|
+
}
|
|
11
18
|
},
|
|
12
19
|
})
|
|
@@ -1,12 +1,19 @@
|
|
|
1
1
|
import { Action } from '@stacksjs/actions'
|
|
2
2
|
import { User } from '@stacksjs/orm'
|
|
3
|
+
import { dashboardOperationalError } from '../dashboard-response'
|
|
3
4
|
|
|
4
5
|
export default new Action({
|
|
5
6
|
name: 'GetUserCount',
|
|
6
7
|
description: 'Gets the total number of users.',
|
|
8
|
+
method: 'GET',
|
|
7
9
|
apiResponse: true,
|
|
8
10
|
|
|
9
11
|
async handle() {
|
|
10
|
-
|
|
12
|
+
try {
|
|
13
|
+
return await User.count()
|
|
14
|
+
}
|
|
15
|
+
catch (error) {
|
|
16
|
+
return dashboardOperationalError(error, 'User total could not be loaded.', 'GetUserCount')
|
|
17
|
+
}
|
|
11
18
|
},
|
|
12
19
|
})
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Action } from '@stacksjs/actions'
|
|
2
2
|
import { Release } from '@stacksjs/orm'
|
|
3
|
+
import { dashboardOperationalError } from '../dashboard-response'
|
|
3
4
|
import { normalizeReleaseRecord, summarizeReleases } from './release-records'
|
|
4
5
|
|
|
5
6
|
export default new Action({
|
|
@@ -9,12 +10,17 @@ export default new Action({
|
|
|
9
10
|
apiResponse: true,
|
|
10
11
|
|
|
11
12
|
async handle() {
|
|
12
|
-
|
|
13
|
-
|
|
13
|
+
try {
|
|
14
|
+
const records = await Release.orderByDesc('id').limit(200).get()
|
|
15
|
+
const releases = records.map(normalizeReleaseRecord)
|
|
14
16
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
17
|
+
return {
|
|
18
|
+
releases,
|
|
19
|
+
summary: summarizeReleases(releases),
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
catch (error) {
|
|
23
|
+
return dashboardOperationalError(error, 'Release history could not be loaded.', 'ReleaseIndexAction')
|
|
18
24
|
}
|
|
19
25
|
},
|
|
20
26
|
})
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Action } from '@stacksjs/actions'
|
|
2
|
+
import { dashboardOperationalError } from '../dashboard-response'
|
|
2
3
|
import { readMailSettings } from './mail-settings'
|
|
3
4
|
|
|
4
5
|
export default new Action({
|
|
@@ -8,9 +9,14 @@ export default new Action({
|
|
|
8
9
|
apiResponse: true,
|
|
9
10
|
|
|
10
11
|
async handle() {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
try {
|
|
13
|
+
return Response.json(
|
|
14
|
+
{ settings: await readMailSettings() },
|
|
15
|
+
{ headers: { 'Cache-Control': 'no-store', Pragma: 'no-cache' } },
|
|
16
|
+
)
|
|
17
|
+
}
|
|
18
|
+
catch (error) {
|
|
19
|
+
return dashboardOperationalError(error, 'Mail settings could not be loaded.', 'MailSettingsGetAction')
|
|
20
|
+
}
|
|
15
21
|
},
|
|
16
22
|
})
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { RequestInstance } from '@stacksjs/types'
|
|
2
2
|
import { Action } from '@stacksjs/actions'
|
|
3
3
|
import { response } from '@stacksjs/router'
|
|
4
|
+
import { dashboardOperationalError } from '../dashboard-response'
|
|
4
5
|
import { updateMailSettings } from './mail-settings'
|
|
5
6
|
|
|
6
7
|
export default new Action({
|
|
@@ -10,7 +11,13 @@ export default new Action({
|
|
|
10
11
|
apiResponse: true,
|
|
11
12
|
|
|
12
13
|
async handle(request: RequestInstance) {
|
|
13
|
-
|
|
14
|
+
let result
|
|
15
|
+
try {
|
|
16
|
+
result = await updateMailSettings(await request.all())
|
|
17
|
+
}
|
|
18
|
+
catch (error) {
|
|
19
|
+
return dashboardOperationalError(error, 'Mail settings could not be saved.', 'MailSettingsUpdateAction', 500)
|
|
20
|
+
}
|
|
14
21
|
|
|
15
22
|
if ('validation' in result) {
|
|
16
23
|
return response.json({
|
package/app/Actions/LogAction.ts
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
|
+
import type { RequestInstance } from '@stacksjs/types'
|
|
1
2
|
import { Action } from '@stacksjs/actions'
|
|
2
3
|
import { log } from '@stacksjs/logging'
|
|
4
|
+
import { response } from '@stacksjs/router'
|
|
3
5
|
import { schema } from '@stacksjs/validation'
|
|
4
6
|
|
|
5
|
-
|
|
6
|
-
message: string
|
|
7
|
-
level: 'info' | 'warn' | 'error'
|
|
8
|
-
}
|
|
7
|
+
type LogLevel = 'info' | 'warn' | 'error'
|
|
9
8
|
|
|
10
9
|
export default new Action({
|
|
11
10
|
name: 'Dummy Logger',
|
|
12
11
|
description: 'This action is used to demo how to POST to a server and upon success, log a message.',
|
|
12
|
+
method: 'POST',
|
|
13
|
+
apiResponse: true,
|
|
13
14
|
|
|
14
15
|
// the request object is optional, but if it is provided, it will be used for validation
|
|
15
16
|
validations: {
|
|
@@ -29,14 +30,13 @@ export default new Action({
|
|
|
29
30
|
},
|
|
30
31
|
},
|
|
31
32
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
if (!request)
|
|
35
|
-
return 'No request was provided.'
|
|
33
|
+
async handle(request: RequestInstance) {
|
|
34
|
+
await request.validate()
|
|
36
35
|
|
|
37
|
-
|
|
38
|
-
|
|
36
|
+
const message = String(request.get('message') || '').trim()
|
|
37
|
+
const level = String(request.get('level')) as LogLevel
|
|
38
|
+
log[level](message)
|
|
39
39
|
|
|
40
|
-
return `Logged
|
|
40
|
+
return response.json({ level, message: `Logged at ${level} level.` })
|
|
41
41
|
},
|
|
42
42
|
})
|
package/ide/vscode/package.json
CHANGED
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@stacksjs/defaults",
|
|
3
3
|
"type": "module",
|
|
4
4
|
"sideEffects": false,
|
|
5
|
-
"version": "0.70.
|
|
5
|
+
"version": "0.70.362",
|
|
6
6
|
"description": "The complete managed Stacks application scaffold, including runtime defaults, AI guidance, editor metadata, and npm-backed project support files.",
|
|
7
7
|
"author": "Chris Breuer",
|
|
8
8
|
"license": "MIT",
|
|
@@ -6,17 +6,16 @@ const record = useReactiveProp('record', null as CampaignRecord | null)
|
|
|
6
6
|
const busy = useReactiveProp('busy', false)
|
|
7
7
|
const error = useReactiveProp('error', '')
|
|
8
8
|
const emit = defineEmits()
|
|
9
|
+
const description = derived(() => `The persisted campaign ${record()?.name || ''} will be permanently removed. Campaigns with delivery history are retained.`)
|
|
9
10
|
</script>
|
|
10
11
|
|
|
11
|
-
<
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
</section>
|
|
22
|
-
</div>
|
|
12
|
+
<ConfirmDialog
|
|
13
|
+
:isOpen="open()"
|
|
14
|
+
title="Delete campaign?"
|
|
15
|
+
:description="description()"
|
|
16
|
+
confirmLabel="Delete campaign"
|
|
17
|
+
:busy="busy()"
|
|
18
|
+
:error="error()"
|
|
19
|
+
@close="emit('close')"
|
|
20
|
+
@confirm="emit('confirm')"
|
|
21
|
+
/>
|
|
@@ -6,20 +6,20 @@ const record = useReactiveProp('record', null as MarketingListRecord | null)
|
|
|
6
6
|
const busy = useReactiveProp('busy', false)
|
|
7
7
|
const error = useReactiveProp('error', '')
|
|
8
8
|
const emit = defineEmits()
|
|
9
|
+
const protectedRecord = derived(() => Boolean(record()?.subscriberCount) || Boolean(record()?.campaignCount))
|
|
10
|
+
const description = derived(() => protectedRecord()
|
|
11
|
+
? `${record()?.name || 'This email list'} has membership or campaign history. Archive it instead to preserve relationships.`
|
|
12
|
+
: `${record()?.name || 'This email list'} will be permanently removed.`)
|
|
9
13
|
</script>
|
|
10
14
|
|
|
11
|
-
<
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
</Button>
|
|
23
|
-
</footer>
|
|
24
|
-
</section>
|
|
25
|
-
</div>
|
|
15
|
+
<ConfirmDialog
|
|
16
|
+
:isOpen="open()"
|
|
17
|
+
title="Delete email list?"
|
|
18
|
+
:description="description()"
|
|
19
|
+
confirmLabel="Delete email list"
|
|
20
|
+
:busy="busy()"
|
|
21
|
+
:disabled="protectedRecord()"
|
|
22
|
+
:error="error()"
|
|
23
|
+
@close="emit('close')"
|
|
24
|
+
@confirm="emit('confirm')"
|
|
25
|
+
/>
|
|
@@ -6,17 +6,16 @@ const record = useReactiveProp('record', null as SocialPostRecord | null)
|
|
|
6
6
|
const busy = useReactiveProp('busy', false)
|
|
7
7
|
const error = useReactiveProp('error', '')
|
|
8
8
|
const emit = defineEmits()
|
|
9
|
+
const description = derived(() => `The persisted ${record()?.platform || 'social'} post will be permanently removed. This does not remove a post already published on the external platform.`)
|
|
9
10
|
</script>
|
|
10
11
|
|
|
11
|
-
<
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
</section>
|
|
22
|
-
</div>
|
|
12
|
+
<ConfirmDialog
|
|
13
|
+
:isOpen="open()"
|
|
14
|
+
title="Delete social post?"
|
|
15
|
+
:description="description()"
|
|
16
|
+
confirmLabel="Delete social post"
|
|
17
|
+
:busy="busy()"
|
|
18
|
+
:error="error()"
|
|
19
|
+
@close="emit('close')"
|
|
20
|
+
@confirm="emit('confirm')"
|
|
21
|
+
/>
|