Files
multica/server/internal/handler/workspace_reserved_slugs.go
Bohan Jiang bda475cbba refactor(reserved-slugs): single JSON source for backend + frontend (#2148)
Reserved workspace slugs lived in two parallel files (`workspace_reserved_slugs.go`
and `packages/core/paths/reserved-slugs.ts`) with no parity check. Adding or
renaming a global route on one side without the other would slip through CI
and surface only when a real user hit the collision.

Collapse the two lists into one source: `server/internal/handler/reserved_slugs.json`.
Go embeds the JSON via `//go:embed` and parses it at package init; the TS file
is regenerated by `scripts/generate-reserved-slugs.mjs` (run via
`pnpm generate:reserved-slugs`). CI re-runs the generator and `git diff
--exit-code`s the TS output, so a stale TS file cannot land. The slug set is
unchanged (87 entries, byte-equivalent slug literals).

Update CLAUDE.md to describe the new "edit JSON, run generator" workflow.

Co-authored-by: multica-agent <github@multica.ai>
2026-05-08 19:14:12 +08:00

50 lines
1.5 KiB
Go

package handler
import (
_ "embed"
"encoding/json"
)
// reservedSlugs are workspace slugs that would collide with frontend top-level
// routes, platform features, or web standards. The frontend URL shape is
// /{workspaceSlug}/... so any slug that matches a top-level route or a
// system-significant name is rejected at workspace creation time.
//
// The list is loaded from reserved_slugs.json (embedded at build time), which
// is the single source of truth shared with the TypeScript side. Edit only
// the JSON; packages/core/paths/reserved-slugs.ts is regenerated from it by
// `pnpm generate:reserved-slugs` and CI fails on any drift.
//go:embed reserved_slugs.json
var reservedSlugsJSON []byte
var reservedSlugs = loadReservedSlugs()
type reservedSlugsFile struct {
Groups []struct {
Slugs []string `json:"slugs"`
} `json:"groups"`
}
func loadReservedSlugs() map[string]bool {
var data reservedSlugsFile
if err := json.Unmarshal(reservedSlugsJSON, &data); err != nil {
// reserved_slugs.json is checked into the repo and embedded into the
// binary; a parse failure is a programming error caught at the very
// first request that touches workspace creation, which is too late.
// Panic at init so the binary refuses to start instead.
panic("handler: parse reserved_slugs.json: " + err.Error())
}
out := make(map[string]bool)
for _, g := range data.Groups {
for _, slug := range g.Slugs {
out[slug] = true
}
}
return out
}
func isReservedSlug(slug string) bool {
return reservedSlugs[slug]
}