Skip to content

Commit 24f1fc2

Browse files
AchoArnoldCopilot
andcommitted
refactor: use entity structs directly with bson tags instead of document types
- Add bson struct tags to entities.Heartbeat and entities.HeartbeatMonitor - Register custom UUID codec so uuid.UUID is stored as string _id in MongoDB - Remove intermediate heartbeatDocument/heartbeatMonitorDocument structs - MongoDB repositories now marshal/unmarshal entities directly Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 440faa5 commit 24f1fc2

5 files changed

Lines changed: 58 additions & 121 deletions

File tree

api/pkg/entities/heartbeat.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,10 @@ import (
88

99
// Heartbeat represents is a pulse from an active phone
1010
type Heartbeat struct {
11-
ID uuid.UUID `json:"id" gorm:"primaryKey;type:uuid;" example:"32343a19-da5e-4b1b-a767-3298a73703cb"`
12-
Owner string `json:"owner" gorm:"index:idx_heartbeats_owner_timestamp" example:"+18005550199"`
13-
Version string `json:"version" example:"344c10f"`
14-
Charging bool `json:"charging" example:"true"`
15-
UserID UserID `json:"user_id" example:"WB7DRDWrJZRGbYrv2CKGkqbzvqdC"`
16-
Timestamp time.Time `json:"timestamp" gorm:"index:idx_heartbeats_owner_timestamp" example:"2022-06-05T14:26:01.520828+03:00"`
11+
ID uuid.UUID `json:"id" gorm:"primaryKey;type:uuid;" bson:"_id" example:"32343a19-da5e-4b1b-a767-3298a73703cb"`
12+
Owner string `json:"owner" gorm:"index:idx_heartbeats_owner_timestamp" bson:"owner" example:"+18005550199"`
13+
Version string `json:"version" bson:"version" example:"344c10f"`
14+
Charging bool `json:"charging" bson:"charging" example:"true"`
15+
UserID UserID `json:"user_id" bson:"user_id" example:"WB7DRDWrJZRGbYrv2CKGkqbzvqdC"`
16+
Timestamp time.Time `json:"timestamp" gorm:"index:idx_heartbeats_owner_timestamp" bson:"timestamp" example:"2022-06-05T14:26:01.520828+03:00"`
1717
}

api/pkg/entities/heartbeat_monitor.go

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,14 @@ import (
88

99
// HeartbeatMonitor is used to monitor heartbeats of a phone
1010
type HeartbeatMonitor struct {
11-
ID uuid.UUID `json:"id" gorm:"primaryKey;type:uuid;" example:"32343a19-da5e-4b1b-a767-3298a73703cb"`
12-
PhoneID uuid.UUID `json:"phone_id" example:"32343a19-da5e-4b1b-a767-3298a73703cb"`
13-
UserID UserID `json:"user_id" example:"WB7DRDWrJZRGbYrv2CKGkqbzvqdC"`
14-
QueueID string `json:"queue_id" example:"0360259236613675274"`
15-
Owner string `json:"owner" example:"+18005550199"`
16-
PhoneOnline bool `json:"phone_online" example:"true" default:"true"`
17-
CreatedAt time.Time `json:"created_at" example:"2022-06-05T14:26:02.302718+03:00"`
18-
UpdatedAt time.Time `json:"updated_at" example:"2022-06-05T14:26:10.303278+03:00"`
11+
ID uuid.UUID `json:"id" gorm:"primaryKey;type:uuid;" bson:"_id" example:"32343a19-da5e-4b1b-a767-3298a73703cb"`
12+
PhoneID uuid.UUID `json:"phone_id" bson:"phone_id" example:"32343a19-da5e-4b1b-a767-3298a73703cb"`
13+
UserID UserID `json:"user_id" bson:"user_id" example:"WB7DRDWrJZRGbYrv2CKGkqbzvqdC"`
14+
QueueID string `json:"queue_id" bson:"queue_id" example:"0360259236613675274"`
15+
Owner string `json:"owner" bson:"owner" example:"+18005550199"`
16+
PhoneOnline bool `json:"phone_online" bson:"phone_online" example:"true" default:"true"`
17+
CreatedAt time.Time `json:"created_at" bson:"created_at" example:"2022-06-05T14:26:02.302718+03:00"`
18+
UpdatedAt time.Time `json:"updated_at" bson:"updated_at" example:"2022-06-05T14:26:10.303278+03:00"`
1919
}
2020

2121
// RequiresCheck returns true if the heartbeat monitor requires a check

api/pkg/repositories/mongo_heartbeat_monitor_repository.go

Lines changed: 4 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -34,36 +34,14 @@ func NewMongoHeartbeatMonitorRepository(
3434
}
3535
}
3636

37-
type heartbeatMonitorDocument struct {
38-
ID string `bson:"_id"`
39-
PhoneID string `bson:"phone_id"`
40-
UserID string `bson:"user_id"`
41-
QueueID string `bson:"queue_id"`
42-
Owner string `bson:"owner"`
43-
PhoneOnline bool `bson:"phone_online"`
44-
CreatedAt time.Time `bson:"created_at"`
45-
UpdatedAt time.Time `bson:"updated_at"`
46-
}
47-
4837
func (repository *mongoHeartbeatMonitorRepository) Store(ctx context.Context, monitor *entities.HeartbeatMonitor) error {
4938
ctx, span := repository.tracer.Start(ctx)
5039
defer span.End()
5140

5241
ctx, cancel := context.WithTimeout(ctx, dbOperationDuration)
5342
defer cancel()
5443

55-
doc := heartbeatMonitorDocument{
56-
ID: monitor.ID.String(),
57-
PhoneID: monitor.PhoneID.String(),
58-
UserID: string(monitor.UserID),
59-
QueueID: monitor.QueueID,
60-
Owner: monitor.Owner,
61-
PhoneOnline: monitor.PhoneOnline,
62-
CreatedAt: monitor.CreatedAt.UTC(),
63-
UpdatedAt: monitor.UpdatedAt.UTC(),
64-
}
65-
66-
_, err := repository.collection.InsertOne(ctx, doc)
44+
_, err := repository.collection.InsertOne(ctx, monitor)
6745
if err != nil {
6846
msg := fmt.Sprintf("cannot save heartbeat monitor with ID [%s]", monitor.ID)
6947
return repository.tracer.WrapErrorSpan(span, stacktrace.Propagate(err, msg))
@@ -84,8 +62,8 @@ func (repository *mongoHeartbeatMonitorRepository) Load(ctx context.Context, use
8462
{"owner", phoneNumber},
8563
}
8664

87-
var doc heartbeatMonitorDocument
88-
err := repository.collection.FindOne(ctx, filter).Decode(&doc)
65+
var monitor entities.HeartbeatMonitor
66+
err := repository.collection.FindOne(ctx, filter).Decode(&monitor)
8967
if err == mongo.ErrNoDocuments {
9068
msg := fmt.Sprintf("heartbeat monitor with userID [%s] and owner [%s] does not exist", userID, phoneNumber)
9169
return nil, repository.tracer.WrapErrorSpan(span, stacktrace.PropagateWithCode(err, ErrCodeNotFound, msg))
@@ -95,12 +73,7 @@ func (repository *mongoHeartbeatMonitorRepository) Load(ctx context.Context, use
9573
return nil, repository.tracer.WrapErrorSpan(span, stacktrace.Propagate(err, msg))
9674
}
9775

98-
monitor, err := docToHeartbeatMonitor(doc)
99-
if err != nil {
100-
return nil, repository.tracer.WrapErrorSpan(span, stacktrace.Propagate(err, "cannot convert heartbeat monitor document"))
101-
}
102-
103-
return monitor, nil
76+
return &monitor, nil
10477
}
10578

10679
func (repository *mongoHeartbeatMonitorRepository) Exists(ctx context.Context, userID entities.UserID, monitorID uuid.UUID) (bool, error) {
@@ -207,24 +180,3 @@ func (repository *mongoHeartbeatMonitorRepository) DeleteAllForUser(ctx context.
207180

208181
return nil
209182
}
210-
211-
func docToHeartbeatMonitor(doc heartbeatMonitorDocument) (*entities.HeartbeatMonitor, error) {
212-
id, err := uuid.Parse(doc.ID)
213-
if err != nil {
214-
return nil, stacktrace.Propagate(err, fmt.Sprintf("cannot parse heartbeat monitor ID [%s]", doc.ID))
215-
}
216-
phoneID, err := uuid.Parse(doc.PhoneID)
217-
if err != nil {
218-
return nil, stacktrace.Propagate(err, fmt.Sprintf("cannot parse heartbeat monitor phone ID [%s]", doc.PhoneID))
219-
}
220-
return &entities.HeartbeatMonitor{
221-
ID: id,
222-
PhoneID: phoneID,
223-
UserID: entities.UserID(doc.UserID),
224-
QueueID: doc.QueueID,
225-
Owner: doc.Owner,
226-
PhoneOnline: doc.PhoneOnline,
227-
CreatedAt: doc.CreatedAt,
228-
UpdatedAt: doc.UpdatedAt,
229-
}, nil
230-
}

api/pkg/repositories/mongo_heartbeat_repository.go

Lines changed: 8 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,7 @@ package repositories
33
import (
44
"context"
55
"fmt"
6-
"time"
76

8-
"github.com/google/uuid"
97
"go.mongodb.org/mongo-driver/v2/bson"
108
"go.mongodb.org/mongo-driver/v2/mongo"
119
"go.mongodb.org/mongo-driver/v2/mongo/options"
@@ -35,32 +33,14 @@ func NewMongoHeartbeatRepository(
3533
}
3634
}
3735

38-
type heartbeatDocument struct {
39-
ID string `bson:"_id"`
40-
Owner string `bson:"owner"`
41-
Version string `bson:"version"`
42-
Charging bool `bson:"charging"`
43-
UserID string `bson:"user_id"`
44-
Timestamp time.Time `bson:"timestamp"`
45-
}
46-
4736
func (repository *mongoHeartbeatRepository) Store(ctx context.Context, heartbeat *entities.Heartbeat) error {
4837
ctx, span, _ := repository.tracer.StartWithLogger(ctx, repository.logger)
4938
defer span.End()
5039

5140
ctx, cancel := context.WithTimeout(ctx, dbOperationDuration)
5241
defer cancel()
5342

54-
doc := heartbeatDocument{
55-
ID: heartbeat.ID.String(),
56-
Owner: heartbeat.Owner,
57-
Version: heartbeat.Version,
58-
Charging: heartbeat.Charging,
59-
UserID: string(heartbeat.UserID),
60-
Timestamp: heartbeat.Timestamp.UTC(),
61-
}
62-
63-
_, err := repository.collection.InsertOne(ctx, doc)
43+
_, err := repository.collection.InsertOne(ctx, heartbeat)
6444
if err != nil {
6545
msg := fmt.Sprintf("cannot save heartbeat with ID [%s]", heartbeat.ID)
6646
return repository.tracer.WrapErrorSpan(span, stacktrace.Propagate(err, msg))
@@ -97,20 +77,14 @@ func (repository *mongoHeartbeatRepository) Index(ctx context.Context, userID en
9777
}
9878
defer cursor.Close(ctx)
9979

100-
var docs []heartbeatDocument
101-
if err = cursor.All(ctx, &docs); err != nil {
80+
var heartbeats []entities.Heartbeat
81+
if err = cursor.All(ctx, &heartbeats); err != nil {
10282
msg := fmt.Sprintf("cannot decode heartbeats for owner [%s]", owner)
10383
return nil, repository.tracer.WrapErrorSpan(span, stacktrace.Propagate(err, msg))
10484
}
10585

106-
heartbeats := make([]entities.Heartbeat, 0, len(docs))
107-
for _, doc := range docs {
108-
hb, convertErr := docToHeartbeat(doc)
109-
if convertErr != nil {
110-
msg := fmt.Sprintf("cannot convert heartbeat document for owner [%s]", owner)
111-
return nil, repository.tracer.WrapErrorSpan(span, stacktrace.Propagate(convertErr, msg))
112-
}
113-
heartbeats = append(heartbeats, *hb)
86+
if heartbeats == nil {
87+
heartbeats = make([]entities.Heartbeat, 0)
11488
}
11589

11690
return &heartbeats, nil
@@ -130,8 +104,8 @@ func (repository *mongoHeartbeatRepository) Last(ctx context.Context, userID ent
130104

131105
opts := options.FindOne().SetSort(bson.D{{"timestamp", -1}})
132106

133-
var doc heartbeatDocument
134-
err := repository.collection.FindOne(ctx, filter, opts).Decode(&doc)
107+
var heartbeat entities.Heartbeat
108+
err := repository.collection.FindOne(ctx, filter, opts).Decode(&heartbeat)
135109
if err == mongo.ErrNoDocuments {
136110
msg := fmt.Sprintf("heartbeat with userID [%s] and owner [%s] does not exist", userID, owner)
137111
return nil, repository.tracer.WrapErrorSpan(span, stacktrace.PropagateWithCode(err, ErrCodeNotFound, msg))
@@ -141,12 +115,7 @@ func (repository *mongoHeartbeatRepository) Last(ctx context.Context, userID ent
141115
return nil, repository.tracer.WrapErrorSpan(span, stacktrace.Propagate(err, msg))
142116
}
143117

144-
heartbeat, err := docToHeartbeat(doc)
145-
if err != nil {
146-
return nil, repository.tracer.WrapErrorSpan(span, stacktrace.Propagate(err, "cannot convert heartbeat document"))
147-
}
148-
149-
return heartbeat, nil
118+
return &heartbeat, nil
150119
}
151120

152121
func (repository *mongoHeartbeatRepository) DeleteAllForUser(ctx context.Context, userID entities.UserID) error {
@@ -164,18 +133,3 @@ func (repository *mongoHeartbeatRepository) DeleteAllForUser(ctx context.Context
164133

165134
return nil
166135
}
167-
168-
func docToHeartbeat(doc heartbeatDocument) (*entities.Heartbeat, error) {
169-
id, err := uuid.Parse(doc.ID)
170-
if err != nil {
171-
return nil, stacktrace.Propagate(err, fmt.Sprintf("cannot parse heartbeat ID [%s]", doc.ID))
172-
}
173-
return &entities.Heartbeat{
174-
ID: id,
175-
Owner: doc.Owner,
176-
Version: doc.Version,
177-
Charging: doc.Charging,
178-
UserID: entities.UserID(doc.UserID),
179-
Timestamp: doc.Timestamp,
180-
}, nil
181-
}

api/pkg/repositories/mongodb.go

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@ package repositories
33
import (
44
"context"
55
"fmt"
6+
"reflect"
67
"time"
78

9+
"github.com/google/uuid"
810
"go.mongodb.org/mongo-driver/v2/bson"
911
"go.mongodb.org/mongo-driver/v2/mongo"
1012
"go.mongodb.org/mongo-driver/v2/mongo/options"
@@ -18,13 +20,42 @@ const (
1820
collectionHeartbeatMonitors = "heartbeat_monitors"
1921
)
2022

23+
// uuidEncodeValue encodes uuid.UUID as a BSON string
24+
func uuidEncodeValue(_ bson.EncodeContext, vw bson.ValueWriter, val reflect.Value) error {
25+
u := val.Interface().(uuid.UUID)
26+
return vw.WriteString(u.String())
27+
}
28+
29+
// uuidDecodeValue decodes a BSON string into uuid.UUID
30+
func uuidDecodeValue(_ bson.DecodeContext, vr bson.ValueReader, val reflect.Value) error {
31+
str, err := vr.ReadString()
32+
if err != nil {
33+
return err
34+
}
35+
parsed, err := uuid.Parse(str)
36+
if err != nil {
37+
return err
38+
}
39+
val.Set(reflect.ValueOf(parsed))
40+
return nil
41+
}
42+
43+
// newMongoRegistry creates a BSON registry that encodes uuid.UUID as strings
44+
func newMongoRegistry() *bson.Registry {
45+
rb := bson.NewRegistry()
46+
rb.RegisterTypeEncoder(reflect.TypeOf(uuid.UUID{}), bson.ValueEncoderFunc(uuidEncodeValue))
47+
rb.RegisterTypeDecoder(reflect.TypeOf(uuid.UUID{}), bson.ValueDecoderFunc(uuidDecodeValue))
48+
return rb
49+
}
50+
2151
// NewMongoDB creates a new *mongo.Client connection to MongoDB Atlas and ensures indexes
2252
func NewMongoDB(uri string) (*mongo.Client, error) {
2353
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
2454
defer cancel()
2555

2656
serverAPI := options.ServerAPI(options.ServerAPIVersion1)
27-
opts := options.Client().ApplyURI(uri).SetServerAPIOptions(serverAPI)
57+
registry := newMongoRegistry()
58+
opts := options.Client().ApplyURI(uri).SetServerAPIOptions(serverAPI).SetRegistry(registry)
2859

2960
client, err := mongo.Connect(opts)
3061
if err != nil {

0 commit comments

Comments
 (0)