Documentation
¶
Overview ¶
Package ads is your paid ad campaigns, launched and paused from one place.
A campaign carries an objective, a budget and reported spend, and runs on Meta, Google, TikTok, Reddit, LinkedIn or Microsoft with the org's own connector token.
It is also the PAID executor of the go-to-market plane: apps/campaign fans its paid channel out to LaunchPaid/PaidSpend/PausePaid (provider.go), and this surface runs the same campaigns standalone.
The AdCampaign entity is the root of the ad hierarchy (campaign → ad sets → ads): a named campaign on an ad Platform (meta/google/tiktok/x), a lifecycle Status (draft/active/paused/completed), an Objective, and Budget/Spend in minor units (cents). The ad-set and ad legs of the hierarchy are follow-ups that hang off this seam; this is the reviewable domain + campaign CRUD they attach to.
Tenant isolation is enforced SERVER-SIDE on every request: the org is principal.Org(c) — the value SanitizeIdentity minted from the VALIDATED bearer owner claim (HIP-0026) — and NEVER a client-supplied header. Every store query filters WHERE org=?, so one tenant can never read or mutate another's data.
Surface (all org-scoped; /v1 only):
GET /v1/ads/summary per-org roll-up (total/active/budget/spend)
GET /v1/ads/campaigns list campaigns (?status=) -> {data:[…]}
POST /v1/ads/campaigns create a campaign -> AdCampaign (201)
GET /v1/ads/campaigns/:id campaign detail -> AdCampaign
PUT /v1/ads/campaigns/:id update a campaign -> AdCampaign
DELETE /v1/ads/campaigns/:id delete a campaign
POST /v1/ads/campaigns/:id/launch run it on the provider -> AdCampaign
serve.go auto-registers GET /v1/ads/health (this subsystem does not set OwnsHealth, so the generic always-ok liveness route serves it).
Index ¶
- func Mount(app cloud.Router, deps cloud.Deps) error
- func PaidSpend(ctx context.Context, org string, ref PaidRef) (int64, error)
- func PausePaid(ctx context.Context, org string, ref PaidRef) error
- func Shutdown() error
- type AdCampaign
- type PaidPlan
- type PaidRef
- type Store
- func (s *Store) Close() error
- func (s *Store) Counts(ctx context.Context, org string) (total, active int, budget, spend int64, err error)
- func (s *Store) CreateCampaign(ctx context.Context, c AdCampaign) (AdCampaign, error)
- func (s *Store) DeleteCampaign(ctx context.Context, org, id string) (bool, error)
- func (s *Store) GetCampaign(ctx context.Context, org, id string) (AdCampaign, error)
- func (s *Store) ListCampaigns(ctx context.Context, org, status string, limit int) ([]AdCampaign, error)
- func (s *Store) MarkLaunched(ctx context.Context, org, id, account, externalID string, updatedAt int64) (AdCampaign, error)
- func (s *Store) UpdateCampaign(ctx context.Context, c AdCampaign) (AdCampaign, error)
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func Mount ¶
Mount wires the ads surface onto app per HIP-0106. It keeps a package global (mounted) for Shutdown, so it constructs the Service value directly — the same "complex flavour" clients/crm uses.
func PaidSpend ¶
PaidSpend reads the provider-reported spend (minor units) for a launched ad campaign. Fail-closed on the token; honest 0 when the platform is unsupported.
Types ¶
type AdCampaign ¶ added in v1.801.350
type AdCampaign struct {
ID string `json:"id"`
Org string `json:"-"`
Name string `json:"name"`
Platform string `json:"platform"`
Account string `json:"account,omitempty"` // provider ad-account ref (Meta act_<id>)
ExternalID string `json:"externalId,omitempty"` // provider campaign id after a launch
Status string `json:"status"`
Objective string `json:"objective"`
Budget int64 `json:"budget"`
Spend int64 `json:"spend"`
CreatedAt int64 `json:"createdAt"`
UpdatedAt int64 `json:"updatedAt"`
}
AdCampaign is an org-scoped ad campaign — the root of the ad hierarchy (campaign → ad sets → ads; the ad-set/ad legs hang off this seam as follow-ups). Budget and Spend are minor units (cents). Platform is the ad network (meta/google/tiktok/x); Status is the lifecycle (draft/active/paused/completed) — both validated at the write layer against the fixed vocabularies in ads.go.
type PaidPlan ¶
type PaidPlan struct {
Platform string
Account string // provider ad-account ref (Meta: act_<id> or <id>)
Name string
Objective string // provider objective enum; defaulted per-provider when empty
BudgetCents int64
ScheduleAt int64
}
PaidPlan is the standalone contract for launching one ad campaign — the campaign paid channel adapts campaign.Plan onto it (apps/wire_seams.go). BudgetCents is the org's own (connector-paid) budget; ScheduleAt an optional start time.
type PaidRef ¶
type PaidRef struct {
Platform string
Account string
ExternalID string
Status string
Detail string
}
PaidRef is a launched ad campaign's durable handle: the provider-side id plus the platform/account needed to read spend or pause it.
func LaunchPaid ¶
LaunchPaid creates the ad campaign on its provider using the org's connector token. Fail-closed: it resolves the token BEFORE any provider call. Meta is executed for real; other connected platforms return errUnsupportedPlatform (the connector is verified, only the provider impl is the remaining gap).
type Store ¶
type Store struct {
// contains filtered or unexported fields
}
Store is the ads database. ONE SQLite file — the system namespace's "ads" — holds every org's records; tenant isolation is the `org` column, enforced on EVERY query. This mirrors clients/crm exactly (the ONE storage pattern). MaxOpenConns(1) serializes writes against the single-writer file.
func (*Store) Counts ¶
func (s *Store) Counts(ctx context.Context, org string) (total, active int, budget, spend int64, err error)
Counts returns the per-org campaign roll-up: total campaigns, how many are active, and the summed budget + spend (cents) — a real, non-fabricated summary for the ads module's overview cards.
func (*Store) CreateCampaign ¶
func (s *Store) CreateCampaign(ctx context.Context, c AdCampaign) (AdCampaign, error)
func (*Store) DeleteCampaign ¶
func (*Store) GetCampaign ¶
func (*Store) ListCampaigns ¶
func (s *Store) ListCampaigns(ctx context.Context, org, status string, limit int) ([]AdCampaign, error)
ListCampaigns lists the org's campaigns, optionally filtered by status (status=="" means all). Most-recently-updated first.
func (*Store) MarkLaunched ¶
func (s *Store) MarkLaunched(ctx context.Context, org, id, account, externalID string, updatedAt int64) (AdCampaign, error)
MarkLaunched records the provider execution on a stored campaign: its account + external id, and status=active. Org-scoped — a cross-tenant id affects zero rows (errNotFound), never a foreign mutation. This is the ONLY writer of external_id, so the launch link is never clobbered by a user edit.
func (*Store) UpdateCampaign ¶
func (s *Store) UpdateCampaign(ctx context.Context, c AdCampaign) (AdCampaign, error)
UpdateCampaign edits the user-owned fields. external_id is deliberately NOT in the SET list: it is launch-owned (MarkLaunched sets it), so editing a campaign never clobbers the link to its live provider execution.