From 5d7dc70f1b7e6fc1a1380160e96aabbdd61fbfa4 Mon Sep 17 00:00:00 2001 From: bagas Date: Wed, 25 Sep 2024 21:20:55 +0700 Subject: [PATCH] Change MySQL UUID storage from unsupported type to VARCHAR(36) --- db/database.go | 262 ++++++++++++++++++++--------- handler/auth/google/setup/setup.go | 1 + types/models/models.go | 41 +++++ 3 files changed, 224 insertions(+), 80 deletions(-) diff --git a/db/database.go b/db/database.go index b718dfb..2d42fbc 100644 --- a/db/database.go +++ b/db/database.go @@ -70,17 +70,17 @@ func NewMYSQLdb(username, password, host, port, dbName string) types.Database { panic("failed to connect database: " + err.Error()) } - err = DB.AutoMigrate(&models.User{}) + err = DB.AutoMigrate(&models.MysqlUser{}) if err != nil { panic(err.Error()) return nil } - err = DB.AutoMigrate(&models.File{}) + err = DB.AutoMigrate(&models.MysqlFile{}) if err != nil { panic(err.Error()) return nil } - err = DB.AutoMigrate(&models.Allowance{}) + err = DB.AutoMigrate(&models.MysqlAllowance{}) if err != nil { panic(err.Error()) return nil @@ -139,8 +139,16 @@ func NewPostgresDB(username, password, host, port, dbName string, mode SSLMode) return &postgresDB{DB} } +func UUIDToString(u uuid.UUID) string { + return u.String() +} + +func StringToUUID(s string) (uuid.UUID, error) { + return uuid.Parse(s) +} + func (db *mySQLdb) IsUserRegistered(email string, username string) bool { - var data models.User + var data models.MysqlUser err := db.DB.Table("users").Where("email = ? OR username = ?", email, username).First(&data).Error if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { @@ -152,8 +160,8 @@ func (db *mySQLdb) IsUserRegistered(email string, username string) bool { } func (db *mySQLdb) IsEmailRegistered(email string) bool { - var data models.User - err := db.DB.Table("users").Where("email = ? ", email).First(&data).Error + var data models.MysqlUser + err := db.DB.Table("users").Where("email = ?", email).First(&data).Error if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return false @@ -164,44 +172,65 @@ func (db *mySQLdb) IsEmailRegistered(email string) bool { } func (db *mySQLdb) CreateUser(user *models.User) error { - err := db.DB.Create(user).Error + mysqlUser := models.MysqlUser{ + UserID: UUIDToString(user.UserID), + Username: user.Username, + Email: user.Email, + Password: user.Password, + Totp: user.Totp, + } + err := db.DB.Create(&mysqlUser).Error if err != nil { return err } - err = db.CreateAllowance(user.UserID) - if err != nil { - return err - } - return nil + return db.CreateAllowance(user.UserID) } func (db *mySQLdb) GetUser(email string) (*models.User, error) { - var user models.User - err := db.DB.Table("users").Where("email = ?", email).First(&user).Error + var mysqlUser models.MysqlUser + err := db.DB.Table("users").Where("email = ?", email).First(&mysqlUser).Error if err != nil { return nil, err } - return &user, nil + userID, _ := StringToUUID(mysqlUser.UserID) + return &models.User{ + UserID: userID, + Username: mysqlUser.Username, + Email: mysqlUser.Email, + Password: mysqlUser.Password, + Totp: mysqlUser.Totp, + }, nil } func (db *mySQLdb) GetAllUsers() ([]models.User, error) { - var users []models.User - err := db.DB.Table("users").Select("user_id, Username, Email").Find(&users).Error + var mysqlUsers []models.MysqlUser + err := db.DB.Table("users").Select("user_id, username, email").Find(&mysqlUsers).Error if err != nil { return nil, err } + + users := make([]models.User, len(mysqlUsers)) + for i, u := range mysqlUsers { + userID, _ := StringToUUID(u.UserID) + users[i] = models.User{ + UserID: userID, + Username: u.Username, + Email: u.Email, + Password: u.Password, + Totp: u.Totp, + } + } return users, nil } func (db *mySQLdb) UpdateUserPassword(email string, password string) error { - var user models.User - err := db.DB.Table("users").Where("email = ?", email).First(&user).Error + var mysqlUser models.MysqlUser + err := db.DB.Table("users").Where("email = ?", email).First(&mysqlUser).Error if err != nil { return err } - user.Password = password - db.Save(&user) - return nil + mysqlUser.Password = password + return db.DB.Save(&mysqlUser).Error } func (db *mySQLdb) CreateAllowance(userID uuid.UUID) error { @@ -210,11 +239,7 @@ func (db *mySQLdb) CreateAllowance(userID uuid.UUID) error { AllowanceByte: 1024 * 1024 * 1024 * 10, AllowanceFile: 10, } - err := db.DB.Create(userAllowance).Error - if err != nil { - return err - } - return nil + return db.DB.Create(userAllowance).Error } func (db *mySQLdb) GetAllowance(userID uuid.UUID) (*models.Allowance, error) { @@ -227,52 +252,116 @@ func (db *mySQLdb) GetAllowance(userID uuid.UUID) (*models.Allowance, error) { } func (db *mySQLdb) CreateFile(file *models.File) error { - err := db.DB.Create(file).Error - if err != nil { - return err + mysqlFile := models.MysqlFile{ + ID: UUIDToString(file.ID), + OwnerID: UUIDToString(file.OwnerID), + Name: file.Name, + Size: file.Size, + TotalChunk: file.TotalChunk, + StartHash: file.StartHash, + EndHash: file.EndHash, + IsPrivate: file.IsPrivate, + Type: file.Type, + Downloaded: file.Downloaded, } - return nil + return db.DB.Create(&mysqlFile).Error } func (db *mySQLdb) GetFile(fileID string) (*models.File, error) { - var file models.File - err := db.DB.Table("files").Where("id = ?", fileID).First(&file).Error + var mysqlFile models.MysqlFile + err := db.DB.Table("files").Where("id = ?", fileID).First(&mysqlFile).Error if err != nil { return nil, err } - return &file, nil + fileIDUUID, err := StringToUUID(mysqlFile.ID) + if err != nil { + return nil, err + } + ownerIDUUID, err := StringToUUID(mysqlFile.OwnerID) + if err != nil { + return nil, err + } + return &models.File{ + ID: fileIDUUID, + OwnerID: ownerIDUUID, + Name: mysqlFile.Name, + Size: mysqlFile.Size, + TotalChunk: mysqlFile.TotalChunk, + StartHash: mysqlFile.StartHash, + EndHash: mysqlFile.EndHash, + IsPrivate: mysqlFile.IsPrivate, + Type: mysqlFile.Type, + Downloaded: mysqlFile.Downloaded, + }, nil } func (db *mySQLdb) RenameFile(fileID string, name string) (*models.File, error) { - var file models.File - err := db.DB.Table("files").Where("id = ?", fileID).First(&file).Error - file.Name = name - err = db.DB.Save(&file).Error - if err != nil { - return &file, err - } - return &file, nil -} - -func (db *mySQLdb) DeleteFile(fileID string) error { - err := db.DB.Table("files").Where("id = ?", fileID).Delete(&models.File{}).Error - if err != nil { - return err - } - return nil -} - -func (db *mySQLdb) GetUserFile(name string, ownerID string) (*models.File, error) { - var file models.File - err := db.DB.Table("files").Where("name = ? AND owner_id = ?", name, ownerID).First(&file).Error + var mysqlFile models.MysqlFile + err := db.DB.Table("files").Where("id = ?", fileID).First(&mysqlFile).Error if err != nil { return nil, err } - return &file, nil + mysqlFile.Name = name + err = db.DB.Save(&mysqlFile).Error + if err != nil { + return nil, err + } + fileIDUUID, err := StringToUUID(mysqlFile.ID) + if err != nil { + return nil, err + } + ownerIDUUID, err := StringToUUID(mysqlFile.OwnerID) + if err != nil { + return nil, err + } + return &models.File{ + ID: fileIDUUID, + OwnerID: ownerIDUUID, + Name: mysqlFile.Name, + Size: mysqlFile.Size, + TotalChunk: mysqlFile.TotalChunk, + StartHash: mysqlFile.StartHash, + EndHash: mysqlFile.EndHash, + IsPrivate: mysqlFile.IsPrivate, + Type: mysqlFile.Type, + Downloaded: mysqlFile.Downloaded, + }, nil +} + +func (db *mySQLdb) DeleteFile(fileID string) error { + return db.DB.Table("files").Where("id = ?", fileID).Delete(&models.MysqlFile{}).Error +} + +func (db *mySQLdb) GetUserFile(name string, ownerID string) (*models.File, error) { + var mysqlFile models.MysqlFile + err := db.DB.Table("files").Where("name = ? AND owner_id = ?", name, ownerID).First(&mysqlFile).Error + if err != nil { + return nil, err + } + fileIDUUID, err := StringToUUID(mysqlFile.ID) + if err != nil { + return nil, err + } + ownerIDUUID, err := StringToUUID(mysqlFile.OwnerID) + if err != nil { + return nil, err + } + return &models.File{ + ID: fileIDUUID, + OwnerID: ownerIDUUID, + Name: mysqlFile.Name, + Size: mysqlFile.Size, + TotalChunk: mysqlFile.TotalChunk, + StartHash: mysqlFile.StartHash, + EndHash: mysqlFile.EndHash, + IsPrivate: mysqlFile.IsPrivate, + Type: mysqlFile.Type, + Downloaded: mysqlFile.Downloaded, + }, nil } func (db *mySQLdb) GetFiles(ownerID string, query string, status types.FileStatus) ([]*models.File, error) { - var files []*models.File + var mysqlFiles []*models.MysqlFile tx := db.DB.Table("files").Where("owner_id = ?", ownerID) if query != "" { @@ -281,55 +370,68 @@ func (db *mySQLdb) GetFiles(ownerID string, query string, status types.FileStatu switch status { case types.Private: - tx = tx.Where("is_private = ?::boolean", true) + tx = tx.Where("is_private = ?", true) case types.Public: - tx = tx.Where("is_private = ?::boolean", false) - default: + tx = tx.Where("is_private = ?", false) } - err := tx.Find(&files).Error + err := tx.Find(&mysqlFiles).Error if err != nil { return nil, err } + files := make([]*models.File, len(mysqlFiles)) + for i, f := range mysqlFiles { + fileIDUUID, err := StringToUUID(f.ID) + if err != nil { + return nil, err + } + ownerIDUUID, err := StringToUUID(f.OwnerID) + if err != nil { + return nil, err + } + files[i] = &models.File{ + ID: fileIDUUID, + OwnerID: ownerIDUUID, + Name: f.Name, + Size: f.Size, + TotalChunk: f.TotalChunk, + StartHash: f.StartHash, + EndHash: f.EndHash, + IsPrivate: f.IsPrivate, + Type: f.Type, + Downloaded: f.Downloaded, + } + } return files, nil } func (db *mySQLdb) IncrementDownloadCount(fileID string) error { - var file models.File - err := db.DB.Table("files").Where("id = ?", fileID).First(&file).Error + var mysqlFile models.MysqlFile + err := db.DB.Table("files").Where("id = ?", fileID).First(&mysqlFile).Error if err != nil { return err } - file.Downloaded = file.Downloaded + 1 - err = db.DB.Updates(file).Error - if err != nil { - return err - } - return nil + mysqlFile.Downloaded++ + return db.DB.Save(&mysqlFile).Error } func (db *mySQLdb) ChangeFileVisibility(fileID string) error { - err := db.DB.Model(&models.File{}).Where("id = ?", fileID).Select("is_private"). - Updates(map[string]interface{}{"is_private": gorm.Expr("NOT is_private")}).Error - + err := db.DB.Model(&models.MysqlFile{}).Where("id = ?", fileID).Update("is_private", gorm.Expr("NOT is_private")).Error if err != nil { return err } return nil } + func (db *mySQLdb) InitializeTotp(email string, secret string) error { - var user models.User - err := db.DB.Table("users").Where("email = ?", email).First(&user).Error + var mysqlUser models.MysqlUser + err := db.DB.Table("users").Where("email = ?", email).First(&mysqlUser).Error if err != nil { return err } - user.Totp = secret - err = db.Save(&user).Error - if err != nil { - return err - } - return nil + mysqlUser.Totp = secret + return db.DB.Save(&mysqlUser).Error } // POSTGRES FUNCTION diff --git a/handler/auth/google/setup/setup.go b/handler/auth/google/setup/setup.go index 44ee283..e150a1a 100644 --- a/handler/auth/google/setup/setup.go +++ b/handler/auth/google/setup/setup.go @@ -99,6 +99,7 @@ func POST(w http.ResponseWriter, r *http.Request) { err = app.Server.Database.CreateUser(&newUser) if err != nil { + app.Server.Logger.Error(err.Error()) component := signupView.Main("Filekeeper - Sign up Page", types.Message{ Code: 0, Message: "Email or Username has been registered", diff --git a/types/models/models.go b/types/models/models.go index 3454142..2a8e0bf 100644 --- a/types/models/models.go +++ b/types/models/models.go @@ -30,3 +30,44 @@ type Allowance struct { AllowanceFile uint64 `gorm:"not null"` User *User `gorm:"foreignKey:UserID;constraint:OnDelete:CASCADE;"` } + +type MysqlUser struct { + UserID string `gorm:"type:varchar(36);primaryKey"` + Username string `gorm:"type:varchar(255);unique;not null"` + Email string `gorm:"type:varchar(255);unique;not null"` + Password string `gorm:"type:text;not null"` + Totp string `gorm:"type:varchar(255);not null"` +} + +func (MysqlUser) TableName() string { + return "users" +} + +type MysqlFile struct { + ID string `gorm:"type:varchar(36);primaryKey"` + OwnerID string `gorm:"type:varchar(36);not null"` + Name string `gorm:"type:text;not null"` + Size uint64 `gorm:"not null"` + TotalChunk uint64 `gorm:"not null"` + StartHash string `gorm:"type:text;not null"` + EndHash string `gorm:"type:text;not null"` + IsPrivate bool `gorm:"not null;default:true"` + Type string `gorm:"type:varchar(5);not null;default:'doc'"` + Downloaded uint64 `gorm:"not null;default:0"` + Owner *MysqlUser `gorm:"foreignKey:OwnerID;constraint:OnDelete:CASCADE;"` +} + +func (MysqlFile) TableName() string { + return "files" +} + +type MysqlAllowance struct { + UserID string `gorm:"type:varchar(36);primaryKey"` + AllowanceByte uint64 `gorm:"not null"` + AllowanceFile uint64 `gorm:"not null"` + User *MysqlUser `gorm:"foreignKey:UserID;constraint:OnDelete:CASCADE;"` +} + +func (MysqlAllowance) TableName() string { + return "allowances" +}