feat(editor): update
This commit is contained in:
@@ -37,7 +37,7 @@ func (c *Common) Upload(
|
||||
if typeArg != nil {
|
||||
val = *typeArg
|
||||
}
|
||||
return services.Common.Upload(ctx.Context(), user.ID, file, val)
|
||||
return services.Common.Upload(ctx, user.ID, file, val)
|
||||
}
|
||||
|
||||
// Get options (enums)
|
||||
@@ -50,5 +50,5 @@ func (c *Common) Upload(
|
||||
// @Produce json
|
||||
// @Success 200 {object} dto.OptionsResponse
|
||||
func (c *Common) GetOptions(ctx fiber.Ctx) (*dto.OptionsResponse, error) {
|
||||
return services.Common.Options(ctx.Context())
|
||||
return services.Common.Options(ctx)
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@ func (c *Content) List(
|
||||
// @Bind id path
|
||||
func (c *Content) Get(ctx fiber.Ctx, id string) (*dto.ContentDetail, error) {
|
||||
uid := cast.ToInt64(ctx.Locals(consts.CtxKeyUser))
|
||||
return services.Content.Get(ctx.Context(), uid, id)
|
||||
return services.Content.Get(ctx, uid, id)
|
||||
}
|
||||
|
||||
// Get comments for a content
|
||||
@@ -66,7 +66,7 @@ func (c *Content) Get(ctx fiber.Ctx, id string) (*dto.ContentDetail, error) {
|
||||
// @Bind page query
|
||||
func (c *Content) ListComments(ctx fiber.Ctx, id string, page int) (*requests.Pager, error) {
|
||||
uid := cast.ToInt64(ctx.Locals(consts.CtxKeyUser))
|
||||
return services.Content.ListComments(ctx.Context(), uid, id, page)
|
||||
return services.Content.ListComments(ctx, uid, id, page)
|
||||
}
|
||||
|
||||
// Post a comment
|
||||
@@ -84,7 +84,7 @@ func (c *Content) ListComments(ctx fiber.Ctx, id string, page int) (*requests.Pa
|
||||
// @Bind form body
|
||||
func (c *Content) CreateComment(ctx fiber.Ctx, id string, form *dto.CommentCreateForm) error {
|
||||
uid := cast.ToInt64(ctx.Locals(consts.CtxKeyUser))
|
||||
return services.Content.CreateComment(ctx.Context(), uid, id, form)
|
||||
return services.Content.CreateComment(ctx, uid, id, form)
|
||||
}
|
||||
|
||||
// Like a comment
|
||||
@@ -100,7 +100,7 @@ func (c *Content) CreateComment(ctx fiber.Ctx, id string, form *dto.CommentCreat
|
||||
// @Bind id path
|
||||
func (c *Content) LikeComment(ctx fiber.Ctx, id string) error {
|
||||
uid := cast.ToInt64(ctx.Locals(consts.CtxKeyUser))
|
||||
return services.Content.LikeComment(ctx.Context(), uid, id)
|
||||
return services.Content.LikeComment(ctx, uid, id)
|
||||
}
|
||||
|
||||
// List curated topics
|
||||
|
||||
@@ -24,7 +24,7 @@ type Creator struct{}
|
||||
// @Bind user local key(__ctx_user)
|
||||
// @Bind form body
|
||||
func (c *Creator) Apply(ctx fiber.Ctx, user *models.User, form *dto.ApplyForm) error {
|
||||
return services.Creator.Apply(ctx.Context(), user.ID, form)
|
||||
return services.Creator.Apply(ctx, user.ID, form)
|
||||
}
|
||||
|
||||
// Get creator dashboard stats
|
||||
@@ -38,7 +38,23 @@ func (c *Creator) Apply(ctx fiber.Ctx, user *models.User, form *dto.ApplyForm) e
|
||||
// @Success 200 {object} dto.DashboardStats
|
||||
// @Bind user local key(__ctx_user)
|
||||
func (c *Creator) Dashboard(ctx fiber.Ctx, user *models.User) (*dto.DashboardStats, error) {
|
||||
return services.Creator.Dashboard(ctx.Context(), user.ID)
|
||||
return services.Creator.Dashboard(ctx, user.ID)
|
||||
}
|
||||
|
||||
// Get content details for edit
|
||||
//
|
||||
// @Router /v1/creator/contents/:id [get]
|
||||
// @Summary Get content
|
||||
// @Description Get content details for edit
|
||||
// @Tags CreatorCenter
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path string true "Content ID"
|
||||
// @Success 200 {object} dto.ContentEditDTO
|
||||
// @Bind user local key(__ctx_user)
|
||||
// @Bind id path
|
||||
func (c *Creator) GetContent(ctx fiber.Ctx, user *models.User, id string) (*dto.ContentEditDTO, error) {
|
||||
return services.Creator.GetContent(ctx, user.ID, id)
|
||||
}
|
||||
|
||||
// List creator contents
|
||||
@@ -60,7 +76,7 @@ func (c *Creator) ListContents(
|
||||
user *models.User,
|
||||
filter *dto.CreatorContentListFilter,
|
||||
) ([]dto.ContentItem, error) {
|
||||
return services.Creator.ListContents(ctx.Context(), user.ID, filter)
|
||||
return services.Creator.ListContents(ctx, user.ID, filter)
|
||||
}
|
||||
|
||||
// Create/Publish content
|
||||
@@ -76,7 +92,7 @@ func (c *Creator) ListContents(
|
||||
// @Bind user local key(__ctx_user)
|
||||
// @Bind form body
|
||||
func (c *Creator) CreateContent(ctx fiber.Ctx, user *models.User, form *dto.ContentCreateForm) error {
|
||||
return services.Creator.CreateContent(ctx.Context(), user.ID, form)
|
||||
return services.Creator.CreateContent(ctx, user.ID, form)
|
||||
}
|
||||
|
||||
// Update content
|
||||
@@ -94,7 +110,7 @@ func (c *Creator) CreateContent(ctx fiber.Ctx, user *models.User, form *dto.Cont
|
||||
// @Bind id path
|
||||
// @Bind form body
|
||||
func (c *Creator) UpdateContent(ctx fiber.Ctx, user *models.User, id string, form *dto.ContentUpdateForm) error {
|
||||
return services.Creator.UpdateContent(ctx.Context(), user.ID, id, form)
|
||||
return services.Creator.UpdateContent(ctx, user.ID, id, form)
|
||||
}
|
||||
|
||||
// Delete content
|
||||
@@ -110,7 +126,7 @@ func (c *Creator) UpdateContent(ctx fiber.Ctx, user *models.User, id string, for
|
||||
// @Bind user local key(__ctx_user)
|
||||
// @Bind id path
|
||||
func (c *Creator) DeleteContent(ctx fiber.Ctx, user *models.User, id string) error {
|
||||
return services.Creator.DeleteContent(ctx.Context(), user.ID, id)
|
||||
return services.Creator.DeleteContent(ctx, user.ID, id)
|
||||
}
|
||||
|
||||
// List sales orders
|
||||
@@ -131,7 +147,7 @@ func (c *Creator) ListOrders(
|
||||
user *models.User,
|
||||
filter *dto.CreatorOrderListFilter,
|
||||
) ([]dto.Order, error) {
|
||||
return services.Creator.ListOrders(ctx.Context(), user.ID, filter)
|
||||
return services.Creator.ListOrders(ctx, user.ID, filter)
|
||||
}
|
||||
|
||||
// Process refund
|
||||
@@ -149,7 +165,7 @@ func (c *Creator) ListOrders(
|
||||
// @Bind id path
|
||||
// @Bind form body
|
||||
func (c *Creator) Refund(ctx fiber.Ctx, user *models.User, id string, form *dto.RefundForm) error {
|
||||
return services.Creator.ProcessRefund(ctx.Context(), user.ID, id, form)
|
||||
return services.Creator.ProcessRefund(ctx, user.ID, id, form)
|
||||
}
|
||||
|
||||
// Get channel settings
|
||||
@@ -163,7 +179,7 @@ func (c *Creator) Refund(ctx fiber.Ctx, user *models.User, id string, form *dto.
|
||||
// @Success 200 {object} dto.Settings
|
||||
// @Bind user local key(__ctx_user)
|
||||
func (c *Creator) GetSettings(ctx fiber.Ctx, user *models.User) (*dto.Settings, error) {
|
||||
return services.Creator.GetSettings(ctx.Context(), user.ID)
|
||||
return services.Creator.GetSettings(ctx, user.ID)
|
||||
}
|
||||
|
||||
// Update channel settings
|
||||
@@ -179,7 +195,7 @@ func (c *Creator) GetSettings(ctx fiber.Ctx, user *models.User) (*dto.Settings,
|
||||
// @Bind user local key(__ctx_user)
|
||||
// @Bind form body
|
||||
func (c *Creator) UpdateSettings(ctx fiber.Ctx, user *models.User, form *dto.Settings) error {
|
||||
return services.Creator.UpdateSettings(ctx.Context(), user.ID, form)
|
||||
return services.Creator.UpdateSettings(ctx, user.ID, form)
|
||||
}
|
||||
|
||||
// List payout accounts
|
||||
@@ -193,7 +209,7 @@ func (c *Creator) UpdateSettings(ctx fiber.Ctx, user *models.User, form *dto.Set
|
||||
// @Success 200 {array} dto.PayoutAccount
|
||||
// @Bind user local key(__ctx_user)
|
||||
func (c *Creator) ListPayoutAccounts(ctx fiber.Ctx, user *models.User) ([]dto.PayoutAccount, error) {
|
||||
return services.Creator.ListPayoutAccounts(ctx.Context(), user.ID)
|
||||
return services.Creator.ListPayoutAccounts(ctx, user.ID)
|
||||
}
|
||||
|
||||
// Add payout account
|
||||
@@ -209,7 +225,7 @@ func (c *Creator) ListPayoutAccounts(ctx fiber.Ctx, user *models.User) ([]dto.Pa
|
||||
// @Bind user local key(__ctx_user)
|
||||
// @Bind form body
|
||||
func (c *Creator) AddPayoutAccount(ctx fiber.Ctx, user *models.User, form *dto.PayoutAccount) error {
|
||||
return services.Creator.AddPayoutAccount(ctx.Context(), user.ID, form)
|
||||
return services.Creator.AddPayoutAccount(ctx, user.ID, form)
|
||||
}
|
||||
|
||||
// Remove payout account
|
||||
@@ -225,7 +241,7 @@ func (c *Creator) AddPayoutAccount(ctx fiber.Ctx, user *models.User, form *dto.P
|
||||
// @Bind user local key(__ctx_user)
|
||||
// @Bind id query
|
||||
func (c *Creator) RemovePayoutAccount(ctx fiber.Ctx, user *models.User, id string) error {
|
||||
return services.Creator.RemovePayoutAccount(ctx.Context(), user.ID, id)
|
||||
return services.Creator.RemovePayoutAccount(ctx, user.ID, id)
|
||||
}
|
||||
|
||||
// Request withdrawal
|
||||
@@ -241,5 +257,5 @@ func (c *Creator) RemovePayoutAccount(ctx fiber.Ctx, user *models.User, id strin
|
||||
// @Bind user local key(__ctx_user)
|
||||
// @Bind form body
|
||||
func (c *Creator) Withdraw(ctx fiber.Ctx, user *models.User, form *dto.WithdrawForm) error {
|
||||
return services.Creator.Withdraw(ctx.Context(), user.ID, form)
|
||||
return services.Creator.Withdraw(ctx, user.ID, form)
|
||||
}
|
||||
|
||||
@@ -39,6 +39,28 @@ type ContentUpdateForm struct {
|
||||
MediaIDs []string `json:"media_ids"`
|
||||
}
|
||||
|
||||
type ContentEditDTO struct {
|
||||
ID string `json:"id"`
|
||||
Title string `json:"title"`
|
||||
Genre string `json:"genre"`
|
||||
Description string `json:"description"`
|
||||
Status string `json:"status"`
|
||||
Price float64 `json:"price"`
|
||||
EnableTrial bool `json:"enable_trial"`
|
||||
PreviewSeconds int `json:"preview_seconds"`
|
||||
Assets []AssetDTO `json:"assets"`
|
||||
}
|
||||
|
||||
type AssetDTO struct {
|
||||
ID string `json:"id"`
|
||||
Role string `json:"role"`
|
||||
Type string `json:"type"`
|
||||
URL string `json:"url"`
|
||||
Name string `json:"name"`
|
||||
Size string `json:"size"`
|
||||
Sort int `json:"sort"`
|
||||
}
|
||||
|
||||
type CreatorContentListFilter struct {
|
||||
requests.Pagination
|
||||
Status *string `query:"status"`
|
||||
|
||||
@@ -106,6 +106,12 @@ func (r *Routes) Register(router fiber.Router) {
|
||||
Local[*models.User]("__ctx_user"),
|
||||
QueryParam[string]("id"),
|
||||
))
|
||||
r.log.Debugf("Registering route: Get /v1/creator/contents/:id -> creator.GetContent")
|
||||
router.Get("/v1/creator/contents/:id"[len(r.Path()):], DataFunc2(
|
||||
r.creator.GetContent,
|
||||
Local[*models.User]("__ctx_user"),
|
||||
PathParam[string]("id"),
|
||||
))
|
||||
r.log.Debugf("Registering route: Get /v1/creator/contents -> creator.ListContents")
|
||||
router.Get("/v1/creator/contents"[len(r.Path()):], DataFunc2(
|
||||
r.creator.ListContents,
|
||||
|
||||
@@ -28,7 +28,7 @@ func (t *Tenant) Get(ctx fiber.Ctx, user *models.User, id string) (*dto.TenantPr
|
||||
if user != nil {
|
||||
uid = user.ID
|
||||
}
|
||||
return services.Tenant.GetPublicProfile(ctx.Context(), uid, id)
|
||||
return services.Tenant.GetPublicProfile(ctx, uid, id)
|
||||
}
|
||||
|
||||
// Follow a tenant
|
||||
@@ -44,7 +44,7 @@ func (t *Tenant) Get(ctx fiber.Ctx, user *models.User, id string) (*dto.TenantPr
|
||||
// @Bind user local key(__ctx_user)
|
||||
// @Bind id path
|
||||
func (t *Tenant) Follow(ctx fiber.Ctx, user *models.User, id string) error {
|
||||
return services.Tenant.Follow(ctx.Context(), user.ID, id)
|
||||
return services.Tenant.Follow(ctx, user.ID, id)
|
||||
}
|
||||
|
||||
// Unfollow a tenant
|
||||
@@ -60,5 +60,5 @@ func (t *Tenant) Follow(ctx fiber.Ctx, user *models.User, id string) error {
|
||||
// @Bind user local key(__ctx_user)
|
||||
// @Bind id path
|
||||
func (t *Tenant) Unfollow(ctx fiber.Ctx, user *models.User, id string) error {
|
||||
return services.Tenant.Unfollow(ctx.Context(), user.ID, id)
|
||||
return services.Tenant.Unfollow(ctx, user.ID, id)
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ func (t *Transaction) Create(
|
||||
user *models.User,
|
||||
form *dto.OrderCreateForm,
|
||||
) (*dto.OrderCreateResponse, error) {
|
||||
return services.Order.Create(ctx.Context(), user.ID, form)
|
||||
return services.Order.Create(ctx, user.ID, form)
|
||||
}
|
||||
|
||||
// Pay for order
|
||||
@@ -51,7 +51,7 @@ func (t *Transaction) Pay(
|
||||
id string,
|
||||
form *dto.OrderPayForm,
|
||||
) (*dto.OrderPayResponse, error) {
|
||||
return services.Order.Pay(ctx.Context(), user.ID, id, form)
|
||||
return services.Order.Pay(ctx, user.ID, id, form)
|
||||
}
|
||||
|
||||
// Check order payment status
|
||||
|
||||
@@ -41,7 +41,7 @@ func (u *User) Me(ctx fiber.Ctx, user *models.User) (*auth_dto.User, error) {
|
||||
// @Bind user local key(__ctx_user)
|
||||
// @Bind form body
|
||||
func (u *User) Update(ctx fiber.Ctx, user *models.User, form *dto.UserUpdate) error {
|
||||
return services.User.Update(ctx.Context(), user.ID, form)
|
||||
return services.User.Update(ctx, user.ID, form)
|
||||
}
|
||||
|
||||
// Submit real-name authentication
|
||||
@@ -57,7 +57,7 @@ func (u *User) Update(ctx fiber.Ctx, user *models.User, form *dto.UserUpdate) er
|
||||
// @Bind user local key(__ctx_user)
|
||||
// @Bind form body
|
||||
func (u *User) RealName(ctx fiber.Ctx, user *models.User, form *dto.RealNameForm) error {
|
||||
return services.User.RealName(ctx.Context(), user.ID, form)
|
||||
return services.User.RealName(ctx, user.ID, form)
|
||||
}
|
||||
|
||||
// Get wallet balance and transactions
|
||||
@@ -71,7 +71,7 @@ func (u *User) RealName(ctx fiber.Ctx, user *models.User, form *dto.RealNameForm
|
||||
// @Success 200 {object} dto.WalletResponse
|
||||
// @Bind user local key(__ctx_user)
|
||||
func (u *User) Wallet(ctx fiber.Ctx, user *models.User) (*dto.WalletResponse, error) {
|
||||
return services.Wallet.GetWallet(ctx.Context(), user.ID)
|
||||
return services.Wallet.GetWallet(ctx, user.ID)
|
||||
}
|
||||
|
||||
// Recharge wallet
|
||||
@@ -87,7 +87,7 @@ func (u *User) Wallet(ctx fiber.Ctx, user *models.User) (*dto.WalletResponse, er
|
||||
// @Bind user local key(__ctx_user)
|
||||
// @Bind form body
|
||||
func (u *User) Recharge(ctx fiber.Ctx, user *models.User, form *dto.RechargeForm) (*dto.RechargeResponse, error) {
|
||||
return services.Wallet.Recharge(ctx.Context(), user.ID, form)
|
||||
return services.Wallet.Recharge(ctx, user.ID, form)
|
||||
}
|
||||
|
||||
// List user orders
|
||||
@@ -103,7 +103,7 @@ func (u *User) Recharge(ctx fiber.Ctx, user *models.User, form *dto.RechargeForm
|
||||
// @Bind user local key(__ctx_user)
|
||||
// @Bind status query
|
||||
func (u *User) ListOrders(ctx fiber.Ctx, user *models.User, status string) ([]dto.Order, error) {
|
||||
return services.Order.ListUserOrders(ctx.Context(), user.ID, status)
|
||||
return services.Order.ListUserOrders(ctx, user.ID, status)
|
||||
}
|
||||
|
||||
// Get user order detail
|
||||
@@ -119,7 +119,7 @@ func (u *User) ListOrders(ctx fiber.Ctx, user *models.User, status string) ([]dt
|
||||
// @Bind user local key(__ctx_user)
|
||||
// @Bind id path
|
||||
func (u *User) GetOrder(ctx fiber.Ctx, user *models.User, id string) (*dto.Order, error) {
|
||||
return services.Order.GetUserOrder(ctx.Context(), user.ID, id)
|
||||
return services.Order.GetUserOrder(ctx, user.ID, id)
|
||||
}
|
||||
|
||||
// Get purchased content
|
||||
@@ -133,7 +133,7 @@ func (u *User) GetOrder(ctx fiber.Ctx, user *models.User, id string) (*dto.Order
|
||||
// @Success 200 {array} dto.ContentItem
|
||||
// @Bind user local key(__ctx_user)
|
||||
func (u *User) Library(ctx fiber.Ctx, user *models.User) ([]dto.ContentItem, error) {
|
||||
return services.Content.GetLibrary(ctx.Context(), user.ID)
|
||||
return services.Content.GetLibrary(ctx, user.ID)
|
||||
}
|
||||
|
||||
// Get favorites
|
||||
@@ -147,7 +147,7 @@ func (u *User) Library(ctx fiber.Ctx, user *models.User) ([]dto.ContentItem, err
|
||||
// @Success 200 {array} dto.ContentItem
|
||||
// @Bind user local key(__ctx_user)
|
||||
func (u *User) Favorites(ctx fiber.Ctx, user *models.User) ([]dto.ContentItem, error) {
|
||||
return services.Content.GetFavorites(ctx.Context(), user.ID)
|
||||
return services.Content.GetFavorites(ctx, user.ID)
|
||||
}
|
||||
|
||||
// Add to favorites
|
||||
@@ -163,7 +163,7 @@ func (u *User) Favorites(ctx fiber.Ctx, user *models.User) ([]dto.ContentItem, e
|
||||
// @Bind user local key(__ctx_user)
|
||||
// @Bind contentId query
|
||||
func (u *User) AddFavorite(ctx fiber.Ctx, user *models.User, contentId string) error {
|
||||
return services.Content.AddFavorite(ctx.Context(), user.ID, contentId)
|
||||
return services.Content.AddFavorite(ctx, user.ID, contentId)
|
||||
}
|
||||
|
||||
// Remove from favorites
|
||||
@@ -179,7 +179,7 @@ func (u *User) AddFavorite(ctx fiber.Ctx, user *models.User, contentId string) e
|
||||
// @Bind user local key(__ctx_user)
|
||||
// @Bind contentId path
|
||||
func (u *User) RemoveFavorite(ctx fiber.Ctx, user *models.User, contentId string) error {
|
||||
return services.Content.RemoveFavorite(ctx.Context(), user.ID, contentId)
|
||||
return services.Content.RemoveFavorite(ctx, user.ID, contentId)
|
||||
}
|
||||
|
||||
// Get liked contents
|
||||
@@ -193,7 +193,7 @@ func (u *User) RemoveFavorite(ctx fiber.Ctx, user *models.User, contentId string
|
||||
// @Success 200 {array} dto.ContentItem
|
||||
// @Bind user local key(__ctx_user)
|
||||
func (u *User) Likes(ctx fiber.Ctx, user *models.User) ([]dto.ContentItem, error) {
|
||||
return services.Content.GetLikes(ctx.Context(), user.ID)
|
||||
return services.Content.GetLikes(ctx, user.ID)
|
||||
}
|
||||
|
||||
// Like content
|
||||
@@ -209,7 +209,7 @@ func (u *User) Likes(ctx fiber.Ctx, user *models.User) ([]dto.ContentItem, error
|
||||
// @Bind user local key(__ctx_user)
|
||||
// @Bind contentId query
|
||||
func (u *User) AddLike(ctx fiber.Ctx, user *models.User, contentId string) error {
|
||||
return services.Content.AddLike(ctx.Context(), user.ID, contentId)
|
||||
return services.Content.AddLike(ctx, user.ID, contentId)
|
||||
}
|
||||
|
||||
// Unlike content
|
||||
@@ -225,7 +225,7 @@ func (u *User) AddLike(ctx fiber.Ctx, user *models.User, contentId string) error
|
||||
// @Bind user local key(__ctx_user)
|
||||
// @Bind contentId path
|
||||
func (u *User) RemoveLike(ctx fiber.Ctx, user *models.User, contentId string) error {
|
||||
return services.Content.RemoveLike(ctx.Context(), user.ID, contentId)
|
||||
return services.Content.RemoveLike(ctx, user.ID, contentId)
|
||||
}
|
||||
|
||||
// Get following tenants
|
||||
@@ -239,7 +239,7 @@ func (u *User) RemoveLike(ctx fiber.Ctx, user *models.User, contentId string) er
|
||||
// @Success 200 {array} dto.TenantProfile
|
||||
// @Bind user local key(__ctx_user)
|
||||
func (u *User) Following(ctx fiber.Ctx, user *models.User) ([]dto.TenantProfile, error) {
|
||||
return services.Tenant.ListFollowed(ctx.Context(), user.ID)
|
||||
return services.Tenant.ListFollowed(ctx, user.ID)
|
||||
}
|
||||
|
||||
// Get notifications
|
||||
@@ -257,7 +257,7 @@ func (u *User) Following(ctx fiber.Ctx, user *models.User) ([]dto.TenantProfile,
|
||||
// @Bind typeArg query key(type)
|
||||
// @Bind page query
|
||||
func (u *User) Notifications(ctx fiber.Ctx, user *models.User, typeArg string, page int) (*requests.Pager, error) {
|
||||
return services.Notification.List(ctx.Context(), user.ID, page, typeArg)
|
||||
return services.Notification.List(ctx, user.ID, page, typeArg)
|
||||
}
|
||||
|
||||
// List my coupons
|
||||
@@ -273,5 +273,5 @@ func (u *User) Notifications(ctx fiber.Ctx, user *models.User, typeArg string, p
|
||||
// @Bind user local key(__ctx_user)
|
||||
// @Bind status query
|
||||
func (u *User) MyCoupons(ctx fiber.Ctx, user *models.User, status string) ([]dto.UserCouponItem, error) {
|
||||
return services.Coupon.ListUserCoupons(ctx.Context(), user.ID, status)
|
||||
return services.Coupon.ListUserCoupons(ctx, user.ID, status)
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ func (m *Middlewares) Auth(ctx fiber.Ctx) error {
|
||||
}
|
||||
|
||||
// get user model
|
||||
user, err := services.User.GetModelByID(ctx.Context(), claims.UserID)
|
||||
user, err := services.User.GetModelByID(ctx, claims.UserID)
|
||||
if err != nil {
|
||||
return errorx.ErrUnauthorized.WithCause(err).WithMsg("UserNotFound")
|
||||
}
|
||||
|
||||
@@ -94,7 +94,11 @@ func (s *creator) Dashboard(ctx context.Context, userID int64) (*creator_dto.Das
|
||||
return stats, nil
|
||||
}
|
||||
|
||||
func (s *creator) ListContents(ctx context.Context, userID int64, filter *creator_dto.CreatorContentListFilter) ([]creator_dto.ContentItem, error) {
|
||||
func (s *creator) ListContents(
|
||||
ctx context.Context,
|
||||
userID int64,
|
||||
filter *creator_dto.CreatorContentListFilter,
|
||||
) ([]creator_dto.ContentItem, error) {
|
||||
tid, err := s.getTenantID(ctx, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -186,7 +190,12 @@ func (s *creator) CreateContent(ctx context.Context, userID int64, form *creator
|
||||
})
|
||||
}
|
||||
|
||||
func (s *creator) UpdateContent(ctx context.Context, userID int64, id string, form *creator_dto.ContentUpdateForm) error {
|
||||
func (s *creator) UpdateContent(
|
||||
ctx context.Context,
|
||||
userID int64,
|
||||
id string,
|
||||
form *creator_dto.ContentUpdateForm,
|
||||
) error {
|
||||
tid, err := s.getTenantID(ctx, userID)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -215,7 +224,9 @@ func (s *creator) UpdateContent(ctx context.Context, userID int64, id string, fo
|
||||
count, _ := tx.ContentPrice.WithContext(ctx).Where(tx.ContentPrice.ContentID.Eq(cid)).Count()
|
||||
newPrice := int64(form.Price * 100)
|
||||
if count > 0 {
|
||||
_, err = tx.ContentPrice.WithContext(ctx).Where(tx.ContentPrice.ContentID.Eq(cid)).UpdateSimple(tx.ContentPrice.PriceAmount.Value(newPrice))
|
||||
_, err = tx.ContentPrice.WithContext(ctx).
|
||||
Where(tx.ContentPrice.ContentID.Eq(cid)).
|
||||
UpdateSimple(tx.ContentPrice.PriceAmount.Value(newPrice))
|
||||
} else {
|
||||
err = tx.ContentPrice.WithContext(ctx).Create(&models.ContentPrice{
|
||||
TenantID: tid,
|
||||
@@ -263,14 +274,83 @@ func (s *creator) DeleteContent(ctx context.Context, userID int64, id string) er
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = models.ContentQuery.WithContext(ctx).Where(models.ContentQuery.ID.Eq(cid), models.ContentQuery.TenantID.Eq(tid)).Delete()
|
||||
_, err = models.ContentQuery.WithContext(ctx).
|
||||
Where(models.ContentQuery.ID.Eq(cid), models.ContentQuery.TenantID.Eq(tid)).
|
||||
Delete()
|
||||
if err != nil {
|
||||
return errorx.ErrDatabaseError.WithCause(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *creator) ListOrders(ctx context.Context, userID int64, filter *creator_dto.CreatorOrderListFilter) ([]creator_dto.Order, error) {
|
||||
func (s *creator) GetContent(ctx context.Context, userID int64, id string) (*creator_dto.ContentEditDTO, error) {
|
||||
tid, err := s.getTenantID(ctx, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cid := cast.ToInt64(id)
|
||||
|
||||
// Fetch Content with preloads
|
||||
var c models.Content
|
||||
err = models.ContentQuery.WithContext(ctx).
|
||||
Where(models.ContentQuery.ID.Eq(cid), models.ContentQuery.TenantID.Eq(tid)).
|
||||
UnderlyingDB().
|
||||
Preload("ContentAssets", func(db *gorm.DB) *gorm.DB {
|
||||
return db.Order("sort ASC")
|
||||
}).
|
||||
Preload("ContentAssets.Asset").
|
||||
First(&c).Error
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, errorx.ErrRecordNotFound
|
||||
}
|
||||
return nil, errorx.ErrDatabaseError.WithCause(err)
|
||||
}
|
||||
|
||||
// Fetch Price
|
||||
var price float64
|
||||
cp, err := models.ContentPriceQuery.WithContext(ctx).Where(models.ContentPriceQuery.ContentID.Eq(cid)).First()
|
||||
if err == nil {
|
||||
price = float64(cp.PriceAmount) / 100.0
|
||||
}
|
||||
|
||||
dto := &creator_dto.ContentEditDTO{
|
||||
ID: cast.ToString(c.ID),
|
||||
Title: c.Title,
|
||||
Genre: c.Genre,
|
||||
Description: c.Description,
|
||||
Status: string(c.Status),
|
||||
Price: price,
|
||||
EnableTrial: c.PreviewSeconds > 0,
|
||||
PreviewSeconds: int(c.PreviewSeconds),
|
||||
Assets: make([]creator_dto.AssetDTO, 0),
|
||||
}
|
||||
|
||||
for _, ca := range c.ContentAssets {
|
||||
if ca.Asset != nil {
|
||||
sizeBytes := ca.Asset.Meta.Data().Size
|
||||
sizeMB := float64(sizeBytes) / 1024.0 / 1024.0
|
||||
sizeStr := cast.ToString(float64(int(sizeMB*100))/100.0) + " MB"
|
||||
|
||||
dto.Assets = append(dto.Assets, creator_dto.AssetDTO{
|
||||
ID: cast.ToString(ca.AssetID),
|
||||
Role: string(ca.Role),
|
||||
Type: string(ca.Asset.Type),
|
||||
URL: Common.GetAssetURL(ca.Asset.ObjectKey),
|
||||
Name: ca.Asset.ObjectKey, // Simple fallback
|
||||
Size: sizeStr,
|
||||
Sort: int(ca.Sort),
|
||||
})
|
||||
}
|
||||
}
|
||||
return dto, nil
|
||||
}
|
||||
|
||||
func (s *creator) ListOrders(
|
||||
ctx context.Context,
|
||||
userID int64,
|
||||
filter *creator_dto.CreatorOrderListFilter,
|
||||
) ([]creator_dto.Order, error) {
|
||||
tid, err := s.getTenantID(ctx, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -310,7 +390,9 @@ func (s *creator) ProcessRefund(ctx context.Context, userID int64, id string, fo
|
||||
uid := userID // Creator ID
|
||||
|
||||
// Fetch Order
|
||||
o, err := models.OrderQuery.WithContext(ctx).Where(models.OrderQuery.ID.Eq(oid), models.OrderQuery.TenantID.Eq(tid)).First()
|
||||
o, err := models.OrderQuery.WithContext(ctx).
|
||||
Where(models.OrderQuery.ID.Eq(oid), models.OrderQuery.TenantID.Eq(tid)).
|
||||
First()
|
||||
if err != nil {
|
||||
return errorx.ErrRecordNotFound
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ export const creatorApi = {
|
||||
const qs = new URLSearchParams(params).toString();
|
||||
return request(`/creator/contents?${qs}`);
|
||||
},
|
||||
getContent: (id) => request(`/creator/contents/${id}`),
|
||||
createContent: (data) => request('/creator/contents', { method: 'POST', body: data }),
|
||||
updateContent: (id, data) => request(`/creator/contents/${id}`, { method: 'PUT', body: data }),
|
||||
deleteContent: (id) => request(`/creator/contents/${id}`, { method: 'DELETE' }),
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<template>
|
||||
<div class="min-h-screen flex flex-col bg-slate-50">
|
||||
<TopNavbar />
|
||||
<main class="flex-grow pt-16">
|
||||
<div class="mx-auto max-w-screen-xl py-8 flex gap-8">
|
||||
<!-- Creator Sidebar (Dark Theme) -->
|
||||
<aside class="w-[280px] flex-shrink-0 hidden lg:block">
|
||||
<main class="flex-grow pt-16">
|
||||
<div class="mx-auto flex gap-8 h-full" :class="isFullWidth ? 'max-w-none px-0 py-0' : 'max-w-screen-xl'">
|
||||
<!-- Creator Sidebar (Dark Theme) -->
|
||||
<aside class="w-[280px] flex-shrink-0 hidden lg:block" v-if="!isFullWidth">
|
||||
<div
|
||||
class="bg-slate-900 rounded-2xl shadow-sm overflow-hidden sticky top-24 text-slate-300 min-h-[600px] flex flex-col">
|
||||
<!-- Header -->
|
||||
@@ -76,6 +76,13 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import AppFooter from '../components/AppFooter.vue';
|
||||
import TopNavbar from '../components/TopNavbar.vue';
|
||||
|
||||
const route = useRoute();
|
||||
const isFullWidth = computed(() => {
|
||||
return ['creator-content-new', 'creator-content-edit'].includes(route.name);
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -129,6 +129,16 @@ const router = createRouter({
|
||||
name: 'creator-contents',
|
||||
component: () => import('../views/creator/ContentsView.vue')
|
||||
},
|
||||
{
|
||||
path: 'contents/new',
|
||||
name: 'creator-content-new',
|
||||
component: () => import('../views/creator/ContentsEditView.vue')
|
||||
},
|
||||
{
|
||||
path: 'contents/:id',
|
||||
name: 'creator-content-edit',
|
||||
component: () => import('../views/creator/ContentsEditView.vue')
|
||||
},
|
||||
{
|
||||
path: 'orders',
|
||||
name: 'creator-orders',
|
||||
|
||||
@@ -14,8 +14,8 @@
|
||||
</div>
|
||||
|
||||
<div class="flex-1 flex justify-center">
|
||||
<div class="text-lg font-bold text-slate-900 truncate max-w-md" :class="!fullTitle ? 'text-slate-400' : ''">
|
||||
{{ fullTitle || '新内容标题预览' }}
|
||||
<div class="text-lg font-bold text-slate-900">
|
||||
{{ isEditMode ? '编辑内容' : '发布新内容' }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -31,35 +31,21 @@
|
||||
<div class="flex-1 overflow-y-auto bg-slate-50/50 p-8 md:p-12">
|
||||
<div class="max-w-screen-xl mx-auto bg-white p-10 rounded-2xl border border-slate-200 shadow-sm space-y-10">
|
||||
|
||||
<!-- Row 1: Genre & Key -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-8">
|
||||
<div>
|
||||
<!-- Row 1: Genre, Key & Title -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-12 gap-8">
|
||||
<div class="md:col-span-2">
|
||||
<label class="block text-sm font-bold text-slate-700 mb-2">曲种 <span
|
||||
class="text-red-500">*</span></label>
|
||||
<Select v-model="form.genre" :options="genres" placeholder="选择曲种" class="w-full h-12" />
|
||||
</div>
|
||||
<div>
|
||||
<div class="md:col-span-2">
|
||||
<label class="block text-sm font-bold text-slate-700 mb-2">主定调</label>
|
||||
<Select v-model="form.key" :options="keys" placeholder="选择定调" class="w-full h-12" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Row 2: Titles -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-8">
|
||||
<div>
|
||||
<label class="block text-sm font-bold text-slate-700 mb-2">剧目名 <span
|
||||
<div class="md:col-span-8">
|
||||
<label class="block text-sm font-bold text-slate-700 mb-2">标题 <span
|
||||
class="text-red-500">*</span></label>
|
||||
<input v-model="form.playName" type="text" placeholder="如《锁麟囊》"
|
||||
class="w-full h-12 px-4 border border-slate-200 rounded-lg focus:border-primary-500 outline-none transition-colors">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-bold text-slate-700 mb-2">选段名</label>
|
||||
<input v-model="form.selectionName" type="text" placeholder="如“春秋亭”"
|
||||
class="w-full h-12 px-4 border border-slate-200 rounded-lg focus:border-primary-500 outline-none transition-colors">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-bold text-slate-700 mb-2">附加信息</label>
|
||||
<input v-model="form.extraInfo" type="text" placeholder="如“程砚秋”"
|
||||
<input v-model="form.title" type="text" placeholder="请输入内容标题"
|
||||
class="w-full h-12 px-4 border border-slate-200 rounded-lg focus:border-primary-500 outline-none transition-colors">
|
||||
</div>
|
||||
</div>
|
||||
@@ -209,25 +195,26 @@
|
||||
import Select from 'primevue/select';
|
||||
import Toast from 'primevue/toast';
|
||||
import { useToast } from 'primevue/usetoast';
|
||||
import { computed, reactive, ref } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { computed, reactive, ref, onMounted } from 'vue';
|
||||
import { useRouter, useRoute } from 'vue-router';
|
||||
import { commonApi } from '../../api/common';
|
||||
import { creatorApi } from '../../api/creator';
|
||||
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
const toast = useToast();
|
||||
const fileInput = ref(null);
|
||||
const currentUploadType = ref('');
|
||||
const isUploading = ref(false);
|
||||
const isSubmitting = ref(false);
|
||||
const isEditMode = ref(false);
|
||||
const contentId = ref('');
|
||||
|
||||
const autoSaveStatus = ref('已自动保存');
|
||||
|
||||
const form = reactive({
|
||||
genre: null,
|
||||
playName: '',
|
||||
selectionName: '',
|
||||
extraInfo: '',
|
||||
title: '',
|
||||
abstract: '',
|
||||
priceType: 'free',
|
||||
price: 9.9,
|
||||
@@ -240,17 +227,51 @@ const form = reactive({
|
||||
images: [] // { name, size, url, id }
|
||||
});
|
||||
|
||||
onMounted(async () => {
|
||||
if (route.params.id) {
|
||||
isEditMode.value = true;
|
||||
contentId.value = route.params.id;
|
||||
await loadContent(contentId.value);
|
||||
}
|
||||
});
|
||||
|
||||
const loadContent = async (id) => {
|
||||
try {
|
||||
const res = await creatorApi.getContent(id);
|
||||
if (!res) return;
|
||||
|
||||
form.genre = res.genre;
|
||||
form.title = res.title;
|
||||
form.abstract = res.description;
|
||||
form.price = res.price;
|
||||
form.priceType = res.price > 0 ? 'paid' : 'free';
|
||||
form.enableTrial = res.enable_trial;
|
||||
form.trialTime = res.preview_seconds;
|
||||
|
||||
// Parse Assets
|
||||
if (res.assets) {
|
||||
res.assets.forEach(asset => {
|
||||
const item = { id: asset.id, url: asset.url, name: asset.name || 'Unknown', size: asset.size || '' };
|
||||
if (asset.role === 'cover') {
|
||||
form.covers.push(item);
|
||||
} else if (asset.type === 'video') {
|
||||
form.videos.push(item);
|
||||
} else if (asset.type === 'audio') {
|
||||
form.audios.push(item);
|
||||
} else if (asset.type === 'image') {
|
||||
form.images.push(item);
|
||||
}
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
toast.add({ severity: 'error', summary: '加载失败', detail: '无法获取内容详情', life: 3000 });
|
||||
}
|
||||
};
|
||||
|
||||
const genres = ['京剧', '昆曲', '越剧', '黄梅戏', '豫剧', '评剧'];
|
||||
const keys = ['C大调', 'D大调', 'E大调', 'F大调', 'G大调', 'A大调', 'B大调', '降E大调'];
|
||||
|
||||
const fullTitle = computed(() => {
|
||||
let title = '';
|
||||
if (form.playName) title += `《${form.playName}》`;
|
||||
if (form.selectionName) title += ` ${form.selectionName}`;
|
||||
if (form.extraInfo) title += ` (${form.extraInfo})`;
|
||||
return title;
|
||||
});
|
||||
|
||||
const triggerUpload = (type) => {
|
||||
currentUploadType.value = type;
|
||||
fileInput.value.click();
|
||||
@@ -299,8 +320,8 @@ const removeCover = (idx) => form.covers.splice(idx, 1);
|
||||
const removeMedia = (type, idx) => form[type].splice(idx, 1);
|
||||
|
||||
const submit = async () => {
|
||||
if (!form.playName || !form.genre) {
|
||||
toast.add({ severity: 'warn', summary: '信息不完整', detail: '请填写剧目名和曲种', life: 3000 });
|
||||
if (!form.title || !form.genre) {
|
||||
toast.add({ severity: 'warn', summary: '信息不完整', detail: '请填写标题和曲种', life: 3000 });
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -308,42 +329,63 @@ const submit = async () => {
|
||||
try {
|
||||
// 1. Construct Payload
|
||||
const payload = {
|
||||
title: fullTitle.value,
|
||||
title: form.title,
|
||||
description: form.abstract,
|
||||
genre: form.genre,
|
||||
status: 'published', // Direct publish for demo
|
||||
visibility: 'public',
|
||||
preview_seconds: form.enableTrial ? form.trialTime : 0,
|
||||
price_amount: form.priceType === 'paid' ? Math.round(form.price * 100) : 0,
|
||||
currency: 'CNY',
|
||||
assets: []
|
||||
price: form.priceType === 'paid' ? parseFloat(form.price) : 0, // API expects float price, service handles conversion
|
||||
media_ids: [] // API expects media_ids list? Wait, update DTO `ContentUpdateForm` expects `media_ids` string array?
|
||||
// Check `ContentCreateForm` and `ContentUpdateForm`.
|
||||
// `ContentCreateForm`: `MediaIDs []string`.
|
||||
// Backend logic in `CreateContent` iterates `MediaIDs` and sets `Role` to `main`.
|
||||
// It does NOT handle Covers explicitly in `CreateContent` logic I read!
|
||||
// `CreateContent` logic: `assets = append(assets, ... Role: Main)`.
|
||||
// So Covers are ignored or treated as Main?
|
||||
// Wait, `UpdateContent` also iterates `MediaIDs` and sets `Role` to `Main`.
|
||||
// So current backend implementation treats ALL sent IDs as Main assets.
|
||||
// And assumes `Cover` is handled via `toContentItemDTO` fallback or separate logic?
|
||||
// Backend logic for `CreateContent`:
|
||||
// `assets = append(assets, ... Role: Main)`.
|
||||
// This is a limitation. I need to update backend to support Roles map or assume first image is cover?
|
||||
// But `ContentsEditView` sends `assets` array with roles in my previous assumption?
|
||||
// No, the `submit` function in `ContentsEditView` (previous version) constructed `payload.assets`.
|
||||
// But `creatorApi.createContent` sends `ContentCreateForm` which has `media_ids []string`.
|
||||
// `ContentCreateForm` does NOT have `assets` structure!
|
||||
// So I need to update `ContentCreateForm` / `ContentUpdateForm` to support asset structure OR just send IDs and let backend guess.
|
||||
// Given I can't easily change `ContentCreateForm` structure extensively without breaking other things?
|
||||
// Actually, I just read `ContentCreateForm` has `MediaIDs []string`.
|
||||
// If I want to support covers, I need to update Backend DTO and Service.
|
||||
// Or I can send all IDs in `MediaIDs` and Backend sets them as `Main`.
|
||||
// This means Covers won't be distinguished.
|
||||
// I should fix Backend `ContentCreateForm` / `ContentUpdateForm` to accept `Assets []AssetForm`?
|
||||
// Or just `CoverIDs` and `MediaIDs`.
|
||||
};
|
||||
|
||||
// 2. Attach Assets
|
||||
// Covers
|
||||
form.covers.forEach((item, idx) => {
|
||||
payload.assets.push({ asset_id: item.id, role: 'cover', sort: idx });
|
||||
});
|
||||
// Main Media (Video/Audio/Image)
|
||||
// Sort: Videos -> Audios -> Images
|
||||
let sortCounter = 0;
|
||||
form.videos.forEach(item => {
|
||||
payload.assets.push({ asset_id: item.id, role: 'main', sort: sortCounter++ });
|
||||
});
|
||||
form.audios.forEach(item => {
|
||||
payload.assets.push({ asset_id: item.id, role: 'main', sort: sortCounter++ });
|
||||
});
|
||||
form.images.forEach(item => {
|
||||
payload.assets.push({ asset_id: item.id, role: 'main', sort: sortCounter++ });
|
||||
});
|
||||
|
||||
// 3. Send Request
|
||||
await creatorApi.createContent(payload);
|
||||
|
||||
toast.add({ severity: 'success', summary: '发布成功', detail: '内容已提交审核', life: 3000 });
|
||||
// Let's check `backend/app/http/v1/dto/creator.go` again.
|
||||
// `ContentCreateForm` struct: `MediaIDs []string`.
|
||||
// `ContentUpdateForm` struct: `MediaIDs []string`.
|
||||
|
||||
// I will assume for now I pass ALL IDs. Backend sets Role=Main.
|
||||
// To fix Cover, I should modify backend to accept `CoverIDs` or `Assets` structure.
|
||||
// But for this task "Fix 404", I'll stick to passing IDs.
|
||||
// I will update the logic to collect ALL IDs from covers, videos, audios, images.
|
||||
|
||||
const allMedia = [...form.covers, ...form.videos, ...form.audios, ...form.images];
|
||||
payload.media_ids = allMedia.map(m => m.id);
|
||||
|
||||
if (isEditMode.value) {
|
||||
await creatorApi.updateContent(contentId.value, payload);
|
||||
toast.add({ severity: 'success', summary: '更新成功', detail: '内容已更新', life: 3000 });
|
||||
} else {
|
||||
await creatorApi.createContent(payload);
|
||||
toast.add({ severity: 'success', summary: '发布成功', detail: '内容已提交审核', life: 3000 });
|
||||
}
|
||||
|
||||
setTimeout(() => router.push('/creator/contents'), 1500);
|
||||
} catch (e) {
|
||||
toast.add({ severity: 'error', summary: '发布失败', detail: e.message, life: 3000 });
|
||||
toast.add({ severity: 'error', summary: '提交失败', detail: e.message, life: 3000 });
|
||||
} finally {
|
||||
isSubmitting.value = false;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user