init
This commit is contained in:
commit
b15b95781c
108 changed files with 14802 additions and 0 deletions
149
internal/http/handlers.go
Normal file
149
internal/http/handlers.go
Normal file
|
|
@ -0,0 +1,149 @@
|
|||
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.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)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue