github.com/cozy/cozy-stack@v0.0.0-20240603063001-31110fa4cae1/worker/sms/sms.go (about) 1 package sms 2 3 import ( 4 "bytes" 5 "encoding/json" 6 "errors" 7 "fmt" 8 "net/http" 9 "runtime" 10 "time" 11 12 "github.com/cozy/cozy-stack/model/contact" 13 "github.com/cozy/cozy-stack/model/instance" 14 "github.com/cozy/cozy-stack/model/job" 15 "github.com/cozy/cozy-stack/model/notification/center" 16 "github.com/cozy/cozy-stack/pkg/config/config" 17 "github.com/cozy/cozy-stack/pkg/logger" 18 "github.com/cozy/cozy-stack/pkg/mail" 19 "github.com/labstack/echo/v4" 20 ) 21 22 func init() { 23 job.AddWorker(&job.WorkerConfig{ 24 WorkerType: "sms", 25 Concurrency: runtime.NumCPU(), 26 MaxExecCount: 1, 27 Timeout: 10 * time.Second, 28 Reserved: true, 29 WorkerFunc: Worker, 30 }) 31 } 32 33 // Worker is the worker that send SMS. 34 func Worker(ctx *job.TaskContext) error { 35 var msg center.SMS 36 if err := ctx.UnmarshalMessage(&msg); err != nil { 37 return err 38 } 39 40 err := sendSMS(ctx, &msg) 41 if err != nil { 42 ctx.Logger().Warnf("could not send SMS notification: %s", err) 43 sendFallbackMail(ctx.Instance, msg.MailFallback) 44 } 45 return err 46 } 47 48 func sendSMS(ctx *job.TaskContext, msg *center.SMS) error { 49 inst := ctx.Instance 50 cfg, err := getConfig(inst) 51 if err != nil { 52 return err 53 } 54 number, err := getMyselfPhoneNumber(inst) 55 if err != nil { 56 return err 57 } 58 switch cfg.Provider { 59 case "api_sen": 60 log := ctx.Logger() 61 return sendSenAPI(cfg, msg, number, log) 62 default: 63 return errors.New("Unknown provider for sending SMS") 64 } 65 } 66 67 func sendSenAPI(cfg *config.SMS, msg *center.SMS, number string, log logger.Logger) error { 68 payload, err := json.Marshal(map[string]interface{}{ 69 "content": msg.Message, 70 "receiver": []interface{}{number}, 71 }) 72 if err != nil { 73 return err 74 } 75 req, err := http.NewRequest(http.MethodPost, cfg.URL, bytes.NewReader(payload)) 76 if err != nil { 77 return err 78 } 79 req.Header.Add(echo.HeaderAccept, echo.MIMEApplicationJSON) 80 req.Header.Add(echo.HeaderContentType, echo.MIMEApplicationJSON) 81 req.Header.Add(echo.HeaderAuthorization, "Bearer "+cfg.Token) 82 res, err := http.DefaultClient.Do(req) 83 if err != nil { 84 return err 85 } 86 defer res.Body.Close() 87 if res.StatusCode == 200 { 88 return nil 89 } 90 91 var body map[string]interface{} 92 if err := json.NewDecoder(res.Body).Decode(&body); err == nil { 93 if t, ok := body["type"].(string); ok { 94 log = log.WithField("type", t) 95 } 96 if detail, ok := body["detail"].(string); ok { 97 log = log.WithField("detail", detail) 98 } 99 log.WithField("status_code", res.StatusCode).Warnf("Cannot send SMS") 100 } 101 return fmt.Errorf("Unexpected status code: %d", res.StatusCode) 102 } 103 104 func getMyselfPhoneNumber(inst *instance.Instance) (string, error) { 105 myself, err := contact.GetMyself(inst) 106 if err != nil { 107 return "", err 108 } 109 number := myself.PrimaryPhoneNumber() 110 if number == "" { 111 return "", errors.New("No phone number in the myself contact document") 112 } 113 return number, nil 114 } 115 116 func getConfig(inst *instance.Instance) (*config.SMS, error) { 117 cfg, ok := config.GetConfig().Notifications.Contexts[inst.ContextName] 118 if !ok { 119 return nil, errors.New("SMS not configured on this context") 120 } 121 return &cfg, nil 122 } 123 124 func sendFallbackMail(inst *instance.Instance, email *mail.Options) { 125 if inst == nil || email == nil { 126 return 127 } 128 msg, err := job.NewMessage(&email) 129 if err != nil { 130 return 131 } 132 _, _ = job.System().PushJob(inst, &job.JobRequest{ 133 WorkerType: "sendmail", 134 Message: msg, 135 }) 136 }