refactor migration system: single source of truth via replay
Eliminated dual-maintenance of schema.sql + versioned migrations. All databases (fresh and existing) now replay migrations in order starting from 0000_initial_schema.py. Removed _is_fresh_db() and the fresh-DB fast-path that skipped migration execution. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
86
CHANGELOG.md
86
CHANGELOG.md
@@ -6,6 +6,92 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Changed
|
||||
- **Migration system: single source of truth** — eliminated dual-maintenance
|
||||
of `schema.sql` + versioned migrations; all databases (fresh and existing)
|
||||
now replay migrations in order starting from `0000_initial_schema.py`;
|
||||
removed `schema.sql`, `_is_fresh_db()`, and the fresh-DB fast-path that
|
||||
skipped migration execution; `migrate()` accepts an optional `db_path`
|
||||
parameter for direct use in tests; test fixtures use cached migration replay
|
||||
instead of loading `schema.sql` directly; removed fragile `_old_schema_sql()`
|
||||
test helper and `TestMigration0001` class; template repo updated to match
|
||||
(deleted `0001_roles_and_billing_customers.py`, projects own their migrations)
|
||||
- **Design system: Bricolage Grotesque + DM Sans** — replaced Inter with
|
||||
Bricolage Grotesque (display headings) and DM Sans (body text); added
|
||||
`--font-display` theme variable; headings use display font via
|
||||
`font-family: var(--font-display)` in base layer; added `--color-forest`
|
||||
(#064E3B) to theme palette
|
||||
- **Glass navbar** — replaced opaque white navbar with semi-transparent
|
||||
`backdrop-filter: blur(14px)` frosted glass effect
|
||||
- **Landing page: dark hero** — navy background with radial blue glow,
|
||||
white text, green badge on dark, white ROI calculator card with stronger
|
||||
shadow; hero section is now full-width outside the container
|
||||
- **Landing page: journey timeline** — replaced 5 left-border cards with
|
||||
numbered step track (01-05) with connecting line, active/upcoming states;
|
||||
CSS grid 5-col desktop, stacks to horizontal layout on mobile
|
||||
- **Landing page: dark CTA card** — replaced plain white CTA section with
|
||||
rounded navy card with noise texture and white inverted button
|
||||
- **Directory card tiers** — pro cards get stronger green left border +
|
||||
subtle box-shadow glow and 48px logo; featured badges more prominent with
|
||||
box-shadow; free/unclaimed cards more visibly muted (lower opacity, lighter
|
||||
border)
|
||||
- **Supplier dashboard sidebar icons** — added inline SVG icons (chart,
|
||||
inbox, building, rocket) to sidebar navigation links
|
||||
- **Supplier dashboard lead cards** — added heat-color left borders
|
||||
(red/amber/gray by heat score) on `.lf-card`
|
||||
|
||||
### Added
|
||||
- **Admin sidebar navigation** — new `base_admin.html` template with
|
||||
persistent sidebar (Overview, Leads, Suppliers, Users, Content, System
|
||||
sections); Heroicons inline SVGs for each nav item; active state via
|
||||
`{% set admin_page %}` in child templates; mobile: horizontal scroll nav;
|
||||
all 20 admin templates now extend `base_admin.html`
|
||||
- **Admin dashboard section labels** — stat card groups labeled "Lead
|
||||
Funnel" and "Supplier Funnel" with color-coded left borders (blue for
|
||||
leads, green for suppliers)
|
||||
|
||||
### Fixed
|
||||
- **Hardcoded Inter on supplier unlock button** — `.lf-unlock-btn` used
|
||||
`font-family: 'Inter'`; changed to `inherit` so it picks up DM Sans
|
||||
|
||||
### Changed
|
||||
- **Admin auth: password → RBAC** — replaced `ADMIN_PASSWORD` env var and
|
||||
session-based password login with role-based access control; admin access is
|
||||
now granted via `ADMIN_EMAILS` env var (comma-separated); on login/dev-login,
|
||||
matching emails auto-receive the `admin` role; removed `/admin/login` and
|
||||
`/admin/logout` routes, `admin_required` decorator, and `login.html` template;
|
||||
all admin routes now use `@role_required("admin")` from `auth/routes.py`
|
||||
- **Billing: separated billing identity from subscriptions** — new
|
||||
`billing_customers` table stores `provider_customer_id` (was on
|
||||
`subscriptions.paddle_customer_id`); subscriptions table renamed
|
||||
`paddle_subscription_id` → `provider_subscription_id` and dropped `UNIQUE`
|
||||
constraint on `user_id` (allows multiple subscriptions per user);
|
||||
`upsert_subscription` now finds existing rows by `provider_subscription_id`
|
||||
instead of `user_id`; webhook handler calls `upsert_billing_customer()` for
|
||||
all subscription events
|
||||
- **Eager-loaded user context** — `load_user()` now JOINs `billing_customers`,
|
||||
`user_roles`, and latest subscription in a single query; adds `g.subscription`
|
||||
and `is_admin` template context variable (replaces `session.get('is_admin')`)
|
||||
|
||||
### Added
|
||||
- **RBAC decorators** — `role_required(*roles)`, `subscription_required(plans,
|
||||
allowed)`, `grant_role()`, `revoke_role()`, `ensure_admin_role()` in
|
||||
`auth/routes.py`
|
||||
- **`user_roles` table** — stores user-role pairs with `UNIQUE(user_id, role)`
|
||||
- **`billing_customers` table** — stores provider customer ID per user
|
||||
- **`ADMIN_EMAILS` config** — parsed from comma-separated env var in `core.py`
|
||||
- **Migration 0011** — adds `user_roles` and `billing_customers` tables,
|
||||
migrates `paddle_customer_id` data, recreates subscriptions table with
|
||||
`provider_subscription_id` column and no `UNIQUE` on `user_id`
|
||||
|
||||
### Removed
|
||||
- `ADMIN_PASSWORD` env var and password-based admin authentication
|
||||
- `/admin/login` and `/admin/logout` routes
|
||||
- `admin/templates/admin/login.html` template
|
||||
- `admin_required` decorator (replaced by `role_required("admin")`)
|
||||
- `subscription_required` from `billing/routes.py` (replaced by version in
|
||||
`auth/routes.py` that reads from `g.subscription`)
|
||||
|
||||
### Fixed
|
||||
- **Webhook crash on null `custom_data`** — Paddle sends `"custom_data": null`
|
||||
on lifecycle events (e.g. `subscription.updated`); `.get("custom_data", {})`
|
||||
|
||||
Reference in New Issue
Block a user