df433a9179
Co-authored-by: Cursor <cursoragent@cursor.com>
110 lines
2.9 KiB
Go
110 lines
2.9 KiB
Go
package server
|
|
|
|
import (
|
|
"crypto/subtle"
|
|
"encoding/json"
|
|
"net/http"
|
|
"strings"
|
|
|
|
"github.com/longbin/agent-notify/internal/remote"
|
|
)
|
|
|
|
func NewHandler(store *Store, token string) http.Handler {
|
|
mux := http.NewServeMux()
|
|
mux.HandleFunc("/healthz", handleHealthz)
|
|
mux.Handle("/api/v1/status", bearerAuth(token, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
switch r.Method {
|
|
case http.MethodPost:
|
|
handleStatusPost(store, w, r)
|
|
case http.MethodGet:
|
|
handleStatusList(store, w, r)
|
|
default:
|
|
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
|
|
}
|
|
})))
|
|
mux.Handle("/api/v1/meta", bearerAuth(token, http.HandlerFunc(handleMeta(store))))
|
|
mux.Handle("/", webHandler())
|
|
return mux
|
|
}
|
|
|
|
func handleHealthz(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != http.MethodGet {
|
|
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
|
|
return
|
|
}
|
|
w.WriteHeader(http.StatusOK)
|
|
}
|
|
|
|
func bearerAuth(token string, next http.Handler) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if !checkBearer(r, token) {
|
|
http.Error(w, "unauthorized", http.StatusUnauthorized)
|
|
return
|
|
}
|
|
next.ServeHTTP(w, r)
|
|
})
|
|
}
|
|
|
|
func checkBearer(r *http.Request, token string) bool {
|
|
if token == "" {
|
|
return false
|
|
}
|
|
h := r.Header.Get("Authorization")
|
|
const prefix = "Bearer "
|
|
if len(h) < len(prefix) || !strings.HasPrefix(h, prefix) {
|
|
return false
|
|
}
|
|
got := h[len(prefix):]
|
|
return subtle.ConstantTimeCompare([]byte(got), []byte(token)) == 1
|
|
}
|
|
|
|
func handleStatusPost(store *Store, w http.ResponseWriter, r *http.Request) {
|
|
defer r.Body.Close()
|
|
var report remote.StatusReport
|
|
if err := json.NewDecoder(r.Body).Decode(&report); err != nil {
|
|
http.Error(w, "bad json", http.StatusBadRequest)
|
|
return
|
|
}
|
|
if err := store.Upsert(report); err != nil {
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
w.Header().Set("Content-Type", "application/json")
|
|
_ = json.NewEncoder(w).Encode(map[string]bool{"ok": true})
|
|
}
|
|
|
|
func handleStatusList(store *Store, w http.ResponseWriter, r *http.Request) {
|
|
filters := ListFilters{
|
|
Host: r.URL.Query().Get("host"),
|
|
CWD: r.URL.Query().Get("cwd"),
|
|
Agent: r.URL.Query().Get("agent"),
|
|
Status: r.URL.Query().Get("status"),
|
|
}
|
|
rows, err := store.List(filters)
|
|
if err != nil {
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
if rows == nil {
|
|
rows = []SessionRow{}
|
|
}
|
|
w.Header().Set("Content-Type", "application/json")
|
|
_ = json.NewEncoder(w).Encode(rows)
|
|
}
|
|
|
|
func handleMeta(store *Store) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != http.MethodGet {
|
|
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
|
|
return
|
|
}
|
|
meta, err := store.Meta()
|
|
if err != nil {
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
w.Header().Set("Content-Type", "application/json")
|
|
_ = json.NewEncoder(w).Encode(meta)
|
|
}
|
|
}
|