feat: user-configurable /to/ and /from/ short links (extends #3 Shlink) #8
Reference in New Issue
Block a user
No description provided.
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
What
Extend the custom memorable-URL feature (#3) so the sender can configure short links under
/to/and/from/prefixes, e.g.:sendmy.cards/to/oma-erikasendmy.cards/from/matthiasm (verbatim): the
/to/and/from/forms are each for short links that users can configure. So it's the existing opt-in custom-slug, but namespaced into ato/…orfrom/…path that reads naturally on the newsendmy.cardsdomain.Behaviour
to/from) plus their slug. Result:<SHLINK_DOMAIN>/to/<slug>or/from/<slug>.to/<slug>(orfrom/<slug>) onSHLINK_DOMAIN(nowsendmy.cards), resolving 302 to the card's/c/{token}— same reserve-before-persist + collision + rollback logic as #3.to/fromare additive options.Key technical point — multi-segment slugs in Shlink
A slug containing a slash (
to/oma-erika) requires Shlink's multi-segment slugs to be enabled (MULTI_SEGMENT_SLUGS_ENABLED=true/ the equivalent on the shared instance at s.flexsiebels.de). Verify this first — if it's off, theto/<slug>short URL creation will fail. Enabling it is an instance-level config on the shared Shlink (coordinate; don't break hihlc/other domains — multi-segment is backward-compatible with existing single-segment slugs, but confirm).Routing (already in place — verify, don't re-add)
The Traefik app-router only claims
/,/compose,/c/,/static/,/health;/to/…and/from/…already fall through to Shlink (priority-50 catch-all) on both post.msbls.de and sendmy.cards. So no Traefik change should be needed — confirmsendmy.cards/to/<existing-slug>302s via Shlink once a slug exists.Reserved paths
Keep the existing reserved-slug guard.
toandfromas bare first segments now belong to Shlink, so ensure the app doesn't try to claim them and the slug-validation allows theto/from/shapes.Constraints
DoD
to/from+ slug;sendmy.cards/to/<slug>(and/from/<slug>) resolve to the card via Shlink.#8 done —
/to/and/from/user-configurable short linksCommit:
c9bb3a62ab(branchmai/hermes/issue-8-feat-user)Multi-segment slugs — verified first, as instructed
They were OFF on the shared Shlink: a
to/xslug got silently rewritten toto-x. I enabled them by addingMULTI_SEGMENT_SLUGS_ENABLED=trueto theshlinkservice in its Dokploy compose (raw composesrvcs-shlink-zgb13fon mlake) and redeployed.post.msbls.de/dania2026→ 302,msbls.de/upcsession→ 302). The flag only allows slashes; it changes nothing about existing slugs, so hihlc & other domains are unaffected.%2F-encodedDELETEofto/<slug>returns 204 (verified live).hihlc.de/kickerreturns 404 — the slug is intact in the Shlink DB; it's a hihlc-domain routing quirk, not caused by this flag. Flagging for visibility, out of #8 scope.)*App changes
–/to/fromnamespace selector inline in the existing Wunsch-Adresse field. The sender picks a namespace + types a segment; result is<domain>/to/<slug>or/from/<slug>. Input + selector both restore on a validation-error re-render.ValidateSlugnow accepts oneto/fromprefix plus a clean segment, or a bare segment (#3). The reserved-path guard applies only to bare slugs —to/composeis fine because/to/composeis never an app route.customSlugbody; reserve-before-persist + collision + rollback are reused as-is.Routing & domain
sendmy.cards(BASE_URL+SHLINK_DOMAIN).SHLINK_DOMAINderives fromBASE_URL's host — nothing hardcoded.post.msbls.de, so I updated both routers to claimsendmy.cards‖post.msbls.deand documented it. The app router still only claims/ /compose /c/ /static/ /health;/to/…and/from/…fall through to the Shlink catch-all (verified live).sendmy.cardsdoes not resolve via DNS yet, so the publicsendmy.cards/...URLs can't 302 until DNS points at mlake and a TLS cert is issued. The Traefik routers + README cover it; only DNS/cert remain.Build / test / e2e — all green
go build ./...,go vet ./...,go test ./...✅ (added tests:NormalizeSlugPrefix,BuildSlug, namespacedValidateSlugcases, compose render/prefill for the selector).post.msbls.de(real binary vs prod DB,STORAGE_BACKEND=localso the prod bucket was untouched, real Shlink key):/to/card → app mintedto/mai-e2e-i8→ DB rowslug=to/mai-e2e-i8; rendered card shows the pretty…/to/mai-e2e-i8URL.post.msbls.de/to/mai-e2e-i8→ 302 → the card's/c/{token}. ✅to/mai-e2e-i8→ 400 „schon vergeben“ with zero orphan cards.DoD status
to/from+ slug; resolves to the card via Shlink (verified on post.msbls.de; sendmy.cards pending DNS).sendmy.cardsDNS at mlake (+ TLS), copy the twodeploy/traefik/*.ymlto/etc/dokploy/traefik/dynamic/(fill the app container name), merge + deploy.✅ Merged & deployed — live on sendmy.cards
Merged
c9bb3a6(mergee84c3c4), deployed. Verified live e2e on sendmy.cards (via forced resolve, public DNS still propagating): composed a/to/card →sendmy.cards/to/otto-e2e-i8302s to the card's/c/{token}. Single-segment slugs (#3) still work. Test data + slug cleaned.No migration / no Traefik change needed (slug field + both-domain routing already live). The worker enabled
MULTI_SEGMENT_SLUGS_ENABLED=trueon the shared Shlink (backward-compatible, other domains unaffected — verified). go build/vet/test green.Not closing — m closes.