@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,9 +1,12 @@
|
|
|
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 } from '@stacksjs/orm'
|
|
4
5
|
import { response } from '@stacksjs/router'
|
|
6
|
+
import { teamOperationalError } from '../../Teams/team-response'
|
|
5
7
|
import { deliverTeamInvitation } from './team-invitation-delivery'
|
|
6
8
|
import {
|
|
9
|
+
changedRows,
|
|
7
10
|
generateInvitationToken,
|
|
8
11
|
hashInvitationToken,
|
|
9
12
|
invitationExpiresAt,
|
|
@@ -23,40 +26,59 @@ export default new Action({
|
|
|
23
26
|
if (!teamId || !invitationId)
|
|
24
27
|
return response.json({ message: 'Invalid team or invitation id.' }, 400)
|
|
25
28
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
'team_invitations.
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
29
|
+
let invitation
|
|
30
|
+
try {
|
|
31
|
+
invitation = await (db as any)
|
|
32
|
+
.selectFrom('team_invitations')
|
|
33
|
+
.innerJoin('teams', 'teams.id', '=', 'team_invitations.team_id')
|
|
34
|
+
.where('team_invitations.id', '=', invitationId)
|
|
35
|
+
.where('team_invitations.team_id', '=', teamId)
|
|
36
|
+
.select([
|
|
37
|
+
'team_invitations.id as id',
|
|
38
|
+
'team_invitations.email as email',
|
|
39
|
+
'team_invitations.role as role',
|
|
40
|
+
'team_invitations.status as status',
|
|
41
|
+
'team_invitations.token_hash as token_hash',
|
|
42
|
+
'teams.name as team_name',
|
|
43
|
+
])
|
|
44
|
+
.executeTakeFirst()
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
return teamOperationalError(error, 'Invitation could not be loaded.', 'TeamInvitationResendAction.lookup', 500)
|
|
48
|
+
}
|
|
39
49
|
if (!invitation)
|
|
40
50
|
return response.json({ message: 'Invitation not found.' }, 404)
|
|
41
51
|
if (!['pending', 'expired'].includes(String(invitation.status)))
|
|
42
52
|
return response.json({ message: 'Only pending or expired invitations can be resent.' }, 409)
|
|
43
53
|
|
|
44
54
|
const token = generateInvitationToken()
|
|
55
|
+
const tokenHash = hashInvitationToken(token)
|
|
45
56
|
const expiresAt = invitationExpiresAt()
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
57
|
+
try {
|
|
58
|
+
const result = await (db as any)
|
|
59
|
+
.updateTable('team_invitations')
|
|
60
|
+
.set({
|
|
61
|
+
token_hash: tokenHash,
|
|
62
|
+
pending_key: `${teamId}:${String(invitation.email).trim().toLowerCase()}`,
|
|
63
|
+
status: 'pending',
|
|
64
|
+
delivery_status: 'pending',
|
|
65
|
+
delivery_error: null,
|
|
66
|
+
expires_at: expiresAt,
|
|
67
|
+
delivered_at: null,
|
|
68
|
+
updated_at: sqlTimestamp(),
|
|
69
|
+
})
|
|
70
|
+
.where('id', '=', invitationId)
|
|
71
|
+
.where('team_id', '=', teamId)
|
|
72
|
+
.where('token_hash', '=', String(invitation.token_hash))
|
|
73
|
+
.executeTakeFirst()
|
|
74
|
+
if (changedRows(result) !== 1)
|
|
75
|
+
return response.json({ message: 'The invitation changed before it could be resent.' }, 409)
|
|
76
|
+
}
|
|
77
|
+
catch (error) {
|
|
78
|
+
if (isUniqueViolation(error))
|
|
79
|
+
return response.json({ message: 'Another pending invitation already exists for this email.' }, 409)
|
|
80
|
+
return teamOperationalError(error, 'Invitation could not be refreshed.', 'TeamInvitationResendAction.update', 500)
|
|
81
|
+
}
|
|
60
82
|
|
|
61
83
|
try {
|
|
62
84
|
await deliverTeamInvitation({
|
|
@@ -65,10 +87,11 @@ export default new Action({
|
|
|
65
87
|
teamName: String(invitation.team_name),
|
|
66
88
|
role: String(invitation.role),
|
|
67
89
|
token,
|
|
90
|
+
tokenHash,
|
|
68
91
|
})
|
|
69
92
|
}
|
|
70
|
-
catch {
|
|
71
|
-
return
|
|
93
|
+
catch (error) {
|
|
94
|
+
return teamOperationalError(error, 'The invitation was refreshed, but email delivery failed.', 'TeamInvitationResendAction.delivery', 502)
|
|
72
95
|
}
|
|
73
96
|
|
|
74
97
|
return {
|
|
@@ -2,9 +2,12 @@ import type { RequestInstance } from '@stacksjs/types'
|
|
|
2
2
|
import { randomUUID } from 'node:crypto'
|
|
3
3
|
import { Action } from '@stacksjs/actions'
|
|
4
4
|
import { db } from '@stacksjs/database'
|
|
5
|
+
import { isUniqueViolation } from '@stacksjs/orm'
|
|
5
6
|
import { response } from '@stacksjs/router'
|
|
7
|
+
import { TeamStateConflictError, teamOperationalError } from '../../Teams/team-response'
|
|
6
8
|
import { deliverTeamInvitation } from './team-invitation-delivery'
|
|
7
9
|
import {
|
|
10
|
+
changedRows,
|
|
8
11
|
generateInvitationToken,
|
|
9
12
|
hashInvitationToken,
|
|
10
13
|
invitationExpiresAt,
|
|
@@ -25,12 +28,12 @@ export default new Action({
|
|
|
25
28
|
method: 'POST',
|
|
26
29
|
apiResponse: true,
|
|
27
30
|
|
|
28
|
-
async handle(request: RequestInstance) {
|
|
31
|
+
async handle(request: RequestInstance<InviteInput>) {
|
|
29
32
|
const teamId = parsePositiveId(request.getParam('id'))
|
|
30
33
|
if (!teamId)
|
|
31
34
|
return response.json({ message: 'Invalid team id.' }, 400)
|
|
32
35
|
|
|
33
|
-
const input = (
|
|
36
|
+
const input = request.all()
|
|
34
37
|
const email = normalizeInvitationEmail(input.email)
|
|
35
38
|
const role = normalizeInvitationRole(input.role ?? 'member')
|
|
36
39
|
if (!email)
|
|
@@ -38,95 +41,123 @@ export default new Action({
|
|
|
38
41
|
if (!role)
|
|
39
42
|
return response.json({ message: 'Choose a valid invitation role.' }, 422)
|
|
40
43
|
|
|
41
|
-
const team = await db
|
|
42
|
-
.selectFrom('teams')
|
|
43
|
-
.where('id', '=', teamId)
|
|
44
|
-
.select(['id', 'name'])
|
|
45
|
-
.executeTakeFirst()
|
|
46
|
-
if (!team)
|
|
47
|
-
return response.json({ message: 'Team not found.' }, 404)
|
|
48
|
-
|
|
49
|
-
const existingUser = await db
|
|
50
|
-
.selectFrom('users')
|
|
51
|
-
.where('email', '=', email)
|
|
52
|
-
.select(['id'])
|
|
53
|
-
.executeTakeFirst()
|
|
54
|
-
if (existingUser) {
|
|
55
|
-
const membership = await (db as any)
|
|
56
|
-
.selectFrom('team_members')
|
|
57
|
-
.where('team_id', '=', teamId)
|
|
58
|
-
.where('user_id', '=', Number(existingUser.id))
|
|
59
|
-
.select(['id'])
|
|
60
|
-
.executeTakeFirst()
|
|
61
|
-
if (membership)
|
|
62
|
-
return response.json({ message: 'This person is already a member of the team.' }, 409)
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
const pending = await (db as any)
|
|
66
|
-
.selectFrom('team_invitations')
|
|
67
|
-
.where('team_id', '=', teamId)
|
|
68
|
-
.where('email', '=', email)
|
|
69
|
-
.where('status', '=', 'pending')
|
|
70
|
-
.select(['id', 'expires_at'])
|
|
71
|
-
.executeTakeFirst()
|
|
72
|
-
if (pending && new Date(String(pending.expires_at).replace(' ', 'T')).getTime() > Date.now()) {
|
|
73
|
-
return response.json({ message: 'A pending invitation already exists. Resend or revoke it from the invitation list.' }, 409)
|
|
74
|
-
}
|
|
75
|
-
if (pending) {
|
|
76
|
-
await (db as any)
|
|
77
|
-
.updateTable('team_invitations')
|
|
78
|
-
.set({ status: 'expired', updated_at: sqlTimestamp() })
|
|
79
|
-
.where('id', '=', Number(pending.id))
|
|
80
|
-
.execute()
|
|
81
|
-
}
|
|
82
|
-
|
|
83
44
|
const token = generateInvitationToken()
|
|
84
45
|
const tokenHash = hashInvitationToken(token)
|
|
46
|
+
const pendingKey = `${teamId}:${email}`
|
|
85
47
|
const expiresAt = invitationExpiresAt()
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
48
|
+
let created: { id: number, teamName: string } | Response
|
|
49
|
+
try {
|
|
50
|
+
const requester = await request.user()
|
|
51
|
+
const requesterId = Number(requester?.id)
|
|
52
|
+
const invitedByUserId = Number.isSafeInteger(requesterId) && requesterId > 0
|
|
53
|
+
? requesterId
|
|
54
|
+
: null
|
|
90
55
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
56
|
+
created = await db.transaction(async (rawTrx) => {
|
|
57
|
+
const trx = rawTrx as unknown as typeof db
|
|
58
|
+
const team = await trx
|
|
59
|
+
.selectFrom('teams')
|
|
60
|
+
.where('id', '=', teamId)
|
|
61
|
+
.select(['id', 'name'])
|
|
62
|
+
.executeTakeFirst()
|
|
63
|
+
if (!team)
|
|
64
|
+
return response.json({ message: 'Team not found.' }, 404)
|
|
65
|
+
|
|
66
|
+
const [existingUser, pending] = await Promise.all([
|
|
67
|
+
trx
|
|
68
|
+
.selectFrom('users')
|
|
69
|
+
.where('email', '=', email)
|
|
70
|
+
.select(['id'])
|
|
71
|
+
.executeTakeFirst(),
|
|
72
|
+
(trx as any)
|
|
73
|
+
.selectFrom('team_invitations')
|
|
74
|
+
.where('team_id', '=', teamId)
|
|
75
|
+
.where('email', '=', email)
|
|
76
|
+
.where('status', '=', 'pending')
|
|
77
|
+
.select(['id', 'expires_at', 'token_hash'])
|
|
78
|
+
.executeTakeFirst(),
|
|
79
|
+
])
|
|
80
|
+
if (existingUser) {
|
|
81
|
+
const membership = await (trx as any)
|
|
82
|
+
.selectFrom('team_members')
|
|
83
|
+
.where('team_id', '=', teamId)
|
|
84
|
+
.where('user_id', '=', Number(existingUser.id))
|
|
85
|
+
.select(['id'])
|
|
86
|
+
.executeTakeFirst()
|
|
87
|
+
if (membership)
|
|
88
|
+
return response.json({ message: 'This person is already a member of the team.' }, 409)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (pending && new Date(String(pending.expires_at).replace(' ', 'T')).getTime() > Date.now()) {
|
|
92
|
+
return response.json({ message: 'A pending invitation already exists. Resend or revoke it from the invitation list.' }, 409)
|
|
93
|
+
}
|
|
94
|
+
if (pending) {
|
|
95
|
+
const expired = await (trx as any)
|
|
96
|
+
.updateTable('team_invitations')
|
|
97
|
+
.set({ status: 'expired', pending_key: null, updated_at: sqlTimestamp() })
|
|
98
|
+
.where('id', '=', Number(pending.id))
|
|
99
|
+
.where('token_hash', '=', String(pending.token_hash))
|
|
100
|
+
.where('status', '=', 'pending')
|
|
101
|
+
.executeTakeFirst()
|
|
102
|
+
if (changedRows(expired) !== 1)
|
|
103
|
+
throw new TeamStateConflictError('The pending invitation changed before it could be replaced.')
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
await (trx as any)
|
|
107
|
+
.insertInto('team_invitations')
|
|
108
|
+
.values({
|
|
109
|
+
team_id: teamId,
|
|
110
|
+
email,
|
|
111
|
+
role,
|
|
112
|
+
token_hash: tokenHash,
|
|
113
|
+
pending_key: pendingKey,
|
|
114
|
+
invited_by_user_id: invitedByUserId,
|
|
115
|
+
status: 'pending',
|
|
116
|
+
delivery_status: 'pending',
|
|
117
|
+
expires_at: expiresAt,
|
|
118
|
+
uuid: randomUUID(),
|
|
119
|
+
})
|
|
120
|
+
.execute()
|
|
121
|
+
|
|
122
|
+
const invitation = await (trx as any)
|
|
123
|
+
.selectFrom('team_invitations')
|
|
124
|
+
.where('token_hash', '=', tokenHash)
|
|
125
|
+
.select(['id'])
|
|
126
|
+
.executeTakeFirst()
|
|
127
|
+
if (!invitation)
|
|
128
|
+
throw new Error('The inserted invitation could not be resolved by its token hash.')
|
|
129
|
+
|
|
130
|
+
return { id: Number(invitation.id), teamName: String(team.name) }
|
|
103
131
|
})
|
|
104
|
-
|
|
132
|
+
}
|
|
133
|
+
catch (error) {
|
|
134
|
+
if (error instanceof TeamStateConflictError)
|
|
135
|
+
return response.json({ message: error.message }, 409)
|
|
136
|
+
if (isUniqueViolation(error))
|
|
137
|
+
return response.json({ message: 'A pending invitation already exists. Resend or revoke it from the invitation list.' }, 409)
|
|
138
|
+
return teamOperationalError(error, 'The invitation could not be created.', 'TeamInviteAction.create', 500)
|
|
139
|
+
}
|
|
105
140
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
.where('token_hash', '=', tokenHash)
|
|
109
|
-
.select(['id'])
|
|
110
|
-
.executeTakeFirst()
|
|
111
|
-
if (!created)
|
|
112
|
-
return response.json({ message: 'The invitation could not be created.' }, 500)
|
|
141
|
+
if (created instanceof Response)
|
|
142
|
+
return created
|
|
113
143
|
|
|
114
144
|
try {
|
|
115
145
|
await deliverTeamInvitation({
|
|
116
|
-
id:
|
|
146
|
+
id: created.id,
|
|
117
147
|
email,
|
|
118
|
-
teamName:
|
|
148
|
+
teamName: created.teamName,
|
|
119
149
|
role,
|
|
120
150
|
token,
|
|
151
|
+
tokenHash,
|
|
121
152
|
})
|
|
122
153
|
}
|
|
123
|
-
catch {
|
|
124
|
-
return
|
|
154
|
+
catch (error) {
|
|
155
|
+
return teamOperationalError(error, 'Invitation created, but email delivery failed. You can retry it from the invitation list.', 'TeamInviteAction.delivery', 502)
|
|
125
156
|
}
|
|
126
157
|
|
|
127
158
|
return response.json({
|
|
128
159
|
invitation: {
|
|
129
|
-
id:
|
|
160
|
+
id: created.id,
|
|
130
161
|
email,
|
|
131
162
|
role,
|
|
132
163
|
status: 'pending',
|
|
@@ -2,8 +2,9 @@ import type { RequestInstance } from '@stacksjs/types'
|
|
|
2
2
|
import { Action } from '@stacksjs/actions'
|
|
3
3
|
import { db } from '@stacksjs/database'
|
|
4
4
|
import { response } from '@stacksjs/router'
|
|
5
|
+
import { TeamStateConflictError, teamOperationalError } from '../../Teams/team-response'
|
|
5
6
|
import { syncTeamMemberCount } from './team-member-count'
|
|
6
|
-
import { parsePositiveId } from './team-records'
|
|
7
|
+
import { changedRows, parsePositiveId } from './team-records'
|
|
7
8
|
|
|
8
9
|
export default new Action({
|
|
9
10
|
name: 'Dashboard Team Member Destroy',
|
|
@@ -17,24 +18,42 @@ export default new Action({
|
|
|
17
18
|
if (!teamId || !memberId)
|
|
18
19
|
return response.json({ message: 'Invalid team or member id.' }, 400)
|
|
19
20
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
.
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
21
|
+
let result: Response | null
|
|
22
|
+
try {
|
|
23
|
+
result = await db.transaction(async (rawTrx) => {
|
|
24
|
+
const trx = rawTrx as unknown as typeof db
|
|
25
|
+
const member = await (trx as any)
|
|
26
|
+
.selectFrom('team_members')
|
|
27
|
+
.where('id', '=', memberId)
|
|
28
|
+
.where('team_id', '=', teamId)
|
|
29
|
+
.select(['id', 'role'])
|
|
30
|
+
.executeTakeFirst()
|
|
31
|
+
if (!member)
|
|
32
|
+
return response.json({ message: 'Team member not found.' }, 404)
|
|
33
|
+
if (member.role === 'owner')
|
|
34
|
+
return response.json({ message: 'Transfer team ownership before removing the owner.' }, 409)
|
|
30
35
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
+
const deleted = await (trx as any)
|
|
37
|
+
.deleteFrom('team_members')
|
|
38
|
+
.where('id', '=', memberId)
|
|
39
|
+
.where('team_id', '=', teamId)
|
|
40
|
+
.where('role', '!=', 'owner')
|
|
41
|
+
.executeTakeFirst()
|
|
42
|
+
if (changedRows(deleted) !== 1)
|
|
43
|
+
throw new TeamStateConflictError('The team member changed before they could be removed.')
|
|
36
44
|
|
|
37
|
-
|
|
45
|
+
await syncTeamMemberCount(teamId, trx)
|
|
46
|
+
return null
|
|
47
|
+
})
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
if (error instanceof TeamStateConflictError)
|
|
51
|
+
return response.json({ message: error.message }, 409)
|
|
52
|
+
return teamOperationalError(error, 'The team member could not be removed.', 'TeamMemberDestroyAction', 500)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (result)
|
|
56
|
+
return result
|
|
38
57
|
|
|
39
58
|
return response.noContent()
|
|
40
59
|
},
|
|
@@ -2,8 +2,9 @@ import type { RequestInstance } from '@stacksjs/types'
|
|
|
2
2
|
import { Action } from '@stacksjs/actions'
|
|
3
3
|
import { db } from '@stacksjs/database'
|
|
4
4
|
import { response } from '@stacksjs/router'
|
|
5
|
+
import { TeamStateConflictError, teamOperationalError } from '../../Teams/team-response'
|
|
5
6
|
import { syncTeamMemberCount } from './team-member-count'
|
|
6
|
-
import { normalizeInvitationRole, parsePositiveId, sqlTimestamp } from './team-records'
|
|
7
|
+
import { changedRows, normalizeInvitationRole, parsePositiveId, sqlTimestamp } from './team-records'
|
|
7
8
|
|
|
8
9
|
interface UpdateInput {
|
|
9
10
|
role?: unknown
|
|
@@ -16,40 +17,52 @@ export default new Action({
|
|
|
16
17
|
method: 'PATCH',
|
|
17
18
|
apiResponse: true,
|
|
18
19
|
|
|
19
|
-
async handle(request: RequestInstance) {
|
|
20
|
+
async handle(request: RequestInstance<UpdateInput>) {
|
|
20
21
|
const teamId = parsePositiveId(request.getParam('id'))
|
|
21
22
|
const memberId = parsePositiveId(request.getParam('memberId'))
|
|
22
23
|
if (!teamId || !memberId)
|
|
23
24
|
return response.json({ message: 'Invalid team or member id.' }, 400)
|
|
24
25
|
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
.
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
26
|
+
const input = request.all()
|
|
27
|
+
try {
|
|
28
|
+
return await db.transaction(async (rawTrx) => {
|
|
29
|
+
const trx = rawTrx as unknown as typeof db
|
|
30
|
+
const member = await (trx as any)
|
|
31
|
+
.selectFrom('team_members')
|
|
32
|
+
.where('id', '=', memberId)
|
|
33
|
+
.where('team_id', '=', teamId)
|
|
34
|
+
.select(['id', 'role', 'status'])
|
|
35
|
+
.executeTakeFirst()
|
|
36
|
+
if (!member)
|
|
37
|
+
return response.json({ message: 'Team member not found.' }, 404)
|
|
38
|
+
if (member.role === 'owner')
|
|
39
|
+
return response.json({ message: 'Transfer team ownership before changing the owner.' }, 409)
|
|
35
40
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
return response.json({ message: 'Choose a valid member status.' }, 422)
|
|
41
|
+
const role = input.role === undefined ? String(member.role) : normalizeInvitationRole(input.role)
|
|
42
|
+
const status = input.status === undefined ? String(member.status) : String(input.status).trim().toLowerCase()
|
|
43
|
+
if (!role)
|
|
44
|
+
return response.json({ message: 'Choose a valid member role.' }, 422)
|
|
45
|
+
if (!['active', 'suspended'].includes(status))
|
|
46
|
+
return response.json({ message: 'Choose a valid member status.' }, 422)
|
|
43
47
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
48
|
+
const updated = await (trx as any)
|
|
49
|
+
.updateTable('team_members')
|
|
50
|
+
.set({ role, status, updated_at: sqlTimestamp() })
|
|
51
|
+
.where('id', '=', memberId)
|
|
52
|
+
.where('team_id', '=', teamId)
|
|
53
|
+
.where('role', '!=', 'owner')
|
|
54
|
+
.executeTakeFirst()
|
|
55
|
+
if (changedRows(updated) !== 1)
|
|
56
|
+
throw new TeamStateConflictError('The team member changed before they could be updated.')
|
|
50
57
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
58
|
+
await syncTeamMemberCount(teamId, trx)
|
|
59
|
+
return { member: { id: memberId, role, status } }
|
|
60
|
+
})
|
|
61
|
+
}
|
|
62
|
+
catch (error) {
|
|
63
|
+
if (error instanceof TeamStateConflictError)
|
|
64
|
+
return response.json({ message: error.message }, 409)
|
|
65
|
+
return teamOperationalError(error, 'The team member could not be updated.', 'TeamMemberUpdateAction', 500)
|
|
66
|
+
}
|
|
54
67
|
},
|
|
55
68
|
})
|
|
@@ -2,6 +2,7 @@ import type { RequestInstance } from '@stacksjs/types'
|
|
|
2
2
|
import { Action } from '@stacksjs/actions'
|
|
3
3
|
import { db } from '@stacksjs/database'
|
|
4
4
|
import { response } from '@stacksjs/router'
|
|
5
|
+
import { teamOperationalError } from '../../Teams/team-response'
|
|
5
6
|
import { invitationStatus, parsePositiveId } from './team-records'
|
|
6
7
|
|
|
7
8
|
export default new Action({
|
|
@@ -15,69 +16,81 @@ export default new Action({
|
|
|
15
16
|
if (!teamId)
|
|
16
17
|
return response.json({ message: 'Invalid team id.' }, 400)
|
|
17
18
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
19
|
+
let team
|
|
20
|
+
try {
|
|
21
|
+
team = await db
|
|
22
|
+
.selectFrom('teams')
|
|
23
|
+
.where('id', '=', teamId)
|
|
24
|
+
.select(['id', 'name'])
|
|
25
|
+
.executeTakeFirst()
|
|
26
|
+
}
|
|
27
|
+
catch (error) {
|
|
28
|
+
return teamOperationalError(error, 'Team could not be loaded.', 'TeamPeopleIndexAction.team')
|
|
29
|
+
}
|
|
23
30
|
if (!team)
|
|
24
31
|
return response.json({ message: 'Team not found.' }, 404)
|
|
25
32
|
|
|
26
|
-
|
|
27
|
-
.
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
33
|
+
try {
|
|
34
|
+
const [members, invitations] = await Promise.all([
|
|
35
|
+
(db as any)
|
|
36
|
+
.selectFrom('team_members')
|
|
37
|
+
.innerJoin('users', 'users.id', '=', 'team_members.user_id')
|
|
38
|
+
.where('team_members.team_id', '=', teamId)
|
|
39
|
+
.select([
|
|
40
|
+
'team_members.id as id',
|
|
41
|
+
'team_members.user_id as user_id',
|
|
42
|
+
'team_members.role as role',
|
|
43
|
+
'team_members.status as status',
|
|
44
|
+
'team_members.created_at as created_at',
|
|
45
|
+
'users.name as name',
|
|
46
|
+
'users.email as email',
|
|
47
|
+
])
|
|
48
|
+
.orderBy('users.name', 'asc')
|
|
49
|
+
.execute(),
|
|
50
|
+
(db as any)
|
|
51
|
+
.selectFrom('team_invitations')
|
|
52
|
+
.where('team_id', '=', teamId)
|
|
53
|
+
.where('status', '=', 'pending')
|
|
54
|
+
.select([
|
|
55
|
+
'id',
|
|
56
|
+
'email',
|
|
57
|
+
'role',
|
|
58
|
+
'status',
|
|
59
|
+
'delivery_status',
|
|
60
|
+
'expires_at',
|
|
61
|
+
'created_at',
|
|
62
|
+
])
|
|
63
|
+
.orderBy('created_at', 'desc')
|
|
64
|
+
.execute(),
|
|
38
65
|
])
|
|
39
|
-
.orderBy('users.name', 'asc')
|
|
40
|
-
.execute()
|
|
41
66
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
status: String(member.status || 'active'),
|
|
70
|
-
joinedAt: member.created_at ? String(member.created_at) : null,
|
|
71
|
-
})),
|
|
72
|
-
invitations: invitations.map((invitation: any) => ({
|
|
73
|
-
id: Number(invitation.id),
|
|
74
|
-
email: String(invitation.email),
|
|
75
|
-
role: String(invitation.role || 'member'),
|
|
76
|
-
status: invitationStatus(invitation.status, invitation.expires_at),
|
|
77
|
-
deliveryStatus: String(invitation.delivery_status || 'pending'),
|
|
78
|
-
expiresAt: String(invitation.expires_at),
|
|
79
|
-
createdAt: invitation.created_at ? String(invitation.created_at) : null,
|
|
80
|
-
})),
|
|
67
|
+
return {
|
|
68
|
+
team: {
|
|
69
|
+
id: Number(team.id),
|
|
70
|
+
name: String(team.name),
|
|
71
|
+
},
|
|
72
|
+
members: members.map((member: any) => ({
|
|
73
|
+
id: Number(member.id),
|
|
74
|
+
userId: Number(member.user_id),
|
|
75
|
+
name: String(member.name || member.email || 'Team member'),
|
|
76
|
+
email: String(member.email || ''),
|
|
77
|
+
role: String(member.role || 'member'),
|
|
78
|
+
status: String(member.status || 'active'),
|
|
79
|
+
joinedAt: member.created_at ? String(member.created_at) : null,
|
|
80
|
+
})),
|
|
81
|
+
invitations: invitations.map((invitation: any) => ({
|
|
82
|
+
id: Number(invitation.id),
|
|
83
|
+
email: String(invitation.email),
|
|
84
|
+
role: String(invitation.role || 'member'),
|
|
85
|
+
status: invitationStatus(invitation.status, invitation.expires_at),
|
|
86
|
+
deliveryStatus: String(invitation.delivery_status || 'pending'),
|
|
87
|
+
expiresAt: String(invitation.expires_at),
|
|
88
|
+
createdAt: invitation.created_at ? String(invitation.created_at) : null,
|
|
89
|
+
})),
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
catch (error) {
|
|
93
|
+
return teamOperationalError(error, 'Team members and invitations could not be loaded.', 'TeamPeopleIndexAction.people')
|
|
81
94
|
}
|
|
82
95
|
},
|
|
83
96
|
})
|