ministry-platform-provider
Version:
TypeScript client library for Ministry Platform API integration
390 lines (310 loc) • 12.4 kB
Markdown
# Ministry Platform Services Documentation
## Overview
The services directory contains specialized service classes that handle specific domains of Ministry Platform functionality. Each service focuses on a particular aspect of the API while sharing common patterns for authentication, error handling, and HTTP communication.
## Service Architecture
```
ministryPlatformProvider
↓
┌─────────────────┬─────────────────┬─────────────────┐
│ TableService │ ProcedureService│CommunicationSvc │
│ DomainService │ MetadataService │ FileService │
└─────────────────┴─────────────────┴─────────────────┘
↓
MinistryPlatformClient
```
## Common Service Patterns
All services follow these consistent patterns:
### Constructor Pattern
```typescript
export class ServiceName {
private client: MinistryPlatformClient;
constructor(client: MinistryPlatformClient) {
this.client = client;
}
}
```
### Method Pattern
```typescript
public async methodName(params): Promise<ReturnType> {
try {
await this.client.ensureValidToken();
const endpoint = '/api/endpoint';
return await this.client.getHttpClient().method<ReturnType>(endpoint, params);
} catch (error) {
console.error('Error message:', error);
throw error;
}
}
```
## Service Classes
### 1. TableService
**Purpose**: Handles all CRUD operations for Ministry Platform tables
**Key Methods**:
- `getTableRecords<T>()` - Query records from any table
- `createTableRecords<T>()` - Create new records
- `updateTableRecords<T>()` - Update existing records
- `deleteTableRecords<T>()` - Delete records by ID
**Usage Examples**:
```typescript
// Get contacts
const contacts = await tableService.getTableRecords<Contact>('Contacts', {
$filter: 'Contact_Status_ID=1',
$select: 'Contact_ID,Display_Name,Email_Address'
});
// Create new contact
const newContacts = await tableService.createTableRecords('Contacts', [{
First_Name: 'John',
Last_Name: 'Doe',
Email_Address: 'john@example.com'
}]);
```
### 2. ProcedureService
**Purpose**: Executes stored procedures with parameter handling
**Key Methods**:
- `getProcedures()` - List available stored procedures
- `executeProcedure()` - Execute with query parameters
- `executeProcedureWithBody()` - Execute with request body parameters
**Usage Examples**:
```typescript
// Get available procedures
const procedures = await procedureService.getProcedures('api_');
// Execute procedure with query params
const results = await procedureService.executeProcedure('api_GetContacts', {
HouseholdId: 123
});
// Execute procedure with body params
const results = await procedureService.executeProcedureWithBody('api_Search', {
SearchCriteria: { FirstName: 'John' }
});
```
### 3. CommunicationService
**Purpose**: Handles email and SMS communications with attachment support
**Key Methods**:
- `createCommunication()` - Create bulk communications
- `sendMessage()` - Send direct messages
- `createCommunicationWithAttachments()` - Handle file attachments (private)
- `sendMessageWithAttachments()` - Handle message attachments (private)
**Usage Examples**:
```typescript
// Create bulk email
const communication = await communicationService.createCommunication({
AuthorUserId: 1,
Subject: 'Newsletter',
Body: '<h1>Weekly Update</h1>',
CommunicationType: 'Email',
Contacts: [12345, 67890]
});
// Send direct message
const message = await communicationService.sendMessage({
FromAddress: { DisplayName: 'Pastor', Address: 'pastor@church.com' },
ToAddresses: [{ DisplayName: 'Member', Address: 'member@example.com' }],
Subject: 'Personal note',
Body: 'Thank you for visiting!'
});
```
### 4. FileService
**Purpose**: Manages file uploads, downloads, and metadata
**Key Methods**:
- `getFilesByRecord()` - Get files attached to records
- `uploadFiles()` - Upload multiple files to records
- `updateFile()` - Update file content/metadata
- `deleteFile()` - Delete files
- `getFileContentByUniqueId()` - Public file download
- `getFileMetadata()` - Get file information
**Usage Examples**:
```typescript
// Upload files to contact
const uploadedFiles = await fileService.uploadFiles('Contacts', 12345, [file1, file2], {
description: 'Profile documents',
isDefaultImage: true
});
// Get files for record
const contactFiles = await fileService.getFilesByRecord('Contacts', 12345);
// Download file content
const fileBlob = await fileService.getFileContentByUniqueId('unique-file-id');
```
### 5. MetadataService
**Purpose**: Handles schema information and metadata operations
**Key Methods**:
- `refreshMetadata()` - Refresh metadata cache
- `getTables()` - Get available tables with metadata
**Usage Examples**:
```typescript
// Refresh metadata cache
await metadataService.refreshMetadata();
// Get table information
const tables = await metadataService.getTables('contact');
tables.forEach(table => {
console.log(`${table.Name}: ${table.AccessLevel}`);
});
```
### 6. DomainService
**Purpose**: Provides domain configuration and global filter information
**Key Methods**:
- `getDomainInfo()` - Get domain configuration
- `getGlobalFilters()` - Get available global filters
**Usage Examples**:
```typescript
// Get domain information
const domainInfo = await domainService.getDomainInfo();
console.log('Domain:', domainInfo.DisplayName);
console.log('Time Zone:', domainInfo.TimeZoneName);
// Get global filters
const filters = await domainService.getGlobalFilters();
filters.forEach(filter => {
console.log(`Filter ${filter.Key}: ${filter.Value}`);
});
```
## Error Handling Patterns
### Standard Error Handling
```typescript
try {
await this.client.ensureValidToken();
return await this.client.getHttpClient().get<T>(endpoint, params);
} catch (error) {
console.error(`Error in ${serviceName}.${methodName}:`, error);
throw error;
}
```
### Enhanced Logging (TableService Example)
```typescript
console.log('TABLESERVICE: createTableRecords called');
console.log('TABLESERVICE: table:', table);
console.log('TABLESERVICE: records:', JSON.stringify(records, null, 2));
try {
console.log('TABLESERVICE: About to call ensureValidToken');
await this.client.ensureValidToken();
const result = await this.client.getHttpClient().post<T[]>(endpoint, records);
console.log('TABLESERVICE: Operation completed successfully');
return result;
} catch (error) {
console.error('TABLESERVICE: Error type:', error?.constructor?.name);
console.error('TABLESERVICE: Error message:', (error as Error)?.message);
throw error;
}
```
## Authentication Flow
Each service method follows this authentication pattern:
1. **Token Validation**: `await this.client.ensureValidToken()`
2. **HTTP Request**: Use the authenticated HTTP client
3. **Error Handling**: Catch and re-throw with context
```typescript
public async serviceMethod(): Promise<ResultType> {
try {
// Step 1: Ensure valid authentication
await this.client.ensureValidToken();
// Step 2: Make authenticated request
const endpoint = '/api/endpoint';
const result = await this.client.getHttpClient().get<ResultType>(endpoint);
// Step 3: Return result
return result;
} catch (error) {
// Step 4: Handle errors with context
console.error(`Error in ${this.constructor.name}.serviceMethod:`, error);
throw error;
}
}
```
## HTTP Method Usage
### GET Requests
- Used for: Reading data, executing procedures with query params
- Methods: `tableService.getTableRecords()`, `procedureService.executeProcedure()`
### POST Requests
- Used for: Creating records, sending communications, executing procedures with body
- Methods: `tableService.createTableRecords()`, `communicationService.createCommunication()`
### PUT Requests
- Used for: Updating records, updating files
- Methods: `tableService.updateTableRecords()`, `fileService.updateFile()`
### DELETE Requests
- Used for: Deleting records and files
- Methods: `tableService.deleteTableRecords()`, `fileService.deleteFile()`
### Form Data Requests
- Used for: File uploads with metadata
- Methods: `fileService.uploadFiles()`, `communicationService` with attachments
## Type Safety Features
### Generic Type Support
```typescript
// Service methods are generic
public async getTableRecords<T>(table: string, params?: TableQueryParams): Promise<T[]>
// Usage with type safety
const contacts = await service.getTableRecords<Contact>('Contacts', params);
// TypeScript knows contacts is Contact[]
```
### Interface Constraints
```typescript
// Records must extend TableRecord interface
public async createTableRecords<T extends TableRecord = TableRecord>(
table: string,
records: T[]
): Promise<T[]>
```
### Parameter Type Safety
```typescript
// Parameters use well-defined interfaces
export interface TableQueryParams {
$select?: string;
$filter?: string;
$orderby?: string;
// ... other parameters
}
```
## Performance Considerations
### Batch Operations
```typescript
// Services support batch operations for efficiency
const multipleRecords = await tableService.createTableRecords('Contacts', [
{ First_Name: 'John', Last_Name: 'Doe' },
{ First_Name: 'Jane', Last_Name: 'Smith' },
{ First_Name: 'Bob', Last_Name: 'Johnson' }
]);
```
### Query Optimization
```typescript
// Use $select to limit returned columns
const contacts = await tableService.getTableRecords('Contacts', {
$select: 'Contact_ID,Display_Name', // Only fetch needed columns
$filter: 'Contact_Status_ID=1', // Filter server-side
$top: 100 // Limit results
});
```
### File Handling
```typescript
// Upload multiple files in single request
const files = await fileService.uploadFiles('Contacts', recordId, [file1, file2, file3]);
// Use unique ID for public file access (no auth required)
const fileContent = await fileService.getFileContentByUniqueId(uniqueId);
```
## Best Practices
1. **Always Check Authentication**: Call `ensureValidToken()` before requests
2. **Use Type Safety**: Specify generic types for better IDE support
3. **Handle Errors Gracefully**: Wrap service calls in try-catch blocks
4. **Log Operations**: Use consistent logging patterns for debugging
5. **Batch When Possible**: Use array parameters for bulk operations
6. **Optimize Queries**: Use `$select` and `$filter` to reduce data transfer
7. **Resource Management**: Properly dispose of file Blobs after use
## Integration Examples
### Multi-Service Workflow
```typescript
class MembershipService {
constructor(private provider: ministryPlatformProvider) {}
async processNewMember(memberData: any, documents: File[]) {
// Create contact record
const [contact] = await this.provider.createTableRecords('Contacts', [memberData]);
// Upload membership documents
await this.provider.uploadFiles('Contacts', contact.Contact_ID, documents);
// Execute membership procedure
await this.provider.executeProcedure('api_ProcessMembership', {
ContactId: contact.Contact_ID
});
// Send welcome email
await this.provider.sendMessage({
FromAddress: { DisplayName: 'Church', Address: 'info@church.com' },
ToAddresses: [{ DisplayName: contact.Display_Name, Address: contact.Email_Address }],
Subject: 'Welcome!',
Body: 'Welcome to our church family!'
});
return contact;
}
}
```
This service architecture provides a clean separation of concerns while maintaining consistency and type safety across all Ministry Platform operations.