package route import ( "context" "encoding/base64" "encoding/json" "net/http" "strconv" "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/davecgh/go-spew/spew" php "github.com/kovetskiy/go-php-serialize" "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 map[int]ErrorCode } type PowowResponseSuccess struct { Success bool ErrorCode ErrorCode } func handlePowowEntrypoint(w http.ResponseWriter, r *http.Request) { ctx := r.Context() ctn := container.Must(ctx) conf := config.Must(ctn) err := r.ParseForm() if err != nil { logger.Error(ctx, "could not parse form", logger.E(errors.WithStack(err))) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) } pr := &PowowRequest{ Payload: make(map[string]interface{}), } pr.APIKey = r.FormValue("APIKey") pr.Command = Command(r.FormValue("Command")) pr.ResponseFormat = r.FormValue("ResponseFormat") if pr.Command == CommandTransactionalSMSUpdate { smsid, err := strconv.ParseFloat(r.FormValue("SmsID"), 64) if err != nil { logger.Error(ctx, "could not convert SmsID to float", logger.E(errors.WithStack(err))) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) } pr.Payload = map[string]interface{}{ "SmsID": smsid, "Content": r.FormValue("Content"), "ShortLink": r.FormValue("ShortLink"), "Language": r.FormValue("Language"), "FromName": r.FormValue("FromName"), "SmsName": r.FormValue("SmsName"), } } if pr.Command == CommandTransactionalSMSSend { customData, err := php.Decode(r.FormValue("CustomData")) if err != nil { logger.Error(ctx, "could not cunserialized custom data", logger.E(errors.WithStack(err))) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) } var smsid float64 if r.FormValue("SmsID") != "" { smsid, err = strconv.ParseFloat(r.FormValue("SmsID"), 64) if err != nil { logger.Error(ctx, "could not convert SmsID to float", logger.E(errors.WithStack(err))) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) } } pr.Payload = map[string]interface{}{ "SmsID": smsid, "MobilePhoneNumber": strings.TrimSpace(r.FormValue("MobilePhoneNumber")), "TimeToSend": r.FormValue("TimeToSend"), "CustomData": customData, } } // Authenticate user if conf.Powow.APIKey != pr.APIKey { res := &PowowResponse{ Success: false, ErrorCode: map[int]ErrorCode{0: ErrorCodeAuthenticationFailure}, } sendPowowResponse(w, res) return } // If phone number must return an error spew.Dump(conf.Powow.InvalidPhoneNumber) spew.Dump(pr.Payload["MobilePhoneNumber"]) if conf.Powow.InvalidPhoneNumber == pr.Payload["MobilePhoneNumber"] { res := &PowowResponse{ Success: false, ErrorCode: map[int]ErrorCode{0: ErrorCodeTransactionSMSSendInvalidMobilePhoneNumber}, } 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: map[int]ErrorCode{0: 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: map[int]ErrorCode{0: ErrorCodeTransactionSMSSendMissingSMSID}, Success: false, }) return } if !valid { sendPowowResponse(w, &PowowResponse{ ErrorCode: map[int]ErrorCode{0: 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: map[int]ErrorCode{0: ErrorCodeTransactionSMSSendInvalidSMSID}, Success: false, }) return } panic(errors.Wrap(err, "could not retrieve sms template")) } customData := make(map[string]interface{}) rawCustomData, exists := req.Payload["CustomData"] if exists { cData, ok := rawCustomData.(map[interface{}]interface{}) if !ok { sendPowowResponse(w, &PowowResponse{ ErrorCode: map[int]ErrorCode{0: ErrorCodeTransactionSMSSendInvalidCustomData}, Success: false, }) return } for k, v := range cData { customData[k.(string)] = v } req.Payload["CustomData"] = customData } 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 { PowowResponseSuccess TransactionalID int }{ PowowResponseSuccess: PowowResponseSuccess{ Success: true, ErrorCode: 0, }, TransactionalID: 0, } sendPowowResponse(w, res) } func createSMSBody(template string, customData map[string]interface{}) (string, error) { content := template spew.Dump(customData) 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 { PowowResponseSuccess SmsID int }{ PowowResponseSuccess: PowowResponseSuccess{ 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: map[int]ErrorCode{0: ErrorCodeTransactionSMSUpdateMissingSMSID}, Success: false, }) return } if !valid { sendPowowResponse(w, &PowowResponse{ ErrorCode: map[int]ErrorCode{0: 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: map[int]ErrorCode{0: 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: map[int]ErrorCode{0: 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, &PowowResponseSuccess{ 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 }