besper-frontend-site-dev-main
Version:
Professional B-esper Frontend Site - Site-wide integration toolkit for full website bot deployment
1,027 lines (893 loc) • 29 kB
JavaScript
/**
* Page Operators Service
* Provides secure, decorated functions for all data actions across pages
* Uses authentication and authorization checks for all operations
*/
import tokenAuthService from './tokenAuth.js';
import supportTicketsService from './supportTicketsService.js';
import notificationsService from './notificationsService.js';
import { getRootApiEndpoint } from './centralizedApi.js';
class PageOperatorsService {
constructor() {
this.authService = tokenAuthService;
this.supportTicketsService = supportTicketsService;
this.notificationsService = notificationsService;
this.operatorCache = new Map();
}
/**
* Decorator for authentication and authorization
*/
withAuth(operation) {
return async (...args) => {
try {
// Check authentication
if (!this.authService.isUserAuthenticated()) {
throw new Error('Authentication required for this operation');
}
// Verify contact access (for entities without Dataverse equivalents)
const contactId = this.authService.getUserPermission('contactId');
if (contactId) {
const canAccessContact = await this.authService.canAccessEntity(
'contact',
contactId,
['read']
);
if (!canAccessContact) {
throw new Error(
'Contact verification failed - operation not permitted'
);
}
}
// Execute the operation
return await operation(...args);
} catch (error) {
console.error('Operator failed:', error);
throw error;
}
};
}
/**
* Decorator for workspace-level authorization
*/
withWorkspaceAuth(requiredPermissions = ['read']) {
return operation => {
return async (entityData, ...args) => {
try {
// Check authentication first
if (!this.authService.isUserAuthenticated()) {
throw new Error('Authentication required for this operation');
}
// Check workspace access via foreign key relationships
const workspaceId =
entityData.workspace_id ||
this.authService.getUserPermission('workspaceId');
const accountId =
entityData.account_id ||
this.authService.getUserPermission('accountId');
const subscriptionId =
entityData.subscription_id ||
this.authService.getUserPermission('subscriptionId');
let hasAccess = false;
// Check access to workspace
if (workspaceId) {
hasAccess = await this.authService.canAccessEntity(
'workspace',
workspaceId,
requiredPermissions
);
}
// Check access to account if workspace access failed
if (!hasAccess && accountId) {
hasAccess = await this.authService.canAccessEntity(
'account',
accountId,
requiredPermissions
);
}
// Check access to subscription if other access failed
if (!hasAccess && subscriptionId) {
hasAccess = await this.authService.canAccessEntity(
'subscription',
subscriptionId,
requiredPermissions
);
}
if (!hasAccess) {
throw new Error(
'Access denied - insufficient permissions for related workspace/account/subscription'
);
}
// Execute the operation
return await operation(entityData, ...args);
} catch (error) {
console.error('Workspace-authorized operator failed:', error);
throw error;
}
};
};
}
// ========================================
// MY BOTS PAGE OPERATORS
// ========================================
/**
* Get user's accessible bots
*/
getBots = this.withAuth(async () => {
const response = await fetch(`${getRootApiEndpoint()}/bots`, {
method: 'GET',
headers: {
Authorization: `Bearer ${this.authService.getToken()}`,
'Content-Type': 'application/json',
},
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return await response.json();
});
/**
* Create new bot
*/
createBot = this.withWorkspaceAuth(['create'])(async botData => {
const response = await fetch(`${getRootApiEndpoint()}/bots`, {
method: 'POST',
headers: {
Authorization: `Bearer ${this.authService.getToken()}`,
'Content-Type': 'application/json',
},
body: JSON.stringify(botData),
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return await response.json();
});
/**
* Update bot configuration
*/
updateBot = this.withWorkspaceAuth(['write'])(async botData => {
const response = await fetch(`${getRootApiEndpoint()}/bots/${botData.id}`, {
method: 'PUT',
headers: {
Authorization: `Bearer ${this.authService.getToken()}`,
'Content-Type': 'application/json',
},
body: JSON.stringify(botData),
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return await response.json();
});
/**
* Delete bot
*/
deleteBot = this.withWorkspaceAuth(['delete'])(async botData => {
const response = await fetch(`${getRootApiEndpoint()}/bots/${botData.id}`, {
method: 'DELETE',
headers: {
Authorization: `Bearer ${this.authService.getToken()}`,
'Content-Type': 'application/json',
},
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return await response.json();
});
/**
* Get bot configuration
*/
getBotConfig = this.withWorkspaceAuth(['read'])(async botData => {
const response = await fetch(
`${getRootApiEndpoint()}/bots/${botData.id}/config`,
{
method: 'GET',
headers: {
Authorization: `Bearer ${this.authService.getToken()}`,
'Content-Type': 'application/json',
},
}
);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return await response.json();
});
/**
* Update bot configuration
*/
updateBotConfig = this.withWorkspaceAuth(['write'])(
async (botData, configData) => {
const response = await fetch(
`${getRootApiEndpoint()}/bots/${botData.id}/config`,
{
method: 'PUT',
headers: {
Authorization: `Bearer ${this.authService.getToken()}`,
'Content-Type': 'application/json',
},
body: JSON.stringify(configData),
}
);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return await response.json();
}
);
// ========================================
// USERS PAGE OPERATORS
// ========================================
/**
* Get workspace users
*/
getWorkspaceUsers = this.withWorkspaceAuth(['read'])(async workspaceData => {
const response = await fetch(
`${getRootApiEndpoint()}/workspaces/${workspaceData.workspace_id}/users`,
{
method: 'GET',
headers: {
Authorization: `Bearer ${this.authService.getToken()}`,
'Content-Type': 'application/json',
},
}
);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return await response.json();
});
/**
* Invite user to workspace
*/
inviteUser = this.withWorkspaceAuth(['write'])(
async (workspaceData, inviteData) => {
const response = await fetch(
`${getRootApiEndpoint()}/workspaces/${workspaceData.workspace_id}/users`,
{
method: 'POST',
headers: {
Authorization: `Bearer ${this.authService.getToken()}`,
'Content-Type': 'application/json',
},
body: JSON.stringify(inviteData),
}
);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return await response.json();
}
);
/**
* Update user permissions
*/
updateUserPermissions = this.withWorkspaceAuth(['write'])(
async (workspaceData, userId, permissions) => {
const response = await fetch(
`${getRootApiEndpoint()}/workspaces/${workspaceData.workspace_id}/users/${userId}/permissions`,
{
method: 'PUT',
headers: {
Authorization: `Bearer ${this.authService.getToken()}`,
'Content-Type': 'application/json',
},
body: JSON.stringify(permissions),
}
);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return await response.json();
}
);
/**
* Remove user from workspace
*/
removeUser = this.withWorkspaceAuth(['delete'])(
async (workspaceData, userId) => {
const response = await fetch(
`${getRootApiEndpoint()}/workspaces/${workspaceData.workspace_id}/users/${userId}`,
{
method: 'DELETE',
headers: {
Authorization: `Bearer ${this.authService.getToken()}`,
},
}
);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return await response.json();
}
);
// ========================================
// WORKSPACE MANAGEMENT OPERATORS
// ========================================
/**
* Get workspace details
*/
getWorkspace = this.withWorkspaceAuth(['read'])(async workspaceData => {
const response = await fetch(
`${getRootApiEndpoint()}/workspaces/${workspaceData.workspace_id}`,
{
method: 'GET',
headers: {
Authorization: `Bearer ${this.authService.getToken()}`,
'Content-Type': 'application/json',
},
}
);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return await response.json();
});
/**
* Update workspace settings
*/
updateWorkspace = this.withWorkspaceAuth(['write'])(
async (workspaceData, updateData) => {
const response = await fetch(
`${getRootApiEndpoint()}/workspaces/${workspaceData.workspace_id}`,
{
method: 'PUT',
headers: {
Authorization: `Bearer ${this.authService.getToken()}`,
'Content-Type': 'application/json',
},
body: JSON.stringify(updateData),
}
);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return await response.json();
}
);
/**
* Get workspace usage statistics
*/
getWorkspaceUsage = this.withWorkspaceAuth(['read'])(async workspaceData => {
const response = await fetch(
`${getRootApiEndpoint()}/workspaces/${workspaceData.workspace_id}/usage`,
{
method: 'GET',
headers: {
Authorization: `Bearer ${this.authService.getToken()}`,
'Content-Type': 'application/json',
},
}
);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return await response.json();
});
// ========================================
// SUBSCRIPTION MANAGEMENT OPERATORS
// ========================================
/**
* Get subscription details
*/
getSubscription = this.withAuth(async subscriptionId => {
// Check access to subscription
const canAccess = await this.authService.canAccessEntity(
'subscription',
subscriptionId,
['read']
);
if (!canAccess) {
throw new Error('Access denied to this subscription');
}
const response = await fetch(
`${getRootApiEndpoint()}/subscriptions/${subscriptionId}`,
{
method: 'GET',
headers: {
Authorization: `Bearer ${this.authService.getToken()}`,
'Content-Type': 'application/json',
},
}
);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return await response.json();
});
/**
* Get user's accessible subscriptions (cost pools)
*/
getAccessibleSubscriptions = this.withAuth(async () => {
const response = await fetch(`${getRootApiEndpoint()}/subscriptions`, {
method: 'GET',
headers: {
Authorization: `Bearer ${this.authService.getToken()}`,
'Content-Type': 'application/json',
},
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return await response.json();
});
/**
* Verify subscription access and payment method status
*/
verifySubscriptionForPurchase = this.withAuth(async subscriptionId => {
// Check access to subscription
const canAccess = await this.authService.canAccessEntity(
'subscription',
subscriptionId,
['read', 'write']
);
if (!canAccess) {
throw new Error('Access denied to this subscription');
}
const response = await fetch(
`${getRootApiEndpoint()}/subscriptions/${subscriptionId}/purchase-verification`,
{
method: 'GET',
headers: {
Authorization: `Bearer ${this.authService.getToken()}`,
'Content-Type': 'application/json',
},
}
);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const verificationResult = await response.json();
// Check if subscription has active payment method
if (!verificationResult.hasActivePaymentMethod) {
throw new Error('Subscription does not have an active payment method');
}
return verificationResult;
});
/**
* Update subscription
*/
updateSubscription = this.withAuth(async (subscriptionId, updateData) => {
// Check access to subscription
const canAccess = await this.authService.canAccessEntity(
'subscription',
subscriptionId,
['write']
);
if (!canAccess) {
throw new Error('Access denied to update this subscription');
}
const response = await fetch(
`${getRootApiEndpoint()}/subscriptions/${subscriptionId}`,
{
method: 'PUT',
headers: {
Authorization: `Bearer ${this.authService.getToken()}`,
'Content-Type': 'application/json',
},
body: JSON.stringify(updateData),
}
);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return await response.json();
});
// ========================================
// PURCHASING OPERATORS
// ========================================
/**
* Create token record in dataverse for purchase
*/
createPurchaseToken = this.withAuth(async (subscriptionId, purchaseData) => {
// Verify subscription access and payment method first
await this.verifySubscriptionForPurchase(subscriptionId);
const response = await fetch(
`${getRootApiEndpoint()}/subscriptions/${subscriptionId}/tokens`,
{
method: 'POST',
headers: {
Authorization: `Bearer ${this.authService.getToken()}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
...purchaseData,
subscription_id: subscriptionId,
created_by: this.authService.getUserPermission('contactId'),
workspace_id: this.authService.getUserPermission('workspaceId'),
account_id: this.authService.getUserPermission('accountId'),
}),
}
);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return await response.json();
});
/**
* Process purchase using APIM operators
*/
processPurchase = this.withAuth(async (subscriptionId, purchaseDetails) => {
// Verify subscription access and payment method
const verification =
await this.verifySubscriptionForPurchase(subscriptionId);
if (!verification.canPurchase) {
throw new Error('Purchase not allowed for this subscription');
}
// Create purchase token record in dataverse
const tokenRecord = await this.createPurchaseToken(subscriptionId, {
product_type: purchaseDetails.productType,
quantity: purchaseDetails.quantity,
amount: purchaseDetails.amount,
currency: purchaseDetails.currency || 'USD',
});
// Process the actual purchase
const response = await fetch(
`${getRootApiEndpoint()}/subscriptions/${subscriptionId}/purchase`,
{
method: 'POST',
headers: {
Authorization: `Bearer ${this.authService.getToken()}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
token_id: tokenRecord.id,
...purchaseDetails,
}),
}
);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return await response.json();
});
/**
* Get purchase history for subscription
*/
getPurchaseHistory = this.withAuth(async (subscriptionId, options = {}) => {
// Check access to subscription
const canAccess = await this.authService.canAccessEntity(
'subscription',
subscriptionId,
['read']
);
if (!canAccess) {
throw new Error(
'Access denied to purchase history for this subscription'
);
}
const queryString = new URLSearchParams(options).toString();
const response = await fetch(
`${getRootApiEndpoint()}/subscriptions/${subscriptionId}/purchases?${queryString}`,
{
method: 'GET',
headers: {
Authorization: `Bearer ${this.authService.getToken()}`,
'Content-Type': 'application/json',
},
}
);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return await response.json();
});
/**
* Get billing history
*/
getBillingHistory = this.withAuth(async (subscriptionId, options = {}) => {
// Check access to subscription
const canAccess = await this.authService.canAccessEntity(
'subscription',
subscriptionId,
['read']
);
if (!canAccess) {
throw new Error(
'Access denied to billing information for this subscription'
);
}
const queryString = new URLSearchParams(options).toString();
const response = await fetch(
`${getRootApiEndpoint()}/subscriptions/${subscriptionId}/billing?${queryString}`,
{
method: 'GET',
headers: {
Authorization: `Bearer ${this.authService.getToken()}`,
'Content-Type': 'application/json',
},
}
);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return await response.json();
});
// ========================================
// PROFILE MANAGEMENT OPERATORS
// ========================================
/**
* Get user profile
*/
getUserProfile = this.withAuth(async () => {
const contactId = this.authService.getUserPermission('contactId');
if (!contactId) {
throw new Error('Contact ID not found in token');
}
// Verify contact access
const canAccess = await this.authService.canAccessEntity(
'contact',
contactId,
['read']
);
if (!canAccess) {
throw new Error('Access denied to user profile');
}
const response = await fetch(`${getRootApiEndpoint()}/profile`, {
method: 'GET',
headers: {
Authorization: `Bearer ${this.authService.getToken()}`,
'Content-Type': 'application/json',
},
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return await response.json();
});
/**
* Update user profile
*/
updateUserProfile = this.withAuth(async profileData => {
const contactId = this.authService.getUserPermission('contactId');
if (!contactId) {
throw new Error('Contact ID not found in token');
}
// Verify contact access
const canAccess = await this.authService.canAccessEntity(
'contact',
contactId,
['write']
);
if (!canAccess) {
throw new Error('Access denied to update user profile');
}
const response = await fetch(`${getRootApiEndpoint()}/profile`, {
method: 'PUT',
headers: {
Authorization: `Bearer ${this.authService.getToken()}`,
'Content-Type': 'application/json',
},
body: JSON.stringify(profileData),
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return await response.json();
});
// ========================================
// SUPPORT TICKETS OPERATORS (from service)
// ========================================
getSupportTickets = this.supportTicketsService.getSupportTickets.bind(
this.supportTicketsService
);
getSupportTicket = this.supportTicketsService.getSupportTicket.bind(
this.supportTicketsService
);
createSupportTicket = this.supportTicketsService.createSupportTicket.bind(
this.supportTicketsService
);
updateSupportTicket = this.supportTicketsService.updateSupportTicket.bind(
this.supportTicketsService
);
addTicketMessage = this.supportTicketsService.addTicketMessage.bind(
this.supportTicketsService
);
closeSupportTicket = this.supportTicketsService.closeSupportTicket.bind(
this.supportTicketsService
);
reopenSupportTicket = this.supportTicketsService.reopenSupportTicket.bind(
this.supportTicketsService
);
// ========================================
// NOTIFICATIONS OPERATORS (from service)
// ========================================
getNotifications = this.notificationsService.getNotifications.bind(
this.notificationsService
);
createNotification = this.notificationsService.createNotification.bind(
this.notificationsService
);
updateNotificationStatus =
this.notificationsService.updateNotificationStatus.bind(
this.notificationsService
);
deleteNotification = this.notificationsService.deleteNotification.bind(
this.notificationsService
);
markAllNotificationsAsRead = this.notificationsService.markAllAsRead.bind(
this.notificationsService
);
// ========================================
// CONTACT-US OPERATORS
// ========================================
/**
* Submit contact form (unauthenticated)
*/
submitContactForm = async formData => {
try {
// This can work for unauthenticated users
const response = await fetch(`${getRootApiEndpoint()}/contact`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
...formData,
source: 'website_contact_form',
timestamp: new Date().toISOString(),
}),
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return await response.json();
} catch (error) {
console.error('Contact form submission failed:', error);
throw error;
}
};
/**
* Submit support ticket via contact form (authenticated)
*/
submitSupportTicketForm = this.withAuth(async formData => {
// This uses the support tickets service
return await this.supportTicketsService.createSupportTicket({
title: formData.subject || 'Support Request',
description: formData.message,
priority: this.determinePriority(formData.subject),
contact_email: formData.email,
contact_name: formData.name,
source: 'contact_form',
});
});
// ========================================
// UTILITY METHODS
// ========================================
/**
* Determine priority based on subject
*/
determinePriority(subject) {
const highPriorityKeywords = [
'urgent',
'critical',
'down',
'broken',
'error',
'billing',
];
const mediumPriorityKeywords = ['issue', 'problem', 'bug', 'help'];
const subjectLower = (subject || '').toLowerCase();
if (highPriorityKeywords.some(keyword => subjectLower.includes(keyword))) {
return 'high';
}
if (
mediumPriorityKeywords.some(keyword => subjectLower.includes(keyword))
) {
return 'medium';
}
return 'low';
}
/**
* Clear operator cache
*/
clearCache() {
this.operatorCache.clear();
}
/**
* Generic operator call method for administrative API endpoints
*/
async callOperator(category, operation, data = {}) {
try {
// Ensure authentication
if (!this.authService.isUserAuthenticated()) {
throw new Error('Authentication required for this operation');
}
// Build the endpoint URL based on category and operation
const endpoint = this.buildOperatorEndpoint(category, operation);
// Prepare the request body with auth data
const requestBody = {
...data,
authData: {
...this.authService.getAuthData(),
...data.authData,
},
};
const response = await fetch(endpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${this.authService.getToken()}`,
},
body: JSON.stringify(requestBody),
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return await response.json();
} catch (error) {
console.error(`Operator ${category}/${operation} failed:`, error);
throw error;
}
}
/**
* Build operator endpoint URL
*/
buildOperatorEndpoint(category, operation) {
const baseUrl = getRootApiEndpoint();
// Map categories and operations to endpoint paths
const endpointMap = {
workspace_management: {
list_workspaces: '/administrative/workspace/list',
create_workspace: '/administrative/workspace/create',
},
workspace_admin_management: {
get_workspace_admins: '/administrative/workspace/get-admins',
update_workspace_admins: '/administrative/workspace/update-admins',
get_users_for_admin_selection:
'/administrative/user/list-for-admin-selection',
},
user_management: {
list_users: '/administrative/user/list',
invite_user: '/administrative/user/invite',
reassign_user: '/administrative/user/reassign',
},
bot_management: {
list_bots: '/administrative/bot-config/list', // Uses existing bot config list endpoint
list_bot_configs: '/administrative/bot-config/list',
create_bot_config: '/administrative/bot-config/create',
cancel_bot_config: '/administrative/bot-config/cancel',
},
subscription_management: {
// Note: No generic list endpoint exists, using payment methods as fallback
list_cost_pools: '/administrative/cost-center/list-payment-methods',
create_cost_center: '/administrative/cost-center/create',
},
cost_pool_management: {
list_cost_centers: '/administrative/cost-center/list-payment-methods',
create_cost_center: '/administrative/cost-center/create',
},
// Note: These endpoints need to be implemented in the function app
account_management: {
get_account_stats: '/administrative/workspace/list', // Fallback to workspace list for now
},
dashboard: {
get_kpis: '/administrative/workspace/list', // Fallback to workspace list for now
get_activity: '/administrative/workspace/list', // Fallback to workspace list for now
get_dashboard_data: '/administrative/workspace/list', // Fallback to workspace list for now
},
};
const categoryEndpoints = endpointMap[category];
if (!categoryEndpoints) {
throw new Error(`Unknown operator category: ${category}`);
}
const endpoint = categoryEndpoints[operation];
if (!endpoint) {
throw new Error(
`Unknown operation: ${operation} in category: ${category}`
);
}
return `${baseUrl}${endpoint}`;
}
/**
* Cleanup resources
*/
destroy() {
this.clearCache();
}
}
// Create singleton instance
const pageOperatorsService = new PageOperatorsService();
export default pageOperatorsService;
export { PageOperatorsService };