You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

145 lines
3.8 KiB
Go

package api
import (
"context"
"errors"
"fmt"
"net/http"
"strconv"
"strings"
"time"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/middleware/cors"
"github.com/gofiber/fiber/v2/middleware/favicon"
"github.com/gofiber/fiber/v2/middleware/filesystem"
"github.com/gofiber/fiber/v2/middleware/logger"
recover "github.com/gofiber/fiber/v2/middleware/recover"
"prayertimes/pkg/prayer"
"prayertimes/templates"
)
type httpError struct {
Message string `json:"message"`
}
type Services struct {
Provider DiyanetProvider
}
type DiyanetProvider interface {
Get(ctx context.Context, locationID string) ([]prayer.Times, error)
GetByCoords(ctx context.Context, coords prayer.Coordinates) ([]prayer.Times, error)
SearchLocations(ctx context.Context, query string) ([]prayer.Location, error)
}
func New(services Services) *fiber.App {
app := fiber.New(fiber.Config{
Immutable: true,
DisableStartupMessage: false,
ReadTimeout: time.Second * 10,
WriteTimeout: time.Second * 30,
ErrorHandler: func(ctx *fiber.Ctx, err error) error {
if errors.Is(err, prayer.ErrInvalidLocation) {
return ctx.Status(http.StatusUnprocessableEntity).JSON(httpError{
Message: err.Error(),
})
}
return fiber.DefaultErrorHandler(ctx, err)
},
})
app.Use(
favicon.New(),
recover.New(recover.Config{EnableStackTrace: true}),
logger.New(),
cors.New(),
)
app.Get("/healthz", func(ctx *fiber.Ctx) error {
return ctx.SendString("OK")
})
app.Get("/api/v1/diyanet/prayertimes", func(ctx *fiber.Ctx) error {
var query struct {
LocationID string `query:"location_id"`
Latitude string `query:"latitude"`
Longitude string `query:"longitude"`
}
if err := ctx.QueryParser(&query); err != nil {
return fmt.Errorf("failed to bind prayer times query parameters: %w", errors.Join(fiber.ErrBadRequest, err))
}
locationID := strings.TrimSpace(query.LocationID)
latitude := strings.TrimSpace(query.Latitude)
longitude := strings.TrimSpace(query.Longitude)
var (
times []prayer.Times
err error
)
switch {
case locationID != "":
times, err = services.Provider.Get(ctx.Context(), locationID)
case latitude != "" && longitude != "":
lat, latErr := strconv.ParseFloat(latitude, 64)
if latErr != nil {
return fmt.Errorf("failed to parse latitude query parameter: %w", errors.Join(fiber.ErrBadRequest, latErr))
}
lng, lngErr := strconv.ParseFloat(longitude, 64)
if lngErr != nil {
return fmt.Errorf("failed to parse longitude query parameter: %w", errors.Join(fiber.ErrBadRequest, lngErr))
}
times, err = services.Provider.GetByCoords(ctx.Context(), prayer.Coordinates{
Latitude: lat,
Longitude: lng,
})
default:
return fmt.Errorf("failed to validate prayer times query parameters: %w", fiber.ErrBadRequest)
}
if err != nil {
return fmt.Errorf("failed to fetch prayer times: %w", err)
}
ctx.Response().Header.Add(fiber.HeaderCacheControl, "max-age=86400")
return ctx.JSON(fiber.Map{
"prayertimes": times,
})
})
app.Get("/api/v1/diyanet/location", func(ctx *fiber.Ctx) error {
var query struct {
Text string `query:"query"`
}
if err := ctx.QueryParser(&query); err != nil {
return fmt.Errorf("failed to bind location query parameters: %w", errors.Join(fiber.ErrBadRequest, err))
}
query.Text = strings.TrimSpace(query.Text)
if query.Text == "" {
return fmt.Errorf("failed to validate location query parameters: %w", fiber.ErrBadRequest)
}
locations, err := services.Provider.SearchLocations(ctx.Context(), query.Text)
if err != nil {
return fmt.Errorf("failed to search locations: %w", err)
}
ctx.Response().Header.Add(fiber.HeaderCacheControl, "max-age=86400")
return ctx.JSON(fiber.Map{
"locations": locations,
})
})
app.Use("/", filesystem.New(filesystem.Config{
Root: templates.HttpFS(),
}))
return app
}