mirror of
https://git.meshkee.com/Meshkee/backend.git
synced 2026-08-12 06:40:58 +04:30
Brand OTP SMS with business name and support local SMS proxy.
Append tenant Farsi name to verification SMS, allow optional domain on send-otp, and add SMS_PROXY_* for local delivery via production. Also include websites SSL sync agent/API wiring. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
co-authored by
Cursor
parent
77f3cb2a67
commit
8f05ee2b58
@@ -36,6 +36,14 @@ export class DomainAdminController {
|
||||
return this.service.syncSsl(user);
|
||||
}
|
||||
|
||||
/** Issue Let's Encrypt for storefront domains missing SSL (websites VM). */
|
||||
@Post('website-ssl')
|
||||
@HttpCode(200)
|
||||
@UseGuards(JwtAuthGuard)
|
||||
issueWebsiteSsl(@CurrentUser() user: AuthUser) {
|
||||
return this.service.issueWebsiteSsl(user);
|
||||
}
|
||||
|
||||
@Post(':domainId/deploy')
|
||||
@HttpCode(202)
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@@ -43,6 +51,13 @@ export class DomainAdminController {
|
||||
return this.service.deploy(domainId, user);
|
||||
}
|
||||
|
||||
@Post(':domainId/issue-ssl')
|
||||
@HttpCode(200)
|
||||
@UseGuards(JwtAuthGuard)
|
||||
issueSsl(@Param('domainId') domainId: string, @CurrentUser() user: AuthUser) {
|
||||
return this.service.issueSsl(domainId, user);
|
||||
}
|
||||
|
||||
@Patch(':domainId')
|
||||
@UseGuards(JwtAuthGuard)
|
||||
update(
|
||||
|
||||
@@ -10,6 +10,7 @@ import { ConfigService } from '@nestjs/config';
|
||||
import { Prisma } from '@prisma/client';
|
||||
import { AuthUser } from '../auth/auth.types';
|
||||
import { PermissionsService } from '../auth/permissions.service';
|
||||
import { probeTlsHost } from '../common/tls-probe';
|
||||
import { PrismaService } from '../prisma/prisma.service';
|
||||
import { WebsiteDeployAgentService } from '../website-deploy/website-deploy-agent.service';
|
||||
import { DisableDomainDto } from './dto/disable-domain.dto';
|
||||
@@ -125,7 +126,118 @@ export class DomainAdminService {
|
||||
|
||||
return {
|
||||
status: 'accepted' as const,
|
||||
message: 'SSL sync started on dashboards server',
|
||||
message: 'Dashboard SSL sync started (manage / business.* / customer.*)',
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Issue Let's Encrypt certs on the websites VM for storefront domains
|
||||
* that have deploy_slug and currently report ssl_enabled=false.
|
||||
*/
|
||||
async issueWebsiteSsl(actor: AuthUser) {
|
||||
await this.assertSuperAdmin(actor);
|
||||
|
||||
const targets = await this.prisma.domain.findMany({
|
||||
where: {
|
||||
isActive: true,
|
||||
sslEnabled: false,
|
||||
deploySlug: { not: null },
|
||||
},
|
||||
select: { id: true, host: true, deploySlug: true },
|
||||
orderBy: { host: 'asc' },
|
||||
});
|
||||
|
||||
if (targets.length === 0) {
|
||||
return {
|
||||
status: 'ok' as const,
|
||||
message: 'No storefront domains need SSL',
|
||||
issued: [] as string[],
|
||||
failed: [] as Array<{ host: string; error: string }>,
|
||||
};
|
||||
}
|
||||
|
||||
const issued: string[] = [];
|
||||
const failed: Array<{ host: string; error: string }> = [];
|
||||
|
||||
for (const domain of targets) {
|
||||
try {
|
||||
await this.websiteDeployAgent.issueSsl({
|
||||
host: domain.host,
|
||||
slug: domain.deploySlug,
|
||||
});
|
||||
const ok = await probeTlsHost(domain.host);
|
||||
await this.prisma.domain.update({
|
||||
where: { id: domain.id },
|
||||
data: { sslEnabled: ok },
|
||||
});
|
||||
if (ok) {
|
||||
issued.push(domain.host);
|
||||
} else {
|
||||
failed.push({
|
||||
host: domain.host,
|
||||
error: 'Certbot finished but TLS probe still failed',
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
failed.push({
|
||||
host: domain.host,
|
||||
error: err instanceof Error ? err.message : 'SSL issue failed',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const message =
|
||||
failed.length === 0
|
||||
? `Issued SSL for ${issued.length} website(s)`
|
||||
: `Issued ${issued.length}, failed ${failed.length} website SSL`;
|
||||
|
||||
return { status: 'ok' as const, message, issued, failed };
|
||||
}
|
||||
|
||||
async issueSsl(domainIdRaw: string, actor: AuthUser) {
|
||||
await this.assertSuperAdmin(actor);
|
||||
|
||||
const domainId = BigInt(domainIdRaw);
|
||||
const domain = await this.prisma.domain.findUnique({ where: { id: domainId } });
|
||||
if (!domain) {
|
||||
throw new NotFoundException('Domain not found');
|
||||
}
|
||||
|
||||
const slug = domain.deploySlug?.trim() || null;
|
||||
if (!slug) {
|
||||
throw new BadRequestException(
|
||||
'This domain has no storefront deploy configured — use Sync dashboard SSL for business/customer hosts',
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
await this.websiteDeployAgent.issueSsl({ host: domain.host, slug });
|
||||
} catch (err) {
|
||||
if (err instanceof ServiceUnavailableException) {
|
||||
throw err;
|
||||
}
|
||||
throw new ServiceUnavailableException(
|
||||
err instanceof Error ? err.message : 'SSL issue failed',
|
||||
);
|
||||
}
|
||||
|
||||
const ok = await probeTlsHost(domain.host);
|
||||
const updated = await this.prisma.domain.update({
|
||||
where: { id: domainId },
|
||||
data: { sslEnabled: ok },
|
||||
});
|
||||
|
||||
if (!ok) {
|
||||
throw new ServiceUnavailableException(
|
||||
`Certbot ran for ${domain.host} but HTTPS probe failed — check DNS for apex and www`,
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
status: 'issued' as const,
|
||||
host: domain.host,
|
||||
sslEnabled: updated.sslEnabled,
|
||||
message: `SSL issued for ${domain.host}`,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user