Initial commit including .env
This commit is contained in:
225
internal/worker/booking_worker.go
Normal file
225
internal/worker/booking_worker.go
Normal file
@@ -0,0 +1,225 @@
|
||||
package worker
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"doormile/db"
|
||||
"doormile/utils"
|
||||
|
||||
nats "github.com/nats-io/nats.go"
|
||||
)
|
||||
|
||||
func StartBookingWorker() {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
utils.Error("BookingWorker: panic recovered, restarting", "error", r)
|
||||
time.Sleep(5 * time.Second)
|
||||
go StartBookingWorker()
|
||||
}
|
||||
}()
|
||||
|
||||
if db.Js == nil {
|
||||
utils.Warn("BookingWorker: NATS JetStream not available, worker not started")
|
||||
return
|
||||
}
|
||||
|
||||
type consumerDef struct {
|
||||
subject string
|
||||
consumer string
|
||||
}
|
||||
|
||||
consumers := []consumerDef{
|
||||
{"api.v1.bookings.create", "bookings_create"},
|
||||
{"api.v1.bookings.update", "bookings_update"},
|
||||
{"api.v1.bookings.cancel", "bookings_cancel"},
|
||||
}
|
||||
|
||||
type subscription struct {
|
||||
subject string
|
||||
sub *nats.Subscription
|
||||
}
|
||||
|
||||
var subs []subscription
|
||||
for _, c := range consumers {
|
||||
sub, err := db.Js.PullSubscribe(c.subject, c.consumer, nats.BindStream("BOOKINGS"))
|
||||
if err != nil {
|
||||
utils.Error("BookingWorker: subscribe failed", "subject", c.subject, "consumer", c.consumer, "error", err)
|
||||
continue
|
||||
}
|
||||
subs = append(subs, subscription{c.subject, sub})
|
||||
utils.Info("BookingWorker: subscribed", "subject", c.subject, "consumer", c.consumer)
|
||||
}
|
||||
|
||||
if len(subs) == 0 {
|
||||
utils.Error("BookingWorker: no subscriptions established, worker exiting")
|
||||
return
|
||||
}
|
||||
|
||||
utils.Info("BookingWorker: running, waiting for messages...")
|
||||
|
||||
for {
|
||||
for _, s := range subs {
|
||||
msgs, err := s.sub.Fetch(10, nats.MaxWait(1*time.Second))
|
||||
if err != nil {
|
||||
if err == nats.ErrTimeout {
|
||||
continue
|
||||
}
|
||||
utils.Warn("BookingWorker: fetch error", "subject", s.subject, "error", err)
|
||||
time.Sleep(1 * time.Second)
|
||||
continue
|
||||
}
|
||||
for _, msg := range msgs {
|
||||
processMessage(msg)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func processMessage(msg *nats.Msg) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
utils.Error("BookingWorker: panic in message handler", "error", r)
|
||||
msg.Nak()
|
||||
}
|
||||
}()
|
||||
|
||||
var payload map[string]interface{}
|
||||
if err := json.Unmarshal(msg.Data, &payload); err != nil {
|
||||
utils.Error("BookingWorker: invalid JSON", "subject", msg.Subject, "error", err)
|
||||
msg.Term()
|
||||
return
|
||||
}
|
||||
|
||||
utils.Info("BookingWorker: processing message", "subject", msg.Subject, "booking_id", payload["booking_id"])
|
||||
|
||||
var ok bool
|
||||
switch msg.Subject {
|
||||
case "api.v1.bookings.create":
|
||||
ok = handleCreate(payload)
|
||||
case "api.v1.bookings.update":
|
||||
ok = handleUpdate(payload)
|
||||
case "api.v1.bookings.cancel":
|
||||
ok = handleCancel(payload)
|
||||
default:
|
||||
utils.Warn("BookingWorker: no handler for subject", "subject", msg.Subject)
|
||||
msg.Ack()
|
||||
return
|
||||
}
|
||||
|
||||
if ok {
|
||||
msg.Ack()
|
||||
} else {
|
||||
msg.Nak()
|
||||
}
|
||||
}
|
||||
|
||||
func handleCreate(payload map[string]interface{}) bool {
|
||||
bid, ok := extractBookingID(payload)
|
||||
if !ok {
|
||||
utils.Error("BookingWorker: missing booking_id in create payload")
|
||||
return false
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
||||
defer cancel()
|
||||
|
||||
mapping := pickFields(payload, "booking_id", "booking_no", "customer_id",
|
||||
"pickup_address", "pickup_pincode", "delivery_address", "delivery_pincode",
|
||||
"status", "created_at")
|
||||
|
||||
if err := db.Rdb.HSet(ctx, fmt.Sprintf("bookings:%s", bid), mapping).Err(); err != nil {
|
||||
utils.Error("BookingWorker: HSET failed on create", "booking_id", bid, "error", err)
|
||||
return false
|
||||
}
|
||||
|
||||
ctx2, cancel2 := context.WithTimeout(context.Background(), 3*time.Second)
|
||||
defer cancel2()
|
||||
db.Rdb.SAdd(ctx2, "bookings:all", bid)
|
||||
|
||||
if cid := strOf(payload["customer_id"]); cid != "" {
|
||||
ctx3, cancel3 := context.WithTimeout(context.Background(), 3*time.Second)
|
||||
defer cancel3()
|
||||
db.Rdb.SAdd(ctx3, fmt.Sprintf("bookings:customer:%s", cid), bid)
|
||||
}
|
||||
|
||||
utils.Info("BookingWorker: created booking in Redis", "booking_id", bid)
|
||||
return true
|
||||
}
|
||||
|
||||
func handleUpdate(payload map[string]interface{}) bool {
|
||||
bid, ok := extractBookingID(payload)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
||||
defer cancel()
|
||||
|
||||
exists, err := db.Rdb.Exists(ctx, fmt.Sprintf("bookings:%s", bid)).Result()
|
||||
if err != nil || exists == 0 {
|
||||
utils.Warn("BookingWorker: booking not in cache for update", "booking_id", bid)
|
||||
return false
|
||||
}
|
||||
|
||||
ctx2, cancel2 := context.WithTimeout(context.Background(), 3*time.Second)
|
||||
defer cancel2()
|
||||
|
||||
mapping := pickFields(payload, "status", "miler_id", "updated_at")
|
||||
if err := db.Rdb.HSet(ctx2, fmt.Sprintf("bookings:%s", bid), mapping).Err(); err != nil {
|
||||
utils.Error("BookingWorker: HSET failed on update", "booking_id", bid, "error", err)
|
||||
return false
|
||||
}
|
||||
|
||||
utils.Info("BookingWorker: updated booking in Redis", "booking_id", bid)
|
||||
return true
|
||||
}
|
||||
|
||||
func handleCancel(payload map[string]interface{}) bool {
|
||||
bid, ok := extractBookingID(payload)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
||||
defer cancel()
|
||||
|
||||
mapping := pickFields(payload, "cancelled_at")
|
||||
mapping["status"] = "Cancelled"
|
||||
|
||||
if err := db.Rdb.HSet(ctx, fmt.Sprintf("bookings:%s", bid), mapping).Err(); err != nil {
|
||||
utils.Error("BookingWorker: HSET failed on cancel", "booking_id", bid, "error", err)
|
||||
return false
|
||||
}
|
||||
|
||||
utils.Info("BookingWorker: cancelled booking in Redis", "booking_id", bid)
|
||||
return true
|
||||
}
|
||||
|
||||
func extractBookingID(payload map[string]interface{}) (string, bool) {
|
||||
v, exists := payload["booking_id"]
|
||||
if !exists || v == nil {
|
||||
return "", false
|
||||
}
|
||||
s := strOf(v)
|
||||
return s, s != ""
|
||||
}
|
||||
|
||||
func strOf(v interface{}) string {
|
||||
if v == nil {
|
||||
return ""
|
||||
}
|
||||
return fmt.Sprintf("%v", v)
|
||||
}
|
||||
|
||||
func pickFields(payload map[string]interface{}, keys ...string) map[string]interface{} {
|
||||
m := make(map[string]interface{}, len(keys))
|
||||
for _, k := range keys {
|
||||
if v, ok := payload[k]; ok && v != nil {
|
||||
m[k] = strOf(v)
|
||||
}
|
||||
}
|
||||
return m
|
||||
}
|
||||
Reference in New Issue
Block a user