Skip to content

Commit

Permalink
Merge pull request #66 from oxiginedev/feat/waitlist
Browse files Browse the repository at this point in the history
Implement waitlist endpoint
  • Loading branch information
NwokoyeChigozie authored Jul 22, 2024
2 parents aa80105 + 9cf3e34 commit 3c85d16
Show file tree
Hide file tree
Showing 8 changed files with 283 additions and 0 deletions.
31 changes: 31 additions & 0 deletions .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<!--- Provide a general summary of your changes in the Title above -->
## Description
<!--- Describe your changes in detail -->
## Related Issue (Link to Github issue)
<!--- This project only accepts pull requests related to open issues -->
<!--- If suggesting a new feature or change, please discuss it in an issue first -->
<!--- If fixing a bug, there should be an issue describing it with steps to reproduce -->
<!--- Please link to the issue here: -->
## How Has This Been Tested?
<!--- Please describe in detail how you tested your changes. -->
<!--- Include details of your testing environment, and the tests you ran to -->
<!--- see how your change affects other areas of the code, etc. -->
## Screenshots (if appropriate - Postman, etc):
## Types of changes
<!--- What types of changes does your code introduce? Put an `x` in all the boxes that apply: -->
- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to change)
## Checklist:
<!--- Go over all the following points, and put an `x` in all the boxes that apply. -->
<!--- If you're unsure about any of these, don't hesitate to ask. We're here to help! -->
- [ ] My code follows the code style of this project.
- [ ] I have updated the documentation accordingly.
- [ ] I have added tests to cover my changes.
- [ ] All new and existing tests passed.
1 change: 1 addition & 0 deletions internal/models/migrations/migrations.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ func AuthMigrationModels() []interface{} {
models.Profile{},
models.Product{},
models.User{},
models.WaitlistUser{},
models.NewsLetter{},
} // an array of db models, example: User{}
}
Expand Down
49 changes: 49 additions & 0 deletions internal/models/waitlist_user.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package models

import (
"errors"
"net/http"
"time"

"github.com/hngprojects/hng_boilerplate_golang_web/pkg/repository/storage/postgresql"
"gorm.io/gorm"
)

var ErrWaitlistUserExist = errors.New("waitlist user exists")

