mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-10-03 23:06:36 +08:00
refactor(architecture): eliminate internal package and complete cordis single-owner model and repository migration
- Physically purged all legacy internal/ packages, centralized pkg/model/ and pkg/repository/ - Migrated domain models and database repositories into self-contained owner plugins (user, auth, message_gateway, admin, upload, risk_control) - Decoupled cross-plugin interactions via pure core/contracts and typed EventBus - Ensured 100% test coverage pass, zero data races (-race clean), and 0 lint issues in make code-check
This commit is contained in:
@@ -11,10 +11,9 @@ import (
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
persistence "github.com/Rain-kl/Wavelet/internal/infra/persistence"
|
||||
"github.com/Rain-kl/Wavelet/internal/model"
|
||||
"github.com/Rain-kl/Wavelet/internal/repository"
|
||||
"github.com/Rain-kl/Wavelet/internal/shared/response"
|
||||
persistence "github.com/Rain-kl/Wavelet/pkg/persistence"
|
||||
|
||||
"github.com/Rain-kl/Wavelet/pkg/response"
|
||||
"github.com/Rain-kl/Wavelet/plugins/domain/auth"
|
||||
"github.com/gin-contrib/sessions"
|
||||
"github.com/gin-gonic/gin"
|
||||
@@ -60,7 +59,7 @@ func Login(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
user, err := repository.GetUserByUsername(c.Request.Context(), req.Username)
|
||||
user, err := GetUserByUsername(c.Request.Context(), req.Username)
|
||||
if err != nil {
|
||||
response.AbortUnauthorized(c, errPasswordMismatch)
|
||||
return
|
||||
@@ -87,7 +86,7 @@ func Register(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
newUser := &model.User{
|
||||
newUser := &User{
|
||||
Username: req.Username,
|
||||
Email: req.Email,
|
||||
IsActive: true,
|
||||
@@ -128,7 +127,7 @@ func ChangePassword(c *gin.Context) {
|
||||
}
|
||||
|
||||
userID := auth.GetUserIDFromContext(c)
|
||||
user, err := repository.GetUserByID(c.Request.Context(), userID)
|
||||
user, err := GetUserByID(c.Request.Context(), userID)
|
||||
if err != nil {
|
||||
response.AbortNotFound(c, errUserNotFound)
|
||||
return
|
||||
@@ -160,7 +159,7 @@ func UpdateProfile(c *gin.Context) {
|
||||
}
|
||||
|
||||
userID := auth.GetUserIDFromContext(c)
|
||||
user, err := repository.GetUserByID(c.Request.Context(), userID)
|
||||
user, err := GetUserByID(c.Request.Context(), userID)
|
||||
if err != nil {
|
||||
response.AbortNotFound(c, errUserNotFound)
|
||||
return
|
||||
@@ -184,7 +183,7 @@ func UpdateProfile(c *gin.Context) {
|
||||
// ListAccessTokens lists access tokens for the current user.
|
||||
func ListAccessTokens(c *gin.Context) {
|
||||
userID := auth.GetUserIDFromContext(c)
|
||||
var tokens []model.AccessToken
|
||||
var tokens []AccessToken
|
||||
gormDB := persistence.DB(c.Request.Context())
|
||||
_ = gormDB.Where("user_id = ?", userID).Find(&tokens).Error
|
||||
c.JSON(http.StatusOK, response.OK(tokens))
|
||||
@@ -215,7 +214,7 @@ func CreateAccessToken(c *gin.Context) {
|
||||
masked = rawToken[:4] + "..." + rawToken[len(rawToken)-4:]
|
||||
}
|
||||
|
||||
token := model.AccessToken{
|
||||
token := AccessToken{
|
||||
UserID: userID,
|
||||
Name: req.Name,
|
||||
TokenHash: tokenHash,
|
||||
@@ -245,7 +244,7 @@ func DeleteAccessToken(c *gin.Context) {
|
||||
}
|
||||
|
||||
userID := auth.GetUserIDFromContext(c)
|
||||
var token model.AccessToken
|
||||
var token AccessToken
|
||||
gormDB := persistence.DB(c.Request.Context())
|
||||
if err := gormDB.Where("id = ? AND user_id = ?", id, userID).First(&token).Error; err != nil {
|
||||
response.AbortNotFound(c, errTokenNotFound)
|
||||
@@ -267,7 +266,7 @@ func RotateAccessToken(c *gin.Context) {
|
||||
}
|
||||
|
||||
userID := auth.GetUserIDFromContext(c)
|
||||
var token model.AccessToken
|
||||
var token AccessToken
|
||||
gormDB := persistence.DB(c.Request.Context())
|
||||
if err := gormDB.Where("id = ? AND user_id = ?", id, userID).First(&token).Error; err != nil {
|
||||
response.AbortNotFound(c, errTokenNotFound)
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
// Copyright 2025 linux.do
|
||||
// Copyright 2026 Arctel.net
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package user
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/Rain-kl/Wavelet/pkg/util"
|
||||
)
|
||||
|
||||
// AccessToken 个人访问令牌实体
|
||||
type AccessToken struct {
|
||||
ID uint64 `json:"id" gorm:"primaryKey;autoIncrement"`
|
||||
UserID uint64 `json:"user_id" gorm:"index;not null"`
|
||||
Name string `json:"name" gorm:"size:128;not null"`
|
||||
TokenHash string `json:"-" gorm:"size:64;uniqueIndex;not null"`
|
||||
MaskedToken string `json:"masked_token" gorm:"size:64;not null"`
|
||||
IsAdmin bool `json:"is_admin" gorm:"default:false"`
|
||||
CreatedAt time.Time `json:"created_at" gorm:"autoCreateTime"`
|
||||
UpdatedAt time.Time `json:"updated_at" gorm:"autoUpdateTime"`
|
||||
}
|
||||
|
||||
// TableName 表名
|
||||
func (AccessToken) TableName() string {
|
||||
return "w_access_tokens"
|
||||
}
|
||||
|
||||
// User 用户表实体
|
||||
type User struct {
|
||||
ID uint64 `json:"id,string" gorm:"primaryKey;not null"`
|
||||
Username string `json:"username" gorm:"size:64;uniqueIndex"`
|
||||
Password string `json:"password,omitempty" gorm:"size:255"`
|
||||
Nickname string `json:"nickname" gorm:"size:255"`
|
||||
Email string `json:"email" gorm:"size:255;index"`
|
||||
AvatarURL string `json:"avatar_url" gorm:"size:255"`
|
||||
IsActive bool `json:"is_active" gorm:"default:true;index"`
|
||||
IsAdmin bool `json:"is_admin" gorm:"default:false"`
|
||||
Bio string `json:"bio" gorm:"size:500"`
|
||||
Phone string `json:"phone" gorm:"size:32"`
|
||||
Gender string `json:"gender" gorm:"size:16"`
|
||||
Website string `json:"website" gorm:"size:255"`
|
||||
Location string `json:"location" gorm:"size:255"`
|
||||
LastLoginAt time.Time `json:"last_login_at" gorm:"index"`
|
||||
CreatedAt time.Time `json:"created_at" gorm:"autoCreateTime;index"`
|
||||
UpdatedAt time.Time `json:"updated_at" gorm:"autoUpdateTime;index"`
|
||||
}
|
||||
|
||||
// TableName 表名
|
||||
func (User) TableName() string {
|
||||
return "w_users"
|
||||
}
|
||||
|
||||
// SetEncryptedPassword 设置加密密码
|
||||
func (u *User) SetEncryptedPassword(password string) error {
|
||||
trimmed := strings.TrimSpace(password)
|
||||
if trimmed == "" {
|
||||
return errors.New("password cannot be empty")
|
||||
}
|
||||
hash, err := util.HashPassword(trimmed)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
u.Password = hash
|
||||
return nil
|
||||
}
|
||||
|
||||
// CheckPassword 校验密码
|
||||
func (u *User) CheckPassword(password string) bool {
|
||||
if u.Password == "" {
|
||||
util.DummyCheckPassword(password)
|
||||
return false
|
||||
}
|
||||
return util.CheckPasswordHash(u.Password, password)
|
||||
}
|
||||
@@ -44,15 +44,18 @@ func New(opts ...Option) *Plugin {
|
||||
return p
|
||||
}
|
||||
|
||||
// PluginName 用户插件唯一名称标识
|
||||
const PluginName = "user"
|
||||
|
||||
// Name returns the unique identifier for the user domain plugin.
|
||||
func (p *Plugin) Name() string {
|
||||
return "user"
|
||||
return PluginName
|
||||
}
|
||||
|
||||
// Manifest returns the plugin metadata.
|
||||
func (p *Plugin) Manifest() core.Manifest {
|
||||
return core.Manifest{
|
||||
Name: "user",
|
||||
Name: PluginName,
|
||||
Version: "1.0.0",
|
||||
Description: "User profiles, credentials, role management, and access token domain plugin",
|
||||
Author: "Wavelet Team",
|
||||
|
||||
@@ -15,8 +15,7 @@ import (
|
||||
|
||||
"github.com/Rain-kl/Wavelet/core"
|
||||
"github.com/Rain-kl/Wavelet/core/contracts"
|
||||
db "github.com/Rain-kl/Wavelet/internal/infra/persistence"
|
||||
"github.com/Rain-kl/Wavelet/internal/model"
|
||||
db "github.com/Rain-kl/Wavelet/pkg/persistence"
|
||||
"github.com/Rain-kl/Wavelet/plugins/domain/user"
|
||||
)
|
||||
|
||||
@@ -27,8 +26,8 @@ func setupTestDB(t *testing.T) *gorm.DB {
|
||||
require.NoError(t, err)
|
||||
|
||||
require.NoError(t, testDB.AutoMigrate(
|
||||
&model.User{},
|
||||
&model.AccessToken{},
|
||||
&user.User{},
|
||||
&user.AccessToken{},
|
||||
))
|
||||
|
||||
db.SetDB(testDB)
|
||||
|
||||
@@ -0,0 +1,161 @@
|
||||
// Copyright 2025 linux.do
|
||||
// Copyright 2026 Arctel.net
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package user
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"strings"
|
||||
|
||||
db "github.com/Rain-kl/Wavelet/pkg/persistence"
|
||||
"github.com/Rain-kl/Wavelet/pkg/util"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// GetUserByID 通过 ID 获取用户
|
||||
func GetUserByID(ctx context.Context, id uint64) (*User, error) {
|
||||
var u User
|
||||
if err := db.DB(ctx).First(&u, id).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &u, nil
|
||||
}
|
||||
|
||||
// GetUserByUsername 通过用户名获取用户
|
||||
func GetUserByUsername(ctx context.Context, username string) (*User, error) {
|
||||
var u User
|
||||
if err := db.DB(ctx).Where("username = ?", username).First(&u).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &u, nil
|
||||
}
|
||||
|
||||
// GetUserByEmail 通过邮箱获取用户
|
||||
func GetUserByEmail(ctx context.Context, email string) (*User, error) {
|
||||
var u User
|
||||
if err := db.DB(ctx).Where("email = ?", email).First(&u).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &u, nil
|
||||
}
|
||||
|
||||
// CreateUser 创建用户
|
||||
func CreateUser(ctx context.Context, u *User) error {
|
||||
return db.DB(ctx).Create(u).Error
|
||||
}
|
||||
|
||||
// UpdateUser 更新用户
|
||||
func UpdateUser(ctx context.Context, u *User) error {
|
||||
return db.DB(ctx).Save(u).Error
|
||||
}
|
||||
|
||||
// ListUsers 分页查询用户
|
||||
func ListUsers(ctx context.Context, page, pageSize int, keyword string) ([]*User, int64, error) {
|
||||
db := db.DB(ctx).Model(&User{})
|
||||
if keyword != "" {
|
||||
escaped := util.EscapeLike(keyword)
|
||||
db = db.Where("username LIKE ? ESCAPE '\\' OR nickname LIKE ? ESCAPE '\\' OR email LIKE ? ESCAPE '\\'", "%"+escaped+"%", "%"+escaped+"%", "%"+escaped+"%")
|
||||
}
|
||||
|
||||
var total int64
|
||||
if err := db.Count(&total).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
var users []*User
|
||||
offset := (page - 1) * pageSize
|
||||
if err := db.Offset(offset).Limit(pageSize).Order("id DESC").Find(&users).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
return users, total, nil
|
||||
}
|
||||
|
||||
// GetAccessTokenByHash 通过 Hash 查询访问令牌
|
||||
func GetAccessTokenByHash(ctx context.Context, tokenHash string) (*AccessToken, error) {
|
||||
var token AccessToken
|
||||
if err := db.DB(ctx).Where("token_hash = ?", tokenHash).First(&token).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &token, nil
|
||||
}
|
||||
|
||||
// AdminUserListFilter 包含后台用户列表过滤条件
|
||||
type AdminUserListFilter struct {
|
||||
Username string
|
||||
Keyword string
|
||||
Page int
|
||||
PageSize int
|
||||
}
|
||||
|
||||
// ListAdminUsers 获取后台管理用户列表
|
||||
func ListAdminUsers(ctx context.Context, filter AdminUserListFilter) (int64, []User, error) {
|
||||
query := db.DB(ctx).Model(&User{})
|
||||
if filter.Username != "" {
|
||||
escaped := util.EscapeLike(strings.ToLower(filter.Username))
|
||||
query = query.Where("LOWER(username) LIKE ? ESCAPE '\\'", "%"+escaped+"%")
|
||||
}
|
||||
if filter.Keyword != "" {
|
||||
escaped := util.EscapeLike(strings.ToLower(filter.Keyword))
|
||||
query = query.Where("LOWER(username) LIKE ? ESCAPE '\\' OR LOWER(nickname) LIKE ? ESCAPE '\\' OR LOWER(email) LIKE ? ESCAPE '\\'",
|
||||
"%"+escaped+"%", "%"+escaped+"%", "%"+escaped+"%")
|
||||
}
|
||||
|
||||
var total int64
|
||||
if err := query.Count(&total).Error; err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
|
||||
var users []User
|
||||
offset := (filter.Page - 1) * filter.PageSize
|
||||
if err := query.Order("id DESC").Offset(offset).Limit(filter.PageSize).Find(&users).Error; err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
return total, users, nil
|
||||
}
|
||||
|
||||
// UpdateUserActive 更新用户激活状态
|
||||
func UpdateUserActive(ctx context.Context, id uint64, active bool) error {
|
||||
return db.DB(ctx).Model(&User{}).Where("id = ?", id).Update("is_active", active).Error
|
||||
}
|
||||
|
||||
// GetActiveUserByID 获取处于激活状态的用户
|
||||
func GetActiveUserByID(ctx context.Context, id uint64) (*User, error) {
|
||||
var u User
|
||||
if err := db.DB(ctx).Where("id = ? AND is_active = ?", id, true).First(&u).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &u, nil
|
||||
}
|
||||
|
||||
// DeleteUserWithRelations 删除用户及其级联关系
|
||||
func DeleteUserWithRelations(ctx context.Context, id uint64) error {
|
||||
return db.DB(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
if err := tx.Where("user_id = ?", id).Delete(&AccessToken{}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return tx.Where("id = ?", id).Delete(&User{}).Error
|
||||
})
|
||||
}
|
||||
|
||||
// GetFirstAdminUser 获取第一个管理员用户
|
||||
func GetFirstAdminUser(ctx context.Context) (*User, error) {
|
||||
var u User
|
||||
if err := db.DB(ctx).Where("is_admin = ?", true).Order("id ASC").First(&u).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &u, nil
|
||||
}
|
||||
|
||||
// ListUsernamesMatchingBase 列出匹配基础用户名的所有用户名
|
||||
func ListUsernamesMatchingBase(ctx context.Context, base string) ([]string, error) {
|
||||
var usernames []string
|
||||
escaped := util.EscapeLike(strings.ToLower(base))
|
||||
if err := db.DB(ctx).Model(&User{}).
|
||||
Where("LOWER(username) LIKE ? ESCAPE '\\'", escaped+"%").
|
||||
Pluck("username", &usernames).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return usernames, nil
|
||||
}
|
||||
@@ -1,20 +1,24 @@
|
||||
// Copyright 2026 Arctel.net
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package user provides user profiles, credentials, role management, and access token domain services.
|
||||
package user
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/Rain-kl/Wavelet/core/contracts"
|
||||
"github.com/Rain-kl/Wavelet/internal/infra/persistence"
|
||||
"github.com/Rain-kl/Wavelet/internal/infra/persistence/idgen"
|
||||
"github.com/Rain-kl/Wavelet/internal/model"
|
||||
"github.com/Rain-kl/Wavelet/internal/repository"
|
||||
db "github.com/Rain-kl/Wavelet/pkg/persistence"
|
||||
"github.com/Rain-kl/Wavelet/pkg/persistence/idgen"
|
||||
|
||||
pkgu "github.com/Rain-kl/Wavelet/pkg/util"
|
||||
)
|
||||
|
||||
func toUserDTO(u *model.User) *contracts.UserDTO {
|
||||
func toUserDTO(u *User) *contracts.UserDTO {
|
||||
if u == nil {
|
||||
return nil
|
||||
}
|
||||
@@ -44,23 +48,23 @@ func newUserService() contracts.UserService {
|
||||
}
|
||||
|
||||
func (s *userServiceImpl) GetUserByID(ctx context.Context, id uint64) (*contracts.UserDTO, error) {
|
||||
u, err := repository.GetUserByID(ctx, id)
|
||||
u, err := GetUserByID(ctx, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return toUserDTO(&u), nil
|
||||
return toUserDTO(u), nil
|
||||
}
|
||||
|
||||
func (s *userServiceImpl) GetUserByUsername(ctx context.Context, username string) (*contracts.UserDTO, error) {
|
||||
u, err := repository.GetUserByUsername(ctx, username)
|
||||
u, err := GetUserByUsername(ctx, username)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return toUserDTO(&u), nil
|
||||
return toUserDTO(u), nil
|
||||
}
|
||||
|
||||
func (s *userServiceImpl) GetUserByEmail(ctx context.Context, email string) (*contracts.UserDTO, error) {
|
||||
var u model.User
|
||||
var u User
|
||||
if err := db.DB(ctx).Where("email = ?", email).First(&u).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -72,7 +76,7 @@ func (s *userServiceImpl) CreateUser(ctx context.Context, req contracts.CreateUs
|
||||
return nil, errors.New("user: username cannot be empty")
|
||||
}
|
||||
|
||||
user := model.User{
|
||||
user := User{
|
||||
ID: idgen.NextUint64ID(),
|
||||
Username: req.Username,
|
||||
Nickname: req.Nickname,
|
||||
@@ -94,7 +98,7 @@ func (s *userServiceImpl) CreateUser(ctx context.Context, req contracts.CreateUs
|
||||
}
|
||||
}
|
||||
|
||||
if err := repository.CreateUser(ctx, &user); err != nil {
|
||||
if err := CreateUser(ctx, &user); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -129,7 +133,7 @@ func (s *userServiceImpl) UpdateProfile(ctx context.Context, id uint64, req cont
|
||||
}
|
||||
updates["updated_at"] = time.Now()
|
||||
|
||||
if err := db.DB(ctx).Model(&model.User{}).Where("id = ?", id).Updates(updates).Error; err != nil {
|
||||
if err := db.DB(ctx).Model(&User{}).Where("id = ?", id).Updates(updates).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -137,7 +141,7 @@ func (s *userServiceImpl) UpdateProfile(ctx context.Context, id uint64, req cont
|
||||
}
|
||||
|
||||
func (s *userServiceImpl) UpdatePassword(ctx context.Context, id uint64, oldPassword, newPassword string) error {
|
||||
var user model.User
|
||||
var user User
|
||||
if err := db.DB(ctx).Where("id = ?", id).First(&user).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -150,7 +154,7 @@ func (s *userServiceImpl) UpdatePassword(ctx context.Context, id uint64, oldPass
|
||||
return err
|
||||
}
|
||||
|
||||
return db.DB(ctx).Model(&model.User{}).Where("id = ?", id).
|
||||
return db.DB(ctx).Model(&User{}).Where("id = ?", id).
|
||||
Updates(map[string]any{
|
||||
"password": user.Password,
|
||||
"updated_at": time.Now(),
|
||||
@@ -158,7 +162,7 @@ func (s *userServiceImpl) UpdatePassword(ctx context.Context, id uint64, oldPass
|
||||
}
|
||||
|
||||
func (s *userServiceImpl) VerifyPassword(ctx context.Context, id uint64, password string) bool {
|
||||
var user model.User
|
||||
var user User
|
||||
if err := db.DB(ctx).Where("id = ?", id).First(&user).Error; err != nil {
|
||||
pkgu.DummyCheckPassword(password)
|
||||
return false
|
||||
@@ -167,7 +171,7 @@ func (s *userServiceImpl) VerifyPassword(ctx context.Context, id uint64, passwor
|
||||
}
|
||||
|
||||
func (s *userServiceImpl) UpdateLastLogin(ctx context.Context, id uint64, _ string) error {
|
||||
return db.DB(ctx).Model(&model.User{}).Where("id = ?", id).
|
||||
return db.DB(ctx).Model(&User{}).Where("id = ?", id).
|
||||
Updates(map[string]any{
|
||||
"last_login_at": time.Now(),
|
||||
"updated_at": time.Now(),
|
||||
@@ -182,13 +186,13 @@ func (s *userServiceImpl) ListUsers(ctx context.Context, page, pageSize int, key
|
||||
pageSize = 20
|
||||
}
|
||||
|
||||
filter := repository.AdminUserListFilter{
|
||||
filter := AdminUserListFilter{
|
||||
Username: keyword,
|
||||
Page: page,
|
||||
PageSize: pageSize,
|
||||
}
|
||||
|
||||
total, users, err := repository.ListAdminUsers(ctx, filter)
|
||||
total, users, err := ListAdminUsers(ctx, filter)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
@@ -202,9 +206,77 @@ func (s *userServiceImpl) ListUsers(ctx context.Context, page, pageSize int, key
|
||||
}
|
||||
|
||||
func (s *userServiceImpl) SetUserActive(ctx context.Context, id uint64, active bool) error {
|
||||
return repository.UpdateUserActive(ctx, id, active)
|
||||
return UpdateUserActive(ctx, id, active)
|
||||
}
|
||||
|
||||
func (s *userServiceImpl) SetUserAdmin(ctx context.Context, id uint64, admin bool) error {
|
||||
return db.DB(ctx).Model(&model.User{}).Where("id = ?", id).Update("is_admin", admin).Error
|
||||
return db.DB(ctx).Model(&User{}).Where("id = ?", id).Update("is_admin", admin).Error
|
||||
}
|
||||
|
||||
func (s *userServiceImpl) VerifyAccessToken(ctx context.Context, tokenHash string) (*contracts.UserDTO, bool, error) {
|
||||
tokenRecord, err := GetAccessTokenByHash(ctx, tokenHash)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
user, err := GetActiveUserByID(ctx, tokenRecord.UserID)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
return toUserDTO(user), tokenRecord.IsAdmin, nil
|
||||
}
|
||||
|
||||
func (s *userServiceImpl) DeleteUser(ctx context.Context, id uint64) error {
|
||||
return DeleteUserWithRelations(ctx, id)
|
||||
}
|
||||
|
||||
func (s *userServiceImpl) CountUsers(ctx context.Context) (int64, error) {
|
||||
var count int64
|
||||
err := db.DB(ctx).Model(&User{}).Count(&count).Error
|
||||
return count, err
|
||||
}
|
||||
|
||||
func (s *userServiceImpl) CountActiveUsers(ctx context.Context) (int64, error) {
|
||||
var count int64
|
||||
err := db.DB(ctx).Model(&User{}).Where("is_active = ?", true).Count(&count).Error
|
||||
return count, err
|
||||
}
|
||||
|
||||
func (s *userServiceImpl) GetFirstAdminUser(ctx context.Context) (*contracts.UserDTO, error) {
|
||||
u, err := GetFirstAdminUser(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return toUserDTO(u), nil
|
||||
}
|
||||
|
||||
func (s *userServiceImpl) UniqueUsername(ctx context.Context, base string) (string, error) {
|
||||
base = strings.TrimSpace(base)
|
||||
if base == "" {
|
||||
base = PluginName
|
||||
}
|
||||
|
||||
existingUsernames, err := ListUsernamesMatchingBase(ctx, base)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
exists := make(map[string]bool, len(existingUsernames))
|
||||
for _, u := range existingUsernames {
|
||||
exists[strings.ToLower(u)] = true
|
||||
}
|
||||
|
||||
if !exists[strings.ToLower(base)] {
|
||||
return base, nil
|
||||
}
|
||||
|
||||
for i := 1; i <= 1000; i++ {
|
||||
candidate := fmt.Sprintf("%s-%d", base, i)
|
||||
if !exists[strings.ToLower(candidate)] {
|
||||
return candidate, nil
|
||||
}
|
||||
}
|
||||
|
||||
return "", errors.New("failed to generate unique username")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user