feat: Initial prototype
This commit is contained in:
@@ -0,0 +1,64 @@
|
||||
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 Diyanet struct {
|
||||
FetcherFunc func(ctx context.Context, url string) (*goquery.Document, error)
|
||||
}
|
||||
|
||||
var reNumeric = regexp.MustCompile(`\d+`)
|
||||
|
||||
func validateLocation(location string) error {
|
||||
if !reNumeric.MatchString(location) {
|
||||
return fmt.Errorf("invalid location id")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d Diyanet) 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,
|
||||
Fajr: el.Find("td:nth-of-type(2)").Text(),
|
||||
Sunrise: el.Find("td:nth-of-type(3)").Text(),
|
||||
Dhuhr: el.Find("td:nth-of-type(4)").Text(),
|
||||
Asr: el.Find("td:nth-of-type(5)").Text(),
|
||||
Maghrib: el.Find("td:nth-of-type(6)").Text(),
|
||||
Isha: el.Find("td:nth-of-type(7)").Text(),
|
||||
}
|
||||
times = append(times, row)
|
||||
})
|
||||
|
||||
return times, err
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
package diyanet
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/PuerkitoBio/goquery"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"prayertimes/internal/scrapeutils"
|
||||
"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>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>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>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 := Diyanet{}
|
||||
_, err := d.Get(context.Background(), " not numeric ")
|
||||
assert.ErrorIs(t, err, prayer.ErrInvalidLocation)
|
||||
})
|
||||
|
||||
t.Run("extracts prayer times", func(t *testing.T) {
|
||||
d := Diyanet{
|
||||
FetcherFunc: mockFetcher(mockHtml).Fetch,
|
||||
}
|
||||
actual, err := d.Get(context.Background(), "1234")
|
||||
assert.NoError(t, err)
|
||||
|
||||
expected := []prayer.Times{
|
||||
{
|
||||
Date: time.Date(2023, time.March, 4, 0, 0, 0, 0, time.UTC),
|
||||
Fajr: "05:48",
|
||||
Sunrise: "07:11",
|
||||
Dhuhr: "13:05",
|
||||
Asr: "16:15",
|
||||
Maghrib: "18:49",
|
||||
Isha: "20:06",
|
||||
},
|
||||
{
|
||||
Date: time.Date(2023, time.March, 5, 0, 0, 0, 0, time.UTC),
|
||||
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 := Diyanet{
|
||||
FetcherFunc: scrapeutils.GetParsed,
|
||||
}
|
||||
times, err := d.Get(context.Background(), "9205")
|
||||
assert.NoError(t, err)
|
||||
assert.Greater(t, len(times), 1)
|
||||
assert.NotZero(t, times[0].Date)
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package prayer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"time"
|
||||
)
|
||||
|
||||
var ErrInvalidLocation = errors.New("invalid location")
|
||||
|
||||
type TimesProvider interface {
|
||||
Get(ctx context.Context, location string) ([]Times, error)
|
||||
}
|
||||
|
||||
type Times struct {
|
||||
Date time.Time `json:"date"`
|
||||
Fajr string `json:"fajr"`
|
||||
Sunrise string `json:"sunrise"`
|
||||
Dhuhr string `json:"dhuhr"`
|
||||
Asr string `json:"asr"`
|
||||
Maghrib string `json:"maghrib"`
|
||||
Isha string `json:"isha"`
|
||||
}
|
||||
Reference in New Issue
Block a user