aboutsummaryrefslogtreecommitdiffhomepage
path: root/cmd/mbf/internal/pkgserver/api.go
diff options
context:
space:
mode:
Diffstat (limited to 'cmd/mbf/internal/pkgserver/api.go')
-rw-r--r--cmd/mbf/internal/pkgserver/api.go202
1 files changed, 0 insertions, 202 deletions
diff --git a/cmd/mbf/internal/pkgserver/api.go b/cmd/mbf/internal/pkgserver/api.go
deleted file mode 100644
index 68ccd471..00000000
--- a/cmd/mbf/internal/pkgserver/api.go
+++ /dev/null
@@ -1,202 +0,0 @@
-// Package pkgserver implements the package metadata service backend.
-package pkgserver
-
-import (
- "context"
- "encoding/json"
- "log"
- "net/http"
- "net/url"
- "path"
- "strconv"
- "sync"
- "time"
-
- "hakurei.app/internal/info"
- "hakurei.app/internal/rosa"
-)
-
-// for lazy initialisation of serveInfo
-var (
- infoPayload struct {
- // Current package count.
- Count int `json:"count"`
- // Hakurei version, set at link time.
- HakureiVersion string `json:"hakurei_version"`
- }
- infoPayloadOnce sync.Once
-)
-
-// handleInfo writes constant system information.
-func handleInfo(w http.ResponseWriter, _ *http.Request) {
- infoPayloadOnce.Do(func() {
- infoPayload.Count = len(rosa.Collect())
- infoPayload.HakureiVersion = info.Version()
- })
- // TODO(mae): cache entire response if no additional fields are planned
- writeAPIPayload(w, infoPayload)
-}
-
-// newStatusHandler returns a [http.HandlerFunc] that offers status files for
-// viewing or download, if available.
-func (index *packageIndex) newStatusHandler(disposition bool) http.HandlerFunc {
- return func(w http.ResponseWriter, r *http.Request) {
- m, ok := index.names[path.Base(r.URL.Path)]
- if !ok || !m.HasReport {
- http.NotFound(w, r)
- return
- }
-
- contentType := "text/plain; charset=utf-8"
- if disposition {
- contentType = "application/octet-stream"
-
- // quoting like this is unsound, but okay, because metadata is hardcoded
- contentDisposition := `attachment; filename="`
- contentDisposition += m.Name + "-"
- if m.Version != "" {
- contentDisposition += m.Version + "-"
- }
- contentDisposition += m.ids + `.log"`
- w.Header().Set("Content-Disposition", contentDisposition)
- }
- w.Header().Set("Content-Type", contentType)
- w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
- if err := func() (err error) {
- defer index.handleAccess(&err)()
- _, err = w.Write(m.status)
- return
- }(); err != nil {
- log.Println(err)
- http.Error(
- w, "cannot deliver status, contact maintainers",
- http.StatusInternalServerError,
- )
- }
- }
-}
-
-// handleGet writes a slice of metadata with specified order.
-func (index *packageIndex) handleGet(w http.ResponseWriter, r *http.Request) {
- q := r.URL.Query()
- limit, err := strconv.Atoi(q.Get("limit"))
- if err != nil || limit > 100 || limit < 1 {
- http.Error(
- w, "limit must be an integer between 1 and 100",
- http.StatusBadRequest,
- )
- return
- }
- i, err := strconv.Atoi(q.Get("index"))
- if err != nil || i >= len(index.sorts[0]) || i < 0 {
- http.Error(
- w, "index must be an integer between 0 and "+
- strconv.Itoa(len(index.sorts[0])-1),
- http.StatusBadRequest,
- )
- return
- }
- sort, err := strconv.Atoi(q.Get("sort"))
- if err != nil || sort >= len(index.sorts) || sort < 0 {
- http.Error(
- w, "sort must be an integer between 0 and "+
- strconv.Itoa(sortOrderEnd),
- http.StatusBadRequest,
- )
- return
- }
- values := index.sorts[sort][i:min(i+limit, len(index.sorts[sort]))]
- writeAPIPayload(w, &struct {
- Values []*metadata `json:"values"`
- }{values})
-}
-
-func (index *packageIndex) handleSearch(w http.ResponseWriter, r *http.Request) {
- q := r.URL.Query()
- limit, err := strconv.Atoi(q.Get("limit"))
- if err != nil || limit > 100 || limit < 1 {
- http.Error(
- w, "limit must be an integer between 1 and 100",
- http.StatusBadRequest,
- )
- return
- }
- i, err := strconv.Atoi(q.Get("index"))
- if err != nil || i >= len(index.sorts[0]) || i < 0 {
- http.Error(
- w, "index must be an integer between 0 and "+
- strconv.Itoa(len(index.sorts[0])-1),
- http.StatusBadRequest,
- )
- return
- }
- search, err := url.QueryUnescape(q.Get("search"))
- if len(search) > 100 || err != nil {
- http.Error(
- w, "search must be a string between 0 and 100 characters long",
- http.StatusBadRequest,
- )
- return
- }
- desc := q.Get("desc") == "true"
- n, res, err := index.performSearchQuery(limit, i, search, desc)
- if err != nil {
- http.Error(w, err.Error(), http.StatusInternalServerError)
- }
- writeAPIPayload(w, &struct {
- Count int `json:"count"`
- Values []searchResult `json:"values"`
- }{n, res})
-}
-
-// apiVersion is the name of the current API revision, as part of the pattern.
-const apiVersion = "v1"
-
-// registerAPI registers API handler functions.
-func (index *packageIndex) registerAPI(mux *http.ServeMux) {
- mux.HandleFunc("GET /api/"+apiVersion+"/info", handleInfo)
- mux.HandleFunc("GET /api/"+apiVersion+"/get", index.handleGet)
- mux.HandleFunc("GET /api/"+apiVersion+"/search", index.handleSearch)
- mux.HandleFunc("GET /api/"+apiVersion+"/status/", index.newStatusHandler(false))
- mux.HandleFunc("GET /status/", index.newStatusHandler(true))
-}
-
-// Register arranges for mux to service API requests.
-func Register(ctx context.Context, mux *http.ServeMux, report *rosa.Report) error {
- var index packageIndex
- index.search = make(searchCache)
- if err := index.populate(report); err != nil {
- return err
- }
- ticker := time.NewTicker(1 * time.Minute)
- go func() {
- for {
- select {
- case <-ctx.Done():
- ticker.Stop()
- return
- case <-ticker.C:
- index.search.clean()
- }
- }
- }()
- index.registerAPI(mux)
- return nil
-}
-
-// writeAPIPayload sets headers common to API responses and encodes payload as
-// JSON for the response body.
-func writeAPIPayload(w http.ResponseWriter, payload any) {
- w.Header().Set("Content-Type", "application/json; charset=utf-8")
- w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
- w.Header().Set("Pragma", "no-cache")
- w.Header().Set("Expires", "0")
-
- if err := json.NewEncoder(w).Encode(payload); err != nil {
- log.Println(err)
- http.Error(
- w, "cannot encode payload, contact maintainers",
- http.StatusInternalServerError,
- )
- }
-}