@rachelallyson/planning-center-people-ts 1.0.0 → 2.0.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.
Files changed (82) hide show
  1. package/CHANGELOG.md +248 -1
  2. package/README.md +28 -0
  3. package/dist/auth.d.ts +64 -0
  4. package/dist/auth.js +98 -0
  5. package/dist/batch.d.ts +47 -0
  6. package/dist/batch.js +376 -0
  7. package/dist/client-manager.d.ts +66 -0
  8. package/dist/client-manager.js +150 -0
  9. package/dist/client.d.ts +71 -0
  10. package/dist/client.js +123 -0
  11. package/dist/core/http.d.ts +47 -0
  12. package/dist/core/http.js +242 -0
  13. package/dist/core/pagination.d.ts +34 -0
  14. package/dist/core/pagination.js +164 -0
  15. package/dist/core.d.ts +5 -0
  16. package/dist/core.js +12 -0
  17. package/dist/helpers.d.ts +125 -100
  18. package/dist/helpers.js +315 -275
  19. package/dist/index.d.ts +17 -5
  20. package/dist/index.js +39 -5
  21. package/dist/matching/matcher.d.ts +41 -0
  22. package/dist/matching/matcher.js +161 -0
  23. package/dist/matching/scoring.d.ts +35 -0
  24. package/dist/matching/scoring.js +141 -0
  25. package/dist/matching/strategies.d.ts +35 -0
  26. package/dist/matching/strategies.js +79 -0
  27. package/dist/modules/base.d.ts +46 -0
  28. package/dist/modules/base.js +82 -0
  29. package/dist/modules/contacts.d.ts +103 -0
  30. package/dist/modules/contacts.js +130 -0
  31. package/dist/modules/fields.d.ts +157 -0
  32. package/dist/modules/fields.js +294 -0
  33. package/dist/modules/households.d.ts +42 -0
  34. package/dist/modules/households.js +74 -0
  35. package/dist/modules/lists.d.ts +62 -0
  36. package/dist/modules/lists.js +92 -0
  37. package/dist/modules/notes.d.ts +74 -0
  38. package/dist/modules/notes.js +125 -0
  39. package/dist/modules/people.d.ts +196 -0
  40. package/dist/modules/people.js +221 -0
  41. package/dist/modules/workflows.d.ts +131 -0
  42. package/dist/modules/workflows.js +221 -0
  43. package/dist/monitoring.d.ts +53 -0
  44. package/dist/monitoring.js +142 -0
  45. package/dist/people/contacts.d.ts +43 -0
  46. package/dist/people/contacts.js +122 -0
  47. package/dist/people/core.d.ts +28 -0
  48. package/dist/people/core.js +69 -0
  49. package/dist/people/fields.d.ts +62 -0
  50. package/dist/people/fields.js +293 -0
  51. package/dist/people/households.d.ts +15 -0
  52. package/dist/people/households.js +31 -0
  53. package/dist/people/index.d.ts +8 -0
  54. package/dist/people/index.js +25 -0
  55. package/dist/people/lists.d.ts +30 -0
  56. package/dist/people/lists.js +37 -0
  57. package/dist/people/notes.d.ts +30 -0
  58. package/dist/people/notes.js +37 -0
  59. package/dist/people/organization.d.ts +12 -0
  60. package/dist/people/organization.js +15 -0
  61. package/dist/people/workflows.d.ts +37 -0
  62. package/dist/people/workflows.js +75 -0
  63. package/dist/testing/index.d.ts +9 -0
  64. package/dist/testing/index.js +24 -0
  65. package/dist/testing/recorder.d.ts +58 -0
  66. package/dist/testing/recorder.js +195 -0
  67. package/dist/testing/simple-builders.d.ts +33 -0
  68. package/dist/testing/simple-builders.js +124 -0
  69. package/dist/testing/simple-factories.d.ts +91 -0
  70. package/dist/testing/simple-factories.js +279 -0
  71. package/dist/testing/types.d.ts +160 -0
  72. package/dist/testing/types.js +5 -0
  73. package/dist/types/batch.d.ts +50 -0
  74. package/dist/types/batch.js +5 -0
  75. package/dist/types/client.d.ts +81 -0
  76. package/dist/types/client.js +5 -0
  77. package/dist/types/events.d.ts +85 -0
  78. package/dist/types/events.js +5 -0
  79. package/dist/types/people.d.ts +73 -79
  80. package/package.json +14 -3
  81. package/dist/people.d.ts +0 -205
  82. package/dist/people.js +0 -598
package/dist/helpers.js CHANGED
@@ -1,359 +1,399 @@
1
1
  "use strict";
2
- /**
3
- * Helper Functions for Common PCO People API Operations
4
- *
5
- * This module provides convenient helper functions for common operations
6
- * that combine multiple API calls or provide higher-level abstractions.
7
- */
8
2
  Object.defineProperty(exports, "__esModule", { value: true });
9
- exports.getCompletePersonProfile = getCompletePersonProfile;
3
+ exports.buildQueryParams = buildQueryParams;
4
+ exports.calculateAge = calculateAge;
5
+ exports.isValidEmail = isValidEmail;
6
+ exports.isValidPhone = isValidPhone;
7
+ exports.formatPersonName = formatPersonName;
8
+ exports.formatDate = formatDate;
9
+ exports.validatePersonData = validatePersonData;
10
+ exports.getPrimaryContact = getPrimaryContact;
10
11
  exports.createPersonWithContact = createPersonWithContact;
11
12
  exports.searchPeople = searchPeople;
12
13
  exports.getPeopleByHousehold = getPeopleByHousehold;
14
+ exports.getCompletePersonProfile = getCompletePersonProfile;
15
+ exports.getOrganizationInfo = getOrganizationInfo;
16
+ exports.getListsWithCategories = getListsWithCategories;
13
17
  exports.getPersonWorkflowCardsWithNotes = getPersonWorkflowCardsWithNotes;
14
18
  exports.createWorkflowCardWithNote = createWorkflowCardWithNote;
15
- exports.getListsWithCategories = getListsWithCategories;
16
- exports.getOrganizationInfo = getOrganizationInfo;
17
19
  exports.exportAllPeopleData = exportAllPeopleData;
18
- exports.validatePersonData = validatePersonData;
19
- exports.isValidEmail = isValidEmail;
20
- exports.isValidPhone = isValidPhone;
21
- exports.formatPersonName = formatPersonName;
22
- exports.getPrimaryContact = getPrimaryContact;
23
- exports.calculateAge = calculateAge;
24
- exports.formatDate = formatDate;
20
+ exports.extractFileUrl = extractFileUrl;
21
+ exports.isFileUrl = isFileUrl;
22
+ exports.getFileExtension = getFileExtension;
23
+ exports.getFilename = getFilename;
24
+ exports.isFileUpload = isFileUpload;
25
+ exports.processFileValue = processFileValue;
25
26
  const people_1 = require("./people");
26
- // ===== Person Management Helpers =====
27
27
  /**
28
- * Get a complete person profile with all related data
28
+ * Transform complex params object into flat query params for API calls
29
29
  */
