feat: Rewrite to use the Diyanet API
This commit is contained in:
@@ -0,0 +1,410 @@
|
|||||||
|
openapi: 3.0.3
|
||||||
|
info:
|
||||||
|
title: Diyanet Namaz Vaktim API
|
||||||
|
version: 1.0.0
|
||||||
|
description: |
|
||||||
|
OpenAPI spec derived from decompiled Android app usage.
|
||||||
|
Base URL and endpoints reflect what the client calls.
|
||||||
|
servers:
|
||||||
|
- url: https://namazvakti.diyanet.gov.tr/api
|
||||||
|
security:
|
||||||
|
- basicAuth: []
|
||||||
|
tags:
|
||||||
|
- name: PrayerTimes
|
||||||
|
- name: Locations
|
||||||
|
- name: Content
|
||||||
|
- name: Mosques
|
||||||
|
components:
|
||||||
|
securitySchemes:
|
||||||
|
basicAuth:
|
||||||
|
type: http
|
||||||
|
scheme: basic
|
||||||
|
schemas:
|
||||||
|
ResultMessage:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
messageType:
|
||||||
|
type: integer
|
||||||
|
nullable: true
|
||||||
|
messageContent:
|
||||||
|
type: string
|
||||||
|
nullable: true
|
||||||
|
messageCode:
|
||||||
|
type: integer
|
||||||
|
nullable: true
|
||||||
|
PrayerTime:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
imsak:
|
||||||
|
type: string
|
||||||
|
nullable: true
|
||||||
|
gunes:
|
||||||
|
type: string
|
||||||
|
nullable: true
|
||||||
|
ogle:
|
||||||
|
type: string
|
||||||
|
nullable: true
|
||||||
|
ikindi:
|
||||||
|
type: string
|
||||||
|
nullable: true
|
||||||
|
aksam:
|
||||||
|
type: string
|
||||||
|
nullable: true
|
||||||
|
yatsi:
|
||||||
|
type: string
|
||||||
|
nullable: true
|
||||||
|
gunes_dogus:
|
||||||
|
type: string
|
||||||
|
nullable: true
|
||||||
|
gunes_batis:
|
||||||
|
type: string
|
||||||
|
nullable: true
|
||||||
|
kible_saati:
|
||||||
|
type: string
|
||||||
|
nullable: true
|
||||||
|
hicri_tarih_uzun:
|
||||||
|
type: string
|
||||||
|
nullable: true
|
||||||
|
hicri_tarih_kisa:
|
||||||
|
type: string
|
||||||
|
nullable: true
|
||||||
|
miladi_tarih_uzun:
|
||||||
|
type: string
|
||||||
|
nullable: true
|
||||||
|
miladi_tarih_uzun_Iso8601:
|
||||||
|
type: string
|
||||||
|
nullable: true
|
||||||
|
miladi_tarih_kisa_Iso8601:
|
||||||
|
type: string
|
||||||
|
nullable: true
|
||||||
|
imsak_bg:
|
||||||
|
type: string
|
||||||
|
nullable: true
|
||||||
|
gunes_bg:
|
||||||
|
type: string
|
||||||
|
nullable: true
|
||||||
|
ogle_bg:
|
||||||
|
type: string
|
||||||
|
nullable: true
|
||||||
|
ikindi_bg:
|
||||||
|
type: string
|
||||||
|
nullable: true
|
||||||
|
aksam_bg:
|
||||||
|
type: string
|
||||||
|
nullable: true
|
||||||
|
yatsi_bg:
|
||||||
|
type: string
|
||||||
|
nullable: true
|
||||||
|
ayin_sekli_url:
|
||||||
|
type: string
|
||||||
|
nullable: true
|
||||||
|
MyLocation:
|
||||||
|
type: object
|
||||||
|
description: Location object returned under "konum".
|
||||||
|
properties:
|
||||||
|
konum_Id:
|
||||||
|
type: integer
|
||||||
|
nullable: true
|
||||||
|
timezone:
|
||||||
|
type: string
|
||||||
|
nullable: true
|
||||||
|
additionalProperties: true
|
||||||
|
ResultLocation:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
konum:
|
||||||
|
$ref: '#/components/schemas/MyLocation'
|
||||||
|
namazVakti:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/components/schemas/PrayerTime'
|
||||||
|
ResultLocationResponse:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
success:
|
||||||
|
type: boolean
|
||||||
|
resultMessage:
|
||||||
|
$ref: '#/components/schemas/ResultMessage'
|
||||||
|
resultObject:
|
||||||
|
$ref: '#/components/schemas/ResultLocation'
|
||||||
|
serverTime:
|
||||||
|
type: string
|
||||||
|
example:
|
||||||
|
success: true
|
||||||
|
resultMessage:
|
||||||
|
messageType: 1
|
||||||
|
messageContent: Namaz vakitleri başarıyla oluşturuldu
|
||||||
|
messageCode: 200
|
||||||
|
resultObject:
|
||||||
|
konum:
|
||||||
|
konum_Id: 9340
|
||||||
|
timezone: Europe/Istanbul
|
||||||
|
namazVakti:
|
||||||
|
- imsak: "2026-02-19T06:19:00.0000000+03:00"
|
||||||
|
gunes: "2026-02-19T07:43:00.0000000+03:00"
|
||||||
|
ogle: "2026-02-19T13:20:00.0000000+03:00"
|
||||||
|
ikindi: "2026-02-19T16:18:00.0000000+03:00"
|
||||||
|
aksam: "2026-02-19T18:47:00.0000000+03:00"
|
||||||
|
yatsi: "2026-02-19T20:06:00.0000000+03:00"
|
||||||
|
gunes_dogus: "2026-02-19T07:50:00.0000000+03:00"
|
||||||
|
gunes_batis: "2026-02-19T18:40:00.0000000+03:00"
|
||||||
|
kible_saati: "2026-02-19T11:44:00.0000000+03:00"
|
||||||
|
hicri_tarih_uzun: 1 Ramazan 1447
|
||||||
|
hicri_tarih_kisa: 1.9.1447
|
||||||
|
miladi_tarih_uzun: 19 Şubat 2026 Perşembe
|
||||||
|
miladi_tarih_uzun_Iso8601: "2026-02-19T00:00:00.0000000+03:00"
|
||||||
|
miladi_tarih_kisa_Iso8601: 19.02.2026
|
||||||
|
imsak_bg: "https://namazvakti.diyanet.gov.tr/api/images/main_imsak.png?v=6"
|
||||||
|
gunes_bg: "https://namazvakti.diyanet.gov.tr/api/images/main_gunes.png?v=6"
|
||||||
|
ogle_bg: "https://namazvakti.diyanet.gov.tr/api/images/main_ogle.png?v=6"
|
||||||
|
ikindi_bg: "https://namazvakti.diyanet.gov.tr/api/images/main_ikindi.png?v=6"
|
||||||
|
aksam_bg: "https://namazvakti.diyanet.gov.tr/api/images/main_aksam.png?v=6"
|
||||||
|
yatsi_bg: "https://namazvakti.diyanet.gov.tr/api/images/main_yatsi.png?v=6"
|
||||||
|
ayin_sekli_url: "https://namazvakti.diyanet.gov.tr/images/r1.gif"
|
||||||
|
serverTime: "20/02/2026 - 23:03"
|
||||||
|
PrayerTimeVersion:
|
||||||
|
type: object
|
||||||
|
additionalProperties: true
|
||||||
|
PrayerTimeVersionResponse:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
success:
|
||||||
|
type: boolean
|
||||||
|
resultMessage:
|
||||||
|
$ref: '#/components/schemas/ResultMessage'
|
||||||
|
resultObject:
|
||||||
|
$ref: '#/components/schemas/PrayerTimeVersion'
|
||||||
|
serverTime:
|
||||||
|
type: string
|
||||||
|
CityInfo:
|
||||||
|
type: object
|
||||||
|
additionalProperties: true
|
||||||
|
CityInfoResponse:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
success:
|
||||||
|
type: boolean
|
||||||
|
resultMessage:
|
||||||
|
$ref: '#/components/schemas/ResultMessage'
|
||||||
|
resultObject:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/components/schemas/CityInfo'
|
||||||
|
serverTime:
|
||||||
|
type: string
|
||||||
|
Mosque:
|
||||||
|
type: object
|
||||||
|
additionalProperties: true
|
||||||
|
MosquesResponse:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
success:
|
||||||
|
type: boolean
|
||||||
|
resultMessage:
|
||||||
|
$ref: '#/components/schemas/ResultMessage'
|
||||||
|
resultObject:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/components/schemas/Mosque'
|
||||||
|
serverTime:
|
||||||
|
type: string
|
||||||
|
SearchLocation:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
cityID:
|
||||||
|
type: integer
|
||||||
|
cityNameTR:
|
||||||
|
type: string
|
||||||
|
stateNameTR:
|
||||||
|
type: string
|
||||||
|
countryNameTR:
|
||||||
|
type: string
|
||||||
|
cityNameEN:
|
||||||
|
type: string
|
||||||
|
stateNameEN:
|
||||||
|
type: string
|
||||||
|
countryNameEN:
|
||||||
|
type: string
|
||||||
|
latitude:
|
||||||
|
type: number
|
||||||
|
format: double
|
||||||
|
longitude:
|
||||||
|
type: number
|
||||||
|
format: double
|
||||||
|
SearchLocationsResults:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
results:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/components/schemas/SearchLocation'
|
||||||
|
SearchLocationsResponse:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
success:
|
||||||
|
type: boolean
|
||||||
|
resultMessage:
|
||||||
|
$ref: '#/components/schemas/ResultMessage'
|
||||||
|
resultObject:
|
||||||
|
$ref: '#/components/schemas/SearchLocationsResults'
|
||||||
|
serverTime:
|
||||||
|
type: string
|
||||||
|
example:
|
||||||
|
success: true
|
||||||
|
resultMessage:
|
||||||
|
messageType: 1
|
||||||
|
messageContent: İlçeler başarıyla listelendi
|
||||||
|
messageCode: 200
|
||||||
|
resultObject:
|
||||||
|
results:
|
||||||
|
- cityID: 9340
|
||||||
|
cityNameTR: İZNİK
|
||||||
|
stateNameTR: BURSA
|
||||||
|
countryNameTR: TÜRKİYE
|
||||||
|
cityNameEN: IZNIK
|
||||||
|
stateNameEN: BURSA
|
||||||
|
countryNameEN: TÜRKİYE
|
||||||
|
latitude: 40.429
|
||||||
|
longitude: 29.711
|
||||||
|
serverTime: "20/02/2026 - 23:01"
|
||||||
|
VersesHadithsPrayersArrays:
|
||||||
|
type: object
|
||||||
|
additionalProperties: true
|
||||||
|
VersesHadithsPrayersResponse:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
success:
|
||||||
|
type: boolean
|
||||||
|
resultMessage:
|
||||||
|
$ref: '#/components/schemas/ResultMessage'
|
||||||
|
resultObject:
|
||||||
|
$ref: '#/components/schemas/VersesHadithsPrayersArrays'
|
||||||
|
serverTime:
|
||||||
|
type: string
|
||||||
|
paths:
|
||||||
|
/NamazVakti/Aylik:
|
||||||
|
get:
|
||||||
|
tags: [PrayerTimes]
|
||||||
|
summary: Get monthly prayer times
|
||||||
|
description: |
|
||||||
|
Returns monthly prayer times for a location.
|
||||||
|
Provide either ilceId or latitude/longitude.
|
||||||
|
parameters:
|
||||||
|
- in: query
|
||||||
|
name: ilceId
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
required: false
|
||||||
|
- in: query
|
||||||
|
name: latitude
|
||||||
|
schema:
|
||||||
|
type: number
|
||||||
|
format: double
|
||||||
|
required: false
|
||||||
|
- in: query
|
||||||
|
name: longitude
|
||||||
|
schema:
|
||||||
|
type: number
|
||||||
|
format: double
|
||||||
|
required: false
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: OK
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/ResultLocationResponse'
|
||||||
|
/Version:
|
||||||
|
get:
|
||||||
|
tags: [PrayerTimes]
|
||||||
|
summary: Get prayer time version
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: OK
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/PrayerTimeVersionResponse'
|
||||||
|
/Search/GetByName:
|
||||||
|
get:
|
||||||
|
tags: [Locations]
|
||||||
|
summary: Search locations by name
|
||||||
|
parameters:
|
||||||
|
- in: query
|
||||||
|
name: searchText
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
required: true
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: OK
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/SearchLocationsResponse'
|
||||||
|
/ilce/GetByCoordinat:
|
||||||
|
get:
|
||||||
|
tags: [Locations]
|
||||||
|
summary: Get city/district info by coordinates
|
||||||
|
parameters:
|
||||||
|
- in: query
|
||||||
|
name: latitude
|
||||||
|
schema:
|
||||||
|
type: number
|
||||||
|
format: double
|
||||||
|
required: true
|
||||||
|
- in: query
|
||||||
|
name: longitude
|
||||||
|
schema:
|
||||||
|
type: number
|
||||||
|
format: double
|
||||||
|
required: true
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: OK
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/CityInfoResponse'
|
||||||
|
/Cami/GetByCoordinat:
|
||||||
|
get:
|
||||||
|
tags: [Mosques]
|
||||||
|
summary: Get mosques near coordinates
|
||||||
|
parameters:
|
||||||
|
- in: query
|
||||||
|
name: latitude
|
||||||
|
schema:
|
||||||
|
type: number
|
||||||
|
format: double
|
||||||
|
required: true
|
||||||
|
- in: query
|
||||||
|
name: longitude
|
||||||
|
schema:
|
||||||
|
type: number
|
||||||
|
format: double
|
||||||
|
required: true
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: OK
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/MosquesResponse'
|
||||||
|
/AyetDuaHadis/GetAll:
|
||||||
|
get:
|
||||||
|
tags: [Content]
|
||||||
|
summary: Get verses, hadiths, and prayers
|
||||||
|
parameters:
|
||||||
|
- in: query
|
||||||
|
name: dil
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
required: true
|
||||||
|
description: Language code (e.g., tr)
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: OK
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/VersesHadithsPrayersResponse'
|
||||||
@@ -4,25 +4,19 @@ go 1.25
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/PuerkitoBio/goquery v1.8.1
|
github.com/PuerkitoBio/goquery v1.8.1
|
||||||
github.com/doug-martin/goqu/v9 v9.18.0
|
|
||||||
github.com/gofiber/fiber/v2 v2.42.0
|
github.com/gofiber/fiber/v2 v2.42.0
|
||||||
github.com/imroc/req/v3 v3.57.0
|
github.com/imroc/req/v3 v3.57.0
|
||||||
github.com/rs/zerolog v1.29.0
|
github.com/rs/zerolog v1.29.0
|
||||||
github.com/samber/lo v1.37.0
|
|
||||||
github.com/stretchr/testify v1.11.1
|
github.com/stretchr/testify v1.11.1
|
||||||
modernc.org/sqlite v1.21.0
|
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/andybalholm/brotli v1.2.0 // indirect
|
github.com/andybalholm/brotli v1.2.0 // indirect
|
||||||
github.com/andybalholm/cascadia v1.3.1 // indirect
|
github.com/andybalholm/cascadia v1.3.1 // indirect
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
|
||||||
github.com/google/go-querystring v1.2.0 // indirect
|
github.com/google/go-querystring v1.2.0 // indirect
|
||||||
github.com/google/pprof v0.0.0-20230228050547-1710fef4ab10 // indirect
|
|
||||||
github.com/google/uuid v1.3.0 // indirect
|
github.com/google/uuid v1.3.0 // indirect
|
||||||
github.com/icholy/digest v1.1.0 // indirect
|
github.com/icholy/digest v1.1.0 // indirect
|
||||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
|
|
||||||
github.com/klauspost/compress v1.18.4 // indirect
|
github.com/klauspost/compress v1.18.4 // indirect
|
||||||
github.com/kr/text v0.2.0 // indirect
|
github.com/kr/text v0.2.0 // indirect
|
||||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||||
@@ -33,7 +27,6 @@ require (
|
|||||||
github.com/quic-go/qpack v0.6.0 // indirect
|
github.com/quic-go/qpack v0.6.0 // indirect
|
||||||
github.com/quic-go/quic-go v0.57.1 // indirect
|
github.com/quic-go/quic-go v0.57.1 // indirect
|
||||||
github.com/refraction-networking/utls v1.8.2 // indirect
|
github.com/refraction-networking/utls v1.8.2 // indirect
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
|
||||||
github.com/rivo/uniseg v0.4.4 // indirect
|
github.com/rivo/uniseg v0.4.4 // indirect
|
||||||
github.com/savsgio/dictpool v0.0.0-20221023140959-7bf2e61cea94 // indirect
|
github.com/savsgio/dictpool v0.0.0-20221023140959-7bf2e61cea94 // indirect
|
||||||
github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee // indirect
|
github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee // indirect
|
||||||
@@ -42,21 +35,8 @@ require (
|
|||||||
github.com/valyala/fasthttp v1.44.0 // indirect
|
github.com/valyala/fasthttp v1.44.0 // indirect
|
||||||
github.com/valyala/tcplisten v1.0.0 // indirect
|
github.com/valyala/tcplisten v1.0.0 // indirect
|
||||||
golang.org/x/crypto v0.48.0 // indirect
|
golang.org/x/crypto v0.48.0 // indirect
|
||||||
golang.org/x/exp v0.0.0-20230304125523-9ff063c70017 // indirect
|
|
||||||
golang.org/x/mod v0.32.0 // indirect
|
|
||||||
golang.org/x/net v0.50.0 // indirect
|
golang.org/x/net v0.50.0 // indirect
|
||||||
golang.org/x/sync v0.19.0 // indirect
|
|
||||||
golang.org/x/sys v0.41.0 // indirect
|
golang.org/x/sys v0.41.0 // indirect
|
||||||
golang.org/x/text v0.34.0 // indirect
|
golang.org/x/text v0.34.0 // indirect
|
||||||
golang.org/x/tools v0.41.0 // indirect
|
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
lukechampine.com/uint128 v1.2.0 // indirect
|
|
||||||
modernc.org/cc/v3 v3.40.0 // indirect
|
|
||||||
modernc.org/ccgo/v3 v3.16.13 // indirect
|
|
||||||
modernc.org/libc v1.22.3 // indirect
|
|
||||||
modernc.org/mathutil v1.5.0 // indirect
|
|
||||||
modernc.org/memory v1.5.0 // indirect
|
|
||||||
modernc.org/opt v0.1.3 // indirect
|
|
||||||
modernc.org/strutil v1.1.3 // indirect
|
|
||||||
modernc.org/token v1.1.0 // indirect
|
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60=
|
|
||||||
github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
|
|
||||||
github.com/PuerkitoBio/goquery v1.8.1 h1:uQxhNlArOIdbrH1tr0UXwdVFgDcZDrZVdcpygAcwmWM=
|
github.com/PuerkitoBio/goquery v1.8.1 h1:uQxhNlArOIdbrH1tr0UXwdVFgDcZDrZVdcpygAcwmWM=
|
||||||
github.com/PuerkitoBio/goquery v1.8.1/go.mod h1:Q8ICL1kNUJ2sXGoAhPGUdYDJvgQgHzJsnnd3H7Ho5jQ=
|
github.com/PuerkitoBio/goquery v1.8.1/go.mod h1:Q8ICL1kNUJ2sXGoAhPGUdYDJvgQgHzJsnnd3H7Ho5jQ=
|
||||||
github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
||||||
@@ -9,34 +7,22 @@ github.com/andybalholm/cascadia v1.3.1 h1:nhxRkql1kdYCc8Snf7D5/D3spOX+dBgjA6u8x0
|
|||||||
github.com/andybalholm/cascadia v1.3.1/go.mod h1:R4bJ1UQfqADjvDa4P6HZHLh/3OxWWEqc0Sk8XGwHqvA=
|
github.com/andybalholm/cascadia v1.3.1/go.mod h1:R4bJ1UQfqADjvDa4P6HZHLh/3OxWWEqc0Sk8XGwHqvA=
|
||||||
github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/denisenkom/go-mssqldb v0.10.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
|
|
||||||
github.com/doug-martin/goqu/v9 v9.18.0 h1:/6bcuEtAe6nsSMVK/M+fOiXUNfyFF3yYtE07DBPFMYY=
|
|
||||||
github.com/doug-martin/goqu/v9 v9.18.0/go.mod h1:nf0Wc2/hV3gYK9LiyqIrzBEVGlI8qW3GuDCEobC4wBQ=
|
|
||||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
|
||||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
|
||||||
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
|
||||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||||
github.com/gofiber/fiber/v2 v2.42.0 h1:Fnp7ybWvS+sjNQsFvkhf4G8OhXswvB6Vee8hM/LyS+8=
|
github.com/gofiber/fiber/v2 v2.42.0 h1:Fnp7ybWvS+sjNQsFvkhf4G8OhXswvB6Vee8hM/LyS+8=
|
||||||
github.com/gofiber/fiber/v2 v2.42.0/go.mod h1:3+SGNjqMh5VQH5Vz2Wdi43zTIV16ktlFd3x3R6O1Zlc=
|
github.com/gofiber/fiber/v2 v2.42.0/go.mod h1:3+SGNjqMh5VQH5Vz2Wdi43zTIV16ktlFd3x3R6O1Zlc=
|
||||||
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
|
|
||||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||||
github.com/google/go-querystring v1.2.0 h1:yhqkPbu2/OH+V9BfpCVPZkNmUXhb2gBxJArfhIxNtP0=
|
github.com/google/go-querystring v1.2.0 h1:yhqkPbu2/OH+V9BfpCVPZkNmUXhb2gBxJArfhIxNtP0=
|
||||||
github.com/google/go-querystring v1.2.0/go.mod h1:8IFJqpSRITyJ8QhQ13bmbeMBDfmeEJZD5A0egEOmkqU=
|
github.com/google/go-querystring v1.2.0/go.mod h1:8IFJqpSRITyJ8QhQ13bmbeMBDfmeEJZD5A0egEOmkqU=
|
||||||
github.com/google/pprof v0.0.0-20230228050547-1710fef4ab10 h1:CqYfpuYIjnlNxM3msdyPRKabhXZWbKjf3Q8BWROFBso=
|
|
||||||
github.com/google/pprof v0.0.0-20230228050547-1710fef4ab10/go.mod h1:79YE0hCXdHag9sBkw2o+N/YnZtTkXi0UT9Nnixa5eYk=
|
|
||||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/icholy/digest v1.1.0 h1:HfGg9Irj7i+IX1o1QAmPfIBNu/Q5A5Tu3n/MED9k9H4=
|
github.com/icholy/digest v1.1.0 h1:HfGg9Irj7i+IX1o1QAmPfIBNu/Q5A5Tu3n/MED9k9H4=
|
||||||
github.com/icholy/digest v1.1.0/go.mod h1:QNrsSGQ5v7v9cReDI0+eyjsXGUoRSUZQHeQ5C4XLa0Y=
|
github.com/icholy/digest v1.1.0/go.mod h1:QNrsSGQ5v7v9cReDI0+eyjsXGUoRSUZQHeQ5C4XLa0Y=
|
||||||
github.com/imroc/req/v3 v3.57.0 h1:LMTUjNRUybUkTPn8oJDq8Kg3JRBOBTcnDhKu7mzupKI=
|
github.com/imroc/req/v3 v3.57.0 h1:LMTUjNRUybUkTPn8oJDq8Kg3JRBOBTcnDhKu7mzupKI=
|
||||||
github.com/imroc/req/v3 v3.57.0/go.mod h1:JL62ey1nvSLq81HORNcosvlf7SxZStONNqOprg0Pz00=
|
github.com/imroc/req/v3 v3.57.0/go.mod h1:JL62ey1nvSLq81HORNcosvlf7SxZStONNqOprg0Pz00=
|
||||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
|
|
||||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
|
|
||||||
github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU=
|
github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU=
|
||||||
github.com/klauspost/compress v1.18.4 h1:RPhnKRAQ4Fh8zU2FY/6ZFDwTVTxgJ/EMydqSTzE9a2c=
|
github.com/klauspost/compress v1.18.4 h1:RPhnKRAQ4Fh8zU2FY/6ZFDwTVTxgJ/EMydqSTzE9a2c=
|
||||||
github.com/klauspost/compress v1.18.4/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
|
github.com/klauspost/compress v1.18.4/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
|
||||||
@@ -44,8 +30,6 @@ github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
|||||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
github.com/lib/pq v1.10.1 h1:6VXZrLU0jHBYyAqrSPa+MgPfnSvTPuMgK+k0o5kVFWo=
|
|
||||||
github.com/lib/pq v1.10.1/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
|
||||||
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
|
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
|
||||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||||
@@ -55,9 +39,6 @@ github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPn
|
|||||||
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||||
github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU=
|
github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU=
|
||||||
github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||||
github.com/mattn/go-sqlite3 v1.14.7/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
|
||||||
github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
|
|
||||||
github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
|
||||||
github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
|
github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
|
||||||
github.com/philhofer/fwd v1.1.2 h1:bnDivRJ1EWPjUIRXV5KfORO897HTbpFAQddBdE8t7Gw=
|
github.com/philhofer/fwd v1.1.2 h1:bnDivRJ1EWPjUIRXV5KfORO897HTbpFAQddBdE8t7Gw=
|
||||||
github.com/philhofer/fwd v1.1.2/go.mod h1:qkPdfjR2SIEbspLqpe1tO4n5yICnr2DY7mqEx2tUTP0=
|
github.com/philhofer/fwd v1.1.2/go.mod h1:qkPdfjR2SIEbspLqpe1tO4n5yICnr2DY7mqEx2tUTP0=
|
||||||
@@ -70,9 +51,6 @@ github.com/quic-go/quic-go v0.57.1 h1:25KAAR9QR8KZrCZRThWMKVAwGoiHIrNbT72ULHTuI1
|
|||||||
github.com/quic-go/quic-go v0.57.1/go.mod h1:ly4QBAjHA2VhdnxhojRsCUOeJwKYg+taDlos92xb1+s=
|
github.com/quic-go/quic-go v0.57.1/go.mod h1:ly4QBAjHA2VhdnxhojRsCUOeJwKYg+taDlos92xb1+s=
|
||||||
github.com/refraction-networking/utls v1.8.2 h1:j4Q1gJj0xngdeH+Ox/qND11aEfhpgoEvV+S9iJ2IdQo=
|
github.com/refraction-networking/utls v1.8.2 h1:j4Q1gJj0xngdeH+Ox/qND11aEfhpgoEvV+S9iJ2IdQo=
|
||||||
github.com/refraction-networking/utls v1.8.2/go.mod h1:jkSOEkLqn+S/jtpEHPOsVv/4V4EVnelwbMQl4vCWXAM=
|
github.com/refraction-networking/utls v1.8.2/go.mod h1:jkSOEkLqn+S/jtpEHPOsVv/4V4EVnelwbMQl4vCWXAM=
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
|
||||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||||
github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis=
|
github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis=
|
||||||
github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||||
@@ -81,17 +59,11 @@ github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncj
|
|||||||
github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
||||||
github.com/rs/zerolog v1.29.0 h1:Zes4hju04hjbvkVkOhdl2HpZa+0PmVwigmo8XoORE5w=
|
github.com/rs/zerolog v1.29.0 h1:Zes4hju04hjbvkVkOhdl2HpZa+0PmVwigmo8XoORE5w=
|
||||||
github.com/rs/zerolog v1.29.0/go.mod h1:NILgTygv/Uej1ra5XxGf82ZFSLk58MFGAUS2o6usyD0=
|
github.com/rs/zerolog v1.29.0/go.mod h1:NILgTygv/Uej1ra5XxGf82ZFSLk58MFGAUS2o6usyD0=
|
||||||
github.com/samber/lo v1.37.0 h1:XjVcB8g6tgUp8rsPsJ2CvhClfImrpL04YpQHXeHPhRw=
|
|
||||||
github.com/samber/lo v1.37.0/go.mod h1:9vaz2O4o8oOnK23pd2TrXufcbdbJIa3b6cstBWKpopA=
|
|
||||||
github.com/savsgio/dictpool v0.0.0-20221023140959-7bf2e61cea94 h1:rmMl4fXJhKMNWl+K+r/fq4FbbKI+Ia2m9hYBLm2h4G4=
|
github.com/savsgio/dictpool v0.0.0-20221023140959-7bf2e61cea94 h1:rmMl4fXJhKMNWl+K+r/fq4FbbKI+Ia2m9hYBLm2h4G4=
|
||||||
github.com/savsgio/dictpool v0.0.0-20221023140959-7bf2e61cea94/go.mod h1:90zrgN3D/WJsDd1iXHT96alCoN2KJo6/4x1DZC3wZs8=
|
github.com/savsgio/dictpool v0.0.0-20221023140959-7bf2e61cea94/go.mod h1:90zrgN3D/WJsDd1iXHT96alCoN2KJo6/4x1DZC3wZs8=
|
||||||
github.com/savsgio/gotils v0.0.0-20220530130905-52f3993e8d6d/go.mod h1:Gy+0tqhJvgGlqnTF8CVGP0AaGRjwBtXs/a5PA0Y3+A4=
|
github.com/savsgio/gotils v0.0.0-20220530130905-52f3993e8d6d/go.mod h1:Gy+0tqhJvgGlqnTF8CVGP0AaGRjwBtXs/a5PA0Y3+A4=
|
||||||
github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee h1:8Iv5m6xEo1NR1AvpV+7XmhI4r39LGNzwUL4YpMuL5vk=
|
github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee h1:8Iv5m6xEo1NR1AvpV+7XmhI4r39LGNzwUL4YpMuL5vk=
|
||||||
github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee/go.mod h1:qwtSXrKuJh/zsFQ12yEE89xfCrGKK63Rr7ctU/uCo4g=
|
github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee/go.mod h1:qwtSXrKuJh/zsFQ12yEE89xfCrGKK63Rr7ctU/uCo4g=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
|
||||||
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
|
|
||||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
|
||||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||||
github.com/tinylib/msgp v1.1.6/go.mod h1:75BAfg2hauQhs3qedfdDZmWAPcFMAvJE5b9rGOMufyw=
|
github.com/tinylib/msgp v1.1.6/go.mod h1:75BAfg2hauQhs3qedfdDZmWAPcFMAvJE5b9rGOMufyw=
|
||||||
@@ -110,21 +82,15 @@ github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5t
|
|||||||
go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y=
|
go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y=
|
||||||
go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU=
|
go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
|
||||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
|
||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||||
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
|
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
|
||||||
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
|
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
|
||||||
golang.org/x/exp v0.0.0-20230304125523-9ff063c70017 h1:3Ea9SZLCB0aRIhSEjM+iaGIlzzeDJdpi579El/YIhEE=
|
|
||||||
golang.org/x/exp v0.0.0-20230304125523-9ff063c70017/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
|
|
||||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||||
golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||||
golang.org/x/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c=
|
|
||||||
golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU=
|
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||||
@@ -141,8 +107,6 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ
|
|||||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
|
||||||
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
@@ -178,44 +142,13 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn
|
|||||||
golang.org/x/tools v0.0.0-20201022035929-9cf592e881e9/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
golang.org/x/tools v0.0.0-20201022035929-9cf592e881e9/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||||
golang.org/x/tools v0.4.0/go.mod h1:UE5sM2OK9E/d67R0ANs2xJizIymRP5gJU295PvKXxjQ=
|
golang.org/x/tools v0.4.0/go.mod h1:UE5sM2OK9E/d67R0ANs2xJizIymRP5gJU295PvKXxjQ=
|
||||||
golang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc=
|
|
||||||
golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg=
|
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU=
|
gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU=
|
||||||
gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU=
|
gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU=
|
||||||
lukechampine.com/uint128 v1.2.0 h1:mBi/5l91vocEN8otkC5bDLhi2KdCticRiwbdB0O+rjI=
|
|
||||||
lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
|
|
||||||
modernc.org/cc/v3 v3.40.0 h1:P3g79IUS/93SYhtoeaHW+kRCIrYaxJ27MFPv+7kaTOw=
|
|
||||||
modernc.org/cc/v3 v3.40.0/go.mod h1:/bTg4dnWkSXowUO6ssQKnOV0yMVxDYNIsIrzqTFDGH0=
|
|
||||||
modernc.org/ccgo/v3 v3.16.13 h1:Mkgdzl46i5F/CNR/Kj80Ri59hC8TKAhZrYSaqvkwzUw=
|
|
||||||
modernc.org/ccgo/v3 v3.16.13/go.mod h1:2Quk+5YgpImhPjv2Qsob1DnZ/4som1lJTodubIcoUkY=
|
|
||||||
modernc.org/ccorpus v1.11.6 h1:J16RXiiqiCgua6+ZvQot4yUuUy8zxgqbqEEUuGPlISk=
|
|
||||||
modernc.org/ccorpus v1.11.6/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ=
|
|
||||||
modernc.org/httpfs v1.0.6 h1:AAgIpFZRXuYnkjftxTAZwMIiwEqAfk8aVB2/oA6nAeM=
|
|
||||||
modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM=
|
|
||||||
modernc.org/libc v1.22.3 h1:D/g6O5ftAfavceqlLOFwaZuA5KYafKwmr30A6iSqoyY=
|
|
||||||
modernc.org/libc v1.22.3/go.mod h1:MQrloYP209xa2zHome2a8HLiLm6k0UT8CoHpV74tOFw=
|
|
||||||
modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ=
|
|
||||||
modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
|
|
||||||
modernc.org/memory v1.5.0 h1:N+/8c5rE6EqugZwHii4IFsaJ7MUhoWX07J5tC/iI5Ds=
|
|
||||||
modernc.org/memory v1.5.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU=
|
|
||||||
modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4=
|
|
||||||
modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
|
|
||||||
modernc.org/sqlite v1.21.0 h1:4aP4MdUf15i3R3M2mx6Q90WHKz3nZLoz96zlB6tNdow=
|
|
||||||
modernc.org/sqlite v1.21.0/go.mod h1:XwQ0wZPIh1iKb5mkvCJ3szzbhk+tykC8ZWqTRTgYRwI=
|
|
||||||
modernc.org/strutil v1.1.3 h1:fNMm+oJklMGYfU9Ylcywl0CO5O6nTfaowNsh2wpPjzY=
|
|
||||||
modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw=
|
|
||||||
modernc.org/tcl v1.15.1 h1:mOQwiEK4p7HruMZcwKTZPw/aqtGM4aY00uzWhlKKYws=
|
|
||||||
modernc.org/tcl v1.15.1/go.mod h1:aEjeGJX2gz1oWKOLDVZ2tnEWLUrIn8H+GFu+akoDhqs=
|
|
||||||
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
|
|
||||||
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
|
|
||||||
modernc.org/z v1.7.0 h1:xkDw/KepgEjeizO2sNco+hqYkU12taxQFqPEmgm1GWE=
|
|
||||||
modernc.org/z v1.7.0/go.mod h1:hVdgNMh8ggTuRG1rGU8x+xGRFfiQUIAw0ZqlPy8+HyQ=
|
|
||||||
|
|||||||
+71
-19
@@ -1,9 +1,12 @@
|
|||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gofiber/fiber/v2"
|
"github.com/gofiber/fiber/v2"
|
||||||
@@ -22,8 +25,13 @@ type httpError struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Services struct {
|
type Services struct {
|
||||||
TimesProvider prayer.TimesProvider
|
Provider DiyanetProvider
|
||||||
LocationTimesProvider prayer.LocationTimesProvider
|
}
|
||||||
|
|
||||||
|
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 {
|
func New(services Services) *fiber.App {
|
||||||
@@ -55,33 +63,77 @@ func New(services Services) *fiber.App {
|
|||||||
})
|
})
|
||||||
|
|
||||||
app.Get("/api/v1/diyanet/prayertimes", func(ctx *fiber.Ctx) error {
|
app.Get("/api/v1/diyanet/prayertimes", func(ctx *fiber.Ctx) error {
|
||||||
var times []prayer.Times
|
var query struct {
|
||||||
var err error
|
LocationID string `query:"location_id"`
|
||||||
|
Latitude string `query:"latitude"`
|
||||||
locationID := ctx.Query("location_id")
|
Longitude string `query:"longitude"`
|
||||||
var loc struct {
|
}
|
||||||
Latitude float64 `query:"latitude"`
|
if err := ctx.QueryParser(&query); err != nil {
|
||||||
Longitude float64 `query:"longitude"`
|
return fmt.Errorf("failed to bind prayer times query parameters: %w", errors.Join(fiber.ErrBadRequest, err))
|
||||||
}
|
}
|
||||||
|
|
||||||
if locationID != "" {
|
locationID := strings.TrimSpace(query.LocationID)
|
||||||
times, err = services.TimesProvider.Get(ctx.Context(), locationID)
|
latitude := strings.TrimSpace(query.Latitude)
|
||||||
} else if err := ctx.QueryParser(&loc); err == nil {
|
longitude := strings.TrimSpace(query.Longitude)
|
||||||
times, err = services.LocationTimesProvider.GetByCoords(ctx.Context(), prayer.Coordinates{
|
|
||||||
Latitude: loc.Latitude,
|
var (
|
||||||
Longitude: loc.Longitude,
|
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,
|
||||||
})
|
})
|
||||||
} else {
|
default:
|
||||||
return fmt.Errorf("%w: missing location id or coordinates", fiber.ErrBadRequest)
|
return fmt.Errorf("failed to validate prayer times query parameters: %w", fiber.ErrBadRequest)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return fmt.Errorf("failed to fetch prayer times: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.Response().Header.Add(fiber.HeaderCacheControl, "max-age=86400")
|
ctx.Response().Header.Add(fiber.HeaderCacheControl, "max-age=86400")
|
||||||
|
return ctx.JSON(fiber.Map{
|
||||||
|
"prayertimes": times,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
return ctx.JSON(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{
|
app.Use("/", filesystem.New(filesystem.Config{
|
||||||
|
|||||||
@@ -1,38 +0,0 @@
|
|||||||
package database
|
|
||||||
|
|
||||||
import (
|
|
||||||
"database/sql"
|
|
||||||
"fmt"
|
|
||||||
"net/url"
|
|
||||||
|
|
||||||
"github.com/doug-martin/goqu/v9"
|
|
||||||
goqusqlite3 "github.com/doug-martin/goqu/v9/dialect/sqlite3"
|
|
||||||
_ "modernc.org/sqlite"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
goqu.RegisterDialect("sqlite3", func() *goqu.SQLDialectOptions {
|
|
||||||
do := goqusqlite3.DialectOptions()
|
|
||||||
do.SupportsReturn = true
|
|
||||||
return do
|
|
||||||
}())
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewSqliteDB(filename string) (*goqu.Database, error) {
|
|
||||||
pragmas := url.Values{
|
|
||||||
"_pragma": {
|
|
||||||
"journal_mode(WAL)",
|
|
||||||
"foreign_keys(1)",
|
|
||||||
"synchronous(NORMAL)",
|
|
||||||
"busy_timeout(5000)",
|
|
||||||
},
|
|
||||||
}.Encode()
|
|
||||||
dsn := fmt.Sprintf("%s?%s", filename, pragmas)
|
|
||||||
|
|
||||||
conn, err := sql.Open("sqlite", dsn)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to open database: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return goqu.New("sqlite3", conn), nil
|
|
||||||
}
|
|
||||||
@@ -1,11 +1,6 @@
|
|||||||
package net
|
package net
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/PuerkitoBio/goquery"
|
|
||||||
"github.com/imroc/req/v3"
|
"github.com/imroc/req/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -15,30 +10,3 @@ var ReqClient = req.NewClient().
|
|||||||
DisableAutoReadResponse().
|
DisableAutoReadResponse().
|
||||||
EnableInsecureSkipVerify().
|
EnableInsecureSkipVerify().
|
||||||
SetUserAgent(userAgent)
|
SetUserAgent(userAgent)
|
||||||
|
|
||||||
func GetHTML(ctx context.Context, url string) (string, error) {
|
|
||||||
res, err := ReqClient.R().SetContext(ctx).SetRetryCount(3).Get(url)
|
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("failed to get url: %w", err)
|
|
||||||
}
|
|
||||||
html, err := res.ToString()
|
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("failed to read response body: %w", err)
|
|
||||||
}
|
|
||||||
return html, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetParsed(ctx context.Context, url string) (*goquery.Document, error) {
|
|
||||||
html, err := GetHTML(ctx, url)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to get html: %w", err)
|
|
||||||
}
|
|
||||||
doc, err := goquery.NewDocumentFromReader(strings.NewReader(html))
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to parse html: %w", err)
|
|
||||||
}
|
|
||||||
return doc, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type Fetcher struct {
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,28 +1,22 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"database/sql"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
|
|
||||||
"prayertimes/internal/api"
|
"prayertimes/internal/api"
|
||||||
"prayertimes/internal/database"
|
|
||||||
"prayertimes/internal/net"
|
"prayertimes/internal/net"
|
||||||
"prayertimes/pkg/dbtimesprovider"
|
|
||||||
"prayertimes/pkg/diyanet"
|
|
||||||
"prayertimes/pkg/diyanetapi"
|
"prayertimes/pkg/diyanetapi"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
port := getDefaultEnv("PORT", "8000")
|
port := getDefaultEnv("PORT", "8000")
|
||||||
|
|
||||||
services, shutdown, err := newServices()
|
services, err := newServices()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal().Err(err).Msg("failed to init services")
|
log.Fatal().Err(err).Msg("failed to init services")
|
||||||
}
|
}
|
||||||
defer shutdown()
|
|
||||||
app := api.New(services)
|
app := api.New(services)
|
||||||
|
|
||||||
err = app.Listen(":" + port)
|
err = app.Listen(":" + port)
|
||||||
@@ -31,25 +25,12 @@ func main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func newServices() (api.Services, func(), error) {
|
func newServices() (api.Services, error) {
|
||||||
diyanetAPIProvider := diyanetapi.New(net.ReqClient)
|
diyanetAPIProvider := diyanetapi.New(net.ReqClient)
|
||||||
diyanetProvider := diyanet.New(net.GetParsed)
|
|
||||||
|
|
||||||
db, err := database.NewSqliteDB(getDefaultEnv("DATABASE_URL", "app.sqlite3"))
|
|
||||||
if err != nil {
|
|
||||||
return api.Services{}, nil, fmt.Errorf("failed to init db: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := dbtimesprovider.Migrate(db.Db.(*sql.DB)); err != nil {
|
|
||||||
return api.Services{}, nil, fmt.Errorf("failed to migrate database: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return api.Services{
|
return api.Services{
|
||||||
TimesProvider: dbtimesprovider.New(db, diyanetProvider),
|
Provider: diyanetAPIProvider,
|
||||||
LocationTimesProvider: diyanetAPIProvider,
|
}, nil
|
||||||
}, func() {
|
|
||||||
defer db.Db.(*sql.DB).Close()
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func getDefaultEnv(name string, defaultValue string) string {
|
func getDefaultEnv(name string, defaultValue string) string {
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,203 +0,0 @@
|
|||||||
package dbtimesprovider
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"context"
|
|
||||||
"database/sql"
|
|
||||||
_ "embed"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/doug-martin/goqu/v9"
|
|
||||||
"github.com/samber/lo"
|
|
||||||
|
|
||||||
"prayertimes/pkg/prayer"
|
|
||||||
)
|
|
||||||
|
|
||||||
//go:embed schema.sql
|
|
||||||
var schema string
|
|
||||||
|
|
||||||
//go:embed locations.jsonl
|
|
||||||
var locationsJSON string
|
|
||||||
|
|
||||||
type Provider struct {
|
|
||||||
db *goqu.Database
|
|
||||||
provider prayer.TimesProvider
|
|
||||||
clockFunc func() time.Time
|
|
||||||
}
|
|
||||||
|
|
||||||
func New(db *goqu.Database, provider prayer.TimesProvider) Provider {
|
|
||||||
return Provider{
|
|
||||||
provider: provider,
|
|
||||||
clockFunc: time.Now,
|
|
||||||
db: db,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p Provider) Name() string {
|
|
||||||
return "db:" + p.provider.Name()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p Provider) Get(ctx context.Context, location string) ([]prayer.Times, error) {
|
|
||||||
times, err := p.loadTimes(ctx, location)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to load prayer times from db: %w", err)
|
|
||||||
}
|
|
||||||
if len(times) > 0 {
|
|
||||||
return times, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
times, err = p.provider.Get(ctx, location)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to get prayer times: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(times) > 0 {
|
|
||||||
if err := p.saveTimes(ctx, location, times); err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to save times to db: %w", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return times, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func Migrate(con *sql.DB) error {
|
|
||||||
db := goqu.New("sqlite3", con)
|
|
||||||
if _, err := db.Exec(schema); err != nil {
|
|
||||||
return fmt.Errorf("failed to migrate: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
count, _ := db.From("locations").Count()
|
|
||||||
if count > 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type entry struct {
|
|
||||||
ID int `json:"id" db:"id"`
|
|
||||||
Country string `json:"country" db:"country"`
|
|
||||||
Region string `json:"region" db:"region"`
|
|
||||||
City string `json:"city" db:"city"`
|
|
||||||
}
|
|
||||||
s := bufio.NewScanner(strings.NewReader(locationsJSON))
|
|
||||||
|
|
||||||
tx, err := db.Begin()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to begin tx: %w", err)
|
|
||||||
}
|
|
||||||
if err := tx.Wrap(func() error {
|
|
||||||
for s.Scan() {
|
|
||||||
var e entry
|
|
||||||
if err := json.Unmarshal(s.Bytes(), &e); err != nil {
|
|
||||||
return fmt.Errorf("failed to parse as json: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
q := tx.Insert("locations").
|
|
||||||
OnConflict(goqu.DoNothing()).
|
|
||||||
Rows(e)
|
|
||||||
|
|
||||||
if _, err := q.Executor().Exec(); err != nil {
|
|
||||||
return fmt.Errorf("failed to insert location: %w", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type prayerTimesRow struct {
|
|
||||||
ProviderID int64 `db:"provider_id"`
|
|
||||||
LocationID string `db:"location_id"`
|
|
||||||
Date string `db:"date"`
|
|
||||||
Fajr string `db:"fajr"`
|
|
||||||
Sunrise string `db:"sunrise"`
|
|
||||||
Dhuhr string `db:"dhuhr"`
|
|
||||||
Asr string `db:"asr"`
|
|
||||||
Maghrib string `db:"maghrib"`
|
|
||||||
Isha string `db:"isha"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r prayerTimesRow) toDomain() prayer.Times {
|
|
||||||
return prayer.Times{
|
|
||||||
Date: r.Date,
|
|
||||||
Fajr: r.Fajr,
|
|
||||||
Sunrise: r.Sunrise,
|
|
||||||
Dhuhr: r.Dhuhr,
|
|
||||||
Asr: r.Asr,
|
|
||||||
Maghrib: r.Maghrib,
|
|
||||||
Isha: r.Isha,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p Provider) saveTimes(ctx context.Context, locationID string, times []prayer.Times) error {
|
|
||||||
providerID, err := p.saveProvider(ctx, p.provider.Name())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
rows := lo.Map(times, func(item prayer.Times, _ int) prayerTimesRow {
|
|
||||||
return prayerTimesRow{
|
|
||||||
ProviderID: providerID,
|
|
||||||
LocationID: locationID,
|
|
||||||
Date: item.Date,
|
|
||||||
Fajr: item.Fajr,
|
|
||||||
Sunrise: item.Sunrise,
|
|
||||||
Dhuhr: item.Dhuhr,
|
|
||||||
Asr: item.Asr,
|
|
||||||
Maghrib: item.Maghrib,
|
|
||||||
Isha: item.Isha,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
q := p.db.
|
|
||||||
Insert("prayer_times").
|
|
||||||
OnConflict(goqu.DoNothing()).
|
|
||||||
Rows(rows)
|
|
||||||
|
|
||||||
if _, err := q.Executor().ExecContext(ctx); err != nil {
|
|
||||||
return fmt.Errorf("failed to save times: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p Provider) loadTimes(ctx context.Context, locationID string) ([]prayer.Times, error) {
|
|
||||||
now := p.clockFunc()
|
|
||||||
today := now.UTC().Truncate(time.Hour * 24)
|
|
||||||
|
|
||||||
q := p.db.
|
|
||||||
From(goqu.T("prayer_times").As("pt")).
|
|
||||||
Join(goqu.T("providers").As("p"), goqu.On(goqu.I("p.id").Eq(goqu.I("pt.provider_id")))).
|
|
||||||
Where(
|
|
||||||
goqu.I("p.name").Eq(p.provider.Name()),
|
|
||||||
goqu.I("pt.location_id").Eq(locationID),
|
|
||||||
goqu.I("pt.date").Gte(today.Format(time.DateOnly)),
|
|
||||||
).
|
|
||||||
Limit(100)
|
|
||||||
|
|
||||||
var rows []prayerTimesRow
|
|
||||||
if err := q.ScanStructsContext(ctx, &rows); err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to scan times: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return lo.Map(rows, func(row prayerTimesRow, _ int) prayer.Times {
|
|
||||||
return row.toDomain()
|
|
||||||
}), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p Provider) saveProvider(ctx context.Context, name string) (int64, error) {
|
|
||||||
q := p.db.Insert("providers").
|
|
||||||
OnConflict(goqu.DoUpdate("name", goqu.Record{"name": name})).
|
|
||||||
Rows(goqu.Record{"name": name}).
|
|
||||||
Returning("id")
|
|
||||||
var id int64
|
|
||||||
_, err := q.Executor().ScanValContext(ctx, &id)
|
|
||||||
if err != nil {
|
|
||||||
return 0, fmt.Errorf("failed to insert provider: %w", err)
|
|
||||||
}
|
|
||||||
return id, nil
|
|
||||||
}
|
|
||||||
@@ -1,122 +0,0 @@
|
|||||||
package dbtimesprovider
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"database/sql"
|
|
||||||
"fmt"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/doug-martin/goqu/v9"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
_ "modernc.org/sqlite"
|
|
||||||
|
|
||||||
"prayertimes/internal/database"
|
|
||||||
"prayertimes/pkg/prayer"
|
|
||||||
)
|
|
||||||
|
|
||||||
func testDB(t *testing.T) *goqu.Database {
|
|
||||||
t.Helper()
|
|
||||||
|
|
||||||
db, err := database.NewSqliteDB(":memory:")
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
err = Migrate(db.Db.(*sql.DB))
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
_, err = db.Insert("locations").Rows(goqu.Record{"id": 1}).Executor().Exec()
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
t.Cleanup(func() {
|
|
||||||
db.Db.(*sql.DB).Close()
|
|
||||||
})
|
|
||||||
|
|
||||||
return db
|
|
||||||
}
|
|
||||||
|
|
||||||
type mockProvider func() ([]prayer.Times, error)
|
|
||||||
|
|
||||||
func (m mockProvider) Get(ctx context.Context, location string) ([]prayer.Times, error) { return m() }
|
|
||||||
|
|
||||||
func (m mockProvider) Name() string { return "mock" }
|
|
||||||
|
|
||||||
func TestProvider_Get(t *testing.T) {
|
|
||||||
then := time.Date(2023, 3, 5, 0, 0, 0, 0, time.UTC)
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
setupDB func(t *testing.T, db *goqu.Database)
|
|
||||||
provider prayer.TimesProvider
|
|
||||||
clock time.Time
|
|
||||||
assertRes func(t *testing.T, db *goqu.Database, times []prayer.Times, err error)
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "provider succeeds, empty db",
|
|
||||||
provider: mockProvider(func() ([]prayer.Times, error) {
|
|
||||||
return []prayer.Times{
|
|
||||||
{Date: "2023-03-04"},
|
|
||||||
{Date: "2023-03-05"},
|
|
||||||
}, nil
|
|
||||||
}),
|
|
||||||
clock: then,
|
|
||||||
assertRes: func(t *testing.T, db *goqu.Database, times []prayer.Times, err error) {
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Len(t, times, 2)
|
|
||||||
|
|
||||||
cnt, err := db.From("prayer_times").Count()
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Equal(t, int64(2), cnt)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "provider fails, empty db",
|
|
||||||
provider: mockProvider(func() ([]prayer.Times, error) {
|
|
||||||
return nil, fmt.Errorf("no")
|
|
||||||
}),
|
|
||||||
clock: then,
|
|
||||||
assertRes: func(t *testing.T, db *goqu.Database, times []prayer.Times, err error) {
|
|
||||||
assert.Error(t, err)
|
|
||||||
assert.Empty(t, times)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "provider fails, populated db",
|
|
||||||
setupDB: func(t *testing.T, db *goqu.Database) {
|
|
||||||
_, err := db.Insert("providers").Rows(goqu.Record{"id": 1, "name": "mock"}).Executor().Exec()
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
_, err = db.Insert("prayer_times").Rows(
|
|
||||||
prayerTimesRow{ProviderID: 1, LocationID: "1", Date: "2023-03-04", Fajr: "01:00", Sunrise: "02:00", Dhuhr: "03:00", Asr: "04:00", Maghrib: "05:00", Isha: "06:00"},
|
|
||||||
prayerTimesRow{ProviderID: 1, LocationID: "1", Date: "2023-03-05", Fajr: "01:00", Sunrise: "02:00", Dhuhr: "03:00", Asr: "04:00", Maghrib: "05:00", Isha: "06:00"},
|
|
||||||
prayerTimesRow{ProviderID: 1, LocationID: "1", Date: "2023-03-06", Fajr: "01:00", Sunrise: "02:00", Dhuhr: "03:00", Asr: "04:00", Maghrib: "05:00", Isha: "06:00"},
|
|
||||||
).Executor().Exec()
|
|
||||||
require.NoError(t, err)
|
|
||||||
},
|
|
||||||
provider: mockProvider(func() ([]prayer.Times, error) {
|
|
||||||
return nil, fmt.Errorf("no")
|
|
||||||
}),
|
|
||||||
clock: then,
|
|
||||||
assertRes: func(t *testing.T, db *goqu.Database, times []prayer.Times, err error) {
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Len(t, times, 2)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, tt := range tests {
|
|
||||||
tt := tt
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
db := testDB(t)
|
|
||||||
p := Provider{
|
|
||||||
db: db,
|
|
||||||
provider: tt.provider,
|
|
||||||
clockFunc: func() time.Time { return tt.clock },
|
|
||||||
}
|
|
||||||
if tt.setupDB != nil {
|
|
||||||
tt.setupDB(t, db)
|
|
||||||
}
|
|
||||||
|
|
||||||
actual, err := p.Get(context.Background(), "1")
|
|
||||||
tt.assertRes(t, db, actual, err)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
CREATE TABLE IF NOT EXISTS locations
|
|
||||||
(
|
|
||||||
id text PRIMARY KEY,
|
|
||||||
country text,
|
|
||||||
city text,
|
|
||||||
region text
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS providers
|
|
||||||
(
|
|
||||||
id integer PRIMARY KEY AUTOINCREMENT,
|
|
||||||
name TEXT NOT NULL UNIQUE
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS prayer_times
|
|
||||||
(
|
|
||||||
provider_id integer NOT NULL REFERENCES providers (id),
|
|
||||||
location_id text NOT NULL REFERENCES locations (id),
|
|
||||||
date datetime NOT NULL,
|
|
||||||
fajr text NOT NULL,
|
|
||||||
sunrise text NOT NULL,
|
|
||||||
dhuhr text NOT NULL,
|
|
||||||
asr text NOT NULL,
|
|
||||||
maghrib text NOT NULL,
|
|
||||||
isha text NOT NULL
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE UNIQUE INDEX IF NOT EXISTS prayer_times__provider__location ON prayer_times (provider_id, location_id, date);
|
|
||||||
@@ -1,72 +0,0 @@
|
|||||||
package diyanet
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"regexp"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/PuerkitoBio/goquery"
|
|
||||||
|
|
||||||
"prayertimes/pkg/prayer"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Fetcher interface {
|
|
||||||
FetchParsed(ctx context.Context, url string) (*goquery.Document, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type Provider struct {
|
|
||||||
FetcherFunc func(ctx context.Context, url string) (*goquery.Document, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
func New(fetcherFunc func(ctx context.Context, url string) (*goquery.Document, error)) *Provider {
|
|
||||||
return &Provider{FetcherFunc: fetcherFunc}
|
|
||||||
}
|
|
||||||
|
|
||||||
var reNumeric = regexp.MustCompile(`\d+`)
|
|
||||||
|
|
||||||
func validateLocation(location string) error {
|
|
||||||
if !reNumeric.MatchString(location) {
|
|
||||||
return fmt.Errorf("invalid location id")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d Provider) Get(ctx context.Context, location string) ([]prayer.Times, error) {
|
|
||||||
if err := validateLocation(location); err != nil {
|
|
||||||
return nil, fmt.Errorf("%w: %v", prayer.ErrInvalidLocation, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
u := fmt.Sprintf("https://namazvakitleri.diyanet.gov.tr/en-US/%s", location)
|
|
||||||
doc, err := d.FetcherFunc(ctx, u)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to fetch location %q: %w", location, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var times []prayer.Times
|
|
||||||
|
|
||||||
doc.Find("#tab-1 .vakit-table tbody tr").Each(func(_ int, el *goquery.Selection) {
|
|
||||||
date := el.Find("td:first-of-type").Text()
|
|
||||||
parsedDate, err := time.Parse("02.01.2006", date)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
row := prayer.Times{
|
|
||||||
Date: parsedDate.Format(time.DateOnly),
|
|
||||||
Fajr: el.Find("td:nth-of-type(3)").Text(),
|
|
||||||
Sunrise: el.Find("td:nth-of-type(4)").Text(),
|
|
||||||
Dhuhr: el.Find("td:nth-of-type(5)").Text(),
|
|
||||||
Asr: el.Find("td:nth-of-type(6)").Text(),
|
|
||||||
Maghrib: el.Find("td:nth-of-type(7)").Text(),
|
|
||||||
Isha: el.Find("td:nth-of-type(8)").Text(),
|
|
||||||
}
|
|
||||||
times = append(times, row)
|
|
||||||
})
|
|
||||||
|
|
||||||
return times, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d Provider) Name() string {
|
|
||||||
return "diyanetweb"
|
|
||||||
}
|
|
||||||
@@ -1,125 +0,0 @@
|
|||||||
package diyanet
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/PuerkitoBio/goquery"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
|
|
||||||
"prayertimes/internal/net"
|
|
||||||
"prayertimes/pkg/prayer"
|
|
||||||
)
|
|
||||||
|
|
||||||
type mockFetcher string
|
|
||||||
|
|
||||||
func (f mockFetcher) Fetch(_ context.Context, _ string) (*goquery.Document, error) {
|
|
||||||
return goquery.NewDocumentFromReader(strings.NewReader(string(f)))
|
|
||||||
}
|
|
||||||
|
|
||||||
const mockHtml = `
|
|
||||||
<div id='tab-1'>
|
|
||||||
<div class='table-responsive'>
|
|
||||||
<table class='table vakit-table'>
|
|
||||||
<caption>Monthly Prayer Times for Some Location</caption>
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Gregorian Calendar Date</th>
|
|
||||||
<th>Hijri Date</th>
|
|
||||||
<th>Fajr</th>
|
|
||||||
<th>Sun</th>
|
|
||||||
<th>Dhuhr</th>
|
|
||||||
<th>Asr</th>
|
|
||||||
<th>Maghrib</th>
|
|
||||||
<th>Isha</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<td>04.03.2023</td>
|
|
||||||
<td>...</td>
|
|
||||||
<td>05:48</td>
|
|
||||||
<td>07:11</td>
|
|
||||||
<td>13:05</td>
|
|
||||||
<td>16:15</td>
|
|
||||||
<td>18:49</td>
|
|
||||||
<td>20:06</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>05.03.2023</td>
|
|
||||||
<td>...</td>
|
|
||||||
<td>05:46</td>
|
|
||||||
<td>07:09</td>
|
|
||||||
<td>13:04</td>
|
|
||||||
<td>16:15</td>
|
|
||||||
<td>18:50</td>
|
|
||||||
<td>20:07</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`
|
|
||||||
|
|
||||||
func TestDiyanet_Get(t *testing.T) {
|
|
||||||
t.Run("validates location", func(t *testing.T) {
|
|
||||||
d := Provider{}
|
|
||||||
_, err := d.Get(context.Background(), " not numeric ")
|
|
||||||
assert.ErrorIs(t, err, prayer.ErrInvalidLocation)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("extracts prayer times", func(t *testing.T) {
|
|
||||||
d := Provider{
|
|
||||||
FetcherFunc: mockFetcher(mockHtml).Fetch,
|
|
||||||
}
|
|
||||||
actual, err := d.Get(context.Background(), "1234")
|
|
||||||
assert.NoError(t, err)
|
|
||||||
|
|
||||||
expected := []prayer.Times{
|
|
||||||
{
|
|
||||||
Date: "2023-03-04",
|
|
||||||
Fajr: "05:48",
|
|
||||||
Sunrise: "07:11",
|
|
||||||
Dhuhr: "13:05",
|
|
||||||
Asr: "16:15",
|
|
||||||
Maghrib: "18:49",
|
|
||||||
Isha: "20:06",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Date: "2023-03-05",
|
|
||||||
Fajr: "05:46",
|
|
||||||
Sunrise: "07:09",
|
|
||||||
Dhuhr: "13:04",
|
|
||||||
Asr: "16:15",
|
|
||||||
Maghrib: "18:50",
|
|
||||||
Isha: "20:07",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
assert.Equal(t, expected, actual)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("real endpoint", func(t *testing.T) {
|
|
||||||
if testing.Short() {
|
|
||||||
t.Skip()
|
|
||||||
}
|
|
||||||
|
|
||||||
d := Provider{
|
|
||||||
FetcherFunc: net.GetParsed,
|
|
||||||
}
|
|
||||||
times, err := d.Get(context.Background(), "11104")
|
|
||||||
if err != nil {
|
|
||||||
t.Skipf("skipping live endpoint test due to upstream/network error: %v", err)
|
|
||||||
}
|
|
||||||
if len(times) == 0 {
|
|
||||||
t.Skip("skipping live endpoint test because upstream returned no times")
|
|
||||||
}
|
|
||||||
|
|
||||||
assert.Greater(t, len(times), 0)
|
|
||||||
assert.NotZero(t, times[0].Date)
|
|
||||||
|
|
||||||
for _, it := range times {
|
|
||||||
t.Logf("%+v", it)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
+117
-29
@@ -2,7 +2,9 @@ package diyanetapi
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/imroc/req/v3"
|
"github.com/imroc/req/v3"
|
||||||
@@ -21,48 +23,136 @@ func New(c *req.Client) Provider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d Provider) GetByCoords(ctx context.Context, coords prayer.Coordinates) ([]prayer.Times, error) {
|
func (d Provider) GetByCoords(ctx context.Context, coords prayer.Coordinates) ([]prayer.Times, error) {
|
||||||
|
locationID, err := d.getLocationIDByCoords(ctx, coords)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to resolve location by coordinates: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
times, err := d.Get(ctx, locationID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get prayer times by coordinates: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return times, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d Provider) getLocationIDByCoords(ctx context.Context, coords prayer.Coordinates) (string, error) {
|
||||||
res, err := d.http.NewRequest().
|
res, err := d.http.NewRequest().
|
||||||
SetContext(ctx).
|
SetContext(ctx).
|
||||||
SetQueryParams(map[string]string{
|
SetQueryParams(map[string]string{
|
||||||
"latitude": fmt.Sprintf("%f", coords.Latitude),
|
"latitude": fmt.Sprintf("%f", coords.Latitude),
|
||||||
"longitude": fmt.Sprintf("%f", coords.Longitude),
|
"longitude": fmt.Sprintf("%f", coords.Longitude),
|
||||||
}).
|
}).
|
||||||
Get("https://namazvakti.diyanet.gov.tr/api/NamazVakti/Aylik")
|
Get("https://namazvakti.diyanet.gov.tr/api/ilce/GetByCoordinat")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to get prayer times by coords: %w", err)
|
return "", fmt.Errorf("failed to get location by coords: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return d.parseResponse(res)
|
var response struct {
|
||||||
|
Success bool `json:"success"`
|
||||||
|
ResultObject []struct {
|
||||||
|
ID int `json:"cityID"`
|
||||||
|
} `json:"resultObject"`
|
||||||
|
}
|
||||||
|
if err := res.Unmarshal(&response); err != nil {
|
||||||
|
return "", fmt.Errorf("failed to unmarshal city response: %w", err)
|
||||||
|
}
|
||||||
|
if !response.Success {
|
||||||
|
return "", fmt.Errorf("failed to get location by coordinates from upstream: %w", errors.New(res.String()))
|
||||||
|
}
|
||||||
|
if len(response.ResultObject) == 0 {
|
||||||
|
return "", fmt.Errorf("failed to resolve location by coordinates: %w", errors.New("empty location result"))
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf("%d", response.ResultObject[0].ID), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d Provider) SearchLocations(ctx context.Context, query string) ([]prayer.Location, error) {
|
||||||
|
query = strings.TrimSpace(query)
|
||||||
|
if query == "" {
|
||||||
|
return []prayer.Location{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := d.http.NewRequest().
|
||||||
|
SetContext(ctx).
|
||||||
|
SetQueryParam("searchText", query).
|
||||||
|
Get("https://namazvakti.diyanet.gov.tr/api/Search/GetByName")
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to search locations: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var response struct {
|
||||||
|
Success bool `json:"success"`
|
||||||
|
ResultObject struct {
|
||||||
|
Results []struct {
|
||||||
|
ID int `json:"cityID"`
|
||||||
|
CityNameTR string `json:"cityNameTR"`
|
||||||
|
StateNameTR string `json:"stateNameTR"`
|
||||||
|
CountryNameTR string `json:"countryNameTR"`
|
||||||
|
CityNameEN string `json:"cityNameEN"`
|
||||||
|
StateNameEN string `json:"stateNameEN"`
|
||||||
|
CountryNameEN string `json:"countryNameEN"`
|
||||||
|
Latitude float64 `json:"latitude"`
|
||||||
|
Longitude float64 `json:"longitude"`
|
||||||
|
} `json:"results"`
|
||||||
|
} `json:"resultObject"`
|
||||||
|
}
|
||||||
|
if err := res.Unmarshal(&response); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to unmarshal search response: %w", err)
|
||||||
|
}
|
||||||
|
if !response.Success {
|
||||||
|
return nil, fmt.Errorf("failed to search locations in upstream: %w", errors.New(res.String()))
|
||||||
|
}
|
||||||
|
|
||||||
|
locations := make([]prayer.Location, 0, len(response.ResultObject.Results))
|
||||||
|
for _, it := range response.ResultObject.Results {
|
||||||
|
locations = append(locations, prayer.Location{
|
||||||
|
ID: it.ID,
|
||||||
|
NameTR: formatLocationName(it.CountryNameTR, it.StateNameTR, it.CityNameTR),
|
||||||
|
NameEN: formatLocationName(it.CountryNameEN, it.StateNameEN, it.CityNameEN),
|
||||||
|
Latitude: it.Latitude,
|
||||||
|
Longitude: it.Longitude,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return locations, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatLocationName(country, state, city string) string {
|
||||||
|
return fmt.Sprintf("%s / %s / %s", strings.TrimSpace(country), strings.TrimSpace(state), strings.TrimSpace(city))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d Provider) Get(ctx context.Context, locationID string) ([]prayer.Times, error) {
|
func (d Provider) Get(ctx context.Context, locationID string) ([]prayer.Times, error) {
|
||||||
res, err := d.http.NewRequest().
|
res, err := d.http.NewRequest().
|
||||||
SetContext(ctx).
|
SetContext(ctx).
|
||||||
SetQueryParam("ilceId", locationID).
|
SetQueryParam("ilceId", locationID).
|
||||||
Get("https://namazvakti.diyanet.gov.tr/api/NamazVakti/Aylik")
|
Get("https://namazvakti.diyanet.gov.tr/api/NamazVakti/Gunluk")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to get prayer times by location id: %w", err)
|
return nil, fmt.Errorf("failed to get prayer times by location id: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return d.parseResponse(res)
|
times, err := d.parseResponse(res)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to parse prayer times response: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return times, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d Provider) parseResponse(res *req.Response) ([]prayer.Times, error) {
|
func (d Provider) parseResponse(res *req.Response) ([]prayer.Times, error) {
|
||||||
var response struct {
|
var response struct {
|
||||||
Success bool `json:"success"`
|
Success bool `json:"success"`
|
||||||
ResultObject struct {
|
ResultObject struct {
|
||||||
Location struct {
|
|
||||||
ID int `json:"konum_Id"`
|
|
||||||
Timezone string `json:"timezone"`
|
|
||||||
} `json:"konum"`
|
|
||||||
PrayerTimes []struct {
|
PrayerTimes []struct {
|
||||||
Date time.Time `json:"miladi_tarih_uzun_Iso8601"`
|
Date time.Time `json:"miladi_tarih_uzun_Iso8601"`
|
||||||
Fajr time.Time `json:"imsak"`
|
DateIslamic string `json:"hicri_tarih_uzun"`
|
||||||
Sunrise time.Time `json:"gunes"`
|
Fajr time.Time `json:"imsak"`
|
||||||
Dhuhr time.Time `json:"ogle"`
|
Sunrise time.Time `json:"gunes"`
|
||||||
Asr time.Time `json:"ikindi"`
|
Dhuhr time.Time `json:"ogle"`
|
||||||
Maghrib time.Time `json:"aksam"`
|
Asr time.Time `json:"ikindi"`
|
||||||
Isha time.Time `json:"yatsi"`
|
Sunset time.Time `json:"gunes_batis"`
|
||||||
|
Maghrib time.Time `json:"aksam"`
|
||||||
|
Isha time.Time `json:"yatsi"`
|
||||||
} `json:"namazVakti"`
|
} `json:"namazVakti"`
|
||||||
} `json:"resultObject"`
|
} `json:"resultObject"`
|
||||||
}
|
}
|
||||||
@@ -70,7 +160,7 @@ func (d Provider) parseResponse(res *req.Response) ([]prayer.Times, error) {
|
|||||||
return nil, fmt.Errorf("failed to unmarshal as json: %w", err)
|
return nil, fmt.Errorf("failed to unmarshal as json: %w", err)
|
||||||
}
|
}
|
||||||
if !response.Success {
|
if !response.Success {
|
||||||
return nil, fmt.Errorf("received error: %s", res.String())
|
return nil, fmt.Errorf("failed to get prayer times from upstream: %w", errors.New(res.String()))
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(response.ResultObject.PrayerTimes) == 0 {
|
if len(response.ResultObject.PrayerTimes) == 0 {
|
||||||
@@ -80,24 +170,22 @@ func (d Provider) parseResponse(res *req.Response) ([]prayer.Times, error) {
|
|||||||
var times []prayer.Times
|
var times []prayer.Times
|
||||||
today := time.Now().UTC().Truncate(time.Hour * 24)
|
today := time.Now().UTC().Truncate(time.Hour * 24)
|
||||||
for _, pt := range response.ResultObject.PrayerTimes {
|
for _, pt := range response.ResultObject.PrayerTimes {
|
||||||
then := prayer.Date(pt.Date).Time()
|
then := pt.Date.UTC().Truncate(time.Hour * 24)
|
||||||
if then.Before(today) {
|
if then.Before(today) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
times = append(times, prayer.Times{
|
times = append(times, prayer.Times{
|
||||||
Date: pt.Date.Format(time.DateOnly),
|
Date: pt.Date.Format(time.DateOnly),
|
||||||
Fajr: pt.Fajr.Format(time.TimeOnly),
|
DateIslamic: pt.DateIslamic,
|
||||||
Sunrise: pt.Sunrise.Format(time.TimeOnly),
|
Fajr: pt.Fajr.Format("15:04"),
|
||||||
Dhuhr: pt.Dhuhr.Format(time.TimeOnly),
|
Sunrise: pt.Sunrise.Format("15:04"),
|
||||||
Asr: pt.Asr.Format(time.TimeOnly),
|
Dhuhr: pt.Dhuhr.Format("15:04"),
|
||||||
Maghrib: pt.Maghrib.Format(time.TimeOnly),
|
Asr: pt.Asr.Format("15:04"),
|
||||||
Isha: pt.Isha.Format(time.TimeOnly),
|
Sunset: pt.Sunset.Format("15:04"),
|
||||||
|
Maghrib: pt.Maghrib.Format("15:04"),
|
||||||
|
Isha: pt.Isha.Format("15:04"),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return times, nil
|
return times, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d Provider) Name() string {
|
|
||||||
return "diyanetapi"
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -19,6 +19,13 @@ func TestDiyanetAPI_GetByCoords(t *testing.T) {
|
|||||||
Latitude: 52.5100846,
|
Latitude: 52.5100846,
|
||||||
Longitude: 13.4518284,
|
Longitude: 13.4518284,
|
||||||
})
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Skipf("skipping live endpoint test due to upstream/network error: %v", err)
|
||||||
|
}
|
||||||
|
if len(times) == 0 {
|
||||||
|
t.Skip("skipping live endpoint test because upstream returned no times")
|
||||||
|
}
|
||||||
|
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.NotEmpty(t, times)
|
assert.NotEmpty(t, times)
|
||||||
t.Logf("%#+v", times[0])
|
t.Logf("%#+v", times[0])
|
||||||
@@ -28,6 +35,13 @@ func TestDiyanetAPI_GetByCoords(t *testing.T) {
|
|||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
times, err := p.Get(context.Background(), "11104")
|
times, err := p.Get(context.Background(), "11104")
|
||||||
|
if err != nil {
|
||||||
|
t.Skipf("skipping live endpoint test due to upstream/network error: %v", err)
|
||||||
|
}
|
||||||
|
if len(times) == 0 {
|
||||||
|
t.Skip("skipping live endpoint test because upstream returned no times")
|
||||||
|
}
|
||||||
|
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.NotEmpty(t, times)
|
assert.NotEmpty(t, times)
|
||||||
for _, time := range times {
|
for _, time := range times {
|
||||||
|
|||||||
+15
-68
@@ -1,85 +1,32 @@
|
|||||||
package prayer
|
package prayer
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"database/sql/driver"
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
"errors"
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var ErrInvalidLocation = errors.New("invalid location")
|
var ErrInvalidLocation = errors.New("invalid location")
|
||||||
|
|
||||||
type TimesProvider interface {
|
|
||||||
Get(ctx context.Context, location string) ([]Times, error)
|
|
||||||
Name() string
|
|
||||||
}
|
|
||||||
|
|
||||||
type Coordinates struct {
|
type Coordinates struct {
|
||||||
Latitude float64
|
Latitude float64
|
||||||
Longitude float64
|
Longitude float64
|
||||||
}
|
}
|
||||||
|
|
||||||
type LocationTimesProvider interface {
|
type Location struct {
|
||||||
GetByCoords(ctx context.Context, coords Coordinates) ([]Times, error)
|
ID int `json:"id"`
|
||||||
Name() string
|
NameTR string `json:"name_tr"`
|
||||||
}
|
NameEN string `json:"name_en"`
|
||||||
|
Latitude float64 `json:"latitude"`
|
||||||
type Date time.Time
|
Longitude float64 `json:"longitude"`
|
||||||
|
|
||||||
func (d Date) String() string {
|
|
||||||
return time.Time(d).Format(time.DateOnly)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Date) Scan(src any) error {
|
|
||||||
switch v := src.(type) {
|
|
||||||
case []byte:
|
|
||||||
return json.Unmarshal(v, d)
|
|
||||||
case string:
|
|
||||||
return json.Unmarshal([]byte(v), d)
|
|
||||||
case time.Time:
|
|
||||||
*d = Date(v)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Date) Value() (driver.Value, error) {
|
|
||||||
return json.Marshal(d)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Date) UnmarshalJSON(bytes []byte) error {
|
|
||||||
var t time.Time
|
|
||||||
if err := json.Unmarshal(bytes, &t); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_, offset := t.Zone()
|
|
||||||
t = t.Add(time.Duration(offset * int(time.Second)))
|
|
||||||
t = t.UTC()
|
|
||||||
*d = Date(t)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d Date) MarshalJSON() ([]byte, error) {
|
|
||||||
t := time.Time(d)
|
|
||||||
return json.Marshal(t.Format(time.DateOnly))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d Date) Time() time.Time {
|
|
||||||
t := time.Time(d)
|
|
||||||
_, offset := t.Zone()
|
|
||||||
t = t.Add(time.Duration(offset * int(time.Second)))
|
|
||||||
t = t.UTC()
|
|
||||||
return t
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Times struct {
|
type Times struct {
|
||||||
Date string `json:"date"`
|
Date string `json:"date"`
|
||||||
Fajr string `json:"fajr"`
|
DateIslamic string `json:"date_islamic,omitempty"`
|
||||||
Sunrise string `json:"sunrise"`
|
Fajr string `json:"fajr"`
|
||||||
Dhuhr string `json:"dhuhr"`
|
Sunrise string `json:"sunrise"`
|
||||||
Asr string `json:"asr"`
|
Dhuhr string `json:"dhuhr"`
|
||||||
Maghrib string `json:"maghrib"`
|
Asr string `json:"asr"`
|
||||||
Isha string `json:"isha"`
|
Sunset string `json:"sunset,omitempty"`
|
||||||
|
Maghrib string `json:"maghrib"`
|
||||||
|
Isha string `json:"isha"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
const app = () => ({
|
const app = () => ({
|
||||||
futureTimes: [],
|
futureTimes: [],
|
||||||
|
|
||||||
locationId: Alpine.$persist('11104'),
|
locationId: Alpine.$persist('11002'),
|
||||||
lastUpdated: Alpine.$persist(null),
|
lastUpdated: Alpine.$persist(null),
|
||||||
userMinutes: 0,
|
userMinutes: 0,
|
||||||
now: new Date(),
|
now: new Date(),
|
||||||
@@ -47,9 +47,11 @@ const app = () => ({
|
|||||||
const elapsedSeconds = (now - updatedAt) / 1000;
|
const elapsedSeconds = (now - updatedAt) / 1000;
|
||||||
|
|
||||||
if (this.geolocation !== null) {
|
if (this.geolocation !== null) {
|
||||||
this.futureTimes = await fetchJSON(`/api/v1/diyanet/prayertimes?latitude=${this.geolocation.latitude}&longitude=${this.geolocation.longitude}`);
|
const response = await fetchJSON(`/api/v1/diyanet/prayertimes?latitude=${this.geolocation.latitude}&longitude=${this.geolocation.longitude}`);
|
||||||
|
this.futureTimes = response.prayertimes ?? [];
|
||||||
} else {
|
} else {
|
||||||
this.futureTimes = await fetchJSON(`/api/v1/diyanet/prayertimes?location_id=${this.locationId}`);
|
const response = await fetchJSON(`/api/v1/diyanet/prayertimes?location_id=${this.locationId}`);
|
||||||
|
this.futureTimes = response.prayertimes ?? [];
|
||||||
}
|
}
|
||||||
this.lastUpdated = now.toISOString();
|
this.lastUpdated = now.toISOString();
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user