update_server/internal/http/handlers.go
2026-06-11 17:44:06 +03:00

154 lines
6 KiB
Go

package httpserver
import (
"net/http"
"strconv"
"time"
)
func (h *handler) health(w http.ResponseWriter, r *http.Request) {
payload := map[string]any{
"status": "ok",
"database": "ok",
"service": h.config.AppName,
"timestamp": time.Now().UTC().Format(time.RFC3339),
}
if h.store == nil {
payload["status"] = "degraded"
payload["database"] = "unavailable"
payload["error"] = "database store is not initialized"
writeJSON(w, http.StatusServiceUnavailable, payload)
return
}
if err := h.store.HealthCheck(r.Context()); err != nil {
payload["status"] = "degraded"
payload["database"] = "unavailable"
payload["error"] = "database ping failed"
writeJSON(w, http.StatusServiceUnavailable, payload)
return
}
writeJSON(w, http.StatusOK, payload)
}
func (h *handler) home(w http.ResponseWriter, r *http.Request) {
data := HomePageData{
PageData: PageData{
Title: "Client Update API Ready",
Eyebrow: "Usable Product Flow",
Heading: h.config.AppName,
BaseURL: h.config.BaseURL,
Description: "Protected admin flows now connect all the way through to bearer-authenticated client endpoints for project discovery, latest-release metadata, release metadata, and private artifact downloads.",
},
Links: []PageLink{
{Label: "Health check", Href: "/healthz", Description: "JSON readiness endpoint covering both the HTTP server and SQLite connectivity."},
{Label: "Admin login", Href: "/admin/login", Description: "Server-rendered login form for the protected admin workspace."},
{Label: "Projects", Href: "/admin/projects", Description: "Create projects, attach tags, upload releases, and inspect the client-facing metadata paths from the protected admin UI."},
{Label: "Tags", Href: "/admin/tags", Description: "Manage reusable tags that projects can use from day one."},
{Label: "API keys", Href: "/admin/api-keys", Description: "Generate hashed API keys, reveal the raw key once, preview accessible projects, and copy the client API quick-start flow."},
{Label: "Client API", Href: "/api/v1", Description: "Versioned JSON API entrypoint documenting the bearer-authenticated project, metadata, and download endpoints."},
},
}
renderPage(w, h.renderer, "home", data)
}
func (h *handler) adminHome(w http.ResponseWriter, r *http.Request) {
projects, err := h.store.Projects.List(r.Context())
if err != nil {
http.Error(w, "project summary lookup failed", http.StatusInternalServerError)
return
}
tags, err := h.store.Tags.List(r.Context())
if err != nil {
http.Error(w, "tag summary lookup failed", http.StatusInternalServerError)
return
}
apiKeys, err := h.store.APIKeys.List(r.Context())
if err != nil {
http.Error(w, "api key summary lookup failed", http.StatusInternalServerError)
return
}
totalReleases := 0
for _, item := range projects {
totalReleases += item.ReleaseCount
}
data := DashboardPageData{
PageData: h.basePageData(
r,
"Admin Dashboard",
"Protected Admin",
"Admin Dashboard",
"Projects, tags, releases, and API keys now feed the client update API so admins can complete the end-to-end product flow in one workspace.",
),
Metrics: []DashboardMetric{
{Label: "Projects", Value: strconv.Itoa(len(projects)), Description: "Active and archived projects available for management."},
{Label: "Tags", Value: strconv.Itoa(len(tags)), Description: "Reusable labels ready for project assignment and future access scopes."},
{Label: "Releases", Value: strconv.Itoa(totalReleases), Description: "Stored release records with checksum and disk path metadata."},
{Label: "API Keys", Value: strconv.Itoa(len(apiKeys)), Description: "Hashed client credentials with permission flags and scope rules."},
},
Links: []PageLink{
{Label: "Manage projects", Href: "/admin/projects", Description: "Open project detail pages to edit metadata, attach tags, upload releases, and inspect client metadata or download paths."},
{Label: "Manage tags", Href: "/admin/tags", Description: "Create tags once and reuse them across projects from the same protected UI."},
{Label: "Manage API keys", Href: "/admin/api-keys", Description: "Generate, revoke, and scope client credentials, then copy the bearer-authenticated client API flow."},
{Label: "Client API", Href: "/api/v1", Description: "Public JSON index for accessible projects, latest release metadata, release metadata, and authenticated downloads."},
},
}
renderPage(w, h.renderer, "admin", data)
}
func (h *handler) apiIndex(w http.ResponseWriter, r *http.Request) {
writeJSON(w, http.StatusOK, map[string]any{
"service": h.config.AppName,
"version": "v1",
"status": "client-api-ready",
"auth": map[string]any{
"type": "bearer",
"header": "Authorization: Bearer <api_key>",
},
"routes": []map[string]string{
{
"method": http.MethodGet,
"path": "/api/v1/projects",
"description": "List active projects accessible to the API key.",
},
{
"method": http.MethodGet,
"path": "/api/v1/projects/{projectSlug}/releases/latest",
"description": "Get the latest active release metadata for an accessible project.",
},
{
"method": http.MethodPost,
"path": "/api/v1/projects/{projectSlug}/releases",
"description": "Upload a release artifact for an accessible project.",
},
{
"method": http.MethodGet,
"path": "/api/v1/releases/{releaseID}",
"description": "Get release metadata for an accessible release.",
},
{
"method": http.MethodGet,
"path": "/api/v1/releases/{releaseID}/download",
"description": "Download the private artifact for an accessible release.",
},
},
})
}
func renderPage(w http.ResponseWriter, renderer *Renderer, name string, data any) {
renderPageStatus(w, renderer, name, http.StatusOK, data)
}
func renderPageStatus(w http.ResponseWriter, renderer *Renderer, name string, status int, data any) {
if err := renderer.Render(w, name, status, data); err != nil {
http.Error(w, "template rendering failed", http.StatusInternalServerError)
}
}