Resolve custom domain SSL via certificate

Replace flattened SSL fields with a certificate
relation loaded through certmanager, and resolve
domain slots from IDs already on the trust center
instead of reloading the compliance page.

Signed-off-by: Bryan Frimin <bryan@probo.com>
This commit is contained in:
Bryan Frimin
2026-07-17 17:39:33 +02:00
parent bc128ec516
commit caeb6d9225
13 changed files with 198 additions and 186 deletions

View File

@@ -37,8 +37,10 @@ const fragment = graphql`
id id
domain domain
managed managed
sslStatus certificate {
provisioningError status
provisioningError
}
canDelete: permission(action: "compliance-portal:custom-domain:delete") canDelete: permission(action: "compliance-portal:custom-domain:delete")
...CompliancePageDomainDialogFragment ...CompliancePageDomainDialogFragment
} }
@@ -52,6 +54,8 @@ export function CompliancePageDomainCard(props: {
const { __ } = useTranslate(); const { __ } = useTranslate();
const domain = useFragment<CompliancePageDomainCardFragment$key>(fragment, fKey); const domain = useFragment<CompliancePageDomainCardFragment$key>(fragment, fKey);
const sslStatus = domain.certificate?.status ?? "PENDING";
const provisioningError = domain.certificate?.provisioningError;
return ( return (
<Card padded> <Card padded>
@@ -62,15 +66,15 @@ export function CompliancePageDomainCard(props: {
{domain.managed && ( {domain.managed && (
<Badge variant="neutral">{__("Managed")}</Badge> <Badge variant="neutral">{__("Managed")}</Badge>
)} )}
<Badge variant={getCustomDomainStatusBadgeVariant(domain.sslStatus)}> <Badge variant={getCustomDomainStatusBadgeVariant(sslStatus)}>
{getCustomDomainStatusBadgeLabel(domain.sslStatus, __)} {getCustomDomainStatusBadgeLabel(sslStatus, __)}
</Badge> </Badge>
</div> </div>
<p className="text-sm text-txt-secondary"> <p className="text-sm text-txt-secondary">
{domain.sslStatus === "ACTIVE" {sslStatus === "ACTIVE"
? __("Verified and serving traffic") ? __("Verified and serving traffic")
: domain.provisioningError : provisioningError
? domain.provisioningError ? provisioningError
: __("Pending DNS verification")} : __("Pending DNS verification")}
</p> </p>
</div> </div>

View File

@@ -39,9 +39,12 @@ import type { CompliancePageDomainDialogFragment$key } from "#/__generated__/cor
const fragment = graphql` const fragment = graphql`
fragment CompliancePageDomainDialogFragment on CustomDomain { fragment CompliancePageDomainDialogFragment on CustomDomain {
sslStatus
domain domain
provisioningError certificate {
status
expiresAt
provisioningError
}
dnsRecords { dnsRecords {
type type
name name
@@ -49,7 +52,6 @@ const fragment = graphql`
ttl ttl
purpose purpose
} }
sslExpiresAt
} }
`; `;
@@ -72,6 +74,9 @@ export function CompliancePageDomainDialog(props: CompliancePageDomainDialogProp
}; };
const domain = useFragment<CompliancePageDomainDialogFragment$key>(fragment, fKey); const domain = useFragment<CompliancePageDomainDialogFragment$key>(fragment, fKey);
const sslStatus = domain.certificate?.status ?? "PENDING";
const expiresAt = domain.certificate?.expiresAt;
const provisioningError = domain.certificate?.provisioningError;
return ( return (
<Dialog <Dialog
@@ -80,14 +85,14 @@ export function CompliancePageDomainDialog(props: CompliancePageDomainDialogProp
title={( title={(
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<span>{domain.domain}</span> <span>{domain.domain}</span>
<Badge variant={getCustomDomainStatusBadgeVariant(domain.sslStatus)}> <Badge variant={getCustomDomainStatusBadgeVariant(sslStatus)}>
{getCustomDomainStatusBadgeLabel(domain.sslStatus, __)} {getCustomDomainStatusBadgeLabel(sslStatus, __)}
</Badge> </Badge>
</div> </div>
)} )}
> >
<DialogContent padded className="space-y-6"> <DialogContent padded className="space-y-6">
{domain.sslStatus === "ACTIVE" {sslStatus === "ACTIVE"
? ( ? (
<div className="bg-subtle rounded-lg p-4"> <div className="bg-subtle rounded-lg p-4">
<div className="flex items-start"> <div className="flex items-start">
@@ -109,11 +114,11 @@ export function CompliancePageDomainDialog(props: CompliancePageDomainDialogProp
"Your custom domain is verified and SSL certificate is active", "Your custom domain is verified and SSL certificate is active",
)} )}
</p> </p>
{domain.sslExpiresAt && ( {expiresAt && (
<p className="text-xs text-txt-tertiary mt-2"> <p className="text-xs text-txt-tertiary mt-2">
{__("SSL expires")} {__("SSL expires")}
{" "} {" "}
{new Date(domain.sslExpiresAt).toLocaleDateString()} {new Date(expiresAt).toLocaleDateString()}
</p> </p>
)} )}
</div> </div>
@@ -122,10 +127,10 @@ export function CompliancePageDomainDialog(props: CompliancePageDomainDialogProp
) )
: ( : (
<div> <div>
{domain.provisioningError && ( {provisioningError && (
<div className="bg-danger-subtle text-danger rounded-lg p-4 mb-4"> <div className="bg-danger-subtle text-danger rounded-lg p-4 mb-4">
<p className="text-sm font-medium mb-1">{__("Provisioning error")}</p> <p className="text-sm font-medium mb-1">{__("Provisioning error")}</p>
<p className="text-sm">{domain.provisioningError}</p> <p className="text-sm">{provisioningError}</p>
</div> </div>
)} )}
@@ -188,7 +193,7 @@ export function CompliancePageDomainDialog(props: CompliancePageDomainDialogProp
))} ))}
</div> </div>
{domain.sslStatus === "PENDING" && ( {sslStatus === "PENDING" && (
<div className="bg-subtle rounded-lg p-4 mt-4"> <div className="bg-subtle rounded-lg p-4 mt-4">
<p className="text-sm"> <p className="text-sm">
{__( {__(

View File

@@ -42,7 +42,11 @@ const createCustomDomainMutation = graphql`
customDomain { customDomain {
id id
domain domain
sslStatus certificate {
status
expiresAt
provisioningError
}
dnsRecords { dnsRecords {
type type
name name
@@ -52,7 +56,6 @@ const createCustomDomainMutation = graphql`
} }
createdAt createdAt
updatedAt updatedAt
sslExpiresAt
canDelete: permission(action: "compliance-portal:custom-domain:delete") canDelete: permission(action: "compliance-portal:custom-domain:delete")
...CompliancePageDomainCardFragment ...CompliancePageDomainCardFragment
} }

View File

@@ -138,73 +138,18 @@ func (s *Service) RemoveCustomDomain(
) )
} }
// GetDefaultDomain returns the compliance page's default probopage subdomain, // GetDomain returns a custom domain by ID.
// or nil when it has not been provisioned yet. func (s *Service) GetDomain(
func (s *Service) GetDefaultDomain(
ctx context.Context, ctx context.Context,
scope coredata.Scoper, scope coredata.Scoper,
compliancePageID gid.GID, domainID gid.GID,
) (*coredata.CustomDomain, error) { ) (*coredata.CustomDomain, error) {
var domain *coredata.CustomDomain domain := &coredata.CustomDomain{}
err := s.pg.WithConn( err := s.pg.WithConn(
ctx, ctx,
func(ctx context.Context, conn pg.Querier) error { func(ctx context.Context, conn pg.Querier) error {
compliancePage := &coredata.TrustCenter{} if err := domain.LoadByID(ctx, conn, scope, domainID); err != nil {
if err := compliancePage.LoadByID(ctx, conn, scope, compliancePageID); err != nil {
return fmt.Errorf("cannot load compliance page: %w", err)
}
if compliancePage.DefaultDomainID == nil {
return nil
}
domain = &coredata.CustomDomain{}
if err := domain.LoadByID(ctx, conn, scope, *compliancePage.DefaultDomainID); err != nil {
if errors.Is(err, coredata.ErrResourceNotFound) {
domain = nil
return nil
}
return fmt.Errorf("cannot load custom domain: %w", err)
}
return nil
},
)
if err != nil {
return nil, err
}
return domain, nil
}
func (s *Service) GetCustomDomain(
ctx context.Context,
scope coredata.Scoper,
compliancePageID gid.GID,
) (*coredata.CustomDomain, error) {
var domain *coredata.CustomDomain
err := s.pg.WithConn(
ctx,
func(ctx context.Context, conn pg.Querier) error {
compliancePage := &coredata.TrustCenter{}
if err := compliancePage.LoadByID(ctx, conn, scope, compliancePageID); err != nil {
return fmt.Errorf("cannot load compliance page: %w", err)
}
if compliancePage.CustomDomainID == nil {
return nil
}
domain = &coredata.CustomDomain{}
if err := domain.LoadByID(ctx, conn, scope, *compliancePage.CustomDomainID); err != nil {
if errors.Is(err, coredata.ErrResourceNotFound) {
domain = nil
return nil
}
return fmt.Errorf("cannot load custom domain: %w", err) return fmt.Errorf("cannot load custom domain: %w", err)
} }

View File

@@ -249,31 +249,21 @@ enum TrustCenterFileOrderField
} }
enum SSLStatus enum SSLStatus
@goModel(model: "go.probo.inc/probo/pkg/coredata.CustomDomainSSLStatus") { @goModel(model: "go.probo.inc/probo/pkg/coredata.CertificateStatus") {
PENDING PENDING
@goEnum( @goEnum(value: "go.probo.inc/probo/pkg/coredata.CertificateStatusPending")
value: "go.probo.inc/probo/pkg/coredata.CustomDomainSSLStatusPending"
)
PROVISIONING PROVISIONING
@goEnum( @goEnum(
value: "go.probo.inc/probo/pkg/coredata.CustomDomainSSLStatusProvisioning" value: "go.probo.inc/probo/pkg/coredata.CertificateStatusProvisioning"
) )
ACTIVE ACTIVE
@goEnum( @goEnum(value: "go.probo.inc/probo/pkg/coredata.CertificateStatusActive")
value: "go.probo.inc/probo/pkg/coredata.CustomDomainSSLStatusActive"
)
RENEWING RENEWING
@goEnum( @goEnum(value: "go.probo.inc/probo/pkg/coredata.CertificateStatusRenewing")
value: "go.probo.inc/probo/pkg/coredata.CustomDomainSSLStatusRenewing"
)
EXPIRED EXPIRED
@goEnum( @goEnum(value: "go.probo.inc/probo/pkg/coredata.CertificateStatusExpired")
value: "go.probo.inc/probo/pkg/coredata.CustomDomainSSLStatusExpired"
)
FAILED FAILED
@goEnum( @goEnum(value: "go.probo.inc/probo/pkg/coredata.CertificateStatusFailed")
value: "go.probo.inc/probo/pkg/coredata.CustomDomainSSLStatusFailed"
)
} }
input TrustCenterAccessOrder input TrustCenterAccessOrder
@@ -648,9 +638,7 @@ type CustomDomain implements Node {
organization: Organization! organization: Organization!
domain: String! domain: String!
managed: Boolean! managed: Boolean!
sslStatus: SSLStatus! certificate: Certificate @goField(forceResolver: true)
sslExpiresAt: Datetime
provisioningError: String
dnsRecords: [DNSRecordInstruction!]! dnsRecords: [DNSRecordInstruction!]!
createdAt: Datetime! createdAt: Datetime!
updatedAt: Datetime! updatedAt: Datetime!
@@ -658,6 +646,13 @@ type CustomDomain implements Node {
permission(action: String!): Boolean! @goField(forceResolver: true) permission(action: String!): Boolean! @goField(forceResolver: true)
} }
type Certificate {
id: ID!
status: SSLStatus!
expiresAt: Datetime
provisioningError: String
}
type DNSRecordInstruction { type DNSRecordInstruction {
type: String! type: String!
name: String! name: String!

View File

@@ -85,26 +85,6 @@ type (
} }
) )
// newCustomDomainType loads the domain's certificate (when present) and builds
// the GraphQL CustomDomain type with its certificate-backed SSL fields.
func (r *Resolver) newCustomDomainType(
ctx context.Context,
scope coredata.Scoper,
domain *coredata.CustomDomain,
) (*types.CustomDomain, error) {
var cert *coredata.Certificate
if domain != nil && domain.CertificateID != nil {
var err error
cert, err = r.certManager.Get(ctx, scope, *domain.CertificateID)
if err != nil {
r.logger.ErrorCtx(ctx, "cannot load certificate", log.Error(err))
return nil, gqlutils.Internal(ctx)
}
}
return types.NewCustomDomain(domain, cert, r.customDomainCname), nil
}
func NewMux( func NewMux(
logger *log.Logger, logger *log.Logger,
proboSvc *probo.Service, proboSvc *probo.Service,

View File

@@ -124,6 +124,30 @@ func (r *compliancePortalCommitmentGroupConnectionResolver) TotalCount(ctx conte
return count, nil return count, nil
} }
// Certificate is the resolver for the certificate field.
func (r *customDomainResolver) Certificate(ctx context.Context, obj *types.CustomDomain) (*types.Certificate, error) {
if obj.Certificate == nil {
return nil, nil
}
scope, err := r.authorize(ctx, obj.ID, management.ActionCustomDomainGet)
if err != nil {
return nil, err
}
cert, err := r.certManager.Get(ctx, scope, obj.Certificate.ID)
if err != nil {
if errors.Is(err, coredata.ErrResourceNotFound) {
return nil, nil
}
r.logger.ErrorCtx(ctx, "cannot load certificate", log.Error(err))
return nil, gqlutils.Internal(ctx)
}
return types.NewCertificate(cert), nil
}
// Permission is the resolver for the permission field. // Permission is the resolver for the permission field.
func (r *customDomainResolver) Permission(ctx context.Context, obj *types.CustomDomain, action string) (bool, error) { func (r *customDomainResolver) Permission(ctx context.Context, obj *types.CustomDomain, action string) (bool, error) {
return r.Resolver.Permission(ctx, obj, action) return r.Resolver.Permission(ctx, obj, action)
@@ -893,13 +917,8 @@ func (r *mutationResolver) CreateCustomDomain(ctx context.Context, input types.C
return nil, gqlutils.Internal(ctx) return nil, gqlutils.Internal(ctx)
} }
customDomain, err := r.newCustomDomainType(ctx, scope, domain)
if err != nil {
return nil, err
}
return &types.CreateCustomDomainPayload{ return &types.CreateCustomDomainPayload{
CustomDomain: customDomain, CustomDomain: types.NewCustomDomain(domain, r.customDomainCname),
}, nil }, nil
} }
@@ -1169,42 +1188,50 @@ func (r *trustCenterResolver) MailingList(ctx context.Context, obj *types.TrustC
// DefaultDomain is the resolver for the defaultDomain field. // DefaultDomain is the resolver for the defaultDomain field.
func (r *trustCenterResolver) DefaultDomain(ctx context.Context, obj *types.TrustCenter) (*types.CustomDomain, error) { func (r *trustCenterResolver) DefaultDomain(ctx context.Context, obj *types.TrustCenter) (*types.CustomDomain, error) {
if obj.DefaultDomain == nil {
return nil, nil
}
scope, err := r.authorize(ctx, obj.ID, management.ActionCustomDomainGet) scope, err := r.authorize(ctx, obj.ID, management.ActionCustomDomainGet)
if err != nil { if err != nil {
return nil, err return nil, err
} }
domain, err := r.management.GetDefaultDomain(ctx, scope, obj.ID) domain, err := r.management.GetDomain(ctx, scope, obj.DefaultDomain.ID)
if err != nil { if err != nil {
if errors.Is(err, coredata.ErrResourceNotFound) {
return nil, nil
}
r.logger.ErrorCtx(ctx, "cannot load default domain", log.Error(err)) r.logger.ErrorCtx(ctx, "cannot load default domain", log.Error(err))
return nil, gqlutils.Internal(ctx) return nil, gqlutils.Internal(ctx)
} }
if domain == nil { return types.NewCustomDomain(domain, r.customDomainCname), nil
return nil, nil
}
return r.newCustomDomainType(ctx, scope, domain)
} }
// CustomDomain is the resolver for the customDomain field. // CustomDomain is the resolver for the customDomain field.
func (r *trustCenterResolver) CustomDomain(ctx context.Context, obj *types.TrustCenter) (*types.CustomDomain, error) { func (r *trustCenterResolver) CustomDomain(ctx context.Context, obj *types.TrustCenter) (*types.CustomDomain, error) {
if obj.CustomDomain == nil {
return nil, nil
}
scope, err := r.authorize(ctx, obj.ID, management.ActionCustomDomainGet) scope, err := r.authorize(ctx, obj.ID, management.ActionCustomDomainGet)
if err != nil { if err != nil {
return nil, err return nil, err
} }
domain, err := r.management.GetCustomDomain(ctx, scope, obj.ID) domain, err := r.management.GetDomain(ctx, scope, obj.CustomDomain.ID)
if err != nil { if err != nil {
if errors.Is(err, coredata.ErrResourceNotFound) {
return nil, nil
}
r.logger.ErrorCtx(ctx, "cannot load custom domain", log.Error(err)) r.logger.ErrorCtx(ctx, "cannot load custom domain", log.Error(err))
return nil, gqlutils.Internal(ctx) return nil, gqlutils.Internal(ctx)
} }
if domain == nil { return types.NewCustomDomain(domain, r.customDomainCname), nil
return nil, nil
}
return r.newCustomDomainType(ctx, scope, domain)
} }
// PublicURL is the resolver for the publicUrl field. // PublicURL is the resolver for the publicUrl field.

View File

@@ -0,0 +1,34 @@
// Copyright (c) 2025-2026 Probo Inc <hello@probo.com>.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
package types
import (
"go.probo.inc/probo/pkg/coredata"
)
func NewCertificate(c *coredata.Certificate) *Certificate {
return &Certificate{
ID: c.ID,
Status: c.Status,
ExpiresAt: c.SSLExpiresAt,
ProvisioningError: c.ProvisioningError,
}
}

View File

@@ -24,46 +24,34 @@ import (
"go.probo.inc/probo/pkg/coredata" "go.probo.inc/probo/pkg/coredata"
) )
// NewCustomDomain builds the GraphQL CustomDomain type. The TLS lifecycle now func NewCustomDomain(d *coredata.CustomDomain, cnameTarget string) *CustomDomain {
// lives on the linked certificate; when cert is nil (certificate not yet domain := &CustomDomain{
// created) the domain reports a pending SSL status.
func NewCustomDomain(d *coredata.CustomDomain, cert *coredata.Certificate, cnameTarget string) *CustomDomain {
result := &CustomDomain{
ID: d.ID, ID: d.ID,
Organization: &Organization{ Organization: &Organization{
ID: d.OrganizationID, ID: d.OrganizationID,
}, },
Domain: d.Domain, Domain: d.Domain,
Managed: d.Managed, Managed: d.Managed,
SslStatus: coredata.CustomDomainSSLStatusPending, DNSRecords: convertDNSRecords(d, cnameTarget),
CreatedAt: d.CreatedAt, CreatedAt: d.CreatedAt,
UpdatedAt: d.UpdatedAt, UpdatedAt: d.UpdatedAt,
} }
if cert != nil { if d.CertificateID != nil {
result.SslStatus = coredata.CustomDomainSSLStatus(cert.Status) domain.Certificate = &Certificate{ID: *d.CertificateID}
result.SslExpiresAt = cert.SSLExpiresAt
result.ProvisioningError = cert.ProvisioningError
} }
// Convert DNS records return domain
result.DNSRecords = convertDNSRecords(d, cnameTarget)
return result
} }
func convertDNSRecords(d *coredata.CustomDomain, cnameTarget string) []*DNSRecordInstruction { func convertDNSRecords(d *coredata.CustomDomain, cnameTarget string) []*DNSRecordInstruction {
var records []*DNSRecordInstruction return []*DNSRecordInstruction{
{
// For HTTP-01 challenges, we just need the domain to point to our servers via CNAME Type: "CNAME",
record := &DNSRecordInstruction{ Name: d.Domain,
Type: "CNAME", Value: cnameTarget,
Name: d.Domain, TTL: 300,
Value: cnameTarget, Purpose: "Point domain to Probo servers",
TTL: 300, },
Purpose: "Point domain to Probo servers",
} }
records = append(records, record)
return records
} }

View File

@@ -47,6 +47,8 @@ type TrustCenter struct {
ComplianceFrameworks *ComplianceFrameworkConnection `json:"complianceFrameworks"` ComplianceFrameworks *ComplianceFrameworkConnection `json:"complianceFrameworks"`
CustomLinks *ComplianceCustomLinkConnection `json:"customLinks"` CustomLinks *ComplianceCustomLinkConnection `json:"customLinks"`
MailingList *MailingList `json:"mailingList,omitempty"` MailingList *MailingList `json:"mailingList,omitempty"`
DefaultDomain *CustomDomain `json:"defaultDomain,omitempty"`
CustomDomain *CustomDomain `json:"customDomain,omitempty"`
Permission bool `json:"permission"` Permission bool `json:"permission"`
} }
@@ -82,5 +84,13 @@ func NewTrustCenter(tc *coredata.TrustCenter) *TrustCenter {
trustCenter.Nda = &File{ID: *tc.NonDisclosureAgreementFileID} trustCenter.Nda = &File{ID: *tc.NonDisclosureAgreementFileID}
} }
if tc.DefaultDomainID != nil {
trustCenter.DefaultDomain = &CustomDomain{ID: *tc.DefaultDomainID}
}
if tc.CustomDomainID != nil {
trustCenter.CustomDomain = &CustomDomain{ID: *tc.CustomDomainID}
}
return trustCenter return trustCenter
} }

