Add lifecycle_status = 'published' to the public-catalog queries (plans and add-ons listings) and reject checkout before any Stripe call unless the product behind the price clears the shared member gate (published + active + public). The currently-enrolled ladder rung stays renderable even if its product is later drafted or retired, fetched directly so members keep seeing what they are on. Introduce a single evaluateMemberGate definition shared by the catalog paths and the operator readiness panel so the surfaces cannot disagree about what is publishable for members.
102 lines
4.0 KiB
Go
102 lines
4.0 KiB
Go
package maintenance
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"log/slog"
|
|
"time"
|
|
|
|
"git.coopcloud.tech/wiki-cafe/member-console/internal/workflows/queues"
|
|
"go.temporal.io/sdk/client"
|
|
)
|
|
|
|
const (
|
|
// WebhookPartitionEnsureScheduleID is the unique identifier for the
|
|
// webhook_events partition ensure schedule.
|
|
WebhookPartitionEnsureScheduleID = "webhook-partition-ensure"
|
|
// WebhookPartitionEnsureWorkflowID is the workflow ID used for
|
|
// schedule-triggered ensure runs. Schedule-spawned runs carry a
|
|
// timestamp-suffixed workflow ID on top of this base
|
|
// (design.md Risks/Trade-offs — resolve via the schedule handle, not a
|
|
// bespoke ID lookup).
|
|
WebhookPartitionEnsureWorkflowID = "webhook-partition-ensure-workflow"
|
|
// DefaultWebhookPartitionEnsureInterval is the default interval between
|
|
// scheduled ensure runs.
|
|
DefaultWebhookPartitionEnsureInterval = 24 * time.Hour
|
|
)
|
|
|
|
// ScheduleManager manages the Temporal schedule for webhook_events
|
|
// partition maintenance.
|
|
type ScheduleManager struct {
|
|
client client.Client
|
|
logger *slog.Logger
|
|
}
|
|
|
|
// NewScheduleManager creates a new ScheduleManager.
|
|
func NewScheduleManager(c client.Client, logger *slog.Logger) *ScheduleManager {
|
|
return &ScheduleManager{client: c, logger: logger}
|
|
}
|
|
|
|
// WebhookPartitionScheduleConfig holds configuration for the ensure
|
|
// schedule.
|
|
type WebhookPartitionScheduleConfig struct {
|
|
// Interval is how often to run the ensure workflow.
|
|
Interval time.Duration
|
|
// TriggerImmediately runs the ensure workflow immediately when creating
|
|
// the schedule. The webhook-partition-maintenance spec requires this
|
|
// (unlike the domains claim expiry sweep, whose boot pass is deliberately
|
|
// the only immediate run): "Recurring Temporal schedule keeps partitions
|
|
// extended" SHALL trigger one run immediately on registration.
|
|
TriggerImmediately bool
|
|
}
|
|
|
|
// EnsureWebhookPartitionSchedule creates or updates the webhook_events
|
|
// partition ensure schedule. If the schedule already exists, it updates the
|
|
// spec; otherwise it creates it (describe-then-update, mirroring
|
|
// internal/workflows/domains/schedule.go's EnsureExpirySweepSchedule).
|
|
func (m *ScheduleManager) EnsureWebhookPartitionSchedule(ctx context.Context, cfg WebhookPartitionScheduleConfig) error {
|
|
if cfg.Interval == 0 {
|
|
cfg.Interval = DefaultWebhookPartitionEnsureInterval
|
|
}
|
|
|
|
scheduleClient := m.client.ScheduleClient()
|
|
|
|
handle := scheduleClient.GetHandle(ctx, WebhookPartitionEnsureScheduleID)
|
|
if _, err := handle.Describe(ctx); err == nil {
|
|
m.logger.Info("updating existing webhook partition ensure schedule", slog.Duration("interval", cfg.Interval))
|
|
if err := handle.Update(ctx, client.ScheduleUpdateOptions{
|
|
DoUpdate: func(schedule client.ScheduleUpdateInput) (*client.ScheduleUpdate, error) {
|
|
schedule.Description.Schedule.Spec = &client.ScheduleSpec{
|
|
Intervals: []client.ScheduleIntervalSpec{{Every: cfg.Interval}},
|
|
}
|
|
schedule.Description.Schedule.Action = &client.ScheduleWorkflowAction{
|
|
ID: WebhookPartitionEnsureWorkflowID,
|
|
Workflow: EnsureWebhookPartitionsWorkflow,
|
|
TaskQueue: queues.Main,
|
|
}
|
|
return &client.ScheduleUpdate{Schedule: &schedule.Description.Schedule}, nil
|
|
},
|
|
}); err != nil {
|
|
return fmt.Errorf("failed to update webhook partition ensure schedule: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
m.logger.Info("creating webhook partition ensure schedule",
|
|
slog.Duration("interval", cfg.Interval), slog.Bool("triggerImmediately", cfg.TriggerImmediately))
|
|
if _, err := scheduleClient.Create(ctx, client.ScheduleOptions{
|
|
ID: WebhookPartitionEnsureScheduleID,
|
|
Spec: client.ScheduleSpec{Intervals: []client.ScheduleIntervalSpec{{Every: cfg.Interval}}},
|
|
Action: &client.ScheduleWorkflowAction{
|
|
ID: WebhookPartitionEnsureWorkflowID,
|
|
Workflow: EnsureWebhookPartitionsWorkflow,
|
|
TaskQueue: queues.Main,
|
|
},
|
|
TriggerImmediately: cfg.TriggerImmediately,
|
|
}); err != nil {
|
|
return fmt.Errorf("failed to create webhook partition ensure schedule: %w", err)
|
|
}
|
|
m.logger.Info("webhook partition ensure schedule created", slog.String("scheduleID", WebhookPartitionEnsureScheduleID))
|
|
return nil
|
|
}
|