package web import ( "net/http" "strings" "time" "ymhut-box/server/unified-management/internal/config" "ymhut-box/server/unified-management/internal/health" "ymhut-box/server/unified-management/internal/notices" ) func (r *router) handleClientBootstrap(w http.ResponseWriter, req *http.Request) { baseURL := requestBaseURL(req, r.cfg.BaseURL) snapshot := r.publicSnapshots.Get("bootstrap|"+baseURL, func(generatedAt time.Time) any { release := r.releases.Manifest(req) sourceCatalog, _ := r.sources.Catalog(false) publicNotices := []map[string]any{} if r.notices != nil { if items, err := r.notices.List(20); err == nil { publicNotices = notices.PublicList(items) } } return map[string]any{ "ok": true, "schemaVersion": 2, "generatedAt": generatedAt.Format(time.RFC3339), "serviceVersion": config.Version, "baseUrl": baseURL, "capabilities": map[string]bool{ "dynamicSources": true, "sourceHealth": true, "feedbackStatus": true, "releaseManifest": true, "endpointCalls": true, "legacyJson": true, }, "endpoints": map[string]string{ "releases": "/api/client/releases", "sources": "/api/client/sources", "clientEndpoints": "/api/client/endpoints", "endpointCalls": "/api/client/endpoint-calls", "notices": "/api/client/notices", "feedback": "/", }, "cache": map[string]int{ "bootstrapSeconds": 300, "releasesSeconds": 300, "sourcesSeconds": 600, "healthSeconds": 300, }, "legacyRoutes": []string{"/update-info.json", "/update-info", "/api/update-info", "/api/releases", "/tool-status.json", "/media-types.json", "/modules.json", "/downloads/:filename"}, "release": release, "sources": sourceCatalog, "feedback": map[string]any{"submit": "/", "status": "/?api=status&code=:code"}, "branding": config.SafeBranding(r.effectiveBranding()), "health": health.Snapshot(r.cfg, r.store), "notices": publicNotices, } }) writePublicSnapshot(w, req, snapshot) } func (r *router) handleClientSources(w http.ResponseWriter, req *http.Request) { baseURL := requestBaseURL(req, r.cfg.BaseURL) snapshot := r.publicSnapshots.Get("sources|"+baseURL, func(_ time.Time) any { catalog, err := r.sources.Catalog(false) if err != nil { return map[string]any{"ok": false, "error": "SOURCES_FAILED"} } return catalog }) writePublicSnapshot(w, req, snapshot) } func (r *router) handleClientEndpoints(w http.ResponseWriter, req *http.Request) { items, err := r.sources.Endpoints(false) if err != nil { writeError(w, http.StatusInternalServerError, "ENDPOINTS_FAILED", err) return } writeJSON(w, http.StatusOK, map[string]any{"ok": true, "items": items}) } func (r *router) handleClientNotices(w http.ResponseWriter, req *http.Request) { if r.notices == nil { writeJSON(w, http.StatusOK, map[string]any{"ok": true, "items": []any{}}) return } path := cleanPath(req.URL.Path) if path == "/api/client/notices" { items, err := r.notices.List(100) if err != nil { writeError(w, http.StatusInternalServerError, "NOTICES_FAILED", err) return } writeJSON(w, http.StatusOK, map[string]any{"ok": true, "items": notices.PublicList(items)}) return } version := strings.TrimPrefix(path, "/api/client/notices/") if version == "" { http.NotFound(w, req) return } doc, err := r.notices.Get(version) if err != nil { writeError(w, http.StatusNotFound, "NOTICE_NOT_FOUND", err) return } writeJSON(w, http.StatusOK, map[string]any{"ok": true, "notice": notices.PublicNotice(doc.Notice), "raw": doc.Parsed}) }