f40ef6b7c0
Align Cloudly with the current typedserver, smartconfig, smartstate, and Docker tooling releases so builds and Docker output stay compatible with the upgraded stack.
212 lines
6.2 KiB
TypeScript
212 lines
6.2 KiB
TypeScript
import * as plugins from '../plugins.js';
|
|
import { DomainManager } from './classes.domainmanager.js';
|
|
|
|
@plugins.smartdata.managed()
|
|
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,
|
|
activationState: domainDataArg.activationState || 'available',
|
|
syncSource: domainDataArg.syncSource ?? null,
|
|
lastSyncAt: domainDataArg.lastSyncAt,
|
|
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`);
|
|
}
|
|
// Merge updates and respect incoming activationState when provided
|
|
Object.assign(domain.data, domainDataArg);
|
|
domain.data.updatedAt = Date.now();
|
|
// Ensure activationState has a sensible default if still missing
|
|
if (!domain.data.activationState) {
|
|
(domain.data as any).activationState = 'available';
|
|
}
|
|
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.unI()
|
|
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;
|
|
}
|
|
}
|