wulf-pulse/lib/services/salesbldr-client.ts
root 9f912aed24 feat: add authentication, user management, and admin features
Added comprehensive authentication and authorization system:

Authentication System:
- Better Auth integration with session management
- Login/logout pages and API routes
- Middleware for route protection
- Auth utilities and client libraries

User Management:
- User list, detail, and invite pages
- User API endpoints (CRUD operations)
- Session management for users
- Profile settings page

Role-Based Access Control:
- Role management pages (list, create, edit)
- Permission system with granular controls
- Role assignment to users
- Role API endpoints

Admin Features:
- Audit log page for tracking system events
- Admin settings page
- Audit service for logging user actions

Additional Features:
- Quotes management pages and components
- SalesBldr API integration
- Email service for notifications

Configuration & Documentation:
- Updated docker-compose.yml
- MCP server configuration (mcp.json)
- CVE-2025-55182 security review documentation
- Standards guide and PRD documents
- Re-enabling authentication documentation

Database Migrations:
- 012: Auth tables (users, sessions, accounts, verifications)
- 013: Role tables (roles, permissions, role_permissions, user_roles)
- 014: Admin settings table

UI Updates:
- Updated dashboard layout
- Enhanced app layout with auth integration
2026-01-31 12:43:14 -05:00

208 lines
6 KiB
TypeScript

interface SalesBldrConfig {
apiUrl: string;
apiKey: string;
}
interface QuoteItem {
id: string;
category?: object;
cost?: number;
mpn?: string;
markup?: number;
deliveryDate?: string;
discount?: number;
unit?: string;
term?: string;
name: string;
price?: number;
quantity: number;
shortDescription?: string;
}
interface Quote {
id: string;
title: string;
number: string;
status: string;
createdAt: string;
sentAt?: string;
expiresAt?: string;
approvedAt?: string;
company?: {
id: string;
name: string;
externalIdentifier?: string;
};
contact?: object;
owner?: object;
items?: QuoteItem[];
opportunity?: {
id: string;
name: string;
description?: string;
};
}
interface QuoteSearchResponse {
results: Quote[];
total: number;
filters?: any[];
sortOptions?: any[];
}
export class SalesBldrClient {
private config: SalesBldrConfig;
constructor(config?: SalesBldrConfig) {
this.config = config || {
apiUrl: process.env.SALESBLDR_API_URL || '',
apiKey: process.env.SALESBLDR_API_KEY || ''
};
}
private ensureConfig() {
if (!this.config.apiUrl || !this.config.apiKey) {
throw new Error('SalesBldr API URL and API Key are required');
}
}
private async request<T>(
endpoint: string,
options: RequestInit = {}
): Promise<T> {
this.ensureConfig();
const url = `${this.config.apiUrl}${endpoint}`;
const headers: HeadersInit = {
'api-key': this.config.apiKey,
'Content-Type': 'application/json',
...options.headers,
};
const response = await fetch(url, {
...options,
headers,
});
if (!response.ok) {
const errorText = await response.text();
throw new Error(
`SalesBldr API error: ${response.status} ${response.statusText} - ${errorText}`
);
}
return response.json();
}
async getQuotes(params?: {
sort?: string;
filters?: string;
query?: string;
size?: number;
from?: number;
}): Promise<QuoteSearchResponse> {
const queryParams = new URLSearchParams();
if (params?.sort) queryParams.append('sort', params.sort);
if (params?.filters) queryParams.append('filters', params.filters);
if (params?.query) queryParams.append('query', params.query);
if (params?.size !== undefined) queryParams.append('size', params.size.toString());
if (params?.from !== undefined) queryParams.append('from', params.from.toString());
const endpoint = `/public-api/quote${queryParams.toString() ? `?${queryParams.toString()}` : ''}`;
return this.request<QuoteSearchResponse>(endpoint);
}
async getOpenQuotes(size: number = 10): Promise<QuoteSearchResponse> {
return this.getQuotes({
filters: 'status:sent|draft',
sort: '-createdAt',
size
});
}
async getApprovedQuotes(size: number = 10): Promise<Quote[]> {
const endpoint = `/public-api/quote/approved`;
return this.request<Quote[]>(endpoint);
}
async getOpportunities(params?: {
sort?: string;
filters?: string;
query?: string;
size?: number;
from?: number;
}): Promise<any> {
const queryParams = new URLSearchParams();
if (params?.sort) queryParams.append('sort', params.sort);
if (params?.filters) queryParams.append('filters', params.filters);
if (params?.query) queryParams.append('query', params.query);
if (params?.size !== undefined) queryParams.append('size', params.size.toString());
if (params?.from !== undefined) queryParams.append('from', params.from.toString());
const endpoint = `/public-api/opportunity${queryParams.toString() ? `?${queryParams.toString()}` : ''}`;
return this.request(endpoint);
}
async getProducts(params?: {
includeBundles?: boolean;
sort?: string;
filters?: string;
query?: string;
size?: number;
from?: number;
}): Promise<any> {
const queryParams = new URLSearchParams();
if (params?.includeBundles !== undefined) queryParams.append('includeBundles', params.includeBundles.toString());
if (params?.sort) queryParams.append('sort', params.sort);
if (params?.filters) queryParams.append('filters', params.filters);
if (params?.query) queryParams.append('query', params.query);
if (params?.size !== undefined) queryParams.append('size', params.size.toString());
if (params?.from !== undefined) queryParams.append('from', params.from.toString());
const endpoint = `/public-api/product${queryParams.toString() ? `?${queryParams.toString()}` : ''}`;
return this.request(endpoint);
}
async getCompanies(params?: {
sort?: string;
filters?: string;
query?: string;
size?: number;
from?: number;
}): Promise<any> {
const queryParams = new URLSearchParams();
if (params?.sort) queryParams.append('sort', params.sort);
if (params?.filters) queryParams.append('filters', params.filters);
if (params?.query) queryParams.append('query', params.query);
if (params?.size !== undefined) queryParams.append('size', params.size.toString());
if (params?.from !== undefined) queryParams.append('from', params.from.toString());
const endpoint = `/public-api/company${queryParams.toString() ? `?${queryParams.toString()}` : ''}`;
return this.request(endpoint);
}
async getContacts(params?: {
sort?: string;
filters?: string;
query?: string;
size?: number;
from?: number;
}): Promise<any> {
const queryParams = new URLSearchParams();
if (params?.sort) queryParams.append('sort', params.sort);
if (params?.filters) queryParams.append('filters', params.filters);
if (params?.query) queryParams.append('query', params.query);
if (params?.size !== undefined) queryParams.append('size', params.size.toString());
if (params?.from !== undefined) queryParams.append('from', params.from.toString());
const endpoint = `/public-api/contact${queryParams.toString() ? `?${queryParams.toString()}` : ''}`;
return this.request(endpoint);
}
}
export const salesBldrClient = new SalesBldrClient();