feat: Return location info and Hijri date from the prayer times endpoint
This commit is contained in:
+131
-7
@@ -16,6 +16,7 @@ import (
|
||||
"github.com/gofiber/fiber/v3/middleware/recover"
|
||||
"github.com/gofiber/fiber/v3/middleware/static"
|
||||
|
||||
"prayertimes/pkg/hijricalendar"
|
||||
"prayertimes/pkg/prayer"
|
||||
"prayertimes/templates"
|
||||
)
|
||||
@@ -30,8 +31,8 @@ type Services struct {
|
||||
}
|
||||
|
||||
type PrayerProvider interface {
|
||||
Get(ctx context.Context, locationID string) ([]prayer.Times, error)
|
||||
GetByCoords(ctx context.Context, coords prayer.Coordinates) ([]prayer.Times, error)
|
||||
Get(ctx context.Context, locationID string) (prayer.TimesResult, error)
|
||||
GetByCoords(ctx context.Context, coords prayer.Coordinates) (prayer.TimesResult, error)
|
||||
}
|
||||
|
||||
type LocationProvider interface {
|
||||
@@ -71,6 +72,7 @@ func New(services Services) *fiber.App {
|
||||
LocationID string `query:"location_id"`
|
||||
Latitude string `query:"latitude"`
|
||||
Longitude string `query:"longitude"`
|
||||
UTC string `query:"utc"`
|
||||
}
|
||||
if err := ctx.Bind().Query(&query); err != nil {
|
||||
return fmt.Errorf("failed to bind prayer times query parameters: %w", errors.Join(fiber.ErrBadRequest, err))
|
||||
@@ -79,15 +81,16 @@ func New(services Services) *fiber.App {
|
||||
locationID := strings.TrimSpace(query.LocationID)
|
||||
latitude := strings.TrimSpace(query.Latitude)
|
||||
longitude := strings.TrimSpace(query.Longitude)
|
||||
utc := strings.TrimSpace(query.UTC) == "1"
|
||||
|
||||
var (
|
||||
times []prayer.Times
|
||||
err error
|
||||
result prayer.TimesResult
|
||||
err error
|
||||
)
|
||||
|
||||
switch {
|
||||
case locationID != "":
|
||||
times, err = services.PrayerProvider.Get(ctx.Context(), locationID)
|
||||
result, err = services.PrayerProvider.Get(ctx.Context(), locationID)
|
||||
case latitude != "" && longitude != "":
|
||||
lat, latErr := strconv.ParseFloat(latitude, 64)
|
||||
if latErr != nil {
|
||||
@@ -98,7 +101,7 @@ func New(services Services) *fiber.App {
|
||||
return fmt.Errorf("failed to parse longitude query parameter: %w", errors.Join(fiber.ErrBadRequest, lngErr))
|
||||
}
|
||||
|
||||
times, err = services.PrayerProvider.GetByCoords(ctx.Context(), prayer.Coordinates{
|
||||
result, err = services.PrayerProvider.GetByCoords(ctx.Context(), prayer.Coordinates{
|
||||
Latitude: lat,
|
||||
Longitude: lng,
|
||||
})
|
||||
@@ -110,9 +113,33 @@ func New(services Services) *fiber.App {
|
||||
return fmt.Errorf("failed to fetch prayer times: %w", err)
|
||||
}
|
||||
|
||||
location := result.Location
|
||||
if location.Latitude != 0 || location.Longitude != 0 {
|
||||
locations, locErr := services.LocationProvider.SearchLocationsByCoords(ctx.Context(), prayer.Coordinates{
|
||||
Latitude: location.Latitude,
|
||||
Longitude: location.Longitude,
|
||||
})
|
||||
if locErr != nil {
|
||||
return fmt.Errorf("failed to enrich prayer times location from database: %w", locErr)
|
||||
}
|
||||
if len(locations) > 0 {
|
||||
dbLocation := locations[0]
|
||||
if strings.TrimSpace(dbLocation.Timezone) == "" {
|
||||
dbLocation.Timezone = location.Timezone
|
||||
}
|
||||
location = dbLocation
|
||||
}
|
||||
}
|
||||
|
||||
mappedTimes, err := mapPrayerTimesForResponse(result.Times, location, utc)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to map prayer times for response: %w", err)
|
||||
}
|
||||
|
||||
ctx.Response().Header.Set(fiber.HeaderCacheControl, "max-age=86400")
|
||||
return ctx.JSON(fiber.Map{
|
||||
"prayertimes": times,
|
||||
"location": location,
|
||||
"prayertimes": mappedTimes,
|
||||
})
|
||||
})
|
||||
|
||||
@@ -169,3 +196,100 @@ func New(services Services) *fiber.App {
|
||||
|
||||
return app
|
||||
}
|
||||
|
||||
type prayerTimesResponse struct {
|
||||
Date string `json:"date"`
|
||||
DateHijri string `json:"date_hijri"`
|
||||
Fajr string `json:"fajr"`
|
||||
Sunrise string `json:"sunrise"`
|
||||
Dhuhr string `json:"dhuhr"`
|
||||
Asr string `json:"asr"`
|
||||
Sunset string `json:"sunset,omitempty"`
|
||||
Maghrib string `json:"maghrib"`
|
||||
Isha string `json:"isha"`
|
||||
}
|
||||
|
||||
func mapPrayerTimesForResponse(times []prayer.Times, location prayer.Location, utc bool) ([]any, error) {
|
||||
if utc {
|
||||
result := make([]any, 0, len(times))
|
||||
for _, item := range times {
|
||||
dateHijri := item.DateHijri
|
||||
if dateHijri == "" {
|
||||
dateHijri = hijricalendar.ToISODate(item.Date)
|
||||
}
|
||||
|
||||
result = append(result, prayer.Times{
|
||||
Date: item.Date.UTC(),
|
||||
DateHijri: dateHijri,
|
||||
Fajr: item.Fajr.UTC(),
|
||||
Sunrise: item.Sunrise.UTC(),
|
||||
Dhuhr: item.Dhuhr.UTC(),
|
||||
Asr: item.Asr.UTC(),
|
||||
Sunset: item.Sunset.UTC(),
|
||||
Maghrib: item.Maghrib.UTC(),
|
||||
Isha: item.Isha.UTC(),
|
||||
})
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
tz := time.UTC
|
||||
if strings.TrimSpace(location.Timezone) != "" {
|
||||
loadedTZ, err := loadTimezone(location.Timezone)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to load location timezone: %w", err)
|
||||
}
|
||||
tz = loadedTZ
|
||||
}
|
||||
|
||||
result := make([]any, 0, len(times))
|
||||
for _, item := range times {
|
||||
dateHijri := item.DateHijri
|
||||
if dateHijri == "" {
|
||||
dateHijri = hijricalendar.ToISODate(item.Date)
|
||||
}
|
||||
|
||||
result = append(result, prayerTimesResponse{
|
||||
Date: item.Date.In(tz).Format(time.DateOnly),
|
||||
DateHijri: dateHijri,
|
||||
Fajr: formatHHMM(item.Fajr, tz),
|
||||
Sunrise: formatHHMM(item.Sunrise, tz),
|
||||
Dhuhr: formatHHMM(item.Dhuhr, tz),
|
||||
Asr: formatHHMM(item.Asr, tz),
|
||||
Sunset: formatHHMM(item.Sunset, tz),
|
||||
Maghrib: formatHHMM(item.Maghrib, tz),
|
||||
Isha: formatHHMM(item.Isha, tz),
|
||||
})
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func formatHHMM(value time.Time, tz *time.Location) string {
|
||||
if value.IsZero() {
|
||||
return ""
|
||||
}
|
||||
return value.In(tz).Format("15:04")
|
||||
}
|
||||
|
||||
func loadTimezone(name string) (*time.Location, error) {
|
||||
if strings.HasPrefix(name, "UTC") {
|
||||
offsetText := strings.TrimPrefix(name, "UTC")
|
||||
if offsetText == "" {
|
||||
return time.UTC, nil
|
||||
}
|
||||
|
||||
offsetHours, err := strconv.Atoi(offsetText)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse utc offset timezone: %w", err)
|
||||
}
|
||||
return time.FixedZone(name, offsetHours*3600), nil
|
||||
}
|
||||
|
||||
loc, err := time.LoadLocation(name)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to load iana timezone: %w", err)
|
||||
}
|
||||
|
||||
return loc, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user