@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
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import type { RequestInstance } from '@stacksjs/types'
|
|
2
2
|
import { Action } from '@stacksjs/actions'
|
|
3
|
-
import {
|
|
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 =
|
|
19
|
-
|
|
20
|
-
|
|
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
|
-
|
|
37
|
-
|
|
39
|
+
context.activeMembers,
|
|
40
|
+
context.listStatus,
|
|
38
41
|
)
|
|
39
42
|
if (validationError)
|
|
40
43
|
return response.json({ message: validationError }, 422)
|
|
41
44
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
return response.json({
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
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 {
|
|
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 {
|
|
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 =
|
|
16
|
-
|
|
17
|
-
|
|
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
|
-
|
|
34
|
-
|
|
32
|
+
context.activeMembers,
|
|
33
|
+
context.listStatus,
|
|
35
34
|
)
|
|
36
35
|
if (validationError)
|
|
37
36
|
return response.json({ message: validationError }, 422)
|
|
38
37
|
|
|
39
|
-
|
|
40
|
-
|
|
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
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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
|
-
|
|
36
|
-
|
|
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 =
|
|
14
|
-
|
|
15
|
-
|
|
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
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
.
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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
|
-
|
|
39
|
-
|
|
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
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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
|
-
|
|
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
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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
|
-
|
|
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
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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
|
|
22
|
-
if (
|
|
23
|
-
return response.json({ message:
|
|
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
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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
|
|
13
|
-
if (!
|
|
14
|
-
return response.json({ message: '
|
|
15
|
-
|
|
16
|
-
|
|
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
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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 {
|
|
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
|
|
17
|
-
if (
|
|
18
|
-
return response.json({ message:
|
|
16
|
+
const validationError = validateSocialPostWriteData(data)
|
|
17
|
+
if (validationError)
|
|
18
|
+
return response.json({ message: validationError }, 422)
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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
|
})
|