30
- async function getCompletePersonProfile(client, personId) {
31
- const [personResponse, emailsResponse, phoneNumbersResponse, addressesResponse, socialProfilesResponse, fieldDataResponse, workflowCardsResponse, notesResponse,] = await Promise.all([
32
- (0, people_1.getPerson)(client, personId),
33
- (0, people_1.getPersonEmails)(client, personId),
34
- (0, people_1.getPersonPhoneNumbers)(client, personId),
35
- (0, people_1.getPersonAddresses)(client, personId),
36
- (0, people_1.getPersonSocialProfiles)(client, personId),
37
- (0, people_1.getPersonFieldData)(client, personId),
38
- (0, people_1.getWorkflowCards)(client, personId),
39
- (0, people_1.getNotes)(client, { where: { person_id: personId } }),
40
- ]);
41
- if (!personResponse.data) {
42
- throw new Error(`Person with ID ${personId} not found`);
30
+ function buildQueryParams(params) {
31
+ const queryParams = {};
32
+ if (params?.where) {
33
+ Object.entries(params.where).forEach(([key, value]) => {
34
+ queryParams[`where[${key}]`] = value;
35
+ });
43
36
  }
44
- return {
45
- addresses: addressesResponse.data,
46
- emails: emailsResponse.data,
47
- fieldData: fieldDataResponse.data,
48
- notes: notesResponse.data,
49
- person: personResponse.data,
50
- phoneNumbers: phoneNumbersResponse.data,
51
- socialProfiles: socialProfilesResponse.data,
52
- workflowCards: workflowCardsResponse.data,
53
- };
37
+ if (params?.include) {
38
+ queryParams.include = params.include.join(',');
39
+ }
40
+ if (params?.per_page) {
41
+ queryParams.per_page = params.per_page;
42
+ }
43
+ if (params?.page) {
44
+ queryParams.page = params.page;
45
+ }
46
+ if (params?.filter) {
47
+ queryParams.filter = params.filter;
48
+ }
49
+ return queryParams;
54
50
  }
55
51
  /**
56
- * Create a person with initial contact information
52
+ * Calculate age from birthdate string
57
53
  */
58
- async function createPersonWithContact(client, personData) {
59
- // Create the person first
60
- const personResponse = await (0, people_1.createPerson)(client, {
61
- first_name: personData.first_name,
62
- last_name: personData.last_name,
63
- });
64
- if (!personResponse.data) {
65
- throw new Error('Failed to create person');
54
+ function calculateAge(birthdate) {
55
+ const birth = new Date(birthdate);
56
+ const today = new Date();
57
+ let age = today.getFullYear() - birth.getFullYear();
58
+ const monthDiff = today.getMonth() - birth.getMonth();
59
+ if (monthDiff < 0 || (monthDiff === 0 && today.getDate() < birth.getDate())) {
60
+ age--;
66
61
  }
67
- const personId = personResponse.data.id;
68
- const results = { person: personResponse.data };
69
- // Add email if provided
70
- if (personData.email) {
71
- const emailResponse = await (0, people_1.createPersonEmail)(client, personId, {
72
- address: personData.email,
73
- location: 'Home',
74
- primary: true,
75
- });
76
- results.email = emailResponse.data;
62
+ return age;
63
+ }
64
+ /**
65
+ * Validate email format
66
+ */
67
+ function isValidEmail(email) {
68
+ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
69
+ return emailRegex.test(email);
70
+ }
71
+ /**
72
+ * Validate phone number format (basic validation)
73
+ */
74
+ function isValidPhone(phone) {
75
+ const phoneRegex = /^[\+]?[1-9][\d]{6,14}$/;
76
+ return phoneRegex.test(phone.replace(/[\s\-\(\)]/g, ''));
77
+ }
78
+ /**
79
+ * Format person name from attributes
80
+ */
81
+ function formatPersonName(person) {
82
+ const firstName = person.nickname || person.first_name || '';
83
+ const lastName = person.last_name || '';
84
+ if (firstName && lastName) {
85
+ return `${firstName} ${lastName}`;
77
86
  }
78
- // Add phone if provided
79
- if (personData.phone) {
80
- const phoneResponse = await (0, people_1.createPersonPhoneNumber)(client, personId, {
81
- location: 'Mobile',
82
- number: personData.phone,
83
- primary: true,
84
- });
85
- results.phone = phoneResponse.data;
87
+ else if (firstName) {
88
+ return firstName;
86
89
  }
87
- // Add address if provided
88
- if (personData.address) {
89
- const addressResponse = await (0, people_1.createPersonAddress)(client, personId, {
90
- ...personData.address,
91
- location: 'Home',
92
- primary: true,
93
- });
94
- results.address = addressResponse.data;
90
+ else if (lastName) {
91
+ return lastName;
95
92
  }
96
- return results;
93
+ return 'Unknown';
97
94
  }
98
95
  /**
99
- * Search for people by multiple criteria
96
+ * Format date string in various formats
100
97
  */
101
- async function searchPeople(client, criteria) {
102
- const whereConditions = {};
103
- if (criteria.name) {
104
- whereConditions.name = criteria.name;
98
+ function formatDate(dateString, format = 'short') {
99
+ const date = new Date(dateString);
100
+ if (isNaN(date.getTime())) {
101
+ return 'Invalid Date';
105
102
  }
106
- if (criteria.email) {
107
- whereConditions.email = criteria.email;
103
+ switch (format) {
104
+ case 'short':
105
+ return date.toLocaleDateString();
106
+ case 'long':
107
+ return date.toLocaleDateString('en-US', {
108
+ year: 'numeric',
109
+ month: 'long',
110
+ day: 'numeric'
111
+ });
112
+ case 'iso':
113
+ return date.toISOString().split('T')[0];
114
+ default:
115
+ return date.toLocaleDateString();
108
116
  }
109
- if (criteria.phone) {
110
- whereConditions.phone = criteria.phone;
117
+ }
118
+ /**
119
+ * Validate person data
120
+ */
121
+ function validatePersonData(data) {
122
+ const errors = [];
123
+ if (data.email && typeof data.email === 'string' && !isValidEmail(data.email)) {
124
+ errors.push('Invalid email format');
111
125
  }
112
- if (criteria.status) {
113
- whereConditions.status = criteria.status;
126
+ if (data.phone && typeof data.phone === 'string' && !isValidPhone(data.phone)) {
127
+ errors.push('Invalid phone format');
114
128
  }
115
- if (criteria.household) {
116
- whereConditions.household = criteria.household;
129
+ if (data.birthdate) {
130
+ const birthDate = new Date(data.birthdate);
131
+ if (isNaN(birthDate.getTime())) {
132
+ errors.push('Invalid birthdate format');
133
+ }
117
134
  }
118
- const response = await (0, people_1.getPeople)(client, {
119
- include: ['emails', 'phone_numbers', 'household'],
120
- where: whereConditions,
121
- });
122
- return response.data;
135
+ return {
136
+ isValid: errors.length === 0,
137
+ errors
138
+ };
123
139
  }
124
140
  /**
125
- * Get people by household
141
+ * Get primary contact information for a person
126
142
  */
127
- async function getPeopleByHousehold(client, householdId) {
128
- const response = await (0, people_1.getPeople)(client, {
129
- include: ['emails', 'phone_numbers', 'addresses'],
130
- where: { household_id: householdId },
131
- });
132
- return response.data;
143
+ async function getPrimaryContact(client, personId, context) {
144
+ const [emails, phones, addresses] = await Promise.all([
145
+ (0, people_1.getPersonEmails)(client, personId, context),
146
+ (0, people_1.getPersonPhoneNumbers)(client, personId, context),
147
+ (0, people_1.getPersonAddresses)(client, personId, context)
148
+ ]);
149
+ const primaryEmail = emails.data.find((e) => e.attributes.primary);
150
+ const primaryPhone = phones.data.find((p) => p.attributes.primary);
151
+ const primaryAddress = addresses.data.find((a) => a.attributes.primary);
152
+ return {
153
+ email: primaryEmail?.attributes?.address || emails.data[0]?.attributes?.address,
154
+ phone: primaryPhone?.attributes?.number || phones.data[0]?.attributes?.number,
155
+ address: (primaryAddress?.attributes?.street || addresses.data[0]?.attributes?.street)
156
+ };
133
157
  }
134
- // ===== Workflow Management Helpers =====
135
158
  /**
136
- * Get all workflow cards for a person with their notes
159
+ * Create a person with contact information
137
160
  */
138
- async function getPersonWorkflowCardsWithNotes(client, personId) {
139
- const workflowCardsResponse = await (0, people_1.getWorkflowCards)(client, personId);
140
- const cardsWithNotes = await Promise.all(workflowCardsResponse.data.map(async (card) => {
141
- const notesResponse = await (0, people_1.getWorkflowCardNotes)(client, personId, card.id);
142
- return {
143
- ...card,
144
- notes: notesResponse.data,
145
- };
146
- }));
147
- return cardsWithNotes;
161
+ async function createPersonWithContact(client, personData, contactData, context) {
162
+ const person = await (0, people_1.createPerson)(client, personData, context);
163
+ const results = { person };
164
+ if (contactData?.email) {
165
+ results.email = await (0, people_1.createPersonEmail)(client, person.data.id, contactData.email, context);
166
+ }
167
+ if (contactData?.phone) {
168
+ results.phone = await (0, people_1.createPersonPhoneNumber)(client, person.data.id, contactData.phone, context);
169
+ }
170
+ if (contactData?.address) {
171
+ results.address = await (0, people_1.createPersonAddress)(client, person.data.id, contactData.address, context);
172
+ }
173
+ return results;
148
174
  }
149
175
  /**
150
- * Create a workflow card with an initial note
176
+ * Search people by multiple criteria
151
177
  */
152
- async function createWorkflowCardWithNote(client, personId, workflowId, cardData) {
153
- const cardResponse = await (0, people_1.createWorkflowCard)(client, workflowId, personId);
154
- if (!cardResponse.data) {
155
- throw new Error('Failed to create workflow card');
178
+ async function searchPeople(client, criteria, context) {
179
+ const where = {};
180
+ if (criteria.status) {
181
+ where.status = criteria.status;
182
+ }
183
+ if (criteria.name) {
184
+ where.name = criteria.name;
156
185
  }
157
- const results = { card: cardResponse.data };
158
- if (cardData.initialNote) {
159
- const noteResponse = await (0, people_1.createWorkflowCardNote)(client, personId, cardResponse.data.id, { note: cardData.initialNote });
160
- results.note = noteResponse.data;
186
+ if (criteria.email) {
187
+ where.email = criteria.email;
161
188
  }
162
- return results;
189
+ return (0, people_1.getPeople)(client, {
190
+ where,
191
+ per_page: criteria.per_page || 25
192
+ }, context);
163
193
  }
164
- // ===== List Management Helpers =====
165
194
  /**
166
- * Get all lists with their categories
195
+ * Get people by household
167
196
  */
168
- async function getListsWithCategories(client) {
169
- const [listsResponse, categoriesResponse] = await Promise.all([
170
- (0, people_1.getLists)(client, { include: ['category'] }),
171
- (0, people_1.getLists)(client), // This should be getListCategories when available
197
+ async function getPeopleByHousehold(client, householdId, context) {
198
+ return (0, people_1.getPeople)(client, {
199
+ where: { household_id: householdId },
200
+ include: ['household']
201
+ }, context);
202
+ }
203
+ /**
204
+ * Get complete person profile with all related data
205
+ */
206
+ async function getCompletePersonProfile(client, personId, context) {
207
+ const [person, emails, phones, addresses, fieldData, workflowCards] = await Promise.all([
208
+ (0, people_1.getPerson)(client, personId, ['household'], context),
209
+ (0, people_1.getPersonEmails)(client, personId, context),
210
+ (0, people_1.getPersonPhoneNumbers)(client, personId, context),
211
+ (0, people_1.getPersonAddresses)(client, personId, context),
212
+ (0, people_1.getPersonFieldData)(client, personId, context),
213
+ (0, people_1.getWorkflowCards)(client, personId, context)
172
214
  ]);
173
- // For now, return lists with basic category info
174
- return listsResponse.data.map(list => ({
175
- ...list,
176
- category: list.relationships?.category?.data,
177
- }));
215
+ return {
216
+ person,
217
+ emails,
218
+ phones,
219
+ addresses,
220
+ fieldData,
221
+ workflowCards
222
+ };
178
223
  }
179
- // ===== Organization Helpers =====
180
224
  /**
181
- * Get organization information with statistics
225
+ * Get organization info with statistics
182
226
  */
183
- async function getOrganizationInfo(client) {
184
- const [organizationResponse, peopleResponse, householdsResponse, listsResponse, workflowsResponse,] = await Promise.all([
185
- (0, people_1.getOrganization)(client),
186
- (0, people_1.getPeople)(client, { per_page: 1 }),
187
- (0, people_1.getHouseholds)(client, { per_page: 1 }),
188
- (0, people_1.getLists)(client, { per_page: 1 }),
189
- (0, people_1.getWorkflows)(client, { per_page: 1 }),
227
+ async function getOrganizationInfo(client, context) {
228
+ const [organization, people, households, lists] = await Promise.all([
229
+ (0, people_1.getOrganization)(client, undefined, context),
230
+ (0, people_1.getPeople)(client, { per_page: 1 }, context),
231
+ (0, people_1.getHouseholds)(client, { per_page: 1 }, context),
232
+ (0, people_1.getLists)(client, { per_page: 1 }, context)
190
233
  ]);
191
- if (!organizationResponse.data) {
192
- throw new Error('Failed to get organization data');
193
- }
194
234
  return {
195
- organization: organizationResponse.data,
235
+ organization,
196
236
  stats: {
197
- totalHouseholds: Number(householdsResponse.meta?.total_count) || 0,
198
- totalLists: Number(listsResponse.meta?.total_count) || 0,
199
- totalPeople: Number(peopleResponse.meta?.total_count) || 0,
200
- totalWorkflows: Number(workflowsResponse.meta?.total_count) || 0,
201
- },
237
+ totalPeople: Number(people.meta?.total_count) || 0,
238
+ totalHouseholds: Number(households.meta?.total_count) || 0,
239
+ totalLists: Number(lists.meta?.total_count) || 0
240
+ }
202
241
  };
203
242
  }
204
- // ===== Data Export Helpers =====
205
243
  /**
206
- * Export all people data to a structured format
244
+ * Get lists with their categories
245
+ */
246
+ async function getListsWithCategories(client, context) {
247
+ const [lists, categories] = await Promise.all([
248
+ (0, people_1.getLists)(client, { include: ['list_category'] }, context),
249
+ (0, people_1.getListCategories)(client, undefined, context)
250
+ ]);
251
+ return { lists, categories };
252
+ }
253
+ /**
254
+ * Get workflow cards with notes for a person
207
255
  */
208
- async function exportAllPeopleData(client, options = {}) {
209
- const { batchSize = 50, includeFieldData = false, includeInactive = false, includeWorkflowCards = false, } = options;
210
- const whereConditions = {};
256
+ async function getPersonWorkflowCardsWithNotes(client, personId, context) {
257
+ const workflowCards = await (0, people_1.getWorkflowCards)(client, personId, context);
258
+ const notes = {};
259
+ for (const card of workflowCards.data) {
260
+ try {
261
+ notes[card.id] = await (0, people_1.getWorkflowCardNotes)(client, personId, card.id, context);
262
+ }
263
+ catch (error) {
264
+ notes[card.id] = { data: [], meta: { total_count: 0 } };
265
+ }
266
+ }
267
+ return { workflowCards, notes };
268
+ }
269
+ /**
270
+ * Create a workflow card with a note
271
+ */
272
+ async function createWorkflowCardWithNote(client, workflowId, personId, noteData, context) {
273
+ const workflowCard = await (0, people_1.createWorkflowCard)(client, workflowId, personId, context);
274
+ const note = await (0, people_1.createWorkflowCardNote)(client, personId, workflowCard.data.id, noteData, context);
275
+ return { workflowCard, note };
276
+ }
277
+ /**
278
+ * Export all people data in a structured format
279
+ */
280
+ async function exportAllPeopleData(client, options = {}, context) {
281
+ const { includeInactive = false, includeFieldData = false, includeWorkflowCards = false, perPage = 100 } = options;
282
+ const where = {};
211
283
  if (!includeInactive) {
212
- whereConditions.status = 'active';
284
+ where.status = 'active';
213
285
  }
214
- const includeArray = [
215
- 'emails',
216
- 'phone_numbers',
217
- 'addresses',
218
- 'social_profiles',
219
- ];
286
+ const include = ['household'];
220
287
  if (includeFieldData) {
221
- includeArray.push('field_data');
288
+ include.push('field_data');
222
289
  }
223
290
  if (includeWorkflowCards) {
224
- includeArray.push('workflow_cards');
225
- }
226
- let allPeople = [];
227
- let page = 1;
228
- let hasMore = true;
229
- while (hasMore) {
230
- const response = await (0, people_1.getPeople)(client, {
231
- include: includeArray,
232
- page,
233
- per_page: batchSize,
234
- where: whereConditions,
235
- });
236
- allPeople = allPeople.concat(response.data);
237
- hasMore = response.links?.next ? true : false;
238
- page++;
291
+ include.push('workflow_cards');
239
292
  }
240
- return allPeople.map(person => ({
241
- addresses: person.relationships?.addresses?.data || [],
242
- emails: person.relationships?.emails?.data || [],
243
- person,
244
- phoneNumbers: person.relationships?.phone_numbers?.data || [],
245
- socialProfiles: person.relationships?.social_profiles?.data || [],
246
- ...(includeFieldData && {
247
- fieldData: person.relationships?.field_data?.data || [],
248
- }),
249
- ...(includeWorkflowCards && {
250
- workflowCards: person.relationships?.workflow_cards?.data || [],
251
- }),
252
- }));
293
+ const [people, households, lists, organization] = await Promise.all([
294
+ (0, people_1.getPeople)(client, { where, include, per_page: perPage }, context),
295
+ (0, people_1.getHouseholds)(client, { per_page: perPage }, context),
296
+ (0, people_1.getLists)(client, { per_page: perPage }, context),
297
+ (0, people_1.getOrganization)(client, undefined, context)
298
+ ]);
299
+ return {
300
+ people: people.data,
301
+ households: households.data,
302
+ lists: lists.data,
303
+ organization: organization.data,
304
+ exportDate: new Date().toISOString(),
305
+ totalCount: Number(people.meta?.total_count) || 0
306
+ };
253
307
  }
254
- // ===== Validation Helpers =====
308
+ // ===== File Handling Utilities =====
255
309
  /**
256
- * Validate person data before creation/update
310
+ * Extracts clean URL from HTML markup that contains file links
311
+ * Handles cases like: <a href="https://onark.s3.us-east-1.amazonaws.com/file.pdf" download>View File: https://onark.s3.us-east-1.amazonaws.com/file.pdf</a>
257
312
  */
258
- function validatePersonData(data) {
259
- const errors = [];
260
- if (!data.first_name && !data.last_name) {
261
- errors.push('Either first_name or last_name is required');
313
+ function extractFileUrl(value) {
314
+ // If it's already a clean URL, return it
315
+ if (value.startsWith('http') && !value.includes('<')) {
316
+ return value;
262
317
  }
263
- if (data.email && !isValidEmail(data.email)) {
264
- errors.push('Invalid email format');
318
+ // Extract URL from HTML anchor tag
319
+ const hrefMatch = /href=["']([^"']+)["']/.exec(value);
320
+ if (hrefMatch) {
321
+ return hrefMatch[1];
265
322
  }
266
- if (data.phone && !isValidPhone(data.phone)) {
267
- errors.push('Invalid phone format');
323
+ // Extract URL from text content (fallback)
324
+ const urlMatch = /(https?:\/\/[^\s<>"']+)/.exec(value);
325
+ if (urlMatch) {
326
+ return urlMatch[1];
268
327
  }
269
- return {
270
- errors,
271
- isValid: errors.length === 0,
272
- };
328
+ // If no URL found, return original value
329
+ return value;
273
330
  }
274
331
  /**
275
- * Validate email format
332
+ * Determines if a value contains a file URL
276
333
  */
277
- function isValidEmail(email) {
278
- const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
279
- return emailRegex.test(email);
334
+ function isFileUrl(value) {
335
+ const cleanUrl = extractFileUrl(value);
336
+ return (cleanUrl.includes('s3.') ||
337
+ cleanUrl.includes('amazonaws.com') ||
338
+ cleanUrl.includes('onark.s3.'));
280
339
  }
281
340
  /**
282
- * Validate phone format (basic validation)
341
+ * Gets file extension from URL
283
342
  */
284
- function isValidPhone(phone) {
285
- const phoneRegex = /^[\+]?[1-9][\d]{0,15}$/;
286
- return phoneRegex.test(phone.replace(/[\s\-\(\)]/g, ''));
343
+ function getFileExtension(url) {
344
+ const cleanUrl = extractFileUrl(url);
345
+ const match = /\.([a-zA-Z0-9]+)(?:[?#]|$)/.exec(cleanUrl);
346
+ return match ? match[1].toLowerCase() : '';
287
347
  }
288
- // ===== Utility Helpers =====
289
348
  /**
290
- * Format person name for display
349
+ * Gets filename from URL
291
350
  */
292
- function formatPersonName(person) {
293
- const attrs = person.attributes;
294
- if (attrs?.first_name && attrs?.last_name) {
295
- return `${attrs.first_name} ${attrs.last_name}`;
296
- }
297
- if (attrs?.first_name) {
298
- return attrs.first_name;
299
- }
300
- if (attrs?.last_name) {
301
- return attrs.last_name;
302
- }
303
- if (attrs?.name) {
304
- return attrs.name;
305
- }
306
- return 'Unknown';
351
+ function getFilename(url) {
352
+ const cleanUrl = extractFileUrl(url);
353
+ const urlParts = cleanUrl.split('/');
354
+ return urlParts[urlParts.length - 1] || 'file';
307
355
  }
308
356
  /**
309
- * Get primary contact information for a person
357
+ * Determines if a field value represents a file upload
310
358
  */
311
- function getPrimaryContact(person, relatedData) {
312
- const primaryEmail = relatedData.emails?.find(email => email.attributes?.primary);
313
- const primaryPhone = relatedData.phoneNumbers?.find(phone => phone.attributes?.primary);
314
- return {
315
- email: primaryEmail?.attributes?.address,
316
- phone: primaryPhone?.attributes?.number,
317
- };
359
+ function isFileUpload(value) {
360
+ return isFileUrl(value) || value.includes('<a href=');
318
361
  }
319
362
  /**
320
- * Calculate age from birthdate
363
+ * Gets MIME type from file extension
321
364
  */
322
- function calculateAge(birthdate) {
323
- if (!birthdate)
324
- return null;
325
- const birth = new Date(birthdate);
326
- const today = new Date();
327
- if (isNaN(birth.getTime()))
328
- return null;
329
- let age = today.getFullYear() - birth.getFullYear();
330
- const monthDiff = today.getMonth() - birth.getMonth();
331
- if (monthDiff < 0 || (monthDiff === 0 && today.getDate() < birth.getDate())) {
332
- age--;
333
- }
334
- return age;
365
+ function getMimeType(extension) {
366
+ const mimeTypes = {
367
+ csv: 'text/csv',
368
+ doc: 'application/msword',
369
+ docx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
370
+ gif: 'image/gif',
371
+ jpeg: 'image/jpeg',
372
+ jpg: 'image/jpeg',
373
+ pdf: 'application/pdf',
374
+ png: 'image/png',
375
+ txt: 'text/plain',
376
+ xls: 'application/vnd.ms-excel',
377
+ xlsx: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
378
+ };
379
+ return mimeTypes[extension] || 'application/octet-stream';
335
380
  }
336
381
  /**
337
- * Format date for display
382
+ * Processes file upload value for PCO field
383
+ * Returns clean URL for text fields, or file data for file fields
338
384
  */
339
- function formatDate(dateString, format = 'short') {
340
- if (!dateString)
341
- return '';
342
- const date = new Date(dateString);
343
- if (isNaN(date.getTime()))
344
- return dateString;
345
- switch (format) {
346
- case 'short':
347
- return date.toLocaleDateString();
348
- case 'long':
349
- return date.toLocaleDateString('en-US', {
350
- day: 'numeric',
351
- month: 'long',
352
- year: 'numeric',
353
- });
354
- case 'iso':
355
- return date.toISOString();
356
- default:
357
- return dateString;
385
+ function processFileValue(value, fieldType = 'text') {
386
+ const cleanUrl = extractFileUrl(value);
387
+ if (fieldType === 'text') {
388
+ return cleanUrl;
358
389
  }
390
+ // For file fields, return metadata object
391
+ const extension = getFileExtension(cleanUrl);
392
+ const filename = getFilename(cleanUrl);
393
+ const contentType = getMimeType(extension);
394
+ return {
395
+ contentType,
396
+ filename,
397
+ url: cleanUrl,
398
+ };
359
399
  }