package app import ( "encoding/json" "io" "net/http" "time" ) func handleTransportSingBoxProfileValidate(w http.ResponseWriter, r *http.Request, id string) { if r.Method != http.MethodPost { http.Error(w, "method not allowed", http.StatusMethodNotAllowed) return } var body SingBoxProfileValidateRequest if r.Body != nil { defer r.Body.Close() if err := json.NewDecoder(io.LimitReader(r.Body, 1<<20)).Decode(&body); err != nil && err != io.EOF { http.Error(w, "bad json", http.StatusBadRequest) return } } checkBinary := true if body.CheckBinary != nil { checkBinary = *body.CheckBinary } singBoxProfilesMu.Lock() defer singBoxProfilesMu.Unlock() st := loadSingBoxProfilesState() idx := findSingBoxProfileIndex(st.Items, id) if idx < 0 { writeJSON(w, http.StatusNotFound, SingBoxProfileValidateResponse{ OK: false, Code: singBoxProfileCodeNotFound, Message: "not found", }) return } cur := st.Items[idx] if body.BaseRevision > 0 && body.BaseRevision != cur.ProfileRevision { writeJSON(w, http.StatusConflict, SingBoxProfileValidateResponse{ OK: false, Code: singBoxProfileCodeRevisionMismatch, Message: "base_revision mismatch", ProfileID: cur.ID, ProfileRevision: cur.ProfileRevision, }) return } eval := evaluateSingBoxProfile(cur, checkBinary) now := time.Now().UTC().Format(time.RFC3339Nano) cur.LastValidatedAt = now cur.UpdatedAt = now if eval.Valid { cur.LastError = "" } else { cur.LastError = joinSingBoxIssueMessages(eval.Errors) } st.Items[idx] = cur if err := saveSingBoxProfilesState(st); err != nil { writeJSON(w, http.StatusInternalServerError, SingBoxProfileValidateResponse{ OK: false, Code: singBoxProfileCodeSaveFailed, Message: "save failed: " + err.Error(), ProfileID: cur.ID, ProfileRevision: cur.ProfileRevision, }) return } status := "success" eventKind := "singbox_profile_validated" respCode := "" respMsg := "valid" if !eval.Valid { status = "failed" eventKind = "singbox_profile_failed" respCode = singBoxProfileCodeValidationFailed respMsg = "validation failed" } _ = appendSingBoxHistory(singBoxProfileHistoryRecord{ At: now, ProfileID: cur.ID, Action: "validate", Status: status, Code: respCode, Message: respMsg, ProfileRevision: cur.ProfileRevision, RenderRevision: cur.RenderRevision, RenderDigest: eval.Digest, Diff: eval.Diff, Errors: eval.Errors, Warnings: eval.Warnings, }) events.push(eventKind, map[string]any{ "id": cur.ID, "valid": eval.Valid, "errors": len(eval.Errors), "warning": len(eval.Warnings), }) writeJSON(w, http.StatusOK, SingBoxProfileValidateResponse{ OK: eval.Valid, Message: respMsg, Code: respCode, ProfileID: cur.ID, ProfileRevision: cur.ProfileRevision, Valid: eval.Valid, Errors: eval.Errors, Warnings: eval.Warnings, RenderDigest: eval.Digest, Diff: eval.Diff, }) }