@stacksjs/defaults 0.70.354 → 0.70.356
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ai/skills/stacks-browse/SKILL.md +17 -2
- package/ai/skills/stacks-browse/scripts/browse.ts +207 -6
- package/ai/skills/stacks-email/SKILL.md +73 -16
- 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 +12 -14
- package/app/Actions/Dashboard/Buddy/BuddyChatAction.ts +2 -3
- package/app/Actions/Dashboard/Ci/RepoRunJobsAction.ts +10 -15
- package/app/Actions/Dashboard/Ci/RepoRunsAction.ts +12 -14
- package/app/Actions/Dashboard/Ci/RunnerHistoryAction.ts +11 -10
- package/app/Actions/Dashboard/Ci/StatusAction.ts +2 -11
- package/app/Actions/Dashboard/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 +76 -61
- package/app/Actions/Dashboard/Commerce/CommercePrintDevicesAction.ts +2 -4
- package/app/Actions/Dashboard/Commerce/CommercePrintLogsAction.ts +4 -7
- package/app/Actions/Dashboard/Commerce/CommerceProductDetailAction.ts +4 -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/PaymentRefundAction.ts +1 -0
- package/app/Actions/Dashboard/Commerce/ProductUnitDefaultAction.ts +1 -0
- 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/Commerce/TaxRateDefaultAction.ts +1 -0
- 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 +26 -21
- 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 +5 -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 +21 -7
- package/app/Actions/Dashboard/Email/CapturedMailIndexAction.ts +2 -4
- package/app/Actions/Dashboard/Email/CapturedMailShowAction.ts +3 -4
- package/app/Actions/Dashboard/Email/InboxActivityAction.ts +28 -16
- package/app/Actions/Dashboard/Email/InboxAttachmentDownloadAction.ts +45 -0
- package/app/Actions/Dashboard/Email/InboxDeleteAction.ts +11 -6
- package/app/Actions/Dashboard/Email/InboxIndexAction.ts +5 -7
- package/app/Actions/Dashboard/Email/InboxMarkReadAction.ts +11 -6
- package/app/Actions/Dashboard/Email/InboxMarkUnreadAction.ts +11 -6
- package/app/Actions/Dashboard/Email/InboxPreferenceShowAction.ts +10 -4
- package/app/Actions/Dashboard/Email/InboxPreferenceUpdateAction.ts +8 -9
- package/app/Actions/Dashboard/Email/InboxShowAction.ts +7 -7
- package/app/Actions/Dashboard/Email/InboxStatsAction.ts +5 -7
- package/app/Actions/Dashboard/Email/inbox-request.ts +33 -0
- package/app/Actions/Dashboard/Infrastructure/InsightsAction.ts +15 -14
- package/app/Actions/Dashboard/Infrastructure/LogIndexAction.ts +4 -11
- package/app/Actions/Dashboard/Infrastructure/ServerShowAction.ts +3 -6
- package/app/Actions/Dashboard/Jobs/JobIndexAction.ts +9 -18
- package/app/Actions/Dashboard/Jobs/JobRetryAction.ts +4 -3
- package/app/Actions/Dashboard/Jobs/JobShowAction.ts +16 -11
- package/app/Actions/Dashboard/Jobs/JobStatsAction.ts +2 -4
- package/app/Actions/Dashboard/Kanban/BoardDestroyAction.ts +7 -8
- package/app/Actions/Dashboard/Kanban/BoardShowAction.ts +11 -19
- package/app/Actions/Dashboard/Kanban/BoardStoreAction.ts +6 -6
- package/app/Actions/Dashboard/Kanban/BoardUpdateAction.ts +7 -8
- package/app/Actions/Dashboard/Kanban/BoardsIndexAction.ts +3 -5
- package/app/Actions/Dashboard/Kanban/BoardsReorderAction.ts +6 -6
- package/app/Actions/Dashboard/Kanban/CardAssigneesSyncAction.ts +13 -12
- package/app/Actions/Dashboard/Kanban/CardCommentDestroyAction.ts +12 -15
- package/app/Actions/Dashboard/Kanban/CardCommentStoreAction.ts +21 -46
- package/app/Actions/Dashboard/Kanban/CardCommentUpdateAction.ts +43 -0
- package/app/Actions/Dashboard/Kanban/CardDestroyAction.ts +7 -8
- package/app/Actions/Dashboard/Kanban/CardLabelsSyncAction.ts +8 -9
- package/app/Actions/Dashboard/Kanban/CardShowAction.ts +6 -7
- package/app/Actions/Dashboard/Kanban/CardStoreAction.ts +9 -9
- package/app/Actions/Dashboard/Kanban/CardUpdateAction.ts +7 -8
- package/app/Actions/Dashboard/Kanban/CardsReorderAction.ts +6 -6
- package/app/Actions/Dashboard/Kanban/ColumnDestroyAction.ts +7 -8
- package/app/Actions/Dashboard/Kanban/ColumnStoreAction.ts +6 -6
- package/app/Actions/Dashboard/Kanban/ColumnUpdateAction.ts +7 -8
- package/app/Actions/Dashboard/Kanban/ColumnsReorderAction.ts +6 -6
- package/app/Actions/Dashboard/Kanban/LabelDestroyAction.ts +6 -7
- package/app/Actions/Dashboard/Kanban/LabelStoreAction.ts +6 -6
- package/app/Actions/Dashboard/Kanban/LabelUpdateAction.ts +7 -8
- package/app/Actions/Dashboard/Kanban/UsersListAction.ts +3 -4
- package/app/Actions/Dashboard/Kanban/kanban-comment.ts +14 -0
- package/app/Actions/Dashboard/Kanban/kanban-model.test.ts +36 -0
- package/app/Actions/Dashboard/Kanban/kanban-model.ts +14 -7
- package/app/Actions/Dashboard/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 +11 -8
- package/app/Actions/Dashboard/Models/ModelShowAction.ts +97 -192
- package/app/Actions/Dashboard/Models/ModelStoreAction.ts +14 -12
- package/app/Actions/Dashboard/Models/ModelUpdateAction.ts +18 -16
- package/app/Actions/Dashboard/Models/ModelsIndexAction.ts +50 -19
- package/app/Actions/Dashboard/Models/model-api.ts +27 -0
- package/app/Actions/Dashboard/Models/model-write.ts +22 -15
- package/app/Actions/Dashboard/Notifications/NotificationDeliveryHistoryAction.ts +49 -40
- package/app/Actions/Dashboard/Notifications/NotificationDeliveryIndexAction.ts +20 -10
- 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 +3 -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 +6 -6
- package/app/Actions/Dashboard/Rbac/PermissionStoreAction.ts +5 -4
- package/app/Actions/Dashboard/Rbac/PermissionsIndexAction.ts +2 -3
- package/app/Actions/Dashboard/Rbac/RoleDestroyAction.ts +6 -6
- package/app/Actions/Dashboard/Rbac/RolePermissionsShowAction.ts +6 -6
- package/app/Actions/Dashboard/Rbac/RolePermissionsSyncAction.ts +6 -9
- package/app/Actions/Dashboard/Rbac/RoleStoreAction.ts +5 -4
- package/app/Actions/Dashboard/Rbac/RolesIndexAction.ts +4 -6
- package/app/Actions/Dashboard/Rbac/UserRolesShowAction.ts +6 -7
- package/app/Actions/Dashboard/Rbac/UserRolesSyncAction.ts +6 -13
- 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 +69 -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 +104 -73
- package/app/Actions/Dashboard/Teams/TeamMemberDestroyAction.ts +36 -17
- package/app/Actions/Dashboard/Teams/TeamMemberUpdateAction.ts +41 -28
- 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-request.ts +13 -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/CardComment.ts +3 -4
- 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/dashboard-api.ts +51 -10
- package/functions/data.ts +0 -1
- package/functions/inbox.test.ts +2 -0
- package/functions/inbox.ts +27 -2
- package/functions/jobs.ts +0 -1
- package/functions/public-application-url.test.ts +13 -0
- package/functions/public-application-url.ts +27 -0
- package/ide/vscode/package.json +1 -1
- package/package.json +1 -1
- package/resources/components/Dashboard/Auth/AccessTokens.stx +15 -0
- package/resources/components/Dashboard/Ci/CiDashboard.stx +24 -6
- package/resources/components/Dashboard/Commerce/CommerceTaxDialog.stx +1 -1
- package/resources/components/Dashboard/Commerce/ProductWaitlistDashboard.stx +8 -7
- package/resources/components/Dashboard/Commerce/RestaurantWaitlistDashboard.stx +8 -7
- package/resources/components/Dashboard/Content/BlogDashboard.stx +14 -2
- package/resources/components/Dashboard/Content/PostsDashboard.stx +7 -1
- package/resources/components/Dashboard/Data/DataRecords.stx +0 -4
- package/resources/components/Dashboard/Deployments/DeploymentDetail.stx +16 -2
- package/resources/components/Dashboard/Email/EmailNavigation.stx +5 -5
- package/resources/components/Dashboard/Email/EmailSidebar.stx +1 -1
- package/resources/components/Dashboard/Email/InboxAttachmentList.stx +53 -0
- package/resources/components/Dashboard/Email/InboxDashboard.stx +5 -4
- package/resources/components/Dashboard/Email/InboxMessageDetail.stx +6 -4
- package/resources/components/Dashboard/Jobs/JobDetail.stx +3 -2
- package/resources/components/Dashboard/Kanban/KanbanBoardDashboard.stx +63 -6
- package/resources/components/Dashboard/Kanban/KanbanBoardsDashboard.stx +3 -6
- package/resources/components/Dashboard/Management/PermissionsDashboard.stx +4 -4
- package/resources/components/Dashboard/Models/ModelRecordsDashboard.stx +7 -13
- package/resources/components/Dashboard/Models/ModelsOverview.stx +1 -1
- package/resources/components/Dashboard/NotFoundDashboard.stx +2 -5
- package/resources/components/Dashboard/Queries/QueryDashboard.stx +6 -2
- package/resources/components/Dashboard/Settings/AppearanceSettingsDashboard.stx +4 -4
- package/resources/components/Dashboard/UI/EmptyState.stx +2 -2
- package/resources/components/Dashboard/UI/PageLayout.stx +3 -3
- package/resources/functions/dashboard/sidebar.ts +4 -4
- package/routes/dashboard-api.ts +8 -20
- package/views/dashboard/.discovered-models.json +54 -0
- package/views/dashboard/layouts/default.stx +19 -16
- package/views/dashboard/stores/auth.ts +15 -14
- package/views/dashboard/stores/ci.ts +13 -7
- package/views/dashboard/stores/kanban.ts +48 -11
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { config } from '@stacksjs/config'
|
|
2
2
|
import { db } from '@stacksjs/database'
|
|
3
3
|
import { sendTeamInvitation } from '../../../Mail/TeamInvitation'
|
|
4
|
-
import {
|
|
4
|
+
import { teamOperationalIssue } from '../../Teams/team-response'
|
|
5
|
+
import { changedRows, invitationUrl, sqlTimestamp } from './team-records'
|
|
5
6
|
|
|
6
7
|
interface DeliveryInput {
|
|
7
8
|
id: number
|
|
@@ -9,12 +10,22 @@ interface DeliveryInput {
|
|
|
9
10
|
teamName: string
|
|
10
11
|
role: string
|
|
11
12
|
token: string
|
|
13
|
+
tokenHash: string
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async function recordDeliveryFailure(input: DeliveryInput, message: string): Promise<void> {
|
|
17
|
+
try {
|
|
18
|
+
await markTeamInvitationDeliveryFailed(input.id, input.tokenHash, message)
|
|
19
|
+
}
|
|
20
|
+
catch (error) {
|
|
21
|
+
teamOperationalIssue(error, 'Invitation delivery failure could not be recorded.', 'deliverTeamInvitation.failureStatus')
|
|
22
|
+
}
|
|
12
23
|
}
|
|
13
24
|
|
|
14
25
|
export async function deliverTeamInvitation(input: DeliveryInput): Promise<void> {
|
|
15
26
|
const baseUrl = String(process.env.APP_URL || config.app.url || '').trim()
|
|
16
27
|
if (!baseUrl) {
|
|
17
|
-
await
|
|
28
|
+
await recordDeliveryFailure(input, 'APP_URL is not configured.')
|
|
18
29
|
throw new Error('APP_URL must be configured before invitations can be delivered.')
|
|
19
30
|
}
|
|
20
31
|
|
|
@@ -26,7 +37,15 @@ export async function deliverTeamInvitation(input: DeliveryInput): Promise<void>
|
|
|
26
37
|
invitationUrl: invitationUrl(baseUrl, input.token),
|
|
27
38
|
expiresInDays: 7,
|
|
28
39
|
})
|
|
29
|
-
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
const message = teamOperationalIssue(error, 'Email delivery failed.', 'deliverTeamInvitation.provider')
|
|
43
|
+
await recordDeliveryFailure(input, message)
|
|
44
|
+
throw error
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
try {
|
|
48
|
+
const result = await (db as any)
|
|
30
49
|
.updateTable('team_invitations')
|
|
31
50
|
.set({
|
|
32
51
|
delivery_status: 'sent',
|
|
@@ -35,16 +54,18 @@ export async function deliverTeamInvitation(input: DeliveryInput): Promise<void>
|
|
|
35
54
|
updated_at: sqlTimestamp(),
|
|
36
55
|
})
|
|
37
56
|
.where('id', '=', input.id)
|
|
38
|
-
.
|
|
57
|
+
.where('token_hash', '=', input.tokenHash)
|
|
58
|
+
.executeTakeFirst()
|
|
59
|
+
if (changedRows(result) !== 1)
|
|
60
|
+
throw new Error('Invitation delivery status changed before it could be recorded.')
|
|
39
61
|
}
|
|
40
62
|
catch (error) {
|
|
41
|
-
|
|
42
|
-
await markTeamInvitationDeliveryFailed(input.id, message)
|
|
63
|
+
teamOperationalIssue(error, 'Invitation delivery success could not be recorded.', 'deliverTeamInvitation.successStatus')
|
|
43
64
|
throw error
|
|
44
65
|
}
|
|
45
66
|
}
|
|
46
67
|
|
|
47
|
-
export async function markTeamInvitationDeliveryFailed(id: number, error: string): Promise<void> {
|
|
68
|
+
export async function markTeamInvitationDeliveryFailed(id: number, tokenHash: string, error: string): Promise<void> {
|
|
48
69
|
await (db as any)
|
|
49
70
|
.updateTable('team_invitations')
|
|
50
71
|
.set({
|
|
@@ -53,5 +74,6 @@ export async function markTeamInvitationDeliveryFailed(id: number, error: string
|
|
|
53
74
|
updated_at: sqlTimestamp(),
|
|
54
75
|
})
|
|
55
76
|
.where('id', '=', id)
|
|
77
|
+
.where('token_hash', '=', tokenHash)
|
|
56
78
|
.execute()
|
|
57
79
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { db } from '@stacksjs/database'
|
|
1
|
+
import { db, sql } from '@stacksjs/database'
|
|
2
2
|
import { sqlTimestamp } from './team-records'
|
|
3
3
|
|
|
4
4
|
export async function syncTeamMemberCount(teamId: number, connection: typeof db = db): Promise<number> {
|
|
@@ -6,9 +6,11 @@ export async function syncTeamMemberCount(teamId: number, connection: typeof db
|
|
|
6
6
|
.selectFrom('team_members')
|
|
7
7
|
.where('team_id', '=', teamId)
|
|
8
8
|
.where('status', '=', 'active')
|
|
9
|
-
.select(
|
|
9
|
+
.select(sql`count(*) as count`)
|
|
10
10
|
.executeTakeFirst()
|
|
11
11
|
const count = Number(row?.count || 0)
|
|
12
|
+
if (!Number.isSafeInteger(count) || count < 0)
|
|
13
|
+
throw new TypeError('Team member count must be a non-negative integer.')
|
|
12
14
|
|
|
13
15
|
await connection
|
|
14
16
|
.updateTable('teams')
|
|
@@ -25,6 +25,8 @@ describe('team records', () => {
|
|
|
25
25
|
expect(changedRows({ changes: 1 })).toBe(1)
|
|
26
26
|
expect(changedRows({ affectedRows: 2 })).toBe(2)
|
|
27
27
|
expect(changedRows([{ numUpdatedRows: 3n }])).toBe(3)
|
|
28
|
+
expect(changedRows({ numDeletedRows: 4n })).toBe(4)
|
|
29
|
+
expect(changedRows({ numInsertedRows: 5n })).toBe(5)
|
|
28
30
|
expect(changedRows(null)).toBe(0)
|
|
29
31
|
})
|
|
30
32
|
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { RequestInstance } from '@stacksjs/types'
|
|
2
|
+
|
|
3
|
+
/** Reads one scalar dashboard input value through the native request bag. */
|
|
4
|
+
export function dashboardRequestValue(
|
|
5
|
+
request: RequestInstance,
|
|
6
|
+
key: string,
|
|
7
|
+
fallback = '',
|
|
8
|
+
): string {
|
|
9
|
+
const value = request.get<unknown>(key, fallback)
|
|
10
|
+
if (Array.isArray(value))
|
|
11
|
+
return value.length > 0 ? String(value[0]).trim() : fallback
|
|
12
|
+
return value == null ? fallback : String(value).trim()
|
|
13
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { response } from '@stacksjs/router'
|
|
2
|
+
|
|
3
|
+
export function dashboardOperationalError(
|
|
4
|
+
error: unknown,
|
|
5
|
+
message: string,
|
|
6
|
+
action: string,
|
|
7
|
+
status = 503,
|
|
8
|
+
): Response {
|
|
9
|
+
console.error(`[dashboard/api] ${action} failed:`, error)
|
|
10
|
+
return response.json({ message }, status)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function dashboardOperationalIssue(error: unknown, message: string, action: string): string {
|
|
14
|
+
console.error(`[dashboard/api] ${action} failed:`, error)
|
|
15
|
+
return message
|
|
16
|
+
}
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
sqlTimestamp,
|
|
14
14
|
} from '../Dashboard/Teams/team-records'
|
|
15
15
|
import { syncTeamMemberCount } from '../Dashboard/Teams/team-member-count'
|
|
16
|
+
import { teamOperationalError } from './team-response'
|
|
16
17
|
|
|
17
18
|
export default new Action({
|
|
18
19
|
name: 'Accept Team Invitation',
|
|
@@ -25,23 +26,38 @@ export default new Action({
|
|
|
25
26
|
if (!token || token.length > 200)
|
|
26
27
|
return response.json({ message: 'Invitation not found.' }, 404)
|
|
27
28
|
|
|
28
|
-
|
|
29
|
+
let authenticated
|
|
30
|
+
try {
|
|
31
|
+
authenticated = await resolveAuthenticatedUser(request as any)
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
return teamOperationalError(error, 'Authentication could not be verified.', 'AcceptInvitationAction.auth')
|
|
35
|
+
}
|
|
29
36
|
if (!authenticated?.id)
|
|
30
37
|
return response.json({ message: 'Sign in to accept this invitation.' }, 401)
|
|
31
38
|
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
.
|
|
39
|
+
const tokenHash = hashInvitationToken(token)
|
|
40
|
+
let user
|
|
41
|
+
let invitation
|
|
42
|
+
try {
|
|
43
|
+
[user, invitation] = await Promise.all([
|
|
44
|
+
db
|
|
45
|
+
.selectFrom('users')
|
|
46
|
+
.where('id', '=', authenticated.id)
|
|
47
|
+
.select(['id', 'email'])
|
|
48
|
+
.executeTakeFirst(),
|
|
49
|
+
(db as any)
|
|
50
|
+
.selectFrom('team_invitations')
|
|
51
|
+
.where('token_hash', '=', tokenHash)
|
|
52
|
+
.select(['id', 'team_id', 'email', 'role', 'status', 'expires_at'])
|
|
53
|
+
.executeTakeFirst(),
|
|
54
|
+
])
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
return teamOperationalError(error, 'Invitation acceptance could not be prepared.', 'AcceptInvitationAction.lookup')
|
|
58
|
+
}
|
|
37
59
|
if (!user)
|
|
38
60
|
return response.json({ message: 'Authenticated user not found.' }, 401)
|
|
39
|
-
|
|
40
|
-
const invitation = await (db as any)
|
|
41
|
-
.selectFrom('team_invitations')
|
|
42
|
-
.where('token_hash', '=', hashInvitationToken(token))
|
|
43
|
-
.select(['id', 'team_id', 'email', 'role', 'status', 'expires_at'])
|
|
44
|
-
.executeTakeFirst()
|
|
45
61
|
if (!invitation)
|
|
46
62
|
return response.json({ message: 'Invitation not found.' }, 404)
|
|
47
63
|
|
|
@@ -58,49 +74,56 @@ export default new Action({
|
|
|
58
74
|
const userId = Number(user.id)
|
|
59
75
|
const now = sqlTimestamp()
|
|
60
76
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
77
|
+
let accepted
|
|
78
|
+
try {
|
|
79
|
+
accepted = await db.transaction(async (rawTrx) => {
|
|
80
|
+
const trx = rawTrx as unknown as typeof db
|
|
81
|
+
const claim = await (trx as any)
|
|
82
|
+
.updateTable('team_invitations')
|
|
83
|
+
.set({
|
|
84
|
+
status: 'accepted',
|
|
85
|
+
token_hash: hashInvitationToken(generateInvitationToken()),
|
|
86
|
+
pending_key: null,
|
|
87
|
+
accepted_by_user_id: userId,
|
|
88
|
+
accepted_at: now,
|
|
89
|
+
updated_at: now,
|
|
90
|
+
})
|
|
91
|
+
.where('id', '=', Number(invitation.id))
|
|
92
|
+
.where('token_hash', '=', tokenHash)
|
|
93
|
+
.where('status', '=', 'pending')
|
|
94
|
+
.executeTakeFirst()
|
|
76
95
|
|
|
77
|
-
|
|
78
|
-
|
|
96
|
+
if (changedRows(claim) !== 1)
|
|
97
|
+
return null
|
|
79
98
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
99
|
+
const existing = await (trx as any)
|
|
100
|
+
.selectFrom('team_members')
|
|
101
|
+
.where('team_id', '=', teamId)
|
|
102
|
+
.where('user_id', '=', userId)
|
|
103
|
+
.select(['id'])
|
|
104
|
+
.executeTakeFirst()
|
|
86
105
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
106
|
+
if (!existing) {
|
|
107
|
+
await (trx as any)
|
|
108
|
+
.insertInto('team_members')
|
|
109
|
+
.values({
|
|
110
|
+
team_id: teamId,
|
|
111
|
+
user_id: userId,
|
|
112
|
+
role: String(invitation.role),
|
|
113
|
+
status: 'active',
|
|
114
|
+
created_at: now,
|
|
115
|
+
uuid: randomUUID(),
|
|
116
|
+
})
|
|
117
|
+
.execute()
|
|
118
|
+
}
|
|
100
119
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
120
|
+
await syncTeamMemberCount(teamId, trx)
|
|
121
|
+
return { alreadyMember: Boolean(existing) }
|
|
122
|
+
})
|
|
123
|
+
}
|
|
124
|
+
catch (error) {
|
|
125
|
+
return teamOperationalError(error, 'The invitation could not be accepted.', 'AcceptInvitationAction.accept', 500)
|
|
126
|
+
}
|
|
104
127
|
|
|
105
128
|
if (!accepted)
|
|
106
129
|
return response.json({ message: 'This invitation is no longer available.' }, 409)
|
|
@@ -3,6 +3,7 @@ import { Action } from '@stacksjs/actions'
|
|
|
3
3
|
import { db } from '@stacksjs/database'
|
|
4
4
|
import { response } from '@stacksjs/router'
|
|
5
5
|
import { hashInvitationToken, invitationStatus } from '../Dashboard/Teams/team-records'
|
|
6
|
+
import { teamOperationalError } from './team-response'
|
|
6
7
|
|
|
7
8
|
export default new Action({
|
|
8
9
|
name: 'Show Team Invitation',
|
|
@@ -15,20 +16,26 @@ export default new Action({
|
|
|
15
16
|
if (!token || token.length > 200)
|
|
16
17
|
return response.json({ message: 'Invitation not found.' }, 404)
|
|
17
18
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
'team_invitations.
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
19
|
+
let invitation
|
|
20
|
+
try {
|
|
21
|
+
invitation = await (db as any)
|
|
22
|
+
.selectFrom('team_invitations')
|
|
23
|
+
.innerJoin('teams', 'teams.id', '=', 'team_invitations.team_id')
|
|
24
|
+
.where('team_invitations.token_hash', '=', hashInvitationToken(token))
|
|
25
|
+
.select([
|
|
26
|
+
'team_invitations.id as id',
|
|
27
|
+
'team_invitations.team_id as team_id',
|
|
28
|
+
'team_invitations.email as email',
|
|
29
|
+
'team_invitations.role as role',
|
|
30
|
+
'team_invitations.status as status',
|
|
31
|
+
'team_invitations.expires_at as expires_at',
|
|
32
|
+
'teams.name as team_name',
|
|
33
|
+
])
|
|
34
|
+
.executeTakeFirst()
|
|
35
|
+
}
|
|
36
|
+
catch (error) {
|
|
37
|
+
return teamOperationalError(error, 'Invitation details could not be loaded.', 'ShowInvitationAction')
|
|
38
|
+
}
|
|
32
39
|
if (!invitation)
|
|
33
40
|
return response.json({ message: 'Invitation not found.' }, 404)
|
|
34
41
|
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { response } from '@stacksjs/router'
|
|
2
|
+
|
|
3
|
+
export class TeamStateConflictError extends Error {
|
|
4
|
+
constructor(message: string) {
|
|
5
|
+
super(message)
|
|
6
|
+
this.name = 'TeamStateConflictError'
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function teamOperationalError(
|
|
11
|
+
error: unknown,
|
|
12
|
+
message: string,
|
|
13
|
+
action: string,
|
|
14
|
+
status = 503,
|
|
15
|
+
): Response {
|
|
16
|
+
console.error(`[teams/api] ${action} failed:`, error)
|
|
17
|
+
return response.json({ message }, status)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function teamOperationalIssue(error: unknown, message: string, action: string): string {
|
|
21
|
+
console.error(`[teams/api] ${action} failed:`, error)
|
|
22
|
+
return message
|
|
23
|
+
}
|
|
@@ -48,7 +48,7 @@ export async function sendOrderConfirmation(options: OrderConfirmationOptions):
|
|
|
48
48
|
subject: `Your ${appName} order #${options.orderId} is confirmed`,
|
|
49
49
|
})
|
|
50
50
|
|
|
51
|
-
await mail.
|
|
51
|
+
await mail.sendOrFail({
|
|
52
52
|
to: [options.to],
|
|
53
53
|
from: { name: appName, address: fromAddress },
|
|
54
54
|
subject: `Your ${appName} order #${options.orderId} is confirmed`,
|
|
@@ -2,13 +2,12 @@ import { defineModel } from '@stacksjs/orm'
|
|
|
2
2
|
import { schema } from '@stacksjs/validation'
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
* Card comment
|
|
5
|
+
* Card comment for the dashboard Kanban surface.
|
|
6
6
|
*
|
|
7
7
|
* Each comment belongs to a card and (optionally) to the user who
|
|
8
8
|
* wrote it. Anonymous comments are allowed via nullable `user_id` —
|
|
9
9
|
* makes the dev dashboard usable on localhost without an auth round-
|
|
10
|
-
* trip, and supports system-generated activity entries
|
|
11
|
-
* (eventually — Phase 4 could write status-change comments here).
|
|
10
|
+
* trip, and supports system-generated activity entries.
|
|
12
11
|
*
|
|
13
12
|
* Distinct from `Comment` (which is post-specific) so the schemas
|
|
14
13
|
* don't fight: post comments carry `author_name`/`author_email` for
|
|
@@ -35,7 +34,7 @@ export default defineModel({
|
|
|
35
34
|
|
|
36
35
|
useApi: {
|
|
37
36
|
uri: 'card-comments',
|
|
38
|
-
routes: ['index', 'store', 'show', 'destroy'],
|
|
37
|
+
routes: ['index', 'store', 'show', 'update', 'destroy'],
|
|
39
38
|
middleware: ['auth'],
|
|
40
39
|
},
|
|
41
40
|
},
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { defineModel } from '@stacksjs/orm'
|
|
2
|
+
import { schema } from '@stacksjs/validation'
|
|
3
|
+
|
|
4
|
+
export default defineModel({
|
|
5
|
+
name: 'EmailIdempotency',
|
|
6
|
+
table: 'email_idempotency',
|
|
7
|
+
primaryKey: 'id',
|
|
8
|
+
autoIncrement: true,
|
|
9
|
+
|
|
10
|
+
traits: {
|
|
11
|
+
useTimestamps: true,
|
|
12
|
+
useApi: {
|
|
13
|
+
uri: 'email-idempotency',
|
|
14
|
+
routes: ['index', 'show', 'destroy'],
|
|
15
|
+
middleware: ['auth'],
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
|
|
19
|
+
attributes: {
|
|
20
|
+
idempotencyKey: {
|
|
21
|
+
required: true,
|
|
22
|
+
hidden: true,
|
|
23
|
+
unique: true,
|
|
24
|
+
validation: {
|
|
25
|
+
rule: schema.string().required().max(255),
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
messageId: {
|
|
29
|
+
required: false,
|
|
30
|
+
validation: {
|
|
31
|
+
rule: schema.string().max(512),
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
recipient: {
|
|
35
|
+
required: false,
|
|
36
|
+
hidden: true,
|
|
37
|
+
validation: {
|
|
38
|
+
rule: schema.string().max(2000),
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
subject: {
|
|
42
|
+
required: false,
|
|
43
|
+
hidden: true,
|
|
44
|
+
validation: {
|
|
45
|
+
rule: schema.string().max(998),
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
provider: {
|
|
49
|
+
required: false,
|
|
50
|
+
validation: {
|
|
51
|
+
rule: schema.string().max(100),
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
success: {
|
|
55
|
+
required: true,
|
|
56
|
+
default: true,
|
|
57
|
+
validation: {
|
|
58
|
+
rule: schema.boolean(),
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
|
|
63
|
+
dashboard: { enabled: false },
|
|
64
|
+
} as const)
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { defineModel } from '@stacksjs/orm'
|
|
2
|
+
import { schema } from '@stacksjs/validation'
|
|
3
|
+
|
|
4
|
+
export default defineModel({
|
|
5
|
+
name: 'EmailSuppression',
|
|
6
|
+
table: 'email_suppressions',
|
|
7
|
+
primaryKey: 'id',
|
|
8
|
+
autoIncrement: true,
|
|
9
|
+
|
|
10
|
+
indexes: [
|
|
11
|
+
{
|
|
12
|
+
name: 'email_suppressions_email_type_unique',
|
|
13
|
+
columns: ['email', 'type'],
|
|
14
|
+
unique: true,
|
|
15
|
+
},
|
|
16
|
+
],
|
|
17
|
+
|
|
18
|
+
traits: {
|
|
19
|
+
useTimestamps: true,
|
|
20
|
+
useApi: {
|
|
21
|
+
uri: 'email-suppressions',
|
|
22
|
+
routes: ['index', 'show', 'destroy'],
|
|
23
|
+
middleware: ['auth'],
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
|
|
27
|
+
attributes: {
|
|
28
|
+
email: {
|
|
29
|
+
required: true,
|
|
30
|
+
fillable: true,
|
|
31
|
+
validation: {
|
|
32
|
+
rule: schema.string().email().required().max(320),
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
type: {
|
|
36
|
+
required: true,
|
|
37
|
+
fillable: true,
|
|
38
|
+
validation: {
|
|
39
|
+
rule: schema.enum(['bounce', 'complaint', 'unsubscribe', 'manual']),
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
reason: {
|
|
43
|
+
required: false,
|
|
44
|
+
fillable: true,
|
|
45
|
+
validation: {
|
|
46
|
+
rule: schema.string().max(2000),
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
|
|
51
|
+
dashboard: { enabled: false },
|
|
52
|
+
} as const)
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { defineModel } from '@stacksjs/orm'
|
|
2
|
+
import { schema } from '@stacksjs/validation'
|
|
3
|
+
|
|
4
|
+
export default defineModel({
|
|
5
|
+
name: 'EmailWebhookEvent',
|
|
6
|
+
table: 'email_webhook_events',
|
|
7
|
+
primaryKey: 'id',
|
|
8
|
+
autoIncrement: true,
|
|
9
|
+
|
|
10
|
+
indexes: [
|
|
11
|
+
{
|
|
12
|
+
name: 'email_webhook_events_provider_event_unique',
|
|
13
|
+
columns: ['provider', 'event_id'],
|
|
14
|
+
unique: true,
|
|
15
|
+
},
|
|
16
|
+
],
|
|
17
|
+
|
|
18
|
+
traits: {
|
|
19
|
+
useTimestamps: true,
|
|
20
|
+
useApi: {
|
|
21
|
+
uri: 'email-webhook-events',
|
|
22
|
+
routes: ['index', 'show', 'destroy'],
|
|
23
|
+
middleware: ['auth'],
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
|
|
27
|
+
attributes: {
|
|
28
|
+
provider: {
|
|
29
|
+
required: true,
|
|
30
|
+
fillable: true,
|
|
31
|
+
validation: {
|
|
32
|
+
rule: schema.enum(['mailgun', 'postmark', 'ses', 'sendgrid']),
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
eventId: {
|
|
36
|
+
required: true,
|
|
37
|
+
hidden: true,
|
|
38
|
+
validation: {
|
|
39
|
+
rule: schema.string().required().max(512),
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
processedAt: {
|
|
43
|
+
required: true,
|
|
44
|
+
validation: {
|
|
45
|
+
rule: schema.timestamp().required(),
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
|
|
50
|
+
dashboard: { enabled: false },
|
|
51
|
+
} as const)
|
|
@@ -20,6 +20,11 @@ export default defineModel({
|
|
|
20
20
|
columns: ['token_hash'],
|
|
21
21
|
unique: true,
|
|
22
22
|
},
|
|
23
|
+
{
|
|
24
|
+
name: 'team_invitations_pending_key_unique',
|
|
25
|
+
columns: ['pending_key'],
|
|
26
|
+
unique: true,
|
|
27
|
+
},
|
|
23
28
|
{
|
|
24
29
|
name: 'team_invitations_team_email_status_index',
|
|
25
30
|
columns: ['team_id', 'email', 'status'],
|
|
@@ -79,6 +84,14 @@ export default defineModel({
|
|
|
79
84
|
},
|
|
80
85
|
},
|
|
81
86
|
|
|
87
|
+
pendingKey: {
|
|
88
|
+
required: false,
|
|
89
|
+
hidden: true,
|
|
90
|
+
validation: {
|
|
91
|
+
rule: schema.string().max(384),
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
|
|
82
95
|
invitedByUserId: {
|
|
83
96
|
required: false,
|
|
84
97
|
validation: {
|