spark-server/internal/module/admin/model/users.go

267 lines
8.0 KiB
Go

package model
import (
"context"
"strings"
"time"
userentity "spark-server/internal/module/user/entity"
"spark-server/internal/pkg/errs"
)
func (m *Repo) ListUsers(ctx context.Context, emailLike string, page, size int) ([]UserRow, int64, error) {
db, err := m.session(ctx)
if err != nil {
return nil, 0, err
}
base := db.Table("users AS u").Joins("LEFT JOIN user_emails e ON e.user_id = u.id")
emailLike = strings.TrimSpace(emailLike)
if emailLike != "" {
base = base.Where("e.email LIKE ?", "%"+emailLike+"%")
}
var total int64
_ = base.Count(&total).Error
var rows []userScanRow
if err := base.Select(userListSelect).Order("u.id DESC").Offset(page * size).Limit(size).Scan(&rows).Error; err != nil {
return nil, 0, mapDBErr(err)
}
return userScanToRows(rows), total, nil
}
const userListSelect = `u.id, u.spark_id, u.nickname, u.avatar, u.status, u.last_active_at, u.created_at,
COALESCE(e.email,'') AS email,
COALESCE((SELECT d.device_id FROM user_devices d WHERE d.user_id = u.id ORDER BY d.last_login_at DESC LIMIT 1), 0) AS device_id,
COALESCE((SELECT d.platform FROM user_devices d WHERE d.user_id = u.id ORDER BY d.last_login_at DESC LIMIT 1), '') AS platform`
func (m *Repo) UsersByEmail(ctx context.Context, email string) ([]UserRow, error) {
db, err := m.session(ctx)
if err != nil {
return nil, err
}
var rows []userScanRow
err = db.Raw(`
SELECT u.id, u.spark_id, u.nickname, u.avatar, u.status, u.last_active_at, u.created_at,
e.email,
COALESCE((SELECT d.device_id FROM user_devices d WHERE d.user_id = u.id ORDER BY d.last_login_at DESC LIMIT 1), 0) AS device_id,
COALESCE((SELECT d.platform FROM user_devices d WHERE d.user_id = u.id ORDER BY d.last_login_at DESC LIMIT 1), '') AS platform
FROM user_emails e
INNER JOIN users u ON u.id = e.user_id
WHERE e.email = ?`, email).Scan(&rows).Error
if err != nil {
return nil, mapDBErr(err)
}
return userScanToRows(rows), nil
}
func (m *Repo) UserByID(ctx context.Context, id int64) (*UserRow, error) {
db, err := m.session(ctx)
if err != nil {
return nil, err
}
var rows []userScanRow
err = db.Raw(`
SELECT u.id, u.spark_id, u.nickname, u.avatar, u.status, u.last_active_at, u.created_at,
COALESCE(e.email,'') AS email,
COALESCE((SELECT d.device_id FROM user_devices d WHERE d.user_id = u.id ORDER BY d.last_login_at DESC LIMIT 1), 0) AS device_id,
COALESCE((SELECT d.platform FROM user_devices d WHERE d.user_id = u.id ORDER BY d.last_login_at DESC LIMIT 1), '') AS platform
FROM users u
LEFT JOIN user_emails e ON e.user_id = u.id
WHERE u.id = ?`, id).Scan(&rows).Error
if err != nil {
return nil, mapDBErr(err)
}
if len(rows) == 0 || rows[0].ID == 0 {
return nil, errs.ErrNotFound
}
r := userScanToRow(rows[0])
return &r, nil
}
func (m *Repo) UsersByNickname(ctx context.Context, nickname string) ([]UserRow, error) {
db, err := m.session(ctx)
if err != nil {
return nil, err
}
var rows []userScanRow
err = db.Raw(`
SELECT u.id, u.spark_id, u.nickname, u.avatar, u.status, u.last_active_at, u.created_at,
COALESCE(e.email,'') AS email, 0 AS device_id, '' AS platform
FROM users u
LEFT JOIN user_emails e ON e.user_id = u.id
WHERE u.nickname LIKE ?
ORDER BY u.id DESC LIMIT 50`, "%"+strings.TrimSpace(nickname)+"%").Scan(&rows).Error
if err != nil {
return nil, mapDBErr(err)
}
return userScanToRows(rows), nil
}
func (m *Repo) UsersByDevice(ctx context.Context, deviceID string, deviceIDNum uint64) ([]UserRow, error) {
db, err := m.session(ctx)
if err != nil {
return nil, err
}
var rows []userScanRow
err = db.Raw(`
SELECT u.id, u.spark_id, u.nickname, u.avatar, u.status, u.last_active_at, u.created_at,
COALESCE(e.email,'') AS email, d.device_id, d.platform
FROM user_devices d
INNER JOIN users u ON u.id = d.user_id
LEFT JOIN user_emails e ON e.user_id = u.id
WHERE CAST(d.device_id AS CHAR) = ? OR d.device_id = ?
ORDER BY d.last_login_at DESC LIMIT 50`, deviceID, deviceIDNum).Scan(&rows).Error
if err != nil {
return nil, mapDBErr(err)
}
return userScanToRows(rows), nil
}
func (m *Repo) UsersByIP(ctx context.Context, ip string) ([]UserRow, error) {
db, err := m.session(ctx)
if err != nil {
return nil, err
}
var rows []userScanRow
err = db.Raw(`
SELECT DISTINCT u.id, u.spark_id, u.nickname, u.avatar, u.status, u.last_active_at, u.created_at,
COALESCE(e.email,'') AS email, d.device_id, d.platform
FROM user_devices d
INNER JOIN users u ON u.id = d.user_id
LEFT JOIN user_emails e ON e.user_id = u.id
WHERE d.last_ip = ?
ORDER BY u.id DESC LIMIT 100`, ip).Scan(&rows).Error
if err != nil {
return nil, mapDBErr(err)
}
return userScanToRows(rows), nil
}
func (m *Repo) SetUserStatusByEmail(ctx context.Context, email string, status userentity.Status) error {
db, err := m.session(ctx)
if err != nil {
return err
}
res := db.Exec(`
UPDATE users u
INNER JOIN user_emails e ON e.user_id = u.id
SET u.status = ?, u.updated_at = ?
WHERE e.email = ?`, int(status), time.Now(), email)
if res.Error != nil {
return mapDBErr(res.Error)
}
if res.RowsAffected == 0 {
return errs.ErrNotFound
}
return nil
}
func (m *Repo) SetNicknameByEmail(ctx context.Context, email, nickname string) error {
db, err := m.session(ctx)
if err != nil {
return err
}
res := db.Exec(`
UPDATE users u
INNER JOIN user_emails e ON e.user_id = u.id
SET u.nickname = ?, u.updated_at = ?
WHERE e.email = ?`, nickname, time.Now(), email)
if res.Error != nil {
return mapDBErr(res.Error)
}
if res.RowsAffected == 0 {
return errs.ErrNotFound
}
return nil
}
func (m *Repo) ClearAvatarByEmail(ctx context.Context, email string) error {
db, err := m.session(ctx)
if err != nil {
return err
}
res := db.Exec(`
UPDATE users u
INNER JOIN user_emails e ON e.user_id = u.id
SET u.avatar = '', u.updated_at = ?
WHERE e.email = ?`, time.Now(), email)
if res.Error != nil {
return mapDBErr(res.Error)
}
if res.RowsAffected == 0 {
return errs.ErrNotFound
}
return nil
}
type SameIPRankRow struct {
IP string
UserCount int64
DeviceCount int64
}
func (m *Repo) SameIPRank(ctx context.Context, limit int) ([]SameIPRankRow, error) {
db, err := m.session(ctx)
if err != nil {
return nil, err
}
var rows []struct {
IP string `gorm:"column:ip"`
UserCount int64 `gorm:"column:user_count"`
DeviceCount int64 `gorm:"column:device_count"`
}
err = db.Raw(`
SELECT last_ip AS ip, COUNT(DISTINCT user_id) AS user_count, COUNT(*) AS device_count
FROM user_devices
WHERE last_ip IS NOT NULL AND last_ip <> '' AND last_ip NOT LIKE '127.%' AND last_ip <> '::1'
GROUP BY last_ip
ORDER BY user_count DESC
LIMIT ?`, limit).Scan(&rows).Error
if err != nil {
return nil, mapDBErr(err)
}
out := make([]SameIPRankRow, len(rows))
for i, r := range rows {
out[i] = SameIPRankRow{IP: r.IP, UserCount: r.UserCount, DeviceCount: r.DeviceCount}
}
return out, nil
}
func (m *Repo) LookupUserIDByEmail(ctx context.Context, email string) (int64, error) {
db, err := m.session(ctx)
if err != nil {
return 0, err
}
var id int64
err = db.Table("user_emails").Select("user_id").Where("email = ?", email).Scan(&id).Error
return id, mapDBErr(err)
}
type userScanRow struct {
ID int64 `gorm:"column:id"`
SparkID string `gorm:"column:spark_id"`
Nickname string `gorm:"column:nickname"`
Avatar string `gorm:"column:avatar"`
Status int `gorm:"column:status"`
LastActiveAt *time.Time `gorm:"column:last_active_at"`
CreatedAt time.Time `gorm:"column:created_at"`
Email string `gorm:"column:email"`
DeviceID uint64 `gorm:"column:device_id"`
Platform string `gorm:"column:platform"`
}
func userScanToRows(rows []userScanRow) []UserRow {
out := make([]UserRow, 0, len(rows))
for _, r := range rows {
out = append(out, userScanToRow(r))
}
return out
}
func userScanToRow(r userScanRow) UserRow {
return UserRow{
ID: r.ID, SparkID: r.SparkID, Nickname: r.Nickname, Avatar: r.Avatar,
Status: r.Status, LastActiveAt: r.LastActiveAt, CreatedAt: r.CreatedAt,
Email: r.Email, DeviceID: r.DeviceID, Platform: r.Platform,
}
}