diff options
| author | Bobby <[email protected]> | 2025-06-16 10:15:15 +0530 |
|---|---|---|
| committer | Bobby <[email protected]> | 2025-06-16 10:15:15 +0530 |
| commit | 782be699f797011a6e71b345658762f7e2013636 (patch) | |
| tree | 0af72643a6188731bd09923143860e9167bba449 /models/user.go | |
| parent | cfa8164f2468ea5a63b4cce2edb01957846b2b12 (diff) | |
| download | imageboard-782be699f797011a6e71b345658762f7e2013636.tar.xz imageboard-782be699f797011a6e71b345658762f7e2013636.zip | |
added user, image, comments, and tags models with functions
Diffstat (limited to 'models/user.go')
| -rw-r--r-- | models/user.go | 313 |
1 files changed, 313 insertions, 0 deletions
diff --git a/models/user.go b/models/user.go new file mode 100644 index 0000000..5607e68 --- /dev/null +++ b/models/user.go @@ -0,0 +1,313 @@ +package models + +import ( + "fmt" + "imageboard/config" + "imageboard/utils/validators" + "strings" + "time" + + "golang.org/x/crypto/bcrypt" + "gorm.io/gorm" +) + +type User struct { + gorm.Model + Username string `gorm:"uniqueIndex;not null;size:255" json:"username"` + Email string `gorm:"not null;size:255" json:"email"` + Password string `gorm:"not null;size:255" json:"-"` + Level UserLevel `gorm:"not null;default:0" json:"level"` + EmailVerified bool `gorm:"not null;default:false" json:"email_verified"` + Bio string `gorm:"default:'';size:500" json:"bio"` + AvatarURL string `gorm:"default:'';size:255" json:"avatar_url"` + WebsiteURL string `gorm:"default:'';size:255" json:"website_url"` + Location string `gorm:"default:'';size:255" json:"location"` + Timezone string `gorm:"default:'UTC';size:50" json:"timezone"` + AccountDisabled bool `gorm:"not null;default:false" json:"-"` + AccountBanned bool `gorm:"not null;default:false" json:"-"` + PostsRequireApproval bool `gorm:"not null;default:false" json:"-"` + IsDeleted bool `gorm:"not null;default:false" json:"-"` + LastLoginAt *time.Time `gorm:"default:null" json:"last_login_at"` + LastActivityAt *time.Time `gorm:"default:null" json:"last_activity_at"` + Images []Image `gorm:"foreignKey:UploaderID" json:"images,omitempty"` +} + +func (u *User) BeforeCreate(tx *gorm.DB) error { + u.Username = strings.TrimSpace(u.Username) + u.Email = strings.TrimSpace(strings.ToLower(u.Email)) + + if u.Username == "" { + return fmt.Errorf("username cannot be empty") + } + + if u.Email == "" { + return fmt.Errorf("email cannot be empty") + } + + if len(u.Username) < 3 { + return fmt.Errorf("username must be at least 3 characters long") + } + + if len(u.Username) > 72 { + return fmt.Errorf("username must not exceed 72 characters") + } + + if !validators.IsValidUsername(u.Username) { + return fmt.Errorf("username can only contain letters, numbers, underscores, and hyphens") + } + + if validators.IsReservedUsername(u.Username) { + return fmt.Errorf("username '%s' is reserved and cannot be used", u.Username) + } + + if !validators.IsValidEmail(u.Email) { + return fmt.Errorf("invalid email format") + } + + // Check if username is already taken + var existingUser User + if err := tx.Where("username = ?", u.Username).First(&existingUser).Error; err == nil { + return fmt.Errorf("username '%s' is already taken", u.Username) + } + + var userCount int64 + if err := tx.Model(&User{}).Where("is_deleted = ?", false).Count(&userCount).Error; err != nil { + return err + } + + if userCount == 0 { + u.Level = UserLevelSuperAdmin // First user becomes Super Admin + } + + return nil +} + +func (u *User) BeforeUpdate(tx *gorm.DB) error { + u.Username = strings.TrimSpace(u.Username) + u.Email = strings.TrimSpace(strings.ToLower(u.Email)) + + return nil +} + +func (u *User) SetPassword(password string) error { + if len(password) < config.Server.MinPasswordLength { + return fmt.Errorf("password must be at least %d characters long", config.Server.MinPasswordLength) + } + + if len(password) > 255 { + return fmt.Errorf("password must not exceed 255 characters") + } + + hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) + if err != nil { + return err + } + + u.Password = string(hashedPassword) + return nil +} + +func (u *User) CheckPassword(password string) bool { + if u.IsDeleted { + return false + } + + err := bcrypt.CompareHashAndPassword([]byte(u.Password), []byte(password)) + return err == nil +} + +func (u *User) IsActive() bool { + return !u.IsDeleted && !u.AccountDisabled && !u.AccountBanned +} + +func (u *User) CanLogin() bool { + return u.IsActive() && (u.EmailVerified || u.IsAdmin()) +} + +func (u *User) IsAdmin() bool { + return u.Level >= UserLevelAdmin +} + +func (u *User) IsModerator() bool { + return u.IsActive() && u.Level >= UserLevelModerator +} + +func (u *User) IsJanitor() bool { + return u.IsActive() && u.Level >= UserLevelJanitor +} + +func (u *User) IsContributor() bool { + return u.IsActive() && u.Level >= UserLevelContributor +} + +func (u *User) IsMember() bool { + return u.IsActive() && u.Level >= UserLevelMember +} + +func (u *User) CanUpload() bool { + return u.IsActive() && (u.EmailVerified || u.IsAdmin()) +} + +func (u *User) CanComment() bool { + return u.IsActive() && (u.EmailVerified || u.IsAdmin()) +} + +func (u *User) CanMessage() bool { + return u.IsActive() && (u.EmailVerified || u.IsAdmin()) +} + +func (u *User) CanCreateTags() bool { + return u.IsContributor() +} + +func (u *User) CanEditTags() bool { + return u.IsJanitor() +} + +func (u *User) CanEditPosts() bool { + return u.IsJanitor() +} + +func (u *User) CanDeletePosts() bool { + return u.IsModerator() +} + +func (u *User) CanApprovePosts() bool { + return u.IsJanitor() +} + +func (u *User) CanEditUser(targetUser *User) bool { + if u.ID == targetUser.ID { + return true + } + + if targetUser.IsDeleted { + return false + } + + return (u.IsAdmin() || u.IsModerator()) && targetUser.Level < u.Level +} + +func (u *User) CanPromoteUser(targetUser *User, newLevel UserLevel) bool { + if u.ID == targetUser.ID || targetUser.IsDeleted { + return false + } + + if u.Level <= UserLevelContributor { + return false + } + + return newLevel > UserLevelMember && newLevel <= u.Level && newLevel <= UserLevelAdmin +} + +func (u *User) CanDemoteUser(targetUser *User, newLevel UserLevel) bool { + if u.ID == targetUser.ID || targetUser.IsDeleted { + return false + } + + if u.Level <= UserLevelContributor { + return false + } + + return newLevel >= UserLevelMember && newLevel < u.Level && newLevel <= UserLevelAdmin +} + +func (u *User) CanDisableUser(targetUser *User) bool { + if u.ID == targetUser.ID || targetUser.IsDeleted { + return false + } + + return (u.IsJanitor() || u.IsModerator() || u.IsAdmin()) && targetUser.Level < u.Level +} + +func (u *User) CanBanUser(targetUser *User) bool { + if u.ID == targetUser.ID || targetUser.IsDeleted { + return false + } + + return (u.IsModerator() || u.IsAdmin()) && targetUser.Level < u.Level +} + +func (u *User) CanDeleteUser(targetUser *User) bool { + if targetUser.IsDeleted { + return false + } + + if u.ID == targetUser.ID { + return true // Users can delete their own account + } + + if u.Level <= UserLevelContributor { + return false + } + + return (u.IsAdmin() || u.IsModerator()) && targetUser.Level < u.Level +} + +func (u *User) CanMakeUserPostsRequireApproval(targetUser *User) bool { + if targetUser.IsDeleted { + return false + } + + return (u.IsJanitor() || u.IsModerator() || u.IsAdmin()) && targetUser.Level < u.Level +} + +func (u *User) GetDailyPostLimit() int { + switch u.Level { + case UserLevelMember: + return 10 + case UserLevelContributor: + return 25 + default: + return -1 // No limit for Janitors, Moderators, and Admins + } +} + +func (u *User) GetDailyRemainingUploadLimit(tx *gorm.DB) (int64, error) { + totalAllowed := u.GetDailyPostLimit() + if totalAllowed == -1 { + return -1, nil + } + + today := time.Now().Truncate(24 * time.Hour) + tomorrow := today.Add(24 * time.Hour) + + var count int64 + err := tx.Model(&Image{}).Where("uploader_id = ? AND created_at >= ? AND created_at < ? AND is_deleted = ?", + u.ID, today, tomorrow, false).Count(&count).Error + + return int64(totalAllowed) - count, err +} + +func (u *User) CanUploadToday(tx *gorm.DB) (bool, error) { + remaining, err := u.GetDailyRemainingUploadLimit(tx) + if err != nil { + return false, err + } + return remaining != 0, nil +} + +func (u *User) UpdateLastUserActivity(tx *gorm.DB) error { + now := time.Now() + u.LastActivityAt = &now + return tx.Model(u).Update("last_activity_at", now).Error +} + +func (u *User) UpdateLastUserLogin(tx *gorm.DB) error { + now := time.Now() + u.LastLoginAt = &now + u.LastActivityAt = &now + return tx.Model(u).Updates(map[string]interface{}{ + "last_login_at": now, + "last_activity_at": now, + }).Error +} + +func (u *User) DeleteUser(tx *gorm.DB) error { + if u.IsDeleted { + return fmt.Errorf("user is already deleted") + } + + u.IsDeleted = true + return tx.Save(u).Error +} |
