mirror of
https://github.com/freedbygrace/DynamoDNS.git
synced 2026-07-26 11:38:13 +00:00
52abff77d4
Replit-Commit-Author: Agent Replit-Commit-Session-Id: 9111ef36-26c8-4085-84ca-a35dc1fec1b5 Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/7083d608-d6d3-4a6a-9a27-6286c5109627/19261c9e-f1ea-40d4-82c2-25bd04d73b3c.jpg
1004 lines
34 KiB
TypeScript
1004 lines
34 KiB
TypeScript
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<User | undefined>;
|
|
getUserByUsername(username: string): Promise<User | undefined>;
|
|
getUserByEmail(email: string): Promise<User | undefined>;
|
|
createUser(user: InsertUser): Promise<User>;
|
|
updateUser(id: string, user: Partial<InsertUser>): Promise<User | undefined>;
|
|
deleteUser(id: string): Promise<boolean>;
|
|
|
|
// Customer management
|
|
getCustomer(id: string): Promise<Customer | undefined>;
|
|
getCustomers(): Promise<Customer[]>;
|
|
createCustomer(customer: InsertCustomer): Promise<Customer>;
|
|
updateCustomer(id: string, customer: Partial<InsertCustomer>): Promise<Customer | undefined>;
|
|
deleteCustomer(id: string): Promise<boolean>;
|
|
|
|
// Customer-User assignments
|
|
getCustomerUsers(customerId: string): Promise<User[]>;
|
|
getUserCustomers(userId: string): Promise<Customer[]>;
|
|
assignUserToCustomer(assignment: InsertCustomerUserAssignment): Promise<CustomerUserAssignment>;
|
|
updateUserCustomerRole(customerId: string, userId: string, role: string): Promise<CustomerUserAssignment | undefined>;
|
|
removeUserFromCustomer(customerId: string, userId: string): Promise<boolean>;
|
|
|
|
// Domain management
|
|
getDomain(id: string): Promise<Domain | undefined>;
|
|
getDomainsByCustomer(customerId: string): Promise<Domain[]>;
|
|
getAllDomains(): Promise<Domain[]>;
|
|
createDomain(domain: InsertDomain): Promise<Domain>;
|
|
updateDomain(id: string, domain: Partial<InsertDomain>): Promise<Domain | undefined>;
|
|
deleteDomain(id: string): Promise<boolean>;
|
|
|
|
// Domain credentials management
|
|
getDomainCredentials(domainId: string): Promise<DomainCredentials | undefined>;
|
|
createDomainCredentials(credentials: InsertDomainCredentials): Promise<DomainCredentials>;
|
|
updateDomainCredentials(domainId: string, credentials: Partial<InsertDomainCredentials>): Promise<DomainCredentials | undefined>;
|
|
|
|
// DNS Record management
|
|
getDnsRecord(id: string): Promise<DnsRecord | undefined>;
|
|
getDnsRecordsByDomain(domainId: string): Promise<DnsRecord[]>;
|
|
createDnsRecord(record: InsertDnsRecord): Promise<DnsRecord>;
|
|
updateDnsRecord(id: string, record: Partial<InsertDnsRecord>): Promise<DnsRecord | undefined>;
|
|
deleteDnsRecord(id: string): Promise<boolean>;
|
|
|
|
// Provider management
|
|
getProvider(id: string): Promise<Provider | undefined>;
|
|
getProviders(): Promise<Provider[]>;
|
|
createProvider(provider: InsertProvider): Promise<Provider>;
|
|
updateProvider(id: string, provider: Partial<InsertProvider>): Promise<Provider | undefined>;
|
|
deleteProvider(id: string): Promise<boolean>;
|
|
|
|
// API Token management
|
|
getApiToken(id: string): Promise<ApiToken | undefined>;
|
|
getApiTokenByToken(token: string): Promise<ApiToken | undefined>;
|
|
getApiTokensByCustomer(customerId: string): Promise<ApiToken[]>;
|
|
getApiTokens(): Promise<ApiToken[]>;
|
|
createApiToken(token: InsertApiToken): Promise<ApiToken>;
|
|
updateApiToken(id: string, token: Partial<InsertApiToken>): Promise<ApiToken | undefined>;
|
|
deleteApiToken(id: string): Promise<boolean>;
|
|
|
|
// API Token Customer Access
|
|
getApiTokenCustomerAccess(tokenId: string): Promise<ApiTokenCustomerAccess[]>;
|
|
addApiTokenCustomerAccess(access: InsertApiTokenCustomerAccess): Promise<ApiTokenCustomerAccess>;
|
|
removeApiTokenCustomerAccess(tokenId: string, customerId: string): Promise<boolean>;
|
|
|
|
// DNS History
|
|
addDnsHistory(recordId: string, action: string, previousValue?: string, newValue?: string, userId?: string): Promise<DnsHistory>;
|
|
getDnsHistoryByRecord(recordId: string): Promise<DnsHistory[]>;
|
|
getDnsHistoryByDomain(domainId: string): Promise<DnsHistory[]>;
|
|
|
|
// Webhook management
|
|
getWebhook(id: string): Promise<Webhook | undefined>;
|
|
getWebhooksByCustomer(customerId: string): Promise<Webhook[]>;
|
|
createWebhook(webhook: InsertWebhook): Promise<Webhook>;
|
|
updateWebhook(id: string, webhook: Partial<InsertWebhook>): Promise<Webhook | undefined>;
|
|
deleteWebhook(id: string): Promise<boolean>;
|
|
triggerWebhook(webhookId: string, payload: any, retryCount?: number): Promise<boolean>;
|
|
|
|
// Webhook Delivery Logs
|
|
addWebhookDeliveryLog(log: InsertWebhookDeliveryLog): Promise<WebhookDeliveryLog>;
|
|
getWebhookDeliveryLog(id: string): Promise<WebhookDeliveryLog | undefined>;
|
|
getWebhookDeliveryLogsByWebhook(webhookId: string): Promise<WebhookDeliveryLog[]>;
|
|
|
|
// DNS Metrics
|
|
addDnsMetric(metric: InsertDnsMetric): Promise<DnsMetric>;
|
|
getDnsMetric(id: string): Promise<DnsMetric | undefined>;
|
|
getDnsMetricsByDomain(domainId: string, metricType?: string, startDate?: Date, endDate?: Date): Promise<DnsMetric[]>;
|
|
getDnsMetricsByRecord(recordId: string, metricType?: string, startDate?: Date, endDate?: Date): Promise<DnsMetric[]>;
|
|
getDnsMetricsByType(metricType: string, startDate?: Date, endDate?: Date): Promise<DnsMetric[]>;
|
|
|
|
// Custom Roles management
|
|
getCustomRole(id: string): Promise<CustomRole | undefined>;
|
|
getCustomRoles(): Promise<CustomRole[]>;
|
|
getCustomRoleByName(name: string): Promise<CustomRole | undefined>;
|
|
createCustomRole(role: InsertCustomRole): Promise<CustomRole>;
|
|
updateCustomRole(id: string, role: Partial<InsertCustomRole>): Promise<CustomRole | undefined>;
|
|
deleteCustomRole(id: string): Promise<boolean>;
|
|
|
|
// Session store
|
|
sessionStore: any;
|
|
}
|
|
|
|
export class MemStorage implements IStorage {
|
|
private usersMap: Map<number, User>;
|
|
private customersMap: Map<number, Customer>;
|
|
private customerUserMap: Map<number, CustomerUserAssignment>;
|
|
private domainsMap: Map<number, Domain>;
|
|
private domainCredentialsMap: Map<number, DomainCredentials>;
|
|
private recordsMap: Map<number, DnsRecord>;
|
|
private providersMap: Map<number, Provider>;
|
|
private apiTokensMap: Map<number, ApiToken>;
|
|
private apiTokenCustomerAccessMap: Map<number, ApiTokenCustomerAccess>;
|
|
private historyMap: Map<number, DnsHistory>;
|
|
private webhooksMap: Map<number, Webhook>;
|
|
private webhookDeliveryLogsMap: Map<number, WebhookDeliveryLog>;
|
|
private metricsMap: Map<number, DnsMetric>;
|
|
private customRolesMap: Map<number, CustomRole>;
|
|
|
|
// 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<User | undefined> {
|
|
return this.usersMap.get(parseInt(id));
|
|
}
|
|
|
|
async getUserByUsername(username: string): Promise<User | undefined> {
|
|
return Array.from(this.usersMap.values()).find(
|
|
(user) => user.username === username,
|
|
);
|
|
}
|
|
|
|
async getUserByEmail(email: string): Promise<User | undefined> {
|
|
return Array.from(this.usersMap.values()).find(
|
|
(user) => user.email === email,
|
|
);
|
|
}
|
|
|
|
async createUser(user: InsertUser): Promise<User> {
|
|
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<InsertUser>): Promise<User | undefined> {
|
|
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<boolean> {
|
|
return this.usersMap.delete(parseInt(id));
|
|
}
|
|
|
|
// Organizations
|
|
async getOrganization(id: string): Promise<Organization | undefined> {
|
|
return this.orgsMap.get(parseInt(id));
|
|
}
|
|
|
|
async getOrganizations(): Promise<Organization[]> {
|
|
return Array.from(this.orgsMap.values());
|
|
}
|
|
|
|
async createOrganization(org: InsertOrganization): Promise<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;
|
|
}
|
|
|
|
async updateOrganization(id: string, orgData: Partial<InsertOrganization>): Promise<Organization | undefined> {
|
|
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<boolean> {
|
|
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<Domain | undefined> {
|
|
return this.domainsMap.get(parseInt(id));
|
|
}
|
|
|
|
async getDomainsByCustomer(customerId: string): Promise<Domain[]> {
|
|
return Array.from(this.domainsMap.values())
|
|
.filter(domain => domain.customerId === customerId);
|
|
}
|
|
|
|
async getAllDomains(): Promise<Domain[]> {
|
|
return Array.from(this.domainsMap.values());
|
|
}
|
|
|
|
async createDomain(domain: InsertDomain): Promise<Domain> {
|
|
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<InsertDomain>): Promise<Domain | undefined> {
|
|
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<boolean> {
|
|
return this.domainsMap.delete(parseInt(id));
|
|
}
|
|
|
|
// DNS Records
|
|
async getDnsRecord(id: string): Promise<DnsRecord | undefined> {
|
|
return this.recordsMap.get(parseInt(id));
|
|
}
|
|
|
|
async getDnsRecordsByDomain(domainId: string): Promise<DnsRecord[]> {
|
|
return Array.from(this.recordsMap.values())
|
|
.filter(record => record.domainId === domainId);
|
|
}
|
|
|
|
async createDnsRecord(record: InsertDnsRecord): Promise<DnsRecord> {
|
|
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<InsertDnsRecord>): Promise<DnsRecord | undefined> {
|
|
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<boolean> {
|
|
return this.recordsMap.delete(parseInt(id));
|
|
}
|
|
|
|
// Providers
|
|
async getProvider(id: string): Promise<Provider | undefined> {
|
|
return this.providersMap.get(parseInt(id));
|
|
}
|
|
|
|
async getProviders(): Promise<Provider[]> {
|
|
return Array.from(this.providersMap.values());
|
|
}
|
|
|
|
async createProvider(provider: InsertProvider): Promise<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;
|
|
}
|
|
|
|
async updateProvider(id: string, providerData: Partial<InsertProvider>): Promise<Provider | undefined> {
|
|
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<boolean> {
|
|
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<ApiToken | undefined> {
|
|
return this.apiTokensMap.get(parseInt(id));
|
|
}
|
|
|
|
async getApiTokenByToken(token: string): Promise<ApiToken | undefined> {
|
|
return Array.from(this.apiTokensMap.values())
|
|
.find(apiToken => apiToken.token === token);
|
|
}
|
|
|
|
async getApiTokensByCustomer(customerId: string): Promise<ApiToken[]> {
|
|
// 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<ApiToken> {
|
|
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<InsertApiToken>): Promise<ApiToken | undefined> {
|
|
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<boolean> {
|
|
// 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<ApiTokenCustomerAccess[]> {
|
|
return Array.from(this.apiTokenCustomerAccessMap.values())
|
|
.filter(access => access.tokenId === tokenId);
|
|
}
|
|
|
|
async addApiTokenCustomerAccess(access: InsertApiTokenCustomerAccess): Promise<ApiTokenCustomerAccess> {
|
|
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<boolean> {
|
|
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<DnsHistory> {
|
|
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<DnsHistory[]> {
|
|
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<DnsHistory[]> {
|
|
// 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<Webhook | undefined> {
|
|
return this.webhooksMap.get(parseInt(id));
|
|
}
|
|
|
|
async getWebhooksByCustomer(customerId: string): Promise<Webhook[]> {
|
|
return Array.from(this.webhooksMap.values())
|
|
.filter(webhook => webhook.customerId === customerId);
|
|
}
|
|
|
|
async createWebhook(webhook: InsertWebhook): Promise<Webhook> {
|
|
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<InsertWebhook>): Promise<Webhook | undefined> {
|
|
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<boolean> {
|
|
return this.webhooksMap.delete(parseInt(id));
|
|
}
|
|
|
|
async triggerWebhook(webhookId: string, payload: any, retryCount: number = 0): Promise<boolean> {
|
|
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<WebhookDeliveryLog> {
|
|
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<WebhookDeliveryLog | undefined> {
|
|
return this.webhookDeliveryLogsMap.get(parseInt(id));
|
|
}
|
|
|
|
async getWebhookDeliveryLogsByWebhook(webhookId: string): Promise<WebhookDeliveryLog[]> {
|
|
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<DnsMetric> {
|
|
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<DnsMetric | undefined> {
|
|
return this.metricsMap.get(parseInt(id));
|
|
}
|
|
|
|
async getDnsMetricsByDomain(
|
|
domainId: string,
|
|
metricType?: string,
|
|
startDate?: Date,
|
|
endDate?: Date
|
|
): Promise<DnsMetric[]> {
|
|
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<DnsMetric[]> {
|
|
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<DnsMetric[]> {
|
|
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<CustomRole | undefined> {
|
|
return this.customRolesMap.get(parseInt(id));
|
|
}
|
|
|
|
async getCustomRoles(): Promise<CustomRole[]> {
|
|
return Array.from(this.customRolesMap.values());
|
|
}
|
|
|
|
async getCustomRoleByName(name: string): Promise<CustomRole | undefined> {
|
|
return Array.from(this.customRolesMap.values())
|
|
.find(role => role.name === name);
|
|
}
|
|
|
|
async createCustomRole(role: InsertCustomRole): Promise<CustomRole> {
|
|
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<InsertCustomRole>): Promise<CustomRole | undefined> {
|
|
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<boolean> {
|
|
return this.customRolesMap.delete(parseInt(id));
|
|
}
|
|
}
|
|
|
|
// Use the improved storage implementation
|
|
export const storage = new ImprovedDatabaseStorage();
|