add base handler and refactor H
This commit is contained in:
parent
fb4d31afba
commit
c7024d7f4e
3
service_a/internal/app/app.go
Normal file
3
service_a/internal/app/app.go
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
package app
|
||||||
|
|
||||||
|
type H map[string]any
|
||||||
16
service_a/internal/domains/handlers.go
Normal file
16
service_a/internal/domains/handlers.go
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
package domains
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
BaseHandler struct{}
|
||||||
|
)
|
||||||
|
|
||||||
|
func (bh *BaseHandler) ToJSON(w http.ResponseWriter, statusCode int, data any) {
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
w.WriteHeader(statusCode)
|
||||||
|
json.NewEncoder(w).Encode(data)
|
||||||
|
}
|
||||||
@ -5,8 +5,6 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
type H map[string]any
|
|
||||||
|
|
||||||
type MeteoData struct {
|
type MeteoData struct {
|
||||||
Timestamp time.Time `csv:"fecha"`
|
Timestamp time.Time `csv:"fecha"`
|
||||||
Location string `csv:"ciudad"`
|
Location string `csv:"ciudad"`
|
||||||
@ -29,18 +27,52 @@ type FileStats struct {
|
|||||||
FileChecksum string `json:"file_checksum"`
|
FileChecksum string `json:"file_checksum"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type GetMeteoData struct {
|
||||||
|
Location string
|
||||||
|
From string
|
||||||
|
To string
|
||||||
|
Page int
|
||||||
|
Limit int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mt *GetMeteoData) Validate() error {
|
||||||
|
if mt.Location == "" {
|
||||||
|
return ErrMissingOrInvalidLocation
|
||||||
|
}
|
||||||
|
|
||||||
|
if mt.From == "" {
|
||||||
|
return ErrMissingOrInvalidFromDate
|
||||||
|
}
|
||||||
|
|
||||||
|
if mt.To == "" {
|
||||||
|
return ErrMissingOrInvalidToDate
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := time.Parse("2006-01-02", mt.From); err != nil {
|
||||||
|
return ErrMissingOrInvalidFromDate
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := time.Parse("2006-01-02", mt.To); err != nil {
|
||||||
|
return ErrMissingOrInvalidToDate
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ErrCannotParseFile = errors.New("cannot parse file")
|
ErrCannotParseFile = errors.New("cannot parse file")
|
||||||
ErrValidateRecord = errors.New("error validating record")
|
ErrValidateRecord = errors.New("error validating record")
|
||||||
ErrRecordNotValid = errors.New("record not valid")
|
ErrRecordNotValid = errors.New("record not valid")
|
||||||
ErrReadingCSVHeader = errors.New("error reading CSV header")
|
ErrReadingCSVHeader = errors.New("error reading CSV header")
|
||||||
ErrReadingCSVRow = errors.New("error reading CSV row")
|
ErrReadingCSVRow = errors.New("error reading CSV row")
|
||||||
ErrMissingOrInvalidDateField = errors.New("missing or invalid date field")
|
ErrMissingOrInvalidDate = errors.New("missing or invalid date")
|
||||||
ErrMissingOrInvalidCityField = errors.New("missing or invalid city field")
|
ErrMissingOrInvalidFromDate = errors.New("missing or invalid from date")
|
||||||
ErrMissingOrInvalidMaxTemp = errors.New("missing or invalid max temp field")
|
ErrMissingOrInvalidToDate = errors.New("missing or invalid to date")
|
||||||
ErrMissingOrInvalidMinTemp = errors.New("missing or invalid min temp field")
|
ErrMissingOrInvalidLocation = errors.New("missing or invalid location")
|
||||||
ErrMissingOrInvalidRainfall = errors.New("missing or invalid rainfall field")
|
ErrMissingOrInvalidMaxTemp = errors.New("missing or invalid max temp")
|
||||||
ErrMissingOrInvalidCloudiness = errors.New("missing or invalid cloudiness field")
|
ErrMissingOrInvalidMinTemp = errors.New("missing or invalid min temp")
|
||||||
|
ErrMissingOrInvalidRainfall = errors.New("missing or invalid rainfall")
|
||||||
|
ErrMissingOrInvalidCloudiness = errors.New("missing or invalid cloudiness")
|
||||||
ErrMaxTempOutOfRange = errors.New("max temp out of range (must be <= 60°C)")
|
ErrMaxTempOutOfRange = errors.New("max temp out of range (must be <= 60°C)")
|
||||||
ErrMinTempOutOfRange = errors.New("min temp out of range (must be >= -20°C)")
|
ErrMinTempOutOfRange = errors.New("min temp out of range (must be >= -20°C)")
|
||||||
ErrRainfallOutOfRange = errors.New("rainfall out of range (must be 0-500 mm)")
|
ErrRainfallOutOfRange = errors.New("rainfall out of range (must be 0-500 mm)")
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import (
|
|||||||
"encoding/csv"
|
"encoding/csv"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"servicea/internal/app"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@ -69,7 +70,7 @@ func (c *CSV) Parse(r io.Reader) ([]MeteoData, []RejectedMeteoData, error) {
|
|||||||
|
|
||||||
rowValue := strings.Join(row, ";")
|
rowValue := strings.Join(row, ";")
|
||||||
|
|
||||||
record := make(H)
|
record := make(app.H)
|
||||||
for i, value := range row {
|
for i, value := range row {
|
||||||
if i < len(header) {
|
if i < len(header) {
|
||||||
record[header[i]] = value
|
record[header[i]] = value
|
||||||
@ -99,17 +100,17 @@ func (c *CSV) Parse(r io.Reader) ([]MeteoData, []RejectedMeteoData, error) {
|
|||||||
return meteoDataList, rejectedDataList, nil
|
return meteoDataList, rejectedDataList, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func normalize(record H) (*MeteoData, error) {
|
func normalize(record app.H) (*MeteoData, error) {
|
||||||
meteoData := &MeteoData{}
|
meteoData := &MeteoData{}
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
meteoData.Timestamp, err = parseDate(record, "Fecha", ErrMissingOrInvalidDateField)
|
meteoData.Timestamp, err = parseDate(record, "Fecha", ErrMissingOrInvalidDate)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
meteoData.Location, err = parseString(record, "Ciudad", ErrMissingOrInvalidCityField)
|
meteoData.Location, err = parseString(record, "Ciudad", ErrMissingOrInvalidLocation)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -137,7 +138,7 @@ func normalize(record H) (*MeteoData, error) {
|
|||||||
return meteoData, nil
|
return meteoData, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseDate(record H, key string, errMissing error) (time.Time, error) {
|
func parseDate(record app.H, key string, errMissing error) (time.Time, error) {
|
||||||
if str, ok := record[key].(string); ok && str != "" {
|
if str, ok := record[key].(string); ok && str != "" {
|
||||||
t, err := time.Parse("2006/01/02", str)
|
t, err := time.Parse("2006/01/02", str)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -148,14 +149,14 @@ func parseDate(record H, key string, errMissing error) (time.Time, error) {
|
|||||||
return time.Time{}, errMissing
|
return time.Time{}, errMissing
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseString(record H, key string, errMissing error) (string, error) {
|
func parseString(record app.H, key string, errMissing error) (string, error) {
|
||||||
if str, ok := record[key].(string); ok && str != "" {
|
if str, ok := record[key].(string); ok && str != "" {
|
||||||
return str, nil
|
return str, nil
|
||||||
}
|
}
|
||||||
return "", errMissing
|
return "", errMissing
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseFloatField(record H, key string, errMissing error) (float32, error) {
|
func parseFloatField(record app.H, key string, errMissing error) (float32, error) {
|
||||||
if str, ok := record[key].(string); ok && str != "" {
|
if str, ok := record[key].(string); ok && str != "" {
|
||||||
str = strings.Replace(str, ",", ".", 1)
|
str = strings.Replace(str, ",", ".", 1)
|
||||||
f, err := strconv.ParseFloat(str, 32)
|
f, err := strconv.ParseFloat(str, 32)
|
||||||
@ -167,7 +168,7 @@ func parseFloatField(record H, key string, errMissing error) (float32, error) {
|
|||||||
return 0, errMissing
|
return 0, errMissing
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseIntField(record H, key string, errMissing error) (int, error) {
|
func parseIntField(record app.H, key string, errMissing error) (int, error) {
|
||||||
if str, ok := record[key].(string); ok && str != "" {
|
if str, ok := record[key].(string); ok && str != "" {
|
||||||
str = strings.TrimSpace(str)
|
str = strings.TrimSpace(str)
|
||||||
i, err := strconv.Atoi(str)
|
i, err := strconv.Atoi(str)
|
||||||
|
|||||||
@ -4,15 +4,18 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"servicea/internal/app"
|
||||||
|
"servicea/internal/domains"
|
||||||
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Handler struct {
|
type Handler struct {
|
||||||
|
domains.BaseHandler
|
||||||
*Service
|
*Service
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -68,13 +71,48 @@ func (h *Handler) IngestCSV(w http.ResponseWriter, r *http.Request) {
|
|||||||
"elapsed_ms", fileStats.ElapsedMS,
|
"elapsed_ms", fileStats.ElapsedMS,
|
||||||
"file_checksum", fileStats.FileChecksum,
|
"file_checksum", fileStats.FileChecksum,
|
||||||
)
|
)
|
||||||
|
h.ToJSON(w, http.StatusOK, app.H{"stats": fileStats})
|
||||||
w.Header().Set("Content-Type", "application/json")
|
|
||||||
json.NewEncoder(w).Encode(H{
|
|
||||||
"stats": fileStats,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Handler) IngestExcel(w http.ResponseWriter, r *http.Request) {
|
func (h *Handler) IngestExcel(w http.ResponseWriter, r *http.Request) {
|
||||||
fmt.Fprintf(w, "hello from excel")
|
fmt.Fprintf(w, "hello from excel")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h *Handler) GetMeteoData(w http.ResponseWriter, r *http.Request) {
|
||||||
|
queryParams := r.URL.Query()
|
||||||
|
pageInt := int(1)
|
||||||
|
limitInt := int(10)
|
||||||
|
|
||||||
|
page := queryParams.Get("page")
|
||||||
|
limit := queryParams.Get("limit")
|
||||||
|
|
||||||
|
if page != "" {
|
||||||
|
p, err := strconv.Atoi(page)
|
||||||
|
if err == nil {
|
||||||
|
pageInt = p
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if limit != "" {
|
||||||
|
l, err := strconv.Atoi(limit)
|
||||||
|
if err == nil {
|
||||||
|
limitInt = l
|
||||||
|
} else {
|
||||||
|
limitInt = 10
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
params := GetMeteoData{
|
||||||
|
Location: queryParams.Get("city"),
|
||||||
|
From: queryParams.Get("from"),
|
||||||
|
To: queryParams.Get("to"),
|
||||||
|
Page: pageInt,
|
||||||
|
Limit: limitInt,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := params.Validate(); err != nil {
|
||||||
|
h.ToJSON(w, http.StatusBadRequest, app.H{"error": err.Error()})
|
||||||
|
}
|
||||||
|
|
||||||
|
slog.Info("params", "params", params)
|
||||||
|
}
|
||||||
|
|||||||
@ -5,4 +5,6 @@ import "net/http"
|
|||||||
func RegisterRoutes(mux *http.ServeMux, handler *Handler) {
|
func RegisterRoutes(mux *http.ServeMux, handler *Handler) {
|
||||||
mux.HandleFunc("POST /ingest/csv", handler.IngestCSV)
|
mux.HandleFunc("POST /ingest/csv", handler.IngestCSV)
|
||||||
mux.HandleFunc("POST /ingest/excel", handler.IngestExcel)
|
mux.HandleFunc("POST /ingest/excel", handler.IngestExcel)
|
||||||
|
|
||||||
|
mux.HandleFunc("GET /data", handler.GetMeteoData)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -48,3 +48,7 @@ func (s *Service) UpdateElapsedMS(ctx context.Context, batchID, elapsedMS int) e
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Service) GetMeteoData(params GetMeteoData) ([]MeteoData, error) {
|
||||||
|
return []MeteoData{}, nil
|
||||||
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user