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) } }