View File

@@ -5306,15 +5306,20 @@ func (r *Resolver) DeleteCustomDomainTool(ctx context.Context, req *mcp.CallTool
return nil, types.DeleteCustomDomainOutput{}, err return nil, types.DeleteCustomDomainOutput{}, err
} }
domain, err := r.management.GetCustomDomain(ctx, scope, input.TrustCenterID) compliancePage, err := r.management.Get(ctx, scope, input.TrustCenterID)
if err != nil { if err != nil {
return nil, types.DeleteCustomDomainOutput{}, fmt.Errorf("cannot get custom domain: %w", err) return nil, types.DeleteCustomDomainOutput{}, fmt.Errorf("cannot load compliance page: %w", err)
} }
if domain == nil { if compliancePage.CustomDomainID == nil {
return nil, types.DeleteCustomDomainOutput{}, fmt.Errorf("compliance page has no custom domain") return nil, types.DeleteCustomDomainOutput{}, fmt.Errorf("compliance page has no custom domain")
} }
domain, err := r.management.GetDomain(ctx, scope, *compliancePage.CustomDomainID)
if err != nil {
return nil, types.DeleteCustomDomainOutput{}, fmt.Errorf("cannot get custom domain: %w", err)
}
var cert *coredata.Certificate var cert *coredata.Certificate
if domain.CertificateID != nil { if domain.CertificateID != nil {
cert, err = r.certManager.Get(ctx, scope, *domain.CertificateID) cert, err = r.certManager.Get(ctx, scope, *domain.CertificateID)

View File

@@ -8737,7 +8737,7 @@ components:
- RENEWING - RENEWING
- EXPIRED - EXPIRED
- FAILED - FAILED
go.probo.inc/mcpgen/type: go.probo.inc/probo/pkg/coredata.CustomDomainSSLStatus go.probo.inc/mcpgen/type: go.probo.inc/probo/pkg/coredata.CertificateStatus
TrustCenterReferenceOrderField: TrustCenterReferenceOrderField:
type: string type: string
@@ -8968,6 +8968,26 @@ components:
type: string type: string
format: date-time format: date-time
Certificate:
type: object
required:
- id
- status
properties:
id:
$ref: "#/components/schemas/GID"
status:
$ref: "#/components/schemas/SSLStatus"
expires_at:
anyOf:
- type: string
format: date-time
- type: "null"
provisioning_error:
anyOf:
- type: string
- type: "null"
CustomDomain: CustomDomain:
type: object type: object
required: required:
@@ -8975,7 +8995,6 @@ components:
- organization_id - organization_id
- domain - domain
- managed - managed
- ssl_status
- created_at - created_at
- updated_at - updated_at
properties: properties:
@@ -8988,12 +9007,9 @@ components:
managed: managed:
type: boolean type: boolean
description: Whether this domain is a Probo-managed probopage subdomain description: Whether this domain is a Probo-managed probopage subdomain
ssl_status: certificate:
$ref: "#/components/schemas/SSLStatus"
ssl_expires_at:
anyOf: anyOf:
- type: string - $ref: "#/components/schemas/Certificate"
format: date-time
- type: "null" - type: "null"
created_at: created_at:
type: string type: string

View File

@@ -24,24 +24,24 @@ import (
"go.probo.inc/probo/pkg/coredata" "go.probo.inc/probo/pkg/coredata"
) )
// NewCustomDomain builds the MCP CustomDomain type. The TLS lifecycle now lives
// on the linked certificate; when cert is nil (certificate not yet created) the
// domain reports a pending SSL status.
func NewCustomDomain(d *coredata.CustomDomain, cert *coredata.Certificate) *CustomDomain { func NewCustomDomain(d *coredata.CustomDomain, cert *coredata.Certificate) *CustomDomain {
result := &CustomDomain{ domain := &CustomDomain{
ID: d.ID, ID: d.ID,
OrganizationID: d.OrganizationID, OrganizationID: d.OrganizationID,
Domain: d.Domain, Domain: d.Domain,
Managed: d.Managed, Managed: d.Managed,
SslStatus: coredata.CustomDomainSSLStatusPending,
CreatedAt: d.CreatedAt, CreatedAt: d.CreatedAt,
UpdatedAt: d.UpdatedAt, UpdatedAt: d.UpdatedAt,
} }
if cert != nil { if cert != nil {
result.SslStatus = coredata.CustomDomainSSLStatus(cert.Status) domain.Certificate = &Certificate{
result.SslExpiresAt = cert.SSLExpiresAt ID: cert.ID,
Status: cert.Status,
ExpiresAt: cert.SSLExpiresAt,
ProvisioningError: cert.ProvisioningError,
}
} }
return result return domain
} }