2
0
Просмотр исходного кода

Location

- implement FindLocationByID
Alexey Kim 2 лет назад
Родитель
Сommit
c078ea5e4f
8 измененных файлов с 241 добавлено и 1 удалено
  1. 8 0
      cache/key.go
  2. 49 0
      cache/sql.go
  3. 33 0
      db/clickhouse.go
  4. 4 0
      db/database.go
  5. 24 0
      db/location.go
  6. 19 1
      graphql/query_location.go
  7. 4 0
      graphql/resolver_impl.go
  8. 100 0
      relation/location.go

+ 8 - 0
cache/key.go

@@ -0,0 +1,8 @@
+package cache
+
+import "time"
+
+type Key interface {
+	TTL() time.Duration
+	String() string
+}

+ 49 - 0
cache/sql.go

@@ -0,0 +1,49 @@
+package cache
+
+import (
+	"fmt"
+	"hash/fnv"
+	"strconv"
+	"time"
+)
+
+type SqlKey struct {
+	format string
+	ttl    time.Duration
+	table  string
+	clause string
+	args   []any
+}
+
+func NewSQLKey(tName string, t time.Duration, clause string, args ...any) *SqlKey {
+	h := fnv.New32a()
+	_, _ = h.Write([]byte(tName))
+
+	return &SqlKey{
+		format: fmt.Sprintf("sql_%s_%s", strconv.Itoa(int(h.Sum32())), "%v"),
+		ttl:    t,
+		table:  tName,
+		clause: clause,
+		args:   args,
+	}
+}
+
+func (k *SqlKey) Table() string {
+	return k.table
+}
+
+func (k *SqlKey) Clause() string {
+	return k.clause
+}
+
+func (k *SqlKey) Args() []any {
+	return k.args
+}
+
+func (k *SqlKey) TTL() time.Duration {
+	return k.ttl
+}
+
+func (k *SqlKey) String() string {
+	return fmt.Sprintf(k.format, k.args...)
+}

+ 33 - 0
db/clickhouse.go

@@ -5,9 +5,12 @@ import (
 	"fmt"
 	"github.com/gshopify/service-wrapper/config"
 	"github.com/gshopify/service-wrapper/db"
+	"github.com/gshopify/service-wrapper/model"
 	"github.com/jellydator/ttlcache/v3"
 	"github.com/mailru/dbr"
 	_ "github.com/mailru/go-clickhouse"
+	"gshopper.com/gshopify/shop/graphql/generated"
+	"gshopper.com/gshopify/shop/relation"
 	"net/url"
 	"sync"
 )
@@ -70,6 +73,36 @@ func New(ctx context.Context, forceDebug bool) (Database, error) {
 	return r, nil
 }
 
+func (db *clickhouse) Location(ln model.LanguageCode, id string) (*generated.Location, error) {
+	var (
+		key = locationKey("id=?", id)
+		l   = ttlcache.LoaderFunc[string, any](
+			func(ttl *ttlcache.Cache[string, any], _ string) *ttlcache.Item[string, any] {
+				var loc relation.Location
+				rows, err := db.session.
+					Select(locationSelection(ln)...).
+					From(key.Table()).
+					Where(key.Clause(), key.Args()...).
+					Limit(1).
+					Load(&loc)
+				if rows < 1 || err != nil {
+					return nil
+				}
+
+				return ttl.Set(key.String(), loc, key.TTL())
+			},
+		)
+	)
+
+	p := db.cache.Get(key.String(), ttlcache.WithLoader[string, any](l))
+	if p == nil {
+		return nil, fmt.Errorf("not found")
+	}
+
+	loc := p.Value().(relation.Location)
+	return loc.As(), nil
+}
+
 func (db *clickhouse) Ping() error {
 	return db.session.Ping()
 }

+ 4 - 0
db/database.go

@@ -1,6 +1,8 @@
 package db
 
 import (
+	"github.com/gshopify/service-wrapper/model"
+	"gshopper.com/gshopify/shop/graphql/generated"
 	"time"
 )
 
