feat: Add Diyanet API provider
feat: Add UI
This commit is contained in:
@@ -0,0 +1,2 @@
|
|||||||
|
/.idea
|
||||||
|
*.sqlite3*
|
||||||
@@ -4,18 +4,21 @@ go 1.20
|
|||||||
|
|
||||||
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/imroc/req/v3 v3.32.3
|
github.com/imroc/req/v3 v3.32.3
|
||||||
|
github.com/rs/zerolog v1.29.0
|
||||||
|
github.com/samber/lo v1.37.0
|
||||||
github.com/stretchr/testify v1.8.2
|
github.com/stretchr/testify v1.8.2
|
||||||
|
modernc.org/sqlite v1.21.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/andybalholm/brotli v1.0.5 // indirect
|
github.com/andybalholm/brotli v1.0.5 // 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/doug-martin/goqu/v9 v9.18.0 // indirect
|
|
||||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect
|
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect
|
||||||
github.com/gofiber/fiber/v2 v2.42.0 // indirect
|
|
||||||
github.com/golang/mock v1.6.0 // indirect
|
github.com/golang/mock v1.6.0 // indirect
|
||||||
github.com/google/pprof v0.0.0-20230228050547-1710fef4ab10 // 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
|
||||||
@@ -23,10 +26,11 @@ require (
|
|||||||
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
||||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
|
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
|
||||||
github.com/klauspost/compress v1.16.0 // indirect
|
github.com/klauspost/compress v1.16.0 // indirect
|
||||||
github.com/kr/pretty v0.3.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
|
||||||
github.com/mattn/go-isatty v0.0.17 // indirect
|
github.com/mattn/go-isatty v0.0.17 // indirect
|
||||||
github.com/mattn/go-runewidth v0.0.14 // indirect
|
github.com/mattn/go-runewidth v0.0.14 // indirect
|
||||||
|
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
|
||||||
github.com/onsi/ginkgo/v2 v2.9.0 // indirect
|
github.com/onsi/ginkgo/v2 v2.9.0 // indirect
|
||||||
github.com/philhofer/fwd v1.1.2 // indirect
|
github.com/philhofer/fwd v1.1.2 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
@@ -36,9 +40,6 @@ require (
|
|||||||
github.com/quic-go/quic-go v0.33.0 // indirect
|
github.com/quic-go/quic-go v0.33.0 // indirect
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // 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/rogpeppe/go-internal v1.9.0 // indirect
|
|
||||||
github.com/rs/zerolog v1.29.0 // indirect
|
|
||||||
github.com/samber/lo v1.37.0 // 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
|
||||||
github.com/tinylib/msgp v1.1.8 // indirect
|
github.com/tinylib/msgp v1.1.8 // indirect
|
||||||
@@ -52,7 +53,7 @@ require (
|
|||||||
golang.org/x/sys v0.6.0 // indirect
|
golang.org/x/sys v0.6.0 // indirect
|
||||||
golang.org/x/text v0.8.0 // indirect
|
golang.org/x/text v0.8.0 // indirect
|
||||||
golang.org/x/tools v0.6.0 // indirect
|
golang.org/x/tools v0.6.0 // indirect
|
||||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
|
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
lukechampine.com/uint128 v1.2.0 // indirect
|
lukechampine.com/uint128 v1.2.0 // indirect
|
||||||
modernc.org/cc/v3 v3.40.0 // indirect
|
modernc.org/cc/v3 v3.40.0 // indirect
|
||||||
@@ -61,7 +62,6 @@ require (
|
|||||||
modernc.org/mathutil v1.5.0 // indirect
|
modernc.org/mathutil v1.5.0 // indirect
|
||||||
modernc.org/memory v1.5.0 // indirect
|
modernc.org/memory v1.5.0 // indirect
|
||||||
modernc.org/opt v0.1.3 // indirect
|
modernc.org/opt v0.1.3 // indirect
|
||||||
modernc.org/sqlite v1.21.0 // indirect
|
|
||||||
modernc.org/strutil v1.1.3 // indirect
|
modernc.org/strutil v1.1.3 // indirect
|
||||||
modernc.org/token v1.1.0 // indirect
|
modernc.org/token v1.1.0 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
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/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=
|
||||||
@@ -44,13 +45,11 @@ github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:C
|
|||||||
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.16.0 h1:iULayQNOReoYUe+1qtKOqw9CwJv3aNQu8ivo7lw1HU4=
|
github.com/klauspost/compress v1.16.0 h1:iULayQNOReoYUe+1qtKOqw9CwJv3aNQu8ivo7lw1HU4=
|
||||||
github.com/klauspost/compress v1.16.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
|
github.com/klauspost/compress v1.16.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
|
||||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
|
||||||
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
|
||||||
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
|
||||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
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/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=
|
||||||
@@ -62,6 +61,9 @@ github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/
|
|||||||
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.7/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
||||||
|
github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
|
||||||
|
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
|
||||||
|
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||||
github.com/onsi/ginkgo/v2 v2.9.0 h1:Tugw2BKlNHTMfG+CheOITkYvk4LAh6MFOvikhGVnhE8=
|
github.com/onsi/ginkgo/v2 v2.9.0 h1:Tugw2BKlNHTMfG+CheOITkYvk4LAh6MFOvikhGVnhE8=
|
||||||
github.com/onsi/ginkgo/v2 v2.9.0/go.mod h1:4xkjoL/tZv4SMWeww56BU5kAt19mVB47gTWxmrTcxyk=
|
github.com/onsi/ginkgo/v2 v2.9.0/go.mod h1:4xkjoL/tZv4SMWeww56BU5kAt19mVB47gTWxmrTcxyk=
|
||||||
github.com/onsi/gomega v1.27.1 h1:rfztXRbg6nv/5f+Raen9RcGoSecHIFgBBLQK3Wdj754=
|
github.com/onsi/gomega v1.27.1 h1:rfztXRbg6nv/5f+Raen9RcGoSecHIFgBBLQK3Wdj754=
|
||||||
@@ -85,9 +87,6 @@ github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qq
|
|||||||
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=
|
||||||
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
|
||||||
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
|
||||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
|
||||||
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=
|
||||||
@@ -100,6 +99,7 @@ github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee h1:8Iv5m6xEo1NR1Avp
|
|||||||
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.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
|
github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
|
||||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
@@ -152,6 +152,7 @@ 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-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/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 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
|
||||||
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/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=
|
||||||
@@ -196,10 +197,8 @@ golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8T
|
|||||||
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=
|
||||||
google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw=
|
google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw=
|
||||||
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-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b h1:QRR6H1YWRnHb4Y/HeNFCTJLFVxaq6wH4YuVdsUOr75U=
|
||||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
|
||||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
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=
|
||||||
@@ -210,6 +209,8 @@ 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/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 h1:Mkgdzl46i5F/CNR/Kj80Ri59hC8TKAhZrYSaqvkwzUw=
|
||||||
modernc.org/ccgo/v3 v3.16.13/go.mod h1:2Quk+5YgpImhPjv2Qsob1DnZ/4som1lJTodubIcoUkY=
|
modernc.org/ccgo/v3 v3.16.13/go.mod h1:2Quk+5YgpImhPjv2Qsob1DnZ/4som1lJTodubIcoUkY=
|
||||||
|
modernc.org/ccorpus v1.11.6 h1:J16RXiiqiCgua6+ZvQot4yUuUy8zxgqbqEEUuGPlISk=
|
||||||
|
modernc.org/httpfs v1.0.6 h1:AAgIpFZRXuYnkjftxTAZwMIiwEqAfk8aVB2/oA6nAeM=
|
||||||
modernc.org/libc v1.22.3 h1:D/g6O5ftAfavceqlLOFwaZuA5KYafKwmr30A6iSqoyY=
|
modernc.org/libc v1.22.3 h1:D/g6O5ftAfavceqlLOFwaZuA5KYafKwmr30A6iSqoyY=
|
||||||
modernc.org/libc v1.22.3/go.mod h1:MQrloYP209xa2zHome2a8HLiLm6k0UT8CoHpV74tOFw=
|
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 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ=
|
||||||
@@ -222,5 +223,7 @@ modernc.org/sqlite v1.21.0 h1:4aP4MdUf15i3R3M2mx6Q90WHKz3nZLoz96zlB6tNdow=
|
|||||||
modernc.org/sqlite v1.21.0/go.mod h1:XwQ0wZPIh1iKb5mkvCJ3szzbhk+tykC8ZWqTRTgYRwI=
|
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 h1:fNMm+oJklMGYfU9Ylcywl0CO5O6nTfaowNsh2wpPjzY=
|
||||||
modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw=
|
modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw=
|
||||||
|
modernc.org/tcl v1.15.1 h1:mOQwiEK4p7HruMZcwKTZPw/aqtGM4aY00uzWhlKKYws=
|
||||||
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
|
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
|
||||||
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
|
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
|
||||||
|
modernc.org/z v1.7.0 h1:xkDw/KepgEjeizO2sNco+hqYkU12taxQFqPEmgm1GWE=
|
||||||
|
|||||||
+31
-9
@@ -9,10 +9,12 @@ import (
|
|||||||
"github.com/gofiber/fiber/v2"
|
"github.com/gofiber/fiber/v2"
|
||||||
"github.com/gofiber/fiber/v2/middleware/cors"
|
"github.com/gofiber/fiber/v2/middleware/cors"
|
||||||
"github.com/gofiber/fiber/v2/middleware/favicon"
|
"github.com/gofiber/fiber/v2/middleware/favicon"
|
||||||
|
"github.com/gofiber/fiber/v2/middleware/filesystem"
|
||||||
"github.com/gofiber/fiber/v2/middleware/logger"
|
"github.com/gofiber/fiber/v2/middleware/logger"
|
||||||
recover "github.com/gofiber/fiber/v2/middleware/recover"
|
recover "github.com/gofiber/fiber/v2/middleware/recover"
|
||||||
|
|
||||||
"prayertimes/pkg/prayer"
|
"prayertimes/pkg/prayer"
|
||||||
|
"prayertimes/templates"
|
||||||
)
|
)
|
||||||
|
|
||||||
type httpError struct {
|
type httpError struct {
|
||||||
@@ -20,7 +22,8 @@ type httpError struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Services struct {
|
type Services struct {
|
||||||
PrayerTimesProvider prayer.TimesProvider
|
TimesProvider prayer.TimesProvider
|
||||||
|
LocationTimesProvider prayer.LocationTimesProvider
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(services Services) *fiber.App {
|
func New(services Services) *fiber.App {
|
||||||
@@ -42,29 +45,48 @@ func New(services Services) *fiber.App {
|
|||||||
|
|
||||||
app.Use(
|
app.Use(
|
||||||
favicon.New(),
|
favicon.New(),
|
||||||
recover.New(recover.Config{
|
recover.New(recover.Config{EnableStackTrace: true}),
|
||||||
EnableStackTrace: true,
|
|
||||||
}),
|
|
||||||
logger.New(),
|
logger.New(),
|
||||||
cors.New(),
|
cors.New(),
|
||||||
)
|
)
|
||||||
|
|
||||||
cacheHeader := fmt.Sprintf("max-age=%0f", (time.Hour * 24).Seconds())
|
|
||||||
app.Get("/healthz", func(ctx *fiber.Ctx) error {
|
app.Get("/healthz", func(ctx *fiber.Ctx) error {
|
||||||
return ctx.SendString("OK")
|
return ctx.SendString("OK")
|
||||||
})
|
})
|
||||||
app.Get("/api/v1/diyanet/prayertimes", func(ctx *fiber.Ctx) error {
|
|
||||||
locationID := ctx.Query("location_id")
|
|
||||||
|
|
||||||
times, err := services.PrayerTimesProvider.Get(ctx.Context(), locationID)
|
app.Get("/api/v1/diyanet/prayertimes", func(ctx *fiber.Ctx) error {
|
||||||
|
var times []prayer.Times
|
||||||
|
var err error
|
||||||
|
|
||||||
|
locationID := ctx.Query("location_id")
|
||||||
|
var loc struct {
|
||||||
|
Latitude float64 `query:"latitude"`
|
||||||
|
Longitude float64 `query:"longitude"`
|
||||||
|
}
|
||||||
|
|
||||||
|
if locationID != "" {
|
||||||
|
times, err = services.TimesProvider.Get(ctx.Context(), locationID)
|
||||||
|
} else if err := ctx.QueryParser(&loc); err == nil {
|
||||||
|
times, err = services.LocationTimesProvider.GetByCoords(ctx.Context(), prayer.Coordinates{
|
||||||
|
Latitude: loc.Latitude,
|
||||||
|
Longitude: loc.Latitude,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
return fmt.Errorf("%w: missing location id or coordinates", fiber.ErrBadRequest)
|
||||||
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.Response().Header.Add(fiber.HeaderCacheControl, cacheHeader)
|
ctx.Response().Header.Add(fiber.HeaderCacheControl, "max-age=86400")
|
||||||
|
|
||||||
return ctx.JSON(times)
|
return ctx.JSON(times)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
app.Use("/", filesystem.New(filesystem.Config{
|
||||||
|
Root: templates.HttpFS(),
|
||||||
|
}))
|
||||||
|
|
||||||
return app
|
return app
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package scrapeutils
|
package net
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@@ -9,9 +9,9 @@ import (
|
|||||||
|
|
||||||
"prayertimes/internal/api"
|
"prayertimes/internal/api"
|
||||||
"prayertimes/internal/database"
|
"prayertimes/internal/database"
|
||||||
"prayertimes/internal/scrapeutils"
|
"prayertimes/internal/net"
|
||||||
"prayertimes/pkg/dbtimesprovider"
|
"prayertimes/pkg/dbtimesprovider"
|
||||||
"prayertimes/pkg/diyanet"
|
"prayertimes/pkg/diyanetapi"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
@@ -31,22 +31,21 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func newServices() (api.Services, func(), error) {
|
func newServices() (api.Services, func(), error) {
|
||||||
diyanetProvider := diyanet.Diyanet{
|
diyanetAPIProvider := diyanetapi.New(net.ReqClient)
|
||||||
FetcherFunc: scrapeutils.GetParsed,
|
|
||||||
}
|
|
||||||
|
|
||||||
db, err := database.NewSqliteDB(getDefaultEnv("DATABASE_URL", "app.sqlite3"))
|
db, err := database.NewSqliteDB(getDefaultEnv("DATABASE_URL", "app.sqlite3"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return api.Services{}, nil, fmt.Errorf("failed to init db: %w", err)
|
return api.Services{}, nil, fmt.Errorf("failed to init db: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
dbProvider := dbtimesprovider.New(db, diyanetProvider)
|
dbProvider := dbtimesprovider.New(db, diyanetAPIProvider)
|
||||||
if err := dbtimesprovider.Migrate(db.Db.(*sql.DB)); err != nil {
|
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{}, nil, fmt.Errorf("failed to migrate database: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return api.Services{
|
return api.Services{
|
||||||
PrayerTimesProvider: dbProvider,
|
TimesProvider: dbProvider,
|
||||||
|
LocationTimesProvider: diyanetAPIProvider,
|
||||||
}, func() {
|
}, func() {
|
||||||
defer db.Db.(*sql.DB).Close()
|
defer db.Db.(*sql.DB).Close()
|
||||||
}, nil
|
}, nil
|
||||||
|
|||||||
@@ -15,10 +15,14 @@ type Fetcher interface {
|
|||||||
FetchParsed(ctx context.Context, url string) (*goquery.Document, error)
|
FetchParsed(ctx context.Context, url string) (*goquery.Document, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type Diyanet struct {
|
type Provider struct {
|
||||||
FetcherFunc func(ctx context.Context, url string) (*goquery.Document, error)
|
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+`)
|
var reNumeric = regexp.MustCompile(`\d+`)
|
||||||
|
|
||||||
func validateLocation(location string) error {
|
func validateLocation(location string) error {
|
||||||
@@ -28,7 +32,7 @@ func validateLocation(location string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d Diyanet) Get(ctx context.Context, location string) ([]prayer.Times, error) {
|
func (d Provider) Get(ctx context.Context, location string) ([]prayer.Times, error) {
|
||||||
if err := validateLocation(location); err != nil {
|
if err := validateLocation(location); err != nil {
|
||||||
return nil, fmt.Errorf("%w: %v", prayer.ErrInvalidLocation, err)
|
return nil, fmt.Errorf("%w: %v", prayer.ErrInvalidLocation, err)
|
||||||
}
|
}
|
||||||
@@ -63,6 +67,6 @@ func (d Diyanet) Get(ctx context.Context, location string) ([]prayer.Times, erro
|
|||||||
return times, err
|
return times, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d Diyanet) Name() string {
|
func (d Provider) Name() string {
|
||||||
return "diyanet"
|
return "diyanet"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import (
|
|||||||
"github.com/PuerkitoBio/goquery"
|
"github.com/PuerkitoBio/goquery"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
"prayertimes/internal/scrapeutils"
|
"prayertimes/internal/net"
|
||||||
"prayertimes/pkg/prayer"
|
"prayertimes/pkg/prayer"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -62,13 +62,13 @@ const mockHtml = `
|
|||||||
|
|
||||||
func TestDiyanet_Get(t *testing.T) {
|
func TestDiyanet_Get(t *testing.T) {
|
||||||
t.Run("validates location", func(t *testing.T) {
|
t.Run("validates location", func(t *testing.T) {
|
||||||
d := Diyanet{}
|
d := Provider{}
|
||||||
_, err := d.Get(context.Background(), " not numeric ")
|
_, err := d.Get(context.Background(), " not numeric ")
|
||||||
assert.ErrorIs(t, err, prayer.ErrInvalidLocation)
|
assert.ErrorIs(t, err, prayer.ErrInvalidLocation)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("extracts prayer times", func(t *testing.T) {
|
t.Run("extracts prayer times", func(t *testing.T) {
|
||||||
d := Diyanet{
|
d := Provider{
|
||||||
FetcherFunc: mockFetcher(mockHtml).Fetch,
|
FetcherFunc: mockFetcher(mockHtml).Fetch,
|
||||||
}
|
}
|
||||||
actual, err := d.Get(context.Background(), "1234")
|
actual, err := d.Get(context.Background(), "1234")
|
||||||
@@ -102,8 +102,8 @@ func TestDiyanet_Get(t *testing.T) {
|
|||||||
t.Skip()
|
t.Skip()
|
||||||
}
|
}
|
||||||
|
|
||||||
d := Diyanet{
|
d := Provider{
|
||||||
FetcherFunc: scrapeutils.GetParsed,
|
FetcherFunc: net.GetParsed,
|
||||||
}
|
}
|
||||||
times, err := d.Get(context.Background(), "9205")
|
times, err := d.Get(context.Background(), "9205")
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|||||||
@@ -0,0 +1,100 @@
|
|||||||
|
package diyanetapi
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/imroc/req/v3"
|
||||||
|
|
||||||
|
"prayertimes/pkg/prayer"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Provider struct {
|
||||||
|
http *req.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(c *req.Client) Provider {
|
||||||
|
return Provider{
|
||||||
|
http: c.Clone().SetCommonBasicAuth("diyanet", "Q6Y3vYt5F3x2txPaaMF3uPgbK99EJhpM"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d Provider) GetByCoords(ctx context.Context, coords prayer.Coordinates) ([]prayer.Times, error) {
|
||||||
|
res, err := d.http.NewRequest().
|
||||||
|
SetContext(ctx).
|
||||||
|
SetQueryParams(map[string]string{
|
||||||
|
"latitude": fmt.Sprintf("%f", coords.Latitude),
|
||||||
|
"longitude": fmt.Sprintf("%f", coords.Longitude),
|
||||||
|
}).
|
||||||
|
Get("https://namazvakti.diyanet.gov.tr/api/NamazVakti/Aylik")
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get prayer times by coords: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return d.parseResponse(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d Provider) Get(ctx context.Context, locationID string) ([]prayer.Times, error) {
|
||||||
|
res, err := d.http.NewRequest().
|
||||||
|
SetContext(ctx).
|
||||||
|
SetQueryParam("ilceId", locationID).
|
||||||
|
Get("https://namazvakti.diyanet.gov.tr/api/NamazVakti/Aylik")
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get prayer times by location id: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return d.parseResponse(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d Provider) parseResponse(res *req.Response) ([]prayer.Times, error) {
|
||||||
|
var response struct {
|
||||||
|
Success bool `json:"success"`
|
||||||
|
ResultObject struct {
|
||||||
|
Location struct {
|
||||||
|
ID int `json:"konum_Id"`
|
||||||
|
Timezone string `json:"timezone"`
|
||||||
|
} `json:"konum"`
|
||||||
|
PrayerTimes []struct {
|
||||||
|
Date time.Time `json:"miladi_tarih_uzun_Iso8601"`
|
||||||
|
Fajr time.Time `json:"imsak"`
|
||||||
|
Sunrise time.Time `json:"gunes"`
|
||||||
|
Dhuhr time.Time `json:"ogle"`
|
||||||
|
Asr time.Time `json:"ikindi"`
|
||||||
|
Maghrib time.Time `json:"aksam"`
|
||||||
|
Isha time.Time `json:"yatsi"`
|
||||||
|
} `json:"namazVakti"`
|
||||||
|
} `json:"resultObject"`
|
||||||
|
}
|
||||||
|
if err := res.Unmarshal(&response); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to unmarshal as json: %w", err)
|
||||||
|
}
|
||||||
|
if !response.Success {
|
||||||
|
return nil, fmt.Errorf("received error: %s", res.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
var times []prayer.Times
|
||||||
|
const format = "15:04"
|
||||||
|
now := time.Now()
|
||||||
|
for _, pt := range response.ResultObject.PrayerTimes {
|
||||||
|
then := pt.Date.UTC().Truncate(time.Hour * 24)
|
||||||
|
if then.Before(now) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
times = append(times, prayer.Times{
|
||||||
|
Date: then,
|
||||||
|
Fajr: pt.Fajr.Format(format),
|
||||||
|
Sunrise: pt.Sunrise.Format(format),
|
||||||
|
Dhuhr: pt.Dhuhr.Format(format),
|
||||||
|
Asr: pt.Asr.Format(format),
|
||||||
|
Maghrib: pt.Maghrib.Format(format),
|
||||||
|
Isha: pt.Isha.Format(format),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return times, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d Provider) Name() string {
|
||||||
|
return "diyanet"
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
package diyanetapi
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
|
"prayertimes/internal/net"
|
||||||
|
"prayertimes/pkg/prayer"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestDiyanetAPI_GetByCoords(t *testing.T) {
|
||||||
|
p := New(net.ReqClient)
|
||||||
|
t.Run("by coords", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
times, err := p.GetByCoords(context.Background(), prayer.Coordinates{
|
||||||
|
Latitude: 52.5100846,
|
||||||
|
Longitude: 13.4518284,
|
||||||
|
})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotEmpty(t, times)
|
||||||
|
t.Logf("%#+v", times[0])
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("by id", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
times, err := p.Get(context.Background(), "11104")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotEmpty(t, times)
|
||||||
|
t.Logf("%#+v", times[0])
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -13,6 +13,16 @@ type TimesProvider interface {
|
|||||||
Name() string
|
Name() string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Coordinates struct {
|
||||||
|
Latitude float64
|
||||||
|
Longitude float64
|
||||||
|
}
|
||||||
|
|
||||||
|
type LocationTimesProvider interface {
|
||||||
|
GetByCoords(ctx context.Context, coords Coordinates) ([]Times, error)
|
||||||
|
Name() string
|
||||||
|
}
|
||||||
|
|
||||||
type Times struct {
|
type Times struct {
|
||||||
Date time.Time `json:"date"`
|
Date time.Time `json:"date"`
|
||||||
Fajr string `json:"fajr"`
|
Fajr string `json:"fajr"`
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
(()=>{function d(t){let e=()=>{let n,l=localStorage;return t.interceptor((r,i,s,a,m)=>{let o=n||`_x_${a}`,u=f(o,l)?g(o,l):r;return s(u),t.effect(()=>{let c=i();p(o,c,l),s(c)}),u},r=>{r.as=i=>(n=i,r),r.using=i=>(l=i,r)})};Object.defineProperty(t,"$persist",{get:()=>e()}),t.magic("persist",e),t.persist=(n,{get:l,set:r},i=localStorage)=>{let s=f(n,i)?g(n,i):l();r(s),t.effect(()=>{let a=l();p(n,a,i),r(a)})}}function f(t,e){return e.getItem(t)!==null}function g(t,e){return JSON.parse(e.getItem(t,e))}function p(t,e,n){n.setItem(t,JSON.stringify(e))}document.addEventListener("alpine:init",()=>{window.Alpine.plugin(d)});})();
|
||||||
+5
File diff suppressed because one or more lines are too long
@@ -0,0 +1,98 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang='en'>
|
||||||
|
<head>
|
||||||
|
<meta charset='UTF-8'>
|
||||||
|
<meta name='viewport'
|
||||||
|
content='width=device-width, initial-scale=1.0'>
|
||||||
|
<title>Document</title>
|
||||||
|
<script defer
|
||||||
|
src='main.js'></script>
|
||||||
|
<script defer
|
||||||
|
src='alpine.persist@3.x.x.min.js'></script>
|
||||||
|
<script defer
|
||||||
|
src='alpine@3.11.1.min.js'></script>
|
||||||
|
<link rel='preconnect'
|
||||||
|
href='https://fonts.googleapis.com'>
|
||||||
|
<link rel='preconnect'
|
||||||
|
href='https://fonts.gstatic.com'
|
||||||
|
crossorigin>
|
||||||
|
<link href='https://fonts.googleapis.com/css2?family=Inter:wght@400..900&family=Noto+Sans+Arabic:wght@400..700&display=swap'
|
||||||
|
rel='stylesheet'>
|
||||||
|
<link rel='stylesheet'
|
||||||
|
href='style.css'>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<div x-data='app()'
|
||||||
|
@hashchange.window='onHash'>
|
||||||
|
<template x-if='debug'>
|
||||||
|
<div>
|
||||||
|
<label>clock: <input type='range'
|
||||||
|
step='1'
|
||||||
|
min='0'
|
||||||
|
max='1439'
|
||||||
|
x-model='userMinutes'> <span x-text='userTime'></span></label>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<input type='text'
|
||||||
|
x-model='locationId'>
|
||||||
|
|
||||||
|
<template x-if='todayTimes'>
|
||||||
|
<div class='current-salath text--center'>
|
||||||
|
<p>
|
||||||
|
<time :datetime='userNow'
|
||||||
|
x-text='formatDate(userNow)'></time>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<span class='salath-name'
|
||||||
|
lang='tr'
|
||||||
|
x-text='todayTimes.currentSalath.name("tr")'></span>
|
||||||
|
<span class='salath-name'
|
||||||
|
lang='de'
|
||||||
|
x-text='todayTimes.currentSalath.name("de")'></span>
|
||||||
|
<span class='salath-name text--arabic'
|
||||||
|
lang='ar'
|
||||||
|
x-text='todayTimes.currentSalath.name("ar")'></span>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<time :datetime='todayTimes.nextSalath.startsAt'
|
||||||
|
class='text--numeric text--time'
|
||||||
|
x-text='todayTimes.nextSalath.untilHuman'></time>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template x-if='todayTimes'>
|
||||||
|
<div class='table-wrapper'>
|
||||||
|
<table class='salath-table'>
|
||||||
|
<tbody>
|
||||||
|
<template x-for='it in todayTimes.times'>
|
||||||
|
<tr class='salath'
|
||||||
|
:class='{current: it.salath === todayTimes.currentSalath.salath}'>
|
||||||
|
<td class='cell-names'>
|
||||||
|
<span class='salath-name'
|
||||||
|
x-text='it.name("tr")'></span>
|
||||||
|
<span class='salath-name'
|
||||||
|
x-text='it.name("de")'></span>
|
||||||
|
<span class='salath-name text--arabic'
|
||||||
|
x-text='it.name("ar")'></span>
|
||||||
|
</td>
|
||||||
|
<td class='cell-times'>
|
||||||
|
<span class='salath-time text--time text--numeric'
|
||||||
|
x-text='it.timeLocal'></span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</template>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<div class='text--center text--numeric'>
|
||||||
|
<p><span class='clock'
|
||||||
|
x-text='formatTime(now)'></span></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,208 @@
|
|||||||
|
const app = () => ({
|
||||||
|
futureTimes: [],
|
||||||
|
|
||||||
|
locationId: Alpine.$persist('11104'),
|
||||||
|
lastUpdated: Alpine.$persist(null),
|
||||||
|
userMinutes: 0,
|
||||||
|
now: new Date(),
|
||||||
|
debug: location.hash === '#debug',
|
||||||
|
geolocation: null,
|
||||||
|
|
||||||
|
async init() {
|
||||||
|
await this.refreshIfStale();
|
||||||
|
setInterval(() => {
|
||||||
|
this.now = new Date();
|
||||||
|
}, 500);
|
||||||
|
|
||||||
|
getUserLocation()
|
||||||
|
.then(loc => {
|
||||||
|
this.geolocation = {latitude: loc.latitude, longitude: loc.longitude};
|
||||||
|
this.refreshIfStale();
|
||||||
|
})
|
||||||
|
.catch(() => this.geolocation = null)
|
||||||
|
},
|
||||||
|
|
||||||
|
onHash() {
|
||||||
|
this.debug = location.hash === '#debug'
|
||||||
|
},
|
||||||
|
|
||||||
|
get userNow() {
|
||||||
|
if (!this.debug) {
|
||||||
|
return this.now;
|
||||||
|
}
|
||||||
|
|
||||||
|
const d = new Date();
|
||||||
|
d.setHours(0, this.userMinutes, 0, 0);
|
||||||
|
return d;
|
||||||
|
},
|
||||||
|
|
||||||
|
get userTime() {
|
||||||
|
return formatTime(this.userNow);
|
||||||
|
},
|
||||||
|
|
||||||
|
async refreshIfStale() {
|
||||||
|
const updatedAt = new Date(this.lastUpdated);
|
||||||
|
const now = new Date();
|
||||||
|
|
||||||
|
const elapsedSeconds = (now - updatedAt) / 1000;
|
||||||
|
|
||||||
|
if (this.geolocation !== null) {
|
||||||
|
this.futureTimes = await fetchJSON(`/api/v1/diyanet/prayertimes?latitude=${this.geolocation.latitude}&longitude=${this.geolocation.longitude}`);
|
||||||
|
} else {
|
||||||
|
this.futureTimes = await fetchJSON(`/api/v1/diyanet/prayertimes?location_id=${this.locationId}`);
|
||||||
|
}
|
||||||
|
this.lastUpdated = now.toISOString();
|
||||||
|
},
|
||||||
|
|
||||||
|
get todayTimes() {
|
||||||
|
if (this.futureTimes.length === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return new PrayerTimes(this.futureTimes[0], () => this.userNow);
|
||||||
|
},
|
||||||
|
|
||||||
|
translate(key, lang) {
|
||||||
|
return translations[key][lang] ?? key
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
class PrayerTimes {
|
||||||
|
static salaths = ['fajr', 'sunrise', 'dhuhr', 'asr', 'maghrib', 'isha'];
|
||||||
|
static translations = {
|
||||||
|
fajr: {tr: 'İmsak', de: 'Frühgebet', ar: 'صلاة الفجر'},
|
||||||
|
sunrise: {tr: 'Güneş', de: 'Sonnenaufgang', ar: 'الشروق'},
|
||||||
|
dhuhr: {tr: 'Öğle', de: 'Mittagsgebet', ar: 'صلاة الظهر'},
|
||||||
|
asr: {tr: 'İkindi', de: 'Nachmittagsgebet', ar: 'صلاة العصر'},
|
||||||
|
maghrib: {tr: 'Akşam', de: 'Abendgebet', ar: 'صلاة المغرب'},
|
||||||
|
isha: {tr: 'Yatsı', de: 'Nachtgebet', ar: 'صلاة العشاء'},
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor({date, ...rest}, clock = () => new Date()) {
|
||||||
|
this.date = date;
|
||||||
|
this.clock = clock
|
||||||
|
this.salathTimes = rest
|
||||||
|
}
|
||||||
|
|
||||||
|
get times() {
|
||||||
|
const now = this.clock()
|
||||||
|
return PrayerTimes.salaths.map(k => {
|
||||||
|
// "2023-03-05T00:00:00Z"
|
||||||
|
const startsAt = new Date(this.date.replace('T00:00', `T${this.salathTimes[k]}`).replace(/Z$/, ''));
|
||||||
|
|
||||||
|
return {
|
||||||
|
salath: k,
|
||||||
|
name: lang => PrayerTimes.translations[k][lang] ?? '??',
|
||||||
|
startsAt,
|
||||||
|
timeLocal: this.salathTimes[k],
|
||||||
|
get untilSeconds() {
|
||||||
|
let untilSeconds = (startsAt - now) / 1000;
|
||||||
|
return now > startsAt ? 0 : untilSeconds;
|
||||||
|
},
|
||||||
|
get untilHuman() {
|
||||||
|
return formatDuration(this.untilSeconds)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
get currentSalath() {
|
||||||
|
let current = this.times.filter(it => it.untilSeconds === 0).at(-1);
|
||||||
|
if (current === undefined) {
|
||||||
|
// we're in isha -> today's fajr is almost the same as tomorrows
|
||||||
|
const prevDay = new Date(this.date);
|
||||||
|
prevDay.setDate(prevDay.getDate() - 1);
|
||||||
|
|
||||||
|
current = new PrayerTimes({date: prevDay.toISOString(), ...this.salathTimes}, this.clock).times.at(-1);
|
||||||
|
}
|
||||||
|
return current
|
||||||
|
}
|
||||||
|
|
||||||
|
get nextSalath() {
|
||||||
|
let next = this.times
|
||||||
|
.filter(it => it.untilSeconds > 0)[0]
|
||||||
|
if (next === undefined) {
|
||||||
|
// we're in isha -> today's fajr is almost the same as tomorrows
|
||||||
|
const nextDay = new Date(this.date);
|
||||||
|
nextDay.setDate(nextDay.getDate() + 1);
|
||||||
|
|
||||||
|
next = new PrayerTimes({date: nextDay.toISOString(), ...this.salathTimes}, this.clock).times[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...next,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {number} seconds
|
||||||
|
* @return {string}
|
||||||
|
* */
|
||||||
|
function formatDuration(seconds) {
|
||||||
|
const d = new Date(0, 0, 0, 0, 0, seconds);
|
||||||
|
return formatTime(d);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Date} then
|
||||||
|
* @return {string}
|
||||||
|
* */
|
||||||
|
function formatTime(then) {
|
||||||
|
return new Intl.DateTimeFormat(navigator.language, {
|
||||||
|
hour: "numeric",
|
||||||
|
minute: "numeric",
|
||||||
|
second: "numeric"
|
||||||
|
}).format(then);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Date} then
|
||||||
|
* @return {string}
|
||||||
|
* */
|
||||||
|
function formatDate(then) {
|
||||||
|
return new Intl.DateTimeFormat(navigator.language, {
|
||||||
|
year: "numeric",
|
||||||
|
month: "2-digit",
|
||||||
|
day: "2-digit"
|
||||||
|
}).format(then);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Date} then
|
||||||
|
* @return {string}
|
||||||
|
* */
|
||||||
|
function formatDateHijri(then) {
|
||||||
|
return new Intl.DateTimeFormat("en-u-ca-islamic-umalqura-nu-latn", {
|
||||||
|
year: "numeric",
|
||||||
|
month: "long",
|
||||||
|
day: "numeric",
|
||||||
|
}).format(then);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} url
|
||||||
|
* @param {RequestInit} req
|
||||||
|
* */
|
||||||
|
async function fetchJSON(url, req = {}) {
|
||||||
|
const res = await fetch(url, {
|
||||||
|
...req,
|
||||||
|
})
|
||||||
|
return res.json()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return {Promise<GeolocationCoordinates>}
|
||||||
|
* */
|
||||||
|
function getUserLocation() {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
if (!navigator.geolocation) {
|
||||||
|
reject("Geolocation is not supported by this browser.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
navigator.geolocation.getCurrentPosition(
|
||||||
|
(position) => resolve(position.coords),
|
||||||
|
(error) => reject(error.message)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
*, *:before, *:after {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
line-height: 1.6;
|
||||||
|
font-family: 'Inter', sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
p + p {
|
||||||
|
margin-top: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text--numeric {
|
||||||
|
font-variant: tabular-nums;
|
||||||
|
letter-spacing: -0.03em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text--center {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text--arabic {
|
||||||
|
font-family: 'Noto Sans Arabic', Inter, sans-serif;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text--time {
|
||||||
|
font-size: 1.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-wrapper {
|
||||||
|
padding: 0 2rem;
|
||||||
|
max-width: 66%;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.salath-table {
|
||||||
|
font-size: 2rem;
|
||||||
|
font-weight: 600;
|
||||||
|
|
||||||
|
width: 100%;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
td {
|
||||||
|
padding: 0.25rem 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cell-names {
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cell-times {
|
||||||
|
white-space: nowrap;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.salath-name {
|
||||||
|
}
|
||||||
|
|
||||||
|
.current {
|
||||||
|
color: tomato;
|
||||||
|
}
|
||||||
|
|
||||||
|
.salath-name + .salath-name:before {
|
||||||
|
content: '/';
|
||||||
|
opacity: 0.25;
|
||||||
|
margin: 0 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.salath-time {
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.clock {
|
||||||
|
font-size: 4rem;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.current-salath {
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package templates
|
||||||
|
|
||||||
|
import (
|
||||||
|
"embed"
|
||||||
|
"io/fs"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:embed static
|
||||||
|
var staticFS embed.FS
|
||||||
|
|
||||||
|
func FS() fs.FS {
|
||||||
|
if os.Getenv("DEBUG") == "1" {
|
||||||
|
return os.DirFS("./templates/static")
|
||||||
|
}
|
||||||
|
|
||||||
|
f, _ := fs.Sub(staticFS, "static")
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
|
func HttpFS() http.FileSystem {
|
||||||
|
return http.FS(FS())
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user