feat: Add API

feat: Add DB-backed time provider
master
Abdussamet Kocak 3 years ago
parent 415a6c8337
commit d1f67fbdb4

1
.gitignore vendored

@ -1 +1,2 @@
/.idea /.idea
*.sqlite3*

@ -12,6 +12,8 @@ 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/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/gofiber/fiber/v2 v2.42.0 // indirect
github.com/golang/mock v1.6.0 // indirect github.com/golang/mock v1.6.0 // indirect
@ -19,6 +21,7 @@ require (
github.com/google/uuid v1.3.0 // indirect github.com/google/uuid v1.3.0 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect
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/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/pretty v0.3.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-colorable v0.1.13 // indirect
@ -31,9 +34,11 @@ require (
github.com/quic-go/qtls-go1-19 v0.2.1 // indirect github.com/quic-go/qtls-go1-19 v0.2.1 // indirect
github.com/quic-go/qtls-go1-20 v0.1.1 // indirect github.com/quic-go/qtls-go1-20 v0.1.1 // indirect
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/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/rogpeppe/go-internal v1.9.0 // indirect
github.com/rs/zerolog v1.29.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
@ -49,4 +54,14 @@ require (
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-20190902080502-41f04d3bba15 // 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/sqlite v1.21.0 // indirect
modernc.org/strutil v1.1.3 // indirect
modernc.org/token v1.1.0 // indirect
) )

@ -1,3 +1,4 @@
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=
@ -10,12 +11,19 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0=
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
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/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
@ -31,6 +39,8 @@ github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+l
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
github.com/imroc/req/v3 v3.32.3 h1:FX3xCN0iWn1wJKHN0EB8l7EjXDTE4j/C8m7zeAb2UH0= github.com/imroc/req/v3 v3.32.3 h1:FX3xCN0iWn1wJKHN0EB8l7EjXDTE4j/C8m7zeAb2UH0=
github.com/imroc/req/v3 v3.32.3/go.mod h1:cZ+7C3L/AYOr4tLGG16hZF90F1WzAdAdzt1xFSlizXY= github.com/imroc/req/v3 v3.32.3/go.mod h1:cZ+7C3L/AYOr4tLGG16hZF90F1WzAdAdzt1xFSlizXY=
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.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=
@ -41,6 +51,7 @@ 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/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=
@ -50,6 +61,7 @@ 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/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=
@ -67,6 +79,9 @@ github.com/quic-go/qtls-go1-20 v0.1.1 h1:KbChDlg82d3IHqaj2bn6GfKRj84Per2VGf5XV3w
github.com/quic-go/qtls-go1-20 v0.1.1/go.mod h1:JKtK6mjbAVcUTN/9jZpvLbGxvdWIKS8uT7EiStoU1SM= github.com/quic-go/qtls-go1-20 v0.1.1/go.mod h1:JKtK6mjbAVcUTN/9jZpvLbGxvdWIKS8uT7EiStoU1SM=
github.com/quic-go/quic-go v0.33.0 h1:ItNoTDN/Fm/zBlq769lLJc8ECe9gYaW40veHCCco7y0= github.com/quic-go/quic-go v0.33.0 h1:ItNoTDN/Fm/zBlq769lLJc8ECe9gYaW40veHCCco7y0=
github.com/quic-go/quic-go v0.33.0/go.mod h1:YMuhaAV9/jIu0XclDXwZPAsP/2Kgr5yMYhe9oxhhOFA= github.com/quic-go/quic-go v0.33.0/go.mod h1:YMuhaAV9/jIu0XclDXwZPAsP/2Kgr5yMYhe9oxhhOFA=
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=
@ -76,6 +91,8 @@ github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/f
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=
@ -85,6 +102,7 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
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/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.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
@ -102,6 +120,8 @@ github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9dec
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
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=
@ -184,3 +204,23 @@ 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=
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=
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/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/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=

@ -2,13 +2,14 @@ package api
import ( import (
"errors" "errors"
"fmt"
"net/http" "net/http"
"time" "time"
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/middleware/cache"
"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/logger"
recover "github.com/gofiber/fiber/v2/middleware/recover" recover "github.com/gofiber/fiber/v2/middleware/recover"
"prayertimes/pkg/prayer" "prayertimes/pkg/prayer"
@ -40,14 +41,16 @@ func New(services Services) *fiber.App {
}) })
app.Use( app.Use(
favicon.New(),
recover.New(recover.Config{ recover.New(recover.Config{
EnableStackTrace: true, EnableStackTrace: true,
}), }),
favicon.New(), logger.New(),
cors.New(), cors.New(),
) )
app.Get("/diyanet/prayertimes", cache.New(), func(ctx *fiber.Ctx) error { cacheHeader := fmt.Sprintf("max-age=%0f", (time.Hour * 24).Seconds())
app.Get("/diyanet/prayertimes", func(ctx *fiber.Ctx) error {
locationID := ctx.Query("location_id") locationID := ctx.Query("location_id")
times, err := services.PrayerTimesProvider.Get(ctx.Context(), locationID) times, err := services.PrayerTimesProvider.Get(ctx.Context(), locationID)
@ -55,6 +58,8 @@ func New(services Services) *fiber.App {
return err return err
} }
ctx.Response().Header.Add(fiber.HeaderCacheControl, cacheHeader)
return ctx.JSON(times) return ctx.JSON(times)
}) })

