diff --git a/backend/app/http/v1/common.go b/backend/app/http/v1/common.go index 7c28744..9664a2b 100644 --- a/backend/app/http/v1/common.go +++ b/backend/app/http/v1/common.go @@ -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) } diff --git a/backend/app/http/v1/content.go b/backend/app/http/v1/content.go index acb844d..36f68b5 100644 --- a/backend/app/http/v1/content.go +++ b/backend/app/http/v1/content.go @@ -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 diff --git a/backend/app/http/v1/creator.go b/backend/app/http/v1/creator.go index 28752da..ee1330f 100644 --- a/backend/app/http/v1/creator.go +++ b/backend/app/http/v1/creator.go @@ -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) } diff --git a/backend/app/http/v1/dto/creator.go b/backend/app/http/v1/dto/creator.go index 8582b24..86582aa 100644 --- a/backend/app/http/v1/dto/creator.go +++ b/backend/app/http/v1/dto/creator.go @@ -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"` diff --git a/backend/app/http/v1/routes.gen.go b/backend/app/http/v1/routes.gen.go index 6bbadd3..a14157e 100644 --- a/backend/app/http/v1/routes.gen.go +++ b/backend/app/http/v1/routes.gen.go @@ -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, diff --git a/backend/app/http/v1/tenant.go b/backend/app/http/v1/tenant.go index 380a470..77579fa 100644 --- a/backend/app/http/v1/tenant.go +++ b/backend/app/http/v1/tenant.go @@ -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) } diff --git a/backend/app/http/v1/transaction.go b/backend/app/http/v1/transaction.go index edf3188..8f57a82 100644 --- a/backend/app/http/v1/transaction.go +++ b/backend/app/http/v1/transaction.go @@ -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 diff --git a/backend/app/http/v1/user.go b/backend/app/http/v1/user.go index 5887fe5..bf0db18 100644 --- a/backend/app/http/v1/user.go +++ b/backend/app/http/v1/user.go @@ -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) } diff --git a/backend/app/middlewares/middlewares.go b/backend/app/middlewares/middlewares.go index be546e2..6ef3c39 100644 --- a/backend/app/middlewares/middlewares.go +++ b/backend/app/middlewares/middlewares.go @@ -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") } diff --git a/backend/app/services/creator.go b/backend/app/services/creator.go index 9c07d85..a69f6a7 100644 --- a/backend/app/services/creator.go +++ b/backend/app/services/creator.go @@ -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 } diff --git a/frontend/portal/src/api/creator.js b/frontend/portal/src/api/creator.js index 4d293fd..71db3f8 100644 --- a/frontend/portal/src/api/creator.js +++ b/frontend/portal/src/api/creator.js @@ -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' }), diff --git a/frontend/portal/src/layout/LayoutCreator.vue b/frontend/portal/src/layout/LayoutCreator.vue index a3dc7ff..b22aba8 100644 --- a/frontend/portal/src/layout/LayoutCreator.vue +++ b/frontend/portal/src/layout/LayoutCreator.vue @@ -1,10 +1,10 @@ diff --git a/frontend/portal/src/router/index.js b/frontend/portal/src/router/index.js index bccc2fd..a1684bf 100644 --- a/frontend/portal/src/router/index.js +++ b/frontend/portal/src/router/index.js @@ -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', diff --git a/frontend/portal/src/views/creator/ContentsEditView.vue b/frontend/portal/src/views/creator/ContentsEditView.vue index 0e5f37d..a2cf8f7 100644 --- a/frontend/portal/src/views/creator/ContentsEditView.vue +++ b/frontend/portal/src/views/creator/ContentsEditView.vue @@ -14,8 +14,8 @@
-
- {{ fullTitle || '新内容标题预览' }} +
+ {{ isEditMode ? '编辑内容' : '发布新内容' }}
@@ -31,35 +31,21 @@
- -
-
+ +
+
-
- - -
-
-
-
- - -
-
- -
@@ -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; }