Help us shape the future of Go! Take the 2026 Go Developer Survey

cloud

package
v0.48.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 28, 2026 License: MIT Imports: 19 Imported by: 0

Documentation

Overview

Package cloud is the self-serve cloud/SaaS PREPARATION skeleton (v0.7 "Kimlik" E3 — docs/CLOUD.md).

Everything here is master-gated by FILEX_CLOUD (config.CloudConfig.Enabled, default OFF). While the flag is off the package is never constructed: api.BuildRouter skips the /api/cloud route block entirely, capabilities carry no cloud field, and the migration-00021 provider columns (plan / limits_json / billing_ref) stay NULL. Turning the flag on only makes sense together with FILEX_MULTI_TENANT=1 — a signed-up "tenant" IS a provider row from the v0.1.61 native multi-tenancy foundation, provisioned through the exact same db.Store.CreateProvider primitive the /api/admin/providers lifecycle API uses (no parallel provisioning path exists).

Index

Constants

This section is empty.

Variables

View Source
var (
	// ErrInvalid — malformed signup input (→ 400).
	ErrInvalid = errors.New("cloud: invalid input")
	// ErrConflict — tenant slug already taken (→ 409).
	ErrConflict = errors.New("cloud: conflict")
	// ErrTokenUnknown — verification token unknown or expired (→ 404).
	ErrTokenUnknown = errors.New("cloud: unknown or expired verification token")
)

Sentinel error kinds the handler maps to HTTP statuses.

View Source
var ErrStripeNotConfigured = errors.New("cloud: stripe not configured")

ErrStripeNotConfigured — no STRIPE_SECRET; the billing endpoints answer 503.

Functions

func VerifyWebhookSignature

func VerifyWebhookSignature(payload []byte, sigHeader, signingSecret string) error

VerifyWebhookSignature drafts the Stripe-Signature check. SKELETON — always rejects until implemented so an accidentally exposed webhook can never be spoofed into doing something.

TODO(cloud-launch): parse `t=…,v1=…`, HMAC-SHA256 over "<t>.<payload>" with the endpoint's signing secret (whsec_…), constant-time compare + 5-minute tolerance window.

Types

type Plan

type Plan struct {
	// ID is the stable plan key stored in providers.plan (e.g. "free", "pro").
	ID string `json:"id"`
	// Name is the display name.
	Name string `json:"name"`
	// PriceMonthly is a display string (e.g. "9.90 EUR"); "" = free.
	PriceMonthly string `json:"price_monthly,omitempty"`
	// StripePriceID links the plan to a Stripe Price for checkout sessions.
	// Empty on free plans / while billing is not wired.
	StripePriceID string `json:"stripe_price_id,omitempty"`
	// Limits are the plan's resource ceilings.
	Limits PlanLimits `json:"limits"`
}

Plan is one entry of the config-driven catalog (FILEX_CLOUD_PLANS).

func DefaultPlans

func DefaultPlans() []Plan

DefaultPlans is the built-in catalog used when FILEX_CLOUD_PLANS is empty: a single free skeleton plan so the signup flow is exercisable without any configuration.

func ParsePlans

func ParsePlans(raw string) ([]Plan, error)

ParsePlans decodes the FILEX_CLOUD_PLANS JSON array. An empty input returns DefaultPlans(). Errors (bad JSON, missing/duplicate ids) are returned so the caller can surface them; callers that must keep booting fall back to DefaultPlans() and report the error via /api/cloud/status.

type PlanLimits

type PlanLimits struct {
	// StorageBytes caps the tenant's total storage. 0 = unlimited.
	StorageBytes int64 `json:"storage_bytes,omitempty"`
	// MaxUsers caps the tenant's user count. 0 = unlimited.
	MaxUsers int `json:"max_users,omitempty"`
}

PlanLimits are the resource ceilings a plan grants a tenant. The zero value means "unlimited" for that dimension. Stamped as a JSON snapshot into providers.limits_json at signup so later catalog edits don't silently change an existing tenant's entitlement.

type Service

type Service struct {
	// contains filtered or unexported fields
}