@ -0,0 +1,38 @@
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,33 +1,61 @@
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/scrapeutils" "prayertimes/internal/scrapeutils"
"prayertimes/pkg/dbtimesprovider"
"prayertimes/pkg/diyanet" "prayertimes/pkg/diyanet"
) )
func main() { func main() {
port, ok := os.LookupEnv("PORT") port := getDefaultEnv("PORT", "8000")
if !ok {
port = "8000" services, shutdown, err := newServices()
if err != nil {
log.Fatal().Err(err).Msg("failed to init services")
} }
services := newServices() defer shutdown()
app := api.New(services) app := api.New(services)
err := app.Listen(":" + port)
err = app.Listen(":" + port)
if err != nil { if err != nil {
log.Fatal().Err(err).Msg("exit with an error") log.Fatal().Err(err).Msg("exit with an error")
} }
} }
func newServices() api.Services { func newServices() (api.Services, func(), error) {
d := diyanet.Diyanet{ diyanetProvider := diyanet.Diyanet{
FetcherFunc: scrapeutils.GetParsed, FetcherFunc: scrapeutils.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)
}
dbProvider := dbtimesprovider.New(db, diyanetProvider)
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{
PrayerTimesProvider: d, PrayerTimesProvider: dbProvider,
}, func() {
defer db.Db.(*sql.DB).Close()
}, nil
}
func getDefaultEnv(name string, defaultValue string) string {
v, ok := os.LookupEnv(name)
if !ok {
return defaultValue
} }
return v
} }

File diff suppressed because it is too large Load Diff

@ -0,0 +1,199 @@
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 && len(times) != 0 {
return times, nil
} else if err != nil {
return nil, fmt.Errorf("failed to load prayer times from db: %w", err)
}
times, err = p.provider.Get(ctx, location)
if err != nil {
return nil, fmt.Errorf("failed to get prayer times: %w", err)
}
if len(times) > 1 {
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 time.Time `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) {
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(p.clockFunc()),
).
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
}

@ -0,0 +1,119 @@
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: time.Date(2023, 3, 4, 0, 0, 0, 0, time.UTC)},
{Date: time.Date(2023, 3, 5, 0, 0, 0, 0, time.UTC)},
}, 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("prayer_times").Rows(
prayerTimesRow{ProviderID: 1, LocationID: "1", Date: time.Date(2023, 3, 4, 0, 0, 0, 0, time.UTC), Fajr: "01:00", Sunrise: "02:00", Dhuhr: "03:00", Asr: "04:00", Maghrib: "05:00", Isha: "06:00"},
prayerTimesRow{ProviderID: 1, LocationID: "1", Date: time.Date(2023, 3, 5, 0, 0, 0, 0, time.UTC), Fajr: "01:00", Sunrise: "02:00", Dhuhr: "03:00", Asr: "04:00", Maghrib: "05:00", Isha: "06:00"},
prayerTimesRow{ProviderID: 1, LocationID: "1", Date: time.Date(2023, 3, 6, 0, 0, 0, 0, time.UTC), 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)
})
}
}

@ -0,0 +1,28 @@
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);

@ -62,3 +62,7 @@ func (d Diyanet) Get(ctx context.Context, location string) ([]prayer.Times, erro
return times, err return times, err
} }
func (d Diyanet) Name() string {
return "diyanet"
}

@ -10,6 +10,7 @@ var ErrInvalidLocation = errors.New("invalid location")
type TimesProvider interface { type TimesProvider interface {
Get(ctx context.Context, location string) ([]Times, error) Get(ctx context.Context, location string) ([]Times, error)
Name() string
} }
type Times struct { type Times struct {

Loading…
Cancel
Save