import { type User, type InsertUser, type Customer, type InsertCustomer, type CustomerUserAssignment, type InsertCustomerUserAssignment, type Domain, type InsertDomain, type DomainCredentials, type InsertDomainCredentials, type DnsRecord, type InsertDnsRecord, type Provider, type InsertProvider, type ApiToken, type InsertApiToken, type ApiTokenCustomerAccess, type InsertApiTokenCustomerAccess, type DnsHistory, type Webhook, type InsertWebhook, type WebhookDeliveryLog, type InsertWebhookDeliveryLog, type DnsMetric, type InsertDnsMetric, type CustomRole, type InsertCustomRole } from "@shared/schema"; import session from "express-session"; import { DatabaseStorage } from "./database-storage"; import { ImprovedDatabaseStorage } from "./improved-database-storage"; export interface IStorage { // User management getUser(id: string): Promise; getUserByUsername(username: string): Promise; getUserByEmail(email: string): Promise; createUser(user: InsertUser): Promise; updateUser(id: string, user: Partial): Promise; deleteUser(id: string): Promise; // Customer management getCustomer(id: string): Promise; getCustomers(): Promise; createCustomer(customer: InsertCustomer): Promise; updateCustomer(id: string, customer: Partial): Promise; deleteCustomer(id: string): Promise; // Customer-User assignments getCustomerUsers(customerId: string): Promise; getUserCustomers(userId: string): Promise; assignUserToCustomer(assignment: InsertCustomerUserAssignment): Promise; updateUserCustomerRole(customerId: string, userId: string, role: string): Promise; removeUserFromCustomer(customerId: string, userId: string): Promise; // Domain management getDomain(id: string): Promise; getDomainsByCustomer(customerId: string): Promise; getAllDomains(): Promise; createDomain(domain: InsertDomain): Promise; updateDomain(id: string, domain: Partial): Promise; deleteDomain(id: string): Promise; // Domain credentials management getDomainCredentials(domainId: string): Promise; createDomainCredentials(credentials: InsertDomainCredentials): Promise; updateDomainCredentials(domainId: string, credentials: Partial): Promise; // DNS Record management getDnsRecord(id: string): Promise; getDnsRecordsByDomain(domainId: string): Promise; createDnsRecord(record: InsertDnsRecord): Promise; updateDnsRecord(id: string, record: Partial): Promise; deleteDnsRecord(id: string): Promise; // Provider management getProvider(id: string): Promise; getProviders(): Promise; createProvider(provider: InsertProvider): Promise; updateProvider(id: string, provider: Partial): Promise; deleteProvider(id: string): Promise; // API Token management getApiToken(id: string): Promise; getApiTokenByToken(token: string): Promise; getApiTokensByCustomer(customerId: string): Promise; getApiTokens(): Promise; createApiToken(token: InsertApiToken): Promise; updateApiToken(id: string, token: Partial): Promise; deleteApiToken(id: string): Promise; // API Token Customer Access getApiTokenCustomerAccess(tokenId: string): Promise; addApiTokenCustomerAccess(access: InsertApiTokenCustomerAccess): Promise; removeApiTokenCustomerAccess(tokenId: string, customerId: string): Promise; // DNS History addDnsHistory(recordId: string, action: string, previousValue?: string, newValue?: string, userId?: string): Promise; getDnsHistoryByRecord(recordId: string): Promise; getDnsHistoryByDomain(domainId: string): Promise; // Webhook management getWebhook(id: string): Promise; getWebhooksByCustomer(customerId: string): Promise; createWebhook(webhook: InsertWebhook): Promise; updateWebhook(id: string, webhook: Partial): Promise; deleteWebhook(id: string): Promise; triggerWebhook(webhookId: string, payload: any, retryCount?: number): Promise; // Webhook Delivery Logs addWebhookDeliveryLog(log: InsertWebhookDeliveryLog): Promise; getWebhookDeliveryLog(id: string): Promise; getWebhookDeliveryLogsByWebhook(webhookId: string): Promise; // DNS Metrics addDnsMetric(metric: InsertDnsMetric): Promise; getDnsMetric(id: string): Promise; getDnsMetricsByDomain(domainId: string, metricType?: string, startDate?: Date, endDate?: Date): Promise; getDnsMetricsByRecord(recordId: string, metricType?: string, startDate?: Date, endDate?: Date): Promise; getDnsMetricsByType(metricType: string, startDate?: Date, endDate?: Date): Promise; // Custom Roles management getCustomRole(id: string): Promise; getCustomRoles(): Promise; getCustomRoleByName(name: string): Promise; createCustomRole(role: InsertCustomRole): Promise; updateCustomRole(id: string, role: Partial): Promise; deleteCustomRole(id: string): Promise; // Session store sessionStore: any; } export class MemStorage implements IStorage { private usersMap: Map; private customersMap: Map; private customerUserMap: Map; private domainsMap: Map; private domainCredentialsMap: Map; private recordsMap: Map; private providersMap: Map; private apiTokensMap: Map; private apiTokenCustomerAccessMap: Map; private historyMap: Map; private webhooksMap: Map; private webhookDeliveryLogsMap: Map; private metricsMap: Map; private customRolesMap: Map; // Counters for IDs private userIdCounter: number; private customerIdCounter: number; private customerUserIdCounter: number; private domainIdCounter: number; private domainCredentialsIdCounter: number; private recordIdCounter: number; private providerIdCounter: number; private apiTokenIdCounter: number; private apiTokenCustomerAccessIdCounter: number; private historyIdCounter: number; private webhookIdCounter: number; private webhookDeliveryLogIdCounter: number; private metricIdCounter: number; private customRoleIdCounter: number; public sessionStore: any; constructor() { this.usersMap = new Map(); this.customersMap = new Map(); this.customerUserMap = new Map(); this.domainsMap = new Map(); this.domainCredentialsMap = new Map(); this.recordsMap = new Map(); this.providersMap = new Map(); this.apiTokensMap = new Map(); this.apiTokenCustomerAccessMap = new Map(); this.historyMap = new Map(); this.webhooksMap = new Map(); this.webhookDeliveryLogsMap = new Map(); this.metricsMap = new Map(); this.customRolesMap = new Map(); this.userIdCounter = 1; this.customerIdCounter = 1; this.customerUserIdCounter = 1; this.domainIdCounter = 1; this.domainCredentialsIdCounter = 1; this.recordIdCounter = 1; this.providerIdCounter = 1; this.apiTokenIdCounter = 1; this.apiTokenCustomerAccessIdCounter = 1; this.historyIdCounter = 1; this.webhookIdCounter = 1; this.webhookDeliveryLogIdCounter = 1; this.metricIdCounter = 1; this.customRoleIdCounter = 1; // Session store is created in the DatabaseStorage class this.sessionStore = null; // Initialize sample data this.initSampleData(); } private initSampleData() { // Create a default provider for Cloudflare const cloudflareProvider: InsertProvider = { name: "Cloudflare", type: "cloudflare", credentials: { apiKey: process.env.CLOUDFLARE_API_KEY || "" }, isActive: true }; // Use synchronous creation to avoid promise issues const provider = this.createProviderSync(cloudflareProvider); // Create sample organization const defaultOrg: InsertOrganization = { name: "Default Organization", isActive: true }; // Create a sample admin user with fixed ID to properly reference it let adminUser: User; const existingAdmin = Array.from(this.usersMap.values()).find(user => user.username === "admin"); if (existingAdmin) { adminUser = existingAdmin; } else { // Create organization synchronously to get its ID directly const org = this.createOrganizationSync(defaultOrg); // Generate a properly hashed password that matches our auth system const password = "ceb37858eae429c7460e3953bd612541e14c78eab805ab3c5be30dd81572b9669e154dfa320ca0fd67ab2b8c7af55124cf761178ebaef73e5eb73e9431eed84d.7e464ae80b7e8ab6639630035c6d37de"; adminUser = { id: "1", username: "admin", password: password, email: "admin@example.com", fullName: "System Admin", role: "admin", organizationId: org.id, createdAt: new Date() }; this.usersMap.set(1, adminUser); this.userIdCounter = 2; // Ensure the next ID is after our fixed one } // Group creation and membership code has been removed } // Users async getUser(id: string): Promise { return this.usersMap.get(parseInt(id)); } async getUserByUsername(username: string): Promise { return Array.from(this.usersMap.values()).find( (user) => user.username === username, ); } async getUserByEmail(email: string): Promise { return Array.from(this.usersMap.values()).find( (user) => user.email === email, ); } async createUser(user: InsertUser): Promise { const numId = this.userIdCounter++; const createdAt = new Date(); const newUser: User = { id: numId.toString(), username: user.username, password: user.password, email: user.email, fullName: user.fullName || null, role: user.role || 'user', createdAt }; this.usersMap.set(numId, newUser); return newUser; } async updateUser(id: string, userData: Partial): Promise { const numId = parseInt(id); const user = await this.getUser(id); if (!user) return undefined; const updatedUser = { ...user, ...userData }; this.usersMap.set(numId, updatedUser); return updatedUser; } async deleteUser(id: string): Promise { return this.usersMap.delete(parseInt(id)); } // Organizations async getOrganization(id: string): Promise { return this.orgsMap.get(parseInt(id)); } async getOrganizations(): Promise { return Array.from(this.orgsMap.values()); } async createOrganization(org: InsertOrganization): Promise { const numId = this.orgIdCounter++; const createdAt = new Date(); const newOrg: Organization = { id: numId.toString(), name: org.name, isActive: org.isActive ?? true, createdAt }; this.orgsMap.set(numId, newOrg); return newOrg; } async updateOrganization(id: string, orgData: Partial): Promise { const numId = parseInt(id); const org = await this.getOrganization(id); if (!org) return undefined; const updatedOrg = { ...org, ...orgData }; this.orgsMap.set(numId, updatedOrg); return updatedOrg; } async deleteOrganization(id: string): Promise { return this.orgsMap.delete(parseInt(id)); } // Synchronous version of createOrganization for use in initialization createOrganizationSync(org: InsertOrganization): Organization { const numId = this.orgIdCounter++; const createdAt = new Date(); const newOrg: Organization = { id: numId.toString(), name: org.name, isActive: org.isActive ?? true, createdAt }; this.orgsMap.set(numId, newOrg); return newOrg; } // Group management methods have been removed // Domains async getDomain(id: string): Promise { return this.domainsMap.get(parseInt(id)); } async getDomainsByCustomer(customerId: string): Promise { return Array.from(this.domainsMap.values()) .filter(domain => domain.customerId === customerId); } async getAllDomains(): Promise { return Array.from(this.domainsMap.values()); } async createDomain(domain: InsertDomain): Promise { const numId = this.domainIdCounter++; const createdAt = new Date(); const lastUpdated = new Date(); const newDomain: Domain = { id: numId.toString(), name: domain.name, customerId: domain.customerId, providerId: domain.providerId || "1", // Default to the first provider if not specified isActive: domain.isActive ?? true, lastUpdated, createdAt }; this.domainsMap.set(numId, newDomain); return newDomain; } async updateDomain(id: string, domainData: Partial): Promise { const numId = parseInt(id); const domain = await this.getDomain(id); if (!domain) return undefined; const updatedDomain = { ...domain, ...domainData, lastUpdated: new Date() }; this.domainsMap.set(numId, updatedDomain); return updatedDomain; } async deleteDomain(id: string): Promise { return this.domainsMap.delete(parseInt(id)); } // DNS Records async getDnsRecord(id: string): Promise { return this.recordsMap.get(parseInt(id)); } async getDnsRecordsByDomain(domainId: string): Promise { return Array.from(this.recordsMap.values()) .filter(record => record.domainId === domainId); } async createDnsRecord(record: InsertDnsRecord): Promise { const numId = this.recordIdCounter++; const createdAt = new Date(); const lastUpdated = new Date(); const newRecord: DnsRecord = { id: numId.toString(), domainId: record.domainId, name: record.name, type: record.type, content: record.content, ttl: record.ttl ?? 3600, proxied: record.proxied ?? false, isActive: record.isActive ?? true, isAutoIP: record.isAutoIP ?? false, notes: record.notes ?? null, priority: record.priority ?? 0, providerRecordId: record.providerRecordId ?? null, lastUpdated, createdAt }; this.recordsMap.set(numId, newRecord); return newRecord; } async updateDnsRecord(id: string, recordData: Partial): Promise { const numId = parseInt(id); const record = await this.getDnsRecord(id); if (!record) return undefined; const updatedRecord = { ...record, ...recordData, lastUpdated: new Date() }; this.recordsMap.set(numId, updatedRecord); return updatedRecord; } async deleteDnsRecord(id: string): Promise { return this.recordsMap.delete(parseInt(id)); } // Providers async getProvider(id: string): Promise { return this.providersMap.get(parseInt(id)); } async getProviders(): Promise { return Array.from(this.providersMap.values()); } async createProvider(provider: InsertProvider): Promise { const numId = this.providerIdCounter++; const createdAt = new Date(); const newProvider: Provider = { id: numId.toString(), name: provider.name, type: provider.type, credentials: provider.credentials ?? null, isActive: provider.isActive ?? true, createdAt }; this.providersMap.set(numId, newProvider); return newProvider; } async updateProvider(id: string, providerData: Partial): Promise { const numId = parseInt(id); const provider = await this.getProvider(id); if (!provider) return undefined; const updatedProvider = { ...provider, ...providerData }; this.providersMap.set(numId, updatedProvider); return updatedProvider; } async deleteProvider(id: string): Promise { return this.providersMap.delete(parseInt(id)); } // Synchronous version of createProvider for use in initialization createProviderSync(provider: InsertProvider): Provider { const numId = this.providerIdCounter++; const createdAt = new Date(); const newProvider: Provider = { id: numId.toString(), name: provider.name, type: provider.type, credentials: provider.credentials ?? null, isActive: provider.isActive ?? true, createdAt }; this.providersMap.set(numId, newProvider); return newProvider; } // API Tokens async getApiToken(id: string): Promise { return this.apiTokensMap.get(parseInt(id)); } async getApiTokenByToken(token: string): Promise { return Array.from(this.apiTokensMap.values()) .find(apiToken => apiToken.token === token); } async getApiTokensByCustomer(customerId: string): Promise { // Get all token-customer access records for this customer const customerAccess = Array.from(this.apiTokenCustomerAccessMap.values()) .filter(access => access.customerId === customerId); // Get the tokens with access to this customer const tokenIds = customerAccess.map(access => access.tokenId); return Array.from(this.apiTokensMap.values()) .filter(token => tokenIds.includes(token.id)); } async createApiToken(token: any): Promise { console.log("Storage createApiToken received:", JSON.stringify(token, null, 2)); const numId = this.apiTokenIdCounter++; const createdAt = new Date(); // Generate default permissions if none provided let permissions: string[] = []; if (token.permissions && Array.isArray(token.permissions)) { permissions = token.permissions; } else if (token.role) { // Default permissions based on role if not explicitly provided if (token.role === 'admin') { permissions = ['admin', 'manager', 'user', 'readonly']; } else if (token.role === 'manager') { permissions = ['manager', 'user', 'readonly']; } else if (token.role === 'user') { permissions = ['user', 'readonly']; } else { permissions = ['readonly']; } } // Handle expiresAt let expiresAt = null; if (token.expiresAt) { console.log("Processing expiresAt from token:", token.expiresAt); if (token.expiresAt instanceof Date) { expiresAt = token.expiresAt; console.log("expiresAt is a Date object:", expiresAt); } else if (typeof token.expiresAt === 'string') { try { expiresAt = new Date(token.expiresAt); console.log("Parsed expiresAt from string:", expiresAt); } catch (e) { console.error("Failed to parse expiresAt date string:", token.expiresAt, e); } } else { console.error("Unexpected expiresAt type:", typeof token.expiresAt); } } else { console.log("No expiresAt provided in token data"); } // Make sure expiresAt is either a Date object or null let finalExpiresAt: Date | null = null; if (expiresAt) { if (expiresAt instanceof Date) { finalExpiresAt = expiresAt; } else if (typeof expiresAt === 'string') { try { finalExpiresAt = new Date(expiresAt); } catch (e) { console.error("Could not convert expiresAt string to Date:", e); } } } const newToken: ApiToken = { id: numId.toString(), name: token.name || "Unnamed Token", token: token.token || "test_token_" + numId, customerId: token.customerId || "1", permissions: permissions, role: token.role || 'readonly', createdBy: token.createdBy || "1", isActive: token.isActive ?? true, expiresAt: finalExpiresAt, createdAt }; console.log("Storing new token:", JSON.stringify(newToken, null, 2)); this.apiTokensMap.set(numId, newToken); return newToken; } async updateApiToken(id: string, tokenData: Partial): Promise { const numId = parseInt(id); const token = await this.getApiToken(id); if (!token) return undefined; // Handle expiresAt specially to ensure it's a Date object or null let finalExpiresAt = token.expiresAt; if ('expiresAt' in tokenData) { const newExpiresAt = tokenData.expiresAt; if (newExpiresAt === null) { finalExpiresAt = null; } else if (newExpiresAt instanceof Date) { finalExpiresAt = newExpiresAt; } else if (typeof newExpiresAt === 'string') { try { finalExpiresAt = new Date(newExpiresAt); } catch (e) { console.error("Could not convert expiresAt string to Date:", e); } } // Remove expiresAt to prevent type issues const { expiresAt, ...otherData } = tokenData; tokenData = otherData; } const updatedToken = { ...token, ...tokenData, expiresAt: finalExpiresAt }; this.apiTokensMap.set(numId, updatedToken); return updatedToken; } async deleteApiToken(id: string): Promise { // First, remove all customer access entries for this token const numId = parseInt(id); const accessEntries = Array.from(this.apiTokenCustomerAccessMap.values()) .filter(access => access.tokenId === id); for (const access of accessEntries) { this.apiTokenCustomerAccessMap.delete(parseInt(access.id)); } // Then delete the token itself return this.apiTokensMap.delete(numId); } // API token customer access management async getApiTokenCustomerAccess(tokenId: string): Promise { return Array.from(this.apiTokenCustomerAccessMap.values()) .filter(access => access.tokenId === tokenId); } async addApiTokenCustomerAccess(access: InsertApiTokenCustomerAccess): Promise { const numId = this.apiTokenCustomerAccessIdCounter++; const createdAt = new Date(); const newAccess: ApiTokenCustomerAccess = { id: numId.toString(), tokenId: access.tokenId, customerId: access.customerId, createdAt }; this.apiTokenCustomerAccessMap.set(numId, newAccess); return newAccess; } async removeApiTokenCustomerAccess(tokenId: string, customerId: string): Promise { const access = Array.from(this.apiTokenCustomerAccessMap.values()) .find(a => a.tokenId === tokenId && a.customerId === customerId); if (!access) return false; return this.apiTokenCustomerAccessMap.delete(parseInt(access.id)); } // DNS History async addDnsHistory( recordId: string, action: string, previousValue?: string, newValue?: string, userId?: string ): Promise { const numId = this.historyIdCounter++; const timestamp = new Date(); const historyEntry: DnsHistory = { id: numId.toString(), recordId, action, previousValue: previousValue ?? null, newValue: newValue ?? null, userId: userId ?? null, timestamp }; this.historyMap.set(numId, historyEntry); return historyEntry; } async getDnsHistoryByRecord(recordId: string): Promise { return Array.from(this.historyMap.values()) .filter(history => history.recordId === recordId) .sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime()); } async getDnsHistoryByDomain(domainId: string): Promise { // Get all records for domain const records = await this.getDnsRecordsByDomain(domainId); const recordIds = records.map(r => r.id); // Get history entries for all records return Array.from(this.historyMap.values()) .filter(history => recordIds.includes(history.recordId)) .sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime()); } // Webhook management async getWebhook(id: string): Promise { return this.webhooksMap.get(parseInt(id)); } async getWebhooksByCustomer(customerId: string): Promise { return Array.from(this.webhooksMap.values()) .filter(webhook => webhook.customerId === customerId); } async createWebhook(webhook: InsertWebhook): Promise { const numId = this.webhookIdCounter++; const createdAt = new Date(); const newWebhook: Webhook = { id: numId.toString(), name: webhook.name, url: webhook.url, customerId: webhook.customerId, secret: webhook.secret ?? null, events: webhook.events, isActive: webhook.isActive ?? true, lastTriggered: null, createdBy: webhook.createdBy, createdAt }; this.webhooksMap.set(numId, newWebhook); return newWebhook; } async updateWebhook(id: string, webhookData: Partial): Promise { const numId = parseInt(id); const webhook = await this.getWebhook(id); if (!webhook) return undefined; const updatedWebhook = { ...webhook, ...webhookData }; this.webhooksMap.set(numId, updatedWebhook); return updatedWebhook; } async deleteWebhook(id: string): Promise { return this.webhooksMap.delete(parseInt(id)); } async triggerWebhook(webhookId: string, payload: any, retryCount: number = 0): Promise { const webhook = await this.getWebhook(webhookId); if (!webhook || !webhook.isActive) return false; try { // Import the webhook utility functions const webhookUtils = await import('./utils/webhook'); console.log(`Triggering webhook ${webhook.name} (${webhook.id})`); // Setup delivery options with the provided retry count const deliveryOptions = { startRetryCount: retryCount }; // In a real implementation with an actual HTTP request, this would call // the deliverWebhook function. For now, we'll simulate the delivery // to avoid making actual HTTP requests in the demo environment. // Simulated webhook delivery (for demo purposes only) const isRetry = retryCount > 0; const deliveryResult = { success: true, statusCode: 200, message: isRetry ? `Webhook delivered successfully after ${retryCount} ${retryCount === 1 ? 'retry' : 'retries'} (simulated)` : 'Webhook delivered successfully (simulated)', timestamp: new Date(), responseBody: JSON.stringify({ success: true, received_at: new Date().toISOString(), message: "Webhook received successfully" }), retryCount }; // In a production environment, you would use the actual delivery code: // const deliveryResult = await deliverWebhook(webhook, payload, deliveryOptions); // Log the delivery attempt await this.addWebhookDeliveryLog({ webhookId: webhook.id, event: payload.event || 'unknown', payload, signature: webhook.secret ? 'simulated-signature' : '', status: deliveryResult.success, statusCode: deliveryResult.statusCode, message: deliveryResult.message, responseBody: deliveryResult.responseBody, retryCount }); // Update the lastTriggered timestamp const updatedWebhook = { ...webhook, lastTriggered: new Date() }; this.webhooksMap.set(parseInt(webhookId), updatedWebhook); return true; } catch (error) { console.error(`Error triggering webhook ${webhook.id}:`, error); // Log the failed delivery attempt await this.addWebhookDeliveryLog({ webhookId: webhook.id, event: payload.event || 'unknown', payload, signature: '', status: false, message: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`, retryCount }); return false; } } // Webhook Delivery Logs async addWebhookDeliveryLog(log: InsertWebhookDeliveryLog): Promise { const numId = this.webhookDeliveryLogIdCounter++; const createdAt = new Date(); const newLog: WebhookDeliveryLog = { id: numId.toString(), webhookId: log.webhookId, event: log.event, payload: log.payload, signature: log.signature || null, status: log.status, statusCode: log.statusCode || null, message: log.message, responseBody: log.responseBody || null, retryCount: log.retryCount || 0, createdAt }; this.webhookDeliveryLogsMap.set(numId, newLog); return newLog; } async getWebhookDeliveryLog(id: string): Promise { return this.webhookDeliveryLogsMap.get(parseInt(id)); } async getWebhookDeliveryLogsByWebhook(webhookId: string): Promise { return Array.from(this.webhookDeliveryLogsMap.values()) .filter(log => log.webhookId === webhookId) .sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime()); } // DNS Metrics async addDnsMetric(metric: InsertDnsMetric): Promise { const numId = this.metricIdCounter++; const timestamp = new Date(); const newMetric: DnsMetric = { id: numId.toString(), domainId: metric.domainId || null, recordId: metric.recordId || null, metricType: metric.metricType, value: metric.value, source: metric.source || null, tags: metric.tags || null, timestamp }; this.metricsMap.set(numId, newMetric); return newMetric; } async getDnsMetric(id: string): Promise { return this.metricsMap.get(parseInt(id)); } async getDnsMetricsByDomain( domainId: string, metricType?: string, startDate?: Date, endDate?: Date ): Promise { let metrics = Array.from(this.metricsMap.values()) .filter(metric => metric.domainId === domainId); if (metricType) { metrics = metrics.filter(metric => metric.metricType === metricType); } if (startDate) { metrics = metrics.filter(metric => metric.timestamp >= startDate); } if (endDate) { metrics = metrics.filter(metric => metric.timestamp <= endDate); } return metrics.sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime()); } async getDnsMetricsByRecord( recordId: string, metricType?: string, startDate?: Date, endDate?: Date ): Promise { let metrics = Array.from(this.metricsMap.values()) .filter(metric => metric.recordId === recordId); if (metricType) { metrics = metrics.filter(metric => metric.metricType === metricType); } if (startDate) { metrics = metrics.filter(metric => metric.timestamp >= startDate); } if (endDate) { metrics = metrics.filter(metric => metric.timestamp <= endDate); } return metrics.sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime()); } async getDnsMetricsByType( metricType: string, startDate?: Date, endDate?: Date ): Promise { let metrics = Array.from(this.metricsMap.values()) .filter(metric => metric.metricType === metricType); if (startDate) { metrics = metrics.filter(metric => metric.timestamp >= startDate); } if (endDate) { metrics = metrics.filter(metric => metric.timestamp <= endDate); } return metrics.sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime()); } // Custom Roles management async getCustomRole(id: string): Promise { return this.customRolesMap.get(parseInt(id)); } async getCustomRoles(): Promise { return Array.from(this.customRolesMap.values()); } async getCustomRoleByName(name: string): Promise { return Array.from(this.customRolesMap.values()) .find(role => role.name === name); } async createCustomRole(role: InsertCustomRole): Promise { const numId = this.customRoleIdCounter++; const createdAt = new Date(); const newRole: CustomRole = { id: numId.toString(), name: role.name, description: role.description || null, permissions: role.permissions, isActive: role.isActive ?? true, createdAt, createdBy: role.createdBy }; this.customRolesMap.set(numId, newRole); return newRole; } async updateCustomRole(id: string, roleData: Partial): Promise { const numId = parseInt(id); const role = await this.getCustomRole(id); if (!role) return undefined; const updatedRole = { ...role, ...roleData }; this.customRolesMap.set(numId, updatedRole); return updatedRole; } async deleteCustomRole(id: string): Promise { return this.customRolesMap.delete(parseInt(id)); } } // Use the improved storage implementation export const storage = new ImprovedDatabaseStorage();