refactor(数据库): 迁移持久层到 Ent 并清理 GORM
将仓储层/基础设施改为 Ent + 原生 SQL 执行路径,并移除 AutoMigrate 与 GORM 依赖。 重构内容包括: - 仓储层改用 Ent/SQL(含 usage_log/account 等复杂查询),统一错误映射 - 基础设施与 setup 初始化切换为 Ent + SQL migrations - 集成测试与 fixtures 迁移到 Ent 事务模型 - 清理遗留 GORM 模型/依赖,补充迁移与文档说明 - 增加根目录 Makefile 便于前后端编译 测试: - go test -tags unit ./... - go test -tags integration ./...
This commit is contained in:
@@ -2,252 +2,412 @@ package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/Wei-Shaw/sub2api/internal/service"
|
||||
"database/sql"
|
||||
"sort"
|
||||
|
||||
dbent "github.com/Wei-Shaw/sub2api/ent"
|
||||
dbuser "github.com/Wei-Shaw/sub2api/ent/user"
|
||||
"github.com/Wei-Shaw/sub2api/ent/userallowedgroup"
|
||||
"github.com/Wei-Shaw/sub2api/ent/usersubscription"
|
||||
"github.com/Wei-Shaw/sub2api/internal/pkg/pagination"
|
||||
|
||||
"github.com/Wei-Shaw/sub2api/internal/service"
|
||||
"github.com/lib/pq"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type userRepository struct {
|
||||
db *gorm.DB
|
||||
client *dbent.Client
|
||||
sql sqlExecutor
|
||||
begin sqlBeginner
|
||||
}
|
||||
|
||||
func NewUserRepository(db *gorm.DB) service.UserRepository {
|
||||
return &userRepository{db: db}
|
||||
func NewUserRepository(client *dbent.Client, sqlDB *sql.DB) service.UserRepository {
|
||||
return newUserRepositoryWithSQL(client, sqlDB)
|
||||
}
|
||||
|
||||
func (r *userRepository) Create(ctx context.Context, user *service.User) error {
|
||||
m := userModelFromService(user)
|
||||
err := r.db.WithContext(ctx).Create(m).Error
|
||||
if err == nil {
|
||||
applyUserModelToService(user, m)
|
||||
func newUserRepositoryWithSQL(client *dbent.Client, sqlq sqlExecutor) *userRepository {
|
||||
var beginner sqlBeginner
|
||||
if b, ok := sqlq.(sqlBeginner); ok {
|
||||
beginner = b
|
||||
}
|
||||
return translatePersistenceError(err, nil, service.ErrEmailExists)
|
||||
return &userRepository{client: client, sql: sqlq, begin: beginner}
|
||||
}
|
||||
|
||||
func (r *userRepository) Create(ctx context.Context, userIn *service.User) error {
|
||||
if userIn == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
exec := r.sql
|
||||
txClient := r.client
|
||||
var sqlTx *sql.Tx
|
||||
var txClientClose func() error
|
||||
|
||||
if r.begin != nil {
|
||||
var err error
|
||||
sqlTx, err = r.begin.BeginTx(ctx, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
exec = sqlTx
|
||||
txClient = entClientFromSQLTx(sqlTx)
|
||||
txClientClose = txClient.Close
|
||||
defer func() { _ = sqlTx.Rollback() }()
|
||||
}
|
||||
if txClientClose != nil {
|
||||
defer func() { _ = txClientClose() }()
|
||||
}
|
||||
|
||||
created, err := txClient.User.Create().
|
||||
SetEmail(userIn.Email).
|
||||
SetUsername(userIn.Username).
|
||||
SetWechat(userIn.Wechat).
|
||||
SetNotes(userIn.Notes).
|
||||
SetPasswordHash(userIn.PasswordHash).
|
||||
SetRole(userIn.Role).
|
||||
SetBalance(userIn.Balance).
|
||||
SetConcurrency(userIn.Concurrency).
|
||||
SetStatus(userIn.Status).
|
||||
Save(ctx)
|
||||
if err != nil {
|
||||
return translatePersistenceError(err, nil, service.ErrEmailExists)
|
||||
}
|
||||
|
||||
if err := r.syncUserAllowedGroups(ctx, txClient, exec, created.ID, userIn.AllowedGroups); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if sqlTx != nil {
|
||||
if err := sqlTx.Commit(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
applyUserEntityToService(userIn, created)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *userRepository) GetByID(ctx context.Context, id int64) (*service.User, error) {
|
||||
var m userModel
|
||||
err := r.db.WithContext(ctx).First(&m, id).Error
|
||||
m, err := r.client.User.Query().Where(dbuser.IDEQ(id)).Only(ctx)
|
||||
if err != nil {
|
||||
return nil, translatePersistenceError(err, service.ErrUserNotFound, nil)
|
||||
}
|
||||
return userModelToService(&m), nil
|
||||
|
||||
out := userEntityToService(m)
|
||||
groups, err := r.loadAllowedGroups(ctx, []int64{id})
|
||||
if err == nil {
|
||||
if v, ok := groups[id]; ok {
|
||||
out.AllowedGroups = v
|
||||
}
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (r *userRepository) GetByEmail(ctx context.Context, email string) (*service.User, error) {
|
||||
var m userModel
|
||||
err := r.db.WithContext(ctx).Where("email = ?", email).First(&m).Error
|
||||
m, err := r.client.User.Query().Where(dbuser.EmailEQ(email)).Only(ctx)
|
||||
if err != nil {
|
||||
return nil, translatePersistenceError(err, service.ErrUserNotFound, nil)
|
||||
}
|
||||
return userModelToService(&m), nil
|
||||
|
||||
out := userEntityToService(m)
|
||||
groups, err := r.loadAllowedGroups(ctx, []int64{m.ID})
|
||||
if err == nil {
|
||||
if v, ok := groups[m.ID]; ok {
|
||||
out.AllowedGroups = v
|
||||
}
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (r *userRepository) Update(ctx context.Context, user *service.User) error {
|
||||
m := userModelFromService(user)
|
||||
err := r.db.WithContext(ctx).Save(m).Error
|
||||
if err == nil {
|
||||
applyUserModelToService(user, m)
|
||||
func (r *userRepository) Update(ctx context.Context, userIn *service.User) error {
|
||||
if userIn == nil {
|
||||
return nil
|
||||
}
|
||||
return translatePersistenceError(err, nil, service.ErrEmailExists)
|
||||
|
||||
exec := r.sql
|
||||
txClient := r.client
|
||||
var sqlTx *sql.Tx
|
||||
var txClientClose func() error
|
||||
|
||||
if r.begin != nil {
|
||||
var err error
|
||||
sqlTx, err = r.begin.BeginTx(ctx, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
exec = sqlTx
|
||||
txClient = entClientFromSQLTx(sqlTx)
|
||||
txClientClose = txClient.Close
|
||||
defer func() { _ = sqlTx.Rollback() }()
|
||||
}
|
||||
if txClientClose != nil {
|
||||
defer func() { _ = txClientClose() }()
|
||||
}
|
||||
|
||||
updated, err := txClient.User.UpdateOneID(userIn.ID).
|
||||
SetEmail(userIn.Email).
|
||||
SetUsername(userIn.Username).
|
||||
SetWechat(userIn.Wechat).
|
||||
SetNotes(userIn.Notes).
|
||||
SetPasswordHash(userIn.PasswordHash).
|
||||
SetRole(userIn.Role).
|
||||
SetBalance(userIn.Balance).
|
||||
SetConcurrency(userIn.Concurrency).
|
||||
SetStatus(userIn.Status).
|
||||
Save(ctx)
|
||||
if err != nil {
|
||||
return translatePersistenceError(err, service.ErrUserNotFound, service.ErrEmailExists)
|
||||
}
|
||||
|
||||
if err := r.syncUserAllowedGroups(ctx, txClient, exec, updated.ID, userIn.AllowedGroups); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if sqlTx != nil {
|
||||
if err := sqlTx.Commit(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
userIn.UpdatedAt = updated.UpdatedAt
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *userRepository) Delete(ctx context.Context, id int64) error {
|
||||
return r.db.WithContext(ctx).Delete(&userModel{}, id).Error
|
||||
_, err := r.client.User.Delete().Where(dbuser.IDEQ(id)).Exec(ctx)
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *userRepository) List(ctx context.Context, params pagination.PaginationParams) ([]service.User, *pagination.PaginationResult, error) {
|
||||
return r.ListWithFilters(ctx, params, "", "", "")
|
||||
}
|
||||
|
||||
// ListWithFilters lists users with optional filtering by status, role, and search query
|
||||
func (r *userRepository) ListWithFilters(ctx context.Context, params pagination.PaginationParams, status, role, search string) ([]service.User, *pagination.PaginationResult, error) {
|
||||
var users []userModel
|
||||
var total int64
|
||||
q := r.client.User.Query()
|
||||
|
||||
db := r.db.WithContext(ctx).Model(&userModel{})
|
||||
|
||||
// Apply filters
|
||||
if status != "" {
|
||||
db = db.Where("status = ?", status)
|
||||
q = q.Where(dbuser.StatusEQ(status))
|
||||
}
|
||||
if role != "" {
|
||||
db = db.Where("role = ?", role)
|
||||
q = q.Where(dbuser.RoleEQ(role))
|
||||
}
|
||||
if search != "" {
|
||||
searchPattern := "%" + search + "%"
|
||||
db = db.Where(
|
||||
"email ILIKE ? OR username ILIKE ? OR wechat ILIKE ?",
|
||||
searchPattern, searchPattern, searchPattern,
|
||||
q = q.Where(
|
||||
dbuser.Or(
|
||||
dbuser.EmailContainsFold(search),
|
||||
dbuser.UsernameContainsFold(search),
|
||||
dbuser.WechatContainsFold(search),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
if err := db.Count(&total).Error; err != nil {
|
||||
total, err := q.Clone().Count(ctx)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// Query users with pagination (reuse the same db with filters applied)
|
||||
if err := db.Offset(params.Offset()).Limit(params.Limit()).Order("id DESC").Find(&users).Error; err != nil {
|
||||
users, err := q.
|
||||
Offset(params.Offset()).
|
||||
Limit(params.Limit()).
|
||||
Order(dbent.Desc(dbuser.FieldID)).
|
||||
All(ctx)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// Batch load subscriptions for all users (avoid N+1)
|
||||
if len(users) > 0 {
|
||||
userIDs := make([]int64, len(users))
|
||||
userMap := make(map[int64]*service.User, len(users))
|
||||
outUsers := make([]service.User, 0, len(users))
|
||||
for i := range users {
|
||||
userIDs[i] = users[i].ID
|
||||
u := userModelToService(&users[i])
|
||||
outUsers = append(outUsers, *u)
|
||||
userMap[u.ID] = &outUsers[len(outUsers)-1]
|
||||
}
|
||||
|
||||
// Query active subscriptions with groups in one query
|
||||
var subscriptions []userSubscriptionModel
|
||||
if err := r.db.WithContext(ctx).
|
||||
Preload("Group").
|
||||
Where("user_id IN ? AND status = ?", userIDs, service.SubscriptionStatusActive).
|
||||
Find(&subscriptions).Error; err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// Associate subscriptions with users
|
||||
for i := range subscriptions {
|
||||
if user, ok := userMap[subscriptions[i].UserID]; ok {
|
||||
user.Subscriptions = append(user.Subscriptions, *userSubscriptionModelToService(&subscriptions[i]))
|
||||
}
|
||||
}
|
||||
|
||||
return outUsers, paginationResultFromTotal(total, params), nil
|
||||
}
|
||||
|
||||
outUsers := make([]service.User, 0, len(users))
|
||||
for i := range users {
|
||||
outUsers = append(outUsers, *userModelToService(&users[i]))
|
||||
if len(users) == 0 {
|
||||
return outUsers, paginationResultFromTotal(int64(total), params), nil
|
||||
}
|
||||
|
||||
return outUsers, paginationResultFromTotal(total, params), nil
|
||||
userIDs := make([]int64, 0, len(users))
|
||||
userMap := make(map[int64]*service.User, len(users))
|
||||
for i := range users {
|
||||
userIDs = append(userIDs, users[i].ID)
|
||||
u := userEntityToService(users[i])
|
||||
outUsers = append(outUsers, *u)
|
||||
userMap[u.ID] = &outUsers[len(outUsers)-1]
|
||||
}
|
||||
|
||||
// Batch load active subscriptions with groups to avoid N+1.
|
||||
subs, err := r.client.UserSubscription.Query().
|
||||
Where(
|
||||
usersubscription.UserIDIn(userIDs...),
|
||||
usersubscription.StatusEQ(service.SubscriptionStatusActive),
|
||||
).
|
||||
WithGroup().
|
||||
All(ctx)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
for i := range subs {
|
||||
if u, ok := userMap[subs[i].UserID]; ok {
|
||||
u.Subscriptions = append(u.Subscriptions, *userSubscriptionEntityToService(subs[i]))
|
||||
}
|
||||
}
|
||||
|
||||
allowedGroupsByUser, err := r.loadAllowedGroups(ctx, userIDs)
|
||||
if err == nil {
|
||||
for id, u := range userMap {
|
||||
if groups, ok := allowedGroupsByUser[id]; ok {
|
||||
u.AllowedGroups = groups
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return outUsers, paginationResultFromTotal(int64(total), params), nil
|
||||
}
|
||||
|
||||
func (r *userRepository) UpdateBalance(ctx context.Context, id int64, amount float64) error {
|
||||
return r.db.WithContext(ctx).Model(&userModel{}).Where("id = ?", id).
|
||||
Update("balance", gorm.Expr("balance + ?", amount)).Error
|
||||
_, err := r.client.User.Update().Where(dbuser.IDEQ(id)).AddBalance(amount).Save(ctx)
|
||||
return err
|
||||
}
|
||||
|
||||
// DeductBalance 扣减用户余额,仅当余额充足时执行
|
||||
func (r *userRepository) DeductBalance(ctx context.Context, id int64, amount float64) error {
|
||||
result := r.db.WithContext(ctx).Model(&userModel{}).
|
||||
Where("id = ? AND balance >= ?", id, amount).
|
||||
Update("balance", gorm.Expr("balance - ?", amount))
|
||||
if result.Error != nil {
|
||||
return result.Error
|
||||
n, err := r.client.User.Update().
|
||||
Where(dbuser.IDEQ(id), dbuser.BalanceGTE(amount)).
|
||||
AddBalance(-amount).
|
||||
Save(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if result.RowsAffected == 0 {
|
||||
if n == 0 {
|
||||
return service.ErrInsufficientBalance
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *userRepository) UpdateConcurrency(ctx context.Context, id int64, amount int) error {
|
||||
return r.db.WithContext(ctx).Model(&userModel{}).Where("id = ?", id).
|
||||
Update("concurrency", gorm.Expr("concurrency + ?", amount)).Error
|
||||
_, err := r.client.User.Update().Where(dbuser.IDEQ(id)).AddConcurrency(amount).Save(ctx)
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *userRepository) ExistsByEmail(ctx context.Context, email string) (bool, error) {
|
||||
var count int64
|
||||
err := r.db.WithContext(ctx).Model(&userModel{}).Where("email = ?", email).Count(&count).Error
|
||||
return count > 0, err
|
||||
return r.client.User.Query().Where(dbuser.EmailEQ(email)).Exist(ctx)
|
||||
}
|
||||
|
||||
// RemoveGroupFromAllowedGroups 从所有用户的 allowed_groups 数组中移除指定的分组ID
|
||||
// 使用 PostgreSQL 的 array_remove 函数
|
||||
func (r *userRepository) RemoveGroupFromAllowedGroups(ctx context.Context, groupID int64) (int64, error) {
|
||||
result := r.db.WithContext(ctx).Model(&userModel{}).
|
||||
Where("? = ANY(allowed_groups)", groupID).
|
||||
Update("allowed_groups", gorm.Expr("array_remove(allowed_groups, ?)", groupID))
|
||||
return result.RowsAffected, result.Error
|
||||
if r.sql == nil {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
joinAffected, err := r.client.UserAllowedGroup.Delete().
|
||||
Where(userallowedgroup.GroupIDEQ(groupID)).
|
||||
Exec(ctx)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
arrayRes, err := r.sql.ExecContext(
|
||||
ctx,
|
||||
"UPDATE users SET allowed_groups = array_remove(allowed_groups, $1), updated_at = NOW() WHERE $1 = ANY(allowed_groups)",
|
||||
groupID,
|
||||
)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
arrayAffected, _ := arrayRes.RowsAffected()
|
||||
|
||||
if int64(joinAffected) > arrayAffected {
|
||||
return int64(joinAffected), nil
|
||||
}
|
||||
return arrayAffected, nil
|
||||
}
|
||||
|
||||
// GetFirstAdmin 获取第一个管理员用户(用于 Admin API Key 认证)
|
||||
func (r *userRepository) GetFirstAdmin(ctx context.Context) (*service.User, error) {
|
||||
var m userModel
|
||||
err := r.db.WithContext(ctx).
|
||||
Where("role = ? AND status = ?", service.RoleAdmin, service.StatusActive).
|
||||
Order("id ASC").
|
||||
First(&m).Error
|
||||
m, err := r.client.User.Query().
|
||||
Where(
|
||||
dbuser.RoleEQ(service.RoleAdmin),
|
||||
dbuser.StatusEQ(service.StatusActive),
|
||||
).
|
||||
Order(dbent.Asc(dbuser.FieldID)).
|
||||
First(ctx)
|
||||
if err != nil {
|
||||
return nil, translatePersistenceError(err, service.ErrUserNotFound, nil)
|
||||
}
|
||||
return userModelToService(&m), nil
|
||||
|
||||
out := userEntityToService(m)
|
||||
groups, err := r.loadAllowedGroups(ctx, []int64{m.ID})
|
||||
if err == nil {
|
||||
if v, ok := groups[m.ID]; ok {
|
||||
out.AllowedGroups = v
|
||||
}
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
type userModel struct {
|
||||
ID int64 `gorm:"primaryKey"`
|
||||
Email string `gorm:"uniqueIndex;size:255;not null"`
|
||||
Username string `gorm:"size:100;default:''"`
|
||||
Wechat string `gorm:"size:100;default:''"`
|
||||
Notes string `gorm:"type:text;default:''"`
|
||||
PasswordHash string `gorm:"size:255;not null"`
|
||||
Role string `gorm:"size:20;default:user;not null"`
|
||||
Balance float64 `gorm:"type:decimal(20,8);default:0;not null"`
|
||||
Concurrency int `gorm:"default:5;not null"`
|
||||
Status string `gorm:"size:20;default:active;not null"`
|
||||
AllowedGroups pq.Int64Array `gorm:"type:bigint[]"`
|
||||
CreatedAt time.Time `gorm:"not null"`
|
||||
UpdatedAt time.Time `gorm:"not null"`
|
||||
DeletedAt gorm.DeletedAt `gorm:"index"`
|
||||
func (r *userRepository) loadAllowedGroups(ctx context.Context, userIDs []int64) (map[int64][]int64, error) {
|
||||
out := make(map[int64][]int64, len(userIDs))
|
||||
if len(userIDs) == 0 {
|
||||
return out, nil
|
||||
}
|
||||
|
||||
rows, err := r.client.UserAllowedGroup.Query().
|
||||
Where(userallowedgroup.UserIDIn(userIDs...)).
|
||||
All(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for i := range rows {
|
||||
out[rows[i].UserID] = append(out[rows[i].UserID], rows[i].GroupID)
|
||||
}
|
||||
|
||||
for userID := range out {
|
||||
sort.Slice(out[userID], func(i, j int) bool { return out[userID][i] < out[userID][j] })
|
||||
}
|
||||
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (userModel) TableName() string { return "users" }
|
||||
|
||||
func userModelToService(m *userModel) *service.User {
|
||||
if m == nil {
|
||||
func (r *userRepository) syncUserAllowedGroups(ctx context.Context, client *dbent.Client, exec sqlExecutor, userID int64, groupIDs []int64) error {
|
||||
if client == nil || exec == nil {
|
||||
return nil
|
||||
}
|
||||
return &service.User{
|
||||
ID: m.ID,
|
||||
Email: m.Email,
|
||||
Username: m.Username,
|
||||
Wechat: m.Wechat,
|
||||
Notes: m.Notes,
|
||||
PasswordHash: m.PasswordHash,
|
||||
Role: m.Role,
|
||||
Balance: m.Balance,
|
||||
Concurrency: m.Concurrency,
|
||||
Status: m.Status,
|
||||
AllowedGroups: []int64(m.AllowedGroups),
|
||||
CreatedAt: m.CreatedAt,
|
||||
UpdatedAt: m.UpdatedAt,
|
||||
|
||||
// Keep join table as the source of truth for reads.
|
||||
if _, err := client.UserAllowedGroup.Delete().Where(userallowedgroup.UserIDEQ(userID)).Exec(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
unique := make(map[int64]struct{}, len(groupIDs))
|
||||
for _, id := range groupIDs {
|
||||
if id <= 0 {
|
||||
continue
|
||||
}
|
||||
unique[id] = struct{}{}
|
||||
}
|
||||
|
||||
legacyGroups := make([]int64, 0, len(unique))
|
||||
if len(unique) > 0 {
|
||||
creates := make([]*dbent.UserAllowedGroupCreate, 0, len(unique))
|
||||
for groupID := range unique {
|
||||
creates = append(creates, client.UserAllowedGroup.Create().SetUserID(userID).SetGroupID(groupID))
|
||||
legacyGroups = append(legacyGroups, groupID)
|
||||
}
|
||||
if err := client.UserAllowedGroup.
|
||||
CreateBulk(creates...).
|
||||
OnConflictColumns(userallowedgroup.FieldUserID, userallowedgroup.FieldGroupID).
|
||||
DoNothing().
|
||||
Exec(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Phase 1 compatibility: keep legacy users.allowed_groups array updated for existing raw SQL paths.
|
||||
var legacy any
|
||||
if len(legacyGroups) > 0 {
|
||||
sort.Slice(legacyGroups, func(i, j int) bool { return legacyGroups[i] < legacyGroups[j] })
|
||||
legacy = pq.Array(legacyGroups)
|
||||
}
|
||||
if _, err := exec.ExecContext(ctx, "UPDATE users SET allowed_groups = $1::bigint[] WHERE id = $2", legacy, userID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func userModelFromService(u *service.User) *userModel {
|
||||
if u == nil {
|
||||
return nil
|
||||
}
|
||||
return &userModel{
|
||||
ID: u.ID,
|
||||
Email: u.Email,
|
||||
Username: u.Username,
|
||||
Wechat: u.Wechat,
|
||||
Notes: u.Notes,
|
||||
PasswordHash: u.PasswordHash,
|
||||
Role: u.Role,
|
||||
Balance: u.Balance,
|
||||
Concurrency: u.Concurrency,
|
||||
Status: u.Status,
|
||||
AllowedGroups: pq.Int64Array(u.AllowedGroups),
|
||||
CreatedAt: u.CreatedAt,
|
||||
UpdatedAt: u.UpdatedAt,
|
||||
}
|
||||
}
|
||||
|
||||
func applyUserModelToService(dst *service.User, src *userModel) {
|
||||
func applyUserEntityToService(dst *service.User, src *dbent.User) {
|
||||
if dst == nil || src == nil {
|
||||
return
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user