@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.
Files changed (55) hide show
  1. package/ai/skills/stacks-browse/SKILL.md +10 -5
  2. package/ai/skills/stacks-browse/scripts/browse.ts +109 -13
  3. package/app/Actions/AI/AskAction.ts +12 -17
  4. package/app/Actions/AI/SummaryAction.ts +12 -17
  5. package/app/Actions/Dashboard/Actions/GetActions.ts +15 -9
  6. package/app/Actions/Dashboard/Analytics/EventStoreAction.ts +21 -9
  7. package/app/Actions/Dashboard/Commerce/ProductUnitDefaultAction.ts +12 -3
  8. package/app/Actions/Dashboard/Commerce/TaxRateDefaultAction.ts +12 -3
  9. package/app/Actions/Dashboard/DashboardHealthAction.ts +34 -14
  10. package/app/Actions/Dashboard/DashboardHomeAction.test.ts +1 -1
  11. package/app/Actions/Dashboard/DashboardHomeAction.ts +31 -4
  12. package/app/Actions/Dashboard/DashboardStatsAction.ts +6 -1
  13. package/app/Actions/Dashboard/Email/InboxSendAction.ts +14 -7
  14. package/app/Actions/Dashboard/Infrastructure/CommandIndexAction.ts +33 -27
  15. package/app/Actions/Dashboard/Infrastructure/EnvironmentIndexAction.ts +10 -4
  16. package/app/Actions/Dashboard/Infrastructure/EnvironmentUpdateAction.ts +8 -1
  17. package/app/Actions/Dashboard/Infrastructure/RequestIndexAction.ts +24 -18
  18. package/app/Actions/Dashboard/Infrastructure/ServerIndexAction.ts +15 -9
  19. package/app/Actions/Dashboard/Infrastructure/ServerShowAction.ts +9 -2
  20. package/app/Actions/Dashboard/Marketing/CampaignCancelAction.ts +23 -5
  21. package/app/Actions/Dashboard/Marketing/CampaignDestroyAction.ts +26 -17
  22. package/app/Actions/Dashboard/Marketing/CampaignIndexAction.ts +49 -43
  23. package/app/Actions/Dashboard/Marketing/CampaignScheduleAction.ts +31 -25
  24. package/app/Actions/Dashboard/Marketing/CampaignSendAction.ts +28 -22
  25. package/app/Actions/Dashboard/Marketing/CampaignStoreAction.ts +15 -10
  26. package/app/Actions/Dashboard/Marketing/CampaignUpdateAction.ts +19 -12
  27. package/app/Actions/Dashboard/Marketing/ListDestroyAction.ts +34 -25
  28. package/app/Actions/Dashboard/Marketing/ListIndexAction.ts +32 -26
  29. package/app/Actions/Dashboard/Marketing/ListStoreAction.ts +31 -18
  30. package/app/Actions/Dashboard/Marketing/ListUpdateAction.ts +35 -19
  31. package/app/Actions/Dashboard/Marketing/SocialPostDestroyAction.ts +15 -5
  32. package/app/Actions/Dashboard/Marketing/SocialPostIndexAction.ts +11 -5
  33. package/app/Actions/Dashboard/Marketing/SocialPostStoreAction.ts +19 -13
  34. package/app/Actions/Dashboard/Marketing/SocialPostUpdateAction.ts +22 -12
  35. package/app/Actions/Dashboard/Marketing/campaign-delivery.ts +34 -0
  36. package/app/Actions/Dashboard/Marketing/marketing-list-records.test.ts +10 -0
  37. package/app/Actions/Dashboard/Marketing/marketing-list-records.ts +10 -0
  38. package/app/Actions/Dashboard/Marketing/marketing-response.ts +28 -0
  39. package/app/Actions/Dashboard/Marketing/social-post-records.test.ts +13 -0
  40. package/app/Actions/Dashboard/Marketing/social-post-records.ts +22 -4
  41. package/app/Actions/Dashboard/Models/GetModels.ts +14 -7
  42. package/app/Actions/Dashboard/Models/GetSubscriberCount.ts +8 -1
  43. package/app/Actions/Dashboard/Models/GetUserCount.ts +8 -1
  44. package/app/Actions/Dashboard/Releases/ReleaseIndexAction.ts +11 -5
  45. package/app/Actions/Dashboard/Settings/MailSettingsGetAction.ts +10 -4
  46. package/app/Actions/Dashboard/Settings/MailSettingsUpdateAction.ts +8 -1
  47. package/app/Actions/LogAction.ts +11 -11
  48. package/ide/vscode/package.json +1 -1
  49. package/package.json +1 -1
  50. package/resources/components/Dashboard/Marketing/CampaignDeleteDialog.stx +11 -12
  51. package/resources/components/Dashboard/Marketing/MarketingListDeleteDialog.stx +15 -15
  52. package/resources/components/Dashboard/Marketing/SocialPostDeleteDialog.stx +11 -12
  53. package/resources/components/Dashboard/UI/ConfirmDialog.stx +51 -53
  54. package/resources/components/Dashboard/UI/Modal.stx +83 -29
  55. package/views/dashboard/stores/auth.ts +18 -2
