@stacksjs/defaults 0.70.355 → 0.70.357
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ai/skills/stacks-email/SKILL.md +38 -1
- package/ai/skills/stacks-orm/SKILL.md +6 -0
- package/app/Actions/ContactAction.ts +2 -2
- package/app/Actions/Dashboard/Analytics/EventAnalyticsAction.ts +2 -3
- package/app/Actions/Dashboard/Analytics/MarketingAnalyticsAction.ts +2 -3
- package/app/Actions/Dashboard/Analytics/SalesAnalyticsAction.ts +2 -3
- package/app/Actions/Dashboard/Analytics/WebAnalyticsAction.ts +9 -3
- package/app/Actions/Dashboard/Auth/MeAction.ts +4 -7
- package/app/Actions/Dashboard/Buddy/BuddyChatAction.ts +2 -3
- package/app/Actions/Dashboard/Cloud/CloudIndexAction.ts +8 -1
- package/app/Actions/Dashboard/Cloud/ServerlessIndexAction.ts +15 -8
- package/app/Actions/Dashboard/Commerce/CommerceCategoriesAction.ts +2 -4
- package/app/Actions/Dashboard/Commerce/CommerceCouponsAction.ts +2 -4
- package/app/Actions/Dashboard/Commerce/CommerceCustomersAction.ts +2 -4
- package/app/Actions/Dashboard/Commerce/CommerceDashboardAction.ts +2 -4
- package/app/Actions/Dashboard/Commerce/CommerceDeliveryAction.ts +2 -4
- package/app/Actions/Dashboard/Commerce/CommerceGiftCardsAction.ts +2 -4
- package/app/Actions/Dashboard/Commerce/CommerceOrdersAction.ts +2 -4
- package/app/Actions/Dashboard/Commerce/CommercePaymentsAction.ts +2 -4
- package/app/Actions/Dashboard/Commerce/CommercePosAction.ts +2 -4
- package/app/Actions/Dashboard/Commerce/CommercePosCheckoutAction.ts +75 -61
- package/app/Actions/Dashboard/Commerce/CommercePrintDevicesAction.ts +2 -4
- package/app/Actions/Dashboard/Commerce/CommercePrintLogsAction.ts +2 -4
- package/app/Actions/Dashboard/Commerce/CommerceProductDetailAction.ts +3 -4
- package/app/Actions/Dashboard/Commerce/CommerceProductsAction.ts +2 -4
- package/app/Actions/Dashboard/Commerce/CommerceTaxesAction.ts +2 -4
- package/app/Actions/Dashboard/Commerce/DeliveryRouteIndexAction.ts +2 -4
- package/app/Actions/Dashboard/Commerce/DigitalDeliveryIndexAction.ts +2 -4
- package/app/Actions/Dashboard/Commerce/DriverIndexAction.ts +2 -4
- package/app/Actions/Dashboard/Commerce/LicenseKeyIndexAction.ts +2 -4
- package/app/Actions/Dashboard/Commerce/LicenseKeyOptionsAction.ts +2 -4
- package/app/Actions/Dashboard/Commerce/ManufacturerIndexAction.ts +2 -4
- package/app/Actions/Dashboard/Commerce/ProductUnitIndexAction.ts +2 -4
- package/app/Actions/Dashboard/Commerce/ProductVariantIndexAction.ts +2 -4
- package/app/Actions/Dashboard/Commerce/ProductWaitlistIndexAction.ts +2 -4
- package/app/Actions/Dashboard/Commerce/RestaurantWaitlistIndexAction.ts +2 -4
- package/app/Actions/Dashboard/Commerce/ReviewIndexAction.ts +2 -4
- package/app/Actions/Dashboard/Commerce/ShippingMethodIndexAction.ts +2 -4
- package/app/Actions/Dashboard/Commerce/ShippingRateIndexAction.ts +2 -4
- package/app/Actions/Dashboard/Commerce/ShippingZoneIndexAction.ts +2 -4
- package/app/Actions/Dashboard/Content/AuthorDestroyAction.ts +23 -9
- package/app/Actions/Dashboard/Content/AuthorIndexAction.ts +32 -26
- package/app/Actions/Dashboard/Content/AuthorStoreAction.ts +44 -24
- package/app/Actions/Dashboard/Content/AuthorUpdateAction.ts +48 -26
- package/app/Actions/Dashboard/Content/CategoryDestroyAction.ts +27 -8
- package/app/Actions/Dashboard/Content/CategoryIndexAction.ts +21 -15
- package/app/Actions/Dashboard/Content/CategoryStoreAction.ts +47 -29
- package/app/Actions/Dashboard/Content/CommentDestroyAction.ts +17 -4
- package/app/Actions/Dashboard/Content/CommentIndexAction.ts +23 -17
- package/app/Actions/Dashboard/Content/CommentUpdateAction.ts +30 -12
- package/app/Actions/Dashboard/Content/ContentDashboardAction.ts +43 -37
- package/app/Actions/Dashboard/Content/PageDestroyAction.ts +17 -4
- package/app/Actions/Dashboard/Content/PageIndexAction.ts +23 -17
- package/app/Actions/Dashboard/Content/PageStoreAction.ts +33 -18
- package/app/Actions/Dashboard/Content/PageUpdateAction.ts +40 -22
- package/app/Actions/Dashboard/Content/PostDestroyAction.ts +24 -11
- package/app/Actions/Dashboard/Content/PostIndexAction.ts +55 -49
- package/app/Actions/Dashboard/Content/PostStoreAction.ts +43 -33
- package/app/Actions/Dashboard/Content/PostUpdateAction.ts +46 -34
- package/app/Actions/Dashboard/Content/TagDestroyAction.ts +27 -8
- package/app/Actions/Dashboard/Content/TagIndexAction.ts +31 -25
- package/app/Actions/Dashboard/Content/TagStoreAction.ts +44 -23
- package/app/Actions/Dashboard/Content/content-input.ts +4 -4
- package/app/Actions/Dashboard/Content/post-input.ts +60 -8
- package/app/Actions/Dashboard/DashboardActivityAction.ts +2 -5
- package/app/Actions/Dashboard/Data/ActivityIndexAction.ts +2 -5
- package/app/Actions/Dashboard/Data/SubscriberIndexAction.ts +2 -5
- package/app/Actions/Dashboard/Data/TeamIndexAction.ts +2 -5
- package/app/Actions/Dashboard/Data/UserIndexAction.ts +2 -5
- package/app/Actions/Dashboard/Deployments/CreateDeployment.ts +23 -19
- package/app/Actions/Dashboard/Deployments/GetAverageDeploymentTime.ts +8 -2
- package/app/Actions/Dashboard/Deployments/GetDeployScript.ts +3 -6
- package/app/Actions/Dashboard/Deployments/GetDeployment.ts +4 -5
- package/app/Actions/Dashboard/Deployments/GetDeploymentCount.ts +7 -1
- package/app/Actions/Dashboard/Deployments/GetDeploymentLiveTerminalOutput.ts +10 -9
- package/app/Actions/Dashboard/Deployments/GetDeployments.ts +2 -4
- package/app/Actions/Dashboard/Deployments/GetRecentDeployments.ts +8 -2
- package/app/Actions/Dashboard/Deployments/UpdateDeployScript.ts +18 -5
- package/app/Actions/Dashboard/Email/CapturedMailIndexAction.ts +2 -4
- package/app/Actions/Dashboard/Email/CapturedMailShowAction.ts +3 -4
- package/app/Actions/Dashboard/Infrastructure/InsightsAction.ts +15 -14
- package/app/Actions/Dashboard/Infrastructure/LogIndexAction.ts +2 -8
- package/app/Actions/Dashboard/Jobs/JobIndexAction.ts +2 -4
- package/app/Actions/Dashboard/Jobs/JobShowAction.ts +13 -8
- package/app/Actions/Dashboard/Jobs/JobStatsAction.ts +2 -4
- package/app/Actions/Dashboard/Kanban/BoardDestroyAction.ts +2 -3
- package/app/Actions/Dashboard/Kanban/BoardShowAction.ts +2 -3
- package/app/Actions/Dashboard/Kanban/BoardStoreAction.ts +2 -3
- package/app/Actions/Dashboard/Kanban/BoardUpdateAction.ts +2 -3
- package/app/Actions/Dashboard/Kanban/BoardsIndexAction.ts +2 -3
- package/app/Actions/Dashboard/Kanban/BoardsReorderAction.ts +2 -3
- package/app/Actions/Dashboard/Kanban/CardAssigneesSyncAction.ts +2 -3
- package/app/Actions/Dashboard/Kanban/CardCommentDestroyAction.ts +2 -3
- package/app/Actions/Dashboard/Kanban/CardCommentStoreAction.ts +2 -3
- package/app/Actions/Dashboard/Kanban/CardCommentUpdateAction.ts +2 -3
- package/app/Actions/Dashboard/Kanban/CardDestroyAction.ts +2 -3
- package/app/Actions/Dashboard/Kanban/CardLabelsSyncAction.ts +2 -3
- package/app/Actions/Dashboard/Kanban/CardShowAction.ts +2 -3
- package/app/Actions/Dashboard/Kanban/CardStoreAction.ts +2 -3
- package/app/Actions/Dashboard/Kanban/CardUpdateAction.ts +2 -3
- package/app/Actions/Dashboard/Kanban/CardsReorderAction.ts +2 -3
- package/app/Actions/Dashboard/Kanban/ColumnDestroyAction.ts +2 -3
- package/app/Actions/Dashboard/Kanban/ColumnStoreAction.ts +2 -3
- package/app/Actions/Dashboard/Kanban/ColumnUpdateAction.ts +2 -3
- package/app/Actions/Dashboard/Kanban/ColumnsReorderAction.ts +2 -3
- package/app/Actions/Dashboard/Kanban/LabelDestroyAction.ts +2 -3
- package/app/Actions/Dashboard/Kanban/LabelStoreAction.ts +2 -3
- package/app/Actions/Dashboard/Kanban/LabelUpdateAction.ts +2 -3
- package/app/Actions/Dashboard/Kanban/UsersListAction.ts +2 -3
- package/app/Actions/Dashboard/Kanban/kanban-response.ts +5 -0
- package/app/Actions/Dashboard/Library/CreateComponent.ts +15 -6
- package/app/Actions/Dashboard/Library/CreateFunction.ts +15 -6
- package/app/Actions/Dashboard/Library/DependencyIndexAction.ts +88 -77
- package/app/Actions/Dashboard/Library/GetAverageReleaseTime.ts +21 -15
- package/app/Actions/Dashboard/Library/GetComponents.ts +14 -7
- package/app/Actions/Dashboard/Library/GetDownloadCount.ts +14 -5
- package/app/Actions/Dashboard/Library/GetFunctions.ts +12 -5
- package/app/Actions/Dashboard/Library/GetReleaseCount.ts +14 -5
- package/app/Actions/Dashboard/Library/GetReleases.ts +17 -10
- package/app/Actions/Dashboard/Library/PackageIndexAction.ts +14 -7
- package/app/Actions/Dashboard/Models/ModelDestroyAction.ts +7 -5
- package/app/Actions/Dashboard/Models/ModelShowAction.ts +82 -174
- package/app/Actions/Dashboard/Models/ModelStoreAction.ts +6 -4
- package/app/Actions/Dashboard/Models/ModelUpdateAction.ts +9 -7
- package/app/Actions/Dashboard/Models/ModelsIndexAction.ts +47 -17
- package/app/Actions/Dashboard/Models/model-write.ts +20 -5
- package/app/Actions/Dashboard/Notifications/NotificationDeliveryHistoryAction.ts +46 -36
- package/app/Actions/Dashboard/Notifications/NotificationDeliveryIndexAction.ts +14 -6
- package/app/Actions/Dashboard/Notifications/NotificationDeliveryOverviewAction.ts +96 -84
- package/app/Actions/Dashboard/Notifications/NotificationDeliveryRetryAction.ts +34 -21
- package/app/Actions/Dashboard/Queries/QueryDashboardAction.ts +2 -4
- package/app/Actions/Dashboard/Queries/QueryShowAction.ts +2 -3
- package/app/Actions/Dashboard/Queue/QueueStatsAction.ts +2 -4
- package/app/Actions/Dashboard/Queue/QueueWorkersAction.ts +2 -4
- package/app/Actions/Dashboard/Rbac/PermissionDestroyAction.ts +2 -2
- package/app/Actions/Dashboard/Rbac/PermissionStoreAction.ts +2 -2
- package/app/Actions/Dashboard/Rbac/PermissionsIndexAction.ts +2 -3
- package/app/Actions/Dashboard/Rbac/RoleDestroyAction.ts +2 -2
- package/app/Actions/Dashboard/Rbac/RolePermissionsShowAction.ts +2 -2
- package/app/Actions/Dashboard/Rbac/RolePermissionsSyncAction.ts +2 -6
- package/app/Actions/Dashboard/Rbac/RoleStoreAction.ts +2 -2
- package/app/Actions/Dashboard/Rbac/RolesIndexAction.ts +4 -6
- package/app/Actions/Dashboard/Rbac/UserRolesShowAction.ts +2 -2
- package/app/Actions/Dashboard/Rbac/UserRolesSyncAction.ts +2 -9
- package/app/Actions/Dashboard/Rbac/UsersListAction.ts +2 -3
- package/app/Actions/Dashboard/Rbac/rbac-response.ts +10 -0
- package/app/Actions/Dashboard/Realtime/RealtimeStatsAction.ts +2 -4
- package/app/Actions/Dashboard/Search/GlobalSearchAction.ts +68 -55
- package/app/Actions/Dashboard/Teams/TeamInvitationDestroyAction.ts +34 -16
- package/app/Actions/Dashboard/Teams/TeamInvitationResendAction.ts +52 -29
- package/app/Actions/Dashboard/Teams/TeamInviteAction.ts +102 -71
- package/app/Actions/Dashboard/Teams/TeamMemberDestroyAction.ts +36 -17
- package/app/Actions/Dashboard/Teams/TeamMemberUpdateAction.ts +39 -26
- package/app/Actions/Dashboard/Teams/TeamPeopleIndexAction.ts +71 -58
- package/app/Actions/Dashboard/Teams/team-invitation-delivery.ts +29 -7
- package/app/Actions/Dashboard/Teams/team-member-count.ts +4 -2
- package/app/Actions/Dashboard/Teams/team-records.test.ts +2 -0
- package/app/Actions/Dashboard/Teams/team-records.ts +2 -0
- package/app/Actions/Dashboard/dashboard-response.ts +16 -0
- package/app/Actions/SendWelcomeEmail.ts +1 -1
- package/app/Actions/Teams/AcceptInvitationAction.ts +74 -51
- package/app/Actions/Teams/ShowInvitationAction.ts +21 -14
- package/app/Actions/Teams/team-response.ts +23 -0
- package/app/Mail/OrderConfirmation.ts +1 -1
- package/app/Mail/SubscriptionConfirmation.ts +1 -1
- package/app/Mail/TeamInvitation.ts +1 -1
- package/app/Models/EmailIdempotency.ts +64 -0
- package/app/Models/EmailSuppression.ts +52 -0
- package/app/Models/EmailWebhookEvent.ts +51 -0
- package/app/Models/TeamInvitation.ts +13 -0
- package/functions/data.ts +0 -1
- package/functions/jobs.ts +0 -1
- package/ide/vscode/package.json +1 -1
- package/package.json +1 -1
- package/resources/components/Dashboard/Data/DataRecords.stx +0 -4
- package/resources/components/Dashboard/Jobs/JobDetail.stx +3 -2
- package/resources/components/Dashboard/Models/ModelRecordsDashboard.stx +7 -13
- package/resources/components/Dashboard/Models/ModelsOverview.stx +1 -1
- package/views/dashboard/.discovered-models.json +30 -0
- package/views/dashboard/stores/auth.ts +15 -14
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Action } from '@stacksjs/actions'
|
|
2
2
|
import { db } from '@stacksjs/database'
|
|
3
|
+
import { dashboardOperationalError } from '../dashboard-response'
|
|
3
4
|
|
|
4
5
|
interface AuthorRow {
|
|
5
6
|
id: number
|
|
@@ -30,36 +31,41 @@ export default new Action({
|
|
|
30
31
|
method: 'GET',
|
|
31
32
|
apiResponse: true,
|
|
32
33
|
async handle() {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
34
|
+
try {
|
|
35
|
+
const [rows, postAuthors] = await Promise.all([
|
|
36
|
+
db.selectFrom('authors').selectAll().orderBy('created_at', 'desc').execute() as unknown as Promise<AuthorRow[]>,
|
|
37
|
+
db.selectFrom('posts').select(['author_id']).execute() as unknown as Promise<Array<{ author_id: number | null }>>,
|
|
38
|
+
])
|
|
37
39
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
40
|
+
const postCounts = new Map<number, number>()
|
|
41
|
+
for (const post of postAuthors) {
|
|
42
|
+
if (post.author_id == null)
|
|
43
|
+
continue
|
|
42
44
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
45
|
+
const id = Number(post.author_id)
|
|
46
|
+
postCounts.set(id, (postCounts.get(id) || 0) + 1)
|
|
47
|
+
}
|
|
46
48
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
49
|
+
const authors = rows.map(row => ({
|
|
50
|
+
id: Number(row.id),
|
|
51
|
+
name: String(row.name || ''),
|
|
52
|
+
email: String(row.email || ''),
|
|
53
|
+
bio: String(row.bio || ''),
|
|
54
|
+
avatar: String(row.avatar || ''),
|
|
55
|
+
postCount: postCounts.get(Number(row.id)) || 0,
|
|
56
|
+
created_at: row.created_at || null,
|
|
57
|
+
updated_at: row.updated_at || null,
|
|
58
|
+
}))
|
|
57
59
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
60
|
+
// Posts whose author no longer exists are not counted: the stat means
|
|
61
|
+
// "posts reachable from this list", which is what the page's Assigned Posts
|
|
62
|
+
// tile claims to show.
|
|
63
|
+
const assignedPostCount = authors.reduce((sum, author) => sum + author.postCount, 0)
|
|
62
64
|
|
|
63
|
-
|
|
65
|
+
return { authors, assignedPostCount }
|
|
66
|
+
}
|
|
67
|
+
catch (error) {
|
|
68
|
+
return dashboardOperationalError(error, 'Authors could not be loaded.', 'AuthorIndexAction')
|
|
69
|
+
}
|
|
64
70
|
},
|
|
65
71
|
})
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import type { RequestInstance } from '@stacksjs/types'
|
|
2
2
|
import { Action } from '@stacksjs/actions'
|
|
3
3
|
import { db } from '@stacksjs/database'
|
|
4
|
+
import { isUniqueViolation, transaction } from '@stacksjs/orm'
|
|
4
5
|
import { response } from '@stacksjs/router'
|
|
5
6
|
import { randomUUIDv7 } from 'bun'
|
|
7
|
+
import { dashboardOperationalError } from '../dashboard-response'
|
|
6
8
|
import { parseAuthorInput } from './author-input'
|
|
7
9
|
import { findRow, insertedId, timestamp } from './content-input'
|
|
8
10
|
|
|
@@ -25,35 +27,53 @@ export default new Action({
|
|
|
25
27
|
if ('message' in input)
|
|
26
28
|
return response.json({ message: input.message }, 422)
|
|
27
29
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
30
|
+
try {
|
|
31
|
+
const author = await transaction(async (rawTrx) => {
|
|
32
|
+
const trx = rawTrx as unknown as typeof db
|
|
33
|
+
const duplicate = await trx
|
|
34
|
+
.selectFrom('authors')
|
|
35
|
+
.select(['id'])
|
|
36
|
+
.where('email', '=', input.data.email)
|
|
37
|
+
.executeTakeFirst()
|
|
33
38
|
|
|
34
|
-
|
|
35
|
-
|
|
39
|
+
if (duplicate)
|
|
40
|
+
return null
|
|
36
41
|
|
|
37
|
-
|
|
42
|
+
const now = timestamp()
|
|
38
43
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
44
|
+
const result = await trx
|
|
45
|
+
.insertInto('authors')
|
|
46
|
+
.values({
|
|
47
|
+
uuid: randomUUIDv7(),
|
|
48
|
+
name: input.data.name,
|
|
49
|
+
email: input.data.email,
|
|
50
|
+
bio: input.data.bio || null,
|
|
51
|
+
avatar: input.data.avatar || null,
|
|
52
|
+
created_at: now,
|
|
53
|
+
updated_at: now,
|
|
54
|
+
} as any)
|
|
55
|
+
.executeTakeFirst()
|
|
51
56
|
|
|
52
|
-
|
|
57
|
+
const id = insertedId(result)
|
|
53
58
|
|
|
54
|
-
|
|
55
|
-
|
|
59
|
+
if (!id)
|
|
60
|
+
throw new Error('Author insert did not return an id.')
|
|
56
61
|
|
|
57
|
-
|
|
62
|
+
const created = await findRow('authors', id, trx)
|
|
63
|
+
if (!created)
|
|
64
|
+
throw new Error('Created author could not be loaded.')
|
|
65
|
+
return created
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
if (!author)
|
|
69
|
+
return response.json({ message: 'An author with that email already exists.' }, 422)
|
|
70
|
+
|
|
71
|
+
return response.json(author, 201)
|
|
72
|
+
}
|
|
73
|
+
catch (error) {
|
|
74
|
+
if (isUniqueViolation(error))
|
|
75
|
+
return response.json({ message: 'An author with that email already exists.' }, 422)
|
|
76
|
+
return dashboardOperationalError(error, 'Author could not be created.', 'AuthorStoreAction', 500)
|
|
77
|
+
}
|
|
58
78
|
},
|
|
59
79
|
})
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import type { RequestInstance } from '@stacksjs/types'
|
|
2
2
|
import { Action } from '@stacksjs/actions'
|
|
3
3
|
import { db } from '@stacksjs/database'
|
|
4
|
+
import { isUniqueViolation, transaction } from '@stacksjs/orm'
|
|
4
5
|
import { response } from '@stacksjs/router'
|
|
6
|
+
import { dashboardOperationalError } from '../dashboard-response'
|
|
5
7
|
import { parseAuthorInput } from './author-input'
|
|
6
8
|
import { findRow, rowExists, rowId, timestamp } from './content-input'
|
|
7
9
|
|
|
@@ -15,36 +17,56 @@ export default new Action({
|
|
|
15
17
|
if (!id)
|
|
16
18
|
return response.json({ message: 'A valid author id is required.' }, 422)
|
|
17
19
|
|
|
18
|
-
if (!await rowExists('authors', id))
|
|
19
|
-
return response.json({ message: 'Author not found.' }, 404)
|
|
20
|
-
|
|
21
20
|
const input = parseAuthorInput(request)
|
|
22
21
|
|
|
23
22
|
if ('message' in input)
|
|
24
23
|
return response.json({ message: input.message }, 422)
|
|
25
24
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
25
|
+
try {
|
|
26
|
+
const result = await transaction(async (rawTrx) => {
|
|
27
|
+
const trx = rawTrx as unknown as typeof db
|
|
28
|
+
if (!await rowExists('authors', id, trx))
|
|
29
|
+
return { kind: 'not-found' } as const
|
|
30
|
+
|
|
31
|
+
const duplicate = await trx
|
|
32
|
+
.selectFrom('authors')
|
|
33
|
+
.select(['id'])
|
|
34
|
+
.where('email', '=', input.data.email)
|
|
35
|
+
.where('id', '!=', id)
|
|
36
|
+
.executeTakeFirst()
|
|
37
|
+
|
|
38
|
+
if (duplicate)
|
|
39
|
+
return { kind: 'duplicate' } as const
|
|
40
|
+
|
|
41
|
+
await trx
|
|
42
|
+
.updateTable('authors')
|
|
43
|
+
.set({
|
|
44
|
+
name: input.data.name,
|
|
45
|
+
email: input.data.email,
|
|
46
|
+
bio: input.data.bio || null,
|
|
47
|
+
avatar: input.data.avatar || null,
|
|
48
|
+
updated_at: timestamp(),
|
|
49
|
+
} as any)
|
|
50
|
+
.where('id', '=', id)
|
|
51
|
+
.execute()
|
|
52
|
+
|
|
53
|
+
const author = await findRow('authors', id, trx)
|
|
54
|
+
if (!author)
|
|
55
|
+
throw new Error('Updated author could not be loaded.')
|
|
56
|
+
return { kind: 'updated', author } as const
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
if (result.kind === 'not-found')
|
|
60
|
+
return response.json({ message: 'Author not found.' }, 404)
|
|
61
|
+
if (result.kind === 'duplicate')
|
|
62
|
+
return response.json({ message: 'An author with that email already exists.' }, 422)
|
|
63
|
+
|
|
64
|
+
return response.json(result.author)
|
|
65
|
+
}
|
|
66
|
+
catch (error) {
|
|
67
|
+
if (isUniqueViolation(error))
|
|
68
|
+
return response.json({ message: 'An author with that email already exists.' }, 422)
|
|
69
|
+
return dashboardOperationalError(error, 'Author could not be updated.', 'AuthorUpdateAction', 500)
|
|
70
|
+
}
|
|
49
71
|
},
|
|
50
72
|
})
|
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
import type { RequestInstance } from '@stacksjs/types'
|
|
2
2
|
import { Action } from '@stacksjs/actions'
|
|
3
|
+
import { db } from '@stacksjs/database'
|
|
4
|
+
import { transaction } from '@stacksjs/orm'
|
|
3
5
|
import { response } from '@stacksjs/router'
|
|
6
|
+
import { dashboardOperationalError } from '../dashboard-response'
|
|
4
7
|
import { rowId } from './content-input'
|
|
5
8
|
|
|
6
9
|
/**
|
|
7
10
|
* `DELETE /api/dashboard/categories/{id}` — deletes a CMS category.
|
|
8
11
|
*
|
|
9
|
-
* Detaches related posts through the model
|
|
10
|
-
* category
|
|
12
|
+
* Detaches related posts through the model-declared pivot before removing the
|
|
13
|
+
* category, with both writes committed by the same transaction.
|
|
11
14
|
*/
|
|
12
15
|
export default new Action({
|
|
13
16
|
name: 'CategoryDestroyAction',
|
|
@@ -19,13 +22,29 @@ export default new Action({
|
|
|
19
22
|
if (!id)
|
|
20
23
|
return response.json({ message: 'A valid category id is required.' }, 422)
|
|
21
24
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
+
try {
|
|
26
|
+
const deleted = await transaction(async (rawTrx) => {
|
|
27
|
+
const trx = rawTrx as unknown as typeof db
|
|
28
|
+
const category = await trx.selectFrom('categories').select(['id']).where('id', '=', id).executeTakeFirst()
|
|
29
|
+
if (!category)
|
|
30
|
+
return false
|
|
25
31
|
|
|
26
|
-
|
|
27
|
-
|
|
32
|
+
await trx
|
|
33
|
+
.deleteFrom('categorizable_models')
|
|
34
|
+
.where('category_id', '=', id)
|
|
35
|
+
.where('categorizable_type', '=', 'posts')
|
|
36
|
+
.execute()
|
|
37
|
+
await trx.deleteFrom('categories').where('id', '=', id).execute()
|
|
38
|
+
return true
|
|
39
|
+
})
|
|
28
40
|
|
|
29
|
-
|
|
41
|
+
if (!deleted)
|
|
42
|
+
return response.json({ message: 'Category not found.' }, 404)
|
|
43
|
+
|
|
44
|
+
return response.json({ message: 'Category deleted.', id })
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
return dashboardOperationalError(error, 'Category could not be deleted.', 'CategoryDestroyAction', 500)
|
|
48
|
+
}
|
|
30
49
|
},
|
|
31
50
|
})
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Action } from '@stacksjs/actions'
|
|
2
2
|
import { db } from '@stacksjs/database'
|
|
3
|
+
import { dashboardOperationalError } from '../dashboard-response'
|
|
3
4
|
|
|
4
5
|
interface CategoryRow {
|
|
5
6
|
id: number
|
|
@@ -29,22 +30,27 @@ export default new Action({
|
|
|
29
30
|
method: 'GET',
|
|
30
31
|
apiResponse: true,
|
|
31
32
|
async handle() {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
33
|
+
try {
|
|
34
|
+
const rows = await db
|
|
35
|
+
.selectFrom('categories')
|
|
36
|
+
.selectAll()
|
|
37
|
+
.orderBy('created_at', 'desc')
|
|
38
|
+
.execute() as unknown as CategoryRow[]
|
|
37
39
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
40
|
+
const categories = rows.map(row => ({
|
|
41
|
+
id: Number(row.id),
|
|
42
|
+
name: String(row.name || ''),
|
|
43
|
+
slug: String(row.slug || ''),
|
|
44
|
+
description: String(row.description || ''),
|
|
45
|
+
is_active: row.is_active == null ? true : Boolean(row.is_active),
|
|
46
|
+
created_at: row.created_at || null,
|
|
47
|
+
updated_at: row.updated_at || null,
|
|
48
|
+
}))
|
|
47
49
|
|
|
48
|
-
|
|
50
|
+
return { categories }
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
return dashboardOperationalError(error, 'Categories could not be loaded.', 'CategoryIndexAction')
|
|
54
|
+
}
|
|
49
55
|
},
|
|
50
56
|
})
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import type { RequestInstance } from '@stacksjs/types'
|
|
2
2
|
import { Action } from '@stacksjs/actions'
|
|
3
3
|
import { db } from '@stacksjs/database'
|
|
4
|
+
import { transaction } from '@stacksjs/orm'
|
|
4
5
|
import { response } from '@stacksjs/router'
|
|
5
6
|
import { randomUUIDv7 } from 'bun'
|
|
7
|
+
import { dashboardOperationalError } from '../dashboard-response'
|
|
6
8
|
import { findRow, insertedId, slugify, str, timestamp } from './content-input'
|
|
7
9
|
|
|
8
10
|
/**
|
|
@@ -26,40 +28,56 @@ export default new Action({
|
|
|
26
28
|
if (!slug)
|
|
27
29
|
return response.json({ message: 'Slug could not be derived from the name; enter one.' }, 422)
|
|
28
30
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
31
|
+
try {
|
|
32
|
+
const category = await transaction(async (rawTrx) => {
|
|
33
|
+
const trx = rawTrx as unknown as typeof db
|
|
34
|
+
const duplicate = await trx
|
|
35
|
+
.selectFrom('categories')
|
|
36
|
+
.select(['id'])
|
|
37
|
+
.where('slug', '=', slug)
|
|
38
|
+
.executeTakeFirst()
|
|
34
39
|
|
|
35
|
-
|
|
36
|
-
|
|
40
|
+
if (duplicate)
|
|
41
|
+
return null
|
|
37
42
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
+
const now = timestamp()
|
|
44
|
+
const displayOrder = await trx
|
|
45
|
+
.selectFrom('categories')
|
|
46
|
+
.select(db.fn.max('display_order').as('max_display_order'))
|
|
47
|
+
.executeTakeFirst()
|
|
43
48
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
49
|
+
const result = await trx
|
|
50
|
+
.insertInto('categories')
|
|
51
|
+
.values({
|
|
52
|
+
uuid: randomUUIDv7(),
|
|
53
|
+
name,
|
|
54
|
+
slug,
|
|
55
|
+
description,
|
|
56
|
+
is_active: 1,
|
|
57
|
+
display_order: Number(displayOrder?.max_display_order || 0) + 1,
|
|
58
|
+
created_at: now,
|
|
59
|
+
updated_at: now,
|
|
60
|
+
} as any)
|
|
61
|
+
.executeTakeFirst()
|
|
57
62
|
|
|
58
|
-
|
|
63
|
+
const id = insertedId(result)
|
|
59
64
|
|
|
60
|
-
|
|
61
|
-
|
|
65
|
+
if (!id)
|
|
66
|
+
throw new Error('Category insert did not return an id.')
|
|
62
67
|
|
|
63
|
-
|
|
68
|
+
const created = await findRow('categories', id, trx)
|
|
69
|
+
if (!created)
|
|
70
|
+
throw new Error('Created category could not be loaded.')
|
|
71
|
+
return created
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
if (!category)
|
|
75
|
+
return response.json({ message: 'A category with that slug already exists.' }, 422)
|
|
76
|
+
|
|
77
|
+
return response.json(category, 201)
|
|
78
|
+
}
|
|
79
|
+
catch (error) {
|
|
80
|
+
return dashboardOperationalError(error, 'Category could not be created.', 'CategoryStoreAction', 500)
|
|
81
|
+
}
|
|
64
82
|
},
|
|
65
83
|
})
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import type { RequestInstance } from '@stacksjs/types'
|
|
2
2
|
import { Action } from '@stacksjs/actions'
|
|
3
3
|
import { db } from '@stacksjs/database'
|
|
4
|
+
import { transaction } from '@stacksjs/orm'
|
|
4
5
|
import { response } from '@stacksjs/router'
|
|
6
|
+
import { dashboardOperationalError } from '../dashboard-response'
|
|
5
7
|
import { rowExists, rowId } from './content-input'
|
|
6
8
|
|
|
7
9
|
/**
|
|
@@ -20,11 +22,22 @@ export default new Action({
|
|
|
20
22
|
if (!id)
|
|
21
23
|
return response.json({ message: 'A valid comment id is required.' }, 422)
|
|
22
24
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
+
try {
|
|
26
|
+
const deleted = await transaction(async (rawTrx) => {
|
|
27
|
+
const trx = rawTrx as unknown as typeof db
|
|
28
|
+
if (!await rowExists('comments', id, trx))
|
|
29
|
+
return false
|
|
30
|
+
await trx.deleteFrom('comments').where('id', '=', id).execute()
|
|
31
|
+
return true
|
|
32
|
+
})
|
|
25
33
|
|
|
26
|
-
|
|
34
|
+
if (!deleted)
|
|
35
|
+
return response.json({ message: 'Comment not found.' }, 404)
|
|
27
36
|
|
|
28
|
-
|
|
37
|
+
return response.json({ message: 'Comment deleted.', id })
|
|
38
|
+
}
|
|
39
|
+
catch (error) {
|
|
40
|
+
return dashboardOperationalError(error, 'Comment could not be deleted.', 'CommentDestroyAction', 500)
|
|
41
|
+
}
|
|
29
42
|
},
|
|
30
43
|
})
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Action } from '@stacksjs/actions'
|
|
2
2
|
import { db } from '@stacksjs/database'
|
|
3
|
+
import { dashboardOperationalError } from '../dashboard-response'
|
|
3
4
|
import { normalizeCommentStatus } from './comment-input'
|
|
4
5
|
|
|
5
6
|
interface CommentRow {
|
|
@@ -33,24 +34,29 @@ export default new Action({
|
|
|
33
34
|
method: 'GET',
|
|
34
35
|
apiResponse: true,
|
|
35
36
|
async handle() {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
37
|
+
try {
|
|
38
|
+
const rows = await db
|
|
39
|
+
.selectFrom('comments')
|
|
40
|
+
.selectAll()
|
|
41
|
+
.orderBy('created_at', 'desc')
|
|
42
|
+
.execute() as unknown as CommentRow[]
|
|
41
43
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
44
|
+
const comments = rows.map(row => ({
|
|
45
|
+
id: Number(row.id),
|
|
46
|
+
author_name: String(row.author_name || ''),
|
|
47
|
+
author_email: String(row.author_email || ''),
|
|
48
|
+
content: String(row.content || row.body || ''),
|
|
49
|
+
post_title: String(row.post_title || ''),
|
|
50
|
+
status: normalizeCommentStatus(row.status),
|
|
51
|
+
is_approved: Boolean(row.is_approved),
|
|
52
|
+
created_at: row.created_at || null,
|
|
53
|
+
updated_at: row.updated_at || null,
|
|
54
|
+
}))
|
|
53
55
|
|
|
54
|
-
|
|
56
|
+
return { comments }
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
return dashboardOperationalError(error, 'Comments could not be loaded.', 'CommentIndexAction')
|
|
60
|
+
}
|
|
55
61
|
},
|
|
56
62
|
})
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import type { RequestInstance } from '@stacksjs/types'
|
|
2
2
|
import { Action } from '@stacksjs/actions'
|
|
3
3
|
import { db } from '@stacksjs/database'
|
|
4
|
+
import { transaction } from '@stacksjs/orm'
|
|
4
5
|
import { response } from '@stacksjs/router'
|
|
6
|
+
import { dashboardOperationalError } from '../dashboard-response'
|
|
5
7
|
import { COMMENT_STATUSES, parseCommentStatus } from './comment-input'
|
|
6
8
|
import { findRow, rowExists, rowId, timestamp } from './content-input'
|
|
7
9
|
|
|
@@ -30,19 +32,35 @@ export default new Action({
|
|
|
30
32
|
if (!status)
|
|
31
33
|
return response.json({ message: `Status must be one of: ${COMMENT_STATUSES.join(', ')}.` }, 422)
|
|
32
34
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
+
try {
|
|
36
|
+
const comment = await transaction(async (rawTrx) => {
|
|
37
|
+
const trx = rawTrx as unknown as typeof db
|
|
38
|
+
if (!await rowExists('comments', id, trx))
|
|
39
|
+
return null
|
|
35
40
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
41
|
+
await trx
|
|
42
|
+
.updateTable('comments')
|
|
43
|
+
.set({
|
|
44
|
+
status,
|
|
45
|
+
is_approved: status === 'approved' ? 1 : 0,
|
|
46
|
+
updated_at: timestamp(),
|
|
47
|
+
} as any)
|
|
48
|
+
.where('id', '=', id)
|
|
49
|
+
.execute()
|
|
45
50
|
|
|
46
|
-
|
|
51
|
+
const updated = await findRow('comments', id, trx)
|
|
52
|
+
if (!updated)
|
|
53
|
+
throw new Error('Updated comment could not be loaded.')
|
|
54
|
+
return updated
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
if (!comment)
|
|
58
|
+
return response.json({ message: 'Comment not found.' }, 404)
|
|
59
|
+
|
|
60
|
+
return response.json(comment)
|
|
61
|
+
}
|
|
62
|
+
catch (error) {
|
|
63
|
+
return dashboardOperationalError(error, 'Comment could not be updated.', 'CommentUpdateAction', 500)
|
|
64
|
+
}
|
|
47
65
|
},
|
|
48
66
|
})
|