147 lines
3.4 KiB
Go
147 lines
3.4 KiB
Go
package admin
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"errors"
|
|
"io"
|
|
"net/http"
|
|
"os"
|
|
"strconv"
|
|
"sync"
|
|
|
|
"git.maze.io/maze/styx/dataset"
|
|
"git.maze.io/maze/styx/logger"
|
|
"git.maze.io/maze/styx/proxy"
|
|
)
|
|
|
|
type Admin struct {
|
|
Storage dataset.Storage
|
|
setupOnce sync.Once
|
|
mux *http.ServeMux
|
|
api *http.ServeMux
|
|
}
|
|
|
|
type apiError struct {
|
|
Code int
|
|
Err error
|
|
}
|
|
|
|
func (err apiError) Error() string {
|
|
return err.Err.Error()
|
|
}
|
|
|
|
func (a *Admin) setup() {
|
|
a.mux = http.NewServeMux()
|
|
|
|
a.api = http.NewServeMux()
|
|
a.api.HandleFunc("GET /groups", a.apiGroups)
|
|
a.api.HandleFunc("POST /group", a.apiGroupCreate)
|
|
a.api.HandleFunc("GET /group/{id}", a.apiGroup)
|
|
a.api.HandleFunc("PATCH /group/{id}", a.apiGroupUpdate)
|
|
a.api.HandleFunc("DELETE /group/{id}", a.apiGroupDelete)
|
|
a.api.HandleFunc("GET /clients", a.apiClients)
|
|
a.api.HandleFunc("GET /client/{id}", a.apiClient)
|
|
a.api.HandleFunc("POST /client", a.apiClientCreate)
|
|
a.api.HandleFunc("PATCH /client/{id}", a.apiClientUpdate)
|
|
a.api.HandleFunc("DELETE /client/{id}", a.apiClientDelete)
|
|
a.api.HandleFunc("GET /lists", a.apiLists)
|
|
a.api.HandleFunc("POST /list", a.apiListCreate)
|
|
a.api.HandleFunc("GET /list/{id}", a.apiList)
|
|
a.api.HandleFunc("DELETE /list/{id}", a.apiListDelete)
|
|
}
|
|
|
|
type Handler interface {
|
|
Handle(pattern string, handler http.Handler)
|
|
}
|
|
|
|
func (a *Admin) Install(handler Handler) {
|
|
a.setupOnce.Do(a.setup)
|
|
handler.Handle("/api/v1/", http.StripPrefix("/api/v1", a.api))
|
|
}
|
|
|
|
func (a *Admin) handleAPIError(w http.ResponseWriter, r *http.Request, err error) {
|
|
code := http.StatusBadRequest
|
|
switch {
|
|
case dataset.IsNotExist(err):
|
|
code = http.StatusNotFound
|
|
case os.IsPermission(err):
|
|
code = http.StatusForbidden
|
|
case errors.Is(err, apiError{}):
|
|
if c := err.(apiError).Code; c > 0 {
|
|
code = c
|
|
}
|
|
}
|
|
|
|
logger.StandardLog.Err(err).Values(logger.Values{
|
|
"code": code,
|
|
"client": r.RemoteAddr,
|
|
"method": r.Method,
|
|
"path": r.URL.Path,
|
|
}).Warn("Unexpected API error encountered")
|
|
|
|
var data []byte
|
|
if err, ok := err.(apiError); ok {
|
|
data, _ = json.Marshal(struct {
|
|
Code int `json:"code"`
|
|
Error string `json:"error"`
|
|
}{code, err.Error()})
|
|
} else {
|
|
data, _ = json.Marshal(struct {
|
|
Code int `json:"code"`
|
|
Error string `json:"error"`
|
|
}{code, http.StatusText(code)})
|
|
}
|
|
|
|
res := proxy.NewResponse(code, io.NopCloser(bytes.NewReader(data)), r)
|
|
res.Header.Set(proxy.HeaderContentType, "application/json")
|
|
|
|
for k, vv := range res.Header {
|
|
if len(vv) >= 1 {
|
|
w.Header().Set(k, vv[0])
|
|
for _, v := range vv[1:] {
|
|
w.Header().Add(k, v)
|
|
}
|
|
}
|
|
}
|
|
w.WriteHeader(code)
|
|
io.Copy(w, res.Body)
|
|
}
|
|
|
|
func (a *Admin) jsonResponse(w http.ResponseWriter, r *http.Request, value any, codes ...int) {
|
|
var (
|
|
code = http.StatusNoContent
|
|
body io.ReadCloser
|
|
size int64
|
|
)
|
|
if value != nil {
|
|
data, err := json.Marshal(value)
|
|
if err != nil {
|
|
a.handleAPIError(w, r, err)
|
|
return
|
|
}
|
|
code = http.StatusOK
|
|
body = io.NopCloser(bytes.NewReader(data))
|
|
size = int64(len(data))
|
|
}
|
|
if len(codes) > 0 {
|
|
code = codes[0]
|
|
}
|
|
|
|
res := proxy.NewResponse(code, body, r)
|
|
res.Close = true
|
|
res.Header.Set(proxy.HeaderContentLength, strconv.FormatInt(size, 10))
|
|
res.Header.Set(proxy.HeaderContentType, "application/json")
|
|
|
|
for k, vv := range res.Header {
|
|
if len(vv) >= 1 {
|
|
w.Header().Set(k, vv[0])
|
|
for _, v := range vv[1:] {
|
|
w.Header().Add(k, v)
|
|
}
|
|
}
|
|
}
|
|
w.WriteHeader(code)
|
|
io.Copy(w, res.Body)
|
|
}
|