Land community fixes: setup wizard, postgres volume, favicon, license expiry - #12
Merged
Conversation
Allows manually issued licenses to carry a fixed expiry date (e.g. invoice-based one-year licenses). Empty keeps the existing behavior: trial plans expire after trial_days, others are perpetual.
POST /admin/licenses/:id/valid-until with an RFC 3339 timestamp sets the expiry; an empty value clears it (perpetual). Status is deliberately untouched — reinstating an expired license remains an explicit separate action.
The issue dialog gains an optional date field (empty = perpetual; trial plans keep their trial_days default). The detail view shows a pencil next to Valid Until for non-Stripe licenses — Stripe-billed expiry is renewal-managed, so editing it there stays hidden. Dates are sent as end-of-day UTC.
Previously the picked date became 23:59:59 UTC, which renders as a confusing mid-evening time for admins west of Greenwich. The date input's prefill is converted back the same way so editing doesn't shift the day.
…irst index.html ships two icon links (svg + sizes=32x32) and browsers often prefer the sized one, so rewriting only the first link never visibly changed the tab icon.
Migration 20260401_plan_checkout_id made plans.checkout_id NOT NULL, and store.CreatePlan fills it via shortID() — but the setup wizard's raw INSERT was never updated, so POST /api/v1/setup/initialize fails with a not-null violation on every fresh install (surfaced to the client as an opaque 500 INTERNAL_ERROR). Export the existing shortID() helper as store.ShortID() (same pattern as store.NewID(), which was exported for this handler) and include checkout_id in the wizard's plan INSERT. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
postgres:18+ official images moved the data directory into a versioned subdirectory and expect the volume to be mounted at /var/lib/postgresql instead of /var/lib/postgresql/data. With the old mount point a fresh `docker compose up` fails: the postgres container loops with "There appears to be PostgreSQL data in /var/lib/postgresql/data (unused mount/volume)" and never becomes healthy, so the keygate container never starts. See docker-library/postgres#37 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…t dates, emit webhook
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Lands the four open community PRs together, plus follow-up hardening on the expiry endpoint.
Merging as one branch so CI validates the actual combined result — none of the four PRs had ever run CI, since fork PRs from first-time contributors sit behind the approval gate.
Included
plans.checkout_idis notnull with no column default, and the wizard's raw INSERT never got the column. Every fresh install hit this.postgres:18keeps data in a versioned subdirectory, so mounting/var/lib/postgresql/databreaks initdb on a fresh volume. Compose has pinned 18 since the first commit, so there is no old-layout data to migrate.Added on top of #6
licenses:writeAPI keys, and the next renewal overwrites whatever gets set.POST /admin/licenses. Back-dating is not an "expire now" shortcut — the grace sweep picks the license up and emails the customer that it expired, so a mistyped year turns into customer-facing mail.license.expiry_changed. Every other lifecycle action dispatches a webhook; this one only wrote an audit entry. Clearing the date sends JSON null so an integration can tell "perpetual now" from "field omitted".assertUsableshort-circuits on the status before it readsvalid_until.Unrelated fix that rode along
WEBHOOK_EVENTSin the dashboard listed 5 of the 10license.*events the backend dispatches. SinceFindWebhooksForEventfilters by subscription, the missing ones — includinglicense.expired— could never be subscribed to and were effectively undeliverable for anyone configuring from the UI.Verified
Full CI locally (vet, unit, Atomic integration against real postgres, build; frontend lint/typecheck/build) and end-to-end in containers on a fresh volume: wizard returns 201 with a populated checkout_id, data survives container recreation, expiry guards return 400/409, webhook deliveries carry the right payload.