package db import ( "context" "time" "git.haelnorr.com/h/golib/hwsauth" "git.haelnorr.com/h/oslstats/internal/permissions" "git.haelnorr.com/h/oslstats/internal/roles" "github.com/bwmarrin/discordgo" "github.com/pkg/errors" "github.com/uptrace/bun" ) type User struct { bun.BaseModel `bun:"table:users,alias:u"` ID int `bun:"id,pk,autoincrement" json:"id"` Username string `bun:"username,unique" json:"username"` CreatedAt int64 `bun:"created_at" json:"created_at"` DiscordID string `bun:"discord_id,unique" json:"discord_id"` Roles []*Role `bun:"m2m:user_roles,join:User=Role" json:"-"` Player *Player `bun:"rel:has-one,join:id=user_id"` } func (u *User) GetID() int { return u.ID } var CurrentUser hwsauth.ContextLoader[*User] // CreateUser creates a new user with the given username and password func CreateUser(ctx context.Context, tx bun.Tx, username string, discorduser *discordgo.User, audit *AuditMeta) (*User, error) { if discorduser == nil { return nil, errors.New("user cannot be nil") } user := &User{ Username: username, CreatedAt: time.Now().Unix(), DiscordID: discorduser.ID, } audit.u = user err := Insert(tx, user). WithAudit(audit, nil). Returning("id"). Exec(ctx) if err != nil { return nil, errors.Wrap(err, "db.Insert") } return user, nil } // GetUserByID queries the database for a user matching the given ID // Returns a BadRequestNotFound error if no user is found func GetUserByID(ctx context.Context, tx bun.Tx, id int) (*User, error) { return GetByID[User](tx, id).Relation("Player").Get(ctx) } // GetUserByUsername queries the database for a user matching the given username // Returns a BadRequestNotFound error if no user is found func GetUserByUsername(ctx context.Context, tx bun.Tx, username string) (*User, error) { if username == "" { return nil, errors.New("username not provided") } return GetByField[User](tx, "username", username).Relation("Player").Get(ctx) } // GetUserByDiscordID queries the database for a user matching the given discord id // Returns a BadRequestNotFound error if no user is found func GetUserByDiscordID(ctx context.Context, tx bun.Tx, discordID string) (*User, error) { if discordID == "" { return nil, errors.New("discord_id not provided") } return GetByField[User](tx, "u.discord_id", discordID).Relation("Player").Get(ctx) } // GetRoles loads all the roles for this user func (u *User) GetRoles(ctx context.Context, tx bun.Tx) ([]*Role, error) { if u == nil { return nil, errors.New("user cannot be nil") } u, err := GetByField[User](tx, "id", u.ID). Relation("Roles").Get(ctx) if err != nil { return nil, errors.Wrap(err, "GetByField") } return u.Roles, nil } // GetPermissions loads and returns all permissions for this user func (u *User) GetPermissions(ctx context.Context, tx bun.Tx) ([]*Permission, error) { if u == nil { return nil, errors.New("user cannot be nil") } return GetList[Permission](tx). Join("JOIN role_permissions AS rp on rp.permission_id = p.id"). Join("JOIN user_roles AS ur ON ur.role_id = rp.role_id"). Where("ur.user_id = ?", u.ID). GetAll(ctx) } // HasPermission checks if user has a specific permission (including wildcard check) func (u *User) HasPermission(ctx context.Context, tx bun.Tx, permissionName permissions.Permission) (bool, error) { if u == nil { return false, errors.New("user cannot be nil") } if permissionName == "" { return false, errors.New("permissionName cannot be empty") } perms, err := u.GetPermissions(ctx, tx) if err != nil { return false, err } for _, p := range perms { if p.Name == permissionName || p.Name == permissions.Wildcard { return true, nil } } return false, nil } // HasRole checks if user has a specific role func (u *User) HasRole(ctx context.Context, tx bun.Tx, roleName roles.Role) (bool, error) { if u == nil { return false, errors.New("user cannot be nil") } return HasRole(ctx, tx, u.ID, roleName) } // IsAdmin is a convenience method to check if user has admin role func (u *User) IsAdmin(ctx context.Context, tx bun.Tx) (bool, error) { if u == nil { return false, errors.New("user cannot be nil") } return u.HasRole(ctx, tx, "admin") } func GetUsers(ctx context.Context, tx bun.Tx, pageOpts *PageOpts) (*List[User], error) { defaults := &PageOpts{1, 50, bun.OrderAsc, "id"} return GetList[User](tx).Relation("Player").GetPaged(ctx, pageOpts, defaults) } // GetUsersWithRoles queries the database for users with their roles preloaded func GetUsersWithRoles(ctx context.Context, tx bun.Tx, pageOpts *PageOpts) (*List[User], error) { defaults := &PageOpts{1, 25, bun.OrderAsc, "id"} return GetList[User](tx).Relation("Roles").GetPaged(ctx, pageOpts, defaults) }