Skip to content

Commit ad1d4a9

Browse files
committed
Add billing migration
1 parent 35b2e84 commit ad1d4a9

20 files changed

Lines changed: 447 additions & 210 deletions

android/app/src/main/java/com/httpsms/HttpSmsApiService.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@ import java.util.logging.Logger.getLogger
1515

1616
class HttpSmsApiService(private val apiKey: String) {
1717
private val apiKeyHeader = "x-api-key"
18-
// private val baseURL = URI("https://49b1-145-14-19-43.ngrok.io")
19-
private val baseURL = URI("https://api.httpsms.com")
18+
private val baseURL = URI("https://a276-145-14-19-43.ngrok.io/")
19+
//private val baseURL = URI("https://api.httpsms.com")
2020
private val jsonMediaType = "application/json; charset=utf-8".toMediaType()
2121
private val client = OkHttpClient.Builder().retryOnConnectionFailure(true).build()
2222

api/cmd/migration/main.go

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"log"
7+
8+
"github.com/NdoleStudio/httpsms/pkg/di"
9+
"github.com/NdoleStudio/httpsms/pkg/events"
10+
"github.com/NdoleStudio/httpsms/pkg/repositories"
11+
"github.com/NdoleStudio/httpsms/pkg/telemetry"
12+
"github.com/cheggaaa/pb/v3"
13+
cloudevents "github.com/cloudevents/sdk-go/v2"
14+
"github.com/google/uuid"
15+
"github.com/joho/godotenv"
16+
"github.com/palantir/stacktrace"
17+
)
18+
19+
func main() {
20+
err := godotenv.Load("../../.env")
21+
if err != nil {
22+
log.Fatal("Error loading .env file")
23+
}
24+
25+
container := di.NewContainer("http-sms")
26+
eventRepo := container.EventRepository()
27+
logger := container.Logger()
28+
29+
cloudEvents, err := eventRepo.FetchAll(context.Background())
30+
if err != nil {
31+
logger.Fatal(stacktrace.Propagate(err, "cannot fetch all cloudEvents"))
32+
}
33+
logger.Info(fmt.Sprintf("fetched %d cloudevents", len(*cloudEvents)))
34+
35+
// create and start new bar
36+
bar := pb.StartNew(len(*cloudEvents))
37+
for _, event := range *cloudEvents {
38+
if event.Type() == events.EventTypeMessageAPISent {
39+
migrateSentEvent(logger, eventRepo, event)
40+
}
41+
if event.Type() == events.EventTypeMessagePhoneReceived {
42+
migrateReceivedEvent(logger, eventRepo, event)
43+
}
44+
bar.Increment()
45+
}
46+
47+
bar.Finish()
48+
}
49+
50+
func migrateReceivedEvent(logger telemetry.Logger, repository repositories.EventRepository, event cloudevents.Event) {
51+
var payload events.MessagePhoneReceivedPayloadV1
52+
if err := event.DataAs(&payload); err != nil {
53+
msg := fmt.Sprintf("cannot decode [%s] into [%T]", event.Data(), payload)
54+
logger.Fatal(stacktrace.Propagate(err, msg))
55+
}
56+
57+
if payload.MessageID != uuid.Nil {
58+
payloadV2 := events.MessagePhoneReceivedPayload{
59+
MessageID: payload.MessageID,
60+
UserID: payload.UserID,
61+
Owner: payload.Owner,
62+
Contact: payload.Contact,
63+
Timestamp: payload.Timestamp,
64+
Content: payload.Content,
65+
}
66+
67+
if err := event.SetData(cloudevents.ApplicationJSON, payloadV2); err != nil {
68+
logger.Fatal(stacktrace.Propagate(err, "cannot set payload v2 data"))
69+
}
70+
71+
if err := repository.Save(context.Background(), event); err != nil {
72+
logger.Fatal(stacktrace.Propagate(err, "cannot save event after updating"))
73+
}
74+
}
75+
}
76+
77+
func migrateSentEvent(logger telemetry.Logger, repository repositories.EventRepository, event cloudevents.Event) {
78+
var payload events.MessageAPISentPayloadV1
79+
if err := event.DataAs(&payload); err != nil {
80+
msg := fmt.Sprintf("cannot decode [%s] into [%T]", event.Data(), payload)
81+
logger.Fatal(stacktrace.Propagate(err, msg))
82+
}
83+
84+
if payload.MessageID != uuid.Nil {
85+
payloadV2 := events.MessageAPISentPayload{
86+
MessageID: payload.MessageID,
87+
UserID: payload.UserID,
88+
Owner: payload.Owner,
89+
MaxSendAttempts: 2,
90+
Contact: payload.Contact,
91+
RequestReceivedAt: payload.RequestReceivedAt,
92+
Content: payload.Content,
93+
}
94+
95+
if err := event.SetData(cloudevents.ApplicationJSON, payloadV2); err != nil {
96+
logger.Fatal(stacktrace.Propagate(err, "cannot set payloadv2 data"))
97+
}
98+
99+
if err := repository.Save(context.Background(), event); err != nil {
100+
logger.Fatal(stacktrace.Propagate(err, "cannot save event after updating"))
101+
}
102+
}
103+
}