Service implements the self-signup skeleton. Constructed ONLY when FILEX_CLOUD=1 (api.BuildRouter); nothing else references it.

func New

func New(store db.Store, m *mailer.Service, plansJSON, baseHost string) *Service

New builds the Service from the runtime config pieces. An invalid FILEX_CLOUD_PLANS does not abort boot: the service falls back to DefaultPlans() and reports the parse error through Status().

func (*Service) PlanByID

func (s *Service) PlanByID(id string) *Plan

PlanByID resolves a plan id; nil when unknown.

func (*Service) Plans

func (s *Service) Plans() []Plan

Plans returns the active catalog.

func (*Service) PlansErr

func (s *Service) PlansErr() string

PlansErr returns the remembered FILEX_CLOUD_PLANS parse error ("" when the catalog loaded cleanly).

func (*Service) Signup

func (s *Service) Signup(ctx context.Context, req SignupRequest) (*SignupResult, error)

Signup validates the request and provisions the tenant DISABLED, pending e-mail verification. Provisioning reuses the exact primitive the /api/admin/providers lifecycle API uses (db.Store.CreateProvider) — no parallel provisioning path — then stamps the plan snapshot into the migration-00021 columns. Storage linking is deliberately NOT part of the skeleton (see docs/CLOUD.md runbook).

func (*Service) Verify

func (s *Service) Verify(ctx context.Context, token string) (*model.Provider, error)

Verify consumes a verification token: the tenant flips Enabled=true through the same UpdateProvider path the admin API uses.

type SignupRequest

type SignupRequest struct {
	// Email of the prospective tenant owner (receives the verification mail).
	Email string `json:"email"`
	// Slug is the requested tenant identifier (DNS-label rules).
	Slug string `json:"slug"`
	// Name is the display name; defaults to the slug.
	Name string `json:"name,omitempty"`
	// Plan is a plan id from the catalog; defaults to the first plan.
	Plan string `json:"plan,omitempty"`
	// Locale is the language the verification mail is written in — the
	// signing-up visitor's (the handler fills it from the request when the
	// body does not say). Any language the server speaks; English otherwise.
	Locale string `json:"locale,omitempty"`
}

SignupRequest is the POST /api/cloud/signup payload.

type SignupResult

type SignupResult struct {
	ProviderID int64  `json:"tenant_id"`
	Slug       string `json:"slug"`
	Host       string `json:"host,omitempty"`
	Plan       string `json:"plan"`
	// MailSent — whether the verification mail went out through SMTP.
	MailSent bool `json:"mail_sent"`
	// VerifyToken is returned ONLY when the mail could not be sent (no/unverified
	// SMTP) so a local/dev flow can complete verification — the same on-screen
	// fallback pattern the share/invite mailer uses. Empty when mailed.
	VerifyToken string `json:"verify_token,omitempty"`
}

SignupResult reports a provisioned (but not yet verified) tenant.

type StripeClient

type StripeClient struct {
	// Secret is the API secret key (sk_test_… / sk_live_…). Empty = not
	// configured; every call returns ErrStripeNotConfigured.
	Secret string
	// HTTP is the transport; nil → a 15s-timeout default client.
	HTTP *http.Client
}

StripeClient is a minimal stdlib-http Stripe caller.

func NewStripe

func NewStripe(secret string) *StripeClient

NewStripe constructs the client (secret may be empty = not configured).

func (*StripeClient) Configured

func (c *StripeClient) Configured() bool

Configured reports whether a secret key is present.

func (*StripeClient) CreateCheckoutSession

func (c *StripeClient) CreateCheckoutSession(ctx context.Context, priceID, successURL, cancelURL string) (string, error)

CreateCheckoutSession drafts POST /v1/checkout/sessions and returns the hosted checkout URL. DRAFT — the request wiring is real but the parameter set is the bare minimum:

TODO(cloud-launch): pass customer_email + client_reference_id (tenant slug) TODO(cloud-launch): set subscription mode metadata for the webhook to map TODO(cloud-launch): idempotency key header (Idempotency-Key) TODO(cloud-launch): typed error decoding (Stripe error JSON envelope)

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL