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  }