You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
166 lines
4.0 KiB
Go
166 lines
4.0 KiB
Go
package citydb
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/doug-martin/goqu/v9"
|
|
|
|
_ "modernc.org/sqlite"
|
|
|
|
"prayertimes/pkg/prayer"
|
|
)
|
|
|
|
type Provider struct {
|
|
db *goqu.Database
|
|
own bool
|
|
}
|
|
|
|
func New(db *goqu.Database) Provider {
|
|
return Provider{db: db, own: false}
|
|
}
|
|
|
|
func NewWithConn(conn *sql.DB) Provider {
|
|
return Provider{db: goqu.New("sqlite3", conn), own: false}
|
|
}
|
|
|
|
func Open(path string) (Provider, error) {
|
|
conn, err := sql.Open("sqlite", path)
|
|
if err != nil {
|
|
return Provider{}, fmt.Errorf("failed to open cities database: %w", err)
|
|
}
|
|
|
|
if err := conn.Ping(); err != nil {
|
|
return Provider{}, fmt.Errorf("failed to connect to cities database: %w", err)
|
|
}
|
|
|
|
return Provider{db: goqu.New("sqlite3", conn), own: true}, nil
|
|
}
|
|
|
|
func (p Provider) Close() error {
|
|
if !p.own {
|
|
return nil
|
|
}
|
|
|
|
if err := p.db.Db.(*sql.DB).Close(); err != nil {
|
|
return fmt.Errorf("failed to close cities database: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
type locationRow struct {
|
|
ID int `db:"geoname_id"`
|
|
Name string `db:"name"`
|
|
ASCIIName string `db:"ascii_name"`
|
|
AlternateNames string `db:"alternate_names"`
|
|
CountryCode string `db:"country_code"`
|
|
Timezone string `db:"timezone"`
|
|
Latitude float64 `db:"latitude"`
|
|
Longitude float64 `db:"longitude"`
|
|
DistanceSq float64 `db:"distance_sq"`
|
|
}
|
|
|
|
func (p Provider) SearchLocations(ctx context.Context, query string) ([]prayer.Location, error) {
|
|
query = strings.TrimSpace(query)
|
|
if query == "" {
|
|
return []prayer.Location{}, nil
|
|
}
|
|
|
|
pattern := "%" + query + "%"
|
|
|
|
q := p.db.
|
|
From("cities").
|
|
Select(
|
|
goqu.I("geoname_id"),
|
|
goqu.I("name"),
|
|
goqu.I("ascii_name"),
|
|
goqu.COALESCE(goqu.I("alternate_names"), "").As("alternate_names"),
|
|
goqu.I("country_code"),
|
|
goqu.I("timezone"),
|
|
goqu.I("latitude"),
|
|
goqu.I("longitude"),
|
|
goqu.V(0).As("distance_sq"),
|
|
).
|
|
Where(
|
|
goqu.Or(
|
|
goqu.L("name LIKE ? COLLATE NOCASE", pattern),
|
|
goqu.L("ascii_name LIKE ? COLLATE NOCASE", pattern),
|
|
goqu.L("alternate_names LIKE ? COLLATE NOCASE", pattern),
|
|
),
|
|
).
|
|
Order(goqu.I("population").Desc(), goqu.I("name").Asc()).
|
|
Limit(50)
|
|
|
|
var rows []locationRow
|
|
if err := q.ScanStructsContext(ctx, &rows); err != nil {
|
|
return nil, fmt.Errorf("failed to query locations from cities database: %w", err)
|
|
}
|
|
|
|
locations := make([]prayer.Location, 0, len(rows))
|
|
for _, row := range rows {
|
|
locations = append(locations, prayer.Location{
|
|
ID: row.ID,
|
|
Name: row.Name,
|
|
ASCIIName: row.ASCIIName,
|
|
AlternateNames: row.AlternateNames,
|
|
CountryCode: row.CountryCode,
|
|
Timezone: row.Timezone,
|
|
Latitude: row.Latitude,
|
|
Longitude: row.Longitude,
|
|
})
|
|
}
|
|
|
|
return locations, nil
|
|
}
|
|
|
|
func (p Provider) SearchLocationsByCoords(ctx context.Context, coords prayer.Coordinates) ([]prayer.Location, error) {
|
|
q := p.db.
|
|
From("cities").
|
|
Select(
|
|
goqu.I("geoname_id"),
|
|
goqu.I("name"),
|
|
goqu.I("ascii_name"),
|
|
goqu.COALESCE(goqu.I("alternate_names"), "").As("alternate_names"),
|
|
goqu.I("country_code"),
|
|
goqu.I("timezone"),
|
|
goqu.I("latitude"),
|
|
goqu.I("longitude"),
|
|
goqu.L(
|
|
"((latitude - ?) * (latitude - ?) + (longitude - ?) * (longitude - ?))",
|
|
coords.Latitude,
|
|
coords.Latitude,
|
|
coords.Longitude,
|
|
coords.Longitude,
|
|
).As("distance_sq"),
|
|
).
|
|
Order(
|
|
goqu.I("distance_sq").Asc(),
|
|
goqu.I("population").Desc(),
|
|
).
|
|
Limit(50)
|
|
|
|
var rows []locationRow
|
|
if err := q.ScanStructsContext(ctx, &rows); err != nil {
|
|
return nil, fmt.Errorf("failed to query locations by coordinates from cities database: %w", err)
|
|
}
|
|
|
|
locations := make([]prayer.Location, 0, len(rows))
|
|
for _, row := range rows {
|
|
locations = append(locations, prayer.Location{
|
|
ID: row.ID,
|
|
Name: row.Name,
|
|
ASCIIName: row.ASCIIName,
|
|
AlternateNames: row.AlternateNames,
|
|
CountryCode: row.CountryCode,
|
|
Timezone: row.Timezone,
|
|
Latitude: row.Latitude,
|
|
Longitude: row.Longitude,
|
|
})
|
|
}
|
|
|
|
return locations, nil
|
|
}
|