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:
2025-12-29 14:21:20 +08:00
parent d648a1e45b
commit 8fa3d18a9c
30 changed files with 2251 additions and 85 deletions

View File

@@ -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
} }

View File

@@ -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
}

View 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)
})
})
}

View File

@@ -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 {
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 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
}

View 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)
})
})
}

View File

@@ -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),
}
} }

View 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
})
})
}

View File

@@ -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
} }

View 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)
})
})
}

View File

@@ -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
} }

View File

@@ -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
} }

View 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)
})
})
}

View File

@@ -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

View File

@@ -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

View File

@@ -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 {

View File

@@ -8,6 +8,8 @@ import (
"context" "context"
"time" "time"
"quyun/v2/pkg/consts"
"go.ipao.vip/gen" "go.ipao.vip/gen"
) )
@@ -20,7 +22,7 @@ type ContentAccess 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"`
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"`

View File

@@ -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")

View File

@@ -8,6 +8,8 @@ import (
"context" "context"
"time" "time"
"quyun/v2/pkg/consts"
"go.ipao.vip/gen" "go.ipao.vip/gen"
) )
@@ -20,10 +22,11 @@ type ContentAsset 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"`
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

View File

@@ -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 {

View File

@@ -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

View File

@@ -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 {

View File

@@ -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"`

View File

@@ -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")

View File

@@ -8,6 +8,8 @@ import (
"context" "context"
"time" "time"
"quyun/v2/pkg/consts"
"go.ipao.vip/gen" "go.ipao.vip/gen"
) )
@@ -19,7 +21,7 @@ type TenantLedger 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"`
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"`

View File

@@ -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")

View File

@@ -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"`
} }

View File

@@ -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")

View File

@@ -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,

View File

@@ -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 "未知类型"
} }