package route import ( "context" "encoding/base64" "encoding/json" "io/ioutil" "net/http" "strings" "forge.cadoles.com/Cadoles/fake-sms/internal/command" "forge.cadoles.com/Cadoles/fake-sms/internal/config" "forge.cadoles.com/Cadoles/fake-sms/internal/model/powow" "forge.cadoles.com/Cadoles/fake-sms/internal/storm" "github.com/pkg/errors" "gitlab.com/wpetit/goweb/cqrs" "gitlab.com/wpetit/goweb/logger" "gitlab.com/wpetit/goweb/middleware/container" "gitlab.com/wpetit/goweb/service" ) type ErrorCode int const ( ErrorCodeInvalidCommand ErrorCode = 99997 ErrorCodeAuthenticationFailure ErrorCode = 99998 ErrorCodeNotEnoughPrivileges ErrorCode = 99999 ErrorCodeTransactionSMSSendMissingSMSID ErrorCode = 1 ErrorCodeTransactionSMSSendMissingMobilePhoneNumber ErrorCode = 2 ErrorCodeTransactionSMSSendInvalidSMSID ErrorCode = 3 ErrorCodeTransactionSMSSendInvalidMobilePhoneNumber ErrorCode = 4 ErrorCodeTransactionSMSSendInvalidCustomData ErrorCode = 5 ErrorCodeTransactionSMSSendInvalidTimeToSend ErrorCode = 6 ErrorCodeTransactionSMSSendAccountSubscribersLimitExceeded ErrorCode = 7 ErrorCodeTransactionSMSSendMobilePhoneNumberCannotBeSaved ErrorCode = 7 ErrorCodeTransactionSMSSendTransactionalIDCannotBeCreated ErrorCode = 9 ErrorCodeTransactionSMSSendSMSSentLimitExceeded ErrorCode = 10 ErrorCodeTransactionSMSUpdateMissingSMSID ErrorCode = 1 ErrorCodeTransactionSMSUpdateInvalidSMSID ErrorCode = 3 ErrorCodeTransactionSMSUpdateInvalidFromName ErrorCode = 4 ErrorCodeTransactionSMSUpdateInvalidLanguage ErrorCode = 7 ) type Command string const ( CommandTransactionalSMSSend = "TransactionalSms.Send" CommandTransactionalSMSCreate = "TransactionalSms.Create" CommandTransactionalSMSUpdate = "TransactionalSms.Update" ) type PowowRequest struct { APIKey string `json:"ApiKey"` Command Command ResponseFormat string Payload map[string]interface{} } type PowowResponse struct { Success bool ErrorCode ErrorCode } func handlePowowEntrypoint(w http.ResponseWriter, r *http.Request) { ctx := r.Context() ctn := container.Must(ctx) conf := config.Must(ctn) data, err := ioutil.ReadAll(r.Body) if err != nil { logger.Error(ctx, "could not read body", logger.E(errors.WithStack(err))) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return } defer r.Body.Close() pr := &PowowRequest{ Payload: make(map[string]interface{}), } if err := json.Unmarshal(data, pr); err != nil { logger.Error(ctx, "could not parse request", logger.E(errors.WithStack(err))) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return } if err := json.Unmarshal(data, &pr.Payload); err != nil { logger.Error(ctx, "could not parse request payload", logger.E(errors.WithStack(err))) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return } // Authenticate user if conf.Powow.APIKey != pr.APIKey { res := &PowowResponse{ Success: false, ErrorCode: ErrorCodeAuthenticationFailure, } sendPowowResponse(w, res) return } // Handle Powow command switch pr.Command { case CommandTransactionalSMSSend: handleTransactionalSMSSend(ctx, ctn, w, pr) return case CommandTransactionalSMSCreate: handleTransactionalSMSCreate(ctx, ctn, w, pr) return case CommandTransactionalSMSUpdate: handleTransactionalSMSUpdate(ctx, ctn, w, pr) return default: res := &PowowResponse{ Success: false, ErrorCode: ErrorCodeInvalidCommand, } sendPowowResponse(w, res) return } } // Mock https://powow4.iroquois.fr/user/docs/api/#send-transactional-sms func handleTransactionalSMSSend(ctx context.Context, ctn *service.Container, w http.ResponseWriter, req *PowowRequest) { bus := cqrs.Must(ctn) db := storm.Must(ctn) smsID, exists, valid := getPowowSMSID(req) if !exists { sendPowowResponse(w, &PowowResponse{ ErrorCode: ErrorCodeTransactionSMSSendMissingSMSID, Success: false, }) return } if !valid { sendPowowResponse(w, &PowowResponse{ ErrorCode: ErrorCodeTransactionSMSSendInvalidSMSID, Success: false, }) return } smsTmpl := &powow.SMSTemplate{} if err := db.One("ID", smsID, smsTmpl); err != nil { if errors.Is(err, storm.ErrNotFound) { sendPowowResponse(w, &PowowResponse{ ErrorCode: ErrorCodeTransactionSMSSendInvalidSMSID, Success: false, }) return } panic(errors.Wrap(err, "could not retrieve sms template")) } customData := make(map[string]interface{}) var ok bool rawCustomData, exists := req.Payload["CustomData"] if exists { customData, ok = rawCustomData.(map[string]interface{}) if !ok { sendPowowResponse(w, &PowowResponse{ ErrorCode: ErrorCodeTransactionSMSSendInvalidCustomData, Success: false, }) return } } body, err := createSMSBody(smsTmpl.Content, customData) if err != nil { panic(errors.Wrap(err, "could not generate sms body")) } req.Payload["_Template"] = smsTmpl storeSMS := &command.StoreSMSRequest{ From: smsTmpl.FromName, Body: body, Recipient: req.Payload["MobilePhoneNumber"].(string), Metadata: req.Payload, } _, err = bus.Exec(ctx, storeSMS) if err != nil { panic(errors.Wrap(err, "could not store sms")) } res := &struct { PowowResponse TransactionalID int }{ PowowResponse: PowowResponse{ Success: true, ErrorCode: 0, }, TransactionalID: 0, } sendPowowResponse(w, res) } func createSMSBody(template string, customData map[string]interface{}) (string, error) { content := template for k, v := range customData { decoded, err := base64.StdEncoding.DecodeString(v.(string)) if err != nil { return "", errors.WithStack(err) } key := "%Subscriber:" + k + "%" content = strings.ReplaceAll(content, key, string(decoded)) } return content, nil } // Mock https://powow4.iroquois.fr/user/docs/api/#create-transactional-sms func handleTransactionalSMSCreate(ctx context.Context, ctn *service.Container, w http.ResponseWriter, req *PowowRequest) { db := storm.Must(ctn) smsTemplate := &powow.SMSTemplate{} if err := db.Save(smsTemplate); err != nil { panic(errors.Wrap(err, "could not save sms template")) } res := &struct { PowowResponse SmsID int }{ PowowResponse: PowowResponse{ Success: true, ErrorCode: 0, }, SmsID: smsTemplate.ID, } sendPowowResponse(w, res) } // Mock https://powow4.iroquois.fr/user/docs/api/#update-transactional-sms func handleTransactionalSMSUpdate(ctx context.Context, ctn *service.Container, w http.ResponseWriter, req *PowowRequest) { smsID, exists, valid := getPowowSMSID(req) if !exists { sendPowowResponse(w, &PowowResponse{ ErrorCode: ErrorCodeTransactionSMSUpdateMissingSMSID, Success: false, }) return } if !valid { sendPowowResponse(w, &PowowResponse{ ErrorCode: ErrorCodeTransactionSMSUpdateInvalidSMSID, Success: false, }) return } db := storm.Must(ctn) smsTmpl := &powow.SMSTemplate{} if err := db.One("ID", smsID, smsTmpl); err != nil { if errors.Is(err, storm.ErrNotFound) { sendPowowResponse(w, &PowowResponse{ ErrorCode: ErrorCodeTransactionSMSUpdateInvalidSMSID, Success: false, }) return } panic(errors.Wrap(err, "could not retrieve sms template")) } rawContent, exists := req.Payload["Content"] if exists { content, ok := rawContent.(string) if ok { smsTmpl.Content = content } } rawLanguage, exists := req.Payload["Language"] if exists { language, ok := rawLanguage.(string) if ok { if !contains(language, "en", "fr", "it", "es", "de", "pt", "pl", "zh") { sendPowowResponse(w, &PowowResponse{ ErrorCode: ErrorCodeTransactionSMSUpdateInvalidLanguage, Success: false, }) return } smsTmpl.Language = language } } rawFromName, exists := req.Payload["FromName"] if exists { fromName, ok := rawFromName.(string) if ok { smsTmpl.FromName = fromName } } rawShortLink, exists := req.Payload["ShortLink"] if exists { shortLink, ok := rawShortLink.(float64) if ok { if shortLink == 1.0 { smsTmpl.ShortLink = 1 } else { smsTmpl.ShortLink = 0 } } } rawSMSName, exists := req.Payload["SmsName"] if exists { smsName, ok := rawSMSName.(string) if ok { smsTmpl.SmsName = smsName } } rawShortLink, exists = req.Payload["ShortLink"] if exists { shortLink, ok := rawShortLink.(float64) if ok { if shortLink == 1.0 { smsTmpl.ShortLink = 1 } else { smsTmpl.ShortLink = 0 } } } if err := db.Save(smsTmpl); err != nil { panic(errors.Wrap(err, "could not save sms template")) } sendPowowResponse(w, &PowowResponse{ ErrorCode: 0, Success: true, }) } func sendPowowResponse(w http.ResponseWriter, res interface{}) { w.Header().Add("Content-Type", "application/json") encoder := json.NewEncoder(w) encoder.SetIndent("", " ") if err := encoder.Encode(res); err != nil { panic(errors.WithStack(err)) } } func getPowowSMSID(req *PowowRequest) (smsID int, exists bool, valid bool) { rawSMSID, exists := req.Payload["SmsID"] if !exists { return -1, false, false } smsIDFloat, ok := rawSMSID.(float64) if !ok { return -1, true, false } return int(smsIDFloat), true, true }