package app import ( "net/http" "time" ) func executeTransportSingBoxProfileRenderLocked( id string, body SingBoxProfileRenderRequest, checkBinary bool, persist bool, ) (int, SingBoxProfileRenderResponse) { st := loadSingBoxProfilesState() idx := findSingBoxProfileIndex(st.Items, id) if idx < 0 { return http.StatusNotFound, SingBoxProfileRenderResponse{ OK: false, Code: singBoxProfileCodeNotFound, Message: "not found", } } cur := st.Items[idx] if body.BaseRevision > 0 && body.BaseRevision != cur.ProfileRevision { return http.StatusConflict, SingBoxProfileRenderResponse{ OK: false, Code: singBoxProfileCodeRevisionMismatch, Message: "base_revision mismatch", ProfileID: cur.ID, ProfileRevision: cur.ProfileRevision, RenderRevision: cur.RenderRevision, } } eval := evaluateSingBoxProfile(cur, checkBinary) if !eval.Valid { now := time.Now().UTC().Format(time.RFC3339Nano) cur.LastError = joinSingBoxIssueMessages(eval.Errors) cur.UpdatedAt = now st.Items[idx] = cur _ = saveSingBoxProfilesState(st) _ = appendSingBoxHistory(singBoxProfileHistoryRecord{ At: now, ProfileID: cur.ID, Action: "render", Status: "failed", Code: singBoxProfileCodeRenderFailed, Message: "render validation failed", ProfileRevision: cur.ProfileRevision, RenderRevision: cur.RenderRevision, Errors: eval.Errors, Warnings: eval.Warnings, Diff: eval.Diff, }) events.push("singbox_profile_failed", map[string]any{ "id": cur.ID, "step": "render", }) return http.StatusOK, SingBoxProfileRenderResponse{ OK: false, Message: "render validation failed", Code: singBoxProfileCodeRenderFailed, ProfileID: cur.ID, ProfileRevision: cur.ProfileRevision, RenderRevision: cur.RenderRevision, Valid: false, Errors: eval.Errors, Warnings: eval.Warnings, Diff: eval.Diff, } } renderPath, err := writeSingBoxRenderedConfig(cur.ID, eval.Config) if err != nil { return http.StatusInternalServerError, SingBoxProfileRenderResponse{ OK: false, Code: singBoxProfileCodeRenderFailed, Message: "write rendered config failed: " + err.Error(), ProfileID: cur.ID, ProfileRevision: cur.ProfileRevision, RenderRevision: cur.RenderRevision, Valid: true, Warnings: eval.Warnings, Diff: eval.Diff, } } renderRevision := cur.RenderRevision now := time.Now().UTC().Format(time.RFC3339Nano) if persist { renderRevision = cur.RenderRevision + 1 cur.RenderRevision = renderRevision cur.LastError = "" cur.UpdatedAt = now st.Items[idx] = cur if err := saveSingBoxProfilesState(st); err != nil { return http.StatusInternalServerError, SingBoxProfileRenderResponse{ OK: false, Code: singBoxProfileCodeSaveFailed, Message: "save failed: " + err.Error(), ProfileID: cur.ID, ProfileRevision: cur.ProfileRevision, RenderRevision: cur.RenderRevision, Valid: true, Warnings: eval.Warnings, Diff: eval.Diff, } } } else { renderRevision++ } _ = appendSingBoxHistory(singBoxProfileHistoryRecord{ At: now, ProfileID: cur.ID, Action: "render", Status: "success", Message: "rendered", ProfileRevision: cur.ProfileRevision, RenderRevision: renderRevision, RenderDigest: eval.Digest, RenderPath: renderPath, Warnings: eval.Warnings, Diff: eval.Diff, }) events.push("singbox_profile_rendered", map[string]any{ "id": cur.ID, "render_revision": renderRevision, }) return http.StatusOK, SingBoxProfileRenderResponse{ OK: true, Message: "rendered", ProfileID: cur.ID, ProfileRevision: cur.ProfileRevision, RenderRevision: renderRevision, RenderPath: renderPath, RenderDigest: eval.Digest, Changed: eval.Changed, Valid: true, Warnings: eval.Warnings, Diff: eval.Diff, Config: eval.Config, } }