mirror of
https://git.meshkee.com/Meshkee/backend.git
synced 2026-08-11 22:30:59 +04:30
Per-row ensure and SSL refresh treat storefront as valid only when both names match, and toast messages list each host explicitly. Co-authored-by: Cursor <cursoragent@cursor.com>
127 lines
4.1 KiB
TypeScript
127 lines
4.1 KiB
TypeScript
import { Injectable, Logger, OnModuleInit } from '@nestjs/common';
|
|
import { ConfigService } from '@nestjs/config';
|
|
import { Cron, CronExpression } from '@nestjs/schedule';
|
|
import { PrismaService } from '../prisma/prisma.service';
|
|
import { probeTlsHosts } from '../common/tls-probe';
|
|
|
|
@Injectable()
|
|
export class InternalSslService implements OnModuleInit {
|
|
private readonly logger = new Logger(InternalSslService.name);
|
|
private refreshRunning = false;
|
|
|
|
constructor(
|
|
private readonly prisma: PrismaService,
|
|
private readonly config: ConfigService,
|
|
) {}
|
|
|
|
onModuleInit() {
|
|
// Warm ssl_enabled shortly after boot so lists are accurate without waiting for cron.
|
|
setTimeout(() => {
|
|
void this.refreshSslStatuses('startup');
|
|
}, 5_000);
|
|
}
|
|
|
|
private async listActiveDomains(): Promise<Array<{ id: bigint; host: string; sslEnabled: boolean }>> {
|
|
return this.prisma.domain.findMany({
|
|
where: { isActive: true },
|
|
select: { id: true, host: true, sslEnabled: true },
|
|
orderBy: { host: 'asc' },
|
|
});
|
|
}
|
|
|
|
private async listActiveApexHosts(): Promise<string[]> {
|
|
const domains = await this.listActiveDomains();
|
|
const hosts: string[] = [];
|
|
for (const { host } of domains) {
|
|
const apex = host.trim().toLowerCase();
|
|
if (apex) hosts.push(apex);
|
|
}
|
|
return hosts;
|
|
}
|
|
|
|
/** Dashboards VPS: manage + business./customer. per active apex. */
|
|
async listDashboardHosts(): Promise<{ hosts: string[] }> {
|
|
const adminHost =
|
|
this.config.get<string>('DASHBOARD_ADMIN_HOST')?.trim() || 'manage.meshkee.com';
|
|
|
|
const hosts = new Set<string>([adminHost]);
|
|
for (const apex of await this.listActiveApexHosts()) {
|
|
hosts.add(`business.${apex}`);
|
|
hosts.add(`customer.${apex}`);
|
|
}
|
|
|
|
return { hosts: [...hosts].sort() };
|
|
}
|
|
|
|
/**
|
|
* API VPS: central api host + api.{apex} aliases for each active domain.
|
|
* Same Nest process; nginx terminates TLS for every name on this list.
|
|
*/
|
|
async listApiHosts(): Promise<{ hosts: string[] }> {
|
|
const centralHost =
|
|
this.config.get<string>('CENTRAL_API_HOST')?.trim() || 'api.meshkee.com';
|
|
|
|
const hosts = new Set<string>([centralHost]);
|
|
for (const apex of await this.listActiveApexHosts()) {
|
|
hosts.add(`api.${apex}`);
|
|
}
|
|
|
|
return { hosts: [...hosts].sort() };
|
|
}
|
|
|
|
/**
|
|
* Probe each active apex host and sync domains.ssl_enabled to the live result.
|
|
* Runs every 2 hours (and once shortly after API boot).
|
|
*/
|
|
@Cron(CronExpression.EVERY_2_HOURS)
|
|
async refreshSslStatusesCron() {
|
|
await this.refreshSslStatuses('cron');
|
|
}
|
|
|
|
async refreshSslStatuses(reason: 'cron' | 'startup' | 'manual' = 'manual') {
|
|
if (this.refreshRunning) {
|
|
this.logger.warn(`SSL status refresh skipped (${reason}): already running`);
|
|
return { updated: 0, checked: 0, skipped: true as const };
|
|
}
|
|
|
|
this.refreshRunning = true;
|
|
const started = Date.now();
|
|
try {
|
|
const domains = await this.listActiveDomains();
|
|
const hostsToProbe = domains.flatMap((d) => {
|
|
const apex = d.host.trim().toLowerCase();
|
|
if (!apex) return [];
|
|
const www = apex.startsWith('www.') ? apex : `www.${apex}`;
|
|
return [apex, www];
|
|
});
|
|
const sslByHost = await probeTlsHosts(hostsToProbe);
|
|
|
|
let updated = 0;
|
|
for (const domain of domains) {
|
|
const hostKey = domain.host.trim().toLowerCase();
|
|
const wwwKey = hostKey.startsWith('www.') ? hostKey : `www.${hostKey}`;
|
|
const sslEnabled =
|
|
(sslByHost.get(hostKey) ?? false) && (sslByHost.get(wwwKey) ?? false);
|
|
if (sslEnabled === domain.sslEnabled) continue;
|
|
|
|
await this.prisma.domain.update({
|
|
where: { id: domain.id },
|
|
data: { sslEnabled },
|
|
});
|
|
updated += 1;
|
|
this.logger.log(
|
|
`SSL status ${domain.host}: ${domain.sslEnabled} → ${sslEnabled}`,
|
|
);
|
|
}
|
|
|
|
this.logger.log(
|
|
`SSL status refresh (${reason}): checked=${domains.length} updated=${updated} in ${Date.now() - started}ms`,
|
|
);
|
|
|
|
return { updated, checked: domains.length, skipped: false as const };
|
|
} finally {
|
|
this.refreshRunning = false;
|
|
}
|
|
}
|
|
}
|