diff --git a/cmd/smsgateway/main.go b/cmd/smsgateway/main.go index 3805c5d..89fa1f9 100644 --- a/cmd/smsgateway/main.go +++ b/cmd/smsgateway/main.go @@ -4,6 +4,7 @@ import ( "log" "bitbucket.org/capcom6/smsgatewaybackend/internal/config" + _ "bitbucket.org/capcom6/smsgatewaybackend/internal/smsgateway" microbase "bitbucket.org/soft-c/gomicrobase" ) diff --git a/go.mod b/go.mod index 19d10ff..ef50397 100644 --- a/go.mod +++ b/go.mod @@ -3,22 +3,24 @@ module bitbucket.org/capcom6/smsgatewaybackend go 1.18 require ( - bitbucket.org/soft-c/gomicrobase v1.1.2-0.20221004142125-98b1dad322bc + bitbucket.org/soft-c/gohelpers v1.0.3-0.20221006072847-aee2524b5192 + bitbucket.org/soft-c/gomicrobase v1.1.2-0.20221006080527-7eeddcd13770 + github.com/go-playground/validator/v10 v10.11.0 + github.com/gofiber/fiber/v2 v2.38.1 + github.com/jaevor/go-nanoid v1.3.0 github.com/joho/godotenv v1.4.0 + gorm.io/gorm v1.23.8 ) require ( - bitbucket.org/soft-c/gohelpers v1.0.2 // indirect github.com/andybalholm/brotli v1.0.4 // indirect github.com/ansrivas/fiberprometheus/v2 v2.4.1 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.1.2 // indirect github.com/go-playground/locales v0.14.0 // indirect github.com/go-playground/universal-translator v0.18.0 // indirect - github.com/go-playground/validator/v10 v10.11.0 // indirect github.com/go-sql-driver/mysql v1.6.0 // indirect github.com/gofiber/adaptor/v2 v2.1.27 // indirect - github.com/gofiber/fiber/v2 v2.38.1 // indirect github.com/golang/protobuf v1.5.2 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect @@ -38,5 +40,4 @@ require ( google.golang.org/protobuf v1.28.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect gorm.io/driver/mysql v1.3.6 // indirect - gorm.io/gorm v1.23.8 // indirect ) diff --git a/go.sum b/go.sum index 37a3332..7d25c7c 100644 --- a/go.sum +++ b/go.sum @@ -1,9 +1,7 @@ -bitbucket.org/soft-c/gohelpers v1.0.2 h1:WP6g2V5jlyoY8ksz0N9VAjcbZeuOAupZSLCEj1kab+s= -bitbucket.org/soft-c/gohelpers v1.0.2/go.mod h1:ltWoO1ZiJoATBjBw6AnV3Qf91qrp2IQCmYZlL/CwKSs= -bitbucket.org/soft-c/gomicrobase v1.1.1 h1:UMgeksfIgR4st2sfITl8GMYwn7HQVoizoygb/yYQNeM= -bitbucket.org/soft-c/gomicrobase v1.1.1/go.mod h1:+Jh2pL0WUtAGJu1twMdQCnxeRXjSbrra4RecZMoe/bw= -bitbucket.org/soft-c/gomicrobase v1.1.2-0.20221004142125-98b1dad322bc h1:Bsq8Uuf6rkg5BYnG9usB0hmCQq5JKAvN2GWW9JhFMUU= -bitbucket.org/soft-c/gomicrobase v1.1.2-0.20221004142125-98b1dad322bc/go.mod h1:Pvv07ulIXfDG5us/ZG5Of7vvjY+tq3fkcrY/MmJUEiE= +bitbucket.org/soft-c/gohelpers v1.0.3-0.20221006072847-aee2524b5192 h1:Mmoyu4csqhWcxCQc0qhSyd9dIt1BCxJ1OYOZA4D/S2U= +bitbucket.org/soft-c/gohelpers v1.0.3-0.20221006072847-aee2524b5192/go.mod h1:AlZzQ1xftfS8rWSwjwTj3/MPMFFfgfNCP3b7xLoEeKM= +bitbucket.org/soft-c/gomicrobase v1.1.2-0.20221006080527-7eeddcd13770 h1:IAEOe5Uv9+zQEaOuT8eHJPUfS64Nc0I9ufq7afFXKUk= +bitbucket.org/soft-c/gomicrobase v1.1.2-0.20221006080527-7eeddcd13770/go.mod h1:Pvv07ulIXfDG5us/ZG5Of7vvjY+tq3fkcrY/MmJUEiE= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= @@ -95,8 +93,6 @@ github.com/gofiber/adaptor/v2 v2.1.25/go.mod h1:gOxtwMVqUStB5goAYtKd+hSvGupdd+aR github.com/gofiber/adaptor/v2 v2.1.27 h1:I3Xjn1qaH6W09shPi8F/kz98AncwbVYWFBV4niBBC0Q= github.com/gofiber/adaptor/v2 v2.1.27/go.mod h1:xcc+UqppJ+XfYVcqxp2fHyoqKctQxxvZ/IyUybNSIlQ= github.com/gofiber/fiber/v2 v2.36.0/go.mod h1:tgCr+lierLwLoVHHO/jn3Niannv34WRkQETU8wiL9fQ= -github.com/gofiber/fiber/v2 v2.37.0 h1:KVboSQ7e0wDbSFXNjXKqoigwp9HYUqgWn4uGFaUO1P8= -github.com/gofiber/fiber/v2 v2.37.0/go.mod h1:xm3pDGlfE1xqVKb77iH8weLU0FFoTeWeK3nbiYM2Nh0= github.com/gofiber/fiber/v2 v2.37.1/go.mod h1:j3UslgQeJQP3mNhBxHnLLE8TPqA1Fd/lrl4gD25rRUY= github.com/gofiber/fiber/v2 v2.38.1 h1:GEQ/Yt3Wsf2a30iTqtLXlBYJZso0JXPovt/tmj5H9jU= github.com/gofiber/fiber/v2 v2.38.1/go.mod h1:t0NlbaXzuGH7I+7M4paE848fNWInZ7mfxI/Er1fTth8= @@ -140,6 +136,7 @@ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= @@ -156,6 +153,8 @@ github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5m github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/jaevor/go-nanoid v1.3.0 h1:nD+iepesZS6pr3uOVf20vR9GdGgJW1HPaR46gtrxzkg= +github.com/jaevor/go-nanoid v1.3.0/go.mod h1:SI+jFaPuddYkqkVQoNGHs81navCtH388TcrH0RqFKgY= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= @@ -174,8 +173,6 @@ github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7V github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.15.0/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= -github.com/klauspost/compress v1.15.9 h1:wKRjX6JRtDdrE9qwa4b/Cip7ACOshUI4smpCQanqjSY= -github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU= github.com/klauspost/compress v1.15.11 h1:Lcadnb3RKGin4FYM/orgq0qde+nc15E5Cbqg4B9Sx9c= github.com/klauspost/compress v1.15.11/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= @@ -246,12 +243,11 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasthttp v1.38.0/go.mod h1:t/G+3rLek+CyY9bnIE+YlMRddxVAAGjhxndDB4i4C0I= -github.com/valyala/fasthttp v1.39.0/go.mod h1:t/G+3rLek+CyY9bnIE+YlMRddxVAAGjhxndDB4i4C0I= github.com/valyala/fasthttp v1.40.0 h1:CRq/00MfruPGFLTQKY8b+8SfdK60TxNztjRMnH0t1Yc= github.com/valyala/fasthttp v1.40.0/go.mod h1:t/G+3rLek+CyY9bnIE+YlMRddxVAAGjhxndDB4i4C0I= github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8= @@ -391,8 +387,6 @@ golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220907062415-87db552b00fd h1:AZeIEzg+8RCELJYq8w+ODLVxFgLMMigSwO/ffKPEd9U= -golang.org/x/sys v0.0.0-20220907062415-87db552b00fd/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec h1:BkDtF2Ih9xZ7le9ndzTA7KJow28VbQW3odyk/8drmuI= golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= diff --git a/internal/smsgateway/app.go b/internal/smsgateway/app.go new file mode 100644 index 0000000..9b76199 --- /dev/null +++ b/internal/smsgateway/app.go @@ -0,0 +1,12 @@ +package smsgateway + +import ( + "bitbucket.org/capcom6/smsgatewaybackend/internal/smsgateway/handlers" + "bitbucket.org/capcom6/smsgatewaybackend/internal/smsgateway/models" + microbase "bitbucket.org/soft-c/gomicrobase" +) + +func init() { + microbase.RegisterMigration(models.Migrate) + microbase.RegisterHandlers(handlers.Register) +} diff --git a/internal/smsgateway/handlers/3rdparty.go b/internal/smsgateway/handlers/3rdparty.go new file mode 100644 index 0000000..0fcf5e5 --- /dev/null +++ b/internal/smsgateway/handlers/3rdparty.go @@ -0,0 +1,29 @@ +package handlers + +import ( + microbase "bitbucket.org/soft-c/gomicrobase" + "github.com/gofiber/fiber/v2" + "github.com/gofiber/fiber/v2/middleware/basicauth" +) + +type thirdPartyHandler struct { + microbase.Handler +} + +func (h *thirdPartyHandler) postMessage(c *fiber.Ctx) error { + return fiber.ErrNotImplemented +} + +func (h *thirdPartyHandler) register(router fiber.Router) { + router.Use(basicauth.New(basicauth.Config{ + Authorizer: func(username string, password string) bool { + return len(username) > 0 && len(password) > 0 + }, + })) + + router.Post("/message", h.postMessage) +} + +func newThirdPartyHandler() *thirdPartyHandler { + return &thirdPartyHandler{} +} diff --git a/internal/smsgateway/handlers/handlers.go b/internal/smsgateway/handlers/handlers.go new file mode 100644 index 0000000..14d24aa --- /dev/null +++ b/internal/smsgateway/handlers/handlers.go @@ -0,0 +1,24 @@ +package handlers + +import ( + "bitbucket.org/capcom6/smsgatewaybackend/internal/smsgateway/repositories" + "bitbucket.org/capcom6/smsgatewaybackend/internal/smsgateway/services" + "github.com/go-playground/validator/v10" + "github.com/gofiber/fiber/v2" + "gorm.io/gorm" +) + +func Register(router fiber.Router, db *gorm.DB) error { + users := repositories.NewUsersRepository(db) + devices := repositories.NewDevicesRepository(db) + messages := repositories.NewMessagesRepository(db) + + validator := validator.New() + authSvc := services.NewAuthService(users, devices) + messagesSvc := services.NewMessagesService(messages) + + newMobileHandler(validator, authSvc, messagesSvc).register(router.Group("/mobile/v1")) + newThirdPartyHandler().register(router.Group("/3rdparty/v1")) + + return nil +} diff --git a/internal/smsgateway/handlers/log.go b/internal/smsgateway/handlers/log.go new file mode 100644 index 0000000..cf3e4b5 --- /dev/null +++ b/internal/smsgateway/handlers/log.go @@ -0,0 +1,16 @@ +package handlers + +import ( + "fmt" + l "log" + "os" + "reflect" + "strings" +) + +type empty struct{} + +var packageName = strings.Split(reflect.TypeOf(empty{}).PkgPath(), "/") +var logPrefix = fmt.Sprintf("[%s] ", packageName[len(packageName)-1]) +var log = l.New(os.Stdout, logPrefix, l.Ldate|l.Ltime|l.Lshortfile) +var errorLog = l.New(os.Stderr, logPrefix, l.Ldate|l.Ltime|l.Lshortfile) diff --git a/internal/smsgateway/handlers/mobile.go b/internal/smsgateway/handlers/mobile.go new file mode 100644 index 0000000..5f8de26 --- /dev/null +++ b/internal/smsgateway/handlers/mobile.go @@ -0,0 +1,113 @@ +package handlers + +import ( + "fmt" + "strings" + + "bitbucket.org/capcom6/smsgatewaybackend/internal/smsgateway/models" + "bitbucket.org/capcom6/smsgatewaybackend/internal/smsgateway/services" + "bitbucket.org/capcom6/smsgatewaybackend/pkg/smsgateway" + "bitbucket.org/soft-c/gohelpers/pkg/fiber/middleware/apikey" + microbase "bitbucket.org/soft-c/gomicrobase" + "github.com/go-playground/validator/v10" + "github.com/gofiber/fiber/v2" + "github.com/jaevor/go-nanoid" +) + +type mobileHandler struct { + microbase.Handler + + authSvc *services.AuthService + messagesSvc *services.MessagesService + + idGen func() string +} + +func (h *mobileHandler) postRegister(c *fiber.Ctx) error { + req := smsgateway.MobileRegisterRequest{} + + if err := h.BodyParserValidator(c, &req); err != nil { + return fiber.NewError(fiber.StatusBadRequest, err.Error()) + } + + id := h.idGen() + login := strings.ToUpper(id[:6]) + password := strings.ToLower(id[7:]) + + user, err := h.authSvc.RegisterUser(login, password) + if err != nil { + return fmt.Errorf("can't create user: %w", err) + } + + device, err := h.authSvc.RegisterDevice(user.ID, req.Name, req.PushToken) + if err != nil { + return fmt.Errorf("can't register device: %w", err) + } + + return c.Status(fiber.StatusCreated).JSON(smsgateway.MobileRegisterResponse{ + Id: device.ID, + Token: device.AuthToken, + Login: login, + Password: password, + }) +} + +func (h *mobileHandler) getMessage(device models.Device, c *fiber.Ctx) error { + messages, err := h.messagesSvc.SelectPending(device.ID) + if err != nil { + return fmt.Errorf("can't get messages: %w", err) + } + + return c.JSON(messages) +} + +func (h *mobileHandler) patchMessage(device models.Device, c *fiber.Ctx) error { + req := []smsgateway.MessageState{} + if err := c.BodyParser(&req); err != nil { + return fiber.NewError(fiber.StatusBadRequest, err.Error()) + } + + if err := h.Validator.Var(req, "required,dive"); err != nil { + return fiber.NewError(fiber.StatusBadRequest, err.Error()) + } + + return fiber.ErrNotImplemented +} + +func (h *mobileHandler) authorize(handler func(models.Device, *fiber.Ctx) error) fiber.Handler { + return func(c *fiber.Ctx) error { + token := c.Locals("token").(string) + + device, err := h.authSvc.AuthorizeDevice(token) + if err != nil { + errorLog.Println(err) + return fiber.ErrUnauthorized + } + + return handler(device, c) + } +} + +func (h *mobileHandler) register(router fiber.Router) { + router.Post("/register", h.postRegister) + + router.Use(apikey.New(apikey.Config{ + Authorizer: func(token string) bool { + return len(token) > 0 + }, + })) + + router.Get("/message", h.authorize(h.getMessage)) + router.Patch("/message", h.authorize(h.patchMessage)) +} + +func newMobileHandler(validator *validator.Validate, authSvc *services.AuthService, messagesSvc *services.MessagesService) *mobileHandler { + idGen, _ := nanoid.Standard(21) + + return &mobileHandler{ + Handler: microbase.Handler{Validator: validator}, + authSvc: authSvc, + messagesSvc: messagesSvc, + idGen: idGen, + } +} diff --git a/internal/smsgateway/models/models.go b/internal/smsgateway/models/models.go new file mode 100644 index 0000000..d460d07 --- /dev/null +++ b/internal/smsgateway/models/models.go @@ -0,0 +1,62 @@ +package models + +import ( + "time" + + "gorm.io/gorm" +) + +type MessageState string + +const ( + MessageStatePending MessageState = "Pending" + MessageStateSent MessageState = "Sent" + MessageStateDelivered MessageState = "Delivered" + MessageStateFailed MessageState = "Failed" +) + +type TimedModel struct { + CreatedAt time.Time `gorm:"not null;default:CURRENT_TIMESTAMP(3)"` + UpdatedAt time.Time `gorm:"not null;default:CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3)"` + DeletedAt gorm.DeletedAt +} + +type User struct { + ID string `gorm:"primaryKey;type:varchar(32)"` + PasswordHash string `gorm:"not null;type:varchar(72)"` + Devices []Device `gorm:"foreignKey:UserID;constraint:OnDelete:CASCADE"` + + TimedModel +} + +type Device struct { + ID string `gorm:"primaryKey;type:char(21)"` + Name string `gorm:"type:varchar(128)"` + AuthToken string `gorm:"not null;uniqueIndex;type:char(21)"` + PushToken string `gorm:"type:varchar(256)"` + + UserID string `gorm:"not null;type:varchar(32)"` + + TimedModel +} + +type Message struct { + ID uint64 `gorm:"primaryKey;type:BIGINT UNSIGNED;autoIncrement"` + DeviceID string `gorm:"not null;type:char(21);uniqueIndex:unq_messages_device_id;index:idx_messages_device_state"` + ExtID string `gorm:"not null;type:varchar(36);uniqueIndex:unq_messages_device_id"` + Message string `gorm:"not null;type:tinytext"` + State MessageState `gorm:"not null;type:enum('Pending','Sent','Delivered','Failed');default:Pending;index:idx_messages_device_state"` + + Device Device `gorm:"foreignKey:DeviceID;constraint:OnDelete:CASCADE"` + Recipients []MessageRecipient `gorm:"foreignKey:MessageID;constraint:OnDelete:CASCADE"` +} + +type MessageRecipient struct { + MessageID uint64 `gorm:"primaryKey;type:BIGINT UNSIGNED"` + PhoneNumber string `gorm:"primaryKey;type:char(11)"` + State MessageState `gorm:"not null;type:enum('Pending','Sent','Delivered','Failed');default:Pending"` +} + +func Migrate(db *gorm.DB) error { + return db.AutoMigrate(&User{}, &Device{}, &Message{}, &MessageRecipient{}) +} diff --git a/internal/smsgateway/repositories/devices.go b/internal/smsgateway/repositories/devices.go new file mode 100644 index 0000000..f8e434c --- /dev/null +++ b/internal/smsgateway/repositories/devices.go @@ -0,0 +1,30 @@ +package repositories + +import ( + "bitbucket.org/capcom6/smsgatewaybackend/internal/smsgateway/models" + "gorm.io/gorm" +) + +var ( + ErrDeviceNotFound = gorm.ErrRecordNotFound +) + +type DevicesRepository struct { + db *gorm.DB +} + +func (r *DevicesRepository) GetByToken(token string) (models.Device, error) { + device := models.Device{} + + return device, r.db.Where("auth_token = ?", token).Take(&device).Error +} + +func (r *DevicesRepository) Insert(device *models.Device) error { + return r.db.Create(device).Error +} + +func NewDevicesRepository(db *gorm.DB) *DevicesRepository { + return &DevicesRepository{ + db: db, + } +} diff --git a/internal/smsgateway/repositories/messages.go b/internal/smsgateway/repositories/messages.go new file mode 100644 index 0000000..4a0da65 --- /dev/null +++ b/internal/smsgateway/repositories/messages.go @@ -0,0 +1,27 @@ +package repositories + +import ( + "bitbucket.org/capcom6/smsgatewaybackend/internal/smsgateway/models" + "gorm.io/gorm" +) + +type MessagesRepository struct { + db *gorm.DB +} + +func (r *MessagesRepository) SelectPending(deviceID string) (messages []models.Message, err error) { + err = r.db. + Where("device_id = ? AND state = ?", deviceID, models.MessageStatePending). + Order("id"). + Preload("Recipients"). + Find(&messages). + Error + + return +} + +func NewMessagesRepository(db *gorm.DB) *MessagesRepository { + return &MessagesRepository{ + db: db, + } +} diff --git a/internal/smsgateway/repositories/users.go b/internal/smsgateway/repositories/users.go new file mode 100644 index 0000000..908d06b --- /dev/null +++ b/internal/smsgateway/repositories/users.go @@ -0,0 +1,30 @@ +package repositories + +import ( + "bitbucket.org/capcom6/smsgatewaybackend/internal/smsgateway/models" + "gorm.io/gorm" +) + +var ( + ErrUserNotFound = gorm.ErrRecordNotFound +) + +type UsersRepository struct { + db *gorm.DB +} + +func (r *UsersRepository) GetByLogin(login string) (models.User, error) { + user := models.User{} + + return user, r.db.Where("id = ?", login).Preload("Devices").Take(&user).Error +} + +func (r *UsersRepository) Insert(user *models.User) error { + return r.db.Create(user).Error +} + +func NewUsersRepository(db *gorm.DB) *UsersRepository { + return &UsersRepository{ + db: db, + } +} diff --git a/internal/smsgateway/services/auth.go b/internal/smsgateway/services/auth.go new file mode 100644 index 0000000..7f4d034 --- /dev/null +++ b/internal/smsgateway/services/auth.go @@ -0,0 +1,60 @@ +package services + +import ( + "fmt" + + "bitbucket.org/capcom6/smsgatewaybackend/internal/smsgateway/models" + "bitbucket.org/capcom6/smsgatewaybackend/internal/smsgateway/repositories" + "bitbucket.org/soft-c/gohelpers/pkg/crypto" + "github.com/jaevor/go-nanoid" +) + +type AuthService struct { + users *repositories.UsersRepository + devices *repositories.DevicesRepository + + idgen func() string +} + +func (s *AuthService) RegisterUser(login, password string) (models.User, error) { + user := models.User{ + ID: login, + } + + var err error + if user.PasswordHash, err = crypto.MakeBCryptHash(password); err != nil { + return user, err + } + + if err = s.users.Insert(&user); err != nil { + return user, fmt.Errorf("can't create user") + } + + return user, nil +} + +func (s *AuthService) RegisterDevice(userID, name, pushToken string) (models.Device, error) { + device := models.Device{ + ID: s.idgen(), + Name: name, + AuthToken: s.idgen(), + PushToken: pushToken, + UserID: userID, + } + + return device, s.devices.Insert(&device) +} + +func (s *AuthService) AuthorizeDevice(token string) (models.Device, error) { + return s.devices.GetByToken(token) +} + +func NewAuthService(users *repositories.UsersRepository, devices *repositories.DevicesRepository) *AuthService { + idgen, _ := nanoid.Standard(21) + + return &AuthService{ + users: users, + devices: devices, + idgen: idgen, + } +} diff --git a/internal/smsgateway/services/messages.go b/internal/smsgateway/services/messages.go new file mode 100644 index 0000000..78c5d00 --- /dev/null +++ b/internal/smsgateway/services/messages.go @@ -0,0 +1,51 @@ +package services + +import ( + "bitbucket.org/capcom6/smsgatewaybackend/internal/smsgateway/models" + "bitbucket.org/capcom6/smsgatewaybackend/internal/smsgateway/repositories" + "bitbucket.org/capcom6/smsgatewaybackend/pkg/smsgateway" + "github.com/jaevor/go-nanoid" +) + +type MessagesService struct { + messages *repositories.MessagesRepository + + idgen func() string +} + +func (s *MessagesService) SelectPending(deviceID string) ([]smsgateway.Message, error) { + messages, err := s.messages.SelectPending(deviceID) + if err != nil { + return nil, err + } + + result := make([]smsgateway.Message, len(messages)) + for i, v := range messages { + result[i] = smsgateway.Message{ + ID: v.ExtID, + Message: v.Message, + PhoneNumbers: s.recipientsToDomain(v.Recipients), + } + } + + return result, nil +} + +func (s *MessagesService) recipientsToDomain(input []models.MessageRecipient) []string { + output := make([]string, len(input)) + + for i, v := range input { + output[i] = v.PhoneNumber + } + + return output +} + +func NewMessagesService(messages *repositories.MessagesRepository) *MessagesService { + idgen, _ := nanoid.Standard(21) + + return &MessagesService{ + messages: messages, + idgen: idgen, + } +} diff --git a/pkg/smsgateway/domain.go b/pkg/smsgateway/domain.go new file mode 100644 index 0000000..faeabec --- /dev/null +++ b/pkg/smsgateway/domain.go @@ -0,0 +1,27 @@ +package smsgateway + +type ProcessState string + +const ( + MessageStatePending ProcessState = "Pending" + MessageStateSent ProcessState = "Sent" + MessageStateDelivered ProcessState = "Delivered" + MessageStateFailed ProcessState = "Failed" +) + +type Message struct { + ID string `json:"id,omitempty" validate:"omitempty,max=36"` + Message string `json:"message" validate:"required,max=256"` + PhoneNumbers []string `json:"phoneNumbers" validate:"required,min=1,max=100"` +} + +type MessageState struct { + ID string `json:"id,omitempty" validate:"omitempty,max=36"` + State ProcessState `json:"state" validate:"required"` + Recipients []RecipientState `json:"recipients" validate:"required,min=1,dive"` +} + +type RecipientState struct { + PhoneNumber string `json:"phoneNumber" validate:"required,len=11"` + State ProcessState `json:"state" validate:"required"` +} diff --git a/pkg/smsgateway/requests.go b/pkg/smsgateway/requests.go new file mode 100644 index 0000000..9950cd8 --- /dev/null +++ b/pkg/smsgateway/requests.go @@ -0,0 +1,6 @@ +package smsgateway + +type MobileRegisterRequest struct { + Name string `json:"name,omitempty" validate:"omitempty,max=128"` + PushToken string `json:"pushToken" validate:"omitempty,max=256"` +} diff --git a/pkg/smsgateway/responses.go b/pkg/smsgateway/responses.go new file mode 100644 index 0000000..3bbbbbb --- /dev/null +++ b/pkg/smsgateway/responses.go @@ -0,0 +1,8 @@ +package smsgateway + +type MobileRegisterResponse struct { + Id string `json:"id"` + Token string `json:"token"` + Login string `json:"login"` + Password string `json:"password"` +}