|
|
|
@ -24,12 +24,29 @@ import (
|
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
|
|
"github.com/jmoiron/sqlx"
|
|
|
|
|
"golang.org/x/text/cases"
|
|
|
|
|
"golang.org/x/text/language"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
type Role struct {
|
|
|
|
|
Name string `db:"role"`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (r *Role) String() string {
|
|
|
|
|
return cases.Title(language.BritishEnglish).String(strings.ToLower(r.Name))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (r *Role) Scan(src any) error {
|
|
|
|
|
value, ok := src.(string)
|
|
|
|
|
if !ok {
|
|
|
|
|
return fmt.Errorf("could not cast %v of %T to string", src, src)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
*r = Role{Name: value}
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type RoleName string
|
|
|
|
|
|
|
|
|
|
const (
|
|
|
|
@ -51,6 +68,7 @@ type User struct {
|
|
|
|
|
Name string `db:"name"`
|
|
|
|
|
Reminder string `db:"reminder"` // reminder email address
|
|
|
|
|
roles []*Role `db:"-"`
|
|
|
|
|
canDelete bool
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (u *User) Roles() ([]*Role, error) {
|
|
|
|
@ -83,6 +101,10 @@ outer:
|
|
|
|
|
return roleMatched, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (u *User) CanDelete() bool {
|
|
|
|
|
return u.canDelete
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type UserModel struct {
|
|
|
|
|
DB *sqlx.DB
|
|
|
|
|
}
|
|
|
|
@ -322,3 +344,157 @@ WHERE v.id = ?
|
|
|
|
|
|
|
|
|
|
return &user, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type UserListOption func(context.Context, []*User) error
|
|
|
|
|
|
|
|
|
|
func (m *UserModel) List(ctx context.Context, options ...UserListOption) ([]*User, error) {
|
|
|
|
|
rows, err := m.DB.QueryxContext(ctx, `SELECT id, name FROM voters ORDER BY name`)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, fmt.Errorf(errCouldNotExecuteQuery, err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
defer func(rows *sqlx.Rows) {
|
|
|
|
|
_ = rows.Close()
|
|
|
|
|
}(rows)
|
|
|
|
|
|
|
|
|
|
users := make([]*User, 0)
|
|
|
|
|
|
|
|
|
|
for rows.Next() {
|
|
|
|
|
if err = rows.Err(); err != nil {
|
|
|
|
|
return nil, fmt.Errorf(errCouldNotFetchRow, err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var user User
|
|
|
|
|
if err = rows.StructScan(&user); err != nil {
|
|
|
|
|
return nil, fmt.Errorf(errCouldNotScanResult, err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
users = append(users, &user)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for _, option := range options {
|
|
|
|
|
if err = option(ctx, users); err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return users, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (m *UserModel) WithRolesOption() UserListOption {
|
|
|
|
|
return func(ctx context.Context, users []*User) error {
|
|
|
|
|
if len(users) == 0 {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
userIDs := make([]int64, len(users))
|
|
|
|
|
for idx := range users {
|
|
|
|
|
userIDs[idx] = users[idx].ID
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
query, args, err := sqlx.In(
|
|
|
|
|
`SELECT voter_id, role FROM user_roles WHERE voter_id IN (?)`,
|
|
|
|
|
userIDs,
|
|
|
|
|
)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf(errCouldNotCreateInQuery, err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
rows, err := m.DB.QueryxContext(ctx, query, args...)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf(errCouldNotExecuteQuery, err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
defer func(rows *sqlx.Rows) {
|
|
|
|
|
_ = rows.Close()
|
|
|
|
|
}(rows)
|
|
|
|
|
|
|
|
|
|
roleMap := make(map[int64][]*Role, len(users))
|
|
|
|
|
|
|
|
|
|
for rows.Next() {
|
|
|
|
|
if err := rows.Err(); err != nil {
|
|
|
|
|
return fmt.Errorf(errCouldNotFetchRow, err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var userID int64
|
|
|
|
|
var role Role
|
|
|
|
|
|
|
|
|
|
if err := rows.Scan(&userID, &role); err != nil {
|
|
|
|
|
return fmt.Errorf(errCouldNotScanResult, err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if _, ok := roleMap[userID]; !ok {
|
|
|
|
|
roleMap[userID] = []*Role{&role}
|
|
|
|
|
} else {
|
|
|
|
|
roleMap[userID] = append(roleMap[userID], &role)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for idx := range users {
|
|
|
|
|
if roles, ok := roleMap[users[idx].ID]; ok {
|
|
|
|
|
users[idx].roles = roles
|
|
|
|
|
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
users[idx].roles = make([]*Role, 0)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (m *UserModel) CanDeleteOption() UserListOption {
|
|
|
|
|
return func(ctx context.Context, users []*User) error {
|
|
|
|
|
if len(users) == 0 {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
userIDs := make([]int64, len(users))
|
|
|
|
|
for idx := range users {
|
|
|
|
|
userIDs[idx] = users[idx].ID
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
query, args, err := sqlx.In(
|
|
|
|
|
`SELECT id
|
|
|
|
|
FROM voters
|
|
|
|
|
WHERE id IN (?)
|
|
|
|
|
AND NOT EXISTS(SELECT * FROM decisions WHERE proponent = voters.id)
|
|
|
|
|
AND NOT EXISTS(SELECT * FROM votes WHERE voter = voters.id)
|
|
|
|
|
AND NOT EXISTS(SELECT * FROM user_roles WHERE voter_id = voters.id)`,
|
|
|
|
|
userIDs,
|
|
|
|
|
)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf(errCouldNotCreateInQuery, err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
rows, err := m.DB.QueryxContext(ctx, query, args...)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf(errCouldNotExecuteQuery, err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
defer func(rows *sqlx.Rows) {
|
|
|
|
|
_ = rows.Close()
|
|
|
|
|
}(rows)
|
|
|
|
|
|
|
|
|
|
for rows.Next() {
|
|
|
|
|
if err := rows.Err(); err != nil {
|
|
|
|
|
return fmt.Errorf(errCouldNotFetchRow, err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var userID int64
|
|
|
|
|
|
|
|
|
|
if err := rows.Scan(&userID); err != nil {
|
|
|
|
|
return fmt.Errorf(errCouldNotScanResult, err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for idx := range users {
|
|
|
|
|
if userID == users[idx].ID {
|
|
|
|
|
users[idx].canDelete = true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|