// upupa_dataist_ir/internal/api/handlers.go package api import ( "database/sql" "encoding/json" "fmt" "log" "net/http" "strconv" "upupa_dataist_ir/internal/store" "upupa_dataist_ir/pkg/models" "github.com/go-chi/chi/v5" ) type API struct { Collections store.CollectionRepository Records store.RecordRepository } func NewAPI(colRepo store.CollectionRepository, recRepo store.RecordRepository) *API { return &API{ Collections: colRepo, Records: recRepo, } } func (a *API) HealthCheck(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) w.Write([]byte("upupa_dataist_ir is running!")) } func (a *API) CreateRecordHandler(w http.ResponseWriter, r *http.Request) { collectionName := chi.URLParam(r, "name") collection, err := a.Collections.GetByName(collectionName) if err != nil { if err == sql.ErrNoRows { http.Error(w, fmt.Sprintf("Collection '%s' not found.", collectionName), http.StatusNotFound) return } log.Printf("Error retrieving collection: %v", err) http.Error(w, "Database error", http.StatusInternalServerError) return } var recordData map[string]interface{} if err := json.NewDecoder(r.Body).Decode(&recordData); err != nil { http.Error(w, "Invalid JSON payload", http.StatusBadRequest) return } newRecord := models.Record{ ID: models.NewID(), CollectionID: collection.ID, Data: recordData, } createdRecord, err := a.Records.CreateRecord(newRecord) if err != nil { log.Printf("Error creating record: %v", err) http.Error(w, "Failed to create record", http.StatusInternalServerError) return } w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusCreated) json.NewEncoder(w).Encode(createdRecord) } func (a *API) GetAllRecordsHandler(w http.ResponseWriter, r *http.Request) { collectionName := chi.URLParam(r, "name") collection, err := a.Collections.GetByName(collectionName) if err != nil { if err == sql.ErrNoRows { http.Error(w, fmt.Sprintf("Collection '%s' not found.", collectionName), http.StatusNotFound) return } log.Printf("Error retrieving collection: %v", err) http.Error(w, "Database error", http.StatusInternalServerError) return } queryParams := r.URL.Query() filters := make(map[string]interface{}) for key, values := range queryParams { if key != "limit" && key != "offset" && key != "orderBy" && len(values) > 0 { filters[key] = values[0] } } var finalFilters map[string]interface{} = nil if len(filters) > 0 { finalFilters = filters } limit := getIntQueryParam(queryParams.Get("limit"), 50) offset := getIntQueryParam(queryParams.Get("offset"), 0) orderBy := queryParams.Get("orderBy") if limit < 0 || limit > 100 { limit = 50 } if offset < 0 { offset = 0 } records, totalItems, err := a.Records.GetAll(collection.ID, finalFilters, orderBy, limit, offset) if err != nil { log.Printf("Error retrieving records: %v", err) http.Error(w, "Failed to retrieve records", http.StatusInternalServerError) return } w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) response := map[string]interface{}{ "items": records, "totalItems": totalItems, "limit": limit, "offset": offset, "orderBy": orderBy, } json.NewEncoder(w).Encode(response) } func (a *API) GetRecordByIDHandler(w http.ResponseWriter, r *http.Request) { collectionName := chi.URLParam(r, "name") recordID := chi.URLParam(r, "id") collection, err := a.Collections.GetByName(collectionName) if err != nil { if err == sql.ErrNoRows { http.Error(w, fmt.Sprintf("Collection '%s' not found.", collectionName), http.StatusNotFound) return } log.Printf("Error retrieving collection: %v", err) http.Error(w, "Database error", http.StatusInternalServerError) return } record, err := a.Records.GetByID(collection.ID, recordID) if err != nil { if err == sql.ErrNoRows { http.Error(w, fmt.Sprintf("Record with ID '%s' not found.", recordID), http.StatusNotFound) return } log.Printf("Error retrieving record: %v", err) http.Error(w, "Failed to retrieve record", http.StatusInternalServerError) return } w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode(record) } func (a *API) UpdateRecordHandler(w http.ResponseWriter, r *http.Request) { collectionName := chi.URLParam(r, "name") recordID := chi.URLParam(r, "id") collection, err := a.Collections.GetByName(collectionName) if err != nil { if err == sql.ErrNoRows { http.Error(w, fmt.Sprintf("Collection '%s' not found.", collectionName), http.StatusNotFound) return } log.Printf("Error retrieving collection: %v", err) http.Error(w, "Database error", http.StatusInternalServerError) return } var updatedData map[string]interface{} if err := json.NewDecoder(r.Body).Decode(&updatedData); err != nil { http.Error(w, "Invalid JSON payload", http.StatusBadRequest) return } recordToUpdate := models.Record{ ID: recordID, CollectionID: collection.ID, Data: updatedData, } if err := a.Records.Update(recordToUpdate); err != nil { if err == sql.ErrNoRows { http.Error(w, fmt.Sprintf("Record with ID '%s' not found.", recordID), http.StatusNotFound) return } log.Printf("Error updating record: %v", err) http.Error(w, "Failed to update record", http.StatusInternalServerError) return } updatedRecord, err := a.Records.GetByID(collection.ID, recordID) if err != nil { log.Printf("Error fetching updated record: %v", err) http.Error(w, "Record updated, but failed to fetch final data.", http.StatusInternalServerError) return } w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode(updatedRecord) } func (a *API) DeleteRecordHandler(w http.ResponseWriter, r *http.Request) { collectionName := chi.URLParam(r, "name") recordID := chi.URLParam(r, "id") collection, err := a.Collections.GetByName(collectionName) if err != nil { if err == sql.ErrNoRows { http.Error(w, fmt.Sprintf("Collection '%s' not found.", collectionName), http.StatusNotFound) return } log.Printf("Error retrieving collection: %v", err) http.Error(w, "Database error", http.StatusInternalServerError) return } if err := a.Records.Delete(collection.ID, recordID); err != nil { if err == sql.ErrNoRows { http.Error(w, fmt.Sprintf("Record with ID '%s' not found.", recordID), http.StatusNotFound) return } log.Printf("Error deleting record: %v", err) http.Error(w, "Failed to delete record", http.StatusInternalServerError) return } w.WriteHeader(http.StatusNoContent) } func getIntQueryParam(param string, defaultValue int) int { if param == "" { return defaultValue } val, err := strconv.Atoi(param) if err != nil { return defaultValue } return val }