convert seen service to use store

This commit is contained in:
Asim Aslam
2021-05-02 14:24:05 +01:00
parent ac62b8b35b
commit eb82bebd03
3 changed files with 88 additions and 86 deletions

View File

@@ -2,15 +2,16 @@ package handler
import ( import (
"context" "context"
"encoding/json"
"fmt"
"strings"
"time" "time"
"github.com/micro/micro/v3/service/auth"
gorm2 "github.com/micro/services/pkg/gorm"
"gorm.io/gorm"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/micro/micro/v3/service/auth"
"github.com/micro/micro/v3/service/errors" "github.com/micro/micro/v3/service/errors"
"github.com/micro/micro/v3/service/logger" "github.com/micro/micro/v3/service/logger"
"github.com/micro/micro/v3/service/store"
pb "github.com/micro/services/seen/proto" pb "github.com/micro/services/seen/proto"
"google.golang.org/protobuf/types/known/timestamppb" "google.golang.org/protobuf/types/known/timestamppb"
) )
@@ -23,16 +24,27 @@ var (
ErrStore = errors.InternalServerError("STORE_ERROR", "Error connecting to the store") ErrStore = errors.InternalServerError("STORE_ERROR", "Error connecting to the store")
) )
type Seen struct { type Seen struct{}
gorm2.Helper
type Record struct {
ID string
UserID string
ResourceID string
ResourceType string
Timestamp time.Time
} }
type SeenInstance struct { func (r *Record) Key() string {
ID string return fmt.Sprintf("%s:%s:%s", r.UserID, r.ResourceType, r.ResourceID)
UserID string `gorm:"uniqueIndex:user_resource"` }
ResourceID string `gorm:"uniqueIndex:user_resource"`
ResourceType string `gorm:"uniqueIndex:user_resource"` func (r *Record) Marshal() []byte {
Timestamp time.Time b, _ := json.Marshal(r)
return b
}
func (r *Record) Unmarshal(b []byte) error {
return json.Unmarshal(b, &r)
} }
// Set a resource as seen by a user. If no timestamp is provided, the current time is used. // Set a resource as seen by a user. If no timestamp is provided, the current time is used.
@@ -58,17 +70,14 @@ func (s *Seen) Set(ctx context.Context, req *pb.SetRequest, rsp *pb.SetResponse)
} }
// find the resource // find the resource
instance := SeenInstance{ instance := &Record{
UserID: req.UserId, UserID: req.UserId,
ResourceID: req.ResourceId, ResourceID: req.ResourceId,
ResourceType: req.ResourceType, ResourceType: req.ResourceType,
} }
db, err := s.GetDBConn(ctx)
if err != nil { _, err := store.Read(instance.Key(), store.ReadLimit(1))
logger.Errorf("Error connecting to DB: %v", err) if err == store.ErrNotFound {
return errors.InternalServerError("DB_ERROR", "Error connecting to DB")
}
if err := db.Where(&instance).First(&instance).Error; err == gorm.ErrRecordNotFound {
instance.ID = uuid.New().String() instance.ID = uuid.New().String()
} else if err != nil { } else if err != nil {
logger.Errorf("Error with store: %v", err) logger.Errorf("Error with store: %v", err)
@@ -77,7 +86,11 @@ func (s *Seen) Set(ctx context.Context, req *pb.SetRequest, rsp *pb.SetResponse)
// update the resource // update the resource
instance.Timestamp = req.Timestamp.AsTime() instance.Timestamp = req.Timestamp.AsTime()
if err := db.Save(&instance).Error; err != nil {
if err := store.Write(&store.Record{
Key: instance.Key(),
Value: instance.Marshal(),
}); err != nil {
logger.Errorf("Error with store: %v", err) logger.Errorf("Error with store: %v", err)
return ErrStore return ErrStore
} }
@@ -103,18 +116,14 @@ func (s *Seen) Unset(ctx context.Context, req *pb.UnsetRequest, rsp *pb.UnsetRes
return ErrMissingResourceType return ErrMissingResourceType
} }
db, err := s.GetDBConn(ctx) instance := &Record{
if err != nil {
logger.Errorf("Error connecting to DB: %v", err)
return errors.InternalServerError("DB_ERROR", "Error connecting to DB")
}
// delete the object from the store
err = db.Delete(SeenInstance{}, SeenInstance{
UserID: req.UserId, UserID: req.UserId,
ResourceID: req.ResourceId, ResourceID: req.ResourceId,
ResourceType: req.ResourceType, ResourceType: req.ResourceType,
}).Error }
if err != nil {
// delete the object from the store
if err := store.Delete(instance.Key()); err != nil {
logger.Errorf("Error with store: %v", err) logger.Errorf("Error with store: %v", err)
return ErrStore return ErrStore
} }
@@ -141,24 +150,54 @@ func (s *Seen) Read(ctx context.Context, req *pb.ReadRequest, rsp *pb.ReadRespon
return ErrMissingResourceType return ErrMissingResourceType
} }
db, err := s.GetDBConn(ctx) // create a key prefix
if err != nil { key := fmt.Sprintf("%s:%s:", req.UserId, req.ResourceType)
logger.Errorf("Error connecting to DB: %v", err)
return errors.InternalServerError("DB_ERROR", "Error connecting to DB") var recs []*store.Record
var err error
// get the records for the resource type
if len(req.ResourceIds) == 1 {
// read the key itself
key = key + req.ResourceIds[0]
recs, err = store.Read(key, store.ReadLimit(1))
} else {
// otherwise read the prefix
recs, err = store.Read(key, store.ReadPrefix())
} }
// query the store
q := db.Where(SeenInstance{UserID: req.UserId, ResourceType: req.ResourceType}) if err != nil {
q = q.Where("resource_id IN (?)", req.ResourceIds)
var data []SeenInstance
if err := q.Find(&data).Error; err != nil {
logger.Errorf("Error with store: %v", err) logger.Errorf("Error with store: %v", err)
return ErrStore return ErrStore
} }
// serialize the response // make an id map
rsp.Timestamps = make(map[string]*timestamppb.Timestamp, len(data)) ids := make(map[string]bool)
for _, i := range data {
rsp.Timestamps[i.ResourceID] = timestamppb.New(i.Timestamp) for _, id := range req.ResourceIds {
ids[id] = true
}
// make the map
rsp.Timestamps = make(map[string]*timestamppb.Timestamp)
// range over records for the user/resource type
// TODO: add some sort of filter query in store
for _, rec := range recs {
// get id
parts := strings.Split(rec.Key, ":")
id := parts[2]
fmt.Println("checking record", rec.Key, id)
if ok := ids[id]; !ok {
continue
}
// add the timestamp for the record
r := new(Record)
r.Unmarshal(rec.Value)
rsp.Timestamps[id] = timestamppb.New(r.Timestamp)
} }
return nil return nil