@@ -12,4 +14,6 @@ const (
 type Database interface {
 	Ping() error
 	Close() error
+
+	Location(ln model.LanguageCode, id string) (*generated.Location, error)
 }

+ 24 - 0
db/location.go

@@ -0,0 +1,24 @@
+package db
+
+import (
+	"github.com/gshopify/service-wrapper/model"
+	"gshopper.com/gshopify/shop/cache"
+	"time"
+)
+
+var (
+	locationSelection = func(ln model.LanguageCode) []string {
+		return append([]string{
+			"id", "created_at", "updated_at", "deleted_at",
+			"address_1", "address_2", "city", "country", "country_code",
+			"province", "province_code", "zip", "phone",
+			"latitude", "longitude",
+		}, ln.SqlFieldSelection("name"))
+	}
+	locationKey = func(clause string, args ...any) *cache.SqlKey {
+		return cache.NewSQLKey(
+			"location",
+			time.Minute,
+			clause, args...)
+	}
+)

+ 19 - 1
graphql/query_location.go

@@ -2,11 +2,29 @@ package graphql
 
 import (
 	"context"
+	"github.com/gshopify/service-wrapper/model"
+	"github.com/gshopify/service-wrapper/server/middleware"
 	"gshopper.com/gshopify/shop/graphql/generated"
 )
 
 func (r *entityResolver) FindLocationByID(ctx context.Context, id string) (*generated.Location, error) {
-	panic("not implemented")
+	var (
+		inContext *middleware.GShopifyContext
+		rawId     string
+		err       error
+	)
+
+	inContext, err = middleware.InContext(ctx)
+	if err != nil {
+		return nil, err
+	}
+
+	rawId, err = model.ParseId(model.GidLocation, id)
+	if err != nil {
+		return nil, err
+	}
+
+	return r.db.Location(inContext.Language, rawId)
 }
 
 func (r *queryResolver) Locations(ctx context.Context,

+ 4 - 0
graphql/resolver_impl.go

@@ -30,6 +30,10 @@ func (r *Resolver) Query() generated.QueryResolver { return &queryResolver{r} }
 func (r *Resolver) Close() error {
 	var err error
 
+	if r.db != nil {
+		err = r.db.Close()
+	}
+
 	return err
 }
 

+ 100 - 0
relation/location.go

@@ -0,0 +1,100 @@
+package relation
+
+import (
+	"github.com/gshopify/service-wrapper/model"
+	"github.com/mailru/dbr"
+	"gshopper.com/gshopify/shop/graphql/generated"
+	"strings"
+)
+
+type Location struct {
+	Id           string          `db:"id"`
+	Name         string          `db:"name"`
+	Address1     dbr.NullString  `db:"address_1"`
+	Address2     dbr.NullString  `db:"address_2"`
+	City         dbr.NullString  `db:"city"`
+	Country      dbr.NullString  `db:"country"`
+	CountryCode  dbr.NullString  `db:"country_code"`
+	Latitude     dbr.NullFloat64 `db:"latitude"`
+	Longitude    dbr.NullFloat64 `db:"longitude"`
+	Phone        dbr.NullString  `db:"phone"`
+	Province     dbr.NullString  `db:"province"`
+	ProvinceCode dbr.NullString  `db:"province_code"`
+	Zip          dbr.NullString  `db:"zip"`
+}
+
+func (l *Location) As() *generated.Location {
+	var (
+		fmtProvince = strings.Builder{}
+		loc         = &generated.Location{
+			Address: &generated.LocationAddress{},
+			ID:      model.NewId(model.GidLocation, l.Id),
+			Name:    l.Name,
+		}
+	)
+
+	if l.Address1.Valid {
+		loc.Address.Address1 = &l.Address1.String
+		loc.Address.Formatted = append(loc.Address.Formatted, l.Address1.String)
+	}
+
+	if l.Address2.Valid {
+		loc.Address.Address2 = &l.Address2.String
+		loc.Address.Formatted = append(loc.Address.Formatted, l.Address2.String)
+	}
+
+	if l.City.Valid {
+		loc.Address.City = &l.City.String
+		loc.Address.Formatted = append(loc.Address.Formatted, l.City.String)
+	}
+
+	if l.ProvinceCode.Valid {
+		loc.Address.ProvinceCode = &l.ProvinceCode.String
+		fmtProvince.WriteString(l.ProvinceCode.String)
+	}
+
+	if l.Province.Valid {
+		loc.Address.Province = &l.Province.String
+		if fmtProvince.Len() == 0 {
+			fmtProvince.WriteString(l.Province.String)
+		}
+	}
+
+	if l.Zip.Valid {
+		loc.Address.Zip = &l.Zip.String
+
+		if fmtProvince.Len() == 0 {
+			fmtProvince.WriteString(" ")
+		}
+
+		fmtProvince.WriteString(l.Zip.String)
+	}
+
+	if fmtProvince.Len() > 0 {
+		loc.Address.Formatted = append(loc.Address.Formatted, fmtProvince.String())
+	}
+
+	if l.Country.Valid {
+		loc.Address.Country = &l.Country.String
+		loc.Address.Formatted = append(loc.Address.Formatted, l.Country.String)
+	}
+
+	if code := model.CountryCode(l.CountryCode.String); code.IsValid() {
+		s := code.String()
+		loc.Address.CountryCode = &s
+	}
+
+	if l.Latitude.Valid {
+		loc.Address.Latitude = &l.Latitude.Float64
+	}
+
+	if l.Longitude.Valid {
+		loc.Address.Longitude = &l.Longitude.Float64
+	}
+
+	if l.Phone.Valid {
+		loc.Address.Phone = &l.Phone.String
+	}
+
+	return loc
+}