From 8540c6d9ea7b107d121ad7c3eed01b88ccf1cc87 Mon Sep 17 00:00:00 2001 From: code Date: Wed, 15 Jul 2026 19:49:13 +0000 Subject: [PATCH] Scope Ecommerce down to Lib helpers; prune stale Done entries in ISSUES.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the single Ecommerce entry with three composable Lib\ pieces (Money, Cart, payment gateway) instead of a framework-owned catalog/ checkout/order-admin system — a project's idea of a "product" is too site-specific to standardize, so it builds its own on Lib\Db like any other feature. Also removes four Done entries (In-house comments, Email verification, Media/file manager, User deletion & email addresses) that nothing open still depends on or references, per this file's own stated retention policy — their history remains in git log and the shipping commits. Co-Authored-By: Claude Sonnet 5 --- novaconium/ISSUES.md | 248 +++++++++++-------------------------------- 1 file changed, 63 insertions(+), 185 deletions(-) diff --git a/novaconium/ISSUES.md b/novaconium/ISSUES.md index eb9fd4d..b06b029 100644 --- a/novaconium/ISSUES.md +++ b/novaconium/ISSUES.md @@ -58,52 +58,85 @@ expected vs. actual behavior. For features, include the motivating use case.> Suggested build order (foundations first): -1. **In-house comments** — admin login & user management (Done - 2026-07-14) unblocked this; comments are tied to real user accounts, - not anonymous. -2. **Ecommerce functionality** — its dependencies (SQLite groundwork, - session handling for the cart, admin login for order/product admin) - are all done now. -3. **Paywall functionality** — needs everything Ecommerce needs, plus - Ecommerce itself for the recurring-billing/payment-gateway plumbing; - build after it rather than in parallel — also now has a concrete - precedent to follow for the "gated content must skip the static cache" - part of its design (see Draft pages (admin-only preview) in Done, and - the caching/auth standing rule in `AGENTS.md`), which was still an open - question when this entry was originally written. +1. **Ecommerce: Lib\Money** — no dependencies, and the other two Ecommerce + pieces below both need it. +2. **Ecommerce: Lib\Cart** — needs Lib\Money. +3. **Ecommerce: payment gateway helper** — needs Lib\Money; independent of + Lib\Cart, so it could also go before it. +4. **Paywall functionality** — needs the payment gateway helper above for + its recurring-billing/payment plumbing; build after it rather than in + parallel — also now has a concrete precedent to follow for the "gated + content must skip the static cache" part of its design (see Draft + pages (admin-only preview) in Done, and the caching/auth standing rule + in `AGENTS.md`), which was still an open question when this entry was + originally written. Session handling (with flash sessions), Draft pages (admin-only preview), -Admin login & user management, and Media/file manager all shipped (see -Done) — 2026-07-14 for the first three, 2026-07-15 for Media/file manager. +and Admin login & user management all shipped (see Done) — every open +entry above still depends on at least one of them. The original single +"Ecommerce functionality" entry was scoped down into the three +Lib\-helper pieces above on 2026-07-15 — see Lib\Money's entry for why. See **Won't Do** below for 404 tracking, dropped in favor of Matomo. -### Ecommerce functionality +### Ecommerce: Lib\Money - **Type:** Feature - **Status:** Backlog - **Priority:** Low -- **Depends on:** SQLite groundwork (Done), Session handling (with flash sessions) (Done), Admin login & user management (Done) -- **Added:** 2026-07-12 +- **Added:** 2026-07-15 -Product catalog, cart, checkout, and order storage — a `products` / -`orders` table in SQLite, a session-based cart (rides on the flash-session -work above), and a payment gateway integration for actually taking money. -Given the project's no-Composer/no-vendored-SDK philosophy, prefer calling -a payment provider's HTTP API directly (e.g. Stripe's REST API via cURL) -over vendoring a full SDK, same reasoning as vendoring only Twig's `src/` -rather than pulling in a package manager. Needs a decision on which -provider(s) to support first. Order/product management rides on admin -login above. Large feature — likely worth its own sub-breakdown (catalog, -cart, checkout, order admin) once it's actually picked up rather than -planning it all up front here. +First and smallest piece of the former "Ecommerce functionality" entry — +scoped down (2026-07-15) from a full catalog/cart/checkout/order-admin +system to a handful of composable `Lib\` helpers, since a project's idea +of a "product" is too site-specific to standardize; the project builds +its own catalog and admin UI on `Lib\Db` the same way it would for any +other feature, the same split `Lib\Comments` already makes for what a +"page" is. `Lib\Money`: integer-cents arithmetic (add/subtract/multiply +by a quantity) and formatting, avoiding the classic float-rounding bugs +of storing prices as floats. No dependencies — the foundation `Lib\Cart` +and the payment gateway helper below both need a non-lossy way to +represent an amount before either can be built. + +### Ecommerce: Lib\Cart + +- **Type:** Feature +- **Status:** Backlog +- **Priority:** Low +- **Depends on:** Ecommerce: Lib\Money, Session handling (with flash sessions) (Done) +- **Added:** 2026-07-15 + +Second piece of the former "Ecommerce functionality" entry (see Lib\Money +above for the scoping note). A session-based cart primitive — add/remove/ +update line items, line and cart totals via `Lib\Money` — riding on +`Lib\Session` the same way `Lib\Csrf`/`Lib\AdminAuth` lazily touch the +native session. Generic on purpose: the cart holds id/qty/price entries a +sidecar hands it, with no opinion on what a "product" is or where its +catalog data comes from. + +### Ecommerce: payment gateway helper + +- **Type:** Feature +- **Status:** Backlog +- **Priority:** Low +- **Depends on:** Ecommerce: Lib\Money +- **Added:** 2026-07-15 + +Third piece of the former "Ecommerce functionality" entry (see Lib\Money +above for the scoping note). A driver-dispatched `Lib\` class for taking +a payment, mirroring `Lib\Mailer`'s `mail_driver` config-key pattern — +Stripe first (one `charge()`-shaped call plus webhook signature +verification), calling the provider's REST API directly via cURL rather +than vendoring an SDK, same reasoning as vendoring only Twig's `src/` +rather than pulling in a package manager. Adding a second provider later +means one more driver case, same as `Lib\Mailer::sendMail()`. ### Paywall functionality - **Type:** Feature - **Status:** Backlog - **Priority:** Low -- **Depends on:** Ecommerce functionality (recurring billing/payment plumbing), SQLite groundwork (Done), Session handling (with flash sessions) (Done), Admin login & user management (Done) +- **Depends on:** Ecommerce: payment gateway helper, SQLite groundwork (Done), Session handling (with flash sessions) (Done), Admin login & user management (Done) - **Added:** 2026-07-12 Subscription/membership content gating, similar to OnlyFans/Patreon: @@ -130,161 +163,6 @@ _Nothing yet._ ## Done -### In-house comments - -- **Type:** Feature -- **Status:** Done -- **Priority:** Medium -- **Depends on:** Admin login & user management (Done), SQLite groundwork (Done) -- **Added:** 2026-07-14 -- **Shipped:** 2026-07-15 - -A self-hosted comment thread — no third-party service (Disqus, Commento, -etc.) — `Lib\Comments` (`novaconium/lib/Comments.php`), a plain static -class any sidecar can call to attach comments to any page, not just blog -posts, the same way `Lib\SpamGuard`/`Lib\FormValidator` are reusable -across any form rather than hardcoded to the contact page. Comments are -tied to a real logged-in account, never anonymous name/email fields — -`App\AdminAuth::currentUser()` already excludes disabled and unverified -accounts (see Email verification below), so there's nothing further to -check before accepting a comment from whoever it returns. - -**Framework-level, not `App/migrations/`.** The original backlog draft -sketched the migration under `App/migrations/`, but comments — like Admin -auth and Media manager — is reusable framework functionality, not project -content, so the migration (`novaconium/migrations/0004_create_comments.sql`), -the class, and the moderation UI (`novaconium/pages/admin/comments/`) all -live under `novaconium/`, consistent with every other feature of this -shape. - -**Moderation model: auto-approve, then moderate.** A comment is visible -the instant it's posted — no pending queue — since only a verified -account can post one at all, there's no anonymous-spam vector to pre-vet -against. An admin can hide (excluded at the query level, not just -visually) or permanently delete any comment after the fact at -`/admin/comments`, mirroring how `/admin/users` disables rather than -pre-vets accounts. Submission still runs through `Lib\SpamGuard`'s -honeypot/timing check and `Lib\Csrf`, same as the contact form. - -**A page needs its own sidecar to use it.** This codebase has no -client-side JS/fetch anywhere — every dynamic feature is a plain -server-rendered POST form — so a comment thread is no different: giving a -page a sidecar is what excludes it from the static HTML cache (only -sidecar-less pages are ever cached), an explicit per-page tradeoff the -same as `/admin/media` and `/search`. `App/pages/blog/comments-demo/` is -the one post under `App/pages/blog/` with a sidecar, added specifically -to demonstrate the pattern without disturbing the other posts (several of -which reference `hello-world` by name as the canonical *sidecar-less* -example — retrofitting it instead would have made those references -wrong). The reusable Twig half is -`novaconium/pages/_partials/comments/thread.twig` — a `_`-prefixed, -never-routable partial, included with `{% if comments is defined %}` so -a layout shared by comment-enabled and sidecar-less pages alike can -include it unconditionally. Documented at `/admin/docs/comments`. - -### Email verification for user accounts - -- **Type:** Feature -- **Status:** Done -- **Priority:** Medium -- **Depends on:** Admin login & user management (Done) -- **Added:** 2026-07-14 -- **Shipped:** 2026-07-15 - -Every user created after the first must click a 24-hour link -(`bin2hex(random_bytes(32))`, same pattern as `Lib\Csrf::token()`) emailed -to their address before `AdminAuth::attempt()` lets them log in at all — -folded into the same generic pass/fail as a disabled account or a wrong -password, no oracle revealing which. Two exemptions, both following the -last-active-admin guard's reasoning that a safety mechanism can't depend on -something that might not be configured: the very first user (auto-verified -at creation, since there's no other admin to vouch for them, and they're -auto-logged-in the same instant) and every row that predates this migration -(`novaconium/migrations/0003_add_users_verification.sql` backfills -`verified_at` from `created_at`). `create-admin-user.php` also auto- -verifies, since lockout recovery can't depend on a working mail transport -either. - -`/verify-email` follows `/admin/logout`'s GET-confirms/POST-mutates shape -(the content-index crawl forces a GET on every route, and mail-security -scanners prefetch links — either would burn a GET-mutates token); an -invalid/expired/already-used token never touches the database either way. -Changing an account's email at `/admin/users` resets verification and -re-sends to the new address; `/admin/users` also grew a per-row **Resend -verification** button, visible only when unverified. - -`Lib\Mailer` grew a second method, `sendMail()`, for this and future -transactional mail (password-reset-by-email later, per the original spec) -— separate from `send()`, which the contact form keeps using unchanged. -`sendMail()` is driver-dispatched via a new `mail_driver` config key: -`'log'` (default) reuses the same `contact-log.txt` fallback so a fresh -checkout can create and verify accounts with zero external dependency; -`'mailjet'` sends through MailJet's Send API v3.1 (`mailjet_api_key`/ -`mailjet_api_secret`/`mail_from_email`/`mail_from_name` in `App/config.php` -— not gitignored, same as every other config key, per the user's decision -that deployer-owned secrets are acceptable here). Adding another provider -later means one more `match` case in `sendMail()` — callers never change. -Documented in `/admin/docs/admin-auth`'s new "Email verification" section. - -### Media/file manager - -- **Type:** Feature -- **Status:** Done -- **Priority:** Medium -- **Added:** 2026-07-12 -- **Shipped:** 2026-07-15 - -An upload/browse/delete UI for media (images, PDFs, etc.) at `/admin/media` -so sidecars and Twig templates have a consistent place to reference -uploaded files from — e.g. a blog post's header image — instead of authors -manually copying files into `public/`. Covered by the existing -`/admin/*` auth gate the moment the page was added, no extra wiring -needed — as planned, there's no separate `*_enabled` flag (unlike admin -auth/content index) since it has no SQLite dependency to gate. Backed by a -plain directory, `public/uploads/` (gitignored per-file with a tracked -`.gitkeep`, same convention as `data/`), rather than a database — no -metadata store (alt text, captions) yet; that's flagged as a natural -SQLite fit if wanted later, not built now. Also removed the top-level -`images/` scaffolding directory/volume (reserved for a future -image-upload feature, per the prior `AGENTS.md` wording): nothing had -ever consumed it, and `public/uploads/` now covers the use case it was -held open for. Safety handling: an extension -allowlist and max upload size, both configurable -(`media_upload_extensions`/`media_upload_max_bytes` in `App/config.php`), -plus filename sanitization (`basename()` + safe-charset reduction, -collision-safe via a `-1`/`-2`/... suffix) and a `realpath()` re-check on -delete to confirm the resolved path still lands inside `public/uploads/` -before unlinking. Documented at `/admin/docs/media-manager` (new topic, -linked from the docs nav/index and the admin dashboard), with a matching -README feature/docs-index entry. - -### User deletion & email addresses - -- **Type:** Feature -- **Status:** Done -- **Priority:** Medium -- **Depends on:** Admin login & user management (Done) -- **Added:** 2026-07-14 -- **Shipped:** 2026-07-14 (b882c30) - -Second same-day follow-up to Admin login & user management (below), -also pre-commit — so the `email` column went into the existing -`0002_create_users.sql` like the roles change before it. `/admin/users` -gained a hard-delete action (username/email become reusable; any live -session dies on its next request via the same per-request row re-check -disabling uses; the last-active-admin guard covers delete as well as -disable/demote) and a change-email action. Every account now requires a -unique email address — validated and normalized (trim + lowercase) via -the existing `Lib\Validate::isEmail()`, so uniqueness is -case-insensitive by construction (verified: `BOB@example.com` collides -with `bob@example.com`) — on the create form, the change-email action, -and `bin/create-admin-user.php` (now ` `). Not used -for login or any mail yet; it exists so the planned email-verification -flow (new Backlog entry above) has an address for every account that -predates it. Delete stays deliberately distinct from disable in the UI -(inside a confirm-style `
` with a warning): disable is -keep-but-shut-out, delete is gone-for-good. - ### User roles, groups & page access control - **Type:** Feature