225 lines
5.1 KiB
Go
225 lines
5.1 KiB
Go
package db
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"time"
|
|
|
|
"git.haelnorr.com/h/oslstats/internal/roles"
|
|
"github.com/pkg/errors"
|
|
"github.com/uptrace/bun"
|
|
)
|
|
|
|
type Role struct {
|
|
bun.BaseModel `bun:"table:roles,alias:r"`
|
|
|
|
ID int `bun:"id,pk,autoincrement"`
|
|
Name roles.Role `bun:"name,unique,notnull"`
|
|
DisplayName string `bun:"display_name,notnull"`
|
|
Description string `bun:"description"`
|
|
IsSystem bool `bun:"is_system,default:false"`
|
|
CreatedAt int64 `bun:"created_at,notnull"`
|
|
UpdatedAt *int64 `bun:"updated_at"`
|
|
|
|
// Relations (loaded on demand)
|
|
Users []User `bun:"m2m:user_roles,join:Role=User"`
|
|
Permissions []Permission `bun:"m2m:role_permissions,join:Role=Permission"`
|
|
}
|
|
|
|
type RolePermission struct {
|
|
RoleID int `bun:",pk"`
|
|
Role *Role `bun:"rel:belongs-to,join:role_id=id"`
|
|
PermissionID int `bun:",pk"`
|
|
Permission *Permission `bun:"rel:belongs-to,join:permission_id=id"`
|
|
}
|
|
|
|
// GetRoleByName queries the database for a role matching the given name
|
|
// Returns nil, nil if no role is found
|
|
func GetRoleByName(ctx context.Context, tx bun.Tx, name roles.Role) (*Role, error) {
|
|
if name == "" {
|
|
return nil, errors.New("name cannot be empty")
|
|
}
|
|
|
|
role := new(Role)
|
|
err := tx.NewSelect().
|
|
Model(role).
|
|
Where("name = ?", name).
|
|
Limit(1).
|
|
Scan(ctx)
|
|
if err != nil {
|
|
if err == sql.ErrNoRows {
|
|
return nil, nil
|
|
}
|
|
return nil, errors.Wrap(err, "tx.NewSelect")
|
|
}
|
|
return role, nil
|
|
}
|
|
|
|
// GetRoleByID queries the database for a role matching the given ID
|
|
// Returns nil, nil if no role is found
|
|
func GetRoleByID(ctx context.Context, tx bun.Tx, id int) (*Role, error) {
|
|
if id <= 0 {
|
|
return nil, errors.New("id must be positive")
|
|
}
|
|
|
|
role := new(Role)
|
|
err := tx.NewSelect().
|
|
Model(role).
|
|
Where("id = ?", id).
|
|
Limit(1).
|
|
Scan(ctx)
|
|
if err != nil {
|
|
if err == sql.ErrNoRows {
|
|
return nil, nil
|
|
}
|
|
return nil, errors.Wrap(err, "tx.NewSelect")
|
|
}
|
|
return role, nil
|
|
}
|
|
|
|
// GetRoleWithPermissions loads a role and all its permissions
|
|
func GetRoleWithPermissions(ctx context.Context, tx bun.Tx, id int) (*Role, error) {
|
|
if id <= 0 {
|
|
return nil, errors.New("id must be positive")
|
|
}
|
|
|
|
role := new(Role)
|
|
err := tx.NewSelect().
|
|
Model(role).
|
|
Where("id = ?", id).
|
|
Relation("Permissions").
|
|
Limit(1).
|
|
Scan(ctx)
|
|
if err != nil {
|
|
if err == sql.ErrNoRows {
|
|
return nil, nil
|
|
}
|
|
return nil, errors.Wrap(err, "tx.NewSelect")
|
|
}
|
|
return role, nil
|
|
}
|
|
|
|
// ListAllRoles returns all roles
|
|
func ListAllRoles(ctx context.Context, tx bun.Tx) ([]*Role, error) {
|
|
var roles []*Role
|
|
err := tx.NewSelect().
|
|
Model(&roles).
|
|
Order("name ASC").
|
|
Scan(ctx)
|
|
if err != nil && err != sql.ErrNoRows {
|
|
return nil, errors.Wrap(err, "tx.NewSelect")
|
|
}
|
|
return roles, nil
|
|
}
|
|
|
|
// CreateRole creates a new role
|
|
func CreateRole(ctx context.Context, tx bun.Tx, role *Role) error {
|
|
if role == nil {
|
|
return errors.New("role cannot be nil")
|
|
}
|
|
role.CreatedAt = time.Now().Unix()
|
|
|
|
_, err := tx.NewInsert().
|
|
Model(role).
|
|
Returning("id").
|
|
Exec(ctx)
|
|
if err != nil {
|
|
return errors.Wrap(err, "tx.NewInsert")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// UpdateRole updates an existing role
|
|
func UpdateRole(ctx context.Context, tx bun.Tx, role *Role) error {
|
|
if role == nil {
|
|
return errors.New("role cannot be nil")
|
|
}
|
|
if role.ID <= 0 {
|
|
return errors.New("role id must be positive")
|
|
}
|
|
|
|
_, err := tx.NewUpdate().
|
|
Model(role).
|
|
WherePK().
|
|
Exec(ctx)
|
|
if err != nil {
|
|
return errors.Wrap(err, "tx.NewUpdate")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// DeleteRole deletes a role (checks IsSystem protection)
|
|
func DeleteRole(ctx context.Context, tx bun.Tx, id int) error {
|
|
if id <= 0 {
|
|
return errors.New("id must be positive")
|
|
}
|
|
|
|
// Check if role is system role
|
|
role, err := GetRoleByID(ctx, tx, id)
|
|
if err != nil {
|
|
return errors.Wrap(err, "GetRoleByID")
|
|
}
|
|
if role == nil {
|
|
return errors.New("role not found")
|
|
}
|
|
if role.IsSystem {
|
|
return errors.New("cannot delete system role")
|
|
}
|
|
|
|
_, err = tx.NewDelete().
|
|
Model((*Role)(nil)).
|
|
Where("id = ?", id).
|
|
Exec(ctx)
|
|
if err != nil {
|
|
return errors.Wrap(err, "tx.NewDelete")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// AddPermissionToRole grants a permission to a role
|
|
func AddPermissionToRole(ctx context.Context, tx bun.Tx, roleID, permissionID int) error {
|
|
if roleID <= 0 {
|
|
return errors.New("roleID must be positive")
|
|
}
|
|
if permissionID <= 0 {
|
|
return errors.New("permissionID must be positive")
|
|
}
|
|
rolePerm := &RolePermission{
|
|
RoleID: roleID,
|
|
PermissionID: permissionID,
|
|
}
|
|
_, err := tx.NewInsert().
|
|
Model(rolePerm).
|
|
On("CONFLICT (role_id, permission_id) DO NOTHING").
|
|
Exec(ctx)
|
|
if err != nil {
|
|
return errors.Wrap(err, "tx.NewInsert")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// RemovePermissionFromRole revokes a permission from a role
|
|
func RemovePermissionFromRole(ctx context.Context, tx bun.Tx, roleID, permissionID int) error {
|
|
if roleID <= 0 {
|
|
return errors.New("roleID must be positive")
|
|
}
|
|
if permissionID <= 0 {
|
|
return errors.New("permissionID must be positive")
|
|
}
|
|
|
|
_, err := tx.NewDelete().
|
|
Model((*RolePermission)(nil)).
|
|
Where("role_id = ?", roleID).
|
|
Where("permission_id = ?", permissionID).
|
|
Exec(ctx)
|
|
if err != nil {
|
|
return errors.Wrap(err, "tx.NewDelete")
|
|
}
|
|
|
|
return nil
|
|
}
|