package routes import ( "bssapp-backend/models" "bssapp-backend/queries" "database/sql" "encoding/json" "net/http" "sort" "strconv" "strings" "github.com/gorilla/mux" ) type MarketMailSavePayload struct { MailIDs []string `json:"mail_ids"` } type MarketMailLookupResponse struct { Markets []models.MarketOption `json:"markets"` Mails []models.MailOption `json:"mails"` } func GetMarketMailMappingLookupsHandler(db *sql.DB) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json; charset=utf-8") markets := make([]models.MarketOption, 0, 64) mails := make([]models.MailOption, 0, 128) marketRows, err := db.Query(queries.GetActiveMarketsForMapping) if err != nil { http.Error(w, "markets lookup error", http.StatusInternalServerError) return } defer marketRows.Close() for marketRows.Next() { var item models.MarketOption if err := marketRows.Scan(&item.ID, &item.Code, &item.Title); err != nil { http.Error(w, "markets scan error", http.StatusInternalServerError) return } markets = append(markets, item) } if err := marketRows.Err(); err != nil { http.Error(w, "markets rows error", http.StatusInternalServerError) return } mailRows, err := db.Query(queries.GetActiveMailsForMapping) if err != nil { http.Error(w, "mails lookup error", http.StatusInternalServerError) return } defer mailRows.Close() for mailRows.Next() { var item models.MailOption if err := mailRows.Scan(&item.ID, &item.Email, &item.DisplayName); err != nil { http.Error(w, "mails scan error", http.StatusInternalServerError) return } mails = append(mails, item) } if err := mailRows.Err(); err != nil { http.Error(w, "mails rows error", http.StatusInternalServerError) return } _ = json.NewEncoder(w).Encode(MarketMailLookupResponse{ Markets: markets, Mails: mails, }) } } func GetMarketMailMappingsHandler(db *sql.DB) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json; charset=utf-8") rows, err := db.Query(queries.GetMarketMailMappingRows) if err != nil { http.Error(w, "mapping query error", http.StatusInternalServerError) return } defer rows.Close() byMarket := make(map[int64]*models.MarketMailMappingRow, 64) order := make([]int64, 0, 64) for rows.Next() { var marketID int64 var marketCode, marketTitle string var mailID sql.NullString var email sql.NullString var displayName sql.NullString if err := rows.Scan( &marketID, &marketCode, &marketTitle, &mailID, &email, &displayName, ); err != nil { http.Error(w, "mapping scan error", http.StatusInternalServerError) return } row, ok := byMarket[marketID] if !ok { row = &models.MarketMailMappingRow{ MarketID: marketID, MarketCode: marketCode, MarketTitle: marketTitle, MailIDs: make([]string, 0, 8), Mails: make([]models.MarketMailOption, 0, 8), } byMarket[marketID] = row order = append(order, marketID) } if mailID.Valid && strings.TrimSpace(mailID.String) != "" { id := strings.TrimSpace(mailID.String) row.MailIDs = append(row.MailIDs, id) label := strings.TrimSpace(displayName.String) if label == "" { label = strings.TrimSpace(email.String) } row.Mails = append(row.Mails, models.MarketMailOption{ ID: id, Label: label, }) } } if err := rows.Err(); err != nil { http.Error(w, "mapping rows error", http.StatusInternalServerError) return } list := make([]models.MarketMailMappingRow, 0, len(order)) for _, marketID := range order { list = append(list, *byMarket[marketID]) } _ = json.NewEncoder(w).Encode(list) } } func SaveMarketMailMappingHandler(db *sql.DB) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json; charset=utf-8") marketIDStr := mux.Vars(r)["marketId"] marketID, err := strconv.ParseInt(marketIDStr, 10, 64) if err != nil || marketID <= 0 { http.Error(w, "invalid market id", http.StatusBadRequest) return } var payload MarketMailSavePayload if err := json.NewDecoder(r.Body).Decode(&payload); err != nil { http.Error(w, "invalid payload", http.StatusBadRequest) return } var marketExists bool if err := db.QueryRow(queries.ExistsActiveMarketByID, marketID).Scan(&marketExists); err != nil { http.Error(w, "market validate error", http.StatusInternalServerError) return } if !marketExists { http.Error(w, "market not found", http.StatusNotFound) return } mailIDs := normalizeIDList(payload.MailIDs) for _, mailID := range mailIDs { var mailExists bool if err := db.QueryRow(queries.ExistsActiveMailByID, mailID).Scan(&mailExists); err != nil { http.Error(w, "mail validate error", http.StatusInternalServerError) return } if !mailExists { http.Error(w, "mail not found: "+mailID, http.StatusBadRequest) return } } tx, err := db.Begin() if err != nil { http.Error(w, "transaction start error", http.StatusInternalServerError) return } defer tx.Rollback() if _, err := tx.Exec(queries.DeleteMarketMailsByMarketID, marketID); err != nil { http.Error(w, "mapping delete error", http.StatusInternalServerError) return } for _, mailID := range mailIDs { if _, err := tx.Exec(queries.InsertMarketMailMapping, marketID, mailID); err != nil { http.Error(w, "mapping insert error", http.StatusInternalServerError) return } } if err := tx.Commit(); err != nil { http.Error(w, "transaction commit error", http.StatusInternalServerError) return } _ = json.NewEncoder(w).Encode(map[string]any{ "success": true, "market_id": marketID, "mail_ids": mailIDs, }) } } func normalizeIDList(ids []string) []string { seen := make(map[string]struct{}, len(ids)) out := make([]string, 0, len(ids)) for _, raw := range ids { id := strings.TrimSpace(raw) if id == "" { continue } if _, ok := seen[id]; ok { continue } seen[id] = struct{}{} out = append(out, id) } sort.Strings(out) return out }