api/cmd/replay/main.go

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ import (
55
"fmt"
66
"log"
77

8+
"github.com/NdoleStudio/httpsms/pkg/events"
9+
"github.com/NdoleStudio/httpsms/pkg/listeners"
10+
811
"github.com/NdoleStudio/httpsms/pkg/di"
912
"github.com/cheggaaa/pb/v3"
1013
"github.com/joho/godotenv"
@@ -20,18 +23,32 @@ func main() {
2023
container := di.NewContainer("http-sms")
2124
eventRepo := container.EventRepository()
2225
logger := container.Logger()
23-
dispatcher := container.EventDispatcher()
2426

25-
events, err := eventRepo.FetchAll(context.Background())
27+
listener, _ := listeners.NewBillingListener(
28+
container.Logger(),
29+
container.Tracer(),
30+
container.BillingService(),
31+
)
32+
33+
cloudEvents, err := eventRepo.FetchAll(context.Background())
2634
if err != nil {
2735
logger.Fatal(stacktrace.Propagate(err, "cannot fetch all events"))
2836
}
29-
logger.Info(fmt.Sprintf("fetched %d cloudevents", len(*events)))
37+
logger.Info(fmt.Sprintf("fetched %d cloudevents", len(*cloudEvents)))
3038

3139
// create and start new bar
32-
bar := pb.StartNew(len(*events))
33-
for _, event := range *events {
34-
dispatcher.Publish(context.Background(), event)
40+
bar := pb.StartNew(len(*cloudEvents))
41+
for _, event := range *cloudEvents {
42+
if event.Type() == events.EventTypeMessageAPISent {
43+
if err := listener.OnMessageAPISent(context.Background(), event); err != nil {
44+
logger.Fatal(stacktrace.Propagate(err, "cannot register api sent event"))
45+
}
46+
}
47+
if event.Type() == events.EventTypeMessagePhoneReceived {
48+
if err := listener.OnMessagePhoneReceived(context.Background(), event); err != nil {
49+
logger.Fatal(stacktrace.Propagate(err, "cannot register api received event"))
50+
}
51+
}
3552
bar.Increment()
3653
}
3754

api/pkg/di/container.go

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,9 @@ type Container struct {
6464
// NewContainer creates a new dependency injection container
6565
func NewContainer(projectID string) (container *Container) {
6666
// Set location to UTC
67-
now.DefaultConfig.TimeLocation = time.UTC
67+
now.DefaultConfig = &now.Config{
68+
TimeLocation: time.UTC,
69+
}
6870

6971
container = &Container{
7072
projectID: projectID,
@@ -480,6 +482,16 @@ func (container *Container) HeartbeatService() (service *services.HeartbeatServi
480482
)
481483
}
482484

485+
// BillingService creates a new instance of services.BillingService
486+
func (container *Container) BillingService() (service *services.BillingService) {
487+
container.logger.Debug(fmt.Sprintf("creating %T", service))
488+
return services.NewBillingService(
489+
container.Logger(),
490+
container.Tracer(),
491+
container.BillingUsageRepository(),
492+
)
493+
}
494+
483495
// PhoneService creates a new instance of services.PhoneService
484496
func (container *Container) PhoneService() (service *services.PhoneService) {
485497
container.logger.Debug(fmt.Sprintf("creating %T", service))
@@ -656,6 +668,20 @@ func (container *Container) RegisterUserListeners() {
656668
}
657669
}
658670

671+
// RegisterBillingListeners registers event listeners for listeners.BillingListener
672+
func (container *Container) RegisterBillingListeners() {
673+
container.logger.Debug(fmt.Sprintf("registering listeners for %T", listeners.BillingListener{}))
674+
_, routes := listeners.NewBillingListener(
675+
container.Logger(),
676+
container.Tracer(),
677+
container.BillingService(),
678+
)
679+
680+
for event, handler := range routes {
681+
container.EventDispatcher().Subscribe(event, handler)
682+
}
683+
}
684+
659685
// MessageService creates a new instance of services.MessageService
660686
func (container *Container) MessageService() (service *services.MessageService) {
661687
container.logger.Debug(fmt.Sprintf("creating %T", service))

api/pkg/events/message_api_sent_event.go

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,21 @@ const EventTypeMessageAPISent = "message.api.sent"
1313

1414
// MessageAPISentPayload is the payload of the EventTypeMessageSent event
1515
type MessageAPISentPayload struct {
16-
ID uuid.UUID `json:"id"`
16+
MessageID uuid.UUID `json:"message_id"`
17+
UserID entities.UserID `json:"user_id"`
18+
Owner string `json:"owner"`
19+
MaxSendAttempts uint `json:"max_send_attempts"`
20+
Contact string `json:"contact"`
21+
RequestReceivedAt time.Time `json:"request_received_at"`
22+
Content string `json:"content"`
23+
}
24+
25+
// MessageAPISentPayloadV1 is the old event
26+
type MessageAPISentPayloadV1 struct {
27+
MessageID uuid.UUID `json:"id"`
1728
UserID entities.UserID `json:"userID"`
1829
Owner string `json:"owner"`
30+
MaxSendAttempts uint `json:"max_send_attempts"`
1931
Contact string `json:"contact"`
2032
RequestReceivedAt time.Time `json:"request_received_at"`
2133
Content string `json:"content"`

api/pkg/events/message_phone_received_event.go

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,17 @@ const EventTypeMessagePhoneReceived = "message.phone.received"
1313

1414
// MessagePhoneReceivedPayload is the payload of the EventTypeMessagePhoneReceived event
1515
type MessagePhoneReceivedPayload struct {
16-
ID uuid.UUID `json:"id"`
16+
MessageID uuid.UUID `json:"message_id"`
17+
UserID entities.UserID `json:"user_id"`
18+
Owner string `json:"owner"`
19+
Contact string `json:"contact"`
20+
Timestamp time.Time `json:"timestamp"`
21+
Content string `json:"content"`
22+
}
23+
24+
// MessagePhoneReceivedPayloadV1 is the payload of the EventTypeMessagePhoneReceived event
25+
type MessagePhoneReceivedPayloadV1 struct {
26+
MessageID uuid.UUID `json:"id"`
1727
UserID entities.UserID `json:"user_id"`
1828
Owner string `json:"owner"`
1929
Contact string `json:"contact"`

api/pkg/handlers/message_handler.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,6 @@ func (h *MessageHandler) GetOutstanding(c *fiber.Ctx) error {
140140
return h.responseInternalServerError(c)
141141
}
142142

143-
ctxLogger.Warn(stacktrace.NewError(fmt.Sprintf("New Message [%s]", spew.Sdump(message))))
144143
return h.responseOK(c, "outstanding message fetched successfully", message)
145144
}
146145

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
package listeners
2+
3+
import (
4+
"context"
5+
"fmt"
6+
7+
"github.com/davecgh/go-spew/spew"
8+
9+
"github.com/NdoleStudio/httpsms/pkg/events"
10+
"github.com/NdoleStudio/httpsms/pkg/services"
11+
"github.com/NdoleStudio/httpsms/pkg/telemetry"
12+
cloudevents "github.com/cloudevents/sdk-go/v2"
13+
"github.com/palantir/stacktrace"
14+
)
15+
16+
// BillingListener handles cloud events which affect billing
17+
type BillingListener struct {
18+
logger telemetry.Logger
19+
tracer telemetry.Tracer
20+
service *services.BillingService
21+
}
22+
23+
// NewBillingListener creates a new instance of UserListener
24+
func NewBillingListener(
25+
logger telemetry.Logger,
26+
tracer telemetry.Tracer,
27+
service *services.BillingService,
28+
) (l *BillingListener, routes map[string]events.EventListener) {
29+
l = &BillingListener{
30+
logger: logger.WithService(fmt.Sprintf("%T", l)),
31+
tracer: tracer,
32+
service: service,
33+
}
34+
35+
return l, map[string]events.EventListener{
36+
events.EventTypeMessageAPISent: l.OnMessageAPISent,
37+
events.EventTypeMessagePhoneReceived: l.OnMessagePhoneReceived,
38+
}
39+
}
40+
41+
// OnMessageAPISent handles the events.EventTypeMessageAPISent event
42+
func (listener *BillingListener) OnMessageAPISent(ctx context.Context, event cloudevents.Event) error {
43+
ctx, span := listener.tracer.Start(ctx)
44+
defer span.End()
45+
46+
var payload events.MessageAPISentPayload
47+
if err := event.DataAs(&payload); err != nil {
48+
msg := fmt.Sprintf("cannot decode [%s] into [%T]", event.Data(), payload)
49+
return listener.tracer.WrapErrorSpan(span, stacktrace.Propagate(err, msg))
50+
}
51+
52+
if err := listener.service.RegisterSentMessage(ctx, payload.MessageID, payload.RequestReceivedAt, payload.UserID); err != nil {
53+
msg := fmt.Sprintf("cannot register sent message for event [%s] for event with ID [%s]", spew.Sdump(payload), event.ID())
54+
return listener.tracer.WrapErrorSpan(span, stacktrace.Propagate(err, msg))
55+
}
56+
57+
return nil
58+
}
59+
60+
// OnMessagePhoneReceived handles the events.EventTypeMessagePhoneReceived event
61+
func (listener *BillingListener) OnMessagePhoneReceived(ctx context.Context, event cloudevents.Event) error {
62+
ctx, span := listener.tracer.Start(ctx)
63+
defer span.End()
64+
65+
var payload events.MessagePhoneReceivedPayload
66+
if err := event.DataAs(&payload); err != nil {
67+
msg := fmt.Sprintf("cannot decode [%s] into [%T]", event.Data(), payload)
68+
return listener.tracer.WrapErrorSpan(span, stacktrace.Propagate(err, msg))
69+
}
70+
71+
if err := listener.service.RegisterReceivedMessage(ctx, payload.MessageID, payload.Timestamp, payload.UserID); err != nil {
72+
msg := fmt.Sprintf("cannot register received message for event [%s] for event with ID [%s]", spew.Sdump(payload), event.ID())
73+
return listener.tracer.WrapErrorSpan(span, stacktrace.Propagate(err, msg))
74+
}
75+
76+
return nil
77+
}

0 commit comments

Comments
 (0)