type WaitlistUser struct {
ID string `json:"id"`
Name string `json:"name"`
Email string `gorm:"uniqueIndex" json:"email"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}

type CreateWaitlistUserRequest struct {
Name string `json:"name" validate:"required"`
Email string `json:"email" validate:"required,email"`
}

func (w *WaitlistUser) CreateWaitlistUser(db *gorm.DB) error {
err := postgresql.CreateOneRecord(db, w)
if err != nil {
if errors.Is(err, gorm.ErrDuplicatedKey) {
return ErrWaitlistUserExist
}
}

return err
}

func (w *WaitlistUser) GetWaitlistUserByEmail(db *gorm.DB) (int, error) {
err, nerr := postgresql.SelectOneFromDb(db, &w, "email = ?", w.Email)
if nerr != nil {
return http.StatusBadRequest, nerr
}

if err != nil {
return http.StatusInternalServerError, err
}

return http.StatusOK, nil
}
48 changes: 48 additions & 0 deletions pkg/controller/waitlist/waitlist.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package waitlist

import (
"net/http"

"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/repository/storage"
"github.com/hngprojects/hng_boilerplate_golang_web/services/waitlist"
"github.com/hngprojects/hng_boilerplate_golang_web/utility"
)

type Controller struct {
DB *storage.Database
Logger *utility.Logger
Validator *validator.Validate
}

func (base *Controller) Create(c *gin.Context) {
var (
req = models.CreateWaitlistUserRequest{}
)

err := c.ShouldBindJSON(&req)
if err != nil {
v := utility.BuildErrorResponse(http.StatusBadRequest, "error", "Failed to parse request body", err, nil)
c.JSON(http.StatusBadRequest, v)
return
}

err = base.Validator.Struct(&req)
if err != nil {
v := utility.BuildErrorResponse(http.StatusUnprocessableEntity, "error", "The given data was invalid", utility.ValidationResponse(err, base.Validator), nil)
c.JSON(http.StatusUnprocessableEntity, v)
return
}

data, code, err := waitlist.SignupWaitlistUserService(base.DB.Postgresql, req)
if err != nil {
rd := utility.BuildErrorResponse(code, "error", err.Error(), nil, nil)
c.JSON(code, rd)
return
}

rd := utility.BuildSuccessResponse(code, "waitlist signup successful", data)
c.JSON(code, rd)
}
2 changes: 2 additions & 0 deletions pkg/router/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ 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)
Waitlist(r, ApiVersion, validator, db, logger)
User(r, ApiVersion, validator, db, logger)
Organisation(r, ApiVersion, validator, db, logger)
Newsletter(r, ApiVersion, validator, db, logger)
Expand Down
21 changes: 21 additions & 0 deletions pkg/router/waitlist.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package router

import (
"fmt"

"github.com/gin-gonic/gin"
"github.com/go-playground/validator/v10"
"github.com/hngprojects/hng_boilerplate_golang_web/pkg/controller/waitlist"
"github.com/hngprojects/hng_boilerplate_golang_web/pkg/repository/storage"
"github.com/hngprojects/hng_boilerplate_golang_web/utility"
)

func Waitlist(r *gin.Engine, ApiVersion string, validator *validator.Validate, db *storage.Database, logger *utility.Logger) *gin.Engine {
controller := waitlist.Controller{DB: db, Validator: validator}

waitlistURL := r.Group(fmt.Sprintf("%v", ApiVersion))
{
waitlistURL.POST("/waitlist", controller.Create)
}
return r
}
42 changes: 42 additions & 0 deletions services/waitlist/waitlist.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package waitlist

import (
"errors"
"net/http"
"strings"

"github.com/hngprojects/hng_boilerplate_golang_web/internal/models"
"github.com/hngprojects/hng_boilerplate_golang_web/utility"
"gorm.io/gorm"
)

func SignupWaitlistUserService(db *gorm.DB, req models.CreateWaitlistUserRequest) (*models.WaitlistUser, int, error) {
user := &models.WaitlistUser{
ID: utility.GenerateUUID(),
Name: req.Name,
Email: req.Email,
}

if req.Email != "" {
req.Email = strings.ToLower(req.Email)

existingUser := &models.WaitlistUser{Email: req.Email}
code, err := existingUser.GetWaitlistUserByEmail(db)
if err != nil {
return nil, code, models.ErrWaitlistUserExist
}
}

err := user.CreateWaitlistUser(db)
if err != nil {
code := http.StatusInternalServerError
if errors.Is(err, models.ErrWaitlistUserExist) {
code = http.StatusBadRequest
}
return nil, code, err
}

//@TODO: implement email sending her

return user, http.StatusCreated, nil
}
89 changes: 89 additions & 0 deletions tests/test_waitlist/waitlist_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package test_waitlist

import (
"bytes"
"encoding/json"
"net/http"
"net/http/httptest"
"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/waitlist"
"github.com/hngprojects/hng_boilerplate_golang_web/pkg/repository/storage"
"github.com/hngprojects/hng_boilerplate_golang_web/tests"
)

func TestWailistSignup(t *testing.T) {
gin.SetMode(gin.TestMode)
logger := tests.Setup()
validate := validator.New()
db := storage.Connection()

ttests := []struct {
Name string
Request models.CreateWaitlistUserRequest
ExpectedCode int
ExpectedMessage string
}{
{
Name: "user can signup on waitlist",
Request: models.CreateWaitlistUserRequest{
Name: "Tester",
Email: "[email protected]",
},
ExpectedCode: http.StatusCreated,
ExpectedMessage: "waitlist signup successful",
},
{
Name: "user can not signup with duplicate email",
Request: models.CreateWaitlistUserRequest{
Name: "Tester",
Email: "[email protected]",
},
ExpectedCode: http.StatusBadRequest,
ExpectedMessage: "waitlist user exists",
},
}

wc := waitlist.Controller{DB: db, Logger: logger, Validator: validate}

for _, tt := range ttests {
r := gin.Default()

r.POST("/api/v1/waitlist", wc.Create)

t.Run(tt.Name, func(t *testing.T) {
var buf bytes.Buffer

err := json.NewEncoder(&buf).Encode(tt.Request)
if err != nil {
t.Fatal(err)
}

req, err := http.NewRequest(http.MethodPost, "/api/v1/waitlist", &buf)
if err != nil {
t.Fatal(err)
}

req.Header.Set("Content-Type", "application/json")

hr := httptest.NewRecorder()
r.ServeHTTP(hr, req)

tests.AssertStatusCode(t, hr.Code, tt.ExpectedCode)

data := tests.ParseResponse(hr)

if tt.ExpectedMessage != "" {
message := data["message"]
if message != nil {
tests.AssertResponseMessage(t, message.(string), tt.ExpectedMessage)
} else {
tests.AssertResponseMessage(t, "", tt.ExpectedMessage)
}
}
})
}
}

0 comments on commit 3c85d16

Please sign in to comment.