From 7998654ede40253b1ccd1fb820c8f809e84fe85e Mon Sep 17 00:00:00 2001 From: zijiren233 Date: Wed, 18 Oct 2023 20:36:21 +0800 Subject: [PATCH] Feat: translate db error --- internal/bootstrap/db.go | 6 +-- internal/db/movie.go | 44 +++++++++++++++++++--- internal/db/relation.go | 10 ++++- internal/db/room.go | 80 +++++++++++++++++++++++++++++++++++----- internal/db/user.go | 66 +++++++++++++++++++++++++++++---- internal/op/user.go | 2 +- internal/op/users.go | 4 +- 7 files changed, 183 insertions(+), 29 deletions(-) diff --git a/internal/bootstrap/db.go b/internal/bootstrap/db.go index 994ae5d..bad2019 100644 --- a/internal/bootstrap/db.go +++ b/internal/bootstrap/db.go @@ -50,7 +50,7 @@ func InitDatabase(ctx context.Context) error { DontSupportRenameColumn: true, SkipInitializeWithVersion: false, }) - opts = append(opts, &gorm.Config{}) + opts = append(opts, &gorm.Config{TranslateError: true}) case conf.DatabaseTypeSqlite3: var dsn string if conf.Conf.Database.DBName == "memory" || strings.HasPrefix(conf.Conf.Database.DBName, ":memory:") { @@ -67,7 +67,7 @@ func InitDatabase(ctx context.Context) error { log.Infof("sqlite3 database file: %s", conf.Conf.Database.DBName) } dialector = sqlite.Open(dsn) - opts = append(opts, &gorm.Config{}) + opts = append(opts, &gorm.Config{TranslateError: true}) case conf.DatabaseTypePostgres: var dsn string if conf.Conf.Database.Port == 0 { @@ -94,7 +94,7 @@ func InitDatabase(ctx context.Context) error { DSN: dsn, PreferSimpleProtocol: true, }) - opts = append(opts, &gorm.Config{}) + opts = append(opts, &gorm.Config{TranslateError: true}) default: log.Fatalf("unknown database type: %s", conf.Conf.Database.Type) } diff --git a/internal/db/movie.go b/internal/db/movie.go index 5a5ff80..019bb16 100644 --- a/internal/db/movie.go +++ b/internal/db/movie.go @@ -1,7 +1,11 @@ package db import ( + "errors" + "fmt" + "github.com/synctv-org/synctv/internal/model" + "gorm.io/gorm" "gorm.io/gorm/clause" ) @@ -12,36 +16,60 @@ func CreateMovie(movie *model.Movie) error { func GetAllMoviesByRoomID(roomID uint) ([]*model.Movie, error) { movies := []*model.Movie{} err := db.Where("room_id = ?", roomID).Order("position ASC").Find(&movies).Error + if err != nil && errors.Is(err, gorm.ErrRecordNotFound) { + return movies, errors.New("room not found") + } return movies, err } func DeleteMovieByID(roomID, id uint) error { - return db.Unscoped().Where("room_id = ? AND id = ?", roomID, id).Delete(&model.Movie{}).Error + err := db.Unscoped().Where("room_id = ? AND id = ?", roomID, id).Delete(&model.Movie{}).Error + if err != nil && errors.Is(err, gorm.ErrRecordNotFound) { + return errors.New("room or movie not found") + } + return err } -// TODO: delete error func LoadAndDeleteMovieByID(roomID, id uint, columns ...clause.Column) (*model.Movie, error) { movie := &model.Movie{} err := db.Unscoped().Clauses(clause.Returning{Columns: columns}).Where("room_id = ? AND id = ?", roomID, id).Delete(movie).Error + if err != nil && errors.Is(err, gorm.ErrRecordNotFound) { + return movie, errors.New("room or movie not found") + } return movie, err } func DeleteMoviesByRoomID(roomID uint) error { - return db.Unscoped().Where("room_id = ?", roomID).Delete(&model.Movie{}).Error + err := db.Unscoped().Where("room_id = ?", roomID).Delete(&model.Movie{}).Error + if err != nil && errors.Is(err, gorm.ErrRecordNotFound) { + return errors.New("room not found") + } + return err } func LoadAndDeleteMoviesByRoomID(roomID uint, columns ...clause.Column) ([]*model.Movie, error) { movies := []*model.Movie{} err := db.Unscoped().Clauses(clause.Returning{Columns: columns}).Where("room_id = ?", roomID).Delete(&movies).Error + if err != nil && errors.Is(err, gorm.ErrRecordNotFound) { + return nil, errors.New("room not found") + } return movies, err } func UpdateMovie(movie *model.Movie, columns ...clause.Column) error { - return db.Model(movie).Clauses(clause.Returning{Columns: columns}).Where("room_id = ? AND id = ?", movie.RoomID, movie.ID).Updates(movie).Error + err := db.Model(movie).Clauses(clause.Returning{Columns: columns}).Where("room_id = ? AND id = ?", movie.RoomID, movie.ID).Updates(movie).Error + if err != nil && errors.Is(err, gorm.ErrRecordNotFound) { + return errors.New("room or movie not found") + } + return err } func SaveMovie(movie *model.Movie, columns ...clause.Column) error { - return db.Model(movie).Clauses(clause.Returning{Columns: columns}).Where("room_id = ? AND id = ?", movie.RoomID, movie.ID).Save(movie).Error + err := db.Model(movie).Clauses(clause.Returning{Columns: columns}).Where("room_id = ? AND id = ?", movie.RoomID, movie.ID).Save(movie).Error + if err != nil && errors.Is(err, gorm.ErrRecordNotFound) { + return errors.New("room or movie not found") + } + return err } func SwapMoviePositions(roomID uint, movie1ID uint, movie2ID uint) (err error) { @@ -57,10 +85,16 @@ func SwapMoviePositions(roomID uint, movie1ID uint, movie2ID uint) (err error) { movie2 := &model.Movie{} err = tx.Select("position").Where("room_id = ? AND id = ?", roomID, movie1ID).First(movie1).Error if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + err = fmt.Errorf("movie with id %d not found", movie1ID) + } return } err = tx.Select("position").Where("room_id = ? AND id = ?", roomID, movie2ID).First(movie2).Error if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + err = fmt.Errorf("movie with id %d not found", movie2ID) + } return } err = tx.Model(&model.Movie{}).Where("room_id = ? AND id = ?", roomID, movie1ID).Update("position", movie2.Position).Error diff --git a/internal/db/relation.go b/internal/db/relation.go index 95fe16d..a232be8 100644 --- a/internal/db/relation.go +++ b/internal/db/relation.go @@ -1,9 +1,17 @@ package db -import "github.com/synctv-org/synctv/internal/model" +import ( + "errors" + + "github.com/synctv-org/synctv/internal/model" + "gorm.io/gorm" +) func GetRoomUserRelation(roomID, userID uint) (*model.RoomUserRelation, error) { roomUserRelation := &model.RoomUserRelation{} err := db.Where("room_id = ? AND user_id = ?", roomID, userID).First(roomUserRelation).Error + if err != nil && errors.Is(err, gorm.ErrRecordNotFound) { + return roomUserRelation, errors.New("room or user not found") + } return roomUserRelation, err } diff --git a/internal/db/room.go b/internal/db/room.go index bbac9f1..2644932 100644 --- a/internal/db/room.go +++ b/internal/db/room.go @@ -1,9 +1,12 @@ package db import ( + "errors" + "github.com/synctv-org/synctv/internal/model" "github.com/zijiren233/stream" "golang.org/x/crypto/bcrypt" + "gorm.io/gorm" ) type CreateRoomConfig func(r *model.Room) @@ -42,46 +45,74 @@ func CreateRoom(name, password string, conf ...CreateRoomConfig) (*model.Room, e for _, c := range conf { c(r) } - return r, db.Create(r).Error + err := db.Create(r).Error + if err != nil && errors.Is(err, gorm.ErrDuplicatedKey) { + return r, errors.New("room already exists") + } + return r, err } func GetRoomByID(id uint) (*model.Room, error) { r := &model.Room{} err := db.Where("id = ?", id).First(r).Error + if err != nil && errors.Is(err, gorm.ErrRecordNotFound) { + return r, errors.New("room not found") + } return r, err } func GetRoomAndCreatorByID(id uint) (*model.Room, error) { r := &model.Room{} err := db.Preload("Creator").Where("id = ?", id).First(r).Error + if err != nil && errors.Is(err, gorm.ErrRecordNotFound) { + return r, errors.New("room not found") + } return r, err } func ChangeRoomSetting(roomID uint, setting model.Setting) error { - return db.Model(&model.Room{}).Where("id = ?", roomID).Update("setting", setting).Error + err := db.Model(&model.Room{}).Where("id = ?", roomID).Update("setting", setting).Error + if err != nil && errors.Is(err, gorm.ErrRecordNotFound) { + return errors.New("room not found") + } + return err } func ChangeUserPermission(roomID uint, userID uint, permission model.Permission) error { - return db.Model(&model.RoomUserRelation{}).Where("room_id = ? AND user_id = ?", roomID, userID).Update("permissions", permission).Error + err := db.Model(&model.RoomUserRelation{}).Where("room_id = ? AND user_id = ?", roomID, userID).Update("permissions", permission).Error + if err != nil && errors.Is(err, gorm.ErrRecordNotFound) { + return errors.New("room or user not found") + } + return err } func HasPermission(roomID uint, userID uint, permission model.Permission) (bool, error) { ur := &model.RoomUserRelation{} err := db.Where("room_id = ? AND user_id = ?", roomID, userID).First(ur).Error if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + err = errors.New("room or user not found") + } return false, err } return ur.Permissions.Has(permission), nil } func DeleteRoomByID(roomID uint) error { - return db.Where("id = ?", roomID).Delete(&model.Room{}).Error + err := db.Where("id = ?", roomID).Delete(&model.Room{}).Error + if err != nil && errors.Is(err, gorm.ErrRecordNotFound) { + return errors.New("room not found") + } + return err } func HasRoom(roomID uint) (bool, error) { r := &model.Room{} err := db.Where("id = ?", roomID).First(r).Error if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + err = nil + } return false, err } return true, nil @@ -91,6 +122,9 @@ func HasRoomByName(name string) (bool, error) { r := &model.Room{} err := db.Where("name = ?", name).First(r).Error if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + err = nil + } return false, err } return true, nil @@ -105,37 +139,63 @@ func SetRoomPassword(roomID uint, password string) error { return err } } - return db.Model(&model.Room{}).Where("id = ?", roomID).Update("hashed_password", hashedPassword).Error + return SetRoomHashedPassword(roomID, hashedPassword) } func SetRoomHashedPassword(roomID uint, hashedPassword []byte) error { - return db.Model(&model.Room{}).Where("id = ?", roomID).Update("hashed_password", hashedPassword).Error + err := db.Model(&model.Room{}).Where("id = ?", roomID).Update("hashed_password", hashedPassword).Error + if err != nil && errors.Is(err, gorm.ErrRecordNotFound) { + return errors.New("room not found") + } + return err } func SetUserRole(roomID uint, userID uint, role model.Role) error { - return db.Model(&model.RoomUserRelation{}).Where("room_id = ? AND user_id = ?", roomID, userID).Update("role", role).Error + err := db.Model(&model.RoomUserRelation{}).Where("room_id = ? AND user_id = ?", roomID, userID).Update("role", role).Error + if err != nil && errors.Is(err, gorm.ErrRecordNotFound) { + return errors.New("room or user not found") + } + return err } func SetUserPermission(roomID uint, userID uint, permission model.Permission) error { - return db.Model(&model.RoomUserRelation{}).Where("room_id = ? AND user_id = ?", roomID, userID).Update("permissions", permission).Error + err := db.Model(&model.RoomUserRelation{}).Where("room_id = ? AND user_id = ?", roomID, userID).Update("permissions", permission).Error + if err != nil && errors.Is(err, gorm.ErrRecordNotFound) { + return errors.New("room or user not found") + } + return err } func AddUserPermission(roomID uint, userID uint, permission model.Permission) error { - return db.Model(&model.RoomUserRelation{}).Where("room_id = ? AND user_id = ?", roomID, userID).Update("permissions", db.Raw("permissions | ?", permission)).Error + err := db.Model(&model.RoomUserRelation{}).Where("room_id = ? AND user_id = ?", roomID, userID).Update("permissions", db.Raw("permissions | ?", permission)).Error + if err != nil && errors.Is(err, gorm.ErrRecordNotFound) { + return errors.New("room or user not found") + } + return err } func RemoveUserPermission(roomID uint, userID uint, permission model.Permission) error { - return db.Model(&model.RoomUserRelation{}).Where("room_id = ? AND user_id = ?", roomID, userID).Update("permissions", db.Raw("permissions & ?", ^permission)).Error + err := db.Model(&model.RoomUserRelation{}).Where("room_id = ? AND user_id = ?", roomID, userID).Update("permissions", db.Raw("permissions & ?", ^permission)).Error + if err != nil && errors.Is(err, gorm.ErrRecordNotFound) { + return errors.New("room or user not found") + } + return err } func GetAllRooms() ([]*model.Room, error) { rooms := []*model.Room{} err := db.Find(&rooms).Error + if err != nil && errors.Is(err, gorm.ErrRecordNotFound) { + return rooms, nil + } return rooms, err } func GetAllRoomsAndCreator() ([]*model.Room, error) { rooms := []*model.Room{} err := db.Preload("Creater").Find(&rooms).Error + if err != nil && errors.Is(err, gorm.ErrRecordNotFound) { + return rooms, nil + } return rooms, err } diff --git a/internal/db/user.go b/internal/db/user.go index ed809fe..5c1ef71 100644 --- a/internal/db/user.go +++ b/internal/db/user.go @@ -1,7 +1,12 @@ package db import ( + "errors" + "github.com/synctv-org/synctv/internal/model" + "github.com/zijiren233/stream" + "golang.org/x/crypto/bcrypt" + "gorm.io/gorm" "gorm.io/gorm/clause" ) @@ -10,7 +15,11 @@ func CreateUser(username string, hashedPassword []byte) (*model.User, error) { Username: username, HashedPassword: hashedPassword, } - return u, db.Where(*u).FirstOrCreate(u).Error + err := db.Create(u).Error + if err != nil && errors.Is(err, gorm.ErrDuplicatedKey) { + return u, errors.New("username already exists") + } + return u, err } func AddUserToRoom(userID uint, roomID uint, role model.Role, permission model.Permission) error { @@ -20,51 +29,94 @@ func AddUserToRoom(userID uint, roomID uint, role model.Role, permission model.P Role: role, Permissions: permission, } - return db.Attrs(ur).FirstOrCreate(ur).Error + err := db.Create(ur).Error + if err != nil && errors.Is(err, gorm.ErrDuplicatedKey) { + return errors.New("user already exists in room") + } + return err } func GetUserByUsername(username string) (*model.User, error) { u := &model.User{} err := db.Where("username = ?", username).First(u).Error + if errors.Is(err, gorm.ErrRecordNotFound) { + return u, errors.New("user not found") + } return u, err } func GetUserByID(id uint) (*model.User, error) { u := &model.User{} err := db.Where("id = ?", id).First(u).Error + if errors.Is(err, gorm.ErrRecordNotFound) { + return u, errors.New("user not found") + } return u, err } func GetUsersByRoomID(roomID uint) ([]model.User, error) { users := []model.User{} err := db.Model(&model.RoomUserRelation{}).Where("room_id = ?", roomID).Find(&users).Error + if err != nil && errors.Is(err, gorm.ErrRecordNotFound) { + return users, errors.New("room not found") + } return users, err } func DeleteUserByID(userID uint) error { - return db.Where("id = ?", userID).Delete(&model.User{}).Error + err := db.Where("id = ?", userID).Delete(&model.User{}).Error + if errors.Is(err, gorm.ErrRecordNotFound) { + return errors.New("user not found") + } + return err } func LoadAndDeleteUserByID(userID uint, columns ...clause.Column) (*model.User, error) { u := &model.User{} err := db.Clauses(clause.Returning{Columns: columns}).Where("id = ?", userID).Delete(u).Error + if errors.Is(err, gorm.ErrRecordNotFound) { + return u, errors.New("user not found") + } return u, err } func DeleteUserByUsername(username string) error { - return db.Where("username = ?", username).Delete(&model.User{}).Error + err := db.Where("username = ?", username).Delete(&model.User{}).Error + if errors.Is(err, gorm.ErrRecordNotFound) { + return errors.New("user not found") + } + return err } func LoadAndDeleteUserByUsername(username string, columns ...clause.Column) (*model.User, error) { u := &model.User{} err := db.Clauses(clause.Returning{Columns: columns}).Where("username = ?", username).Delete(u).Error + if errors.Is(err, gorm.ErrRecordNotFound) { + return u, errors.New("user not found") + } return u, err } -func SetUserPassword(userID uint, hashedPassword []byte) error { - return db.Model(&model.User{}).Where("id = ?", userID).Update("hashed_password", hashedPassword).Error +func SetUserPassword(userID uint, password string) error { + var hashedPassword []byte + if password != "" { + var err error + hashedPassword, err = bcrypt.GenerateFromPassword(stream.StringToBytes(password), bcrypt.DefaultCost) + if err != nil { + return err + } + } + return SetUserHashedPassword(userID, hashedPassword) +} + +func SetUserHashedPassword(userID uint, hashedPassword []byte) error { + err := db.Model(&model.User{}).Where("id = ?", userID).Update("hashed_password", hashedPassword).Error + if errors.Is(err, gorm.ErrRecordNotFound) { + return errors.New("user not found") + } + return err } -func UpdateUser(u *model.User) error { +func SaveUser(u *model.User) error { return db.Save(u).Error } diff --git a/internal/op/user.go b/internal/op/user.go index 15c32ed..e550bf8 100644 --- a/internal/op/user.go +++ b/internal/op/user.go @@ -70,5 +70,5 @@ func (u *User) SetPassword(password string) error { } u.HashedPassword = hashedPassword atomic.AddUint32(&u.version, 1) - return db.SetUserPassword(u.ID, hashedPassword) + return db.SetUserHashedPassword(u.ID, hashedPassword) } diff --git a/internal/op/users.go b/internal/op/users.go index 32b2835..3ffb610 100644 --- a/internal/op/users.go +++ b/internal/op/users.go @@ -100,9 +100,9 @@ func DeleteUserByUsername(username string) error { return nil } -func UpdateUser(u *model.User) error { +func SaveUser(u *model.User) error { userCache.Remove(u.ID) - return db.UpdateUser(u) + return db.SaveUser(u) } func GetUserName(userID uint) string {