-
+
Sign up
-
+
Log in
diff --git a/front/src/SignUp.vue b/front/src/SignUp.vue
new file mode 100644
index 0000000..d8df471
--- /dev/null
+++ b/front/src/SignUp.vue
@@ -0,0 +1,71 @@
+
+
+
+
+
+
+
diff --git a/front/src/api.js b/front/src/api.js
index 79ffd7b..c8acbf8 100644
--- a/front/src/api.js
+++ b/front/src/api.js
@@ -27,3 +27,13 @@ export function postBook(book) {
body: JSON.stringify(book.value)
})
}
+
+export function postSignup(user) {
+ return fetch(baseUrl + '/auth/signup', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json'
+ },
+ body: JSON.stringify(user.value)
+ })
+}
diff --git a/front/src/main.js b/front/src/main.js
index f71efad..c9c2219 100644
--- a/front/src/main.js
+++ b/front/src/main.js
@@ -3,11 +3,13 @@ import { createRouter, createWebHistory } from 'vue-router'
import App from './App.vue'
import BooksBrowser from './BooksBrowser.vue'
import AddBook from './AddBook.vue'
+import SignUp from './SignUp.vue'
const routes = [
{ path: '/', component: BooksBrowser },
{ path: '/add', component: AddBook },
+ { path: '/signup', component: SignUp },
]
export const router = createRouter({
diff --git a/internal/api/dto.go b/internal/api/dto.go
index 7bd917a..0ae5657 100644
--- a/internal/api/dto.go
+++ b/internal/api/dto.go
@@ -5,3 +5,8 @@ type bookPostCreate struct {
Author string `json:"author" binding:"max=100"`
Rating int `json:"rating" binding:"min=0,max=10"`
}
+
+type userPostCreate struct {
+ Username string `json:"username" binding:"required,min=2,max=20"`
+ Password string `json:"password" binding:"required,min=6,max=100"`
+}
diff --git a/internal/api/mapper.go b/internal/api/mapper.go
index fbec2a6..7b602a6 100644
--- a/internal/api/mapper.go
+++ b/internal/api/mapper.go
@@ -1,9 +1,27 @@
package api
-import "git.artlef.fr/PersonalLibraryManager/internal/model"
+import (
+ "git.artlef.fr/PersonalLibraryManager/internal/model"
+ "golang.org/x/crypto/bcrypt"
+)
func (b bookPostCreate) toBook() model.Book {
- return model.Book{Title: b.Title,
+ return model.Book{
+ Title: b.Title,
Author: b.Author,
- Rating: b.Rating}
+ Rating: b.Rating,
+ }
+}
+
+func (u userPostCreate) toUser() (model.User, error) {
+ user := model.User{
+ Name: u.Username,
+ Password: "",
+ }
+ hashedPassword, err := bcrypt.GenerateFromPassword([]byte(u.Password), bcrypt.DefaultCost)
+ if err != nil {
+ return user, err
+ }
+ user.Password = string(hashedPassword)
+ return user, nil
}
diff --git a/internal/api/routes.go b/internal/api/routes.go
index ad49b6d..fbcecda 100644
--- a/internal/api/routes.go
+++ b/internal/api/routes.go
@@ -20,15 +20,47 @@ func PostBookHandler(c *gin.Context, db *gorm.DB) {
var book bookPostCreate
err := c.ShouldBindJSON(&book)
if err != nil {
- var ve validator.ValidationErrors
- if errors.As(err, &ve) {
- c.JSON(http.StatusBadRequest, getValidationErrors(&ve))
- } else {
- c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
- }
+ manageBindingError(c, err)
return
}
bookDb := book.toBook()
- db.Model(&model.Book{}).Save(&bookDb)
+ err = db.Model(&model.Book{}).Save(&bookDb).Error
+ if err != nil {
+ manageDefaultError(c, err)
+ return
+ }
c.String(200, "Success")
}
+
+func PostUserHandler(c *gin.Context, db *gorm.DB) {
+ var user userPostCreate
+ err := c.ShouldBindJSON(&user)
+ if err != nil {
+ manageBindingError(c, err)
+ return
+ }
+ userDb, err := user.toUser()
+ if err != nil {
+ manageDefaultError(c, err)
+ return
+ }
+ err = db.Model(&model.User{}).Save(&userDb).Error
+ if err != nil {
+ manageDefaultError(c, err)
+ return
+ }
+ c.String(200, "Success")
+}
+
+func manageBindingError(c *gin.Context, err error) {
+ var ve validator.ValidationErrors
+ if errors.As(err, &ve) {
+ c.JSON(http.StatusBadRequest, getValidationErrors(&ve))
+ } else {
+ manageDefaultError(c, err)
+ }
+}
+
+func manageDefaultError(c *gin.Context, err error) {
+ c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
+}
diff --git a/internal/api/validator.go b/internal/api/validator.go
index a63cf3c..6e8d8b0 100644
--- a/internal/api/validator.go
+++ b/internal/api/validator.go
@@ -27,6 +27,8 @@ func computeValidationMessage(fe *validator.FieldError) string {
switch tag {
case "required":
return fmt.Sprintf("%s is required.", (*fe).Field())
+ case "min":
+ return fmt.Sprintf("%s is not long enough. It should be at least %s characters.", (*fe).Field(), (*fe).Param())
case "max":
return fmt.Sprintf("%s is too long. It should be under %s characters.", (*fe).Field(), (*fe).Param())
default:
diff --git a/internal/db/init.go b/internal/db/init.go
index 70bee6d..d729392 100644
--- a/internal/db/init.go
+++ b/internal/db/init.go
@@ -19,6 +19,7 @@ func Initdb(databasePath string, demoDataPath string) *gorm.DB {
}
// Migrate the schema
db.AutoMigrate(&model.Book{})
+ db.AutoMigrate(&model.User{})
var book model.Book
queryResult := db.Limit(1).Find(&book)
if queryResult.RowsAffected == 0 && demoDataPath != "" {
diff --git a/internal/model/model.go b/internal/model/book.go
similarity index 56%
rename from internal/model/model.go
rename to internal/model/book.go
index 0e424a1..d7dc7a9 100644
--- a/internal/model/model.go
+++ b/internal/model/book.go
@@ -4,7 +4,7 @@ import "gorm.io/gorm"
type Book struct {
gorm.Model
- Title string `json:"title"`
+ Title string `json:"title" gorm:"not null"`
Author string `json:"author"`
- Rating int `json:"rating"`
+ Rating int `json:"rating"`
}
diff --git a/internal/model/user.go b/internal/model/user.go
new file mode 100644
index 0000000..0f5ae43
--- /dev/null
+++ b/internal/model/user.go
@@ -0,0 +1,9 @@
+package model
+
+import "gorm.io/gorm"
+
+type User struct {
+ gorm.Model
+ Name string `gorm:"index;uniqueIndex"`
+ Password string
+}
diff --git a/main.go b/main.go
index bb09d61..7c480c2 100644
--- a/main.go
+++ b/main.go
@@ -25,5 +25,8 @@ func setup(config *config.Config) *gin.Engine {
r.POST("/book", func(c *gin.Context) {
api.PostBookHandler(c, db)
})
+ r.POST("/auth/signup", func(c *gin.Context) {
+ api.PostUserHandler(c, db)
+ })
return r
}
diff --git a/user_test.go b/user_test.go
new file mode 100644
index 0000000..79f3511
--- /dev/null
+++ b/user_test.go
@@ -0,0 +1,67 @@
+package main
+
+import (
+ "net/http"
+ "net/http/httptest"
+ "strings"
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestPostUserHandler_Working(t *testing.T) {
+ userJson :=
+ `{
+ "username": "artlef",
+ "password": "123456789"
+ }`
+ testPostUserHandler(t, userJson, 200)
+}
+
+func TestPostUserHandler_UsernameTooSmall(t *testing.T) {
+ userJson :=
+ `{
+ "username": "a",
+ "password": "123456789"
+ }`
+ testPostUserHandler(t, userJson, 400)
+}
+
+func TestPostUserHandler_UsernameTooBig(t *testing.T) {
+ userJson :=
+ `{
+ "username": "thisusernameistoolong",
+ "password": "123456789"
+ }`
+ testPostUserHandler(t, userJson, 400)
+}
+
+func TestPostUserHandler_PasswordTooSmall(t *testing.T) {
+ userJson :=
+ `{
+ "username": "thisusernameisok",
+ "password": "lol"
+ }`
+ testPostUserHandler(t, userJson, 400)
+}
+
+func TestPostUserHandler_PasswordTooBig(t *testing.T) {
+ userJson :=
+ `{
+ "username": "thisusernameisok",
+ "password": "According to all known laws of aviation, there is no way a bee should be able to fly. Its wings are too small to get its fat little body off the ground."
+ }`
+ testPostUserHandler(t, userJson, 400)
+}
+
+func testPostUserHandler(t *testing.T, userJson string, expectedCode int) {
+ router := testSetup()
+ w := httptest.NewRecorder()
+
+ req, _ := http.NewRequest("POST", "/auth/signup",
+ strings.NewReader(string(userJson)))
+ router.ServeHTTP(w, req)
+
+ assert.Equal(t, expectedCode, w.Code)
+
+}