Refactor order and tenant ledger models to use consts for Currency and Type fields; add new UserStatus values; implement comprehensive test cases for content, creator, order, super, and wallet services.
This commit is contained in:
@@ -4,12 +4,73 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"mime/multipart"
|
"mime/multipart"
|
||||||
|
|
||||||
|
"quyun/v2/app/errorx"
|
||||||
common_dto "quyun/v2/app/http/v1/dto"
|
common_dto "quyun/v2/app/http/v1/dto"
|
||||||
|
"quyun/v2/database/fields"
|
||||||
|
"quyun/v2/database/models"
|
||||||
|
"quyun/v2/pkg/consts"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"github.com/spf13/cast"
|
||||||
|
"go.ipao.vip/gen/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
// @provider
|
// @provider
|
||||||
type common struct{}
|
type common struct{}
|
||||||
|
|
||||||
func (s *common) Upload(ctx context.Context, file *multipart.FileHeader, typeArg string) (*common_dto.UploadResult, error) {
|
func (s *common) Upload(ctx context.Context, file *multipart.FileHeader, typeArg string) (*common_dto.UploadResult, error) {
|
||||||
return &common_dto.UploadResult{}, nil
|
userID := ctx.Value(consts.CtxKeyUser)
|
||||||
|
if userID == nil {
|
||||||
|
return nil, errorx.ErrUnauthorized
|
||||||
|
}
|
||||||
|
uid := cast.ToInt64(userID)
|
||||||
|
|
||||||
|
// Mock Upload to S3/MinIO
|
||||||
|
// objectKey := uuid.NewString() + filepath.Ext(file.Filename)
|
||||||
|
objectKey := uuid.NewString() + "_" + file.Filename
|
||||||
|
url := "http://mock-storage/" + objectKey
|
||||||
|
|
||||||
|
// Determine TenantID.
|
||||||
|
// Uploads usually happen in context of a tenant? Or personal?
|
||||||
|
// For now assume user's owned tenant if any, or 0.
|
||||||
|
// MediaAsset has TenantID (NOT NULL).
|
||||||
|
// We need to fetch tenant.
|
||||||
|
t, err := models.TenantQuery.WithContext(ctx).Where(models.TenantQuery.UserID.Eq(uid)).First()
|
||||||
|
var tid int64 = 0
|
||||||
|
if err == nil {
|
||||||
|
tid = t.ID
|
||||||
|
}
|
||||||
|
// If no tenant, and TenantID is NOT NULL, we have a problem for regular users uploading avatar?
|
||||||
|
// Users avatar is URL string in `users` table.
|
||||||
|
// MediaAssets table is for TENANT content.
|
||||||
|
// If this is for user avatar upload, maybe we don't use MediaAssets?
|
||||||
|
// But `upload` endpoint is generic.
|
||||||
|
// Let's assume tid=0 is allowed if system bucket, or enforce tenant.
|
||||||
|
// If table says NOT NULL, 0 is valid int64.
|
||||||
|
|
||||||
|
asset := &models.MediaAsset{
|
||||||
|
TenantID: tid,
|
||||||
|
UserID: uid,
|
||||||
|
Type: consts.MediaAssetType(typeArg),
|
||||||
|
Status: consts.MediaAssetStatusUploaded,
|
||||||
|
Provider: "mock",
|
||||||
|
Bucket: "default",
|
||||||
|
ObjectKey: objectKey,
|
||||||
|
Meta: types.NewJSONType(fields.MediaAssetMeta{
|
||||||
|
Size: file.Size,
|
||||||
|
// MimeType?
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := models.MediaAssetQuery.WithContext(ctx).Create(asset); err != nil {
|
||||||
|
return nil, errorx.ErrDatabaseError
|
||||||
|
}
|
||||||
|
|
||||||
|
return &common_dto.UploadResult{
|
||||||
|
ID: cast.ToString(asset.ID),
|
||||||
|
URL: url,
|
||||||
|
Filename: file.Filename,
|
||||||
|
Size: file.Size,
|
||||||
|
MimeType: file.Header.Get("Content-Type"),
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
@@ -2,28 +2,187 @@ package services
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"quyun/v2/app/errorx"
|
||||||
content_dto "quyun/v2/app/http/v1/dto"
|
content_dto "quyun/v2/app/http/v1/dto"
|
||||||
user_dto "quyun/v2/app/http/v1/dto"
|
user_dto "quyun/v2/app/http/v1/dto"
|
||||||
"quyun/v2/app/requests"
|
"quyun/v2/app/requests"
|
||||||
|
"quyun/v2/database/models"
|
||||||
|
"quyun/v2/pkg/consts"
|
||||||
|
|
||||||
|
"github.com/spf13/cast"
|
||||||
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
// @provider
|
// @provider
|
||||||
type content struct{}
|
type content struct{}
|
||||||
|
|
||||||
func (s *content) List(ctx context.Context, keyword, genre, tenantId, sort string, page int) (*requests.Pager, error) {
|
func (s *content) List(ctx context.Context, keyword, genre, tenantId, sort string, page int) (*requests.Pager, error) {
|
||||||
return &requests.Pager{}, nil
|
tbl, q := models.ContentQuery.QueryContext(ctx)
|
||||||
|
|
||||||
|
// Filters
|
||||||
|
q = q.Where(tbl.Status.Eq(consts.ContentStatusPublished))
|
||||||
|
if keyword != "" {
|
||||||
|
q = q.Where(tbl.Title.Like("%" + keyword + "%"))
|
||||||
|
}
|
||||||
|
if genre != "" {
|
||||||
|
q = q.Where(tbl.Genre.Eq(genre))
|
||||||
|
}
|
||||||
|
if tenantId != "" {
|
||||||
|
tid := cast.ToInt64(tenantId)
|
||||||
|
q = q.Where(tbl.TenantID.Eq(tid))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Preload Author
|
||||||
|
q = q.Preload(tbl.Author)
|
||||||
|
|
||||||
|
// Sort
|
||||||
|
switch sort {
|
||||||
|
case "hot":
|
||||||
|
q = q.Order(tbl.Views.Desc())
|
||||||
|
case "price_asc":
|
||||||
|
q = q.Order(tbl.ID.Desc())
|
||||||
|
default: // latest
|
||||||
|
q = q.Order(tbl.PublishedAt.Desc())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pagination
|
||||||
|
p := requests.Pagination{Page: int64(page), Limit: 10}
|
||||||
|
total, err := q.Count()
|
||||||
|
if err != nil {
|
||||||
|
return nil, errorx.ErrDatabaseError
|
||||||
|
}
|
||||||
|
|
||||||
|
list, err := q.Offset(int(p.Offset())).Limit(int(p.Limit)).Find()
|
||||||
|
if err != nil {
|
||||||
|
return nil, errorx.ErrDatabaseError
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert to DTO
|
||||||
|
data := make([]content_dto.ContentItem, len(list))
|
||||||
|
for i, item := range list {
|
||||||
|
data[i] = s.toContentItemDTO(item)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &requests.Pager{
|
||||||
|
Pagination: requests.Pagination{
|
||||||
|
Page: p.Page,
|
||||||
|
Limit: p.Limit,
|
||||||
|
},
|
||||||
|
Total: total,
|
||||||
|
Items: data,
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *content) Get(ctx context.Context, id string) (*content_dto.ContentDetail, error) {
|
func (s *content) Get(ctx context.Context, id string) (*content_dto.ContentDetail, error) {
|
||||||
return &content_dto.ContentDetail{}, nil
|
cid := cast.ToInt64(id)
|
||||||
|
_, q := models.ContentQuery.QueryContext(ctx)
|
||||||
|
|
||||||
|
var item models.Content
|
||||||
|
// Use UnderlyingDB for complex nested preloading
|
||||||
|
err := q.UnderlyingDB().
|
||||||
|
Preload("Author").
|
||||||
|
Preload("ContentAssets", func(db *gorm.DB) *gorm.DB {
|
||||||
|
return db.Order("sort ASC")
|
||||||
|
}).
|
||||||
|
Preload("ContentAssets.Asset").
|
||||||
|
Where("id = ?", cid).
|
||||||
|
First(&item).Error
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
return nil, errorx.ErrRecordNotFound
|
||||||
|
}
|
||||||
|
return nil, errorx.ErrDatabaseError
|
||||||
|
}
|
||||||
|
|
||||||
|
// Interaction status (isLiked, isFavorited)
|
||||||
|
userID := ctx.Value(consts.CtxKeyUser)
|
||||||
|
isLiked := false
|
||||||
|
isFavorited := false
|
||||||
|
if userID != nil {
|
||||||
|
// uid := cast.ToInt64(userID) // Unused for now until interaction query implemented
|
||||||
|
// ... check likes ...
|
||||||
|
}
|
||||||
|
|
||||||
|
detail := &content_dto.ContentDetail{
|
||||||
|
ContentItem: s.toContentItemDTO(&item),
|
||||||
|
Description: item.Description,
|
||||||
|
Body: item.Body,
|
||||||
|
MediaUrls: s.toMediaURLs(item.ContentAssets),
|
||||||
|
IsLiked: isLiked,
|
||||||
|
IsFavorited: isFavorited,
|
||||||
|
}
|
||||||
|
|
||||||
|
return detail, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *content) ListComments(ctx context.Context, id string, page int) (*requests.Pager, error) {
|
func (s *content) ListComments(ctx context.Context, id string, page int) (*requests.Pager, error) {
|
||||||
return &requests.Pager{}, nil
|
cid := cast.ToInt64(id)
|
||||||
|
tbl, q := models.CommentQuery.QueryContext(ctx)
|
||||||
|
|
||||||
|
q = q.Where(tbl.ContentID.Eq(cid)).Preload(tbl.User)
|
||||||
|
q = q.Order(tbl.CreatedAt.Desc())
|
||||||
|
|
||||||
|
p := requests.Pagination{Page: int64(page), Limit: 10}
|
||||||
|
total, err := q.Count()
|
||||||
|
if err != nil {
|
||||||
|
return nil, errorx.ErrDatabaseError
|
||||||
|
}
|
||||||
|
|
||||||
|
list, err := q.Offset(int(p.Offset())).Limit(int(p.Limit)).Find()
|
||||||
|
if err != nil {
|
||||||
|
return nil, errorx.ErrDatabaseError
|
||||||
|
}
|
||||||
|
|
||||||
|
data := make([]content_dto.Comment, len(list))
|
||||||
|
for i, v := range list {
|
||||||
|
data[i] = content_dto.Comment{
|
||||||
|
ID: cast.ToString(v.ID),
|
||||||
|
Content: v.Content,
|
||||||
|
UserID: cast.ToString(v.UserID),
|
||||||
|
UserNickname: v.User.Nickname, // Preloaded
|
||||||
|
UserAvatar: v.User.Avatar,
|
||||||
|
CreateTime: v.CreatedAt.Format("2006-01-02 15:04:05"),
|
||||||
|
Likes: int(v.Likes),
|
||||||
|
ReplyTo: cast.ToString(v.ReplyTo),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &requests.Pager{
|
||||||
|
Pagination: requests.Pagination{
|
||||||
|
Page: p.Page,
|
||||||
|
Limit: p.Limit,
|
||||||
|
},
|
||||||
|
Total: total,
|
||||||
|
Items: data,
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *content) CreateComment(ctx context.Context, id string, form *content_dto.CommentCreateForm) error {
|
func (s *content) CreateComment(ctx context.Context, id string, form *content_dto.CommentCreateForm) error {
|
||||||
|
userID := ctx.Value(consts.CtxKeyUser)
|
||||||
|
if userID == nil {
|
||||||
|
return errorx.ErrUnauthorized
|
||||||
|
}
|
||||||
|
uid := cast.ToInt64(userID)
|
||||||
|
cid := cast.ToInt64(id)
|
||||||
|
|
||||||
|
c, err := models.ContentQuery.WithContext(ctx).Where(models.ContentQuery.ID.Eq(cid)).First()
|
||||||
|
if err != nil {
|
||||||
|
return errorx.ErrRecordNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
comment := &models.Comment{
|
||||||
|
TenantID: c.TenantID,
|
||||||
|
UserID: uid,
|
||||||
|
ContentID: cid,
|
||||||
|
Content: form.Content,
|
||||||
|
ReplyTo: cast.ToInt64(form.ReplyTo),
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := models.CommentQuery.WithContext(ctx).Create(comment); err != nil {
|
||||||
|
return errorx.ErrDatabaseError
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,3 +221,37 @@ func (s *content) RemoveLike(ctx context.Context, contentId string) error {
|
|||||||
func (s *content) ListTopics(ctx context.Context) ([]content_dto.Topic, error) {
|
func (s *content) ListTopics(ctx context.Context) ([]content_dto.Topic, error) {
|
||||||
return []content_dto.Topic{}, nil
|
return []content_dto.Topic{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Helpers
|
||||||
|
|
||||||
|
func (s *content) toContentItemDTO(item *models.Content) content_dto.ContentItem {
|
||||||
|
dto := content_dto.ContentItem{
|
||||||
|
ID: cast.ToString(item.ID),
|
||||||
|
Title: item.Title,
|
||||||
|
Genre: item.Genre,
|
||||||
|
AuthorID: cast.ToString(item.UserID),
|
||||||
|
Views: int(item.Views),
|
||||||
|
Likes: int(item.Likes),
|
||||||
|
}
|
||||||
|
if item.Author != nil {
|
||||||
|
dto.AuthorName = item.Author.Nickname
|
||||||
|
dto.AuthorAvatar = item.Author.Avatar
|
||||||
|
}
|
||||||
|
return dto
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *content) toMediaURLs(assets []*models.ContentAsset) []content_dto.MediaURL {
|
||||||
|
var urls []content_dto.MediaURL
|
||||||
|
for _, ca := range assets {
|
||||||
|
if ca.Asset != nil {
|
||||||
|
// Construct URL based on Asset info (Bucket/Key/Provider)
|
||||||
|
// For prototype: mock url
|
||||||
|
url := "http://mock/" + ca.Asset.ObjectKey
|
||||||
|
urls = append(urls, content_dto.MediaURL{
|
||||||
|
Type: string(ca.Asset.Type), // Assuming type is enum or string
|
||||||
|
URL: url,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return urls
|
||||||
|
}
|
||||||
|
|||||||
151
backend/app/services/content_test.go
Normal file
151
backend/app/services/content_test.go
Normal file
@@ -0,0 +1,151 @@
|
|||||||
|
package services
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"quyun/v2/app/commands/testx"
|
||||||
|
content_dto "quyun/v2/app/http/v1/dto"
|
||||||
|
"quyun/v2/database"
|
||||||
|
"quyun/v2/database/models"
|
||||||
|
"quyun/v2/pkg/consts"
|
||||||
|
|
||||||
|
. "github.com/smartystreets/goconvey/convey"
|
||||||
|
"github.com/spf13/cast"
|
||||||
|
"github.com/stretchr/testify/suite"
|
||||||
|
"go.ipao.vip/atom/contracts"
|
||||||
|
"go.uber.org/dig"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ContentTestSuiteInjectParams struct {
|
||||||
|
dig.In
|
||||||
|
|
||||||
|
DB *sql.DB
|
||||||
|
Initials []contracts.Initial `group:"initials"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ContentTestSuite struct {
|
||||||
|
suite.Suite
|
||||||
|
ContentTestSuiteInjectParams
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_Content(t *testing.T) {
|
||||||
|
providers := testx.Default().With(Provide)
|
||||||
|
|
||||||
|
testx.Serve(providers, t, func(p ContentTestSuiteInjectParams) {
|
||||||
|
suite.Run(t, &ContentTestSuite{ContentTestSuiteInjectParams: p})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ContentTestSuite) Test_List() {
|
||||||
|
Convey("List", s.T(), func() {
|
||||||
|
ctx := s.T().Context()
|
||||||
|
database.Truncate(ctx, s.DB, models.TableNameContent, models.TableNameUser)
|
||||||
|
|
||||||
|
// Create Author
|
||||||
|
author := &models.User{Nickname: "Author1", Username: "author1", Phone: "13800000001"}
|
||||||
|
models.UserQuery.WithContext(ctx).Create(author)
|
||||||
|
|
||||||
|
// Create Contents
|
||||||
|
c1 := &models.Content{
|
||||||
|
TenantID: 1,
|
||||||
|
UserID: author.ID,
|
||||||
|
Title: "Content A",
|
||||||
|
Status: consts.ContentStatusPublished,
|
||||||
|
Genre: "video",
|
||||||
|
}
|
||||||
|
c2 := &models.Content{
|
||||||
|
TenantID: 1,
|
||||||
|
UserID: author.ID,
|
||||||
|
Title: "Content B",
|
||||||
|
Status: consts.ContentStatusDraft, // Draft
|
||||||
|
Genre: "video",
|
||||||
|
}
|
||||||
|
models.ContentQuery.WithContext(ctx).Create(c1, c2)
|
||||||
|
|
||||||
|
Convey("should list only published contents", func() {
|
||||||
|
res, err := Content.List(ctx, "", "", "1", "", 1)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(res.Total, ShouldEqual, 1)
|
||||||
|
items := res.Items.([]content_dto.ContentItem)
|
||||||
|
So(items[0].Title, ShouldEqual, "Content A")
|
||||||
|
So(items[0].AuthorName, ShouldEqual, "Author1")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ContentTestSuite) Test_Get() {
|
||||||
|
Convey("Get", s.T(), func() {
|
||||||
|
ctx := s.T().Context()
|
||||||
|
database.Truncate(ctx, s.DB, models.TableNameContent, models.TableNameMediaAsset, models.TableNameContentAsset, models.TableNameUser)
|
||||||
|
|
||||||
|
// Author
|
||||||
|
author := &models.User{Nickname: "Author1", Username: "author1", Phone: "13800000002"}
|
||||||
|
models.UserQuery.WithContext(ctx).Create(author)
|
||||||
|
|
||||||
|
// Asset
|
||||||
|
asset := &models.MediaAsset{
|
||||||
|
TenantID: 1,
|
||||||
|
UserID: author.ID,
|
||||||
|
ObjectKey: "test.mp4",
|
||||||
|
Type: consts.MediaAssetTypeVideo,
|
||||||
|
}
|
||||||
|
models.MediaAssetQuery.WithContext(ctx).Create(asset)
|
||||||
|
|
||||||
|
// Content
|
||||||
|
content := &models.Content{
|
||||||
|
TenantID: 1,
|
||||||
|
UserID: author.ID,
|
||||||
|
Title: "Detail Content",
|
||||||
|
Status: consts.ContentStatusPublished,
|
||||||
|
}
|
||||||
|
models.ContentQuery.WithContext(ctx).Create(content)
|
||||||
|
|
||||||
|
// Link Asset
|
||||||
|
ca := &models.ContentAsset{
|
||||||
|
TenantID: 1,
|
||||||
|
UserID: author.ID,
|
||||||
|
ContentID: content.ID,
|
||||||
|
AssetID: asset.ID,
|
||||||
|
Sort: 1,
|
||||||
|
}
|
||||||
|
models.ContentAssetQuery.WithContext(ctx).Create(ca)
|
||||||
|
|
||||||
|
Convey("should get detail with assets", func() {
|
||||||
|
detail, err := Content.Get(ctx, cast.ToString(content.ID))
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(detail.Title, ShouldEqual, "Detail Content")
|
||||||
|
So(detail.AuthorName, ShouldEqual, "Author1")
|
||||||
|
So(len(detail.MediaUrls), ShouldEqual, 1)
|
||||||
|
So(detail.MediaUrls[0].URL, ShouldEndWith, "test.mp4")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ContentTestSuite) Test_CreateComment() {
|
||||||
|
Convey("CreateComment", s.T(), func() {
|
||||||
|
ctx := s.T().Context()
|
||||||
|
database.Truncate(ctx, s.DB, models.TableNameContent, models.TableNameComment, models.TableNameUser)
|
||||||
|
|
||||||
|
// User & Content
|
||||||
|
u := &models.User{Username: "user1", Phone: "13900000001"}
|
||||||
|
models.UserQuery.WithContext(ctx).Create(u)
|
||||||
|
c := &models.Content{TenantID: 1, UserID: u.ID, Title: "C"}
|
||||||
|
models.ContentQuery.WithContext(ctx).Create(c)
|
||||||
|
|
||||||
|
// Auth context
|
||||||
|
ctx = context.WithValue(ctx, consts.CtxKeyUser, u.ID)
|
||||||
|
|
||||||
|
Convey("should create comment", func() {
|
||||||
|
form := &content_dto.CommentCreateForm{
|
||||||
|
Content: "Nice!",
|
||||||
|
}
|
||||||
|
err := Content.CreateComment(ctx, cast.ToString(c.ID), form)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
count, _ := models.CommentQuery.WithContext(ctx).Where(models.CommentQuery.ContentID.Eq(c.ID)).Count()
|
||||||
|
So(count, ShouldEqual, 1)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -2,27 +2,174 @@ package services
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"quyun/v2/app/errorx"
|
||||||
creator_dto "quyun/v2/app/http/v1/dto"
|
creator_dto "quyun/v2/app/http/v1/dto"
|
||||||
|
"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
|
// @provider
|
||||||
type creator struct{}
|
type creator struct{}
|
||||||
|
|
||||||
func (s *creator) Apply(ctx context.Context, form *creator_dto.ApplyForm) error {
|
func (s *creator) Apply(ctx context.Context, form *creator_dto.ApplyForm) error {
|
||||||
|
userID := ctx.Value(consts.CtxKeyUser)
|
||||||
|
if userID == nil {
|
||||||
|
return errorx.ErrUnauthorized
|
||||||
|
}
|
||||||
|
uid := cast.ToInt64(userID)
|
||||||
|
|
||||||
|
tbl, q := models.TenantQuery.QueryContext(ctx)
|
||||||
|
// Check if already has a tenant
|
||||||
|
count, _ := q.Where(tbl.UserID.Eq(uid)).Count()
|
||||||
|
if count > 0 {
|
||||||
|
return errorx.ErrBadRequest.WithMsg("您已是创作者")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create Tenant
|
||||||
|
tenant := &models.Tenant{
|
||||||
|
UserID: uid,
|
||||||
|
Name: form.Name,
|
||||||
|
// Bio/Avatar in config
|
||||||
|
Code: uuid.NewString()[:8], // Generate random code
|
||||||
|
UUID: types.UUID(uuid.New()),
|
||||||
|
Status: consts.TenantStatusPendingVerify,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := q.Create(tenant); err != nil {
|
||||||
|
return errorx.ErrDatabaseError
|
||||||
|
}
|
||||||
|
|
||||||
|
// Also add user as tenant_admin in tenant_users
|
||||||
|
tu := &models.TenantUser{
|
||||||
|
TenantID: tenant.ID,
|
||||||
|
UserID: uid,
|
||||||
|
Role: types.Array[consts.TenantUserRole]{consts.TenantUserRoleTenantAdmin},
|
||||||
|
Status: consts.UserStatusVerified,
|
||||||
|
}
|
||||||
|
if err := models.TenantUserQuery.WithContext(ctx).Create(tu); err != nil {
|
||||||
|
return errorx.ErrDatabaseError
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *creator) Dashboard(ctx context.Context) (*creator_dto.DashboardStats, error) {
|
func (s *creator) Dashboard(ctx context.Context) (*creator_dto.DashboardStats, error) {
|
||||||
return &creator_dto.DashboardStats{}, nil
|
tid, err := s.getTenantID(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mock stats for now or query
|
||||||
|
// Followers: count tenant_users
|
||||||
|
followers, _ := models.TenantUserQuery.WithContext(ctx).Where(models.TenantUserQuery.TenantID.Eq(tid)).Count()
|
||||||
|
|
||||||
|
stats := &creator_dto.DashboardStats{
|
||||||
|
TotalFollowers: creator_dto.IntStatItem{Value: int(followers)},
|
||||||
|
TotalRevenue: creator_dto.FloatStatItem{Value: 0},
|
||||||
|
PendingRefunds: 0,
|
||||||
|
NewMessages: 0,
|
||||||
|
}
|
||||||
|
return stats, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *creator) ListContents(ctx context.Context, status, genre, keyword string) ([]creator_dto.ContentItem, error) {
|
func (s *creator) ListContents(ctx context.Context, status, genre, keyword string) ([]creator_dto.ContentItem, error) {
|
||||||
return []creator_dto.ContentItem{}, nil
|
tid, err := s.getTenantID(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
tbl, q := models.ContentQuery.QueryContext(ctx)
|
||||||
|
q = q.Where(tbl.TenantID.Eq(tid))
|
||||||
|
|
||||||
|
if status != "" {
|
||||||
|
q = q.Where(tbl.Status.Eq(consts.ContentStatus(status)))
|
||||||
|
}
|
||||||
|
if genre != "" {
|
||||||
|
q = q.Where(tbl.Genre.Eq(genre))
|
||||||
|
}
|
||||||
|
if keyword != "" {
|
||||||
|
q = q.Where(tbl.Title.Like("%" + keyword + "%"))
|
||||||
|
}
|
||||||
|
|
||||||
|
list, err := q.Order(tbl.CreatedAt.Desc()).Find()
|
||||||
|
if err != nil {
|
||||||
|
return nil, errorx.ErrDatabaseError
|
||||||
|
}
|
||||||
|
|
||||||
|
var data []creator_dto.ContentItem
|
||||||
|
for _, item := range list {
|
||||||
|
data = append(data, creator_dto.ContentItem{
|
||||||
|
ID: cast.ToString(item.ID),
|
||||||
|
Title: item.Title,
|
||||||
|
Genre: item.Genre,
|
||||||
|
Views: int(item.Views),
|
||||||
|
Likes: int(item.Likes),
|
||||||
|
IsPurchased: false,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return data, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *creator) CreateContent(ctx context.Context, form *creator_dto.ContentCreateForm) error {
|
func (s *creator) CreateContent(ctx context.Context, form *creator_dto.ContentCreateForm) error {
|
||||||
return nil
|
tid, err := s.getTenantID(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
uid := cast.ToInt64(ctx.Value(consts.CtxKeyUser))
|
||||||
|
|
||||||
|
return models.Q.Transaction(func(tx *models.Query) error {
|
||||||
|
// 1. Create Content
|
||||||
|
content := &models.Content{
|
||||||
|
TenantID: tid,
|
||||||
|
UserID: uid,
|
||||||
|
Title: form.Title,
|
||||||
|
Genre: form.Genre,
|
||||||
|
Status: consts.ContentStatusPublished,
|
||||||
|
}
|
||||||
|
if err := tx.Content.WithContext(ctx).Create(content); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Link Assets
|
||||||
|
if len(form.MediaIDs) > 0 {
|
||||||
|
var assets []*models.ContentAsset
|
||||||
|
for i, mid := range form.MediaIDs {
|
||||||
|
assets = append(assets, &models.ContentAsset{
|
||||||
|
TenantID: tid,
|
||||||
|
UserID: uid,
|
||||||
|
ContentID: content.ID,
|
||||||
|
AssetID: cast.ToInt64(mid),
|
||||||
|
Sort: int32(i),
|
||||||
|
Role: consts.ContentAssetRoleMain,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if err := tx.ContentAsset.WithContext(ctx).Create(assets...); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Set Price
|
||||||
|
price := &models.ContentPrice{
|
||||||
|
TenantID: tid,
|
||||||
|
UserID: uid,
|
||||||
|
ContentID: content.ID,
|
||||||
|
PriceAmount: int64(form.Price * 100), // Convert to cents
|
||||||
|
Currency: consts.CurrencyCNY,
|
||||||
|
}
|
||||||
|
if err := tx.ContentPrice.WithContext(ctx).Create(price); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *creator) UpdateContent(ctx context.Context, id string, form *creator_dto.ContentUpdateForm) error {
|
func (s *creator) UpdateContent(ctx context.Context, id string, form *creator_dto.ContentUpdateForm) error {
|
||||||
@@ -30,11 +177,43 @@ func (s *creator) UpdateContent(ctx context.Context, id string, form *creator_dt
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *creator) DeleteContent(ctx context.Context, id string) error {
|
func (s *creator) DeleteContent(ctx context.Context, id string) error {
|
||||||
|
cid := cast.ToInt64(id)
|
||||||
|
tid, err := s.getTenantID(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = models.ContentQuery.WithContext(ctx).Where(models.ContentQuery.ID.Eq(cid), models.ContentQuery.TenantID.Eq(tid)).Delete()
|
||||||
|
if err != nil {
|
||||||
|
return errorx.ErrDatabaseError
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *creator) ListOrders(ctx context.Context, status, keyword string) ([]creator_dto.Order, error) {
|
func (s *creator) ListOrders(ctx context.Context, status, keyword string) ([]creator_dto.Order, error) {
|
||||||
return []creator_dto.Order{}, nil
|
tid, err := s.getTenantID(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
tbl, q := models.OrderQuery.QueryContext(ctx)
|
||||||
|
q = q.Where(tbl.TenantID.Eq(tid))
|
||||||
|
// Filters...
|
||||||
|
list, err := q.Order(tbl.CreatedAt.Desc()).Find()
|
||||||
|
if err != nil {
|
||||||
|
return nil, errorx.ErrDatabaseError
|
||||||
|
}
|
||||||
|
|
||||||
|
var data []creator_dto.Order
|
||||||
|
for _, o := range list {
|
||||||
|
data = append(data, creator_dto.Order{
|
||||||
|
ID: cast.ToString(o.ID),
|
||||||
|
Status: string(o.Status), // Enum conversion
|
||||||
|
Amount: float64(o.AmountPaid) / 100.0,
|
||||||
|
CreateTime: o.CreatedAt.Format(time.RFC3339),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return data, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *creator) ProcessRefund(ctx context.Context, id string, form *creator_dto.RefundForm) error {
|
func (s *creator) ProcessRefund(ctx context.Context, id string, form *creator_dto.RefundForm) error {
|
||||||
@@ -42,7 +221,19 @@ func (s *creator) ProcessRefund(ctx context.Context, id string, form *creator_dt
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *creator) GetSettings(ctx context.Context) (*creator_dto.Settings, error) {
|
func (s *creator) GetSettings(ctx context.Context) (*creator_dto.Settings, error) {
|
||||||
return &creator_dto.Settings{}, nil
|
tid, err := s.getTenantID(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
t, err := models.TenantQuery.WithContext(ctx).Where(models.TenantQuery.ID.Eq(tid)).First()
|
||||||
|
if err != nil {
|
||||||
|
return nil, errorx.ErrRecordNotFound
|
||||||
|
}
|
||||||
|
// Extract from t.Config
|
||||||
|
return &creator_dto.Settings{
|
||||||
|
Name: t.Name,
|
||||||
|
// Bio/Avatar from Config
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *creator) UpdateSettings(ctx context.Context, form *creator_dto.Settings) error {
|
func (s *creator) UpdateSettings(ctx context.Context, form *creator_dto.Settings) error {
|
||||||
@@ -64,3 +255,23 @@ func (s *creator) RemovePayoutAccount(ctx context.Context, id string) error {
|
|||||||
func (s *creator) Withdraw(ctx context.Context, form *creator_dto.WithdrawForm) error {
|
func (s *creator) Withdraw(ctx context.Context, form *creator_dto.WithdrawForm) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Helpers
|
||||||
|
|
||||||
|
func (s *creator) getTenantID(ctx context.Context) (int64, error) {
|
||||||
|
userID := ctx.Value(consts.CtxKeyUser)
|
||||||
|
if userID == nil {
|
||||||
|
return 0, errorx.ErrUnauthorized
|
||||||
|
}
|
||||||
|
uid := cast.ToInt64(userID)
|
||||||
|
|
||||||
|
// Simple check: User owns tenant
|
||||||
|
t, err := models.TenantQuery.WithContext(ctx).Where(models.TenantQuery.UserID.Eq(uid)).First()
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
return 0, errorx.ErrPermissionDenied.WithMsg("非创作者")
|
||||||
|
}
|
||||||
|
return 0, errorx.ErrDatabaseError
|
||||||
|
}
|
||||||
|
return t.ID, nil
|
||||||
|
}
|
||||||
|
|||||||
106
backend/app/services/creator_test.go
Normal file
106
backend/app/services/creator_test.go
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
package services
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"quyun/v2/app/commands/testx"
|
||||||
|
creator_dto "quyun/v2/app/http/v1/dto"
|
||||||
|
"quyun/v2/database"
|
||||||
|
"quyun/v2/database/models"
|
||||||
|
"quyun/v2/pkg/consts"
|
||||||
|
|
||||||
|
. "github.com/smartystreets/goconvey/convey"
|
||||||
|
"github.com/stretchr/testify/suite"
|
||||||
|
"go.ipao.vip/atom/contracts"
|
||||||
|
"go.uber.org/dig"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CreatorTestSuiteInjectParams struct {
|
||||||
|
dig.In
|
||||||
|
|
||||||
|
DB *sql.DB
|
||||||
|
Initials []contracts.Initial `group:"initials"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CreatorTestSuite struct {
|
||||||
|
suite.Suite
|
||||||
|
CreatorTestSuiteInjectParams
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_Creator(t *testing.T) {
|
||||||
|
providers := testx.Default().With(Provide)
|
||||||
|
|
||||||
|
testx.Serve(providers, t, func(p CreatorTestSuiteInjectParams) {
|
||||||
|
suite.Run(t, &CreatorTestSuite{CreatorTestSuiteInjectParams: p})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *CreatorTestSuite) Test_Apply() {
|
||||||
|
Convey("Apply", s.T(), func() {
|
||||||
|
ctx := s.T().Context()
|
||||||
|
database.Truncate(ctx, s.DB, models.TableNameTenant, models.TableNameTenantUser, models.TableNameUser)
|
||||||
|
|
||||||
|
u := &models.User{Username: "creator1", Phone: "13700000001"}
|
||||||
|
models.UserQuery.WithContext(ctx).Create(u)
|
||||||
|
ctx = context.WithValue(ctx, consts.CtxKeyUser, u.ID)
|
||||||
|
|
||||||
|
Convey("should create tenant", func() {
|
||||||
|
form := &creator_dto.ApplyForm{
|
||||||
|
Name: "My Channel",
|
||||||
|
}
|
||||||
|
err := Creator.Apply(ctx, form)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
t, _ := models.TenantQuery.WithContext(ctx).Where(models.TenantQuery.UserID.Eq(u.ID)).First()
|
||||||
|
So(t, ShouldNotBeNil)
|
||||||
|
So(t.Name, ShouldEqual, "My Channel")
|
||||||
|
So(t.Status, ShouldEqual, consts.TenantStatusPendingVerify)
|
||||||
|
|
||||||
|
// Check admin role
|
||||||
|
tu, _ := models.TenantUserQuery.WithContext(ctx).Where(models.TenantUserQuery.TenantID.Eq(t.ID)).First()
|
||||||
|
So(tu, ShouldNotBeNil)
|
||||||
|
// Role is array, check contains? Or first element?
|
||||||
|
// types.Array is likely []T.
|
||||||
|
So(len(tu.Role), ShouldEqual, 1)
|
||||||
|
So(tu.Role[0], ShouldEqual, consts.TenantUserRoleTenantAdmin)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *CreatorTestSuite) Test_CreateContent() {
|
||||||
|
Convey("CreateContent", s.T(), func() {
|
||||||
|
ctx := s.T().Context()
|
||||||
|
database.Truncate(ctx, s.DB, models.TableNameTenant, models.TableNameContent, models.TableNameContentAsset, models.TableNameContentPrice, models.TableNameUser)
|
||||||
|
|
||||||
|
u := &models.User{Username: "creator2", Phone: "13700000002"}
|
||||||
|
models.UserQuery.WithContext(ctx).Create(u)
|
||||||
|
ctx = context.WithValue(ctx, consts.CtxKeyUser, u.ID)
|
||||||
|
|
||||||
|
// Create Tenant manually
|
||||||
|
t := &models.Tenant{UserID: u.ID, Name: "Channel 2", Code: "123", Status: consts.TenantStatusVerified}
|
||||||
|
models.TenantQuery.WithContext(ctx).Create(t)
|
||||||
|
|
||||||
|
Convey("should create content and assets", func() {
|
||||||
|
form := &creator_dto.ContentCreateForm{
|
||||||
|
Title: "New Song",
|
||||||
|
Genre: "audio",
|
||||||
|
Price: 9.99,
|
||||||
|
// MediaIDs: ... need media asset
|
||||||
|
}
|
||||||
|
err := Creator.CreateContent(ctx, form)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
c, _ := models.ContentQuery.WithContext(ctx).Where(models.ContentQuery.Title.Eq("New Song")).First()
|
||||||
|
So(c, ShouldNotBeNil)
|
||||||
|
So(c.UserID, ShouldEqual, u.ID)
|
||||||
|
So(c.TenantID, ShouldEqual, t.ID)
|
||||||
|
|
||||||
|
// Check Price
|
||||||
|
p, _ := models.ContentPriceQuery.WithContext(ctx).Where(models.ContentPriceQuery.ContentID.Eq(c.ID)).First()
|
||||||
|
So(p, ShouldNotBeNil)
|
||||||
|
So(p.PriceAmount, ShouldEqual, 999)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -2,30 +2,240 @@ package services
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"quyun/v2/app/errorx"
|
||||||
transaction_dto "quyun/v2/app/http/v1/dto"
|
transaction_dto "quyun/v2/app/http/v1/dto"
|
||||||
user_dto "quyun/v2/app/http/v1/dto"
|
user_dto "quyun/v2/app/http/v1/dto"
|
||||||
|
"quyun/v2/database/fields"
|
||||||
|
"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
|
// @provider
|
||||||
type order struct{}
|
type order struct{}
|
||||||
|
|
||||||
func (s *order) ListUserOrders(ctx context.Context, status string) ([]user_dto.Order, error) {
|
func (s *order) ListUserOrders(ctx context.Context, status string) ([]user_dto.Order, error) {
|
||||||
return []user_dto.Order{}, nil
|
userID := ctx.Value(consts.CtxKeyUser)
|
||||||
|
if userID == nil {
|
||||||
|
return nil, errorx.ErrUnauthorized
|
||||||
|
}
|
||||||
|
uid := cast.ToInt64(userID)
|
||||||
|
|
||||||
|
tbl, q := models.OrderQuery.QueryContext(ctx)
|
||||||
|
q = q.Where(tbl.UserID.Eq(uid))
|
||||||
|
|
||||||
|
if status != "" && status != "all" {
|
||||||
|
q = q.Where(tbl.Status.Eq(consts.OrderStatus(status)))
|
||||||
|
}
|
||||||
|
|
||||||
|
list, err := q.Order(tbl.CreatedAt.Desc()).Find()
|
||||||
|
if err != nil {
|
||||||
|
return nil, errorx.ErrDatabaseError
|
||||||
|
}
|
||||||
|
|
||||||
|
var data []user_dto.Order
|
||||||
|
for _, v := range list {
|
||||||
|
data = append(data, s.toUserOrderDTO(v))
|
||||||
|
}
|
||||||
|
return data, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *order) GetUserOrder(ctx context.Context, id string) (*user_dto.Order, error) {
|
func (s *order) GetUserOrder(ctx context.Context, id string) (*user_dto.Order, error) {
|
||||||
return &user_dto.Order{}, nil
|
userID := ctx.Value(consts.CtxKeyUser)
|
||||||
|
if userID == nil {
|
||||||
|
return nil, errorx.ErrUnauthorized
|
||||||
|
}
|
||||||
|
uid := cast.ToInt64(userID)
|
||||||
|
oid := cast.ToInt64(id)
|
||||||
|
|
||||||
|
tbl, q := models.OrderQuery.QueryContext(ctx)
|
||||||
|
item, err := q.Where(tbl.ID.Eq(oid), tbl.UserID.Eq(uid)).First()
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
return nil, errorx.ErrRecordNotFound
|
||||||
|
}
|
||||||
|
return nil, errorx.ErrDatabaseError
|
||||||
|
}
|
||||||
|
|
||||||
|
dto := s.toUserOrderDTO(item)
|
||||||
|
return &dto, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *order) Create(ctx context.Context, form *transaction_dto.OrderCreateForm) (*transaction_dto.OrderCreateResponse, error) {
|
func (s *order) Create(ctx context.Context, form *transaction_dto.OrderCreateForm) (*transaction_dto.OrderCreateResponse, error) {
|
||||||
return &transaction_dto.OrderCreateResponse{}, nil
|
userID := ctx.Value(consts.CtxKeyUser)
|
||||||
|
if userID == nil {
|
||||||
|
return nil, errorx.ErrUnauthorized
|
||||||
|
}
|
||||||
|
uid := cast.ToInt64(userID)
|
||||||
|
cid := cast.ToInt64(form.ContentID)
|
||||||
|
|
||||||
|
// 1. Fetch Content & Price
|
||||||
|
content, err := models.ContentQuery.WithContext(ctx).Where(models.ContentQuery.ID.Eq(cid)).First()
|
||||||
|
if err != nil {
|
||||||
|
return nil, errorx.ErrRecordNotFound.WithMsg("内容不存在")
|
||||||
|
}
|
||||||
|
if content.Status != consts.ContentStatusPublished {
|
||||||
|
return nil, errorx.ErrBusinessLogic.WithMsg("内容未发布")
|
||||||
|
}
|
||||||
|
|
||||||
|
price, err := models.ContentPriceQuery.WithContext(ctx).Where(models.ContentPriceQuery.ContentID.Eq(cid)).First()
|
||||||
|
if err != nil {
|
||||||
|
return nil, errorx.ErrDataCorrupted.WithMsg("价格信息缺失")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Create Order (Status: Created)
|
||||||
|
order := &models.Order{
|
||||||
|
TenantID: content.TenantID,
|
||||||
|
UserID: uid,
|
||||||
|
Type: consts.OrderTypeContentPurchase,
|
||||||
|
Status: consts.OrderStatusCreated,
|
||||||
|
Currency: price.Currency,
|
||||||
|
AmountOriginal: price.PriceAmount,
|
||||||
|
AmountDiscount: 0, // Calculate discount if needed
|
||||||
|
AmountPaid: price.PriceAmount, // Expected to pay
|
||||||
|
IdempotencyKey: uuid.NewString(), // Should be from client ideally
|
||||||
|
Snapshot: types.NewJSONType(fields.OrdersSnapshot{}), // Populate details
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := models.OrderQuery.WithContext(ctx).Create(order); err != nil {
|
||||||
|
return nil, errorx.ErrDatabaseError
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Create Order Item
|
||||||
|
item := &models.OrderItem{
|
||||||
|
TenantID: content.TenantID,
|
||||||
|
UserID: uid,
|
||||||
|
OrderID: order.ID,
|
||||||
|
ContentID: cid,
|
||||||
|
ContentUserID: content.UserID,
|
||||||
|
AmountPaid: order.AmountPaid,
|
||||||
|
}
|
||||||
|
if err := models.OrderItemQuery.WithContext(ctx).Create(item); err != nil {
|
||||||
|
return nil, errorx.ErrDatabaseError
|
||||||
|
}
|
||||||
|
|
||||||
|
return &transaction_dto.OrderCreateResponse{
|
||||||
|
OrderID: cast.ToString(order.ID),
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *order) Pay(ctx context.Context, id string, form *transaction_dto.OrderPayForm) (*transaction_dto.OrderPayResponse, error) {
|
func (s *order) Pay(ctx context.Context, id string, form *transaction_dto.OrderPayForm) (*transaction_dto.OrderPayResponse, error) {
|
||||||
return &transaction_dto.OrderPayResponse{}, nil
|
userID := ctx.Value(consts.CtxKeyUser)
|
||||||
|
if userID == nil {
|
||||||
|
return nil, errorx.ErrUnauthorized
|
||||||
|
}
|
||||||
|
uid := cast.ToInt64(userID)
|
||||||
|
oid := cast.ToInt64(id)
|
||||||
|
|
||||||
|
// Fetch Order
|
||||||
|
o, err := models.OrderQuery.WithContext(ctx).Where(models.OrderQuery.ID.Eq(oid), models.OrderQuery.UserID.Eq(uid)).First()
|
||||||
|
if err != nil {
|
||||||
|
return nil, errorx.ErrRecordNotFound
|
||||||
|
}
|
||||||
|
if o.Status != consts.OrderStatusCreated {
|
||||||
|
return nil, errorx.ErrStatusConflict.WithMsg("订单状态不可支付")
|
||||||
|
}
|
||||||
|
|
||||||
|
if form.Method == "balance" {
|
||||||
|
return s.payWithBalance(ctx, o)
|
||||||
|
}
|
||||||
|
|
||||||
|
// External payment (mock)
|
||||||
|
return &transaction_dto.OrderPayResponse{
|
||||||
|
PayParams: "mock_pay_params",
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *order) payWithBalance(ctx context.Context, o *models.Order) (*transaction_dto.OrderPayResponse, error) {
|
||||||
|
err := models.Q.Transaction(func(tx *models.Query) error {
|
||||||
|
// 1. Deduct User Balance
|
||||||
|
info, err := tx.User.WithContext(ctx).
|
||||||
|
Where(tx.User.ID.Eq(o.UserID), tx.User.Balance.Gte(o.AmountPaid)).
|
||||||
|
Update(tx.User.Balance, gorm.Expr("balance - ?", o.AmountPaid))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if info.RowsAffected == 0 {
|
||||||
|
return errorx.ErrQuotaExceeded.WithMsg("余额不足")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Update Order Status
|
||||||
|
now := time.Now()
|
||||||
|
_, err = tx.Order.WithContext(ctx).Where(tx.Order.ID.Eq(o.ID)).Updates(&models.Order{
|
||||||
|
Status: consts.OrderStatusPaid,
|
||||||
|
PaidAt: now,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Grant Content Access
|
||||||
|
items, _ := tx.OrderItem.WithContext(ctx).Where(tx.OrderItem.OrderID.Eq(o.ID)).Find()
|
||||||
|
for _, item := range items {
|
||||||
|
access := &models.ContentAccess{
|
||||||
|
TenantID: item.TenantID,
|
||||||
|
UserID: o.UserID,
|
||||||
|
ContentID: item.ContentID,
|
||||||
|
OrderID: o.ID,
|
||||||
|
Status: consts.ContentAccessStatusActive,
|
||||||
|
}
|
||||||
|
if err := tx.ContentAccess.WithContext(ctx).Save(access); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Create Tenant Ledger (Revenue)
|
||||||
|
t, err := tx.Tenant.WithContext(ctx).Where(tx.Tenant.ID.Eq(o.TenantID)).First()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
ledger := &models.TenantLedger{
|
||||||
|
TenantID: o.TenantID,
|
||||||
|
UserID: t.UserID, // Owner
|
||||||
|
OrderID: o.ID,
|
||||||
|
Type: consts.TenantLedgerTypeDebitPurchase, // Income from purchase
|
||||||
|
Amount: o.AmountPaid,
|
||||||
|
BalanceBefore: 0, // TODO: Fetch previous balance if tracking tenant balance
|
||||||
|
BalanceAfter: 0, // TODO
|
||||||
|
FrozenBefore: 0,
|
||||||
|
FrozenAfter: 0,
|
||||||
|
IdempotencyKey: uuid.NewString(),
|
||||||
|
Remark: "内容销售收入",
|
||||||
|
OperatorUserID: o.UserID,
|
||||||
|
}
|
||||||
|
if err := tx.TenantLedger.WithContext(ctx).Create(ledger); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &transaction_dto.OrderPayResponse{
|
||||||
|
PayParams: "balance_paid",
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *order) Status(ctx context.Context, id string) (*transaction_dto.OrderStatusResponse, error) {
|
func (s *order) Status(ctx context.Context, id string) (*transaction_dto.OrderStatusResponse, error) {
|
||||||
return &transaction_dto.OrderStatusResponse{}, nil
|
// ... check status ...
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *order) toUserOrderDTO(o *models.Order) user_dto.Order {
|
||||||
|
return user_dto.Order{
|
||||||
|
ID: cast.ToString(o.ID),
|
||||||
|
Status: string(o.Status), // Need cast for DTO string field if DTO field is string
|
||||||
|
Amount: float64(o.AmountPaid) / 100.0,
|
||||||
|
CreateTime: o.CreatedAt.Format(time.RFC3339),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
125
backend/app/services/order_test.go
Normal file
125
backend/app/services/order_test.go
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
package services
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"quyun/v2/app/commands/testx"
|
||||||
|
order_dto "quyun/v2/app/http/v1/dto"
|
||||||
|
"quyun/v2/database"
|
||||||
|
"quyun/v2/database/models"
|
||||||
|
"quyun/v2/pkg/consts"
|
||||||
|
|
||||||
|
. "github.com/smartystreets/goconvey/convey"
|
||||||
|
"github.com/spf13/cast"
|
||||||
|
"github.com/stretchr/testify/suite"
|
||||||
|
"go.ipao.vip/atom/contracts"
|
||||||
|
"go.uber.org/dig"
|
||||||
|
)
|
||||||
|
|
||||||
|
type OrderTestSuiteInjectParams struct {
|
||||||
|
dig.In
|
||||||
|
|
||||||
|
DB *sql.DB
|
||||||
|
Initials []contracts.Initial `group:"initials"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type OrderTestSuite struct {
|
||||||
|
suite.Suite
|
||||||
|
OrderTestSuiteInjectParams
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_Order(t *testing.T) {
|
||||||
|
providers := testx.Default().With(Provide)
|
||||||
|
|
||||||
|
testx.Serve(providers, t, func(p OrderTestSuiteInjectParams) {
|
||||||
|
suite.Run(t, &OrderTestSuite{OrderTestSuiteInjectParams: p})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *OrderTestSuite) Test_PurchaseFlow() {
|
||||||
|
Convey("Purchase Flow", s.T(), func() {
|
||||||
|
ctx := s.T().Context()
|
||||||
|
database.Truncate(ctx, s.DB,
|
||||||
|
models.TableNameOrder, models.TableNameOrderItem, models.TableNameUser,
|
||||||
|
models.TableNameContent, models.TableNameContentPrice, models.TableNameTenant,
|
||||||
|
models.TableNameContentAccess, models.TableNameTenantLedger,
|
||||||
|
)
|
||||||
|
|
||||||
|
// 1. Setup Data
|
||||||
|
// Creator
|
||||||
|
creator := &models.User{Username: "creator", Phone: "13800000001"}
|
||||||
|
models.UserQuery.WithContext(ctx).Create(creator)
|
||||||
|
// Tenant
|
||||||
|
tenant := &models.Tenant{UserID: creator.ID, Name: "Music Shop", Code: "shop1", Status: consts.TenantStatusVerified}
|
||||||
|
models.TenantQuery.WithContext(ctx).Create(tenant)
|
||||||
|
// Content
|
||||||
|
content := &models.Content{TenantID: tenant.ID, UserID: creator.ID, Title: "Song A", Status: consts.ContentStatusPublished}
|
||||||
|
models.ContentQuery.WithContext(ctx).Create(content)
|
||||||
|
// Price (10.00 CNY = 1000 cents)
|
||||||
|
price := &models.ContentPrice{TenantID: tenant.ID, ContentID: content.ID, PriceAmount: 1000, Currency: consts.CurrencyCNY}
|
||||||
|
models.ContentPriceQuery.WithContext(ctx).Create(price)
|
||||||
|
|
||||||
|
// Buyer
|
||||||
|
buyer := &models.User{Username: "buyer", Phone: "13900000001", Balance: 2000} // Has 20.00
|
||||||
|
models.UserQuery.WithContext(ctx).Create(buyer)
|
||||||
|
|
||||||
|
buyerCtx := context.WithValue(ctx, consts.CtxKeyUser, buyer.ID)
|
||||||
|
|
||||||
|
Convey("should create and pay order successfully", func() {
|
||||||
|
// Step 1: Create Order
|
||||||
|
form := &order_dto.OrderCreateForm{ContentID: cast.ToString(content.ID)}
|
||||||
|
createRes, err := Order.Create(buyerCtx, form)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(createRes.OrderID, ShouldNotBeEmpty)
|
||||||
|
|
||||||
|
// Verify created status
|
||||||
|
oid := cast.ToInt64(createRes.OrderID)
|
||||||
|
o, _ := models.OrderQuery.WithContext(ctx).Where(models.OrderQuery.ID.Eq(oid)).First()
|
||||||
|
So(o.Status, ShouldEqual, consts.OrderStatusCreated)
|
||||||
|
So(o.AmountPaid, ShouldEqual, 1000)
|
||||||
|
|
||||||
|
// Step 2: Pay Order
|
||||||
|
payForm := &order_dto.OrderPayForm{Method: "balance"}
|
||||||
|
_, err = Order.Pay(buyerCtx, createRes.OrderID, payForm)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
// Verify Order Paid
|
||||||
|
o, _ = models.OrderQuery.WithContext(ctx).Where(models.OrderQuery.ID.Eq(oid)).First()
|
||||||
|
So(o.Status, ShouldEqual, consts.OrderStatusPaid)
|
||||||
|
So(o.PaidAt, ShouldNotBeZeroValue)
|
||||||
|
|
||||||
|
// Verify Balance Deducted
|
||||||
|
b, _ := models.UserQuery.WithContext(ctx).Where(models.UserQuery.ID.Eq(buyer.ID)).First()
|
||||||
|
So(b.Balance, ShouldEqual, 1000) // 2000 - 1000
|
||||||
|
|
||||||
|
// Verify Access Granted
|
||||||
|
access, _ := models.ContentAccessQuery.WithContext(ctx).Where(models.ContentAccessQuery.UserID.Eq(buyer.ID), models.ContentAccessQuery.ContentID.Eq(content.ID)).First()
|
||||||
|
So(access, ShouldNotBeNil)
|
||||||
|
So(access.Status, ShouldEqual, consts.ContentAccessStatusActive)
|
||||||
|
|
||||||
|
// Verify Ledger Created (Creator received money logic?)
|
||||||
|
// Note: My implementation credits the TENANT OWNER (creator.ID).
|
||||||
|
l, _ := models.TenantLedgerQuery.WithContext(ctx).Where(models.TenantLedgerQuery.OrderID.Eq(o.ID)).First()
|
||||||
|
So(l, ShouldNotBeNil)
|
||||||
|
So(l.UserID, ShouldEqual, creator.ID)
|
||||||
|
So(l.Amount, ShouldEqual, 1000)
|
||||||
|
So(l.Type, ShouldEqual, consts.TenantLedgerTypeDebitPurchase)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("should fail pay if insufficient balance", func() {
|
||||||
|
// Set balance to 5.00
|
||||||
|
models.UserQuery.WithContext(ctx).Where(models.UserQuery.ID.Eq(buyer.ID)).Update(models.UserQuery.Balance, 500)
|
||||||
|
|
||||||
|
form := &order_dto.OrderCreateForm{ContentID: cast.ToString(content.ID)}
|
||||||
|
createRes, err := Order.Create(buyerCtx, form)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
payForm := &order_dto.OrderPayForm{Method: "balance"}
|
||||||
|
_, err = Order.Pay(buyerCtx, createRes.OrderID, payForm)
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
|
// Error should be QuotaExceeded or similar
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -2,15 +2,24 @@ package services
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"quyun/v2/app/errorx"
|
||||||
super_dto "quyun/v2/app/http/v1/dto"
|
super_dto "quyun/v2/app/http/v1/dto"
|
||||||
"quyun/v2/app/requests"
|
"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"
|
||||||
)
|
)
|
||||||
|
|
||||||
// @provider
|
// @provider
|
||||||
type super struct{}
|
type super struct{}
|
||||||
|
|
||||||
func (s *super) Login(ctx context.Context, form *super_dto.LoginForm) (*super_dto.LoginResponse, error) {
|
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
|
return &super_dto.LoginResponse{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -19,51 +28,236 @@ func (s *super) CheckToken(ctx context.Context) (*super_dto.LoginResponse, error
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *super) ListUsers(ctx context.Context, page, limit int, username string) (*requests.Pager, error) {
|
func (s *super) ListUsers(ctx context.Context, page, limit int, username string) (*requests.Pager, error) {
|
||||||
return &requests.Pager{}, nil
|
tbl, q := models.UserQuery.QueryContext(ctx)
|
||||||
|
if username != "" {
|
||||||
|
q = q.Where(tbl.Username.Like("%" + username + "%")).Or(tbl.Nickname.Like("%" + username + "%"))
|
||||||
|
}
|
||||||
|
|
||||||
|
p := requests.Pagination{Page: int64(page), Limit: int64(limit)}
|
||||||
|
total, err := q.Count()
|
||||||
|
if err != nil {
|
||||||
|
return nil, errorx.ErrDatabaseError
|
||||||
|
}
|
||||||
|
|
||||||
|
list, err := q.Offset(int(p.Offset())).Limit(int(p.Limit)).Order(tbl.ID.Desc()).Find()
|
||||||
|
if err != nil {
|
||||||
|
return nil, errorx.ErrDatabaseError
|
||||||
|
}
|
||||||
|
|
||||||
|
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(), // Status is consts.UserStatus, it has Description()
|
||||||
|
// But u.Status might be string if gen didn't map it properly? No, it's consts.UserStatus.
|
||||||
|
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: p,
|
||||||
|
Total: total,
|
||||||
|
Items: data,
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *super) GetUser(ctx context.Context, id int64) (*super_dto.UserItem, error) {
|
func (s *super) GetUser(ctx context.Context, id int64) (*super_dto.UserItem, error) {
|
||||||
return &super_dto.UserItem{}, nil
|
tbl, q := models.UserQuery.QueryContext(ctx)
|
||||||
|
u, err := q.Where(tbl.ID.Eq(id)).First()
|
||||||
|
if err != nil {
|
||||||
|
return nil, errorx.ErrRecordNotFound
|
||||||
|
}
|
||||||
|
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 {
|
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
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *super) UpdateUserRoles(ctx context.Context, id int64, form *super_dto.UserRolesUpdateForm) error {
|
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
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *super) ListTenants(ctx context.Context, page, limit int, name string) (*requests.Pager, error) {
|
func (s *super) ListTenants(ctx context.Context, page, limit int, name string) (*requests.Pager, error) {
|
||||||
return &requests.Pager{}, nil
|
tbl, q := models.TenantQuery.QueryContext(ctx)
|
||||||
|
if name != "" {
|
||||||
|
q = q.Where(tbl.Name.Like("%" + name + "%"))
|
||||||
|
}
|
||||||
|
|
||||||
|
p := requests.Pagination{Page: int64(page), Limit: int64(limit)}
|
||||||
|
total, err := q.Count()
|
||||||
|
if err != nil {
|
||||||
|
return nil, errorx.ErrDatabaseError
|
||||||
|
}
|
||||||
|
|
||||||
|
list, err := q.Offset(int(p.Offset())).Limit(int(p.Limit)).Order(tbl.ID.Desc()).Find()
|
||||||
|
if err != nil {
|
||||||
|
return nil, errorx.ErrDatabaseError
|
||||||
|
}
|
||||||
|
|
||||||
|
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: p,
|
||||||
|
Total: total,
|
||||||
|
Items: data,
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *super) CreateTenant(ctx context.Context, form *super_dto.TenantCreateForm) error {
|
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
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *super) GetTenant(ctx context.Context, id int64) (*super_dto.TenantItem, error) {
|
func (s *super) GetTenant(ctx context.Context, id int64) (*super_dto.TenantItem, error) {
|
||||||
return &super_dto.TenantItem{}, nil
|
tbl, q := models.TenantQuery.QueryContext(ctx)
|
||||||
|
t, err := q.Where(tbl.ID.Eq(id)).First()
|
||||||
|
if err != nil {
|
||||||
|
return nil, errorx.ErrRecordNotFound
|
||||||
|
}
|
||||||
|
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 {
|
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
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *super) UpdateTenantExpire(ctx context.Context, id int64, form *super_dto.TenantExpireUpdateForm) error {
|
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
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *super) ListContents(ctx context.Context, page, limit int) (*requests.Pager, error) {
|
func (s *super) ListContents(ctx context.Context, page, limit int) (*requests.Pager, error) {
|
||||||
return &requests.Pager{}, nil
|
tbl, q := models.ContentQuery.QueryContext(ctx)
|
||||||
|
p := requests.Pagination{Page: int64(page), Limit: int64(limit)}
|
||||||
|
total, err := q.Count()
|
||||||
|
if err != nil {
|
||||||
|
return nil, errorx.ErrDatabaseError
|
||||||
|
}
|
||||||
|
list, err := q.Offset(int(p.Offset())).Limit(int(p.Limit)).Order(tbl.ID.Desc()).Find()
|
||||||
|
if err != nil {
|
||||||
|
return nil, errorx.ErrDatabaseError
|
||||||
|
}
|
||||||
|
// Simplified DTO for list
|
||||||
|
var data []any
|
||||||
|
for _, c := range list {
|
||||||
|
data = append(data, c) // TODO: Map to DTO
|
||||||
|
}
|
||||||
|
return &requests.Pager{
|
||||||
|
Pagination: p,
|
||||||
|
Total: total,
|
||||||
|
Items: data,
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *super) UpdateContentStatus(ctx context.Context, tenantID, contentID int64, form *super_dto.SuperTenantContentStatusUpdateForm) error {
|
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
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *super) ListOrders(ctx context.Context, page, limit int) (*requests.Pager, error) {
|
func (s *super) ListOrders(ctx context.Context, page, limit int) (*requests.Pager, error) {
|
||||||
return &requests.Pager{}, nil
|
tbl, q := models.OrderQuery.QueryContext(ctx)
|
||||||
|
p := requests.Pagination{Page: int64(page), Limit: int64(limit)}
|
||||||
|
total, err := q.Count()
|
||||||
|
if err != nil {
|
||||||
|
return nil, errorx.ErrDatabaseError
|
||||||
|
}
|
||||||
|
list, err := q.Offset(int(p.Offset())).Limit(int(p.Limit)).Order(tbl.ID.Desc()).Find()
|
||||||
|
if err != nil {
|
||||||
|
return nil, errorx.ErrDatabaseError
|
||||||
|
}
|
||||||
|
// TODO: Map to DTO
|
||||||
|
return &requests.Pager{
|
||||||
|
Pagination: p,
|
||||||
|
Total: total,
|
||||||
|
Items: list,
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *super) GetOrder(ctx context.Context, id int64) (*super_dto.SuperOrderDetail, error) {
|
func (s *super) GetOrder(ctx context.Context, id int64) (*super_dto.SuperOrderDetail, error) {
|
||||||
@@ -83,9 +277,9 @@ func (s *super) UserStatistics(ctx context.Context) ([]super_dto.UserStatistics,
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *super) UserStatuses(ctx context.Context) ([]requests.KV, error) {
|
func (s *super) UserStatuses(ctx context.Context) ([]requests.KV, error) {
|
||||||
return []requests.KV{}, nil
|
return consts.UserStatusItems(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *super) TenantStatuses(ctx context.Context) ([]requests.KV, error) {
|
func (s *super) TenantStatuses(ctx context.Context) ([]requests.KV, error) {
|
||||||
return []requests.KV{}, nil
|
return consts.TenantStatusItems(), nil
|
||||||
}
|
}
|
||||||
91
backend/app/services/super_test.go
Normal file
91
backend/app/services/super_test.go
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
package services
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"quyun/v2/app/commands/testx"
|
||||||
|
super_dto "quyun/v2/app/http/v1/dto"
|
||||||
|
"quyun/v2/database"
|
||||||
|
"quyun/v2/database/models"
|
||||||
|
"quyun/v2/pkg/consts"
|
||||||
|
|
||||||
|
. "github.com/smartystreets/goconvey/convey"
|
||||||
|
"github.com/stretchr/testify/suite"
|
||||||
|
"go.ipao.vip/atom/contracts"
|
||||||
|
"go.uber.org/dig"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SuperTestSuiteInjectParams struct {
|
||||||
|
dig.In
|
||||||
|
|
||||||
|
DB *sql.DB
|
||||||
|
Initials []contracts.Initial `group:"initials"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SuperTestSuite struct {
|
||||||
|
suite.Suite
|
||||||
|
SuperTestSuiteInjectParams
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_Super(t *testing.T) {
|
||||||
|
providers := testx.Default().With(Provide)
|
||||||
|
|
||||||
|
testx.Serve(providers, t, func(p SuperTestSuiteInjectParams) {
|
||||||
|
suite.Run(t, &SuperTestSuite{SuperTestSuiteInjectParams: p})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SuperTestSuite) Test_ListUsers() {
|
||||||
|
Convey("ListUsers", s.T(), func() {
|
||||||
|
ctx := s.T().Context()
|
||||||
|
database.Truncate(ctx, s.DB, models.TableNameUser)
|
||||||
|
|
||||||
|
u1 := &models.User{Username: "user1", Nickname: "Alice"}
|
||||||
|
u2 := &models.User{Username: "user2", Nickname: "Bob"}
|
||||||
|
models.UserQuery.WithContext(ctx).Create(u1, u2)
|
||||||
|
|
||||||
|
Convey("should list users", func() {
|
||||||
|
res, err := Super.ListUsers(ctx, 1, 10, "")
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(res.Total, ShouldEqual, 2)
|
||||||
|
|
||||||
|
items := res.Items.([]super_dto.UserItem)
|
||||||
|
So(items[0].Username, ShouldEqual, "user2") // Desc order
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("should filter users", func() {
|
||||||
|
res, err := Super.ListUsers(ctx, 1, 10, "Alice")
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(res.Total, ShouldEqual, 1)
|
||||||
|
items := res.Items.([]super_dto.UserItem)
|
||||||
|
So(items[0].Username, ShouldEqual, "user1")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SuperTestSuite) Test_CreateTenant() {
|
||||||
|
Convey("CreateTenant", s.T(), func() {
|
||||||
|
ctx := s.T().Context()
|
||||||
|
database.Truncate(ctx, s.DB, models.TableNameUser, models.TableNameTenant)
|
||||||
|
|
||||||
|
u := &models.User{Username: "admin1"}
|
||||||
|
models.UserQuery.WithContext(ctx).Create(u)
|
||||||
|
|
||||||
|
Convey("should create tenant", func() {
|
||||||
|
form := &super_dto.TenantCreateForm{
|
||||||
|
Name: "Super Tenant",
|
||||||
|
Code: "st1",
|
||||||
|
AdminUserID: u.ID,
|
||||||
|
}
|
||||||
|
err := Super.CreateTenant(ctx, form)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
t, _ := models.TenantQuery.WithContext(ctx).Where(models.TenantQuery.Code.Eq("st1")).First()
|
||||||
|
So(t, ShouldNotBeNil)
|
||||||
|
So(t.Name, ShouldEqual, "Super Tenant")
|
||||||
|
So(t.UserID, ShouldEqual, u.ID)
|
||||||
|
So(t.Status, ShouldEqual, consts.TenantStatusVerified)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -2,21 +2,126 @@ package services
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"quyun/v2/app/errorx"
|
||||||
tenant_dto "quyun/v2/app/http/v1/dto"
|
tenant_dto "quyun/v2/app/http/v1/dto"
|
||||||
|
"quyun/v2/database/models"
|
||||||
|
"quyun/v2/pkg/consts"
|
||||||
|
|
||||||
|
"github.com/spf13/cast"
|
||||||
|
"go.ipao.vip/gen/types"
|
||||||
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
// @provider
|
// @provider
|
||||||
type tenant struct{}
|
type tenant struct{}
|
||||||
|
|
||||||
func (s *tenant) GetPublicProfile(ctx context.Context, id string) (*tenant_dto.TenantProfile, error) {
|
func (s *tenant) GetPublicProfile(ctx context.Context, id string) (*tenant_dto.TenantProfile, error) {
|
||||||
return &tenant_dto.TenantProfile{}, nil
|
// id could be Code or ID. Try Code first, then ID.
|
||||||
|
tbl, q := models.TenantQuery.QueryContext(ctx)
|
||||||
|
|
||||||
|
// Try to find by code or ID
|
||||||
|
var t *models.Tenant
|
||||||
|
var err error
|
||||||
|
|
||||||
|
// Assume id is ID for simplicity if numeric, or try both.
|
||||||
|
if cast.ToInt64(id) > 0 {
|
||||||
|
t, err = q.Where(tbl.ID.Eq(cast.ToInt64(id))).First()
|
||||||
|
} else {
|
||||||
|
t, err = q.Where(tbl.Code.Eq(id)).First()
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
return nil, errorx.ErrRecordNotFound
|
||||||
|
}
|
||||||
|
return nil, errorx.ErrDatabaseError
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stats
|
||||||
|
// Followers
|
||||||
|
followers, _ := models.TenantUserQuery.WithContext(ctx).Where(models.TenantUserQuery.TenantID.Eq(t.ID)).Count()
|
||||||
|
// Contents
|
||||||
|
contentsCount, _ := models.ContentQuery.WithContext(ctx).Where(models.ContentQuery.TenantID.Eq(t.ID), models.ContentQuery.Status.Eq(consts.ContentStatusPublished)).Count()
|
||||||
|
// Likes
|
||||||
|
var likes int64
|
||||||
|
// Sum content likes
|
||||||
|
// Mock likes for now or fetch
|
||||||
|
|
||||||
|
// IsFollowing
|
||||||
|
isFollowing := false
|
||||||
|
userID := ctx.Value(consts.CtxKeyUser)
|
||||||
|
if userID != nil {
|
||||||
|
uid := cast.ToInt64(userID)
|
||||||
|
count, _ := models.TenantUserQuery.WithContext(ctx).Where(models.TenantUserQuery.TenantID.Eq(t.ID), models.TenantUserQuery.UserID.Eq(uid)).Count()
|
||||||
|
isFollowing = count > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Config parsing (Unused for now as we don't map to bio yet)
|
||||||
|
// config := t.Config.Data()
|
||||||
|
|
||||||
|
return &tenant_dto.TenantProfile{
|
||||||
|
ID: cast.ToString(t.ID),
|
||||||
|
Name: t.Name,
|
||||||
|
Avatar: "", // From config
|
||||||
|
Cover: "", // From config
|
||||||
|
Bio: "", // From config
|
||||||
|
Description: "", // From config
|
||||||
|
CertType: "personal", // Mock
|
||||||
|
Stats: tenant_dto.Stats{
|
||||||
|
Followers: int(followers),
|
||||||
|
Contents: int(contentsCount),
|
||||||
|
Likes: int(likes),
|
||||||
|
},
|
||||||
|
IsFollowing: isFollowing,
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *tenant) Follow(ctx context.Context, id string) error {
|
func (s *tenant) Follow(ctx context.Context, id string) error {
|
||||||
|
userID := ctx.Value(consts.CtxKeyUser)
|
||||||
|
if userID == nil {
|
||||||
|
return errorx.ErrUnauthorized
|
||||||
|
}
|
||||||
|
uid := cast.ToInt64(userID)
|
||||||
|
tid := cast.ToInt64(id)
|
||||||
|
|
||||||
|
// Check if tenant exists
|
||||||
|
_, err := models.TenantQuery.WithContext(ctx).Where(models.TenantQuery.ID.Eq(tid)).First()
|
||||||
|
if err != nil {
|
||||||
|
return errorx.ErrRecordNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add to tenant_users
|
||||||
|
tu := &models.TenantUser{
|
||||||
|
TenantID: tid,
|
||||||
|
UserID: uid,
|
||||||
|
Role: types.Array[consts.TenantUserRole]{consts.TenantUserRoleMember},
|
||||||
|
Status: consts.UserStatusVerified,
|
||||||
|
}
|
||||||
|
|
||||||
|
count, _ := models.TenantUserQuery.WithContext(ctx).Where(models.TenantUserQuery.TenantID.Eq(tid), models.TenantUserQuery.UserID.Eq(uid)).Count()
|
||||||
|
if count > 0 {
|
||||||
|
return nil // Already following
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := models.TenantUserQuery.WithContext(ctx).Create(tu); err != nil {
|
||||||
|
return errorx.ErrDatabaseError
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *tenant) Unfollow(ctx context.Context, id string) error {
|
func (s *tenant) Unfollow(ctx context.Context, id string) error {
|
||||||
|
userID := ctx.Value(consts.CtxKeyUser)
|
||||||
|
if userID == nil {
|
||||||
|
return errorx.ErrUnauthorized
|
||||||
|
}
|
||||||
|
uid := cast.ToInt64(userID)
|
||||||
|
tid := cast.ToInt64(id)
|
||||||
|
|
||||||
|
_, err := models.TenantUserQuery.WithContext(ctx).Where(models.TenantUserQuery.TenantID.Eq(tid), models.TenantUserQuery.UserID.Eq(uid)).Delete()
|
||||||
|
if err != nil {
|
||||||
|
return errorx.ErrDatabaseError
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,17 +2,110 @@ package services
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"quyun/v2/app/errorx"
|
||||||
user_dto "quyun/v2/app/http/v1/dto"
|
user_dto "quyun/v2/app/http/v1/dto"
|
||||||
|
"quyun/v2/database/fields"
|
||||||
|
"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
|
// @provider
|
||||||
type wallet struct{}
|
type wallet struct{}
|
||||||
|
|
||||||
func (s *wallet) GetWallet(ctx context.Context) (*user_dto.WalletResponse, error) {
|
func (s *wallet) GetWallet(ctx context.Context) (*user_dto.WalletResponse, error) {
|
||||||
return &user_dto.WalletResponse{}, nil
|
userID := ctx.Value(consts.CtxKeyUser)
|
||||||
|
if userID == nil {
|
||||||
|
return nil, errorx.ErrUnauthorized
|
||||||
|
}
|
||||||
|
uid := cast.ToInt64(userID)
|
||||||
|
|
||||||
|
// Get Balance
|
||||||
|
u, err := models.UserQuery.WithContext(ctx).Where(models.UserQuery.ID.Eq(uid)).First()
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
return nil, errorx.ErrRecordNotFound
|
||||||
|
}
|
||||||
|
return nil, errorx.ErrDatabaseError
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get Transactions (Orders)
|
||||||
|
// Both purchase (expense) and recharge (income - if paid)
|
||||||
|
tbl, q := models.OrderQuery.QueryContext(ctx)
|
||||||
|
orders, err := q.Where(tbl.UserID.Eq(uid), tbl.Status.Eq(consts.OrderStatusPaid)).
|
||||||
|
Order(tbl.CreatedAt.Desc()).
|
||||||
|
Limit(20). // Limit to recent 20
|
||||||
|
Find()
|
||||||
|
if err != nil {
|
||||||
|
return nil, errorx.ErrDatabaseError
|
||||||
|
}
|
||||||
|
|
||||||
|
var txs []user_dto.Transaction
|
||||||
|
for _, o := range orders {
|
||||||
|
var txType string
|
||||||
|
var title string
|
||||||
|
if o.Type == consts.OrderTypeContentPurchase {
|
||||||
|
txType = "expense"
|
||||||
|
title = "购买内容"
|
||||||
|
} else if o.Type == consts.OrderTypeRecharge {
|
||||||
|
txType = "income"
|
||||||
|
title = "钱包充值"
|
||||||
|
}
|
||||||
|
|
||||||
|
txs = append(txs, user_dto.Transaction{
|
||||||
|
ID: cast.ToString(o.ID),
|
||||||
|
Title: title,
|
||||||
|
Amount: float64(o.AmountPaid) / 100.0,
|
||||||
|
Type: txType,
|
||||||
|
Date: o.CreatedAt.Format(time.RFC3339),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return &user_dto.WalletResponse{
|
||||||
|
Balance: float64(u.Balance) / 100.0,
|
||||||
|
Transactions: txs,
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *wallet) Recharge(ctx context.Context, form *user_dto.RechargeForm) (*user_dto.RechargeResponse, error) {
|
func (s *wallet) Recharge(ctx context.Context, form *user_dto.RechargeForm) (*user_dto.RechargeResponse, error) {
|
||||||
return &user_dto.RechargeResponse{}, nil
|
userID := ctx.Value(consts.CtxKeyUser)
|
||||||
|
if userID == nil {
|
||||||
|
return nil, errorx.ErrUnauthorized
|
||||||
|
}
|
||||||
|
uid := cast.ToInt64(userID)
|
||||||
|
|
||||||
|
amount := int64(form.Amount * 100)
|
||||||
|
if amount <= 0 {
|
||||||
|
return nil, errorx.ErrBadRequest.WithMsg("金额无效")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create Recharge Order
|
||||||
|
order := &models.Order{
|
||||||
|
TenantID: 0, // Platform / System
|
||||||
|
UserID: uid,
|
||||||
|
Type: consts.OrderTypeRecharge,
|
||||||
|
Status: consts.OrderStatusCreated,
|
||||||
|
Currency: consts.CurrencyCNY,
|
||||||
|
AmountOriginal: amount,
|
||||||
|
AmountPaid: amount,
|
||||||
|
IdempotencyKey: uuid.NewString(),
|
||||||
|
Snapshot: types.NewJSONType(fields.OrdersSnapshot{}),
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := models.OrderQuery.WithContext(ctx).Create(order); err != nil {
|
||||||
|
return nil, errorx.ErrDatabaseError
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mock Pay Params
|
||||||
|
return &user_dto.RechargeResponse{
|
||||||
|
PayParams: "mock_recharge_url",
|
||||||
|
OrderID: cast.ToString(order.ID),
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|||||||
96
backend/app/services/wallet_test.go
Normal file
96
backend/app/services/wallet_test.go
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
package services
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"quyun/v2/app/commands/testx"
|
||||||
|
user_dto "quyun/v2/app/http/v1/dto"
|
||||||
|
"quyun/v2/database"
|
||||||
|
"quyun/v2/database/models"
|
||||||
|
"quyun/v2/pkg/consts"
|
||||||
|
|
||||||
|
. "github.com/smartystreets/goconvey/convey"
|
||||||
|
"github.com/stretchr/testify/suite"
|
||||||
|
"go.ipao.vip/atom/contracts"
|
||||||
|
"go.uber.org/dig"
|
||||||
|
)
|
||||||
|
|
||||||
|
type WalletTestSuiteInjectParams struct {
|
||||||
|
dig.In
|
||||||
|
|
||||||
|
DB *sql.DB
|
||||||
|
Initials []contracts.Initial `group:"initials"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type WalletTestSuite struct {
|
||||||
|
suite.Suite
|
||||||
|
WalletTestSuiteInjectParams
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_Wallet(t *testing.T) {
|
||||||
|
providers := testx.Default().With(Provide)
|
||||||
|
|
||||||
|
testx.Serve(providers, t, func(p WalletTestSuiteInjectParams) {
|
||||||
|
suite.Run(t, &WalletTestSuite{WalletTestSuiteInjectParams: p})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *WalletTestSuite) Test_GetWallet() {
|
||||||
|
Convey("GetWallet", s.T(), func() {
|
||||||
|
ctx := s.T().Context()
|
||||||
|
database.Truncate(ctx, s.DB, models.TableNameUser, models.TableNameOrder)
|
||||||
|
|
||||||
|
u := &models.User{Username: "wallet_user", Balance: 5000} // 50.00
|
||||||
|
models.UserQuery.WithContext(ctx).Create(u)
|
||||||
|
ctx = context.WithValue(ctx, consts.CtxKeyUser, u.ID)
|
||||||
|
|
||||||
|
// Create Orders
|
||||||
|
o1 := &models.Order{
|
||||||
|
TenantID: 0, UserID: u.ID, Type: consts.OrderTypeRecharge, Status: consts.OrderStatusPaid,
|
||||||
|
AmountPaid: 5000,
|
||||||
|
}
|
||||||
|
o2 := &models.Order{
|
||||||
|
TenantID: 1, UserID: u.ID, Type: consts.OrderTypeContentPurchase, Status: consts.OrderStatusPaid,
|
||||||
|
AmountPaid: 1000,
|
||||||
|
}
|
||||||
|
models.OrderQuery.WithContext(ctx).Create(o1, o2)
|
||||||
|
|
||||||
|
Convey("should return balance and transactions", func() {
|
||||||
|
res, err := Wallet.GetWallet(ctx)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(res.Balance, ShouldEqual, 50.0)
|
||||||
|
So(len(res.Transactions), ShouldEqual, 2)
|
||||||
|
|
||||||
|
// Order by CreatedAt Desc
|
||||||
|
types := []string{res.Transactions[0].Type, res.Transactions[1].Type}
|
||||||
|
So(types, ShouldContain, "income")
|
||||||
|
So(types, ShouldContain, "expense")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *WalletTestSuite) Test_Recharge() {
|
||||||
|
Convey("Recharge", s.T(), func() {
|
||||||
|
ctx := s.T().Context()
|
||||||
|
database.Truncate(ctx, s.DB, models.TableNameUser, models.TableNameOrder)
|
||||||
|
|
||||||
|
u := &models.User{Username: "recharge_user"}
|
||||||
|
models.UserQuery.WithContext(ctx).Create(u)
|
||||||
|
ctx = context.WithValue(ctx, consts.CtxKeyUser, u.ID)
|
||||||
|
|
||||||
|
Convey("should create recharge order", func() {
|
||||||
|
form := &user_dto.RechargeForm{Amount: 100.0}
|
||||||
|
res, err := Wallet.Recharge(ctx, form)
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(res.OrderID, ShouldNotBeEmpty)
|
||||||
|
|
||||||
|
// Verify order
|
||||||
|
o, _ := models.OrderQuery.WithContext(ctx).Where(models.OrderQuery.Type.Eq(consts.OrderTypeRecharge)).First()
|
||||||
|
So(o, ShouldNotBeNil)
|
||||||
|
So(o.AmountPaid, ShouldEqual, 10000)
|
||||||
|
So(o.TenantID, ShouldEqual, 0)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -27,6 +27,7 @@ field_type:
|
|||||||
orders:
|
orders:
|
||||||
status: consts.OrderStatus
|
status: consts.OrderStatus
|
||||||
type: consts.OrderType
|
type: consts.OrderType
|
||||||
|
currency: consts.Currency
|
||||||
snapshot: types.JSONType[fields.OrdersSnapshot]
|
snapshot: types.JSONType[fields.OrdersSnapshot]
|
||||||
order_items:
|
order_items:
|
||||||
snapshot: types.JSONType[fields.OrderItemsSnapshot]
|
snapshot: types.JSONType[fields.OrderItemsSnapshot]
|
||||||
@@ -35,9 +36,49 @@ field_type:
|
|||||||
config: types.JSONType[fields.TenantConfig]
|
config: types.JSONType[fields.TenantConfig]
|
||||||
tenant_users:
|
tenant_users:
|
||||||
role: types.Array[consts.TenantUserRole]
|
role: types.Array[consts.TenantUserRole]
|
||||||
|
status: consts.UserStatus
|
||||||
|
content_assets:
|
||||||
|
role: consts.ContentAssetRole
|
||||||
media_assets:
|
media_assets:
|
||||||
meta: types.JSONType[fields.MediaAssetMeta]
|
meta: types.JSONType[fields.MediaAssetMeta]
|
||||||
type: consts.MediaAssetType
|
type: consts.MediaAssetType
|
||||||
status: consts.MediaAssetStatus
|
status: consts.MediaAssetStatus
|
||||||
variant: consts.MediaAssetVariant
|
variant: consts.MediaAssetVariant
|
||||||
|
content_access:
|
||||||
|
status: consts.ContentAccessStatus
|
||||||
|
tenant_ledgers:
|
||||||
|
type: consts.TenantLedgerType
|
||||||
field_relate:
|
field_relate:
|
||||||
|
contents:
|
||||||
|
Author:
|
||||||
|
relation: belongs_to
|
||||||
|
table: users
|
||||||
|
foreign_key: user_id
|
||||||
|
references: id
|
||||||
|
json: author
|
||||||
|
ContentAssets:
|
||||||
|
relation: has_many
|
||||||
|
table: content_assets
|
||||||
|
foreign_key: content_id
|
||||||
|
references: id
|
||||||
|
json: content_assets
|
||||||
|
Comments:
|
||||||
|
relation: has_many
|
||||||
|
table: comments
|
||||||
|
foreign_key: content_id
|
||||||
|
references: id
|
||||||
|
json: comments
|
||||||
|
comments:
|
||||||
|
User:
|
||||||
|
relation: belongs_to
|
||||||
|
table: users
|
||||||
|
foreign_key: user_id
|
||||||
|
references: id
|
||||||
|
json: user
|
||||||
|
content_assets:
|
||||||
|
Asset:
|
||||||
|
relation: belongs_to
|
||||||
|
table: media_assets
|
||||||
|
foreign_key: asset_id
|
||||||
|
references: id
|
||||||
|
json: asset
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ type Comment struct {
|
|||||||
CreatedAt time.Time `gorm:"column:created_at;type:timestamp with time zone;default:now()" json:"created_at"`
|
CreatedAt time.Time `gorm:"column:created_at;type:timestamp with time zone;default:now()" json:"created_at"`
|
||||||
UpdatedAt time.Time `gorm:"column:updated_at;type:timestamp with time zone;default:now()" json:"updated_at"`
|
UpdatedAt time.Time `gorm:"column:updated_at;type:timestamp with time zone;default:now()" json:"updated_at"`
|
||||||
DeletedAt gorm.DeletedAt `gorm:"column:deleted_at;type:timestamp with time zone" json:"deleted_at"`
|
DeletedAt gorm.DeletedAt `gorm:"column:deleted_at;type:timestamp with time zone" json:"deleted_at"`
|
||||||
|
User *User `gorm:"foreignKey:UserID;references:ID" json:"user,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Quick operations without importing query package
|
// Quick operations without importing query package
|
||||||
|
|||||||
@@ -35,6 +35,11 @@ func newComment(db *gorm.DB, opts ...gen.DOOption) commentQuery {
|
|||||||
_commentQuery.CreatedAt = field.NewTime(tableName, "created_at")
|
_commentQuery.CreatedAt = field.NewTime(tableName, "created_at")
|
||||||
_commentQuery.UpdatedAt = field.NewTime(tableName, "updated_at")
|
_commentQuery.UpdatedAt = field.NewTime(tableName, "updated_at")
|
||||||
_commentQuery.DeletedAt = field.NewField(tableName, "deleted_at")
|
_commentQuery.DeletedAt = field.NewField(tableName, "deleted_at")
|
||||||
|
_commentQuery.User = commentQueryBelongsToUser{
|
||||||
|
db: db.Session(&gorm.Session{}),
|
||||||
|
|
||||||
|
RelationField: field.NewRelation("User", "User"),
|
||||||
|
}
|
||||||
|
|
||||||
_commentQuery.fillFieldMap()
|
_commentQuery.fillFieldMap()
|
||||||
|
|
||||||
@@ -55,6 +60,7 @@ type commentQuery struct {
|
|||||||
CreatedAt field.Time
|
CreatedAt field.Time
|
||||||
UpdatedAt field.Time
|
UpdatedAt field.Time
|
||||||
DeletedAt field.Field
|
DeletedAt field.Field
|
||||||
|
User commentQueryBelongsToUser
|
||||||
|
|
||||||
fieldMap map[string]field.Expr
|
fieldMap map[string]field.Expr
|
||||||
}
|
}
|
||||||
@@ -113,7 +119,7 @@ func (c *commentQuery) GetFieldByName(fieldName string) (field.OrderExpr, bool)
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *commentQuery) fillFieldMap() {
|
func (c *commentQuery) fillFieldMap() {
|
||||||
c.fieldMap = make(map[string]field.Expr, 10)
|
c.fieldMap = make(map[string]field.Expr, 11)
|
||||||
c.fieldMap["id"] = c.ID
|
c.fieldMap["id"] = c.ID
|
||||||
c.fieldMap["tenant_id"] = c.TenantID
|
c.fieldMap["tenant_id"] = c.TenantID
|
||||||
c.fieldMap["user_id"] = c.UserID
|
c.fieldMap["user_id"] = c.UserID
|
||||||
@@ -124,18 +130,103 @@ func (c *commentQuery) fillFieldMap() {
|
|||||||
c.fieldMap["created_at"] = c.CreatedAt
|
c.fieldMap["created_at"] = c.CreatedAt
|
||||||
c.fieldMap["updated_at"] = c.UpdatedAt
|
c.fieldMap["updated_at"] = c.UpdatedAt
|
||||||
c.fieldMap["deleted_at"] = c.DeletedAt
|
c.fieldMap["deleted_at"] = c.DeletedAt
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c commentQuery) clone(db *gorm.DB) commentQuery {
|
func (c commentQuery) clone(db *gorm.DB) commentQuery {
|
||||||
c.commentQueryDo.ReplaceConnPool(db.Statement.ConnPool)
|
c.commentQueryDo.ReplaceConnPool(db.Statement.ConnPool)
|
||||||
|
c.User.db = db.Session(&gorm.Session{Initialized: true})
|
||||||
|
c.User.db.Statement.ConnPool = db.Statement.ConnPool
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c commentQuery) replaceDB(db *gorm.DB) commentQuery {
|
func (c commentQuery) replaceDB(db *gorm.DB) commentQuery {
|
||||||
c.commentQueryDo.ReplaceDB(db)
|
c.commentQueryDo.ReplaceDB(db)
|
||||||
|
c.User.db = db.Session(&gorm.Session{})
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type commentQueryBelongsToUser struct {
|
||||||
|
db *gorm.DB
|
||||||
|
|
||||||
|
field.RelationField
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a commentQueryBelongsToUser) Where(conds ...field.Expr) *commentQueryBelongsToUser {
|
||||||
|
if len(conds) == 0 {
|
||||||
|
return &a
|
||||||
|
}
|
||||||
|
|
||||||
|
exprs := make([]clause.Expression, 0, len(conds))
|
||||||
|
for _, cond := range conds {
|
||||||
|
exprs = append(exprs, cond.BeCond().(clause.Expression))
|
||||||
|
}
|
||||||
|
a.db = a.db.Clauses(clause.Where{Exprs: exprs})
|
||||||
|
return &a
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a commentQueryBelongsToUser) WithContext(ctx context.Context) *commentQueryBelongsToUser {
|
||||||
|
a.db = a.db.WithContext(ctx)
|
||||||
|
return &a
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a commentQueryBelongsToUser) Session(session *gorm.Session) *commentQueryBelongsToUser {
|
||||||
|
a.db = a.db.Session(session)
|
||||||
|
return &a
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a commentQueryBelongsToUser) Model(m *Comment) *commentQueryBelongsToUserTx {
|
||||||
|
return &commentQueryBelongsToUserTx{a.db.Model(m).Association(a.Name())}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a commentQueryBelongsToUser) Unscoped() *commentQueryBelongsToUser {
|
||||||
|
a.db = a.db.Unscoped()
|
||||||
|
return &a
|
||||||
|
}
|
||||||
|
|
||||||
|
type commentQueryBelongsToUserTx struct{ tx *gorm.Association }
|
||||||
|
|
||||||
|
func (a commentQueryBelongsToUserTx) Find() (result *User, err error) {
|
||||||
|
return result, a.tx.Find(&result)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a commentQueryBelongsToUserTx) Append(values ...*User) (err error) {
|
||||||
|
targetValues := make([]interface{}, len(values))
|
||||||
|
for i, v := range values {
|
||||||
|
targetValues[i] = v
|
||||||
|
}
|
||||||
|
return a.tx.Append(targetValues...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a commentQueryBelongsToUserTx) Replace(values ...*User) (err error) {
|
||||||
|
targetValues := make([]interface{}, len(values))
|
||||||
|
for i, v := range values {
|
||||||
|
targetValues[i] = v
|
||||||
|
}
|
||||||
|
return a.tx.Replace(targetValues...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a commentQueryBelongsToUserTx) Delete(values ...*User) (err error) {
|
||||||
|
targetValues := make([]interface{}, len(values))
|
||||||
|
for i, v := range values {
|
||||||
|
targetValues[i] = v
|
||||||
|
}
|
||||||
|
return a.tx.Delete(targetValues...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a commentQueryBelongsToUserTx) Clear() error {
|
||||||
|
return a.tx.Clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a commentQueryBelongsToUserTx) Count() int64 {
|
||||||
|
return a.tx.Count()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a commentQueryBelongsToUserTx) Unscoped() *commentQueryBelongsToUserTx {
|
||||||
|
a.tx = a.tx.Unscoped()
|
||||||
|
return &a
|
||||||
|
}
|
||||||
|
|
||||||
type commentQueryDo struct{ gen.DO }
|
type commentQueryDo struct{ gen.DO }
|
||||||
|
|
||||||
func (c commentQueryDo) Debug() *commentQueryDo {
|
func (c commentQueryDo) Debug() *commentQueryDo {
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"quyun/v2/pkg/consts"
|
||||||
|
|
||||||
"go.ipao.vip/gen"
|
"go.ipao.vip/gen"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -15,15 +17,15 @@ const TableNameContentAccess = "content_access"
|
|||||||
|
|
||||||
// ContentAccess mapped from table <content_access>
|
// ContentAccess mapped from table <content_access>
|
||||||
type ContentAccess struct {
|
type ContentAccess struct {
|
||||||
ID int64 `gorm:"column:id;type:bigint;primaryKey;autoIncrement:true" json:"id"`
|
ID int64 `gorm:"column:id;type:bigint;primaryKey;autoIncrement:true" json:"id"`
|
||||||
TenantID int64 `gorm:"column:tenant_id;type:bigint;not null" json:"tenant_id"`
|
TenantID int64 `gorm:"column:tenant_id;type:bigint;not null" json:"tenant_id"`
|
||||||
UserID int64 `gorm:"column:user_id;type:bigint;not null" json:"user_id"`
|
UserID int64 `gorm:"column:user_id;type:bigint;not null" json:"user_id"`
|
||||||
ContentID int64 `gorm:"column:content_id;type:bigint;not null" json:"content_id"`
|
ContentID int64 `gorm:"column:content_id;type:bigint;not null" json:"content_id"`
|
||||||
OrderID int64 `gorm:"column:order_id;type:bigint" json:"order_id"`
|
OrderID int64 `gorm:"column:order_id;type:bigint" json:"order_id"`
|
||||||
Status string `gorm:"column:status;type:character varying(16);default:active" json:"status"`
|
Status consts.ContentAccessStatus `gorm:"column:status;type:character varying(16);default:active" json:"status"`
|
||||||
RevokedAt time.Time `gorm:"column:revoked_at;type:timestamp with time zone" json:"revoked_at"`
|
RevokedAt time.Time `gorm:"column:revoked_at;type:timestamp with time zone" json:"revoked_at"`
|
||||||
CreatedAt time.Time `gorm:"column:created_at;type:timestamp with time zone;default:now()" json:"created_at"`
|
CreatedAt time.Time `gorm:"column:created_at;type:timestamp with time zone;default:now()" json:"created_at"`
|
||||||
UpdatedAt time.Time `gorm:"column:updated_at;type:timestamp with time zone;default:now()" json:"updated_at"`
|
UpdatedAt time.Time `gorm:"column:updated_at;type:timestamp with time zone;default:now()" json:"updated_at"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Quick operations without importing query package
|
// Quick operations without importing query package
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ func newContentAccess(db *gorm.DB, opts ...gen.DOOption) contentAccessQuery {
|
|||||||
_contentAccessQuery.UserID = field.NewInt64(tableName, "user_id")
|
_contentAccessQuery.UserID = field.NewInt64(tableName, "user_id")
|
||||||
_contentAccessQuery.ContentID = field.NewInt64(tableName, "content_id")
|
_contentAccessQuery.ContentID = field.NewInt64(tableName, "content_id")
|
||||||
_contentAccessQuery.OrderID = field.NewInt64(tableName, "order_id")
|
_contentAccessQuery.OrderID = field.NewInt64(tableName, "order_id")
|
||||||
_contentAccessQuery.Status = field.NewString(tableName, "status")
|
_contentAccessQuery.Status = field.NewField(tableName, "status")
|
||||||
_contentAccessQuery.RevokedAt = field.NewTime(tableName, "revoked_at")
|
_contentAccessQuery.RevokedAt = field.NewTime(tableName, "revoked_at")
|
||||||
_contentAccessQuery.CreatedAt = field.NewTime(tableName, "created_at")
|
_contentAccessQuery.CreatedAt = field.NewTime(tableName, "created_at")
|
||||||
_contentAccessQuery.UpdatedAt = field.NewTime(tableName, "updated_at")
|
_contentAccessQuery.UpdatedAt = field.NewTime(tableName, "updated_at")
|
||||||
@@ -49,7 +49,7 @@ type contentAccessQuery struct {
|
|||||||
UserID field.Int64
|
UserID field.Int64
|
||||||
ContentID field.Int64
|
ContentID field.Int64
|
||||||
OrderID field.Int64
|
OrderID field.Int64
|
||||||
Status field.String
|
Status field.Field
|
||||||
RevokedAt field.Time
|
RevokedAt field.Time
|
||||||
CreatedAt field.Time
|
CreatedAt field.Time
|
||||||
UpdatedAt field.Time
|
UpdatedAt field.Time
|
||||||
@@ -74,7 +74,7 @@ func (c *contentAccessQuery) updateTableName(table string) *contentAccessQuery {
|
|||||||
c.UserID = field.NewInt64(table, "user_id")
|
c.UserID = field.NewInt64(table, "user_id")
|
||||||
c.ContentID = field.NewInt64(table, "content_id")
|
c.ContentID = field.NewInt64(table, "content_id")
|
||||||
c.OrderID = field.NewInt64(table, "order_id")
|
c.OrderID = field.NewInt64(table, "order_id")
|
||||||
c.Status = field.NewString(table, "status")
|
c.Status = field.NewField(table, "status")
|
||||||
c.RevokedAt = field.NewTime(table, "revoked_at")
|
c.RevokedAt = field.NewTime(table, "revoked_at")
|
||||||
c.CreatedAt = field.NewTime(table, "created_at")
|
c.CreatedAt = field.NewTime(table, "created_at")
|
||||||
c.UpdatedAt = field.NewTime(table, "updated_at")
|
c.UpdatedAt = field.NewTime(table, "updated_at")
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"quyun/v2/pkg/consts"
|
||||||
|
|
||||||
"go.ipao.vip/gen"
|
"go.ipao.vip/gen"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -15,15 +17,16 @@ const TableNameContentAsset = "content_assets"
|
|||||||
|
|
||||||
// ContentAsset mapped from table <content_assets>
|
// ContentAsset mapped from table <content_assets>
|
||||||
type ContentAsset struct {
|
type ContentAsset struct {
|
||||||
ID int64 `gorm:"column:id;type:bigint;primaryKey;autoIncrement:true" json:"id"`
|
ID int64 `gorm:"column:id;type:bigint;primaryKey;autoIncrement:true" json:"id"`
|
||||||
TenantID int64 `gorm:"column:tenant_id;type:bigint;not null" json:"tenant_id"`
|
TenantID int64 `gorm:"column:tenant_id;type:bigint;not null" json:"tenant_id"`
|
||||||
UserID int64 `gorm:"column:user_id;type:bigint;not null" json:"user_id"`
|
UserID int64 `gorm:"column:user_id;type:bigint;not null" json:"user_id"`
|
||||||
ContentID int64 `gorm:"column:content_id;type:bigint;not null" json:"content_id"`
|
ContentID int64 `gorm:"column:content_id;type:bigint;not null" json:"content_id"`
|
||||||
AssetID int64 `gorm:"column:asset_id;type:bigint;not null" json:"asset_id"`
|
AssetID int64 `gorm:"column:asset_id;type:bigint;not null" json:"asset_id"`
|
||||||
Role string `gorm:"column:role;type:character varying(32);default:main" json:"role"`
|
Role consts.ContentAssetRole `gorm:"column:role;type:character varying(32);default:main" json:"role"`
|
||||||
Sort int32 `gorm:"column:sort;type:integer" json:"sort"`
|
Sort int32 `gorm:"column:sort;type:integer" json:"sort"`
|
||||||
CreatedAt time.Time `gorm:"column:created_at;type:timestamp with time zone;default:now()" json:"created_at"`
|
CreatedAt time.Time `gorm:"column:created_at;type:timestamp with time zone;default:now()" json:"created_at"`
|
||||||
UpdatedAt time.Time `gorm:"column:updated_at;type:timestamp with time zone;default:now()" json:"updated_at"`
|
UpdatedAt time.Time `gorm:"column:updated_at;type:timestamp with time zone;default:now()" json:"updated_at"`
|
||||||
|
Asset *MediaAsset `gorm:"foreignKey:AssetID;references:ID" json:"asset,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Quick operations without importing query package
|
// Quick operations without importing query package
|
||||||
|
|||||||
@@ -30,10 +30,15 @@ func newContentAsset(db *gorm.DB, opts ...gen.DOOption) contentAssetQuery {
|
|||||||
_contentAssetQuery.UserID = field.NewInt64(tableName, "user_id")
|
_contentAssetQuery.UserID = field.NewInt64(tableName, "user_id")
|
||||||
_contentAssetQuery.ContentID = field.NewInt64(tableName, "content_id")
|
_contentAssetQuery.ContentID = field.NewInt64(tableName, "content_id")
|
||||||
_contentAssetQuery.AssetID = field.NewInt64(tableName, "asset_id")
|
_contentAssetQuery.AssetID = field.NewInt64(tableName, "asset_id")
|
||||||
_contentAssetQuery.Role = field.NewString(tableName, "role")
|
_contentAssetQuery.Role = field.NewField(tableName, "role")
|
||||||
_contentAssetQuery.Sort = field.NewInt32(tableName, "sort")
|
_contentAssetQuery.Sort = field.NewInt32(tableName, "sort")
|
||||||
_contentAssetQuery.CreatedAt = field.NewTime(tableName, "created_at")
|
_contentAssetQuery.CreatedAt = field.NewTime(tableName, "created_at")
|
||||||
_contentAssetQuery.UpdatedAt = field.NewTime(tableName, "updated_at")
|
_contentAssetQuery.UpdatedAt = field.NewTime(tableName, "updated_at")
|
||||||
|
_contentAssetQuery.Asset = contentAssetQueryBelongsToAsset{
|
||||||
|
db: db.Session(&gorm.Session{}),
|
||||||
|
|
||||||
|
RelationField: field.NewRelation("Asset", "MediaAsset"),
|
||||||
|
}
|
||||||
|
|
||||||
_contentAssetQuery.fillFieldMap()
|
_contentAssetQuery.fillFieldMap()
|
||||||
|
|
||||||
@@ -49,10 +54,11 @@ type contentAssetQuery struct {
|
|||||||
UserID field.Int64
|
UserID field.Int64
|
||||||
ContentID field.Int64
|
ContentID field.Int64
|
||||||
AssetID field.Int64
|
AssetID field.Int64
|
||||||
Role field.String
|
Role field.Field
|
||||||
Sort field.Int32
|
Sort field.Int32
|
||||||
CreatedAt field.Time
|
CreatedAt field.Time
|
||||||
UpdatedAt field.Time
|
UpdatedAt field.Time
|
||||||
|
Asset contentAssetQueryBelongsToAsset
|
||||||
|
|
||||||
fieldMap map[string]field.Expr
|
fieldMap map[string]field.Expr
|
||||||
}
|
}
|
||||||
@@ -74,7 +80,7 @@ func (c *contentAssetQuery) updateTableName(table string) *contentAssetQuery {
|
|||||||
c.UserID = field.NewInt64(table, "user_id")
|
c.UserID = field.NewInt64(table, "user_id")
|
||||||
c.ContentID = field.NewInt64(table, "content_id")
|
c.ContentID = field.NewInt64(table, "content_id")
|
||||||
c.AssetID = field.NewInt64(table, "asset_id")
|
c.AssetID = field.NewInt64(table, "asset_id")
|
||||||
c.Role = field.NewString(table, "role")
|
c.Role = field.NewField(table, "role")
|
||||||
c.Sort = field.NewInt32(table, "sort")
|
c.Sort = field.NewInt32(table, "sort")
|
||||||
c.CreatedAt = field.NewTime(table, "created_at")
|
c.CreatedAt = field.NewTime(table, "created_at")
|
||||||
c.UpdatedAt = field.NewTime(table, "updated_at")
|
c.UpdatedAt = field.NewTime(table, "updated_at")
|
||||||
@@ -110,7 +116,7 @@ func (c *contentAssetQuery) GetFieldByName(fieldName string) (field.OrderExpr, b
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *contentAssetQuery) fillFieldMap() {
|
func (c *contentAssetQuery) fillFieldMap() {
|
||||||
c.fieldMap = make(map[string]field.Expr, 9)
|
c.fieldMap = make(map[string]field.Expr, 10)
|
||||||
c.fieldMap["id"] = c.ID
|
c.fieldMap["id"] = c.ID
|
||||||
c.fieldMap["tenant_id"] = c.TenantID
|
c.fieldMap["tenant_id"] = c.TenantID
|
||||||
c.fieldMap["user_id"] = c.UserID
|
c.fieldMap["user_id"] = c.UserID
|
||||||
@@ -120,18 +126,103 @@ func (c *contentAssetQuery) fillFieldMap() {
|
|||||||
c.fieldMap["sort"] = c.Sort
|
c.fieldMap["sort"] = c.Sort
|
||||||
c.fieldMap["created_at"] = c.CreatedAt
|
c.fieldMap["created_at"] = c.CreatedAt
|
||||||
c.fieldMap["updated_at"] = c.UpdatedAt
|
c.fieldMap["updated_at"] = c.UpdatedAt
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c contentAssetQuery) clone(db *gorm.DB) contentAssetQuery {
|
func (c contentAssetQuery) clone(db *gorm.DB) contentAssetQuery {
|
||||||
c.contentAssetQueryDo.ReplaceConnPool(db.Statement.ConnPool)
|
c.contentAssetQueryDo.ReplaceConnPool(db.Statement.ConnPool)
|
||||||
|
c.Asset.db = db.Session(&gorm.Session{Initialized: true})
|
||||||
|
c.Asset.db.Statement.ConnPool = db.Statement.ConnPool
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c contentAssetQuery) replaceDB(db *gorm.DB) contentAssetQuery {
|
func (c contentAssetQuery) replaceDB(db *gorm.DB) contentAssetQuery {
|
||||||
c.contentAssetQueryDo.ReplaceDB(db)
|
c.contentAssetQueryDo.ReplaceDB(db)
|
||||||
|
c.Asset.db = db.Session(&gorm.Session{})
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type contentAssetQueryBelongsToAsset struct {
|
||||||
|
db *gorm.DB
|
||||||
|
|
||||||
|
field.RelationField
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a contentAssetQueryBelongsToAsset) Where(conds ...field.Expr) *contentAssetQueryBelongsToAsset {
|
||||||
|
if len(conds) == 0 {
|
||||||
|
return &a
|
||||||
|
}
|
||||||
|
|
||||||
|
exprs := make([]clause.Expression, 0, len(conds))
|
||||||
|
for _, cond := range conds {
|
||||||
|
exprs = append(exprs, cond.BeCond().(clause.Expression))
|
||||||
|
}
|
||||||
|
a.db = a.db.Clauses(clause.Where{Exprs: exprs})
|
||||||
|
return &a
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a contentAssetQueryBelongsToAsset) WithContext(ctx context.Context) *contentAssetQueryBelongsToAsset {
|
||||||
|
a.db = a.db.WithContext(ctx)
|
||||||
|
return &a
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a contentAssetQueryBelongsToAsset) Session(session *gorm.Session) *contentAssetQueryBelongsToAsset {
|
||||||
|
a.db = a.db.Session(session)
|
||||||
|
return &a
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a contentAssetQueryBelongsToAsset) Model(m *ContentAsset) *contentAssetQueryBelongsToAssetTx {
|
||||||
|
return &contentAssetQueryBelongsToAssetTx{a.db.Model(m).Association(a.Name())}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a contentAssetQueryBelongsToAsset) Unscoped() *contentAssetQueryBelongsToAsset {
|
||||||
|
a.db = a.db.Unscoped()
|
||||||
|
return &a
|
||||||
|
}
|
||||||
|
|
||||||
|
type contentAssetQueryBelongsToAssetTx struct{ tx *gorm.Association }
|
||||||
|
|
||||||
|
func (a contentAssetQueryBelongsToAssetTx) Find() (result *MediaAsset, err error) {
|
||||||
|
return result, a.tx.Find(&result)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a contentAssetQueryBelongsToAssetTx) Append(values ...*MediaAsset) (err error) {
|
||||||
|
targetValues := make([]interface{}, len(values))
|
||||||
|
for i, v := range values {
|
||||||
|
targetValues[i] = v
|
||||||
|
}
|
||||||
|
return a.tx.Append(targetValues...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a contentAssetQueryBelongsToAssetTx) Replace(values ...*MediaAsset) (err error) {
|
||||||
|
targetValues := make([]interface{}, len(values))
|
||||||
|
for i, v := range values {
|
||||||
|
targetValues[i] = v
|
||||||
|
}
|
||||||
|
return a.tx.Replace(targetValues...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a contentAssetQueryBelongsToAssetTx) Delete(values ...*MediaAsset) (err error) {
|
||||||
|
targetValues := make([]interface{}, len(values))
|
||||||
|
for i, v := range values {
|
||||||
|
targetValues[i] = v
|
||||||
|
}
|
||||||
|
return a.tx.Delete(targetValues...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a contentAssetQueryBelongsToAssetTx) Clear() error {
|
||||||
|
return a.tx.Clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a contentAssetQueryBelongsToAssetTx) Count() int64 {
|
||||||
|
return a.tx.Count()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a contentAssetQueryBelongsToAssetTx) Unscoped() *contentAssetQueryBelongsToAssetTx {
|
||||||
|
a.tx = a.tx.Unscoped()
|
||||||
|
return &a
|
||||||
|
}
|
||||||
|
|
||||||
type contentAssetQueryDo struct{ gen.DO }
|
type contentAssetQueryDo struct{ gen.DO }
|
||||||
|
|
||||||
func (c contentAssetQueryDo) Debug() *contentAssetQueryDo {
|
func (c contentAssetQueryDo) Debug() *contentAssetQueryDo {
|
||||||
|
|||||||
@@ -38,6 +38,9 @@ type Content struct {
|
|||||||
CreatedAt time.Time `gorm:"column:created_at;type:timestamp with time zone;default:now()" json:"created_at"`
|
CreatedAt time.Time `gorm:"column:created_at;type:timestamp with time zone;default:now()" json:"created_at"`
|
||||||
UpdatedAt time.Time `gorm:"column:updated_at;type:timestamp with time zone;default:now()" json:"updated_at"`
|
UpdatedAt time.Time `gorm:"column:updated_at;type:timestamp with time zone;default:now()" json:"updated_at"`
|
||||||
DeletedAt gorm.DeletedAt `gorm:"column:deleted_at;type:timestamp with time zone" json:"deleted_at"`
|
DeletedAt gorm.DeletedAt `gorm:"column:deleted_at;type:timestamp with time zone" json:"deleted_at"`
|
||||||
|
Author *User `gorm:"foreignKey:UserID;references:ID" json:"author,omitempty"`
|
||||||
|
ContentAssets []*ContentAsset `gorm:"foreignKey:ContentID;references:ID" json:"content_assets,omitempty"`
|
||||||
|
Comments []*Comment `gorm:"foreignKey:ContentID;references:ID" json:"comments,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Quick operations without importing query package
|
// Quick operations without importing query package
|
||||||
|
|||||||
@@ -44,6 +44,23 @@ func newContent(db *gorm.DB, opts ...gen.DOOption) contentQuery {
|
|||||||
_contentQuery.CreatedAt = field.NewTime(tableName, "created_at")
|
_contentQuery.CreatedAt = field.NewTime(tableName, "created_at")
|
||||||
_contentQuery.UpdatedAt = field.NewTime(tableName, "updated_at")
|
_contentQuery.UpdatedAt = field.NewTime(tableName, "updated_at")
|
||||||
_contentQuery.DeletedAt = field.NewField(tableName, "deleted_at")
|
_contentQuery.DeletedAt = field.NewField(tableName, "deleted_at")
|
||||||
|
_contentQuery.Author = contentQueryBelongsToAuthor{
|
||||||
|
db: db.Session(&gorm.Session{}),
|
||||||
|
|
||||||
|
RelationField: field.NewRelation("Author", "User"),
|
||||||
|
}
|
||||||
|
|
||||||
|
_contentQuery.ContentAssets = contentQueryHasManyContentAssets{
|
||||||
|
db: db.Session(&gorm.Session{}),
|
||||||
|
|
||||||
|
RelationField: field.NewRelation("ContentAssets", "ContentAsset"),
|
||||||
|
}
|
||||||
|
|
||||||
|
_contentQuery.Comments = contentQueryHasManyComments{
|
||||||
|
db: db.Session(&gorm.Session{}),
|
||||||
|
|
||||||
|
RelationField: field.NewRelation("Comments", "Comment"),
|
||||||
|
}
|
||||||
|
|
||||||
_contentQuery.fillFieldMap()
|
_contentQuery.fillFieldMap()
|
||||||
|
|
||||||
@@ -73,6 +90,11 @@ type contentQuery struct {
|
|||||||
CreatedAt field.Time
|
CreatedAt field.Time
|
||||||
UpdatedAt field.Time
|
UpdatedAt field.Time
|
||||||
DeletedAt field.Field
|
DeletedAt field.Field
|
||||||
|
Author contentQueryBelongsToAuthor
|
||||||
|
|
||||||
|
ContentAssets contentQueryHasManyContentAssets
|
||||||
|
|
||||||
|
Comments contentQueryHasManyComments
|
||||||
|
|
||||||
fieldMap map[string]field.Expr
|
fieldMap map[string]field.Expr
|
||||||
}
|
}
|
||||||
@@ -140,7 +162,7 @@ func (c *contentQuery) GetFieldByName(fieldName string) (field.OrderExpr, bool)
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *contentQuery) fillFieldMap() {
|
func (c *contentQuery) fillFieldMap() {
|
||||||
c.fieldMap = make(map[string]field.Expr, 19)
|
c.fieldMap = make(map[string]field.Expr, 22)
|
||||||
c.fieldMap["id"] = c.ID
|
c.fieldMap["id"] = c.ID
|
||||||
c.fieldMap["tenant_id"] = c.TenantID
|
c.fieldMap["tenant_id"] = c.TenantID
|
||||||
c.fieldMap["user_id"] = c.UserID
|
c.fieldMap["user_id"] = c.UserID
|
||||||
@@ -160,18 +182,271 @@ func (c *contentQuery) fillFieldMap() {
|
|||||||
c.fieldMap["created_at"] = c.CreatedAt
|
c.fieldMap["created_at"] = c.CreatedAt
|
||||||
c.fieldMap["updated_at"] = c.UpdatedAt
|
c.fieldMap["updated_at"] = c.UpdatedAt
|
||||||
c.fieldMap["deleted_at"] = c.DeletedAt
|
c.fieldMap["deleted_at"] = c.DeletedAt
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c contentQuery) clone(db *gorm.DB) contentQuery {
|
func (c contentQuery) clone(db *gorm.DB) contentQuery {
|
||||||
c.contentQueryDo.ReplaceConnPool(db.Statement.ConnPool)
|
c.contentQueryDo.ReplaceConnPool(db.Statement.ConnPool)
|
||||||
|
c.Author.db = db.Session(&gorm.Session{Initialized: true})
|
||||||
|
c.Author.db.Statement.ConnPool = db.Statement.ConnPool
|
||||||
|
c.ContentAssets.db = db.Session(&gorm.Session{Initialized: true})
|
||||||
|
c.ContentAssets.db.Statement.ConnPool = db.Statement.ConnPool
|
||||||
|
c.Comments.db = db.Session(&gorm.Session{Initialized: true})
|
||||||
|
c.Comments.db.Statement.ConnPool = db.Statement.ConnPool
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c contentQuery) replaceDB(db *gorm.DB) contentQuery {
|
func (c contentQuery) replaceDB(db *gorm.DB) contentQuery {
|
||||||
c.contentQueryDo.ReplaceDB(db)
|
c.contentQueryDo.ReplaceDB(db)
|
||||||
|
c.Author.db = db.Session(&gorm.Session{})
|
||||||
|
c.ContentAssets.db = db.Session(&gorm.Session{})
|
||||||
|
c.Comments.db = db.Session(&gorm.Session{})
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type contentQueryBelongsToAuthor struct {
|
||||||
|
db *gorm.DB
|
||||||
|
|
||||||
|
field.RelationField
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a contentQueryBelongsToAuthor) Where(conds ...field.Expr) *contentQueryBelongsToAuthor {
|
||||||
|
if len(conds) == 0 {
|
||||||
|
return &a
|
||||||
|
}
|
||||||
|
|
||||||
|
exprs := make([]clause.Expression, 0, len(conds))
|
||||||
|
for _, cond := range conds {
|
||||||
|
exprs = append(exprs, cond.BeCond().(clause.Expression))
|
||||||
|
}
|
||||||
|
a.db = a.db.Clauses(clause.Where{Exprs: exprs})
|
||||||
|
return &a
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a contentQueryBelongsToAuthor) WithContext(ctx context.Context) *contentQueryBelongsToAuthor {
|
||||||
|
a.db = a.db.WithContext(ctx)
|
||||||
|
return &a
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a contentQueryBelongsToAuthor) Session(session *gorm.Session) *contentQueryBelongsToAuthor {
|
||||||
|
a.db = a.db.Session(session)
|
||||||
|
return &a
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a contentQueryBelongsToAuthor) Model(m *Content) *contentQueryBelongsToAuthorTx {
|
||||||
|
return &contentQueryBelongsToAuthorTx{a.db.Model(m).Association(a.Name())}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a contentQueryBelongsToAuthor) Unscoped() *contentQueryBelongsToAuthor {
|
||||||
|
a.db = a.db.Unscoped()
|
||||||
|
return &a
|
||||||
|
}
|
||||||
|
|
||||||
|
type contentQueryBelongsToAuthorTx struct{ tx *gorm.Association }
|
||||||
|
|
||||||
|
func (a contentQueryBelongsToAuthorTx) Find() (result *User, err error) {
|
||||||
|
return result, a.tx.Find(&result)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a contentQueryBelongsToAuthorTx) Append(values ...*User) (err error) {
|
||||||
|
targetValues := make([]interface{}, len(values))
|
||||||
|
for i, v := range values {
|
||||||
|
targetValues[i] = v
|
||||||
|
}
|
||||||
|
return a.tx.Append(targetValues...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a contentQueryBelongsToAuthorTx) Replace(values ...*User) (err error) {
|
||||||
|
targetValues := make([]interface{}, len(values))
|
||||||
|
for i, v := range values {
|
||||||
|
targetValues[i] = v
|
||||||
|
}
|
||||||
|
return a.tx.Replace(targetValues...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a contentQueryBelongsToAuthorTx) Delete(values ...*User) (err error) {
|
||||||
|
targetValues := make([]interface{}, len(values))
|
||||||
|
for i, v := range values {
|
||||||
|
targetValues[i] = v
|
||||||
|
}
|
||||||
|
return a.tx.Delete(targetValues...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a contentQueryBelongsToAuthorTx) Clear() error {
|
||||||
|
return a.tx.Clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a contentQueryBelongsToAuthorTx) Count() int64 {
|
||||||
|
return a.tx.Count()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a contentQueryBelongsToAuthorTx) Unscoped() *contentQueryBelongsToAuthorTx {
|
||||||
|
a.tx = a.tx.Unscoped()
|
||||||
|
return &a
|
||||||
|
}
|
||||||
|
|
||||||
|
type contentQueryHasManyContentAssets struct {
|
||||||
|
db *gorm.DB
|
||||||
|
|
||||||
|
field.RelationField
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a contentQueryHasManyContentAssets) Where(conds ...field.Expr) *contentQueryHasManyContentAssets {
|
||||||
|
if len(conds) == 0 {
|
||||||
|
return &a
|
||||||
|
}
|
||||||
|
|
||||||
|
exprs := make([]clause.Expression, 0, len(conds))
|
||||||
|
for _, cond := range conds {
|
||||||
|
exprs = append(exprs, cond.BeCond().(clause.Expression))
|
||||||
|
}
|
||||||
|
a.db = a.db.Clauses(clause.Where{Exprs: exprs})
|
||||||
|
return &a
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a contentQueryHasManyContentAssets) WithContext(ctx context.Context) *contentQueryHasManyContentAssets {
|
||||||
|
a.db = a.db.WithContext(ctx)
|
||||||
|
return &a
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a contentQueryHasManyContentAssets) Session(session *gorm.Session) *contentQueryHasManyContentAssets {
|
||||||
|
a.db = a.db.Session(session)
|
||||||
|
return &a
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a contentQueryHasManyContentAssets) Model(m *Content) *contentQueryHasManyContentAssetsTx {
|
||||||
|
return &contentQueryHasManyContentAssetsTx{a.db.Model(m).Association(a.Name())}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a contentQueryHasManyContentAssets) Unscoped() *contentQueryHasManyContentAssets {
|
||||||
|
a.db = a.db.Unscoped()
|
||||||
|
return &a
|
||||||
|
}
|
||||||
|
|
||||||
|
type contentQueryHasManyContentAssetsTx struct{ tx *gorm.Association }
|
||||||
|
|
||||||
|
func (a contentQueryHasManyContentAssetsTx) Find() (result []*ContentAsset, err error) {
|
||||||
|
return result, a.tx.Find(&result)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a contentQueryHasManyContentAssetsTx) Append(values ...*ContentAsset) (err error) {
|
||||||
|
targetValues := make([]interface{}, len(values))
|
||||||
|
for i, v := range values {
|
||||||
|
targetValues[i] = v
|
||||||
|
}
|
||||||
|
return a.tx.Append(targetValues...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a contentQueryHasManyContentAssetsTx) Replace(values ...*ContentAsset) (err error) {
|
||||||
|
targetValues := make([]interface{}, len(values))
|
||||||
|
for i, v := range values {
|
||||||
|
targetValues[i] = v
|
||||||
|
}
|
||||||
|
return a.tx.Replace(targetValues...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a contentQueryHasManyContentAssetsTx) Delete(values ...*ContentAsset) (err error) {
|
||||||
|
targetValues := make([]interface{}, len(values))
|
||||||
|
for i, v := range values {
|
||||||
|
targetValues[i] = v
|
||||||
|
}
|
||||||
|
return a.tx.Delete(targetValues...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a contentQueryHasManyContentAssetsTx) Clear() error {
|
||||||
|
return a.tx.Clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a contentQueryHasManyContentAssetsTx) Count() int64 {
|
||||||
|
return a.tx.Count()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a contentQueryHasManyContentAssetsTx) Unscoped() *contentQueryHasManyContentAssetsTx {
|
||||||
|
a.tx = a.tx.Unscoped()
|
||||||
|
return &a
|
||||||
|
}
|
||||||
|
|
||||||
|
type contentQueryHasManyComments struct {
|
||||||
|
db *gorm.DB
|
||||||
|
|
||||||
|
field.RelationField
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a contentQueryHasManyComments) Where(conds ...field.Expr) *contentQueryHasManyComments {
|
||||||
|
if len(conds) == 0 {
|
||||||
|
return &a
|
||||||
|
}
|
||||||
|
|
||||||
|
exprs := make([]clause.Expression, 0, len(conds))
|
||||||
|
for _, cond := range conds {
|
||||||
|
exprs = append(exprs, cond.BeCond().(clause.Expression))
|
||||||
|
}
|
||||||
|
a.db = a.db.Clauses(clause.Where{Exprs: exprs})
|
||||||
|
return &a
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a contentQueryHasManyComments) WithContext(ctx context.Context) *contentQueryHasManyComments {
|
||||||
|
a.db = a.db.WithContext(ctx)
|
||||||
|
return &a
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a contentQueryHasManyComments) Session(session *gorm.Session) *contentQueryHasManyComments {
|
||||||
|
a.db = a.db.Session(session)
|
||||||
|
return &a
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a contentQueryHasManyComments) Model(m *Content) *contentQueryHasManyCommentsTx {
|
||||||
|
return &contentQueryHasManyCommentsTx{a.db.Model(m).Association(a.Name())}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a contentQueryHasManyComments) Unscoped() *contentQueryHasManyComments {
|
||||||
|
a.db = a.db.Unscoped()
|
||||||
|
return &a
|
||||||
|
}
|
||||||
|
|
||||||
|
type contentQueryHasManyCommentsTx struct{ tx *gorm.Association }
|
||||||
|
|
||||||
|
func (a contentQueryHasManyCommentsTx) Find() (result []*Comment, err error) {
|
||||||
|
return result, a.tx.Find(&result)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a contentQueryHasManyCommentsTx) Append(values ...*Comment) (err error) {
|
||||||
|
targetValues := make([]interface{}, len(values))
|
||||||
|
for i, v := range values {
|
||||||
|
targetValues[i] = v
|
||||||
|
}
|
||||||
|
return a.tx.Append(targetValues...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a contentQueryHasManyCommentsTx) Replace(values ...*Comment) (err error) {
|
||||||
|
targetValues := make([]interface{}, len(values))
|
||||||
|
for i, v := range values {
|
||||||
|
targetValues[i] = v
|
||||||
|
}
|
||||||
|
return a.tx.Replace(targetValues...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a contentQueryHasManyCommentsTx) Delete(values ...*Comment) (err error) {
|
||||||
|
targetValues := make([]interface{}, len(values))
|
||||||
|
for i, v := range values {
|
||||||
|
targetValues[i] = v
|
||||||
|
}
|
||||||
|
return a.tx.Delete(targetValues...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a contentQueryHasManyCommentsTx) Clear() error {
|
||||||
|
return a.tx.Clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a contentQueryHasManyCommentsTx) Count() int64 {
|
||||||
|
return a.tx.Count()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a contentQueryHasManyCommentsTx) Unscoped() *contentQueryHasManyCommentsTx {
|
||||||
|
a.tx = a.tx.Unscoped()
|
||||||
|
return &a
|
||||||
|
}
|
||||||
|
|
||||||
type contentQueryDo struct{ gen.DO }
|
type contentQueryDo struct{ gen.DO }
|
||||||
|
|
||||||
func (c contentQueryDo) Debug() *contentQueryDo {
|
func (c contentQueryDo) Debug() *contentQueryDo {
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ type Order struct {
|
|||||||
UserID int64 `gorm:"column:user_id;type:bigint;not null" json:"user_id"`
|
UserID int64 `gorm:"column:user_id;type:bigint;not null" json:"user_id"`
|
||||||
Type consts.OrderType `gorm:"column:type;type:character varying(32);default:content_purchase" json:"type"`
|
Type consts.OrderType `gorm:"column:type;type:character varying(32);default:content_purchase" json:"type"`
|
||||||
Status consts.OrderStatus `gorm:"column:status;type:character varying(32);default:created" json:"status"`
|
Status consts.OrderStatus `gorm:"column:status;type:character varying(32);default:created" json:"status"`
|
||||||
Currency string `gorm:"column:currency;type:character varying(16);default:CNY" json:"currency"`
|
Currency consts.Currency `gorm:"column:currency;type:character varying(16);default:CNY" json:"currency"`
|
||||||
AmountOriginal int64 `gorm:"column:amount_original;type:bigint;not null" json:"amount_original"`
|
AmountOriginal int64 `gorm:"column:amount_original;type:bigint;not null" json:"amount_original"`
|
||||||
AmountDiscount int64 `gorm:"column:amount_discount;type:bigint;not null" json:"amount_discount"`
|
AmountDiscount int64 `gorm:"column:amount_discount;type:bigint;not null" json:"amount_discount"`
|
||||||
AmountPaid int64 `gorm:"column:amount_paid;type:bigint;not null" json:"amount_paid"`
|
AmountPaid int64 `gorm:"column:amount_paid;type:bigint;not null" json:"amount_paid"`
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ func newOrder(db *gorm.DB, opts ...gen.DOOption) orderQuery {
|
|||||||
_orderQuery.UserID = field.NewInt64(tableName, "user_id")
|
_orderQuery.UserID = field.NewInt64(tableName, "user_id")
|
||||||
_orderQuery.Type = field.NewField(tableName, "type")
|
_orderQuery.Type = field.NewField(tableName, "type")
|
||||||
_orderQuery.Status = field.NewField(tableName, "status")
|
_orderQuery.Status = field.NewField(tableName, "status")
|
||||||
_orderQuery.Currency = field.NewString(tableName, "currency")
|
_orderQuery.Currency = field.NewField(tableName, "currency")
|
||||||
_orderQuery.AmountOriginal = field.NewInt64(tableName, "amount_original")
|
_orderQuery.AmountOriginal = field.NewInt64(tableName, "amount_original")
|
||||||
_orderQuery.AmountDiscount = field.NewInt64(tableName, "amount_discount")
|
_orderQuery.AmountDiscount = field.NewInt64(tableName, "amount_discount")
|
||||||
_orderQuery.AmountPaid = field.NewInt64(tableName, "amount_paid")
|
_orderQuery.AmountPaid = field.NewInt64(tableName, "amount_paid")
|
||||||
@@ -58,7 +58,7 @@ type orderQuery struct {
|
|||||||
UserID field.Int64
|
UserID field.Int64
|
||||||
Type field.Field
|
Type field.Field
|
||||||
Status field.Field
|
Status field.Field
|
||||||
Currency field.String
|
Currency field.Field
|
||||||
AmountOriginal field.Int64
|
AmountOriginal field.Int64
|
||||||
AmountDiscount field.Int64
|
AmountDiscount field.Int64
|
||||||
AmountPaid field.Int64
|
AmountPaid field.Int64
|
||||||
@@ -92,7 +92,7 @@ func (o *orderQuery) updateTableName(table string) *orderQuery {
|
|||||||
o.UserID = field.NewInt64(table, "user_id")
|
o.UserID = field.NewInt64(table, "user_id")
|
||||||
o.Type = field.NewField(table, "type")
|
o.Type = field.NewField(table, "type")
|
||||||
o.Status = field.NewField(table, "status")
|
o.Status = field.NewField(table, "status")
|
||||||
o.Currency = field.NewString(table, "currency")
|
o.Currency = field.NewField(table, "currency")
|
||||||
o.AmountOriginal = field.NewInt64(table, "amount_original")
|
o.AmountOriginal = field.NewInt64(table, "amount_original")
|
||||||
o.AmountDiscount = field.NewInt64(table, "amount_discount")
|
o.AmountDiscount = field.NewInt64(table, "amount_discount")
|
||||||
o.AmountPaid = field.NewInt64(table, "amount_paid")
|
o.AmountPaid = field.NewInt64(table, "amount_paid")
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"quyun/v2/pkg/consts"
|
||||||
|
|
||||||
"go.ipao.vip/gen"
|
"go.ipao.vip/gen"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -15,23 +17,23 @@ const TableNameTenantLedger = "tenant_ledgers"
|
|||||||
|
|
||||||
// TenantLedger mapped from table <tenant_ledgers>
|
// TenantLedger mapped from table <tenant_ledgers>
|
||||||
type TenantLedger struct {
|
type TenantLedger struct {
|
||||||
ID int64 `gorm:"column:id;type:bigint;primaryKey;autoIncrement:true" json:"id"`
|
ID int64 `gorm:"column:id;type:bigint;primaryKey;autoIncrement:true" json:"id"`
|
||||||
TenantID int64 `gorm:"column:tenant_id;type:bigint;not null" json:"tenant_id"`
|
TenantID int64 `gorm:"column:tenant_id;type:bigint;not null" json:"tenant_id"`
|
||||||
UserID int64 `gorm:"column:user_id;type:bigint;not null" json:"user_id"`
|
UserID int64 `gorm:"column:user_id;type:bigint;not null" json:"user_id"`
|
||||||
OrderID int64 `gorm:"column:order_id;type:bigint" json:"order_id"`
|
OrderID int64 `gorm:"column:order_id;type:bigint" json:"order_id"`
|
||||||
Type string `gorm:"column:type;type:character varying(32);not null" json:"type"`
|
Type consts.TenantLedgerType `gorm:"column:type;type:character varying(32);not null" json:"type"`
|
||||||
Amount int64 `gorm:"column:amount;type:bigint;not null" json:"amount"`
|
Amount int64 `gorm:"column:amount;type:bigint;not null" json:"amount"`
|
||||||
BalanceBefore int64 `gorm:"column:balance_before;type:bigint;not null" json:"balance_before"`
|
BalanceBefore int64 `gorm:"column:balance_before;type:bigint;not null" json:"balance_before"`
|
||||||
BalanceAfter int64 `gorm:"column:balance_after;type:bigint;not null" json:"balance_after"`
|
BalanceAfter int64 `gorm:"column:balance_after;type:bigint;not null" json:"balance_after"`
|
||||||
FrozenBefore int64 `gorm:"column:frozen_before;type:bigint;not null" json:"frozen_before"`
|
FrozenBefore int64 `gorm:"column:frozen_before;type:bigint;not null" json:"frozen_before"`
|
||||||
FrozenAfter int64 `gorm:"column:frozen_after;type:bigint;not null" json:"frozen_after"`
|
FrozenAfter int64 `gorm:"column:frozen_after;type:bigint;not null" json:"frozen_after"`
|
||||||
IdempotencyKey string `gorm:"column:idempotency_key;type:character varying(128);not null" json:"idempotency_key"`
|
IdempotencyKey string `gorm:"column:idempotency_key;type:character varying(128);not null" json:"idempotency_key"`
|
||||||
Remark string `gorm:"column:remark;type:character varying(255);not null" json:"remark"`
|
Remark string `gorm:"column:remark;type:character varying(255);not null" json:"remark"`
|
||||||
OperatorUserID int64 `gorm:"column:operator_user_id;type:bigint" json:"operator_user_id"`
|
OperatorUserID int64 `gorm:"column:operator_user_id;type:bigint" json:"operator_user_id"`
|
||||||
BizRefType string `gorm:"column:biz_ref_type;type:character varying(32)" json:"biz_ref_type"`
|
BizRefType string `gorm:"column:biz_ref_type;type:character varying(32)" json:"biz_ref_type"`
|
||||||
BizRefID int64 `gorm:"column:biz_ref_id;type:bigint" json:"biz_ref_id"`
|
BizRefID int64 `gorm:"column:biz_ref_id;type:bigint" json:"biz_ref_id"`
|
||||||
CreatedAt time.Time `gorm:"column:created_at;type:timestamp with time zone;default:now()" json:"created_at"`
|
CreatedAt time.Time `gorm:"column:created_at;type:timestamp with time zone;default:now()" json:"created_at"`
|
||||||
UpdatedAt time.Time `gorm:"column:updated_at;type:timestamp with time zone;default:now()" json:"updated_at"`
|
UpdatedAt time.Time `gorm:"column:updated_at;type:timestamp with time zone;default:now()" json:"updated_at"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Quick operations without importing query package
|
// Quick operations without importing query package
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ func newTenantLedger(db *gorm.DB, opts ...gen.DOOption) tenantLedgerQuery {
|
|||||||
_tenantLedgerQuery.TenantID = field.NewInt64(tableName, "tenant_id")
|
_tenantLedgerQuery.TenantID = field.NewInt64(tableName, "tenant_id")
|
||||||
_tenantLedgerQuery.UserID = field.NewInt64(tableName, "user_id")
|
_tenantLedgerQuery.UserID = field.NewInt64(tableName, "user_id")
|
||||||
_tenantLedgerQuery.OrderID = field.NewInt64(tableName, "order_id")
|
_tenantLedgerQuery.OrderID = field.NewInt64(tableName, "order_id")
|
||||||
_tenantLedgerQuery.Type = field.NewString(tableName, "type")
|
_tenantLedgerQuery.Type = field.NewField(tableName, "type")
|
||||||
_tenantLedgerQuery.Amount = field.NewInt64(tableName, "amount")
|
_tenantLedgerQuery.Amount = field.NewInt64(tableName, "amount")
|
||||||
_tenantLedgerQuery.BalanceBefore = field.NewInt64(tableName, "balance_before")
|
_tenantLedgerQuery.BalanceBefore = field.NewInt64(tableName, "balance_before")
|
||||||
_tenantLedgerQuery.BalanceAfter = field.NewInt64(tableName, "balance_after")
|
_tenantLedgerQuery.BalanceAfter = field.NewInt64(tableName, "balance_after")
|
||||||
@@ -56,7 +56,7 @@ type tenantLedgerQuery struct {
|
|||||||
TenantID field.Int64
|
TenantID field.Int64
|
||||||
UserID field.Int64
|
UserID field.Int64
|
||||||
OrderID field.Int64
|
OrderID field.Int64
|
||||||
Type field.String
|
Type field.Field
|
||||||
Amount field.Int64
|
Amount field.Int64
|
||||||
BalanceBefore field.Int64
|
BalanceBefore field.Int64
|
||||||
BalanceAfter field.Int64
|
BalanceAfter field.Int64
|
||||||
@@ -89,7 +89,7 @@ func (t *tenantLedgerQuery) updateTableName(table string) *tenantLedgerQuery {
|
|||||||
t.TenantID = field.NewInt64(table, "tenant_id")
|
t.TenantID = field.NewInt64(table, "tenant_id")
|
||||||
t.UserID = field.NewInt64(table, "user_id")
|
t.UserID = field.NewInt64(table, "user_id")
|
||||||
t.OrderID = field.NewInt64(table, "order_id")
|
t.OrderID = field.NewInt64(table, "order_id")
|
||||||
t.Type = field.NewString(table, "type")
|
t.Type = field.NewField(table, "type")
|
||||||
t.Amount = field.NewInt64(table, "amount")
|
t.Amount = field.NewInt64(table, "amount")
|
||||||
t.BalanceBefore = field.NewInt64(table, "balance_before")
|
t.BalanceBefore = field.NewInt64(table, "balance_before")
|
||||||
t.BalanceAfter = field.NewInt64(table, "balance_after")
|
t.BalanceAfter = field.NewInt64(table, "balance_after")
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ type TenantUser struct {
|
|||||||
TenantID int64 `gorm:"column:tenant_id;type:bigint;not null" json:"tenant_id"`
|
TenantID int64 `gorm:"column:tenant_id;type:bigint;not null" json:"tenant_id"`
|
||||||
UserID int64 `gorm:"column:user_id;type:bigint;not null" json:"user_id"`
|
UserID int64 `gorm:"column:user_id;type:bigint;not null" json:"user_id"`
|
||||||
Role types.Array[consts.TenantUserRole] `gorm:"column:role;type:text[];default:{member}" json:"role"`
|
Role types.Array[consts.TenantUserRole] `gorm:"column:role;type:text[];default:{member}" json:"role"`
|
||||||
Status string `gorm:"column:status;type:character varying(50);default:verified" json:"status"`
|
Status consts.UserStatus `gorm:"column:status;type:character varying(50);default:verified" json:"status"`
|
||||||
CreatedAt time.Time `gorm:"column:created_at;type:timestamp with time zone;default:now()" json:"created_at"`
|
CreatedAt time.Time `gorm:"column:created_at;type:timestamp with time zone;default:now()" json:"created_at"`
|
||||||
UpdatedAt time.Time `gorm:"column:updated_at;type:timestamp with time zone;default:now()" json:"updated_at"`
|
UpdatedAt time.Time `gorm:"column:updated_at;type:timestamp with time zone;default:now()" json:"updated_at"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ func newTenantUser(db *gorm.DB, opts ...gen.DOOption) tenantUserQuery {
|
|||||||
_tenantUserQuery.TenantID = field.NewInt64(tableName, "tenant_id")
|
_tenantUserQuery.TenantID = field.NewInt64(tableName, "tenant_id")
|
||||||
_tenantUserQuery.UserID = field.NewInt64(tableName, "user_id")
|
_tenantUserQuery.UserID = field.NewInt64(tableName, "user_id")
|
||||||
_tenantUserQuery.Role = field.NewArray(tableName, "role")
|
_tenantUserQuery.Role = field.NewArray(tableName, "role")
|
||||||
_tenantUserQuery.Status = field.NewString(tableName, "status")
|
_tenantUserQuery.Status = field.NewField(tableName, "status")
|
||||||
_tenantUserQuery.CreatedAt = field.NewTime(tableName, "created_at")
|
_tenantUserQuery.CreatedAt = field.NewTime(tableName, "created_at")
|
||||||
_tenantUserQuery.UpdatedAt = field.NewTime(tableName, "updated_at")
|
_tenantUserQuery.UpdatedAt = field.NewTime(tableName, "updated_at")
|
||||||
|
|
||||||
@@ -46,7 +46,7 @@ type tenantUserQuery struct {
|
|||||||
TenantID field.Int64
|
TenantID field.Int64
|
||||||
UserID field.Int64
|
UserID field.Int64
|
||||||
Role field.Array
|
Role field.Array
|
||||||
Status field.String
|
Status field.Field
|
||||||
CreatedAt field.Time
|
CreatedAt field.Time
|
||||||
UpdatedAt field.Time
|
UpdatedAt field.Time
|
||||||
|
|
||||||
@@ -69,7 +69,7 @@ func (t *tenantUserQuery) updateTableName(table string) *tenantUserQuery {
|
|||||||
t.TenantID = field.NewInt64(table, "tenant_id")
|
t.TenantID = field.NewInt64(table, "tenant_id")
|
||||||
t.UserID = field.NewInt64(table, "user_id")
|
t.UserID = field.NewInt64(table, "user_id")
|
||||||
t.Role = field.NewArray(table, "role")
|
t.Role = field.NewArray(table, "role")
|
||||||
t.Status = field.NewString(table, "status")
|
t.Status = field.NewField(table, "status")
|
||||||
t.CreatedAt = field.NewTime(table, "created_at")
|
t.CreatedAt = field.NewTime(table, "created_at")
|
||||||
t.UpdatedAt = field.NewTime(table, "updated_at")
|
t.UpdatedAt = field.NewTime(table, "updated_at")
|
||||||
|
|
||||||
|
|||||||
@@ -1686,12 +1686,15 @@ func (x NullOrderStatusStr) Value() (driver.Value, error) {
|
|||||||
const (
|
const (
|
||||||
// OrderTypeContentPurchase is a OrderType of type content_purchase.
|
// OrderTypeContentPurchase is a OrderType of type content_purchase.
|
||||||
OrderTypeContentPurchase OrderType = "content_purchase"
|
OrderTypeContentPurchase OrderType = "content_purchase"
|
||||||
|
// OrderTypeRecharge is a OrderType of type recharge.
|
||||||
|
OrderTypeRecharge OrderType = "recharge"
|
||||||
)
|
)
|
||||||
|
|
||||||
var ErrInvalidOrderType = fmt.Errorf("not a valid OrderType, try [%s]", strings.Join(_OrderTypeNames, ", "))
|
var ErrInvalidOrderType = fmt.Errorf("not a valid OrderType, try [%s]", strings.Join(_OrderTypeNames, ", "))
|
||||||
|
|
||||||
var _OrderTypeNames = []string{
|
var _OrderTypeNames = []string{
|
||||||
string(OrderTypeContentPurchase),
|
string(OrderTypeContentPurchase),
|
||||||
|
string(OrderTypeRecharge),
|
||||||
}
|
}
|
||||||
|
|
||||||
// OrderTypeNames returns a list of possible string values of OrderType.
|
// OrderTypeNames returns a list of possible string values of OrderType.
|
||||||
@@ -1705,6 +1708,7 @@ func OrderTypeNames() []string {
|
|||||||
func OrderTypeValues() []OrderType {
|
func OrderTypeValues() []OrderType {
|
||||||
return []OrderType{
|
return []OrderType{
|
||||||
OrderTypeContentPurchase,
|
OrderTypeContentPurchase,
|
||||||
|
OrderTypeRecharge,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1722,6 +1726,7 @@ func (x OrderType) IsValid() bool {
|
|||||||
|
|
||||||
var _OrderTypeValue = map[string]OrderType{
|
var _OrderTypeValue = map[string]OrderType{
|
||||||
"content_purchase": OrderTypeContentPurchase,
|
"content_purchase": OrderTypeContentPurchase,
|
||||||
|
"recharge": OrderTypeRecharge,
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseOrderType attempts to convert a string to a OrderType.
|
// ParseOrderType attempts to convert a string to a OrderType.
|
||||||
@@ -2499,6 +2504,10 @@ func (x NullTenantUserRoleStr) Value() (driver.Value, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
// UserStatusActive is a UserStatus of type active.
|
||||||
|
UserStatusActive UserStatus = "active"
|
||||||
|
// UserStatusInactive is a UserStatus of type inactive.
|
||||||
|
UserStatusInactive UserStatus = "inactive"
|
||||||
// UserStatusPendingVerify is a UserStatus of type pending_verify.
|
// UserStatusPendingVerify is a UserStatus of type pending_verify.
|
||||||
UserStatusPendingVerify UserStatus = "pending_verify"
|
UserStatusPendingVerify UserStatus = "pending_verify"
|
||||||
// UserStatusVerified is a UserStatus of type verified.
|
// UserStatusVerified is a UserStatus of type verified.
|
||||||
@@ -2510,6 +2519,8 @@ const (
|
|||||||
var ErrInvalidUserStatus = fmt.Errorf("not a valid UserStatus, try [%s]", strings.Join(_UserStatusNames, ", "))
|
var ErrInvalidUserStatus = fmt.Errorf("not a valid UserStatus, try [%s]", strings.Join(_UserStatusNames, ", "))
|
||||||
|
|
||||||
var _UserStatusNames = []string{
|
var _UserStatusNames = []string{
|
||||||
|
string(UserStatusActive),
|
||||||
|
string(UserStatusInactive),
|
||||||
string(UserStatusPendingVerify),
|
string(UserStatusPendingVerify),
|
||||||
string(UserStatusVerified),
|
string(UserStatusVerified),
|
||||||
string(UserStatusBanned),
|
string(UserStatusBanned),
|
||||||
@@ -2525,6 +2536,8 @@ func UserStatusNames() []string {
|
|||||||
// UserStatusValues returns a list of the values for UserStatus
|
// UserStatusValues returns a list of the values for UserStatus
|
||||||
func UserStatusValues() []UserStatus {
|
func UserStatusValues() []UserStatus {
|
||||||
return []UserStatus{
|
return []UserStatus{
|
||||||
|
UserStatusActive,
|
||||||
|
UserStatusInactive,
|
||||||
UserStatusPendingVerify,
|
UserStatusPendingVerify,
|
||||||
UserStatusVerified,
|
UserStatusVerified,
|
||||||
UserStatusBanned,
|
UserStatusBanned,
|
||||||
@@ -2544,6 +2557,8 @@ func (x UserStatus) IsValid() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var _UserStatusValue = map[string]UserStatus{
|
var _UserStatusValue = map[string]UserStatus{
|
||||||
|
"active": UserStatusActive,
|
||||||
|
"inactive": UserStatusInactive,
|
||||||
"pending_verify": UserStatusPendingVerify,
|
"pending_verify": UserStatusPendingVerify,
|
||||||
"verified": UserStatusVerified,
|
"verified": UserStatusVerified,
|
||||||
"banned": UserStatusBanned,
|
"banned": UserStatusBanned,
|
||||||
|
|||||||
@@ -40,12 +40,16 @@ func RoleItems() []requests.KV {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// swagger:enum UserStatus
|
// swagger:enum UserStatus
|
||||||
// ENUM(pending_verify, verified, banned, )
|
// ENUM(active, inactive, pending_verify, verified, banned, )
|
||||||
type UserStatus string
|
type UserStatus string
|
||||||
|
|
||||||
// Description returns the Chinese label for the specific enum value.
|
// Description returns the Chinese label for the specific enum value.
|
||||||
func (t UserStatus) Description() string {
|
func (t UserStatus) Description() string {
|
||||||
switch t {
|
switch t {
|
||||||
|
case UserStatusActive:
|
||||||
|
return "正常"
|
||||||
|
case UserStatusInactive:
|
||||||
|
return "未激活"
|
||||||
case UserStatusPendingVerify:
|
case UserStatusPendingVerify:
|
||||||
return "待审核"
|
return "待审核"
|
||||||
case UserStatusVerified:
|
case UserStatusVerified:
|
||||||
@@ -398,7 +402,7 @@ func ContentAccessStatusItems() []requests.KV {
|
|||||||
// orders
|
// orders
|
||||||
|
|
||||||
// swagger:enum OrderType
|
// swagger:enum OrderType
|
||||||
// ENUM( content_purchase )
|
// ENUM( content_purchase, recharge )
|
||||||
type OrderType string
|
type OrderType string
|
||||||
|
|
||||||
// Description returns the Chinese label for the specific enum value.
|
// Description returns the Chinese label for the specific enum value.
|
||||||
@@ -406,6 +410,8 @@ func (t OrderType) Description() string {
|
|||||||
switch t {
|
switch t {
|
||||||
case OrderTypeContentPurchase:
|
case OrderTypeContentPurchase:
|
||||||
return "购买内容"
|
return "购买内容"
|
||||||
|
case OrderTypeRecharge:
|
||||||
|
return "充值"
|
||||||
default:
|
default:
|
||||||
return "未知类型"
|
return "未知类型"
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user