cloud

package
v0.46.1 Latest Latest
Warning

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

Go to latest
Published: Sep 26, 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