feat(banner): per-campaign colour overrides and urgent alerts
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Successful in 10s
CI / integration (pull_request) Successful in 15s
CI / ui (pull_request) Successful in 1m8s
CI / conformance (pull_request) Successful in 9s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m50s

Non-default campaigns gain an optional colour override (background / text /
link) in two sets — one for every theme, one for the dark theme only — and an
"urgent" flag.

- Colours ride profile.get as six trailing FlatBuffers strings on
  BannerCampaign (backward-compatible). The client resolves the cascade
  (dark <- dark ?? all, light <- all) per rendered theme and derives the strip
  border from the background in JS (no CSS color-mix, for the old Android
  WebView floor); AdBanner applies them as inline vars scoped to the strip.
- Urgent is resolved entirely server-side: while any enabled, in-window urgent
  campaign exists, computeActiveSet returns only the urgent campaigns and
  bannerFor skips the eligibility gate — so a system notice reaches every viewer
  (paid / hint-holding / no_banner included) and preempts the ordinary feed. No
  wire field; it appears on each viewer's next profile.get.
- Admin console (/_gm/banners): native colour pickers + a live light/dark
  preview of the strip, and an urgent toggle. The default campaign stays plain,
  enforced by the service and a DB CHECK.

Migration 00009 is additive (nullable colour columns + a bool default +
all-or-nothing / hex / default-plain CHECKs) — expand-contract, rollback-safe.

Docs: ARCHITECTURE §10, UI_DESIGN, FUNCTIONAL (+ru). Tests: ads unit (urgent
preempt + colour validation), codec + resolver unit, gateway transcode, and
integration (colour round-trip + urgent bypass against real Postgres).
This commit is contained in:
Ilia Denisov
2026-07-05 15:36:35 +02:00
parent ac383880b7
commit 6db9178449
32 changed files with 1297 additions and 152 deletions
@@ -0,0 +1,60 @@
-- Per-campaign banner colour overrides and the urgent (always-show) flag.
--
-- ad_campaigns gains two optional colour sets and an urgent flag, all for
-- non-default campaigns only:
-- override_bg/fg/link — paints the strip on every theme
-- override_bg/fg/link_dark — further overrides the dark theme
-- urgent — force the banner on every viewer (bypassing
-- eligibility) and suppress all non-urgent
-- campaigns while any urgent one is active
--
-- Each colour set is all-or-nothing (bg+fg+link together, or absent) and every
-- colour is a "#rrggbb" hex string. The default (house) campaign stays plain: no
-- colours, never urgent.
--
-- Expand-contract: additive (nullable columns plus one NOT NULL boolean with a
-- default), so a backend image rollback stays DB-safe — older code ignores them.
-- The ad_campaigns table shape changes, so its generated go-jet model is
-- regenerated (by hand here, to keep the diff to this one table).
-- +goose Up
ALTER TABLE backend.ad_campaigns
ADD COLUMN override_bg text,
ADD COLUMN override_fg text,
ADD COLUMN override_link text,
ADD COLUMN override_bg_dark text,
ADD COLUMN override_fg_dark text,
ADD COLUMN override_link_dark text,
ADD COLUMN urgent boolean DEFAULT false NOT NULL,
ADD CONSTRAINT ad_campaigns_override_all_chk CHECK (
(override_bg IS NULL AND override_fg IS NULL AND override_link IS NULL)
OR (override_bg IS NOT NULL AND override_fg IS NOT NULL AND override_link IS NOT NULL)
),
ADD CONSTRAINT ad_campaigns_override_dark_chk CHECK (
(override_bg_dark IS NULL AND override_fg_dark IS NULL AND override_link_dark IS NULL)
OR (override_bg_dark IS NOT NULL AND override_fg_dark IS NOT NULL AND override_link_dark IS NOT NULL)
),
ADD CONSTRAINT ad_campaigns_override_hex_chk CHECK (
(override_bg IS NULL OR override_bg ~ '^#[0-9a-fA-F]{6}$')
AND (override_fg IS NULL OR override_fg ~ '^#[0-9a-fA-F]{6}$')
AND (override_link IS NULL OR override_link ~ '^#[0-9a-fA-F]{6}$')
AND (override_bg_dark IS NULL OR override_bg_dark ~ '^#[0-9a-fA-F]{6}$')
AND (override_fg_dark IS NULL OR override_fg_dark ~ '^#[0-9a-fA-F]{6}$')
AND (override_link_dark IS NULL OR override_link_dark ~ '^#[0-9a-fA-F]{6}$')
),
ADD CONSTRAINT ad_campaigns_default_plain_chk CHECK (
(NOT is_default)
OR (override_bg IS NULL AND override_fg IS NULL AND override_link IS NULL
AND override_bg_dark IS NULL AND override_fg_dark IS NULL AND override_link_dark IS NULL
AND NOT urgent)
);
-- +goose Down
ALTER TABLE backend.ad_campaigns
DROP COLUMN override_bg,
DROP COLUMN override_fg,
DROP COLUMN override_link,
DROP COLUMN override_bg_dark,
DROP COLUMN override_fg_dark,
DROP COLUMN override_link_dark,
DROP COLUMN urgent;