@headless-lms/core 0.3.0 → 0.5.0
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/dist/assets/events.d.ts +24 -18
- package/dist/assets/file-urls.d.ts +11 -7
- package/dist/assets/file-urls.js +14 -4
- package/dist/assets/file-urls.js.map +1 -1
- package/dist/assets/index.d.ts +5 -4
- package/dist/assets/index.js +4 -3
- package/dist/assets/jobs.d.ts +15 -0
- package/dist/assets/jobs.js +30 -0
- package/dist/assets/jobs.js.map +1 -0
- package/dist/assets/ports.d.ts +37 -17
- package/dist/assets/service.d.ts +30 -15
- package/dist/assets/service.js +229 -59
- package/dist/assets/service.js.map +1 -1
- package/dist/automations/actions.js +2 -63
- package/dist/automations/actions.js.map +1 -1
- package/dist/automations/catalog.d.ts +1 -2
- package/dist/automations/catalog.js +137 -65
- package/dist/automations/catalog.js.map +1 -1
- package/dist/automations/community-derivations.js +20 -20
- package/dist/automations/community-derivations.js.map +1 -1
- package/dist/automations/community-mail.d.ts +36 -20
- package/dist/automations/community-mail.js +32 -24
- package/dist/automations/community-mail.js.map +1 -1
- package/dist/automations/event-permissions.d.ts +12 -0
- package/dist/automations/event-permissions.js +38 -0
- package/dist/automations/event-permissions.js.map +1 -0
- package/dist/automations/events.d.ts +0 -10
- package/dist/automations/index.d.ts +12 -4
- package/dist/automations/index.js +9 -1
- package/dist/automations/notifications/applicable.d.ts +8 -0
- package/dist/automations/notifications/applicable.js +12 -0
- package/dist/automations/notifications/applicable.js.map +1 -0
- package/dist/automations/notifications/debounce.d.ts +25 -0
- package/dist/automations/notifications/debounce.js +93 -0
- package/dist/automations/notifications/debounce.js.map +1 -0
- package/dist/automations/notifications/defaults.d.ts +8 -0
- package/dist/automations/notifications/defaults.js +346 -0
- package/dist/automations/notifications/defaults.js.map +1 -0
- package/dist/automations/notifications/delivery.d.ts +28 -0
- package/dist/automations/notifications/delivery.js +88 -0
- package/dist/automations/notifications/delivery.js.map +1 -0
- package/dist/automations/notifications/processor.d.ts +64 -0
- package/dist/automations/notifications/processor.js +270 -0
- package/dist/automations/notifications/processor.js.map +1 -0
- package/dist/automations/notifications/rules/content.d.ts +6 -0
- package/dist/automations/notifications/rules/content.js +96 -0
- package/dist/automations/notifications/rules/content.js.map +1 -0
- package/dist/automations/notifications/rules/discussion.d.ts +7 -0
- package/dist/automations/notifications/rules/discussion.js +678 -0
- package/dist/automations/notifications/rules/discussion.js.map +1 -0
- package/dist/automations/notifications/rules/entitlements.d.ts +6 -0
- package/dist/automations/notifications/rules/entitlements.js +276 -0
- package/dist/automations/notifications/rules/entitlements.js.map +1 -0
- package/dist/automations/notifications/rules/events.d.ts +9 -0
- package/dist/automations/notifications/rules/events.js +27 -0
- package/dist/automations/notifications/rules/events.js.map +1 -0
- package/dist/automations/notifications/rules/index.d.ts +6 -0
- package/dist/automations/notifications/rules/index.js +15 -0
- package/dist/automations/notifications/rules/index.js.map +1 -0
- package/dist/automations/notifications/rules/organizations.d.ts +6 -0
- package/dist/automations/notifications/rules/organizations.js +35 -0
- package/dist/automations/notifications/rules/organizations.js.map +1 -0
- package/dist/automations/notifications/rules/recipients.d.ts +32 -0
- package/dist/automations/notifications/rules/recipients.js +98 -0
- package/dist/automations/notifications/rules/recipients.js.map +1 -0
- package/dist/automations/notifications/service.d.ts +29 -0
- package/dist/automations/notifications/service.js +45 -0
- package/dist/automations/notifications/service.js.map +1 -0
- package/dist/automations/notifications/types.d.ts +7 -0
- package/dist/automations/notifications/types.js +560 -0
- package/dist/automations/notifications/types.js.map +1 -0
- package/dist/automations/ports.d.ts +134 -21
- package/dist/automations/service.d.ts +30 -30
- package/dist/automations/service.js +111 -144
- package/dist/automations/service.js.map +1 -1
- package/dist/content/event-sight.d.ts +11 -0
- package/dist/content/event-sight.js +11 -0
- package/dist/content/event-sight.js.map +1 -0
- package/dist/content/events.d.ts +437 -46
- package/dist/content/events.js +45 -1
- package/dist/content/events.js.map +1 -1
- package/dist/content/index.d.ts +8 -4
- package/dist/content/index.js +5 -1
- package/dist/content/jobs.d.ts +16 -0
- package/dist/content/jobs.js +37 -0
- package/dist/content/jobs.js.map +1 -0
- package/dist/content/model.d.ts +2 -2
- package/dist/content/notification-categories.d.ts +37 -0
- package/dist/content/notification-categories.js +73 -0
- package/dist/content/notification-categories.js.map +1 -0
- package/dist/content/ports.d.ts +132 -68
- package/dist/content/service.d.ts +100 -68
- package/dist/content/service.js +703 -146
- package/dist/content/service.js.map +1 -1
- package/dist/content/titles.d.ts +25 -0
- package/dist/content/titles.js +82 -0
- package/dist/content/titles.js.map +1 -0
- package/dist/content/types.d.ts +2 -2
- package/dist/discussion/authors.d.ts +4 -2
- package/dist/discussion/authors.js +9 -5
- package/dist/discussion/authors.js.map +1 -1
- package/dist/discussion/chats.d.ts +63 -39
- package/dist/discussion/chats.js +455 -190
- package/dist/discussion/chats.js.map +1 -1
- package/dist/discussion/digests.d.ts +24 -0
- package/dist/discussion/digests.js +104 -0
- package/dist/discussion/digests.js.map +1 -0
- package/dist/discussion/events.d.ts +227 -23
- package/dist/discussion/events.js +114 -19
- package/dist/discussion/events.js.map +1 -1
- package/dist/discussion/index.d.ts +12 -4
- package/dist/discussion/index.js +9 -2
- package/dist/discussion/jobs.d.ts +16 -0
- package/dist/discussion/jobs.js +40 -0
- package/dist/discussion/jobs.js.map +1 -0
- package/dist/discussion/live.d.ts +106 -0
- package/dist/discussion/live.js +642 -0
- package/dist/discussion/live.js.map +1 -0
- package/dist/discussion/mentions.d.ts +11 -0
- package/dist/discussion/mentions.js +35 -0
- package/dist/discussion/mentions.js.map +1 -0
- package/dist/discussion/ports.d.ts +279 -114
- package/dist/discussion/ports.js.map +1 -1
- package/dist/discussion/posts.d.ts +60 -44
- package/dist/discussion/posts.js +453 -287
- package/dist/discussion/posts.js.map +1 -1
- package/dist/discussion/rules.d.ts +18 -0
- package/dist/discussion/rules.js +22 -0
- package/dist/discussion/rules.js.map +1 -0
- package/dist/discussion/service.d.ts +50 -35
- package/dist/discussion/service.js +333 -205
- package/dist/discussion/service.js.map +1 -1
- package/dist/discussion/subscriptions.d.ts +37 -0
- package/dist/discussion/subscriptions.js +115 -0
- package/dist/discussion/subscriptions.js.map +1 -0
- package/dist/discussion/titles.d.ts +28 -0
- package/dist/discussion/titles.js +98 -0
- package/dist/discussion/titles.js.map +1 -0
- package/dist/entitlements/events.d.ts +20 -20
- package/dist/entitlements/index.d.ts +5 -4
- package/dist/entitlements/index.js +3 -2
- package/dist/entitlements/jobs.d.ts +12 -0
- package/dist/entitlements/jobs.js +18 -0
- package/dist/entitlements/jobs.js.map +1 -0
- package/dist/entitlements/model.d.ts +2 -2
- package/dist/entitlements/ports.d.ts +68 -38
- package/dist/entitlements/rules.d.ts +3 -6
- package/dist/entitlements/rules.js +5 -10
- package/dist/entitlements/rules.js.map +1 -1
- package/dist/entitlements/service.d.ts +43 -28
- package/dist/entitlements/service.js +387 -160
- package/dist/entitlements/service.js.map +1 -1
- package/dist/identity/devices.d.ts +30 -0
- package/dist/identity/devices.js +109 -0
- package/dist/identity/devices.js.map +1 -0
- package/dist/identity/index.d.ts +6 -5
- package/dist/identity/index.js +4 -3
- package/dist/identity/model.d.ts +2 -2
- package/dist/identity/notification-settings.d.ts +28 -0
- package/dist/identity/notification-settings.js +114 -0
- package/dist/identity/notification-settings.js.map +1 -0
- package/dist/identity/ports.d.ts +80 -56
- package/dist/identity/service.d.ts +8 -7
- package/dist/identity/service.js +17 -13
- package/dist/identity/service.js.map +1 -1
- package/dist/integrations/ports.d.ts +9 -8
- package/dist/integrations/service.d.ts +9 -8
- package/dist/integrations/service.js +33 -16
- package/dist/integrations/service.js.map +1 -1
- package/dist/organizations/default-roles.d.ts +22 -0
- package/dist/organizations/default-roles.js +89 -0
- package/dist/organizations/default-roles.js.map +1 -0
- package/dist/organizations/effective-permissions.d.ts +45 -0
- package/dist/organizations/effective-permissions.js +113 -0
- package/dist/organizations/effective-permissions.js.map +1 -0
- package/dist/organizations/events.d.ts +320 -181
- package/dist/organizations/events.js +90 -18
- package/dist/organizations/events.js.map +1 -1
- package/dist/organizations/index.d.ts +15 -10
- package/dist/organizations/index.js +8 -3
- package/dist/organizations/members.d.ts +66 -8
- package/dist/organizations/members.js.map +1 -1
- package/dist/organizations/notification-catalog.d.ts +31 -0
- package/dist/organizations/notification-catalog.js +32 -0
- package/dist/organizations/notification-catalog.js.map +1 -0
- package/dist/organizations/notification-categories.d.ts +34 -0
- package/dist/organizations/notification-categories.js +66 -0
- package/dist/organizations/notification-categories.js.map +1 -0
- package/dist/organizations/org-roles.d.ts +33 -0
- package/dist/organizations/org-roles.js +49 -0
- package/dist/organizations/org-roles.js.map +1 -0
- package/dist/organizations/permissions.d.ts +311 -0
- package/dist/organizations/permissions.js +327 -0
- package/dist/organizations/permissions.js.map +1 -0
- package/dist/organizations/ports.d.ts +187 -65
- package/dist/organizations/service.d.ts +104 -35
- package/dist/organizations/service.js +1247 -323
- package/dist/organizations/service.js.map +1 -1
- package/dist/organizations/testing.d.ts +18 -0
- package/dist/organizations/testing.js +37 -0
- package/dist/organizations/testing.js.map +1 -0
- package/dist/organizations/types.d.ts +5 -16
- package/dist/progress/index.d.ts +2 -2
- package/dist/progress/ports.d.ts +11 -3
- package/dist/progress/service.d.ts +6 -5
- package/dist/progress/service.js +23 -8
- package/dist/progress/service.js.map +1 -1
- package/dist/reporting/admin/index.d.ts +6 -4
- package/dist/reporting/admin/model.d.ts +36 -3
- package/dist/reporting/admin/ports.d.ts +39 -14
- package/dist/reporting/admin/service.d.ts +32 -11
- package/dist/reporting/admin/service.js +102 -18
- package/dist/reporting/admin/service.js.map +1 -1
- package/dist/reporting/events/index.d.ts +27 -0
- package/dist/reporting/events/index.js +20 -0
- package/dist/reporting/events/index.js.map +1 -0
- package/dist/reporting/member/calendar.d.ts +11 -0
- package/dist/reporting/member/calendar.js +75 -0
- package/dist/reporting/member/calendar.js.map +1 -0
- package/dist/reporting/member/index.d.ts +3 -2
- package/dist/reporting/member/model.d.ts +80 -26
- package/dist/reporting/member/ports.d.ts +50 -28
- package/dist/reporting/member/service.d.ts +81 -35
- package/dist/reporting/member/service.js +438 -145
- package/dist/reporting/member/service.js.map +1 -1
- package/dist/shared/actor.d.ts +22 -3
- package/dist/shared/errors.d.ts +15 -2
- package/dist/shared/errors.js +21 -2
- package/dist/shared/errors.js.map +1 -1
- package/dist/shared/id.d.ts +7 -0
- package/dist/shared/id.js +8 -1
- package/dist/shared/id.js.map +1 -1
- package/dist/shared/logger.d.ts +2 -1
- package/dist/shared/logger.js +10 -1
- package/dist/shared/logger.js.map +1 -1
- package/dist/shared/mailer.d.ts +11 -1
- package/dist/shared/mailer.js +32 -5
- package/dist/shared/mailer.js.map +1 -1
- package/dist/shared/notification-categories.d.ts +16 -0
- package/dist/shared/notification-categories.js +94 -0
- package/dist/shared/notification-categories.js.map +1 -0
- package/dist/shared/notification-settings.d.ts +14 -0
- package/dist/shared/notification-settings.js +61 -0
- package/dist/shared/notification-settings.js.map +1 -0
- package/dist/shared/ports.d.ts +68 -2
- package/dist/shared/settings.d.ts +2 -1
- package/dist/shared/settings.js +1 -0
- package/dist/shared/settings.js.map +1 -1
- package/dist/types/automations.d.ts +8 -2
- package/dist/types/content.d.ts +2 -2
- package/dist/types/discussion.d.ts +4 -5
- package/dist/types/email-templates.d.ts +119 -6
- package/dist/types/entitlements.d.ts +2 -2
- package/dist/types/identity.d.ts +14 -17
- package/dist/types/index.d.ts +13 -12
- package/dist/types/index.js +2 -1
- package/dist/types/notifications.d.ts +157 -0
- package/dist/types/notifications.js +1 -0
- package/dist/types/organizations.d.ts +3 -2
- package/dist/types/ports.d.ts +8 -1
- package/dist/types/ports.js +7 -1
- package/dist/types/ports.js.map +1 -0
- package/dist/types/schemas/assets.d.ts +5 -5
- package/dist/types/schemas/assets.js +4 -3
- package/dist/types/schemas/assets.js.map +1 -1
- package/dist/types/schemas/automations.d.ts +1 -10
- package/dist/types/schemas/automations.js +1 -3
- package/dist/types/schemas/automations.js.map +1 -1
- package/dist/types/schemas/content.d.ts +596 -11
- package/dist/types/schemas/content.js +225 -9
- package/dist/types/schemas/content.js.map +1 -1
- package/dist/types/schemas/discussion.d.ts +362 -66
- package/dist/types/schemas/discussion.js +123 -15
- package/dist/types/schemas/discussion.js.map +1 -1
- package/dist/types/schemas/entitlements.d.ts +14 -1
- package/dist/types/schemas/entitlements.js +11 -4
- package/dist/types/schemas/entitlements.js.map +1 -1
- package/dist/types/schemas/identity.d.ts +1 -85
- package/dist/types/schemas/identity.js +1 -42
- package/dist/types/schemas/identity.js.map +1 -1
- package/dist/types/schemas/index.d.ts +8 -7
- package/dist/types/schemas/index.js +8 -7
- package/dist/types/schemas/notifications.d.ts +96 -0
- package/dist/types/schemas/notifications.js +57 -0
- package/dist/types/schemas/notifications.js.map +1 -0
- package/dist/types/schemas/organizations.d.ts +96 -60
- package/dist/types/schemas/organizations.js +78 -27
- package/dist/types/schemas/organizations.js.map +1 -1
- package/dist/types/schemas/progress.d.ts +0 -1
- package/dist/types/schemas/progress.js +0 -1
- package/dist/types/schemas/progress.js.map +1 -1
- package/package.json +1 -1
- package/dist/automations/community-notifications.d.ts +0 -16
- package/dist/automations/community-notifications.js +0 -84
- package/dist/automations/community-notifications.js.map +0 -1
- package/dist/automations/seeds.d.ts +0 -17
- package/dist/automations/seeds.js +0 -235
- package/dist/automations/seeds.js.map +0 -1
- package/dist/identity/notifications.d.ts +0 -27
- package/dist/identity/notifications.js +0 -101
- package/dist/identity/notifications.js.map +0 -1
- package/dist/organizations/access-scopes.d.ts +0 -19
- package/dist/organizations/access-scopes.js +0 -89
- package/dist/organizations/access-scopes.js.map +0 -1
- package/dist/organizations/roles.d.ts +0 -19
- package/dist/organizations/roles.js +0 -82
- package/dist/organizations/roles.js.map +0 -1
package/dist/identity/ports.d.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { JsonValue } from "../types/shared.js";
|
|
2
|
-
import { CreateUserInput,
|
|
2
|
+
import { CreateUserInput, UpdateUserInput, UserProfile, UserProfileView } from "../types/schemas/identity.js";
|
|
3
3
|
import { User } from "../types/identity.js";
|
|
4
|
+
import { DeviceView, NotificationCategory, PushDevice, PushProvider, SettingsScope, StoredSetting } from "../types/notifications.js";
|
|
4
5
|
import "../types/index.js";
|
|
6
|
+
import { Actor } from "../shared/actor.js";
|
|
5
7
|
import { OutboxAppender, UnitOfWork } from "../shared/ports.js";
|
|
6
8
|
import "./model.js";
|
|
7
9
|
import "./types.js";
|
|
@@ -55,48 +57,10 @@ interface SessionAdmin {
|
|
|
55
57
|
/** Points the caller's current session at `orgId`. */
|
|
56
58
|
stampActiveOrganization(headers: AuthHeaders, orgId: string): Promise<boolean>;
|
|
57
59
|
}
|
|
58
|
-
interface NewNotification {
|
|
59
|
-
userId: string;
|
|
60
|
-
kind: NotificationKind;
|
|
61
|
-
actorOrgUserId: string | null;
|
|
62
|
-
communityId: string | null;
|
|
63
|
-
spaceId: string | null;
|
|
64
|
-
postId: string | null;
|
|
65
|
-
commentId: string | null;
|
|
66
|
-
chatId: string | null;
|
|
67
|
-
messageId: string | null;
|
|
68
|
-
}
|
|
69
|
-
interface NotificationsQuery {
|
|
70
|
-
page: number;
|
|
71
|
-
pageSize: number;
|
|
72
|
-
}
|
|
73
|
-
interface NotificationPage {
|
|
74
|
-
rows: Notification[];
|
|
75
|
-
total: number;
|
|
76
|
-
page: number;
|
|
77
|
-
pageSize: number;
|
|
78
|
-
unreadCount: number;
|
|
79
|
-
}
|
|
80
|
-
/** What automations calls to tell people. */
|
|
81
|
-
interface NotificationDesk {
|
|
82
|
-
notify(orgId: string, notifications: NewNotification[]): Promise<void>;
|
|
83
|
-
preferencesFor(orgId: string, userIds: string[], kind: NotificationKind): Promise<Record<string, NotificationPreference>>;
|
|
84
|
-
}
|
|
85
|
-
/** A person's own notifications and preferences. */
|
|
86
|
-
interface NotificationInbox {
|
|
87
|
-
listNotifications(orgId: string, userId: string, query: NotificationsQuery): Promise<NotificationPage>;
|
|
88
|
-
markNotificationRead(orgId: string, userId: string, notificationId: string): Promise<Notification>;
|
|
89
|
-
markAllNotificationsRead(orgId: string, userId: string): Promise<{
|
|
90
|
-
updated: number;
|
|
91
|
-
}>;
|
|
92
|
-
notificationPreferences(orgId: string, userId: string): Promise<NotificationPreference[]>;
|
|
93
|
-
setNotificationPreferences(orgId: string, userId: string, preferences: NotificationPreference[]): Promise<NotificationPreference[]>;
|
|
94
|
-
}
|
|
95
|
-
interface NotificationService extends NotificationDesk, NotificationInbox {}
|
|
96
60
|
interface IdentityService extends UserProvisioner, UserResolver, UserEditor {
|
|
97
|
-
profileOf(
|
|
61
|
+
profileOf(actor: MemberActor): Promise<UserProfileView>;
|
|
98
62
|
displayName(userId: string | null): Promise<string>;
|
|
99
|
-
updateProfile(
|
|
63
|
+
updateProfile(patch: UpdateProfileInput, actor: MemberActor): Promise<UserProfileView>;
|
|
100
64
|
}
|
|
101
65
|
interface IdentityRepository {
|
|
102
66
|
insertUser(input: CreateUserInput): Promise<User>;
|
|
@@ -107,25 +71,85 @@ interface IdentityRepository {
|
|
|
107
71
|
findProfile(userId: string): Promise<UserProfile | null>;
|
|
108
72
|
upsertProfile(userId: string, patch: ProfilePatch): Promise<UserProfile>;
|
|
109
73
|
}
|
|
110
|
-
interface NotificationsRepository {
|
|
111
|
-
insert(orgId: string, rows: Notification[]): Promise<void>;
|
|
112
|
-
page(orgId: string, userId: string, query: NotificationsQuery): Promise<{
|
|
113
|
-
rows: Notification[];
|
|
114
|
-
total: number;
|
|
115
|
-
unreadCount: number;
|
|
116
|
-
}>;
|
|
117
|
-
markRead(orgId: string, userId: string, notificationId: string, at: Date): Promise<Notification | null>;
|
|
118
|
-
markAllRead(orgId: string, userId: string, at: Date): Promise<number>;
|
|
119
|
-
preferences(orgId: string, userIds: string[]): Promise<(NotificationPreference & {
|
|
120
|
-
userId: string;
|
|
121
|
-
})[]>;
|
|
122
|
-
upsertPreferences(orgId: string, userId: string, preferences: NotificationPreference[]): Promise<void>;
|
|
123
|
-
}
|
|
124
74
|
interface IdentityTxScope {
|
|
125
75
|
identity: IdentityRepository;
|
|
126
76
|
outbox: OutboxAppender;
|
|
127
77
|
}
|
|
128
78
|
type IdentityUnitOfWork = UnitOfWork<IdentityTxScope>;
|
|
79
|
+
type MemberActor = Actor & {
|
|
80
|
+
userId: string;
|
|
81
|
+
};
|
|
82
|
+
interface SettingChange {
|
|
83
|
+
category: string;
|
|
84
|
+
spaceId?: string;
|
|
85
|
+
channel: string;
|
|
86
|
+
enabled: boolean | null;
|
|
87
|
+
}
|
|
88
|
+
interface NotificationSettingsService {
|
|
89
|
+
get(orgId: string, userId: string, scope: SettingsScope): Promise<StoredSetting[]>;
|
|
90
|
+
getMany(orgId: string, userIds: string[], scope: SettingsScope, categories: string[]): Promise<Record<string, StoredSetting[]>>;
|
|
91
|
+
set(scope: SettingsScope, changes: SettingChange[], actor: MemberActor): Promise<void>;
|
|
92
|
+
switchedOn(orgId: string, communityId: string, category: string, spaceId: string | null): Promise<string[]>;
|
|
93
|
+
}
|
|
94
|
+
interface NotificationCategoryReader {
|
|
95
|
+
communityList(orgId: string, communityId: string): Promise<NotificationCategory[]>;
|
|
96
|
+
orgList(orgId: string): Promise<NotificationCategory[]>;
|
|
97
|
+
}
|
|
98
|
+
interface CommunityAccessReader {
|
|
99
|
+
canOpen(orgId: string, actor: Actor, communityId: string): Promise<boolean>;
|
|
100
|
+
}
|
|
101
|
+
interface SettingKey {
|
|
102
|
+
communityId: string | null;
|
|
103
|
+
spaceId: string | null;
|
|
104
|
+
category: string;
|
|
105
|
+
channel: string;
|
|
106
|
+
}
|
|
107
|
+
interface NotificationSettingsRepository {
|
|
108
|
+
forUsers(orgId: string, userIds: string[], communityId: string | null, categories: string[] | null): Promise<(StoredSetting & {
|
|
109
|
+
userId: string;
|
|
110
|
+
})[]>;
|
|
111
|
+
apply(orgId: string, userId: string, upserts: (SettingKey & {
|
|
112
|
+
enabled: boolean;
|
|
113
|
+
})[], deletes: SettingKey[], at: Date): Promise<void>;
|
|
114
|
+
switchedOn(orgId: string, communityId: string, category: string, spaceId: string | null): Promise<string[]>;
|
|
115
|
+
}
|
|
116
|
+
interface DeviceRegistration {
|
|
117
|
+
provider: PushProvider;
|
|
118
|
+
token: string;
|
|
119
|
+
keys?: {
|
|
120
|
+
p256dh: string;
|
|
121
|
+
auth: string;
|
|
122
|
+
};
|
|
123
|
+
appId?: string;
|
|
124
|
+
environment?: 'production' | 'sandbox';
|
|
125
|
+
}
|
|
126
|
+
interface DevicesService {
|
|
127
|
+
register(input: DeviceRegistration, actor: MemberActor): Promise<PushDevice>;
|
|
128
|
+
list(actor: MemberActor): Promise<DeviceView[]>;
|
|
129
|
+
remove(deviceId: string, actor: MemberActor): Promise<void>;
|
|
130
|
+
activeDevices(orgId: string, userId: string, since: Date): Promise<PushDevice[]>;
|
|
131
|
+
withActiveDevices(orgId: string, userIds: string[], since: Date): Promise<string[]>;
|
|
132
|
+
removeDead(orgId: string, deviceIds: string[]): Promise<void>;
|
|
133
|
+
pushConfig(): {
|
|
134
|
+
vapidPublicKey: string | null;
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
interface NewDevice {
|
|
138
|
+
userId: string;
|
|
139
|
+
provider: PushProvider;
|
|
140
|
+
token: string;
|
|
141
|
+
p256dh: string | null;
|
|
142
|
+
auth: string | null;
|
|
143
|
+
appId: string | null;
|
|
144
|
+
environment: 'production' | 'sandbox' | null;
|
|
145
|
+
}
|
|
146
|
+
interface DevicesRepository {
|
|
147
|
+
upsert(orgId: string, device: NewDevice, at: Date): Promise<DeviceView>;
|
|
148
|
+
listForUser(orgId: string, userId: string, since: Date | null): Promise<DeviceView[]>;
|
|
149
|
+
usersWithDevices(orgId: string, userIds: string[], since: Date): Promise<string[]>;
|
|
150
|
+
deleteOwn(orgId: string, userId: string, deviceId: string): Promise<boolean>;
|
|
151
|
+
deleteMany(orgId: string, deviceIds: string[]): Promise<void>;
|
|
152
|
+
}
|
|
129
153
|
//#endregion
|
|
130
|
-
export { AuthHeaders, IdentityRepository, IdentityService, IdentityTxScope, IdentityUnitOfWork,
|
|
154
|
+
export { AuthHeaders, CommunityAccessReader, DeviceRegistration, DevicesRepository, DevicesService, IdentityRepository, IdentityService, IdentityTxScope, IdentityUnitOfWork, MemberActor, NewDevice, NotificationCategoryReader, NotificationSettingsRepository, NotificationSettingsService, ProfilePatch, SessionAdmin, SettingChange, SettingKey, UpdateProfileInput, UserEditor, UserProvisioner, UserResolver };
|
|
131
155
|
//# sourceMappingURL=ports.d.ts.map
|
|
@@ -3,13 +3,14 @@ import { CreateUserInput, UpdateUserInput, UserProfileView } from "../types/sche
|
|
|
3
3
|
import { User } from "../types/identity.js";
|
|
4
4
|
import "../shared/ports.js";
|
|
5
5
|
import { FileUrls } from "../assets/file-urls.js";
|
|
6
|
-
import { MemberImageReader } from "../assets/ports.js";
|
|
6
|
+
import { AssetFiles, MemberImageReader } from "../assets/ports.js";
|
|
7
7
|
import "../assets/index.js";
|
|
8
|
+
import { Mailer } from "../shared/mailer.js";
|
|
8
9
|
import "./model.js";
|
|
9
10
|
import "./types.js";
|
|
10
|
-
import { IdentityRepository, IdentityService, IdentityUnitOfWork, UpdateProfileInput } from "./ports.js";
|
|
11
|
-
import { Mailer } from "../shared/mailer.js";
|
|
11
|
+
import { IdentityRepository, IdentityService, IdentityUnitOfWork, MemberActor, UpdateProfileInput } from "./ports.js";
|
|
12
12
|
//#region src/identity/service.d.ts
|
|
13
|
+
declare const UNNAMED_MEMBER = "A member";
|
|
13
14
|
declare class IdentityServiceImpl implements IdentityService {
|
|
14
15
|
private readonly repo;
|
|
15
16
|
private readonly logger;
|
|
@@ -22,7 +23,7 @@ declare class IdentityServiceImpl implements IdentityService {
|
|
|
22
23
|
logger?: Logger;
|
|
23
24
|
mailer: Mailer;
|
|
24
25
|
uow: IdentityUnitOfWork;
|
|
25
|
-
images: MemberImageReader
|
|
26
|
+
images: MemberImageReader & Pick<AssetFiles, 'syncPublicImage'>;
|
|
26
27
|
fileUrls: FileUrls;
|
|
27
28
|
});
|
|
28
29
|
createUser(input: CreateUserInput): Promise<User>;
|
|
@@ -42,11 +43,11 @@ declare class IdentityServiceImpl implements IdentityService {
|
|
|
42
43
|
email: string;
|
|
43
44
|
url: string;
|
|
44
45
|
}): Promise<void>;
|
|
45
|
-
profileOf(
|
|
46
|
+
profileOf(actor: MemberActor): Promise<UserProfileView>;
|
|
46
47
|
private view;
|
|
47
48
|
displayName(userId: string | null): Promise<string>;
|
|
48
|
-
updateProfile(
|
|
49
|
+
updateProfile(patch: UpdateProfileInput, actor: MemberActor): Promise<UserProfileView>;
|
|
49
50
|
}
|
|
50
51
|
//#endregion
|
|
51
|
-
export { IdentityServiceImpl };
|
|
52
|
+
export { IdentityServiceImpl, UNNAMED_MEMBER };
|
|
52
53
|
//# sourceMappingURL=service.d.ts.map
|
package/dist/identity/service.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { genId } from "../shared/id.js";
|
|
2
|
-
import { ConflictError, ForbiddenError, NotFoundError } from "../shared/errors.js";
|
|
3
1
|
import { noopLogger } from "../shared/logger.js";
|
|
2
|
+
import { ConflictError, NotFoundError } from "../shared/errors.js";
|
|
3
|
+
import { genId } from "../shared/id.js";
|
|
4
4
|
import { identityEvents } from "./events.js";
|
|
5
5
|
//#region src/identity/service.ts
|
|
6
|
-
const
|
|
6
|
+
const UNNAMED_MEMBER = "A member";
|
|
7
7
|
var IdentityServiceImpl = class {
|
|
8
8
|
repo;
|
|
9
9
|
logger;
|
|
@@ -88,9 +88,10 @@ var IdentityServiceImpl = class {
|
|
|
88
88
|
async sendMagicLink(input) {
|
|
89
89
|
await this.mailer.send(input.email, "magicLink", { url: input.url });
|
|
90
90
|
}
|
|
91
|
-
async profileOf(
|
|
91
|
+
async profileOf(actor) {
|
|
92
|
+
const userId = actor.userId;
|
|
92
93
|
const stored = await this.repo.findProfile(userId);
|
|
93
|
-
if (stored) return this.view(orgId, stored);
|
|
94
|
+
if (stored) return this.view(actor.orgId, actor.id, stored);
|
|
94
95
|
const never = /* @__PURE__ */ new Date(0);
|
|
95
96
|
return {
|
|
96
97
|
userId,
|
|
@@ -105,22 +106,24 @@ var IdentityServiceImpl = class {
|
|
|
105
106
|
updatedAt: never
|
|
106
107
|
};
|
|
107
108
|
}
|
|
108
|
-
view(orgId, profile) {
|
|
109
|
+
view(orgId, orgUserId, profile) {
|
|
109
110
|
const imageAssetId = profile.imageAssetOrgId === orgId ? profile.imageAssetId : null;
|
|
110
111
|
return {
|
|
111
112
|
...profile,
|
|
112
|
-
imageUrl: imageAssetId ? this.fileUrls.
|
|
113
|
+
imageUrl: imageAssetId ? this.fileUrls.publicImage(orgId, orgUserId, imageAssetId) : null
|
|
113
114
|
};
|
|
114
115
|
}
|
|
115
116
|
async displayName(userId) {
|
|
116
|
-
if (userId === null) return
|
|
117
|
+
if (userId === null) return UNNAMED_MEMBER;
|
|
117
118
|
const [profile, user] = await Promise.all([this.repo.findProfile(userId), this.repo.findUserById(userId)]);
|
|
118
119
|
const named = profile?.displayName?.trim();
|
|
119
120
|
if (named) return named;
|
|
120
|
-
return [user?.firstName, user?.lastName].filter(Boolean).join(" ").trim() ||
|
|
121
|
+
return [user?.firstName, user?.lastName].filter(Boolean).join(" ").trim() || "A member";
|
|
121
122
|
}
|
|
122
|
-
async updateProfile(
|
|
123
|
-
|
|
123
|
+
async updateProfile(patch, actor) {
|
|
124
|
+
const orgId = actor.orgId;
|
|
125
|
+
const userId = actor.userId;
|
|
126
|
+
const orgUserId = actor.id;
|
|
124
127
|
if (!await this.repo.findUserById(userId)) throw new NotFoundError("User", userId);
|
|
125
128
|
if (patch.imageAssetId != null) {
|
|
126
129
|
if (!await this.images.ownsImage(orgId, patch.imageAssetId, orgUserId)) {
|
|
@@ -138,10 +141,11 @@ var IdentityServiceImpl = class {
|
|
|
138
141
|
imageAssetOrgId
|
|
139
142
|
});
|
|
140
143
|
this.logger.info("profile updated", { userId });
|
|
141
|
-
|
|
144
|
+
if (patch.imageAssetId !== void 0) await this.images.syncPublicImage(orgId, orgUserId, profile.imageAssetOrgId === orgId ? profile.imageAssetId : null);
|
|
145
|
+
return this.view(orgId, orgUserId, profile);
|
|
142
146
|
}
|
|
143
147
|
};
|
|
144
148
|
//#endregion
|
|
145
|
-
export { IdentityServiceImpl };
|
|
149
|
+
export { IdentityServiceImpl, UNNAMED_MEMBER };
|
|
146
150
|
|
|
147
151
|
//# sourceMappingURL=service.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"service.js","names":[],"sources":["../../src/identity/service.ts"],"sourcesContent":["import type { IdentityRepository, IdentityService, IdentityUnitOfWork } from './ports.js';\nimport type { User, UserProfile, UserProfileView } from './model.js';\nimport type { UpdateProfileInput } from './ports.js';\nimport type { CreateUserInput, UpdateUserInput } from './types.js';\nimport type { Logger } from '../shared/ports.js';\nimport { noopLogger } from '../shared/logger.js';\nimport { genId } from '../shared/id.js';\nimport { ConflictError, ForbiddenError, NotFoundError } from '../shared/errors.js';\nimport type { Mailer } from '../shared/mailer.js';\nimport type { FileUrls, MemberImageReader } from '../assets/index.js';\nimport { identityEvents } from './events.js';\n\nconst UNNAMED = 'A member';\n\nexport class IdentityServiceImpl implements IdentityService {\n private readonly repo: IdentityRepository;\n private readonly logger: Logger;\n private readonly uow: IdentityUnitOfWork;\n private readonly mailer: Pick<Mailer, 'send'>;\n private readonly images: MemberImageReader;\n private readonly fileUrls: FileUrls;\n\n constructor(input: {\n repo: IdentityRepository;\n logger?: Logger;\n mailer: Mailer;\n uow: IdentityUnitOfWork;\n images: MemberImageReader;\n fileUrls: FileUrls;\n }) {\n this.repo = input.repo;\n this.mailer = input.mailer;\n this.logger = input.logger ?? noopLogger;\n this.uow = input.uow;\n this.images = input.images;\n this.fileUrls = input.fileUrls;\n }\n\n async createUser(input: CreateUserInput): Promise<User> {\n const created = await this.uow.run(async ({ identity, outbox }) => {\n const user = await identity.insertUser(input);\n await outbox.append([identityEvents.userCreated.make({ orgId: '-', data: user })]);\n return user;\n });\n this.logger.info('user created', { userId: created.id });\n return created;\n }\n\n async linkOrCreateUser(input: CreateUserInput): Promise<User> {\n const existing = await this.repo.findUserByEmail(input.email);\n if (!existing) {\n const id = input.id ?? genId('user');\n return this.createUser({ ...input, id, externalId: id });\n }\n if (existing.externalId !== null) {\n throw new ConflictError('That email already has an account');\n }\n // Existing user without external link, so invited. update the user with the\n // id.\n const linked = await this.uow.run(async ({ identity, outbox }) => {\n const user = await identity.updateUser(existing.id, {\n externalId: existing.id,\n ...(input.firstName !== undefined && { firstName: input.firstName }),\n ...(input.lastName !== undefined && { lastName: input.lastName }),\n });\n await outbox.append([identityEvents.userUpdated.make({ orgId: '-', data: user! })]);\n return user!;\n });\n this.logger.info('user linked to auth account', { userId: linked.id });\n return linked;\n }\n\n async updateUser(id: string, input: UpdateUserInput): Promise<User> {\n const person = await this.repo.findUserById(id);\n if (!person) {\n throw new NotFoundError('User', id);\n }\n\n const updated = await this.uow.run(async ({ identity, outbox }) => {\n const user = await identity.updateUser(id, input);\n await outbox.append([identityEvents.userUpdated.make({ orgId: '-', data: user! })]);\n return user!;\n });\n this.logger.info('user updated', { userId: updated.id });\n return updated;\n }\n\n async getUserByExternalId(externalId: string): Promise<User | null> {\n return this.repo.findUserByExternalId(externalId);\n }\n\n async getUserByEmail(email: string): Promise<User | null> {\n return this.repo.findUserByEmail(email);\n }\n\n async getUserById(id: string): Promise<User | null> {\n return this.repo.findUserById(id);\n }\n\n async sendPasswordReset(input: { email: string; url: string }): Promise<void> {\n await this.mailer.send(input.email, 'passwordReset', { resetUrl: input.url });\n }\n\n async sendPasswordChanged(input: { email: string }): Promise<void> {\n await this.mailer.send(input.email, 'passwordChanged', {});\n }\n\n async sendMagicLink(input: { email: string; url: string }): Promise<void> {\n await this.mailer.send(input.email, 'magicLink', { url: input.url });\n }\n\n async profileOf(orgId: string, userId: string): Promise<UserProfileView> {\n const stored = await this.repo.findProfile(userId);\n if (stored) {\n return this.view(orgId, stored);\n }\n const never = new Date(0);\n return {\n userId,\n displayName: null,\n headline: null,\n bio: null,\n imageAssetOrgId: null,\n imageAssetId: null,\n imageUrl: null,\n extra: {},\n createdAt: never,\n updatedAt: never,\n };\n }\n\n private view(orgId: string, profile: UserProfile): UserProfileView {\n const imageAssetId = profile.imageAssetOrgId === orgId ? profile.imageAssetId : null;\n return { ...profile, imageUrl: imageAssetId ? this.fileUrls.memberUpload(imageAssetId) : null };\n }\n\n async displayName(userId: string | null): Promise<string> {\n if (userId === null) {\n return UNNAMED;\n }\n const [profile, user] = await Promise.all([\n this.repo.findProfile(userId),\n this.repo.findUserById(userId),\n ]);\n const named = profile?.displayName?.trim();\n if (named) {\n return named;\n }\n const composed = [user?.firstName, user?.lastName].filter(Boolean).join(' ').trim();\n return composed || UNNAMED;\n }\n\n async updateProfile(\n orgId: string,\n userId: string,\n actorUserId: string,\n orgUserId: string,\n patch: UpdateProfileInput,\n ): Promise<UserProfileView> {\n if (actorUserId !== userId) {\n throw new ForbiddenError('You can only update your own profile');\n }\n const user = await this.repo.findUserById(userId);\n if (!user) {\n throw new NotFoundError('User', userId);\n }\n if (patch.imageAssetId != null) {\n const owned = await this.images.ownsImage(orgId, patch.imageAssetId, orgUserId);\n if (!owned) {\n this.logger.debug('profile photo refused', {\n userId,\n orgUserId,\n assetId: patch.imageAssetId,\n });\n throw new NotFoundError('Asset', patch.imageAssetId);\n }\n }\n const imageAssetOrgId =\n patch.imageAssetId === undefined ? undefined : patch.imageAssetId === null ? null : orgId;\n const profile = await this.repo.upsertProfile(userId, { ...patch, imageAssetOrgId });\n this.logger.info('profile updated', { userId });\n return this.view(orgId, profile);\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"service.js","names":[],"sources":["../../src/identity/service.ts"],"sourcesContent":["import type { IdentityRepository, IdentityService, IdentityUnitOfWork } from './ports.js';\nimport type { User, UserProfile, UserProfileView } from './model.js';\nimport type { MemberActor, UpdateProfileInput } from './ports.js';\nimport type { CreateUserInput, UpdateUserInput } from './types.js';\nimport type { Logger } from '../shared/ports.js';\nimport { noopLogger } from '../shared/logger.js';\nimport { genId } from '../shared/id.js';\nimport { ConflictError, NotFoundError } from '../shared/errors.js';\nimport type { Mailer } from '../shared/mailer.js';\nimport type { AssetFiles, FileUrls, MemberImageReader } from '../assets/index.js';\nimport { identityEvents } from './events.js';\n\nexport const UNNAMED_MEMBER = 'A member';\n\nexport class IdentityServiceImpl implements IdentityService {\n private readonly repo: IdentityRepository;\n private readonly logger: Logger;\n private readonly uow: IdentityUnitOfWork;\n private readonly mailer: Pick<Mailer, 'send'>;\n private readonly images: MemberImageReader & Pick<AssetFiles, 'syncPublicImage'>;\n private readonly fileUrls: FileUrls;\n\n constructor(input: {\n repo: IdentityRepository;\n logger?: Logger;\n mailer: Mailer;\n uow: IdentityUnitOfWork;\n images: MemberImageReader & Pick<AssetFiles, 'syncPublicImage'>;\n fileUrls: FileUrls;\n }) {\n this.repo = input.repo;\n this.mailer = input.mailer;\n this.logger = input.logger ?? noopLogger;\n this.uow = input.uow;\n this.images = input.images;\n this.fileUrls = input.fileUrls;\n }\n\n async createUser(input: CreateUserInput): Promise<User> {\n const created = await this.uow.run(async ({ identity, outbox }) => {\n const user = await identity.insertUser(input);\n await outbox.append([identityEvents.userCreated.make({ orgId: '-', data: user })]);\n return user;\n });\n this.logger.info('user created', { userId: created.id });\n return created;\n }\n\n async linkOrCreateUser(input: CreateUserInput): Promise<User> {\n const existing = await this.repo.findUserByEmail(input.email);\n if (!existing) {\n const id = input.id ?? genId('user');\n return this.createUser({ ...input, id, externalId: id });\n }\n if (existing.externalId !== null) {\n throw new ConflictError('That email already has an account');\n }\n // Existing user without external link, so invited. update the user with the\n // id.\n const linked = await this.uow.run(async ({ identity, outbox }) => {\n const user = await identity.updateUser(existing.id, {\n externalId: existing.id,\n ...(input.firstName !== undefined && { firstName: input.firstName }),\n ...(input.lastName !== undefined && { lastName: input.lastName }),\n });\n await outbox.append([identityEvents.userUpdated.make({ orgId: '-', data: user! })]);\n return user!;\n });\n this.logger.info('user linked to auth account', { userId: linked.id });\n return linked;\n }\n\n async updateUser(id: string, input: UpdateUserInput): Promise<User> {\n const person = await this.repo.findUserById(id);\n if (!person) {\n throw new NotFoundError('User', id);\n }\n\n const updated = await this.uow.run(async ({ identity, outbox }) => {\n const user = await identity.updateUser(id, input);\n await outbox.append([identityEvents.userUpdated.make({ orgId: '-', data: user! })]);\n return user!;\n });\n this.logger.info('user updated', { userId: updated.id });\n return updated;\n }\n\n async getUserByExternalId(externalId: string): Promise<User | null> {\n return this.repo.findUserByExternalId(externalId);\n }\n\n async getUserByEmail(email: string): Promise<User | null> {\n return this.repo.findUserByEmail(email);\n }\n\n async getUserById(id: string): Promise<User | null> {\n return this.repo.findUserById(id);\n }\n\n async sendPasswordReset(input: { email: string; url: string }): Promise<void> {\n await this.mailer.send(input.email, 'passwordReset', { resetUrl: input.url });\n }\n\n async sendPasswordChanged(input: { email: string }): Promise<void> {\n await this.mailer.send(input.email, 'passwordChanged', {});\n }\n\n async sendMagicLink(input: { email: string; url: string }): Promise<void> {\n await this.mailer.send(input.email, 'magicLink', { url: input.url });\n }\n\n async profileOf(actor: MemberActor): Promise<UserProfileView> {\n const userId = actor.userId;\n const stored = await this.repo.findProfile(userId);\n if (stored) {\n return this.view(actor.orgId, actor.id, stored);\n }\n const never = new Date(0);\n return {\n userId,\n displayName: null,\n headline: null,\n bio: null,\n imageAssetOrgId: null,\n imageAssetId: null,\n imageUrl: null,\n extra: {},\n createdAt: never,\n updatedAt: never,\n };\n }\n\n private view(orgId: string, orgUserId: string, profile: UserProfile): UserProfileView {\n const imageAssetId = profile.imageAssetOrgId === orgId ? profile.imageAssetId : null;\n return {\n ...profile,\n imageUrl: imageAssetId ? this.fileUrls.publicImage(orgId, orgUserId, imageAssetId) : null,\n };\n }\n\n async displayName(userId: string | null): Promise<string> {\n if (userId === null) {\n return UNNAMED_MEMBER;\n }\n const [profile, user] = await Promise.all([\n this.repo.findProfile(userId),\n this.repo.findUserById(userId),\n ]);\n const named = profile?.displayName?.trim();\n if (named) {\n return named;\n }\n const composed = [user?.firstName, user?.lastName].filter(Boolean).join(' ').trim();\n return composed || UNNAMED_MEMBER;\n }\n\n async updateProfile(patch: UpdateProfileInput, actor: MemberActor): Promise<UserProfileView> {\n const orgId = actor.orgId;\n const userId = actor.userId;\n const orgUserId = actor.id;\n const user = await this.repo.findUserById(userId);\n if (!user) {\n throw new NotFoundError('User', userId);\n }\n if (patch.imageAssetId != null) {\n const owned = await this.images.ownsImage(orgId, patch.imageAssetId, orgUserId);\n if (!owned) {\n this.logger.debug('profile photo refused', {\n userId,\n orgUserId,\n assetId: patch.imageAssetId,\n });\n throw new NotFoundError('Asset', patch.imageAssetId);\n }\n }\n const imageAssetOrgId =\n patch.imageAssetId === undefined ? undefined : patch.imageAssetId === null ? null : orgId;\n const profile = await this.repo.upsertProfile(userId, { ...patch, imageAssetOrgId });\n this.logger.info('profile updated', { userId });\n if (patch.imageAssetId !== undefined) {\n await this.images.syncPublicImage(\n orgId,\n orgUserId,\n profile.imageAssetOrgId === orgId ? profile.imageAssetId : null,\n );\n }\n return this.view(orgId, orgUserId, profile);\n }\n}\n"],"mappings":"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"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { ConfigureInput, ConnectInput, Connection, Validation } from "../types/schemas/integrations.js";
|
|
2
2
|
import { Action, ActionContext, Integration } from "../types/integrations.js";
|
|
3
3
|
import "../types/index.js";
|
|
4
|
+
import { Actor } from "../shared/actor.js";
|
|
4
5
|
import { CredentialStore, OutboxAppender, UnitOfWork } from "../shared/ports.js";
|
|
5
6
|
import "./model.js";
|
|
6
7
|
//#region src/integrations/ports.d.ts
|
|
@@ -23,22 +24,22 @@ interface AvailableIntegration {
|
|
|
23
24
|
}
|
|
24
25
|
interface IntegrationsService {
|
|
25
26
|
/** The integrations declared in this deployment, with their config schemas. */
|
|
26
|
-
available(): AvailableIntegration[];
|
|
27
|
+
available(actor: Actor): AvailableIntegration[];
|
|
27
28
|
/** Establish a connection for an org; stores its credential and configuration. */
|
|
28
|
-
connect(
|
|
29
|
+
connect(input: ConnectInput, actor: Actor): Promise<Connection>;
|
|
29
30
|
/** Replace a connection's stored secrets (re-auth / token refresh). */
|
|
30
|
-
reconnect(
|
|
31
|
+
reconnect(id: string, secrets: Record<string, unknown>, actor: Actor): Promise<Connection>;
|
|
31
32
|
/** Set or change a connection's configuration and/or active flag. */
|
|
32
|
-
configure(
|
|
33
|
+
configure(id: string, input: ConfigureInput, actor: Actor): Promise<Connection>;
|
|
33
34
|
/** Remove a connection and destroy its credential. */
|
|
34
|
-
disconnect(
|
|
35
|
-
list(
|
|
36
|
-
get(
|
|
35
|
+
disconnect(id: string, actor: Actor): Promise<void>;
|
|
36
|
+
list(actor: Actor): Promise<Connection[]>;
|
|
37
|
+
get(id: string, actor: Actor): Promise<Connection>;
|
|
37
38
|
/** The connection consumers (billing, automations) resolve an integration by id. */
|
|
38
39
|
getByIntegration(orgId: string, integrationId: string): Promise<Connection | null>;
|
|
39
40
|
/** Invoke one of a connection's actions with its credential revealed at point
|
|
40
41
|
* of use (e.g. a listing action that feeds a client-side picker). */
|
|
41
|
-
invoke(
|
|
42
|
+
invoke(connectionId: string, actionId: string, input: Record<string, unknown>, actor: Actor): Promise<unknown>;
|
|
42
43
|
}
|
|
43
44
|
interface ConnectionsRepository {
|
|
44
45
|
insert(orgId: string, connection: Connection): Promise<Connection>;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Logger } from "../types/ports.js";
|
|
2
2
|
import { ConfigureInput, ConnectInput, Connection } from "../types/schemas/integrations.js";
|
|
3
|
+
import { Actor } from "../shared/actor.js";
|
|
3
4
|
import { CredentialStore } from "../shared/ports.js";
|
|
4
5
|
import "./model.js";
|
|
5
6
|
import { ConnectionsRepository, IntegrationsRegistry, IntegrationsService, IntegrationsUnitOfWork } from "./ports.js";
|
|
@@ -21,7 +22,7 @@ declare class IntegrationsServiceImpl implements IntegrationsService {
|
|
|
21
22
|
private readonly credentials;
|
|
22
23
|
private readonly logger;
|
|
23
24
|
constructor(params: IntegrationsServiceParams);
|
|
24
|
-
available(): {
|
|
25
|
+
available(actor: Actor): {
|
|
25
26
|
id: string;
|
|
26
27
|
configSchema: Record<string, unknown>;
|
|
27
28
|
secretsSchema: Record<string, unknown>;
|
|
@@ -33,14 +34,14 @@ declare class IntegrationsServiceImpl implements IntegrationsService {
|
|
|
33
34
|
}[];
|
|
34
35
|
}[];
|
|
35
36
|
private validate;
|
|
36
|
-
connect(
|
|
37
|
-
reconnect(
|
|
38
|
-
configure(
|
|
39
|
-
disconnect(
|
|
40
|
-
list(
|
|
41
|
-
get(
|
|
37
|
+
connect(input: ConnectInput, actor: Actor): Promise<Connection>;
|
|
38
|
+
reconnect(id: string, secrets: Record<string, unknown>, actor: Actor): Promise<Connection>;
|
|
39
|
+
configure(id: string, input: ConfigureInput, actor: Actor): Promise<Connection>;
|
|
40
|
+
disconnect(id: string, actor: Actor): Promise<void>;
|
|
41
|
+
list(actor: Actor): Promise<Connection[]>;
|
|
42
|
+
get(id: string, actor: Actor): Promise<Connection>;
|
|
42
43
|
getByIntegration(orgId: string, integrationId: string): Promise<Connection | null>;
|
|
43
|
-
invoke(
|
|
44
|
+
invoke(connectionId: string, actionId: string, input: Record<string, unknown>, actor: Actor): Promise<unknown>;
|
|
44
45
|
}
|
|
45
46
|
//#endregion
|
|
46
47
|
export { IntegrationsServiceImpl, IntegrationsServiceParams };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { genId } from "../shared/id.js";
|
|
2
|
-
import { ConflictError, NotFoundError } from "../shared/errors.js";
|
|
3
1
|
import { noopLogger } from "../shared/logger.js";
|
|
2
|
+
import { ConflictError, NotFoundError } from "../shared/errors.js";
|
|
3
|
+
import { genId } from "../shared/id.js";
|
|
4
4
|
import { ActionInvocationError, AlreadyConnectedError, InvalidConfigError, UnknownIntegrationError } from "./model.js";
|
|
5
5
|
import { integrationEvents } from "./events.js";
|
|
6
6
|
//#region src/integrations/service.ts
|
|
@@ -17,7 +17,8 @@ var IntegrationsServiceImpl = class {
|
|
|
17
17
|
this.credentials = params.credentials;
|
|
18
18
|
this.logger = params.logger ?? noopLogger;
|
|
19
19
|
}
|
|
20
|
-
available() {
|
|
20
|
+
available(actor) {
|
|
21
|
+
actor.permissions.require("integrations:read");
|
|
21
22
|
return this.registry.list().map((integration) => ({
|
|
22
23
|
id: integration.id,
|
|
23
24
|
configSchema: integration.configSchema(),
|
|
@@ -45,7 +46,9 @@ var IntegrationsServiceImpl = class {
|
|
|
45
46
|
throw new InvalidConfigError(integrationId, result.errors);
|
|
46
47
|
}
|
|
47
48
|
}
|
|
48
|
-
async connect(
|
|
49
|
+
async connect(input, actor) {
|
|
50
|
+
actor.permissions.require("integrations:write");
|
|
51
|
+
const orgId = actor.orgId;
|
|
49
52
|
const config = input.config ?? {};
|
|
50
53
|
this.validate(input.integrationId, config);
|
|
51
54
|
if (await this.repo.findByIntegration(orgId, input.integrationId)) {
|
|
@@ -81,9 +84,11 @@ var IntegrationsServiceImpl = class {
|
|
|
81
84
|
});
|
|
82
85
|
return connection;
|
|
83
86
|
}
|
|
84
|
-
async reconnect(
|
|
87
|
+
async reconnect(id, secrets, actor) {
|
|
88
|
+
actor.permissions.require("integrations:write");
|
|
89
|
+
const orgId = actor.orgId;
|
|
85
90
|
const connection = await this.repo.findById(orgId, id);
|
|
86
|
-
if (!connection)
|
|
91
|
+
if (!connection) throw new NotFoundError("Connection", id);
|
|
87
92
|
const updated = await this.uow.run(async ({ connections, credentials, outbox }) => {
|
|
88
93
|
await credentials.update(orgId, connection.credentialRef, secrets);
|
|
89
94
|
const result = await connections.update(orgId, id, { updatedAt: /* @__PURE__ */ new Date() });
|
|
@@ -98,11 +103,14 @@ var IntegrationsServiceImpl = class {
|
|
|
98
103
|
connectionId: id,
|
|
99
104
|
integrationId: connection.integrationId
|
|
100
105
|
});
|
|
106
|
+
if (!updated) throw new NotFoundError("Connection", id);
|
|
101
107
|
return updated;
|
|
102
108
|
}
|
|
103
|
-
async configure(
|
|
109
|
+
async configure(id, input, actor) {
|
|
110
|
+
actor.permissions.require("integrations:write");
|
|
111
|
+
const orgId = actor.orgId;
|
|
104
112
|
const connection = await this.repo.findById(orgId, id);
|
|
105
|
-
if (!connection)
|
|
113
|
+
if (!connection) throw new NotFoundError("Connection", id);
|
|
106
114
|
if (input.config !== void 0) this.validate(connection.integrationId, input.config);
|
|
107
115
|
const updated = await this.uow.run(async ({ connections, outbox }) => {
|
|
108
116
|
const result = await connections.update(orgId, id, {
|
|
@@ -121,11 +129,14 @@ var IntegrationsServiceImpl = class {
|
|
|
121
129
|
connectionId: id,
|
|
122
130
|
integrationId: connection.integrationId
|
|
123
131
|
});
|
|
132
|
+
if (!updated) throw new NotFoundError("Connection", id);
|
|
124
133
|
return updated;
|
|
125
134
|
}
|
|
126
|
-
async disconnect(
|
|
135
|
+
async disconnect(id, actor) {
|
|
136
|
+
actor.permissions.require("integrations:delete");
|
|
137
|
+
const orgId = actor.orgId;
|
|
127
138
|
const connection = await this.repo.findById(orgId, id);
|
|
128
|
-
if (!connection)
|
|
139
|
+
if (!connection) throw new NotFoundError("Connection", id);
|
|
129
140
|
const deleted = await this.uow.run(async ({ connections, credentials, outbox }) => {
|
|
130
141
|
const ok = await connections.delete(orgId, id);
|
|
131
142
|
await credentials.destroy(orgId, connection.credentialRef);
|
|
@@ -140,18 +151,24 @@ var IntegrationsServiceImpl = class {
|
|
|
140
151
|
connectionId: id,
|
|
141
152
|
integrationId: connection.integrationId
|
|
142
153
|
});
|
|
143
|
-
|
|
154
|
+
if (!deleted) throw new NotFoundError("Connection", id);
|
|
144
155
|
}
|
|
145
|
-
list(
|
|
146
|
-
|
|
156
|
+
async list(actor) {
|
|
157
|
+
actor.permissions.require("integrations:read");
|
|
158
|
+
return this.repo.list(actor.orgId);
|
|
147
159
|
}
|
|
148
|
-
get(
|
|
149
|
-
|
|
160
|
+
async get(id, actor) {
|
|
161
|
+
actor.permissions.require("integrations:read");
|
|
162
|
+
const connection = await this.repo.findById(actor.orgId, id);
|
|
163
|
+
if (!connection) throw new NotFoundError("Connection", id);
|
|
164
|
+
return connection;
|
|
150
165
|
}
|
|
151
166
|
getByIntegration(orgId, integrationId) {
|
|
152
167
|
return this.repo.findByIntegration(orgId, integrationId);
|
|
153
168
|
}
|
|
154
|
-
async invoke(
|
|
169
|
+
async invoke(connectionId, actionId, input, actor) {
|
|
170
|
+
actor.permissions.require("integrations:write");
|
|
171
|
+
const orgId = actor.orgId;
|
|
155
172
|
const connection = await this.repo.findById(orgId, connectionId);
|
|
156
173
|
if (!connection) throw new NotFoundError("Connection", connectionId);
|
|
157
174
|
if (!connection.active) throw new ConflictError(`connection "${connectionId}" is inactive`);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"service.js","names":[],"sources":["../../src/integrations/service.ts"],"sourcesContent":["// integrations context — service implementation (inbound port). Owns the\n// connection lifecycle; credentials live in the shared secure credential store\n// (this service holds only the ref). The integrations the system supports are\n// declared at startup via the IntegrationsRegistry: connect/configure reject\n// unknown integration ids and validate config with the integration's own\n// validator. The domain never calls the external service — consumers take the\n// connection, reveal the credential at point of use, and build their own adapter.\n//\n// Mutations run inside the context's UnitOfWork: credential writes, connection\n// writes, and the outbox append commit in ONE transaction (transactional\n// outbox; this also closed the historical orphan-credential window). The\n// outbox relay — not this service — publishes to the EventBus.\nimport { genId } from '../shared/id.js';\nimport {\n ActionInvocationError,\n AlreadyConnectedError,\n InvalidConfigError,\n UnknownIntegrationError,\n type ConfigureInput,\n type ConnectInput,\n type Connection,\n} from './model.js';\nimport type {\n ConnectionsRepository,\n IntegrationsRegistry,\n IntegrationsService,\n IntegrationsUnitOfWork,\n} from './ports.js';\nimport type { CredentialStore, Logger } from '../shared/ports.js';\nimport { ConflictError, NotFoundError } from '../shared/errors.js';\nimport { noopLogger } from '../shared/logger.js';\nimport { integrationEvents } from './events.js';\n\nexport type IntegrationsServiceParams = {\n registry: IntegrationsRegistry;\n /** Read-only access (find/list) — runs outside any transaction. */\n repo: ConnectionsRepository;\n /** Atomic write scope: tx-bound connections repo + credential store + outbox. */\n uow: IntegrationsUnitOfWork;\n /** Point-of-use credential reveal for action invocation — reads only. */\n credentials: CredentialStore;\n logger?: Logger;\n};\n\nexport class IntegrationsServiceImpl implements IntegrationsService {\n private readonly registry: IntegrationsRegistry;\n private readonly repo: ConnectionsRepository;\n private readonly uow: IntegrationsUnitOfWork;\n private readonly credentials: CredentialStore;\n private readonly logger: Logger;\n\n constructor(params: IntegrationsServiceParams) {\n this.registry = params.registry;\n this.repo = params.repo;\n this.uow = params.uow;\n this.credentials = params.credentials;\n this.logger = params.logger ?? noopLogger;\n }\n\n available() {\n return this.registry.list().map((integration) => ({\n id: integration.id,\n configSchema: integration.configSchema(),\n secretsSchema: integration.secretsSchema(),\n actions: integration.actions.map((action) => ({\n id: action.id,\n description: action.description,\n inputSchema: action.inputSchema(),\n outputSchema: action.outputSchema(),\n })),\n }));\n }\n\n private validate(integrationId: string, config: Record<string, unknown>): void {\n const integration = this.registry.get(integrationId);\n if (!integration) {\n this.logger.warn('unknown integration rejected', { integrationId });\n throw new UnknownIntegrationError(integrationId);\n }\n const result = integration.validateConfig(config);\n if (!result.ok) {\n this.logger.warn('invalid integration config rejected', {\n integrationId,\n errors: result.errors,\n });\n throw new InvalidConfigError(integrationId, result.errors);\n }\n }\n\n async connect(orgId: string, input: ConnectInput): Promise<Connection> {\n const config = input.config ?? {};\n this.validate(input.integrationId, config);\n const existing = await this.repo.findByIntegration(orgId, input.integrationId);\n if (existing) {\n this.logger.warn('duplicate connection rejected', {\n orgId,\n integrationId: input.integrationId,\n });\n throw new AlreadyConnectedError(input.integrationId);\n }\n const connection = await this.uow.run(async ({ connections, credentials, outbox }) => {\n const credentialRef = await credentials.store(orgId, input.secrets);\n const at = new Date();\n const created = await connections.insert(orgId, {\n orgId,\n id: genId('connection'),\n integrationId: input.integrationId,\n config,\n active: true,\n credentialRef,\n createdAt: at,\n updatedAt: at,\n });\n await outbox.append([\n integrationEvents.connectionCreated.make({\n orgId,\n data: created,\n }),\n ]);\n return created;\n });\n this.logger.info('integration connected', {\n orgId,\n integrationId: connection.integrationId,\n connectionId: connection.id,\n });\n return connection;\n }\n\n async reconnect(\n orgId: string,\n id: string,\n secrets: Record<string, unknown>,\n ): Promise<Connection | null> {\n const connection = await this.repo.findById(orgId, id);\n if (!connection) {\n return null;\n }\n const updated = await this.uow.run(async ({ connections, credentials, outbox }) => {\n await credentials.update(orgId, connection.credentialRef, secrets);\n const result = await connections.update(orgId, id, { updatedAt: new Date() });\n if (result) {\n await outbox.append([integrationEvents.connectionUpdated.make({ orgId, data: result })]);\n }\n return result;\n });\n this.logger.info('integration credentials rotated', {\n orgId,\n connectionId: id,\n integrationId: connection.integrationId,\n });\n return updated;\n }\n\n async configure(orgId: string, id: string, input: ConfigureInput): Promise<Connection | null> {\n const connection = await this.repo.findById(orgId, id);\n if (!connection) {\n return null;\n }\n if (input.config !== undefined) {\n this.validate(connection.integrationId, input.config);\n }\n const updated = await this.uow.run(async ({ connections, outbox }) => {\n const result = await connections.update(orgId, id, {\n ...(input.config !== undefined ? { config: input.config } : {}),\n ...(input.active !== undefined ? { active: input.active } : {}),\n updatedAt: new Date(),\n });\n if (result) {\n await outbox.append([integrationEvents.connectionUpdated.make({ orgId, data: result })]);\n }\n return result;\n });\n this.logger.info('integration configured', {\n orgId,\n connectionId: id,\n integrationId: connection.integrationId,\n });\n return updated;\n }\n\n async disconnect(orgId: string, id: string): Promise<boolean> {\n const connection = await this.repo.findById(orgId, id);\n if (!connection) {\n return false;\n }\n const deleted = await this.uow.run(async ({ connections, credentials, outbox }) => {\n // Connection first: it holds the FK onto the credential row.\n const ok = await connections.delete(orgId, id);\n await credentials.destroy(orgId, connection.credentialRef);\n await outbox.append([integrationEvents.connectionRemoved.make({ orgId, data: connection })]);\n return ok;\n });\n this.logger.info('integration disconnected', {\n orgId,\n connectionId: id,\n integrationId: connection.integrationId,\n });\n return deleted;\n }\n\n list(orgId: string): Promise<Connection[]> {\n return this.repo.list(orgId);\n }\n\n get(orgId: string, id: string): Promise<Connection | null> {\n return this.repo.findById(orgId, id);\n }\n\n getByIntegration(orgId: string, integrationId: string): Promise<Connection | null> {\n return this.repo.findByIntegration(orgId, integrationId);\n }\n\n async invoke(\n orgId: string,\n connectionId: string,\n actionId: string,\n input: Record<string, unknown>,\n ): Promise<unknown> {\n const connection = await this.repo.findById(orgId, connectionId);\n if (!connection) {\n throw new NotFoundError('Connection', connectionId);\n }\n if (!connection.active) {\n throw new ConflictError(`connection \"${connectionId}\" is inactive`);\n }\n const integration = this.registry.get(connection.integrationId);\n if (!integration) {\n throw new UnknownIntegrationError(connection.integrationId);\n }\n const action = integration.actions.find((a) => a.id === actionId);\n if (!action) {\n throw new NotFoundError('Action', actionId);\n }\n const secrets = await this.credentials.reveal(orgId, connection.credentialRef);\n if (!secrets) {\n throw new NotFoundError('Credential', connectionId);\n }\n try {\n return await action.invoke({ secrets, config: connection.config }, input);\n } catch (err) {\n this.logger.warn('integration action failed', {\n orgId,\n connectionId,\n integrationId: connection.integrationId,\n actionId,\n err,\n });\n throw new ActionInvocationError(connection.integrationId, actionId, err);\n }\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"service.js","names":[],"sources":["../../src/integrations/service.ts"],"sourcesContent":["// integrations context — service implementation (inbound port). Owns the\n// connection lifecycle; credentials live in the shared secure credential store\n// (this service holds only the ref). The integrations the system supports are\n// declared at startup via the IntegrationsRegistry: connect/configure reject\n// unknown integration ids and validate config with the integration's own\n// validator. The domain never calls the external service — consumers take the\n// connection, reveal the credential at point of use, and build their own adapter.\n//\n// Mutations run inside the context's UnitOfWork: credential writes, connection\n// writes, and the outbox append commit in ONE transaction (transactional\n// outbox; this also closed the historical orphan-credential window). The\n// outbox relay — not this service — publishes to the EventBus.\nimport { genId } from '../shared/id.js';\nimport {\n ActionInvocationError,\n AlreadyConnectedError,\n InvalidConfigError,\n UnknownIntegrationError,\n type ConfigureInput,\n type ConnectInput,\n type Connection,\n} from './model.js';\nimport type {\n ConnectionsRepository,\n IntegrationsRegistry,\n IntegrationsService,\n IntegrationsUnitOfWork,\n} from './ports.js';\nimport type { CredentialStore, Logger } from '../shared/ports.js';\nimport type { Actor } from '../shared/actor.js';\nimport { ConflictError, NotFoundError } from '../shared/errors.js';\nimport { noopLogger } from '../shared/logger.js';\nimport { integrationEvents } from './events.js';\n\nexport type IntegrationsServiceParams = {\n registry: IntegrationsRegistry;\n /** Read-only access (find/list) — runs outside any transaction. */\n repo: ConnectionsRepository;\n /** Atomic write scope: tx-bound connections repo + credential store + outbox. */\n uow: IntegrationsUnitOfWork;\n /** Point-of-use credential reveal for action invocation — reads only. */\n credentials: CredentialStore;\n logger?: Logger;\n};\n\nexport class IntegrationsServiceImpl implements IntegrationsService {\n private readonly registry: IntegrationsRegistry;\n private readonly repo: ConnectionsRepository;\n private readonly uow: IntegrationsUnitOfWork;\n private readonly credentials: CredentialStore;\n private readonly logger: Logger;\n\n constructor(params: IntegrationsServiceParams) {\n this.registry = params.registry;\n this.repo = params.repo;\n this.uow = params.uow;\n this.credentials = params.credentials;\n this.logger = params.logger ?? noopLogger;\n }\n\n available(actor: Actor) {\n actor.permissions.require('integrations:read');\n return this.registry.list().map((integration) => ({\n id: integration.id,\n configSchema: integration.configSchema(),\n secretsSchema: integration.secretsSchema(),\n actions: integration.actions.map((action) => ({\n id: action.id,\n description: action.description,\n inputSchema: action.inputSchema(),\n outputSchema: action.outputSchema(),\n })),\n }));\n }\n\n private validate(integrationId: string, config: Record<string, unknown>): void {\n const integration = this.registry.get(integrationId);\n if (!integration) {\n this.logger.warn('unknown integration rejected', { integrationId });\n throw new UnknownIntegrationError(integrationId);\n }\n const result = integration.validateConfig(config);\n if (!result.ok) {\n this.logger.warn('invalid integration config rejected', {\n integrationId,\n errors: result.errors,\n });\n throw new InvalidConfigError(integrationId, result.errors);\n }\n }\n\n async connect(input: ConnectInput, actor: Actor): Promise<Connection> {\n actor.permissions.require('integrations:write');\n const orgId = actor.orgId;\n const config = input.config ?? {};\n this.validate(input.integrationId, config);\n const existing = await this.repo.findByIntegration(orgId, input.integrationId);\n if (existing) {\n this.logger.warn('duplicate connection rejected', {\n orgId,\n integrationId: input.integrationId,\n });\n throw new AlreadyConnectedError(input.integrationId);\n }\n const connection = await this.uow.run(async ({ connections, credentials, outbox }) => {\n const credentialRef = await credentials.store(orgId, input.secrets);\n const at = new Date();\n const created = await connections.insert(orgId, {\n orgId,\n id: genId('connection'),\n integrationId: input.integrationId,\n config,\n active: true,\n credentialRef,\n createdAt: at,\n updatedAt: at,\n });\n await outbox.append([\n integrationEvents.connectionCreated.make({\n orgId,\n data: created,\n }),\n ]);\n return created;\n });\n this.logger.info('integration connected', {\n orgId,\n integrationId: connection.integrationId,\n connectionId: connection.id,\n });\n return connection;\n }\n\n async reconnect(id: string, secrets: Record<string, unknown>, actor: Actor): Promise<Connection> {\n actor.permissions.require('integrations:write');\n const orgId = actor.orgId;\n const connection = await this.repo.findById(orgId, id);\n if (!connection) {\n throw new NotFoundError('Connection', id);\n }\n const updated = await this.uow.run(async ({ connections, credentials, outbox }) => {\n await credentials.update(orgId, connection.credentialRef, secrets);\n const result = await connections.update(orgId, id, { updatedAt: new Date() });\n if (result) {\n await outbox.append([integrationEvents.connectionUpdated.make({ orgId, data: result })]);\n }\n return result;\n });\n this.logger.info('integration credentials rotated', {\n orgId,\n connectionId: id,\n integrationId: connection.integrationId,\n });\n if (!updated) {\n throw new NotFoundError('Connection', id);\n }\n return updated;\n }\n\n async configure(id: string, input: ConfigureInput, actor: Actor): Promise<Connection> {\n actor.permissions.require('integrations:write');\n const orgId = actor.orgId;\n const connection = await this.repo.findById(orgId, id);\n if (!connection) {\n throw new NotFoundError('Connection', id);\n }\n if (input.config !== undefined) {\n this.validate(connection.integrationId, input.config);\n }\n const updated = await this.uow.run(async ({ connections, outbox }) => {\n const result = await connections.update(orgId, id, {\n ...(input.config !== undefined ? { config: input.config } : {}),\n ...(input.active !== undefined ? { active: input.active } : {}),\n updatedAt: new Date(),\n });\n if (result) {\n await outbox.append([integrationEvents.connectionUpdated.make({ orgId, data: result })]);\n }\n return result;\n });\n this.logger.info('integration configured', {\n orgId,\n connectionId: id,\n integrationId: connection.integrationId,\n });\n if (!updated) {\n throw new NotFoundError('Connection', id);\n }\n return updated;\n }\n\n async disconnect(id: string, actor: Actor): Promise<void> {\n actor.permissions.require('integrations:delete');\n const orgId = actor.orgId;\n const connection = await this.repo.findById(orgId, id);\n if (!connection) {\n throw new NotFoundError('Connection', id);\n }\n const deleted = await this.uow.run(async ({ connections, credentials, outbox }) => {\n // Connection first: it holds the FK onto the credential row.\n const ok = await connections.delete(orgId, id);\n await credentials.destroy(orgId, connection.credentialRef);\n await outbox.append([integrationEvents.connectionRemoved.make({ orgId, data: connection })]);\n return ok;\n });\n this.logger.info('integration disconnected', {\n orgId,\n connectionId: id,\n integrationId: connection.integrationId,\n });\n if (!deleted) {\n throw new NotFoundError('Connection', id);\n }\n }\n\n async list(actor: Actor): Promise<Connection[]> {\n actor.permissions.require('integrations:read');\n return this.repo.list(actor.orgId);\n }\n\n async get(id: string, actor: Actor): Promise<Connection> {\n actor.permissions.require('integrations:read');\n const connection = await this.repo.findById(actor.orgId, id);\n if (!connection) {\n throw new NotFoundError('Connection', id);\n }\n return connection;\n }\n\n getByIntegration(orgId: string, integrationId: string): Promise<Connection | null> {\n return this.repo.findByIntegration(orgId, integrationId);\n }\n\n async invoke(\n connectionId: string,\n actionId: string,\n input: Record<string, unknown>,\n actor: Actor,\n ): Promise<unknown> {\n actor.permissions.require('integrations:write');\n const orgId = actor.orgId;\n const connection = await this.repo.findById(orgId, connectionId);\n if (!connection) {\n throw new NotFoundError('Connection', connectionId);\n }\n if (!connection.active) {\n throw new ConflictError(`connection \"${connectionId}\" is inactive`);\n }\n const integration = this.registry.get(connection.integrationId);\n if (!integration) {\n throw new UnknownIntegrationError(connection.integrationId);\n }\n const action = integration.actions.find((a) => a.id === actionId);\n if (!action) {\n throw new NotFoundError('Action', actionId);\n }\n const secrets = await this.credentials.reveal(orgId, connection.credentialRef);\n if (!secrets) {\n throw new NotFoundError('Credential', connectionId);\n }\n try {\n return await action.invoke({ secrets, config: connection.config }, input);\n } catch (err) {\n this.logger.warn('integration action failed', {\n orgId,\n connectionId,\n integrationId: connection.integrationId,\n actionId,\n err,\n });\n throw new ActionInvocationError(connection.integrationId, actionId, err);\n }\n }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { Level, Permission } from "../types/schemas/organizations.js";
|
|
2
|
+
import "../types/index.js";
|
|
3
|
+
//#region src/organizations/default-roles.d.ts
|
|
4
|
+
interface RoleDefinition {
|
|
5
|
+
key: string;
|
|
6
|
+
name: string;
|
|
7
|
+
description: string;
|
|
8
|
+
levels: Level[];
|
|
9
|
+
team: boolean;
|
|
10
|
+
permissions: Permission[];
|
|
11
|
+
}
|
|
12
|
+
declare const OWNER_ROLE_KEY = "owner";
|
|
13
|
+
declare const MEMBER_ROLE_KEY = "member";
|
|
14
|
+
declare const DEFAULT_ROLES: readonly RoleDefinition[];
|
|
15
|
+
declare class RoleSetError extends Error {
|
|
16
|
+
constructor(message: string);
|
|
17
|
+
}
|
|
18
|
+
declare function validateRoleSet(roles: readonly RoleDefinition[]): void;
|
|
19
|
+
declare function defaultHolders(permission: Permission): string[];
|
|
20
|
+
//#endregion
|
|
21
|
+
export { DEFAULT_ROLES, MEMBER_ROLE_KEY, OWNER_ROLE_KEY, RoleDefinition, RoleSetError, defaultHolders, validateRoleSet };
|
|
22
|
+
//# sourceMappingURL=default-roles.d.ts.map
|