Files
oslstats/internal/db/user.go

153 lines
4.6 KiB
Go

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)
}