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.
146 lines
3.9 KiB
Go
146 lines
3.9 KiB
Go
package api
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"net/http"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/gofiber/fiber/v3"
|
|
"github.com/gofiber/fiber/v3/middleware/cors"
|
|
"github.com/gofiber/fiber/v3/middleware/favicon"
|
|
"github.com/gofiber/fiber/v3/middleware/logger"
|
|
"github.com/gofiber/fiber/v3/middleware/recover"
|
|
"github.com/gofiber/fiber/v3/middleware/static"
|
|
|
|
"prayertimes/pkg/prayer"
|
|
"prayertimes/templates"
|
|
)
|
|
|
|
type httpError struct {
|
|
Message string `json:"message"`
|
|
}
|
|
|
|
type Services struct {
|
|
PrayerProvider PrayerProvider
|
|
LocationProvider LocationProvider
|
|
}
|
|
|
|
type PrayerProvider interface {
|
|
Get(ctx context.Context, locationID string) ([]prayer.Times, error)
|
|
GetByCoords(ctx context.Context, coords prayer.Coordinates) ([]prayer.Times, error)
|
|
}
|
|
|
|
type LocationProvider interface {
|
|
SearchLocations(ctx context.Context, query string) ([]prayer.Location, error)
|
|
}
|
|
|
|
func New(services Services) *fiber.App {
|
|
app := fiber.New(fiber.Config{
|
|
Immutable: true,
|
|
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.Bind().Query(&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.PrayerProvider.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.PrayerProvider.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.Set(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.Bind().Query(&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.LocationProvider.SearchLocations(ctx.Context(), query.Text)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to search locations: %w", err)
|
|
}
|
|
|
|
ctx.Response().Header.Set(fiber.HeaderCacheControl, "max-age=86400")
|
|
return ctx.JSON(fiber.Map{
|
|
"locations": locations,
|
|
})
|
|
})
|
|
|
|
app.Use("/", static.New("", static.Config{FS: templates.FS()}))
|
|
|
|
return app
|
|
}
|