import type { Cloudly } from '../classes.cloudly.js'; import * as plugins from '../plugins.js'; import { Domain } from './classes.domain.js'; interface IWorkHosterDomain { name: string; nameservers?: string[]; capabilities?: { canCreateSubdomains: boolean; canManageDnsRecords: boolean; canIssueCertificates: boolean; canHostEmail: boolean; }; } export class DomainManager { public typedrouter = new plugins.typedrequest.TypedRouter(); public cloudlyRef: Cloudly; get db() { return this.cloudlyRef.mongodbConnector.smartdataDb; } public CDomain = plugins.smartdata.setDefaultManagerForDoc(this, Domain); constructor(cloudlyRef: Cloudly) { this.cloudlyRef = cloudlyRef; this.cloudlyRef.typedrouter.addTypedRouter(this.typedrouter); // Get all domains this.typedrouter.addTypedHandler( new plugins.typedrequest.TypedHandler( 'getDomains', async (reqArg) => { await plugins.smartguard.passGuardsOrReject(reqArg, [ this.cloudlyRef.authManager.validIdentityGuard, ]); const domains = await this.CDomain.getDomains(); return { domains: await Promise.all( domains.map((domain) => domain.createSavableObject()) ), }; } ) ); // Get domain by ID this.typedrouter.addTypedHandler( new plugins.typedrequest.TypedHandler( 'getDomainById', async (reqArg) => { await plugins.smartguard.passGuardsOrReject(reqArg, [ this.cloudlyRef.authManager.validIdentityGuard, ]); const domain = await this.CDomain.getDomainById(reqArg.domainId); if (!domain) { throw new Error(`Domain with id ${reqArg.domainId} not found`); } return { domain: await domain.createSavableObject(), }; } ) ); // Create domain this.typedrouter.addTypedHandler( new plugins.typedrequest.TypedHandler( 'createDomain', async (reqArg) => { await plugins.smartguard.passGuardsOrReject(reqArg, [ this.cloudlyRef.authManager.validIdentityGuard, ]); // Check if domain already exists const existingDomain = await this.CDomain.getDomainByName(reqArg.domainData.name); if (existingDomain) { throw new Error(`Domain ${reqArg.domainData.name} already exists`); } const domain = await this.CDomain.createDomain(reqArg.domainData); return { domain: await domain.createSavableObject(), }; } ) ); // Update domain this.typedrouter.addTypedHandler( new plugins.typedrequest.TypedHandler( 'updateDomain', async (reqArg) => { await plugins.smartguard.passGuardsOrReject(reqArg, [ this.cloudlyRef.authManager.validIdentityGuard, ]); const domain = await this.CDomain.updateDomain( reqArg.domainId, reqArg.domainData ); return { domain: await domain.createSavableObject(), }; } ) ); // Delete domain this.typedrouter.addTypedHandler( new plugins.typedrequest.TypedHandler( 'deleteDomain', async (reqArg) => { await plugins.smartguard.passGuardsOrReject(reqArg, [ this.cloudlyRef.authManager.validIdentityGuard, ]); const success = await this.CDomain.deleteDomain(reqArg.domainId); return { success, }; } ) ); // Verify domain this.typedrouter.addTypedHandler( new plugins.typedrequest.TypedHandler( 'verifyDomain', async (reqArg) => { await plugins.smartguard.passGuardsOrReject(reqArg, [ this.cloudlyRef.authManager.validIdentityGuard, ]); const domain = await this.CDomain.getDomainById(reqArg.domainId); if (!domain) { throw new Error(`Domain with id ${reqArg.domainId} not found`); } const verificationResult = await domain.verifyDomain(reqArg.verificationMethod); return { domain: await domain.createSavableObject(), verificationResult, }; } ) ); } /** * Initialize the domain manager */ public async init() { await this.syncExternalGatewayDomains().catch((error) => { console.log(`External gateway domain sync failed: ${(error as Error).message}`); }); console.log('Domain Manager initialized'); } public async syncExternalGatewayDomains(): Promise { const settings = await this.cloudlyRef.settingsManager.getSettings(); if (!settings.dcrouterGatewayUrl || !settings.dcrouterGatewayApiToken) { return 0; } const typedRequest = new plugins.typedrequest.TypedRequest( `${settings.dcrouterGatewayUrl.replace(/\/+$/, '')}/typedrequest`, 'getWorkHosterDomains', ); const response = await typedRequest.fire({ apiToken: settings.dcrouterGatewayApiToken, }) as { domains: IWorkHosterDomain[] }; const activeDomainNames = new Set(); for (const gatewayDomain of response.domains) { const domainName = gatewayDomain.name.trim().toLowerCase(); if (!domainName) continue; activeDomainNames.add(domainName); const existingDomain = await this.CDomain.getDomainByName(domainName); const tags = Array.from(new Set([...(existingDomain?.data.tags || []), 'dcrouter'])); const domainData: Partial = { name: domainName, status: 'active', verificationStatus: 'not_required', nameservers: gatewayDomain.nameservers || existingDomain?.data.nameservers || [], autoRenew: gatewayDomain.capabilities?.canIssueCertificates !== false, activationState: 'available', syncSource: 'manual', lastSyncAt: Date.now(), isExternal: true, tags, }; if (existingDomain) { await this.CDomain.updateDomain(existingDomain.id, domainData); } else { await this.CDomain.createDomain(domainData as plugins.servezoneInterfaces.data.IDomain['data']); } } const knownDomains = await this.CDomain.getDomains(); for (const domain of knownDomains) { if (domain.data.tags?.includes('dcrouter') && !activeDomainNames.has(domain.data.name)) { await this.CDomain.updateDomain(domain.id, { activationState: 'ignored', lastSyncAt: Date.now(), }); } } console.log(`Synced ${activeDomainNames.size} domain(s) from external dcrouter gateway`); return activeDomainNames.size; } /** * Stop the domain manager */ public async stop() { console.log('Domain Manager stopped'); } /** * Get all active domains */ public async getActiveDomains() { const domains = await this.CDomain.getInstances({ 'data.status': 'active', }); return domains; } /** * Get domains that are expiring soon */ public async getExpiringDomains(daysThreshold: number = 30) { const domains = await this.CDomain.getDomains(); return domains.filter(domain => domain.isExpiringSoon(daysThreshold)); } /** * Check if a domain name is available (not in our system) */ public async isDomainAvailable(domainName: string): Promise { const existingDomain = await this.CDomain.getDomainByName(domainName); return !existingDomain; } }