feat(dns): Implement DNS management functionality
- Added DnsManager and DnsEntry classes to handle DNS entries. - Introduced new interfaces for DNS entry requests and data structures. - Updated Cloudly class to include DnsManager instance. - Enhanced app state to manage DNS entries and actions for creating, updating, and deleting DNS records. - Created UI components for DNS management, including forms for adding and editing DNS entries. - Updated overview and services views to reflect DNS entries. - Added validation and formatting methods for DNS entries.
This commit is contained in:
		| @@ -25,6 +25,8 @@ import { ExternalRegistryManager } from './manager.externalregistry/index.js'; | ||||
| import { ImageManager } from './manager.image/classes.imagemanager.js'; | ||||
| import { ServiceManager } from './manager.service/classes.servicemanager.js'; | ||||
| import { DeploymentManager } from './manager.deployment/classes.deploymentmanager.js'; | ||||
| import { DnsManager } from './manager.dns/classes.dnsmanager.js'; | ||||
| import { DomainManager } from './manager.domain/classes.domainmanager.js'; | ||||
| import { logger } from './logger.js'; | ||||
| import { CloudlyAuthManager } from './manager.auth/classes.authmanager.js'; | ||||
| import { CloudlySettingsManager } from './manager.settings/classes.settingsmanager.js'; | ||||
| @@ -64,6 +66,8 @@ export class Cloudly { | ||||
|   public imageManager: ImageManager; | ||||
|   public serviceManager: ServiceManager; | ||||
|   public deploymentManager: DeploymentManager; | ||||
|   public dnsManager: DnsManager; | ||||
|   public domainManager: DomainManager; | ||||
|   public taskManager: CloudlyTaskmanager; | ||||
|   public nodeManager: CloudlyNodeManager; | ||||
|   public baremetalManager: CloudlyBaremetalManager; | ||||
| @@ -95,6 +99,8 @@ export class Cloudly { | ||||
|     this.imageManager = new ImageManager(this); | ||||
|     this.serviceManager = new ServiceManager(this); | ||||
|     this.deploymentManager = new DeploymentManager(this); | ||||
|     this.dnsManager = new DnsManager(this); | ||||
|     this.domainManager = new DomainManager(this); | ||||
|     this.taskManager = new CloudlyTaskmanager(this); | ||||
|     this.secretManager = new CloudlySecretManager(this); | ||||
|     this.nodeManager = new CloudlyNodeManager(this); | ||||
|   | ||||
							
								
								
									
										148
									
								
								ts/manager.dns/classes.dnsentry.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										148
									
								
								ts/manager.dns/classes.dnsentry.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,148 @@ | ||||
| import * as plugins from '../plugins.js'; | ||||
| import { DnsManager } from './classes.dnsmanager.js'; | ||||
|  | ||||
| export class DnsEntry extends plugins.smartdata.SmartDataDbDoc< | ||||
|   DnsEntry, | ||||
|   plugins.servezoneInterfaces.data.IDnsEntry, | ||||
|   DnsManager | ||||
| > { | ||||
|   // STATIC | ||||
|   public static async getDnsEntryById(dnsEntryIdArg: string) { | ||||
|     const dnsEntry = await this.getInstance({ | ||||
|       id: dnsEntryIdArg, | ||||
|     }); | ||||
|     return dnsEntry; | ||||
|   } | ||||
|  | ||||
|   public static async getDnsEntries(filterArg?: { zone?: string }) { | ||||
|     const filter: any = {}; | ||||
|     if (filterArg?.zone) { | ||||
|       filter['data.zone'] = filterArg.zone; | ||||
|     } | ||||
|     const dnsEntries = await this.getInstances(filter); | ||||
|     return dnsEntries; | ||||
|   } | ||||
|  | ||||
|   public static async getDnsZones() { | ||||
|     const dnsEntries = await this.getInstances({}); | ||||
|     const zones = new Set<string>(); | ||||
|     for (const entry of dnsEntries) { | ||||
|       if (entry.data.zone) { | ||||
|         zones.add(entry.data.zone); | ||||
|       } | ||||
|     } | ||||
|     return Array.from(zones).sort(); | ||||
|   } | ||||
|  | ||||
|   public static async createDnsEntry(dnsEntryDataArg: plugins.servezoneInterfaces.data.IDnsEntry['data']) { | ||||
|     const dnsEntry = new DnsEntry(); | ||||
|     dnsEntry.id = await DnsEntry.getNewId(); | ||||
|     dnsEntry.data = { | ||||
|       ...dnsEntryDataArg, | ||||
|       ttl: dnsEntryDataArg.ttl || 3600, // Default TTL: 1 hour | ||||
|       active: dnsEntryDataArg.active !== false, // Default to active | ||||
|       createdAt: Date.now(), | ||||
|       updatedAt: Date.now(), | ||||
|     }; | ||||
|     await dnsEntry.save(); | ||||
|     return dnsEntry; | ||||
|   } | ||||
|  | ||||
|   public static async updateDnsEntry( | ||||
|     dnsEntryIdArg: string, | ||||
|     dnsEntryDataArg: Partial<plugins.servezoneInterfaces.data.IDnsEntry['data']> | ||||
|   ) { | ||||
|     const dnsEntry = await this.getInstance({ | ||||
|       id: dnsEntryIdArg, | ||||
|     }); | ||||
|     if (!dnsEntry) { | ||||
|       throw new Error(`DNS entry with id ${dnsEntryIdArg} not found`); | ||||
|     } | ||||
|     Object.assign(dnsEntry.data, dnsEntryDataArg, { | ||||
|       updatedAt: Date.now(), | ||||
|     }); | ||||
|     await dnsEntry.save(); | ||||
|     return dnsEntry; | ||||
|   } | ||||
|  | ||||
|   public static async deleteDnsEntry(dnsEntryIdArg: string) { | ||||
|     const dnsEntry = await this.getInstance({ | ||||
|       id: dnsEntryIdArg, | ||||
|     }); | ||||
|     if (!dnsEntry) { | ||||
|       throw new Error(`DNS entry with id ${dnsEntryIdArg} not found`); | ||||
|     } | ||||
|     await dnsEntry.delete(); | ||||
|     return true; | ||||
|   } | ||||
|  | ||||
|   // INSTANCE | ||||
|   @plugins.smartdata.svDb() | ||||
|   public id: string; | ||||
|  | ||||
|   @plugins.smartdata.svDb() | ||||
|   public data: plugins.servezoneInterfaces.data.IDnsEntry['data']; | ||||
|  | ||||
|   /** | ||||
|    * Validates the DNS entry data | ||||
|    */ | ||||
|   public validateData(): boolean { | ||||
|     const { type, name, value, zone } = this.data; | ||||
|      | ||||
|     // Basic validation | ||||
|     if (!type || !name || !value || !zone) { | ||||
|       return false; | ||||
|     } | ||||
|  | ||||
|     // Type-specific validation | ||||
|     switch (type) { | ||||
|       case 'A': | ||||
|         // Validate IPv4 address | ||||
|         return /^(\d{1,3}\.){3}\d{1,3}$/.test(value); | ||||
|       case 'AAAA': | ||||
|         // Validate IPv6 address (simplified) | ||||
|         return /^([0-9a-fA-F]{0,4}:){7}[0-9a-fA-F]{0,4}$/.test(value); | ||||
|       case 'MX': | ||||
|         // MX records must have priority | ||||
|         return this.data.priority !== undefined && this.data.priority >= 0; | ||||
|       case 'SRV': | ||||
|         // SRV records must have priority, weight, and port | ||||
|         return ( | ||||
|           this.data.priority !== undefined && | ||||
|           this.data.weight !== undefined && | ||||
|           this.data.port !== undefined | ||||
|         ); | ||||
|       case 'CNAME': | ||||
|       case 'NS': | ||||
|       case 'PTR': | ||||
|         // These should point to valid domain names | ||||
|         return /^[a-zA-Z0-9.-]+$/.test(value); | ||||
|       case 'TXT': | ||||
|       case 'CAA': | ||||
|       case 'SOA': | ||||
|         // These can contain any text | ||||
|         return true; | ||||
|       default: | ||||
|         return false; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Get a formatted string representation of the DNS entry | ||||
|    */ | ||||
|   public toFormattedString(): string { | ||||
|     const { type, name, value, ttl, priority } = this.data; | ||||
|     let result = `${name} ${ttl} IN ${type}`; | ||||
|      | ||||
|     if (priority !== undefined) { | ||||
|       result += ` ${priority}`; | ||||
|     } | ||||
|      | ||||
|     if (type === 'SRV' && this.data.weight !== undefined && this.data.port !== undefined) { | ||||
|       result += ` ${this.data.weight} ${this.data.port}`; | ||||
|     } | ||||
|      | ||||
|     result += ` ${value}`; | ||||
|     return result; | ||||
|   } | ||||
| } | ||||
							
								
								
									
										152
									
								
								ts/manager.dns/classes.dnsmanager.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										152
									
								
								ts/manager.dns/classes.dnsmanager.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,152 @@ | ||||
| import type { Cloudly } from '../classes.cloudly.js'; | ||||
| import * as plugins from '../plugins.js'; | ||||
| import { DnsEntry } from './classes.dnsentry.js'; | ||||
|  | ||||
| export class DnsManager { | ||||
|   public typedrouter = new plugins.typedrequest.TypedRouter(); | ||||
|   public cloudlyRef: Cloudly; | ||||
|  | ||||
|   get db() { | ||||
|     return this.cloudlyRef.mongodbConnector.smartdataDb; | ||||
|   } | ||||
|  | ||||
|   public CDnsEntry = plugins.smartdata.setDefaultManagerForDoc(this, DnsEntry); | ||||
|  | ||||
|   constructor(cloudlyRef: Cloudly) { | ||||
|     this.cloudlyRef = cloudlyRef; | ||||
|      | ||||
|     this.cloudlyRef.typedrouter.addTypedRouter(this.typedrouter); | ||||
|  | ||||
|     // Get all DNS entries | ||||
|     this.typedrouter.addTypedHandler( | ||||
|       new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.dns.IRequest_Any_Cloudly_GetDnsEntries>( | ||||
|         'getDnsEntries', | ||||
|         async (reqArg) => { | ||||
|           await plugins.smartguard.passGuardsOrReject(reqArg, [ | ||||
|             this.cloudlyRef.authManager.validIdentityGuard, | ||||
|           ]); | ||||
|  | ||||
|           const dnsEntries = await this.CDnsEntry.getDnsEntries( | ||||
|             reqArg.zone ? { zone: reqArg.zone } : undefined | ||||
|           ); | ||||
|  | ||||
|           return { | ||||
|             dnsEntries: await Promise.all( | ||||
|               dnsEntries.map((entry) => entry.createSavableObject()) | ||||
|             ), | ||||
|           }; | ||||
|         } | ||||
|       ) | ||||
|     ); | ||||
|  | ||||
|     // Get DNS entry by ID | ||||
|     this.typedrouter.addTypedHandler( | ||||
|       new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.dns.IRequest_Any_Cloudly_GetDnsEntryById>( | ||||
|         'getDnsEntryById', | ||||
|         async (reqArg) => { | ||||
|           await plugins.smartguard.passGuardsOrReject(reqArg, [ | ||||
|             this.cloudlyRef.authManager.validIdentityGuard, | ||||
|           ]); | ||||
|  | ||||
|           const dnsEntry = await this.CDnsEntry.getDnsEntryById(reqArg.dnsEntryId); | ||||
|           if (!dnsEntry) { | ||||
|             throw new Error(`DNS entry with id ${reqArg.dnsEntryId} not found`); | ||||
|           } | ||||
|  | ||||
|           return { | ||||
|             dnsEntry: await dnsEntry.createSavableObject(), | ||||
|           }; | ||||
|         } | ||||
|       ) | ||||
|     ); | ||||
|  | ||||
|     // Create DNS entry | ||||
|     this.typedrouter.addTypedHandler( | ||||
|       new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.dns.IRequest_Any_Cloudly_CreateDnsEntry>( | ||||
|         'createDnsEntry', | ||||
|         async (reqArg) => { | ||||
|           await plugins.smartguard.passGuardsOrReject(reqArg, [ | ||||
|             this.cloudlyRef.authManager.validIdentityGuard, | ||||
|           ]); | ||||
|  | ||||
|           const dnsEntry = await this.CDnsEntry.createDnsEntry(reqArg.dnsEntryData); | ||||
|  | ||||
|           return { | ||||
|             dnsEntry: await dnsEntry.createSavableObject(), | ||||
|           }; | ||||
|         } | ||||
|       ) | ||||
|     ); | ||||
|  | ||||
|     // Update DNS entry | ||||
|     this.typedrouter.addTypedHandler( | ||||
|       new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.dns.IRequest_Any_Cloudly_UpdateDnsEntry>( | ||||
|         'updateDnsEntry', | ||||
|         async (reqArg) => { | ||||
|           await plugins.smartguard.passGuardsOrReject(reqArg, [ | ||||
|             this.cloudlyRef.authManager.validIdentityGuard, | ||||
|           ]); | ||||
|  | ||||
|           const dnsEntry = await this.CDnsEntry.updateDnsEntry( | ||||
|             reqArg.dnsEntryId, | ||||
|             reqArg.dnsEntryData | ||||
|           ); | ||||
|  | ||||
|           return { | ||||
|             dnsEntry: await dnsEntry.createSavableObject(), | ||||
|           }; | ||||
|         } | ||||
|       ) | ||||
|     ); | ||||
|  | ||||
|     // Delete DNS entry | ||||
|     this.typedrouter.addTypedHandler( | ||||
|       new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.dns.IRequest_Any_Cloudly_DeleteDnsEntry>( | ||||
|         'deleteDnsEntry', | ||||
|         async (reqArg) => { | ||||
|           await plugins.smartguard.passGuardsOrReject(reqArg, [ | ||||
|             this.cloudlyRef.authManager.validIdentityGuard, | ||||
|           ]); | ||||
|  | ||||
|           const success = await this.CDnsEntry.deleteDnsEntry(reqArg.dnsEntryId); | ||||
|  | ||||
|           return { | ||||
|             success, | ||||
|           }; | ||||
|         } | ||||
|       ) | ||||
|     ); | ||||
|  | ||||
|     // Get DNS zones | ||||
|     this.typedrouter.addTypedHandler( | ||||
|       new plugins.typedrequest.TypedHandler<plugins.servezoneInterfaces.requests.dns.IRequest_Any_Cloudly_GetDnsZones>( | ||||
|         'getDnsZones', | ||||
|         async (reqArg) => { | ||||
|           await plugins.smartguard.passGuardsOrReject(reqArg, [ | ||||
|             this.cloudlyRef.authManager.validIdentityGuard, | ||||
|           ]); | ||||
|  | ||||
|           const zones = await this.CDnsEntry.getDnsZones(); | ||||
|  | ||||
|           return { | ||||
|             zones, | ||||
|           }; | ||||
|         } | ||||
|       ) | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Initialize the DNS manager | ||||
|    */ | ||||
|   public async init() { | ||||
|     console.log('DNS Manager initialized'); | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Stop the DNS manager | ||||
|    */ | ||||
|   public async stop() { | ||||
|     console.log('DNS Manager stopped'); | ||||
|   } | ||||
| } | ||||
							
								
								
									
										203
									
								
								ts/manager.domain/classes.domain.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										203
									
								
								ts/manager.domain/classes.domain.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,203 @@ | ||||
| import * as plugins from '../plugins.js'; | ||||
| import { DomainManager } from './classes.domainmanager.js'; | ||||
|  | ||||
| export class Domain extends plugins.smartdata.SmartDataDbDoc< | ||||
|   Domain, | ||||
|   plugins.servezoneInterfaces.data.IDomain, | ||||
|   DomainManager | ||||
| > { | ||||
|   // STATIC | ||||
|   public static async getDomainById(domainIdArg: string) { | ||||
|     const domain = await this.getInstance({ | ||||
|       id: domainIdArg, | ||||
|     }); | ||||
|     return domain; | ||||
|   } | ||||
|  | ||||
|   public static async getDomainByName(domainNameArg: string) { | ||||
|     const domain = await this.getInstance({ | ||||
|       'data.name': domainNameArg, | ||||
|     }); | ||||
|     return domain; | ||||
|   } | ||||
|  | ||||
|   public static async getDomains() { | ||||
|     const domains = await this.getInstances({}); | ||||
|     return domains; | ||||
|   } | ||||
|  | ||||
|   public static async createDomain(domainDataArg: plugins.servezoneInterfaces.data.IDomain['data']) { | ||||
|     const domain = new Domain(); | ||||
|     domain.id = await Domain.getNewId(); | ||||
|     domain.data = { | ||||
|       ...domainDataArg, | ||||
|       status: domainDataArg.status || 'pending', | ||||
|       verificationStatus: domainDataArg.verificationStatus || 'pending', | ||||
|       nameservers: domainDataArg.nameservers || [], | ||||
|       autoRenew: domainDataArg.autoRenew !== false, | ||||
|       createdAt: Date.now(), | ||||
|       updatedAt: Date.now(), | ||||
|     }; | ||||
|     await domain.save(); | ||||
|     return domain; | ||||
|   } | ||||
|  | ||||
|   public static async updateDomain( | ||||
|     domainIdArg: string, | ||||
|     domainDataArg: Partial<plugins.servezoneInterfaces.data.IDomain['data']> | ||||
|   ) { | ||||
|     const domain = await this.getInstance({ | ||||
|       id: domainIdArg, | ||||
|     }); | ||||
|     if (!domain) { | ||||
|       throw new Error(`Domain with id ${domainIdArg} not found`); | ||||
|     } | ||||
|     Object.assign(domain.data, domainDataArg, { | ||||
|       updatedAt: Date.now(), | ||||
|     }); | ||||
|     await domain.save(); | ||||
|     return domain; | ||||
|   } | ||||
|  | ||||
|   public static async deleteDomain(domainIdArg: string) { | ||||
|     const domain = await this.getInstance({ | ||||
|       id: domainIdArg, | ||||
|     }); | ||||
|     if (!domain) { | ||||
|       throw new Error(`Domain with id ${domainIdArg} not found`); | ||||
|     } | ||||
|      | ||||
|     // Check if there are DNS entries for this domain | ||||
|     const dnsManager = domain.manager.cloudlyRef.dnsManager; | ||||
|     const dnsEntries = await dnsManager.CDnsEntry.getInstances({ | ||||
|       'data.zone': domain.data.name, | ||||
|     }); | ||||
|      | ||||
|     if (dnsEntries.length > 0) { | ||||
|       console.log(`Warning: Deleting domain ${domain.data.name} with ${dnsEntries.length} DNS entries`); | ||||
|       // Optionally delete associated DNS entries | ||||
|       for (const dnsEntry of dnsEntries) { | ||||
|         await dnsEntry.delete(); | ||||
|       } | ||||
|     } | ||||
|      | ||||
|     await domain.delete(); | ||||
|     return true; | ||||
|   } | ||||
|  | ||||
|   // INSTANCE | ||||
|   @plugins.smartdata.svDb() | ||||
|   public id: string; | ||||
|  | ||||
|   @plugins.smartdata.svDb() | ||||
|   public data: plugins.servezoneInterfaces.data.IDomain['data']; | ||||
|  | ||||
|   /** | ||||
|    * Verify domain ownership | ||||
|    */ | ||||
|   public async verifyDomain(methodArg?: 'dns' | 'http' | 'email' | 'manual') { | ||||
|     const method = methodArg || this.data.verificationMethod || 'dns'; | ||||
|      | ||||
|     // Generate verification token if not exists | ||||
|     if (!this.data.verificationToken) { | ||||
|       this.data.verificationToken = plugins.smartunique.shortId(); | ||||
|       await this.save(); | ||||
|     } | ||||
|      | ||||
|     let verificationResult = { | ||||
|       success: false, | ||||
|       message: '', | ||||
|       details: {} as any, | ||||
|     }; | ||||
|      | ||||
|     switch (method) { | ||||
|       case 'dns': | ||||
|         // Check for TXT record with verification token | ||||
|         verificationResult = await this.verifyViaDns(); | ||||
|         break; | ||||
|       case 'http': | ||||
|         // Check for file at well-known URL | ||||
|         verificationResult = await this.verifyViaHttp(); | ||||
|         break; | ||||
|       case 'email': | ||||
|         // Send verification email | ||||
|         verificationResult = await this.verifyViaEmail(); | ||||
|         break; | ||||
|       case 'manual': | ||||
|         // Manual verification | ||||
|         verificationResult.success = true; | ||||
|         verificationResult.message = 'Manually verified'; | ||||
|         break; | ||||
|     } | ||||
|      | ||||
|     // Update verification status | ||||
|     if (verificationResult.success) { | ||||
|       this.data.verificationStatus = 'verified'; | ||||
|       this.data.lastVerificationAt = Date.now(); | ||||
|       this.data.verificationMethod = method; | ||||
|     } else { | ||||
|       this.data.verificationStatus = 'failed'; | ||||
|       this.data.lastVerificationAt = Date.now(); | ||||
|     } | ||||
|      | ||||
|     await this.save(); | ||||
|     return verificationResult; | ||||
|   } | ||||
|    | ||||
|   private async verifyViaDns(): Promise<{ success: boolean; message: string; details: any }> { | ||||
|     // TODO: Implement DNS verification | ||||
|     // Look for TXT record _cloudly-verify.{domain} with value {verificationToken} | ||||
|     return { | ||||
|       success: false, | ||||
|       message: 'DNS verification not yet implemented', | ||||
|       details: { | ||||
|         expectedRecord: `_cloudly-verify.${this.data.name}`, | ||||
|         expectedValue: this.data.verificationToken, | ||||
|       }, | ||||
|     }; | ||||
|   } | ||||
|    | ||||
|   private async verifyViaHttp(): Promise<{ success: boolean; message: string; details: any }> { | ||||
|     // TODO: Implement HTTP verification | ||||
|     // Check for file at http://{domain}/.well-known/cloudly-verify.txt | ||||
|     return { | ||||
|       success: false, | ||||
|       message: 'HTTP verification not yet implemented', | ||||
|       details: { | ||||
|         expectedUrl: `http://${this.data.name}/.well-known/cloudly-verify.txt`, | ||||
|         expectedContent: this.data.verificationToken, | ||||
|       }, | ||||
|     }; | ||||
|   } | ||||
|    | ||||
|   private async verifyViaEmail(): Promise<{ success: boolean; message: string; details: any }> { | ||||
|     // TODO: Implement email verification | ||||
|     return { | ||||
|       success: false, | ||||
|       message: 'Email verification not yet implemented', | ||||
|       details: {}, | ||||
|     }; | ||||
|   } | ||||
|    | ||||
|   /** | ||||
|    * Check if domain is expiring soon | ||||
|    */ | ||||
|   public isExpiringSoon(daysThreshold: number = 30): boolean { | ||||
|     if (!this.data.expiresAt) { | ||||
|       return false; | ||||
|     } | ||||
|     const daysUntilExpiry = (this.data.expiresAt - Date.now()) / (1000 * 60 * 60 * 24); | ||||
|     return daysUntilExpiry <= daysThreshold; | ||||
|   } | ||||
|    | ||||
|   /** | ||||
|    * Get all DNS entries for this domain | ||||
|    */ | ||||
|   public async getDnsEntries() { | ||||
|     const dnsManager = this.manager.cloudlyRef.dnsManager; | ||||
|     const dnsEntries = await dnsManager.CDnsEntry.getInstances({ | ||||
|       'data.zone': this.data.name, | ||||
|     }); | ||||
|     return dnsEntries; | ||||
|   } | ||||
| } | ||||
							
								
								
									
										188
									
								
								ts/manager.domain/classes.domainmanager.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										188
									
								
								ts/manager.domain/classes.domainmanager.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,188 @@ | ||||
| import type { Cloudly } from '../classes.cloudly.js'; | ||||
| import * as plugins from '../plugins.js'; | ||||
| import { Domain } from './classes.domain.js'; | ||||
|  | ||||
| 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<plugins.servezoneInterfaces.requests.domain.IRequest_Any_Cloudly_GetDomains>( | ||||
|         '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<plugins.servezoneInterfaces.requests.domain.IRequest_Any_Cloudly_GetDomainById>( | ||||
|         '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<plugins.servezoneInterfaces.requests.domain.IRequest_Any_Cloudly_CreateDomain>( | ||||
|         '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<plugins.servezoneInterfaces.requests.domain.IRequest_Any_Cloudly_UpdateDomain>( | ||||
|         '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<plugins.servezoneInterfaces.requests.domain.IRequest_Any_Cloudly_DeleteDomain>( | ||||
|         '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<plugins.servezoneInterfaces.requests.domain.IRequest_Any_Cloudly_VerifyDomain>( | ||||
|         '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() { | ||||
|     console.log('Domain Manager initialized'); | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * 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<boolean> { | ||||
|     const existingDomain = await this.CDomain.getDomainByName(domainName); | ||||
|     return !existingDomain; | ||||
|   } | ||||
| } | ||||
		Reference in New Issue
	
	Block a user