Files
warmbly/internal/api/handler
Matt 0f3552b1bf feat(admin): per-org limit overrides + product hard caps
Wire the write path for the override table from the previous migration:

  GET /admin/organizations/:id/overrides   view_organizations
  PUT /admin/organizations/:id/overrides   manage_organizations

PUT is a partial upsert — nil fields leave existing values untouched,
and 0 explicitly removes that column's override (back to plan default
or the product hard cap). Every write stamps granted_by/granted_at and
fires an admin audit log with the diff the admin asked for.

Introduce product-level hard caps in config/constants.go so plans that
advertise "unlimited" still have a real backstop: 200 mailboxes, 500
total campaigns, 100 active campaigns, 100 team members, 1M contacts,
1k daily campaign sends. GetEffectiveLimits resolves per-field as
override > 0 ?: plan ?: hard_cap and now never returns nil pointers,
so downstream limit checks compare against a concrete ceiling on every
plan tier. CanAddMember / CanAddCampaign / CanAddEmailAccount now call
GetEffectiveLimits instead of GetOrganizationLimits, so admin overrides
and the product hard cap both bite at runtime.

AdminOrgDetail surfaces three limit blocks side-by-side — plan, raw
override row (0 = inherit), and effective limits — so the UI can show
exactly where each enforced number came from. Slice 2 UI lands in the
next commit.

Daily creation throttles ("no 1000 new campaigns in one day even on an
unlimited plan") are explicitly out of scope; they need a per-day
counter, tracked as a TODO on the hard-cap block.
2026-05-28 08:54:07 +02:00
..
2026-01-17 14:11:14 +00:00
2026-01-29 05:59:04 +01:00
2026-01-29 05:59:04 +01:00
2026-01-29 05:59:04 +01:00
2026-01-26 16:04:42 +01:00
2026-02-14 05:38:27 +01:00
2026-01-29 05:59:04 +01:00
2026-01-17 14:11:14 +00:00
2026-01-17 14:11:14 +00:00