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
208 lines
6 KiB
TypeScript
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();
|