package routes import ( "errors" "net/http" "strconv" "time" "git.artlef.fr/bibliomane/internal/appcontext" "git.artlef.fr/bibliomane/internal/dto" "git.artlef.fr/bibliomane/internal/model" "git.artlef.fr/bibliomane/internal/myvalidator" "github.com/gin-gonic/gin" "github.com/go-playground/validator/v10" "gorm.io/gorm" ) func PutUserBookHandler(ac appcontext.AppContext) { bookId64, err := strconv.ParseUint(ac.C.Param("id"), 10, 64) bookId := uint(bookId64) if err != nil { ac.C.JSON(http.StatusBadRequest, gin.H{"error": err}) return } err = myvalidator.ValidateId(ac.Db, bookId, &model.Book{}) if err != nil { myvalidator.ReturnErrorsAsJsonResponse(&ac, err) return } user, err := ac.GetAuthenticatedUser() if err != nil { myvalidator.ReturnErrorsAsJsonResponse(&ac, err) return } var userBookPut dto.UserBookPutUpdate err = ac.C.ShouldBindJSON(&userBookPut) if err != nil { myvalidator.ReturnErrorsAsJsonResponse(&ac, err) return } userbook, err := fetchOrCreateUserBook(ac, bookId, &user) if err != nil { myvalidator.ReturnErrorsAsJsonResponse(&ac, err) return } if userBookPut.Read != nil { err = updateReadStatus(&userbook, &userBookPut) if err != nil { myvalidator.ReturnErrorsAsJsonResponse(&ac, err) return } } if userBookPut.WantRead != nil { userbook.WantRead = *userBookPut.WantRead } if userBookPut.StartDate != nil { d, err := parseDate(*userBookPut.StartDate) if err != nil { myvalidator.ReturnErrorsAsJsonResponse(&ac, err) return } userbook.StartReadDate = d } if userBookPut.Rating != nil { err = validateRating(*userBookPut.Rating) if err != nil { myvalidator.ReturnErrorsAsJsonResponse(&ac, err) return } updateRating(&userbook, &userBookPut) } if userBookPut.Review != nil { userbook.Review = *userBookPut.Review } ac.Db.Save(&userbook) ac.C.String(http.StatusOK, "Success") } func validateRating(rating int) error { //struct used for validation var ratingStruct struct { Rating int `validate:"gte=0,lte=10"` } ratingStruct.Rating = rating validate := validator.New() return validate.Struct(ratingStruct) } func updateReadStatus(userbook *model.UserBook, userBookPut *dto.UserBookPutUpdate) error { userbook.Read = *userBookPut.Read if userBookPut.EndDate != nil && *userBookPut.EndDate != "" { d, err := parseDate(*userBookPut.EndDate) if err != nil { return err } userbook.EndReadDate = d } //remove the book from "wanted" list when it is marked as read. if userbook.Read { userbook.WantRead = false } //clear the date when unread if !userbook.Read { userbook.EndReadDate = nil } return nil } func updateRating(userbook *model.UserBook, userBookPut *dto.UserBookPutUpdate) { userbook.Rating = *userBookPut.Rating //if rated, set to "read" (a rating = 0 means unrated) if userbook.Rating > 0 { userbook.Read = true //if set to read, remove want read userbook.WantRead = false } } func parseDate(dateToParse string) (*time.Time, error) { //string equal to "null" to unset value if dateToParse == "null" { return nil, nil } else { startDate, err := time.Parse(time.DateOnly, dateToParse) return &startDate, err } } func fetchOrCreateUserBook(ac appcontext.AppContext, bookId uint, user *model.User) (model.UserBook, error) { var userbook model.UserBook res := ac.Db.Where("user_id = ? AND book_id = ?", user.ID, bookId).First(&userbook) err := res.Error if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { userbook = createUserBook(bookId, user) err = ac.Db.Save(&userbook).Error if err != nil { return userbook, err } return userbook, nil } else { return userbook, err } } else { return userbook, nil } } func createUserBook(bookId uint, user *model.User) model.UserBook { return model.UserBook{ UserID: user.ID, BookID: bookId, Read: false, WantRead: false, Rating: 0, } }