3 Commits
Author SHA1 Message Date
omarandClaude Opus 5 3290d6ed1a fix(groups): Preview shows the draft on screen, not the last saved group
test / go vet + go test + spa build (push) Successful in 16s
release / test gate (push) Successful in 14s
release / binaries + release (push) Successful in 54s
release / docker image (push) Successful in 28s
The Preview button posted the draft the operator was editing and the handler
threw it away — it loaded the group from the store and assembled that. Tighten
a regex, press Preview, and the answer came back byte-identical (and, after the
first call, from cache in under a millisecond), so the button looked dead.
Reproduced in the panel: a filter dropping five countries left all 32 nodes on
screen, unchanged.

  - POST /api/groups/{id}/preview now overlays the posted body on the stored
    group before assembling. The body stays optional, so an empty request still
    means "preview exactly what is stored", and identity fields (id, token,
    created_at) keep their stored values whatever the body claims. Nothing is
    written: previewing must not be a side effect.
  - New POST /api/groups/preview previews a group that does not exist yet. The
    editor used to SAVE an unsaved group just to get an id to preview it, which
    left a half-made group behind whenever someone pressed Preview to look
    around and then cancelled.

Covered by two tests: the draft filter must change the result and must not be
persisted, and previewing a draft must create no group.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
v0.1.2
2026-08-08 18:39:31 +03:00
omarandClaude Opus 5 a887090e9b fix(api): accept the whole object a client reads back, empty stamps and all
test / go vet + go test + spa build (push) Successful in 6m35s
release / test gate (push) Successful in 14s
release / binaries + release (push) Successful in 54s
release / docker image (push) Successful in 29s
Adding a subscription from the panel failed with

    invalid JSON body: parsing time "" as "2006-01-02T15:04:05Z07:00":
    cannot parse "" as "2006"

The forms hold a whole Source/Group so a row can be read, edited and shown
back, and a freshly built one carries created_at/updated_at as "". The API
decoded straight onto model.Source, so the stdlib's time decoder rejected the
request before a single field was looked at. Groups had the identical bug; only
hand-written curl payloads, which omit the server-owned fields, ever worked.

Fixed on both sides:

  - model.Source and model.Group now decode empty and null timestamps as
    "not provided" and keep whatever the record already had, so an update that
    decodes onto a loaded row cannot wipe its stamps. A malformed date is still
    an error — the tolerance is for empty, not for junk.
  - the panel sends only the operator-writable fields, so timestamps, hit
    counters and last-fetch state never travel back at all.

Covered by internal/model/json_test.go, which decodes the exact body from the
report, and verified through the panel's own form.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
v0.1.1
2026-08-08 17:56:31 +03:00
omarandClaude Opus 5 9605b906f7 feat: Prizma — a subscription panel in front of other subscription panels
test / go vet + go test + spa build (push) Successful in 6m41s
release / test gate (push) Successful in 17s
release / binaries + release (push) Successful in 56s
release / docker image (push) Successful in 34s
Upstream panels (Remnawave and friends) pin a subscription to one device
through the x-hwid header. Prizma holds that HWID per source, presents it on
every upstream fetch, and hands out its own link that any number of devices
may use. Everything else — the client's User-Agent, the response body, the
profile-title / subscription-userinfo / announce headers — is proxied through
untouched.

Two link kinds behind /sub/{token}:

  source  byte-for-byte proxy of one upstream, format chosen by the client
  group   several sources merged into one link: parallel fetch, parse, regex
          filtering by node name and by node content, protocol allow-list,
          dedupe, rename template, rendered in the negotiated format

Formats parse and render both ways: URI lists, base64, Clash/Mihomo YAML,
sing-box JSON, and Xray JSON including the Happ-style array of whole configs.
A node keeps the raw payload it was born from, so same-format rendering is
byte-identical and no vendor-specific field is ever dropped.

Access control is HWID-based and self-switching: an empty whitelist means
everyone passes except banned devices; whitelisting a single device locks the
links to the whitelist. Every device that fetches a link is recorded with its
UA, IP, hit count and timestamps, and can be banned, whitelisted or labelled
from the panel.

Ships as one static binary with the React admin panel embedded (CGO-free, so
linux/amd64+arm64, windows and darwin cross-compile from anywhere), as a
docker image, and with Gitea CI that gates releases on the test suite.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
v0.1.0
2026-07-31 13:18:23 +03:00