1
0
mirror of https://github.com/raseels-repos/golang-saas-starter-kit.git synced 2025-06-06 23:46:29 +02:00

340 lines
7.8 KiB
Go
Raw Permalink Normal View History

2019-08-01 11:34:03 -08:00
package geonames
import (
"archive/zip"
"bufio"
"bytes"
"context"
2019-08-17 11:15:45 -08:00
"crypto/md5"
2019-08-01 11:34:03 -08:00
"encoding/csv"
"fmt"
"io"
"net/http"
2019-08-17 11:15:45 -08:00
"os"
"path/filepath"
2019-08-01 11:34:03 -08:00
"strconv"
"strings"
"time"
2019-08-01 11:34:03 -08:00
"geeks-accelerator/oss/saas-starter-kit/internal/platform/web/webcontext"
2019-08-01 11:34:03 -08:00
"github.com/huandu/go-sqlbuilder"
// "github.com/jmoiron/sqlx"
2019-08-01 11:34:03 -08:00
"github.com/pkg/errors"
"github.com/sethgrid/pester"
"github.com/shopspring/decimal"
"gopkg.in/DataDog/dd-trace-go.v1/ddtrace/tracer"
)
const (
// The database table for Geoname
geonamesTableName = "geonames"
)
// List of country codes that will geonames will be downloaded for.
func ValidGeonameCountries(ctx context.Context) []string {
if webcontext.ContextEnv(ctx) == webcontext.Env_Dev {
return []string{"US"}
}
return []string{
"AD", "AR", "AS", "AT", "AU", "AX", "BD", "BE", "BG", "BM",
"BR", "BY", "CA", "CH", "CO", "CR", "CZ", "DE", "DK", "DO",
"DZ", "ES", "FI", "FO", "FR", "GB", "GF", "GG", "GL", "GP",
"GT", "GU", "HR", "HU", "IE", "IM", "IN", "IS", "IT", "JE",
"JP", "LI", "LK", "LT", "LU", "LV", "MC", "MD", "MH", "MK",
"MP", "MQ", "MT", "MX", "MY", "NC", "NL", "NO", "NZ", "PH",
"PK", "PL", "PM", "PR", "PT", "RE", "RO", "RU", "SE", "SI",
"SJ", "SK", "SM", "TH", "TR", "UA", "US", "UY", "VA", "VI",
"WF", "YT", "ZA"}
}
2019-08-01 11:34:03 -08:00
// FindGeonames ....
func (repo *Repository) FindGeonames(ctx context.Context, orderBy, where string, args ...interface{}) ([]*Geoname, error) {
2019-08-01 11:34:03 -08:00
span, ctx := tracer.StartSpanFromContext(ctx, "internal.geonames.FindGeonames")
defer span.Finish()
query := sqlbuilder.NewSelectBuilder()
query.Select("country_code,postal_code,place_name,state_name,state_code,county_name,county_code,community_name,community_code,latitude,longitude,accuracy")
query.From(geonamesTableName)
if orderBy == "" {
orderBy = "postal_code"
}
query.OrderBy(orderBy)
if where != "" {
query.Where(where)
}
queryStr, queryArgs := query.Build()
queryStr = repo.DbConn.Rebind(queryStr)
2019-08-01 11:34:03 -08:00
args = append(args, queryArgs...)
// fetch all places from the db
rows, err := repo.DbConn.QueryContext(ctx, queryStr, args...)
2019-08-01 11:34:03 -08:00
if err != nil {
err = errors.Wrapf(err, "query - %s", query.String())
err = errors.WithMessage(err, "find regions failed")
return nil, err
}
// iterate over each row
resp := []*Geoname{}
for rows.Next() {
var (
v Geoname
err error
)
err = rows.Scan(&v.CountryCode, &v.PostalCode, &v.PlaceName, &v.StateName, &v.StateCode, &v.CountyName, &v.CountyCode, &v.CommunityName, &v.CommunityCode, &v.Latitude, &v.Longitude, &v.Accuracy)
2019-08-01 11:34:03 -08:00
if err != nil {
return nil, errors.WithStack(err)
} else if v.PostalCode == "" {
2019-08-01 11:34:03 -08:00
continue
}
resp = append(resp, &v)
}
return resp, nil
}
// FindGeonamePostalCodes ....
func (repo *Repository) FindGeonamePostalCodes(ctx context.Context, where string, args ...interface{}) ([]string, error) {
2019-08-01 11:34:03 -08:00
span, ctx := tracer.StartSpanFromContext(ctx, "internal.geonames.FindGeonamePostalCodes")
defer span.Finish()
query := sqlbuilder.NewSelectBuilder()
query.Select("postal_code")
query.From(geonamesTableName)
if where != "" {
query.Where(where)
}
queryStr, queryArgs := query.Build()
queryStr = repo.DbConn.Rebind(queryStr)
2019-08-01 11:34:03 -08:00
args = append(args, queryArgs...)
// fetch all places from the db
rows, err := repo.DbConn.QueryContext(ctx, queryStr, args...)
2019-08-01 11:34:03 -08:00
if err != nil {
err = errors.Wrapf(err, "query - %s", query.String())
err = errors.WithMessage(err, "find regions failed")
return nil, err
}
// iterate over each row
resp := []string{}
for rows.Next() {
var (
v string
err error
)
err = rows.Scan(&v)
if err != nil {
return nil, errors.WithStack(err)
} else if v == "" {
continue
}
resp = append(resp, v)
}
return resp, nil
}
// FindGeonameRegions ....
func (repo *Repository) FindGeonameRegions(ctx context.Context, orderBy, where string, args ...interface{}) ([]*Region, error) {
2019-08-01 11:34:03 -08:00
span, ctx := tracer.StartSpanFromContext(ctx, "internal.geonames.FindGeonameRegions")
defer span.Finish()
query := sqlbuilder.NewSelectBuilder()
query.Select("distinct state_code", "state_name")
query.From(geonamesTableName)
if orderBy == "" {
orderBy = "state_name"
}
query.OrderBy(orderBy)
if where != "" {
query.Where(where)
}
queryStr, queryArgs := query.Build()
queryStr = repo.DbConn.Rebind(queryStr)
2019-08-01 11:34:03 -08:00
args = append(args, queryArgs...)
// fetch all places from the db
rows, err := repo.DbConn.QueryContext(ctx, queryStr, args...)
2019-08-01 11:34:03 -08:00
if err != nil {
err = errors.Wrapf(err, "query - %s", query.String())
err = errors.WithMessage(err, "find regions failed")
return nil, err
}
// iterate over each row
resp := []*Region{}
for rows.Next() {
var (
v Region
err error
)
err = rows.Scan(&v.Code, &v.Name)
2019-08-01 11:34:03 -08:00
if err != nil {
err = errors.Wrapf(err, "query - %s", query.String())
return nil, err
} else if v.Code == "" || v.Name == "" {
2019-08-01 11:34:03 -08:00
continue
}
resp = append(resp, &v)
}
return resp, nil
}
2019-08-17 11:15:45 -08:00
// GetGeonameCountry downloads geoname data for the country.
// Parses data and returns slice of Geoname
func (repo *Repository) GetGeonameCountry(ctx context.Context, country string) ([]Geoname, error) {
2019-08-17 11:15:45 -08:00
res := make([]Geoname, 0)
var err error
var resp *http.Response
2019-08-01 11:34:03 -08:00
u := fmt.Sprintf("http://download.geonames.org/export/zip/%s.zip", country)
2019-08-17 11:15:45 -08:00
h := fmt.Sprintf("%x", md5.Sum([]byte(u)))
cp := filepath.Join(os.TempDir(), h+".zip")
2019-08-01 11:34:03 -08:00
2019-08-17 11:15:45 -08:00
if _, err := os.Stat(cp); err != nil {
resp, err = pester.Get(u)
2019-08-01 11:34:03 -08:00
if err != nil {
2019-08-17 11:15:45 -08:00
// Add re-try three times after failing first time
// This reduces the risk when network is lagy, we still have chance to re-try.
for i := 0; i < 3; i++ {
resp, err = pester.Get(u)
if err == nil {
break
}
time.Sleep(time.Second * 1)
2019-08-01 11:34:03 -08:00
}
if err != nil {
2019-08-17 11:15:45 -08:00
err = errors.WithMessagef(err, "Failed to read countries from '%s'", u)
return res, err
2019-08-01 11:34:03 -08:00
}
}
2019-08-17 11:15:45 -08:00
defer resp.Body.Close()
2019-08-01 11:34:03 -08:00
2019-08-17 11:15:45 -08:00
// Create the file
out, err := os.Create(cp)
if err != nil {
return nil, err
2019-08-01 11:34:03 -08:00
}
2019-08-17 11:15:45 -08:00
defer out.Close()
2019-08-17 11:15:45 -08:00
// Write the body to file
_, err = io.Copy(out, resp.Body)
if err != nil {
2019-08-17 11:15:45 -08:00
return nil, err
}
2019-08-17 11:15:45 -08:00
out.Close()
2019-08-01 11:34:03 -08:00
}
2019-08-17 11:15:45 -08:00
f, err := os.Open(cp)
2019-08-01 11:34:03 -08:00
if err != nil {
2019-08-17 11:15:45 -08:00
return nil, err
2019-08-01 11:34:03 -08:00
}
2019-08-17 11:15:45 -08:00
defer f.Close()
br := bufio.NewReader(f)
2019-08-01 11:34:03 -08:00
buff := bytes.NewBuffer([]byte{})
size, err := io.Copy(buff, br)
if err != nil {
err = errors.WithStack(err)
return res, err
2019-08-01 11:34:03 -08:00
}
b := bytes.NewReader(buff.Bytes())
zr, err := zip.NewReader(b, size)
if err != nil {
err = errors.WithStack(err)
return res, err
2019-08-01 11:34:03 -08:00
}
for _, f := range zr.File {
if f.Name == "readme.txt" {
continue
}
fh, err := f.Open()
if err != nil {
err = errors.WithStack(err)
return res, err
2019-08-01 11:34:03 -08:00
}
scanner := bufio.NewScanner(fh)
for scanner.Scan() {
line := scanner.Text()
if strings.Contains(line, "\"") {
line = strings.Replace(line, "\"", "\\\"", -1)
}
r := csv.NewReader(strings.NewReader(line))
r.Comma = '\t' // Use tab-delimited instead of comma <---- here!
r.LazyQuotes = true
r.FieldsPerRecord = -1
lines, err := r.ReadAll()
if err != nil {
err = errors.WithStack(err)
2019-08-01 11:34:03 -08:00
continue
}
for _, row := range lines {
gn := Geoname{
CountryCode: row[0],
PostalCode: row[1],
PlaceName: row[2],
StateName: row[3],
StateCode: row[4],
CountyName: row[5],
CountyCode: row[6],
2019-08-01 11:34:03 -08:00
CommunityName: row[7],
CommunityCode: row[8],
}
if row[9] != "" {
gn.Latitude, err = decimal.NewFromString(row[9])
if err != nil {
err = errors.WithStack(err)
2019-08-01 11:34:03 -08:00
}
}
if row[10] != "" {
gn.Longitude, err = decimal.NewFromString(row[10])
if err != nil {
err = errors.WithStack(err)
2019-08-01 11:34:03 -08:00
}
}
if row[11] != "" {
gn.Accuracy, err = strconv.Atoi(row[11])
if err != nil {
err = errors.WithStack(err)
2019-08-01 11:34:03 -08:00
}
}
res = append(res, gn)
2019-08-01 11:34:03 -08:00
}
}
if err := scanner.Err(); err != nil {
err = errors.WithStack(err)
2019-08-01 11:34:03 -08:00
}
}
return res, err
2019-08-01 11:34:03 -08:00
}