Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/64/user role enum #70

Merged
merged 6 commits into from
Nov 18, 2024
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .sqlc/migrations/20241108045258_create_users_table.sql
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,4 @@ CREATE TABLE users (
-- +goose Down
-- +goose StatementBegin
DROP TABLE users;
-- +goose StatementEnd
-- +goose StatementEnd
17 changes: 17 additions & 0 deletions .sqlc/migrations/20241116191633_user_role_enum.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
-- +goose Up
-- +goose StatementBegin
BEGIN;

CREATE TYPE user_role AS ENUM ('admin', 'startup_owner', 'investor');

COMMIT;
-- +goose StatementEnd

-- +goose Down
-- +goose StatementBegin
BEGIN;

DROP TYPE user_role;

COMMIT;
-- +goose StatementEnd
29 changes: 29 additions & 0 deletions .sqlc/migrations/20241116192218_alter_users_role_col.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
-- +goose Up
-- +goose StatementBegin
BEGIN;

ALTER TABLE users ADD COLUMN role_enum user_role NOT NULL;

UPDATE users
SET role_enum = role::user_role;

ALTER TABLE users DROP COLUMN role;
ALTER TABLE users RENAME COLUMN role_enum TO role;

COMMIT;
-- +goose StatementEnd

-- +goose Down
-- +goose StatementBegin
BEGIN;

ALTER TABLE users ADD COLUMN role_varchar VARCHAR(50) NOT NULL;

UPDATE users
SET role_varchar = role::text;

ALTER TABLE users DROP COLUMN role;
ALTER TABLE users RENAME COLUMN role_varchar TO role;

COMMIT;
-- +goose StatementEnd
65 changes: 64 additions & 1 deletion db/models.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 6 additions & 6 deletions db/users.sql.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

33 changes: 32 additions & 1 deletion internal/middleware/req_validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"net/http"
"reflect"

"github.com/KonferCA/NoKap/db"
"github.com/go-playground/validator/v10"
"github.com/labstack/echo/v4"
"github.com/rs/zerolog/log"
Expand All @@ -29,7 +30,9 @@ func (rv *RequestBodyValidator) Validate(i interface{}) error {
// Creates a new request validator that can be set to an Echo instance
// and used for validating request bodies with c.Validate()
func NewRequestBodyValidator() *RequestBodyValidator {
return &RequestBodyValidator{validator: validator.New()}
v := validator.New()
v.RegisterValidation("valid_user_role", validateUserRole)
return &RequestBodyValidator{validator: v}
}

// Middleware that validates the incoming request body with the given structType.
Expand Down Expand Up @@ -57,3 +60,31 @@ func ValidateRequestBody(structType reflect.Type) echo.MiddlewareFunc {
}
}
}

// validateUserRole validates the "valid_user_role" tag using the
// the generated valid method from SQLc.
func validateUserRole(fl validator.FieldLevel) bool {
field := fl.Field()

// handle string type
if field.Kind() == reflect.String {
str := field.String()
ur := db.UserRole(str)
return ur.Valid()
}

// handle db.UserRole type
if field.Type() == reflect.TypeOf(db.UserRole("")) {
ur := field.Interface().(db.UserRole)
return ur.Valid()

}

// handle pointer to db.UserRole
if field.Type() == reflect.TypeOf((*db.UserRole)(nil)) && !field.IsNil() {
ur := field.Interface().(*db.UserRole)
return ur.Valid()
}

return false
}
33 changes: 16 additions & 17 deletions internal/server/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@ package server
import (
"context"
"net/http"
"reflect"

"github.com/KonferCA/NoKap/db"
"github.com/KonferCA/NoKap/internal/jwt"
mw "github.com/KonferCA/NoKap/internal/middleware"
"github.com/jackc/pgx/v5/pgtype"
"github.com/labstack/echo/v4"
"golang.org/x/crypto/bcrypt"
Expand All @@ -14,21 +16,19 @@ import (
func (s *Server) setupAuthRoutes() {
auth := s.apiV1.Group("/auth")
auth.Use(s.authLimiter.RateLimit()) // special rate limit for auth routes
auth.POST("/signup", s.handleSignup)
auth.POST("/signin", s.handleSignin)
auth.POST("/signup", s.handleSignup, mw.ValidateRequestBody(reflect.TypeOf(SignupRequest{})))
auth.POST("/signin", s.handleSignin, mw.ValidateRequestBody(reflect.TypeOf(SigninRequest{})))
}

func (s *Server) handleSignup(c echo.Context) error {
var req SignupRequest
if err := c.Bind(&req); err != nil {
return echo.NewHTTPError(http.StatusBadRequest, "invalid request body")
var req *SignupRequest
req, ok := c.Get(mw.REQUEST_BODY_KEY).(*SignupRequest)
if !ok {
// not good... no bueno
return echo.NewHTTPError(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
}

if err := c.Validate(&req); err != nil {
return err
}

ctx := context.Background()
ctx := c.Request().Context()
existingUser, err := s.queries.GetUserByEmail(ctx, req.Email)
if err == nil && existingUser.ID != "" {
return echo.NewHTTPError(http.StatusConflict, "email already registered")
Expand Down Expand Up @@ -70,13 +70,12 @@ func (s *Server) handleSignup(c echo.Context) error {
}

func (s *Server) handleSignin(c echo.Context) error {
var req SigninRequest
if err := c.Bind(&req); err != nil {
return echo.NewHTTPError(http.StatusBadRequest, "invalid request body")
}

if err := c.Validate(&req); err != nil {
return err
req, ok := c.Get(mw.REQUEST_BODY_KEY).(*SigninRequest)
if !ok {
// no bueno...
// should never really reach this state since the validator should reject
// the request body if it is not a proper SigninRequest type
return echo.NewHTTPError(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
}

ctx := context.Background()
Expand Down
2 changes: 1 addition & 1 deletion internal/server/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ type SignupRequest struct {
Password string `json:"password" validate:"required,min=8"`
FirstName string `json:"first_name" validate:"required"`
LastName string `json:"last_name" validate:"required"`
Role string `json:"role" validate:"required,oneof=startup_owner admin investor"`
Role string `json:"role" validate:"required,valid_user_role"`
}

type SigninRequest struct {
Expand Down
4 changes: 4 additions & 0 deletions sqlc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ sql:
out: "db"
sql_package: "pgx/v5"
emit_pointers_for_null_types: true
emit_enum_valid_method: true
emit_all_enum_values: true
overrides:
- db_type: "uuid"
go_type: "string"
Expand All @@ -20,3 +22,5 @@ sql:
go_type: "string"
- db_type: "pg_catalog.timestamp"
go_type: "time.Time"
- db_type: "user_role"
go_type: "string"
juancwu marked this conversation as resolved.
Show resolved Hide resolved
Loading