package main import ( "context" "encoding/base64" "encoding/json" "errors" "fmt" "io" "log/slog" "net/http" "strings" "sync" "github.com/stephenafamo/bob" "gitlab.artefactual.com/dcosme/am-scripts/assets" "gitlab.artefactual.com/dcosme/am-scripts/database/mcp/models" ) func server() error { fileServer := http.FileServer(http.FS(assets.Assets)) http.Handle("/assets/", http.StripPrefix("/assets/", fileServer)) state := &State{ Theme: ThemeLight, Mysql: &Service{ Name: "Mysql", Status: "Disconnected", }, } http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { Home(state).Render(w) }) http.HandleFunc("/upload", state.uploadInput) http.HandleFunc("/mysql", state.connectDB) http.HandleFunc("/report", state.report) slog.Info("listening", "addr", "4001") err := http.ListenAndServe(":4001", http.DefaultServeMux) if err != nil { return err } return nil } func (state *State) report(w http.ResponseWriter, r *http.Request) { mu := sync.Mutex{} mu.Lock() defer mu.Unlock() err := func() error { if len(state.Input) == 0 && state.Report == nil { return errors.New("need input before creating report") } if state.Mysql.Status != "Connected" { return errors.New("need database online") } slog.Info("creating report") for idx, record := range state.Input { q := models.LocationsPackages.Query() like := fmt.Sprintf("%%%s%%", record) q.Apply(models.SelectWhere.LocationsPackages.CurrentPath.Like(like)) res, err := q.All(context.Background(), state.db) assertNoErr(err) row := []string{record} for _, r := range res { slog.Info("AIP Found: " + r.CurrentPath) row = append(row, r.CurrentPath) } state.Report[idx] = row } return Report(state).Render(w) }() if err != nil { state.Err = err slog.Error(err.Error()) Report(state).Render(w) } } func (state *State) connectDB(w http.ResponseWriter, r *http.Request) { err := func() error { state.Mysql.Err = nil err := r.ParseForm() if err != nil { return err } connString := r.PostFormValue("mysql") slog.Info("connecting mysql", "conn", connString) db, err := openDB(connString) if err != nil { return fmt.Errorf("%s: %w", connString, err) } state.db = bob.NewDB(db) slog.Info("connected to database") state.Mysql.Status = "Connected" state.Mysql.URL = connString return Home(state).Render(w) }() if err != nil { state.Mysql.Err = err slog.Error(err.Error()) Home(state).Render(w) } } func (state *State) uploadInput(w http.ResponseWriter, r *http.Request) { err := func() error { type InputFile struct { Name string `json:"name"` Contents string `json:"contents"` Mime string `json:"mime"` } type UploadPayload struct { InputFile []InputFile `json:"input-file"` } var payload UploadPayload err := bind(&payload, r) if err != nil { return err } if len(payload.InputFile) != 1 { return fmt.Errorf("exactly one file is allowed to be uploaded, got: %d", len(payload.InputFile)) } file := payload.InputFile[0] raw := file.Contents content, err := base64.StdEncoding.DecodeString(raw) if err != nil { return err } lines := strings.Split(strings.TrimSpace(string(content)), "\n") slog.Info("file uploaded", "name", file.Name) state.Input = lines state.Report = make([][]string, len(lines)) return Home(state).Render(w) }() if err != nil { slog.Error(err.Error()) w.WriteHeader(http.StatusInternalServerError) } } func bind(v any, r *http.Request) error { body, err := io.ReadAll(r.Body) if err != nil { return err } json.Unmarshal(body, v) return nil }