162 lines
3.8 KiB
Go
162 lines
3.8 KiB
Go
package hmndata
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
|
|
"git.handmade.network/hmn/hmn/src/db"
|
|
"git.handmade.network/hmn/hmn/src/models"
|
|
"git.handmade.network/hmn/hmn/src/oops"
|
|
"git.handmade.network/hmn/hmn/src/perf"
|
|
)
|
|
|
|
type UsersQuery struct {
|
|
// Ignored when using FetchUser
|
|
UserIDs []int // if empty, all users
|
|
Usernames []string // if empty, all users
|
|
DiscordUserIDs []string // if empty, no Discord filtering
|
|
|
|
// Flags to modify behavior
|
|
AnyStatus bool // Bypasses shadowban system
|
|
}
|
|
|
|
/*
|
|
Fetches users and related models from the database according to all the given
|
|
query params. For the most correct results, provide as much information as you have
|
|
on hand.
|
|
*/
|
|
func FetchUsers(
|
|
ctx context.Context,
|
|
dbConn db.ConnOrTx,
|
|
currentUser *models.User,
|
|
q UsersQuery,
|
|
) ([]*models.User, error) {
|
|
perf := perf.ExtractPerf(ctx)
|
|
perf.StartBlock("SQL", "Fetch users")
|
|
defer perf.EndBlock()
|
|
|
|
var currentUserID *int
|
|
if currentUser != nil {
|
|
currentUserID = ¤tUser.ID
|
|
}
|
|
|
|
for i := range q.Usernames {
|
|
q.Usernames[i] = strings.ToLower(q.Usernames[i])
|
|
}
|
|
|
|
type userRow struct {
|
|
User models.User `db:"hmn_user"`
|
|
AvatarAsset *models.Asset `db:"avatar"`
|
|
DiscordUser *models.DiscordUser `db:"discord_user"`
|
|
}
|
|
|
|
var qb db.QueryBuilder
|
|
qb.Add(`
|
|
SELECT $columns
|
|
FROM
|
|
hmn_user
|
|
LEFT JOIN asset AS avatar ON avatar.id = hmn_user.avatar_asset_id
|
|
LEFT JOIN discord_user ON discord_user.hmn_user_id = hmn_user.id
|
|
WHERE
|
|
TRUE
|
|
`)
|
|
if len(q.UserIDs) > 0 {
|
|
qb.Add(`AND hmn_user.id = ANY($?)`, q.UserIDs)
|
|
}
|
|
if len(q.Usernames) > 0 {
|
|
qb.Add(`AND LOWER(hmn_user.username) = ANY($?)`, q.Usernames)
|
|
}
|
|
if len(q.DiscordUserIDs) > 0 {
|
|
qb.Add(`AND discord_user.userid = ANY($?)`, q.DiscordUserIDs)
|
|
}
|
|
if !q.AnyStatus {
|
|
if currentUser == nil {
|
|
qb.Add(`AND hmn_user.status = $?`, models.UserStatusApproved)
|
|
} else if !currentUser.IsStaff {
|
|
qb.Add(
|
|
`
|
|
AND (
|
|
hmn_user.status = $? -- user is Approved
|
|
OR hmn_user.id = $? -- getting self
|
|
)
|
|
`,
|
|
models.UserStatusApproved,
|
|
currentUserID,
|
|
)
|
|
}
|
|
}
|
|
|
|
userRows, err := db.Query[userRow](ctx, dbConn, qb.String(), qb.Args()...)
|
|
if err != nil {
|
|
return nil, oops.New(err, "failed to fetch users")
|
|
}
|
|
|
|
result := make([]*models.User, len(userRows))
|
|
for i, row := range userRows {
|
|
user := row.User
|
|
user.AvatarAsset = row.AvatarAsset
|
|
user.DiscordUser = row.DiscordUser
|
|
result[i] = &user
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
/*
|
|
Fetches a single user and related data. A wrapper around FetchUsers.
|
|
As with FetchUsers, provide as much information as you know to get the
|
|
most correct results.
|
|
|
|
Returns db.NotFound if no result is found.
|
|
*/
|
|
func FetchUser(
|
|
ctx context.Context,
|
|
dbConn db.ConnOrTx,
|
|
currentUser *models.User,
|
|
userID int,
|
|
q UsersQuery,
|
|
) (*models.User, error) {
|
|
q.UserIDs = []int{userID}
|
|
|
|
res, err := FetchUsers(ctx, dbConn, currentUser, q)
|
|
if err != nil {
|
|
return nil, oops.New(err, "failed to fetch user")
|
|
}
|
|
|
|
if len(res) == 0 {
|
|
return nil, db.NotFound
|
|
}
|
|
|
|
return res[0], nil
|
|
}
|
|
|
|
/*
|
|
Fetches a single user and related data. A wrapper around FetchUsers.
|
|
As with FetchUsers, provide as much information as you know to get the
|
|
most correct results.
|
|
|
|
Returns db.NotFound if no result is found.
|
|
*/
|
|
func FetchUserByUsername(
|
|
ctx context.Context,
|
|
dbConn db.ConnOrTx,
|
|
currentUser *models.User,
|
|
username string,
|
|
q UsersQuery,
|
|
) (*models.User, error) {
|
|
q.Usernames = []string{username}
|
|
|
|
res, err := FetchUsers(ctx, dbConn, currentUser, q)
|
|
if err != nil {
|
|
return nil, oops.New(err, "failed to fetch user")
|
|
}
|
|
|
|
if len(res) == 0 {
|
|
return nil, db.NotFound
|
|
}
|
|
|
|
return res[0], nil
|
|
}
|
|
|
|
// NOTE(ben): Someday we can add CountUsers...I don't have a need for it right now.
|