mirror of
https://github.com/multica-ai/multica.git
synced 2026-07-23 18:17:44 +02:00
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>
50 lines
1.5 KiB
Go
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]
|
|
}
|