-
Notifications
You must be signed in to change notification settings - Fork 50
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #66 from oxiginedev/feat/waitlist
Implement waitlist endpoint
- Loading branch information
Showing
8 changed files
with
283 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} | ||
} | ||
}) | ||
} | ||
} |