diff --git a/api/server.go b/api/server.go index bea424e..0c2d2f0 100644 --- a/api/server.go +++ b/api/server.go @@ -13,7 +13,7 @@ type Server struct { config util.Config store db.Store tokenMaker token.Maker - router *gin.Engine + Router *gin.Engine } func NewServer(config util.Config, store db.Store) (*Server, error) { @@ -35,11 +35,11 @@ func NewServer(config util.Config, store db.Store) (*Server, error) { func (server *Server) getRoutes() { router := gin.Default() - router.POST("/user/create", server.createUser) + router.POST("/user/signup", server.createUser) - server.router = router + server.Router = router } func (server *Server) Start(address string) error { - return server.router.Run(address) + return server.Router.Run(address) } diff --git a/api/user.go b/api/user.go index 1e3e6f8..61452e4 100644 --- a/api/user.go +++ b/api/user.go @@ -1,27 +1,106 @@ package api import ( + "database/sql" + "errors" + "fmt" "net/http" + db "git.nochill.in/nochill/hiling_go/db/sqlc" + "git.nochill.in/nochill/hiling_go/util" "github.com/gin-gonic/gin" + "github.com/go-playground/validator/v10" + "github.com/lib/pq" ) -type CreateUserRequest struct { - ClientIpV4 string `json:"client_ip" binding:"required"` +type createUserRequest struct { + Username string `json:"username" binding:"required,alphanum"` + Password string `json:"password" binding:"required,min=7"` +} + +type createUserResponse struct { + ID int32 `json:"id"` + Username string `json:"username"` + AvatarPicture string `json:"avatar_picture"` // avatar_url + BannedAt sql.NullTime `json:"banned_at"` + BannedUntil sql.NullTime `json:"banned_until"` + BanReason string `json:"ban_reason"` + IsPermaban sql.NullBool `json:"is_permaban"` + IsAdmin sql.NullBool `json:"is_admin"` + IsCritics sql.NullBool `json:"is_critics"` + IsVerified sql.NullBool `json:"is_verified"` + CreatedAt sql.NullTime `json:"created_at"` + UpdatedAt sql.NullTime `json:"updated_at"` +} + +type ApiError struct { + Field string + Msg string +} + +func msgForTag(field string, param string, tag string) string { + switch tag { + case "min": + return fmt.Sprintf("%s character min %s", field, param) + default: + return fmt.Sprintf("%s %s", field, tag) + } } func (server *Server) createUser(ctx *gin.Context) { - var req CreateUserRequest + var req createUserRequest if err := ctx.ShouldBindJSON(&req); err != nil { - ctx.JSON(http.StatusBadRequest, errorResponse(err, "")) - return + if err != nil { + var ve validator.ValidationErrors + if errors.As(err, &ve) { + out := make([]ApiError, len(ve)) + for i, fe := range ve { + out[i] = ApiError{fe.Field(), msgForTag(fe.Field(), fe.Param(), fe.Tag())} + } + ctx.JSON(http.StatusBadRequest, gin.H{"errors": out}) + } + return + } } - err := server.store.CreateUser(ctx, req.ClientIpV4) + hashedPassword, err := util.HashPassword(req.Password) if err != nil { - ctx.JSON(http.StatusInternalServerError, errorResponse(err, "")) + ctx.JSON(http.StatusInternalServerError, ErrorResponse(err, "Something went wrong")) return } - ctx.Writer.WriteHeader(http.StatusOK) + arg := db.CreateUserParams{ + Username: req.Username, + Password: hashedPassword, + } + + user, err := server.store.CreateUser(ctx, arg) + if err != nil { + if pqErr, ok := err.(*pq.Error); ok { + switch pqErr.Code.Name() { + case "foreign_key_violation", "unique_violation": + ctx.JSON(http.StatusConflict, ErrorResponse(err, "Something went wrong while try to save")) + } + } + + ctx.JSON(http.StatusInternalServerError, ErrorResponse(err, "Something went wrong")) + return + } + + res := createUserResponse{ + ID: user.ID, + Username: user.Username, + AvatarPicture: user.AvatarPicture.String, + BannedAt: sql.NullTime{Valid: user.BannedAt.Valid, Time: user.BannedAt.Time}, + BannedUntil: sql.NullTime{Valid: user.BannedUntil.Valid, Time: user.BannedUntil.Time}, + BanReason: user.BanReason.String, + IsPermaban: user.IsPermaban, + IsAdmin: user.IsAdmin, + IsCritics: user.IsCritics, + IsVerified: user.IsVerified, + CreatedAt: sql.NullTime{Valid: true, Time: user.CreatedAt.Time}, + UpdatedAt: sql.NullTime{Valid: true, Time: user.UpdatedAt.Time}, + } + + ctx.JSON(http.StatusOK, res) } diff --git a/util/config.go b/util/config.go index 5edce58..deaf5c0 100644 --- a/util/config.go +++ b/util/config.go @@ -9,6 +9,7 @@ import ( type Config struct { DBDriver string `mapstructure:"DB_TYPE"` DBSource string `mapstructure:"DB_SOURCE"` + DBSourceTest string `mapstructure:"DB_SOURCE_TEST"` ServerAddress string `mapstructure:"SERVER_ADDRESS"` TokenSymmetricKey string `mapstructure:"TOKEN_SYMMETRIC_KEY"` TokenDuration time.Duration `mapstructure:"TOKEN_DURATION"` diff --git a/util/password.go b/util/password.go index 3b7d3f5..7882b2e 100644 --- a/util/password.go +++ b/util/password.go @@ -9,7 +9,7 @@ import ( func HashPassword(password string) (string, error) { hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) if err != nil { - return "", fmt.Errorf("Failed to hash password: %w", err) + return "", fmt.Errorf("failed to hash password: %w", err) } return string(hashedPassword), nil }