moved repo to user model
This commit is contained in:
@@ -1,11 +1,20 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"GoMembership/internal/config"
|
||||
"GoMembership/internal/constants"
|
||||
"GoMembership/internal/utils"
|
||||
"GoMembership/pkg/errors"
|
||||
"GoMembership/pkg/logger"
|
||||
"fmt"
|
||||
"slices"
|
||||
"time"
|
||||
|
||||
"github.com/alexedwards/argon2id"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/clause"
|
||||
)
|
||||
|
||||
type User struct {
|
||||
@@ -28,9 +37,8 @@ type User struct {
|
||||
Consents []Consent `gorm:"constraint:OnUpdate:CASCADE"`
|
||||
BankAccount BankAccount `gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" json:"bank_account"`
|
||||
BankAccountID uint
|
||||
Verification Verification `gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE;"`
|
||||
VerificationID uint
|
||||
Membership Membership `gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" json:"membership"`
|
||||
Verifications *[]Verification `gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE;"`
|
||||
Membership Membership `gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" json:"membership"`
|
||||
MembershipID uint
|
||||
Licence *Licence `gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" json:"licence"`
|
||||
LicenceID uint
|
||||
@@ -69,6 +77,268 @@ func (u *User) PasswordMatches(plaintextPassword string) (bool, error) {
|
||||
return argon2id.ComparePasswordAndHash(plaintextPassword, u.Password)
|
||||
}
|
||||
|
||||
func (u *User) PasswordExists() bool {
|
||||
return u.Password != ""
|
||||
}
|
||||
|
||||
func (u *User) Delete(db *gorm.DB) error {
|
||||
return db.Delete(&User{}, "id = ?", u.ID).Error
|
||||
}
|
||||
|
||||
func (u *User) Create(db *gorm.DB) error {
|
||||
return db.Transaction(func(tx *gorm.DB) error {
|
||||
// Create the base User record (omit associations to handle them separately)
|
||||
if err := tx.Create(u).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Replace associated Categories (assumes Categories already exist)
|
||||
if u.Licence != nil && len(u.Licence.Categories) > 0 {
|
||||
if err := tx.Model(u.Licence).Association("Categories").Replace(u.Licence.Categories); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
logger.Info.Printf("user created: %#v", u.Safe())
|
||||
// Preload all associations to return the fully populated User
|
||||
return tx.
|
||||
Preload("Membership").
|
||||
Preload("Membership.SubscriptionModel").
|
||||
Preload("Licence").
|
||||
Preload("Licence.Categories").
|
||||
First(u, u.ID).Error // Refresh the user object with all associations
|
||||
})
|
||||
}
|
||||
|
||||
func (u *User) Update(db *gorm.DB) error {
|
||||
|
||||
err := db.Transaction(func(tx *gorm.DB) error {
|
||||
// Check if the user exists in the database
|
||||
var existingUser User
|
||||
|
||||
logger.Info.Printf("updating user: %#v", u)
|
||||
if err := tx.
|
||||
Preload("Membership").
|
||||
Preload("Membership.SubscriptionModel").
|
||||
Preload("Licence").
|
||||
Preload("Licence.Categories").
|
||||
Preload("Verifications").
|
||||
First(&existingUser, u.ID).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
// Update the user's main fields
|
||||
result := tx.Session(&gorm.Session{FullSaveAssociations: true}).Omit("Password", "Membership", "Licence", "Verifications").Updates(u)
|
||||
if result.Error != nil {
|
||||
logger.Error.Printf("User update error in update user: %#v", result.Error)
|
||||
return result.Error
|
||||
}
|
||||
if result.RowsAffected == 0 {
|
||||
return errors.ErrNoRowsAffected
|
||||
}
|
||||
|
||||
if u.Password != "" {
|
||||
if err := tx.Model(&existingUser).
|
||||
Update("Password", u.Password).Error; err != nil {
|
||||
logger.Error.Printf("Password update error in update user: %#v", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Update the Membership if provided
|
||||
if u.Membership.ID != 0 {
|
||||
if err := tx.Model(&existingUser.Membership).Updates(u.Membership).Error; err != nil {
|
||||
logger.Error.Printf("Membership update error in update user: %#v", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
if u.Licence != nil {
|
||||
u.Licence.UserID = existingUser.ID
|
||||
|
||||
if err := tx.Save(u.Licence).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := tx.Model(&existingUser).Update("LicenceID", u.Licence.ID).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := tx.Model(u.Licence).Association("Categories").Replace(u.Licence.Categories); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// if u.Licence != nil {
|
||||
// if existingUser.Licence == nil || existingUser.LicenceID == 0 {
|
||||
// u.Licence.UserID = existingUser.ID // Ensure Licence belongs to User
|
||||
// if err := tx.Create(u.Licence).Error; err != nil {
|
||||
// return err
|
||||
// }
|
||||
// existingUser.Licence = u.Licence
|
||||
// existingUser.LicenceID = u.Licence.ID
|
||||
// if err := tx.Model(&existingUser).Update("LicenceID", u.Licence.ID).Error; err != nil {
|
||||
// return err
|
||||
// }
|
||||
// }
|
||||
// if err := tx.Model(existingUser.Licence).Updates(u.Licence).Error; err != nil {
|
||||
// return err
|
||||
// }
|
||||
// // Update Categories association
|
||||
// if err := tx.Model(existingUser.Licence).Association("Categories").Replace(u.Licence.Categories); err != nil {
|
||||
// return err
|
||||
// }
|
||||
// }
|
||||
|
||||
if u.Verifications != nil {
|
||||
if err := tx.Save(*u.Verifications).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return db.
|
||||
Preload("Membership").
|
||||
Preload("Membership.SubscriptionModel").
|
||||
Preload("Licence").
|
||||
Preload("Licence.Categories").
|
||||
Preload("Verifications").
|
||||
First(&u, u.ID).Error
|
||||
}
|
||||
|
||||
func (u *User) FromID(db *gorm.DB, userID *uint) error {
|
||||
var user User
|
||||
result := db.
|
||||
Preload(clause.Associations).
|
||||
Preload("Membership").
|
||||
Preload("Membership.SubscriptionModel").
|
||||
Preload("Licence").
|
||||
Preload("Licence.Categories").
|
||||
Preload("Verifications").
|
||||
First(&user, userID)
|
||||
if result.Error != nil {
|
||||
if result.Error == gorm.ErrRecordNotFound {
|
||||
return gorm.ErrRecordNotFound
|
||||
}
|
||||
return result.Error
|
||||
}
|
||||
*u = user
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *User) FromEmail(db *gorm.DB, email *string) error {
|
||||
var user User
|
||||
result := db.
|
||||
Preload(clause.Associations).
|
||||
Preload("Membership").
|
||||
Preload("Membership.SubscriptionModel").
|
||||
Preload("Licence").
|
||||
Preload("Licence.Categories").
|
||||
Preload("Verifications").
|
||||
Where("email = ?", email).First(&user)
|
||||
if result.Error != nil {
|
||||
if result.Error == gorm.ErrRecordNotFound {
|
||||
return gorm.ErrRecordNotFound
|
||||
}
|
||||
return result.Error
|
||||
}
|
||||
*u = user
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *User) FromContext(db *gorm.DB, c *gin.Context) error {
|
||||
tokenString, err := c.Cookie("jwt")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
jwtUserID, err := extractUserIDFrom(tokenString)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = u.FromID(db, &jwtUserID); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *User) IsVerified() bool {
|
||||
return u.Status > constants.DisabledStatus
|
||||
}
|
||||
|
||||
func (u *User) HasPrivilege(privilege int8) bool {
|
||||
return u.RoleID >= privilege
|
||||
}
|
||||
|
||||
func (u *User) IsAdmin() bool {
|
||||
return u.RoleID == constants.Roles.Admin
|
||||
}
|
||||
|
||||
func (u *User) IsMember() bool {
|
||||
return u.RoleID == constants.Roles.Member
|
||||
}
|
||||
|
||||
func (u *User) IsSupporter() bool {
|
||||
return u.RoleID == constants.Roles.Supporter
|
||||
}
|
||||
|
||||
func (u *User) SetVerification(verificationType string) (*Verification, error) {
|
||||
if u.Verifications == nil {
|
||||
u.Verifications = new([]Verification)
|
||||
}
|
||||
token, err := utils.GenerateVerificationToken()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
v := Verification{
|
||||
UserID: u.ID,
|
||||
VerificationToken: token,
|
||||
Type: verificationType,
|
||||
}
|
||||
if vi := slices.IndexFunc(*u.Verifications, func(vsl Verification) bool { return vsl.Type == v.Type }); vi > -1 {
|
||||
(*u.Verifications)[vi] = v
|
||||
} else {
|
||||
*u.Verifications = append(*u.Verifications, v)
|
||||
}
|
||||
return &v, nil
|
||||
}
|
||||
|
||||
func (u *User) GetVerification(verificationType string) (*Verification, error) {
|
||||
if u.Verifications == nil {
|
||||
return nil, errors.ErrNoData
|
||||
}
|
||||
vi := slices.IndexFunc(*u.Verifications, func(vsl Verification) bool { return vsl.Type == verificationType })
|
||||
if vi == -1 {
|
||||
return nil, errors.ErrNotFound
|
||||
}
|
||||
return &(*u.Verifications)[vi], nil
|
||||
}
|
||||
|
||||
func (u *User) Verify(token string, verificationType string) bool {
|
||||
if token == "" || verificationType == "" {
|
||||
logger.Error.Printf("token or verification type are empty in user.Verify")
|
||||
return false
|
||||
}
|
||||
|
||||
vi := slices.IndexFunc(*u.Verifications, func(vsl Verification) bool {
|
||||
return vsl.Type == verificationType && vsl.VerificationToken == token
|
||||
})
|
||||
|
||||
if vi == -1 {
|
||||
logger.Error.Printf("Couldn't find verification in users verifications")
|
||||
return false
|
||||
}
|
||||
|
||||
if (*u.Verifications)[vi].VerifiedAt != nil {
|
||||
logger.Error.Printf("VerifiedAt is not nil, already verified?: %#v", (*u.Verifications)[vi])
|
||||
return false
|
||||
}
|
||||
|
||||
t := time.Now()
|
||||
(*u.Verifications)[vi].VerifiedAt = &t
|
||||
return true
|
||||
}
|
||||
|
||||
func (u *User) Safe() map[string]interface{} {
|
||||
result := map[string]interface{}{
|
||||
"email": u.Email,
|
||||
@@ -128,3 +398,61 @@ func (u *User) Safe() map[string]interface{} {
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func extractUserIDFrom(tokenString string) (uint, error) {
|
||||
jwtSigningMethod := jwt.SigningMethodHS256
|
||||
jwtParser := jwt.NewParser(jwt.WithValidMethods([]string{jwtSigningMethod.Alg()}))
|
||||
token, err := jwtParser.Parse(tokenString, func(_ *jwt.Token) (interface{}, error) {
|
||||
return []byte(config.Auth.JWTSecret), nil
|
||||
})
|
||||
|
||||
// Handle parsing errors (excluding expiration error)
|
||||
if err != nil && !errors.Is(err, jwt.ErrTokenExpired) || token == nil {
|
||||
logger.Error.Printf("Error parsing token: %v", err)
|
||||
return 0, err
|
||||
}
|
||||
|
||||
claims, ok := token.Claims.(jwt.MapClaims)
|
||||
if !ok {
|
||||
logger.Error.Print("Invalid token claims structure")
|
||||
return 0, fmt.Errorf("invalid token claims format")
|
||||
}
|
||||
|
||||
// Validate required session_id claim
|
||||
if _, exists := claims["session_id"]; !exists {
|
||||
logger.Error.Print("Missing session_id in token claims")
|
||||
return 0, fmt.Errorf("missing session_id claim")
|
||||
}
|
||||
|
||||
// Return token, claims, and original error (might be expiration)
|
||||
|
||||
if _, exists := claims["session_id"]; !exists {
|
||||
logger.Error.Print("Missing session_id in token claims")
|
||||
return 0, fmt.Errorf("missing session_id claim")
|
||||
}
|
||||
id, ok := claims["user_id"]
|
||||
if !ok {
|
||||
return 0, fmt.Errorf("missing user_id claim")
|
||||
}
|
||||
|
||||
return uint(id.(float64)), nil
|
||||
}
|
||||
|
||||
func GetUsersWhere(db *gorm.DB, where map[string]interface{}) (*[]User, error) {
|
||||
var users []User
|
||||
result := db.
|
||||
Preload(clause.Associations).
|
||||
Preload("Membership").
|
||||
Preload("Membership.SubscriptionModel").
|
||||
Preload("Licence").
|
||||
Preload("Licence.Categories").
|
||||
Preload("Verifications").
|
||||
Where(where).Find(&users)
|
||||
if result.Error != nil {
|
||||
if result.Error == gorm.ErrRecordNotFound {
|
||||
return nil, gorm.ErrRecordNotFound
|
||||
}
|
||||
return nil, result.Error
|
||||
}
|
||||
return &users, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user