package app import ( "encoding/json" "io" "net/http" "strings" ) func handleTransportSingBoxProfiles(w http.ResponseWriter, r *http.Request) { switch r.Method { case http.MethodGet: enabledOnly := strings.EqualFold(strings.TrimSpace(r.URL.Query().Get("enabled_only")), "true") modeRaw := strings.TrimSpace(r.URL.Query().Get("mode")) modeFilter := SingBoxProfileMode("") if modeRaw != "" { parsed, ok := normalizeSingBoxProfileMode(SingBoxProfileMode(modeRaw)) if !ok { writeJSON(w, http.StatusBadRequest, SingBoxProfilesResponse{ OK: false, Code: singBoxProfileCodeBadMode, Message: "mode must be typed|raw", }) return } modeFilter = parsed } protocolFilter := normalizeSingBoxProtocol(r.URL.Query().Get("protocol")) singBoxProfilesMu.Lock() st := loadSingBoxProfilesState() singBoxProfilesMu.Unlock() items := make([]SingBoxProfile, 0, len(st.Items)) for _, it := range st.Items { if enabledOnly && !it.Enabled { continue } if modeFilter != "" && it.Mode != modeFilter { continue } if protocolFilter != "" && it.Protocol != protocolFilter { continue } items = append(items, it) } writeJSON(w, http.StatusOK, SingBoxProfilesResponse{ OK: true, Message: "ok", Count: len(items), ActiveProfileID: st.ActiveProfileID, Items: items, }) case http.MethodPost: var body SingBoxProfileCreateRequest 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 } } singBoxProfilesMu.Lock() defer singBoxProfilesMu.Unlock() st := loadSingBoxProfilesState() item, code, err := createSingBoxProfileLocked(&st, body) if err != nil { status := http.StatusBadRequest switch code { case singBoxProfileCodeIDConflict: status = http.StatusConflict case singBoxProfileCodeSecretsFailed: status = http.StatusInternalServerError } writeJSON(w, status, SingBoxProfilesResponse{ OK: false, Code: code, Message: err.Error(), }) return } if err := saveSingBoxProfilesState(st); err != nil { if strings.TrimSpace(item.ID) != "" { _ = writeSingBoxSecrets(item.ID, nil) } writeJSON(w, http.StatusInternalServerError, SingBoxProfilesResponse{ OK: false, Code: singBoxProfileCodeSaveFailed, Message: "save failed: " + err.Error(), }) return } events.push("singbox_profile_saved", map[string]any{ "id": item.ID, "revision": item.ProfileRevision, }) writeJSON(w, http.StatusOK, SingBoxProfilesResponse{ OK: true, Message: "created", ActiveProfileID: st.ActiveProfileID, Item: &item, }) default: http.Error(w, "method not allowed", http.StatusMethodNotAllowed) } }