From 4130c9b4bee55f181da23b362f345713e867c8e3 Mon Sep 17 00:00:00 2001 From: Cyberguru1 Date: Sat, 20 Jul 2024 06:25:18 -0500 Subject: [PATCH] api for organization creation --- go.sum | 8 --- internal/models/organisation.go | 36 +++++++++++- internal/models/seed/seed.go | 6 +- internal/models/user.go | 17 +++++- pkg/controller/organisation/organisation.go | 63 +++++++++++++++++++++ pkg/router/organisation.go | 25 ++++++++ pkg/router/router.go | 1 + services/organisation/organisation.go | 61 ++++++++++++++++++++ 8 files changed, 203 insertions(+), 14 deletions(-) create mode 100644 pkg/controller/organisation/organisation.go create mode 100644 pkg/router/organisation.go create mode 100644 services/organisation/organisation.go diff --git a/go.sum b/go.sum index d4d7bb2d..f5e184cf 100644 --- a/go.sum +++ b/go.sum @@ -15,7 +15,6 @@ github.com/didip/tollbooth v4.0.2+incompatible/go.mod h1:A9b0665CE6l1KmzpDws2++e github.com/elliotchance/phpserialize v1.4.0 h1:cAp/9+KSnEbUC8oYCE32n2n84BeW8HOY3HMDI8hG2OY= github.com/elliotchance/phpserialize v1.4.0/go.mod h1:gt7XX9+ETUcLXbtTKEuyrqW3lcLUAeS/AnGZ2e49TZs= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= -github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= @@ -27,7 +26,6 @@ github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU= github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= -github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= @@ -39,7 +37,6 @@ github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MG github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA= github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= -github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= @@ -64,9 +61,7 @@ github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuV github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= -github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= @@ -88,9 +83,7 @@ github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6 github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= -github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= -github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ= github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= @@ -155,7 +148,6 @@ google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFW google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/models/organisation.go b/internal/models/organisation.go index 003befc0..6fc0399f 100644 --- a/internal/models/organisation.go +++ b/internal/models/organisation.go @@ -4,17 +4,38 @@ import ( "time" "gorm.io/gorm" + + "github.com/hngprojects/hng_boilerplate_golang_web/pkg/repository/storage/postgresql" ) type Organisation struct { - ID string `gorm:"type:uuid;primaryKey;unique;not null" json:"id"` + ID string `gorm:"type:uuid;primaryKey;unique;not null" json:"id"` Name string `gorm:"type:varchar(255);not null" json:"name"` Description string `gorm:"type:text" json:"description"` - Users []User `gorm:"many2many:user_organisations;foreignKey:ID;joinForeignKey:org_id;References:ID;joinReferences:user_id" json:"users"` + Email string `gorm:"type:varchar(255);unique" json:"email"` + State string `gorm:"type:varchar(255)" json:"state"` + Industry string `gorm:"type:varchar(255)" json:"industry"` + Type string `gorm:"type:varchar(255)" json:"type"` + Address string `gorm:"type:varchar(255)" json:"address"` + Country string `gorm:"type:varchar(255)" json:"country"` + Slug string `gorm:"type:varchar(255)" json:"slug"` + OwnerID string `gorm:"type:uuid;" json:"owner_id"` + Users []User `gorm:"many2many:user_organisations;foreignKey:ID;joinForeignKey:org_id;References:ID;joinReferences:user_id"` CreatedAt time.Time `gorm:"column:created_at; not null; autoCreateTime" json:"created_at"` UpdatedAt time.Time `gorm:"column:updated_at; null; autoUpdateTime" json:"updated_at"` } +type CreateOrgRequestModel struct { + Name string `json:"name" validate:"required,min=2,max=255"` + Description string `json:"description" ` + Email string `json:"email" validate:"required,email"` + State string `json:"state" validate:"required"` + Industry string `json:"industry" validate:"required"` + Type string `json:"type" validate:"required"` + Address string `json:"address" validate:"required"` + Country string `json:"country" validate:"required"` +} + func AddUserToOrganisation(db *gorm.DB, user interface{}, orgs []interface{}) error { // Add user to organisation @@ -26,3 +47,14 @@ func AddUserToOrganisation(db *gorm.DB, user interface{}, orgs []interface{}) er return nil } + +func (c *Organisation) CreateOrganisation(db *gorm.DB) error { + + err := postgresql.CreateOneRecord(db, &c) + + if err != nil { + return err + } + + return nil +} diff --git a/internal/models/seed/seed.go b/internal/models/seed/seed.go index 3332075a..aeb809f1 100644 --- a/internal/models/seed/seed.go +++ b/internal/models/seed/seed.go @@ -50,9 +50,9 @@ func SeedDatabase(db *gorm.DB) { } organisations := []models.Organisation{ - {ID: utility.GenerateUUID(), Name: "Org1", Description: "Description1"}, - {ID: utility.GenerateUUID(), Name: "Org2", Description: "Description2"}, - {ID: utility.GenerateUUID(), Name: "Org3", Description: "Description3"}, + {ID: utility.GenerateUUID(), Name: "Org1", Email: fmt.Sprintf(utility.RandomString(4)+"@gmail.com"),Description: "Description1", OwnerID: Userid1}, + {ID: utility.GenerateUUID(), Name: "Org2", Email: fmt.Sprintf(utility.RandomString(4)+"@gmail.com"),Description: "Description2", OwnerID: Userid1}, + {ID: utility.GenerateUUID(), Name: "Org3", Email: fmt.Sprintf(utility.RandomString(4)+"@gmail.com"),Description: "Description3", OwnerID: Userid2}, } var existingUser models.User diff --git a/internal/models/user.go b/internal/models/user.go index 7f2aa4a7..770002d1 100644 --- a/internal/models/user.go +++ b/internal/models/user.go @@ -1,11 +1,14 @@ package models import ( + "errors" "time" + + "gorm.io/gorm" ) type User struct { - ID string `gorm:"type:uuid;primaryKey;unique;not null" json:"id"` + ID string `gorm:"type:uuid;primaryKey;unique;not null" json:"id"` Name string `gorm:"column:name; type:varchar(255)" json:"name"` Email string `gorm:"column:email; type:varchar(255)" json:"email"` Profile Profile `gorm:"foreignKey:Userid;constraint:OnUpdate:CASCADE,OnDelete:SET NULL;" json:"profile"` @@ -14,3 +17,15 @@ type User struct { CreatedAt time.Time `gorm:"column:created_at; not null; autoCreateTime" json:"created_at"` UpdatedAt time.Time `gorm:"column:updated_at; null; autoUpdateTime" json:"updated_at"` } + +func GetUserByID(db *gorm.DB, userID string) (User, error) { + var user User + result := db.First(&user, "id = ?", userID) + if result.Error != nil { + if errors.Is(result.Error, gorm.ErrRecordNotFound) { + return user, errors.New("user not found") + } + return user, result.Error + } + return user, nil +} diff --git a/pkg/controller/organisation/organisation.go b/pkg/controller/organisation/organisation.go new file mode 100644 index 00000000..618ffe7e --- /dev/null +++ b/pkg/controller/organisation/organisation.go @@ -0,0 +1,63 @@ +package organisation + +import ( + "net/http" + + "github.com/gin-gonic/gin" + "github.com/go-playground/validator/v10" + + "github.com/hngprojects/hng_boilerplate_golang_web/external/request" + "github.com/hngprojects/hng_boilerplate_golang_web/internal/models" + "github.com/hngprojects/hng_boilerplate_golang_web/pkg/repository/storage" + service "github.com/hngprojects/hng_boilerplate_golang_web/services/organisation" + "github.com/hngprojects/hng_boilerplate_golang_web/utility" +) + +type Controller struct { + Db *storage.Database + Validator *validator.Validate + Logger *utility.Logger + ExtReq request.ExternalRequest +} + +func (base *Controller) CreateOrganisation(c *gin.Context) { + + var ( + req = models.CreateOrgRequestModel{} + ) + + err := c.ShouldBind(&req) + if err != nil { + rd := utility.BuildErrorResponse(http.StatusBadRequest, "error", "Failed to parse request body", err, nil) + c.JSON(http.StatusBadRequest, rd) + return + } + + err = base.Validator.Struct(&req) + if err != nil { + rd := utility.BuildErrorResponse(http.StatusBadRequest, "error", "Validation failed", utility.ValidationResponse(err, base.Validator), nil) + c.JSON(http.StatusBadRequest, rd) + return + } + + reqData, err := service.ValidateCreateOrgRequest(req, base.Db.Postgresql) + if err != nil { + rd := utility.BuildErrorResponse(http.StatusBadRequest, "error", err.Error(), err, nil) + c.JSON(http.StatusBadRequest, rd) + return + } + + userId := "0190ceb3-d6c4-7491-90db-3408a5b05b30" + + respData, err := service.CreateOrganisation(reqData, base.Db.Postgresql, userId) + if err != nil { + rd := utility.BuildErrorResponse(http.StatusBadRequest, "error", err.Error(), err, nil) + c.JSON(http.StatusBadRequest, rd) + return + } + + base.Logger.Info("organisation created successfully") + rd := utility.BuildSuccessResponse(http.StatusOK, "organisation created successfully", respData) + + c.JSON(http.StatusOK, rd) +} diff --git a/pkg/router/organisation.go b/pkg/router/organisation.go new file mode 100644 index 00000000..cdea7e6e --- /dev/null +++ b/pkg/router/organisation.go @@ -0,0 +1,25 @@ +package router + +import ( + "fmt" + + "github.com/gin-gonic/gin" + "github.com/go-playground/validator/v10" + + "github.com/hngprojects/hng_boilerplate_golang_web/external/request" + "github.com/hngprojects/hng_boilerplate_golang_web/pkg/controller/organisation" + "github.com/hngprojects/hng_boilerplate_golang_web/pkg/repository/storage" + "github.com/hngprojects/hng_boilerplate_golang_web/utility" +) + +func Organisation(r *gin.Engine, ApiVersion string, validator *validator.Validate, db *storage.Database, logger *utility.Logger) *gin.Engine { + extReq := request.ExternalRequest{Logger: logger, Test: false} + organisation := organisation.Controller{Db: db, Validator: validator, Logger: logger, ExtReq: extReq} + + organisationUrl := r.Group(fmt.Sprintf("%v", ApiVersion)) + { + organisationUrl.POST("/organisations", organisation.CreateOrganisation) + + } + return r +} diff --git a/pkg/router/router.go b/pkg/router/router.go index e59e2f17..7c6f75bc 100644 --- a/pkg/router/router.go +++ b/pkg/router/router.go @@ -35,6 +35,7 @@ func Setup(logger *utility.Logger, validator *validator.Validate, db *storage.Da Health(r, ApiVersion, validator, db, logger) Seed(r, ApiVersion, validator, db, logger) + Organisation(r, ApiVersion, validator, db, logger) r.GET("/", func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{ diff --git a/services/organisation/organisation.go b/services/organisation/organisation.go new file mode 100644 index 00000000..fa301620 --- /dev/null +++ b/services/organisation/organisation.go @@ -0,0 +1,61 @@ +package service + +import ( + "errors" + "strings" + + "gorm.io/gorm" + + "github.com/hngprojects/hng_boilerplate_golang_web/internal/models" + "github.com/hngprojects/hng_boilerplate_golang_web/pkg/repository/storage/postgresql" + "github.com/hngprojects/hng_boilerplate_golang_web/utility" +) + +func ValidateCreateOrgRequest(req models.CreateOrgRequestModel, db *gorm.DB) (models.CreateOrgRequestModel, error) { + + org := models.Organisation{} + + // Check if the organization already exists + exists := postgresql.CheckExists(db, &org, "email = ?", req.Email) + if exists { + return req, errors.New("organization already exists with the given email") + } + + return req, nil +} + +func CreateOrganisation(req models.CreateOrgRequestModel, db *gorm.DB, userId string) (*models.Organisation, error) { + + org := models.Organisation{ + ID: utility.GenerateUUID(), + Name: strings.ToLower(req.Name), + Description: strings.ToLower(req.Description), + Email: strings.ToLower(req.Email), + State: strings.ToLower(req.State), + Industry: strings.ToLower(req.Industry), + Type: strings.ToLower(req.Type), + OwnerID: userId, + Address: strings.ToLower(req.Address), + Country: strings.ToLower(req.Country), + } + + err := org.CreateOrganisation(db) + + if err != nil { + return nil, err + } + + user, err := models.GetUserByID(db, userId) + + if err != nil { + return nil, err + } + + err = models.AddUserToOrganisation(db, &user, []interface{}{&org}) + + if err != nil { + return nil, err + } + + return &org, nil +}