feat: expand superadmin edits and minio docs
This commit is contained in:
@@ -261,10 +261,15 @@ func (s *super) ListUsers(ctx context.Context, filter *super_dto.UserListFilter)
|
||||
CreatedAt: s.formatTime(u.CreatedAt),
|
||||
UpdatedAt: s.formatTime(u.UpdatedAt),
|
||||
},
|
||||
Balance: u.Balance,
|
||||
BalanceFrozen: u.BalanceFrozen,
|
||||
OwnedTenantCount: ownedCountMap[u.ID],
|
||||
JoinedTenantCount: joinedCountMap[u.ID],
|
||||
Balance: u.Balance,
|
||||
BalanceFrozen: u.BalanceFrozen,
|
||||
Nickname: u.Nickname,
|
||||
Avatar: u.Avatar,
|
||||
Gender: u.Gender,
|
||||
Bio: u.Bio,
|
||||
IsRealNameVerified: u.IsRealNameVerified,
|
||||
OwnedTenantCount: ownedCountMap[u.ID],
|
||||
JoinedTenantCount: joinedCountMap[u.ID],
|
||||
})
|
||||
}
|
||||
|
||||
@@ -295,8 +300,13 @@ func (s *super) GetUser(ctx context.Context, id int64) (*super_dto.UserItem, err
|
||||
CreatedAt: u.CreatedAt.Format(time.RFC3339),
|
||||
UpdatedAt: u.UpdatedAt.Format(time.RFC3339),
|
||||
},
|
||||
Balance: u.Balance,
|
||||
BalanceFrozen: u.BalanceFrozen,
|
||||
Balance: u.Balance,
|
||||
BalanceFrozen: u.BalanceFrozen,
|
||||
Nickname: u.Nickname,
|
||||
Avatar: u.Avatar,
|
||||
Gender: u.Gender,
|
||||
Bio: u.Bio,
|
||||
IsRealNameVerified: u.IsRealNameVerified,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -705,6 +715,84 @@ func (s *super) UpdateUserRoles(ctx context.Context, id int64, form *super_dto.U
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *super) UpdateUserProfile(ctx context.Context, operatorID, userID int64, form *super_dto.SuperUserProfileUpdateForm) error {
|
||||
if operatorID == 0 {
|
||||
return errorx.ErrUnauthorized.WithMsg("缺少操作者信息")
|
||||
}
|
||||
if userID == 0 || form == nil {
|
||||
return errorx.ErrBadRequest.WithMsg("更新参数不能为空")
|
||||
}
|
||||
if form.Gender != nil && !form.Gender.IsValid() {
|
||||
return errorx.ErrBadRequest.WithMsg("性别非法")
|
||||
}
|
||||
|
||||
tbl, q := models.UserQuery.QueryContext(ctx)
|
||||
user, err := q.Where(tbl.ID.Eq(userID)).First()
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return errorx.ErrRecordNotFound
|
||||
}
|
||||
return errorx.ErrDatabaseError.WithCause(err)
|
||||
}
|
||||
|
||||
updates := make(map[string]any)
|
||||
if form.Nickname != nil {
|
||||
updates["nickname"] = strings.TrimSpace(*form.Nickname)
|
||||
}
|
||||
if form.Avatar != nil {
|
||||
updates["avatar"] = strings.TrimSpace(*form.Avatar)
|
||||
}
|
||||
if form.Gender != nil {
|
||||
updates["gender"] = *form.Gender
|
||||
}
|
||||
if form.Bio != nil {
|
||||
updates["bio"] = strings.TrimSpace(*form.Bio)
|
||||
}
|
||||
if form.IsRealNameVerified != nil {
|
||||
updates["is_real_name_verified"] = *form.IsRealNameVerified
|
||||
if *form.IsRealNameVerified {
|
||||
updates["verified_at"] = time.Now()
|
||||
} else {
|
||||
updates["verified_at"] = time.Time{}
|
||||
}
|
||||
}
|
||||
|
||||
if form.RealName != nil || form.IDCard != nil {
|
||||
// 更新实名信息时保持原有元数据,避免覆盖其它字段。
|
||||
metaMap := make(map[string]any)
|
||||
if len(user.Metas) > 0 {
|
||||
_ = json.Unmarshal(user.Metas, &metaMap)
|
||||
}
|
||||
if form.RealName != nil {
|
||||
metaMap["real_name"] = strings.TrimSpace(*form.RealName)
|
||||
}
|
||||
if form.IDCard != nil {
|
||||
idCard := strings.TrimSpace(*form.IDCard)
|
||||
if idCard != "" {
|
||||
metaMap["id_card"] = "ENC:" + idCard
|
||||
} else {
|
||||
metaMap["id_card"] = ""
|
||||
}
|
||||
}
|
||||
raw, _ := json.Marshal(metaMap)
|
||||
updates["metas"] = types.JSON(raw)
|
||||
}
|
||||
|
||||
if len(updates) == 0 {
|
||||
return errorx.ErrBadRequest.WithMsg("没有可更新的字段")
|
||||
}
|
||||
|
||||
if _, err := q.Where(tbl.ID.Eq(userID)).Updates(updates); err != nil {
|
||||
return errorx.ErrDatabaseError.WithCause(err)
|
||||
}
|
||||
|
||||
if Audit != nil {
|
||||
Audit.Log(ctx, 0, operatorID, "update_user_profile", cast.ToString(userID), "Update user profile")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *super) ListTenants(ctx context.Context, filter *super_dto.TenantListFilter) (*requests.Pager, error) {
|
||||
if filter == nil {
|
||||
filter = &super_dto.TenantListFilter{}
|
||||
@@ -1333,25 +1421,114 @@ func (s *super) ReviewCreatorApplication(ctx context.Context, operatorID, tenant
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *super) CreateTenant(ctx context.Context, form *super_dto.TenantCreateForm) error {
|
||||
uid := form.AdminUserID
|
||||
if _, err := models.UserQuery.WithContext(ctx).Where(models.UserQuery.ID.Eq(uid)).First(); err != nil {
|
||||
return errorx.ErrRecordNotFound.WithMsg("用户不存在")
|
||||
func (s *super) GetCreatorSettings(ctx context.Context, tenantID int64) (*v1_dto.Settings, error) {
|
||||
if tenantID == 0 {
|
||||
return nil, errorx.ErrBadRequest.WithMsg("租户ID不能为空")
|
||||
}
|
||||
|
||||
t := &models.Tenant{
|
||||
UserID: uid,
|
||||
Name: form.Name,
|
||||
Code: form.Code,
|
||||
UUID: types.UUID(uuid.New()),
|
||||
Status: consts.TenantStatusVerified,
|
||||
t, err := models.TenantQuery.WithContext(ctx).Where(models.TenantQuery.ID.Eq(tenantID)).First()
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, errorx.ErrRecordNotFound
|
||||
}
|
||||
return nil, errorx.ErrDatabaseError.WithCause(err)
|
||||
}
|
||||
if err := models.TenantQuery.WithContext(ctx).Create(t); err != nil {
|
||||
|
||||
cfg := t.Config.Data()
|
||||
return &v1_dto.Settings{
|
||||
ID: t.ID,
|
||||
Name: t.Name,
|
||||
Bio: cfg.Bio,
|
||||
Avatar: cfg.Avatar,
|
||||
Cover: cfg.Cover,
|
||||
Description: cfg.Description,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *super) UpdateCreatorSettings(ctx context.Context, operatorID, tenantID int64, form *v1_dto.Settings) error {
|
||||
if operatorID == 0 {
|
||||
return errorx.ErrUnauthorized.WithMsg("缺少操作者信息")
|
||||
}
|
||||
if tenantID == 0 || form == nil {
|
||||
return errorx.ErrBadRequest.WithMsg("更新参数不能为空")
|
||||
}
|
||||
|
||||
t, err := models.TenantQuery.WithContext(ctx).Where(models.TenantQuery.ID.Eq(tenantID)).First()
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return errorx.ErrRecordNotFound
|
||||
}
|
||||
return errorx.ErrDatabaseError.WithCause(err)
|
||||
}
|
||||
|
||||
// 超管可直接更新租户设置与展示信息。
|
||||
cfg := t.Config.Data()
|
||||
cfg.Bio = form.Bio
|
||||
cfg.Avatar = form.Avatar
|
||||
cfg.Cover = form.Cover
|
||||
cfg.Description = form.Description
|
||||
|
||||
_, err = models.TenantQuery.WithContext(ctx).Where(models.TenantQuery.ID.Eq(tenantID)).Updates(&models.Tenant{
|
||||
Name: form.Name,
|
||||
Config: types.NewJSONType(cfg),
|
||||
})
|
||||
if err != nil {
|
||||
return errorx.ErrDatabaseError.WithCause(err)
|
||||
}
|
||||
|
||||
if Audit != nil {
|
||||
Audit.Log(ctx, tenantID, operatorID, "update_creator_settings", cast.ToString(tenantID), "Update creator settings")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *super) CreateTenant(ctx context.Context, form *super_dto.TenantCreateForm) error {
|
||||
if form == nil {
|
||||
return errorx.ErrBadRequest.WithMsg("创建参数不能为空")
|
||||
}
|
||||
if form.Duration <= 0 {
|
||||
return errorx.ErrBadRequest.WithMsg("租户有效期不能为空")
|
||||
}
|
||||
uid := form.AdminUserID
|
||||
|
||||
expiredAt := time.Now().AddDate(0, 0, form.Duration)
|
||||
return models.Q.Transaction(func(tx *models.Query) error {
|
||||
// 校验管理员用户存在,避免创建脏数据。
|
||||
if _, err := tx.User.WithContext(ctx).Where(tx.User.ID.Eq(uid)).First(); err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return errorx.ErrRecordNotFound.WithMsg("用户不存在")
|
||||
}
|
||||
return errorx.ErrDatabaseError.WithCause(err)
|
||||
}
|
||||
|
||||
t := &models.Tenant{
|
||||
UserID: uid,
|
||||
Name: form.Name,
|
||||
Code: form.Code,
|
||||
UUID: types.UUID(uuid.New()),
|
||||
Status: consts.TenantStatusVerified,
|
||||
ExpiredAt: expiredAt,
|
||||
}
|
||||
if err := tx.Tenant.WithContext(ctx).Create(t); err != nil {
|
||||
return errorx.ErrDatabaseError.WithCause(err)
|
||||
}
|
||||
|
||||
// 同步写入管理员成员关系,保证租户成员视角一致。
|
||||
tu := &models.TenantUser{
|
||||
TenantID: t.ID,
|
||||
UserID: uid,
|
||||
Role: types.Array[consts.TenantUserRole]{consts.TenantUserRoleTenantAdmin},
|
||||
Status: consts.UserStatusVerified,
|
||||
}
|
||||
if err := tx.TenantUser.WithContext(ctx).Create(tu); err != nil {
|
||||
return errorx.ErrDatabaseError.WithCause(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (s *super) GetTenant(ctx context.Context, id int64) (*super_dto.TenantItem, error) {
|
||||
tbl, q := models.TenantQuery.QueryContext(ctx)
|
||||
t, err := q.Where(tbl.ID.Eq(id)).First()
|
||||
@@ -1611,6 +1788,123 @@ func (s *super) ListPayoutAccounts(ctx context.Context, filter *super_dto.SuperP
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *super) CreatePayoutAccount(ctx context.Context, operatorID, tenantID int64, form *super_dto.SuperPayoutAccountCreateForm) error {
|
||||
if operatorID == 0 {
|
||||
return errorx.ErrUnauthorized.WithMsg("缺少操作者信息")
|
||||
}
|
||||
if tenantID == 0 || form == nil {
|
||||
return errorx.ErrBadRequest.WithMsg("创建参数不能为空")
|
||||
}
|
||||
if form.UserID == 0 {
|
||||
return errorx.ErrBadRequest.WithMsg("用户ID不能为空")
|
||||
}
|
||||
|
||||
accountType := strings.TrimSpace(form.Type)
|
||||
if accountType == "" {
|
||||
return errorx.ErrBadRequest.WithMsg("账户类型不能为空")
|
||||
}
|
||||
if !consts.PayoutAccountType(accountType).IsValid() {
|
||||
return errorx.ErrBadRequest.WithMsg("账户类型非法")
|
||||
}
|
||||
|
||||
// 校验租户与用户存在,避免关联脏数据。
|
||||
if _, err := models.TenantQuery.WithContext(ctx).Where(models.TenantQuery.ID.Eq(tenantID)).First(); err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return errorx.ErrRecordNotFound.WithMsg("租户不存在")
|
||||
}
|
||||
return errorx.ErrDatabaseError.WithCause(err)
|
||||
}
|
||||
if _, err := models.UserQuery.WithContext(ctx).Where(models.UserQuery.ID.Eq(form.UserID)).First(); err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return errorx.ErrRecordNotFound.WithMsg("用户不存在")
|
||||
}
|
||||
return errorx.ErrDatabaseError.WithCause(err)
|
||||
}
|
||||
|
||||
pa := &models.PayoutAccount{
|
||||
TenantID: tenantID,
|
||||
UserID: form.UserID,
|
||||
Type: consts.PayoutAccountType(accountType),
|
||||
Name: strings.TrimSpace(form.Name),
|
||||
Account: strings.TrimSpace(form.Account),
|
||||
Realname: strings.TrimSpace(form.Realname),
|
||||
Status: consts.PayoutAccountStatusPending,
|
||||
}
|
||||
if err := models.PayoutAccountQuery.WithContext(ctx).Create(pa); err != nil {
|
||||
return errorx.ErrDatabaseError.WithCause(err)
|
||||
}
|
||||
|
||||
if Audit != nil {
|
||||
Audit.Log(ctx, tenantID, operatorID, "create_payout_account", cast.ToString(pa.ID), "Create payout account")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *super) UpdatePayoutAccount(ctx context.Context, operatorID, id int64, form *super_dto.SuperPayoutAccountUpdateForm) error {
|
||||
if operatorID == 0 {
|
||||
return errorx.ErrUnauthorized.WithMsg("缺少操作者信息")
|
||||
}
|
||||
if id == 0 || form == nil {
|
||||
return errorx.ErrBadRequest.WithMsg("更新参数不能为空")
|
||||
}
|
||||
|
||||
tbl, q := models.PayoutAccountQuery.QueryContext(ctx)
|
||||
account, err := q.Where(tbl.ID.Eq(id)).First()
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return errorx.ErrRecordNotFound.WithMsg("结算账户不存在")
|
||||
}
|
||||
return errorx.ErrDatabaseError.WithCause(err)
|
||||
}
|
||||
|
||||
updates := make(map[string]any)
|
||||
changed := false
|
||||
if form.Type != nil {
|
||||
typ := strings.TrimSpace(*form.Type)
|
||||
if typ == "" {
|
||||
return errorx.ErrBadRequest.WithMsg("账户类型不能为空")
|
||||
}
|
||||
if !consts.PayoutAccountType(typ).IsValid() {
|
||||
return errorx.ErrBadRequest.WithMsg("账户类型非法")
|
||||
}
|
||||
updates["type"] = consts.PayoutAccountType(typ)
|
||||
changed = true
|
||||
}
|
||||
if form.Name != nil {
|
||||
updates["name"] = strings.TrimSpace(*form.Name)
|
||||
changed = true
|
||||
}
|
||||
if form.Account != nil {
|
||||
updates["account"] = strings.TrimSpace(*form.Account)
|
||||
changed = true
|
||||
}
|
||||
if form.Realname != nil {
|
||||
updates["realname"] = strings.TrimSpace(*form.Realname)
|
||||
changed = true
|
||||
}
|
||||
if len(updates) == 0 {
|
||||
return errorx.ErrBadRequest.WithMsg("没有可更新的字段")
|
||||
}
|
||||
|
||||
if changed && account.Status == consts.PayoutAccountStatusApproved {
|
||||
updates["status"] = consts.PayoutAccountStatusPending
|
||||
updates["reviewed_by"] = int64(0)
|
||||
updates["reviewed_at"] = time.Time{}
|
||||
updates["review_reason"] = ""
|
||||
}
|
||||
|
||||
if _, err := q.Where(tbl.ID.Eq(id)).Updates(updates); err != nil {
|
||||
return errorx.ErrDatabaseError.WithCause(err)
|
||||
}
|
||||
|
||||
if Audit != nil {
|
||||
Audit.Log(ctx, account.TenantID, operatorID, "update_payout_account", cast.ToString(id), "Update payout account")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *super) RemovePayoutAccount(ctx context.Context, operatorID, id int64) error {
|
||||
if operatorID == 0 {
|
||||
return errorx.ErrUnauthorized.WithMsg("缺少操作者信息")
|
||||
@@ -4716,6 +5010,112 @@ func (s *super) CreateNotificationTemplate(ctx context.Context, form *super_dto.
|
||||
return item, nil
|
||||
}
|
||||
|
||||
func (s *super) UpdateNotificationTemplate(ctx context.Context, operatorID, id int64, form *super_dto.SuperNotificationTemplateUpdateForm) (*super_dto.SuperNotificationTemplateItem, error) {
|
||||
if operatorID == 0 {
|
||||
return nil, errorx.ErrUnauthorized.WithMsg("缺少操作者信息")
|
||||
}
|
||||
if id == 0 || form == nil {
|
||||
return nil, errorx.ErrBadRequest.WithMsg("模板参数不能为空")
|
||||
}
|
||||
|
||||
tbl, q := models.NotificationTemplateQuery.QueryContext(ctx)
|
||||
if _, err := q.Where(tbl.ID.Eq(id)).First(); err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, errorx.ErrRecordNotFound.WithMsg("模板不存在")
|
||||
}
|
||||
return nil, errorx.ErrDatabaseError.WithCause(err)
|
||||
}
|
||||
|
||||
updates := make(map[string]any)
|
||||
if form.TenantID != nil {
|
||||
if *form.TenantID < 0 {
|
||||
return nil, errorx.ErrBadRequest.WithMsg("租户ID无效")
|
||||
}
|
||||
if *form.TenantID > 0 {
|
||||
// 校验租户存在,避免模板指向无效租户。
|
||||
tenantTbl, tenantQuery := models.TenantQuery.QueryContext(ctx)
|
||||
if _, err := tenantQuery.Where(tenantTbl.ID.Eq(*form.TenantID)).First(); err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, errorx.ErrRecordNotFound.WithMsg("租户不存在")
|
||||
}
|
||||
return nil, errorx.ErrDatabaseError.WithCause(err)
|
||||
}
|
||||
}
|
||||
updates["tenant_id"] = *form.TenantID
|
||||
}
|
||||
if form.Name != nil {
|
||||
name := strings.TrimSpace(*form.Name)
|
||||
if name == "" {
|
||||
return nil, errorx.ErrBadRequest.WithMsg("模板名称不能为空")
|
||||
}
|
||||
updates["name"] = name
|
||||
}
|
||||
if form.Type != nil {
|
||||
if !form.Type.IsValid() {
|
||||
return nil, errorx.ErrBadRequest.WithMsg("通知类型非法")
|
||||
}
|
||||
updates["type"] = *form.Type
|
||||
}
|
||||
if form.Title != nil {
|
||||
title := strings.TrimSpace(*form.Title)
|
||||
if title == "" {
|
||||
return nil, errorx.ErrBadRequest.WithMsg("模板标题不能为空")
|
||||
}
|
||||
updates["title"] = title
|
||||
}
|
||||
if form.Content != nil {
|
||||
content := strings.TrimSpace(*form.Content)
|
||||
if content == "" {
|
||||
return nil, errorx.ErrBadRequest.WithMsg("模板内容不能为空")
|
||||
}
|
||||
updates["content"] = content
|
||||
}
|
||||
if form.IsActive != nil {
|
||||
updates["is_active"] = *form.IsActive
|
||||
}
|
||||
if len(updates) == 0 {
|
||||
return nil, errorx.ErrBadRequest.WithMsg("没有可更新的字段")
|
||||
}
|
||||
|
||||
if _, err := q.Where(tbl.ID.Eq(id)).Updates(updates); err != nil {
|
||||
return nil, errorx.ErrDatabaseError.WithCause(err)
|
||||
}
|
||||
|
||||
updated, err := q.Where(tbl.ID.Eq(id)).First()
|
||||
if err != nil {
|
||||
return nil, errorx.ErrDatabaseError.WithCause(err)
|
||||
}
|
||||
|
||||
item := &super_dto.SuperNotificationTemplateItem{
|
||||
ID: updated.ID,
|
||||
TenantID: updated.TenantID,
|
||||
Name: updated.Name,
|
||||
Type: updated.Type,
|
||||
Title: updated.Title,
|
||||
Content: updated.Content,
|
||||
IsActive: updated.IsActive,
|
||||
CreatedAt: s.formatTime(updated.CreatedAt),
|
||||
UpdatedAt: s.formatTime(updated.UpdatedAt),
|
||||
}
|
||||
if updated.TenantID > 0 {
|
||||
tenantTbl, tenantQuery := models.TenantQuery.QueryContext(ctx)
|
||||
tenant, err := tenantQuery.Where(tenantTbl.ID.Eq(updated.TenantID)).First()
|
||||
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, errorx.ErrDatabaseError.WithCause(err)
|
||||
}
|
||||
if tenant != nil {
|
||||
item.TenantCode = tenant.Code
|
||||
item.TenantName = tenant.Name
|
||||
}
|
||||
}
|
||||
|
||||
if Audit != nil {
|
||||
Audit.Log(ctx, updated.TenantID, operatorID, "update_notification_template", cast.ToString(id), "Update notification template")
|
||||
}
|
||||
|
||||
return item, nil
|
||||
}
|
||||
|
||||
func (s *super) ListAuditLogs(ctx context.Context, filter *super_dto.SuperAuditLogListFilter) (*requests.Pager, error) {
|
||||
if filter == nil {
|
||||
filter = &super_dto.SuperAuditLogListFilter{}
|
||||
|
||||
@@ -2,6 +2,7 @@ package services
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -9,8 +10,10 @@ import (
|
||||
"quyun/v2/app/commands/testx"
|
||||
"quyun/v2/app/errorx"
|
||||
super_dto "quyun/v2/app/http/super/v1/dto"
|
||||
v1_dto "quyun/v2/app/http/v1/dto"
|
||||
"quyun/v2/app/requests"
|
||||
"quyun/v2/database"
|
||||
"quyun/v2/database/fields"
|
||||
"quyun/v2/database/models"
|
||||
"quyun/v2/pkg/consts"
|
||||
|
||||
@@ -140,10 +143,12 @@ func (s *SuperTestSuite) Test_CreateTenant() {
|
||||
models.UserQuery.WithContext(ctx).Create(u)
|
||||
|
||||
Convey("should create tenant", func() {
|
||||
startAt := time.Now()
|
||||
form := &super_dto.TenantCreateForm{
|
||||
Name: "Super Tenant",
|
||||
Code: "st1",
|
||||
AdminUserID: u.ID,
|
||||
Duration: 7,
|
||||
}
|
||||
err := Super.CreateTenant(ctx, form)
|
||||
So(err, ShouldBeNil)
|
||||
@@ -153,6 +158,13 @@ func (s *SuperTestSuite) Test_CreateTenant() {
|
||||
So(t.Name, ShouldEqual, "Super Tenant")
|
||||
So(t.UserID, ShouldEqual, u.ID)
|
||||
So(t.Status, ShouldEqual, consts.TenantStatusVerified)
|
||||
So(t.ExpiredAt.After(startAt), ShouldBeTrue)
|
||||
|
||||
tu, _ := models.TenantUserQuery.WithContext(ctx).
|
||||
Where(models.TenantUserQuery.TenantID.Eq(t.ID), models.TenantUserQuery.UserID.Eq(u.ID)).
|
||||
First()
|
||||
So(tu, ShouldNotBeNil)
|
||||
So(tu.Status, ShouldEqual, consts.UserStatusVerified)
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -1203,3 +1215,195 @@ func (s *SuperTestSuite) Test_PayoutAccountReview() {
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func (s *SuperTestSuite) Test_UpdateUserProfile() {
|
||||
Convey("UpdateUserProfile", s.T(), func() {
|
||||
ctx := s.T().Context()
|
||||
database.Truncate(ctx, s.DB, models.TableNameUser)
|
||||
|
||||
meta := types.JSON([]byte(`{"real_name":"Old Name","id_card":"ENC:1111"}`))
|
||||
u := &models.User{
|
||||
Username: "profile_user",
|
||||
Nickname: "Old",
|
||||
Avatar: "http://old-avatar",
|
||||
Gender: consts.GenderSecret,
|
||||
Bio: "old bio",
|
||||
Metas: meta,
|
||||
}
|
||||
models.UserQuery.WithContext(ctx).Create(u)
|
||||
|
||||
Convey("should update profile fields and real-name meta", func() {
|
||||
form := &super_dto.SuperUserProfileUpdateForm{
|
||||
Nickname: lo.ToPtr("New Nick"),
|
||||
Avatar: lo.ToPtr("http://new-avatar"),
|
||||
Gender: lo.ToPtr(consts.GenderMale),
|
||||
Bio: lo.ToPtr("new bio"),
|
||||
IsRealNameVerified: lo.ToPtr(true),
|
||||
RealName: lo.ToPtr("New Name"),
|
||||
IDCard: lo.ToPtr("123456789012345678"),
|
||||
}
|
||||
err := Super.UpdateUserProfile(ctx, 1001, u.ID, form)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
updated, err := models.UserQuery.WithContext(ctx).Where(models.UserQuery.ID.Eq(u.ID)).First()
|
||||
So(err, ShouldBeNil)
|
||||
So(updated.Nickname, ShouldEqual, "New Nick")
|
||||
So(updated.Avatar, ShouldEqual, "http://new-avatar")
|
||||
So(updated.Gender, ShouldEqual, consts.GenderMale)
|
||||
So(updated.Bio, ShouldEqual, "new bio")
|
||||
So(updated.IsRealNameVerified, ShouldBeTrue)
|
||||
So(updated.VerifiedAt.IsZero(), ShouldBeFalse)
|
||||
|
||||
metaMap := make(map[string]interface{})
|
||||
So(json.Unmarshal(updated.Metas, &metaMap), ShouldBeNil)
|
||||
So(metaMap["real_name"], ShouldEqual, "New Name")
|
||||
So(metaMap["id_card"], ShouldEqual, "ENC:123456789012345678")
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func (s *SuperTestSuite) Test_UpdateCreatorSettings() {
|
||||
Convey("UpdateCreatorSettings", s.T(), func() {
|
||||
ctx := s.T().Context()
|
||||
database.Truncate(ctx, s.DB, models.TableNameTenant, models.TableNameUser)
|
||||
|
||||
owner := &models.User{Username: "settings_owner"}
|
||||
models.UserQuery.WithContext(ctx).Create(owner)
|
||||
|
||||
tenant := &models.Tenant{
|
||||
UserID: owner.ID,
|
||||
Name: "Old Tenant",
|
||||
Code: "creator-settings",
|
||||
Status: consts.TenantStatusVerified,
|
||||
Config: types.NewJSONType(fields.TenantConfig{}),
|
||||
}
|
||||
models.TenantQuery.WithContext(ctx).Create(tenant)
|
||||
|
||||
form := &v1_dto.Settings{
|
||||
Name: "New Tenant",
|
||||
Bio: "new bio",
|
||||
Avatar: "http://avatar",
|
||||
Cover: "http://cover",
|
||||
Description: "new description",
|
||||
}
|
||||
err := Super.UpdateCreatorSettings(ctx, 2001, tenant.ID, form)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
updated, err := models.TenantQuery.WithContext(ctx).Where(models.TenantQuery.ID.Eq(tenant.ID)).First()
|
||||
So(err, ShouldBeNil)
|
||||
So(updated.Name, ShouldEqual, "New Tenant")
|
||||
cfg := updated.Config.Data()
|
||||
So(cfg.Bio, ShouldEqual, "new bio")
|
||||
So(cfg.Avatar, ShouldEqual, "http://avatar")
|
||||
So(cfg.Cover, ShouldEqual, "http://cover")
|
||||
So(cfg.Description, ShouldEqual, "new description")
|
||||
})
|
||||
}
|
||||
|
||||
func (s *SuperTestSuite) Test_PayoutAccountCreateUpdate() {
|
||||
Convey("PayoutAccountCreateUpdate", s.T(), func() {
|
||||
ctx := s.T().Context()
|
||||
database.Truncate(ctx, s.DB, models.TableNamePayoutAccount, models.TableNameUser, models.TableNameTenant)
|
||||
|
||||
admin := &models.User{Username: "payout_admin_2"}
|
||||
owner := &models.User{Username: "payout_owner_2"}
|
||||
models.UserQuery.WithContext(ctx).Create(admin, owner)
|
||||
|
||||
tenant := &models.Tenant{
|
||||
UserID: owner.ID,
|
||||
Name: "Payout Tenant 2",
|
||||
Code: "payout-2",
|
||||
Status: consts.TenantStatusVerified,
|
||||
}
|
||||
models.TenantQuery.WithContext(ctx).Create(tenant)
|
||||
|
||||
Convey("should create payout account", func() {
|
||||
err := Super.CreatePayoutAccount(ctx, admin.ID, tenant.ID, &super_dto.SuperPayoutAccountCreateForm{
|
||||
UserID: owner.ID,
|
||||
Type: "bank",
|
||||
Name: "Bank",
|
||||
Account: "123",
|
||||
Realname: "Owner",
|
||||
})
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
account, err := models.PayoutAccountQuery.WithContext(ctx).
|
||||
Where(models.PayoutAccountQuery.TenantID.Eq(tenant.ID), models.PayoutAccountQuery.UserID.Eq(owner.ID)).
|
||||
First()
|
||||
So(err, ShouldBeNil)
|
||||
So(account.Status, ShouldEqual, consts.PayoutAccountStatusPending)
|
||||
})
|
||||
|
||||
Convey("should reset status when updating approved account", func() {
|
||||
account := &models.PayoutAccount{
|
||||
TenantID: tenant.ID,
|
||||
UserID: owner.ID,
|
||||
Type: consts.PayoutAccountTypeBank,
|
||||
Name: "Bank",
|
||||
Account: "111",
|
||||
Realname: "Owner",
|
||||
Status: consts.PayoutAccountStatusApproved,
|
||||
ReviewedBy: admin.ID,
|
||||
ReviewReason: "ok",
|
||||
ReviewedAt: time.Now(),
|
||||
}
|
||||
models.PayoutAccountQuery.WithContext(ctx).Create(account)
|
||||
|
||||
err := Super.UpdatePayoutAccount(ctx, admin.ID, account.ID, &super_dto.SuperPayoutAccountUpdateForm{
|
||||
Account: lo.ToPtr("222"),
|
||||
})
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
updated, err := models.PayoutAccountQuery.WithContext(ctx).Where(models.PayoutAccountQuery.ID.Eq(account.ID)).First()
|
||||
So(err, ShouldBeNil)
|
||||
So(updated.Account, ShouldEqual, "222")
|
||||
So(updated.Status, ShouldEqual, consts.PayoutAccountStatusPending)
|
||||
So(updated.ReviewedBy, ShouldEqual, int64(0))
|
||||
So(updated.ReviewReason, ShouldEqual, "")
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func (s *SuperTestSuite) Test_UpdateNotificationTemplate() {
|
||||
Convey("UpdateNotificationTemplate", s.T(), func() {
|
||||
ctx := s.T().Context()
|
||||
database.Truncate(ctx, s.DB, models.TableNameNotificationTemplate, models.TableNameTenant, models.TableNameUser)
|
||||
|
||||
owner := &models.User{Username: "tmpl_owner"}
|
||||
models.UserQuery.WithContext(ctx).Create(owner)
|
||||
|
||||
tenant := &models.Tenant{
|
||||
UserID: owner.ID,
|
||||
Name: "Template Tenant",
|
||||
Code: "tmpl",
|
||||
Status: consts.TenantStatusVerified,
|
||||
}
|
||||
models.TenantQuery.WithContext(ctx).Create(tenant)
|
||||
|
||||
tmpl := &models.NotificationTemplate{
|
||||
TenantID: tenant.ID,
|
||||
Name: "Old Template",
|
||||
Type: consts.NotificationTypeSystem,
|
||||
Title: "Old Title",
|
||||
Content: "Old Content",
|
||||
IsActive: true,
|
||||
}
|
||||
models.NotificationTemplateQuery.WithContext(ctx).Create(tmpl)
|
||||
|
||||
item, err := Super.UpdateNotificationTemplate(ctx, 3001, tmpl.ID, &super_dto.SuperNotificationTemplateUpdateForm{
|
||||
Name: lo.ToPtr("New Template"),
|
||||
Title: lo.ToPtr("New Title"),
|
||||
Content: lo.ToPtr("New Content"),
|
||||
IsActive: lo.ToPtr(false),
|
||||
})
|
||||
So(err, ShouldBeNil)
|
||||
So(item.Name, ShouldEqual, "New Template")
|
||||
So(item.IsActive, ShouldBeFalse)
|
||||
|
||||
updated, err := models.NotificationTemplateQuery.WithContext(ctx).Where(models.NotificationTemplateQuery.ID.Eq(tmpl.ID)).First()
|
||||
So(err, ShouldBeNil)
|
||||
So(updated.Title, ShouldEqual, "New Title")
|
||||
So(updated.Content, ShouldEqual, "New Content")
|
||||
So(updated.IsActive, ShouldBeFalse)
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user