From 3169b5422d8e4d55861491dc9efc4f7aacd93d1c Mon Sep 17 00:00:00 2001 From: Ebite God'sgift Uloamaka Date: Wed, 7 Aug 2024 15:36:09 +0100 Subject: [PATCH 01/45] fix: input validation for create help center topic --- pkg/controller/helpcenter/helpcenter.go | 5 +++-- services/helpcenter/helpcenter.go | 4 ++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/pkg/controller/helpcenter/helpcenter.go b/pkg/controller/helpcenter/helpcenter.go index c6e1fb7b..c34de93b 100644 --- a/pkg/controller/helpcenter/helpcenter.go +++ b/pkg/controller/helpcenter/helpcenter.go @@ -52,14 +52,15 @@ func (base *Controller) CreateHelpCenterTopic(c *gin.Context) { } req.Author = user.Name + req.Title = utility.CleanStringInput(req.Title) + req.Content = utility.CleanStringInput(req.Content) if err := base.Validator.Struct(&req); err != nil { - rd := utility.BuildErrorResponse(http.StatusUnprocessableEntity, "error", "Validation failed", utility.ValidationResponse(err, base.Validator), nil) + rd := utility.BuildErrorResponse(http.StatusUnprocessableEntity, "error", "Input validation failed", utility.ValidationResponse(err, base.Validator), nil) c.JSON(http.StatusUnprocessableEntity, rd) return } - respData, err := service.CreateHelpCenterTopic(req, base.Db.Postgresql) if err != nil { rd := utility.BuildErrorResponse(http.StatusInternalServerError, "error", "Failed to add Topic", err, nil) diff --git a/services/helpcenter/helpcenter.go b/services/helpcenter/helpcenter.go index 8f41f185..62e6fa6d 100644 --- a/services/helpcenter/helpcenter.go +++ b/services/helpcenter/helpcenter.go @@ -77,6 +77,10 @@ func SearchHelpCenterTopics(c *gin.Context, db *gorm.DB, query string) ([]HelpCn return nil, paginationResponse, err } + if len(topics) == 0 { + return nil, paginationResponse, gorm.ErrRecordNotFound + } + var topicSummaries []HelpCntSummary for _, topic := range topics { summary := HelpCntSummary{ From aa4be0d2a6d4a5aa08e39e4b217dc04f84cf5aca Mon Sep 17 00:00:00 2001 From: Ebite God'sgift Uloamaka Date: Wed, 7 Aug 2024 15:44:01 +0100 Subject: [PATCH 02/45] fix: input validation for create help center topic --- static/swagger.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/static/swagger.yaml b/static/swagger.yaml index 510e7522..612b2d65 100644 --- a/static/swagger.yaml +++ b/static/swagger.yaml @@ -4869,7 +4869,7 @@ paths: $ref: '#/components/schemas/CreateHelpCenter' responses: '201': - description: Topic created successfully + description: Topic added successfully content: application/json: schema: From 124e0fb577a81baa6f1812fb1a3473e81262c768 Mon Sep 17 00:00:00 2001 From: Ebite God'sgift Uloamaka Date: Wed, 7 Aug 2024 15:49:03 +0100 Subject: [PATCH 03/45] fix: input validation for create help center topic --- static/swagger.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/static/swagger.yaml b/static/swagger.yaml index 612b2d65..510e7522 100644 --- a/static/swagger.yaml +++ b/static/swagger.yaml @@ -4869,7 +4869,7 @@ paths: $ref: '#/components/schemas/CreateHelpCenter' responses: '201': - description: Topic added successfully + description: Topic created successfully content: application/json: schema: From 29b5e3488208090dd239dc242dbe63329d503d42 Mon Sep 17 00:00:00 2001 From: Ebite God'sgift Uloamaka Date: Wed, 7 Aug 2024 17:29:47 +0100 Subject: [PATCH 04/45] fix: update on the create jobs model --- internal/models/jobpost.go | 67 ++++++++++++++++++++++++++----------- services/jobpost/jobpost.go | 67 +++++++++++++++++++++++++++---------- 2 files changed, 96 insertions(+), 38 deletions(-) diff --git a/internal/models/jobpost.go b/internal/models/jobpost.go index f2269ed2..ca617418 100644 --- a/internal/models/jobpost.go +++ b/internal/models/jobpost.go @@ -8,39 +8,66 @@ import ( ) +// type JobPost struct { +// ID string `gorm:"type:uuid; primaryKey" json:"id"` +// Title string `gorm:"column:title; type:varchar(255); not null" json:"title"` +// Salary string `gorm:"column:salary; type:varchar(255)" json:"salary"` +// JobType string `gorm:"column:job_type; type:varchar(50); not null" json:"job_type"` +// WorkMode string `gorm:"column:work_mode; type:varchar(50); not null" json:"work_mode"` +// Deadline time.Time `gorm:"column:deadline; not null" json:"deadline"` +// Location string `gorm:"column:location; type:varchar(255); not null" json:"location"` +// HowToApply string `gorm:"column:how_to_apply; type:varchar(500)" json:"how_to_apply"` +// Experience string `gorm:"column:experience; type:varchar(50); not null" json:"experience"` +// JobBenefits string `gorm:"column:job_benefits; type:varchar(500)" json:"job_benefits"` +// CompanyName string `gorm:"column:company_name; type:varchar(255); not null" json:"company_name"` +// Description string `gorm:"column:description; type:varchar(500); not null" json:"description"` +// KeyResponsibilities string `gorm:"column:key_responsibilities; type:varchar(500)" json:"key_responsibilities"` +// Qualifications string `gorm:"column:qualifications; type:varchar(500)" json:"qualifications"` +// 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 JobPost struct { ID string `gorm:"type:uuid; primaryKey" json:"id"` - Title string `gorm:"column:title; type:varchar(255); not null" json:"title"` - Salary string `gorm:"column:salary; type:varchar(255)" json:"salary"` - JobType string `gorm:"column:job_type; type:varchar(50); not null" json:"job_type"` - WorkMode string `gorm:"column:work_mode; type:varchar(50); not null" json:"work_mode"` - Deadline time.Time `gorm:"column:deadline; not null" json:"deadline"` - Location string `gorm:"column:location; type:varchar(255); not null" json:"location"` - HowToApply string `gorm:"column:how_to_apply; type:varchar(500)" json:"how_to_apply"` - Experience string `gorm:"column:experience; type:varchar(50); not null" json:"experience"` - JobBenefits string `gorm:"column:job_benefits; type:varchar(500)" json:"job_benefits"` - CompanyName string `gorm:"column:company_name; type:varchar(255); not null" json:"company_name"` - Description string `gorm:"column:description; type:varchar(500); not null" json:"description"` - KeyResponsibilities string `gorm:"column:key_responsibilities; type:varchar(500)" json:"key_responsibilities"` - Qualifications string `gorm:"column:qualifications; type:varchar(500)" json:"qualifications"` + Title string `gorm:"column:title; type:varchar(255); not null" json:"title"` + SalaryRange string `gorm:"column:salary_range; type:varchar(255); not null" json:"salary_range"` + JobType string `gorm:"column:job_type; type:varchar(50); not null" json:"job_type"` + Location string `gorm:"column:location; type:varchar(255); not null" json:"location"` + Deadline time.Time `gorm:"column:deadline; not null" json:"deadline"` + JobMode string `gorm:"column:job_mode; type:varchar(50); not null" json:"job_mode"` + Experience string `gorm:"column:experience; type:varchar(50); not null" json:"experience"` + CompanyName string `gorm:"column:company_name; type:varchar(255); not null" json:"company_name"` + Description string `gorm:"column:description; type:varchar(500); not null" json:"description"` 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"` + UpdatedAt time.Time `gorm:"column:updated_at; null; autoUpdateTime" json:"updated_at"` } +// type CreateJobPostModel struct { +// Title string `json:"title" validate:"required,min=2,max=255"` +// Salary string `json:"salary" validate:"required"` +// JobType string `json:"job_type" validate:"required"` +// Location string `json:"location" validate:"required,min=2,max=255"` +// Deadline time.Time `json:"deadline" validate:"required"` +// WorkMode string `json:"work_mode" validate:"required"` +// Experience string `json:"experience" validate:"required"` +// HowToApply string `json:"how_to_apply" validate:"required"` +// JobBenefits string `json:"job_benefits" validate:"required,min=2,max=500"` +// CompanyName string `json:"company_name" validate:"required,min=2,max=255"` +// Description string `json:"description" validate:"required,min=2,max=500"` +// KeyResponsibilities string `json:"key_responsibilities" validate:"required,min=2,max=500"` +// Qualifications string `json:"qualifications" validate:"required,min=2,max=500"` +// } + type CreateJobPostModel struct { Title string `json:"title" validate:"required,min=2,max=255"` - Salary string `json:"salary" validate:"required"` + SalaryRange string `json:"salary_range" validate:"required"` JobType string `json:"job_type" validate:"required"` Location string `json:"location" validate:"required,min=2,max=255"` Deadline time.Time `json:"deadline" validate:"required"` - WorkMode string `json:"work_mode" validate:"required"` + JobMode string `json:"job_mode" validate:"required"` Experience string `json:"experience" validate:"required"` - HowToApply string `json:"how_to_apply" validate:"required"` - JobBenefits string `json:"job_benefits" validate:"required,min=2,max=500"` CompanyName string `json:"company_name" validate:"required,min=2,max=255"` Description string `json:"description" validate:"required,min=2,max=500"` - KeyResponsibilities string `json:"key_responsibilities" validate:"required,min=2,max=500"` - Qualifications string `json:"qualifications" validate:"required,min=2,max=500"` } func (j *JobPost) CreateJobPost(db *gorm.DB) error { diff --git a/services/jobpost/jobpost.go b/services/jobpost/jobpost.go index 7d8edf84..58349ce4 100644 --- a/services/jobpost/jobpost.go +++ b/services/jobpost/jobpost.go @@ -8,30 +8,50 @@ import ( "github.com/hngprojects/hng_boilerplate_golang_web/pkg/repository/storage/postgresql" ) +// type JobPostSummary struct { +// ID string `json:"id"` +// Title string `json:"title"` +// Description string `json:"description"` +// Location string `json:"location"` +// Salary string `json:"salary"` +// } type JobPostSummary struct { - ID string `json:"id"` - Title string `json:"title"` - Description string `json:"description"` - Location string `json:"location"` - Salary string `json:"salary"` + ID string `json:"id"` + Title string `json:"title"` + Location string `json:"location"` + Description string `json:"description"` + SalaryRange string `json:"salary_range"` } func CreateJobPost(req models.CreateJobPostModel, db *gorm.DB) (models.JobPost, error) { - jobpost := models.JobPost{ + // jobpost := models.JobPost{ + // ID: utility.GenerateUUID(), + // Title: req.Title, + // Salary: req.Salary, + // JobType: req.JobType, + // Location: req.Location, + // Deadline: req.Deadline, + // WorkMode: req.WorkMode, + // Experience: req.Experience, + // HowToApply: req.HowToApply, + // JobBenefits: req.JobBenefits, + // Description: req.Description, + // CompanyName: req.CompanyName, + // KeyResponsibilities: req.KeyResponsibilities, + // Qualifications: req.Qualifications, + // } + + jobpost := models.JobPost{ ID: utility.GenerateUUID(), Title: req.Title, - Salary: req.Salary, + JobMode: req.JobMode, JobType: req.JobType, Location: req.Location, Deadline: req.Deadline, - WorkMode: req.WorkMode, - Experience: req.Experience, - HowToApply: req.HowToApply, - JobBenefits: req.JobBenefits, + SalaryRange: req.SalaryRange, + Experience: req.Experience, Description: req.Description, CompanyName: req.CompanyName, - KeyResponsibilities: req.KeyResponsibilities, - Qualifications: req.Qualifications, } if err := jobpost.CreateJobPost(db); @@ -51,14 +71,25 @@ func GetPaginatedJobPosts(c *gin.Context, db *gorm.DB) ([]JobPostSummary, postgr return nil, paginationResponse, err } + if len(jobPosts) == 0 { + return nil, paginationResponse, gorm.ErrRecordNotFound + } + var jobPostSummaries []JobPostSummary for _, job := range jobPosts { + // summary := JobPostSummary{ + // ID: job.ID, + // Title: job.Title, + // Description: job.Description, + // Location: job.Location, + // Salary: job.Salary, + // } summary := JobPostSummary{ - ID: job.ID, - Title: job.Title, - Description: job.Description, - Location: job.Location, - Salary: job.Salary, + ID: job.ID, + Title: job.Title, + Description: job.Description, + Location: job.Location, + SalaryRange: job.SalaryRange, } jobPostSummaries = append(jobPostSummaries, summary) } From 596a7139fd2ef909529ad15856659e87c80923bb Mon Sep 17 00:00:00 2001 From: Ebite God'sgift Uloamaka Date: Wed, 7 Aug 2024 17:35:14 +0100 Subject: [PATCH 05/45] fix: input validation for create help center topic --- tests/test_helpcenter/helpcenter_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_helpcenter/helpcenter_test.go b/tests/test_helpcenter/helpcenter_test.go index 27f87c6e..61276fb2 100644 --- a/tests/test_helpcenter/helpcenter_test.go +++ b/tests/test_helpcenter/helpcenter_test.go @@ -76,7 +76,7 @@ func TestHelpCenterCreate(t *testing.T) { Content: "", }, ExpectedCode: http.StatusUnprocessableEntity, - Message: "Validation failed", + Message: "Input validation failed", ErrorField: "CreateHelpCenter.Content", ErrorMessage: "Content is a required field", Headers: map[string]string{ From 4c362fa8c348fc53412633def603d9b2b44a55a5 Mon Sep 17 00:00:00 2001 From: Iretoms Date: Wed, 7 Aug 2024 23:44:04 +0100 Subject: [PATCH 06/45] feat: implement testimonial creation and e2e tests --- internal/models/migrations/migrations.go | 1 + internal/models/testimonial.go | 32 ++++++ pkg/controller/blog/blog.go | 2 +- pkg/controller/testimonial/testimonial.go | 63 ++++++++++++ pkg/router/router.go | 1 + pkg/router/testimonial.go | 25 +++++ services/testimonial/testimonial.go | 25 +++++ tests/test_testimonial/base.go | 37 +++++++ tests/test_testimonial/create_test.go | 119 ++++++++++++++++++++++ 9 files changed, 304 insertions(+), 1 deletion(-) create mode 100644 internal/models/testimonial.go create mode 100644 pkg/controller/testimonial/testimonial.go create mode 100644 pkg/router/testimonial.go create mode 100644 services/testimonial/testimonial.go create mode 100644 tests/test_testimonial/base.go create mode 100644 tests/test_testimonial/create_test.go diff --git a/internal/models/migrations/migrations.go b/internal/models/migrations/migrations.go index 6d45b714..56d093e1 100644 --- a/internal/models/migrations/migrations.go +++ b/internal/models/migrations/migrations.go @@ -4,6 +4,7 @@ import "github.com/hngprojects/hng_boilerplate_golang_web/internal/models" func AuthMigrationModels() []interface{} { return []interface{}{ + models.Testimonial{}, models.SqueezeUser{}, models.Blog{}, models.AccessToken{}, diff --git a/internal/models/testimonial.go b/internal/models/testimonial.go new file mode 100644 index 00000000..a9f50a09 --- /dev/null +++ b/internal/models/testimonial.go @@ -0,0 +1,32 @@ +package models + +import ( + "time" + + "github.com/hngprojects/hng_boilerplate_golang_web/pkg/repository/storage/postgresql" + "gorm.io/gorm" +) + +type Testimonial struct { + ID string `gorm:"type:uuid;primary_key" json:"id"` + UserID string `gorm:"type:uuid;not null" json:"user_id"` + Name string `gorm:"type:varchar(255);not null" json:"name"` + Content string `gorm:"type:text;not null" json:"content"` + CreatedAt time.Time `gorm:"not null;autoCreateTime" json:"created_at"` + UpdatedAt time.Time `gorm:"not null;autoUpdateTime" json:"updated_at"` +} + +type TestimonialReq struct { + Name string `json:"name" validate:"required"` + Content string `json:"content" validate:"required"` +} + +func (t *Testimonial) Create(db *gorm.DB) error { + err := postgresql.CreateOneRecord(db, &t) + + if err != nil { + return err + } + + return nil +} diff --git a/pkg/controller/blog/blog.go b/pkg/controller/blog/blog.go index ab28475d..428e21c6 100644 --- a/pkg/controller/blog/blog.go +++ b/pkg/controller/blog/blog.go @@ -176,7 +176,7 @@ func (base *Controller) UpdateBlogById(c *gin.Context){ c.JSON(http.StatusNotFound, rd) return } - rd := utility.BuildErrorResponse(http.StatusBadRequest, "error", err.Error(), "failed to update blog", nil) + rd := utility.BuildErrorResponse(http.StatusInternalServerError, "error", err.Error(), "failed to update blog", nil) c.JSON(http.StatusInternalServerError, rd) return } diff --git a/pkg/controller/testimonial/testimonial.go b/pkg/controller/testimonial/testimonial.go new file mode 100644 index 00000000..d113a559 --- /dev/null +++ b/pkg/controller/testimonial/testimonial.go @@ -0,0 +1,63 @@ +package testimonial + +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/middleware" + "github.com/hngprojects/hng_boilerplate_golang_web/pkg/repository/storage" + service "github.com/hngprojects/hng_boilerplate_golang_web/services/testimonial" + "github.com/hngprojects/hng_boilerplate_golang_web/utility" +) + +type Controller struct { + Db *storage.Database + Logger *utility.Logger + Validator *validator.Validate + ExtReq request.ExternalRequest +} + +func (base *Controller) Create(c *gin.Context) { + var req models.TestimonialReq + + if err := c.ShouldBind(&req); err != nil { + rd := utility.BuildErrorResponse(http.StatusBadRequest, "error", "Failed to parse request body", err, nil) + c.JSON(http.StatusBadRequest, rd) + return + } + + if err := base.Validator.Struct(&req); err != nil { + rd := utility.BuildErrorResponse(http.StatusUnprocessableEntity, "error", "Validation failed", utility.ValidationResponse(err, base.Validator), nil) + c.JSON(http.StatusUnprocessableEntity, rd) + return + } + + userID, err := middleware.GetUserClaims(c, base.Db.Postgresql, "user_id") + if err != nil { + if err.Error() == "user claims not found" { + rd := utility.BuildErrorResponse(http.StatusBadRequest, "error", err.Error(), "failed to create testimonial", nil) + c.JSON(http.StatusNotFound, rd) + return + } + rd := utility.BuildErrorResponse(http.StatusInternalServerError, "error", err.Error(), "failed to create testimonial", nil) + c.JSON(http.StatusInternalServerError, rd) + return + } + userId := userID.(string) + + testimonial, err := service.CreateTestimonial(base.Db.Postgresql, req, userId) + + if err != nil { + rd := utility.BuildErrorResponse(http.StatusBadRequest, "error", err.Error(), "failed to create testimonial", nil) + c.JSON(http.StatusBadRequest, rd) + return + } + + base.Logger.Info("testimonial created successfully") + rd := utility.BuildSuccessResponse(http.StatusCreated, "testimonial created successfully", testimonial) + c.JSON(http.StatusCreated, rd) + +} diff --git a/pkg/router/router.go b/pkg/router/router.go index 1cf462e2..e9154f42 100644 --- a/pkg/router/router.go +++ b/pkg/router/router.go @@ -37,6 +37,7 @@ func Setup(logger *utility.Logger, validator *validator.Validate, db *storage.Da Health(r, ApiVersion, validator, db, logger) Seed(r, ApiVersion, validator, db, logger) Invite(r, ApiVersion, validator, db, logger) + Testimonial(r, ApiVersion, validator, db, logger) Squeeze(r, ApiVersion, validator, db, logger) Blog(r, ApiVersion, validator, db, logger) Waitlist(r, ApiVersion, validator, db, logger) diff --git a/pkg/router/testimonial.go b/pkg/router/testimonial.go new file mode 100644 index 00000000..aaec318c --- /dev/null +++ b/pkg/router/testimonial.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/internal/models" + "github.com/hngprojects/hng_boilerplate_golang_web/pkg/controller/testimonial" + "github.com/hngprojects/hng_boilerplate_golang_web/pkg/middleware" + "github.com/hngprojects/hng_boilerplate_golang_web/pkg/repository/storage" + "github.com/hngprojects/hng_boilerplate_golang_web/utility" +) + +func Testimonial(r *gin.Engine, ApiVersion string, validator *validator.Validate, db *storage.Database, logger *utility.Logger) *gin.Engine { + extReq := request.ExternalRequest{Logger: logger, Test: false} + controller := testimonial.Controller{Db: db, Logger: logger, Validator: validator, ExtReq: extReq} + + squeezeURL := r.Group(fmt.Sprintf("%v", ApiVersion), middleware.Authorize(db.Postgresql, models.RoleIdentity.User)) + { + squeezeURL.POST("/testimonials", controller.Create) + } + return r +} diff --git a/services/testimonial/testimonial.go b/services/testimonial/testimonial.go new file mode 100644 index 00000000..c4748edc --- /dev/null +++ b/services/testimonial/testimonial.go @@ -0,0 +1,25 @@ +package service + +import ( + "github.com/hngprojects/hng_boilerplate_golang_web/internal/models" + "github.com/hngprojects/hng_boilerplate_golang_web/utility" + "gorm.io/gorm" +) + +func CreateTestimonial(db *gorm.DB, req models.TestimonialReq, userId string) (*models.Testimonial, error) { + testimonial := &models.Testimonial{ + ID: utility.GenerateUUID(), + UserID: userId, + Name: req.Name, + Content: req.Content, + } + + err := testimonial.Create(db) + + if err != nil { + return nil, err + } + + return testimonial, nil + +} diff --git a/tests/test_testimonial/base.go b/tests/test_testimonial/base.go new file mode 100644 index 00000000..fd81b529 --- /dev/null +++ b/tests/test_testimonial/base.go @@ -0,0 +1,37 @@ +package test_testimonial + +import ( + "github.com/gin-gonic/gin" + "github.com/go-playground/validator/v10" + "github.com/hngprojects/hng_boilerplate_golang_web/internal/models" + "github.com/hngprojects/hng_boilerplate_golang_web/pkg/controller/testimonial" + "github.com/hngprojects/hng_boilerplate_golang_web/pkg/middleware" + "github.com/hngprojects/hng_boilerplate_golang_web/pkg/repository/storage" + tst "github.com/hngprojects/hng_boilerplate_golang_web/tests" +) + +func SetupTestimonialTestRouter() (*gin.Engine, *testimonial.Controller) { + gin.SetMode(gin.TestMode) + + logger := tst.Setup() + db := storage.Connection() + validator := validator.New() + + testimonialController := &testimonial.Controller{ + Db: db, + Validator: validator, + Logger: logger, + } + + r := gin.Default() + SetupTestimonialRoutes(r, testimonialController) + return r, testimonialController +} + +func SetupTestimonialRoutes(r *gin.Engine, testimonialController *testimonial.Controller) { + r.POST( + "/api/v1/testimonials", + middleware.Authorize(testimonialController.Db.Postgresql, models.RoleIdentity.User), + testimonialController.Create, + ) +} diff --git a/tests/test_testimonial/create_test.go b/tests/test_testimonial/create_test.go new file mode 100644 index 00000000..a25563fa --- /dev/null +++ b/tests/test_testimonial/create_test.go @@ -0,0 +1,119 @@ +package test_testimonial + +import ( + "bytes" + "encoding/json" + "fmt" + "net/http" + "net/http/httptest" + "testing" + + "github.com/gin-gonic/gin" + "github.com/hngprojects/hng_boilerplate_golang_web/internal/models" + "github.com/hngprojects/hng_boilerplate_golang_web/pkg/controller/auth" + tst "github.com/hngprojects/hng_boilerplate_golang_web/tests" + "github.com/hngprojects/hng_boilerplate_golang_web/utility" +) + +func TestCreateTestimonial(t *testing.T) { + _, testimonialController := SetupTestimonialTestRouter() + db := testimonialController.Db.Postgresql + currUUID := utility.GenerateUUID() + password, _ := utility.HashPassword("password") + + user := models.User{ + ID: utility.GenerateUUID(), + Name: "Regular User", + Email: fmt.Sprintf("user%v@qa.team", currUUID), + Password: password, + Role: int(models.RoleIdentity.User), + } + + db.Create(&user) + + setup := func() (*gin.Engine, *auth.Controller) { + router, testimonialController := SetupTestimonialTestRouter() + authController := auth.Controller{ + Db: testimonialController.Db, + Validator: testimonialController.Validator, + Logger: testimonialController.Logger, + } + + return router, &authController + } + + router, authController := setup() + + loginData := models.LoginRequestModel{ + Email: user.Email, + Password: "password", + } + token := tst.GetLoginToken(t, router, *authController, loginData) + + tests := []struct { + Name string + RequestBody models.TestimonialReq + ExpectedCode int + Message string + Headers map[string]string + }{ + { + Name: "Successful creation of testimonial", + RequestBody: models.TestimonialReq{ + Name: "testtestimonial", + Content: "testcontent", + }, + ExpectedCode: http.StatusCreated, + Message: "testimonial created successfully", + Headers: map[string]string{ + "Content-Type": "application/json", + "Authorization": "Bearer " + token, + }, + }, + { + Name: "Unauthorized Access", + RequestBody: models.TestimonialReq{ + Name: "testtestimonial", + Content: "testcontent", + }, + ExpectedCode: http.StatusUnauthorized, + Message: "Token could not be found!", + Headers: map[string]string{ + "Content-Type": "application/json", + }, + }, + { + Name: "Validation failed", + RequestBody: models.TestimonialReq{ + Content: "testcontent", + }, + ExpectedCode: http.StatusUnprocessableEntity, + Message: "Validation failed", + Headers: map[string]string{ + "Content-Type": "application/json", + "Authorization": "Bearer " + token, + }, + }, + } + + for _, test := range tests { + t.Run(test.Name, func(t *testing.T) { + var b bytes.Buffer + json.NewEncoder(&b).Encode(test.RequestBody) + + req, _ := http.NewRequest(http.MethodPost, "/api/v1/testimonials", &b) + + for i, v := range test.Headers { + req.Header.Set(i, v) + } + + resp := httptest.NewRecorder() + router.ServeHTTP(resp, req) + + tst.AssertStatusCode(t, resp.Code, test.ExpectedCode) + response := tst.ParseResponse(resp) + tst.AssertResponseMessage(t, response["message"].(string), test.Message) + + }) + } +} From 60429457ecb44561c66dbfa64378aa5fb1dbe965 Mon Sep 17 00:00:00 2001 From: Ayotola Ajisola <49092282+Ajinx1@users.noreply.github.com> Date: Wed, 7 Aug 2024 23:48:00 +0100 Subject: [PATCH 07/45] fix:uncommented update timezone test --- tests/test_superadmin/update_timezone_test.go | 258 +++++++++--------- 1 file changed, 129 insertions(+), 129 deletions(-) diff --git a/tests/test_superadmin/update_timezone_test.go b/tests/test_superadmin/update_timezone_test.go index 720a11e0..c751726e 100644 --- a/tests/test_superadmin/update_timezone_test.go +++ b/tests/test_superadmin/update_timezone_test.go @@ -1,131 +1,131 @@ package test_superadmin -// import ( -// "bytes" -// "encoding/json" -// "fmt" -// "net/http" -// "net/http/httptest" -// "testing" - -// "github.com/gin-gonic/gin" -// "github.com/hngprojects/hng_boilerplate_golang_web/internal/models" -// "github.com/hngprojects/hng_boilerplate_golang_web/pkg/controller/auth" -// "github.com/hngprojects/hng_boilerplate_golang_web/tests" -// "github.com/hngprojects/hng_boilerplate_golang_web/utility" -// ) - -// func TestUpdateTimezone(t *testing.T) { -// _, saController := SetupSATestRouter() -// db := saController.Db.Postgresql - -// currUUID := utility.GenerateUUID() -// password, _ := utility.HashPassword("password") - -// adminUser := models.User{ -// ID: utility.GenerateUUID(), -// Name: "Admin User", -// Email: fmt.Sprintf("admin%v@qa.team", currUUID), -// Password: password, -// Role: int(models.RoleIdentity.SuperAdmin), -// } - -// db.Create(&adminUser) - -// timezone := models.Timezone{ -// ID: utility.GenerateUUID(), -// Timezone: fmt.Sprintf("America/New_York-%s", utility.RandomString(10)), -// GmtOffset: fmt.Sprintf("-05:00+%s", utility.RandomString(3)), -// Description: fmt.Sprintf("western -%s", utility.RandomString(3)), -// } -// db.Create(&timezone) - -// setup := func() (*gin.Engine, *auth.Controller) { -// router, saController := SetupSATestRouter() -// authController := auth.Controller{ -// Db: saController.Db, -// Validator: saController.Validator, -// Logger: saController.Logger, -// } - -// return router, &authController -// } - -// t.Run("Successful Update Timezone", func(t *testing.T) { -// router, authController := setup() - -// loginData := models.LoginRequestModel{ -// Email: adminUser.Email, -// Password: "password", -// } -// token := tests.GetLoginToken(t, router, *authController, loginData) - -// updateTimezone := models.Timezone{ -// Timezone: fmt.Sprintf("America/Los_Angeles-%s", utility.RandomString(10)), -// GmtOffset: fmt.Sprintf("-08:00+%s", utility.RandomString(3)), -// Description: fmt.Sprintf("pacific -%s", utility.RandomString(3)), -// } -// jsonBody, _ := json.Marshal(updateTimezone) - -// req, _ := http.NewRequest(http.MethodPatch, fmt.Sprintf("/api/v1/timezones/%s", timezone.ID), bytes.NewBuffer(jsonBody)) -// req.Header.Set("Content-Type", "application/json") -// req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token)) - -// resp := httptest.NewRecorder() -// router.ServeHTTP(resp, req) - -// tests.AssertStatusCode(t, resp.Code, http.StatusOK) -// response := tests.ParseResponse(resp) -// tests.AssertResponseMessage(t, response["message"].(string), "Timezone updated successfully") -// }) - -// t.Run("Timezone Not Found", func(t *testing.T) { -// router, authController := setup() - -// loginData := models.LoginRequestModel{ -// Email: adminUser.Email, -// Password: "password", -// } -// token := tests.GetLoginToken(t, router, *authController, loginData) - -// updateTimezone := models.Timezone{ -// Timezone: fmt.Sprintf("America/Los_Angeles-%s", utility.RandomString(10)), -// GmtOffset: fmt.Sprintf("-08:00+%s", utility.RandomString(3)), -// Description: fmt.Sprintf("pacific -%s", utility.RandomString(3)), -// } -// jsonBody, _ := json.Marshal(updateTimezone) - -// req, _ := http.NewRequest(http.MethodPatch, fmt.Sprintf("/api/v1/timezones/%s", utility.GenerateUUID()), bytes.NewBuffer(jsonBody)) -// req.Header.Set("Content-Type", "application/json") -// req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token)) - -// resp := httptest.NewRecorder() -// router.ServeHTTP(resp, req) - -// tests.AssertStatusCode(t, resp.Code, http.StatusNotFound) -// response := tests.ParseResponse(resp) -// tests.AssertResponseMessage(t, response["message"].(string), "record not found") -// }) - -// t.Run("Unauthorized Access", func(t *testing.T) { -// router, _ := setup() - -// updateTimezone := models.Timezone{ -// Timezone: fmt.Sprintf("America/Los_Angeles-%s", utility.RandomString(18)), -// GmtOffset: fmt.Sprintf("-08:00+%s", utility.RandomString(3)), -// Description: fmt.Sprintf("pacific -%s", utility.RandomString(3)), -// } -// jsonBody, _ := json.Marshal(updateTimezone) - -// req, _ := http.NewRequest(http.MethodPatch, fmt.Sprintf("/api/v1/timezones/%s", timezone.ID), bytes.NewBuffer(jsonBody)) -// req.Header.Set("Content-Type", "application/json") -// req.Header.Set("Authorization", "Bearer invalid_token") - -// resp := httptest.NewRecorder() -// router.ServeHTTP(resp, req) - -// tests.AssertStatusCode(t, resp.Code, http.StatusUnauthorized) -// response := tests.ParseResponse(resp) -// tests.AssertResponseMessage(t, response["message"].(string), "Token is invalid!") -// }) -// } +import ( + "bytes" + "encoding/json" + "fmt" + "net/http" + "net/http/httptest" + "testing" + + "github.com/gin-gonic/gin" + "github.com/hngprojects/hng_boilerplate_golang_web/internal/models" + "github.com/hngprojects/hng_boilerplate_golang_web/pkg/controller/auth" + "github.com/hngprojects/hng_boilerplate_golang_web/tests" + "github.com/hngprojects/hng_boilerplate_golang_web/utility" +) + +func TestUpdateTimezone(t *testing.T) { + _, saController := SetupSATestRouter() + db := saController.Db.Postgresql + + currUUID := utility.GenerateUUID() + password, _ := utility.HashPassword("password") + + adminUser := models.User{ + ID: utility.GenerateUUID(), + Name: "Admin User", + Email: fmt.Sprintf("admin%v@qa.team", currUUID), + Password: password, + Role: int(models.RoleIdentity.SuperAdmin), + } + + db.Create(&adminUser) + + timezone := models.Timezone{ + ID: utility.GenerateUUID(), + Timezone: fmt.Sprintf("America/New_York-%s", utility.RandomString(10)), + GmtOffset: fmt.Sprintf("-05:00+%s", utility.RandomString(3)), + Description: fmt.Sprintf("western -%s", utility.RandomString(3)), + } + db.Create(&timezone) + + setup := func() (*gin.Engine, *auth.Controller) { + router, saController := SetupSATestRouter() + authController := auth.Controller{ + Db: saController.Db, + Validator: saController.Validator, + Logger: saController.Logger, + } + + return router, &authController + } + + t.Run("Successful Update Timezone", func(t *testing.T) { + router, authController := setup() + + loginData := models.LoginRequestModel{ + Email: adminUser.Email, + Password: "password", + } + token := tests.GetLoginToken(t, router, *authController, loginData) + + updateTimezone := models.Timezone{ + Timezone: fmt.Sprintf("America/Los_Angeles-%s", utility.RandomString(10)), + GmtOffset: fmt.Sprintf("-08:00+%s", utility.RandomString(3)), + Description: fmt.Sprintf("pacific -%s", utility.RandomString(3)), + } + jsonBody, _ := json.Marshal(updateTimezone) + + req, _ := http.NewRequest(http.MethodPatch, fmt.Sprintf("/api/v1/timezones/%s", timezone.ID), bytes.NewBuffer(jsonBody)) + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token)) + + resp := httptest.NewRecorder() + router.ServeHTTP(resp, req) + + tests.AssertStatusCode(t, resp.Code, http.StatusOK) + response := tests.ParseResponse(resp) + tests.AssertResponseMessage(t, response["message"].(string), "Timezone updated successfully") + }) + + t.Run("Timezone Not Found", func(t *testing.T) { + router, authController := setup() + + loginData := models.LoginRequestModel{ + Email: adminUser.Email, + Password: "password", + } + token := tests.GetLoginToken(t, router, *authController, loginData) + + updateTimezone := models.Timezone{ + Timezone: fmt.Sprintf("America/Los_Angeles-%s", utility.RandomString(10)), + GmtOffset: fmt.Sprintf("-08:00+%s", utility.RandomString(3)), + Description: fmt.Sprintf("pacific -%s", utility.RandomString(3)), + } + jsonBody, _ := json.Marshal(updateTimezone) + + req, _ := http.NewRequest(http.MethodPatch, fmt.Sprintf("/api/v1/timezones/%s", utility.GenerateUUID()), bytes.NewBuffer(jsonBody)) + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token)) + + resp := httptest.NewRecorder() + router.ServeHTTP(resp, req) + + tests.AssertStatusCode(t, resp.Code, http.StatusNotFound) + response := tests.ParseResponse(resp) + tests.AssertResponseMessage(t, response["message"].(string), "record not found") + }) + + t.Run("Unauthorized Access", func(t *testing.T) { + router, _ := setup() + + updateTimezone := models.Timezone{ + Timezone: fmt.Sprintf("America/Los_Angeles-%s", utility.RandomString(18)), + GmtOffset: fmt.Sprintf("-08:00+%s", utility.RandomString(3)), + Description: fmt.Sprintf("pacific -%s", utility.RandomString(3)), + } + jsonBody, _ := json.Marshal(updateTimezone) + + req, _ := http.NewRequest(http.MethodPatch, fmt.Sprintf("/api/v1/timezones/%s", timezone.ID), bytes.NewBuffer(jsonBody)) + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Authorization", "Bearer invalid_token") + + resp := httptest.NewRecorder() + router.ServeHTTP(resp, req) + + tests.AssertStatusCode(t, resp.Code, http.StatusUnauthorized) + response := tests.ParseResponse(resp) + tests.AssertResponseMessage(t, response["message"].(string), "Token is invalid!") + }) +} From 109d25f957a37f811cbaf990c83ef00292d3033e Mon Sep 17 00:00:00 2001 From: Micah Shallom Bawa Date: Thu, 8 Aug 2024 06:00:46 +0100 Subject: [PATCH 08/45] chore: endpoint fixes and testcases --- internal/models/notifications.go | 82 ++++++----- .../notificationCRUD/notifications.go | 125 +++------------- pkg/controller/notificationCRUD/settings.go | 101 +++++++++++++ pkg/router/notification.go | 22 ++- pkg/router/router.go | 1 + services/notificationCRUD/notifications.go | 40 +++--- tests/base.go | 29 ++++ tests/test_notifications/base.go | 78 ++++++++++ .../notificationSettings_test.go | 103 ++++++++++++++ tests/test_notifications/notification_test.go | 133 ++++++++++++++++++ 10 files changed, 550 insertions(+), 164 deletions(-) create mode 100644 pkg/controller/notificationCRUD/settings.go create mode 100644 tests/test_notifications/base.go create mode 100644 tests/test_notifications/notificationSettings_test.go create mode 100644 tests/test_notifications/notification_test.go diff --git a/internal/models/notifications.go b/internal/models/notifications.go index d3e3c932..f2e2e726 100644 --- a/internal/models/notifications.go +++ b/internal/models/notifications.go @@ -1,6 +1,7 @@ package models import ( + "errors" "time" "github.com/gin-gonic/gin" @@ -33,6 +34,10 @@ type NotificationReq struct { Message string `json:"message"` } +type UpdateNotificationReq struct { + IsRead bool `json:"is_read"` +} + func (n *Notification) CreateNotification(db *gorm.DB) (Notification, error) { err := postgresql.CreateOneRecord(db, &n) @@ -40,7 +45,7 @@ func (n *Notification) CreateNotification(db *gorm.DB) (Notification, error) { notification := Notification{ ID: n.ID, UserID: n.UserID, - Message: n.UserID, + Message: n.Message, } if err != nil { @@ -49,16 +54,31 @@ func (n *Notification) CreateNotification(db *gorm.DB) (Notification, error) { return notification, nil } -func (n *Notification) FetchAllNotifications(db *gorm.DB, c *gin.Context) ([]Notification, map[string]interface{}, error) { +func (n *Notification) GetNotificationByID(db *gorm.DB, ID string) (Notification, error) { + var notification Notification + + err, _ := postgresql.SelectOneFromDb(db, ¬ification, "id = ?", ID) + if err != nil { + return notification, err + } + return notification, nil +} + +func (n *Notification) FetchAllNotifications(db *gorm.DB, c *gin.Context) ([]Notification, map[string]int64, error) { var notifications []Notification - type additionalData map[string]interface{} + type additionalData map[string]int64 - totalCount, err := postgresql.CountRecords(db, &n) + err := postgresql.SelectAllFromDb(db, "", ¬ifications, "") if err != nil { return nil, additionalData{}, err } - unreadCount, err := postgresql.CountSpecificRecords(db, &n, "is_read = false") + totalCount, err := postgresql.CountRecords(db, ¬ifications) + if err != nil { + return nil, additionalData{}, err + } + + unreadCount, err := postgresql.CountSpecificRecords(db, ¬ifications, "is_read = false") if err != nil { return nil, additionalData{}, err } @@ -68,16 +88,12 @@ func (n *Notification) FetchAllNotifications(db *gorm.DB, c *gin.Context) ([]Not "unread_count": unreadCount, } - err = postgresql.SelectAllFromDb(db, "", ¬ifications, "") - if err != nil { - return nil, additionalData{}, err - } return notifications, data, nil } -func (n *Notification) FetchUnReadNotifications(db *gorm.DB, c *gin.Context) ([]Notification, map[string]interface{}, error) { +func (n *Notification) FetchUnReadNotifications(db *gorm.DB, c *gin.Context) ([]Notification, map[string]int64, error) { var notifications []Notification - type additionalData map[string]interface{} + type additionalData map[string]int64 totalCount, err := postgresql.CountRecords(db, &n) if err != nil { @@ -101,53 +117,55 @@ func (n *Notification) FetchUnReadNotifications(db *gorm.DB, c *gin.Context) ([] return notifications, data, nil } -func (n *Notification) UpdateNotification(db *gorm.DB, ID string) (Notification, error) { - n.ID = ID +func (n *Notification) UpdateNotification(db *gorm.DB, notifReq UpdateNotificationReq, ID string) (*Notification, error) { - exists := postgresql.CheckExists(db, &Notification{}, "id = ?", ID) + exists := postgresql.CheckExists(db, &n, "id = ?", ID) if !exists { - return Notification{}, gorm.ErrRecordNotFound + return nil, gorm.ErrRecordNotFound } - err, nerr := postgresql.SelectOneFromDb(db, &n, "id = ?", ID) + oldNotification, err := n.GetNotificationByID(db, ID) if err != nil { - return Notification{}, nerr + return nil, err } - _, err = postgresql.SaveAllFields(db, n) + res, err := postgresql.UpdateFields(db, &n, notifReq, ID) if err != nil { - return Notification{}, err + return nil, err } - updatedNotification := Notification{} - err = db.First(&updatedNotification, "id = ?", ID).Error - if err != nil { - return Notification{}, err + if res.RowsAffected == 0 { + return nil, errors.New("failed to update notification") } - updatedNotification.UserID = n.UserID - updatedNotification.Message = n.Message - updatedNotification.IsRead = n.IsRead - return updatedNotification, nil + n.ID = oldNotification.ID + n.UserID = oldNotification.UserID + n.Message = oldNotification.Message + + return n, nil } -func (n *Notification) DeleteNotificationByID(db *gorm.DB, ID string) error { +func (n *Notification) DeleteNotificationByUserID(db *gorm.DB, ID string) error { + var notifications []Notification - exists := postgresql.CheckExists(db, &n, "id = ?", ID) + exists := postgresql.CheckExists(db, ¬ifications, "user_id = ?", ID) if !exists { return gorm.ErrRecordNotFound } - err := postgresql.DeleteRecordFromDb(db, &n) - + err := postgresql.SelectAllFromDb(db, "", ¬ifications, "user_id = ?", ID) if err != nil { return err } + err = postgresql.DeleteRecordFromDb(db, ¬ifications) + if err != nil { + return err + } return nil } -func (n *NotificationSettings) GetNotificationByID(db *gorm.DB, ID string) (NotificationSettings, error) { +func (n *NotificationSettings) GetNotificationSettingsByID(db *gorm.DB, ID string) (NotificationSettings, error) { var notificationSettings NotificationSettings err, nerr := postgresql.SelectOneFromDb(db, ¬ificationSettings, "user_id = ?", ID) diff --git a/pkg/controller/notificationCRUD/notifications.go b/pkg/controller/notificationCRUD/notifications.go index 4239d430..89058732 100644 --- a/pkg/controller/notificationCRUD/notifications.go +++ b/pkg/controller/notificationCRUD/notifications.go @@ -22,96 +22,6 @@ type Controller struct { ExtReq request.ExternalRequest } -func (base *Controller) UpdateNotificationSettings(c *gin.Context) { - var req models.NotificationSettings - - claims, exists := c.Get("userClaims") - if !exists { - rd := utility.BuildErrorResponse(http.StatusBadRequest, "error", "unable to get user claims", nil, nil) - c.JSON(http.StatusBadRequest, rd) - return - } - userClaims := claims.(jwt.MapClaims) - userId := userClaims["user_id"].(string) - - if err := c.ShouldBindJSON(&req); err != nil { - rd := utility.BuildErrorResponse(http.StatusBadRequest, "error", "Invalid request body", err, nil) - c.JSON(http.StatusBadRequest, rd) - return - } - - if err := base.Validator.Struct(&req); err != nil { - rd := utility.BuildErrorResponse(http.StatusUnprocessableEntity, "error", "Validation failed", utility.ValidationResponse(err, base.Validator), nil) - c.JSON(http.StatusUnprocessableEntity, rd) - return - } - - _, err := notificationcrud.GetNotificationSettings(base.Db.Postgresql, userId) - - if err != nil { - rd := utility.BuildErrorResponse(http.StatusInternalServerError, "error", "Failed to fetch notification settings", err, nil) - c.JSON(http.StatusInternalServerError, rd) - return - } - - updated, err := notificationcrud.UpdateNotificationSettings(base.Db.Postgresql, req, userId) - if err != nil { - rd := utility.BuildErrorResponse(http.StatusInternalServerError, "error", "Failed to update notification settings", err, nil) - c.JSON(http.StatusInternalServerError, rd) - return - } - - base.Logger.Info("Notification settings updated successfully") - rd := utility.BuildSuccessResponse(http.StatusOK, "Notification settings updated successfully", updated) - c.JSON(http.StatusOK, rd) -} - -func (base *Controller) GetNotificationSettings(c *gin.Context) { - claims, exists := c.Get("userClaims") - if !exists { - rd := utility.BuildErrorResponse(http.StatusBadRequest, "error", "unable to get user claims", nil, nil) - c.JSON(http.StatusBadRequest, rd) - return - } - userClaims := claims.(jwt.MapClaims) - userId := userClaims["user_id"].(string) - - _, err := notificationcrud.GetNotificationSettings(base.Db.Postgresql, userId) - if err != nil { - notificationSettings := models.NotificationSettings{ - ID: utility.GenerateUUID(), - UserID: userId, - MobilePushNotifications: false, - EmailNotificationActivityInWorkspace: false, - EmailNotificationAlwaysSendEmailNotifications: false, - EmailNotificationEmailDigest: false, - EmailNotificationAnnouncementAndUpdateEmails: false, - SlackNotificationsActivityOnYourWorkspace: false, - SlackNotificationsAlwaysSendEmailNotifications: false, - SlackNotificationsAnnouncementAndUpdateEmails: false, - } - - _, err := notificationSettings.CreateNotificationSettings(base.Db.Postgresql) - if err != nil { - base.Logger.Info("Failed to create notification settings") - rd := utility.BuildErrorResponse(http.StatusInternalServerError, "error", "Failed to create notification settings", err, nil) - c.JSON(http.StatusInternalServerError, rd) - return - } - } - - notificationSettings, err := notificationcrud.GetNotificationSettings(base.Db.Postgresql, userId) - if err != nil { - rd := utility.BuildErrorResponse(http.StatusInternalServerError, "error", "Failed to fetch notification settings", err, nil) - c.JSON(http.StatusInternalServerError, rd) - return - } - - base.Logger.Info("Notification settings retrieved successfully") - rd := utility.BuildSuccessResponse(http.StatusOK, "Notification settings retrieved successfully", notificationSettings) - c.JSON(http.StatusOK, rd) -} - func (base *Controller) CreateNotification(c *gin.Context) { var req models.NotificationReq @@ -155,6 +65,7 @@ func (base *Controller) CreateNotification(c *gin.Context) { func (base *Controller) FetchAllNotifications(c *gin.Context) { respData, addedData, err := notificationcrud.GetAllNotifications(c, base.Db.Postgresql) if err != nil { + base.Logger.Error("Failed to fetch notifications", err) rd := utility.BuildErrorResponse(http.StatusInternalServerError, "error", "Failed to fetch notifications", err, nil) c.JSON(http.StatusInternalServerError, rd) return @@ -172,16 +83,10 @@ func (base *Controller) FetchAllNotifications(c *gin.Context) { } func (base *Controller) FetchUnReadNotifications(c *gin.Context) { - is_read := c.Param("is_read") - - if is_read != "true" && is_read != "false" { - rd := utility.BuildErrorResponse(http.StatusBadRequest, "error", "Invalid Query Parameter", nil, nil) - c.JSON(http.StatusBadRequest, rd) - return - } respData, addedData, err := notificationcrud.GetUnreadNotifications(c, base.Db.Postgresql) if err != nil { + base.Logger.Error("Failed to fetch unread notifications", err) rd := utility.BuildErrorResponse(http.StatusInternalServerError, "error", "Failed to fetch unread notifications", err, nil) c.JSON(http.StatusInternalServerError, rd) return @@ -199,22 +104,25 @@ func (base *Controller) FetchUnReadNotifications(c *gin.Context) { } func (base *Controller) UpdateNotification(c *gin.Context) { - var req models.Notification - id := c.Param("notification_id") + var req models.UpdateNotificationReq + id := c.Param("notificationId") if _, err := uuid.Parse(id); err != nil { + base.Logger.Error("Invalid ID format", err) rd := utility.BuildErrorResponse(http.StatusBadRequest, "error", "Invalid ID format", err, nil) c.JSON(http.StatusBadRequest, rd) return } if err := c.ShouldBindJSON(&req); err != nil { + base.Logger.Error("Invalid request body", err) rd := utility.BuildErrorResponse(http.StatusBadRequest, "error", "Invalid request body", err, nil) c.JSON(http.StatusBadRequest, rd) return } if err := base.Validator.Struct(&req); err != nil { + base.Logger.Error("Validation failed", err) rd := utility.BuildErrorResponse(http.StatusUnprocessableEntity, "error", "Validation failed", utility.ValidationResponse(err, base.Validator), nil) c.JSON(http.StatusUnprocessableEntity, rd) return @@ -223,9 +131,11 @@ func (base *Controller) UpdateNotification(c *gin.Context) { result, err := notificationcrud.UpdateNotification(base.Db.Postgresql, req, id) if err != nil { if err == gorm.ErrRecordNotFound { + base.Logger.Error("Notification not found", err) rd := utility.BuildErrorResponse(http.StatusNotFound, "error", "notification not found", err, nil) c.JSON(http.StatusNotFound, rd) } else { + base.Logger.Error("Failed to update notification", err) rd := utility.BuildErrorResponse(http.StatusInternalServerError, "error", "Failed to update notification", err, nil) c.JSON(http.StatusInternalServerError, rd) } @@ -238,27 +148,34 @@ func (base *Controller) UpdateNotification(c *gin.Context) { } func (base *Controller) DeleteNotification(c *gin.Context) { - id := c.Param("notification_id") + claims, exists := c.Get("userClaims") + if !exists { + rd := utility.BuildErrorResponse(http.StatusBadRequest, "error", "unable to get user claims", nil, nil) + c.JSON(http.StatusBadRequest, rd) + return + } + userClaims := claims.(jwt.MapClaims) + userId := userClaims["user_id"].(string) - if _, err := uuid.Parse(id); err != nil { + if _, err := uuid.Parse(userId); err != nil { rd := utility.BuildErrorResponse(http.StatusBadRequest, "error", "Invalid ID format", err, nil) c.JSON(http.StatusBadRequest, rd) return } - err := notificationcrud.DeleteNotification(base.Db.Postgresql, id) + err := notificationcrud.DeleteNotification(base.Db.Postgresql, userId) if err != nil { if err == gorm.ErrRecordNotFound { rd := utility.BuildErrorResponse(http.StatusNotFound, "error", "Notification not found", err, nil) c.JSON(http.StatusNotFound, rd) } else { - rd := utility.BuildErrorResponse(http.StatusInternalServerError, "error", "Failed to delete Notification", err, nil) + rd := utility.BuildErrorResponse(http.StatusInternalServerError, "error", "Failed to clear Notifications", err, []models.Notification{}) c.JSON(http.StatusInternalServerError, rd) } return } - base.Logger.Info("Notification deleted successfully") + base.Logger.Info("Notifications cleared successfully") rd := utility.BuildSuccessResponse(http.StatusNoContent, "", nil) c.JSON(http.StatusNoContent, rd) diff --git a/pkg/controller/notificationCRUD/settings.go b/pkg/controller/notificationCRUD/settings.go new file mode 100644 index 00000000..209a0a74 --- /dev/null +++ b/pkg/controller/notificationCRUD/settings.go @@ -0,0 +1,101 @@ +package notificationCRUD + +import ( + "net/http" + + "github.com/gin-gonic/gin" + "github.com/golang-jwt/jwt" + "github.com/hngprojects/hng_boilerplate_golang_web/internal/models" + notificationcrud "github.com/hngprojects/hng_boilerplate_golang_web/services/notificationCRUD" + "github.com/hngprojects/hng_boilerplate_golang_web/utility" +) + +func (base *Controller) UpdateNotificationSettings(c *gin.Context) { + var req models.NotificationSettings + + claims, exists := c.Get("userClaims") + if !exists { + rd := utility.BuildErrorResponse(http.StatusBadRequest, "error", "unable to get user claims", nil, nil) + c.JSON(http.StatusBadRequest, rd) + return + } + userClaims := claims.(jwt.MapClaims) + userId := userClaims["user_id"].(string) + + if err := c.ShouldBindJSON(&req); err != nil { + rd := utility.BuildErrorResponse(http.StatusBadRequest, "error", "Invalid request body", err, nil) + c.JSON(http.StatusBadRequest, rd) + return + } + + if err := base.Validator.Struct(&req); err != nil { + rd := utility.BuildErrorResponse(http.StatusUnprocessableEntity, "error", "Validation failed", utility.ValidationResponse(err, base.Validator), nil) + c.JSON(http.StatusUnprocessableEntity, rd) + return + } + + _, err := notificationcrud.GetNotificationSettings(base.Db.Postgresql, userId) + + if err != nil { + rd := utility.BuildErrorResponse(http.StatusInternalServerError, "error", "Failed to fetch notification settings", err, nil) + c.JSON(http.StatusInternalServerError, rd) + return + } + + updated, err := notificationcrud.UpdateNotificationSettings(base.Db.Postgresql, req, userId) + if err != nil { + rd := utility.BuildErrorResponse(http.StatusInternalServerError, "error", "Failed to update notification settings", err, nil) + c.JSON(http.StatusInternalServerError, rd) + return + } + + base.Logger.Info("Notification settings updated successfully") + rd := utility.BuildSuccessResponse(http.StatusOK, "Notification settings updated successfully", updated) + c.JSON(http.StatusOK, rd) +} + +func (base *Controller) GetNotificationSettings(c *gin.Context) { + claims, exists := c.Get("userClaims") + if !exists { + rd := utility.BuildErrorResponse(http.StatusBadRequest, "error", "unable to get user claims", nil, nil) + c.JSON(http.StatusBadRequest, rd) + return + } + userClaims := claims.(jwt.MapClaims) + userId := userClaims["user_id"].(string) + + _, err := notificationcrud.GetNotificationSettings(base.Db.Postgresql, userId) + if err != nil { + notificationSettings := models.NotificationSettings{ + ID: utility.GenerateUUID(), + UserID: userId, + MobilePushNotifications: false, + EmailNotificationActivityInWorkspace: false, + EmailNotificationAlwaysSendEmailNotifications: false, + EmailNotificationEmailDigest: false, + EmailNotificationAnnouncementAndUpdateEmails: false, + SlackNotificationsActivityOnYourWorkspace: false, + SlackNotificationsAlwaysSendEmailNotifications: false, + SlackNotificationsAnnouncementAndUpdateEmails: false, + } + + _, err := notificationSettings.CreateNotificationSettings(base.Db.Postgresql) + if err != nil { + base.Logger.Info("Failed to create notification settings") + rd := utility.BuildErrorResponse(http.StatusInternalServerError, "error", "Failed to create notification settings", err, nil) + c.JSON(http.StatusInternalServerError, rd) + return + } + } + + notificationSettings, err := notificationcrud.GetNotificationSettings(base.Db.Postgresql, userId) + if err != nil { + rd := utility.BuildErrorResponse(http.StatusInternalServerError, "error", "Failed to fetch notification settings", err, nil) + c.JSON(http.StatusInternalServerError, rd) + return + } + + base.Logger.Info("Notification settings retrieved successfully") + rd := utility.BuildSuccessResponse(http.StatusOK, "Notification settings retrieved successfully", notificationSettings) + c.JSON(http.StatusOK, rd) +} diff --git a/pkg/router/notification.go b/pkg/router/notification.go index 1e071d06..6c8f870b 100644 --- a/pkg/router/notification.go +++ b/pkg/router/notification.go @@ -15,13 +15,23 @@ import ( func Notification(r *gin.Engine, ApiVersion string, validator *validator.Validate, db *storage.Database, logger *utility.Logger) *gin.Engine { extReq := request.ExternalRequest{Logger: logger, Test: false} controller := notificationCRUD.Controller{Db: db, Validator: validator, Logger: logger, ExtReq: extReq} - notificationUrl := r.Group(fmt.Sprintf("%v", ApiVersion), middleware.Authorize(db.Postgresql)) + notificationUrl := r.Group(fmt.Sprintf("%v/notifications", ApiVersion), middleware.Authorize(db.Postgresql)) + { + notificationUrl.POST("/global", controller.CreateNotification) + notificationUrl.GET("/all", controller.FetchAllNotifications) + notificationUrl.GET("/unread", controller.FetchUnReadNotifications) + notificationUrl.PATCH("/:notificationId", controller.UpdateNotification) + notificationUrl.DELETE("/clear", controller.DeleteNotification) + + } + return r +} + +func NotificationSettings(r *gin.Engine, ApiVersion string, validator *validator.Validate, db *storage.Database, logger *utility.Logger) *gin.Engine { + extReq := request.ExternalRequest{Logger: logger, Test: false} + controller := notificationCRUD.Controller{Db: db, Validator: validator, Logger: logger, ExtReq: extReq} + notificationUrl := r.Group(fmt.Sprintf("%v/settings", ApiVersion), middleware.Authorize(db.Postgresql)) { - notificationUrl.POST("/notifications", controller.CreateNotification) - notificationUrl.GET("/notifications", controller.FetchAllNotifications) - notificationUrl.GET("/notifications/:is_read", controller.FetchUnReadNotifications) - notificationUrl.PATCH("/notifications/:notification_id", controller.UpdateNotification) - notificationUrl.DELETE("/notifications/:notification_id", controller.DeleteNotification) notificationUrl.GET("/notification-settings", controller.GetNotificationSettings) notificationUrl.PATCH("/notification-settings", controller.UpdateNotificationSettings) } diff --git a/pkg/router/router.go b/pkg/router/router.go index c4e3225f..a5b27436 100644 --- a/pkg/router/router.go +++ b/pkg/router/router.go @@ -54,6 +54,7 @@ func Setup(logger *utility.Logger, validator *validator.Validate, db *storage.Da HelpCenter(r, ApiVersion, validator, db, logger) Profile(r, ApiVersion, validator, db, logger) Contact(r, ApiVersion, validator, db, logger) + NotificationSettings(r, ApiVersion, validator, db, logger) r.GET("/", func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{ diff --git a/services/notificationCRUD/notifications.go b/services/notificationCRUD/notifications.go index 9c7659ff..980caf42 100644 --- a/services/notificationCRUD/notifications.go +++ b/services/notificationCRUD/notifications.go @@ -19,20 +19,21 @@ func CreateNotification(db *gorm.DB, notificationReq models.NotificationReq, use if err != nil { return notification, err } + return createdNotification, nil } -func GetAllNotifications(c *gin.Context, db *gorm.DB) ([]models.Notification, map[string]interface{}, error) { - var notification models.Notification +func GetAllNotifications(c *gin.Context, db *gorm.DB) ([]models.Notification, map[string]int64, error) { + var n models.Notification - notifications, addedData, err := notification.FetchAllNotifications(db, c) + notifications, addedData, err := n.FetchAllNotifications(db, c) if err != nil { return nil, nil, err } return notifications, addedData, nil } -func GetUnreadNotifications(c *gin.Context, db *gorm.DB) ([]models.Notification, map[string]interface{}, error) { +func GetUnreadNotifications(c *gin.Context, db *gorm.DB) ([]models.Notification, map[string]int64, error) { var notification models.Notification notifications, addedData, err := notification.FetchUnReadNotifications(db, c) @@ -42,14 +43,22 @@ func GetUnreadNotifications(c *gin.Context, db *gorm.DB) ([]models.Notification, return notifications, addedData, nil } -func UpdateNotification(db *gorm.DB, notificationReq models.Notification, ID string) (models.Notification, error) { - updatedNotification, err := notificationReq.UpdateNotification(db, ID) +func UpdateNotification(db *gorm.DB, notificationReq models.UpdateNotificationReq, ID string) (*models.Notification, error) { + var n models.Notification + + updatedNotification, err := n.UpdateNotification(db, notificationReq, ID) if err != nil { return updatedNotification, err } return updatedNotification, nil } +func DeleteNotification(db *gorm.DB, ID string) error { + var n models.Notification + + return n.DeleteNotificationByUserID(db, ID) +} + func UpdateNotificationSettings(db *gorm.DB, notificationSettings models.NotificationSettings, ID string) (models.NotificationSettings, error) { var notificationSet models.NotificationSettings @@ -71,25 +80,12 @@ func UpdateNotificationSettings(db *gorm.DB, notificationSettings models.Notific return updatedNotificationSettings, nil } - - -func DeleteNotification(db *gorm.DB, ID string) error { - notification := models.Notification{ID: ID} - - err := notification.DeleteNotificationByID(db, ID) - if err != nil { - return err - } - return nil -} - - -func GetNotificationSettings(db *gorm.DB, id string) (models.NotificationSettings, error){ +func GetNotificationSettings(db *gorm.DB, id string) (models.NotificationSettings, error) { var notificationSettings models.NotificationSettings - notificationSettings, err := notificationSettings.GetNotificationByID(db, id) + notificationSettings, err := notificationSettings.GetNotificationSettingsByID(db, id) if err != nil { return notificationSettings, err } return notificationSettings, nil -} \ No newline at end of file +} diff --git a/tests/base.go b/tests/base.go index b27b5fac..bc8935ce 100644 --- a/tests/base.go +++ b/tests/base.go @@ -16,6 +16,7 @@ import ( "github.com/hngprojects/hng_boilerplate_golang_web/internal/models/migrations" "github.com/hngprojects/hng_boilerplate_golang_web/internal/models/seed" "github.com/hngprojects/hng_boilerplate_golang_web/pkg/controller/auth" + "github.com/hngprojects/hng_boilerplate_golang_web/pkg/controller/notificationCRUD" "github.com/hngprojects/hng_boilerplate_golang_web/pkg/controller/organisation" "github.com/hngprojects/hng_boilerplate_golang_web/pkg/middleware" "github.com/hngprojects/hng_boilerplate_golang_web/pkg/repository/storage" @@ -163,3 +164,31 @@ func CreateOrganisation(t *testing.T, r *gin.Engine, db *storage.Database, org o orgID := dataM["id"].(string) return orgID } + +func CreateNotification(t *testing.T, r *gin.Engine, db *storage.Database, not notificationCRUD.Controller, notData models.NotificationReq, token string) string { + var ( + orgPath = "/api/v1/notifications" + orgURI = url.URL{Path: orgPath} + ) + orgUrl := r.Group(fmt.Sprintf("%v", "/api/v1"), middleware.Authorize(db.Postgresql)) + { + orgUrl.POST("/notifications", not.CreateNotification) + } + var b bytes.Buffer + json.NewEncoder(&b).Encode(notData) + req, err := http.NewRequest(http.MethodPost, orgURI.String(), &b) + if err != nil { + t.Fatal(err) + } + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Authorization", "Bearer "+token) + + rr := httptest.NewRecorder() + r.ServeHTTP(rr, req) + + //get the response + data := ParseResponse(rr) + dataM := data["data"].(map[string]interface{}) + notID := dataM["id"].(string) + return notID +} diff --git a/tests/test_notifications/base.go b/tests/test_notifications/base.go new file mode 100644 index 00000000..34388439 --- /dev/null +++ b/tests/test_notifications/base.go @@ -0,0 +1,78 @@ +package test_notifications + +import ( + "fmt" + "testing" + + "github.com/gin-gonic/gin" + "github.com/go-playground/validator/v10" + + "github.com/hngprojects/hng_boilerplate_golang_web/internal/models" + "github.com/hngprojects/hng_boilerplate_golang_web/pkg/controller/auth" + "github.com/hngprojects/hng_boilerplate_golang_web/pkg/controller/notificationCRUD" + + "github.com/hngprojects/hng_boilerplate_golang_web/pkg/repository/storage" + tst "github.com/hngprojects/hng_boilerplate_golang_web/tests" + "github.com/hngprojects/hng_boilerplate_golang_web/utility" +) + +type NotificationSetupResp struct { + NotificationController *notificationCRUD.Controller + Router *gin.Engine + Token string + NotifID string + DB *storage.Database +} + +func NotifSetup(t *testing.T, admin bool) NotificationSetupResp { + logger := tst.Setup() + gin.SetMode(gin.TestMode) + validatorRef := validator.New() + db := storage.Connection() + currUUID := utility.GenerateUUID() + email := fmt.Sprintf("testuser" + currUUID + "@qa.team") + + userSignUpData := models.CreateUserRequestModel{ + Email: email, + PhoneNumber: fmt.Sprintf("+234%v", utility.GetRandomNumbersInRange(7000000000, 9099999999)), + FirstName: "test", + LastName: "user", + Password: "password", + UserName: fmt.Sprintf("test_username%v", currUUID), + } + + loginData := models.LoginRequestModel{ + Email: userSignUpData.Email, + Password: userSignUpData.Password, + } + + notifController := ¬ificationCRUD.Controller{ + Db: db, + Validator: validatorRef, + Logger: logger, + } + + + auth := auth.Controller{Db: db, Validator: validatorRef, Logger: logger} + r := gin.Default() + tst.SignupUser(t, r, auth, userSignUpData, admin) + token := tst.GetLoginToken(t, r, auth, loginData) + + //create an organisation + notReq := models.NotificationReq{ + Message: "Test Notification", + } + + not := notificationCRUD.Controller{Db: db, Validator: validatorRef, Logger: logger} + not_id := tst.CreateNotification(t, r, db, not, notReq, token) + + notifResp := NotificationSetupResp{ + NotificationController: notifController, + Router: r, + Token: token, + NotifID: not_id, + DB: db, + } + + return notifResp +} diff --git a/tests/test_notifications/notificationSettings_test.go b/tests/test_notifications/notificationSettings_test.go new file mode 100644 index 00000000..637715ec --- /dev/null +++ b/tests/test_notifications/notificationSettings_test.go @@ -0,0 +1,103 @@ +package test_notifications + +import ( + "bytes" + "encoding/json" + "fmt" + "net/http" + "net/http/httptest" + "testing" + + "github.com/gin-gonic/gin" + + "github.com/hngprojects/hng_boilerplate_golang_web/pkg/middleware" + tst "github.com/hngprojects/hng_boilerplate_golang_web/tests" +) + +func TestNotificationSettings(t *testing.T) { + + setup := NotifSetup(t, false) + + tests := []struct { + Name string + Method string + RequestURI string + RequestBody interface{} + ExpectedCode int + Message string + ErrorField string + ErrorMessage string + Headers map[string]string + }{ + { + Name: "Get Notification Settings Action", + RequestURI: "/api/v1/settings/notification-settings", + Method: http.MethodGet, + ExpectedCode: http.StatusOK, + Message: "Notification settings retrieved successfully", + Headers: map[string]string{ + "Content-Type": "application/json", + "Authorization": "Bearer " + setup.Token, + }, + }, { + Name: "Patch Notifications Settings Action", + RequestURI: "/api/v1/settings/notification-settings", + Method: http.MethodPatch, + ExpectedCode: http.StatusOK, + Message: "Notification settings updated successfully", + Headers: map[string]string{ + "Content-Type": "application/json", + "Authorization": "Bearer " + setup.Token, + }, + }, + } + + for _, test := range tests { + r := gin.Default() + + notificationUrl := r.Group(fmt.Sprintf("%v", "/api/v1/settings/"), middleware.Authorize(setup.DB.Postgresql)) + { + notificationUrl.GET("/notification-settings", setup.NotificationController.GetNotificationSettings) + notificationUrl.PATCH("/notification-settings", setup.NotificationController.UpdateNotificationSettings) + } + + t.Run(test.Name, func(t *testing.T) { + var b bytes.Buffer + json.NewEncoder(&b).Encode(test.RequestBody) + + req, err := http.NewRequest(test.Method, test.RequestURI, &b) + if err != nil { + t.Fatal(err) + } + + for i, v := range test.Headers { + req.Header.Set(i, v) + } + + rr := httptest.NewRecorder() + r.ServeHTTP(rr, req) + + tst.AssertStatusCode(t, rr.Code, test.ExpectedCode) + + data := tst.ParseResponse(rr) + + code := int(data["status_code"].(float64)) + tst.AssertStatusCode(t, code, test.ExpectedCode) + + if test.Message != "" { + message := data["message"] + if message != nil { + tst.AssertResponseMessage(t, message.(string), test.Message) + } else { + tst.AssertResponseMessage(t, "", test.Message) + } + + if test.ErrorField != "" { + errorData := data["error"].(map[string]interface{}) + errorMessage := errorData[test.ErrorField].(string) + tst.AssertResponseMessage(t, errorMessage, test.ErrorMessage) + } + } + }) + } +} diff --git a/tests/test_notifications/notification_test.go b/tests/test_notifications/notification_test.go new file mode 100644 index 00000000..80ebca08 --- /dev/null +++ b/tests/test_notifications/notification_test.go @@ -0,0 +1,133 @@ +package test_notifications + +import ( + "bytes" + "encoding/json" + "fmt" + "net/http" + "net/http/httptest" + "testing" + + "github.com/gin-gonic/gin" + + "github.com/hngprojects/hng_boilerplate_golang_web/internal/models" + + "github.com/hngprojects/hng_boilerplate_golang_web/pkg/middleware" + tst "github.com/hngprojects/hng_boilerplate_golang_web/tests" +) + +func TestNotification(t *testing.T) { + + setup := NotifSetup(t, false) + + tests := []struct { + Name string + RequestBody interface{} + Method string + RequestURI string + ExpectedCode int + Message string + ErrorField string + ErrorMessage string + Headers map[string]string + }{ + { + Name: "Notification Creation Action", + RequestBody: models.NotificationReq{ + Message: "Welcome to HNGi8", + }, + RequestURI: "/api/v1/notifications/global", + Method: http.MethodPost, + ExpectedCode: http.StatusCreated, + Message: "Notification created successfully", + Headers: map[string]string{ + "Content-Type": "application/json", + "Authorization": "Bearer " + setup.Token, + }, + }, { + Name: "Fetch All Notifications Action", + RequestURI: "/api/v1/notifications/all", + Method: http.MethodGet, + ExpectedCode: http.StatusOK, + Message: "Notifications retrieved successfully", + Headers: map[string]string{ + "Content-Type": "application/json", + "Authorization": "Bearer " + setup.Token, + }, + }, { + Name: "Fetch Unread Notifications Action", + RequestURI: "/api/v1/notifications/unread", + Method: http.MethodGet, + ExpectedCode: http.StatusOK, + Message: "Unread Notifications retrieved successfully", + Headers: map[string]string{ + "Content-Type": "application/json", + "Authorization": "Bearer " + setup.Token, + }, + },{ + Name: "Update Notification Action", + RequestBody: models.UpdateNotificationReq{ + IsRead: true, + }, + RequestURI: fmt.Sprintf("/api/v1/notifications/%s", setup.NotifID), + Method: http.MethodPut, + ExpectedCode: http.StatusOK, + Message: "Notification updated successfully", + Headers: map[string]string{ + "Content-Type": "application/json", + "Authorization": "Bearer " + setup.Token, + }, + }, + } + + for _, test := range tests { + r := gin.Default() + + notificationUrl := r.Group(fmt.Sprintf("%v", "/api/v1/notifications"), middleware.Authorize(setup.DB.Postgresql)) + { + notificationUrl.POST("/global", setup.NotificationController.CreateNotification) + notificationUrl.GET("/all", setup.NotificationController.FetchAllNotifications) + notificationUrl.GET("/unread", setup.NotificationController.FetchUnReadNotifications) + notificationUrl.PUT("/:notificationId", setup.NotificationController.UpdateNotification) + } + + t.Run(test.Name, func(t *testing.T) { + var b bytes.Buffer + json.NewEncoder(&b).Encode(test.RequestBody) + + req, err := http.NewRequest(test.Method, test.RequestURI, &b) + if err != nil { + t.Fatal(err) + } + + for i, v := range test.Headers { + req.Header.Set(i, v) + } + + rr := httptest.NewRecorder() + r.ServeHTTP(rr, req) + + tst.AssertStatusCode(t, rr.Code, test.ExpectedCode) + + data := tst.ParseResponse(rr) + + code := int(data["status_code"].(float64)) + tst.AssertStatusCode(t, code, test.ExpectedCode) + + if test.Message != "" { + message := data["message"] + if message != nil { + tst.AssertResponseMessage(t, message.(string), test.Message) + } else { + tst.AssertResponseMessage(t, "", test.Message) + } + + if test.ErrorField != "" { + errorData := data["error"].(map[string]interface{}) + errorMessage := errorData[test.ErrorField].(string) + tst.AssertResponseMessage(t, errorMessage, test.ErrorMessage) + } + } + }) + } +} From 82a56b43833cb00da405537bb86e9c619e8f027d Mon Sep 17 00:00:00 2001 From: Osinachi Chukwujama Date: Thu, 8 Aug 2024 06:53:55 +0100 Subject: [PATCH 09/45] chore: update the workflows --- .github/workflows/development.yml | 75 +++++++++++------------------- .github/workflows/production.yml | 77 ++++++++++++------------------- .github/workflows/staging.yml | 76 +++++++++++------------------- 3 files changed, 85 insertions(+), 143 deletions(-) diff --git a/.github/workflows/development.yml b/.github/workflows/development.yml index 0fd33c1d..02d8796d 100644 --- a/.github/workflows/development.yml +++ b/.github/workflows/development.yml @@ -44,6 +44,7 @@ jobs: --health-retries 5 ports: - 6379:6379 + steps: - name: Checkout Code uses: actions/checkout@v4 @@ -58,48 +59,28 @@ jobs: - name: Run the application run: nohup ./development_app > /dev/null 2>&1 & - name: Wait for application to start - run: sleep 30s - - name: Test for reachability - run: curl http://localhost:8019 + run: | + for i in {1..30}; do + curl -s http://localhost:8019 && break + sleep 1 + done - name: Run All Tests - run: go test ./... -timeout 99999s + run: go test ./... -timeout 300s deploy: runs-on: ubuntu-latest needs: build_and_test if: github.event_name == 'push' environment: development - env: - SSH_USERNAME: ${{ secrets.SSH_USERNAME }} - SSH_HOST: ${{ secrets.SSH_HOST }} - SSH_PORT: ${{ secrets.SSH_PORT }} - SSH_PASSWORD: ${{ secrets.SSH_PASSWORD }} - SERVER_PORT: "7000" - DB_NAME: "development_db" - USERNAME: "development_user" - APP_NAME: "development" - APP_URL: "https://deployment.api-golang.boilerplate.hng.tech" - REDIS_PORT: "6130" - REDIS_HOST: "0.0.0.0" - REDIS_DB: "0" - GOOGLE_CLIENT_ID: ${{ secrets.GOOGLE_CLIENT_ID }} - GOOGLE_CLIENT_SECRET: ${{ secrets.GOOGLE_CLIENT_SECRET }} - FACEBOOK_CLIENT_ID: ${{ secrets.FACEBOOK_CLIENT_ID }} - FACEBOOK_CLIENT_SECRET: ${{ secrets.FACEBOOK_CLIENT_SECRET }} - SESSION_SECRET: ${{ secrets.SESSION_SECRET }} - MAIL_SERVER: ${{ secrets.MAIL_SERVER }} - MAIL_USERNAME: ${{ secrets.MAIL_USERNAME }} - MAIL_PASSWORD: ${{ secrets.MAIL_PASSWORD }} - MAIL_PORT: ${{ secrets.MAIL_PORT }} steps: - name: SSH into server and deploy uses: appleboy/ssh-action@v1.0.3 with: - host: ${{ env.SSH_HOST }} - username: ${{ env.SSH_USERNAME }} - password: ${{ env.SSH_PASSWORD }} - port: ${{ env.SSH_PORT }} + host: ${{ secrets.SSH_HOST }} + username: ${{ secrets.SSH_USERNAME }} + password: ${{ secrets.SSH_PASSWORD }} + port: ${{ secrets.SSH_PORT }} script: | export APPROOT=~/deployments/development mkdir -p $APPROOT @@ -115,21 +96,21 @@ jobs: fi bash ./scripts/deploy_app.sh development https://github.com/${{ github.repository}} \ - SERVER_PORT=${{ env.SERVER_PORT }} \ - DB_NAME=${{ env.DB_NAME }} \ - USERNAME=${{ env.USERNAME }} \ - APP_NAME=${{ env.APP_NAME }} \ - APP_URL=${{ env.APP_URL }} \ - REDIS_PORT=${{ env.REDIS_PORT }} \ - REDIS_HOST=${{ env.REDIS_HOST }} \ - REDIS_DB=${{ env.REDIS_DB }} \ - GOOGLE_CLIENT_ID=${{ env.GOOGLE_CLIENT_ID }} \ - GOOGLE_CLIENT_SECRET=${{ env.GOOGLE_CLIENT_SECRET }} \ - FACEBOOK_CLIENT_ID=${{ env.FACEBOOK_CLIENT_ID }} \ - FACEBOOK_CLIENT_SECRET=${{ env.FACEBOOK_CLIENT_SECRET }} \ - SESSION_SECRET=${{ env.SESSION_SECRET }} \ - MAIL_SERVER=${{ env.MAIL_SERVER }} \ - MAIL_USERNAME=${{ env.MAIL_USERNAME }} \ - MAIL_PASSWORD=${{ env.MAIL_PASSWORD }} \ - MAIL_PORT=${{ env.MAIL_PORT }} \ + SERVER_PORT=${{ secrets.SERVER_PORT }} \ + DB_NAME=${{ secrets.DB_NAME }} \ + USERNAME=${{ secrets.USERNAME }} \ + APP_NAME="development" \ + APP_URL="https://deployment.api-golang.boilerplate.hng.tech" \ + REDIS_PORT=${{ secrets.REDIS_PORT }} \ + REDIS_HOST=${{ secrets.REDIS_HOST }} \ + REDIS_DB="0" \ + GOOGLE_CLIENT_ID=${{ secrets.GOOGLE_CLIENT_ID }} \ + GOOGLE_CLIENT_SECRET=${{ secrets.GOOGLE_CLIENT_SECRET }} \ + FACEBOOK_CLIENT_ID=${{ secrets.FACEBOOK_CLIENT_ID }} \ + FACEBOOK_CLIENT_SECRET=${{ secrets.FACEBOOK_CLIENT_SECRET }} \ + SESSION_SECRET=${{ secrets.SESSION_SECRET }} \ + MAIL_SERVER=${{ secrets.MAIL_SERVER }} \ + MAIL_USERNAME=${{ secrets.MAIL_USERNAME }} \ + MAIL_PASSWORD=${{ secrets.MAIL_PASSWORD }} \ + MAIL_PORT=${{ secrets.MAIL_PORT }} \ MIGRATE=true diff --git a/.github/workflows/production.yml b/.github/workflows/production.yml index 0db67a8a..0436c4db 100644 --- a/.github/workflows/production.yml +++ b/.github/workflows/production.yml @@ -54,47 +54,28 @@ jobs: - name: Run the application run: nohup ./production_app > /dev/null 2>&1 & - name: Wait for application to start - run: sleep 30s - - name: Test for reachability - run: curl http://localhost:8019 + run: | + for i in {1..30}; do + curl -s http://localhost:8019 && break + sleep 1 + done - name: Run All Tests - run: go test ./... -timeout 99999s + run: go test ./... -timeout 300s deploy: runs-on: ubuntu-latest needs: build_and_test if: github.event_name == 'push' environment: production - env: - SSH_USERNAME: ${{ secrets.SSH_USERNAME }} - SSH_HOST: ${{ secrets.SSH_HOST }} - SSH_PORT: ${{ secrets.SSH_PORT }} - SSH_PASSWORD: ${{ secrets.SSH_PASSWORD }} - SERVER_PORT: 9000 - DB_NAME: "production_db" - USERNAME: "production_user" - APP_NAME: "production" - APP_URL: "https://api-golang.boilerplate.hng.tech" - REDIS_PORT: 6132 - REDIS_HOST: "0.0.0.0" - REDIS_DB: 0 - GOOGLE_CLIENT_ID: ${{ secrets.GOOGLE_CLIENT_ID }} - GOOGLE_CLIENT_SECRET: ${{ secrets.GOOGLE_CLIENT_SECRET }} - FACEBOOK_CLIENT_ID: ${{ secrets.FACEBOOK_CLIENT_ID }} - FACEBOOK_CLIENT_SECRET: ${{ secrets.FACEBOOK_CLIENT_SECRET }} - SESSION_SECRET: ${{ secrets.SESSION_SECRET }} - MAIL_SERVER: ${{ secrets.MAIL_SERVER }} - MAIL_USERNAME: ${{ secrets.MAIL_USERNAME }} - MAIL_PASSWORD: ${{ secrets.MAIL_PASSWORD }} - MAIL_PORT: ${{ secrets.MAIL_PORT }} + steps: - name: SSH into server and deploy uses: appleboy/ssh-action@v1.0.3 with: - host: ${{ env.SSH_HOST }} - username: ${{ env.SSH_USERNAME }} - password: ${{ env.SSH_PASSWORD }} - port: ${{ env.SSH_PORT }} + host: ${{ secrets.SSH_HOST }} + username: ${{ secrets.SSH_USERNAME }} + password: ${{ secrets.SSH_PASSWORD }} + port: ${{ secrets.SSH_PORT }} script: | export APPROOT=~/deployments/production mkdir -p $APPROOT @@ -109,22 +90,22 @@ jobs: git clone -b main https://github.com/${{ github.repository}} . || { echo "Failed to clone repository"; exit 1; } fi - bash ./scripts/deploy_app.sh production https://github.com/${{ github.repository}} - SERVER_PORT=${{ env.SERVER_PORT }} \ - DB_NAME=${{ env.DB_NAME }} \ - USERNAME=${{ env.USERNAME }} \ - APP_NAME=${{ env.APP_NAME }} \ - APP_URL=${{ env.APP_URL }} \ - REDIS_PORT=${{ env.REDIS_PORT }} \ - REDIS_HOST=${{ env.REDIS_HOST }} \ - REDIS_DB=${{ env.REDIS_DB }} \ - GOOGLE_CLIENT_ID=${{ env.GOOGLE_CLIENT_ID }} \ - GOOGLE_CLIENT_SECRET=${{ env.GOOGLE_CLIENT_SECRET }} \ - FACEBOOK_CLIENT_ID=${{ env.FACEBOOK_CLIENT_ID }} \ - FACEBOOK_CLIENT_SECRET=${{ env.FACEBOOK_CLIENT_SECRET }} \ - SESSION_SECRET=${{ env.SESSION_SECRET }} \ - MAIL_SERVER=${{ env.MAIL_SERVER }} \ - MAIL_USERNAME=${{ env.MAIL_USERNAME }} \ - MAIL_PASSWORD=${{ env.MAIL_PASSWORD }} \ - MAIL_PORT=${{ env.MAIL_PORT }} \ + bash ./scripts/deploy_app.sh production https://github.com/${{ github.repository}} \ + SERVER_PORT=${{ secrets.SERVER_PORT }} \ + DB_NAME=${{ secrets.DB_NAME }} \ + USERNAME=${{ secrets.USERNAME }} \ + APP_NAME="production" \ + APP_URL="https://api-golang.boilerplate.hng.tech" \ + REDIS_PORT=${{ secrets.REDIS_PORT }} \ + REDIS_HOST=${{ secrets.REDIS_HOST }} \ + REDIS_DB="0" \ + GOOGLE_CLIENT_ID=${{ secrets.GOOGLE_CLIENT_ID }} \ + GOOGLE_CLIENT_SECRET=${{ secrets.GOOGLE_CLIENT_SECRET }} \ + FACEBOOK_CLIENT_ID=${{ secrets.FACEBOOK_CLIENT_ID }} \ + FACEBOOK_CLIENT_SECRET=${{ secrets.FACEBOOK_CLIENT_SECRET }} \ + SESSION_SECRET=${{ secrets.SESSION_SECRET }} \ + MAIL_SERVER=${{ secrets.MAIL_SERVER }} \ + MAIL_USERNAME=${{ secrets.MAIL_USERNAME }} \ + MAIL_PASSWORD=${{ secrets.MAIL_PASSWORD }} \ + MAIL_PORT=${{ secrets.MAIL_PORT }} \ MIGRATE=true diff --git a/.github/workflows/staging.yml b/.github/workflows/staging.yml index b3d1197b..f6e802fd 100644 --- a/.github/workflows/staging.yml +++ b/.github/workflows/staging.yml @@ -54,48 +54,28 @@ jobs: - name: Run the application run: nohup ./staging_app > /dev/null 2>&1 & - name: Wait for application to start - run: sleep 30s - - name: Test for reachability - run: curl http://localhost:8019 + run: | + for i in {1..30}; do + curl -s http://localhost:8019 && break + sleep 1 + done - name: Run All Tests - run: go test ./... -timeout 99999s + run: go test ./... -timeout 300s deploy: runs-on: ubuntu-latest needs: build_and_test if: github.event_name == 'push' environment: staging - env: - SSH_USERNAME: ${{ secrets.SSH_USERNAME }} - SSH_HOST: ${{ secrets.SSH_HOST }} - SSH_PORT: ${{ secrets.SSH_PORT }} - SSH_PASSWORD: ${{ secrets.SSH_PASSWORD }} - SERVER_PORT: 8000 - DB_NAME: "staging_db" - USERNAME: "staging_user" - APP_NAME: "staging" - APP_URL: "https://staging.api-golang.boilerplate.hng.tech" - REDIS_PORT: 6131 - REDIS_HOST: "0.0.0.0" - REDIS_DB: 0 - GOOGLE_CLIENT_ID: ${{ secrets.GOOGLE_CLIENT_ID }} - GOOGLE_CLIENT_SECRET: ${{ secrets.GOOGLE_CLIENT_SECRET }} - FACEBOOK_CLIENT_ID: ${{ secrets.FACEBOOK_CLIENT_ID }} - FACEBOOK_CLIENT_SECRET: ${{ secrets.FACEBOOK_CLIENT_SECRET }} - SESSION_SECRET: ${{ secrets.SESSION_SECRET }} - MAIL_SERVER: ${{ secrets.MAIL_SERVER }} - MAIL_USERNAME: ${{ secrets.MAIL_USERNAME }} - MAIL_PASSWORD: ${{ secrets.MAIL_PASSWORD }} - MAIL_PORT: ${{ secrets.MAIL_PORT }} steps: - name: SSH into server and deploy uses: appleboy/ssh-action@v1.0.3 with: - host: ${{ env.SSH_HOST }} - username: ${{ env.SSH_USERNAME }} - password: ${{ env.SSH_PASSWORD }} - port: ${{ env.SSH_PORT }} + host: ${{ secrets.SSH_HOST }} + username: ${{ secrets.SSH_USERNAME }} + password: ${{ secrets.SSH_PASSWORD }} + port: ${{ secrets.SSH_PORT }} script: | export APPROOT=~/deployments/staging mkdir -p $APPROOT @@ -110,22 +90,22 @@ jobs: git clone -b staging https://github.com/${{ github.repository}} . || { echo "Failed to clone repository"; exit 1; } fi - bash ./scripts/deploy_app.sh staging https://github.com/${{ github.repository}} - SERVER_PORT=${{ env.SERVER_PORT }} \ - DB_NAME=${{ env.DB_NAME }} \ - USERNAME=${{ env.USERNAME }} \ - APP_NAME=${{ env.APP_NAME }} \ - APP_URL=${{ env.APP_URL }} \ - REDIS_PORT=${{ env.REDIS_PORT }} \ - REDIS_HOST=${{ env.REDIS_HOST }} \ - REDIS_DB=${{ env.REDIS_DB }} \ - GOOGLE_CLIENT_ID=${{ env.GOOGLE_CLIENT_ID }} \ - GOOGLE_CLIENT_SECRET=${{ env.GOOGLE_CLIENT_SECRET }} \ - FACEBOOK_CLIENT_ID=${{ env.FACEBOOK_CLIENT_ID }} \ - FACEBOOK_CLIENT_SECRET=${{ env.FACEBOOK_CLIENT_SECRET }} \ - SESSION_SECRET=${{ env.SESSION_SECRET }} \ - MAIL_SERVER=${{ env.MAIL_SERVER }} \ - MAIL_USERNAME=${{ env.MAIL_USERNAME }} \ - MAIL_PASSWORD=${{ env.MAIL_PASSWORD }} \ - MAIL_PORT=${{ env.MAIL_PORT }} \ + bash ./scripts/deploy_app.sh staging https://github.com/${{ github.repository}} \ + SERVER_PORT=${{ secrets.SERVER_PORT }} \ + DB_NAME=${{ secrets.DB_NAME }} \ + USERNAME=${{ secrets.USERNAME }} \ + APP_NAME="staging" \ + APP_URL="https://staging.api-golang.boilerplate.hng.tech" \ + REDIS_PORT=${{ secrets.REDIS_PORT }} \ + REDIS_HOST=${{ secrets.REDIS_HOST }} \ + REDIS_DB="0" \ + GOOGLE_CLIENT_ID=${{ secrets.GOOGLE_CLIENT_ID }} \ + GOOGLE_CLIENT_SECRET=${{ secrets.GOOGLE_CLIENT_SECRET }} \ + FACEBOOK_CLIENT_ID=${{ secrets.FACEBOOK_CLIENT_ID }} \ + FACEBOOK_CLIENT_SECRET=${{ secrets.FACEBOOK_CLIENT_SECRET }} \ + SESSION_SECRET=${{ secrets.SESSION_SECRET }} \ + MAIL_SERVER=${{ secrets.MAIL_SERVER }} \ + MAIL_USERNAME=${{ secrets.MAIL_USERNAME }} \ + MAIL_PASSWORD=${{ secrets.MAIL_PASSWORD }} \ + MAIL_PORT=${{ secrets.MAIL_PORT }} \ MIGRATE=true From 7eee2fdc40f4501d080c8271eea940b24da37a98 Mon Sep 17 00:00:00 2001 From: ekedonald <143762587+ekedonald@users.noreply.github.com> Date: Thu, 8 Aug 2024 09:00:59 +0100 Subject: [PATCH 10/45] Update development.yml Modified the event trigger for the deploy job from push to pull request. Signed-off-by: ekedonald <143762587+ekedonald@users.noreply.github.com> --- .github/workflows/development.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/development.yml b/.github/workflows/development.yml index 02d8796d..8a5f7c3b 100644 --- a/.github/workflows/development.yml +++ b/.github/workflows/development.yml @@ -70,7 +70,7 @@ jobs: deploy: runs-on: ubuntu-latest needs: build_and_test - if: github.event_name == 'push' + if: github.event_name == 'pull_request' environment: development steps: From f0684c86a64abe083ed0f6692cdc714a857a9427 Mon Sep 17 00:00:00 2001 From: ekedonald <143762587+ekedonald@users.noreply.github.com> Date: Thu, 8 Aug 2024 09:05:44 +0100 Subject: [PATCH 11/45] Update production.yml Modified the event trigger for the deploy job from push to pull request. Signed-off-by: ekedonald <143762587+ekedonald@users.noreply.github.com> --- .github/workflows/production.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/production.yml b/.github/workflows/production.yml index 0436c4db..a1478627 100644 --- a/.github/workflows/production.yml +++ b/.github/workflows/production.yml @@ -65,7 +65,7 @@ jobs: deploy: runs-on: ubuntu-latest needs: build_and_test - if: github.event_name == 'push' + if: github.event_name == 'pull_request' environment: production steps: From f6481de767ef375502e387258144d4335df4c29a Mon Sep 17 00:00:00 2001 From: ekedonald <143762587+ekedonald@users.noreply.github.com> Date: Thu, 8 Aug 2024 09:11:36 +0100 Subject: [PATCH 12/45] Update staging.yml Modified the event trigger for the deploy job from push to pull request. Signed-off-by: ekedonald <143762587+ekedonald@users.noreply.github.com> --- .github/workflows/staging.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/staging.yml b/.github/workflows/staging.yml index f6e802fd..19dbb94c 100644 --- a/.github/workflows/staging.yml +++ b/.github/workflows/staging.yml @@ -65,7 +65,7 @@ jobs: deploy: runs-on: ubuntu-latest needs: build_and_test - if: github.event_name == 'push' + if: github.event_name == 'pull_request' environment: staging steps: From 039fc787bd52a20078cde47f4bd7942111bcfa83 Mon Sep 17 00:00:00 2001 From: Ayotola Ajisola <49092282+Ajinx1@users.noreply.github.com> Date: Thu, 8 Aug 2024 11:12:49 +0100 Subject: [PATCH 13/45] feat: added get user data privacy settings --- internal/models/data_privacy.go | 59 +++++++++ internal/models/migrations/migrations.go | 1 + internal/models/seed/seed.go | 9 ++ internal/models/user.go | 1 + pkg/controller/user/data_privacy.go | 26 ++++ pkg/router/user.go | 1 + services/user/data_privacy.go | 65 ++++++++++ static/swagger.yaml | 144 ++++++++++++++++++++-- tests/test_users/base.go | 3 + tests/test_users/get_user_privacy_test.go | 140 +++++++++++++++++++++ 10 files changed, 440 insertions(+), 9 deletions(-) create mode 100644 internal/models/data_privacy.go create mode 100644 pkg/controller/user/data_privacy.go create mode 100644 services/user/data_privacy.go create mode 100644 tests/test_users/get_user_privacy_test.go diff --git a/internal/models/data_privacy.go b/internal/models/data_privacy.go new file mode 100644 index 00000000..7239d646 --- /dev/null +++ b/internal/models/data_privacy.go @@ -0,0 +1,59 @@ +package models + +import ( + "time" + + "github.com/hngprojects/hng_boilerplate_golang_web/pkg/repository/storage/postgresql" + "github.com/hngprojects/hng_boilerplate_golang_web/utility" + "gorm.io/gorm" +) + +type DataPrivacySettings struct { + ID string `gorm:"type:uuid;primaryKey;unique;not null" json:"id"` + UserID string `gorm:"type:uuid;not null" json:"user_id"` + ProfileVisibility bool `gorm:"default:false" json:"profile_visibility"` + ShareDataWithPartners bool `gorm:"default:false" json:"share_data_with_partners"` + ReceiveEmailUpdates bool `gorm:"default:false" json:"receive_email_updates"` + Enable2FA bool `gorm:"default:false" json:"enable_2fa"` + UseDataEncryption bool `gorm:"default:false" json:"use_data_encryption"` + AllowAnalytics bool `gorm:"default:false" json:"allow_analytics"` + PersonalizedAds bool `gorm:"default:false" json:"personalized_ads"` + CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"` + UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"` + DeletedAt gorm.DeletedAt `gorm:"index" json:"-"` +} + +func (d *DataPrivacySettings) BeforeCreate(tx *gorm.DB) (err error) { + + if d.ID == "" { + d.ID = utility.GenerateUUID() + } + return +} + +func (d *DataPrivacySettings) CreateDataPrivacySettings(db *gorm.DB) error { + err := postgresql.CreateOneRecord(db, &d) + + if err != nil { + return err + } + + return nil +} + +func (d *DataPrivacySettings) UpdateDataPrivacySettings(db *gorm.DB) error { + _, err := postgresql.SaveAllFields(db, &d) + return err +} + +func (d *DataPrivacySettings) GetUserDataPrivacySettingsByID(db *gorm.DB, userID string) (DataPrivacySettings, error) { + var user DataPrivacySettings + + query := db.Where("user_id = ?", userID) + if err := query.First(&user).Error; err != nil { + return user, err + } + + return user, nil + +} diff --git a/internal/models/migrations/migrations.go b/internal/models/migrations/migrations.go index 56d093e1..1b21554c 100644 --- a/internal/models/migrations/migrations.go +++ b/internal/models/migrations/migrations.go @@ -31,6 +31,7 @@ func AuthMigrationModels() []interface{} { models.NotificationSettings{}, models.HelpCenter{}, models.ContactUs{}, + models.DataPrivacySettings{}, } // an array of db models, example: User{} } diff --git a/internal/models/seed/seed.go b/internal/models/seed/seed.go index 23e75a2a..f88c28f6 100644 --- a/internal/models/seed/seed.go +++ b/internal/models/seed/seed.go @@ -38,10 +38,15 @@ func SeedDatabase(db *gorm.DB) { }, Region: models.UserRegionTimezoneLanguage{ ID: utility.GenerateUUID(), + UserID: Userid1, RegionID: utility.GenerateUUID(), LanguageID: utility.GenerateUUID(), TimezoneID: utility.GenerateUUID(), }, + DataPrivacy: models.DataPrivacySettings{ + ID: utility.GenerateUUID(), + UserID: Userid1, + }, Products: []models.Product{ {ID: utility.GenerateUUID(), Name: "Product1", Description: "Description1", Price: 45.33, OwnerID: Userid1}, {ID: utility.GenerateUUID(), Name: "Product2", Description: "Description2", Price: 45.33, OwnerID: Userid1}, @@ -61,6 +66,10 @@ func SeedDatabase(db *gorm.DB) { Phone: "0987654321", AvatarURL: "http://example.com/avatar2.jpg", }, + DataPrivacy: models.DataPrivacySettings{ + ID: utility.GenerateUUID(), + UserID: Userid1, + }, Products: []models.Product{ {ID: utility.GenerateUUID(), Name: "Product3", Description: "Description3", Price: 45.33, OwnerID: Userid2}, {ID: utility.GenerateUUID(), Name: "Product4", Description: "Description4", Price: 45.33, OwnerID: Userid2}, diff --git a/internal/models/user.go b/internal/models/user.go index e6b537f2..1db5c60a 100644 --- a/internal/models/user.go +++ b/internal/models/user.go @@ -16,6 +16,7 @@ type User struct { Password string `gorm:"column:password; type:text; not null" json:"-"` Profile Profile `gorm:"foreignKey:Userid;constraint:OnUpdate:CASCADE,OnDelete:SET NULL;" json:"profile"` Region UserRegionTimezoneLanguage `gorm:"foreignKey:UserID;constraint:OnUpdate:CASCADE,OnDelete:SET NULL;" json:"region"` + DataPrivacy DataPrivacySettings `gorm:"foreignKey:UserID;constraint:OnUpdate:CASCADE,OnDelete:SET NULL;" json:"data_privacy"` Organisations []Organisation `gorm:"many2many:user_organisations;constraint:OnUpdate:CASCADE,OnDelete:SET NULL;" json:"organisations" ` // many to many relationship Products []Product `gorm:"foreignKey:OwnerID" json:"products"` Blogs []Blog `gorm:"foreignKey:AuthorID" json:"blogs"` diff --git a/pkg/controller/user/data_privacy.go b/pkg/controller/user/data_privacy.go new file mode 100644 index 00000000..147223dd --- /dev/null +++ b/pkg/controller/user/data_privacy.go @@ -0,0 +1,26 @@ +package user + +import ( + "net/http" + + "github.com/gin-gonic/gin" + service "github.com/hngprojects/hng_boilerplate_golang_web/services/user" + "github.com/hngprojects/hng_boilerplate_golang_web/utility" +) + +func (base *Controller) GetUserDataPrivacySettings(c *gin.Context) { + var ( + userID = c.Param("user_id") + ) + + respData, code, err := service.GetUserDataPrivacySettings(userID, base.Db.Postgresql, c) + if err != nil { + rd := utility.BuildErrorResponse(code, "error", err.Error(), err, nil) + c.JSON(code, rd) + return + } + + rd := utility.BuildSuccessResponse(http.StatusOK, "User data privacy settings retrieved successfully", respData) + c.JSON(http.StatusOK, rd) + +} diff --git a/pkg/router/user.go b/pkg/router/user.go index 04246ee8..5481248b 100644 --- a/pkg/router/user.go +++ b/pkg/router/user.go @@ -28,6 +28,7 @@ func User(r *gin.Engine, ApiVersion string, validator *validator.Validate, db *s userUrl.PUT("/users/:user_id/roles/:role_id", user.AssignRoleToUser) userUrl.PUT("/users/:user_id/regions", user.UpdateUserRegion) userUrl.GET("/users/:user_id/regions", user.GetUserRegion) + userUrl.GET("/users/:user_id/data-privacy-settings", user.GetUserDataPrivacySettings) } adminUrl.GET("/users", user.GetAllUsers) diff --git a/services/user/data_privacy.go b/services/user/data_privacy.go new file mode 100644 index 00000000..c9632b73 --- /dev/null +++ b/services/user/data_privacy.go @@ -0,0 +1,65 @@ +package user + +import ( + "errors" + "net/http" + + "github.com/gin-gonic/gin" + "github.com/hngprojects/hng_boilerplate_golang_web/internal/models" + "github.com/hngprojects/hng_boilerplate_golang_web/pkg/middleware" + "gorm.io/gorm" +) + +func GetUserDataPrivacySettings(userIDStr string, + db *gorm.DB, c *gin.Context) (*models.DataPrivacySettings, int, error) { + var ( + currentUser models.User + privacyData models.DataPrivacySettings + theData models.DataPrivacySettings + ) + + userId, err := middleware.GetUserClaims(c, db, "user_id") + if err != nil { + return &theData, http.StatusNotFound, err + } + + currentUserID, ok := userId.(string) + if !ok { + return &theData, http.StatusBadRequest, errors.New("user_id is not of type string") + } + + currentUser, code, err := GetUser(currentUserID, db) + if err != nil { + return &theData, code, err + } + + _, code, err = GetUser(userIDStr, db) + if err != nil { + return &theData, code, err + } + + isSuperAdmin := currentUser.CheckUserIsAdmin(db) + if !isSuperAdmin && currentUserID != userIDStr { + return &theData, http.StatusForbidden, errors.New("user does not have permission to view this user's privacy settings") + } + + if theData, err = privacyData.GetUserDataPrivacySettingsByID(db, userIDStr); err != nil { + if err.Error() == "record not found" { + theModel := models.DataPrivacySettings{ + UserID: userIDStr, + } + err := theModel.CreateDataPrivacySettings(db) + if err != nil { + return nil, http.StatusBadRequest, err + } + + if theData, err = privacyData.GetUserDataPrivacySettingsByID(db, userIDStr); err != nil { + return nil, http.StatusBadRequest, err + } + return &theData, http.StatusCreated, nil + } + return &theData, http.StatusBadRequest, err + } + + return &theData, http.StatusOK, nil +} diff --git a/static/swagger.yaml b/static/swagger.yaml index 5d7d3f12..7da368b0 100644 --- a/static/swagger.yaml +++ b/static/swagger.yaml @@ -263,7 +263,7 @@ paths: properties: id: type: string - example: "user_id_here" + example: "userId_here" email: type: string example: "user@example.com" @@ -355,7 +355,7 @@ paths: properties: id: type: string - example: "user_id_here" + example: "userId_here" email: type: string example: "user@example.com" @@ -1144,7 +1144,7 @@ paths: $ref: '#/components/schemas/ServerErrorSchema' - /newsletter-subscription/deleted: + /newsletter-subscription/deleted: get: tags: - newsletter @@ -1681,7 +1681,7 @@ paths: schema: $ref: '#/components/schemas/ServerErrorSchema' - /users/{user_id}/regions: + /users/{userId}/regions: get: tags: - user @@ -1690,7 +1690,7 @@ paths: - bearerAuth: [] description: Retrieves the region information for a specific user. parameters: - - name: user_id + - name: userId in: path required: true schema: @@ -1737,7 +1737,7 @@ paths: - bearerAuth: [] description: Updates the region for a specific user. parameters: - - name: user_id + - name: userId in: path required: true schema: @@ -1781,6 +1781,54 @@ paths: schema: $ref: '#/components/schemas/ServerErrorSchema' + /users/{userId}/data-privacy-settings: + get: + tags: + - user + summary: Get user data privacy settings + security: + - bearerAuth: [] + description: Retrieves the data privacy information for a specific user. + parameters: + - name: userId + in: path + required: true + schema: + type: string + format: uuid + description: ID of the user to retrieve the data privacy information for. + responses: + '200': + description: User data privacy settings retrieved successfully + content: + application/json: + schema: + $ref: '#/components/schemas/UserDataPrivacySchema' + '400': + description: Bad request + content: + application/json: + schema: + $ref: '#/components/schemas/BadRequestErrorSchema' + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/UnauthorizedErrorSchema' + '404': + description: Not found + content: + application/json: + schema: + $ref: '#/components/schemas/NotFoundErrorSchema' + '500': + description: Server error + content: + application/json: + schema: + $ref: '#/components/schemas/ServerErrorSchema' + /organizations/{org_id}/roles: post: tags: @@ -4322,7 +4370,7 @@ paths: notification_id: type: string example: 01910c39-01e0-73b7-bed9-d326a6ea5197 - user_id: + userId: type: string example: 01910b39-01e0-73b7-bed9-d326a6ea5197 message: @@ -5766,7 +5814,7 @@ components: type: string format: uuid example: 019109d8-a5a6-7f4d-a3ec-ee3bd0383ee5 - user_id: + userId: type: string format: uuid example: 019109d8-a5a6-7f47-8cc5-d9b3f62293f8 @@ -6055,7 +6103,7 @@ components: addUserToOrganizationReq: type: object properties: - user_id: + userId: type: string format: uuid example: 01910544-d1e1-7ada-bdac-c761e527ec91 @@ -6222,6 +6270,84 @@ components: - subject - message + UserDataPrivacySchema: + type: object + properties: + status: + type: string + example: success + status_code: + type: integer + example: 200 + message: + type: string + example: User data privacy settings retrieved successfully + data: + type: object + properties: + id: + type: string + format: uuid + example: 01913139-0969-7ed5-a830-2f20f6b2c4e7 + user_id: + type: string + format: uuid + example: 0191289e-fba3-7674-85bb-0c4a63ed8ebf + profile_visibility: + type: boolean + example: false + share_data_with_partners: + type: boolean + example: false + receive_email_updates: + type: boolean + example: false + enable_2fa: + type: boolean + example: false + use_data_encryption: + type: boolean + example: false + allow_analytics: + type: boolean + example: false + personalized_ads: + type: boolean + example: false + created_at: + type: string + format: date-time + example: 2024-08-08T10:01:47.242+01:00 + updated_at: + type: string + format: date-time + example: 2024-08-08T10:01:47.242+01:00 + + UserDataPrivacyUpdateSchema: + type: object + properties: + profile_visibility: + type: boolean + example: false + share_data_with_partners: + type: boolean + example: false + receive_email_updates: + type: boolean + example: false + enable_2fa: + type: boolean + example: true + use_data_encryption: + type: boolean + example: false + allow_analytics: + type: boolean + example: true + personalized_ads: + type: boolean + example: true + responses: UnauthorizedError: description: Authentication information is missing or invalid diff --git a/tests/test_users/base.go b/tests/test_users/base.go index 9447a6dc..f9e6e9c0 100644 --- a/tests/test_users/base.go +++ b/tests/test_users/base.go @@ -57,4 +57,7 @@ func SetupUsersRoutes(r *gin.Engine, userController *user.Controller) { r.GET("/api/v1/users/:user_id/regions", middleware.Authorize(userController.Db.Postgresql, models.RoleIdentity.SuperAdmin, models.RoleIdentity.User), userController.GetUserRegion) + r.GET("/api/v1/users/:user_id/data-privacy-settings", + middleware.Authorize(userController.Db.Postgresql, models.RoleIdentity.SuperAdmin, models.RoleIdentity.User), + userController.GetUserDataPrivacySettings) } diff --git a/tests/test_users/get_user_privacy_test.go b/tests/test_users/get_user_privacy_test.go new file mode 100644 index 00000000..2ebf4188 --- /dev/null +++ b/tests/test_users/get_user_privacy_test.go @@ -0,0 +1,140 @@ +package test_users + +import ( + "fmt" + "net/http" + "net/http/httptest" + "testing" + + "github.com/gin-gonic/gin" + "github.com/hngprojects/hng_boilerplate_golang_web/internal/models" + "github.com/hngprojects/hng_boilerplate_golang_web/pkg/controller/auth" + "github.com/hngprojects/hng_boilerplate_golang_web/tests" + "github.com/hngprojects/hng_boilerplate_golang_web/utility" +) + +func TestGetUserDataPrivacy(t *testing.T) { + + _, userController := SetupUsersTestRouter() + db := userController.Db.Postgresql + currUUID := utility.GenerateUUID() + password, _ := utility.HashPassword("password") + + adminUser := models.User{ + ID: utility.GenerateUUID(), + Name: "Admin User", + Email: fmt.Sprintf("admin%v@tester.team", currUUID), + Password: password, + Role: int(models.RoleIdentity.SuperAdmin), + } + regularUser := models.User{ + ID: utility.GenerateUUID(), + Name: "Regular User", + Email: fmt.Sprintf("user%v@qa.team", currUUID), + Password: password, + Role: int(models.RoleIdentity.User), + } + + dataPrivacy := models.DataPrivacySettings{ + ID: utility.GenerateUUID(), + UserID: regularUser.ID, + ProfileVisibility: false, + ShareDataWithPartners: false, + ReceiveEmailUpdates: false, + Enable2FA: true, + UseDataEncryption: false, + AllowAnalytics: true, + PersonalizedAds: true, + } + + db.Create(&adminUser) + db.Create(®ularUser) + db.Create(&dataPrivacy) + + setup := func() (*gin.Engine, *auth.Controller) { + router, userController := SetupUsersTestRouter() + authController := auth.Controller{ + Db: userController.Db, + Validator: userController.Validator, + Logger: userController.Logger, + } + + return router, &authController + } + + t.Run("Successful Get User Data Privacy for admin", func(t *testing.T) { + router, authController := setup() + loginData := models.LoginRequestModel{ + Email: adminUser.Email, + Password: "password", + } + token := tests.GetLoginToken(t, router, *authController, loginData) + + req, _ := http.NewRequest(http.MethodGet, fmt.Sprintf("/api/v1/users/%s/data-privacy-settings", regularUser.ID), nil) + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token)) + + resp := httptest.NewRecorder() + router.ServeHTTP(resp, req) + + tests.AssertStatusCode(t, resp.Code, http.StatusOK) + response := tests.ParseResponse(resp) + tests.AssertResponseMessage(t, response["status"].(string), "success") + tests.AssertResponseMessage(t, response["message"].(string), "User data privacy settings retrieved successfully") + }) + + t.Run("Successful Get User Data Privacy for regular user", func(t *testing.T) { + router, authController := setup() + loginData := models.LoginRequestModel{ + Email: regularUser.Email, + Password: "password", + } + token := tests.GetLoginToken(t, router, *authController, loginData) + + req, _ := http.NewRequest(http.MethodGet, fmt.Sprintf("/api/v1/users/%s/data-privacy-settings", regularUser.ID), nil) + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token)) + + resp := httptest.NewRecorder() + router.ServeHTTP(resp, req) + + tests.AssertStatusCode(t, resp.Code, http.StatusOK) + response := tests.ParseResponse(resp) + tests.AssertResponseMessage(t, response["message"].(string), "User data privacy settings retrieved successfully") + }) + + t.Run("Unauthorized", func(t *testing.T) { + router, _ := setup() + + req, _ := http.NewRequest(http.MethodGet, fmt.Sprintf("/api/v1/users/%s/data-privacy-settings", regularUser.ID), nil) + req.Header.Set("Content-Type", "application/json") + + resp := httptest.NewRecorder() + router.ServeHTTP(resp, req) + + tests.AssertStatusCode(t, resp.Code, http.StatusUnauthorized) + response := tests.ParseResponse(resp) + tests.AssertResponseMessage(t, response["message"].(string), "Token could not be found!") + }) + + t.Run("User ID not found", func(t *testing.T) { + router, authController := setup() + loginData := models.LoginRequestModel{ + Email: adminUser.Email, + Password: "password", + } + token := tests.GetLoginToken(t, router, *authController, loginData) + + req, _ := http.NewRequest(http.MethodGet, fmt.Sprintf("/api/v1/users/%s/data-privacy-settings", utility.GenerateUUID()), nil) + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token)) + + resp := httptest.NewRecorder() + router.ServeHTTP(resp, req) + + tests.AssertStatusCode(t, resp.Code, http.StatusNotFound) + response := tests.ParseResponse(resp) + tests.AssertResponseMessage(t, response["status"].(string), "error") + tests.AssertResponseMessage(t, response["message"].(string), "user not found") + }) +} From 3afa4aa999a462784b02b79c8c520a2f5c62fc66 Mon Sep 17 00:00:00 2001 From: Ayotola Ajisola <49092282+Ajinx1@users.noreply.github.com> Date: Thu, 8 Aug 2024 12:37:42 +0100 Subject: [PATCH 14/45] feat: added update user privacy settings and passed test cases with swagger doc updated --- pkg/controller/user/data_privacy.go | 36 ++++ pkg/router/user.go | 1 + services/user/data_privacy.go | 53 ++++++ static/swagger.yaml | 56 +++++- tests/test_users/base.go | 3 + tests/test_users/update_user_privacy_test.go | 187 +++++++++++++++++++ 6 files changed, 334 insertions(+), 2 deletions(-) create mode 100644 tests/test_users/update_user_privacy_test.go diff --git a/pkg/controller/user/data_privacy.go b/pkg/controller/user/data_privacy.go index 147223dd..8733c63d 100644 --- a/pkg/controller/user/data_privacy.go +++ b/pkg/controller/user/data_privacy.go @@ -4,6 +4,7 @@ import ( "net/http" "github.com/gin-gonic/gin" + "github.com/hngprojects/hng_boilerplate_golang_web/internal/models" service "github.com/hngprojects/hng_boilerplate_golang_web/services/user" "github.com/hngprojects/hng_boilerplate_golang_web/utility" ) @@ -24,3 +25,38 @@ func (base *Controller) GetUserDataPrivacySettings(c *gin.Context) { c.JSON(http.StatusOK, rd) } + +func (base *Controller) UpdateUserDataPrivacySettings(c *gin.Context) { + var ( + userID = c.Param("user_id") + req = models.DataPrivacySettings{} + ) + + 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.StatusUnprocessableEntity, "error", "Validation failed", + utility.ValidationResponse(err, base.Validator), nil) + c.JSON(http.StatusUnprocessableEntity, rd) + return + } + + respData, code, err := service.UpdateUserDataPrivacySettings(req, userID, base.Db.Postgresql, c) + if err != nil { + rd := utility.BuildErrorResponse(code, "error", err.Error(), err, nil) + c.JSON(code, rd) + return + } + + base.Logger.Info("user data privacy settings updated successfully") + + rd := utility.BuildSuccessResponse(http.StatusOK, "User data privacy settings updated successfully", respData) + c.JSON(http.StatusOK, rd) + +} diff --git a/pkg/router/user.go b/pkg/router/user.go index 5481248b..130789fb 100644 --- a/pkg/router/user.go +++ b/pkg/router/user.go @@ -29,6 +29,7 @@ func User(r *gin.Engine, ApiVersion string, validator *validator.Validate, db *s userUrl.PUT("/users/:user_id/regions", user.UpdateUserRegion) userUrl.GET("/users/:user_id/regions", user.GetUserRegion) userUrl.GET("/users/:user_id/data-privacy-settings", user.GetUserDataPrivacySettings) + userUrl.PUT("/users/:user_id/data-privacy-settings", user.UpdateUserDataPrivacySettings) } adminUrl.GET("/users", user.GetAllUsers) diff --git a/services/user/data_privacy.go b/services/user/data_privacy.go index c9632b73..9632fdbe 100644 --- a/services/user/data_privacy.go +++ b/services/user/data_privacy.go @@ -63,3 +63,56 @@ func GetUserDataPrivacySettings(userIDStr string, return &theData, http.StatusOK, nil } + +func UpdateUserDataPrivacySettings(userData models.DataPrivacySettings, userIDStr string, + db *gorm.DB, c *gin.Context) (*models.DataPrivacySettings, int, error) { + var ( + currentUser models.User + privacyData models.DataPrivacySettings + theData models.DataPrivacySettings + ) + + userId, err := middleware.GetUserClaims(c, db, "user_id") + if err != nil { + return &theData, http.StatusNotFound, err + } + + currentUserID, ok := userId.(string) + if !ok { + return &theData, http.StatusBadRequest, errors.New("user_id is not of type string") + } + + currentUser, code, err := GetUser(currentUserID, db) + if err != nil { + return &theData, code, err + } + + _, code, err = GetUser(userIDStr, db) + if err != nil { + return &theData, code, err + } + + isSuperAdmin := currentUser.CheckUserIsAdmin(db) + if !isSuperAdmin && currentUserID != userIDStr { + return &theData, http.StatusForbidden, errors.New("user does not have permission to update this user") + } + + if theData, err = privacyData.GetUserDataPrivacySettingsByID(db, userIDStr); err != nil { + return &theData, http.StatusBadRequest, err + } else { + + theData.AllowAnalytics = userData.AllowAnalytics + theData.Enable2FA = userData.Enable2FA + theData.PersonalizedAds = userData.PersonalizedAds + theData.ProfileVisibility = userData.ProfileVisibility + theData.UseDataEncryption = userData.UseDataEncryption + theData.ShareDataWithPartners = userData.ShareDataWithPartners + theData.ReceiveEmailUpdates = userData.ReceiveEmailUpdates + + if err := theData.UpdateDataPrivacySettings(db); err != nil { + return &theData, http.StatusBadRequest, err + } + return &theData, http.StatusOK, nil + } + +} diff --git a/static/swagger.yaml b/static/swagger.yaml index 7da368b0..11b3509b 100644 --- a/static/swagger.yaml +++ b/static/swagger.yaml @@ -1803,7 +1803,7 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/UserDataPrivacySchema' + $ref: '#/components/schemas/UserDataPrivacyResponseSchema' '400': description: Bad request content: @@ -1829,6 +1829,58 @@ paths: schema: $ref: '#/components/schemas/ServerErrorSchema' + put: + tags: + - user + summary: Update user data privacy settings + security: + - bearerAuth: [] + description: Updates the data privacy settings for a specific user. + parameters: + - name: userId + in: path + required: true + schema: + type: string + description: ID of the user to update the data privacy info for. + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/UserDataPrivacyUpdateSchema' + responses: + '200': + description: User data privacy updated successfully + content: + application/json: + schema: + $ref: '#/components/schemas/UserDataPrivacyResponseSchema' + '400': + description: Bad request + content: + application/json: + schema: + $ref: '#/components/schemas/BadRequestErrorSchema' + '401': + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/UnauthorizedErrorSchema' + '422': + description: Validation error + content: + application/json: + schema: + $ref: '#/components/schemas/UnprocessedEntityErrorSchema' + '500': + description: Server error + content: + application/json: + schema: + $ref: '#/components/schemas/ServerErrorSchema' + /organizations/{org_id}/roles: post: tags: @@ -6270,7 +6322,7 @@ components: - subject - message - UserDataPrivacySchema: + UserDataPrivacyResponseSchema: type: object properties: status: diff --git a/tests/test_users/base.go b/tests/test_users/base.go index f9e6e9c0..64b878c4 100644 --- a/tests/test_users/base.go +++ b/tests/test_users/base.go @@ -60,4 +60,7 @@ func SetupUsersRoutes(r *gin.Engine, userController *user.Controller) { r.GET("/api/v1/users/:user_id/data-privacy-settings", middleware.Authorize(userController.Db.Postgresql, models.RoleIdentity.SuperAdmin, models.RoleIdentity.User), userController.GetUserDataPrivacySettings) + r.PUT("/api/v1/users/:user_id/data-privacy-settings", + middleware.Authorize(userController.Db.Postgresql, models.RoleIdentity.SuperAdmin, models.RoleIdentity.User), + userController.UpdateUserDataPrivacySettings) } diff --git a/tests/test_users/update_user_privacy_test.go b/tests/test_users/update_user_privacy_test.go new file mode 100644 index 00000000..6c864283 --- /dev/null +++ b/tests/test_users/update_user_privacy_test.go @@ -0,0 +1,187 @@ +package test_users + +import ( + "bytes" + "encoding/json" + "fmt" + "net/http" + "net/http/httptest" + "testing" + + "github.com/gin-gonic/gin" + "github.com/hngprojects/hng_boilerplate_golang_web/internal/models" + "github.com/hngprojects/hng_boilerplate_golang_web/pkg/controller/auth" + "github.com/hngprojects/hng_boilerplate_golang_web/tests" + "github.com/hngprojects/hng_boilerplate_golang_web/utility" +) + +func TestUpdateUserDataPrivacy(t *testing.T) { + + _, userController := SetupUsersTestRouter() + db := userController.Db.Postgresql + currUUID := utility.GenerateUUID() + password, _ := utility.HashPassword("password") + + adminUser := models.User{ + ID: utility.GenerateUUID(), + Name: "Admin User", + Email: fmt.Sprintf("admin%v@qa.team", currUUID), + Password: password, + Role: int(models.RoleIdentity.SuperAdmin), + } + regularUser := models.User{ + ID: utility.GenerateUUID(), + Name: "Regular User", + Email: fmt.Sprintf("user%v@qa.team", currUUID), + Password: password, + Role: int(models.RoleIdentity.User), + } + + dataPrivacy := models.DataPrivacySettings{ + ID: utility.GenerateUUID(), + UserID: regularUser.ID, + ProfileVisibility: false, + ShareDataWithPartners: false, + ReceiveEmailUpdates: false, + Enable2FA: true, + UseDataEncryption: false, + AllowAnalytics: true, + PersonalizedAds: true, + } + + db.Create(&adminUser) + db.Create(®ularUser) + db.Create(&dataPrivacy) + + setup := func() (*gin.Engine, *auth.Controller) { + router, userController := SetupUsersTestRouter() + authController := auth.Controller{ + Db: userController.Db, + Validator: userController.Validator, + Logger: userController.Logger, + } + + return router, &authController + } + + t.Run("Successful Update User Data Privacy for admin", func(t *testing.T) { + router, authController := setup() + loginData := models.LoginRequestModel{ + Email: adminUser.Email, + Password: "password", + } + token := tests.GetLoginToken(t, router, *authController, loginData) + + updateData := models.DataPrivacySettings{ + ProfileVisibility: true, + ShareDataWithPartners: true, + ReceiveEmailUpdates: true, + Enable2FA: false, + UseDataEncryption: true, + AllowAnalytics: false, + PersonalizedAds: false, + } + body, _ := json.Marshal(updateData) + + req, _ := http.NewRequest(http.MethodPut, fmt.Sprintf("/api/v1/users/%s/data-privacy-settings", regularUser.ID), bytes.NewReader(body)) + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token)) + + resp := httptest.NewRecorder() + router.ServeHTTP(resp, req) + + tests.AssertStatusCode(t, resp.Code, http.StatusOK) + response := tests.ParseResponse(resp) + tests.AssertResponseMessage(t, response["status"].(string), "success") + tests.AssertResponseMessage(t, response["message"].(string), "User data privacy settings updated successfully") + }) + + t.Run("Successful Update User Data Privacy for regular user", func(t *testing.T) { + router, authController := setup() + loginData := models.LoginRequestModel{ + Email: regularUser.Email, + Password: "password", + } + token := tests.GetLoginToken(t, router, *authController, loginData) + + updateData := models.DataPrivacySettings{ + ProfileVisibility: true, + ShareDataWithPartners: true, + ReceiveEmailUpdates: true, + Enable2FA: false, + UseDataEncryption: true, + AllowAnalytics: false, + PersonalizedAds: false, + } + body, _ := json.Marshal(updateData) + + req, _ := http.NewRequest(http.MethodPut, fmt.Sprintf("/api/v1/users/%s/data-privacy-settings", regularUser.ID), bytes.NewReader(body)) + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token)) + + resp := httptest.NewRecorder() + router.ServeHTTP(resp, req) + + tests.AssertStatusCode(t, resp.Code, http.StatusOK) + response := tests.ParseResponse(resp) + tests.AssertResponseMessage(t, response["status"].(string), "success") + tests.AssertResponseMessage(t, response["message"].(string), "User data privacy settings updated successfully") + }) + + t.Run("Unauthorized", func(t *testing.T) { + router, _ := setup() + + updateData := models.DataPrivacySettings{ + ProfileVisibility: true, + ShareDataWithPartners: true, + ReceiveEmailUpdates: true, + Enable2FA: false, + UseDataEncryption: true, + AllowAnalytics: false, + PersonalizedAds: false, + } + body, _ := json.Marshal(updateData) + + req, _ := http.NewRequest(http.MethodPut, fmt.Sprintf("/api/v1/users/%s/data-privacy-settings", regularUser.ID), bytes.NewReader(body)) + req.Header.Set("Content-Type", "application/json") + + resp := httptest.NewRecorder() + router.ServeHTTP(resp, req) + + tests.AssertStatusCode(t, resp.Code, http.StatusUnauthorized) + response := tests.ParseResponse(resp) + tests.AssertResponseMessage(t, response["message"].(string), "Token could not be found!") + }) + + t.Run("User ID not found", func(t *testing.T) { + router, authController := setup() + loginData := models.LoginRequestModel{ + Email: adminUser.Email, + Password: "password", + } + token := tests.GetLoginToken(t, router, *authController, loginData) + + updateData := models.DataPrivacySettings{ + ProfileVisibility: true, + ShareDataWithPartners: true, + ReceiveEmailUpdates: true, + Enable2FA: false, + UseDataEncryption: true, + AllowAnalytics: false, + PersonalizedAds: false, + } + body, _ := json.Marshal(updateData) + + req, _ := http.NewRequest(http.MethodPut, fmt.Sprintf("/api/v1/users/%s/data-privacy-settings", utility.GenerateUUID()), bytes.NewReader(body)) + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token)) + + resp := httptest.NewRecorder() + router.ServeHTTP(resp, req) + + tests.AssertStatusCode(t, resp.Code, http.StatusNotFound) + response := tests.ParseResponse(resp) + tests.AssertResponseMessage(t, response["status"].(string), "error") + tests.AssertResponseMessage(t, response["message"].(string), "user not found") + }) +} From 1706f0fc1990373c5a067645c573235b752b7b0b Mon Sep 17 00:00:00 2001 From: Iretoms Date: Thu, 8 Aug 2024 12:49:47 +0100 Subject: [PATCH 15/45] feat: implement get organizations of a user and e2e test --- pkg/controller/user/user.go | 16 +++++++++-- pkg/router/user.go | 2 +- tests/test_users/base.go | 2 +- tests/test_users/get_user_org_test.go | 40 ++++----------------------- 4 files changed, 20 insertions(+), 40 deletions(-) diff --git a/pkg/controller/user/user.go b/pkg/controller/user/user.go index f50f1fae..4248d18c 100644 --- a/pkg/controller/user/user.go +++ b/pkg/controller/user/user.go @@ -8,6 +8,7 @@ import ( "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/middleware" "github.com/hngprojects/hng_boilerplate_golang_web/pkg/repository/storage" service "github.com/hngprojects/hng_boilerplate_golang_web/services/user" "github.com/hngprojects/hng_boilerplate_golang_web/utility" @@ -53,9 +54,18 @@ func (base *Controller) GetAUser(c *gin.Context) { func (base *Controller) GetAUserOrganisation(c *gin.Context) { - var ( - userID = c.Param("user_id") - ) + userId, err := middleware.GetUserClaims(c, base.Db.Postgresql, "user_id") + if err != nil { + if err.Error() == "user claims not found" { + rd := utility.BuildErrorResponse(http.StatusBadRequest, "error", err.Error(), "failed to retrieve organisations", nil) + c.JSON(http.StatusNotFound, rd) + return + } + rd := utility.BuildErrorResponse(http.StatusInternalServerError, "error", err.Error(), "failed to retrieve organisations", nil) + c.JSON(http.StatusInternalServerError, rd) + return + } + userID := userId.(string) userData, code, err := service.GetAUserOrganisation(userID, base.Db.Postgresql, c) if err != nil { diff --git a/pkg/router/user.go b/pkg/router/user.go index 04246ee8..551844b8 100644 --- a/pkg/router/user.go +++ b/pkg/router/user.go @@ -24,7 +24,7 @@ func User(r *gin.Engine, ApiVersion string, validator *validator.Validate, db *s userUrl.GET("/users/:user_id", user.GetAUser) userUrl.DELETE("/users/:user_id", user.DeleteAUser) userUrl.PUT("/users/:user_id", user.UpdateAUser) - userUrl.GET("/users/:user_id/organizations", user.GetAUserOrganisation) + userUrl.GET("/organizations", user.GetAUserOrganisation) userUrl.PUT("/users/:user_id/roles/:role_id", user.AssignRoleToUser) userUrl.PUT("/users/:user_id/regions", user.UpdateUserRegion) userUrl.GET("/users/:user_id/regions", user.GetUserRegion) diff --git a/tests/test_users/base.go b/tests/test_users/base.go index 9447a6dc..42522498 100644 --- a/tests/test_users/base.go +++ b/tests/test_users/base.go @@ -48,7 +48,7 @@ func SetupUsersRoutes(r *gin.Engine, userController *user.Controller) { r.PUT("/api/v1/users/:user_id", middleware.Authorize(userController.Db.Postgresql, models.RoleIdentity.SuperAdmin, models.RoleIdentity.User), userController.UpdateAUser) - r.GET("/api/v1/users/:user_id/organizations", + r.GET("/api/v1/organizations", middleware.Authorize(userController.Db.Postgresql, models.RoleIdentity.SuperAdmin, models.RoleIdentity.User), userController.GetAUserOrganisation) r.PUT("/api/v1/users/:user_id/regions", diff --git a/tests/test_users/get_user_org_test.go b/tests/test_users/get_user_org_test.go index 073be6d7..92d556ba 100644 --- a/tests/test_users/get_user_org_test.go +++ b/tests/test_users/get_user_org_test.go @@ -19,14 +19,6 @@ func TestGetUserOrganisations(t *testing.T) { currUUID := utility.GenerateUUID() password, _ := utility.HashPassword("password") - // Creating test users and organisations - adminUser := models.User{ - ID: utility.GenerateUUID(), - Name: "Admin User", - Email: fmt.Sprintf("admin%v+1@qa1.team", utility.GenerateUUID()), - Password: password, - Role: int(models.RoleIdentity.SuperAdmin), - } regularUser := models.User{ ID: utility.GenerateUUID(), Name: "Regular User", @@ -38,16 +30,15 @@ func TestGetUserOrganisations(t *testing.T) { ID: utility.GenerateUUID(), Name: "Organisation 1", Email: fmt.Sprintf("admorg1%v+2@qa1.team", utility.GenerateUUID()), - OwnerID: adminUser.ID, + OwnerID: regularUser.ID, } org2 := models.Organisation{ ID: utility.GenerateUUID(), Name: "Organisation 2", Email: fmt.Sprintf("adorg%v+1@qa1.team", utility.GenerateUUID()), - OwnerID: adminUser.ID, + OwnerID: regularUser.ID, } - db.Create(&adminUser) db.Create(®ularUser) db.Create(&org1) db.Create(&org2) @@ -79,12 +70,12 @@ func TestGetUserOrganisations(t *testing.T) { router, authController := setup() loginData := models.LoginRequestModel{ - Email: adminUser.Email, + Email: regularUser.Email, Password: "password", } token := tests.GetLoginToken(t, router, *authController, loginData) - req, _ := http.NewRequest(http.MethodGet, fmt.Sprintf("/api/v1/users/%s/organizations", regularUser.ID), nil) + req, _ := http.NewRequest(http.MethodGet, "/api/v1/organizations", nil) req.Header.Set("Content-Type", "application/json") req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token)) @@ -96,31 +87,10 @@ func TestGetUserOrganisations(t *testing.T) { tests.AssertResponseMessage(t, response["message"].(string), "User organisations retrieved successfully") }) - t.Run("User Not Found", func(t *testing.T) { - router, authController := setup() - - loginData := models.LoginRequestModel{ - Email: adminUser.Email, - Password: "password", - } - token := tests.GetLoginToken(t, router, *authController, loginData) - - req, _ := http.NewRequest(http.MethodGet, fmt.Sprintf("/api/v1/users/%s/organizations", utility.GenerateUUID()), nil) - req.Header.Set("Content-Type", "application/json") - req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token)) - - resp := httptest.NewRecorder() - router.ServeHTTP(resp, req) - - tests.AssertStatusCode(t, resp.Code, http.StatusBadRequest) - response := tests.ParseResponse(resp) - tests.AssertResponseMessage(t, response["message"].(string), "user not found") - }) - t.Run("Unauthorized Access", func(t *testing.T) { router, _ := setup() - req, _ := http.NewRequest(http.MethodGet, fmt.Sprintf("/api/v1/users/%s/organizations", regularUser.ID), nil) + req, _ := http.NewRequest(http.MethodGet, "/api/v1/organizations", nil) req.Header.Set("Content-Type", "application/json") req.Header.Set("Authorization", "Bearer invalid_token") From 851c37108766cb5d7e0852900aeccd11721990fc Mon Sep 17 00:00:00 2001 From: Iretoms Date: Thu, 8 Aug 2024 13:09:23 +0100 Subject: [PATCH 16/45] fix: update yaml file --- static/swagger.yaml | 2455 +++++++++++++++++++++---------------------- 1 file changed, 1210 insertions(+), 1245 deletions(-) diff --git a/static/swagger.yaml b/static/swagger.yaml index 5d7d3f12..38279819 100644 --- a/static/swagger.yaml +++ b/static/swagger.yaml @@ -18,7 +18,7 @@ servers: tags: - name: auth - description: API for user registration and authentication + description: API for user registration and authentication - name: contact description: API for contact us management - name: roles @@ -28,7 +28,7 @@ tags: - name: newsletter description: API for newsletter related operations - name: faq - description: API for FAQ related operations + description: API for FAQ related operations - name: waitlist description: API for waitlist related operations - name: language @@ -45,7 +45,6 @@ tags: description: API for products related operations paths: - # Auth paths /auth/register: post: @@ -79,7 +78,7 @@ paths: - first_name - last_name responses: - '201': + "201": description: User registered successfully content: application/json: @@ -99,21 +98,20 @@ paths: data: $ref: "#/components/schemas/UserSchema" - '400': + "400": description: Invalid input content: application/json: schema: $ref: "#/components/schemas/BadRequestErrorSchema" - - '422': + + "422": description: Unprocessed Entity content: application/json: schema: $ref: "#/components/schemas/UnprocessedEntityErrorSchema" - - + /auth/login: post: tags: @@ -134,7 +132,7 @@ paths: - email - password responses: - '200': + "200": description: User login successfully content: application/json: @@ -154,19 +152,19 @@ paths: example: Login Successfully data: $ref: "#/components/schemas/UserSchema" - '401': + "401": description: Unauthorized content: application/json: schema: $ref: "#/components/schemas/UnauthorizedErrorSchema" - '422': + "422": description: Unprocessed Entity content: - application/json: - schema: + application/json: + schema: $ref: "#/components/schemas/UnprocessedEntityErrorSchema" - + /auth/logout: post: tags: @@ -175,7 +173,7 @@ paths: security: - bearerAuth: [] responses: - '200': + "200": description: User logout successfully content: application/json: @@ -196,28 +194,28 @@ paths: data: type: object example: {} - '401': + "401": description: Unauthorized content: application/json: schema: $ref: "#/components/schemas/UnauthorizedErrorSchema" - '400': + "400": description: Invalid input content: - application/json: - schema: - type: object - properties: - status: - type: string - example: "Bad Request" - message: - type: string - example: "message" - status_code: - type: integer - example: 400 + application/json: + schema: + type: object + properties: + status: + type: string + example: "Bad Request" + message: + type: string + example: "message" + status_code: + type: integer + example: 400 /auth/google: post: tags: @@ -236,25 +234,25 @@ paths: - id_token responses: - '200': + "200": description: user sign in succesfully content: - application/json: - schema: - type: object - properties: - status: - type: string - example: "success" - message: - type: string - example: "user sign in successfully" - status_code: - type: integer - example: 200 - data: - type: object - properties: + application/json: + schema: + type: object + properties: + status: + type: string + example: "success" + message: + type: string + example: "user sign in successfully" + status_code: + type: integer + example: 200 + data: + type: object + properties: access_token: type: string example: "access_token" @@ -279,37 +277,37 @@ paths: role: type: string example: "user" - - '400': + + "400": description: Invalid input content: - application/json: - schema: - type: object - properties: - status: - type: string - example: "Bad Request" - message: - type: string - example: "message" - status_code: - type: integer - example: 400 - '422': + application/json: + schema: + type: object + properties: + status: + type: string + example: "Bad Request" + message: + type: string + example: "message" + status_code: + type: integer + example: 400 + "422": description: Unprocessed Entity content: - application/json: - schema: + application/json: + schema: $ref: "#/components/schemas/UnprocessedEntityErrorSchema" - '401': + "401": description: Unauthorized content: application/json: schema: $ref: "#/components/schemas/UnauthorizedErrorSchema" - + /auth/facebook: post: tags: @@ -328,25 +326,25 @@ paths: - id_token responses: - '200': + "200": description: user sign in succesfully content: - application/json: - schema: - type: object - properties: - status: - type: string - example: "success" - message: - type: string - example: "user sign in successfully" - status_code: - type: integer - example: 200 - data: - type: object - properties: + application/json: + schema: + type: object + properties: + status: + type: string + example: "success" + message: + type: string + example: "user sign in successfully" + status_code: + type: integer + example: 200 + data: + type: object + properties: access_token: type: string example: "access_token" @@ -371,31 +369,31 @@ paths: role: type: string example: "user" - - '400': + + "400": description: Invalid input content: - application/json: - schema: - type: object - properties: - status: - type: string - example: "Bad Request" - message: - type: string - example: "message" - status_code: - type: integer - example: 400 - '422': + application/json: + schema: + type: object + properties: + status: + type: string + example: "Bad Request" + message: + type: string + example: "message" + status_code: + type: integer + example: 400 + "422": description: Unprocessed Entity content: - application/json: - schema: + application/json: + schema: $ref: "#/components/schemas/UnprocessedEntityErrorSchema" - '401': + "401": description: Unauthorized content: application/json: @@ -425,46 +423,46 @@ paths: - new_password responses: - '200': + "200": description: Password updated successfully content: - application/json: - schema: - type: object - properties: - status: - type: string - example: "success" - message: - type: string - example: "Password updated successfully" - status_code: - type: integer - example: 200 - '400': + application/json: + schema: + type: object + properties: + status: + type: string + example: "success" + message: + type: string + example: "Password updated successfully" + status_code: + type: integer + example: 200 + "400": description: Invalid input content: - application/json: - schema: - type: object - properties: - status: - type: string - example: "Bad Request" - message: - type: string - example: "message" - status_code: - type: integer - example: 400 - '422': + application/json: + schema: + type: object + properties: + status: + type: string + example: "Bad Request" + message: + type: string + example: "message" + status_code: + type: integer + example: 400 + "422": description: Unprocessed Entity content: - application/json: - schema: + application/json: + schema: $ref: "#/components/schemas/UnprocessedEntityErrorSchema" - '401': + "401": description: Unauthorized content: application/json: @@ -477,10 +475,10 @@ paths: - auth summary: Request a magic link for authentication description: |- - Uses Cases: - - Sign up a new user - - Sign in an existing user - - Sign in with auto sign-up a new user if not exists + Uses Cases: + - Sign up a new user + - Sign in an existing user + - Sign in with auto sign-up a new user if not exists requestBody: required: true content: @@ -493,42 +491,42 @@ paths: required: - email responses: - '200': + "200": description: Magic link sent successfully content: - application/json: - schema: - type: object - properties: - status: - type: string - example: "success" - message: - type: string - example: "Magic link sent succesfully" - statusCode: - type: integer - example: 200 - - '400': + application/json: + schema: + type: object + properties: + status: + type: string + example: "success" + message: + type: string + example: "Magic link sent succesfully" + statusCode: + type: integer + example: 200 + + "400": description: Invalid input content: application/json: schema: - $ref: '#/components/schemas/BadRequestErrorSchema' - '422': + $ref: "#/components/schemas/BadRequestErrorSchema" + "422": description: Validation error content: application/json: schema: - $ref: '#/components/schemas/UnprocessedEntityErrorSchema' - '500': + $ref: "#/components/schemas/UnprocessedEntityErrorSchema" + "500": description: Server error content: application/json: schema: - $ref: '#/components/schemas/ServerErrorSchema' - + $ref: "#/components/schemas/ServerErrorSchema" + /auth/magick-link/verify: post: tags: @@ -546,43 +544,42 @@ paths: required: - token responses: - '200': + "200": description: Token verified successfully content: - application/json: - schema: - type: object - properties: - status: - type: string - example: "success" - statusCode: - type: integer - example: 200 - message: - type: string - example: "Token verified successfully" - data: - $ref: "#/components/schemas/UserSchema" - '400': + application/json: + schema: + type: object + properties: + status: + type: string + example: "success" + statusCode: + type: integer + example: 200 + message: + type: string + example: "Token verified successfully" + data: + $ref: "#/components/schemas/UserSchema" + "400": description: Invalid input content: application/json: schema: - $ref: '#/components/schemas/BadRequestErrorSchema' - '422': + $ref: "#/components/schemas/BadRequestErrorSchema" + "422": description: Validation error content: application/json: schema: - $ref: '#/components/schemas/UnprocessedEntityErrorSchema' - '500': + $ref: "#/components/schemas/UnprocessedEntityErrorSchema" + "500": description: Server error content: application/json: schema: - $ref: '#/components/schemas/ServerErrorSchema' - + $ref: "#/components/schemas/ServerErrorSchema" /auth/password-reset: post: @@ -601,42 +598,41 @@ paths: required: - email responses: - '200': + "200": description: Password reset link sent successfully content: - application/json: - schema: - type: object - properties: - status: - type: string - example: "success" - message: - type: string - example: "Password reset link sent successfully" - statusCode: - type: integer - example: 200 - '400': + application/json: + schema: + type: object + properties: + status: + type: string + example: "success" + message: + type: string + example: "Password reset link sent successfully" + statusCode: + type: integer + example: 200 + "400": description: Invalid input content: application/json: schema: - $ref: '#/components/schemas/BadRequestErrorSchema' - '422': + $ref: "#/components/schemas/BadRequestErrorSchema" + "422": description: Validation error content: application/json: schema: - $ref: '#/components/schemas/UnprocessedEntityErrorSchema' - '500': + $ref: "#/components/schemas/UnprocessedEntityErrorSchema" + "500": description: Server error content: application/json: schema: - $ref: '#/components/schemas/ServerErrorSchema' - - + $ref: "#/components/schemas/ServerErrorSchema" + /auth/password-reset/verify: post: tags: @@ -658,40 +654,40 @@ paths: - new_password responses: - '200': + "200": description: Password reset successfully content: - application/json: - schema: - type: object - properties: - status: - type: string - example: "success" - message: - type: string - example: "Password reset successfully" - statusCode: - type: integer - example: 200 - '400': + application/json: + schema: + type: object + properties: + status: + type: string + example: "success" + message: + type: string + example: "Password reset successfully" + statusCode: + type: integer + example: 200 + "400": description: Invalid input content: application/json: schema: - $ref: '#/components/schemas/BadRequestErrorSchema' - '422': + $ref: "#/components/schemas/BadRequestErrorSchema" + "422": description: Validation error content: application/json: schema: - $ref: '#/components/schemas/UnprocessedEntityErrorSchema' - '500': + $ref: "#/components/schemas/UnprocessedEntityErrorSchema" + "500": description: Server error content: application/json: schema: - $ref: '#/components/schemas/ServerErrorSchema' + $ref: "#/components/schemas/ServerErrorSchema" #User path /users/{userId}: @@ -712,36 +708,36 @@ paths: type: string description: ID of the user responses: - '200': + "200": description: A user information content: application/json: schema: - $ref: '#/components/schemas/UserSchema' - '401': + $ref: "#/components/schemas/UserSchema" + "401": description: Authentication error content: application/json: schema: - $ref: '#/components/schemas/UnauthorizedErrorSchema' - '404': + $ref: "#/components/schemas/UnauthorizedErrorSchema" + "404": description: User not found content: application/json: schema: - $ref: '#/components/schemas/NotFoundErrorSchema' - '400': + $ref: "#/components/schemas/NotFoundErrorSchema" + "400": description: Bad request content: application/json: schema: - $ref: '#/components/schemas/BadRequestErrorSchema' - '500': + $ref: "#/components/schemas/BadRequestErrorSchema" + "500": description: Server error content: application/json: schema: - $ref: '#/components/schemas/ServerErrorSchema' + $ref: "#/components/schemas/ServerErrorSchema" put: tags: @@ -764,39 +760,39 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/UpdateUser' + $ref: "#/components/schemas/UpdateUser" required: true responses: - '200': + "200": description: User updated successfully content: application/json: schema: - $ref: '#/components/schemas/UserSchema' - '401': + $ref: "#/components/schemas/UserSchema" + "401": description: Authentication error content: application/json: schema: - $ref: '#/components/schemas/UnauthorizedErrorSchema' - '422': + $ref: "#/components/schemas/UnauthorizedErrorSchema" + "422": description: Validation error content: application/json: schema: - $ref: '#/components/schemas/UnprocessedEntityErrorSchema' - '400': + $ref: "#/components/schemas/UnprocessedEntityErrorSchema" + "400": description: Bad request content: application/json: schema: - $ref: '#/components/schemas/BadRequestErrorSchema' - '500': + $ref: "#/components/schemas/BadRequestErrorSchema" + "500": description: Server error content: application/json: schema: - $ref: '#/components/schemas/ServerErrorSchema' + $ref: "#/components/schemas/ServerErrorSchema" delete: tags: @@ -815,88 +811,36 @@ paths: type: string description: ID of the user to delete responses: - '200': + "200": description: User deleted successfully content: application/json: schema: - $ref: '#/components/schemas/SuccessResponseSchema' - '401': + $ref: "#/components/schemas/SuccessResponseSchema" + "401": description: Authentication error content: application/json: schema: - $ref: '#/components/schemas/UnauthorizedErrorSchema' - '404': + $ref: "#/components/schemas/UnauthorizedErrorSchema" + "404": description: User not found content: application/json: schema: - $ref: '#/components/schemas/NotFoundErrorSchema' - '400': + $ref: "#/components/schemas/NotFoundErrorSchema" + "400": description: Bad Request content: application/json: schema: - $ref: '#/components/schemas/BadRequestErrorSchema' - '500': - description: Server error - content: - application/json: - schema: - $ref: '#/components/schemas/ServerErrorSchema' - - /users/{userId}/organisations: - get: - tags: - - user - #- super admin - summary: Query organisations where a user belongs - security: - - bearerAuth: [] - description: Retrieves a list of organisations where the specified user belongs. - operationId: getUserorganisations - parameters: - - name: userId - in: path - required: true - schema: - type: string - description: ID of the user to query organisations for - responses: - '200': - description: A list of organisations - content: - application/json: - schema: - type: array - items: - $ref: '#/components/schemas/Organisation' - '401': - description: Authentication error - content: - application/json: - schema: - $ref: '#/components/schemas/UnauthorizedErrorSchema' - '404': - description: User not found - content: - application/json: - schema: - $ref: '#/components/schemas/NotFoundErrorSchema' - '400': - description: Bad request - content: - application/json: - schema: - $ref: '#/components/schemas/BadRequestErrorSchema' - '500': + $ref: "#/components/schemas/BadRequestErrorSchema" + "500": description: Server error content: application/json: schema: - $ref: '#/components/schemas/ServerErrorSchema' - + $ref: "#/components/schemas/ServerErrorSchema" /users/{userId}/role/{roleId}: put: @@ -919,43 +863,43 @@ paths: type: integer responses: - '200': + "200": description: Role updated successfully content: - application/json: - schema: - type: object - properties: - status: - type: string - example: "success" - message: - type: string - example: "Role updated successfully" - status_code: - type: integer - example: 200 - '400': + application/json: + schema: + type: object + properties: + status: + type: string + example: "success" + message: + type: string + example: "Role updated successfully" + status_code: + type: integer + example: 200 + "400": description: Bad request content: - application/json: - schema: - $ref: "#/components/schemas/BadRequestErrorSchema" + application/json: + schema: + $ref: "#/components/schemas/BadRequestErrorSchema" - '404': + "404": description: Not found content: - application/json: - schema: + application/json: + schema: $ref: "#/components/schemas/NotFoundErrorSchema" - '401': + "401": description: Unauthorized content: application/json: schema: $ref: "#/components/schemas/UnauthorizedErrorSchema" - + /users: get: tags: @@ -965,29 +909,29 @@ paths: - bearerAuth: [] description: Retrieves a paginated list of users. parameters: - - $ref: '#/components/parameters/PageLimitParam' - - $ref: '#/components/parameters/LimitParam' + - $ref: "#/components/parameters/PageLimitParam" + - $ref: "#/components/parameters/LimitParam" responses: - '200': + "200": description: A list of users content: application/json: schema: type: array items: - $ref: '#/components/schemas/UserSchema' - '401': + $ref: "#/components/schemas/UserSchema" + "401": description: Authentication error content: application/json: schema: - $ref: '#/components/schemas/UnauthorizedErrorSchema' - '500': + $ref: "#/components/schemas/UnauthorizedErrorSchema" + "500": description: Server error content: application/json: schema: - $ref: '#/components/schemas/ServerErrorSchema' + $ref: "#/components/schemas/ServerErrorSchema" /newsletter-subscription: get: @@ -998,29 +942,29 @@ paths: - bearerAuth: [] description: Retrieves a paginated list of newsletters. parameters: - - $ref: '#/components/parameters/PageLimitParam' - - $ref: '#/components/parameters/LimitParam' + - $ref: "#/components/parameters/PageLimitParam" + - $ref: "#/components/parameters/LimitParam" responses: - '200': + "200": description: A list of newsletters content: application/json: schema: type: array items: - $ref: '#/components/schemas/NewsletterSchema' - '400': + $ref: "#/components/schemas/NewsletterSchema" + "400": description: Bad Request content: application/json: schema: - $ref: '#/components/schemas/BadRequestErrorSchema' - '500': + $ref: "#/components/schemas/BadRequestErrorSchema" + "500": description: Server error content: application/json: schema: - $ref: '#/components/schemas/ServerErrorSchema' + $ref: "#/components/schemas/ServerErrorSchema" post: tags: @@ -1032,32 +976,32 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/NewsletterSchema' + $ref: "#/components/schemas/NewsletterSchema" responses: - '201': + "201": description: Email added successfully content: application/json: schema: - $ref: '#/components/schemas/SuccessResponseSchema2' - '400': + $ref: "#/components/schemas/SuccessResponseSchema2" + "400": description: Bad Request content: application/json: schema: - $ref: '#/components/schemas/BadRequestErrorSchema' - '401': + $ref: "#/components/schemas/BadRequestErrorSchema" + "401": description: Authentication error content: application/json: schema: - $ref: '#/components/schemas/UnauthorizedErrorSchema' - '500': + $ref: "#/components/schemas/UnauthorizedErrorSchema" + "500": description: Server error content: application/json: schema: - $ref: '#/components/schemas/ServerErrorSchema' + $ref: "#/components/schemas/ServerErrorSchema" /newsletter-subscription/{Id}: delete: @@ -1076,30 +1020,30 @@ paths: format: uuid description: ID of the newsletter to delete responses: - '200': + "200": description: newsletter deleted successfully content: application/json: schema: - $ref: '#/components/schemas/SuccessResponseSchema' - '404': + $ref: "#/components/schemas/SuccessResponseSchema" + "404": description: Email not found content: application/json: schema: - $ref: '#/components/schemas/NotFoundErrorSchema' - '401': + $ref: "#/components/schemas/NotFoundErrorSchema" + "401": description: Authentication error content: application/json: schema: - $ref: '#/components/schemas/UnauthorizedErrorSchema' - '500': + $ref: "#/components/schemas/UnauthorizedErrorSchema" + "500": description: Server error content: application/json: schema: - $ref: '#/components/schemas/ServerErrorSchema' + $ref: "#/components/schemas/ServerErrorSchema" /newsletter-subscription/restore/{Id}: patch: @@ -1118,31 +1062,30 @@ paths: format: uuid description: ID of the newsletter to restore responses: - '200': + "200": description: Newsletter email restored successfully content: application/json: schema: - $ref: '#/components/schemas/SuccessResponseSchema' - '404': + $ref: "#/components/schemas/SuccessResponseSchema" + "404": description: Email not found content: application/json: schema: - $ref: '#/components/schemas/NotFoundErrorSchema' - '401': + $ref: "#/components/schemas/NotFoundErrorSchema" + "401": description: Authentication error content: application/json: schema: - $ref: '#/components/schemas/UnauthorizedErrorSchema' - '500': + $ref: "#/components/schemas/UnauthorizedErrorSchema" + "500": description: Server error content: application/json: schema: - $ref: '#/components/schemas/ServerErrorSchema' - + $ref: "#/components/schemas/ServerErrorSchema" /newsletter-subscription/deleted: get: @@ -1153,29 +1096,29 @@ paths: - bearerAuth: [] description: Retrieves a paginated list of deleted newsletters emails. parameters: - - $ref: '#/components/parameters/PageLimitParam' - - $ref: '#/components/parameters/LimitParam' + - $ref: "#/components/parameters/PageLimitParam" + - $ref: "#/components/parameters/LimitParam" responses: - '200': + "200": description: A list of all deleted newsletters emails content: application/json: schema: type: array items: - $ref: '#/components/schemas/NewsletterSchema' - '400': + $ref: "#/components/schemas/NewsletterSchema" + "400": description: Bad Request content: application/json: schema: - $ref: '#/components/schemas/BadRequestErrorSchema' - '500': + $ref: "#/components/schemas/BadRequestErrorSchema" + "500": description: Server error content: application/json: schema: - $ref: '#/components/schemas/ServerErrorSchema' + $ref: "#/components/schemas/ServerErrorSchema" /faq: get: @@ -1184,31 +1127,31 @@ paths: summary: Get all FAQs description: Retrieves a paginated list of FAQs. parameters: - - $ref: '#/components/parameters/PageLimitParam' - - $ref: '#/components/parameters/LimitParam' + - $ref: "#/components/parameters/PageLimitParam" + - $ref: "#/components/parameters/LimitParam" responses: - '200': + "200": description: A list of FAQs content: application/json: schema: type: array items: - $ref: '#/components/schemas/FAQSchema' - - '400': + $ref: "#/components/schemas/FAQSchema" + + "400": description: Bad Request content: application/json: schema: - $ref: '#/components/schemas/BadRequestErrorSchema' + $ref: "#/components/schemas/BadRequestErrorSchema" - '500': + "500": description: Server error content: application/json: schema: - $ref: '#/components/schemas/ServerErrorSchema' + $ref: "#/components/schemas/ServerErrorSchema" post: tags: @@ -1222,32 +1165,32 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/FAQSchema' + $ref: "#/components/schemas/FAQSchema" responses: - '201': + "201": description: FAQ created successfully content: application/json: schema: - $ref: '#/components/schemas/SuccessResponseSchema2' - '400': + $ref: "#/components/schemas/SuccessResponseSchema2" + "400": description: Bad Request content: application/json: schema: - $ref: '#/components/schemas/BadRequestErrorSchema' - '401': + $ref: "#/components/schemas/BadRequestErrorSchema" + "401": description: Authentication error content: application/json: schema: - $ref: '#/components/schemas/UnauthorizedErrorSchema' - '500': + $ref: "#/components/schemas/UnauthorizedErrorSchema" + "500": description: Server error content: application/json: schema: - $ref: '#/components/schemas/ServerErrorSchema' + $ref: "#/components/schemas/ServerErrorSchema" /faq/{faqId}: put: @@ -1270,38 +1213,38 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/FAQSchema' + $ref: "#/components/schemas/FAQSchema" responses: - '200': + "200": description: FAQ updated successfully content: application/json: schema: - $ref: '#/components/schemas/SuccessResponseSchema' - '400': + $ref: "#/components/schemas/SuccessResponseSchema" + "400": description: Bad Request content: application/json: schema: - $ref: '#/components/schemas/BadRequestErrorSchema' - '401': + $ref: "#/components/schemas/BadRequestErrorSchema" + "401": description: Authentication error content: application/json: schema: - $ref: '#/components/schemas/UnauthorizedErrorSchema' - '404': + $ref: "#/components/schemas/UnauthorizedErrorSchema" + "404": description: FAQ not found content: application/json: schema: - $ref: '#/components/schemas/NotFoundErrorSchema' - '500': + $ref: "#/components/schemas/NotFoundErrorSchema" + "500": description: Server error content: application/json: schema: - $ref: '#/components/schemas/ServerErrorSchema' + $ref: "#/components/schemas/ServerErrorSchema" delete: tags: @@ -1319,30 +1262,30 @@ paths: format: uuid description: ID of the FAQ to delete responses: - '200': + "200": description: FAQ deleted successfully content: application/json: schema: - $ref: '#/components/schemas/SuccessResponseSchema' - '404': + $ref: "#/components/schemas/SuccessResponseSchema" + "404": description: FAQ not found content: application/json: schema: - $ref: '#/components/schemas/NotFoundErrorSchema' - '401': + $ref: "#/components/schemas/NotFoundErrorSchema" + "401": description: Authentication error content: application/json: schema: - $ref: '#/components/schemas/UnauthorizedErrorSchema' - '500': + $ref: "#/components/schemas/UnauthorizedErrorSchema" + "500": description: Server error content: application/json: schema: - $ref: '#/components/schemas/ServerErrorSchema' + $ref: "#/components/schemas/ServerErrorSchema" /languages: get: @@ -1353,26 +1296,26 @@ paths: - bearerAuth: [] description: Retrieves a list of all available languages. responses: - '200': + "200": description: A list of languages content: application/json: schema: type: array items: - $ref: '#/components/schemas/LanguageSchema' - '400': + $ref: "#/components/schemas/LanguageSchema" + "400": description: Bad request error content: application/json: schema: - $ref: '#/components/schemas/BadRequestErrorSchema' - '401': + $ref: "#/components/schemas/BadRequestErrorSchema" + "401": description: Authentication error content: application/json: schema: - $ref: '#/components/schemas/UnauthorizedErrorSchema' + $ref: "#/components/schemas/UnauthorizedErrorSchema" post: tags: @@ -1386,32 +1329,32 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/LanguageSchema' + $ref: "#/components/schemas/LanguageSchema" responses: - '201': + "201": description: Language created successfully content: application/json: schema: - $ref: '#/components/schemas/SuccessResponseSchema2' - '400': + $ref: "#/components/schemas/SuccessResponseSchema2" + "400": description: Bad Request content: application/json: schema: - $ref: '#/components/schemas/BadRequestErrorSchema' - '401': + $ref: "#/components/schemas/BadRequestErrorSchema" + "401": description: Authentication error content: application/json: schema: - $ref: '#/components/schemas/UnauthorizedErrorSchema' - '500': + $ref: "#/components/schemas/UnauthorizedErrorSchema" + "500": description: Server error content: application/json: schema: - $ref: '#/components/schemas/ServerErrorSchema' + $ref: "#/components/schemas/ServerErrorSchema" /timezones: get: @@ -1422,26 +1365,26 @@ paths: - bearerAuth: [] description: Retrieves a list of all available timezones. responses: - '200': + "200": description: A list of timezones content: application/json: schema: type: array items: - $ref: '#/components/schemas/TimezoneSchema' - '400': + $ref: "#/components/schemas/TimezoneSchema" + "400": description: Bad request error content: application/json: schema: - $ref: '#/components/schemas/BadRequestErrorSchema' - '401': + $ref: "#/components/schemas/BadRequestErrorSchema" + "401": description: Authentication error content: application/json: schema: - $ref: '#/components/schemas/UnauthorizedErrorSchema' + $ref: "#/components/schemas/UnauthorizedErrorSchema" post: tags: @@ -1455,32 +1398,32 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/TimezoneSchema' + $ref: "#/components/schemas/TimezoneSchema" responses: - '201': + "201": description: Timezone created successfully content: application/json: schema: - $ref: '#/components/schemas/SuccessResponseSchema2' - '400': + $ref: "#/components/schemas/SuccessResponseSchema2" + "400": description: Bad Request content: application/json: schema: - $ref: '#/components/schemas/BadRequestErrorSchema' - '401': + $ref: "#/components/schemas/BadRequestErrorSchema" + "401": description: Authentication error content: application/json: schema: - $ref: '#/components/schemas/UnauthorizedErrorSchema' - '500': + $ref: "#/components/schemas/UnauthorizedErrorSchema" + "500": description: Server error content: application/json: schema: - $ref: '#/components/schemas/ServerErrorSchema' + $ref: "#/components/schemas/ServerErrorSchema" /timezones/{id}: patch: @@ -1503,38 +1446,38 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/TimezoneSchema' + $ref: "#/components/schemas/TimezoneSchema" responses: - '200': + "200": description: Timezone updated successfully content: application/json: schema: - $ref: '#/components/schemas/SuccessResponseSchema' - '400': + $ref: "#/components/schemas/SuccessResponseSchema" + "400": description: Bad Request content: application/json: schema: - $ref: '#/components/schemas/BadRequestErrorSchema' - '422': + $ref: "#/components/schemas/BadRequestErrorSchema" + "422": description: Validation error content: application/json: schema: - $ref: '#/components/schemas/UnprocessedEntityErrorSchema' - '401': + $ref: "#/components/schemas/UnprocessedEntityErrorSchema" + "401": description: Authentication error content: application/json: schema: - $ref: '#/components/schemas/UnauthorizedErrorSchema' - '500': + $ref: "#/components/schemas/UnauthorizedErrorSchema" + "500": description: Server error content: application/json: schema: - $ref: '#/components/schemas/ServerErrorSchema' + $ref: "#/components/schemas/ServerErrorSchema" /regions: get: @@ -1545,26 +1488,26 @@ paths: - bearerAuth: [] description: Retrieves a list of all available regions. responses: - '200': + "200": description: A list of regions content: application/json: schema: type: array items: - $ref: '#/components/schemas/RegionSchema' - '400': + $ref: "#/components/schemas/RegionSchema" + "400": description: Bad request error content: application/json: schema: - $ref: '#/components/schemas/BadRequestErrorSchema' - '401': + $ref: "#/components/schemas/BadRequestErrorSchema" + "401": description: Authentication error content: application/json: schema: - $ref: '#/components/schemas/UnauthorizedErrorSchema' + $ref: "#/components/schemas/UnauthorizedErrorSchema" post: tags: @@ -1578,38 +1521,38 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/RegionSchema' + $ref: "#/components/schemas/RegionSchema" responses: - '201': + "201": description: Region created successfully content: application/json: schema: - $ref: '#/components/schemas/SuccessResponseSchema2' - '400': + $ref: "#/components/schemas/SuccessResponseSchema2" + "400": description: Bad Request content: application/json: schema: - $ref: '#/components/schemas/BadRequestErrorSchema' - '401': + $ref: "#/components/schemas/BadRequestErrorSchema" + "401": description: Authentication error content: application/json: schema: - $ref: '#/components/schemas/UnauthorizedErrorSchema' - '422': + $ref: "#/components/schemas/UnauthorizedErrorSchema" + "422": description: Validation error content: application/json: schema: - $ref: '#/components/schemas/UnprocessedEntityErrorSchema' - '500': + $ref: "#/components/schemas/UnprocessedEntityErrorSchema" + "500": description: Server error content: application/json: schema: - $ref: '#/components/schemas/ServerErrorSchema' + $ref: "#/components/schemas/ServerErrorSchema" /waitlist: get: @@ -1620,29 +1563,29 @@ paths: - bearerAuth: [] description: Retrieves a paginated list of waitlist entries. parameters: - - $ref: '#/components/parameters/PageLimitParam' - - $ref: '#/components/parameters/LimitParam' + - $ref: "#/components/parameters/PageLimitParam" + - $ref: "#/components/parameters/LimitParam" responses: - '200': + "200": description: A list of waitlist entries content: application/json: schema: type: array items: - $ref: '#/components/schemas/WaitlistSchema' - '400': + $ref: "#/components/schemas/WaitlistSchema" + "400": description: Bad request error content: application/json: schema: - $ref: '#/components/schemas/BadRequestErrorSchema' - '401': + $ref: "#/components/schemas/BadRequestErrorSchema" + "401": description: Authentication error content: application/json: schema: - $ref: '#/components/schemas/UnauthorizedErrorSchema' + $ref: "#/components/schemas/UnauthorizedErrorSchema" post: tags: @@ -1654,32 +1597,32 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/WaitlistSchema' + $ref: "#/components/schemas/WaitlistSchema" responses: - '201': + "201": description: Waitlist entry created successfully content: application/json: schema: - $ref: '#/components/schemas/SuccessResponseSchema2' - '400': + $ref: "#/components/schemas/SuccessResponseSchema2" + "400": description: Bad Request content: application/json: schema: - $ref: '#/components/schemas/BadRequestErrorSchema' - '422': + $ref: "#/components/schemas/BadRequestErrorSchema" + "422": description: Validation error content: application/json: schema: - $ref: '#/components/schemas/UnprocessedEntityErrorSchema' - '500': + $ref: "#/components/schemas/UnprocessedEntityErrorSchema" + "500": description: Server error content: application/json: schema: - $ref: '#/components/schemas/ServerErrorSchema' + $ref: "#/components/schemas/ServerErrorSchema" /users/{user_id}/regions: get: @@ -1698,36 +1641,36 @@ paths: format: uuid description: ID of the user to retrieve the region information for. responses: - '200': + "200": description: User region retrieved successfully content: application/json: schema: - $ref: '#/components/schemas/UserRegionResponseSchema' - '400': + $ref: "#/components/schemas/UserRegionResponseSchema" + "400": description: Bad request content: application/json: schema: - $ref: '#/components/schemas/BadRequestErrorSchema' - '401': + $ref: "#/components/schemas/BadRequestErrorSchema" + "401": description: Unauthorized content: application/json: schema: - $ref: '#/components/schemas/UnauthorizedErrorSchema' - '404': + $ref: "#/components/schemas/UnauthorizedErrorSchema" + "404": description: Not found content: application/json: schema: - $ref: '#/components/schemas/NotFoundErrorSchema' - '500': + $ref: "#/components/schemas/NotFoundErrorSchema" + "500": description: Server error content: application/json: schema: - $ref: '#/components/schemas/ServerErrorSchema' + $ref: "#/components/schemas/ServerErrorSchema" put: tags: @@ -1748,38 +1691,38 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/UserRegionUpdateSchema' + $ref: "#/components/schemas/UserRegionUpdateSchema" responses: - '200': + "200": description: User region updated successfully content: application/json: schema: - $ref: '#/components/schemas/UserRegionUpdateResponseSchema' - '400': + $ref: "#/components/schemas/UserRegionUpdateResponseSchema" + "400": description: Bad request content: application/json: schema: - $ref: '#/components/schemas/BadRequestErrorSchema' - '401': + $ref: "#/components/schemas/BadRequestErrorSchema" + "401": description: Unauthorized content: application/json: schema: - $ref: '#/components/schemas/UnauthorizedErrorSchema' - '422': + $ref: "#/components/schemas/UnauthorizedErrorSchema" + "422": description: Validation error content: application/json: schema: - $ref: '#/components/schemas/UnprocessedEntityErrorSchema' - '500': + $ref: "#/components/schemas/UnprocessedEntityErrorSchema" + "500": description: Server error content: application/json: schema: - $ref: '#/components/schemas/ServerErrorSchema' + $ref: "#/components/schemas/ServerErrorSchema" /organizations/{org_id}/roles: post: @@ -1802,38 +1745,38 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/OrgRoleCreateSchema' + $ref: "#/components/schemas/OrgRoleCreateSchema" responses: - '201': + "201": description: Role created successfully content: application/json: schema: - $ref: '#/components/schemas/OrgRoleCreateResponseSchema' - '400': + $ref: "#/components/schemas/OrgRoleCreateResponseSchema" + "400": description: Bad request content: application/json: schema: - $ref: '#/components/schemas/BadRequestErrorSchema' - '401': + $ref: "#/components/schemas/BadRequestErrorSchema" + "401": description: Unauthorized content: application/json: schema: - $ref: '#/components/schemas/UnauthorizedErrorSchema' - '403': + $ref: "#/components/schemas/UnauthorizedErrorSchema" + "403": description: Forbidden content: application/json: schema: - $ref: '#/components/schemas/ForbiddenErrorSchema' - '422': + $ref: "#/components/schemas/ForbiddenErrorSchema" + "422": description: Validation error content: application/json: schema: - $ref: '#/components/schemas/UnprocessedEntityErrorSchema' + $ref: "#/components/schemas/UnprocessedEntityErrorSchema" get: tags: @@ -1851,36 +1794,36 @@ paths: format: uuid description: ID of the organization to retrieve roles for. responses: - '200': + "200": description: Roles retrieved successfully content: application/json: schema: - $ref: '#/components/schemas/OrgRolesListResponseSchema' - '400': + $ref: "#/components/schemas/OrgRolesListResponseSchema" + "400": description: Bad request content: application/json: schema: - $ref: '#/components/schemas/BadRequestErrorSchema' - '401': + $ref: "#/components/schemas/BadRequestErrorSchema" + "401": description: Unauthorized content: application/json: schema: - $ref: '#/components/schemas/UnauthorizedErrorSchema' - '404': + $ref: "#/components/schemas/UnauthorizedErrorSchema" + "404": description: Not found content: application/json: schema: - $ref: '#/components/schemas/NotFoundErrorSchema' - '500': + $ref: "#/components/schemas/NotFoundErrorSchema" + "500": description: Server error content: application/json: schema: - $ref: '#/components/schemas/ServerErrorSchema' + $ref: "#/components/schemas/ServerErrorSchema" /organizations/{org_id}/roles/{role_id}: get: @@ -1906,36 +1849,36 @@ paths: format: uuid description: ID of the role to retrieve. responses: - '200': + "200": description: Role retrieved successfully content: application/json: schema: - $ref: '#/components/schemas/OrgRoleResponseSchema' - '400': + $ref: "#/components/schemas/OrgRoleResponseSchema" + "400": description: Bad request content: application/json: schema: - $ref: '#/components/schemas/BadRequestErrorSchema' - '401': + $ref: "#/components/schemas/BadRequestErrorSchema" + "401": description: Unauthorized content: application/json: schema: - $ref: '#/components/schemas/UnauthorizedErrorSchema' - '404': + $ref: "#/components/schemas/UnauthorizedErrorSchema" + "404": description: Not found content: application/json: schema: - $ref: '#/components/schemas/NotFoundErrorSchema' - '500': + $ref: "#/components/schemas/NotFoundErrorSchema" + "500": description: Server error content: application/json: schema: - $ref: '#/components/schemas/ServerErrorSchema' + $ref: "#/components/schemas/ServerErrorSchema" patch: tags: @@ -1964,38 +1907,38 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/OrgRoleUpdateSchema' + $ref: "#/components/schemas/OrgRoleUpdateSchema" responses: - '200': + "200": description: Role updated successfully content: application/json: schema: - $ref: '#/components/schemas/OrgRoleUpdateResponseSchema' - '400': + $ref: "#/components/schemas/OrgRoleUpdateResponseSchema" + "400": description: Bad request content: application/json: schema: - $ref: '#/components/schemas/BadRequestErrorSchema' - '401': + $ref: "#/components/schemas/BadRequestErrorSchema" + "401": description: Unauthorized content: application/json: schema: - $ref: '#/components/schemas/UnauthorizedErrorSchema' - '403': + $ref: "#/components/schemas/UnauthorizedErrorSchema" + "403": description: Forbidden content: application/json: schema: - $ref: '#/components/schemas/ForbiddenErrorSchema' - '422': + $ref: "#/components/schemas/ForbiddenErrorSchema" + "422": description: Validation error content: application/json: schema: - $ref: '#/components/schemas/UnprocessedEntityErrorSchema' + $ref: "#/components/schemas/UnprocessedEntityErrorSchema" delete: tags: @@ -2020,42 +1963,42 @@ paths: format: uuid description: ID of the role to delete. responses: - '200': + "200": description: Role deleted successfully content: application/json: schema: - $ref: '#/components/schemas/OrgRoleDeleteResponseSchema' - '400': + $ref: "#/components/schemas/OrgRoleDeleteResponseSchema" + "400": description: Bad request content: application/json: schema: - $ref: '#/components/schemas/BadRequestErrorSchema' - '401': + $ref: "#/components/schemas/BadRequestErrorSchema" + "401": description: Unauthorized content: application/json: schema: - $ref: '#/components/schemas/UnauthorizedErrorSchema' - '403': + $ref: "#/components/schemas/UnauthorizedErrorSchema" + "403": description: Forbidden content: application/json: schema: - $ref: '#/components/schemas/ForbiddenErrorSchema' - '404': + $ref: "#/components/schemas/ForbiddenErrorSchema" + "404": description: Not found content: application/json: schema: - $ref: '#/components/schemas/NotFoundErrorSchema' - '500': + $ref: "#/components/schemas/NotFoundErrorSchema" + "500": description: Server error content: application/json: schema: - $ref: '#/components/schemas/ServerErrorSchema' + $ref: "#/components/schemas/ServerErrorSchema" /organizations/{org_id}/roles/{role_id}/permissions: patch: @@ -2085,38 +2028,38 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/RolePermissionsUpdateSchema' + $ref: "#/components/schemas/RolePermissionsUpdateSchema" responses: - '200': + "200": description: Permissions updated successfully content: application/json: schema: - $ref: '#/components/schemas/RolePermissionsUpdateResponseSchema' - '400': + $ref: "#/components/schemas/RolePermissionsUpdateResponseSchema" + "400": description: Bad request content: application/json: schema: - $ref: '#/components/schemas/BadRequestErrorSchema' - '401': + $ref: "#/components/schemas/BadRequestErrorSchema" + "401": description: Unauthorized content: application/json: schema: - $ref: '#/components/schemas/UnauthorizedErrorSchema' - '403': + $ref: "#/components/schemas/UnauthorizedErrorSchema" + "403": description: Forbidden content: application/json: schema: - $ref: '#/components/schemas/ForbiddenErrorSchema' - '422': + $ref: "#/components/schemas/ForbiddenErrorSchema" + "422": description: Validation error content: application/json: schema: - $ref: '#/components/schemas/UnprocessedEntityErrorSchema' + $ref: "#/components/schemas/UnprocessedEntityErrorSchema" /jobs: get: tags: @@ -2140,20 +2083,20 @@ paths: description: The number of items per page example: 6 responses: - '200': + "200": description: A list of job posts content: application/json: schema: type: array items: - $ref: '#/components/schemas/JobPost' - '500': + $ref: "#/components/schemas/JobPost" + "500": description: Failed to fetch job posts content: application/json: schema: - $ref: '#/components/schemas/ErrorResponse' + $ref: "#/components/schemas/ErrorResponse" post: tags: - Job Posts @@ -2167,26 +2110,26 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/CreateJobPost' + $ref: "#/components/schemas/CreateJobPost" responses: - '201': + "201": description: Job post created successfully content: application/json: schema: - $ref: '#/components/schemas/JobPostResponse' - '400': + $ref: "#/components/schemas/JobPostResponse" + "400": description: Invalid input content: application/json: schema: - $ref: '#/components/schemas/ErrorResponse' - '500': + $ref: "#/components/schemas/ErrorResponse" + "500": description: Failed to create job post content: application/json: schema: - $ref: '#/components/schemas/ErrorResponse' + $ref: "#/components/schemas/ErrorResponse" /jobs/{job_id}: get: @@ -2203,30 +2146,30 @@ paths: type: string description: The ID of the job post to fetch responses: - '200': + "200": description: Job post fetched successfully content: application/json: schema: - $ref: '#/components/schemas/JobPostResponse' - '400': + $ref: "#/components/schemas/JobPostResponse" + "400": description: Invalid ID format content: application/json: schema: - $ref: '#/components/schemas/ErrorResponse' - '404': + $ref: "#/components/schemas/ErrorResponse" + "404": description: Job post not found content: application/json: schema: - $ref: '#/components/schemas/ErrorResponse' - '500': + $ref: "#/components/schemas/ErrorResponse" + "500": description: Failed to fetch job post content: application/json: schema: - $ref: '#/components/schemas/ErrorResponse' + $ref: "#/components/schemas/ErrorResponse" patch: tags: - Job Posts @@ -2247,32 +2190,32 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/UpdateJobPost' + $ref: "#/components/schemas/UpdateJobPost" responses: - '200': + "200": description: Job post updated successfully content: application/json: schema: - $ref: '#/components/schemas/JobPostResponse' - '400': + $ref: "#/components/schemas/JobPostResponse" + "400": description: Invalid ID format content: application/json: schema: - $ref: '#/components/schemas/ErrorResponse' - '404': + $ref: "#/components/schemas/ErrorResponse" + "404": description: Job post not found content: application/json: schema: - $ref: '#/components/schemas/ErrorResponse' - '500': + $ref: "#/components/schemas/ErrorResponse" + "500": description: Failed to update job post content: application/json: schema: - $ref: '#/components/schemas/ErrorResponse' + $ref: "#/components/schemas/ErrorResponse" delete: tags: - Job Posts @@ -2289,29 +2232,28 @@ paths: type: string description: The ID of the job post to delete responses: - '204': + "204": description: No content - - '400': + + "400": description: Invalid ID format content: application/json: schema: - $ref: '#/components/schemas/ErrorResponse' - '404': + $ref: "#/components/schemas/ErrorResponse" + "404": description: Job post not found content: application/json: schema: - $ref: '#/components/schemas/ErrorResponse' - '500': + $ref: "#/components/schemas/ErrorResponse" + "500": description: Failed to delete job post content: application/json: schema: - $ref: '#/components/schemas/ErrorResponse' - - + $ref: "#/components/schemas/ErrorResponse" + /organizations: post: tags: @@ -2325,40 +2267,75 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/createOrganizationReq' + $ref: "#/components/schemas/createOrganizationReq" responses: - '201': + "201": description: organization created successfully content: application/json: schema: - $ref: '#/components/schemas/organizationSuccessResponse' - '400': + $ref: "#/components/schemas/organizationSuccessResponse" + "400": description: Bad request content: application/json: schema: - $ref: '#/components/schemas/BadRequestErrorSchema' - '401': + $ref: "#/components/schemas/BadRequestErrorSchema" + "401": description: Unauthorized content: application/json: schema: - $ref: '#/components/schemas/UnauthorizedErrorSchema' - '404': + $ref: "#/components/schemas/UnauthorizedErrorSchema" + "404": description: Not found content: application/json: schema: - $ref: '#/components/schemas/NotFoundErrorSchema' - '500': + $ref: "#/components/schemas/NotFoundErrorSchema" + "500": + description: Server error + content: + application/json: + schema: + $ref: "#/components/schemas/ServerErrorSchema" + + get: + tags: + - organization + summary: Query organisations where a user belongs + security: + - bearerAuth: [] + description: Retrieves a list of organisations where the specified user belongs. + operationId: getUserorganisations + responses: + "200": + description: A list of organisations + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/Organisation" + "401": + description: Authentication error + content: + application/json: + schema: + $ref: "#/components/schemas/UnauthorizedErrorSchema" + "400": + description: Bad request + content: + application/json: + schema: + $ref: "#/components/schemas/BadRequestErrorSchema" + "500": description: Server error content: application/json: schema: - $ref: '#/components/schemas/ServerErrorSchema' - - + $ref: "#/components/schemas/ServerErrorSchema" + /organizations/{orgId}: patch: tags: @@ -2379,39 +2356,39 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/createOrganizationReq' + $ref: "#/components/schemas/createOrganizationReq" responses: - '200': + "200": description: organization updated successfully content: application/json: schema: - $ref: '#/components/schemas/organizationUSuccessResponse' - '400': + $ref: "#/components/schemas/organizationUSuccessResponse" + "400": description: Bad request content: application/json: schema: - $ref: '#/components/schemas/BadRequestErrorSchema' - '401': + $ref: "#/components/schemas/BadRequestErrorSchema" + "401": description: Unauthorized content: application/json: schema: - $ref: '#/components/schemas/UnauthorizedErrorSchema' - '404': + $ref: "#/components/schemas/UnauthorizedErrorSchema" + "404": description: Not found content: application/json: schema: - $ref: '#/components/schemas/NotFoundErrorSchema' - '500': + $ref: "#/components/schemas/NotFoundErrorSchema" + "500": description: Server error content: application/json: schema: - $ref: '#/components/schemas/ServerErrorSchema' - + $ref: "#/components/schemas/ServerErrorSchema" + get: tags: - organization @@ -2427,37 +2404,37 @@ paths: required: true description: retrieve an organization responses: - '200': + "200": description: organization retrieved successfully content: application/json: schema: - $ref: '#/components/schemas/organizationGSuccessResponse' - '400': + $ref: "#/components/schemas/organizationGSuccessResponse" + "400": description: Bad request content: application/json: schema: - $ref: '#/components/schemas/BadRequestErrorSchema' - '401': + $ref: "#/components/schemas/BadRequestErrorSchema" + "401": description: Unauthorized content: application/json: schema: - $ref: '#/components/schemas/UnauthorizedErrorSchema' - '404': + $ref: "#/components/schemas/UnauthorizedErrorSchema" + "404": description: Not found content: application/json: schema: - $ref: '#/components/schemas/NotFoundErrorSchema' - '500': + $ref: "#/components/schemas/NotFoundErrorSchema" + "500": description: Server error content: application/json: schema: - $ref: '#/components/schemas/ServerErrorSchema' - + $ref: "#/components/schemas/ServerErrorSchema" + delete: tags: - organization @@ -2471,48 +2448,46 @@ paths: type: string format: uuid required: true - + responses: - '204': + "204": description: Organisations deleted successfully - '400': + "400": description: Invalid orgId format content: application/json: schema: $ref: "#/components/schemas/BadRequestErrorSchema" - '401': + "401": description: Unauthorized content: application/json: schema: $ref: "#/components/schemas/UnauthorizedErrorSchema" - - - '403': + + "403": description: Forbidden content: application/json: schema: $ref: "#/components/schemas/ForbiddenErrorSchema" - - '404': + + "404": description: Not found content: application/json: schema: $ref: "#/components/schemas/NotFoundErrorSchema" - - '500': + + "500": description: Internal server error content: application/json: schema: $ref: "#/components/schemas/ServerErrorSchema" - - + /organizations/{orgId}/users: post: tags: @@ -2533,9 +2508,9 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/addUserToOrganizationReq' + $ref: "#/components/schemas/addUserToOrganizationReq" responses: - '200': + "200": description: User added successfully content: application/json: @@ -2552,33 +2527,32 @@ paths: message: type: string example: User added successfully - - '400': + "400": description: Bad request content: application/json: schema: - $ref: '#/components/schemas/BadRequestErrorSchema' - '401': + $ref: "#/components/schemas/BadRequestErrorSchema" + "401": description: Unauthorized content: application/json: schema: - $ref: '#/components/schemas/UnauthorizedErrorSchema' - '404': + $ref: "#/components/schemas/UnauthorizedErrorSchema" + "404": description: Not found content: application/json: schema: - $ref: '#/components/schemas/NotFoundErrorSchema' - '500': + $ref: "#/components/schemas/NotFoundErrorSchema" + "500": description: Server error content: application/json: schema: - $ref: '#/components/schemas/ServerErrorSchema' - + $ref: "#/components/schemas/ServerErrorSchema" + get: tags: - organization @@ -2594,13 +2568,13 @@ paths: required: true responses: - '200': + "200": description: Users retrieved successfully content: application/json: schema: - type: object - properties: + type: object + properties: status: type: string example: success @@ -2626,32 +2600,31 @@ paths: name: type: string example: Iretoms - '400': + "400": description: Bad request content: application/json: schema: - $ref: '#/components/schemas/BadRequestErrorSchema' - '401': + $ref: "#/components/schemas/BadRequestErrorSchema" + "401": description: Unauthorized content: application/json: schema: - $ref: '#/components/schemas/UnauthorizedErrorSchema' - '404': + $ref: "#/components/schemas/UnauthorizedErrorSchema" + "404": description: Not found content: application/json: schema: - $ref: '#/components/schemas/NotFoundErrorSchema' - '500': + $ref: "#/components/schemas/NotFoundErrorSchema" + "500": description: Server error content: application/json: schema: - $ref: '#/components/schemas/ServerErrorSchema' - - + $ref: "#/components/schemas/ServerErrorSchema" + /blogs: post: tags: @@ -2679,7 +2652,7 @@ paths: type: string example: http://image.com responses: - '201': + "201": description: Blog post created successfully content: application/json: @@ -2694,59 +2667,59 @@ paths: example: 201 message: type: string - example: Blog post created successfully. + example: Blog post created successfully. data: type: object properties: - id: - type: string - format: uuid - title: - type: string - example: New Blog Post - content: - type: string - example: This is the content of the new blog post - image_url: - type: string - example: http://image.com - category: - type: string - example: Tech - author: - type: string - author_id: - type: string - format: uuid - created_at: - type: string - format: date-time - - '400': + id: + type: string + format: uuid + title: + type: string + example: New Blog Post + content: + type: string + example: This is the content of the new blog post + image_url: + type: string + example: http://image.com + category: + type: string + example: Tech + author: + type: string + author_id: + type: string + format: uuid + created_at: + type: string + format: date-time + + "400": description: Bad request content: application/json: schema: - $ref: '#/components/schemas/BadRequestErrorSchema' - '401': + $ref: "#/components/schemas/BadRequestErrorSchema" + "401": description: Unauthorized content: application/json: schema: - $ref: '#/components/schemas/UnauthorizedErrorSchema' - '404': + $ref: "#/components/schemas/UnauthorizedErrorSchema" + "404": description: Not found content: application/json: schema: - $ref: '#/components/schemas/NotFoundErrorSchema' - '500': + $ref: "#/components/schemas/NotFoundErrorSchema" + "500": description: Server error content: application/json: schema: - $ref: '#/components/schemas/ServerErrorSchema' - + $ref: "#/components/schemas/ServerErrorSchema" + get: tags: - blogs @@ -2767,7 +2740,7 @@ paths: description: The number of items per page example: 6 responses: - '200': + "200": description: A list of all blogs content: application/json: @@ -2782,7 +2755,7 @@ paths: example: 200 message: type: string - example: Blogs retrieved successfully. + example: Blogs retrieved successfully. data: type: array items: @@ -2799,7 +2772,7 @@ paths: example: This is the content of the new blog post image_url: type: string - example: http://image.com + example: http://image.com category: type: string example: Tech @@ -2811,14 +2784,14 @@ paths: created_at: type: string format: date-time - - '400': + + "400": description: Failed to fetch all blogs content: application/json: schema: - $ref: '#/components/schemas/ErrorResponse' - + $ref: "#/components/schemas/ErrorResponse" + /blogs/{blogId}: get: tags: @@ -2831,7 +2804,7 @@ paths: schema: type: integer responses: - '200': + "200": description: Successful response content: application/json: @@ -2846,52 +2819,52 @@ paths: example: 200 message: type: string - example: Blog post retrieved successfully. + example: Blog post retrieved successfully. data: type: object properties: - id: - type: string - format: uuid - title: - type: string - example: New Blog Post - content: - type: string - example: This is the content of the new blog post - image_url: - type: string - example: http://image.com - category: - type: string - example: Tech - author: - type: string - author_id: - type: string - format: uuid - created_at: - type: string - format: date-time - '400': + id: + type: string + format: uuid + title: + type: string + example: New Blog Post + content: + type: string + example: This is the content of the new blog post + image_url: + type: string + example: http://image.com + category: + type: string + example: Tech + author: + type: string + author_id: + type: string + format: uuid + created_at: + type: string + format: date-time + "400": description: Bad request content: application/json: schema: - $ref: '#/components/schemas/BadRequestErrorSchema' - '404': + $ref: "#/components/schemas/BadRequestErrorSchema" + "404": description: Not found content: application/json: schema: - $ref: '#/components/schemas/NotFoundErrorSchema' - '500': + $ref: "#/components/schemas/NotFoundErrorSchema" + "500": description: Server error content: application/json: schema: - $ref: '#/components/schemas/ServerErrorSchema' - + $ref: "#/components/schemas/ServerErrorSchema" + delete: tags: - blogs @@ -2905,47 +2878,46 @@ paths: type: string format: uuid required: true - + responses: - '204': + "204": description: blog deleted successfully - '400': + "400": description: Invalid orgId format content: application/json: schema: $ref: "#/components/schemas/BadRequestErrorSchema" - '401': + "401": description: Unauthorized content: application/json: schema: $ref: "#/components/schemas/UnauthorizedErrorSchema" - - - '403': + + "403": description: Forbidden content: application/json: schema: $ref: "#/components/schemas/ForbiddenErrorSchema" - - '404': + + "404": description: Not found content: application/json: schema: $ref: "#/components/schemas/NotFoundErrorSchema" - - '500': + + "500": description: Internal server error content: application/json: schema: $ref: "#/components/schemas/ServerErrorSchema" - + /blogs/edit/{blogId}: patch: tags: @@ -2980,7 +2952,7 @@ paths: type: string example: http://solarsystem.com responses: - '200': + "200": description: Blog post updated successfully content: application/json: @@ -2995,189 +2967,188 @@ paths: example: 200 message: type: string - example: Blog post updated successfully. + example: Blog post updated successfully. data: type: object properties: - id: - type: string - format: uuid - title: - type: string - example: Updated Blog Post - content: - type: string - example: This is the content of the updated blog post - image_url: - type: string - example: http://solarsystem.com - category: - type: string - example: space - author: - type: string - author_id: - type: string - format: uuid - updated_at: - type: string - format: date-time - - '400': + id: + type: string + format: uuid + title: + type: string + example: Updated Blog Post + content: + type: string + example: This is the content of the updated blog post + image_url: + type: string + example: http://solarsystem.com + category: + type: string + example: space + author: + type: string + author_id: + type: string + format: uuid + updated_at: + type: string + format: date-time + + "400": description: Bad request content: application/json: schema: - $ref: '#/components/schemas/BadRequestErrorSchema' - '401': - description: Unauthorized + $ref: "#/components/schemas/BadRequestErrorSchema" + "401": + description: Unauthorized + content: + application/json: + schema: + $ref: "#/components/schemas/UnauthorizedErrorSchema" + "404": + description: Not found + content: + application/json: + schema: + $ref: "#/components/schemas/NotFoundErrorSchema" + "500": + description: Server error + content: + application/json: + schema: + $ref: "#/components/schemas/ServerErrorSchema" + + # product routes + /products: + post: + tags: + - products + summary: Create a new product + description: Creates a new product entry. + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + name: + type: string + description: The name of the product + example: "Product 1" + description: + type: string + description: A brief description of the product + example: "This is a product" + price: + type: number + description: The price of the product + example: 100.00 + stock: + type: integer + description: The stock quantity of the product + example: 100 + category: + type: string + description: The category of the product + example: "Category 1" + + responses: + "201": + description: Product created successfully + content: + application/json: + schema: + $ref: "#/components/schemas/SuccessResponseSchema2" + "400": + description: Bad Request content: application/json: schema: - $ref: '#/components/schemas/UnauthorizedErrorSchema' - '404': - description: Not found + $ref: "#/components/schemas/BadRequestErrorSchema" + "401": + description: Authentication error content: application/json: schema: - $ref: '#/components/schemas/NotFoundErrorSchema' - '500': + $ref: "#/components/schemas/UnauthorizedErrorSchema" + "500": description: Server error content: application/json: schema: - $ref: '#/components/schemas/ServerErrorSchema' - -# product routes - /products: - post: - tags: - - products - summary: Create a new product - description: Creates a new product entry. - requestBody: - required: true + $ref: "#/components/schemas/ServerErrorSchema" + + put: + tags: + - products + summary: Update a product + description: Updates a product entry. + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + id: + type: string + description: The id of the product to be updated + example: "1" + name: + type: string + description: The name of the product + example: "Product 1" + description: + type: string + description: A brief description of the product + example: "This is a product" + price: + type: number + description: The price of the product + example: 100.00 + stock: + type: integer + description: The stock quantity of the product + example: 100 + category: + type: string + description: The category of the product + example: "Category 1" + responses: + "200": + description: Product updated successfully content: application/json: schema: - type: object - properties: - name: - type: string - description: The name of the product - example: "Product 1" - description: - type: string - description: A brief description of the product - example: "This is a product" - price: - type: number - description: The price of the product - example: 100.00 - stock: - type: integer - description: The stock quantity of the product - example: 100 - category: - type: string - description: The category of the product - example: "Category 1" - - responses: - - '201': - description: Product created successfully - content: - application/json: - schema: - $ref: '#/components/schemas/SuccessResponseSchema2' - '400': - description: Bad Request - content: - application/json: - schema: - $ref: '#/components/schemas/BadRequestErrorSchema' - '401': - description: Authentication error - content: - application/json: - schema: - $ref: '#/components/schemas/UnauthorizedErrorSchema' - '500': - description: Server error - content: - application/json: - schema: - $ref: '#/components/schemas/ServerErrorSchema' - - put: - tags: - - products - summary: Update a product - description: Updates a product entry. - requestBody: - required: true + $ref: "#/components/schemas/SuccessResponseSchema" + "400": + description: Bad Request content: application/json: schema: - type: object - properties: - id: - type: string - description: The id of the product to be updated - example: "1" - name: - type: string - description: The name of the product - example: "Product 1" - description: - type: string - description: A brief description of the product - example: "This is a product" - price: - type: number - description: The price of the product - example: 100.00 - stock: - type: integer - description: The stock quantity of the product - example: 100 - category: - type: string - description: The category of the product - example: "Category 1" - responses: - '200': - description: Product updated successfully - content: - application/json: - schema: - $ref: '#/components/schemas/SuccessResponseSchema' - '400': - description: Bad Request - content: - application/json: - schema: - $ref: '#/components/schemas/BadRequestErrorSchema' - '401': - description: Authentication error - content: - application/json: - schema: - $ref: '#/components/schemas/UnauthorizedErrorSchema' - '500': - description: Server error - content: - application/json: - schema: - $ref: '#/components/schemas/ServerErrorSchema' + $ref: "#/components/schemas/BadRequestErrorSchema" + "401": + description: Authentication error + content: + application/json: + schema: + $ref: "#/components/schemas/UnauthorizedErrorSchema" + "500": + description: Server error + content: + application/json: + schema: + $ref: "#/components/schemas/ServerErrorSchema" get: tags: - products summary: Get all products description: Get all products. responses: - '200': + "200": description: Products retrieved successfully content: application/json: @@ -3213,30 +3184,30 @@ paths: type: string description: The category of the product example: "Category 1" - '400': + "400": description: Bad Request content: application/json: schema: - $ref: '#/components/schemas/BadRequestErrorSchema' - '401': + $ref: "#/components/schemas/BadRequestErrorSchema" + "401": description: Authentication error content: application/json: schema: - $ref: '#/components/schemas/UnauthorizedErrorSchema' - '404': + $ref: "#/components/schemas/UnauthorizedErrorSchema" + "404": description: Not Found content: application/json: schema: - $ref: '#/components/schemas/NotFoundErrorSchema' - '500': + $ref: "#/components/schemas/NotFoundErrorSchema" + "500": description: Server error content: application/json: schema: - $ref: '#/components/schemas/ServerErrorSchema' + $ref: "#/components/schemas/ServerErrorSchema" /products/{product_id}: get: @@ -3253,7 +3224,7 @@ paths: description: The id of the product to be retrieved example: "1" responses: - '200': + "200": description: Product retrieved successfully content: application/json: @@ -3284,30 +3255,30 @@ paths: type: string description: The category of the product example: "Category 1" - '400': + "400": description: Bad Request content: application/json: schema: - $ref: '#/components/schemas/BadRequestErrorSchema' - '401': + $ref: "#/components/schemas/BadRequestErrorSchema" + "401": description: Authentication error content: application/json: schema: - $ref: '#/components/schemas/UnauthorizedErrorSchema' - '404': + $ref: "#/components/schemas/UnauthorizedErrorSchema" + "404": description: Not Found content: application/json: schema: - $ref: '#/components/schemas/NotFoundErrorSchema' - '500': + $ref: "#/components/schemas/NotFoundErrorSchema" + "500": description: Server error content: application/json: schema: - $ref: '#/components/schemas/ServerErrorSchema' + $ref: "#/components/schemas/ServerErrorSchema" delete: tags: - products @@ -3321,33 +3292,33 @@ paths: type: string description: The id of the product to be retrieved example: "1" - + responses: - '200': + "200": description: Product deleted successfully content: application/json: schema: - $ref: '#/components/schemas/SuccessResponseSchema' - '400': + $ref: "#/components/schemas/SuccessResponseSchema" + "400": description: Bad Request content: application/json: schema: - $ref: '#/components/schemas/BadRequestErrorSchema' - '401': + $ref: "#/components/schemas/BadRequestErrorSchema" + "401": description: Authentication error content: application/json: schema: - $ref: '#/components/schemas/UnauthorizedErrorSchema' - '500': + $ref: "#/components/schemas/UnauthorizedErrorSchema" + "500": description: Server error content: application/json: schema: - $ref: '#/components/schemas/ServerErrorSchema' - + $ref: "#/components/schemas/ServerErrorSchema" + /products/categories/{category}: get: tags: @@ -3363,7 +3334,7 @@ paths: description: The category of the products to be retrieved example: "Category 1" responses: - '200': + "200": description: Products retrieved successfully content: application/json: @@ -3399,115 +3370,114 @@ paths: type: string description: The category of the product example: "Category 1" - '400': + "400": description: Bad Request content: application/json: schema: - $ref: '#/components/schemas/BadRequestErrorSchema' - '401': + $ref: "#/components/schemas/BadRequestErrorSchema" + "401": description: Authentication error content: application/json: schema: - $ref: '#/components/schemas/UnauthorizedErrorSchema' - '404': + $ref: "#/components/schemas/UnauthorizedErrorSchema" + "404": description: Not Found content: application/json: schema: - $ref: '#/components/schemas/NotFoundErrorSchema' - '500': + $ref: "#/components/schemas/NotFoundErrorSchema" + "500": description: Server error content: application/json: schema: - $ref: '#/components/schemas/ServerErrorSchema' - - + $ref: "#/components/schemas/ServerErrorSchema" + /products/filter: get: - tags: - - products - summary: Filter products - description: Filter products by category and price. - parameters: - - name: category - in: query - required: false + tags: + - products + summary: Filter products + description: Filter products by category and price. + parameters: + - name: category + in: query + required: false + schema: + type: string + description: The category of the products to be retrieved + example: "Category 1" + - name: price + in: query + required: false + schema: + type: number + description: The price of the products to be retrieved + example: 100.00 + responses: + "200": + description: Products retrieved successfully + content: + application/json: schema: - type: string - description: The category of the products to be retrieved - example: "Category 1" - - name: price - in: query - required: false - schema: - type: number - description: The price of the products to be retrieved - example: 100.00 - responses: - '200': - description: Products retrieved successfully - content: - application/json: - schema: - type: object - properties: - products: - type: array - items: - type: object - properties: - id: - type: string - description: The id of the product - example: "1" - name: - type: string - description: The name of the product - example: "Product 1" - description: - type: string - description: A brief description of the product - example: "This is a product" - price: - type: number - description: The price of the product - example: 100.00 - stock: - type: integer - description: The stock quantity of the product - example: 100 - category: - type: string - description: The category of the product - example: "Category 1" - '400': - description: Bad Request - content: - application/json: - schema: - $ref: '#/components/schemas/BadRequestErrorSchema' - '401': - description: Authentication error - content: - application/json: - schema: - $ref: '#/components/schemas/UnauthorizedErrorSchema' - '404': - description: Not Found - content: - application/json: - schema: - $ref: '#/components/schemas/NotFoundErrorSchema' - '500': - description: Server error - content: - application/json: - schema: - $ref: '#/components/schemas/ServerErrorSchema' - + type: object + properties: + products: + type: array + items: + type: object + properties: + id: + type: string + description: The id of the product + example: "1" + name: + type: string + description: The name of the product + example: "Product 1" + description: + type: string + description: A brief description of the product + example: "This is a product" + price: + type: number + description: The price of the product + example: 100.00 + stock: + type: integer + description: The stock quantity of the product + example: 100 + category: + type: string + description: The category of the product + example: "Category 1" + "400": + description: Bad Request + content: + application/json: + schema: + $ref: "#/components/schemas/BadRequestErrorSchema" + "401": + description: Authentication error + content: + application/json: + schema: + $ref: "#/components/schemas/UnauthorizedErrorSchema" + "404": + description: Not Found + content: + application/json: + schema: + $ref: "#/components/schemas/NotFoundErrorSchema" + "500": + description: Server error + content: + application/json: + schema: + $ref: "#/components/schemas/ServerErrorSchema" + /products/image/{product_id}: patch: tags: @@ -3534,37 +3504,37 @@ paths: description: The image of the product example: "image.jpg" responses: - '200': + "200": description: Product image updated successfully content: application/json: schema: - $ref: '#/components/schemas/SuccessResponseSchema' - '400': + $ref: "#/components/schemas/SuccessResponseSchema" + "400": description: Bad Request content: application/json: schema: - $ref: '#/components/schemas/BadRequestErrorSchema' - '401': + $ref: "#/components/schemas/BadRequestErrorSchema" + "401": description: Authentication error content: application/json: schema: - $ref: '#/components/schemas/UnauthorizedErrorSchema' - '404': + $ref: "#/components/schemas/UnauthorizedErrorSchema" + "404": description: Not Found content: application/json: schema: - $ref: '#/components/schemas/NotFoundErrorSchema' - '500': + $ref: "#/components/schemas/NotFoundErrorSchema" + "500": description: Server error content: application/json: schema: - $ref: '#/components/schemas/ServerErrorSchema' - + $ref: "#/components/schemas/ServerErrorSchema" + /invite/create: post: tags: @@ -3585,7 +3555,7 @@ paths: - organizationId - email responses: - '201': + "201": description: Invitation link created and sent successfully content: application/json: @@ -3604,20 +3574,20 @@ paths: status_code: type: integer example: 201 - - '400': + + "400": description: Invalid input content: application/json: schema: $ref: "#/components/schemas/BadRequestErrorSchema" - '401': + "401": description: Unauthorized content: application/json: schema: $ref: "#/components/schemas/UnauthorizedErrorSchema" - '500': + "500": description: Internal server error content: application/json: @@ -3646,7 +3616,7 @@ paths: - emails - org_id responses: - '201': + "201": description: Invitations sent successfully content: application/json: @@ -3677,19 +3647,19 @@ paths: organization: type: string example: testorg - '400': + "400": description: Invalid input content: application/json: schema: $ref: "#/components/schemas/BadRequestErrorSchema" - '403': + "403": description: Forbidden content: application/json: schema: $ref: "#/components/schemas/ForbiddenErrorSchema" - '500': + "500": description: Internal server error content: application/json: @@ -3708,7 +3678,7 @@ paths: schema: type: string responses: - '200': + "200": description: Invitation accepted successfully content: application/json: @@ -3721,7 +3691,7 @@ paths: status: type: integer example: 200 - '400': + "400": description: Invalid or expired invitation link content: application/json: @@ -3760,7 +3730,7 @@ paths: required: - invitationLink responses: - '200': + "200": description: Invitation accepted successfully content: application/json: @@ -3773,7 +3743,7 @@ paths: status: type: integer example: 200 - '400': + "400": description: Invalid or expired invitation link content: application/json: @@ -3803,7 +3773,7 @@ paths: security: - bearerAuth: [] responses: - '200': + "200": description: Request successful content: application/json: @@ -3841,7 +3811,7 @@ paths: invitation_token: type: string example: invitation-token - '400': + "400": description: Invalid request content: application/json: @@ -3875,7 +3845,7 @@ paths: - name - body responses: - '201': + "201": description: Template created successfully content: application/json: @@ -3911,7 +3881,7 @@ paths: type: string format: date-time example: 2024-08-01T05:35:48.3211659+01:00 - '422': + "422": description: Validation failed content: application/json: @@ -3933,7 +3903,7 @@ paths: TemplateRequest.Body: type: string example: Body is a required field - '401': + "401": description: Unauthorized content: application/json: @@ -3957,7 +3927,7 @@ paths: - email_template summary: Retrieve all email templates responses: - '200': + "200": description: Templates successfully retrieved content: application/json: @@ -3995,7 +3965,7 @@ paths: type: string format: date-time example: 2024-08-01T00:55:14.011782+01:00 - '401': + "401": description: Unauthorized content: application/json: @@ -4026,7 +3996,7 @@ paths: schema: type: string responses: - '200': + "200": description: Template successfully retrieved content: application/json: @@ -4062,7 +4032,7 @@ paths: type: string format: date-time example: 2024-08-01T02:00:14.011782+01:00 - '401': + "401": description: Unauthorized content: application/json: @@ -4078,7 +4048,7 @@ paths: message: type: string example: Unauthorized - '404': + "404": description: Template not found content: application/json: @@ -4122,7 +4092,7 @@ paths: - name - body responses: - '200': + "200": description: Template successfully updated content: application/json: @@ -4158,7 +4128,7 @@ paths: type: string format: date-time example: 2024-08-01T03:00:14.011782+01:00 - '422': + "422": description: Validation failed content: application/json: @@ -4180,7 +4150,7 @@ paths: TemplateRequest.Body: type: string example: Body is a required field - '401': + "401": description: Unauthorized content: application/json: @@ -4196,7 +4166,7 @@ paths: message: type: string example: Unauthorized - '404': + "404": description: Template not found content: application/json: @@ -4226,7 +4196,7 @@ paths: schema: type: string responses: - '200': + "200": description: Template successfully deleted content: application/json: @@ -4242,7 +4212,7 @@ paths: message: type: string example: Template successfully deleted - '401': + "401": description: Unauthorized content: application/json: @@ -4258,7 +4228,7 @@ paths: message: type: string example: Unauthorized - '404': + "404": description: Template not found content: application/json: @@ -4276,7 +4246,7 @@ paths: example: Template not found security: - bearerAuth: [] - + /api/v1/notifications: post: tags: @@ -4295,7 +4265,7 @@ paths: required: - message responses: - '201': + "201": description: Notification created successfully content: application/json: @@ -4335,7 +4305,7 @@ paths: type: string format: date-time example: 2024-08-01T05:35:48.3211659+01:00 - '400': + "400": description: Invalid request content: application/json: @@ -4351,7 +4321,7 @@ paths: message: type: string example: Invalid request - '401': + "401": description: Unauthorized request content: application/json: @@ -4375,7 +4345,7 @@ paths: - notifications summary: Retrieve all notifications responses: - '200': + "200": description: Notifications retrieved successfully content: application/json: @@ -4418,7 +4388,7 @@ paths: type: string format: date-time example: 2024-08-01T00:55:14.011782+01:00 - '400': + "400": description: Invalid request content: application/json: @@ -4434,7 +4404,7 @@ paths: message: type: string example: Invalid request - '401': + "401": description: Unauthorized request content: application/json: @@ -4466,7 +4436,7 @@ paths: type: boolean example: false responses: - '200': + "200": description: Unread notifications retrieved successfully content: application/json: @@ -4509,7 +4479,7 @@ paths: type: string format: date-time example: 2024-08-01T00:55:14.011782+01:00 - '400': + "400": description: Invalid request content: application/json: @@ -4525,7 +4495,7 @@ paths: message: type: string example: Invalid request - '401': + "401": description: Unauthorized request content: application/json: @@ -4569,7 +4539,7 @@ paths: required: - is_read responses: - '200': + "200": description: Notification updated successfully content: application/json: @@ -4598,7 +4568,7 @@ paths: type: string format: date-time example: 2024-08-01T06:00:14.011782+01:00 - '400': + "400": description: Invalid request content: application/json: @@ -4614,7 +4584,7 @@ paths: message: type: string example: Invalid request - '401': + "401": description: Unauthorized request content: application/json: @@ -4630,7 +4600,7 @@ paths: message: type: string example: Unauthorized request - '404': + "404": description: Notification not found content: application/json: @@ -4661,7 +4631,7 @@ paths: type: string example: 01910b38-22db-79a1-a766-26b69757e3d2 responses: - '200': + "200": description: Notification deleted successfully content: application/json: @@ -4683,7 +4653,7 @@ paths: notifications: type: array items: {} - '400': + "400": description: Invalid request content: application/json: @@ -4699,7 +4669,7 @@ paths: message: type: string example: Invalid request - '401': + "401": description: Unauthorized request content: application/json: @@ -4715,7 +4685,7 @@ paths: message: type: string example: Unauthorized request - '404': + "404": description: Notification not found content: application/json: @@ -4771,7 +4741,7 @@ paths: type: boolean example: true responses: - '200': + "200": description: Notification preferences updated successfully content: application/json: @@ -4814,7 +4784,7 @@ paths: slack_notifications_announcement_and_update_emails: type: boolean example: true - '400': + "400": description: Invalid input content: application/json: @@ -4830,7 +4800,7 @@ paths: message: type: string example: Invalid input - '401': + "401": description: Unauthorized content: application/json: @@ -4854,7 +4824,7 @@ paths: - notification_settings summary: Retrieve notification settings responses: - '200': + "200": description: Notification preferences retrieved successfully content: application/json: @@ -4897,7 +4867,7 @@ paths: slack_notifications_announcement_and_update_emails: type: boolean example: true - '400': + "400": description: Invalid request content: application/json: @@ -4913,7 +4883,7 @@ paths: message: type: string example: Invalid request - '401': + "401": description: Unauthorized content: application/json: @@ -4929,7 +4899,7 @@ paths: message: type: string example: Unauthorized - + /help-center/topics: post: summary: Create a new Help Center topic @@ -4943,15 +4913,15 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/CreateHelpCenter' + $ref: "#/components/schemas/CreateHelpCenter" responses: - '201': + "201": description: Topic created successfully content: application/json: schema: - $ref: '#/components/schemas/HelpCenter' - '400': + $ref: "#/components/schemas/HelpCenter" + "400": description: Invalid request body content: application/json: @@ -4964,9 +4934,9 @@ paths: message: type: string example: Invalid request body - '401': - $ref: '#/components/responses/UnauthorizedError' - '422': + "401": + $ref: "#/components/responses/UnauthorizedError" + "422": description: Validation failed content: application/json: @@ -4979,7 +4949,7 @@ paths: message: type: string example: Validation failed - '500': + "500": description: Failed to add topic content: application/json: @@ -4992,7 +4962,7 @@ paths: message: type: string example: Failed to add topic - + get: summary: Fetch all Help Center topics description: Retrieves all Help Center topics. @@ -5012,18 +4982,18 @@ paths: schema: type: integer description: The number of items per page - example: 6 + example: 6 responses: - '200': + "200": description: Topics retrieved successfully content: application/json: schema: type: array items: - $ref: '#/components/schemas/HelpCenter' - '401': - $ref: '#/components/responses/UnauthorizedError' + $ref: "#/components/schemas/HelpCenter" + "401": + $ref: "#/components/responses/UnauthorizedError" /help-center/topics/{id}: get: @@ -5040,16 +5010,16 @@ paths: required: true description: The ID of the Help Center topic to fetch responses: - '200': + "200": description: Topic retrieved successfully content: application/json: schema: - $ref: '#/components/schemas/HelpCenter' - '401': - $ref: '#/components/responses/UnauthorizedError' - '404': - $ref: '#/components/responses/NotFoundError' + $ref: "#/components/schemas/HelpCenter" + "401": + $ref: "#/components/responses/UnauthorizedError" + "404": + $ref: "#/components/responses/NotFoundError" patch: summary: Update a Help Center topic by ID @@ -5071,19 +5041,19 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/CreateHelpCenter' + $ref: "#/components/schemas/CreateHelpCenter" responses: - '200': + "200": description: Topic updated successfully content: application/json: schema: - $ref: '#/components/schemas/HelpCenter' - '401': - $ref: '#/components/responses/UnauthorizedError' - '404': - $ref: '#/components/responses/NotFoundError' - '500': + $ref: "#/components/schemas/HelpCenter" + "401": + $ref: "#/components/responses/UnauthorizedError" + "404": + $ref: "#/components/responses/NotFoundError" + "500": description: Failed to update topic content: application/json: @@ -5096,7 +5066,7 @@ paths: message: type: string example: Failed to update topic - + delete: summary: Delete a Help Center topic by ID description: Deletes a Help Center topic by its ID. @@ -5113,13 +5083,13 @@ paths: required: true description: The ID of the Help Center topic to delete responses: - '204': + "204": description: Topic deleted successfully - '401': - $ref: '#/components/responses/UnauthorizedError' - '404': - $ref: '#/components/responses/NotFoundError' - '500': + "401": + $ref: "#/components/responses/UnauthorizedError" + "404": + $ref: "#/components/responses/NotFoundError" + "500": description: Failed to delete topic content: application/json: @@ -5147,16 +5117,16 @@ paths: required: true description: The search Topic title responses: - '200': + "200": description: Search results retrieved successfully content: application/json: schema: type: array items: - $ref: '#/components/schemas/HelpCenter' - '401': - $ref: '#/components/responses/UnauthorizedError' + $ref: "#/components/schemas/HelpCenter" + "401": + $ref: "#/components/responses/UnauthorizedError" /contact: post: @@ -5190,30 +5160,30 @@ paths: - subject - message responses: - '201': + "201": description: Contact added successfully content: application/json: schema: - $ref: '#/components/schemas/SuccessResponseSchema2' - '400': + $ref: "#/components/schemas/SuccessResponseSchema2" + "400": description: Bad Request content: application/json: schema: - $ref: '#/components/schemas/BadRequestErrorSchema' - '422': + $ref: "#/components/schemas/BadRequestErrorSchema" + "422": description: Unprocessable Entity content: application/json: schema: - $ref: '#/components/schemas/UnprocessedEntityErrorSchema' - '500': + $ref: "#/components/schemas/UnprocessedEntityErrorSchema" + "500": description: Server error content: application/json: schema: - $ref: '#/components/schemas/ServerErrorSchema' + $ref: "#/components/schemas/ServerErrorSchema" get: tags: @@ -5223,35 +5193,35 @@ paths: - bearerAuth: [] description: Retrieves all contact entries. parameters: - - $ref: '#/components/parameters/PageLimitParam' - - $ref: '#/components/parameters/LimitParam' + - $ref: "#/components/parameters/PageLimitParam" + - $ref: "#/components/parameters/LimitParam" responses: - '200': + "200": description: A list of contacts content: application/json: schema: type: array items: - $ref: '#/components/schemas/ContactSchema' - '400': + $ref: "#/components/schemas/ContactSchema" + "400": description: Bad Request content: application/json: schema: - $ref: '#/components/schemas/BadRequestErrorSchema' - '422': + $ref: "#/components/schemas/BadRequestErrorSchema" + "422": description: Unprocessable Entity content: application/json: schema: - $ref: '#/components/schemas/UnprocessedEntityErrorSchema' - '500': + $ref: "#/components/schemas/UnprocessedEntityErrorSchema" + "500": description: Server error content: application/json: schema: - $ref: '#/components/schemas/ServerErrorSchema' + $ref: "#/components/schemas/ServerErrorSchema" /contact/{id}: delete: @@ -5270,43 +5240,43 @@ paths: format: uuid description: ID of the contact to delete responses: - '200': + "200": description: Contact deleted successfully content: application/json: schema: - $ref: '#/components/schemas/SuccessResponseSchema' - '404': + $ref: "#/components/schemas/SuccessResponseSchema" + "404": description: Contact not found content: application/json: schema: - $ref: '#/components/schemas/NotFoundErrorSchema' - '400': + $ref: "#/components/schemas/NotFoundErrorSchema" + "400": description: Bad Request content: application/json: schema: - $ref: '#/components/schemas/BadRequestErrorSchema' - '422': + $ref: "#/components/schemas/BadRequestErrorSchema" + "422": description: Unprocessable Entity content: application/json: schema: - $ref: '#/components/schemas/UnprocessedEntityErrorSchema' - '401': + $ref: "#/components/schemas/UnprocessedEntityErrorSchema" + "401": description: Authentication error content: application/json: schema: - $ref: '#/components/schemas/UnauthorizedErrorSchema' - '500': + $ref: "#/components/schemas/UnauthorizedErrorSchema" + "500": description: Server error content: application/json: schema: - $ref: '#/components/schemas/ServerErrorSchema' - + $ref: "#/components/schemas/ServerErrorSchema" + /contact/id/{id}: get: tags: @@ -5324,42 +5294,42 @@ paths: format: uuid description: ID of the contact to retrieve responses: - '200': + "200": description: Contact details content: application/json: schema: - $ref: '#/components/schemas/ContactSchema' - '404': + $ref: "#/components/schemas/ContactSchema" + "404": description: Contact not found content: application/json: schema: - $ref: '#/components/schemas/NotFoundErrorSchema' - '400': + $ref: "#/components/schemas/NotFoundErrorSchema" + "400": description: Bad Request content: application/json: schema: - $ref: '#/components/schemas/BadRequestErrorSchema' - '422': + $ref: "#/components/schemas/BadRequestErrorSchema" + "422": description: Unprocessable Entity content: application/json: schema: - $ref: '#/components/schemas/UnprocessedEntityErrorSchema' - '401': + $ref: "#/components/schemas/UnprocessedEntityErrorSchema" + "401": description: Authentication error content: application/json: schema: - $ref: '#/components/schemas/UnauthorizedErrorSchema' - '500': + $ref: "#/components/schemas/UnauthorizedErrorSchema" + "500": description: Server error content: application/json: schema: - $ref: '#/components/schemas/ServerErrorSchema' + $ref: "#/components/schemas/ServerErrorSchema" /contact/email/{email}: get: @@ -5378,47 +5348,45 @@ paths: format: email description: Email of the contact to retrieve responses: - '200': + "200": description: Contact details content: application/json: schema: - $ref: '#/components/schemas/ContactSchema' - '404': + $ref: "#/components/schemas/ContactSchema" + "404": description: Contact not found content: application/json: schema: - $ref: '#/components/schemas/NotFoundErrorSchema' - '400': + $ref: "#/components/schemas/NotFoundErrorSchema" + "400": description: Bad Request content: application/json: schema: - $ref: '#/components/schemas/BadRequestErrorSchema' - '422': + $ref: "#/components/schemas/BadRequestErrorSchema" + "422": description: Unprocessable Entity content: application/json: schema: - $ref: '#/components/schemas/UnprocessedEntityErrorSchema' - '401': + $ref: "#/components/schemas/UnprocessedEntityErrorSchema" + "401": description: Authentication error content: application/json: schema: - $ref: '#/components/schemas/UnauthorizedErrorSchema' - '500': + $ref: "#/components/schemas/UnauthorizedErrorSchema" + "500": description: Server error content: application/json: schema: - $ref: '#/components/schemas/ServerErrorSchema' + $ref: "#/components/schemas/ServerErrorSchema" components: - schemas: - NotFoundErrorSchema: type: object properties: @@ -5512,7 +5480,7 @@ components: type: string description: > A basic message describing the problem with the request. Usually missing if `error` is present. - + UnprocessedEntityErrorSchema: type: object properties: @@ -5533,7 +5501,7 @@ components: message: type: string example: "Validation failed" - + UnauthorizedErrorSchema: type: object properties: @@ -5600,7 +5568,7 @@ components: description: User's primary contact phone number. username: type: string - description: Last updated time + description: Last updated time created_at: type: string format: date-time @@ -5627,7 +5595,7 @@ components: name: type: string description: organisation name - description: + description: type: string description: organisation description @@ -5706,7 +5674,6 @@ components: type: string description: type: string - RegionSchema: type: object @@ -5746,7 +5713,7 @@ components: type: integer message: type: string - + UserRegionResponseSchema: type: object properties: @@ -5820,7 +5787,7 @@ components: roles: type: array items: - $ref: '#/components/schemas/OrgRoleResponseSchema' + $ref: "#/components/schemas/OrgRoleResponseSchema" OrgRoleResponseSchema: type: object @@ -5893,7 +5860,7 @@ components: type: integer message: type: string - + CreateJobPost: type: object properties: @@ -6036,7 +6003,7 @@ components: type: string example: Job post created successfully data: - $ref: '#/components/schemas/JobPost' + $ref: "#/components/schemas/JobPost" ErrorResponse: type: object @@ -6050,8 +6017,7 @@ components: message: type: string example: An error occurred - - + addUserToOrganizationReq: type: object properties: @@ -6059,8 +6025,7 @@ components: type: string format: uuid example: 01910544-d1e1-7ada-bdac-c761e527ec91 - - + createOrganizationReq: type: object properties: @@ -6080,7 +6045,7 @@ components: type: string country: type: string - + organizationResData: type: object description: Object describing the organisation data @@ -6092,22 +6057,22 @@ components: name: type: string example: daveOrg - description: + description: type: string example: something something email: type: string example: org@gmail.com - state: + state: type: string example: lagos industry: type: string example: tech - type: + type: type: string example: sometype - address: + address: type: string example: somewhere somewhere created_at: @@ -6118,7 +6083,7 @@ components: type: string format: date-time example: 2024-07-30T21:11:21.9538358+01:00 - + organizationSuccessResponse: type: object properties: @@ -6132,8 +6097,8 @@ components: type: string example: organization created successfully data: - $ref: '#/components/schemas/organizationResData' - + $ref: "#/components/schemas/organizationResData" + organizationUSuccessResponse: type: object properties: @@ -6147,8 +6112,8 @@ components: type: string example: organization updated successfully data: - $ref: '#/components/schemas/organizationResData' - + $ref: "#/components/schemas/organizationResData" + organizationGSuccessResponse: type: object properties: @@ -6162,8 +6127,8 @@ components: type: string example: organization retrieved successfully data: - $ref: '#/components/schemas/organizationResData' - + $ref: "#/components/schemas/organizationResData" + CreateHelpCenter: type: object properties: @@ -6199,7 +6164,7 @@ components: updated_at: type: string format: date-time - + ContactSchema: type: object properties: @@ -6249,7 +6214,7 @@ components: example: error message: type: string - example: Not Found + example: Not Found parameters: PageLimitParam: @@ -6270,7 +6235,7 @@ components: default: 10 securitySchemes: - bearerAuth: - type: http - scheme: bearer - bearerFormat: JWT \ No newline at end of file + bearerAuth: + type: http + scheme: bearer + bearerFormat: JWT From 0e6fe53e771eee4bd748c766d642f83553752004 Mon Sep 17 00:00:00 2001 From: ekedonald Date: Thu, 8 Aug 2024 14:13:02 +0100 Subject: [PATCH 17/45] upload docker config files --- Dockerfile | 57 ++++++++++++++++++---------------- docker-compose-development.yml | 39 +++++++++++++++++++++++ docker-compose-production.yml | 39 +++++++++++++++++++++++ docker-compose-staging.yml | 39 +++++++++++++++++++++++ 4 files changed, 148 insertions(+), 26 deletions(-) create mode 100644 docker-compose-development.yml create mode 100644 docker-compose-production.yml create mode 100644 docker-compose-staging.yml diff --git a/Dockerfile b/Dockerfile index 64d880b0..0998b822 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,33 +1,38 @@ -FROM golang:1.23rc2-alpine3.20 AS build-stage - -WORKDIR / +# Build stage +FROM golang:1.20.1-alpine3.17 as build +# Set the Current Working Directory inside the container +WORKDIR /usr/src/app +# Copy go.mod and go.sum files COPY go.mod go.sum ./ +# Download all dependencies. Dependencies will be cached if the go.mod and go.sum files are not changed +RUN go mod download && go mod verify -RUN go mod download - - +# Copy the source code into the container COPY . . -RUN go build -o production_app main.go - -FROM alpine:latest - - -RUN addgroup -S nonroot && adduser -S nonroot -G nonroot - -WORKDIR / - - -COPY --from=build-stage production_app . - - -EXPOSE 8080 - - -USER nonroot:nonroot - - -ENTRYPOINT ["./production_app"] \ No newline at end of file +# Build the Go app +RUN if test -e app.env; then echo 'found app.env'; else mv app-sample.env app.env; fi; \ + go build -v -o /dist/app-name + +# Wait-for-it stage +FROM alpine:3.17 as wait +RUN apk add --no-cache bash +ADD https://github.com/vishnubob/wait-for-it/raw/master/wait-for-it.sh /wait-for-it.sh +RUN chmod +x /wait-for-it.sh + +# Deployment stage +FROM alpine:3.17 +WORKDIR /usr/src/app +COPY --from=build /usr/src/app ./ +COPY --from=build /dist/app-name /usr/local/bin/app-name +COPY --from=wait /wait-for-it.sh /wait-for-it.sh + +# Install bash (required for wait-for-it script) +RUN apk add --no-cache bash + +# Wait for DB and Redis, then start the application +# CMD /wait-for-it.sh $DB_HOST:$DB_PORT -t 10 -- /wait-for-it.sh $REDIS_HOST:$REDIS_PORT -t 10 -- app-name +CMD app-name \ No newline at end of file diff --git a/docker-compose-development.yml b/docker-compose-development.yml new file mode 100644 index 00000000..ff5ef9c9 --- /dev/null +++ b/docker-compose-development.yml @@ -0,0 +1,39 @@ +name: golang_dev + +services: + db: + image: postgres:16 + environment: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: password + POSTGRES_DB: db_name + POSTGRES_PORT: 5432 + volumes: + - db_data:/var/lib/postgresql/data + + redis: + image: redis:latest + + backend: + image: ${COMPOSE_PROJECT_NAME} + build: + context: . + depends_on: + - db + - redis + ports: + - "8000:8019" + env_file: + - app.env + + # nginx: + # image: nginx:latest + # ports: + # - "5000:80" + # depends_on: + # - backend + # volumes: + # - ./nginx.conf:/etc/nginx/nginx.conf + +volumes: + db_data: \ No newline at end of file diff --git a/docker-compose-production.yml b/docker-compose-production.yml new file mode 100644 index 00000000..2221e748 --- /dev/null +++ b/docker-compose-production.yml @@ -0,0 +1,39 @@ +name: golang_prod + +services: + db: + image: postgres:16 + environment: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: password + POSTGRES_DB: db_name + POSTGRES_PORT: 5432 + volumes: + - db_data:/var/lib/postgresql/data + + redis: + image: redis:latest + + backend: + image: ${COMPOSE_PROJECT_NAME} + build: + context: . + depends_on: + - db + - redis + ports: + - "8002:8019" + env_file: + - app.env + + # nginx: + # image: nginx:latest + # ports: + # - "5000:80" + # depends_on: + # - backend + # volumes: + # - ./nginx.conf:/etc/nginx/nginx.conf + +volumes: + db_data: \ No newline at end of file diff --git a/docker-compose-staging.yml b/docker-compose-staging.yml new file mode 100644 index 00000000..909e7ce3 --- /dev/null +++ b/docker-compose-staging.yml @@ -0,0 +1,39 @@ +name: golang_staging + +services: + db: + image: postgres:16 + environment: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: password + POSTGRES_DB: db_name + POSTGRES_PORT: 5432 + volumes: + - db_data:/var/lib/postgresql/data + + redis: + image: redis:latest + + backend: + image: ${COMPOSE_PROJECT_NAME} + build: + context: . + depends_on: + - db + - redis + ports: + - "8001:8019" + env_file: + - app.env + + # nginx: + # image: nginx:latest + # ports: + # - "5000:80" + # depends_on: + # - backend + # volumes: + # - ./nginx.conf:/etc/nginx/nginx.conf + +volumes: + db_data: \ No newline at end of file From f9dbdd1f2afdfa0624351bcd61454c5877201dfd Mon Sep 17 00:00:00 2001 From: Ebite God'sgift Uloamaka Date: Thu, 8 Aug 2024 14:32:24 +0100 Subject: [PATCH 18/45] fix: create jobs endpoint --- internal/models/jobpost.go | 61 +++++++++++----------------- pkg/controller/jobpost/jobpost.go | 8 ++-- services/jobpost/jobpost.go | 47 ++++++--------------- static/swagger.yaml | 41 +++++++++---------- tests/test_jobpost/jobpost_test.go | 65 +++++++++++++----------------- 5 files changed, 88 insertions(+), 134 deletions(-) diff --git a/internal/models/jobpost.go b/internal/models/jobpost.go index ca617418..891315c6 100644 --- a/internal/models/jobpost.go +++ b/internal/models/jobpost.go @@ -1,32 +1,15 @@ package models import ( + "reflect" "time" + "github.com/gin-gonic/gin" "github.com/hngprojects/hng_boilerplate_golang_web/pkg/repository/storage/postgresql" + "github.com/hngprojects/hng_boilerplate_golang_web/utility" "gorm.io/gorm" ) - -// type JobPost struct { -// ID string `gorm:"type:uuid; primaryKey" json:"id"` -// Title string `gorm:"column:title; type:varchar(255); not null" json:"title"` -// Salary string `gorm:"column:salary; type:varchar(255)" json:"salary"` -// JobType string `gorm:"column:job_type; type:varchar(50); not null" json:"job_type"` -// WorkMode string `gorm:"column:work_mode; type:varchar(50); not null" json:"work_mode"` -// Deadline time.Time `gorm:"column:deadline; not null" json:"deadline"` -// Location string `gorm:"column:location; type:varchar(255); not null" json:"location"` -// HowToApply string `gorm:"column:how_to_apply; type:varchar(500)" json:"how_to_apply"` -// Experience string `gorm:"column:experience; type:varchar(50); not null" json:"experience"` -// JobBenefits string `gorm:"column:job_benefits; type:varchar(500)" json:"job_benefits"` -// CompanyName string `gorm:"column:company_name; type:varchar(255); not null" json:"company_name"` -// Description string `gorm:"column:description; type:varchar(500); not null" json:"description"` -// KeyResponsibilities string `gorm:"column:key_responsibilities; type:varchar(500)" json:"key_responsibilities"` -// Qualifications string `gorm:"column:qualifications; type:varchar(500)" json:"qualifications"` -// 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 JobPost struct { ID string `gorm:"type:uuid; primaryKey" json:"id"` Title string `gorm:"column:title; type:varchar(255); not null" json:"title"` @@ -35,29 +18,16 @@ type JobPost struct { Location string `gorm:"column:location; type:varchar(255); not null" json:"location"` Deadline time.Time `gorm:"column:deadline; not null" json:"deadline"` JobMode string `gorm:"column:job_mode; type:varchar(50); not null" json:"job_mode"` - Experience string `gorm:"column:experience; type:varchar(50); not null" json:"experience"` CompanyName string `gorm:"column:company_name; type:varchar(255); not null" json:"company_name"` Description string `gorm:"column:description; type:varchar(500); not null" json:"description"` + Benefits string `gorm:"column:benefits; type:varchar(500)" json:"benefits"` + ExperienceLevel string `gorm:"column:experience_level; type:varchar(50); not null" json:"experience_level"` + KeyResponsibilities string `gorm:"column:key_responsibilities; type:varchar(500)" json:"key_responsibilities"` + Qualifications string `gorm:"column:qualifications; type:varchar(500)" json:"qualifications"` 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 CreateJobPostModel struct { -// Title string `json:"title" validate:"required,min=2,max=255"` -// Salary string `json:"salary" validate:"required"` -// JobType string `json:"job_type" validate:"required"` -// Location string `json:"location" validate:"required,min=2,max=255"` -// Deadline time.Time `json:"deadline" validate:"required"` -// WorkMode string `json:"work_mode" validate:"required"` -// Experience string `json:"experience" validate:"required"` -// HowToApply string `json:"how_to_apply" validate:"required"` -// JobBenefits string `json:"job_benefits" validate:"required,min=2,max=500"` -// CompanyName string `json:"company_name" validate:"required,min=2,max=255"` -// Description string `json:"description" validate:"required,min=2,max=500"` -// KeyResponsibilities string `json:"key_responsibilities" validate:"required,min=2,max=500"` -// Qualifications string `json:"qualifications" validate:"required,min=2,max=500"` -// } - type CreateJobPostModel struct { Title string `json:"title" validate:"required,min=2,max=255"` SalaryRange string `json:"salary_range" validate:"required"` @@ -65,9 +35,24 @@ type CreateJobPostModel struct { Location string `json:"location" validate:"required,min=2,max=255"` Deadline time.Time `json:"deadline" validate:"required"` JobMode string `json:"job_mode" validate:"required"` - Experience string `json:"experience" validate:"required"` + Benefits string `json:"benefits" validate:"required,min=2,max=500"` CompanyName string `json:"company_name" validate:"required,min=2,max=255"` Description string `json:"description" validate:"required,min=2,max=500"` + ExperienceLevel string `json:"experience_level" validate:"required,min=2,max=500"` + KeyResponsibilities string `json:"key_responsibilities" validate:"required,min=2,max=500"` + Qualifications string `json:"qualifications" validate:"required,min=2,max=500"` +} + +func (m *CreateJobPostModel) Sanitize() { + v := reflect.ValueOf(m).Elem() + + for i := 0; i < v.NumField(); i++ { + field := v.Field(i) + if field.Kind() == reflect.String { + cleanedValue := utility.CleanStringInput(field.String()) + field.SetString(cleanedValue) + } + } } func (j *JobPost) CreateJobPost(db *gorm.DB) error { diff --git a/pkg/controller/jobpost/jobpost.go b/pkg/controller/jobpost/jobpost.go index f4c6dd71..30e81cef 100644 --- a/pkg/controller/jobpost/jobpost.go +++ b/pkg/controller/jobpost/jobpost.go @@ -30,6 +30,8 @@ func (base *Controller) CreateJobPost(c *gin.Context) { return } + // req.Sanitize() + if err := base.Validator.Struct(&req); err != nil { rd := utility.BuildErrorResponse(http.StatusUnprocessableEntity, "error", "Validation failed", utility.ValidationResponse(err, base.Validator), nil) c.JSON(http.StatusUnprocessableEntity, rd) @@ -43,8 +45,8 @@ func (base *Controller) CreateJobPost(c *gin.Context) { return } - base.Logger.Info("Job post created successfully") - rd := utility.BuildSuccessResponse(http.StatusCreated, "Job post created successfully", respData) + base.Logger.Info("Job created successfully") + rd := utility.BuildSuccessResponse(http.StatusCreated, "Job created successfully", respData) c.JSON(http.StatusCreated, rd) } @@ -53,7 +55,7 @@ func (base *Controller) FetchAllJobPost(c *gin.Context) { jobPosts, paginationResponse, err := service.GetPaginatedJobPosts(c, base.Db.Postgresql) if err != nil { if err == gorm.ErrRecordNotFound { - rd := utility.BuildErrorResponse(http.StatusNotFound, "error", "No Job post not found", err, nil) + rd := utility.BuildErrorResponse(http.StatusNotFound, "error", "Jobs not found", err, nil) c.JSON(http.StatusNotFound, rd) } else { rd := utility.BuildErrorResponse(http.StatusInternalServerError, "error", "Failed to fetch job post", err, nil) diff --git a/services/jobpost/jobpost.go b/services/jobpost/jobpost.go index 58349ce4..f3338b03 100644 --- a/services/jobpost/jobpost.go +++ b/services/jobpost/jobpost.go @@ -24,34 +24,20 @@ type JobPostSummary struct { } func CreateJobPost(req models.CreateJobPostModel, db *gorm.DB) (models.JobPost, error) { - // jobpost := models.JobPost{ - // ID: utility.GenerateUUID(), - // Title: req.Title, - // Salary: req.Salary, - // JobType: req.JobType, - // Location: req.Location, - // Deadline: req.Deadline, - // WorkMode: req.WorkMode, - // Experience: req.Experience, - // HowToApply: req.HowToApply, - // JobBenefits: req.JobBenefits, - // Description: req.Description, - // CompanyName: req.CompanyName, - // KeyResponsibilities: req.KeyResponsibilities, - // Qualifications: req.Qualifications, - // } - jobpost := models.JobPost{ - ID: utility.GenerateUUID(), - Title: req.Title, - JobMode: req.JobMode, - JobType: req.JobType, - Location: req.Location, - Deadline: req.Deadline, - SalaryRange: req.SalaryRange, - Experience: req.Experience, - Description: req.Description, - CompanyName: req.CompanyName, + ID: utility.GenerateUUID(), + Title: req.Title, + JobMode: req.JobMode, + JobType: req.JobType, + Location: req.Location, + Deadline: req.Deadline, + Benefits: req.Benefits, + SalaryRange: req.SalaryRange, + Description: req.Description, + CompanyName: req.CompanyName, + ExperienceLevel: req.ExperienceLevel, + KeyResponsibilities: req.KeyResponsibilities, + Qualifications: req.Qualifications, } if err := jobpost.CreateJobPost(db); @@ -77,13 +63,6 @@ func GetPaginatedJobPosts(c *gin.Context, db *gorm.DB) ([]JobPostSummary, postgr var jobPostSummaries []JobPostSummary for _, job := range jobPosts { - // summary := JobPostSummary{ - // ID: job.ID, - // Title: job.Title, - // Description: job.Description, - // Location: job.Location, - // Salary: job.Salary, - // } summary := JobPostSummary{ ID: job.ID, Title: job.Title, diff --git a/static/swagger.yaml b/static/swagger.yaml index 510e7522..3808597c 100644 --- a/static/swagger.yaml +++ b/static/swagger.yaml @@ -2064,15 +2064,15 @@ paths: example: 6 responses: '200': - description: A list of job posts + description: A list of paginated job posts content: application/json: schema: type: array items: $ref: '#/components/schemas/JobPost' - '500': - description: Failed to fetch job posts + '404': + description: Jobs not found content: application/json: schema: @@ -2139,7 +2139,7 @@ paths: schema: $ref: '#/components/schemas/ErrorResponse' '404': - description: Job post not found + description: Job not found content: application/json: schema: @@ -5822,7 +5822,7 @@ components: properties: title: type: string - salary: + salary_range: type: string job_type: type: string @@ -5830,13 +5830,11 @@ components: type: string deadline: type: string - work_mode: - type: string - experience: + job_mode: type: string - how_to_apply: + experience_level: type: string - job_benefits: + benefits: type: string company_name: type: string @@ -5853,7 +5851,7 @@ components: title: type: string example: Updated Software Engineer Intern - salary: + salary_range: type: string example: 6000-8000 USD job_type: @@ -5866,16 +5864,13 @@ components: type: string format: date-time example: 2024-12-31T23:59:59Z - work_mode: + job_mode: type: string example: hybrid - experience: + experience_level: type: string example: Entry level (0-3 years) - how_to_apply: - type: string - example: Submit your resume and cover letter to hr@company.com - job_benefits: + benefits: type: string example: Flexible hours, Health insurance, Stock options company_name: @@ -5900,7 +5895,7 @@ components: title: type: string example: Junior Backend intern - salary: + salary_range: type: string example: 500,000 NGN job_type: @@ -5913,16 +5908,16 @@ components: type: string format: date-time example: 2024-12-31T23:59:59Z - work_mode: + job_mode: type: string example: remote - experience: + experience_level: type: string - example: Entry level (0-2 years) + example: 0-2 years how_to_apply: type: string example: Submit your resume and cover letter to hr@company.com - job_benefits: + benefits: type: string example: Flexible hours, Remote work, Health insurance company_name: @@ -5957,7 +5952,7 @@ components: example: 201 message: type: string - example: Job post created successfully + example: Job created successfully data: $ref: '#/components/schemas/JobPost' diff --git a/tests/test_jobpost/jobpost_test.go b/tests/test_jobpost/jobpost_test.go index 64ebad02..d78d4097 100644 --- a/tests/test_jobpost/jobpost_test.go +++ b/tests/test_jobpost/jobpost_test.go @@ -62,21 +62,20 @@ func TestJobPostCreate(t *testing.T) { Name: "Successful job post creation", RequestBody: models.CreateJobPostModel{ Title: "Software Engineer Intern", - Salary: "5000-7000 USD", + SalaryRange: "below_30k", JobType: "internship", Location: "San Francisco, CA", Deadline: time.Now().AddDate(0, 1, 0), - WorkMode: "remote", - Experience: "Entry level (0-2 years)", - HowToApply: "Submit your resume and cover letter to hr@company.com", - JobBenefits: "Flexible hours, Remote work, Health insurance", + JobMode: "remote", + ExperienceLevel: "0-2 years", + Benefits: "Flexible hours, Remote work, Health insurance", CompanyName: "Tech Innovators", Description: "We are looking for a passionate Software Engineer Intern to join our team. You will be working on exciting projects and gain hands-on experience.", KeyResponsibilities: "Develop and maintain web applications, Collaborate with the team on various projects, Participate in code reviews", Qualifications: "Ability to work solo, Bachelor degree", }, ExpectedCode: http.StatusCreated, - Message: "Job post created successfully", + Message: "Job created successfully", Headers: map[string]string{ "Content-Type": "application/json", "Authorization": "Bearer " + token, @@ -85,14 +84,13 @@ func TestJobPostCreate(t *testing.T) { Name: "Invalid job type", RequestBody: models.CreateJobPostModel{ Title: "Software Engineer Intern", - Salary: "5000-7000 USD", + SalaryRange: "below_30k", JobType: "", Location: "San Francisco, CA", Deadline: time.Now().AddDate(0, 1, 0), - WorkMode: "remote", - Experience: "Entry level (0-2 years)", - HowToApply: "Submit your resume and cover letter to hr@company.com", - JobBenefits: "Flexible hours, Remote work, Health insurance", + JobMode: "remote", + ExperienceLevel: "0-2 years", + Benefits: "Flexible hours, Remote work, Health insurance", CompanyName: "Tech Innovators", Description: "We are looking for a passionate Software Engineer Intern to join our team. You will be working on exciting projects and gain hands-on experience.", KeyResponsibilities: "Develop and maintain web applications, Collaborate with the team on various projects, Participate in code reviews", @@ -110,14 +108,13 @@ func TestJobPostCreate(t *testing.T) { Name: "User unauthorized", RequestBody: models.CreateJobPostModel{ Title: "Software Engineer Intern", - Salary: "5000-7000 USD", + SalaryRange: "below_30k", JobType: "internship", Location: "San Francisco, CA", Deadline: time.Now().AddDate(0, 1, 0), - WorkMode: "remote", - Experience: "Entry level (0-2 years)", - HowToApply: "Submit your resume and cover letter to hr@company.com", - JobBenefits: "Flexible hours, Remote work, Health insurance", + JobMode: "remote", + ExperienceLevel: "2 years", + Benefits: "Flexible hours, Remote work, Health insurance", CompanyName: "Tech Innovators", Description: "We are looking for a passionate Software Engineer Intern to join our team. You will be working on exciting projects and gain hands-on experience.", KeyResponsibilities: "Develop and maintain web applications, Collaborate with the team on various projects, Participate in code reviews", @@ -255,14 +252,13 @@ func TestFetchJobPostById(t *testing.T) { jobPostData := models.CreateJobPostModel{ Title: "Software Engineer Intern", - Salary: "5000-7000 USD", + SalaryRange: "below_30k", JobType: "internship", Location: "San Francisco, CA", Deadline: time.Now().AddDate(0, 1, 0), - WorkMode: "remote", - Experience: "Entry level (0-2 years)", - HowToApply: "Submit your resume and cover letter to hr@company.com", - JobBenefits: "Flexible hours, Remote work, Health insurance", + JobMode: "remote", + ExperienceLevel: "2 years", + Benefits: "Flexible hours, Remote work, Health insurance", CompanyName: "Tech Innovators", Description: "We are looking for a passionate Software Engineer Intern to join our team. You will be working on exciting projects and gain hands-on experience.", KeyResponsibilities: "Develop and maintain web applications, Collaborate with the team on various projects, Participate in code reviews", @@ -362,14 +358,13 @@ func TestUpdateJobPostById(t *testing.T) { jobPostData := models.CreateJobPostModel{ Title: "Software Engineer Intern", - Salary: "5000-7000 USD", + SalaryRange: "below_30k", JobType: "internship", Location: "San Francisco, CA", Deadline: time.Now().AddDate(0, 1, 0), - WorkMode: "remote", - Experience: "Entry level (0-2 years)", - HowToApply: "Submit your resume and cover letter to hr@company.com", - JobBenefits: "Flexible hours, Remote work, Health insurance", + JobMode: "remote", + ExperienceLevel: "2 years", + Benefits: "Flexible hours, Remote work, Health insurance", CompanyName: "Tech Innovators", Description: "We are looking for a passionate Software Engineer Intern to join our team. You will be working on exciting projects and gain hands-on experience.", KeyResponsibilities: "Develop and maintain web applications, Collaborate with the team on various projects, Participate in code reviews", @@ -400,14 +395,13 @@ func TestUpdateJobPostById(t *testing.T) { updatedJobPostData := models.JobPost{ Title: "Updated Software Engineer Intern", - Salary: "6000-8000 USD", + SalaryRange: "below_30k", JobType: "full-time", Location: "San Francisco, CA", Deadline: time.Now().AddDate(0, 1, 0), - WorkMode: "hybrid", - Experience: "Entry level (0-3 years)", - HowToApply: "Submit your resume and cover letter to hr@company.com", - JobBenefits: "Flexible hours, Health insurance, Stock options", + JobMode: "hybrid", + ExperienceLevel: "3 years", + Benefits: "Flexible hours, Health insurance, Stock options", CompanyName: "Tech Innovators Inc.", Description: "We are looking for a passionate Software Engineer Intern to join our team. You'll work on exciting projects and gain valuable experience.", KeyResponsibilities: "Develop and maintain web applications, Collaborate with the team on various projects, Participate in code reviews", @@ -482,14 +476,13 @@ func TestDeleteJobPostById(t *testing.T) { jobPostData := models.CreateJobPostModel{ Title: "Software Engineer Intern", - Salary: "5000-7000 USD", + SalaryRange: "below_30k", JobType: "internship", Location: "San Francisco, CA", Deadline: time.Now().AddDate(0, 1, 0), - WorkMode: "remote", - Experience: "Entry level (0-2 years)", - HowToApply: "Submit your resume and cover letter to hr@company.com", - JobBenefits: "Flexible hours, Remote work, Health insurance", + JobMode: "remote", + ExperienceLevel: "2 years", + Benefits: "Flexible hours, Remote work, Health insurance", CompanyName: "Tech Innovators", Description: "We are looking for a passionate Software Engineer Intern to join our team. You will be working on exciting projects and gain hands-on experience.", KeyResponsibilities: "Develop and maintain web applications, Collaborate with the team on various projects, Participate in code reviews", From 944462dc6833fdaccf73cc94b754f570dc7584c7 Mon Sep 17 00:00:00 2001 From: Iretoms Date: Thu, 8 Aug 2024 14:38:02 +0100 Subject: [PATCH 19/45] fix: update yaml file --- static/swagger.yaml | 2397 ++++++++++++++++++++++--------------------- 1 file changed, 1211 insertions(+), 1186 deletions(-) diff --git a/static/swagger.yaml b/static/swagger.yaml index 38279819..1727a0e4 100644 --- a/static/swagger.yaml +++ b/static/swagger.yaml @@ -18,7 +18,7 @@ servers: tags: - name: auth - description: API for user registration and authentication + description: API for user registration and authentication - name: contact description: API for contact us management - name: roles @@ -28,7 +28,7 @@ tags: - name: newsletter description: API for newsletter related operations - name: faq - description: API for FAQ related operations + description: API for FAQ related operations - name: waitlist description: API for waitlist related operations - name: language @@ -45,6 +45,7 @@ tags: description: API for products related operations paths: + # Auth paths /auth/register: post: @@ -78,7 +79,7 @@ paths: - first_name - last_name responses: - "201": + '201': description: User registered successfully content: application/json: @@ -98,20 +99,21 @@ paths: data: $ref: "#/components/schemas/UserSchema" - "400": + '400': description: Invalid input content: application/json: schema: $ref: "#/components/schemas/BadRequestErrorSchema" - - "422": + + '422': description: Unprocessed Entity content: application/json: schema: $ref: "#/components/schemas/UnprocessedEntityErrorSchema" - + + /auth/login: post: tags: @@ -132,7 +134,7 @@ paths: - email - password responses: - "200": + '200': description: User login successfully content: application/json: @@ -152,19 +154,19 @@ paths: example: Login Successfully data: $ref: "#/components/schemas/UserSchema" - "401": + '401': description: Unauthorized content: application/json: schema: $ref: "#/components/schemas/UnauthorizedErrorSchema" - "422": + '422': description: Unprocessed Entity content: - application/json: - schema: + application/json: + schema: $ref: "#/components/schemas/UnprocessedEntityErrorSchema" - + /auth/logout: post: tags: @@ -173,7 +175,7 @@ paths: security: - bearerAuth: [] responses: - "200": + '200': description: User logout successfully content: application/json: @@ -194,28 +196,28 @@ paths: data: type: object example: {} - "401": + '401': description: Unauthorized content: application/json: schema: $ref: "#/components/schemas/UnauthorizedErrorSchema" - "400": + '400': description: Invalid input content: - application/json: - schema: - type: object - properties: - status: - type: string - example: "Bad Request" - message: - type: string - example: "message" - status_code: - type: integer - example: 400 + application/json: + schema: + type: object + properties: + status: + type: string + example: "Bad Request" + message: + type: string + example: "message" + status_code: + type: integer + example: 400 /auth/google: post: tags: @@ -234,25 +236,25 @@ paths: - id_token responses: - "200": + '200': description: user sign in succesfully content: - application/json: - schema: - type: object - properties: - status: - type: string - example: "success" - message: - type: string - example: "user sign in successfully" - status_code: - type: integer - example: 200 - data: - type: object - properties: + application/json: + schema: + type: object + properties: + status: + type: string + example: "success" + message: + type: string + example: "user sign in successfully" + status_code: + type: integer + example: 200 + data: + type: object + properties: access_token: type: string example: "access_token" @@ -277,37 +279,37 @@ paths: role: type: string example: "user" - - "400": + + '400': description: Invalid input content: - application/json: - schema: - type: object - properties: - status: - type: string - example: "Bad Request" - message: - type: string - example: "message" - status_code: - type: integer - example: 400 - "422": + application/json: + schema: + type: object + properties: + status: + type: string + example: "Bad Request" + message: + type: string + example: "message" + status_code: + type: integer + example: 400 + '422': description: Unprocessed Entity content: - application/json: - schema: + application/json: + schema: $ref: "#/components/schemas/UnprocessedEntityErrorSchema" - "401": + '401': description: Unauthorized content: application/json: schema: $ref: "#/components/schemas/UnauthorizedErrorSchema" - + /auth/facebook: post: tags: @@ -326,25 +328,25 @@ paths: - id_token responses: - "200": + '200': description: user sign in succesfully content: - application/json: - schema: - type: object - properties: - status: - type: string - example: "success" - message: - type: string - example: "user sign in successfully" - status_code: - type: integer - example: 200 - data: - type: object - properties: + application/json: + schema: + type: object + properties: + status: + type: string + example: "success" + message: + type: string + example: "user sign in successfully" + status_code: + type: integer + example: 200 + data: + type: object + properties: access_token: type: string example: "access_token" @@ -369,31 +371,31 @@ paths: role: type: string example: "user" - - "400": + + '400': description: Invalid input content: - application/json: - schema: - type: object - properties: - status: - type: string - example: "Bad Request" - message: - type: string - example: "message" - status_code: - type: integer - example: 400 - "422": + application/json: + schema: + type: object + properties: + status: + type: string + example: "Bad Request" + message: + type: string + example: "message" + status_code: + type: integer + example: 400 + '422': description: Unprocessed Entity content: - application/json: - schema: + application/json: + schema: $ref: "#/components/schemas/UnprocessedEntityErrorSchema" - "401": + '401': description: Unauthorized content: application/json: @@ -423,46 +425,46 @@ paths: - new_password responses: - "200": + '200': description: Password updated successfully content: - application/json: - schema: - type: object - properties: - status: - type: string - example: "success" - message: - type: string - example: "Password updated successfully" - status_code: - type: integer - example: 200 - "400": + application/json: + schema: + type: object + properties: + status: + type: string + example: "success" + message: + type: string + example: "Password updated successfully" + status_code: + type: integer + example: 200 + '400': description: Invalid input content: - application/json: - schema: - type: object - properties: - status: - type: string - example: "Bad Request" - message: - type: string - example: "message" - status_code: - type: integer - example: 400 - "422": + application/json: + schema: + type: object + properties: + status: + type: string + example: "Bad Request" + message: + type: string + example: "message" + status_code: + type: integer + example: 400 + '422': description: Unprocessed Entity content: - application/json: - schema: + application/json: + schema: $ref: "#/components/schemas/UnprocessedEntityErrorSchema" - "401": + '401': description: Unauthorized content: application/json: @@ -475,10 +477,10 @@ paths: - auth summary: Request a magic link for authentication description: |- - Uses Cases: - - Sign up a new user - - Sign in an existing user - - Sign in with auto sign-up a new user if not exists + Uses Cases: + - Sign up a new user + - Sign in an existing user + - Sign in with auto sign-up a new user if not exists requestBody: required: true content: @@ -491,42 +493,42 @@ paths: required: - email responses: - "200": + '200': description: Magic link sent successfully content: - application/json: - schema: - type: object - properties: - status: - type: string - example: "success" - message: - type: string - example: "Magic link sent succesfully" - statusCode: - type: integer - example: 200 - - "400": + application/json: + schema: + type: object + properties: + status: + type: string + example: "success" + message: + type: string + example: "Magic link sent succesfully" + statusCode: + type: integer + example: 200 + + '400': description: Invalid input content: application/json: schema: - $ref: "#/components/schemas/BadRequestErrorSchema" - "422": + $ref: '#/components/schemas/BadRequestErrorSchema' + '422': description: Validation error content: application/json: schema: - $ref: "#/components/schemas/UnprocessedEntityErrorSchema" - "500": + $ref: '#/components/schemas/UnprocessedEntityErrorSchema' + '500': description: Server error content: application/json: schema: - $ref: "#/components/schemas/ServerErrorSchema" - + $ref: '#/components/schemas/ServerErrorSchema' + /auth/magick-link/verify: post: tags: @@ -544,42 +546,43 @@ paths: required: - token responses: - "200": + '200': description: Token verified successfully content: - application/json: - schema: - type: object - properties: - status: - type: string - example: "success" - statusCode: - type: integer - example: 200 - message: - type: string - example: "Token verified successfully" - data: - $ref: "#/components/schemas/UserSchema" - "400": + application/json: + schema: + type: object + properties: + status: + type: string + example: "success" + statusCode: + type: integer + example: 200 + message: + type: string + example: "Token verified successfully" + data: + $ref: "#/components/schemas/UserSchema" + '400': description: Invalid input content: application/json: schema: - $ref: "#/components/schemas/BadRequestErrorSchema" - "422": + $ref: '#/components/schemas/BadRequestErrorSchema' + '422': description: Validation error content: application/json: schema: - $ref: "#/components/schemas/UnprocessedEntityErrorSchema" - "500": + $ref: '#/components/schemas/UnprocessedEntityErrorSchema' + '500': description: Server error content: application/json: schema: - $ref: "#/components/schemas/ServerErrorSchema" + $ref: '#/components/schemas/ServerErrorSchema' + /auth/password-reset: post: @@ -598,41 +601,42 @@ paths: required: - email responses: - "200": + '200': description: Password reset link sent successfully content: - application/json: - schema: - type: object - properties: - status: - type: string - example: "success" - message: - type: string - example: "Password reset link sent successfully" - statusCode: - type: integer - example: 200 - "400": + application/json: + schema: + type: object + properties: + status: + type: string + example: "success" + message: + type: string + example: "Password reset link sent successfully" + statusCode: + type: integer + example: 200 + '400': description: Invalid input content: application/json: schema: - $ref: "#/components/schemas/BadRequestErrorSchema" - "422": + $ref: '#/components/schemas/BadRequestErrorSchema' + '422': description: Validation error content: application/json: schema: - $ref: "#/components/schemas/UnprocessedEntityErrorSchema" - "500": + $ref: '#/components/schemas/UnprocessedEntityErrorSchema' + '500': description: Server error content: application/json: schema: - $ref: "#/components/schemas/ServerErrorSchema" - + $ref: '#/components/schemas/ServerErrorSchema' + + /auth/password-reset/verify: post: tags: @@ -654,40 +658,40 @@ paths: - new_password responses: - "200": + '200': description: Password reset successfully content: - application/json: - schema: - type: object - properties: - status: - type: string - example: "success" - message: - type: string - example: "Password reset successfully" - statusCode: - type: integer - example: 200 - "400": + application/json: + schema: + type: object + properties: + status: + type: string + example: "success" + message: + type: string + example: "Password reset successfully" + statusCode: + type: integer + example: 200 + '400': description: Invalid input content: application/json: schema: - $ref: "#/components/schemas/BadRequestErrorSchema" - "422": + $ref: '#/components/schemas/BadRequestErrorSchema' + '422': description: Validation error content: application/json: schema: - $ref: "#/components/schemas/UnprocessedEntityErrorSchema" - "500": + $ref: '#/components/schemas/UnprocessedEntityErrorSchema' + '500': description: Server error content: application/json: schema: - $ref: "#/components/schemas/ServerErrorSchema" + $ref: '#/components/schemas/ServerErrorSchema' #User path /users/{userId}: @@ -708,36 +712,36 @@ paths: type: string description: ID of the user responses: - "200": + '200': description: A user information content: application/json: schema: - $ref: "#/components/schemas/UserSchema" - "401": + $ref: '#/components/schemas/UserSchema' + '401': description: Authentication error content: application/json: schema: - $ref: "#/components/schemas/UnauthorizedErrorSchema" - "404": + $ref: '#/components/schemas/UnauthorizedErrorSchema' + '404': description: User not found content: application/json: schema: - $ref: "#/components/schemas/NotFoundErrorSchema" - "400": + $ref: '#/components/schemas/NotFoundErrorSchema' + '400': description: Bad request content: application/json: schema: - $ref: "#/components/schemas/BadRequestErrorSchema" - "500": + $ref: '#/components/schemas/BadRequestErrorSchema' + '500': description: Server error content: application/json: schema: - $ref: "#/components/schemas/ServerErrorSchema" + $ref: '#/components/schemas/ServerErrorSchema' put: tags: @@ -760,39 +764,39 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/UpdateUser" + $ref: '#/components/schemas/UpdateUser' required: true responses: - "200": + '200': description: User updated successfully content: application/json: schema: - $ref: "#/components/schemas/UserSchema" - "401": + $ref: '#/components/schemas/UserSchema' + '401': description: Authentication error content: application/json: schema: - $ref: "#/components/schemas/UnauthorizedErrorSchema" - "422": + $ref: '#/components/schemas/UnauthorizedErrorSchema' + '422': description: Validation error content: application/json: schema: - $ref: "#/components/schemas/UnprocessedEntityErrorSchema" - "400": + $ref: '#/components/schemas/UnprocessedEntityErrorSchema' + '400': description: Bad request content: application/json: schema: - $ref: "#/components/schemas/BadRequestErrorSchema" - "500": + $ref: '#/components/schemas/BadRequestErrorSchema' + '500': description: Server error content: application/json: schema: - $ref: "#/components/schemas/ServerErrorSchema" + $ref: '#/components/schemas/ServerErrorSchema' delete: tags: @@ -811,37 +815,37 @@ paths: type: string description: ID of the user to delete responses: - "200": + '200': description: User deleted successfully content: application/json: schema: - $ref: "#/components/schemas/SuccessResponseSchema" - "401": + $ref: '#/components/schemas/SuccessResponseSchema' + '401': description: Authentication error content: application/json: schema: - $ref: "#/components/schemas/UnauthorizedErrorSchema" - "404": + $ref: '#/components/schemas/UnauthorizedErrorSchema' + '404': description: User not found content: application/json: schema: - $ref: "#/components/schemas/NotFoundErrorSchema" - "400": + $ref: '#/components/schemas/NotFoundErrorSchema' + '400': description: Bad Request content: application/json: schema: - $ref: "#/components/schemas/BadRequestErrorSchema" - "500": + $ref: '#/components/schemas/BadRequestErrorSchema' + '500': description: Server error content: application/json: schema: - $ref: "#/components/schemas/ServerErrorSchema" - + $ref: '#/components/schemas/ServerErrorSchema' + /users/{userId}/role/{roleId}: put: tags: @@ -863,43 +867,43 @@ paths: type: integer responses: - "200": + '200': description: Role updated successfully content: - application/json: - schema: - type: object - properties: - status: - type: string - example: "success" - message: - type: string - example: "Role updated successfully" - status_code: - type: integer - example: 200 - "400": + application/json: + schema: + type: object + properties: + status: + type: string + example: "success" + message: + type: string + example: "Role updated successfully" + status_code: + type: integer + example: 200 + '400': description: Bad request content: - application/json: - schema: - $ref: "#/components/schemas/BadRequestErrorSchema" + application/json: + schema: + $ref: "#/components/schemas/BadRequestErrorSchema" - "404": + '404': description: Not found content: - application/json: - schema: + application/json: + schema: $ref: "#/components/schemas/NotFoundErrorSchema" - "401": + '401': description: Unauthorized content: application/json: schema: $ref: "#/components/schemas/UnauthorizedErrorSchema" - + /users: get: tags: @@ -909,29 +913,29 @@ paths: - bearerAuth: [] description: Retrieves a paginated list of users. parameters: - - $ref: "#/components/parameters/PageLimitParam" - - $ref: "#/components/parameters/LimitParam" + - $ref: '#/components/parameters/PageLimitParam' + - $ref: '#/components/parameters/LimitParam' responses: - "200": + '200': description: A list of users content: application/json: schema: type: array items: - $ref: "#/components/schemas/UserSchema" - "401": + $ref: '#/components/schemas/UserSchema' + '401': description: Authentication error content: application/json: schema: - $ref: "#/components/schemas/UnauthorizedErrorSchema" - "500": + $ref: '#/components/schemas/UnauthorizedErrorSchema' + '500': description: Server error content: application/json: schema: - $ref: "#/components/schemas/ServerErrorSchema" + $ref: '#/components/schemas/ServerErrorSchema' /newsletter-subscription: get: @@ -942,29 +946,29 @@ paths: - bearerAuth: [] description: Retrieves a paginated list of newsletters. parameters: - - $ref: "#/components/parameters/PageLimitParam" - - $ref: "#/components/parameters/LimitParam" + - $ref: '#/components/parameters/PageLimitParam' + - $ref: '#/components/parameters/LimitParam' responses: - "200": + '200': description: A list of newsletters content: application/json: schema: type: array items: - $ref: "#/components/schemas/NewsletterSchema" - "400": + $ref: '#/components/schemas/NewsletterSchema' + '400': description: Bad Request content: application/json: schema: - $ref: "#/components/schemas/BadRequestErrorSchema" - "500": + $ref: '#/components/schemas/BadRequestErrorSchema' + '500': description: Server error content: application/json: schema: - $ref: "#/components/schemas/ServerErrorSchema" + $ref: '#/components/schemas/ServerErrorSchema' post: tags: @@ -976,32 +980,32 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/NewsletterSchema" + $ref: '#/components/schemas/NewsletterSchema' responses: - "201": + '201': description: Email added successfully content: application/json: schema: - $ref: "#/components/schemas/SuccessResponseSchema2" - "400": + $ref: '#/components/schemas/SuccessResponseSchema2' + '400': description: Bad Request content: application/json: schema: - $ref: "#/components/schemas/BadRequestErrorSchema" - "401": + $ref: '#/components/schemas/BadRequestErrorSchema' + '401': description: Authentication error content: application/json: schema: - $ref: "#/components/schemas/UnauthorizedErrorSchema" - "500": + $ref: '#/components/schemas/UnauthorizedErrorSchema' + '500': description: Server error content: application/json: schema: - $ref: "#/components/schemas/ServerErrorSchema" + $ref: '#/components/schemas/ServerErrorSchema' /newsletter-subscription/{Id}: delete: @@ -1020,30 +1024,30 @@ paths: format: uuid description: ID of the newsletter to delete responses: - "200": + '200': description: newsletter deleted successfully content: application/json: schema: - $ref: "#/components/schemas/SuccessResponseSchema" - "404": + $ref: '#/components/schemas/SuccessResponseSchema' + '404': description: Email not found content: application/json: schema: - $ref: "#/components/schemas/NotFoundErrorSchema" - "401": + $ref: '#/components/schemas/NotFoundErrorSchema' + '401': description: Authentication error content: application/json: schema: - $ref: "#/components/schemas/UnauthorizedErrorSchema" - "500": + $ref: '#/components/schemas/UnauthorizedErrorSchema' + '500': description: Server error content: application/json: schema: - $ref: "#/components/schemas/ServerErrorSchema" + $ref: '#/components/schemas/ServerErrorSchema' /newsletter-subscription/restore/{Id}: patch: @@ -1062,32 +1066,33 @@ paths: format: uuid description: ID of the newsletter to restore responses: - "200": + '200': description: Newsletter email restored successfully content: application/json: schema: - $ref: "#/components/schemas/SuccessResponseSchema" - "404": + $ref: '#/components/schemas/SuccessResponseSchema' + '404': description: Email not found content: application/json: schema: - $ref: "#/components/schemas/NotFoundErrorSchema" - "401": + $ref: '#/components/schemas/NotFoundErrorSchema' + '401': description: Authentication error content: application/json: schema: - $ref: "#/components/schemas/UnauthorizedErrorSchema" - "500": + $ref: '#/components/schemas/UnauthorizedErrorSchema' + '500': description: Server error content: application/json: schema: - $ref: "#/components/schemas/ServerErrorSchema" + $ref: '#/components/schemas/ServerErrorSchema' + - /newsletter-subscription/deleted: + /newsletter-subscription/deleted: get: tags: - newsletter @@ -1096,29 +1101,29 @@ paths: - bearerAuth: [] description: Retrieves a paginated list of deleted newsletters emails. parameters: - - $ref: "#/components/parameters/PageLimitParam" - - $ref: "#/components/parameters/LimitParam" + - $ref: '#/components/parameters/PageLimitParam' + - $ref: '#/components/parameters/LimitParam' responses: - "200": + '200': description: A list of all deleted newsletters emails content: application/json: schema: type: array items: - $ref: "#/components/schemas/NewsletterSchema" - "400": + $ref: '#/components/schemas/NewsletterSchema' + '400': description: Bad Request content: application/json: schema: - $ref: "#/components/schemas/BadRequestErrorSchema" - "500": + $ref: '#/components/schemas/BadRequestErrorSchema' + '500': description: Server error content: application/json: schema: - $ref: "#/components/schemas/ServerErrorSchema" + $ref: '#/components/schemas/ServerErrorSchema' /faq: get: @@ -1127,31 +1132,31 @@ paths: summary: Get all FAQs description: Retrieves a paginated list of FAQs. parameters: - - $ref: "#/components/parameters/PageLimitParam" - - $ref: "#/components/parameters/LimitParam" + - $ref: '#/components/parameters/PageLimitParam' + - $ref: '#/components/parameters/LimitParam' responses: - "200": + '200': description: A list of FAQs content: application/json: schema: type: array items: - $ref: "#/components/schemas/FAQSchema" - - "400": + $ref: '#/components/schemas/FAQSchema' + + '400': description: Bad Request content: application/json: schema: - $ref: "#/components/schemas/BadRequestErrorSchema" + $ref: '#/components/schemas/BadRequestErrorSchema' - "500": + '500': description: Server error content: application/json: schema: - $ref: "#/components/schemas/ServerErrorSchema" + $ref: '#/components/schemas/ServerErrorSchema' post: tags: @@ -1165,32 +1170,32 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/FAQSchema" + $ref: '#/components/schemas/FAQSchema' responses: - "201": + '201': description: FAQ created successfully content: application/json: schema: - $ref: "#/components/schemas/SuccessResponseSchema2" - "400": + $ref: '#/components/schemas/SuccessResponseSchema2' + '400': description: Bad Request content: application/json: schema: - $ref: "#/components/schemas/BadRequestErrorSchema" - "401": + $ref: '#/components/schemas/BadRequestErrorSchema' + '401': description: Authentication error content: application/json: schema: - $ref: "#/components/schemas/UnauthorizedErrorSchema" - "500": + $ref: '#/components/schemas/UnauthorizedErrorSchema' + '500': description: Server error content: application/json: schema: - $ref: "#/components/schemas/ServerErrorSchema" + $ref: '#/components/schemas/ServerErrorSchema' /faq/{faqId}: put: @@ -1213,38 +1218,38 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/FAQSchema" + $ref: '#/components/schemas/FAQSchema' responses: - "200": + '200': description: FAQ updated successfully content: application/json: schema: - $ref: "#/components/schemas/SuccessResponseSchema" - "400": + $ref: '#/components/schemas/SuccessResponseSchema' + '400': description: Bad Request content: application/json: schema: - $ref: "#/components/schemas/BadRequestErrorSchema" - "401": + $ref: '#/components/schemas/BadRequestErrorSchema' + '401': description: Authentication error content: application/json: schema: - $ref: "#/components/schemas/UnauthorizedErrorSchema" - "404": + $ref: '#/components/schemas/UnauthorizedErrorSchema' + '404': description: FAQ not found content: application/json: schema: - $ref: "#/components/schemas/NotFoundErrorSchema" - "500": + $ref: '#/components/schemas/NotFoundErrorSchema' + '500': description: Server error content: application/json: schema: - $ref: "#/components/schemas/ServerErrorSchema" + $ref: '#/components/schemas/ServerErrorSchema' delete: tags: @@ -1262,30 +1267,30 @@ paths: format: uuid description: ID of the FAQ to delete responses: - "200": + '200': description: FAQ deleted successfully content: application/json: schema: - $ref: "#/components/schemas/SuccessResponseSchema" - "404": + $ref: '#/components/schemas/SuccessResponseSchema' + '404': description: FAQ not found content: application/json: schema: - $ref: "#/components/schemas/NotFoundErrorSchema" - "401": + $ref: '#/components/schemas/NotFoundErrorSchema' + '401': description: Authentication error content: application/json: schema: - $ref: "#/components/schemas/UnauthorizedErrorSchema" - "500": + $ref: '#/components/schemas/UnauthorizedErrorSchema' + '500': description: Server error content: application/json: schema: - $ref: "#/components/schemas/ServerErrorSchema" + $ref: '#/components/schemas/ServerErrorSchema' /languages: get: @@ -1296,26 +1301,26 @@ paths: - bearerAuth: [] description: Retrieves a list of all available languages. responses: - "200": + '200': description: A list of languages content: application/json: schema: type: array items: - $ref: "#/components/schemas/LanguageSchema" - "400": + $ref: '#/components/schemas/LanguageSchema' + '400': description: Bad request error content: application/json: schema: - $ref: "#/components/schemas/BadRequestErrorSchema" - "401": + $ref: '#/components/schemas/BadRequestErrorSchema' + '401': description: Authentication error content: application/json: schema: - $ref: "#/components/schemas/UnauthorizedErrorSchema" + $ref: '#/components/schemas/UnauthorizedErrorSchema' post: tags: @@ -1329,32 +1334,32 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/LanguageSchema" + $ref: '#/components/schemas/LanguageSchema' responses: - "201": + '201': description: Language created successfully content: application/json: schema: - $ref: "#/components/schemas/SuccessResponseSchema2" - "400": + $ref: '#/components/schemas/SuccessResponseSchema2' + '400': description: Bad Request content: application/json: schema: - $ref: "#/components/schemas/BadRequestErrorSchema" - "401": + $ref: '#/components/schemas/BadRequestErrorSchema' + '401': description: Authentication error content: application/json: schema: - $ref: "#/components/schemas/UnauthorizedErrorSchema" - "500": + $ref: '#/components/schemas/UnauthorizedErrorSchema' + '500': description: Server error content: application/json: schema: - $ref: "#/components/schemas/ServerErrorSchema" + $ref: '#/components/schemas/ServerErrorSchema' /timezones: get: @@ -1365,26 +1370,26 @@ paths: - bearerAuth: [] description: Retrieves a list of all available timezones. responses: - "200": + '200': description: A list of timezones content: application/json: schema: type: array items: - $ref: "#/components/schemas/TimezoneSchema" - "400": + $ref: '#/components/schemas/TimezoneSchema' + '400': description: Bad request error content: application/json: schema: - $ref: "#/components/schemas/BadRequestErrorSchema" - "401": + $ref: '#/components/schemas/BadRequestErrorSchema' + '401': description: Authentication error content: application/json: schema: - $ref: "#/components/schemas/UnauthorizedErrorSchema" + $ref: '#/components/schemas/UnauthorizedErrorSchema' post: tags: @@ -1398,32 +1403,32 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/TimezoneSchema" + $ref: '#/components/schemas/TimezoneSchema' responses: - "201": + '201': description: Timezone created successfully content: application/json: schema: - $ref: "#/components/schemas/SuccessResponseSchema2" - "400": + $ref: '#/components/schemas/SuccessResponseSchema2' + '400': description: Bad Request content: application/json: schema: - $ref: "#/components/schemas/BadRequestErrorSchema" - "401": + $ref: '#/components/schemas/BadRequestErrorSchema' + '401': description: Authentication error content: application/json: schema: - $ref: "#/components/schemas/UnauthorizedErrorSchema" - "500": + $ref: '#/components/schemas/UnauthorizedErrorSchema' + '500': description: Server error content: application/json: schema: - $ref: "#/components/schemas/ServerErrorSchema" + $ref: '#/components/schemas/ServerErrorSchema' /timezones/{id}: patch: @@ -1434,7 +1439,7 @@ paths: - bearerAuth: [] description: Update an existing timezone entry. parameters: - - name: timezone_id + - name: id in: path required: true schema: @@ -1446,38 +1451,38 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/TimezoneSchema" + $ref: '#/components/schemas/TimezoneSchema' responses: - "200": + '200': description: Timezone updated successfully content: application/json: schema: - $ref: "#/components/schemas/SuccessResponseSchema" - "400": + $ref: '#/components/schemas/SuccessResponseSchema' + '400': description: Bad Request content: application/json: schema: - $ref: "#/components/schemas/BadRequestErrorSchema" - "422": + $ref: '#/components/schemas/BadRequestErrorSchema' + '422': description: Validation error content: application/json: schema: - $ref: "#/components/schemas/UnprocessedEntityErrorSchema" - "401": + $ref: '#/components/schemas/UnprocessedEntityErrorSchema' + '401': description: Authentication error content: application/json: schema: - $ref: "#/components/schemas/UnauthorizedErrorSchema" - "500": + $ref: '#/components/schemas/UnauthorizedErrorSchema' + '500': description: Server error content: application/json: schema: - $ref: "#/components/schemas/ServerErrorSchema" + $ref: '#/components/schemas/ServerErrorSchema' /regions: get: @@ -1488,26 +1493,26 @@ paths: - bearerAuth: [] description: Retrieves a list of all available regions. responses: - "200": + '200': description: A list of regions content: application/json: schema: type: array items: - $ref: "#/components/schemas/RegionSchema" - "400": + $ref: '#/components/schemas/RegionSchema' + '400': description: Bad request error content: application/json: schema: - $ref: "#/components/schemas/BadRequestErrorSchema" - "401": + $ref: '#/components/schemas/BadRequestErrorSchema' + '401': description: Authentication error content: application/json: schema: - $ref: "#/components/schemas/UnauthorizedErrorSchema" + $ref: '#/components/schemas/UnauthorizedErrorSchema' post: tags: @@ -1521,38 +1526,38 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/RegionSchema" + $ref: '#/components/schemas/RegionSchema' responses: - "201": + '201': description: Region created successfully content: application/json: schema: - $ref: "#/components/schemas/SuccessResponseSchema2" - "400": + $ref: '#/components/schemas/SuccessResponseSchema2' + '400': description: Bad Request content: application/json: schema: - $ref: "#/components/schemas/BadRequestErrorSchema" - "401": + $ref: '#/components/schemas/BadRequestErrorSchema' + '401': description: Authentication error content: application/json: schema: - $ref: "#/components/schemas/UnauthorizedErrorSchema" - "422": + $ref: '#/components/schemas/UnauthorizedErrorSchema' + '422': description: Validation error content: application/json: schema: - $ref: "#/components/schemas/UnprocessedEntityErrorSchema" - "500": + $ref: '#/components/schemas/UnprocessedEntityErrorSchema' + '500': description: Server error content: application/json: schema: - $ref: "#/components/schemas/ServerErrorSchema" + $ref: '#/components/schemas/ServerErrorSchema' /waitlist: get: @@ -1563,29 +1568,29 @@ paths: - bearerAuth: [] description: Retrieves a paginated list of waitlist entries. parameters: - - $ref: "#/components/parameters/PageLimitParam" - - $ref: "#/components/parameters/LimitParam" + - $ref: '#/components/parameters/PageLimitParam' + - $ref: '#/components/parameters/LimitParam' responses: - "200": + '200': description: A list of waitlist entries content: application/json: schema: type: array items: - $ref: "#/components/schemas/WaitlistSchema" - "400": + $ref: '#/components/schemas/WaitlistSchema' + '400': description: Bad request error content: application/json: schema: - $ref: "#/components/schemas/BadRequestErrorSchema" - "401": + $ref: '#/components/schemas/BadRequestErrorSchema' + '401': description: Authentication error content: application/json: schema: - $ref: "#/components/schemas/UnauthorizedErrorSchema" + $ref: '#/components/schemas/UnauthorizedErrorSchema' post: tags: @@ -1597,32 +1602,32 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/WaitlistSchema" + $ref: '#/components/schemas/WaitlistSchema' responses: - "201": + '201': description: Waitlist entry created successfully content: application/json: schema: - $ref: "#/components/schemas/SuccessResponseSchema2" - "400": + $ref: '#/components/schemas/SuccessResponseSchema2' + '400': description: Bad Request content: application/json: schema: - $ref: "#/components/schemas/BadRequestErrorSchema" - "422": + $ref: '#/components/schemas/BadRequestErrorSchema' + '422': description: Validation error content: application/json: schema: - $ref: "#/components/schemas/UnprocessedEntityErrorSchema" - "500": + $ref: '#/components/schemas/UnprocessedEntityErrorSchema' + '500': description: Server error content: application/json: schema: - $ref: "#/components/schemas/ServerErrorSchema" + $ref: '#/components/schemas/ServerErrorSchema' /users/{user_id}/regions: get: @@ -1641,36 +1646,36 @@ paths: format: uuid description: ID of the user to retrieve the region information for. responses: - "200": + '200': description: User region retrieved successfully content: application/json: schema: - $ref: "#/components/schemas/UserRegionResponseSchema" - "400": + $ref: '#/components/schemas/UserRegionResponseSchema' + '400': description: Bad request content: application/json: schema: - $ref: "#/components/schemas/BadRequestErrorSchema" - "401": + $ref: '#/components/schemas/BadRequestErrorSchema' + '401': description: Unauthorized content: application/json: schema: - $ref: "#/components/schemas/UnauthorizedErrorSchema" - "404": + $ref: '#/components/schemas/UnauthorizedErrorSchema' + '404': description: Not found content: application/json: schema: - $ref: "#/components/schemas/NotFoundErrorSchema" - "500": + $ref: '#/components/schemas/NotFoundErrorSchema' + '500': description: Server error content: application/json: schema: - $ref: "#/components/schemas/ServerErrorSchema" + $ref: '#/components/schemas/ServerErrorSchema' put: tags: @@ -1691,38 +1696,38 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/UserRegionUpdateSchema" + $ref: '#/components/schemas/UserRegionUpdateSchema' responses: - "200": + '200': description: User region updated successfully content: application/json: schema: - $ref: "#/components/schemas/UserRegionUpdateResponseSchema" - "400": + $ref: '#/components/schemas/UserRegionUpdateResponseSchema' + '400': description: Bad request content: application/json: schema: - $ref: "#/components/schemas/BadRequestErrorSchema" - "401": + $ref: '#/components/schemas/BadRequestErrorSchema' + '401': description: Unauthorized content: application/json: schema: - $ref: "#/components/schemas/UnauthorizedErrorSchema" - "422": + $ref: '#/components/schemas/UnauthorizedErrorSchema' + '422': description: Validation error content: application/json: schema: - $ref: "#/components/schemas/UnprocessedEntityErrorSchema" - "500": + $ref: '#/components/schemas/UnprocessedEntityErrorSchema' + '500': description: Server error content: application/json: schema: - $ref: "#/components/schemas/ServerErrorSchema" + $ref: '#/components/schemas/ServerErrorSchema' /organizations/{org_id}/roles: post: @@ -1745,38 +1750,38 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/OrgRoleCreateSchema" + $ref: '#/components/schemas/OrgRoleCreateSchema' responses: - "201": + '201': description: Role created successfully content: application/json: schema: - $ref: "#/components/schemas/OrgRoleCreateResponseSchema" - "400": + $ref: '#/components/schemas/OrgRoleCreateResponseSchema' + '400': description: Bad request content: application/json: schema: - $ref: "#/components/schemas/BadRequestErrorSchema" - "401": + $ref: '#/components/schemas/BadRequestErrorSchema' + '401': description: Unauthorized content: application/json: schema: - $ref: "#/components/schemas/UnauthorizedErrorSchema" - "403": + $ref: '#/components/schemas/UnauthorizedErrorSchema' + '403': description: Forbidden content: application/json: schema: - $ref: "#/components/schemas/ForbiddenErrorSchema" - "422": + $ref: '#/components/schemas/ForbiddenErrorSchema' + '422': description: Validation error content: application/json: schema: - $ref: "#/components/schemas/UnprocessedEntityErrorSchema" + $ref: '#/components/schemas/UnprocessedEntityErrorSchema' get: tags: @@ -1794,36 +1799,36 @@ paths: format: uuid description: ID of the organization to retrieve roles for. responses: - "200": + '200': description: Roles retrieved successfully content: application/json: schema: - $ref: "#/components/schemas/OrgRolesListResponseSchema" - "400": + $ref: '#/components/schemas/OrgRolesListResponseSchema' + '400': description: Bad request content: application/json: schema: - $ref: "#/components/schemas/BadRequestErrorSchema" - "401": + $ref: '#/components/schemas/BadRequestErrorSchema' + '401': description: Unauthorized content: application/json: schema: - $ref: "#/components/schemas/UnauthorizedErrorSchema" - "404": + $ref: '#/components/schemas/UnauthorizedErrorSchema' + '404': description: Not found content: application/json: schema: - $ref: "#/components/schemas/NotFoundErrorSchema" - "500": + $ref: '#/components/schemas/NotFoundErrorSchema' + '500': description: Server error content: application/json: schema: - $ref: "#/components/schemas/ServerErrorSchema" + $ref: '#/components/schemas/ServerErrorSchema' /organizations/{org_id}/roles/{role_id}: get: @@ -1849,36 +1854,36 @@ paths: format: uuid description: ID of the role to retrieve. responses: - "200": + '200': description: Role retrieved successfully content: application/json: schema: - $ref: "#/components/schemas/OrgRoleResponseSchema" - "400": + $ref: '#/components/schemas/OrgRoleResponseSchema' + '400': description: Bad request content: application/json: schema: - $ref: "#/components/schemas/BadRequestErrorSchema" - "401": + $ref: '#/components/schemas/BadRequestErrorSchema' + '401': description: Unauthorized content: application/json: schema: - $ref: "#/components/schemas/UnauthorizedErrorSchema" - "404": + $ref: '#/components/schemas/UnauthorizedErrorSchema' + '404': description: Not found content: application/json: schema: - $ref: "#/components/schemas/NotFoundErrorSchema" - "500": + $ref: '#/components/schemas/NotFoundErrorSchema' + '500': description: Server error content: application/json: schema: - $ref: "#/components/schemas/ServerErrorSchema" + $ref: '#/components/schemas/ServerErrorSchema' patch: tags: @@ -1907,38 +1912,38 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/OrgRoleUpdateSchema" + $ref: '#/components/schemas/OrgRoleUpdateSchema' responses: - "200": + '200': description: Role updated successfully content: application/json: schema: - $ref: "#/components/schemas/OrgRoleUpdateResponseSchema" - "400": + $ref: '#/components/schemas/OrgRoleUpdateResponseSchema' + '400': description: Bad request content: application/json: schema: - $ref: "#/components/schemas/BadRequestErrorSchema" - "401": + $ref: '#/components/schemas/BadRequestErrorSchema' + '401': description: Unauthorized content: application/json: schema: - $ref: "#/components/schemas/UnauthorizedErrorSchema" - "403": + $ref: '#/components/schemas/UnauthorizedErrorSchema' + '403': description: Forbidden content: application/json: schema: - $ref: "#/components/schemas/ForbiddenErrorSchema" - "422": + $ref: '#/components/schemas/ForbiddenErrorSchema' + '422': description: Validation error content: application/json: schema: - $ref: "#/components/schemas/UnprocessedEntityErrorSchema" + $ref: '#/components/schemas/UnprocessedEntityErrorSchema' delete: tags: @@ -1963,42 +1968,42 @@ paths: format: uuid description: ID of the role to delete. responses: - "200": + '200': description: Role deleted successfully content: application/json: schema: - $ref: "#/components/schemas/OrgRoleDeleteResponseSchema" - "400": + $ref: '#/components/schemas/OrgRoleDeleteResponseSchema' + '400': description: Bad request content: application/json: schema: - $ref: "#/components/schemas/BadRequestErrorSchema" - "401": + $ref: '#/components/schemas/BadRequestErrorSchema' + '401': description: Unauthorized content: application/json: schema: - $ref: "#/components/schemas/UnauthorizedErrorSchema" - "403": + $ref: '#/components/schemas/UnauthorizedErrorSchema' + '403': description: Forbidden content: application/json: schema: - $ref: "#/components/schemas/ForbiddenErrorSchema" - "404": + $ref: '#/components/schemas/ForbiddenErrorSchema' + '404': description: Not found content: application/json: schema: - $ref: "#/components/schemas/NotFoundErrorSchema" - "500": + $ref: '#/components/schemas/NotFoundErrorSchema' + '500': description: Server error content: application/json: schema: - $ref: "#/components/schemas/ServerErrorSchema" + $ref: '#/components/schemas/ServerErrorSchema' /organizations/{org_id}/roles/{role_id}/permissions: patch: @@ -2028,38 +2033,38 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/RolePermissionsUpdateSchema" + $ref: '#/components/schemas/RolePermissionsUpdateSchema' responses: - "200": + '200': description: Permissions updated successfully content: application/json: schema: - $ref: "#/components/schemas/RolePermissionsUpdateResponseSchema" - "400": + $ref: '#/components/schemas/RolePermissionsUpdateResponseSchema' + '400': description: Bad request content: application/json: schema: - $ref: "#/components/schemas/BadRequestErrorSchema" - "401": + $ref: '#/components/schemas/BadRequestErrorSchema' + '401': description: Unauthorized content: application/json: schema: - $ref: "#/components/schemas/UnauthorizedErrorSchema" - "403": + $ref: '#/components/schemas/UnauthorizedErrorSchema' + '403': description: Forbidden content: application/json: schema: - $ref: "#/components/schemas/ForbiddenErrorSchema" - "422": + $ref: '#/components/schemas/ForbiddenErrorSchema' + '422': description: Validation error content: application/json: schema: - $ref: "#/components/schemas/UnprocessedEntityErrorSchema" + $ref: '#/components/schemas/UnprocessedEntityErrorSchema' /jobs: get: tags: @@ -2083,20 +2088,20 @@ paths: description: The number of items per page example: 6 responses: - "200": + '200': description: A list of job posts content: application/json: schema: type: array items: - $ref: "#/components/schemas/JobPost" - "500": + $ref: '#/components/schemas/JobPost' + '500': description: Failed to fetch job posts content: application/json: schema: - $ref: "#/components/schemas/ErrorResponse" + $ref: '#/components/schemas/ErrorResponse' post: tags: - Job Posts @@ -2110,26 +2115,26 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/CreateJobPost" + $ref: '#/components/schemas/CreateJobPost' responses: - "201": + '201': description: Job post created successfully content: application/json: schema: - $ref: "#/components/schemas/JobPostResponse" - "400": + $ref: '#/components/schemas/JobPostResponse' + '400': description: Invalid input content: application/json: schema: - $ref: "#/components/schemas/ErrorResponse" - "500": + $ref: '#/components/schemas/ErrorResponse' + '500': description: Failed to create job post content: application/json: schema: - $ref: "#/components/schemas/ErrorResponse" + $ref: '#/components/schemas/ErrorResponse' /jobs/{job_id}: get: @@ -2146,30 +2151,30 @@ paths: type: string description: The ID of the job post to fetch responses: - "200": + '200': description: Job post fetched successfully content: application/json: schema: - $ref: "#/components/schemas/JobPostResponse" - "400": + $ref: '#/components/schemas/JobPostResponse' + '400': description: Invalid ID format content: application/json: schema: - $ref: "#/components/schemas/ErrorResponse" - "404": + $ref: '#/components/schemas/ErrorResponse' + '404': description: Job post not found content: application/json: schema: - $ref: "#/components/schemas/ErrorResponse" - "500": + $ref: '#/components/schemas/ErrorResponse' + '500': description: Failed to fetch job post content: application/json: schema: - $ref: "#/components/schemas/ErrorResponse" + $ref: '#/components/schemas/ErrorResponse' patch: tags: - Job Posts @@ -2190,32 +2195,32 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/UpdateJobPost" + $ref: '#/components/schemas/UpdateJobPost' responses: - "200": + '200': description: Job post updated successfully content: application/json: schema: - $ref: "#/components/schemas/JobPostResponse" - "400": + $ref: '#/components/schemas/JobPostResponse' + '400': description: Invalid ID format content: application/json: schema: - $ref: "#/components/schemas/ErrorResponse" - "404": + $ref: '#/components/schemas/ErrorResponse' + '404': description: Job post not found content: application/json: schema: - $ref: "#/components/schemas/ErrorResponse" - "500": + $ref: '#/components/schemas/ErrorResponse' + '500': description: Failed to update job post content: application/json: schema: - $ref: "#/components/schemas/ErrorResponse" + $ref: '#/components/schemas/ErrorResponse' delete: tags: - Job Posts @@ -2232,28 +2237,29 @@ paths: type: string description: The ID of the job post to delete responses: - "204": + '204': description: No content - - "400": + + '400': description: Invalid ID format content: application/json: schema: - $ref: "#/components/schemas/ErrorResponse" - "404": + $ref: '#/components/schemas/ErrorResponse' + '404': description: Job post not found content: application/json: schema: - $ref: "#/components/schemas/ErrorResponse" - "500": + $ref: '#/components/schemas/ErrorResponse' + '500': description: Failed to delete job post content: application/json: schema: - $ref: "#/components/schemas/ErrorResponse" - + $ref: '#/components/schemas/ErrorResponse' + + /organizations: post: tags: @@ -2267,38 +2273,38 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/createOrganizationReq" + $ref: '#/components/schemas/createOrganizationReq' responses: - "201": + '201': description: organization created successfully content: application/json: schema: - $ref: "#/components/schemas/organizationSuccessResponse" - "400": + $ref: '#/components/schemas/organizationSuccessResponse' + '400': description: Bad request content: application/json: schema: - $ref: "#/components/schemas/BadRequestErrorSchema" - "401": + $ref: '#/components/schemas/BadRequestErrorSchema' + '401': description: Unauthorized content: application/json: schema: - $ref: "#/components/schemas/UnauthorizedErrorSchema" - "404": + $ref: '#/components/schemas/UnauthorizedErrorSchema' + '404': description: Not found content: application/json: schema: - $ref: "#/components/schemas/NotFoundErrorSchema" - "500": + $ref: '#/components/schemas/NotFoundErrorSchema' + '500': description: Server error content: application/json: schema: - $ref: "#/components/schemas/ServerErrorSchema" + $ref: '#/components/schemas/ServerErrorSchema' get: tags: @@ -2309,33 +2315,40 @@ paths: description: Retrieves a list of organisations where the specified user belongs. operationId: getUserorganisations responses: - "200": + '200': description: A list of organisations content: application/json: schema: type: array items: - $ref: "#/components/schemas/Organisation" - "401": + $ref: '#/components/schemas/Organisation' + '401': description: Authentication error content: application/json: schema: - $ref: "#/components/schemas/UnauthorizedErrorSchema" - "400": + $ref: '#/components/schemas/UnauthorizedErrorSchema' + '404': + description: User not found + content: + application/json: + schema: + $ref: '#/components/schemas/NotFoundErrorSchema' + '400': description: Bad request content: application/json: schema: - $ref: "#/components/schemas/BadRequestErrorSchema" - "500": + $ref: '#/components/schemas/BadRequestErrorSchema' + '500': description: Server error content: application/json: schema: - $ref: "#/components/schemas/ServerErrorSchema" - + $ref: '#/components/schemas/ServerErrorSchema' + + /organizations/{orgId}: patch: tags: @@ -2356,39 +2369,39 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/createOrganizationReq" + $ref: '#/components/schemas/createOrganizationReq' responses: - "200": + '200': description: organization updated successfully content: application/json: schema: - $ref: "#/components/schemas/organizationUSuccessResponse" - "400": + $ref: '#/components/schemas/organizationUSuccessResponse' + '400': description: Bad request content: application/json: schema: - $ref: "#/components/schemas/BadRequestErrorSchema" - "401": + $ref: '#/components/schemas/BadRequestErrorSchema' + '401': description: Unauthorized content: application/json: schema: - $ref: "#/components/schemas/UnauthorizedErrorSchema" - "404": + $ref: '#/components/schemas/UnauthorizedErrorSchema' + '404': description: Not found content: application/json: schema: - $ref: "#/components/schemas/NotFoundErrorSchema" - "500": + $ref: '#/components/schemas/NotFoundErrorSchema' + '500': description: Server error content: application/json: schema: - $ref: "#/components/schemas/ServerErrorSchema" - + $ref: '#/components/schemas/ServerErrorSchema' + get: tags: - organization @@ -2404,37 +2417,37 @@ paths: required: true description: retrieve an organization responses: - "200": + '200': description: organization retrieved successfully content: application/json: schema: - $ref: "#/components/schemas/organizationGSuccessResponse" - "400": + $ref: '#/components/schemas/organizationGSuccessResponse' + '400': description: Bad request content: application/json: schema: - $ref: "#/components/schemas/BadRequestErrorSchema" - "401": + $ref: '#/components/schemas/BadRequestErrorSchema' + '401': description: Unauthorized content: application/json: schema: - $ref: "#/components/schemas/UnauthorizedErrorSchema" - "404": + $ref: '#/components/schemas/UnauthorizedErrorSchema' + '404': description: Not found content: application/json: schema: - $ref: "#/components/schemas/NotFoundErrorSchema" - "500": + $ref: '#/components/schemas/NotFoundErrorSchema' + '500': description: Server error content: application/json: schema: - $ref: "#/components/schemas/ServerErrorSchema" - + $ref: '#/components/schemas/ServerErrorSchema' + delete: tags: - organization @@ -2448,46 +2461,48 @@ paths: type: string format: uuid required: true - + responses: - "204": + '204': description: Organisations deleted successfully - "400": + '400': description: Invalid orgId format content: application/json: schema: $ref: "#/components/schemas/BadRequestErrorSchema" - "401": + '401': description: Unauthorized content: application/json: schema: $ref: "#/components/schemas/UnauthorizedErrorSchema" - - "403": + + + '403': description: Forbidden content: application/json: schema: $ref: "#/components/schemas/ForbiddenErrorSchema" - - "404": + + '404': description: Not found content: application/json: schema: $ref: "#/components/schemas/NotFoundErrorSchema" - - "500": + + '500': description: Internal server error content: application/json: schema: $ref: "#/components/schemas/ServerErrorSchema" - + + /organizations/{orgId}/users: post: tags: @@ -2508,9 +2523,9 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/addUserToOrganizationReq" + $ref: '#/components/schemas/addUserToOrganizationReq' responses: - "200": + '200': description: User added successfully content: application/json: @@ -2527,32 +2542,33 @@ paths: message: type: string example: User added successfully + - "400": + '400': description: Bad request content: application/json: schema: - $ref: "#/components/schemas/BadRequestErrorSchema" - "401": + $ref: '#/components/schemas/BadRequestErrorSchema' + '401': description: Unauthorized content: application/json: schema: - $ref: "#/components/schemas/UnauthorizedErrorSchema" - "404": + $ref: '#/components/schemas/UnauthorizedErrorSchema' + '404': description: Not found content: application/json: schema: - $ref: "#/components/schemas/NotFoundErrorSchema" - "500": + $ref: '#/components/schemas/NotFoundErrorSchema' + '500': description: Server error content: application/json: schema: - $ref: "#/components/schemas/ServerErrorSchema" - + $ref: '#/components/schemas/ServerErrorSchema' + get: tags: - organization @@ -2568,13 +2584,13 @@ paths: required: true responses: - "200": + '200': description: Users retrieved successfully content: application/json: schema: - type: object - properties: + type: object + properties: status: type: string example: success @@ -2600,31 +2616,32 @@ paths: name: type: string example: Iretoms - "400": + '400': description: Bad request content: application/json: schema: - $ref: "#/components/schemas/BadRequestErrorSchema" - "401": + $ref: '#/components/schemas/BadRequestErrorSchema' + '401': description: Unauthorized content: application/json: schema: - $ref: "#/components/schemas/UnauthorizedErrorSchema" - "404": + $ref: '#/components/schemas/UnauthorizedErrorSchema' + '404': description: Not found content: application/json: schema: - $ref: "#/components/schemas/NotFoundErrorSchema" - "500": + $ref: '#/components/schemas/NotFoundErrorSchema' + '500': description: Server error content: application/json: schema: - $ref: "#/components/schemas/ServerErrorSchema" - + $ref: '#/components/schemas/ServerErrorSchema' + + /blogs: post: tags: @@ -2652,7 +2669,7 @@ paths: type: string example: http://image.com responses: - "201": + '201': description: Blog post created successfully content: application/json: @@ -2667,59 +2684,59 @@ paths: example: 201 message: type: string - example: Blog post created successfully. + example: Blog post created successfully. data: type: object properties: - id: - type: string - format: uuid - title: - type: string - example: New Blog Post - content: - type: string - example: This is the content of the new blog post - image_url: - type: string - example: http://image.com - category: - type: string - example: Tech - author: - type: string - author_id: - type: string - format: uuid - created_at: - type: string - format: date-time - - "400": + id: + type: string + format: uuid + title: + type: string + example: New Blog Post + content: + type: string + example: This is the content of the new blog post + image_url: + type: string + example: http://image.com + category: + type: string + example: Tech + author: + type: string + author_id: + type: string + format: uuid + created_at: + type: string + format: date-time + + '400': description: Bad request content: application/json: schema: - $ref: "#/components/schemas/BadRequestErrorSchema" - "401": + $ref: '#/components/schemas/BadRequestErrorSchema' + '401': description: Unauthorized content: application/json: schema: - $ref: "#/components/schemas/UnauthorizedErrorSchema" - "404": + $ref: '#/components/schemas/UnauthorizedErrorSchema' + '404': description: Not found content: application/json: schema: - $ref: "#/components/schemas/NotFoundErrorSchema" - "500": + $ref: '#/components/schemas/NotFoundErrorSchema' + '500': description: Server error content: application/json: schema: - $ref: "#/components/schemas/ServerErrorSchema" - + $ref: '#/components/schemas/ServerErrorSchema' + get: tags: - blogs @@ -2740,7 +2757,7 @@ paths: description: The number of items per page example: 6 responses: - "200": + '200': description: A list of all blogs content: application/json: @@ -2755,7 +2772,7 @@ paths: example: 200 message: type: string - example: Blogs retrieved successfully. + example: Blogs retrieved successfully. data: type: array items: @@ -2772,7 +2789,7 @@ paths: example: This is the content of the new blog post image_url: type: string - example: http://image.com + example: http://image.com category: type: string example: Tech @@ -2784,14 +2801,14 @@ paths: created_at: type: string format: date-time - - "400": + + '400': description: Failed to fetch all blogs content: application/json: schema: - $ref: "#/components/schemas/ErrorResponse" - + $ref: '#/components/schemas/ErrorResponse' + /blogs/{blogId}: get: tags: @@ -2804,7 +2821,7 @@ paths: schema: type: integer responses: - "200": + '200': description: Successful response content: application/json: @@ -2819,52 +2836,52 @@ paths: example: 200 message: type: string - example: Blog post retrieved successfully. + example: Blog post retrieved successfully. data: type: object properties: - id: - type: string - format: uuid - title: - type: string - example: New Blog Post - content: - type: string - example: This is the content of the new blog post - image_url: - type: string - example: http://image.com - category: - type: string - example: Tech - author: - type: string - author_id: - type: string - format: uuid - created_at: - type: string - format: date-time - "400": + id: + type: string + format: uuid + title: + type: string + example: New Blog Post + content: + type: string + example: This is the content of the new blog post + image_url: + type: string + example: http://image.com + category: + type: string + example: Tech + author: + type: string + author_id: + type: string + format: uuid + created_at: + type: string + format: date-time + '400': description: Bad request content: application/json: schema: - $ref: "#/components/schemas/BadRequestErrorSchema" - "404": + $ref: '#/components/schemas/BadRequestErrorSchema' + '404': description: Not found content: application/json: schema: - $ref: "#/components/schemas/NotFoundErrorSchema" - "500": + $ref: '#/components/schemas/NotFoundErrorSchema' + '500': description: Server error content: application/json: schema: - $ref: "#/components/schemas/ServerErrorSchema" - + $ref: '#/components/schemas/ServerErrorSchema' + delete: tags: - blogs @@ -2878,46 +2895,47 @@ paths: type: string format: uuid required: true - + responses: - "204": + '204': description: blog deleted successfully - "400": + '400': description: Invalid orgId format content: application/json: schema: $ref: "#/components/schemas/BadRequestErrorSchema" - "401": + '401': description: Unauthorized content: application/json: schema: $ref: "#/components/schemas/UnauthorizedErrorSchema" - - "403": + + + '403': description: Forbidden content: application/json: schema: $ref: "#/components/schemas/ForbiddenErrorSchema" - - "404": + + '404': description: Not found content: application/json: schema: $ref: "#/components/schemas/NotFoundErrorSchema" - - "500": + + '500': description: Internal server error content: application/json: schema: $ref: "#/components/schemas/ServerErrorSchema" - + /blogs/edit/{blogId}: patch: tags: @@ -2952,7 +2970,7 @@ paths: type: string example: http://solarsystem.com responses: - "200": + '200': description: Blog post updated successfully content: application/json: @@ -2967,188 +2985,189 @@ paths: example: 200 message: type: string - example: Blog post updated successfully. + example: Blog post updated successfully. data: type: object properties: - id: - type: string - format: uuid - title: - type: string - example: Updated Blog Post - content: - type: string - example: This is the content of the updated blog post - image_url: - type: string - example: http://solarsystem.com - category: - type: string - example: space - author: - type: string - author_id: - type: string - format: uuid - updated_at: - type: string - format: date-time - - "400": - description: Bad request - content: - application/json: - schema: - $ref: "#/components/schemas/BadRequestErrorSchema" - "401": - description: Unauthorized - content: - application/json: - schema: - $ref: "#/components/schemas/UnauthorizedErrorSchema" - "404": - description: Not found - content: - application/json: - schema: - $ref: "#/components/schemas/NotFoundErrorSchema" - "500": - description: Server error - content: - application/json: - schema: - $ref: "#/components/schemas/ServerErrorSchema" - - # product routes - /products: - post: - tags: - - products - summary: Create a new product - description: Creates a new product entry. - requestBody: - required: true - content: - application/json: - schema: - type: object - properties: - name: - type: string - description: The name of the product - example: "Product 1" - description: - type: string - description: A brief description of the product - example: "This is a product" - price: - type: number - description: The price of the product - example: 100.00 - stock: - type: integer - description: The stock quantity of the product - example: 100 - category: - type: string - description: The category of the product - example: "Category 1" - - responses: - "201": - description: Product created successfully + id: + type: string + format: uuid + title: + type: string + example: Updated Blog Post + content: + type: string + example: This is the content of the updated blog post + image_url: + type: string + example: http://solarsystem.com + category: + type: string + example: space + author: + type: string + author_id: + type: string + format: uuid + updated_at: + type: string + format: date-time + + '400': + description: Bad request content: application/json: schema: - $ref: "#/components/schemas/SuccessResponseSchema2" - "400": - description: Bad Request + $ref: '#/components/schemas/BadRequestErrorSchema' + '401': + description: Unauthorized content: application/json: schema: - $ref: "#/components/schemas/BadRequestErrorSchema" - "401": - description: Authentication error + $ref: '#/components/schemas/UnauthorizedErrorSchema' + '404': + description: Not found content: application/json: schema: - $ref: "#/components/schemas/UnauthorizedErrorSchema" - "500": + $ref: '#/components/schemas/NotFoundErrorSchema' + '500': description: Server error content: application/json: schema: - $ref: "#/components/schemas/ServerErrorSchema" - - put: - tags: - - products - summary: Update a product - description: Updates a product entry. - requestBody: - required: true - content: - application/json: - schema: - type: object - properties: - id: - type: string - description: The id of the product to be updated - example: "1" - name: - type: string - description: The name of the product - example: "Product 1" - description: - type: string - description: A brief description of the product - example: "This is a product" - price: - type: number - description: The price of the product - example: 100.00 - stock: - type: integer - description: The stock quantity of the product - example: 100 - category: - type: string - description: The category of the product - example: "Category 1" - responses: - "200": - description: Product updated successfully - content: - application/json: - schema: - $ref: "#/components/schemas/SuccessResponseSchema" - "400": - description: Bad Request - content: - application/json: - schema: - $ref: "#/components/schemas/BadRequestErrorSchema" - "401": - description: Authentication error + $ref: '#/components/schemas/ServerErrorSchema' + +# product routes + /products: + post: + tags: + - products + summary: Create a new product + description: Creates a new product entry. + requestBody: + required: true content: application/json: schema: - $ref: "#/components/schemas/UnauthorizedErrorSchema" - "500": - description: Server error + type: object + properties: + name: + type: string + description: The name of the product + example: "Product 1" + description: + type: string + description: A brief description of the product + example: "This is a product" + price: + type: number + description: The price of the product + example: 100.00 + stock: + type: integer + description: The stock quantity of the product + example: 100 + category: + type: string + description: The category of the product + example: "Category 1" + + responses: + + '201': + description: Product created successfully + content: + application/json: + schema: + $ref: '#/components/schemas/SuccessResponseSchema2' + '400': + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/BadRequestErrorSchema' + '401': + description: Authentication error + content: + application/json: + schema: + $ref: '#/components/schemas/UnauthorizedErrorSchema' + '500': + description: Server error + content: + application/json: + schema: + $ref: '#/components/schemas/ServerErrorSchema' + + put: + tags: + - products + summary: Update a product + description: Updates a product entry. + requestBody: + required: true content: application/json: schema: - $ref: "#/components/schemas/ServerErrorSchema" + type: object + properties: + id: + type: string + description: The id of the product to be updated + example: "1" + name: + type: string + description: The name of the product + example: "Product 1" + description: + type: string + description: A brief description of the product + example: "This is a product" + price: + type: number + description: The price of the product + example: 100.00 + stock: + type: integer + description: The stock quantity of the product + example: 100 + category: + type: string + description: The category of the product + example: "Category 1" + responses: + '200': + description: Product updated successfully + content: + application/json: + schema: + $ref: '#/components/schemas/SuccessResponseSchema' + '400': + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/BadRequestErrorSchema' + '401': + description: Authentication error + content: + application/json: + schema: + $ref: '#/components/schemas/UnauthorizedErrorSchema' + '500': + description: Server error + content: + application/json: + schema: + $ref: '#/components/schemas/ServerErrorSchema' get: tags: - products summary: Get all products description: Get all products. responses: - "200": + '200': description: Products retrieved successfully content: application/json: @@ -3184,30 +3203,30 @@ paths: type: string description: The category of the product example: "Category 1" - "400": + '400': description: Bad Request content: application/json: schema: - $ref: "#/components/schemas/BadRequestErrorSchema" - "401": + $ref: '#/components/schemas/BadRequestErrorSchema' + '401': description: Authentication error content: application/json: schema: - $ref: "#/components/schemas/UnauthorizedErrorSchema" - "404": + $ref: '#/components/schemas/UnauthorizedErrorSchema' + '404': description: Not Found content: application/json: schema: - $ref: "#/components/schemas/NotFoundErrorSchema" - "500": + $ref: '#/components/schemas/NotFoundErrorSchema' + '500': description: Server error content: application/json: schema: - $ref: "#/components/schemas/ServerErrorSchema" + $ref: '#/components/schemas/ServerErrorSchema' /products/{product_id}: get: @@ -3224,7 +3243,7 @@ paths: description: The id of the product to be retrieved example: "1" responses: - "200": + '200': description: Product retrieved successfully content: application/json: @@ -3255,30 +3274,30 @@ paths: type: string description: The category of the product example: "Category 1" - "400": + '400': description: Bad Request content: application/json: schema: - $ref: "#/components/schemas/BadRequestErrorSchema" - "401": + $ref: '#/components/schemas/BadRequestErrorSchema' + '401': description: Authentication error content: application/json: schema: - $ref: "#/components/schemas/UnauthorizedErrorSchema" - "404": + $ref: '#/components/schemas/UnauthorizedErrorSchema' + '404': description: Not Found content: application/json: schema: - $ref: "#/components/schemas/NotFoundErrorSchema" - "500": + $ref: '#/components/schemas/NotFoundErrorSchema' + '500': description: Server error content: application/json: schema: - $ref: "#/components/schemas/ServerErrorSchema" + $ref: '#/components/schemas/ServerErrorSchema' delete: tags: - products @@ -3292,33 +3311,33 @@ paths: type: string description: The id of the product to be retrieved example: "1" - + responses: - "200": + '200': description: Product deleted successfully content: application/json: schema: - $ref: "#/components/schemas/SuccessResponseSchema" - "400": + $ref: '#/components/schemas/SuccessResponseSchema' + '400': description: Bad Request content: application/json: schema: - $ref: "#/components/schemas/BadRequestErrorSchema" - "401": + $ref: '#/components/schemas/BadRequestErrorSchema' + '401': description: Authentication error content: application/json: schema: - $ref: "#/components/schemas/UnauthorizedErrorSchema" - "500": + $ref: '#/components/schemas/UnauthorizedErrorSchema' + '500': description: Server error content: application/json: schema: - $ref: "#/components/schemas/ServerErrorSchema" - + $ref: '#/components/schemas/ServerErrorSchema' + /products/categories/{category}: get: tags: @@ -3334,7 +3353,7 @@ paths: description: The category of the products to be retrieved example: "Category 1" responses: - "200": + '200': description: Products retrieved successfully content: application/json: @@ -3370,114 +3389,115 @@ paths: type: string description: The category of the product example: "Category 1" - "400": + '400': description: Bad Request content: application/json: schema: - $ref: "#/components/schemas/BadRequestErrorSchema" - "401": + $ref: '#/components/schemas/BadRequestErrorSchema' + '401': description: Authentication error content: application/json: schema: - $ref: "#/components/schemas/UnauthorizedErrorSchema" - "404": + $ref: '#/components/schemas/UnauthorizedErrorSchema' + '404': description: Not Found content: application/json: schema: - $ref: "#/components/schemas/NotFoundErrorSchema" - "500": + $ref: '#/components/schemas/NotFoundErrorSchema' + '500': description: Server error content: application/json: schema: - $ref: "#/components/schemas/ServerErrorSchema" - + $ref: '#/components/schemas/ServerErrorSchema' + + /products/filter: get: - tags: - - products - summary: Filter products - description: Filter products by category and price. - parameters: - - name: category - in: query - required: false - schema: - type: string - description: The category of the products to be retrieved - example: "Category 1" - - name: price - in: query - required: false - schema: - type: number - description: The price of the products to be retrieved - example: 100.00 - responses: - "200": - description: Products retrieved successfully - content: - application/json: - schema: - type: object - properties: - products: - type: array - items: - type: object - properties: - id: - type: string - description: The id of the product - example: "1" - name: - type: string - description: The name of the product - example: "Product 1" - description: - type: string - description: A brief description of the product - example: "This is a product" - price: - type: number - description: The price of the product - example: 100.00 - stock: - type: integer - description: The stock quantity of the product - example: 100 - category: - type: string - description: The category of the product - example: "Category 1" - "400": - description: Bad Request - content: - application/json: - schema: - $ref: "#/components/schemas/BadRequestErrorSchema" - "401": - description: Authentication error - content: - application/json: - schema: - $ref: "#/components/schemas/UnauthorizedErrorSchema" - "404": - description: Not Found - content: - application/json: - schema: - $ref: "#/components/schemas/NotFoundErrorSchema" - "500": - description: Server error - content: - application/json: + tags: + - products + summary: Filter products + description: Filter products by category and price. + parameters: + - name: category + in: query + required: false schema: - $ref: "#/components/schemas/ServerErrorSchema" - + type: string + description: The category of the products to be retrieved + example: "Category 1" + - name: price + in: query + required: false + schema: + type: number + description: The price of the products to be retrieved + example: 100.00 + responses: + '200': + description: Products retrieved successfully + content: + application/json: + schema: + type: object + properties: + products: + type: array + items: + type: object + properties: + id: + type: string + description: The id of the product + example: "1" + name: + type: string + description: The name of the product + example: "Product 1" + description: + type: string + description: A brief description of the product + example: "This is a product" + price: + type: number + description: The price of the product + example: 100.00 + stock: + type: integer + description: The stock quantity of the product + example: 100 + category: + type: string + description: The category of the product + example: "Category 1" + '400': + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/BadRequestErrorSchema' + '401': + description: Authentication error + content: + application/json: + schema: + $ref: '#/components/schemas/UnauthorizedErrorSchema' + '404': + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/NotFoundErrorSchema' + '500': + description: Server error + content: + application/json: + schema: + $ref: '#/components/schemas/ServerErrorSchema' + /products/image/{product_id}: patch: tags: @@ -3504,37 +3524,37 @@ paths: description: The image of the product example: "image.jpg" responses: - "200": + '200': description: Product image updated successfully content: application/json: schema: - $ref: "#/components/schemas/SuccessResponseSchema" - "400": + $ref: '#/components/schemas/SuccessResponseSchema' + '400': description: Bad Request content: application/json: schema: - $ref: "#/components/schemas/BadRequestErrorSchema" - "401": + $ref: '#/components/schemas/BadRequestErrorSchema' + '401': description: Authentication error content: application/json: schema: - $ref: "#/components/schemas/UnauthorizedErrorSchema" - "404": + $ref: '#/components/schemas/UnauthorizedErrorSchema' + '404': description: Not Found content: application/json: schema: - $ref: "#/components/schemas/NotFoundErrorSchema" - "500": + $ref: '#/components/schemas/NotFoundErrorSchema' + '500': description: Server error content: application/json: schema: - $ref: "#/components/schemas/ServerErrorSchema" - + $ref: '#/components/schemas/ServerErrorSchema' + /invite/create: post: tags: @@ -3555,7 +3575,7 @@ paths: - organizationId - email responses: - "201": + '201': description: Invitation link created and sent successfully content: application/json: @@ -3574,20 +3594,20 @@ paths: status_code: type: integer example: 201 - - "400": + + '400': description: Invalid input content: application/json: schema: $ref: "#/components/schemas/BadRequestErrorSchema" - "401": + '401': description: Unauthorized content: application/json: schema: $ref: "#/components/schemas/UnauthorizedErrorSchema" - "500": + '500': description: Internal server error content: application/json: @@ -3616,7 +3636,7 @@ paths: - emails - org_id responses: - "201": + '201': description: Invitations sent successfully content: application/json: @@ -3647,19 +3667,19 @@ paths: organization: type: string example: testorg - "400": + '400': description: Invalid input content: application/json: schema: $ref: "#/components/schemas/BadRequestErrorSchema" - "403": + '403': description: Forbidden content: application/json: schema: $ref: "#/components/schemas/ForbiddenErrorSchema" - "500": + '500': description: Internal server error content: application/json: @@ -3678,7 +3698,7 @@ paths: schema: type: string responses: - "200": + '200': description: Invitation accepted successfully content: application/json: @@ -3691,7 +3711,7 @@ paths: status: type: integer example: 200 - "400": + '400': description: Invalid or expired invitation link content: application/json: @@ -3730,7 +3750,7 @@ paths: required: - invitationLink responses: - "200": + '200': description: Invitation accepted successfully content: application/json: @@ -3743,7 +3763,7 @@ paths: status: type: integer example: 200 - "400": + '400': description: Invalid or expired invitation link content: application/json: @@ -3773,7 +3793,7 @@ paths: security: - bearerAuth: [] responses: - "200": + '200': description: Request successful content: application/json: @@ -3811,7 +3831,7 @@ paths: invitation_token: type: string example: invitation-token - "400": + '400': description: Invalid request content: application/json: @@ -3845,7 +3865,7 @@ paths: - name - body responses: - "201": + '201': description: Template created successfully content: application/json: @@ -3881,7 +3901,7 @@ paths: type: string format: date-time example: 2024-08-01T05:35:48.3211659+01:00 - "422": + '422': description: Validation failed content: application/json: @@ -3903,7 +3923,7 @@ paths: TemplateRequest.Body: type: string example: Body is a required field - "401": + '401': description: Unauthorized content: application/json: @@ -3927,7 +3947,7 @@ paths: - email_template summary: Retrieve all email templates responses: - "200": + '200': description: Templates successfully retrieved content: application/json: @@ -3965,7 +3985,7 @@ paths: type: string format: date-time example: 2024-08-01T00:55:14.011782+01:00 - "401": + '401': description: Unauthorized content: application/json: @@ -3996,7 +4016,7 @@ paths: schema: type: string responses: - "200": + '200': description: Template successfully retrieved content: application/json: @@ -4032,7 +4052,7 @@ paths: type: string format: date-time example: 2024-08-01T02:00:14.011782+01:00 - "401": + '401': description: Unauthorized content: application/json: @@ -4048,7 +4068,7 @@ paths: message: type: string example: Unauthorized - "404": + '404': description: Template not found content: application/json: @@ -4092,7 +4112,7 @@ paths: - name - body responses: - "200": + '200': description: Template successfully updated content: application/json: @@ -4128,7 +4148,7 @@ paths: type: string format: date-time example: 2024-08-01T03:00:14.011782+01:00 - "422": + '422': description: Validation failed content: application/json: @@ -4150,7 +4170,7 @@ paths: TemplateRequest.Body: type: string example: Body is a required field - "401": + '401': description: Unauthorized content: application/json: @@ -4166,7 +4186,7 @@ paths: message: type: string example: Unauthorized - "404": + '404': description: Template not found content: application/json: @@ -4196,7 +4216,7 @@ paths: schema: type: string responses: - "200": + '200': description: Template successfully deleted content: application/json: @@ -4212,7 +4232,7 @@ paths: message: type: string example: Template successfully deleted - "401": + '401': description: Unauthorized content: application/json: @@ -4228,7 +4248,7 @@ paths: message: type: string example: Unauthorized - "404": + '404': description: Template not found content: application/json: @@ -4246,7 +4266,7 @@ paths: example: Template not found security: - bearerAuth: [] - + /api/v1/notifications: post: tags: @@ -4265,7 +4285,7 @@ paths: required: - message responses: - "201": + '201': description: Notification created successfully content: application/json: @@ -4305,7 +4325,7 @@ paths: type: string format: date-time example: 2024-08-01T05:35:48.3211659+01:00 - "400": + '400': description: Invalid request content: application/json: @@ -4321,7 +4341,7 @@ paths: message: type: string example: Invalid request - "401": + '401': description: Unauthorized request content: application/json: @@ -4345,7 +4365,7 @@ paths: - notifications summary: Retrieve all notifications responses: - "200": + '200': description: Notifications retrieved successfully content: application/json: @@ -4388,7 +4408,7 @@ paths: type: string format: date-time example: 2024-08-01T00:55:14.011782+01:00 - "400": + '400': description: Invalid request content: application/json: @@ -4404,7 +4424,7 @@ paths: message: type: string example: Invalid request - "401": + '401': description: Unauthorized request content: application/json: @@ -4436,7 +4456,7 @@ paths: type: boolean example: false responses: - "200": + '200': description: Unread notifications retrieved successfully content: application/json: @@ -4479,7 +4499,7 @@ paths: type: string format: date-time example: 2024-08-01T00:55:14.011782+01:00 - "400": + '400': description: Invalid request content: application/json: @@ -4495,7 +4515,7 @@ paths: message: type: string example: Invalid request - "401": + '401': description: Unauthorized request content: application/json: @@ -4539,7 +4559,7 @@ paths: required: - is_read responses: - "200": + '200': description: Notification updated successfully content: application/json: @@ -4568,7 +4588,7 @@ paths: type: string format: date-time example: 2024-08-01T06:00:14.011782+01:00 - "400": + '400': description: Invalid request content: application/json: @@ -4584,7 +4604,7 @@ paths: message: type: string example: Invalid request - "401": + '401': description: Unauthorized request content: application/json: @@ -4600,7 +4620,7 @@ paths: message: type: string example: Unauthorized request - "404": + '404': description: Notification not found content: application/json: @@ -4631,7 +4651,7 @@ paths: type: string example: 01910b38-22db-79a1-a766-26b69757e3d2 responses: - "200": + '200': description: Notification deleted successfully content: application/json: @@ -4653,7 +4673,7 @@ paths: notifications: type: array items: {} - "400": + '400': description: Invalid request content: application/json: @@ -4669,7 +4689,7 @@ paths: message: type: string example: Invalid request - "401": + '401': description: Unauthorized request content: application/json: @@ -4685,7 +4705,7 @@ paths: message: type: string example: Unauthorized request - "404": + '404': description: Notification not found content: application/json: @@ -4741,7 +4761,7 @@ paths: type: boolean example: true responses: - "200": + '200': description: Notification preferences updated successfully content: application/json: @@ -4784,7 +4804,7 @@ paths: slack_notifications_announcement_and_update_emails: type: boolean example: true - "400": + '400': description: Invalid input content: application/json: @@ -4800,7 +4820,7 @@ paths: message: type: string example: Invalid input - "401": + '401': description: Unauthorized content: application/json: @@ -4824,7 +4844,7 @@ paths: - notification_settings summary: Retrieve notification settings responses: - "200": + '200': description: Notification preferences retrieved successfully content: application/json: @@ -4867,7 +4887,7 @@ paths: slack_notifications_announcement_and_update_emails: type: boolean example: true - "400": + '400': description: Invalid request content: application/json: @@ -4883,7 +4903,7 @@ paths: message: type: string example: Invalid request - "401": + '401': description: Unauthorized content: application/json: @@ -4899,7 +4919,7 @@ paths: message: type: string example: Unauthorized - + /help-center/topics: post: summary: Create a new Help Center topic @@ -4913,15 +4933,15 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/CreateHelpCenter" + $ref: '#/components/schemas/CreateHelpCenter' responses: - "201": + '201': description: Topic created successfully content: application/json: schema: - $ref: "#/components/schemas/HelpCenter" - "400": + $ref: '#/components/schemas/HelpCenter' + '400': description: Invalid request body content: application/json: @@ -4934,9 +4954,9 @@ paths: message: type: string example: Invalid request body - "401": - $ref: "#/components/responses/UnauthorizedError" - "422": + '401': + $ref: '#/components/responses/UnauthorizedError' + '422': description: Validation failed content: application/json: @@ -4949,7 +4969,7 @@ paths: message: type: string example: Validation failed - "500": + '500': description: Failed to add topic content: application/json: @@ -4962,7 +4982,7 @@ paths: message: type: string example: Failed to add topic - + get: summary: Fetch all Help Center topics description: Retrieves all Help Center topics. @@ -4982,18 +5002,18 @@ paths: schema: type: integer description: The number of items per page - example: 6 + example: 6 responses: - "200": + '200': description: Topics retrieved successfully content: application/json: schema: type: array items: - $ref: "#/components/schemas/HelpCenter" - "401": - $ref: "#/components/responses/UnauthorizedError" + $ref: '#/components/schemas/HelpCenter' + '401': + $ref: '#/components/responses/UnauthorizedError' /help-center/topics/{id}: get: @@ -5010,16 +5030,16 @@ paths: required: true description: The ID of the Help Center topic to fetch responses: - "200": + '200': description: Topic retrieved successfully content: application/json: schema: - $ref: "#/components/schemas/HelpCenter" - "401": - $ref: "#/components/responses/UnauthorizedError" - "404": - $ref: "#/components/responses/NotFoundError" + $ref: '#/components/schemas/HelpCenter' + '401': + $ref: '#/components/responses/UnauthorizedError' + '404': + $ref: '#/components/responses/NotFoundError' patch: summary: Update a Help Center topic by ID @@ -5041,19 +5061,19 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/CreateHelpCenter" + $ref: '#/components/schemas/CreateHelpCenter' responses: - "200": + '200': description: Topic updated successfully content: application/json: schema: - $ref: "#/components/schemas/HelpCenter" - "401": - $ref: "#/components/responses/UnauthorizedError" - "404": - $ref: "#/components/responses/NotFoundError" - "500": + $ref: '#/components/schemas/HelpCenter' + '401': + $ref: '#/components/responses/UnauthorizedError' + '404': + $ref: '#/components/responses/NotFoundError' + '500': description: Failed to update topic content: application/json: @@ -5066,7 +5086,7 @@ paths: message: type: string example: Failed to update topic - + delete: summary: Delete a Help Center topic by ID description: Deletes a Help Center topic by its ID. @@ -5083,13 +5103,13 @@ paths: required: true description: The ID of the Help Center topic to delete responses: - "204": + '204': description: Topic deleted successfully - "401": - $ref: "#/components/responses/UnauthorizedError" - "404": - $ref: "#/components/responses/NotFoundError" - "500": + '401': + $ref: '#/components/responses/UnauthorizedError' + '404': + $ref: '#/components/responses/NotFoundError' + '500': description: Failed to delete topic content: application/json: @@ -5117,16 +5137,16 @@ paths: required: true description: The search Topic title responses: - "200": + '200': description: Search results retrieved successfully content: application/json: schema: type: array items: - $ref: "#/components/schemas/HelpCenter" - "401": - $ref: "#/components/responses/UnauthorizedError" + $ref: '#/components/schemas/HelpCenter' + '401': + $ref: '#/components/responses/UnauthorizedError' /contact: post: @@ -5160,30 +5180,30 @@ paths: - subject - message responses: - "201": + '201': description: Contact added successfully content: application/json: schema: - $ref: "#/components/schemas/SuccessResponseSchema2" - "400": + $ref: '#/components/schemas/SuccessResponseSchema2' + '400': description: Bad Request content: application/json: schema: - $ref: "#/components/schemas/BadRequestErrorSchema" - "422": + $ref: '#/components/schemas/BadRequestErrorSchema' + '422': description: Unprocessable Entity content: application/json: schema: - $ref: "#/components/schemas/UnprocessedEntityErrorSchema" - "500": + $ref: '#/components/schemas/UnprocessedEntityErrorSchema' + '500': description: Server error content: application/json: schema: - $ref: "#/components/schemas/ServerErrorSchema" + $ref: '#/components/schemas/ServerErrorSchema' get: tags: @@ -5193,35 +5213,35 @@ paths: - bearerAuth: [] description: Retrieves all contact entries. parameters: - - $ref: "#/components/parameters/PageLimitParam" - - $ref: "#/components/parameters/LimitParam" + - $ref: '#/components/parameters/PageLimitParam' + - $ref: '#/components/parameters/LimitParam' responses: - "200": + '200': description: A list of contacts content: application/json: schema: type: array items: - $ref: "#/components/schemas/ContactSchema" - "400": + $ref: '#/components/schemas/ContactSchema' + '400': description: Bad Request content: application/json: schema: - $ref: "#/components/schemas/BadRequestErrorSchema" - "422": + $ref: '#/components/schemas/BadRequestErrorSchema' + '422': description: Unprocessable Entity content: application/json: schema: - $ref: "#/components/schemas/UnprocessedEntityErrorSchema" - "500": + $ref: '#/components/schemas/UnprocessedEntityErrorSchema' + '500': description: Server error content: application/json: schema: - $ref: "#/components/schemas/ServerErrorSchema" + $ref: '#/components/schemas/ServerErrorSchema' /contact/{id}: delete: @@ -5240,43 +5260,43 @@ paths: format: uuid description: ID of the contact to delete responses: - "200": + '200': description: Contact deleted successfully content: application/json: schema: - $ref: "#/components/schemas/SuccessResponseSchema" - "404": + $ref: '#/components/schemas/SuccessResponseSchema' + '404': description: Contact not found content: application/json: schema: - $ref: "#/components/schemas/NotFoundErrorSchema" - "400": + $ref: '#/components/schemas/NotFoundErrorSchema' + '400': description: Bad Request content: application/json: schema: - $ref: "#/components/schemas/BadRequestErrorSchema" - "422": + $ref: '#/components/schemas/BadRequestErrorSchema' + '422': description: Unprocessable Entity content: application/json: schema: - $ref: "#/components/schemas/UnprocessedEntityErrorSchema" - "401": + $ref: '#/components/schemas/UnprocessedEntityErrorSchema' + '401': description: Authentication error content: application/json: schema: - $ref: "#/components/schemas/UnauthorizedErrorSchema" - "500": + $ref: '#/components/schemas/UnauthorizedErrorSchema' + '500': description: Server error content: application/json: schema: - $ref: "#/components/schemas/ServerErrorSchema" - + $ref: '#/components/schemas/ServerErrorSchema' + /contact/id/{id}: get: tags: @@ -5294,42 +5314,42 @@ paths: format: uuid description: ID of the contact to retrieve responses: - "200": + '200': description: Contact details content: application/json: schema: - $ref: "#/components/schemas/ContactSchema" - "404": + $ref: '#/components/schemas/ContactSchema' + '404': description: Contact not found content: application/json: schema: - $ref: "#/components/schemas/NotFoundErrorSchema" - "400": + $ref: '#/components/schemas/NotFoundErrorSchema' + '400': description: Bad Request content: application/json: schema: - $ref: "#/components/schemas/BadRequestErrorSchema" - "422": + $ref: '#/components/schemas/BadRequestErrorSchema' + '422': description: Unprocessable Entity content: application/json: schema: - $ref: "#/components/schemas/UnprocessedEntityErrorSchema" - "401": + $ref: '#/components/schemas/UnprocessedEntityErrorSchema' + '401': description: Authentication error content: application/json: schema: - $ref: "#/components/schemas/UnauthorizedErrorSchema" - "500": + $ref: '#/components/schemas/UnauthorizedErrorSchema' + '500': description: Server error content: application/json: schema: - $ref: "#/components/schemas/ServerErrorSchema" + $ref: '#/components/schemas/ServerErrorSchema' /contact/email/{email}: get: @@ -5348,45 +5368,47 @@ paths: format: email description: Email of the contact to retrieve responses: - "200": + '200': description: Contact details content: application/json: schema: - $ref: "#/components/schemas/ContactSchema" - "404": + $ref: '#/components/schemas/ContactSchema' + '404': description: Contact not found content: application/json: schema: - $ref: "#/components/schemas/NotFoundErrorSchema" - "400": + $ref: '#/components/schemas/NotFoundErrorSchema' + '400': description: Bad Request content: application/json: schema: - $ref: "#/components/schemas/BadRequestErrorSchema" - "422": + $ref: '#/components/schemas/BadRequestErrorSchema' + '422': description: Unprocessable Entity content: application/json: schema: - $ref: "#/components/schemas/UnprocessedEntityErrorSchema" - "401": + $ref: '#/components/schemas/UnprocessedEntityErrorSchema' + '401': description: Authentication error content: application/json: schema: - $ref: "#/components/schemas/UnauthorizedErrorSchema" - "500": + $ref: '#/components/schemas/UnauthorizedErrorSchema' + '500': description: Server error content: application/json: schema: - $ref: "#/components/schemas/ServerErrorSchema" + $ref: '#/components/schemas/ServerErrorSchema' components: + schemas: + NotFoundErrorSchema: type: object properties: @@ -5480,7 +5502,7 @@ components: type: string description: > A basic message describing the problem with the request. Usually missing if `error` is present. - + UnprocessedEntityErrorSchema: type: object properties: @@ -5501,7 +5523,7 @@ components: message: type: string example: "Validation failed" - + UnauthorizedErrorSchema: type: object properties: @@ -5568,7 +5590,7 @@ components: description: User's primary contact phone number. username: type: string - description: Last updated time + description: Last updated time created_at: type: string format: date-time @@ -5595,7 +5617,7 @@ components: name: type: string description: organisation name - description: + description: type: string description: organisation description @@ -5674,6 +5696,7 @@ components: type: string description: type: string + RegionSchema: type: object @@ -5713,7 +5736,7 @@ components: type: integer message: type: string - + UserRegionResponseSchema: type: object properties: @@ -5787,7 +5810,7 @@ components: roles: type: array items: - $ref: "#/components/schemas/OrgRoleResponseSchema" + $ref: '#/components/schemas/OrgRoleResponseSchema' OrgRoleResponseSchema: type: object @@ -5860,7 +5883,7 @@ components: type: integer message: type: string - + CreateJobPost: type: object properties: @@ -6003,7 +6026,7 @@ components: type: string example: Job post created successfully data: - $ref: "#/components/schemas/JobPost" + $ref: '#/components/schemas/JobPost' ErrorResponse: type: object @@ -6017,7 +6040,8 @@ components: message: type: string example: An error occurred - + + addUserToOrganizationReq: type: object properties: @@ -6025,7 +6049,8 @@ components: type: string format: uuid example: 01910544-d1e1-7ada-bdac-c761e527ec91 - + + createOrganizationReq: type: object properties: @@ -6045,7 +6070,7 @@ components: type: string country: type: string - + organizationResData: type: object description: Object describing the organisation data @@ -6057,22 +6082,22 @@ components: name: type: string example: daveOrg - description: + description: type: string example: something something email: type: string example: org@gmail.com - state: + state: type: string example: lagos industry: type: string example: tech - type: + type: type: string example: sometype - address: + address: type: string example: somewhere somewhere created_at: @@ -6083,7 +6108,7 @@ components: type: string format: date-time example: 2024-07-30T21:11:21.9538358+01:00 - + organizationSuccessResponse: type: object properties: @@ -6097,8 +6122,8 @@ components: type: string example: organization created successfully data: - $ref: "#/components/schemas/organizationResData" - + $ref: '#/components/schemas/organizationResData' + organizationUSuccessResponse: type: object properties: @@ -6112,8 +6137,8 @@ components: type: string example: organization updated successfully data: - $ref: "#/components/schemas/organizationResData" - + $ref: '#/components/schemas/organizationResData' + organizationGSuccessResponse: type: object properties: @@ -6127,8 +6152,8 @@ components: type: string example: organization retrieved successfully data: - $ref: "#/components/schemas/organizationResData" - + $ref: '#/components/schemas/organizationResData' + CreateHelpCenter: type: object properties: @@ -6164,7 +6189,7 @@ components: updated_at: type: string format: date-time - + ContactSchema: type: object properties: @@ -6214,7 +6239,7 @@ components: example: error message: type: string - example: Not Found + example: Not Found parameters: PageLimitParam: @@ -6235,7 +6260,7 @@ components: default: 10 securitySchemes: - bearerAuth: - type: http - scheme: bearer - bearerFormat: JWT + bearerAuth: + type: http + scheme: bearer + bearerFormat: JWT \ No newline at end of file From cdfed020fad38a965555e857721e6347ac310641 Mon Sep 17 00:00:00 2001 From: Osinachi Chukwujama Date: Thu, 8 Aug 2024 14:47:01 +0100 Subject: [PATCH 20/45] chore: setup docker build steps --- .github/workflows/development.yml | 49 +++++++++++++++++++++++++++++++ .github/workflows/production.yml | 49 +++++++++++++++++++++++++++++++ .github/workflows/staging.yml | 49 +++++++++++++++++++++++++++++++ 3 files changed, 147 insertions(+) diff --git a/.github/workflows/development.yml b/.github/workflows/development.yml index 8a5f7c3b..9739d6c3 100644 --- a/.github/workflows/development.yml +++ b/.github/workflows/development.yml @@ -9,6 +9,44 @@ on: - dev jobs: + build_docker_image: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + - name: Build Docker image + run: docker build -t golang_dev . + - name: Save and compress Docker image + run: docker save golang_dev | gzip > golang_dev.tar.gz + - name: Upload Docker image + uses: actions/upload-artifact@v4 + with: + name: golang_dev + path: golang_dev.tar.gz + + upload_docker_image: + runs-on: ubuntu-latest + needs: build + if: github.event.repository.fork == false + environment: + name: "development" + url: ${{ vars.URL }} + steps: + - name: Download Docker image + uses: actions/download-artifact@v4 + with: + name: golang_dev + path: . + + - name: Copy image to server + uses: appleboy/scp-action@master + with: + host: ${{ secrets.SSH_HOST }} + username: ${{ secrets.SSH_USERNAME }} + password: ${{ secrets.SSH_PASSWORD }} + source: golang_dev.tar.gz + target: "/tmp" + build_and_test: runs-on: ubuntu-latest environment: development @@ -114,3 +152,14 @@ jobs: MAIL_PASSWORD=${{ secrets.MAIL_PASSWORD }} \ MAIL_PORT=${{ secrets.MAIL_PORT }} \ MIGRATE=true + - name: Deploy on server + uses: appleboy/ssh-action@master + with: + host: ${{ secrets.HOST }} + username: ${{ secrets.USERNAME }} + password: ${{ secrets.PASSWORD }} + script: | + gunzip -c /tmp/golang_dev.tar.gz | docker load + rm -f /tmp/golang_dev.tar.gz + cd ~/deployments/development + docker compose up -d \ No newline at end of file diff --git a/.github/workflows/production.yml b/.github/workflows/production.yml index a1478627..4a444766 100644 --- a/.github/workflows/production.yml +++ b/.github/workflows/production.yml @@ -9,6 +9,44 @@ on: - main jobs: + build_docker_image: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + - name: Build Docker image + run: docker build -t golang_prod . + - name: Save and compress Docker image + run: docker save golang_prod | gzip > golang_prod.tar.gz + - name: Upload Docker image + uses: actions/upload-artifact@v4 + with: + name: golang_prod + path: golang_prod.tar.gz + + upload_docker_image: + runs-on: ubuntu-latest + needs: build + if: github.event.repository.fork == false + environment: + name: "development" + url: ${{ vars.URL }} + steps: + - name: Download Docker image + uses: actions/download-artifact@v4 + with: + name: golang_prod + path: . + + - name: Copy image to server + uses: appleboy/scp-action@master + with: + host: ${{ secrets.SSH_HOST }} + username: ${{ secrets.SSH_USERNAME }} + password: ${{ secrets.SSH_PASSWORD }} + source: golang_prod.tar.gz + target: "/tmp" + build_and_test: runs-on: ubuntu-latest environment: production @@ -109,3 +147,14 @@ jobs: MAIL_PASSWORD=${{ secrets.MAIL_PASSWORD }} \ MAIL_PORT=${{ secrets.MAIL_PORT }} \ MIGRATE=true + - name: Deploy on server + uses: appleboy/ssh-action@master + with: + host: ${{ secrets.HOST }} + username: ${{ secrets.USERNAME }} + password: ${{ secrets.PASSWORD }} + script: | + gunzip -c /tmp/golang_prod.tar.gz | docker load + rm -f /tmp/golang_prod.tar.gz + cd ~/deployments/production + docker compose up -d \ No newline at end of file diff --git a/.github/workflows/staging.yml b/.github/workflows/staging.yml index 19dbb94c..ef57092c 100644 --- a/.github/workflows/staging.yml +++ b/.github/workflows/staging.yml @@ -9,6 +9,44 @@ on: - staging jobs: + build_docker_image: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + - name: Build Docker image + run: docker build -t golang_staging . + - name: Save and compress Docker image + run: docker save golang_staging | gzip > golang_staging.tar.gz + - name: Upload Docker image + uses: actions/upload-artifact@v4 + with: + name: golang_staging + path: golang_staging.tar.gz + + upload_docker_image: + runs-on: ubuntu-latest + needs: build + if: github.event.repository.fork == false + environment: + name: "development" + url: ${{ vars.URL }} + steps: + - name: Download Docker image + uses: actions/download-artifact@v4 + with: + name: golang_staging + path: . + + - name: Copy image to server + uses: appleboy/scp-action@master + with: + host: ${{ secrets.SSH_HOST }} + username: ${{ secrets.SSH_USERNAME }} + password: ${{ secrets.SSH_PASSWORD }} + source: golang_staging.tar.gz + target: "/tmp" + build_and_test: runs-on: ubuntu-latest environment: staging @@ -109,3 +147,14 @@ jobs: MAIL_PASSWORD=${{ secrets.MAIL_PASSWORD }} \ MAIL_PORT=${{ secrets.MAIL_PORT }} \ MIGRATE=true + - name: Deploy on server + uses: appleboy/ssh-action@master + with: + host: ${{ secrets.HOST }} + username: ${{ secrets.USERNAME }} + password: ${{ secrets.PASSWORD }} + script: | + gunzip -c /tmp/golang_staging.tar.gz | docker load + rm -f /tmp/golang_staging.tar.gz + cd ~/deployments/staging + docker compose up -d \ No newline at end of file From 07d9b7b51ac1b4218b78c964608a3290dcd9c752 Mon Sep 17 00:00:00 2001 From: Osinachi Chukwujama Date: Thu, 8 Aug 2024 14:51:41 +0100 Subject: [PATCH 21/45] chore: update nginx config --- docker-compose-development.yml | 16 ++++++++-------- docker-compose-production.yml | 16 ++++++++-------- docker-compose-staging.yml | 16 ++++++++-------- nginx.conf | 27 +++++++++++++++++++++++++++ 4 files changed, 51 insertions(+), 24 deletions(-) create mode 100644 nginx.conf diff --git a/docker-compose-development.yml b/docker-compose-development.yml index ff5ef9c9..863928fc 100644 --- a/docker-compose-development.yml +++ b/docker-compose-development.yml @@ -26,14 +26,14 @@ services: env_file: - app.env - # nginx: - # image: nginx:latest - # ports: - # - "5000:80" - # depends_on: - # - backend - # volumes: - # - ./nginx.conf:/etc/nginx/nginx.conf + nginx: + image: nginx:latest + ports: + - "8000:80" + depends_on: + - backend + volumes: + - ./nginx.conf:/etc/nginx/nginx.conf volumes: db_data: \ No newline at end of file diff --git a/docker-compose-production.yml b/docker-compose-production.yml index 2221e748..0a33ccd4 100644 --- a/docker-compose-production.yml +++ b/docker-compose-production.yml @@ -26,14 +26,14 @@ services: env_file: - app.env - # nginx: - # image: nginx:latest - # ports: - # - "5000:80" - # depends_on: - # - backend - # volumes: - # - ./nginx.conf:/etc/nginx/nginx.conf + nginx: + image: nginx:latest + ports: + - "8002:80" + depends_on: + - backend + volumes: + - ./nginx.conf:/etc/nginx/nginx.conf volumes: db_data: \ No newline at end of file diff --git a/docker-compose-staging.yml b/docker-compose-staging.yml index 909e7ce3..40232000 100644 --- a/docker-compose-staging.yml +++ b/docker-compose-staging.yml @@ -26,14 +26,14 @@ services: env_file: - app.env - # nginx: - # image: nginx:latest - # ports: - # - "5000:80" - # depends_on: - # - backend - # volumes: - # - ./nginx.conf:/etc/nginx/nginx.conf + nginx: + image: nginx:latest + ports: + - "8001:80" + depends_on: + - backend + volumes: + - ./nginx.conf:/etc/nginx/nginx.conf volumes: db_data: \ No newline at end of file diff --git a/nginx.conf b/nginx.conf new file mode 100644 index 00000000..412f3723 --- /dev/null +++ b/nginx.conf @@ -0,0 +1,27 @@ +worker_processes 1; + +events { worker_connections 1024; } + +http { + upstream backend { + least_conn; + server backend:8000; + } + + server { + listen 80; + + server_name _; + + location / { + proxy_pass http://backend; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection keep-alive; + proxy_set_header Host $host; + proxy_cache_bypass $http_upgrade; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + } +} From e5072a4ff8f103f563a2c516523b34eaf3dfcb8c Mon Sep 17 00:00:00 2001 From: Osinachi Chukwujama Date: Thu, 8 Aug 2024 15:10:20 +0100 Subject: [PATCH 22/45] chore: add health-check and update compose files --- .github/workflows/development.yml | 7 ++++--- .github/workflows/production.yml | 7 ++++--- .github/workflows/staging.yml | 7 ++++--- docker-compose-development.yml | 5 +++++ docker-compose-production.yml | 5 +++++ docker-compose-staging.yml | 5 +++++ run_development_app.sh | 3 --- run_production_app.sh | 3 --- run_staging_app.sh | 3 --- 9 files changed, 27 insertions(+), 18 deletions(-) delete mode 100644 run_development_app.sh delete mode 100644 run_production_app.sh delete mode 100644 run_staging_app.sh diff --git a/.github/workflows/development.yml b/.github/workflows/development.yml index 9739d6c3..4d8ff104 100644 --- a/.github/workflows/development.yml +++ b/.github/workflows/development.yml @@ -26,7 +26,7 @@ jobs: upload_docker_image: runs-on: ubuntu-latest - needs: build + needs: build_docker_image if: github.event.repository.fork == false environment: name: "development" @@ -152,8 +152,9 @@ jobs: MAIL_PASSWORD=${{ secrets.MAIL_PASSWORD }} \ MAIL_PORT=${{ secrets.MAIL_PORT }} \ MIGRATE=true + - name: Deploy on server - uses: appleboy/ssh-action@master + uses: appleboy/ssh-action@v1.0.3 with: host: ${{ secrets.HOST }} username: ${{ secrets.USERNAME }} @@ -162,4 +163,4 @@ jobs: gunzip -c /tmp/golang_dev.tar.gz | docker load rm -f /tmp/golang_dev.tar.gz cd ~/deployments/development - docker compose up -d \ No newline at end of file + docker compose -f docker-compose-development.yml up -d \ No newline at end of file diff --git a/.github/workflows/production.yml b/.github/workflows/production.yml index 4a444766..2753a651 100644 --- a/.github/workflows/production.yml +++ b/.github/workflows/production.yml @@ -26,7 +26,7 @@ jobs: upload_docker_image: runs-on: ubuntu-latest - needs: build + needs: build_docker_image if: github.event.repository.fork == false environment: name: "development" @@ -147,8 +147,9 @@ jobs: MAIL_PASSWORD=${{ secrets.MAIL_PASSWORD }} \ MAIL_PORT=${{ secrets.MAIL_PORT }} \ MIGRATE=true + - name: Deploy on server - uses: appleboy/ssh-action@master + uses: appleboy/ssh-action@v1.0.3 with: host: ${{ secrets.HOST }} username: ${{ secrets.USERNAME }} @@ -157,4 +158,4 @@ jobs: gunzip -c /tmp/golang_prod.tar.gz | docker load rm -f /tmp/golang_prod.tar.gz cd ~/deployments/production - docker compose up -d \ No newline at end of file + docker compose -f docker-compose-production.yml up -d \ No newline at end of file diff --git a/.github/workflows/staging.yml b/.github/workflows/staging.yml index ef57092c..ae44279b 100644 --- a/.github/workflows/staging.yml +++ b/.github/workflows/staging.yml @@ -26,7 +26,7 @@ jobs: upload_docker_image: runs-on: ubuntu-latest - needs: build + needs: build_docker_image if: github.event.repository.fork == false environment: name: "development" @@ -147,8 +147,9 @@ jobs: MAIL_PASSWORD=${{ secrets.MAIL_PASSWORD }} \ MAIL_PORT=${{ secrets.MAIL_PORT }} \ MIGRATE=true + - name: Deploy on server - uses: appleboy/ssh-action@master + uses: appleboy/ssh-action@v1.0.3 with: host: ${{ secrets.HOST }} username: ${{ secrets.USERNAME }} @@ -157,4 +158,4 @@ jobs: gunzip -c /tmp/golang_staging.tar.gz | docker load rm -f /tmp/golang_staging.tar.gz cd ~/deployments/staging - docker compose up -d \ No newline at end of file + docker compose -f docker-compose-staging.yml up -d \ No newline at end of file diff --git a/docker-compose-development.yml b/docker-compose-development.yml index 863928fc..10d21094 100644 --- a/docker-compose-development.yml +++ b/docker-compose-development.yml @@ -10,6 +10,11 @@ services: POSTGRES_PORT: 5432 volumes: - db_data:/var/lib/postgresql/data + healthcheck: + test: ["CMD-SHELL", "pg_isready -U postgres"] + interval: 10s + timeout: 10s + retries: 2 redis: image: redis:latest diff --git a/docker-compose-production.yml b/docker-compose-production.yml index 0a33ccd4..688426de 100644 --- a/docker-compose-production.yml +++ b/docker-compose-production.yml @@ -10,6 +10,11 @@ services: POSTGRES_PORT: 5432 volumes: - db_data:/var/lib/postgresql/data + healthcheck: + test: ["CMD-SHELL", "pg_isready -U postgres"] + interval: 10s + timeout: 10s + retries: 2 redis: image: redis:latest diff --git a/docker-compose-staging.yml b/docker-compose-staging.yml index 40232000..00448788 100644 --- a/docker-compose-staging.yml +++ b/docker-compose-staging.yml @@ -10,6 +10,11 @@ services: POSTGRES_PORT: 5432 volumes: - db_data:/var/lib/postgresql/data + healthcheck: + test: ["CMD-SHELL", "pg_isready -U postgres"] + interval: 10s + timeout: 10s + retries: 2 redis: image: redis:latest diff --git a/run_development_app.sh b/run_development_app.sh deleted file mode 100644 index 7331f00b..00000000 --- a/run_development_app.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/bash - -~/deployments/development/development_app \ No newline at end of file diff --git a/run_production_app.sh b/run_production_app.sh deleted file mode 100644 index 2030af1f..00000000 --- a/run_production_app.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/bash - -~/deployments/production/production_app \ No newline at end of file diff --git a/run_staging_app.sh b/run_staging_app.sh deleted file mode 100644 index 04d6d417..00000000 --- a/run_staging_app.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/bash - -~/deployments/staging/staging_app \ No newline at end of file From d230dc8bcfd90ecc995c04ad70b122f26b47cf69 Mon Sep 17 00:00:00 2001 From: Osinachi Chukwujama Date: Thu, 8 Aug 2024 15:24:06 +0100 Subject: [PATCH 23/45] chore: update workflow file --- .github/workflows/development.yml | 16 +++++++++++----- .github/workflows/production.yml | 2 +- .github/workflows/staging.yml | 2 +- run_development_app.sh | 3 +++ run_production_app.sh | 3 +++ run_staging_app.sh | 3 +++ 6 files changed, 22 insertions(+), 7 deletions(-) create mode 100644 run_development_app.sh create mode 100644 run_production_app.sh create mode 100644 run_staging_app.sh diff --git a/.github/workflows/development.yml b/.github/workflows/development.yml index 4d8ff104..04ba4176 100644 --- a/.github/workflows/development.yml +++ b/.github/workflows/development.yml @@ -108,17 +108,23 @@ jobs: deploy: runs-on: ubuntu-latest needs: build_and_test - if: github.event_name == 'pull_request' + if: github.event_name == 'push' environment: development + env: + SSH_USERNAME: ${{ secrets.SSH_USERNAME }} + SSH_HOST: ${{ secrets.SSH_HOST }} + SSH_PORT: ${{ secrets.SSH_PORT }} + SSH_PASSWORD: ${{ secrets.SSH_PASSWORD }} + steps: - name: SSH into server and deploy uses: appleboy/ssh-action@v1.0.3 with: - host: ${{ secrets.SSH_HOST }} - username: ${{ secrets.SSH_USERNAME }} - password: ${{ secrets.SSH_PASSWORD }} - port: ${{ secrets.SSH_PORT }} + host: ${{ env.SSH_HOST }} + username: ${{ env.SSH_USERNAME }} + password: ${{ env.SSH_PASSWORD }} + port: ${{ env.SSH_PORT }} script: | export APPROOT=~/deployments/development mkdir -p $APPROOT diff --git a/.github/workflows/production.yml b/.github/workflows/production.yml index 2753a651..5967c08a 100644 --- a/.github/workflows/production.yml +++ b/.github/workflows/production.yml @@ -103,7 +103,7 @@ jobs: deploy: runs-on: ubuntu-latest needs: build_and_test - if: github.event_name == 'pull_request' + if: github.event_name == 'push' environment: production steps: diff --git a/.github/workflows/staging.yml b/.github/workflows/staging.yml index ae44279b..dc5333aa 100644 --- a/.github/workflows/staging.yml +++ b/.github/workflows/staging.yml @@ -103,7 +103,7 @@ jobs: deploy: runs-on: ubuntu-latest needs: build_and_test - if: github.event_name == 'pull_request' + if: github.event_name == 'push' environment: staging steps: diff --git a/run_development_app.sh b/run_development_app.sh new file mode 100644 index 00000000..7331f00b --- /dev/null +++ b/run_development_app.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +~/deployments/development/development_app \ No newline at end of file diff --git a/run_production_app.sh b/run_production_app.sh new file mode 100644 index 00000000..2030af1f --- /dev/null +++ b/run_production_app.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +~/deployments/production/production_app \ No newline at end of file diff --git a/run_staging_app.sh b/run_staging_app.sh new file mode 100644 index 00000000..04d6d417 --- /dev/null +++ b/run_staging_app.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +~/deployments/staging/staging_app \ No newline at end of file From ededf0bfc7c3cf96c8c8375f05736e2eb9222a82 Mon Sep 17 00:00:00 2001 From: Osinachi Chukwujama Date: Thu, 8 Aug 2024 15:28:29 +0100 Subject: [PATCH 24/45] chore: update workflow file --- .github/workflows/development.yml | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/.github/workflows/development.yml b/.github/workflows/development.yml index 04ba4176..e34e62af 100644 --- a/.github/workflows/development.yml +++ b/.github/workflows/development.yml @@ -159,12 +159,24 @@ jobs: MAIL_PORT=${{ secrets.MAIL_PORT }} \ MIGRATE=true + + run_docker_container: + runs-on: ubuntu-latest + needs: upload_docker_image + if: github.event.repository.fork == false + environment: development + env: + SSH_USERNAME: ${{ secrets.SSH_USERNAME }} + SSH_HOST: ${{ secrets.SSH_HOST }} + SSH_PASSWORD: ${{ secrets.SSH_PASSWORD }} + + steps: - name: Deploy on server uses: appleboy/ssh-action@v1.0.3 with: - host: ${{ secrets.HOST }} - username: ${{ secrets.USERNAME }} - password: ${{ secrets.PASSWORD }} + host: ${{ env.HOST }} + username: ${{ env.USERNAME }} + password: ${{ env.PASSWORD }} script: | gunzip -c /tmp/golang_dev.tar.gz | docker load rm -f /tmp/golang_dev.tar.gz From 8854c5066992729bc00847212337e2218b5b4f90 Mon Sep 17 00:00:00 2001 From: Osinachi Chukwujama Date: Thu, 8 Aug 2024 15:41:58 +0100 Subject: [PATCH 25/45] chore: update workflow to include checks --- .github/workflows/development.yml | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/.github/workflows/development.yml b/.github/workflows/development.yml index e34e62af..341243fc 100644 --- a/.github/workflows/development.yml +++ b/.github/workflows/development.yml @@ -11,6 +11,7 @@ on: jobs: build_docker_image: runs-on: ubuntu-latest + if: github.event_name == 'push' steps: - name: Checkout code uses: actions/checkout@v4 @@ -28,6 +29,7 @@ jobs: runs-on: ubuntu-latest needs: build_docker_image if: github.event.repository.fork == false + if: github.event_name == 'push' environment: name: "development" url: ${{ vars.URL }} @@ -39,7 +41,7 @@ jobs: path: . - name: Copy image to server - uses: appleboy/scp-action@master + uses: appleboy/scp-action@v0.1.7 with: host: ${{ secrets.SSH_HOST }} username: ${{ secrets.SSH_USERNAME }} @@ -164,6 +166,7 @@ jobs: runs-on: ubuntu-latest needs: upload_docker_image if: github.event.repository.fork == false + if: github.event_name == 'push' environment: development env: SSH_USERNAME: ${{ secrets.SSH_USERNAME }} @@ -179,6 +182,7 @@ jobs: password: ${{ env.PASSWORD }} script: | gunzip -c /tmp/golang_dev.tar.gz | docker load - rm -f /tmp/golang_dev.tar.gz - cd ~/deployments/development - docker compose -f docker-compose-development.yml up -d \ No newline at end of file + docker ps + # rm -f /tmp/golang_dev.tar.gz + # cd ~/deployments/development + # docker compose -f docker-compose-development.yml up -d \ No newline at end of file From 03d255c7f7ca6691d4108523ca466cad2b58332f Mon Sep 17 00:00:00 2001 From: Osinachi Chukwujama Date: Thu, 8 Aug 2024 15:48:04 +0100 Subject: [PATCH 26/45] chore: update workflows --- .github/workflows/development.yml | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/.github/workflows/development.yml b/.github/workflows/development.yml index 341243fc..dca63320 100644 --- a/.github/workflows/development.yml +++ b/.github/workflows/development.yml @@ -28,8 +28,7 @@ jobs: upload_docker_image: runs-on: ubuntu-latest needs: build_docker_image - if: github.event.repository.fork == false - if: github.event_name == 'push' + if: github.event.repository.fork == false && github.event_name == 'push' environment: name: "development" url: ${{ vars.URL }} @@ -118,7 +117,6 @@ jobs: SSH_PORT: ${{ secrets.SSH_PORT }} SSH_PASSWORD: ${{ secrets.SSH_PASSWORD }} - steps: - name: SSH into server and deploy uses: appleboy/ssh-action@v1.0.3 @@ -165,8 +163,7 @@ jobs: run_docker_container: runs-on: ubuntu-latest needs: upload_docker_image - if: github.event.repository.fork == false - if: github.event_name == 'push' + if: github.event.repository.fork == false && github.event_name == 'push' environment: development env: SSH_USERNAME: ${{ secrets.SSH_USERNAME }} From 37e5156a78c3f08030f2f7a08d717115baecf730 Mon Sep 17 00:00:00 2001 From: urizennnn Date: Thu, 8 Aug 2024 16:48:24 +0100 Subject: [PATCH 27/45] chore:created 2fa --- .gitignore | 2 +- dump.rdb | Bin 0 -> 88 bytes go.mod | 19 ++--- go.sum | 23 ++++++ internal/models/key.go | 12 ++++ internal/models/migrations/migrations.go | 1 + internal/models/user.go | 1 + pkg/controller/key/key.go | 51 +++++++++++++ pkg/router/auth.go | 4 ++ services/key/key.go | 88 +++++++++++++++++++++++ 10 files changed, 192 insertions(+), 9 deletions(-) create mode 100644 dump.rdb create mode 100644 internal/models/key.go create mode 100644 pkg/controller/key/key.go create mode 100644 services/key/key.go diff --git a/.gitignore b/.gitignore index aada9c13..8a5944af 100644 --- a/.gitignore +++ b/.gitignore @@ -15,7 +15,7 @@ *.out # Dependency directories (remove the comment below to include it) - vendor/ +vendor/ # Go workspace file go.work diff --git a/dump.rdb b/dump.rdb new file mode 100644 index 0000000000000000000000000000000000000000..f566dbf8c10af58324baea8b89a3b64ec02a0fdd GIT binary patch literal 88 zcmWG?b@2=~FfcUy#aWb^l3A=uO-d|IJ;3lkmL=uxH$&Y#0F5;v&;S4c literal 0 HcmV?d00001 diff --git a/go.mod b/go.mod index 36873ab3..1fbcdfcb 100644 --- a/go.mod +++ b/go.mod @@ -30,7 +30,7 @@ require ( github.com/sirupsen/logrus v1.9.3 github.com/spf13/viper v1.19.0 github.com/stretchr/testify v1.9.0 - golang.org/x/crypto v0.25.0 + golang.org/x/crypto v0.26.0 golang.org/x/time v0.5.0 google.golang.org/api v0.171.0 gorm.io/driver/postgres v1.5.9 @@ -44,6 +44,7 @@ require ( github.com/PuerkitoBio/purell v1.1.1 // indirect github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect github.com/aymerick/douceur v0.2.0 // indirect + github.com/boombuler/barcode v1.0.2 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/go-logr/logr v1.4.1 // indirect github.com/go-logr/stdr v1.2.2 // indirect @@ -59,13 +60,15 @@ require ( github.com/jackc/puddle/v2 v2.2.1 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/mailru/easyjson v0.7.6 // indirect + github.com/pquerna/otp v1.4.0 // indirect + github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e // indirect github.com/swaggo/swag v1.8.12 // indirect go.opencensus.io v0.24.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect go.opentelemetry.io/otel v1.24.0 // indirect go.opentelemetry.io/otel/metric v1.24.0 // indirect go.opentelemetry.io/otel/trace v1.24.0 // indirect - golang.org/x/sync v0.7.0 // indirect + golang.org/x/sync v0.8.0 // indirect golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240314234333-6e1732d8331c // indirect google.golang.org/grpc v1.62.1 // indirect @@ -74,8 +77,8 @@ require ( require ( github.com/beorn7/perks v1.0.1 // indirect - github.com/bytedance/sonic v1.11.6 // indirect - github.com/bytedance/sonic/loader v0.1.1 // indirect + github.com/bytedance/sonic v1.12.1 // indirect + github.com/bytedance/sonic/loader v0.2.0 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/cloudwego/base64x v0.1.4 // indirect github.com/cloudwego/iasm v0.2.0 // indirect @@ -122,12 +125,12 @@ require ( github.com/ugorji/go/codec v1.2.12 // indirect go.uber.org/atomic v1.9.0 // indirect go.uber.org/multierr v1.9.0 // indirect - golang.org/x/arch v0.8.0 // indirect + golang.org/x/arch v0.9.0 // indirect golang.org/x/exp v0.0.0-20231214170342-aacd6d4b4611 // indirect - golang.org/x/net v0.27.0 // indirect + golang.org/x/net v0.28.0 // indirect golang.org/x/oauth2 v0.18.0 // indirect - golang.org/x/sys v0.22.0 // indirect - golang.org/x/text v0.16.0 // indirect + golang.org/x/sys v0.23.0 // indirect + golang.org/x/text v0.17.0 // indirect google.golang.org/appengine v1.6.8 // indirect google.golang.org/protobuf v1.34.2 // indirect gopkg.in/ini.v1 v1.67.0 // indirect diff --git a/go.sum b/go.sum index 4ac49978..7e9505d7 100644 --- a/go.sum +++ b/go.sum @@ -14,10 +14,17 @@ github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuP github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= +github.com/boombuler/barcode v1.0.2 h1:79yrbttoZrLGkL/oOI8hBrUKucwOL0oOjUgEguGMcJ4= +github.com/boombuler/barcode v1.0.2/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0= github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4= +github.com/bytedance/sonic v1.12.1 h1:jWl5Qz1fy7X1ioY74WqO0KjAMtAGQs4sYnjiEBiyX24= +github.com/bytedance/sonic v1.12.1/go.mod h1:B8Gt/XvtZ3Fqj+iSKMypzymZxw/FVwgIGKzMzT9r/rk= github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM= github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= +github.com/bytedance/sonic/loader v0.2.0 h1:zNprn+lsIP06C/IqCHs3gPQIvnvpKbbxyXQP1iU4kWM= +github.com/bytedance/sonic/loader v0.2.0/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= @@ -213,6 +220,8 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE 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/pquerna/otp v1.4.0 h1:wZvl1TIVxKRThZIBiwOOHOGP/1+nZyWBil9Y2XNEDzg= +github.com/pquerna/otp v1.4.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg= github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE= github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= @@ -229,6 +238,8 @@ github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6g github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0= +github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M= github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= @@ -285,11 +296,15 @@ go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTV golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc= golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= +golang.org/x/arch v0.9.0 h1:ub9TgUInamJ8mrZIGlBG6/4TqWeMszd4N8lNorbrr6k= +golang.org/x/arch v0.9.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= +golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= +golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20231214170342-aacd6d4b4611 h1:qCEDpW1G+vcj3Y7Fy52pEM1AWm3abj8WimGYejI3SC4= golang.org/x/exp v0.0.0-20231214170342-aacd6d4b4611/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI= @@ -311,6 +326,8 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= +golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= +golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.18.0 h1:09qnuIAgzdx1XplqJvW6CQqMCtGZykZWcXzPMPUusvI= golang.org/x/oauth2 v0.18.0/go.mod h1:Wf7knwG0MPoWIMMBgFlEaSUDaKskp0dCfrlJRJXbBi8= @@ -320,6 +337,8 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -334,6 +353,8 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM= +golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= @@ -345,6 +366,8 @@ golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= +golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= diff --git a/internal/models/key.go b/internal/models/key.go new file mode 100644 index 00000000..895a0bf5 --- /dev/null +++ b/internal/models/key.go @@ -0,0 +1,12 @@ +package models + +type Key struct { + ID string `gorm:"type:uuid;primaryKey;unique;not null" json:"id"` + UserID string `gorm:"type:uuid;not null" json:"user_id"` + Key string `gorm:"type:varchar(255);not null" json:"key"` + User *User `gorm:"constraint:OnUpdate:CASCADE,OnDelete:SET NULL;" json:"-"` +} + +type VerifyKeyRequestModel struct { + Key string `json:"key" validate:"required"` +} diff --git a/internal/models/migrations/migrations.go b/internal/models/migrations/migrations.go index 1b21554c..991951b4 100644 --- a/internal/models/migrations/migrations.go +++ b/internal/models/migrations/migrations.go @@ -32,6 +32,7 @@ func AuthMigrationModels() []interface{} { models.HelpCenter{}, models.ContactUs{}, models.DataPrivacySettings{}, + models.Key{}, } // an array of db models, example: User{} } diff --git a/internal/models/user.go b/internal/models/user.go index 1db5c60a..ed1fca69 100644 --- a/internal/models/user.go +++ b/internal/models/user.go @@ -15,6 +15,7 @@ type User struct { Email string `gorm:"column:email; type:varchar(255)" json:"email"` Password string `gorm:"column:password; type:text; not null" json:"-"` Profile Profile `gorm:"foreignKey:Userid;constraint:OnUpdate:CASCADE,OnDelete:SET NULL;" json:"profile"` + Key Key `gorm:"foreignKey:UserID;constraint:OnUpdate:CASCADE,OnDelete:SET NULL;" json:"key"` Region UserRegionTimezoneLanguage `gorm:"foreignKey:UserID;constraint:OnUpdate:CASCADE,OnDelete:SET NULL;" json:"region"` DataPrivacy DataPrivacySettings `gorm:"foreignKey:UserID;constraint:OnUpdate:CASCADE,OnDelete:SET NULL;" json:"data_privacy"` Organisations []Organisation `gorm:"many2many:user_organisations;constraint:OnUpdate:CASCADE,OnDelete:SET NULL;" json:"organisations" ` // many to many relationship diff --git a/pkg/controller/key/key.go b/pkg/controller/key/key.go new file mode 100644 index 00000000..bb6524ff --- /dev/null +++ b/pkg/controller/key/key.go @@ -0,0 +1,51 @@ +package key + +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" + "github.com/hngprojects/hng_boilerplate_golang_web/services/key" + "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) CreateKey(c *gin.Context) { + + respData, code, err := key.CreateKey(base.Db.Postgresql, c) + if err != nil { + rd := utility.BuildErrorResponse(http.StatusBadRequest, "error", err.Error(), err, nil) + c.JSON(http.StatusBadRequest, rd) + return + } + + base.Logger.Info("Key created successfully") + rd := utility.BuildSuccessResponse(http.StatusCreated, "Key created successfully", respData) + + c.JSON(code, rd) +} + +func (base *Controller) VerifyKey(c *gin.Context) { + req := models.VerifyKeyRequestModel{} + + respData, code, err := key.VerifyKey(req, base.Db.Postgresql, c) + if err != nil { + rd := utility.BuildErrorResponse(http.StatusBadRequest, "error", err.Error(), err, nil) + c.JSON(http.StatusBadRequest, rd) + return + } + + base.Logger.Info("Key verified successfully") + rd := utility.BuildSuccessResponse(http.StatusOK, "Key verified successfully", respData) + + c.JSON(code, rd) +} diff --git a/pkg/router/auth.go b/pkg/router/auth.go index b842e479..74e37a18 100644 --- a/pkg/router/auth.go +++ b/pkg/router/auth.go @@ -9,6 +9,7 @@ import ( "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/controller/auth" + "github.com/hngprojects/hng_boilerplate_golang_web/pkg/controller/key" "github.com/hngprojects/hng_boilerplate_golang_web/pkg/middleware" "github.com/hngprojects/hng_boilerplate_golang_web/pkg/repository/storage" "github.com/hngprojects/hng_boilerplate_golang_web/utility" @@ -17,6 +18,7 @@ import ( func Auth(r *gin.Engine, ApiVersion string, validator *validator.Validate, db *storage.Database, logger *utility.Logger) *gin.Engine { extReq := request.ExternalRequest{Logger: logger, Test: false} auth := auth.Controller{Db: db, Validator: validator, Logger: logger, ExtReq: extReq} + key := key.Controller{Db: db, Validator: validator, Logger: logger, ExtReq: extReq} authUrl := r.Group(fmt.Sprintf("%v/auth", ApiVersion)) { @@ -26,6 +28,8 @@ func Auth(r *gin.Engine, ApiVersion string, validator *validator.Validate, db *s authUrl.POST("/password-reset/verify", auth.VerifyResetToken) authUrl.POST("/magick-link", auth.RequestMagicLink) authUrl.POST("/magick-link/verify", auth.VerifyMagicLink) + authUrl.POST("/2fa/enable", key.CreateKey) + authUrl.POST("/2fa/verify", key.VerifyKey) } authUrlSec := r.Group(fmt.Sprintf("%v/auth", ApiVersion), diff --git a/services/key/key.go b/services/key/key.go new file mode 100644 index 00000000..da5b328c --- /dev/null +++ b/services/key/key.go @@ -0,0 +1,88 @@ +package key + +import ( + "errors" + "log" + "net/http" + + "github.com/gin-gonic/gin" + "github.com/hngprojects/hng_boilerplate_golang_web/internal/models" + "github.com/hngprojects/hng_boilerplate_golang_web/pkg/middleware" + "github.com/hngprojects/hng_boilerplate_golang_web/utility" + "github.com/pquerna/otp/totp" + "github.com/skip2/go-qrcode" + "gorm.io/gorm" +) + +func CreateKey(db *gorm.DB, c *gin.Context) (gin.H, int, error) { + userID, _ := middleware.GetIdFromToken(c) + log.Print(userID) + + if userID == "" { + return nil, http.StatusBadRequest, errors.New("User is not authenticated") + } + + var existingKey models.Key + if err := db.Where("user_id = ?", userID).First(&existingKey).Error; err == nil { + return nil, http.StatusConflict, errors.New("Key for this user already exists") + } else if !errors.Is(err, gorm.ErrRecordNotFound) { + return nil, http.StatusInternalServerError, err + } + + secret, err := totp.Generate(totp.GenerateOpts{ + Issuer: "HNG_KIMIKO", + AccountName: userID, + }) + if err != nil { + return nil, http.StatusInternalServerError, err + } + + var user models.User + if err := db.Where("id = ?", userID).First(&user).Error; err != nil { + db.Rollback() + return nil, http.StatusNotFound, errors.New("User not found") + } + + keyModel := models.Key{ + ID: utility.GenerateUUID(), + UserID: userID, + Key: secret.Secret(), + } + + if err := db.Create(&keyModel).Error; err != nil { + db.Rollback() + return nil, http.StatusInternalServerError, err + } + + db.Commit() + + png, err := qrcode.Encode(secret.URL(), qrcode.Medium, 256) + if err != nil { + return nil, http.StatusInternalServerError, err + } + + return gin.H{ + "secret": secret.Secret(), + "qr_code": png, + }, http.StatusCreated, nil +} + +func VerifyKey(req models.VerifyKeyRequestModel, db *gorm.DB, c *gin.Context) (gin.H, int, error) { + userID, _ := middleware.GetIdFromToken(c) + key := req.Key + if key == "" || userID == "" { + return nil, http.StatusBadRequest, errors.New("Key and User ID are required") + } + var keyModel models.Key + if err := db.Where("user_id = ?", userID).First(&keyModel).Error; err != nil { + return nil, http.StatusNotFound, errors.New("Key not found") + } + + if !totp.Validate(key, keyModel.Key) { + return nil, http.StatusUnauthorized, errors.New("Invalid key") + } + + return gin.H{ + "message": "Key verified successfully", + }, http.StatusOK, nil +} From f5a2d0632aa78e493fc998b3f5c4d8d77a46c9f1 Mon Sep 17 00:00:00 2001 From: Osinachi Chukwujama Date: Thu, 8 Aug 2024 16:52:25 +0100 Subject: [PATCH 28/45] bugfix: workflows --- .github/workflows/development.yml | 7 +++-- .github/workflows/production.yml | 43 ++++++++++++++++++++++++------- .github/workflows/staging.yml | 43 ++++++++++++++++++++++++------- docker-compose-development.yml | 22 ++++++++-------- docker-compose-production.yml | 22 ++++++++-------- docker-compose-staging.yml | 18 ++++++------- 6 files changed, 100 insertions(+), 55 deletions(-) diff --git a/.github/workflows/development.yml b/.github/workflows/development.yml index dca63320..4f774aa1 100644 --- a/.github/workflows/development.yml +++ b/.github/workflows/development.yml @@ -179,7 +179,6 @@ jobs: password: ${{ env.PASSWORD }} script: | gunzip -c /tmp/golang_dev.tar.gz | docker load - docker ps - # rm -f /tmp/golang_dev.tar.gz - # cd ~/deployments/development - # docker compose -f docker-compose-development.yml up -d \ No newline at end of file + rm -f /tmp/golang_dev.tar.gz + cd ~/deployments/development + docker compose -f docker-compose-development.yml up -d \ No newline at end of file diff --git a/.github/workflows/production.yml b/.github/workflows/production.yml index 5967c08a..a9504a21 100644 --- a/.github/workflows/production.yml +++ b/.github/workflows/production.yml @@ -11,6 +11,7 @@ on: jobs: build_docker_image: runs-on: ubuntu-latest + if: github.event_name == 'push' steps: - name: Checkout code uses: actions/checkout@v4 @@ -27,9 +28,9 @@ jobs: upload_docker_image: runs-on: ubuntu-latest needs: build_docker_image - if: github.event.repository.fork == false + if: github.event.repository.fork == false && github.event_name == 'push' environment: - name: "development" + name: "production" url: ${{ vars.URL }} steps: - name: Download Docker image @@ -39,7 +40,7 @@ jobs: path: . - name: Copy image to server - uses: appleboy/scp-action@master + uses: appleboy/scp-action@v0.1.7 with: host: ${{ secrets.SSH_HOST }} username: ${{ secrets.SSH_USERNAME }} @@ -60,6 +61,10 @@ jobs: TEST_TIMEZONE: Africa/Lagos TEST_SSLMODE: disable TEST_MIGRATE: true + REDIS_PORT: 6379 + REDIS_HOST: localhost + REDIS_DB: 0 + services: postgres: image: postgres:latest @@ -78,6 +83,7 @@ jobs: --health-retries 5 ports: - 6379:6379 + steps: - name: Checkout Code uses: actions/checkout@v4 @@ -105,15 +111,20 @@ jobs: needs: build_and_test if: github.event_name == 'push' environment: production + env: + SSH_USERNAME: ${{ secrets.SSH_USERNAME }} + SSH_HOST: ${{ secrets.SSH_HOST }} + SSH_PORT: ${{ secrets.SSH_PORT }} + SSH_PASSWORD: ${{ secrets.SSH_PASSWORD }} steps: - name: SSH into server and deploy uses: appleboy/ssh-action@v1.0.3 with: - host: ${{ secrets.SSH_HOST }} - username: ${{ secrets.SSH_USERNAME }} - password: ${{ secrets.SSH_PASSWORD }} - port: ${{ secrets.SSH_PORT }} + host: ${{ env.SSH_HOST }} + username: ${{ env.SSH_USERNAME }} + password: ${{ env.SSH_PASSWORD }} + port: ${{ env.SSH_PORT }} script: | export APPROOT=~/deployments/production mkdir -p $APPROOT @@ -148,12 +159,24 @@ jobs: MAIL_PORT=${{ secrets.MAIL_PORT }} \ MIGRATE=true + + run_docker_container: + runs-on: ubuntu-latest + needs: upload_docker_image + if: github.event.repository.fork == false && github.event_name == 'push' + environment: production + env: + SSH_USERNAME: ${{ secrets.SSH_USERNAME }} + SSH_HOST: ${{ secrets.SSH_HOST }} + SSH_PASSWORD: ${{ secrets.SSH_PASSWORD }} + + steps: - name: Deploy on server uses: appleboy/ssh-action@v1.0.3 with: - host: ${{ secrets.HOST }} - username: ${{ secrets.USERNAME }} - password: ${{ secrets.PASSWORD }} + host: ${{ env.HOST }} + username: ${{ env.USERNAME }} + password: ${{ env.PASSWORD }} script: | gunzip -c /tmp/golang_prod.tar.gz | docker load rm -f /tmp/golang_prod.tar.gz diff --git a/.github/workflows/staging.yml b/.github/workflows/staging.yml index dc5333aa..6e85dbcc 100644 --- a/.github/workflows/staging.yml +++ b/.github/workflows/staging.yml @@ -11,6 +11,7 @@ on: jobs: build_docker_image: runs-on: ubuntu-latest + if: github.event_name == 'push' steps: - name: Checkout code uses: actions/checkout@v4 @@ -27,9 +28,9 @@ jobs: upload_docker_image: runs-on: ubuntu-latest needs: build_docker_image - if: github.event.repository.fork == false + if: github.event.repository.fork == false && github.event_name == 'push' environment: - name: "development" + name: "staging" url: ${{ vars.URL }} steps: - name: Download Docker image @@ -39,7 +40,7 @@ jobs: path: . - name: Copy image to server - uses: appleboy/scp-action@master + uses: appleboy/scp-action@v0.1.7 with: host: ${{ secrets.SSH_HOST }} username: ${{ secrets.SSH_USERNAME }} @@ -60,6 +61,10 @@ jobs: TEST_TIMEZONE: Africa/Lagos TEST_SSLMODE: disable TEST_MIGRATE: true + REDIS_PORT: 6379 + REDIS_HOST: localhost + REDIS_DB: 0 + services: postgres: image: postgres:latest @@ -78,6 +83,7 @@ jobs: --health-retries 5 ports: - 6379:6379 + steps: - name: Checkout Code uses: actions/checkout@v4 @@ -105,15 +111,20 @@ jobs: needs: build_and_test if: github.event_name == 'push' environment: staging + env: + SSH_USERNAME: ${{ secrets.SSH_USERNAME }} + SSH_HOST: ${{ secrets.SSH_HOST }} + SSH_PORT: ${{ secrets.SSH_PORT }} + SSH_PASSWORD: ${{ secrets.SSH_PASSWORD }} steps: - name: SSH into server and deploy uses: appleboy/ssh-action@v1.0.3 with: - host: ${{ secrets.SSH_HOST }} - username: ${{ secrets.SSH_USERNAME }} - password: ${{ secrets.SSH_PASSWORD }} - port: ${{ secrets.SSH_PORT }} + host: ${{ env.SSH_HOST }} + username: ${{ env.SSH_USERNAME }} + password: ${{ env.SSH_PASSWORD }} + port: ${{ env.SSH_PORT }} script: | export APPROOT=~/deployments/staging mkdir -p $APPROOT @@ -148,12 +159,24 @@ jobs: MAIL_PORT=${{ secrets.MAIL_PORT }} \ MIGRATE=true + + run_docker_container: + runs-on: ubuntu-latest + needs: upload_docker_image + if: github.event.repository.fork == false && github.event_name == 'push' + environment: staging + env: + SSH_USERNAME: ${{ secrets.SSH_USERNAME }} + SSH_HOST: ${{ secrets.SSH_HOST }} + SSH_PASSWORD: ${{ secrets.SSH_PASSWORD }} + + steps: - name: Deploy on server uses: appleboy/ssh-action@v1.0.3 with: - host: ${{ secrets.HOST }} - username: ${{ secrets.USERNAME }} - password: ${{ secrets.PASSWORD }} + host: ${{ env.HOST }} + username: ${{ env.USERNAME }} + password: ${{ env.PASSWORD }} script: | gunzip -c /tmp/golang_staging.tar.gz | docker load rm -f /tmp/golang_staging.tar.gz diff --git a/docker-compose-development.yml b/docker-compose-development.yml index 10d21094..564fa581 100644 --- a/docker-compose-development.yml +++ b/docker-compose-development.yml @@ -4,9 +4,9 @@ services: db: image: postgres:16 environment: - POSTGRES_USER: postgres + POSTGRES_USER: development_user POSTGRES_PASSWORD: password - POSTGRES_DB: db_name + POSTGRES_DB: development_db POSTGRES_PORT: 5432 volumes: - db_data:/var/lib/postgresql/data @@ -27,18 +27,18 @@ services: - db - redis ports: - - "8000:8019" + - "8000:7000" env_file: - app.env - nginx: - image: nginx:latest - ports: - - "8000:80" - depends_on: - - backend - volumes: - - ./nginx.conf:/etc/nginx/nginx.conf + # nginx: + # image: nginx:latest + # ports: + # - "8000:80" + # depends_on: + # - backend + # volumes: + # - ./nginx.conf:/etc/nginx/nginx.conf volumes: db_data: \ No newline at end of file diff --git a/docker-compose-production.yml b/docker-compose-production.yml index 688426de..f60f443c 100644 --- a/docker-compose-production.yml +++ b/docker-compose-production.yml @@ -4,9 +4,9 @@ services: db: image: postgres:16 environment: - POSTGRES_USER: postgres + POSTGRES_USER: production_user POSTGRES_PASSWORD: password - POSTGRES_DB: db_name + POSTGRES_DB: production_db POSTGRES_PORT: 5432 volumes: - db_data:/var/lib/postgresql/data @@ -27,18 +27,18 @@ services: - db - redis ports: - - "8002:8019" + - "8002:7002" env_file: - app.env - nginx: - image: nginx:latest - ports: - - "8002:80" - depends_on: - - backend - volumes: - - ./nginx.conf:/etc/nginx/nginx.conf + # nginx: + # image: nginx:latest + # ports: + # - "8002:80" + # depends_on: + # - backend + # volumes: + # - ./nginx.conf:/etc/nginx/nginx.conf volumes: db_data: \ No newline at end of file diff --git a/docker-compose-staging.yml b/docker-compose-staging.yml index 00448788..d352c2bd 100644 --- a/docker-compose-staging.yml +++ b/docker-compose-staging.yml @@ -27,18 +27,18 @@ services: - db - redis ports: - - "8001:8019" + - "8001:7010" env_file: - app.env - nginx: - image: nginx:latest - ports: - - "8001:80" - depends_on: - - backend - volumes: - - ./nginx.conf:/etc/nginx/nginx.conf + # nginx: + # image: nginx:latest + # ports: + # - "8001:80" + # depends_on: + # - backend + # volumes: + # - ./nginx.conf:/etc/nginx/nginx.conf volumes: db_data: \ No newline at end of file From 44211fda9370ddbb5c4d0c6ded8f8c1cd4de3a16 Mon Sep 17 00:00:00 2001 From: urizennnn Date: Thu, 8 Aug 2024 19:58:54 +0100 Subject: [PATCH 29/45] chore:added docs for 2fa --- static/swagger.yaml | 100 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 99 insertions(+), 1 deletion(-) diff --git a/static/swagger.yaml b/static/swagger.yaml index 1c4f80da..2d53c89c 100644 --- a/static/swagger.yaml +++ b/static/swagger.yaml @@ -693,6 +693,104 @@ paths: schema: $ref: '#/components/schemas/ServerErrorSchema' + /auth/2fa/enable: + post: + tags: + - auth + summary: Enable two-factor authentication + security: + - bearerAuth: [] + requestBody: + required: true + responses: + '201': + description: Two-factor authentication enabled successfully + content: + application/json: + schema: + type: object + properties: + status: + type: string + example: "success" + message: + type: string + example: "Two-factor authentication enabled successfully" + statusCode: + type: integer + example: 201 + data: + type: object + properties: + secret: + type: string + example: "secret" + qr_code: + type: string + example: "qr_code" + '400': + description: Invalid input + content: + application/json: + schema: + $ref: '#/components/schemas/BadRequestErrorSchema' + + + /auth/2fa/verify: + post: + tags: + - auth + summary: Verify two-factor authentication + security: + - bearerAuth: [] + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + token: + type: string + required: + - token + responses: + '200': + description: Token verified successfully + content: + application/json: + schema: + type: object + properties: + status: + type: string + example: "success" + statusCode: + type: integer + example: 200 + message: + type: string + example: "Token verified successfully" + data: + $ref: "#/components/schemas/UserSchema" + '400': + description: Invalid input + content: + application/json: + schema: + $ref: '#/components/schemas/BadRequestErrorSchema' + '422': + description: Validation error + content: + application/json: + schema: + $ref: '#/components/schemas/UnprocessedEntityErrorSchema' + '500': + description: Server error + content: + application/json: + schema: + $ref: '#/components/schemas/ServerErrorSchema' #User path /users/{userId}: get: @@ -6436,4 +6534,4 @@ components: bearerAuth: type: http scheme: bearer - bearerFormat: JWT \ No newline at end of file + bearerFormat: JWT From 226bda2d6067456cd1ac768ba11b1e80b8509fc6 Mon Sep 17 00:00:00 2001 From: urizennnn Date: Thu, 8 Aug 2024 20:08:38 +0100 Subject: [PATCH 30/45] fix:updated swagger --- static/swagger.yaml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/static/swagger.yaml b/static/swagger.yaml index 2d53c89c..6b576757 100644 --- a/static/swagger.yaml +++ b/static/swagger.yaml @@ -700,9 +700,7 @@ paths: summary: Enable two-factor authentication security: - bearerAuth: [] - requestBody: - required: true - responses: + responses: '201': description: Two-factor authentication enabled successfully content: @@ -719,7 +717,7 @@ paths: statusCode: type: integer example: 201 - data: + data: type: object properties: secret: From eb4fd90764df8e58da08e06e247bcfb42a44105f Mon Sep 17 00:00:00 2001 From: ekedonald Date: Thu, 8 Aug 2024 20:21:34 +0100 Subject: [PATCH 31/45] Upload pr_deploy workflow files for production, staging and development. --- .github/workflows/development_pr_deploy.yml | 39 +++++++++++++++++++++ .github/workflows/production_pr_deploy.yml | 39 +++++++++++++++++++++ .github/workflows/staging_pr_deploy.yml | 39 +++++++++++++++++++++ 3 files changed, 117 insertions(+) create mode 100644 .github/workflows/development_pr_deploy.yml create mode 100644 .github/workflows/production_pr_deploy.yml create mode 100644 .github/workflows/staging_pr_deploy.yml diff --git a/.github/workflows/development_pr_deploy.yml b/.github/workflows/development_pr_deploy.yml new file mode 100644 index 00000000..7b91cf9e --- /dev/null +++ b/.github/workflows/development_pr_deploy.yml @@ -0,0 +1,39 @@ +name: PR Deploy +on: + pull_request: + types: [opened, synchronize, reopened, closed] + workflow_dispatch: + +jobs: + deploy-pr: + environment: + name: development + # url: ${{ steps.deploy.outputs.preview-url }} + runs-on: ubuntu-latest + env: + SSH_USERNAME: ${{ secrets.SSH_USERNAME }} + SSH_HOST: ${{ secrets.SSH_HOST }} + SSH_PORT: ${{ secrets.SSH_PORT }} + SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }} + + steps: + - name: Checkout to branch + uses: actions/checkout@v4 + - id: deploy + name: Pull Request Deploy + uses: hngprojects/pr-deploy@dev + with: + server_host: ${{ env.SSH_HOST }} + server_username: ${{ env.SSH_USERNAME }} + server_password: ${{ secrets.SERVER_PASSWORD }} + server_port: ${{ env.SSH_PORT }} + comment: true + context: '.' + dockerfile: 'Dockerfile' + exposed_port: '8019' + # host_volume_path: '/var/' + # container_volume_path: '/var/' + github_token: ${{ secrets.GITHUB_TOKEN }} + - name: Print Preview Url + run: | + echo "Preview Url: ${{ steps.deploy.outputs.preview-url }}" \ No newline at end of file diff --git a/.github/workflows/production_pr_deploy.yml b/.github/workflows/production_pr_deploy.yml new file mode 100644 index 00000000..7d0ca780 --- /dev/null +++ b/.github/workflows/production_pr_deploy.yml @@ -0,0 +1,39 @@ +name: PR Deploy +on: + pull_request: + types: [opened, synchronize, reopened, closed] + workflow_dispatch: + +jobs: + deploy-pr: + environment: + name: production + # url: ${{ steps.deploy.outputs.preview-url }} + runs-on: ubuntu-latest + env: + SSH_USERNAME: ${{ secrets.SSH_USERNAME }} + SSH_HOST: ${{ secrets.SSH_HOST }} + SSH_PORT: ${{ secrets.SSH_PORT }} + SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }} + + steps: + - name: Checkout to branch + uses: actions/checkout@v4 + - id: deploy + name: Pull Request Deploy + uses: hngprojects/pr-deploy@main + with: + server_host: ${{ env.SSH_HOST }} + server_username: ${{ env.SSH_USERNAME }} + server_password: ${{ secrets.SERVER_PASSWORD }} + server_port: ${{ env.SSH_PORT }} + comment: true + context: '.' + dockerfile: 'Dockerfile' + exposed_port: '8019' + # host_volume_path: '/var/' + # container_volume_path: '/var/' + github_token: ${{ secrets.GITHUB_TOKEN }} + - name: Print Preview Url + run: | + echo "Preview Url: ${{ steps.deploy.outputs.preview-url }}" \ No newline at end of file diff --git a/.github/workflows/staging_pr_deploy.yml b/.github/workflows/staging_pr_deploy.yml new file mode 100644 index 00000000..f9a5877e --- /dev/null +++ b/.github/workflows/staging_pr_deploy.yml @@ -0,0 +1,39 @@ +name: PR Deploy +on: + pull_request: + types: [opened, synchronize, reopened, closed] + workflow_dispatch: + +jobs: + deploy-pr: + environment: + name: staging + # url: ${{ steps.deploy.outputs.preview-url }} + runs-on: ubuntu-latest + env: + SSH_USERNAME: ${{ secrets.SSH_USERNAME }} + SSH_HOST: ${{ secrets.SSH_HOST }} + SSH_PORT: ${{ secrets.SSH_PORT }} + SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }} + + steps: + - name: Checkout to branch + uses: actions/checkout@v4 + - id: deploy + name: Pull Request Deploy + uses: hngprojects/pr-deploy@staging + with: + server_host: ${{ env.SSH_HOST }} + server_username: ${{ env.SSH_USERNAME }} + server_password: ${{ secrets.SERVER_PASSWORD }} + server_port: ${{ env.SSH_PORT }} + comment: true + context: '.' + dockerfile: 'Dockerfile' + exposed_port: '8019' + # host_volume_path: '/var/' + # container_volume_path: '/var/' + github_token: ${{ secrets.GITHUB_TOKEN }} + - name: Print Preview Url + run: | + echo "Preview Url: ${{ steps.deploy.outputs.preview-url }}" \ No newline at end of file From 56a0dc57a8856958527364fcb1ea2f2afbec96d6 Mon Sep 17 00:00:00 2001 From: ekedonald Date: Thu, 8 Aug 2024 20:53:25 +0100 Subject: [PATCH 32/45] Modify pr_deploy workflow files for production, staging and development. --- .github/workflows/development_pr_deploy.yml | 6 +++--- .github/workflows/production_pr_deploy.yml | 12 ++++++------ .github/workflows/staging_pr_deploy.yml | 6 +++--- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/.github/workflows/development_pr_deploy.yml b/.github/workflows/development_pr_deploy.yml index 7b91cf9e..5d7a1e6a 100644 --- a/.github/workflows/development_pr_deploy.yml +++ b/.github/workflows/development_pr_deploy.yml @@ -1,4 +1,4 @@ -name: PR Deploy +name: PR Deploy for Development on: pull_request: types: [opened, synchronize, reopened, closed] @@ -14,7 +14,7 @@ jobs: SSH_USERNAME: ${{ secrets.SSH_USERNAME }} SSH_HOST: ${{ secrets.SSH_HOST }} SSH_PORT: ${{ secrets.SSH_PORT }} - SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }} + SSH_PASSWORD: ${{ secrets.SSH_PASSWORD }} steps: - name: Checkout to branch @@ -25,7 +25,7 @@ jobs: with: server_host: ${{ env.SSH_HOST }} server_username: ${{ env.SSH_USERNAME }} - server_password: ${{ secrets.SERVER_PASSWORD }} + server_password: ${{ env.SSH_PASSWORD }} server_port: ${{ env.SSH_PORT }} comment: true context: '.' diff --git a/.github/workflows/production_pr_deploy.yml b/.github/workflows/production_pr_deploy.yml index 7d0ca780..917bb5d5 100644 --- a/.github/workflows/production_pr_deploy.yml +++ b/.github/workflows/production_pr_deploy.yml @@ -1,4 +1,4 @@ -name: PR Deploy +name: PR Deploy for Production on: pull_request: types: [opened, synchronize, reopened, closed] @@ -11,10 +11,10 @@ jobs: # url: ${{ steps.deploy.outputs.preview-url }} runs-on: ubuntu-latest env: - SSH_USERNAME: ${{ secrets.SSH_USERNAME }} - SSH_HOST: ${{ secrets.SSH_HOST }} - SSH_PORT: ${{ secrets.SSH_PORT }} - SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }} + SSH_USERNAME: ${{ secrets.SSH_USERNAME }} + SSH_HOST: ${{ secrets.SSH_HOST }} + SSH_PORT: ${{ secrets.SSH_PORT }} + SSH_PASSWORD: ${{ secrets.SSH_PASSWORD }} steps: - name: Checkout to branch @@ -25,7 +25,7 @@ jobs: with: server_host: ${{ env.SSH_HOST }} server_username: ${{ env.SSH_USERNAME }} - server_password: ${{ secrets.SERVER_PASSWORD }} + server_password: ${{ env.SSH_PASSWORD }} server_port: ${{ env.SSH_PORT }} comment: true context: '.' diff --git a/.github/workflows/staging_pr_deploy.yml b/.github/workflows/staging_pr_deploy.yml index f9a5877e..2763add1 100644 --- a/.github/workflows/staging_pr_deploy.yml +++ b/.github/workflows/staging_pr_deploy.yml @@ -1,4 +1,4 @@ -name: PR Deploy +name: PR Deploy for Staging on: pull_request: types: [opened, synchronize, reopened, closed] @@ -14,7 +14,7 @@ jobs: SSH_USERNAME: ${{ secrets.SSH_USERNAME }} SSH_HOST: ${{ secrets.SSH_HOST }} SSH_PORT: ${{ secrets.SSH_PORT }} - SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }} + SSH_PASSWORD: ${{ secrets.SSH_PASSWORD }} steps: - name: Checkout to branch @@ -25,7 +25,7 @@ jobs: with: server_host: ${{ env.SSH_HOST }} server_username: ${{ env.SSH_USERNAME }} - server_password: ${{ secrets.SERVER_PASSWORD }} + server_password: ${{ env.SSH_PASSWORD }} server_port: ${{ env.SSH_PORT }} comment: true context: '.' From 6d4307c6540b4becde050d85ad4c135408de5d2b Mon Sep 17 00:00:00 2001 From: ekedonald Date: Thu, 8 Aug 2024 21:03:34 +0100 Subject: [PATCH 33/45] PR deploy bug fix. --- .github/workflows/staging_pr_deploy.yml | 39 ------------------------- 1 file changed, 39 deletions(-) delete mode 100644 .github/workflows/staging_pr_deploy.yml diff --git a/.github/workflows/staging_pr_deploy.yml b/.github/workflows/staging_pr_deploy.yml deleted file mode 100644 index 2763add1..00000000 --- a/.github/workflows/staging_pr_deploy.yml +++ /dev/null @@ -1,39 +0,0 @@ -name: PR Deploy for Staging -on: - pull_request: - types: [opened, synchronize, reopened, closed] - workflow_dispatch: - -jobs: - deploy-pr: - environment: - name: staging - # url: ${{ steps.deploy.outputs.preview-url }} - runs-on: ubuntu-latest - env: - SSH_USERNAME: ${{ secrets.SSH_USERNAME }} - SSH_HOST: ${{ secrets.SSH_HOST }} - SSH_PORT: ${{ secrets.SSH_PORT }} - SSH_PASSWORD: ${{ secrets.SSH_PASSWORD }} - - steps: - - name: Checkout to branch - uses: actions/checkout@v4 - - id: deploy - name: Pull Request Deploy - uses: hngprojects/pr-deploy@staging - with: - server_host: ${{ env.SSH_HOST }} - server_username: ${{ env.SSH_USERNAME }} - server_password: ${{ env.SSH_PASSWORD }} - server_port: ${{ env.SSH_PORT }} - comment: true - context: '.' - dockerfile: 'Dockerfile' - exposed_port: '8019' - # host_volume_path: '/var/' - # container_volume_path: '/var/' - github_token: ${{ secrets.GITHUB_TOKEN }} - - name: Print Preview Url - run: | - echo "Preview Url: ${{ steps.deploy.outputs.preview-url }}" \ No newline at end of file From e646cadb71b29a9f7163a9a9782407b9d38cefec Mon Sep 17 00:00:00 2001 From: Osinachi Chukwujama Date: Thu, 8 Aug 2024 22:53:13 +0100 Subject: [PATCH 34/45] chore: set up different nginx configs --- .github/workflows/development.yml | 2 +- docker-compose-production.yml | 16 +++++------ docker-compose-staging.yml | 16 +++++------ ...pose-development.yml => docker-compose.yml | 16 +++++------ nginx.conf => nginx/nginx.dev.conf | 0 nginx/nginx.prod.conf | 27 +++++++++++++++++++ nginx/nginx.staging.conf | 27 +++++++++++++++++++ 7 files changed, 79 insertions(+), 25 deletions(-) rename docker-compose-development.yml => docker-compose.yml (79%) rename nginx.conf => nginx/nginx.dev.conf (100%) create mode 100644 nginx/nginx.prod.conf create mode 100644 nginx/nginx.staging.conf diff --git a/.github/workflows/development.yml b/.github/workflows/development.yml index 4f774aa1..962fc84c 100644 --- a/.github/workflows/development.yml +++ b/.github/workflows/development.yml @@ -181,4 +181,4 @@ jobs: gunzip -c /tmp/golang_dev.tar.gz | docker load rm -f /tmp/golang_dev.tar.gz cd ~/deployments/development - docker compose -f docker-compose-development.yml up -d \ No newline at end of file + docker compose -f docker-compose.yml up -d \ No newline at end of file diff --git a/docker-compose-production.yml b/docker-compose-production.yml index f60f443c..f367771f 100644 --- a/docker-compose-production.yml +++ b/docker-compose-production.yml @@ -31,14 +31,14 @@ services: env_file: - app.env - # nginx: - # image: nginx:latest - # ports: - # - "8002:80" - # depends_on: - # - backend - # volumes: - # - ./nginx.conf:/etc/nginx/nginx.conf + nginx: + image: nginx:latest + ports: + - "8117:80" + depends_on: + - backend + volumes: + - ./nginx/nginx.prod.conf:/etc/nginx/nginx.conf volumes: db_data: \ No newline at end of file diff --git a/docker-compose-staging.yml b/docker-compose-staging.yml index d352c2bd..fdafdf5f 100644 --- a/docker-compose-staging.yml +++ b/docker-compose-staging.yml @@ -31,14 +31,14 @@ services: env_file: - app.env - # nginx: - # image: nginx:latest - # ports: - # - "8001:80" - # depends_on: - # - backend - # volumes: - # - ./nginx.conf:/etc/nginx/nginx.conf + nginx: + image: nginx:latest + ports: + - "8116:80" + depends_on: + - backend + volumes: + - ./nginx/nginx.staging.conf:/etc/nginx/nginx.conf volumes: db_data: \ No newline at end of file diff --git a/docker-compose-development.yml b/docker-compose.yml similarity index 79% rename from docker-compose-development.yml rename to docker-compose.yml index 564fa581..a293d3fe 100644 --- a/docker-compose-development.yml +++ b/docker-compose.yml @@ -31,14 +31,14 @@ services: env_file: - app.env - # nginx: - # image: nginx:latest - # ports: - # - "8000:80" - # depends_on: - # - backend - # volumes: - # - ./nginx.conf:/etc/nginx/nginx.conf + nginx: + image: nginx:latest + ports: + - "8115:80" + depends_on: + - backend + volumes: + - ./nginx/nginx.dev.conf:/etc/nginx/nginx.conf volumes: db_data: \ No newline at end of file diff --git a/nginx.conf b/nginx/nginx.dev.conf similarity index 100% rename from nginx.conf rename to nginx/nginx.dev.conf diff --git a/nginx/nginx.prod.conf b/nginx/nginx.prod.conf new file mode 100644 index 00000000..0e5cffa3 --- /dev/null +++ b/nginx/nginx.prod.conf @@ -0,0 +1,27 @@ +worker_processes 1; + +events { worker_connections 1024; } + +http { + upstream backend { + least_conn; + server backend:8002; + } + + server { + listen 80; + + server_name _; + + location / { + proxy_pass http://backend; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection keep-alive; + proxy_set_header Host $host; + proxy_cache_bypass $http_upgrade; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + } +} diff --git a/nginx/nginx.staging.conf b/nginx/nginx.staging.conf new file mode 100644 index 00000000..39bee9f8 --- /dev/null +++ b/nginx/nginx.staging.conf @@ -0,0 +1,27 @@ +worker_processes 1; + +events { worker_connections 1024; } + +http { + upstream backend { + least_conn; + server backend:8001; + } + + server { + listen 80; + + server_name _; + + location / { + proxy_pass http://backend; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection keep-alive; + proxy_set_header Host $host; + proxy_cache_bypass $http_upgrade; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + } +} From d699b442ffcbc9fb77e88c85d0bc9a1aa0a9473d Mon Sep 17 00:00:00 2001 From: ekedonald Date: Fri, 9 Aug 2024 01:45:12 +0100 Subject: [PATCH 35/45] bug fix docker --- docker-compose.yml | 22 ++++++++-------------- nginx/nginx.dev.conf => nginx.conf | 0 2 files changed, 8 insertions(+), 14 deletions(-) rename nginx/nginx.dev.conf => nginx.conf (100%) diff --git a/docker-compose.yml b/docker-compose.yml index a293d3fe..9efec73a 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,20 +1,16 @@ -name: golang_dev +name: golang services: db: image: postgres:16 + env_file: + - app.env environment: - POSTGRES_USER: development_user - POSTGRES_PASSWORD: password - POSTGRES_DB: development_db - POSTGRES_PORT: 5432 + POSTGRES_USER: ${USERNAME} + POSTGRES_PASSWORD: ${PASSWORD} + POSTGRES_DB: ${DB_NAME} volumes: - db_data:/var/lib/postgresql/data - healthcheck: - test: ["CMD-SHELL", "pg_isready -U postgres"] - interval: 10s - timeout: 10s - retries: 2 redis: image: redis:latest @@ -26,19 +22,17 @@ services: depends_on: - db - redis - ports: - - "8000:7000" env_file: - app.env nginx: image: nginx:latest ports: - - "8115:80" + - "7000:80" depends_on: - backend volumes: - - ./nginx/nginx.dev.conf:/etc/nginx/nginx.conf + - ./nginx.conf:/etc/nginx/nginx.conf volumes: db_data: \ No newline at end of file diff --git a/nginx/nginx.dev.conf b/nginx.conf similarity index 100% rename from nginx/nginx.dev.conf rename to nginx.conf From b579a964fbcd703f466ab368093961689496d81b Mon Sep 17 00:00:00 2001 From: ekedonald <143762587+ekedonald@users.noreply.github.com> Date: Fri, 9 Aug 2024 02:31:24 +0100 Subject: [PATCH 36/45] Update development.yml Signed-off-by: ekedonald <143762587+ekedonald@users.noreply.github.com> --- .github/workflows/development.yml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.github/workflows/development.yml b/.github/workflows/development.yml index 962fc84c..92488693 100644 --- a/.github/workflows/development.yml +++ b/.github/workflows/development.yml @@ -1,9 +1,6 @@ name: Build, Test, and Deploy for Development on: - push: - branches: - - dev pull_request: branches: - dev @@ -181,4 +178,4 @@ jobs: gunzip -c /tmp/golang_dev.tar.gz | docker load rm -f /tmp/golang_dev.tar.gz cd ~/deployments/development - docker compose -f docker-compose.yml up -d \ No newline at end of file + docker compose -f docker-compose.yml up -d From 3d7a5ff8b7f05d8387c08a2ed1dd5a902e09a49d Mon Sep 17 00:00:00 2001 From: ekedonald <143762587+ekedonald@users.noreply.github.com> Date: Fri, 9 Aug 2024 02:50:50 +0100 Subject: [PATCH 37/45] Create dev-deploy.yml Signed-off-by: ekedonald <143762587+ekedonald@users.noreply.github.com> --- .github/workflows/dev-deploy.yml | 62 ++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 .github/workflows/dev-deploy.yml diff --git a/.github/workflows/dev-deploy.yml b/.github/workflows/dev-deploy.yml new file mode 100644 index 00000000..13c47f10 --- /dev/null +++ b/.github/workflows/dev-deploy.yml @@ -0,0 +1,62 @@ +name: Dev Deployment + +on: + workflow_dispatch: + push: + branches: + - dev + +jobs: + build: + runs-on: ubuntu-latest + if: github.event.repository.fork == false + steps: + - uses: actions/checkout@v2 + + - name: Build Docker image + run: docker build -t golang_backend . + + - name: Save and compress Docker image + run: docker save golang_backend | gzip > golang_backend.tar.gz + + - name: Upload Docker image + uses: actions/upload-artifact@v2 + with: + name: golang_backend + path: golang_backend.tar.gz + + deploy: + needs: build + runs-on: ubuntu-latest + + environment: + name: "development" + url: ${{ vars.URL }} + + steps: + - name: Download Docker image + uses: actions/download-artifact@v2 + with: + name: golang_backend + path: . + + - name: Copy image to server + uses: appleboy/scp-action@master + with: + host: ${{ secrets.HOST }} + username: ${{ secrets.USERNAME }} + password: ${{ secrets.PASSWORD }} + source: golang_backend.tar.gz + target: "/tmp" + + - name: Deploy on server + uses: appleboy/ssh-action@master + with: + host: ${{ secrets.HOST }} + username: ${{ secrets.USERNAME }} + password: ${{ secrets.PASSWORD }} + script: | + gunzip -c /tmp/golang_backend.tar.gz | docker load + rm -f /tmp/golang_backend.tar.gz + cd ~/hng_boilerplate_golang_web + docker compose -p golang up -d From 2bd6a44dbcc255ee8cc707e1ad46c7837cf6f5da Mon Sep 17 00:00:00 2001 From: ekedonald <143762587+ekedonald@users.noreply.github.com> Date: Fri, 9 Aug 2024 02:51:29 +0100 Subject: [PATCH 38/45] Update docker-compose.yml Signed-off-by: ekedonald <143762587+ekedonald@users.noreply.github.com> --- docker-compose.yml | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 9efec73a..809d2a28 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -4,11 +4,7 @@ services: db: image: postgres:16 env_file: - - app.env - environment: - POSTGRES_USER: ${USERNAME} - POSTGRES_PASSWORD: ${PASSWORD} - POSTGRES_DB: ${DB_NAME} + - ./app.env volumes: - db_data:/var/lib/postgresql/data @@ -16,14 +12,14 @@ services: image: redis:latest backend: - image: ${COMPOSE_PROJECT_NAME} + image: golang_backend build: context: . depends_on: - db - redis env_file: - - app.env + - ./app.env nginx: image: nginx:latest @@ -35,4 +31,4 @@ services: - ./nginx.conf:/etc/nginx/nginx.conf volumes: - db_data: \ No newline at end of file + db_data: From b27f78b0b0710533713d45f39c30af47f2029f89 Mon Sep 17 00:00:00 2001 From: ekedonald <143762587+ekedonald@users.noreply.github.com> Date: Fri, 9 Aug 2024 02:52:18 +0100 Subject: [PATCH 39/45] Update nginx.conf Signed-off-by: ekedonald <143762587+ekedonald@users.noreply.github.com> --- nginx.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nginx.conf b/nginx.conf index 412f3723..2d3c33c7 100644 --- a/nginx.conf +++ b/nginx.conf @@ -5,7 +5,7 @@ events { worker_connections 1024; } http { upstream backend { least_conn; - server backend:8000; + server backend:7000; } server { From 9665fa095992311514273b41bf16dedaa6d9689f Mon Sep 17 00:00:00 2001 From: ekedonald <143762587+ekedonald@users.noreply.github.com> Date: Fri, 9 Aug 2024 03:03:49 +0100 Subject: [PATCH 40/45] Update dev-deploy.yml Signed-off-by: ekedonald <143762587+ekedonald@users.noreply.github.com> --- .github/workflows/dev-deploy.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/dev-deploy.yml b/.github/workflows/dev-deploy.yml index 13c47f10..42212ec0 100644 --- a/.github/workflows/dev-deploy.yml +++ b/.github/workflows/dev-deploy.yml @@ -43,18 +43,18 @@ jobs: - name: Copy image to server uses: appleboy/scp-action@master with: - host: ${{ secrets.HOST }} - username: ${{ secrets.USERNAME }} - password: ${{ secrets.PASSWORD }} + host: ${{ secrets.SSH_HOST }} + username: ${{ secrets.SSH_USERNAME }} + password: ${{ secrets.SSH_PASSWORD }} source: golang_backend.tar.gz target: "/tmp" - name: Deploy on server uses: appleboy/ssh-action@master with: - host: ${{ secrets.HOST }} - username: ${{ secrets.USERNAME }} - password: ${{ secrets.PASSWORD }} + host: ${{ secrets.SSH_HOST }} + username: ${{ secrets.SSH_USERNAME }} + password: ${{ secrets.SSH_PASSWORD }} script: | gunzip -c /tmp/golang_backend.tar.gz | docker load rm -f /tmp/golang_backend.tar.gz From a59663513e54a9c914bca2c6861eb1f2c76fa9d9 Mon Sep 17 00:00:00 2001 From: ekedonald <143762587+ekedonald@users.noreply.github.com> Date: Fri, 9 Aug 2024 03:15:39 +0100 Subject: [PATCH 41/45] Update dev-deploy.yml Signed-off-by: ekedonald <143762587+ekedonald@users.noreply.github.com> --- .github/workflows/dev-deploy.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/dev-deploy.yml b/.github/workflows/dev-deploy.yml index 42212ec0..11e9a5eb 100644 --- a/.github/workflows/dev-deploy.yml +++ b/.github/workflows/dev-deploy.yml @@ -59,4 +59,6 @@ jobs: gunzip -c /tmp/golang_backend.tar.gz | docker load rm -f /tmp/golang_backend.tar.gz cd ~/hng_boilerplate_golang_web + git checkout dev + git pull origin dev docker compose -p golang up -d From d828c68bf27ee0ab465a3c12bf7d21e3c4d3750e Mon Sep 17 00:00:00 2001 From: Osinachi Chukwujama Date: Fri, 9 Aug 2024 07:07:13 +0100 Subject: [PATCH 42/45] chore: setup successful docker builds --- .github/workflows/development.yml | 1 + .github/workflows/development_pr_deploy.yml | 1 + .github/workflows/production.yml | 1 + .github/workflows/production_pr_deploy.yml | 1 + .github/workflows/staging.yml | 1 + Dockerfile | 9 ++--- docker-compose-production.yml | 34 +++++++---------- docker-compose-staging.yml | 34 +++++++---------- docker-compose.yml | 30 ++++++++------- scripts/map_envs.sh | 41 +++++++++++++++++++++ 10 files changed, 95 insertions(+), 58 deletions(-) create mode 100644 scripts/map_envs.sh diff --git a/.github/workflows/development.yml b/.github/workflows/development.yml index 92488693..76754a49 100644 --- a/.github/workflows/development.yml +++ b/.github/workflows/development.yml @@ -178,4 +178,5 @@ jobs: gunzip -c /tmp/golang_dev.tar.gz | docker load rm -f /tmp/golang_dev.tar.gz cd ~/deployments/development + bash ./scripts/map_envs.sh app.env POSTGRES_USER=DB_USER POSTGRES_DB=DB_NAME POSTGRES_PASSWORD=PASSWORD docker compose -f docker-compose.yml up -d diff --git a/.github/workflows/development_pr_deploy.yml b/.github/workflows/development_pr_deploy.yml index 5d7a1e6a..71aaaebd 100644 --- a/.github/workflows/development_pr_deploy.yml +++ b/.github/workflows/development_pr_deploy.yml @@ -1,6 +1,7 @@ name: PR Deploy for Development on: pull_request: + branches: [dev] types: [opened, synchronize, reopened, closed] workflow_dispatch: diff --git a/.github/workflows/production.yml b/.github/workflows/production.yml index a9504a21..04d09f9a 100644 --- a/.github/workflows/production.yml +++ b/.github/workflows/production.yml @@ -181,4 +181,5 @@ jobs: gunzip -c /tmp/golang_prod.tar.gz | docker load rm -f /tmp/golang_prod.tar.gz cd ~/deployments/production + bash ./scripts/map_envs.sh app.env POSTGRES_USER=DB_USER POSTGRES_DB=DB_NAME POSTGRES_PASSWORD=PASSWORD docker compose -f docker-compose-production.yml up -d \ No newline at end of file diff --git a/.github/workflows/production_pr_deploy.yml b/.github/workflows/production_pr_deploy.yml index 917bb5d5..72015e17 100644 --- a/.github/workflows/production_pr_deploy.yml +++ b/.github/workflows/production_pr_deploy.yml @@ -1,6 +1,7 @@ name: PR Deploy for Production on: pull_request: + branches: [main] types: [opened, synchronize, reopened, closed] workflow_dispatch: diff --git a/.github/workflows/staging.yml b/.github/workflows/staging.yml index 6e85dbcc..809d87ca 100644 --- a/.github/workflows/staging.yml +++ b/.github/workflows/staging.yml @@ -181,4 +181,5 @@ jobs: gunzip -c /tmp/golang_staging.tar.gz | docker load rm -f /tmp/golang_staging.tar.gz cd ~/deployments/staging + bash ./scripts/map_envs.sh app.env POSTGRES_USER=DB_USER POSTGRES_DB=DB_NAME POSTGRES_PASSWORD=PASSWORD docker compose -f docker-compose-staging.yml up -d \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 0998b822..0cfe4aea 100644 --- a/Dockerfile +++ b/Dockerfile @@ -14,8 +14,7 @@ RUN go mod download && go mod verify COPY . . # Build the Go app -RUN if test -e app.env; then echo 'found app.env'; else mv app-sample.env app.env; fi; \ - go build -v -o /dist/app-name +RUN go build -v -o /dist/golang_app # Wait-for-it stage FROM alpine:3.17 as wait @@ -27,12 +26,12 @@ RUN chmod +x /wait-for-it.sh FROM alpine:3.17 WORKDIR /usr/src/app COPY --from=build /usr/src/app ./ -COPY --from=build /dist/app-name /usr/local/bin/app-name +COPY --from=build /dist/golang_app /usr/local/bin/golang_app COPY --from=wait /wait-for-it.sh /wait-for-it.sh # Install bash (required for wait-for-it script) RUN apk add --no-cache bash # Wait for DB and Redis, then start the application -# CMD /wait-for-it.sh $DB_HOST:$DB_PORT -t 10 -- /wait-for-it.sh $REDIS_HOST:$REDIS_PORT -t 10 -- app-name -CMD app-name \ No newline at end of file +# CMD /wait-for-it.sh $DB_HOST:$DB_PORT -t 10 -- /wait-for-it.sh $REDIS_HOST:$REDIS_PORT -t 10 -- golang_app +CMD golang_app \ No newline at end of file diff --git a/docker-compose-production.yml b/docker-compose-production.yml index f367771f..a6b568e2 100644 --- a/docker-compose-production.yml +++ b/docker-compose-production.yml @@ -3,42 +3,36 @@ name: golang_prod services: db: image: postgres:16 - environment: - POSTGRES_USER: production_user - POSTGRES_PASSWORD: password - POSTGRES_DB: production_db - POSTGRES_PORT: 5432 + env_file: + - app.env volumes: - - db_data:/var/lib/postgresql/data + - ../pgsql_volumes/golang_prod/:/var/lib/postgresql/data healthcheck: - test: ["CMD-SHELL", "pg_isready -U postgres"] - interval: 10s - timeout: 10s - retries: 2 + test: ["CMD-SHELL", "pg_isready -U production_user"] + interval: 10s + timeout: 5s + retries: 2 redis: image: redis:latest backend: - image: ${COMPOSE_PROJECT_NAME} + image: golang_prod build: context: . depends_on: - - db - - redis - ports: - - "8002:7002" + db: + condition: service_healthy + redis: + condition: service_started env_file: - app.env nginx: image: nginx:latest - ports: - - "8117:80" depends_on: - backend + ports: + - "7002:80" volumes: - ./nginx/nginx.prod.conf:/etc/nginx/nginx.conf - -volumes: - db_data: \ No newline at end of file diff --git a/docker-compose-staging.yml b/docker-compose-staging.yml index fdafdf5f..5b3529c9 100644 --- a/docker-compose-staging.yml +++ b/docker-compose-staging.yml @@ -3,42 +3,36 @@ name: golang_staging services: db: image: postgres:16 - environment: - POSTGRES_USER: postgres - POSTGRES_PASSWORD: password - POSTGRES_DB: db_name - POSTGRES_PORT: 5432 + env_file: + - app.env volumes: - - db_data:/var/lib/postgresql/data + - ../pgsql_volumes/golang_staging/:/var/lib/postgresql/data healthcheck: - test: ["CMD-SHELL", "pg_isready -U postgres"] - interval: 10s - timeout: 10s - retries: 2 + test: ["CMD-SHELL", "pg_isready -U staging_user"] + interval: 10s + timeout: 5s + retries: 2 redis: image: redis:latest backend: - image: ${COMPOSE_PROJECT_NAME} + image: golang_staging build: context: . depends_on: - - db - - redis - ports: - - "8001:7010" + db: + condition: service_healthy + redis: + condition: service_started env_file: - app.env nginx: image: nginx:latest - ports: - - "8116:80" depends_on: - backend + ports: + - "7001:80" volumes: - ./nginx/nginx.staging.conf:/etc/nginx/nginx.conf - -volumes: - db_data: \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index 809d2a28..b9db2242 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,34 +1,38 @@ -name: golang +name: golang_dev services: db: image: postgres:16 env_file: - - ./app.env + - app.env volumes: - - db_data:/var/lib/postgresql/data + - ../pgsql_volumes/golang_dev/:/var/lib/postgresql/data + healthcheck: + test: ["CMD-SHELL", "pg_isready -U development_user"] + interval: 10s + timeout: 5s + retries: 2 redis: image: redis:latest backend: - image: golang_backend + image: golang_dev build: context: . depends_on: - - db - - redis + db: + condition: service_healthy + redis: + condition: service_started env_file: - - ./app.env - + - app.env + nginx: image: nginx:latest - ports: - - "7000:80" depends_on: - backend + ports: + - "7000:80" volumes: - ./nginx.conf:/etc/nginx/nginx.conf - -volumes: - db_data: diff --git a/scripts/map_envs.sh b/scripts/map_envs.sh new file mode 100644 index 00000000..6c8f3967 --- /dev/null +++ b/scripts/map_envs.sh @@ -0,0 +1,41 @@ +#!/bin/bash + +if [ "$#" -lt 2 ]; then + echo "Description: creates new environment variables from existing ones" + echo "Usage: $0 .env_file NEW_ENV1=EXISTING_ENV1 [NEW_ENV2=EXISTING_ENV2 ...]" + exit 1 +fi + +env_file="$1" +shift + +if [ ! -f "$env_file" ]; then + echo "Error: .env file '$env_file' not found." + exit 1 +fi + +export $(grep -v '^#' "$env_file" | xargs) + +for arg in "$@"; do + IFS='=' read -r new_env existing_env <<< "$arg" + + if [ -z "${!existing_env}" ]; then + echo "Warning: Existing environment variable '$existing_env' is not set." + continue + fi + + # Get the value of the existing environment variable + value="${!existing_env}" + + # Export the new environment variable with the value of the existing one + export "$new_env=$value" +done + +# Write the new environment variables to the .env file +{ + echo + for arg in "$@"; do + IFS='=' read -r new_env _ <<< "$arg" + echo "$new_env=${!new_env}" + done +} >> "$env_file" From 6b1d15b5da6bd459092c8ea1375cb0288879b14f Mon Sep 17 00:00:00 2001 From: Osinachi Chukwujama Date: Fri, 9 Aug 2024 07:28:22 +0100 Subject: [PATCH 43/45] chore: update dev deploy --- .github/workflows/dev-deploy.yml | 67 +++++++++++++++++-------------- .github/workflows/development.yml | 62 ---------------------------- 2 files changed, 37 insertions(+), 92 deletions(-) diff --git a/.github/workflows/dev-deploy.yml b/.github/workflows/dev-deploy.yml index 11e9a5eb..6437ab95 100644 --- a/.github/workflows/dev-deploy.yml +++ b/.github/workflows/dev-deploy.yml @@ -7,58 +7,65 @@ on: - dev jobs: - build: + build_docker_image: runs-on: ubuntu-latest - if: github.event.repository.fork == false + if: github.event_name == 'push' steps: - - uses: actions/checkout@v2 - + - name: Checkout code + uses: actions/checkout@v4 - name: Build Docker image - run: docker build -t golang_backend . - + run: docker build -t golang_dev . - name: Save and compress Docker image - run: docker save golang_backend | gzip > golang_backend.tar.gz - + run: docker save golang_dev | gzip > golang_dev.tar.gz - name: Upload Docker image - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 with: - name: golang_backend - path: golang_backend.tar.gz - - deploy: - needs: build - runs-on: ubuntu-latest + name: golang_dev + path: golang_dev.tar.gz + upload_docker_image: + runs-on: ubuntu-latest + needs: build_docker_image + if: github.event.repository.fork == false environment: name: "development" url: ${{ vars.URL }} - steps: - name: Download Docker image - uses: actions/download-artifact@v2 + uses: actions/download-artifact@v4 with: - name: golang_backend + name: golang_dev path: . - name: Copy image to server - uses: appleboy/scp-action@master + uses: appleboy/scp-action@v0.1.7 with: host: ${{ secrets.SSH_HOST }} username: ${{ secrets.SSH_USERNAME }} password: ${{ secrets.SSH_PASSWORD }} - source: golang_backend.tar.gz + source: golang_dev.tar.gz target: "/tmp" + + run_docker_container: + runs-on: ubuntu-latest + needs: upload_docker_image + if: github.event.repository.fork == false + environment: development + env: + SSH_USERNAME: ${{ secrets.SSH_USERNAME }} + SSH_HOST: ${{ secrets.SSH_HOST }} + SSH_PASSWORD: ${{ secrets.SSH_PASSWORD }} + steps: - name: Deploy on server - uses: appleboy/ssh-action@master + uses: appleboy/ssh-action@v1.0.3 with: - host: ${{ secrets.SSH_HOST }} - username: ${{ secrets.SSH_USERNAME }} - password: ${{ secrets.SSH_PASSWORD }} + host: ${{ env.HOST }} + username: ${{ env.USERNAME }} + password: ${{ env.PASSWORD }} script: | - gunzip -c /tmp/golang_backend.tar.gz | docker load - rm -f /tmp/golang_backend.tar.gz - cd ~/hng_boilerplate_golang_web - git checkout dev - git pull origin dev - docker compose -p golang up -d + gunzip -c /tmp/golang_dev.tar.gz | docker load + rm -f /tmp/golang_dev.tar.gz + cd ~/deployments/development + bash ./scripts/map_envs.sh app.env POSTGRES_USER=DB_USER POSTGRES_DB=DB_NAME POSTGRES_PASSWORD=PASSWORD + docker compose -f docker-compose.yml up -d diff --git a/.github/workflows/development.yml b/.github/workflows/development.yml index 76754a49..3683235e 100644 --- a/.github/workflows/development.yml +++ b/.github/workflows/development.yml @@ -6,45 +6,6 @@ on: - dev jobs: - build_docker_image: - runs-on: ubuntu-latest - if: github.event_name == 'push' - steps: - - name: Checkout code - uses: actions/checkout@v4 - - name: Build Docker image - run: docker build -t golang_dev . - - name: Save and compress Docker image - run: docker save golang_dev | gzip > golang_dev.tar.gz - - name: Upload Docker image - uses: actions/upload-artifact@v4 - with: - name: golang_dev - path: golang_dev.tar.gz - - upload_docker_image: - runs-on: ubuntu-latest - needs: build_docker_image - if: github.event.repository.fork == false && github.event_name == 'push' - environment: - name: "development" - url: ${{ vars.URL }} - steps: - - name: Download Docker image - uses: actions/download-artifact@v4 - with: - name: golang_dev - path: . - - - name: Copy image to server - uses: appleboy/scp-action@v0.1.7 - with: - host: ${{ secrets.SSH_HOST }} - username: ${{ secrets.SSH_USERNAME }} - password: ${{ secrets.SSH_PASSWORD }} - source: golang_dev.tar.gz - target: "/tmp" - build_and_test: runs-on: ubuntu-latest environment: development @@ -157,26 +118,3 @@ jobs: MIGRATE=true - run_docker_container: - runs-on: ubuntu-latest - needs: upload_docker_image - if: github.event.repository.fork == false && github.event_name == 'push' - environment: development - env: - SSH_USERNAME: ${{ secrets.SSH_USERNAME }} - SSH_HOST: ${{ secrets.SSH_HOST }} - SSH_PASSWORD: ${{ secrets.SSH_PASSWORD }} - - steps: - - name: Deploy on server - uses: appleboy/ssh-action@v1.0.3 - with: - host: ${{ env.HOST }} - username: ${{ env.USERNAME }} - password: ${{ env.PASSWORD }} - script: | - gunzip -c /tmp/golang_dev.tar.gz | docker load - rm -f /tmp/golang_dev.tar.gz - cd ~/deployments/development - bash ./scripts/map_envs.sh app.env POSTGRES_USER=DB_USER POSTGRES_DB=DB_NAME POSTGRES_PASSWORD=PASSWORD - docker compose -f docker-compose.yml up -d From 94661bfbfeccd87ff700b7bebb5842b1b6bf408a Mon Sep 17 00:00:00 2001 From: Osinachi Chukwujama Date: Fri, 9 Aug 2024 07:52:33 +0100 Subject: [PATCH 44/45] chore: augment dev deploy set up script --- .github/workflows/dev-deploy.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/dev-deploy.yml b/.github/workflows/dev-deploy.yml index 6437ab95..2780b136 100644 --- a/.github/workflows/dev-deploy.yml +++ b/.github/workflows/dev-deploy.yml @@ -67,5 +67,8 @@ jobs: gunzip -c /tmp/golang_dev.tar.gz | docker load rm -f /tmp/golang_dev.tar.gz cd ~/deployments/development - bash ./scripts/map_envs.sh app.env POSTGRES_USER=DB_USER POSTGRES_DB=DB_NAME POSTGRES_PASSWORD=PASSWORD + git reset --hard + git pull origin dev + bash ./scripts/map_envs.sh app.env POSTGRES_USER=USERNAME POSTGRES_DB=DB_NAME POSTGRES_PASSWORD=PASSWORD + docker compose down docker compose -f docker-compose.yml up -d From 9f80e03a5fd9857b6f382b46f9349678338a4baf Mon Sep 17 00:00:00 2001 From: Osinachi Chukwujama Date: Fri, 9 Aug 2024 09:21:17 +0100 Subject: [PATCH 45/45] chore: fix workflow dispatch job --- .github/workflows/dev-deploy.yml | 2 -- docker-compose.yml | 4 ++++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/dev-deploy.yml b/.github/workflows/dev-deploy.yml index 2780b136..935b1dfc 100644 --- a/.github/workflows/dev-deploy.yml +++ b/.github/workflows/dev-deploy.yml @@ -69,6 +69,4 @@ jobs: cd ~/deployments/development git reset --hard git pull origin dev - bash ./scripts/map_envs.sh app.env POSTGRES_USER=USERNAME POSTGRES_DB=DB_NAME POSTGRES_PASSWORD=PASSWORD - docker compose down docker compose -f docker-compose.yml up -d diff --git a/docker-compose.yml b/docker-compose.yml index b9db2242..8c5ec249 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -5,6 +5,10 @@ services: image: postgres:16 env_file: - app.env + environment: + POSTGRES_DB: development_db + POSTGRES_PASSWORD: password + POSTGRES_USER: development_user volumes: - ../pgsql_volumes/golang_dev/:/var/lib/postgresql/data healthcheck: