package api import ( "database/sql" "encoding/json" "io" "net/http" "strings" "time" "marketing-system-data-tool/server/internal/exporter" ) type TemplatesAPI struct{ meta *sql.DB marketing *sql.DB } func TemplatesHandler(meta, marketing *sql.DB) http.Handler { api := &TemplatesAPI{meta: meta, marketing: marketing} return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { p := strings.TrimPrefix(r.URL.Path, "/api/templates") if r.Method == http.MethodPost && p == "" { api.createTemplate(w, r) return } if r.Method == http.MethodGet && p == "" { api.listTemplates(w, r) return } if strings.HasPrefix(p, "/") { id := strings.TrimPrefix(p, "/") if r.Method == http.MethodGet { api.getTemplate(w, r, id) return } if r.Method == http.MethodPatch { api.patchTemplate(w, r, id) return } if r.Method == http.MethodPost && strings.HasSuffix(p, "/validate") { id = strings.TrimSuffix(id, "/validate") api.validateTemplate(w, r, id) return } } w.WriteHeader(http.StatusNotFound) }) } type TemplatePayload struct{ Name string `json:"name"` Datasource string `json:"datasource"` MainTable string `json:"main_table"` Fields []string `json:"fields"` Filters map[string]interface{} `json:"filters"` FileFormat string `json:"file_format"` OwnerID uint64 `json:"owner_id"` Visibility string `json:"visibility"` } func (a *TemplatesAPI) createTemplate(w http.ResponseWriter, r *http.Request) { b, _ := io.ReadAll(r.Body) var p TemplatePayload json.Unmarshal(b, &p) wl := map[string]bool{ "order.order_number": true, "order.creator": true, "order.out_trade_no": true, "order.type": true, "order.status": true, "order.contract_price": true, "order.num": true, "order.total": true, "order.pay_amount": true, "order.create_time": true, "order.update_time": true, } req := exporter.BuildRequest{MainTable: p.MainTable, Fields: p.Fields, Filters: p.Filters} q, args, err := exporter.BuildSQL(req, wl) if err != nil { w.WriteHeader(http.StatusBadRequest) w.Write([]byte(err.Error())) return } _, score, err := exporter.RunExplain(a.marketing, q, args) if err != nil { w.WriteHeader(http.StatusBadRequest) w.Write([]byte(err.Error())) return } now := time.Now() _, err = a.meta.Exec( "INSERT INTO export_templates (name, datasource, main_table, fields_json, filters_json, file_format, visibility, owner_id, enabled, explain_score, last_validated_at) VALUES (?,?,?,?,?,?,?,?,?,?,?)", p.Name, p.Datasource, p.MainTable, toJSON(p.Fields), toJSON(p.Filters), p.FileFormat, p.Visibility, p.OwnerID, 1, score, now, ) if err != nil { w.WriteHeader(http.StatusInternalServerError) w.Write([]byte(err.Error())) return } w.WriteHeader(http.StatusCreated) w.Write([]byte("ok")) } func (a *TemplatesAPI) listTemplates(w http.ResponseWriter, r *http.Request) { rows, err := a.meta.Query("SELECT id,name,datasource,main_table,file_format,visibility,owner_id,enabled,explain_score,last_validated_at,created_at,updated_at FROM export_templates ORDER BY updated_at DESC LIMIT 200") if err != nil { w.WriteHeader(http.StatusInternalServerError) w.Write([]byte(err.Error())) return } defer rows.Close() out := []map[string]interface{}{} for rows.Next() { var id uint64 var name, datasource, mainTable, fileFormat, visibility string var ownerID uint64 var enabled int var explainScore sql.NullInt64 var lastValidatedAt sql.NullTime var createdAt, updatedAt time.Time err := rows.Scan(&id, &name, &datasource, &mainTable, &fileFormat, &visibility, &ownerID, &enabled, &explainScore, &lastValidatedAt, &createdAt, &updatedAt) if err != nil { w.WriteHeader(http.StatusInternalServerError) w.Write([]byte(err.Error())) return } m := map[string]interface{}{"id": id, "name": name, "datasource": datasource, "main_table": mainTable, "file_format": fileFormat, "visibility": visibility, "owner_id": ownerID, "enabled": enabled == 1, "explain_score": explainScore.Int64, "last_validated_at": lastValidatedAt.Time, "created_at": createdAt, "updated_at": updatedAt} out = append(out, m) } b, _ := json.Marshal(out) w.Header().Set("Content-Type", "application/json") w.Write(b) } func (a *TemplatesAPI) getTemplate(w http.ResponseWriter, r *http.Request, id string) { row := a.meta.QueryRow("SELECT id,name,datasource,main_table,fields_json,filters_json,file_format,visibility,owner_id,enabled,explain_score,last_validated_at,created_at,updated_at FROM export_templates WHERE id=?", id) var m = map[string]interface{}{} var tid uint64 var name, datasource, mainTable, fileFormat, visibility string var ownerID uint64 var enabled int var explainScore sql.NullInt64 var lastValidatedAt sql.NullTime var createdAt, updatedAt time.Time var fields, filters []byte err := row.Scan(&tid, &name, &datasource, &mainTable, &fields, &filters, &fileFormat, &visibility, &ownerID, &enabled, &explainScore, &lastValidatedAt, &createdAt, &updatedAt) if err != nil { w.WriteHeader(http.StatusNotFound) w.Write([]byte("not found")) return } m["id"] = tid m["name"] = name m["datasource"] = datasource m["main_table"] = mainTable m["file_format"] = fileFormat m["visibility"] = visibility m["owner_id"] = ownerID m["enabled"] = enabled == 1 m["explain_score"] = explainScore.Int64 m["last_validated_at"] = lastValidatedAt.Time m["created_at"] = createdAt m["updated_at"] = updatedAt m["fields"] = fromJSON(fields) m["filters"] = fromJSON(filters) b, _ := json.Marshal(m) w.Header().Set("Content-Type", "application/json") w.Write(b) } func (a *TemplatesAPI) patchTemplate(w http.ResponseWriter, r *http.Request, id string) { b, _ := io.ReadAll(r.Body) var p map[string]interface{} json.Unmarshal(b, &p) set := []string{} args := []interface{}{} for k, v := range p { switch k { case "name", "visibility", "file_format": set = append(set, k+"=?") args = append(args, v) case "enabled": set = append(set, "enabled=?") if v.(bool) { args = append(args, 1) } else { args = append(args, 0) } } } if len(set) == 0 { w.WriteHeader(http.StatusBadRequest) w.Write([]byte("no patch")) return } args = append(args, id) _, err := a.meta.Exec("UPDATE export_templates SET "+strings.Join(set, ",")+" WHERE id=?", args...) if err != nil { w.WriteHeader(http.StatusInternalServerError) w.Write([]byte(err.Error())) return } w.Write([]byte("ok")) } func (a *TemplatesAPI) validateTemplate(w http.ResponseWriter, r *http.Request, id string) { row := a.meta.QueryRow("SELECT main_table, fields_json, filters_json FROM export_templates WHERE id=?", id) var main string var fields, filters []byte err := row.Scan(&main, &fields, &filters) if err != nil { w.WriteHeader(http.StatusNotFound) w.Write([]byte("not found")) return } var fs []string var fl map[string]interface{} json.Unmarshal(fields, &fs) json.Unmarshal(filters, &fl) wl := map[string]bool{ "order.order_number": true, "order.creator": true, "order.out_trade_no": true, "order.type": true, "order.status": true, "order.contract_price": true, "order.num": true, "order.total": true, "order.pay_amount": true, "order.create_time": true, "order.update_time": true, } req := exporter.BuildRequest{MainTable: main, Fields: fs, Filters: fl} q, args, err := exporter.BuildSQL(req, wl) if err != nil { w.WriteHeader(http.StatusBadRequest) w.Write([]byte(err.Error())) return } _, score, err := exporter.RunExplain(a.marketing, q, args) if err != nil { w.WriteHeader(http.StatusBadRequest) w.Write([]byte(err.Error())) return } now := time.Now() _, err = a.meta.Exec("UPDATE export_templates SET explain_score=?, last_validated_at=? WHERE id=?", score, now, id) if err != nil { w.WriteHeader(http.StatusInternalServerError) w.Write([]byte(err.Error())) return } w.Write([]byte("ok")) } func toJSON(v interface{}) []byte { b, _ := json.Marshal(v) return b } func fromJSON(b []byte) interface{} { var v interface{} json.Unmarshal(b, &v) return v }