github.com/oinume/lekcije@v0.0.0-20231017100347-5b4c5eb6ab24/backend/interface/http/webhook.go (about) 1 package http 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "net/http" 7 "strconv" 8 "time" 9 10 "github.com/jinzhu/gorm" 11 "go.uber.org/zap" 12 "go.uber.org/zap/zapcore" 13 14 "github.com/oinume/lekcije/backend/event_logger" 15 "github.com/oinume/lekcije/backend/model" 16 "github.com/oinume/lekcije/backend/model2" 17 ) 18 19 type SendGridEventValues struct { 20 Timestamp int64 `json:"timestamp"` 21 Event string `json:"event"` 22 Email string `json:"email"` 23 SGEventID string `json:"sg_event_id"` 24 UserAgent string `json:"useragent"` 25 URL string `json:"url"` // Only when event=click 26 // Custom args 27 EmailType string `json:"email_type"` 28 UserID string `json:"user_id"` 29 TeacherIDs string `json:"teacher_ids"` 30 } 31 32 func (v *SendGridEventValues) GetUserID() uint32 { 33 if id, err := strconv.ParseUint(v.UserID, 10, 32); err == nil { 34 return uint32(id) 35 } 36 return 0 37 } 38 39 func (v *SendGridEventValues) IsEventClick() bool { 40 return v.Event == "click" 41 } 42 43 func (v *SendGridEventValues) IsEventOpen() bool { 44 return v.Event == "open" 45 } 46 47 func (v *SendGridEventValues) LogToFile(logger *zap.Logger) { 48 fields := []zapcore.Field{ 49 zap.Time("timestamp", time.Unix(v.Timestamp, 0)), 50 zap.String("sgEventID", v.SGEventID), 51 zap.String("email", v.Email), 52 } 53 54 var userID uint32 55 if id, err := strconv.ParseUint(v.UserID, 10, 32); err == nil { 56 userID = uint32(id) 57 } 58 if v.EmailType != "" { 59 fields = append(fields, zap.String("emailType", v.EmailType)) 60 } 61 if v.TeacherIDs != "" { 62 fields = append(fields, zap.String("teacherIDs", v.TeacherIDs)) 63 } 64 if v.IsEventOpen() || v.IsEventClick() { 65 fields = append(fields, zap.String("userAgent", v.UserAgent)) 66 } 67 if v.IsEventClick() { 68 fields = append(fields, zap.String("url", v.URL)) 69 } 70 71 event_logger.New(logger).Log( 72 userID, 73 model2.GAMeasurementEventCategoryEmail, 74 v.Event, 75 fields..., 76 ) 77 } 78 79 func (v *SendGridEventValues) LogToDB(db *gorm.DB) error { 80 eventLogEmail := &model.EventLogEmail{ 81 Datetime: time.Unix(v.Timestamp, 0), 82 Event: v.Event, 83 EmailType: v.EmailType, 84 UserID: v.GetUserID(), 85 UserAgent: v.UserAgent, 86 TeacherIDs: v.TeacherIDs, 87 URL: v.URL, 88 } 89 if v.EmailType == "" { 90 eventLogEmail.EmailType = model2.EmailTypeNewLessonNotifier 91 } 92 return model.NewEventLogEmailService(db).Create(eventLogEmail) 93 } 94 95 func (s *server) postAPISendGridEventWebhookHandler() http.HandlerFunc { 96 return func(w http.ResponseWriter, r *http.Request) { 97 s.postAPISendGridEventWebhook(w, r) 98 } 99 } 100 101 func (s *server) postAPISendGridEventWebhook(w http.ResponseWriter, r *http.Request) { 102 values := make([]SendGridEventValues, 0, 1000) 103 if err := json.NewDecoder(r.Body).Decode(&values); err != nil { 104 internalServerError(r.Context(), s.errorRecorder, w, err, 0) 105 return 106 } 107 defer r.Body.Close() 108 // datetime, user_id, event(enum), event_id(varchar), text 109 110 userService := model.NewUserService(s.db) 111 for _, v := range values { 112 v.LogToFile(s.accessLogger) 113 if err := v.LogToDB(s.db); err != nil { 114 internalServerError(r.Context(), s.errorRecorder, w, err, 0) 115 return 116 } 117 if v.EmailType == model2.EmailTypeNewLessonNotifier && v.IsEventOpen() { 118 if err := userService.UpdateOpenNotificationAt(v.GetUserID(), time.Unix(v.Timestamp, 0).UTC()); err != nil { 119 internalServerError(r.Context(), s.errorRecorder, w, err, 0) 120 return 121 } 122 } 123 } 124 125 w.Header().Set("Content-Type", "text/plain; charset=utf-8") 126 w.WriteHeader(http.StatusOK) 127 _, _ = fmt.Fprint(w, "OK") 128 }