package main import ( "bytes" "encoding/json" "fmt" "io" "net/http" "path" "strconv" "hakurei.app/internal/info" "hakurei.app/internal/pkg" "hakurei.app/internal/rosa" ) type InfoPayload struct { Count int `json:"count"` HakureiVersion string `json:"hakurei_version"` } func NewInfoPayload(index *PackageIndex) InfoPayload { count := len(index.sorts[0]) return InfoPayload{ Count: count, HakureiVersion: info.Version(), } } func serveInfo(index *PackageIndex) func(http.ResponseWriter, *http.Request) { return func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) w.Header().Set("Content-Type", "text/json; charset=utf-8") w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate") WritePayload(w, NewInfoPayload(index)) } } func serveStatus(index *PackageIndex) func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) { download := path.Dir(r.URL.Path) == "/status" if index == nil { http.Error(w, "index is nil", http.StatusInternalServerError) return } name := path.Base(r.URL.Path) p, ok := rosa.ResolveName(name) if !ok { http.NotFound(w, r) return } m := rosa.GetMetadata(p) pk, ok := index.names[m.Name] if !ok { http.NotFound(w, r) return } if len(pk.status) > 0 { if download { w.Header().Set("Content-Type", "application/octet-stream") } else { w.Header().Set("Content-Type", "text/plain; charset=utf-8") } if download { var version string if pk.Version != "\u0000" { version = pk.Version } else { version = "unknown" } w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s-%s-%s.log\"", pk.Name, version, pkg.Encode(pk.ident.Value()))) } w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate") w.WriteHeader(http.StatusOK) _, err := io.Copy(w, bytes.NewReader(pk.status)) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) } } else { http.NotFound(w, r) } } } type GetPayload struct { Count int `json:"count"` Values []PackageIndexEntry `json:"values"` } func NewGetPayload(values []*PackageIndexEntry) GetPayload { count := len(values) v := make([]PackageIndexEntry, count) for i, _ := range values { v[i] = *values[i] } return GetPayload{ Count: count, Values: v, } } func serveGet(index *PackageIndex) func(http.ResponseWriter, *http.Request) { return func(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, fmt.Sprintf("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, fmt.Sprintf("index must be an integer between 0 and %d", 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, fmt.Sprintf("sort must be an integer between 0 and %d", len(index.sorts)-1), http.StatusBadRequest) return } values := index.sorts[sort][i:min(i+limit, len(index.sorts[sort]))] WritePayload(w, NewGetPayload(values)) } } const ApiVersion = "v1" func apiRoutes(mux *http.ServeMux, index *PackageIndex) { mux.HandleFunc(fmt.Sprintf("GET /api/%s/info", ApiVersion), serveInfo(index)) mux.HandleFunc(fmt.Sprintf("GET /api/%s/get", ApiVersion), serveGet(index)) mux.HandleFunc(fmt.Sprintf("GET /api/%s/status/", ApiVersion), serveStatus(index)) mux.HandleFunc("GET /status/", serveStatus(index)) } func WritePayload(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") err := json.NewEncoder(w).Encode(payload) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) } }