@memberjunction/integration-connectors 5.51.0 → 6.1.0-edge.1
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/LICENSE +7 -0
- package/README.md +109 -277
- package/dist/index.d.ts +0 -36
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -38
- package/dist/index.js.map +1 -1
- package/package.json +14 -19
- package/dist/AptifyConnector.d.ts +0 -95
- package/dist/AptifyConnector.d.ts.map +0 -1
- package/dist/AptifyConnector.js +0 -760
- package/dist/AptifyConnector.js.map +0 -1
- package/dist/BlackbaudConnector.d.ts +0 -64
- package/dist/BlackbaudConnector.d.ts.map +0 -1
- package/dist/BlackbaudConnector.js +0 -916
- package/dist/BlackbaudConnector.js.map +0 -1
- package/dist/ConstantContactConnector.d.ts +0 -137
- package/dist/ConstantContactConnector.d.ts.map +0 -1
- package/dist/ConstantContactConnector.js +0 -663
- package/dist/ConstantContactConnector.js.map +0 -1
- package/dist/CventConnector.d.ts +0 -146
- package/dist/CventConnector.d.ts.map +0 -1
- package/dist/CventConnector.js +0 -540
- package/dist/CventConnector.js.map +0 -1
- package/dist/DynamicsDataverseConnector.d.ts +0 -296
- package/dist/DynamicsDataverseConnector.d.ts.map +0 -1
- package/dist/DynamicsDataverseConnector.js +0 -790
- package/dist/DynamicsDataverseConnector.js.map +0 -1
- package/dist/FileFeedConnector.d.ts +0 -64
- package/dist/FileFeedConnector.d.ts.map +0 -1
- package/dist/FileFeedConnector.js +0 -190
- package/dist/FileFeedConnector.js.map +0 -1
- package/dist/FontevaConnector.d.ts +0 -153
- package/dist/FontevaConnector.d.ts.map +0 -1
- package/dist/FontevaConnector.js +0 -421
- package/dist/FontevaConnector.js.map +0 -1
- package/dist/GrowthZoneConnector.d.ts +0 -183
- package/dist/GrowthZoneConnector.d.ts.map +0 -1
- package/dist/GrowthZoneConnector.js +0 -702
- package/dist/GrowthZoneConnector.js.map +0 -1
- package/dist/HivebriteConnector.d.ts +0 -151
- package/dist/HivebriteConnector.d.ts.map +0 -1
- package/dist/HivebriteConnector.js +0 -533
- package/dist/HivebriteConnector.js.map +0 -1
- package/dist/HubSpotConnector.d.ts +0 -523
- package/dist/HubSpotConnector.d.ts.map +0 -1
- package/dist/HubSpotConnector.js +0 -3110
- package/dist/HubSpotConnector.js.map +0 -1
- package/dist/IMISConnector.d.ts +0 -171
- package/dist/IMISConnector.d.ts.map +0 -1
- package/dist/IMISConnector.js +0 -695
- package/dist/IMISConnector.js.map +0 -1
- package/dist/MJToMJConnector.d.ts +0 -279
- package/dist/MJToMJConnector.d.ts.map +0 -1
- package/dist/MJToMJConnector.js +0 -893
- package/dist/MJToMJConnector.js.map +0 -1
- package/dist/MagnetMailConnector.d.ts +0 -163
- package/dist/MagnetMailConnector.d.ts.map +0 -1
- package/dist/MagnetMailConnector.js +0 -823
- package/dist/MagnetMailConnector.js.map +0 -1
- package/dist/MailchimpConnector.d.ts +0 -197
- package/dist/MailchimpConnector.d.ts.map +0 -1
- package/dist/MailchimpConnector.js +0 -791
- package/dist/MailchimpConnector.js.map +0 -1
- package/dist/MemberSuiteConnector.d.ts +0 -185
- package/dist/MemberSuiteConnector.d.ts.map +0 -1
- package/dist/MemberSuiteConnector.js +0 -762
- package/dist/MemberSuiteConnector.js.map +0 -1
- package/dist/NeonCRMConnector.d.ts +0 -273
- package/dist/NeonCRMConnector.d.ts.map +0 -1
- package/dist/NeonCRMConnector.js +0 -1039
- package/dist/NeonCRMConnector.js.map +0 -1
- package/dist/NetForumConnector.d.ts +0 -170
- package/dist/NetForumConnector.d.ts.map +0 -1
- package/dist/NetForumConnector.js +0 -778
- package/dist/NetForumConnector.js.map +0 -1
- package/dist/NetSuiteConnector.d.ts +0 -136
- package/dist/NetSuiteConnector.d.ts.map +0 -1
- package/dist/NetSuiteConnector.js +0 -703
- package/dist/NetSuiteConnector.js.map +0 -1
- package/dist/NimbleAMSConnector.d.ts +0 -142
- package/dist/NimbleAMSConnector.d.ts.map +0 -1
- package/dist/NimbleAMSConnector.js +0 -703
- package/dist/NimbleAMSConnector.js.map +0 -1
- package/dist/NoviConnector.d.ts +0 -178
- package/dist/NoviConnector.d.ts.map +0 -1
- package/dist/NoviConnector.js +0 -566
- package/dist/NoviConnector.js.map +0 -1
- package/dist/ORCIDConnector.d.ts +0 -207
- package/dist/ORCIDConnector.d.ts.map +0 -1
- package/dist/ORCIDConnector.js +0 -708
- package/dist/ORCIDConnector.js.map +0 -1
- package/dist/OpenWaterConnector.d.ts +0 -173
- package/dist/OpenWaterConnector.d.ts.map +0 -1
- package/dist/OpenWaterConnector.js +0 -893
- package/dist/OpenWaterConnector.js.map +0 -1
- package/dist/PathLMSConnector.d.ts +0 -395
- package/dist/PathLMSConnector.d.ts.map +0 -1
- package/dist/PathLMSConnector.js +0 -1281
- package/dist/PathLMSConnector.js.map +0 -1
- package/dist/PheedLoopConnector.d.ts +0 -119
- package/dist/PheedLoopConnector.d.ts.map +0 -1
- package/dist/PheedLoopConnector.js +0 -429
- package/dist/PheedLoopConnector.js.map +0 -1
- package/dist/PropFuelConnector.d.ts +0 -153
- package/dist/PropFuelConnector.d.ts.map +0 -1
- package/dist/PropFuelConnector.js +0 -450
- package/dist/PropFuelConnector.js.map +0 -1
- package/dist/QuickBooksConnector.d.ts +0 -92
- package/dist/QuickBooksConnector.d.ts.map +0 -1
- package/dist/QuickBooksConnector.js +0 -713
- package/dist/QuickBooksConnector.js.map +0 -1
- package/dist/RasaConnector.d.ts +0 -152
- package/dist/RasaConnector.d.ts.map +0 -1
- package/dist/RasaConnector.js +0 -929
- package/dist/RasaConnector.js.map +0 -1
- package/dist/Reach360Connector.d.ts +0 -175
- package/dist/Reach360Connector.d.ts.map +0 -1
- package/dist/Reach360Connector.js +0 -796
- package/dist/Reach360Connector.js.map +0 -1
- package/dist/RelationalDBConnector.d.ts +0 -108
- package/dist/RelationalDBConnector.d.ts.map +0 -1
- package/dist/RelationalDBConnector.js +0 -228
- package/dist/RelationalDBConnector.js.map +0 -1
- package/dist/RhythmConnector.d.ts +0 -188
- package/dist/RhythmConnector.d.ts.map +0 -1
- package/dist/RhythmConnector.js +0 -784
- package/dist/RhythmConnector.js.map +0 -1
- package/dist/SageIntacctConnector.d.ts +0 -337
- package/dist/SageIntacctConnector.d.ts.map +0 -1
- package/dist/SageIntacctConnector.js +0 -2081
- package/dist/SageIntacctConnector.js.map +0 -1
- package/dist/SalesforceConnector.d.ts +0 -461
- package/dist/SalesforceConnector.d.ts.map +0 -1
- package/dist/SalesforceConnector.js +0 -1862
- package/dist/SalesforceConnector.js.map +0 -1
- package/dist/SharePointConnector.d.ts +0 -188
- package/dist/SharePointConnector.d.ts.map +0 -1
- package/dist/SharePointConnector.js +0 -716
- package/dist/SharePointConnector.js.map +0 -1
- package/dist/WicketConnector.d.ts +0 -252
- package/dist/WicketConnector.d.ts.map +0 -1
- package/dist/WicketConnector.js +0 -938
- package/dist/WicketConnector.js.map +0 -1
- package/dist/WildApricotConnector.d.ts +0 -201
- package/dist/WildApricotConnector.d.ts.map +0 -1
- package/dist/WildApricotConnector.js +0 -1129
- package/dist/WildApricotConnector.js.map +0 -1
- package/dist/YourMembershipConnector.d.ts +0 -349
- package/dist/YourMembershipConnector.d.ts.map +0 -1
- package/dist/YourMembershipConnector.js +0 -4062
- package/dist/YourMembershipConnector.js.map +0 -1
- package/dist/generate-integration-actions.d.ts +0 -23
- package/dist/generate-integration-actions.d.ts.map +0 -1
- package/dist/generate-integration-actions.js +0 -201
- package/dist/generate-integration-actions.js.map +0 -1
|
@@ -1,916 +0,0 @@
|
|
|
1
|
-
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
-
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
-
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
-
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
-
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
-
};
|
|
7
|
-
/**
|
|
8
|
-
* BlackbaudConnector — Integration connector for Blackbaud Raiser's Edge NXT (SKY API).
|
|
9
|
-
*
|
|
10
|
-
* API Documentation: https://developer.blackbaud.com/skyapi/
|
|
11
|
-
*
|
|
12
|
-
* Auth: OAuth 2.0 (access_token + refresh_token) PLUS subscription key header.
|
|
13
|
-
* - Authorization: Bearer {access_token}
|
|
14
|
-
* - bb-api-subscription-key: {subscription_key}
|
|
15
|
-
* Refresh via https://oauth2.sky.blackbaud.com/token
|
|
16
|
-
* Base URL: https://api.sky.blackbaud.com/
|
|
17
|
-
* Pagination: Offset-based via next_link in response
|
|
18
|
-
* Rate limits: 10 calls/second hard limit — enforced with 120ms minimum interval
|
|
19
|
-
* Incremental: Date filter params on list endpoints
|
|
20
|
-
* CRUD: Full on Constituents, Gifts, Pledges, Notes, Relationships
|
|
21
|
-
*
|
|
22
|
-
* API Categories:
|
|
23
|
-
* - Constituent API (implemented) — constituents, addresses, phones, emails, relationships, notes
|
|
24
|
-
* - Gift API (implemented) — gifts, pledges, gift splits, soft credits
|
|
25
|
-
* - Fundraising API (implemented) — fundraisers, campaigns, appeals, funds
|
|
26
|
-
* - Opportunity API (implemented, read-only) — prospect opportunities
|
|
27
|
-
* - Event API (NOT implemented) — separate Sky Signup product, not RE NXT
|
|
28
|
-
* - Education API (NOT implemented) — Higher Education product, separate license
|
|
29
|
-
* - Merchant Services API (NOT implemented) — payment terminal APIs, hardware-specific
|
|
30
|
-
* - Church Management (NOT implemented) — separate product (Church Management)
|
|
31
|
-
* - Webhooks (available) — Constituent, Gift, Payment, Custom Field events; not implemented as receiver
|
|
32
|
-
*/
|
|
33
|
-
import { RegisterClass } from '@memberjunction/global';
|
|
34
|
-
import { Metadata } from '@memberjunction/core';
|
|
35
|
-
import { BaseIntegrationConnector, BaseRESTIntegrationConnector, } from '@memberjunction/integration-engine';
|
|
36
|
-
// ─── Constants ────────────────────────────────────────────────────────────
|
|
37
|
-
const BB_TOKEN_URL = 'https://oauth2.sky.blackbaud.com/token';
|
|
38
|
-
const BB_API_BASE = 'https://api.sky.blackbaud.com';
|
|
39
|
-
const BB_PAGE_SIZE = 500;
|
|
40
|
-
const TOKEN_REFRESH_BUFFER_MS = 60_000;
|
|
41
|
-
const MAX_RETRIES = 3;
|
|
42
|
-
const REQUEST_TIMEOUT_MS = 30_000;
|
|
43
|
-
// 10 calls/second = 100ms minimum. Use 120ms to stay comfortably under.
|
|
44
|
-
const MIN_REQUEST_INTERVAL_MS = 120;
|
|
45
|
-
// ─── Static Object Definitions ────────────────────────────────────────────
|
|
46
|
-
const BB_OBJECTS = [
|
|
47
|
-
{
|
|
48
|
-
Name: 'Constituents',
|
|
49
|
-
DisplayName: 'Constituent',
|
|
50
|
-
Description: 'Individual donors, prospects, volunteers, and organizational constituents',
|
|
51
|
-
SupportsWrite: true,
|
|
52
|
-
Fields: [
|
|
53
|
-
{ Name: 'id', DisplayName: 'ID', Type: 'string', IsRequired: false, IsReadOnly: true, IsPrimaryKey: true, Description: 'Constituent GUID' },
|
|
54
|
-
{ Name: 'type', DisplayName: 'Type', Type: 'string', IsRequired: false, IsReadOnly: false, IsPrimaryKey: false, Description: 'Individual or Organization' },
|
|
55
|
-
{ Name: 'lookup_id', DisplayName: 'Lookup ID', Type: 'string', IsRequired: false, IsReadOnly: false, IsPrimaryKey: false, Description: 'External lookup ID' },
|
|
56
|
-
{ Name: 'first', DisplayName: 'First Name', Type: 'string', IsRequired: false, IsReadOnly: false, IsPrimaryKey: false, Description: 'First name (individuals)' },
|
|
57
|
-
{ Name: 'last', DisplayName: 'Last Name', Type: 'string', IsRequired: false, IsReadOnly: false, IsPrimaryKey: false, Description: 'Last name (individuals)' },
|
|
58
|
-
{ Name: 'name', DisplayName: 'Name', Type: 'string', IsRequired: false, IsReadOnly: false, IsPrimaryKey: false, Description: 'Full name or organization name' },
|
|
59
|
-
{ Name: 'email', DisplayName: 'Email', Type: 'string', IsRequired: false, IsReadOnly: false, IsPrimaryKey: false, Description: 'Primary email address object' },
|
|
60
|
-
{ Name: 'phone', DisplayName: 'Phone', Type: 'string', IsRequired: false, IsReadOnly: false, IsPrimaryKey: false, Description: 'Primary phone object' },
|
|
61
|
-
{ Name: 'address', DisplayName: 'Address', Type: 'string', IsRequired: false, IsReadOnly: false, IsPrimaryKey: false, Description: 'Primary address object' },
|
|
62
|
-
{ Name: 'gender', DisplayName: 'Gender', Type: 'string', IsRequired: false, IsReadOnly: false, IsPrimaryKey: false, Description: 'Gender (individuals)' },
|
|
63
|
-
{ Name: 'birthdate', DisplayName: 'Birthdate', Type: 'datetime', IsRequired: false, IsReadOnly: false, IsPrimaryKey: false, Description: 'Date of birth (individuals)' },
|
|
64
|
-
{ Name: 'deceased', DisplayName: 'Deceased', Type: 'boolean', IsRequired: false, IsReadOnly: false, IsPrimaryKey: false, Description: 'Whether constituent is deceased' },
|
|
65
|
-
{ Name: 'inactive', DisplayName: 'Inactive', Type: 'boolean', IsRequired: false, IsReadOnly: false, IsPrimaryKey: false, Description: 'Whether constituent is inactive' },
|
|
66
|
-
{ Name: 'date_added', DisplayName: 'Date Added', Type: 'datetime', IsRequired: false, IsReadOnly: true, IsPrimaryKey: false, Description: 'Record creation date' },
|
|
67
|
-
{ Name: 'date_modified', DisplayName: 'Date Modified', Type: 'datetime', IsRequired: false, IsReadOnly: true, IsPrimaryKey: false, Description: 'Last modification date' },
|
|
68
|
-
],
|
|
69
|
-
},
|
|
70
|
-
{
|
|
71
|
-
Name: 'ConstituentAddresses',
|
|
72
|
-
DisplayName: 'Constituent Address',
|
|
73
|
-
Description: 'Addresses for constituents (parent-child under Constituents)',
|
|
74
|
-
SupportsWrite: true,
|
|
75
|
-
Fields: [
|
|
76
|
-
{ Name: 'id', DisplayName: 'ID', Type: 'string', IsRequired: false, IsReadOnly: true, IsPrimaryKey: true, Description: 'Address GUID' },
|
|
77
|
-
{ Name: 'constituent_id', DisplayName: 'Constituent ID', Type: 'string', IsRequired: true, IsReadOnly: true, IsPrimaryKey: false, Description: 'Parent constituent GUID' },
|
|
78
|
-
{ Name: 'type', DisplayName: 'Type', Type: 'string', IsRequired: false, IsReadOnly: false, IsPrimaryKey: false, Description: 'Address type (Home, Business, etc.)' },
|
|
79
|
-
{ Name: 'address_lines', DisplayName: 'Street', Type: 'string', IsRequired: false, IsReadOnly: false, IsPrimaryKey: false, Description: 'Street address lines' },
|
|
80
|
-
{ Name: 'city', DisplayName: 'City', Type: 'string', IsRequired: false, IsReadOnly: false, IsPrimaryKey: false, Description: 'City' },
|
|
81
|
-
{ Name: 'state', DisplayName: 'State', Type: 'string', IsRequired: false, IsReadOnly: false, IsPrimaryKey: false, Description: 'State/province' },
|
|
82
|
-
{ Name: 'postal_code', DisplayName: 'Postal Code', Type: 'string', IsRequired: false, IsReadOnly: false, IsPrimaryKey: false, Description: 'ZIP/postal code' },
|
|
83
|
-
{ Name: 'country', DisplayName: 'Country', Type: 'string', IsRequired: false, IsReadOnly: false, IsPrimaryKey: false, Description: 'Country' },
|
|
84
|
-
{ Name: 'preferred', DisplayName: 'Preferred', Type: 'boolean', IsRequired: false, IsReadOnly: false, IsPrimaryKey: false, Description: 'Whether this is the preferred address' },
|
|
85
|
-
],
|
|
86
|
-
},
|
|
87
|
-
{
|
|
88
|
-
Name: 'ConstituentPhones',
|
|
89
|
-
DisplayName: 'Constituent Phone',
|
|
90
|
-
Description: 'Phone numbers for constituents (parent-child under Constituents)',
|
|
91
|
-
SupportsWrite: true,
|
|
92
|
-
Fields: [
|
|
93
|
-
{ Name: 'id', DisplayName: 'ID', Type: 'string', IsRequired: false, IsReadOnly: true, IsPrimaryKey: true, Description: 'Phone GUID' },
|
|
94
|
-
{ Name: 'constituent_id', DisplayName: 'Constituent ID', Type: 'string', IsRequired: true, IsReadOnly: true, IsPrimaryKey: false, Description: 'Parent constituent GUID' },
|
|
95
|
-
{ Name: 'type', DisplayName: 'Type', Type: 'string', IsRequired: false, IsReadOnly: false, IsPrimaryKey: false, Description: 'Phone type (Home, Business, Mobile, etc.)' },
|
|
96
|
-
{ Name: 'number', DisplayName: 'Number', Type: 'string', IsRequired: true, IsReadOnly: false, IsPrimaryKey: false, Description: 'Phone number' },
|
|
97
|
-
{ Name: 'primary', DisplayName: 'Primary', Type: 'boolean', IsRequired: false, IsReadOnly: false, IsPrimaryKey: false, Description: 'Whether this is the primary phone' },
|
|
98
|
-
],
|
|
99
|
-
},
|
|
100
|
-
{
|
|
101
|
-
Name: 'ConstituentEmails',
|
|
102
|
-
DisplayName: 'Constituent Email',
|
|
103
|
-
Description: 'Email addresses for constituents (parent-child under Constituents)',
|
|
104
|
-
SupportsWrite: true,
|
|
105
|
-
Fields: [
|
|
106
|
-
{ Name: 'id', DisplayName: 'ID', Type: 'string', IsRequired: false, IsReadOnly: true, IsPrimaryKey: true, Description: 'Email GUID' },
|
|
107
|
-
{ Name: 'constituent_id', DisplayName: 'Constituent ID', Type: 'string', IsRequired: true, IsReadOnly: true, IsPrimaryKey: false, Description: 'Parent constituent GUID' },
|
|
108
|
-
{ Name: 'type', DisplayName: 'Type', Type: 'string', IsRequired: false, IsReadOnly: false, IsPrimaryKey: false, Description: 'Email type (Personal, Business, etc.)' },
|
|
109
|
-
{ Name: 'address', DisplayName: 'Address', Type: 'string', IsRequired: true, IsReadOnly: false, IsPrimaryKey: false, Description: 'Email address value' },
|
|
110
|
-
{ Name: 'primary', DisplayName: 'Primary', Type: 'boolean', IsRequired: false, IsReadOnly: false, IsPrimaryKey: false, Description: 'Whether this is the primary email' },
|
|
111
|
-
],
|
|
112
|
-
},
|
|
113
|
-
{
|
|
114
|
-
Name: 'Gifts',
|
|
115
|
-
DisplayName: 'Gift',
|
|
116
|
-
Description: 'Donation and gift records linked to constituents',
|
|
117
|
-
SupportsWrite: true,
|
|
118
|
-
Fields: [
|
|
119
|
-
{ Name: 'id', DisplayName: 'ID', Type: 'string', IsRequired: false, IsReadOnly: true, IsPrimaryKey: true, Description: 'Gift GUID' },
|
|
120
|
-
{ Name: 'constituent_id', DisplayName: 'Constituent ID', Type: 'string', IsRequired: true, IsReadOnly: false, IsPrimaryKey: false, Description: 'Donor constituent GUID' },
|
|
121
|
-
{ Name: 'type', DisplayName: 'Type', Type: 'string', IsRequired: true, IsReadOnly: false, IsPrimaryKey: false, Description: 'Gift type (Donation, RecurringGift, GiftInKind, etc.)' },
|
|
122
|
-
{ Name: 'amount', DisplayName: 'Amount', Type: 'string', IsRequired: true, IsReadOnly: false, IsPrimaryKey: false, Description: 'Gift amount object { value, currency }' },
|
|
123
|
-
{ Name: 'date', DisplayName: 'Gift Date', Type: 'datetime', IsRequired: true, IsReadOnly: false, IsPrimaryKey: false, Description: 'Gift date' },
|
|
124
|
-
{ Name: 'fund', DisplayName: 'Fund', Type: 'string', IsRequired: false, IsReadOnly: false, IsPrimaryKey: false, Description: 'Fund designation' },
|
|
125
|
-
{ Name: 'campaign', DisplayName: 'Campaign', Type: 'string', IsRequired: false, IsReadOnly: false, IsPrimaryKey: false, Description: 'Campaign designation' },
|
|
126
|
-
{ Name: 'appeal', DisplayName: 'Appeal', Type: 'string', IsRequired: false, IsReadOnly: false, IsPrimaryKey: false, Description: 'Appeal designation' },
|
|
127
|
-
{ Name: 'acknowledgement', DisplayName: 'Acknowledgement', Type: 'string', IsRequired: false, IsReadOnly: false, IsPrimaryKey: false, Description: 'Acknowledgement status' },
|
|
128
|
-
{ Name: 'date_added', DisplayName: 'Date Added', Type: 'datetime', IsRequired: false, IsReadOnly: true, IsPrimaryKey: false, Description: 'Record creation date' },
|
|
129
|
-
{ Name: 'date_modified', DisplayName: 'Date Modified', Type: 'datetime', IsRequired: false, IsReadOnly: true, IsPrimaryKey: false, Description: 'Last modification date' },
|
|
130
|
-
],
|
|
131
|
-
},
|
|
132
|
-
{
|
|
133
|
-
Name: 'Fundraisers',
|
|
134
|
-
DisplayName: 'Fundraiser',
|
|
135
|
-
Description: 'Fundraising campaigns and appeals',
|
|
136
|
-
SupportsWrite: false,
|
|
137
|
-
Fields: [
|
|
138
|
-
{ Name: 'id', DisplayName: 'ID', Type: 'string', IsRequired: false, IsReadOnly: true, IsPrimaryKey: true, Description: 'Fundraiser GUID' },
|
|
139
|
-
{ Name: 'description', DisplayName: 'Description', Type: 'string', IsRequired: false, IsReadOnly: true, IsPrimaryKey: false, Description: 'Fundraiser name/description' },
|
|
140
|
-
{ Name: 'start_date', DisplayName: 'Start Date', Type: 'datetime', IsRequired: false, IsReadOnly: true, IsPrimaryKey: false, Description: 'Fundraiser start date' },
|
|
141
|
-
{ Name: 'end_date', DisplayName: 'End Date', Type: 'datetime', IsRequired: false, IsReadOnly: true, IsPrimaryKey: false, Description: 'Fundraiser end date' },
|
|
142
|
-
{ Name: 'goal', DisplayName: 'Goal', Type: 'decimal', IsRequired: false, IsReadOnly: true, IsPrimaryKey: false, Description: 'Fundraising goal amount' },
|
|
143
|
-
],
|
|
144
|
-
},
|
|
145
|
-
{
|
|
146
|
-
Name: 'Campaigns',
|
|
147
|
-
DisplayName: 'Campaign',
|
|
148
|
-
Description: 'Fundraising campaigns',
|
|
149
|
-
SupportsWrite: false,
|
|
150
|
-
Fields: [
|
|
151
|
-
{ Name: 'id', DisplayName: 'ID', Type: 'string', IsRequired: false, IsReadOnly: true, IsPrimaryKey: true, Description: 'Campaign GUID' },
|
|
152
|
-
{ Name: 'description', DisplayName: 'Description', Type: 'string', IsRequired: false, IsReadOnly: true, IsPrimaryKey: false, Description: 'Campaign name/description' },
|
|
153
|
-
{ Name: 'start_date', DisplayName: 'Start Date', Type: 'datetime', IsRequired: false, IsReadOnly: true, IsPrimaryKey: false, Description: 'Campaign start date' },
|
|
154
|
-
{ Name: 'end_date', DisplayName: 'End Date', Type: 'datetime', IsRequired: false, IsReadOnly: true, IsPrimaryKey: false, Description: 'Campaign end date' },
|
|
155
|
-
{ Name: 'goal', DisplayName: 'Goal', Type: 'decimal', IsRequired: false, IsReadOnly: true, IsPrimaryKey: false, Description: 'Campaign goal' },
|
|
156
|
-
],
|
|
157
|
-
},
|
|
158
|
-
{
|
|
159
|
-
Name: 'Opportunities',
|
|
160
|
-
DisplayName: 'Opportunity',
|
|
161
|
-
Description: 'Major gift prospect opportunities (read-only)',
|
|
162
|
-
SupportsWrite: false,
|
|
163
|
-
Fields: [
|
|
164
|
-
{ Name: 'id', DisplayName: 'ID', Type: 'string', IsRequired: false, IsReadOnly: true, IsPrimaryKey: true, Description: 'Opportunity GUID' },
|
|
165
|
-
{ Name: 'constituent_id', DisplayName: 'Constituent ID', Type: 'string', IsRequired: true, IsReadOnly: true, IsPrimaryKey: false, Description: 'Prospect constituent GUID' },
|
|
166
|
-
{ Name: 'name', DisplayName: 'Name', Type: 'string', IsRequired: false, IsReadOnly: true, IsPrimaryKey: false, Description: 'Opportunity name' },
|
|
167
|
-
{ Name: 'status', DisplayName: 'Status', Type: 'string', IsRequired: false, IsReadOnly: true, IsPrimaryKey: false, Description: 'Opportunity status' },
|
|
168
|
-
{ Name: 'ask_date', DisplayName: 'Ask Date', Type: 'datetime', IsRequired: false, IsReadOnly: true, IsPrimaryKey: false, Description: 'Date of ask' },
|
|
169
|
-
{ Name: 'ask_amount', DisplayName: 'Ask Amount', Type: 'decimal', IsRequired: false, IsReadOnly: true, IsPrimaryKey: false, Description: 'Amount asked' },
|
|
170
|
-
{ Name: 'expected_date', DisplayName: 'Expected Date', Type: 'datetime', IsRequired: false, IsReadOnly: true, IsPrimaryKey: false, Description: 'Expected close date' },
|
|
171
|
-
],
|
|
172
|
-
},
|
|
173
|
-
// ─── Additional SKY API endpoints — lean overlay ──
|
|
174
|
-
// Constituent sub-resources
|
|
175
|
-
{ Name: 'ConstituentRelationships', DisplayName: 'Relationship', Description: 'Constituent relationships', SupportsWrite: true, Fields: [
|
|
176
|
-
{ Name: 'id', DisplayName: 'ID', Type: 'string', IsRequired: false, IsReadOnly: true, IsPrimaryKey: true, Description: 'Relationship ID' },
|
|
177
|
-
{ Name: 'constituent_id', DisplayName: 'Constituent ID', Type: 'string', IsRequired: true, IsReadOnly: true, IsPrimaryKey: false, Description: 'FK → Constituents' },
|
|
178
|
-
] },
|
|
179
|
-
{ Name: 'ConstituentNotes', DisplayName: 'Note', Description: 'Constituent notes', SupportsWrite: true, Fields: [
|
|
180
|
-
{ Name: 'id', DisplayName: 'ID', Type: 'string', IsRequired: false, IsReadOnly: true, IsPrimaryKey: true, Description: 'Note ID' },
|
|
181
|
-
{ Name: 'constituent_id', DisplayName: 'Constituent ID', Type: 'string', IsRequired: true, IsReadOnly: true, IsPrimaryKey: false, Description: 'FK → Constituents' },
|
|
182
|
-
{ Name: 'date_added', DisplayName: 'Added', Type: 'datetime', IsRequired: false, IsReadOnly: true, IsPrimaryKey: false, Description: 'Incremental date' },
|
|
183
|
-
] },
|
|
184
|
-
{ Name: 'ConstituentActions', DisplayName: 'Action', Description: 'Constituent actions/activities', SupportsWrite: true, Fields: [
|
|
185
|
-
{ Name: 'id', DisplayName: 'ID', Type: 'string', IsRequired: false, IsReadOnly: true, IsPrimaryKey: true, Description: 'Action ID' },
|
|
186
|
-
{ Name: 'constituent_id', DisplayName: 'Constituent ID', Type: 'string', IsRequired: true, IsReadOnly: true, IsPrimaryKey: false, Description: 'FK → Constituents' },
|
|
187
|
-
{ Name: 'date', DisplayName: 'Date', Type: 'datetime', IsRequired: false, IsReadOnly: false, IsPrimaryKey: false, Description: 'Incremental date' },
|
|
188
|
-
] },
|
|
189
|
-
{ Name: 'ConstituentCustomFields', DisplayName: 'Custom Field', Description: 'Constituent custom field values', SupportsWrite: true, Fields: [
|
|
190
|
-
{ Name: 'id', DisplayName: 'ID', Type: 'string', IsRequired: false, IsReadOnly: true, IsPrimaryKey: true, Description: 'Custom field ID' },
|
|
191
|
-
{ Name: 'parent_id', DisplayName: 'Constituent ID', Type: 'string', IsRequired: true, IsReadOnly: true, IsPrimaryKey: false, Description: 'FK → Constituents' },
|
|
192
|
-
] },
|
|
193
|
-
{ Name: 'ConstituentCodes', DisplayName: 'Constituent Code', Description: 'Constituent classification codes', SupportsWrite: true, Fields: [
|
|
194
|
-
{ Name: 'id', DisplayName: 'ID', Type: 'string', IsRequired: false, IsReadOnly: true, IsPrimaryKey: true, Description: 'Code ID' },
|
|
195
|
-
{ Name: 'constituent_id', DisplayName: 'Constituent ID', Type: 'string', IsRequired: true, IsReadOnly: true, IsPrimaryKey: false, Description: 'FK → Constituents' },
|
|
196
|
-
] },
|
|
197
|
-
{ Name: 'ConstituentEducation', DisplayName: 'Education', Description: 'Constituent education records', SupportsWrite: true, Fields: [
|
|
198
|
-
{ Name: 'id', DisplayName: 'ID', Type: 'string', IsRequired: false, IsReadOnly: true, IsPrimaryKey: true, Description: 'Education ID' },
|
|
199
|
-
{ Name: 'constituent_id', DisplayName: 'Constituent ID', Type: 'string', IsRequired: true, IsReadOnly: true, IsPrimaryKey: false, Description: 'FK → Constituents' },
|
|
200
|
-
] },
|
|
201
|
-
{ Name: 'ConstituentOnlinePresence', DisplayName: 'Online Presence', Description: 'Social media/web profiles', SupportsWrite: true, Fields: [
|
|
202
|
-
{ Name: 'id', DisplayName: 'ID', Type: 'string', IsRequired: false, IsReadOnly: true, IsPrimaryKey: true, Description: 'Presence ID' },
|
|
203
|
-
{ Name: 'constituent_id', DisplayName: 'Constituent ID', Type: 'string', IsRequired: true, IsReadOnly: true, IsPrimaryKey: false, Description: 'FK → Constituents' },
|
|
204
|
-
] },
|
|
205
|
-
{ Name: 'ConstituentMemberships', DisplayName: 'Membership', Description: 'Constituent memberships', SupportsWrite: true, Fields: [
|
|
206
|
-
{ Name: 'id', DisplayName: 'ID', Type: 'string', IsRequired: false, IsReadOnly: true, IsPrimaryKey: true, Description: 'Membership ID' },
|
|
207
|
-
{ Name: 'constituent_id', DisplayName: 'Constituent ID', Type: 'string', IsRequired: true, IsReadOnly: true, IsPrimaryKey: false, Description: 'FK → Constituents' },
|
|
208
|
-
] },
|
|
209
|
-
// Gift sub-resources
|
|
210
|
-
{ Name: 'GiftCustomFields', DisplayName: 'Gift Custom Field', Description: 'Custom field values on gifts', SupportsWrite: true, Fields: [
|
|
211
|
-
{ Name: 'id', DisplayName: 'ID', Type: 'string', IsRequired: false, IsReadOnly: true, IsPrimaryKey: true, Description: 'Custom field ID' },
|
|
212
|
-
{ Name: 'parent_id', DisplayName: 'Gift ID', Type: 'string', IsRequired: true, IsReadOnly: true, IsPrimaryKey: false, Description: 'FK → Gifts' },
|
|
213
|
-
] },
|
|
214
|
-
{ Name: 'GiftSoftCredits', DisplayName: 'Soft Credit', Description: 'Soft credit allocations on gifts', SupportsWrite: true, Fields: [
|
|
215
|
-
{ Name: 'id', DisplayName: 'ID', Type: 'string', IsRequired: false, IsReadOnly: true, IsPrimaryKey: true, Description: 'Soft credit ID' },
|
|
216
|
-
{ Name: 'gift_id', DisplayName: 'Gift ID', Type: 'string', IsRequired: true, IsReadOnly: true, IsPrimaryKey: false, Description: 'FK → Gifts' },
|
|
217
|
-
] },
|
|
218
|
-
// Pledges & Recurring Gifts
|
|
219
|
-
{ Name: 'Pledges', DisplayName: 'Pledge', Description: 'Pledge commitments', SupportsWrite: true, Fields: [
|
|
220
|
-
{ Name: 'id', DisplayName: 'ID', Type: 'string', IsRequired: false, IsReadOnly: true, IsPrimaryKey: true, Description: 'Pledge ID' },
|
|
221
|
-
{ Name: 'constituent_id', DisplayName: 'Constituent ID', Type: 'string', IsRequired: true, IsReadOnly: true, IsPrimaryKey: false, Description: 'FK → Constituents' },
|
|
222
|
-
{ Name: 'date_added', DisplayName: 'Added', Type: 'datetime', IsRequired: false, IsReadOnly: true, IsPrimaryKey: false, Description: 'Incremental date' },
|
|
223
|
-
] },
|
|
224
|
-
{ Name: 'RecurringGifts', DisplayName: 'Recurring Gift', Description: 'Recurring gift/donation schedules', SupportsWrite: false, Fields: [
|
|
225
|
-
{ Name: 'id', DisplayName: 'ID', Type: 'string', IsRequired: false, IsReadOnly: true, IsPrimaryKey: true, Description: 'Recurring gift ID' },
|
|
226
|
-
{ Name: 'constituent_id', DisplayName: 'Constituent ID', Type: 'string', IsRequired: true, IsReadOnly: true, IsPrimaryKey: false, Description: 'FK → Constituents' },
|
|
227
|
-
] },
|
|
228
|
-
// Funds & Appeals
|
|
229
|
-
{ Name: 'Funds', DisplayName: 'Fund', Description: 'Fund designations', SupportsWrite: true, Fields: [
|
|
230
|
-
{ Name: 'id', DisplayName: 'ID', Type: 'string', IsRequired: false, IsReadOnly: true, IsPrimaryKey: true, Description: 'Fund ID' },
|
|
231
|
-
{ Name: 'date_added', DisplayName: 'Added', Type: 'datetime', IsRequired: false, IsReadOnly: true, IsPrimaryKey: false, Description: 'Incremental date' },
|
|
232
|
-
] },
|
|
233
|
-
{ Name: 'Appeals', DisplayName: 'Appeal', Description: 'Fundraising appeals', SupportsWrite: true, Fields: [
|
|
234
|
-
{ Name: 'id', DisplayName: 'ID', Type: 'string', IsRequired: false, IsReadOnly: true, IsPrimaryKey: true, Description: 'Appeal ID' },
|
|
235
|
-
{ Name: 'date_added', DisplayName: 'Added', Type: 'datetime', IsRequired: false, IsReadOnly: true, IsPrimaryKey: false, Description: 'Incremental date' },
|
|
236
|
-
] },
|
|
237
|
-
// Events
|
|
238
|
-
{ Name: 'Events', DisplayName: 'Event', Description: 'Fundraising/special events', SupportsWrite: true, Fields: [
|
|
239
|
-
{ Name: 'id', DisplayName: 'ID', Type: 'string', IsRequired: false, IsReadOnly: true, IsPrimaryKey: true, Description: 'Event ID' },
|
|
240
|
-
{ Name: 'start_date', DisplayName: 'Start', Type: 'datetime', IsRequired: false, IsReadOnly: false, IsPrimaryKey: false, Description: 'Incremental date' },
|
|
241
|
-
] },
|
|
242
|
-
{ Name: 'EventParticipants', DisplayName: 'Event Participant', Description: 'Event attendees/registrants', SupportsWrite: true, Fields: [
|
|
243
|
-
{ Name: 'id', DisplayName: 'ID', Type: 'string', IsRequired: false, IsReadOnly: true, IsPrimaryKey: true, Description: 'Participant ID' },
|
|
244
|
-
{ Name: 'event_id', DisplayName: 'Event ID', Type: 'string', IsRequired: true, IsReadOnly: true, IsPrimaryKey: false, Description: 'FK → Events' },
|
|
245
|
-
{ Name: 'constituent_id', DisplayName: 'Constituent ID', Type: 'string', IsRequired: false, IsReadOnly: true, IsPrimaryKey: false, Description: 'FK → Constituents' },
|
|
246
|
-
] },
|
|
247
|
-
// Lists
|
|
248
|
-
{ Name: 'Lists', DisplayName: 'List', Description: 'Constituent lists', SupportsWrite: true, Fields: [
|
|
249
|
-
{ Name: 'id', DisplayName: 'ID', Type: 'string', IsRequired: false, IsReadOnly: true, IsPrimaryKey: true, Description: 'List ID' },
|
|
250
|
-
{ Name: 'date_modified', DisplayName: 'Modified', Type: 'datetime', IsRequired: false, IsReadOnly: true, IsPrimaryKey: false, Description: 'Incremental date' },
|
|
251
|
-
] },
|
|
252
|
-
// Tributes
|
|
253
|
-
{ Name: 'Tributes', DisplayName: 'Tribute', Description: 'Gift tribute/memorial records', SupportsWrite: true, Fields: [
|
|
254
|
-
{ Name: 'id', DisplayName: 'ID', Type: 'string', IsRequired: false, IsReadOnly: true, IsPrimaryKey: true, Description: 'Tribute ID' },
|
|
255
|
-
] },
|
|
256
|
-
// GL
|
|
257
|
-
{ Name: 'GLAccounts', DisplayName: 'GL Account', Description: 'General ledger accounts', SupportsWrite: true, Fields: [
|
|
258
|
-
{ Name: 'id', DisplayName: 'ID', Type: 'string', IsRequired: false, IsReadOnly: true, IsPrimaryKey: true, Description: 'GL Account ID' },
|
|
259
|
-
] },
|
|
260
|
-
{ Name: 'GLTransactionDistributions', DisplayName: 'GL Distribution', Description: 'GL transaction distributions', SupportsWrite: false, Fields: [
|
|
261
|
-
{ Name: 'id', DisplayName: 'ID', Type: 'string', IsRequired: false, IsReadOnly: true, IsPrimaryKey: true, Description: 'Distribution ID' },
|
|
262
|
-
{ Name: 'post_date', DisplayName: 'Post Date', Type: 'datetime', IsRequired: false, IsReadOnly: true, IsPrimaryKey: false, Description: 'Incremental date' },
|
|
263
|
-
] },
|
|
264
|
-
// Lookup types (read-only)
|
|
265
|
-
{ Name: 'MembershipTypes', DisplayName: 'Membership Type', Description: 'Membership type definitions', SupportsWrite: false, Fields: [
|
|
266
|
-
{ Name: 'id', DisplayName: 'ID', Type: 'string', IsRequired: false, IsReadOnly: true, IsPrimaryKey: true, Description: 'Type ID' },
|
|
267
|
-
] },
|
|
268
|
-
{ Name: 'GiftTypes', DisplayName: 'Gift Type', Description: 'Gift type lookup', SupportsWrite: false, Fields: [
|
|
269
|
-
{ Name: 'id', DisplayName: 'ID', Type: 'string', IsRequired: false, IsReadOnly: true, IsPrimaryKey: true, Description: 'Type ID' },
|
|
270
|
-
] },
|
|
271
|
-
{ Name: 'ActionTypes', DisplayName: 'Action Type', Description: 'Action type lookup', SupportsWrite: false, Fields: [
|
|
272
|
-
{ Name: 'id', DisplayName: 'ID', Type: 'string', IsRequired: false, IsReadOnly: true, IsPrimaryKey: true, Description: 'Type ID' },
|
|
273
|
-
] },
|
|
274
|
-
{ Name: 'NoteTypes', DisplayName: 'Note Type', Description: 'Note type lookup', SupportsWrite: false, Fields: [
|
|
275
|
-
{ Name: 'id', DisplayName: 'ID', Type: 'string', IsRequired: false, IsReadOnly: true, IsPrimaryKey: true, Description: 'Type ID' },
|
|
276
|
-
] },
|
|
277
|
-
{ Name: 'FundTypes', DisplayName: 'Fund Type', Description: 'Fund type lookup', SupportsWrite: false, Fields: [
|
|
278
|
-
{ Name: 'id', DisplayName: 'ID', Type: 'string', IsRequired: false, IsReadOnly: true, IsPrimaryKey: true, Description: 'Type ID' },
|
|
279
|
-
] },
|
|
280
|
-
// ─── Remaining Blackbaud sub-resources ──
|
|
281
|
-
{ Name: 'ConstituentAliases', DisplayName: 'Alias', Description: 'Constituent name aliases', SupportsWrite: true, Fields: [
|
|
282
|
-
{ Name: 'id', DisplayName: 'ID', Type: 'string', IsRequired: false, IsReadOnly: true, IsPrimaryKey: true, Description: 'Alias ID' },
|
|
283
|
-
{ Name: 'constituent_id', DisplayName: 'Constituent ID', Type: 'string', IsRequired: true, IsReadOnly: true, IsPrimaryKey: false, Description: 'FK → Constituents' },
|
|
284
|
-
] },
|
|
285
|
-
{ Name: 'ConstituentSolicitCodes', DisplayName: 'Solicit Code', Description: 'Solicitation codes', SupportsWrite: true, Fields: [
|
|
286
|
-
{ Name: 'id', DisplayName: 'ID', Type: 'string', IsRequired: false, IsReadOnly: true, IsPrimaryKey: true, Description: 'Code ID' },
|
|
287
|
-
{ Name: 'constituent_id', DisplayName: 'Constituent ID', Type: 'string', IsRequired: true, IsReadOnly: true, IsPrimaryKey: false, Description: 'FK → Constituents' },
|
|
288
|
-
] },
|
|
289
|
-
{ Name: 'ConstituentRatings', DisplayName: 'Rating', Description: 'Constituent prospect ratings', SupportsWrite: true, Fields: [
|
|
290
|
-
{ Name: 'id', DisplayName: 'ID', Type: 'string', IsRequired: false, IsReadOnly: true, IsPrimaryKey: true, Description: 'Rating ID' },
|
|
291
|
-
{ Name: 'constituent_id', DisplayName: 'Constituent ID', Type: 'string', IsRequired: true, IsReadOnly: true, IsPrimaryKey: false, Description: 'FK → Constituents' },
|
|
292
|
-
] },
|
|
293
|
-
{ Name: 'ConstituentAttachments', DisplayName: 'Constituent Attachment', Description: 'File attachments on constituents', SupportsWrite: true, Fields: [
|
|
294
|
-
{ Name: 'id', DisplayName: 'ID', Type: 'string', IsRequired: false, IsReadOnly: true, IsPrimaryKey: true, Description: 'Attachment ID' },
|
|
295
|
-
{ Name: 'parent_id', DisplayName: 'Constituent ID', Type: 'string', IsRequired: true, IsReadOnly: true, IsPrimaryKey: false, Description: 'FK → Constituents' },
|
|
296
|
-
] },
|
|
297
|
-
{ Name: 'ConstituentConsent', DisplayName: 'GDPR Consent', Description: 'GDPR consent records', SupportsWrite: true, Fields: [
|
|
298
|
-
{ Name: 'constituent_id', DisplayName: 'Constituent ID', Type: 'string', IsRequired: true, IsReadOnly: true, IsPrimaryKey: true, Description: 'FK → Constituents' },
|
|
299
|
-
] },
|
|
300
|
-
{ Name: 'ConstituentFundraiserAssignments', DisplayName: 'Fundraiser Assignment', Description: 'Fundraiser-to-constituent assignments', SupportsWrite: false, Fields: [
|
|
301
|
-
{ Name: 'id', DisplayName: 'ID', Type: 'string', IsRequired: false, IsReadOnly: true, IsPrimaryKey: true, Description: 'Assignment ID' },
|
|
302
|
-
{ Name: 'constituent_id', DisplayName: 'Constituent ID', Type: 'string', IsRequired: true, IsReadOnly: true, IsPrimaryKey: false, Description: 'FK → Constituents' },
|
|
303
|
-
] },
|
|
304
|
-
// Action sub-resources
|
|
305
|
-
{ Name: 'ConstituentActionCustomFields', DisplayName: 'Action Custom Field', Description: 'Custom fields on actions', SupportsWrite: true, Fields: [
|
|
306
|
-
{ Name: 'id', DisplayName: 'ID', Type: 'string', IsRequired: false, IsReadOnly: true, IsPrimaryKey: true, Description: 'Custom field ID' },
|
|
307
|
-
{ Name: 'parent_id', DisplayName: 'Action ID', Type: 'string', IsRequired: true, IsReadOnly: true, IsPrimaryKey: false, Description: 'FK → ConstituentActions' },
|
|
308
|
-
] },
|
|
309
|
-
{ Name: 'ConstituentActionAttachments', DisplayName: 'Action Attachment', Description: 'File attachments on actions', SupportsWrite: true, Fields: [
|
|
310
|
-
{ Name: 'id', DisplayName: 'ID', Type: 'string', IsRequired: false, IsReadOnly: true, IsPrimaryKey: true, Description: 'Attachment ID' },
|
|
311
|
-
{ Name: 'parent_id', DisplayName: 'Action ID', Type: 'string', IsRequired: true, IsReadOnly: true, IsPrimaryKey: false, Description: 'FK → ConstituentActions' },
|
|
312
|
-
] },
|
|
313
|
-
// Gift sub-resources
|
|
314
|
-
{ Name: 'GiftAttachments', DisplayName: 'Gift Attachment', Description: 'File attachments on gifts', SupportsWrite: true, Fields: [
|
|
315
|
-
{ Name: 'id', DisplayName: 'ID', Type: 'string', IsRequired: false, IsReadOnly: true, IsPrimaryKey: true, Description: 'Attachment ID' },
|
|
316
|
-
{ Name: 'parent_id', DisplayName: 'Gift ID', Type: 'string', IsRequired: true, IsReadOnly: true, IsPrimaryKey: false, Description: 'FK → Gifts' },
|
|
317
|
-
] },
|
|
318
|
-
// Fund/Appeal sub-resources
|
|
319
|
-
{ Name: 'FundCustomFields', DisplayName: 'Fund Custom Field', Description: 'Custom fields on funds', SupportsWrite: true, Fields: [
|
|
320
|
-
{ Name: 'id', DisplayName: 'ID', Type: 'string', IsRequired: false, IsReadOnly: true, IsPrimaryKey: true, Description: 'Custom field ID' },
|
|
321
|
-
{ Name: 'parent_id', DisplayName: 'Fund ID', Type: 'string', IsRequired: true, IsReadOnly: true, IsPrimaryKey: false, Description: 'FK → Funds' },
|
|
322
|
-
] },
|
|
323
|
-
{ Name: 'AppealCustomFields', DisplayName: 'Appeal Custom Field', Description: 'Custom fields on appeals', SupportsWrite: true, Fields: [
|
|
324
|
-
{ Name: 'id', DisplayName: 'ID', Type: 'string', IsRequired: false, IsReadOnly: true, IsPrimaryKey: true, Description: 'Custom field ID' },
|
|
325
|
-
{ Name: 'parent_id', DisplayName: 'Appeal ID', Type: 'string', IsRequired: true, IsReadOnly: true, IsPrimaryKey: false, Description: 'FK → Appeals' },
|
|
326
|
-
] },
|
|
327
|
-
// RecurringGiftSchedules REMOVED — schedules are a sub-object property of recurring gifts, not a separate endpoint (verified)
|
|
328
|
-
// Event sub-resources
|
|
329
|
-
{ Name: 'EventCategories', DisplayName: 'Event Category', Description: 'Event category definitions', SupportsWrite: false, Fields: [
|
|
330
|
-
{ Name: 'id', DisplayName: 'ID', Type: 'string', IsRequired: false, IsReadOnly: true, IsPrimaryKey: true, Description: 'Category ID' },
|
|
331
|
-
] },
|
|
332
|
-
{ Name: 'EventFees', DisplayName: 'Event Fee', Description: 'Fee types on events', SupportsWrite: true, Fields: [
|
|
333
|
-
{ Name: 'id', DisplayName: 'ID', Type: 'string', IsRequired: false, IsReadOnly: true, IsPrimaryKey: true, Description: 'Fee ID' },
|
|
334
|
-
{ Name: 'event_id', DisplayName: 'Event ID', Type: 'string', IsRequired: true, IsReadOnly: true, IsPrimaryKey: false, Description: 'FK → Events' },
|
|
335
|
-
] },
|
|
336
|
-
{ Name: 'EventAttachments', DisplayName: 'Event Attachment', Description: 'File attachments on events', SupportsWrite: true, Fields: [
|
|
337
|
-
{ Name: 'id', DisplayName: 'ID', Type: 'string', IsRequired: false, IsReadOnly: true, IsPrimaryKey: true, Description: 'Attachment ID' },
|
|
338
|
-
{ Name: 'parent_id', DisplayName: 'Event ID', Type: 'string', IsRequired: true, IsReadOnly: true, IsPrimaryKey: false, Description: 'FK → Events' },
|
|
339
|
-
] },
|
|
340
|
-
{ Name: 'EventParticipantFees', DisplayName: 'Participant Fee', Description: 'Fees paid by event participants', SupportsWrite: true, Fields: [
|
|
341
|
-
{ Name: 'id', DisplayName: 'ID', Type: 'string', IsRequired: false, IsReadOnly: true, IsPrimaryKey: true, Description: 'Participant fee ID' },
|
|
342
|
-
{ Name: 'participant_id', DisplayName: 'Participant ID', Type: 'string', IsRequired: true, IsReadOnly: true, IsPrimaryKey: false, Description: 'FK → EventParticipants' },
|
|
343
|
-
] },
|
|
344
|
-
// Opportunity sub-resources
|
|
345
|
-
{ Name: 'OpportunityCustomFields', DisplayName: 'Opportunity Custom Field', Description: 'Custom fields on opportunities', SupportsWrite: true, Fields: [
|
|
346
|
-
{ Name: 'id', DisplayName: 'ID', Type: 'string', IsRequired: false, IsReadOnly: true, IsPrimaryKey: true, Description: 'Custom field ID' },
|
|
347
|
-
{ Name: 'parent_id', DisplayName: 'Opportunity ID', Type: 'string', IsRequired: true, IsReadOnly: true, IsPrimaryKey: false, Description: 'FK → Opportunities' },
|
|
348
|
-
] },
|
|
349
|
-
{ Name: 'OpportunityFundraisers', DisplayName: 'Opportunity Fundraiser', Description: 'Fundraisers assigned to opportunities', SupportsWrite: true, Fields: [
|
|
350
|
-
{ Name: 'id', DisplayName: 'ID', Type: 'string', IsRequired: false, IsReadOnly: true, IsPrimaryKey: true, Description: 'Assignment ID' },
|
|
351
|
-
{ Name: 'opportunity_id', DisplayName: 'Opportunity ID', Type: 'string', IsRequired: true, IsReadOnly: true, IsPrimaryKey: false, Description: 'FK → Opportunities' },
|
|
352
|
-
] },
|
|
353
|
-
// Lists sub-resource
|
|
354
|
-
{ Name: 'ListConstituents', DisplayName: 'List Member', Description: 'Constituents in a list', SupportsWrite: true, Fields: [
|
|
355
|
-
{ Name: 'id', DisplayName: 'ID', Type: 'string', IsRequired: false, IsReadOnly: true, IsPrimaryKey: true, Description: 'Constituent ID in list' },
|
|
356
|
-
{ Name: 'list_id', DisplayName: 'List ID', Type: 'string', IsRequired: true, IsReadOnly: true, IsPrimaryKey: false, Description: 'FK → Lists' },
|
|
357
|
-
] },
|
|
358
|
-
// Tribute sub-resources
|
|
359
|
-
{ Name: 'TributeTypes', DisplayName: 'Tribute Type', Description: 'Tribute type lookup', SupportsWrite: false, Fields: [
|
|
360
|
-
{ Name: 'id', DisplayName: 'ID', Type: 'string', IsRequired: false, IsReadOnly: true, IsPrimaryKey: true, Description: 'Type ID' },
|
|
361
|
-
] },
|
|
362
|
-
{ Name: 'TributeAcknowledgees', DisplayName: 'Tribute Acknowledgee', Description: 'Acknowledgee records on tributes', SupportsWrite: true, Fields: [
|
|
363
|
-
{ Name: 'id', DisplayName: 'ID', Type: 'string', IsRequired: false, IsReadOnly: true, IsPrimaryKey: true, Description: 'Acknowledgee ID' },
|
|
364
|
-
{ Name: 'tribute_id', DisplayName: 'Tribute ID', Type: 'string', IsRequired: true, IsReadOnly: true, IsPrimaryKey: false, Description: 'FK → Tributes' },
|
|
365
|
-
] },
|
|
366
|
-
// GL sub-resources
|
|
367
|
-
{ Name: 'GLProjects', DisplayName: 'GL Project', Description: 'General ledger projects', SupportsWrite: true, Fields: [
|
|
368
|
-
{ Name: 'id', DisplayName: 'ID', Type: 'string', IsRequired: false, IsReadOnly: true, IsPrimaryKey: true, Description: 'Project ID' },
|
|
369
|
-
] },
|
|
370
|
-
{ Name: 'GLJournalEntries', DisplayName: 'GL Journal Entry', Description: 'GL journal entries', SupportsWrite: true, Fields: [
|
|
371
|
-
{ Name: 'id', DisplayName: 'ID', Type: 'string', IsRequired: false, IsReadOnly: true, IsPrimaryKey: true, Description: 'JE ID' },
|
|
372
|
-
{ Name: 'post_date', DisplayName: 'Post Date', Type: 'datetime', IsRequired: false, IsReadOnly: true, IsPrimaryKey: false, Description: 'Incremental date' },
|
|
373
|
-
] },
|
|
374
|
-
{ Name: 'GLFiscalYears', DisplayName: 'Fiscal Year', Description: 'Fiscal year definitions', SupportsWrite: false, Fields: [
|
|
375
|
-
{ Name: 'id', DisplayName: 'ID', Type: 'string', IsRequired: false, IsReadOnly: true, IsPrimaryKey: true, Description: 'FY ID' },
|
|
376
|
-
] },
|
|
377
|
-
// Communication preferences
|
|
378
|
-
{ Name: 'CommunicationPreferences', DisplayName: 'Comm Preference', Description: 'Communication preferences per constituent', SupportsWrite: true, Fields: [
|
|
379
|
-
{ Name: 'constituent_id', DisplayName: 'Constituent ID', Type: 'string', IsRequired: true, IsReadOnly: true, IsPrimaryKey: true, Description: 'FK → Constituents' },
|
|
380
|
-
] },
|
|
381
|
-
// ConsentChannels REMOVED — not a standalone endpoint, embedded in communication preferences model (verified)
|
|
382
|
-
// Remaining lookups
|
|
383
|
-
{ Name: 'ConstituentCodeTypes', DisplayName: 'Code Type', Description: 'Constituent code type definitions', SupportsWrite: false, Fields: [
|
|
384
|
-
{ Name: 'id', DisplayName: 'ID', Type: 'string', IsRequired: false, IsReadOnly: true, IsPrimaryKey: true, Description: 'Type ID' },
|
|
385
|
-
] },
|
|
386
|
-
{ Name: 'CustomFieldCategories', DisplayName: 'CF Category', Description: 'Custom field category definitions', SupportsWrite: false, Fields: [
|
|
387
|
-
{ Name: 'id', DisplayName: 'ID', Type: 'string', IsRequired: false, IsReadOnly: true, IsPrimaryKey: true, Description: 'Category ID' },
|
|
388
|
-
] },
|
|
389
|
-
{ Name: 'SuffixCodes', DisplayName: 'Suffix Code', Description: 'Name suffix lookup', SupportsWrite: false, Fields: [
|
|
390
|
-
{ Name: 'id', DisplayName: 'ID', Type: 'string', IsRequired: false, IsReadOnly: true, IsPrimaryKey: true, Description: 'Code ID' },
|
|
391
|
-
] },
|
|
392
|
-
{ Name: 'TitleCodes', DisplayName: 'Title Code', Description: 'Name title/prefix lookup', SupportsWrite: false, Fields: [
|
|
393
|
-
{ Name: 'id', DisplayName: 'ID', Type: 'string', IsRequired: false, IsReadOnly: true, IsPrimaryKey: true, Description: 'Code ID' },
|
|
394
|
-
] },
|
|
395
|
-
{ Name: 'GiftSubtypes', DisplayName: 'Gift Subtype', Description: 'Gift subtype lookup', SupportsWrite: false, Fields: [
|
|
396
|
-
{ Name: 'id', DisplayName: 'ID', Type: 'string', IsRequired: false, IsReadOnly: true, IsPrimaryKey: true, Description: 'Subtype ID' },
|
|
397
|
-
] },
|
|
398
|
-
{ Name: 'OpportunityTypes', DisplayName: 'Opportunity Type', Description: 'Opportunity type lookup', SupportsWrite: false, Fields: [
|
|
399
|
-
{ Name: 'id', DisplayName: 'ID', Type: 'string', IsRequired: false, IsReadOnly: true, IsPrimaryKey: true, Description: 'Type ID' },
|
|
400
|
-
] },
|
|
401
|
-
{ Name: 'ActionStatusTypes', DisplayName: 'Action Status', Description: 'Action status type lookup', SupportsWrite: false, Fields: [
|
|
402
|
-
{ Name: 'id', DisplayName: 'ID', Type: 'string', IsRequired: false, IsReadOnly: true, IsPrimaryKey: true, Description: 'Status ID' },
|
|
403
|
-
] },
|
|
404
|
-
{ Name: 'EventFeeTypes', DisplayName: 'Event Fee Type', Description: 'Event fee type lookup', SupportsWrite: false, Fields: [
|
|
405
|
-
{ Name: 'id', DisplayName: 'ID', Type: 'string', IsRequired: false, IsReadOnly: true, IsPrimaryKey: true, Description: 'Type ID' },
|
|
406
|
-
] },
|
|
407
|
-
{ Name: 'MembershipStanding', DisplayName: 'Membership Standing', Description: 'Membership standing lookup', SupportsWrite: false, Fields: [
|
|
408
|
-
{ Name: 'id', DisplayName: 'ID', Type: 'string', IsRequired: false, IsReadOnly: true, IsPrimaryKey: true, Description: 'Standing ID' },
|
|
409
|
-
] },
|
|
410
|
-
{ Name: 'MembershipDues', DisplayName: 'Membership Dues', Description: 'Dues records per membership', SupportsWrite: false, Fields: [
|
|
411
|
-
{ Name: 'id', DisplayName: 'ID', Type: 'string', IsRequired: false, IsReadOnly: true, IsPrimaryKey: true, Description: 'Dues ID' },
|
|
412
|
-
] },
|
|
413
|
-
// Remaining sub-resources from audit
|
|
414
|
-
{ Name: 'PledgeCustomFields', DisplayName: 'Pledge Custom Field', Description: 'Custom fields on pledges', SupportsWrite: false, Fields: [
|
|
415
|
-
{ Name: 'id', DisplayName: 'ID', Type: 'string', IsRequired: false, IsReadOnly: true, IsPrimaryKey: true, Description: 'CF ID' },
|
|
416
|
-
{ Name: 'parent_id', DisplayName: 'Pledge ID', Type: 'string', IsRequired: true, IsReadOnly: true, IsPrimaryKey: false, Description: 'FK → Pledges' },
|
|
417
|
-
] },
|
|
418
|
-
{ Name: 'OpportunityAttachments', DisplayName: 'Opportunity Attachment', Description: 'File attachments on opportunities', SupportsWrite: true, Fields: [
|
|
419
|
-
{ Name: 'id', DisplayName: 'ID', Type: 'string', IsRequired: false, IsReadOnly: true, IsPrimaryKey: true, Description: 'Attachment ID' },
|
|
420
|
-
{ Name: 'parent_id', DisplayName: 'Opportunity ID', Type: 'string', IsRequired: true, IsReadOnly: true, IsPrimaryKey: false, Description: 'FK → Opportunities' },
|
|
421
|
-
] },
|
|
422
|
-
{ Name: 'OpportunityStatus', DisplayName: 'Opportunity Status', Description: 'Opportunity status lookup', SupportsWrite: false, Fields: [
|
|
423
|
-
{ Name: 'id', DisplayName: 'ID', Type: 'string', IsRequired: false, IsReadOnly: true, IsPrimaryKey: true, Description: 'Status ID' },
|
|
424
|
-
] },
|
|
425
|
-
// ConsentCategories REMOVED — not a standalone endpoint, embedded in communication preferences model (verified)
|
|
426
|
-
{ Name: 'EventParticipantDonations', DisplayName: 'Participant Donation', Description: 'Donations from event participants', SupportsWrite: false, Fields: [
|
|
427
|
-
{ Name: 'id', DisplayName: 'ID', Type: 'string', IsRequired: false, IsReadOnly: true, IsPrimaryKey: true, Description: 'Donation ID' },
|
|
428
|
-
{ Name: 'participant_id', DisplayName: 'Participant ID', Type: 'string', IsRequired: true, IsReadOnly: true, IsPrimaryKey: false, Description: 'FK → EventParticipants' },
|
|
429
|
-
] },
|
|
430
|
-
];
|
|
431
|
-
// ─── Connector ────────────────────────────────────────────────────────────
|
|
432
|
-
let BlackbaudConnector = class BlackbaudConnector extends BaseRESTIntegrationConnector {
|
|
433
|
-
constructor() {
|
|
434
|
-
super(...arguments);
|
|
435
|
-
this.tokenCache = null;
|
|
436
|
-
this.lastRequestTime = 0;
|
|
437
|
-
}
|
|
438
|
-
get IntegrationName() { return 'Blackbaud'; }
|
|
439
|
-
get SupportsCreate() { return true; }
|
|
440
|
-
get SupportsUpdate() { return true; }
|
|
441
|
-
get SupportsDelete() { return true; }
|
|
442
|
-
GetIntegrationObjects() {
|
|
443
|
-
return BB_OBJECTS;
|
|
444
|
-
}
|
|
445
|
-
GetActionGeneratorConfig() {
|
|
446
|
-
const config = super.GetActionGeneratorConfig();
|
|
447
|
-
if (!config)
|
|
448
|
-
return null;
|
|
449
|
-
config.IconClass = 'fa-solid fa-hand-holding-heart';
|
|
450
|
-
config.CategoryDescription = 'Blackbaud Raiser\'s Edge NXT for constituents, gifts, campaigns, and fundraising';
|
|
451
|
-
config.ParentCategoryName = 'Nonprofit/Fundraising';
|
|
452
|
-
config.IncludeSearch = true;
|
|
453
|
-
config.IncludeList = true;
|
|
454
|
-
return config;
|
|
455
|
-
}
|
|
456
|
-
// ─── Discovery ─────────────────────────────────────────────────────
|
|
457
|
-
async DiscoverObjects(_companyIntegration, _contextUser) {
|
|
458
|
-
return BB_OBJECTS.map(obj => ({
|
|
459
|
-
Name: obj.Name,
|
|
460
|
-
Label: obj.DisplayName,
|
|
461
|
-
Description: obj.Description,
|
|
462
|
-
SupportsIncrementalSync: ['Constituents', 'Gifts'].includes(obj.Name),
|
|
463
|
-
SupportsWrite: obj.SupportsWrite ?? false,
|
|
464
|
-
}));
|
|
465
|
-
}
|
|
466
|
-
async DiscoverFields(companyIntegration, objectName, contextUser) {
|
|
467
|
-
try {
|
|
468
|
-
const auth = await this.Authenticate(companyIntegration, contextUser);
|
|
469
|
-
const headers = this.BuildHeaders(auth);
|
|
470
|
-
const apiPath = objectName.replace(/([a-z])([A-Z])/g, '$1/$2').toLowerCase();
|
|
471
|
-
const response = await this.MakeHTTPRequest(auth, `${BB_API_BASE}/constituent/v1/${apiPath}?limit=1`, 'GET', headers);
|
|
472
|
-
if (response.Status === 200) {
|
|
473
|
-
const records = this.NormalizeResponse(response.Body, null);
|
|
474
|
-
if (records.length > 0)
|
|
475
|
-
return this.InferFieldsWithOverlay(records[0], objectName, BB_OBJECTS);
|
|
476
|
-
}
|
|
477
|
-
}
|
|
478
|
-
catch { /* fall through */ }
|
|
479
|
-
const staticObj = BB_OBJECTS.find(o => o.Name.toLowerCase() === objectName.toLowerCase());
|
|
480
|
-
if (!staticObj)
|
|
481
|
-
return [];
|
|
482
|
-
return staticObj.Fields.map(f => ({ Name: f.Name, Label: f.DisplayName, Description: f.Description, DataType: f.Type, IsRequired: f.IsRequired, IsUniqueKey: f.IsPrimaryKey, IsReadOnly: f.IsReadOnly }));
|
|
483
|
-
}
|
|
484
|
-
InferFieldsWithOverlay(sample, objectName, allObjects) {
|
|
485
|
-
const staticObj = allObjects.find(o => o.Name.toLowerCase() === objectName.toLowerCase());
|
|
486
|
-
const staticMap = new Map((staticObj?.Fields ?? []).map(f => [f.Name.toLowerCase(), f]));
|
|
487
|
-
const fields = [];
|
|
488
|
-
for (const [key, value] of Object.entries(sample)) {
|
|
489
|
-
if (key === 'links' || key === '_links')
|
|
490
|
-
continue;
|
|
491
|
-
const sf = staticMap.get(key.toLowerCase());
|
|
492
|
-
fields.push({ Name: key, Label: sf?.DisplayName ?? key, Description: sf?.Description ?? '',
|
|
493
|
-
DataType: sf?.Type ?? this.InferTypeVal(value), IsRequired: sf?.IsRequired ?? false, IsUniqueKey: sf?.IsPrimaryKey ?? false, IsReadOnly: sf?.IsReadOnly ?? false });
|
|
494
|
-
}
|
|
495
|
-
return fields;
|
|
496
|
-
}
|
|
497
|
-
InferTypeVal(v) {
|
|
498
|
-
if (v === null || v === undefined)
|
|
499
|
-
return 'string';
|
|
500
|
-
if (typeof v === 'number')
|
|
501
|
-
return Number.isInteger(v) ? 'number' : 'decimal';
|
|
502
|
-
if (typeof v === 'boolean')
|
|
503
|
-
return 'boolean';
|
|
504
|
-
if (typeof v === 'string' && /^\d{4}-\d{2}-\d{2}/.test(v))
|
|
505
|
-
return 'datetime';
|
|
506
|
-
return 'string';
|
|
507
|
-
}
|
|
508
|
-
// ─── Auth ──────────────────────────────────────────────────────────
|
|
509
|
-
async Authenticate(companyIntegration, contextUser) {
|
|
510
|
-
const config = await this.ParseConfig(companyIntegration, contextUser);
|
|
511
|
-
if (this.tokenCache && this.tokenCache.ExpiresAt > Date.now() + TOKEN_REFRESH_BUFFER_MS) {
|
|
512
|
-
return {
|
|
513
|
-
Token: this.tokenCache.AccessToken,
|
|
514
|
-
TokenType: 'Bearer',
|
|
515
|
-
Config: config,
|
|
516
|
-
SubscriptionKey: config.SubscriptionKey,
|
|
517
|
-
};
|
|
518
|
-
}
|
|
519
|
-
const refreshToken = this.tokenCache?.RefreshToken ?? config.RefreshToken;
|
|
520
|
-
if (refreshToken) {
|
|
521
|
-
try {
|
|
522
|
-
const refreshed = await this.RefreshToken(config, refreshToken);
|
|
523
|
-
this.tokenCache = refreshed;
|
|
524
|
-
return {
|
|
525
|
-
Token: refreshed.AccessToken,
|
|
526
|
-
TokenType: 'Bearer',
|
|
527
|
-
Config: config,
|
|
528
|
-
SubscriptionKey: config.SubscriptionKey,
|
|
529
|
-
};
|
|
530
|
-
}
|
|
531
|
-
catch {
|
|
532
|
-
// Fall through to use stored access token
|
|
533
|
-
}
|
|
534
|
-
}
|
|
535
|
-
this.tokenCache = {
|
|
536
|
-
AccessToken: config.AccessToken,
|
|
537
|
-
RefreshToken: config.RefreshToken,
|
|
538
|
-
ExpiresAt: Date.now() + (3600 * 1000),
|
|
539
|
-
};
|
|
540
|
-
return {
|
|
541
|
-
Token: config.AccessToken,
|
|
542
|
-
TokenType: 'Bearer',
|
|
543
|
-
Config: config,
|
|
544
|
-
SubscriptionKey: config.SubscriptionKey,
|
|
545
|
-
};
|
|
546
|
-
}
|
|
547
|
-
async RefreshToken(config, refreshToken) {
|
|
548
|
-
const basicAuth = Buffer.from(`${config.ClientID}:${config.ClientSecret}`).toString('base64');
|
|
549
|
-
const body = new URLSearchParams({
|
|
550
|
-
grant_type: 'refresh_token',
|
|
551
|
-
refresh_token: refreshToken,
|
|
552
|
-
});
|
|
553
|
-
const response = await fetch(BB_TOKEN_URL, {
|
|
554
|
-
method: 'POST',
|
|
555
|
-
headers: {
|
|
556
|
-
'Authorization': `Basic ${basicAuth}`,
|
|
557
|
-
'Content-Type': 'application/x-www-form-urlencoded',
|
|
558
|
-
},
|
|
559
|
-
body: body.toString(),
|
|
560
|
-
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
|
|
561
|
-
});
|
|
562
|
-
if (!response.ok) {
|
|
563
|
-
throw new Error(`Blackbaud token refresh failed: ${response.status}`);
|
|
564
|
-
}
|
|
565
|
-
const data = await response.json();
|
|
566
|
-
return {
|
|
567
|
-
AccessToken: data.access_token,
|
|
568
|
-
RefreshToken: data.refresh_token ?? refreshToken,
|
|
569
|
-
ExpiresAt: Date.now() + ((data.expires_in ?? 3600) * 1000),
|
|
570
|
-
};
|
|
571
|
-
}
|
|
572
|
-
async ParseConfig(companyIntegration, contextUser, provider) {
|
|
573
|
-
const credentialID = companyIntegration.CredentialID;
|
|
574
|
-
if (credentialID) {
|
|
575
|
-
const md = provider ?? new Metadata();
|
|
576
|
-
const credential = await md.GetEntityObject('MJ: Credentials', contextUser);
|
|
577
|
-
const loaded = await credential.Load(credentialID);
|
|
578
|
-
if (loaded && credential.Values) {
|
|
579
|
-
const parsed = JSON.parse(credential.Values);
|
|
580
|
-
if (parsed['ClientID'] && parsed['SubscriptionKey']) {
|
|
581
|
-
return {
|
|
582
|
-
ClientID: parsed['ClientID'],
|
|
583
|
-
ClientSecret: parsed['ClientSecret'] ?? '',
|
|
584
|
-
SubscriptionKey: parsed['SubscriptionKey'],
|
|
585
|
-
AccessToken: parsed['AccessToken'] ?? '',
|
|
586
|
-
RefreshToken: parsed['RefreshToken'] ?? '',
|
|
587
|
-
};
|
|
588
|
-
}
|
|
589
|
-
}
|
|
590
|
-
}
|
|
591
|
-
const configJson = companyIntegration.Configuration;
|
|
592
|
-
if (configJson) {
|
|
593
|
-
const parsed = JSON.parse(configJson);
|
|
594
|
-
return {
|
|
595
|
-
ClientID: parsed['ClientID'] ?? '',
|
|
596
|
-
ClientSecret: parsed['ClientSecret'] ?? '',
|
|
597
|
-
SubscriptionKey: parsed['SubscriptionKey'] ?? '',
|
|
598
|
-
AccessToken: parsed['AccessToken'] ?? '',
|
|
599
|
-
RefreshToken: parsed['RefreshToken'] ?? '',
|
|
600
|
-
};
|
|
601
|
-
}
|
|
602
|
-
throw new Error('No Blackbaud credentials found. Set ClientID, ClientSecret, SubscriptionKey, AccessToken, RefreshToken in credential Values or Configuration JSON.');
|
|
603
|
-
}
|
|
604
|
-
// ─── TestConnection ────────────────────────────────────────────────
|
|
605
|
-
async TestConnection(companyIntegration, contextUser) {
|
|
606
|
-
try {
|
|
607
|
-
const auth = await this.Authenticate(companyIntegration, contextUser);
|
|
608
|
-
const headers = this.BuildHeaders(auth);
|
|
609
|
-
const url = `${BB_API_BASE}/constituent/v1/constituents?limit=1`;
|
|
610
|
-
const response = await this.MakeHTTPRequest(auth, url, 'GET', headers);
|
|
611
|
-
if (response.Status === 200) {
|
|
612
|
-
const body = response.Body;
|
|
613
|
-
return { Success: true, Message: `Connected to Blackbaud SKY API — ${body.count ?? 0} total constituents` };
|
|
614
|
-
}
|
|
615
|
-
return { Success: false, Message: `API returned ${response.Status}` };
|
|
616
|
-
}
|
|
617
|
-
catch (err) {
|
|
618
|
-
return { Success: false, Message: err instanceof Error ? err.message : String(err) };
|
|
619
|
-
}
|
|
620
|
-
}
|
|
621
|
-
// ─── URL Building ──────────────────────────────────────────────────
|
|
622
|
-
GetBaseURL(_companyIntegration, _auth) {
|
|
623
|
-
return BB_API_BASE;
|
|
624
|
-
}
|
|
625
|
-
BuildPaginatedURL(basePath, _obj, _page, offset, _cursor) {
|
|
626
|
-
const sep = basePath.includes('?') ? '&' : '?';
|
|
627
|
-
return `${basePath}${sep}limit=${BB_PAGE_SIZE}&offset=${offset}`;
|
|
628
|
-
}
|
|
629
|
-
// ─── Response Parsing ──────────────────────────────────────────────
|
|
630
|
-
NormalizeResponse(rawBody, _responseDataKey) {
|
|
631
|
-
if (Array.isArray(rawBody))
|
|
632
|
-
return rawBody;
|
|
633
|
-
const body = rawBody;
|
|
634
|
-
if (Array.isArray(body['value']))
|
|
635
|
-
return body['value'];
|
|
636
|
-
if (body && Object.keys(body).length > 0)
|
|
637
|
-
return [body];
|
|
638
|
-
return [];
|
|
639
|
-
}
|
|
640
|
-
ExtractPaginationInfo(rawBody, _paginationType, _currentPage, currentOffset, _pageSize) {
|
|
641
|
-
const body = rawBody;
|
|
642
|
-
const records = this.NormalizeResponse(rawBody, null);
|
|
643
|
-
const hasMore = !!body.next_link;
|
|
644
|
-
return { HasMore: hasMore, NextOffset: currentOffset + records.length };
|
|
645
|
-
}
|
|
646
|
-
// ─── FetchChanges ──────────────────────────────────────────────────
|
|
647
|
-
async FetchChanges(ctx) {
|
|
648
|
-
const objectLower = ctx.ObjectName.toLowerCase();
|
|
649
|
-
switch (objectLower) {
|
|
650
|
-
case 'constituents': return this.FetchConstituents(ctx);
|
|
651
|
-
case 'constituentaddresses': return this.FetchConstituentChildren(ctx, 'addresses', 'ConstituentAddresses');
|
|
652
|
-
case 'constituentphones': return this.FetchConstituentChildren(ctx, 'phones', 'ConstituentPhones');
|
|
653
|
-
case 'constituentemails': return this.FetchConstituentChildren(ctx, 'emailaddresses', 'ConstituentEmails');
|
|
654
|
-
case 'gifts': return this.FetchGifts(ctx);
|
|
655
|
-
case 'fundraisers': return this.FetchList(ctx, `${BB_API_BASE}/fundraising/v1/fundraisers`, 'Fundraisers');
|
|
656
|
-
case 'campaigns': return this.FetchList(ctx, `${BB_API_BASE}/fundraising/v1/campaigns`, 'Campaigns');
|
|
657
|
-
case 'opportunities': return this.FetchList(ctx, `${BB_API_BASE}/opportunity/v1/opportunities`, 'Opportunities');
|
|
658
|
-
default: return this.FetchGenericBBObject(ctx);
|
|
659
|
-
}
|
|
660
|
-
}
|
|
661
|
-
async FetchGenericBBObject(ctx) {
|
|
662
|
-
// Generic fetch — maps object name to likely SKY API path
|
|
663
|
-
const apiPath = ctx.ObjectName.replace(/([a-z])([A-Z])/g, '$1/$2').toLowerCase();
|
|
664
|
-
const url = `${BB_API_BASE}/constituent/v1/${apiPath}`;
|
|
665
|
-
return this.FetchList(ctx, url, ctx.ObjectName);
|
|
666
|
-
}
|
|
667
|
-
async FetchConstituents(ctx) {
|
|
668
|
-
const auth = await this.Authenticate(ctx.CompanyIntegration, ctx.ContextUser);
|
|
669
|
-
const headers = this.BuildHeaders(auth);
|
|
670
|
-
const offset = ctx.CurrentOffset ?? 0;
|
|
671
|
-
let url = `${BB_API_BASE}/constituent/v1/constituents?limit=${BB_PAGE_SIZE}&offset=${offset}`;
|
|
672
|
-
if (ctx.WatermarkValue) {
|
|
673
|
-
url += `&date_modified=${encodeURIComponent(ctx.WatermarkValue)}`;
|
|
674
|
-
}
|
|
675
|
-
return this.ExecuteListFetch(auth, headers, url, 'Constituents', 'id', offset, ctx);
|
|
676
|
-
}
|
|
677
|
-
async FetchGifts(ctx) {
|
|
678
|
-
const auth = await this.Authenticate(ctx.CompanyIntegration, ctx.ContextUser);
|
|
679
|
-
const headers = this.BuildHeaders(auth);
|
|
680
|
-
const offset = ctx.CurrentOffset ?? 0;
|
|
681
|
-
let url = `${BB_API_BASE}/gift/v1/gifts?limit=${BB_PAGE_SIZE}&offset=${offset}`;
|
|
682
|
-
if (ctx.WatermarkValue) {
|
|
683
|
-
url += `&date_modified=${encodeURIComponent(ctx.WatermarkValue)}`;
|
|
684
|
-
}
|
|
685
|
-
return this.ExecuteListFetch(auth, headers, url, 'Gifts', 'id', offset, ctx);
|
|
686
|
-
}
|
|
687
|
-
async FetchList(ctx, endpoint, objectType) {
|
|
688
|
-
const auth = await this.Authenticate(ctx.CompanyIntegration, ctx.ContextUser);
|
|
689
|
-
const headers = this.BuildHeaders(auth);
|
|
690
|
-
const offset = ctx.CurrentOffset ?? 0;
|
|
691
|
-
let url = `${endpoint}?limit=${BB_PAGE_SIZE}&offset=${offset}`;
|
|
692
|
-
// Blackbaud SKY API endpoint families that document a `date_modified`
|
|
693
|
-
// query filter: Constituent API + Gift API + Fundraising API +
|
|
694
|
-
// Opportunity API. The connector identifies them by URL substring so the
|
|
695
|
-
// metadata can flip SupportsIncrementalSync=true for IOs in those
|
|
696
|
-
// families and FetchList will honor the watermark.
|
|
697
|
-
if (ctx.WatermarkValue && this.SupportsDateModified(endpoint)) {
|
|
698
|
-
url += `&date_modified=${encodeURIComponent(ctx.WatermarkValue)}`;
|
|
699
|
-
}
|
|
700
|
-
return this.ExecuteListFetch(auth, headers, url, objectType, 'id', offset, ctx);
|
|
701
|
-
}
|
|
702
|
-
/**
|
|
703
|
-
* Returns true if a Blackbaud SKY API endpoint is in a family documented to
|
|
704
|
-
* accept the `date_modified` query filter. Conservative — only includes
|
|
705
|
-
* endpoint roots where the SKY API docs surface the filter. Add new prefixes
|
|
706
|
-
* after verifying against the vendor docs for that endpoint family.
|
|
707
|
-
*/
|
|
708
|
-
SupportsDateModified(endpoint) {
|
|
709
|
-
const supportedPrefixes = [
|
|
710
|
-
'/constituent/v1/',
|
|
711
|
-
'/gift/v1/',
|
|
712
|
-
'/fundraising/v1/',
|
|
713
|
-
'/opportunity/v1/',
|
|
714
|
-
];
|
|
715
|
-
return supportedPrefixes.some(p => endpoint.includes(p));
|
|
716
|
-
}
|
|
717
|
-
async FetchConstituentChildren(ctx, childEndpoint, objectType) {
|
|
718
|
-
// Blackbaud provides flat list endpoints for child resources — no need to iterate parents
|
|
719
|
-
const auth = await this.Authenticate(ctx.CompanyIntegration, ctx.ContextUser);
|
|
720
|
-
const headers = this.BuildHeaders(auth);
|
|
721
|
-
const offset = ctx.CurrentOffset ?? 0;
|
|
722
|
-
let url = `${BB_API_BASE}/constituent/v1/constituents/${childEndpoint}?limit=${BB_PAGE_SIZE}&offset=${offset}`;
|
|
723
|
-
// Constituent-child endpoints are in the Constituent API family —
|
|
724
|
-
// honor the watermark when provided.
|
|
725
|
-
if (ctx.WatermarkValue) {
|
|
726
|
-
url += `&date_modified=${encodeURIComponent(ctx.WatermarkValue)}`;
|
|
727
|
-
}
|
|
728
|
-
return this.ExecuteListFetch(auth, headers, url, objectType, 'id', offset, ctx);
|
|
729
|
-
}
|
|
730
|
-
async ExecuteListFetch(auth, headers, url, objectType, pkField, offset, ctx) {
|
|
731
|
-
const response = await this.MakeHTTPRequest(auth, url, 'GET', headers);
|
|
732
|
-
if (response.Status < 200 || response.Status >= 300) {
|
|
733
|
-
throw new Error(`Blackbaud ${objectType} API error: ${response.Status}`);
|
|
734
|
-
}
|
|
735
|
-
const body = response.Body;
|
|
736
|
-
const records = this.NormalizeResponse(response.Body, null);
|
|
737
|
-
const externalRecords = records.map(r => ({
|
|
738
|
-
ExternalID: String(r[pkField] ?? ''),
|
|
739
|
-
ObjectType: objectType,
|
|
740
|
-
Fields: r,
|
|
741
|
-
}));
|
|
742
|
-
const hasMore = !!body.next_link;
|
|
743
|
-
let newWatermark;
|
|
744
|
-
if (!hasMore) {
|
|
745
|
-
for (const r of records) {
|
|
746
|
-
const modified = r['date_modified'];
|
|
747
|
-
if (modified && (!newWatermark || modified > newWatermark))
|
|
748
|
-
newWatermark = modified;
|
|
749
|
-
}
|
|
750
|
-
}
|
|
751
|
-
return {
|
|
752
|
-
Records: externalRecords,
|
|
753
|
-
HasMore: hasMore,
|
|
754
|
-
NextOffset: offset + records.length,
|
|
755
|
-
NewWatermarkValue: !hasMore ? newWatermark : undefined,
|
|
756
|
-
};
|
|
757
|
-
}
|
|
758
|
-
// ─── CRUD ──────────────────────────────────────────────────────────
|
|
759
|
-
async CreateRecord(ctx) {
|
|
760
|
-
const auth = await this.Authenticate(ctx.CompanyIntegration, ctx.ContextUser);
|
|
761
|
-
const headers = { ...this.BuildHeaders(auth), 'Content-Type': 'application/json' };
|
|
762
|
-
const url = this.CRUDEndpointURL(ctx.ObjectName, ctx.Attributes, null);
|
|
763
|
-
const response = await this.MakeHTTPRequest(auth, url, 'POST', headers, ctx.Attributes);
|
|
764
|
-
if (response.Status >= 200 && response.Status < 300) {
|
|
765
|
-
const body = response.Body;
|
|
766
|
-
return this.BuildCreatedResult(String(body['id'] ?? ''), response.Status, ctx.ObjectName);
|
|
767
|
-
}
|
|
768
|
-
return this.BuildCRUDError(response, 'CreateRecord', ctx.ObjectName);
|
|
769
|
-
}
|
|
770
|
-
async UpdateRecord(ctx) {
|
|
771
|
-
const auth = await this.Authenticate(ctx.CompanyIntegration, ctx.ContextUser);
|
|
772
|
-
const headers = { ...this.BuildHeaders(auth), 'Content-Type': 'application/json' };
|
|
773
|
-
const url = this.CRUDEndpointURL(ctx.ObjectName, ctx.Attributes, ctx.ExternalID);
|
|
774
|
-
const response = await this.MakeHTTPRequest(auth, url, 'PATCH', headers, ctx.Attributes);
|
|
775
|
-
if (response.Status >= 200 && response.Status < 300) {
|
|
776
|
-
return { Success: true, ExternalID: ctx.ExternalID, StatusCode: response.Status };
|
|
777
|
-
}
|
|
778
|
-
return this.BuildCRUDError(response, 'UpdateRecord', ctx.ObjectName);
|
|
779
|
-
}
|
|
780
|
-
async DeleteRecord(ctx) {
|
|
781
|
-
const auth = await this.Authenticate(ctx.CompanyIntegration, ctx.ContextUser);
|
|
782
|
-
const headers = this.BuildHeaders(auth);
|
|
783
|
-
const url = this.CRUDEndpointURL(ctx.ObjectName, {}, ctx.ExternalID);
|
|
784
|
-
const response = await this.MakeHTTPRequest(auth, url, 'DELETE', headers);
|
|
785
|
-
if (response.Status === 204 || (response.Status >= 200 && response.Status < 300)) {
|
|
786
|
-
return { Success: true, ExternalID: ctx.ExternalID, StatusCode: response.Status };
|
|
787
|
-
}
|
|
788
|
-
return this.BuildCRUDError(response, 'DeleteRecord', ctx.ObjectName);
|
|
789
|
-
}
|
|
790
|
-
CRUDEndpointURL(objectName, attrs, externalID) {
|
|
791
|
-
const lower = objectName.toLowerCase();
|
|
792
|
-
if (lower === 'constituentaddresses') {
|
|
793
|
-
const base = `${BB_API_BASE}/constituent/v1/addresses`;
|
|
794
|
-
return externalID ? `${base}/${externalID}` : base;
|
|
795
|
-
}
|
|
796
|
-
if (lower === 'constituentphones') {
|
|
797
|
-
const base = `${BB_API_BASE}/constituent/v1/phones`;
|
|
798
|
-
return externalID ? `${base}/${externalID}` : base;
|
|
799
|
-
}
|
|
800
|
-
if (lower === 'constituentemails') {
|
|
801
|
-
const base = `${BB_API_BASE}/constituent/v1/emailaddresses`;
|
|
802
|
-
return externalID ? `${base}/${externalID}` : base;
|
|
803
|
-
}
|
|
804
|
-
const endpointMap = {
|
|
805
|
-
'constituents': `${BB_API_BASE}/constituent/v1/constituents`,
|
|
806
|
-
'gifts': `${BB_API_BASE}/gift/v1/gifts`,
|
|
807
|
-
};
|
|
808
|
-
const base = endpointMap[lower] ?? `${BB_API_BASE}/${lower}`;
|
|
809
|
-
return externalID ? `${base}/${externalID}` : base;
|
|
810
|
-
}
|
|
811
|
-
BuildCRUDError(response, operation, objectName) {
|
|
812
|
-
const bodyStr = typeof response.Body === 'string' ? response.Body : JSON.stringify(response.Body);
|
|
813
|
-
return {
|
|
814
|
-
Success: false,
|
|
815
|
-
ExternalID: '',
|
|
816
|
-
StatusCode: response.Status,
|
|
817
|
-
ErrorMessage: `${operation} failed for ${objectName}: HTTP ${response.Status} — ${bodyStr?.substring(0, 300)}`,
|
|
818
|
-
};
|
|
819
|
-
}
|
|
820
|
-
// ─── Headers ───────────────────────────────────────────────────────
|
|
821
|
-
BuildHeaders(auth) {
|
|
822
|
-
const bbAuth = auth;
|
|
823
|
-
return {
|
|
824
|
-
'Authorization': `Bearer ${auth.Token}`,
|
|
825
|
-
'bb-api-subscription-key': bbAuth.SubscriptionKey,
|
|
826
|
-
'Accept': 'application/json',
|
|
827
|
-
'User-Agent': 'MemberJunction-Integration/1.0',
|
|
828
|
-
};
|
|
829
|
-
}
|
|
830
|
-
// ─── HTTP Transport ────────────────────────────────────────────────
|
|
831
|
-
async MakeHTTPRequest(_auth, url, method, headers, body) {
|
|
832
|
-
await this.Throttle();
|
|
833
|
-
for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
|
|
834
|
-
const response = await this.FetchWithTimeout(url, method, headers, body);
|
|
835
|
-
this.lastRequestTime = Date.now();
|
|
836
|
-
if (response.status === 401 && attempt === 0) {
|
|
837
|
-
this.tokenCache = null;
|
|
838
|
-
console.warn('[Blackbaud] 401 — clearing token cache for retry');
|
|
839
|
-
continue;
|
|
840
|
-
}
|
|
841
|
-
if (response.status === 429) {
|
|
842
|
-
const retryAfter = parseInt(response.headers.get('Retry-After') ?? '10', 10);
|
|
843
|
-
const delay = Math.min(retryAfter * 1000, 60_000);
|
|
844
|
-
console.warn(`[Blackbaud] Rate limited (429), waiting ${delay}ms`);
|
|
845
|
-
await this.Sleep(delay);
|
|
846
|
-
continue;
|
|
847
|
-
}
|
|
848
|
-
if (response.status >= 500 && attempt < MAX_RETRIES) {
|
|
849
|
-
const delay = Math.min(1000 * Math.pow(2, attempt), 30_000);
|
|
850
|
-
console.warn(`[Blackbaud] Server error ${response.status}, retrying in ${delay}ms`);
|
|
851
|
-
await this.Sleep(delay);
|
|
852
|
-
continue;
|
|
853
|
-
}
|
|
854
|
-
const responseBody = await this.ParseBody(response);
|
|
855
|
-
return this.ToRESTResponse(response, responseBody);
|
|
856
|
-
}
|
|
857
|
-
throw new Error(`Blackbaud request failed after ${MAX_RETRIES} retries: ${url}`);
|
|
858
|
-
}
|
|
859
|
-
async FetchWithTimeout(url, method, headers, body) {
|
|
860
|
-
const controller = new AbortController();
|
|
861
|
-
const timeoutId = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
|
|
862
|
-
try {
|
|
863
|
-
const opts = { method, headers, signal: controller.signal };
|
|
864
|
-
if (body && (method === 'POST' || method === 'PUT' || method === 'PATCH')) {
|
|
865
|
-
opts.body = JSON.stringify(body);
|
|
866
|
-
}
|
|
867
|
-
return await fetch(url, opts);
|
|
868
|
-
}
|
|
869
|
-
catch (err) {
|
|
870
|
-
if (err instanceof Error && err.name === 'AbortError') {
|
|
871
|
-
throw new Error(`Blackbaud request timed out: ${url}`);
|
|
872
|
-
}
|
|
873
|
-
throw err;
|
|
874
|
-
}
|
|
875
|
-
finally {
|
|
876
|
-
clearTimeout(timeoutId);
|
|
877
|
-
}
|
|
878
|
-
}
|
|
879
|
-
async ParseBody(response) {
|
|
880
|
-
const ct = response.headers.get('content-type') ?? '';
|
|
881
|
-
if (ct.includes('json'))
|
|
882
|
-
return response.json();
|
|
883
|
-
return response.text();
|
|
884
|
-
}
|
|
885
|
-
ToRESTResponse(response, body) {
|
|
886
|
-
const hdrs = {};
|
|
887
|
-
response.headers.forEach((v, k) => { hdrs[k.toLowerCase()] = v; });
|
|
888
|
-
return { Status: response.status, Body: body, Headers: hdrs };
|
|
889
|
-
}
|
|
890
|
-
async Throttle() {
|
|
891
|
-
const elapsed = Date.now() - this.lastRequestTime;
|
|
892
|
-
if (elapsed < MIN_REQUEST_INTERVAL_MS)
|
|
893
|
-
await this.Sleep(MIN_REQUEST_INTERVAL_MS - elapsed);
|
|
894
|
-
}
|
|
895
|
-
Sleep(ms) {
|
|
896
|
-
return new Promise(resolve => setTimeout(resolve, ms));
|
|
897
|
-
}
|
|
898
|
-
// ─── Default Field Mappings ────────────────────────────────────────
|
|
899
|
-
GetDefaultFieldMappings(objectName) {
|
|
900
|
-
const obj = BB_OBJECTS.find(o => o.Name.toLowerCase() === objectName.toLowerCase());
|
|
901
|
-
if (!obj)
|
|
902
|
-
return [];
|
|
903
|
-
return obj.Fields.map(f => ({
|
|
904
|
-
SourceFieldName: f.Name,
|
|
905
|
-
DestinationFieldName: f.Name,
|
|
906
|
-
IsKeyField: f.IsPrimaryKey,
|
|
907
|
-
}));
|
|
908
|
-
}
|
|
909
|
-
};
|
|
910
|
-
BlackbaudConnector = __decorate([
|
|
911
|
-
RegisterClass(BaseIntegrationConnector, 'BlackbaudConnector')
|
|
912
|
-
], BlackbaudConnector);
|
|
913
|
-
export { BlackbaudConnector };
|
|
914
|
-
// Tree-shaking prevention — REQUIRED
|
|
915
|
-
export function LoadBlackbaudConnector() { }
|
|
916
|
-
//# sourceMappingURL=BlackbaudConnector.js.map
|