115 lines
3.0 KiB
Go
115 lines
3.0 KiB
Go
package db
|
|
|
|
import (
|
|
"context"
|
|
"time"
|
|
|
|
"git.haelnorr.com/h/oslstats/internal/roles"
|
|
"github.com/pkg/errors"
|
|
"github.com/uptrace/bun"
|
|
)
|
|
|
|
type UserRole struct {
|
|
bun.BaseModel `bun:"table:user_roles,alias:ur"`
|
|
|
|
ID int `bun:"id,pk,autoincrement"`
|
|
UserID int `bun:"user_id,notnull"`
|
|
RoleID int `bun:"role_id,notnull"`
|
|
GrantedBy *int `bun:"granted_by"`
|
|
GrantedAt int64 `bun:"granted_at,notnull"` // TODO: default now
|
|
ExpiresAt *int64 `bun:"expires_at"`
|
|
|
|
// Relations
|
|
User *User `bun:"rel:belongs-to,join:user_id=id"`
|
|
Role *Role `bun:"rel:belongs-to,join:role_id=id"`
|
|
}
|
|
|
|
// GetUserRoles loads all roles for a given user
|
|
func GetUserRoles(ctx context.Context, tx bun.Tx, userID int) ([]*Role, error) {
|
|
if userID <= 0 {
|
|
return nil, errors.New("userID must be positive")
|
|
}
|
|
|
|
var roles []*Role
|
|
err := tx.NewSelect().
|
|
Model(&roles).
|
|
// TODO: why are we joining? can we do relation?
|
|
Join("JOIN user_roles AS ur ON ur.role_id = r.id").
|
|
Where("ur.user_id = ?", userID).
|
|
Where("ur.expires_at IS NULL OR ur.expires_at > ?", time.Now().Unix()).
|
|
Scan(ctx)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "tx.NewSelect")
|
|
}
|
|
return roles, nil
|
|
}
|
|
|
|
// AssignRole grants a role to a user
|
|
func AssignRole(ctx context.Context, tx bun.Tx, userID, roleID int, grantedBy *int) error {
|
|
if userID <= 0 {
|
|
return errors.New("userID must be positive")
|
|
}
|
|
if roleID <= 0 {
|
|
return errors.New("roleID must be positive")
|
|
}
|
|
|
|
now := time.Now().Unix()
|
|
|
|
// TODO: use proper m2m table instead of raw SQL
|
|
_, err := tx.ExecContext(ctx, `
|
|
INSERT INTO user_roles (user_id, role_id, granted_by, granted_at)
|
|
VALUES ($1, $2, $3, $4)
|
|
ON CONFLICT (user_id, role_id) DO NOTHING
|
|
`, userID, roleID, grantedBy, now)
|
|
if err != nil {
|
|
return errors.Wrap(err, "tx.ExecContext")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// RevokeRole removes a role from a user
|
|
func RevokeRole(ctx context.Context, tx bun.Tx, userID, roleID int) error {
|
|
if userID <= 0 {
|
|
return errors.New("userID must be positive")
|
|
}
|
|
if roleID <= 0 {
|
|
return errors.New("roleID must be positive")
|
|
}
|
|
|
|
// TODO: use proper m2m table instead of raw sql
|
|
_, err := tx.ExecContext(ctx, `
|
|
DELETE FROM user_roles
|
|
WHERE user_id = $1 AND role_id = $2
|
|
`, userID, roleID)
|
|
if err != nil {
|
|
return errors.Wrap(err, "tx.ExecContext")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// HasRole checks if a user has a specific role
|
|
func HasRole(ctx context.Context, tx bun.Tx, userID int, roleName roles.Role) (bool, error) {
|
|
if userID <= 0 {
|
|
return false, errors.New("userID must be positive")
|
|
}
|
|
if roleName == "" {
|
|
return false, errors.New("roleName cannot be empty")
|
|
}
|
|
|
|
// TODO: use proper m2m table instead of TableExpr and Join?
|
|
count, err := tx.NewSelect().
|
|
TableExpr("user_roles AS ur").
|
|
Join("JOIN roles AS r ON r.id = ur.role_id").
|
|
Where("ur.user_id = ?", userID).
|
|
Where("r.name = ?", roleName).
|
|
Where("ur.expires_at IS NULL OR ur.expires_at > ?", time.Now().Unix()).
|
|
Count(ctx)
|
|
if err != nil {
|
|
return false, errors.Wrap(err, "tx.NewSelect")
|
|
}
|
|
|
|
return count > 0, nil
|
|
}
|