feat: Return location info and Hijri date from the prayer times endpoint

This commit is contained in:
2026-02-21 02:07:22 +03:00
parent 07a9703a89
commit c106d57fe6
9 changed files with 358 additions and 87 deletions
+131 -7
View File
@@ -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
}