fix(api): проверять ответы настроек и выпуска сертификатов

This commit is contained in:
Denozordec committed 2026-10-01 13:48:50 +07:00
1 parent adb6589816
commit df89e88f4e
5 files changed
+139 -65

No files matched your search

+55 -63
View File
@@ -1,85 +1,77 @@
import { certificatesStateSchema } from "@mmapp/contracts/certificates"
import type {
AcmeCloudflareSettingsDto,
CertificateIssueJob,
CertificateIssueRequest,
CertificateRenewSettingsDto,
CertificatesListResponse,
import { z } from "zod"
import {
acmeCloudflareSettingsDtoSchema,
certificateIssueJobSchema,
certificateIssueRequestSchema,
certificateRenewSettingsDtoSchema,
certificatesListResponseSchema,
certificatesStateSchema,
putAcmeCloudflareSettingsSchema,
putCertificateRenewSettingsSchema,
testAcmeCloudflareSettingsSchema,
} from "@mmapp/contracts/certificates"
import { requestJson } from "@/shared/api/http-client"
import type { CertificateIssueRequest } from "@mmapp/contracts/certificates"
import { ApiClientError, requestJson } from "@/shared/api/http-client"
export async function listCertificates(baseUrl: string): Promise<CertificatesListResponse> {
return requestJson<CertificatesListResponse>(baseUrl, "/api/certificates")
const issueAckSchema = z.object({ jobId: z.string().min(1).refine(value => value.trim() === value) })
const acmeTestSchema = z.object({ ok: z.boolean(), message: z.string().optional() })
function certificateJson(baseUrl: string, path: string, options?: RequestInit) {
const signal = AbortSignal.any([...(options?.signal ? [options.signal] : []), AbortSignal.timeout(30_000)])
return requestJson<unknown>(baseUrl, path, { ...options, signal })
}
export async function refreshCertificates(baseUrl: string): Promise<CertificatesListResponse> {
return requestJson<CertificatesListResponse>(baseUrl, "/api/certificates/refresh", { method: "POST" })
export async function listCertificates(baseUrl: string, options?: RequestInit) {
return certificatesListResponseSchema.parse(await certificateJson(baseUrl, "/api/certificates", { ...options, method: "GET" }))
}
export async function createCertificateIssueJob(
baseUrl: string,
payload: CertificateIssueRequest,
): Promise<{ jobId: string }> {
return requestJson<{ jobId: string }>(baseUrl, "/api/certificates/issue", {
method: "POST",
body: JSON.stringify(payload),
})
export async function refreshCertificates(baseUrl: string, options?: RequestInit) {
return certificatesListResponseSchema.parse(await certificateJson(baseUrl, "/api/certificates/refresh", { ...options, method: "POST" }))
}
export async function getCertificateIssueJob(
baseUrl: string,
jobId: string,
): Promise<CertificateIssueJob> {
return requestJson<CertificateIssueJob>(baseUrl, `/api/certificates/issue/${encodeURIComponent(jobId)}`)
export async function createCertificateIssueJob(baseUrl: string, payload: CertificateIssueRequest, options?: RequestInit) {
const body = certificateIssueRequestSchema.parse(payload)
return issueAckSchema.parse(await certificateJson(baseUrl, "/api/certificates/issue", { ...options, method: "POST", body: JSON.stringify(body) }))
}
export async function getAcmeSettings(baseUrl: string): Promise<AcmeCloudflareSettingsDto> {
return requestJson<AcmeCloudflareSettingsDto>(baseUrl, "/api/certificates/acme-settings")
export async function getCertificateIssueJob(baseUrl: string, jobId: string, options?: RequestInit) {
const result = certificateIssueJobSchema.parse(await certificateJson(baseUrl, "/api/certificates/issue/" + encodeURIComponent(jobId), { ...options, method: "GET" }))
if (result.id !== jobId) throw new Error("Ответ относится к другой задаче выпуска")
return result
}
export async function putAcmeSettings(
baseUrl: string,
payload: {
directoryUrl?: string
defaultZoneId?: string | null
cloudflareApiToken?: string | null
},
): Promise<AcmeCloudflareSettingsDto> {
return requestJson<AcmeCloudflareSettingsDto>(baseUrl, "/api/certificates/acme-settings", {
method: "PUT",
body: JSON.stringify(payload),
})
export async function getAcmeSettings(baseUrl: string, options?: RequestInit) {
return acmeCloudflareSettingsDtoSchema.parse(await certificateJson(baseUrl, "/api/certificates/acme-settings", { ...options, method: "GET" }))
}
export async function testAcmeSettings(
baseUrl: string,
payload?: { cloudflareApiToken?: string },
): Promise<{ ok: boolean; message?: string }> {
return requestJson<{ ok: boolean; message?: string }>(baseUrl, "/api/certificates/acme-settings/test", {
method: "POST",
body: JSON.stringify(payload ?? {}),
})
export async function putAcmeSettings(baseUrl: string, payload: { directoryUrl?: string; defaultZoneId?: string | null; cloudflareApiToken?: string | null }, options?: RequestInit) {
const body = putAcmeCloudflareSettingsSchema.parse(payload)
return acmeCloudflareSettingsDtoSchema.parse(await certificateJson(baseUrl, "/api/certificates/acme-settings", { ...options, method: "PUT", body: JSON.stringify(body) }))
}
export async function getCertificateRenewSettings(baseUrl: string): Promise<CertificateRenewSettingsDto> {
return requestJson<CertificateRenewSettingsDto>(baseUrl, "/api/certificates/renew-settings")
export async function testAcmeSettings(baseUrl: string, payload?: { cloudflareApiToken?: string }, options?: RequestInit) {
const body = testAcmeCloudflareSettingsSchema.parse(payload ?? {})
try {
return acmeTestSchema.parse(await certificateJson(baseUrl, "/api/certificates/acme-settings/test", { ...options, method: "POST", body: JSON.stringify(body) }))
} catch (error) {
// Backend возвращает message для отказа Cloudflare, а не стандартное error.
if (error instanceof ApiClientError) {
const failure = acmeTestSchema.safeParse(error.payload)
if (failure.success && !failure.data.ok && failure.data.message) throw new ApiClientError(failure.data.message, error.status, error.payload)
}
throw error
}
}
export async function putCertificateRenewSettings(
baseUrl: string,
payload: {
enabled?: boolean
intervalSec?: number
renewBeforeDays?: number
},
): Promise<CertificateRenewSettingsDto> {
return requestJson<CertificateRenewSettingsDto>(baseUrl, "/api/certificates/renew-settings", {
method: "PUT",
body: JSON.stringify(payload),
})
export async function getCertificateRenewSettings(baseUrl: string, options?: RequestInit) {
return certificateRenewSettingsDtoSchema.parse(await certificateJson(baseUrl, "/api/certificates/renew-settings", { ...options, method: "GET" }))
}
export async function putCertificateRenewSettings(baseUrl: string, payload: { enabled?: boolean; intervalSec?: number; renewBeforeDays?: number }, options?: RequestInit) {
const body = putCertificateRenewSettingsSchema.parse(payload)
return certificateRenewSettingsDtoSchema.parse(await certificateJson(baseUrl, "/api/certificates/renew-settings", { ...options, method: "PUT", body: JSON.stringify(body) }))
}
export async function readCertificatesState(baseUrl: string, options?: RequestInit) {
const signal = AbortSignal.any([...(options?.signal ? [options.signal] : []), AbortSignal.timeout(30_000)])
return certificatesStateSchema.parse(await requestJson<unknown>(baseUrl, "/api/certificates/state", { ...options, method: "GET", signal }))
return certificatesStateSchema.parse(await certificateJson(baseUrl, "/api/certificates/state", { ...options, method: "GET" }))
}