View File

@@ -2,13 +2,13 @@ package handler_test
import ( import (
"context" "context"
"database/sql"
"os"
"testing" "testing"
"time" "time"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/micro/micro/v3/service/auth" "github.com/micro/micro/v3/service/auth"
"github.com/micro/micro/v3/service/store"
"github.com/micro/micro/v3/service/store/memory"
"github.com/micro/services/seen/handler" "github.com/micro/services/seen/handler"
pb "github.com/micro/services/seen/proto" pb "github.com/micro/services/seen/proto"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@@ -16,24 +16,8 @@ import (
) )
func testHandler(t *testing.T) *handler.Seen { func testHandler(t *testing.T) *handler.Seen {
// connect to the database store.DefaultStore = memory.NewStore()
addr := os.Getenv("POSTGRES_URL") return &handler.Seen{}
if len(addr) == 0 {
addr = "postgresql://postgres@localhost:5432/postgres?sslmode=disable"
}
sqlDB, err := sql.Open("pgx", addr)
if err != nil {
t.Fatalf("Failed to open connection to DB %s", err)
}
// clean any data from a previous run
if _, err := sqlDB.Exec("DROP TABLE IF EXISTS micro_seen_instances CASCADE"); err != nil {
t.Fatalf("Error cleaning database: %v", err)
}
h := &handler.Seen{}
h.DBConn(sqlDB).Migrations(&handler.SeenInstance{})
return h
} }
func TestSet(t *testing.T) { func TestSet(t *testing.T) {

View File

@@ -1,20 +1,12 @@
package main package main
import ( import (
"database/sql" "github.com/micro/micro/v3/service"
"github.com/micro/micro/v3/service/logger"
"github.com/micro/services/seen/handler" "github.com/micro/services/seen/handler"
pb "github.com/micro/services/seen/proto" pb "github.com/micro/services/seen/proto"
"github.com/micro/micro/v3/service"
"github.com/micro/micro/v3/service/config"
"github.com/micro/micro/v3/service/logger"
_ "github.com/jackc/pgx/v4/stdlib"
) )
var dbAddress = "postgresql://postgres:postgres@localhost:5432/seen?sslmode=disable"
func main() { func main() {
// Create service // Create service
srv := service.New( srv := service.New(
@@ -22,21 +14,8 @@ func main() {
service.Version("latest"), service.Version("latest"),
) )
// Connect to the database
cfg, err := config.Get("seen.database")
if err != nil {
logger.Fatalf("Error loading config: %v", err)
}
addr := cfg.String(dbAddress)
sqlDB, err := sql.Open("pgx", addr)
if err != nil {
logger.Fatalf("Failed to open connection to DB %s", err)
}
h := &handler.Seen{}
h.DBConn(sqlDB).Migrations(&handler.SeenInstance{})
// Register handler // Register handler
pb.RegisterSeenHandler(srv.Server(), h) pb.RegisterSeenHandler(srv.Server(), new(handler.Seen))
// Run service // Run service
if err := srv.Run(); err != nil { if err := srv.Run(); err != nil {