cre-sync/lib/settings/settings-service.ts
BusyBee3333 4e6467ffb0 Add CRESync CRM application with Setup page
- Build complete Next.js CRM for commercial real estate
- Add authentication with JWT sessions and role-based access
- Add GoHighLevel API integration for contacts, conversations, opportunities
- Add AI-powered Control Center with tool calling
- Add Setup page with onboarding checklist (/setup)
- Add sidebar navigation with Setup menu item
- Fix type errors in onboarding API, GHL services, and control center tools
- Add Prisma schema with SQLite for local development
- Add UI components with clay morphism design system

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-14 17:30:55 -05:00

201 lines
5.2 KiB
TypeScript

import { prisma } from '@/lib/db';
import { encrypt, decrypt, maskValue } from './encryption';
import { SystemSettings } from '@/types/admin';
// Keys that should be encrypted
const ENCRYPTED_KEYS = [
'ghlClientSecret',
'ghlAccessToken',
'ghlRefreshToken',
'ghlAgencyApiKey',
'ghlPrivateToken',
'ghlWebhookSecret',
'stripeSecretKey',
'stripeWebhookSecret',
'clickupApiKey',
'claudeApiKey',
'openaiApiKey',
'mcpServerUrl',
];
export class SettingsService {
// Get a single setting
async get(key: string): Promise<string | null> {
const setting = await prisma.systemSettings.findUnique({
where: { key },
});
if (!setting) return null;
return setting.isEncrypted ? decrypt(setting.value) : setting.value;
}
// Get all settings
async getAll(): Promise<SystemSettings> {
const settings = await prisma.systemSettings.findMany();
const result: Record<string, any> = {};
for (const setting of settings) {
result[setting.key] = setting.isEncrypted
? decrypt(setting.value)
: setting.value;
}
return result as SystemSettings;
}
// Get all settings with sensitive values masked (for display)
async getAllMasked(): Promise<Record<string, string>> {
const settings = await prisma.systemSettings.findMany();
const result: Record<string, string> = {};
for (const setting of settings) {
if (setting.isEncrypted) {
const decrypted = decrypt(setting.value);
result[setting.key] = maskValue(decrypted);
} else {
result[setting.key] = setting.value;
}
}
return result;
}
// Set a single setting
async set(key: string, value: string, updatedBy?: string): Promise<void> {
const isEncrypted = ENCRYPTED_KEYS.includes(key);
const storedValue = isEncrypted ? encrypt(value) : value;
await prisma.systemSettings.upsert({
where: { key },
update: {
value: storedValue,
isEncrypted,
updatedBy,
updatedAt: new Date(),
},
create: {
key,
value: storedValue,
isEncrypted,
updatedBy,
},
});
}
// Set multiple settings at once
async setMany(
settings: Partial<SystemSettings>,
updatedBy?: string
): Promise<void> {
const operations = Object.entries(settings).map(([key, value]) => {
if (value === undefined || value === null) return null;
const isEncrypted = ENCRYPTED_KEYS.includes(key);
const storedValue = isEncrypted ? encrypt(String(value)) : String(value);
return prisma.systemSettings.upsert({
where: { key },
update: {
value: storedValue,
isEncrypted,
updatedBy,
updatedAt: new Date(),
},
create: {
key,
value: storedValue,
isEncrypted,
updatedBy,
},
});
}).filter(Boolean);
await prisma.$transaction(operations as any);
}
// Delete a setting
async delete(key: string): Promise<void> {
await prisma.systemSettings.delete({
where: { key },
});
}
// Test GHL connection with current settings
async testGHLConnection(): Promise<{ success: boolean; error?: string; details?: any }> {
try {
const accessToken = await this.get('ghlAccessToken');
const locationId = await this.get('ghlLocationId');
if (!accessToken) {
return { success: false, error: 'Missing GHL Access Token. Please configure OAuth credentials.' };
}
if (!locationId) {
return { success: false, error: 'Missing GHL Location ID.' };
}
// Test v2 API connection by fetching location details
const response = await fetch(
`https://services.leadconnectorhq.com/locations/${locationId}`,
{
headers: {
'Authorization': `Bearer ${accessToken}`,
'Version': '2021-07-28',
'Accept': 'application/json',
},
}
);
if (!response.ok) {
const errorText = await response.text();
return {
success: false,
error: `GHL API returned ${response.status}: ${response.statusText}`,
details: errorText
};
}
const data = await response.json();
return {
success: true,
details: { locationName: data.location?.name || data.name || 'Connected' }
};
} catch (error) {
return {
success: false,
error: error instanceof Error ? error.message : 'Unknown error'
};
}
}
// Test Stripe connection
async testStripeConnection(): Promise<{ success: boolean; error?: string }> {
try {
const secretKey = await this.get('stripeSecretKey');
if (!secretKey) {
return { success: false, error: 'Missing Stripe secret key' };
}
// We'll implement actual Stripe test later
// For now, just check the key format
if (!secretKey.startsWith('sk_')) {
return { success: false, error: 'Invalid Stripe key format' };
}
return { success: true };
} catch (error) {
return {
success: false,
error: error instanceof Error ? error.message : 'Unknown error'
};
}
}
}
// Singleton instance
export const settingsService = new SettingsService();