refactoring
This commit is contained in:
BIN
cmd/.db.go.swp
Normal file
BIN
cmd/.db.go.swp
Normal file
Binary file not shown.
69
cmd/db.go
Normal file
69
cmd/db.go
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
_ "modernc.org/sqlite"
|
||||||
|
)
|
||||||
|
|
||||||
|
type DB struct {
|
||||||
|
*sql.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
type Cache DB
|
||||||
|
|
||||||
|
var cacheAddr = "/tmp/turbomaps-er.db"
|
||||||
|
|
||||||
|
func NewCache(ctx context.Context) Cache {
|
||||||
|
ctx, can := context.WithTimeout(ctx, 5*time.Second)
|
||||||
|
defer can()
|
||||||
|
|
||||||
|
db, err := sql.Open("sqlite", cacheAddr)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := db.PingContext(ctx); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return Cache(DB{DB: db})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (db Cache) Get(ctx context.Context, k string) ([]byte, error) {
|
||||||
|
if err := db.init(ctx); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
row := db.QueryRowContext(ctx, `
|
||||||
|
SELECT v FROM cache WHERE k=$1
|
||||||
|
`, k)
|
||||||
|
|
||||||
|
var v []byte
|
||||||
|
if err := row.Scan(&v); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return v, row.Err()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (db Cache) Set(ctx context.Context, k string, v []byte) error {
|
||||||
|
if err := db.init(ctx); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := db.ExecContext(ctx, `
|
||||||
|
INSERT INTO cache (k, v) VALUES ($1, $2)
|
||||||
|
ON CONFLICT DO UPDATE SET v=$2 WHERE k=$1
|
||||||
|
`, k, v)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (db Cache) init(ctx context.Context) error {
|
||||||
|
_, err := db.ExecContext(ctx, `
|
||||||
|
CREATE TABLE IF NOT EXISTS cache(k TEXT PRIMARY KEY, v TEXT)
|
||||||
|
`)
|
||||||
|
return err
|
||||||
|
}
|
||||||
26
cmd/db_test.go
Normal file
26
cmd/db_test.go
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"path"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCache(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
cacheAddr = path.Join(t.TempDir(), "test.db")
|
||||||
|
|
||||||
|
c := NewCache(ctx)
|
||||||
|
k := "k"
|
||||||
|
v := []byte("v")
|
||||||
|
if err := c.Set(ctx, k, v); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if got, err := c.Get(ctx, k); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
} else if !bytes.Equal(v, got) {
|
||||||
|
t.Fatalf("expected %q but got %q", v, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
75
cmd/main.go
75
cmd/main.go
@@ -1,75 +0,0 @@
|
|||||||
package cmd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"golang.org/x/time/rate"
|
|
||||||
"googlemaps.github.io/maps"
|
|
||||||
)
|
|
||||||
|
|
||||||
func Run(ctx context.Context) error {
|
|
||||||
rps := 2
|
|
||||||
limit := 200
|
|
||||||
|
|
||||||
limiter := rate.NewLimiter(rate.Limit(rps), 1)
|
|
||||||
|
|
||||||
c, err := maps.NewClient(maps.WithAPIKey(os.Getenv("GOOGLE_PLACES_API_KEY")))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := c.TextSearch(ctx, &maps.TextSearchRequest{
|
|
||||||
Query: os.Args[1],
|
|
||||||
Location: nil,
|
|
||||||
Radius: uint(0),
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
} else if len(resp.Results) < 1 {
|
|
||||||
return fmt.Errorf("no results for %q", os.Args[1])
|
|
||||||
}
|
|
||||||
origin := resp.Results[0].Geometry.Location
|
|
||||||
|
|
||||||
type Result struct {
|
|
||||||
Name string
|
|
||||||
Lat float64
|
|
||||||
Lng float64
|
|
||||||
Address string
|
|
||||||
}
|
|
||||||
results := []Result{}
|
|
||||||
var nextToken string
|
|
||||||
for len(results) < limit {
|
|
||||||
limiter.Wait(ctx)
|
|
||||||
resp, err := c.TextSearch(ctx, &maps.TextSearchRequest{
|
|
||||||
Query: os.Args[2],
|
|
||||||
Location: &origin,
|
|
||||||
Radius: uint(250),
|
|
||||||
PageToken: nextToken,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for _, result := range resp.Results {
|
|
||||||
results = append(results, Result{
|
|
||||||
Name: result.Name,
|
|
||||||
Lat: result.Geometry.Location.Lat,
|
|
||||||
Lng: result.Geometry.Location.Lng,
|
|
||||||
Address: result.FormattedAddress,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
nextToken = resp.NextPageToken
|
|
||||||
if nextToken == "" {
|
|
||||||
break
|
|
||||||
} else {
|
|
||||||
log.Printf("%d...", len(resp.Results))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
b, _ := json.Marshal(results)
|
|
||||||
fmt.Printf("%s\n", b)
|
|
||||||
|
|
||||||
return ctx.Err()
|
|
||||||
}
|
|
||||||
96
cmd/maps.go
Normal file
96
cmd/maps.go
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"golang.org/x/time/rate"
|
||||||
|
"googlemaps.github.io/maps"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Maps struct {
|
||||||
|
around Location
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewMapsOf(ctx context.Context, town string) (*Maps, error) {
|
||||||
|
m := &Maps{}
|
||||||
|
var err error
|
||||||
|
m.around, err = m.textSearchOne(ctx, town)
|
||||||
|
return m, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var rps = 2
|
||||||
|
var limit = 200
|
||||||
|
var mapsLimiter = rate.NewLimiter(rate.Limit(rps), 1)
|
||||||
|
|
||||||
|
type Location struct {
|
||||||
|
Name string
|
||||||
|
Lat float64
|
||||||
|
Lng float64
|
||||||
|
Address string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Maps) textSearchOne(ctx context.Context, query string) (Location, error) {
|
||||||
|
results, err := m.textSearch(ctx, query)
|
||||||
|
if err != nil {
|
||||||
|
return Location{}, err
|
||||||
|
} else if len(results) < 1 {
|
||||||
|
return Location{}, fmt.Errorf("no results for %q", query)
|
||||||
|
}
|
||||||
|
return results[0], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Maps) textSearch(ctx context.Context, query string) ([]Location, error) {
|
||||||
|
locations := []Location{}
|
||||||
|
nextToken := ""
|
||||||
|
for {
|
||||||
|
results, err := m._textSearch(ctx, query, nextToken)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for _, result := range results.Results {
|
||||||
|
locations = append(locations, Location{
|
||||||
|
Name: result.Name,
|
||||||
|
Lat: result.Geometry.Location.Lat,
|
||||||
|
Lng: result.Geometry.Location.Lng,
|
||||||
|
Address: result.FormattedAddress,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
nextToken = results.NextPageToken
|
||||||
|
if nextToken == "" {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return locations, ctx.Err()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Maps) _textSearch(ctx context.Context, query string, nextToken string) (maps.PlacesSearchResponse, error) {
|
||||||
|
mapsLimiter.Wait(ctx)
|
||||||
|
|
||||||
|
var location *maps.LatLng
|
||||||
|
radius := uint(0)
|
||||||
|
if m.around == (Location{}) {
|
||||||
|
radius = 250
|
||||||
|
} else {
|
||||||
|
location = &maps.LatLng{
|
||||||
|
Lat: m.around.Lat,
|
||||||
|
Lng: m.around.Lng,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return m.client().TextSearch(ctx, &maps.TextSearchRequest{
|
||||||
|
Query: query,
|
||||||
|
Location: location,
|
||||||
|
Radius: radius,
|
||||||
|
PageToken: nextToken,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*Maps) client() *maps.Client {
|
||||||
|
c, err := maps.NewClient(maps.WithAPIKey(os.Getenv("GOOGLE_PLACES_API_KEY")))
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return c
|
||||||
|
}
|
||||||
9
cmd/run.go
Normal file
9
cmd/run.go
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Run(ctx context.Context) error {
|
||||||
|
return ctx.Err()
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user