@@ -1,13 +1,14 @@
1
1
  import type { RequestInstance } from '@stacksjs/types'
2
2
  import { Action } from '@stacksjs/actions'
3
- import { db } from '@stacksjs/database'
4
- import { campaigns } from '@stacksjs/newsletter'
5
- import { Campaign, EmailList } from '@stacksjs/orm'
3
+ import { campaigns, CampaignStateConflictError } from '@stacksjs/newsletter'
6
4
  import { response } from '@stacksjs/router'
5
+ import { dashboardOperationalError } from '../dashboard-response'
7
6
  import {
8
7
  campaignScheduleIso,
8
+ loadCampaignDeliveryContext,
9
9
  validateCampaignDelivery,
10
10
  } from './campaign-delivery'
11
+ import { marketingRecordId } from './marketing-response'
11
12
 
12
13
  export default new Action({
13
14
  name: 'CampaignScheduleAction',
@@ -15,35 +16,40 @@ export default new Action({
15
16
  method: 'POST',
16
17
 
17
18
  async handle(request: RequestInstance) {
18
- const id = Number(request.getParam('id'))
19
- const campaign = await Campaign.find(id)
20
- if (!campaign)
19
+ const id = marketingRecordId(request)
20
+ if (!id)
21
+ return response.json({ message: 'A valid campaign id is required.' }, 400)
22
+ const schedule = campaignScheduleIso((await request.all()).scheduledAt)
23
+ if (schedule.error)
24
+ return response.json({ message: schedule.error }, 422)
25
+
26
+ let context
27
+ try {
28
+ context = await loadCampaignDeliveryContext(id)
29
+ }
30
+ catch (error) {
31
+ return dashboardOperationalError(error, 'Campaign delivery prerequisites could not be loaded.', 'CampaignScheduleAction.prerequisites')
32
+ }
33
+ if (!context.campaign)
21
34
  return response.json({ message: 'Campaign not found.' }, 404)
22
35
 
23
- const listId = Number(campaign.get('email_list_id') || 0)
24
- const list = listId ? await EmailList.find(listId) : null
25
- const memberRow = listId
26
- ? await db
27
- .selectFrom('email_list_subscribers')
28
- .select(db.fn.count('id').as('count'))
29
- .where('email_list_id', '=', listId)
30
- .where('status', '=', 'subscribed')
31
- .executeTakeFirst()
32
- : null
33
36
  const validationError = validateCampaignDelivery(
34
- campaign,
37
+ context.campaign,
35
38
  'schedule',
36
- Number(memberRow?.count || 0),
37
- String(list?.get('status') || ''),
39
+ context.activeMembers,
40
+ context.listStatus,
38
41
  )
39
42
  if (validationError)
40
43
  return response.json({ message: validationError }, 422)
41
44
 
42
- const schedule = campaignScheduleIso((await request.all()).scheduledAt)
43
- if (schedule.error)
44
- return response.json({ message: schedule.error }, 422)
45
-
46
- await campaigns.schedule(id, schedule.value)
47
- return response.json({ id, status: 'scheduled', scheduledAt: schedule.value }, 202)
45
+ try {
46
+ await campaigns.schedule(id, schedule.value)
47
+ return response.json({ id, status: 'scheduled', scheduledAt: schedule.value }, 202)
48
+ }
49
+ catch (error) {
50
+ if (error instanceof CampaignStateConflictError)
51
+ return response.json({ message: 'Campaign delivery state changed. Refresh and try again.' }, 409)
52
+ return dashboardOperationalError(error, 'Campaign could not be scheduled.', 'CampaignScheduleAction.queue', 500)
53
+ }
48
54
  },
49
55
  })
@@ -1,10 +1,10 @@
1
1
  import type { RequestInstance } from '@stacksjs/types'
2
2
  import { Action } from '@stacksjs/actions'
3
- import { db } from '@stacksjs/database'
4
- import { campaigns } from '@stacksjs/newsletter'
5
- import { Campaign, EmailList } from '@stacksjs/orm'
3
+ import { campaigns, CampaignStateConflictError } from '@stacksjs/newsletter'
6
4
  import { response } from '@stacksjs/router'
7
- import { validateCampaignDelivery } from './campaign-delivery'
5
+ import { dashboardOperationalError } from '../dashboard-response'
6
+ import { loadCampaignDeliveryContext, validateCampaignDelivery } from './campaign-delivery'
7
+ import { marketingRecordId } from './marketing-response'
8
8
 
9
9
  export default new Action({
10
10
  name: 'CampaignSendAction',
@@ -12,31 +12,37 @@ export default new Action({
12
12
  method: 'POST',
13
13
 
14
14
  async handle(request: RequestInstance) {
15
- const id = Number(request.getParam('id'))
16
- const campaign = await Campaign.find(id)
17
- if (!campaign)
15
+ const id = marketingRecordId(request)
16
+ if (!id)
17
+ return response.json({ message: 'A valid campaign id is required.' }, 400)
18
+
19
+ let context
20
+ try {
21
+ context = await loadCampaignDeliveryContext(id)
22
+ }
23
+ catch (error) {
24
+ return dashboardOperationalError(error, 'Campaign delivery prerequisites could not be loaded.', 'CampaignSendAction.prerequisites')
25
+ }
26
+ if (!context.campaign)
18
27
  return response.json({ message: 'Campaign not found.' }, 404)
19
28
 
20
- const listId = Number(campaign.get('email_list_id') || 0)
21
- const list = listId ? await EmailList.find(listId) : null
22
- const memberRow = listId
23
- ? await db
24
- .selectFrom('email_list_subscribers')
25
- .select(db.fn.count('id').as('count'))
26
- .where('email_list_id', '=', listId)
27
- .where('status', '=', 'subscribed')
28
- .executeTakeFirst()
29
- : null
30
29
  const validationError = validateCampaignDelivery(
31
- campaign,
30
+ context.campaign,
32
31
  'send',
33
- Number(memberRow?.count || 0),
34
- String(list?.get('status') || ''),
32
+ context.activeMembers,
33
+ context.listStatus,
35
34
  )
36
35
  if (validationError)
37
36
  return response.json({ message: validationError }, 422)
38
37
 
39
- await campaigns.sendNow(id)
40
- return response.json({ id, status: 'sending' }, 202)
38
+ try {
39
+ await campaigns.sendNow(id)
40
+ return response.json({ id, status: 'sending' }, 202)
41
+ }
42
+ catch (error) {
43
+ if (error instanceof CampaignStateConflictError)
44
+ return response.json({ message: 'Campaign delivery state changed. Refresh and try again.' }, 409)
45
+ return dashboardOperationalError(error, 'Campaign could not be queued.', 'CampaignSendAction.queue', 500)
46
+ }
41
47
  },
42
48
  })
@@ -4,6 +4,7 @@ import { config } from '@stacksjs/config'
4
4
  import { Campaign } from '@stacksjs/orm'
5
5
  import { response } from '@stacksjs/router'
6
6
  import { campaignWriteData, validateCampaignWriteData } from './campaign-records'
7
+ import { marketingModelError } from './marketing-response'
7
8
 
8
9
  export default new Action({
9
10
  name: 'CampaignStoreAction',
@@ -12,7 +13,6 @@ export default new Action({
12
13
  model: Campaign,
13
14
 
14
15
  async handle(request: RequestInstance) {
15
- await request.validate()
16
16
  const data = campaignWriteData(
17
17
  await request.all(),
18
18
  String((config as any).commerce?.currency || 'USD').toUpperCase(),
@@ -23,14 +23,19 @@ export default new Action({
23
23
  if (['sending', 'sent'].includes(data.status))
24
24
  return response.json({ message: 'Campaign delivery status is managed by the send pipeline.' }, 422)
25
25
 
26
- const campaign = await Campaign.create({
27
- ...data,
28
- audience_size: 0,
29
- sent_count: 0,
30
- open_rate: null,
31
- click_rate: null,
32
- conversion_rate: null,
33
- })
34
- return response.json({ id: campaign.get('id') }, 201)
26
+ try {
27
+ const campaign = await Campaign.create({
28
+ ...data,
29
+ audience_size: 0,
30
+ sent_count: 0,
31
+ open_rate: null,
32
+ click_rate: null,
33
+ conversion_rate: null,
34
+ })
35
+ return response.json({ id: campaign.get('id') }, 201)
36
+ }
37
+ catch (error) {
38
+ return marketingModelError(error, 'Campaign could not be created.', 'CampaignStoreAction')
39
+ }
35
40
  },
36
41
  })
@@ -4,6 +4,7 @@ import { config } from '@stacksjs/config'
4
4
  import { Campaign } from '@stacksjs/orm'
5
5
  import { response } from '@stacksjs/router'
6
6
  import { campaignWriteData, validateCampaignWriteData } from './campaign-records'
7
+ import { marketingModelError, marketingRecordId } from './marketing-response'
7
8
 
8
9
  export default new Action({
9
10
  name: 'CampaignUpdateAction',
@@ -12,16 +13,9 @@ export default new Action({
12
13
  model: Campaign,
13
14
 
14
15
  async handle(request: RequestInstance) {
15
- await request.validate()
16
- const id = Number(request.getParam('id'))
17
- const campaign = await Campaign.find(id)
18
- if (!campaign)
19
- return response.json({ message: 'Campaign not found.' }, 404)
20
-
21
- const currentStatus = String(campaign.get('status') || '')
22
- if (['sending', 'sent'].includes(currentStatus))
23
- return response.json({ message: 'Campaigns that entered delivery cannot be edited.' }, 409)
24
-
16
+ const id = marketingRecordId(request)
17
+ if (!id)
18
+ return response.json({ message: 'A valid campaign id is required.' }, 400)
25
19
  const data = campaignWriteData(
26
20
  await request.all(),
27
21
  String((config as any).commerce?.currency || 'USD').toUpperCase(),
@@ -32,7 +26,20 @@ export default new Action({
32
26
  if (['sending', 'sent'].includes(data.status))
33
27
  return response.json({ message: 'Campaign delivery status is managed by the send pipeline.' }, 422)
34
28
 
35
- await campaign.update(data)
36
- return response.json({ id })
29
+ try {
30
+ const campaign = await Campaign.find(id)
31
+ if (!campaign)
32
+ return response.json({ message: 'Campaign not found.' }, 404)
33
+
34
+ const currentStatus = String(campaign.get('status') || '')
35
+ if (['sending', 'sent'].includes(currentStatus))
36
+ return response.json({ message: 'Campaigns that entered delivery cannot be edited.' }, 409)
37
+
38
+ await campaign.update(data)
39
+ return response.json({ id })
40
+ }
41
+ catch (error) {
42
+ return marketingModelError(error, 'Campaign could not be updated.', 'CampaignUpdateAction')
43
+ }
37
44
  },
38
45
  })
@@ -3,6 +3,7 @@ import { Action } from '@stacksjs/actions'
3
3
  import { db } from '@stacksjs/database'
4
4
  import { EmailList } from '@stacksjs/orm'
5
5
  import { response } from '@stacksjs/router'
6
+ import { marketingModelError, marketingRecordId } from './marketing-response'
6
7
 
7
8
  export default new Action({
8
9
  name: 'MarketingListDestroyAction',
@@ -10,32 +11,40 @@ export default new Action({
10
11
  method: 'DELETE',
11
12
 
12
13
  async handle(request: RequestInstance) {
13
- const id = Number(request.getParam('id'))
14
- const list = await EmailList.find(id)
15
- if (!list)
16
- return response.json({ message: 'Email list not found.' }, 404)
14
+ const id = marketingRecordId(request)
15
+ if (!id)
16
+ return response.json({ message: 'A valid email list id is required.' }, 400)
17
17
 
18
- const [membership, campaign] = await Promise.all([
19
- db
20
- .selectFrom('email_list_subscribers')
21
- .select(db.fn.count('id').as('count'))
22
- .where('email_list_id', '=', id)
23
- .executeTakeFirst(),
24
- db
25
- .selectFrom('campaigns')
26
- .select(db.fn.count('id').as('count'))
27
- .where('email_list_id', '=', id)
28
- .executeTakeFirst(),
29
- ])
30
- const membershipCount = Number(membership?.count || 0)
31
- const campaignCount = Number(campaign?.count || 0)
32
- if (membershipCount > 0 || campaignCount > 0) {
33
- return response.json({
34
- message: 'Archive this list instead. Lists with memberships or campaigns cannot be deleted.',
35
- }, 409)
36
- }
18
+ try {
19
+ const list = await EmailList.find(id)
20
+ if (!list)
21
+ return response.json({ message: 'Email list not found.' }, 404)
22
+
23
+ const [membership, campaign] = await Promise.all([
24
+ db
25
+ .selectFrom('email_list_subscribers')
26
+ .select(db.fn.count('id').as('count'))
27
+ .where('email_list_id', '=', id)
28
+ .executeTakeFirst(),
29
+ db
30
+ .selectFrom('campaigns')
31
+ .select(db.fn.count('id').as('count'))
32
+ .where('email_list_id', '=', id)
33
+ .executeTakeFirst(),
34
+ ])
35
+ const membershipCount = Number(membership?.count || 0)
36
+ const campaignCount = Number(campaign?.count || 0)
37
+ if (membershipCount > 0 || campaignCount > 0) {
38
+ return response.json({
39
+ message: 'Archive this list instead. Lists with memberships or campaigns cannot be deleted.',
40
+ }, 409)
41
+ }
37
42
 
38
- await list.delete()
39
- return response.noContent()
43
+ await list.delete()
44
+ return response.noContent()
45
+ }
46
+ catch (error) {
47
+ return marketingModelError(error, 'Email list could not be deleted.', 'ListDestroyAction')
48
+ }
40
49
  },
41
50
  })
@@ -1,6 +1,7 @@
1
1
  import { Action } from '@stacksjs/actions'
2
2
  import { db } from '@stacksjs/database'
3
3
  import { EmailList } from '@stacksjs/orm'
4
+ import { dashboardOperationalError } from '../dashboard-response'
4
5
  import { normalizeMarketingLists } from './marketing-list-records'
5
6
 
6
7
  export default new Action({
@@ -15,32 +16,37 @@ export default new Action({
15
16
  .slice(0, 19)
16
17
  .replace('T', ' ')
17
18
 
18
- const [lists, membershipRows, newMembershipRows, campaignRows] = await Promise.all([
19
- EmailList.orderBy('name', 'asc').get(),
20
- db
21
- .selectFrom('email_list_subscribers')
22
- .select(['email_list_id', 'status', db.fn.count('id').as('count')])
23
- .groupBy(['email_list_id', 'status'])
24
- .execute(),
25
- db
26
- .selectFrom('email_list_subscribers')
27
- .select(['email_list_id', db.fn.count('id').as('count')])
28
- .where('status', '=', 'subscribed')
29
- .where('subscribed_at', '>=', weekStart)
30
- .groupBy('email_list_id')
31
- .execute(),
32
- db
33
- .selectFrom('campaigns')
34
- .select([
35
- 'email_list_id',
36
- db.fn.count('id').as('count'),
37
- db.fn.max('sent_at').as('last_sent_at'),
38
- ])
39
- .whereNotNull('email_list_id')
40
- .groupBy('email_list_id')
41
- .execute(),
42
- ])
19
+ try {
20
+ const [lists, membershipRows, newMembershipRows, campaignRows] = await Promise.all([
21
+ EmailList.orderBy('name', 'asc').get(),
22
+ db
23
+ .selectFrom('email_list_subscribers')
24
+ .select(['email_list_id', 'status', db.fn.count('id').as('count')])
25
+ .groupBy(['email_list_id', 'status'])
26
+ .execute(),
27
+ db
28
+ .selectFrom('email_list_subscribers')
29
+ .select(['email_list_id', db.fn.count('id').as('count')])
30
+ .where('status', '=', 'subscribed')
31
+ .where('subscribed_at', '>=', weekStart)
32
+ .groupBy('email_list_id')
33
+ .execute(),
34
+ db
35
+ .selectFrom('campaigns')
36
+ .select([
37
+ 'email_list_id',
38
+ db.fn.count('id').as('count'),
39
+ db.fn.max('sent_at').as('last_sent_at'),
40
+ ])
41
+ .whereNotNull('email_list_id')
42
+ .groupBy('email_list_id')
43
+ .execute(),
44
+ ])
43
45
 
44
- return normalizeMarketingLists(lists, membershipRows, newMembershipRows, campaignRows)
46
+ return normalizeMarketingLists(lists, membershipRows, newMembershipRows, campaignRows)
47
+ }
48
+ catch (error) {
49
+ return dashboardOperationalError(error, 'Email lists could not be loaded.', 'ListIndexAction')
50
+ }
45
51
  },
46
52
  })
@@ -2,7 +2,8 @@ import type { RequestInstance } from '@stacksjs/types'
2
2
  import { Action } from '@stacksjs/actions'
3
3
  import { EmailList } from '@stacksjs/orm'
4
4
  import { response } from '@stacksjs/router'
5
- import { marketingListWriteData } from './marketing-list-records'
5
+ import { marketingListWriteData, validateMarketingListWriteData } from './marketing-list-records'
6
+ import { marketingModelError } from './marketing-response'
6
7
 
7
8
  export default new Action({
8
9
  name: 'MarketingListStoreAction',
@@ -11,26 +12,38 @@ export default new Action({
11
12
  model: EmailList,
12
13
 
13
14
  async handle(request: RequestInstance) {
14
- await request.validate()
15
15
  const data = marketingListWriteData(await request.all())
16
+ const validationError = validateMarketingListWriteData(data)
17
+ if (validationError)
18
+ return response.json({ message: validationError }, 422)
16
19
 
17
- const duplicate = await EmailList.where('slug', data.slug).first()
18
- if (duplicate)
19
- return response.json({ message: 'An email list with this slug already exists.' }, 422)
20
+ try {
21
+ const duplicate = await EmailList.where('slug', data.slug).first()
22
+ if (duplicate)
23
+ return response.json({ message: 'An email list with this slug already exists.' }, 422)
20
24
 
21
- const list = await EmailList.create({
22
- name: data.name,
23
- slug: data.slug,
24
- description: data.description,
25
- status: data.status,
26
- is_public: data.isPublic,
27
- double_opt_in: data.doubleOptIn,
28
- subscriber_count: 0,
29
- active_count: 0,
30
- unsubscribed_count: 0,
31
- bounced_count: 0,
32
- })
25
+ const list = await EmailList.create({
26
+ name: data.name,
27
+ slug: data.slug,
28
+ description: data.description,
29
+ status: data.status,
30
+ is_public: data.isPublic,
31
+ double_opt_in: data.doubleOptIn,
32
+ subscriber_count: 0,
33
+ active_count: 0,
34
+ unsubscribed_count: 0,
35
+ bounced_count: 0,
36
+ })
33
37
 
34
- return response.json({ id: list.get('id') }, 201)
38
+ return response.json({ id: list.get('id') }, 201)
39
+ }
40
+ catch (error) {
41
+ return marketingModelError(
42
+ error,
43
+ 'Email list could not be created.',
44
+ 'ListStoreAction',
45
+ 'An email list with this slug already exists.',
46
+ )
47
+ }
35
48
  },
36
49
  })
@@ -2,7 +2,8 @@ import type { RequestInstance } from '@stacksjs/types'
2
2
  import { Action } from '@stacksjs/actions'
3
3
  import { EmailList } from '@stacksjs/orm'
4
4
  import { response } from '@stacksjs/router'
5
- import { marketingListWriteData } from './marketing-list-records'
5
+ import { marketingListWriteData, validateMarketingListWriteData } from './marketing-list-records'
6
+ import { marketingModelError, marketingRecordId } from './marketing-response'
6
7
 
7
8
  export default new Action({
8
9
  name: 'MarketingListUpdateAction',
@@ -11,25 +12,40 @@ export default new Action({
11
12
  model: EmailList,
12
13
 
13
14
  async handle(request: RequestInstance) {
14
- await request.validate()
15
- const id = Number(request.getParam('id'))
16
- const list = await EmailList.find(id)
17
- if (!list)
18
- return response.json({ message: 'Email list not found.' }, 404)
19
-
15
+ const id = marketingRecordId(request)
16
+ if (!id)
17
+ return response.json({ message: 'A valid email list id is required.' }, 400)
20
18
  const data = marketingListWriteData(await request.all())
21
- const duplicates = await EmailList.where('slug', data.slug).get()
22
- if (duplicates.some(record => Number(record.get('id')) !== id))
23
- return response.json({ message: 'An email list with this slug already exists.' }, 422)
19
+ const validationError = validateMarketingListWriteData(data)
20
+ if (validationError)
21
+ return response.json({ message: validationError }, 422)
22
+
23
+ try {
24
+ const list = await EmailList.find(id)
25
+ if (!list)
26
+ return response.json({ message: 'Email list not found.' }, 404)
27
+
28
+ const duplicates = await EmailList.where('slug', data.slug).get()
29
+ if (duplicates.some(record => Number(record.get('id')) !== id))
30
+ return response.json({ message: 'An email list with this slug already exists.' }, 422)
24
31
 
25
- await list.update({
26
- name: data.name,
27
- slug: data.slug,
28
- description: data.description,
29
- status: data.status,
30
- is_public: data.isPublic,
31
- double_opt_in: data.doubleOptIn,
32
- })
33
- return response.json({ id })
32
+ await list.update({
33
+ name: data.name,
34
+ slug: data.slug,
35
+ description: data.description,
36
+ status: data.status,
37
+ is_public: data.isPublic,
38
+ double_opt_in: data.doubleOptIn,
39
+ })
40
+ return response.json({ id })
41
+ }
42
+ catch (error) {
43
+ return marketingModelError(
44
+ error,
45
+ 'Email list could not be updated.',
46
+ 'ListUpdateAction',
47
+ 'An email list with this slug already exists.',
48
+ )
49
+ }
34
50
  },
35
51
  })
@@ -2,6 +2,7 @@ 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 { marketingModelError, marketingRecordId } from './marketing-response'
5
6
 
6
7
  export default new Action({
7
8
  name: 'SocialPostDestroyAction',
@@ -9,10 +10,19 @@ export default new Action({
9
10
  method: 'DELETE',
10
11
 
11
12
  async handle(request: RequestInstance) {
12
- const post = await SocialPost.find(Number(request.getParam('id')))
13
- if (!post)
14
- return response.json({ message: 'Social post not found.' }, 404)
15
- await post.delete()
16
- return response.noContent()
13
+ const id = marketingRecordId(request)
14
+ if (!id)
15
+ return response.json({ message: 'A valid social post id is required.' }, 400)
16
+
17
+ try {
18
+ const post = await SocialPost.find(id)
19
+ if (!post)
20
+ return response.json({ message: 'Social post not found.' }, 404)
21
+ await post.delete()
22
+ return response.noContent()
23
+ }
24
+ catch (error) {
25
+ return marketingModelError(error, 'Social post could not be deleted.', 'SocialPostDestroyAction')
26
+ }
17
27
  },
18
28
  })
@@ -1,5 +1,6 @@
1
1
  import { Action } from '@stacksjs/actions'
2
2
  import { SocialPost, User } from '@stacksjs/orm'
3
+ import { dashboardOperationalError } from '../dashboard-response'
3
4
  import { normalizeSocialPosts } from './social-post-records'
4
5
 
5
6
  export default new Action({
@@ -9,10 +10,15 @@ export default new Action({
9
10
  apiResponse: true,
10
11
 
11
12
  async handle() {
12
- const [posts, users] = await Promise.all([
13
- SocialPost.orderByDesc('id').limit(500).get(),
14
- User.orderBy('name', 'asc').limit(500).get(),
15
- ])
16
- return normalizeSocialPosts(posts, users)
13
+ try {
14
+ const [posts, users] = await Promise.all([
15
+ SocialPost.orderByDesc('id').limit(500).get(),
16
+ User.orderBy('name', 'asc').limit(500).get(),
17
+ ])
18
+ return normalizeSocialPosts(posts, users)
19
+ }
20
+ catch (error) {
21
+ return dashboardOperationalError(error, 'Social posts could not be loaded.', 'SocialPostIndexAction')
22
+ }
17
23
  },
18
24
  })
@@ -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 { socialPostWriteData, validateSocialPostSchedule } from './social-post-records'
5
+ import { marketingModelError } from './marketing-response'
6
+ import { socialPostWriteData, validateSocialPostWriteData } from './social-post-records'
6
7
 
7
8
  export default new Action({
8
9
  name: 'SocialPostStoreAction',
@@ -11,19 +12,24 @@ export default new Action({
11
12
  model: SocialPost,
12
13
 
13
14
  async handle(request: RequestInstance) {
14
- await request.validate()
15
15
  const data = socialPostWriteData(await request.all())
16
- const scheduleError = validateSocialPostSchedule(data)
17
- if (scheduleError)
18
- return response.json({ message: scheduleError }, 422)
16
+ const validationError = validateSocialPostWriteData(data)
17
+ if (validationError)
18
+ return response.json({ message: validationError }, 422)
19
19
 
20
- const post = await SocialPost.create({
21
- ...data,
22
- likes: 0,
23
- shares: 0,
24
- comments: 0,
25
- reach: 0,
26
- })
27
- return response.json({ id: post.get('id') }, 201)
20
+ try {
21
+ const post = await SocialPost.create({
22
+ ...data,
23
+ platform: data.platform as Exclude<typeof data.platform, ''>,
24
+ likes: 0,
25
+ shares: 0,
26
+ comments: 0,
27
+ reach: 0,
28
+ })
29
+ return response.json({ id: post.get('id') }, 201)
30
+ }
31
+ catch (error) {
32
+ return marketingModelError(error, 'Social post could not be created.', 'SocialPostStoreAction')
33
+ }
28
34
  },
29
35
  })