388 lines
12 KiB
Go
388 lines
12 KiB
Go
package services
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"time"
|
|
|
|
"quyun/v2/app/errorx"
|
|
super_dto "quyun/v2/app/http/super/v1/dto"
|
|
"quyun/v2/app/requests"
|
|
"quyun/v2/database/models"
|
|
"quyun/v2/pkg/consts"
|
|
|
|
"github.com/google/uuid"
|
|
"github.com/spf13/cast"
|
|
"go.ipao.vip/gen/types"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
// @provider
|
|
type super struct{}
|
|
|
|
func (s *super) Login(ctx context.Context, form *super_dto.LoginForm) (*super_dto.LoginResponse, error) {
|
|
// TODO: Admin specific login or reuse User service
|
|
return &super_dto.LoginResponse{}, nil
|
|
}
|
|
|
|
func (s *super) CheckToken(ctx context.Context) (*super_dto.LoginResponse, error) {
|
|
return &super_dto.LoginResponse{}, nil
|
|
}
|
|
|
|
func (s *super) ListUsers(ctx context.Context, filter *super_dto.UserListFilter) (*requests.Pager, error) {
|
|
tbl, q := models.UserQuery.QueryContext(ctx)
|
|
if filter.Username != nil && *filter.Username != "" {
|
|
q = q.Where(tbl.Username.Like("%" + *filter.Username + "%")).Or(tbl.Nickname.Like("%" + *filter.Username + "%"))
|
|
}
|
|
|
|
filter.Pagination.Format()
|
|
total, err := q.Count()
|
|
if err != nil {
|
|
return nil, errorx.ErrDatabaseError.WithCause(err)
|
|
}
|
|
|
|
list, err := q.Offset(int(filter.Pagination.Offset())).Limit(int(filter.Pagination.Limit)).Order(tbl.ID.Desc()).Find()
|
|
if err != nil {
|
|
return nil, errorx.ErrDatabaseError.WithCause(err)
|
|
}
|
|
|
|
var data []super_dto.UserItem
|
|
for _, u := range list {
|
|
data = append(data, super_dto.UserItem{
|
|
SuperUserLite: super_dto.SuperUserLite{
|
|
ID: u.ID,
|
|
Username: u.Username,
|
|
Roles: u.Roles,
|
|
Status: u.Status,
|
|
StatusDescription: u.Status.Description(),
|
|
CreatedAt: u.CreatedAt.Format(time.RFC3339),
|
|
UpdatedAt: u.UpdatedAt.Format(time.RFC3339),
|
|
},
|
|
Balance: u.Balance,
|
|
BalanceFrozen: u.BalanceFrozen,
|
|
})
|
|
}
|
|
|
|
return &requests.Pager{
|
|
Pagination: filter.Pagination,
|
|
Total: total,
|
|
Items: data,
|
|
}, nil
|
|
}
|
|
|
|
func (s *super) GetUser(ctx context.Context, id int64) (*super_dto.UserItem, error) {
|
|
tbl, q := models.UserQuery.QueryContext(ctx)
|
|
u, err := q.Where(tbl.ID.Eq(id)).First()
|
|
if err != nil {
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return nil, errorx.ErrRecordNotFound
|
|
}
|
|
return nil, errorx.ErrDatabaseError.WithCause(err)
|
|
}
|
|
return &super_dto.UserItem{
|
|
SuperUserLite: super_dto.SuperUserLite{
|
|
ID: u.ID,
|
|
Username: u.Username,
|
|
Roles: u.Roles,
|
|
Status: u.Status,
|
|
StatusDescription: u.Status.Description(),
|
|
CreatedAt: u.CreatedAt.Format(time.RFC3339),
|
|
UpdatedAt: u.UpdatedAt.Format(time.RFC3339),
|
|
},
|
|
Balance: u.Balance,
|
|
BalanceFrozen: u.BalanceFrozen,
|
|
}, nil
|
|
}
|
|
|
|
func (s *super) UpdateUserStatus(ctx context.Context, id int64, form *super_dto.UserStatusUpdateForm) error {
|
|
tbl, q := models.UserQuery.QueryContext(ctx)
|
|
_, err := q.Where(tbl.ID.Eq(id)).Update(tbl.Status, consts.UserStatus(form.Status))
|
|
if err != nil {
|
|
return errorx.ErrDatabaseError.WithCause(err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *super) UpdateUserRoles(ctx context.Context, id int64, form *super_dto.UserRolesUpdateForm) error {
|
|
var roles types.Array[consts.Role]
|
|
for _, r := range form.Roles {
|
|
roles = append(roles, r)
|
|
}
|
|
tbl, q := models.UserQuery.QueryContext(ctx)
|
|
_, err := q.Where(tbl.ID.Eq(id)).Update(tbl.Roles, roles)
|
|
if err != nil {
|
|
return errorx.ErrDatabaseError.WithCause(err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *super) ListTenants(ctx context.Context, filter *super_dto.TenantListFilter) (*requests.Pager, error) {
|
|
tbl, q := models.TenantQuery.QueryContext(ctx)
|
|
if filter.Name != nil && *filter.Name != "" {
|
|
q = q.Where(tbl.Name.Like("%" + *filter.Name + "%"))
|
|
}
|
|
|
|
filter.Pagination.Format()
|
|
total, err := q.Count()
|
|
if err != nil {
|
|
return nil, errorx.ErrDatabaseError.WithCause(err)
|
|
}
|
|
|
|
list, err := q.Offset(int(filter.Pagination.Offset())).Limit(int(filter.Pagination.Limit)).Order(tbl.ID.Desc()).Find()
|
|
if err != nil {
|
|
return nil, errorx.ErrDatabaseError.WithCause(err)
|
|
}
|
|
|
|
var data []super_dto.TenantItem
|
|
for _, t := range list {
|
|
data = append(data, super_dto.TenantItem{
|
|
ID: t.ID,
|
|
UUID: t.UUID.String(),
|
|
Name: t.Name,
|
|
Code: t.Code,
|
|
Status: t.Status,
|
|
StatusDescription: t.Status.Description(),
|
|
UserID: t.UserID,
|
|
CreatedAt: t.CreatedAt.Format(time.RFC3339),
|
|
UpdatedAt: t.UpdatedAt.Format(time.RFC3339),
|
|
})
|
|
}
|
|
|
|
return &requests.Pager{
|
|
Pagination: filter.Pagination,
|
|
Total: total,
|
|
Items: data,
|
|
}, nil
|
|
}
|
|
|
|
func (s *super) CreateTenant(ctx context.Context, form *super_dto.TenantCreateForm) error {
|
|
uid := cast.ToInt64(form.AdminUserID)
|
|
if _, err := models.UserQuery.WithContext(ctx).Where(models.UserQuery.ID.Eq(uid)).First(); err != nil {
|
|
return errorx.ErrRecordNotFound.WithMsg("用户不存在")
|
|
}
|
|
|
|
t := &models.Tenant{
|
|
UserID: uid,
|
|
Name: form.Name,
|
|
Code: form.Code,
|
|
UUID: types.UUID(uuid.New()),
|
|
Status: consts.TenantStatusVerified,
|
|
}
|
|
if err := models.TenantQuery.WithContext(ctx).Create(t); 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()
|
|
if err != nil {
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return nil, errorx.ErrRecordNotFound
|
|
}
|
|
return nil, errorx.ErrDatabaseError.WithCause(err)
|
|
}
|
|
return &super_dto.TenantItem{
|
|
ID: t.ID,
|
|
UUID: t.UUID.String(),
|
|
Name: t.Name,
|
|
Code: t.Code,
|
|
Status: t.Status,
|
|
StatusDescription: t.Status.Description(),
|
|
UserID: t.UserID,
|
|
CreatedAt: t.CreatedAt.Format(time.RFC3339),
|
|
UpdatedAt: t.UpdatedAt.Format(time.RFC3339),
|
|
}, nil
|
|
}
|
|
|
|
func (s *super) UpdateTenantStatus(ctx context.Context, id int64, form *super_dto.TenantStatusUpdateForm) error {
|
|
tbl, q := models.TenantQuery.QueryContext(ctx)
|
|
_, err := q.Where(tbl.ID.Eq(id)).Update(tbl.Status, consts.TenantStatus(form.Status))
|
|
if err != nil {
|
|
return errorx.ErrDatabaseError.WithCause(err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *super) UpdateTenantExpire(ctx context.Context, id int64, form *super_dto.TenantExpireUpdateForm) error {
|
|
expire := time.Now().AddDate(0, 0, form.Duration)
|
|
tbl, q := models.TenantQuery.QueryContext(ctx)
|
|
_, err := q.Where(tbl.ID.Eq(id)).Update(tbl.ExpiredAt, expire)
|
|
if err != nil {
|
|
return errorx.ErrDatabaseError.WithCause(err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *super) ListContents(ctx context.Context, filter *super_dto.SuperContentListFilter) (*requests.Pager, error) {
|
|
tbl, q := models.ContentQuery.QueryContext(ctx)
|
|
|
|
filter.Pagination.Format()
|
|
total, err := q.Count()
|
|
if err != nil {
|
|
return nil, errorx.ErrDatabaseError.WithCause(err)
|
|
}
|
|
list, err := q.Offset(int(filter.Pagination.Offset())).Limit(int(filter.Pagination.Limit)).Order(tbl.ID.Desc()).Find()
|
|
if err != nil {
|
|
return nil, errorx.ErrDatabaseError.WithCause(err)
|
|
}
|
|
// Simplified DTO for list
|
|
var data []any
|
|
for _, c := range list {
|
|
data = append(data, c) // TODO: Map to DTO
|
|
}
|
|
return &requests.Pager{
|
|
Pagination: filter.Pagination,
|
|
Total: total,
|
|
Items: data,
|
|
}, nil
|
|
}
|
|
|
|
func (s *super) UpdateContentStatus(ctx context.Context, tenantID, contentID int64, form *super_dto.SuperTenantContentStatusUpdateForm) error {
|
|
tbl, q := models.ContentQuery.QueryContext(ctx)
|
|
_, err := q.Where(tbl.ID.Eq(contentID), tbl.TenantID.Eq(tenantID)).Update(tbl.Status, consts.ContentStatus(form.Status))
|
|
if err != nil {
|
|
return errorx.ErrDatabaseError.WithCause(err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *super) ListOrders(ctx context.Context, filter *super_dto.SuperOrderListFilter) (*requests.Pager, error) {
|
|
tbl, q := models.OrderQuery.QueryContext(ctx)
|
|
|
|
filter.Pagination.Format()
|
|
total, err := q.Count()
|
|
if err != nil {
|
|
return nil, errorx.ErrDatabaseError.WithCause(err)
|
|
}
|
|
list, err := q.Offset(int(filter.Pagination.Offset())).Limit(int(filter.Pagination.Limit)).Order(tbl.ID.Desc()).Find()
|
|
if err != nil {
|
|
return nil, errorx.ErrDatabaseError.WithCause(err)
|
|
}
|
|
// TODO: Map to DTO
|
|
return &requests.Pager{
|
|
Pagination: filter.Pagination,
|
|
Total: total,
|
|
Items: list,
|
|
}, nil
|
|
}
|
|
|
|
func (s *super) GetOrder(ctx context.Context, id int64) (*super_dto.SuperOrderDetail, error) {
|
|
return &super_dto.SuperOrderDetail{}, nil
|
|
}
|
|
|
|
func (s *super) RefundOrder(ctx context.Context, id int64, form *super_dto.SuperOrderRefundForm) error {
|
|
return nil
|
|
}
|
|
|
|
func (s *super) OrderStatistics(ctx context.Context) (*super_dto.OrderStatisticsResponse, error) {
|
|
return &super_dto.OrderStatisticsResponse{}, nil
|
|
}
|
|
|
|
func (s *super) UserStatistics(ctx context.Context) ([]super_dto.UserStatistics, error) {
|
|
return []super_dto.UserStatistics{}, nil
|
|
}
|
|
|
|
func (s *super) UserStatuses(ctx context.Context) ([]requests.KV, error) {
|
|
return consts.UserStatusItems(), nil
|
|
}
|
|
|
|
func (s *super) TenantStatuses(ctx context.Context) ([]requests.KV, error) {
|
|
return consts.TenantStatusItems(), nil
|
|
}
|
|
|
|
func (s *super) ListWithdrawals(ctx context.Context, filter *super_dto.SuperOrderListFilter) (*requests.Pager, error) {
|
|
tbl, q := models.OrderQuery.QueryContext(ctx)
|
|
q = q.Where(tbl.Type.Eq(consts.OrderTypeWithdrawal))
|
|
|
|
filter.Pagination.Format()
|
|
total, err := q.Count()
|
|
if err != nil {
|
|
return nil, errorx.ErrDatabaseError.WithCause(err)
|
|
}
|
|
|
|
list, err := q.Offset(int(filter.Pagination.Offset())).Limit(int(filter.Pagination.Limit)).Order(tbl.ID.Desc()).Find()
|
|
if err != nil {
|
|
return nil, errorx.ErrDatabaseError.WithCause(err)
|
|
}
|
|
|
|
// TODO: Map to SuperOrderItem properly with Tenant/User lookup
|
|
return &requests.Pager{
|
|
Pagination: filter.Pagination,
|
|
Total: total,
|
|
Items: list,
|
|
}, nil
|
|
}
|
|
|
|
func (s *super) ApproveWithdrawal(ctx context.Context, id int64) error {
|
|
o, err := models.OrderQuery.WithContext(ctx).Where(models.OrderQuery.ID.Eq(id)).First()
|
|
if err != nil {
|
|
return errorx.ErrRecordNotFound
|
|
}
|
|
if o.Status != consts.OrderStatusCreated {
|
|
return errorx.ErrStatusConflict.WithMsg("订单状态不正确")
|
|
}
|
|
|
|
// Mark as Paid (Assumes external transfer done)
|
|
_, err = models.OrderQuery.WithContext(ctx).Where(models.OrderQuery.ID.Eq(id)).Updates(&models.Order{
|
|
Status: consts.OrderStatusPaid,
|
|
PaidAt: time.Now(),
|
|
UpdatedAt: time.Now(),
|
|
})
|
|
if err == nil && Audit != nil {
|
|
Audit.Log(ctx, "approve_withdrawal", cast.ToString(id), "Approved withdrawal")
|
|
}
|
|
return err
|
|
}
|
|
|
|
func (s *super) RejectWithdrawal(ctx context.Context, id int64, reason string) error {
|
|
err := models.Q.Transaction(func(tx *models.Query) error {
|
|
o, err := tx.Order.WithContext(ctx).Where(tx.Order.ID.Eq(id)).First()
|
|
if err != nil {
|
|
return errorx.ErrRecordNotFound
|
|
}
|
|
if o.Status != consts.OrderStatusCreated {
|
|
return errorx.ErrStatusConflict.WithMsg("订单状态不正确")
|
|
}
|
|
|
|
// Refund User Balance
|
|
_, err = tx.User.WithContext(ctx).Where(tx.User.ID.Eq(o.UserID)).Update(tx.User.Balance, gorm.Expr("balance + ?", o.AmountPaid))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Update Order
|
|
_, err = tx.Order.WithContext(ctx).Where(tx.Order.ID.Eq(id)).Updates(&models.Order{
|
|
Status: consts.OrderStatusFailed, // or Canceled
|
|
RefundReason: reason,
|
|
UpdatedAt: time.Now(),
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Create Ledger (Adjustment/Unfreeze)
|
|
ledger := &models.TenantLedger{
|
|
TenantID: o.TenantID,
|
|
UserID: o.UserID,
|
|
OrderID: o.ID,
|
|
Type: consts.TenantLedgerTypeAdjustment,
|
|
Amount: o.AmountPaid,
|
|
Remark: "提现拒绝返还: " + reason,
|
|
OperatorUserID: 0, // System/Admin
|
|
IdempotencyKey: uuid.NewString(),
|
|
}
|
|
if err := tx.TenantLedger.WithContext(ctx).Create(ledger); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
})
|
|
|
|
if err == nil && Audit != nil {
|
|
Audit.Log(ctx, "reject_withdrawal", cast.ToString(id), "Rejected: "+reason)
|
|
}
|
|
return err
|
|
}
|