Skip to content

Feature/license valid until#6

Open
KurtJacobson wants to merge 6 commits into
tabloy:mainfrom
KurtJacobson:feature/license-valid-until
Open

Feature/license valid until#6
KurtJacobson wants to merge 6 commits into
tabloy:mainfrom
KurtJacobson:feature/license-valid-until

Conversation

@KurtJacobson

Copy link
Copy Markdown

What does this PR do?

Adds the ability to set, edit, and clear an expiration date on manually issued licenses:

  • POST /admin/licenses accepts an optional valid_until (RFC 3339, must be in the future). Empty preserves existing behavior: trial plans default to now + trial_days, everything else is perpetual. An explicit value takes precedence over the trial default.

  • New endpoint POST /admin/licenses/:id/valid-until sets or clears the expiry on an existing license (empty = perpetual). It deliberately does not touch license status — re-arming an expired license remains an explicit /reinstate action, so a date edit can't silently reactivate a revoked customer. Changes are audit-logged.

  • Admin UI: optional "Valid until" date field in the issue dialog, and an edit control on the detail view (hidden for Stripe-billed licenses, whose expiry is renewal-managed). Dates mean end-of-day 23:59:59 in the admin's local timezone. Licenses without an expiry now display "Perpetual" instead of a dash. English and Chinese strings included.

Existing expiry enforcement (internal/service/expiry.go) is untouched - this only adds ways to set the date it already honors.

Why?

valid_until is currently only set automatically (trial plans via trial_days, subscriptions via Stripe renewals), so fixed-term manual licensing — e.g. an invoice-based one-year license - isn't possible. This makes easy in the API and the admin dashboard.

How to test

  1. Admin > Licenses > Issue License: pick a plan, set "Valid until" to a future date > the license shows that date (23:59 local) in the table and detail view.
  2. Issue a license with the field empty > shows "Perpetual".
  3. In the detail view, click the pencil next to Valid Until > change the date > Save > date updates. Clear the field and save > back to "Perpetual".
  4. Verify the edit control does not appear on a Stripe-billed license.
  5. API: POST /admin/licenses with valid_until: "2020-01-01T00:00:00Z" > 400 (past date); malformed timestamp > 400.
  6. Confirm /license/verify returns 404 for a license who's valid_until has passed (existing enforcement).

Checklist

  • go vet ./... passes
  • go test ./... passes
  • Frontend builds (cd web && bun run build)
  • Tested manually in browser

Screenshots

image image

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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant