321 lines
8.6 KiB
Go
321 lines
8.6 KiB
Go
package http
|
|
|
|
import (
|
|
_ "embed"
|
|
"strconv"
|
|
"time"
|
|
|
|
"quyun/app/jobs"
|
|
"quyun/app/models"
|
|
"quyun/app/requests"
|
|
"quyun/database/conds"
|
|
"quyun/database/fields"
|
|
"quyun/database/schemas/public/model"
|
|
"quyun/providers/ali"
|
|
"quyun/providers/job"
|
|
"quyun/providers/wepay"
|
|
|
|
"github.com/go-pay/gopay/wechat/v3"
|
|
"github.com/gofiber/fiber/v3"
|
|
"github.com/pkg/errors"
|
|
"github.com/samber/lo"
|
|
log "github.com/sirupsen/logrus"
|
|
)
|
|
|
|
type ListQuery struct {
|
|
Keyword *string `query:"keyword"`
|
|
}
|
|
|
|
// @provider
|
|
type posts struct {
|
|
wepay *wepay.Client
|
|
oss *ali.OSSClient
|
|
job *job.Job
|
|
}
|
|
|
|
// List posts
|
|
//
|
|
// @Router /posts [get]
|
|
// @Bind pagination query
|
|
// @Bind query query
|
|
// @Bind user local
|
|
func (ctl *posts) List(ctx fiber.Ctx, pagination *requests.Pagination, query *ListQuery, user *model.Users) (*requests.Pager, error) {
|
|
conds := []conds.Cond{
|
|
conds.Post_NotDeleted(),
|
|
conds.Post_Status(fields.PostStatusPublished),
|
|
conds.Post_Like(query.Keyword),
|
|
}
|
|
|
|
pager, err := models.Posts.List(ctx.Context(), pagination, conds...)
|
|
if err != nil {
|
|
log.WithError(err).Errorf("post list err: %v", err)
|
|
return nil, err
|
|
}
|
|
|
|
postIds := lo.Map(pager.Items.([]model.Posts), func(item model.Posts, _ int) int64 { return item.ID })
|
|
if len(postIds) > 0 {
|
|
userBoughtIds, err := models.Users.BatchCheckHasBought(ctx.Context(), user.ID, postIds)
|
|
if err != nil {
|
|
log.WithError(err).Errorf("BatchCheckHasBought err: %v", err)
|
|
}
|
|
|
|
items := lo.FilterMap(pager.Items.([]model.Posts), func(item model.Posts, _ int) (PostItem, bool) {
|
|
medias, err := models.Posts.GetMediaByIds(ctx.Context(), item.HeadImages.Data)
|
|
if err != nil {
|
|
log.Errorf("GetMediaByIds err: %v", err)
|
|
return PostItem{}, false
|
|
}
|
|
mediaUrls := lo.FilterMap(medias, func(item model.Medias, _ int) (string, bool) {
|
|
url, err := ctl.oss.GetSignedUrl(ctx.Context(), item.Path)
|
|
if err != nil {
|
|
log.WithError(err).Errorf("head image GetSignedUrl err: %v", err)
|
|
return "", false
|
|
}
|
|
|
|
return url, true
|
|
})
|
|
|
|
_, bought := userBoughtIds[item.ID]
|
|
|
|
return PostItem{
|
|
ID: item.ID,
|
|
Title: item.Title,
|
|
Description: item.Description,
|
|
Price: item.Price,
|
|
Discount: item.Discount,
|
|
Views: item.Views,
|
|
Likes: item.Likes,
|
|
Tags: item.Tags.Data,
|
|
HeadImages: mediaUrls,
|
|
Bought: bought,
|
|
}, true
|
|
})
|
|
|
|
pager.Items = items
|
|
}
|
|
|
|
return pager, nil
|
|
}
|
|
|
|
type PostItem struct {
|
|
ID int64 `json:"id"`
|
|
Bought bool `json:"bought"`
|
|
Title string `json:"title"`
|
|
Description string `json:"description"`
|
|
Content string `json:"content"`
|
|
Price int64 `json:"price"`
|
|
Discount int16 `json:"discount"`
|
|
Views int64 `json:"views"`
|
|
Likes int64 `json:"likes"`
|
|
Tags []string `json:"tags"`
|
|
HeadImages []string `json:"head_images"`
|
|
}
|
|
|
|
// Show
|
|
//
|
|
// @Router /posts/:id/show [get]
|
|
// @Bind id path
|
|
// @Bind user local
|
|
func (ctl *posts) Show(ctx fiber.Ctx, id int64, user *model.Users) (*PostItem, error) {
|
|
log.Infof("Fetching post with ID: %d", id)
|
|
|
|
post, err := models.Posts.GetByID(ctx.Context(), id, conds.Post_NotDeleted(), conds.Post_Status(fields.PostStatusPublished))
|
|
if err != nil {
|
|
log.WithError(err).Errorf("GetByID err: %v", err)
|
|
return nil, err
|
|
}
|
|
|
|
bought, err := models.Users.HasBought(ctx.Context(), user.ID, post.ID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
medias, err := models.Posts.GetMediaByIds(ctx.Context(), post.HeadImages.Data)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
mediaUrls := lo.FilterMap(medias, func(item model.Medias, _ int) (string, bool) {
|
|
url, err := ctl.oss.GetSignedUrl(ctx.Context(), item.Path)
|
|
if err != nil {
|
|
return "", false
|
|
}
|
|
|
|
return url, true
|
|
})
|
|
|
|
return &PostItem{
|
|
ID: post.ID,
|
|
Title: post.Title,
|
|
Description: post.Description,
|
|
Content: post.Content,
|
|
Price: post.Price,
|
|
Discount: post.Discount,
|
|
Views: post.Views,
|
|
Likes: post.Likes,
|
|
Tags: post.Tags.Data,
|
|
HeadImages: mediaUrls,
|
|
Bought: bought,
|
|
}, nil
|
|
}
|
|
|
|
type PlayUrl struct {
|
|
Url string `json:"url"`
|
|
}
|
|
|
|
// Play
|
|
//
|
|
// @Router /posts/:id/play [get]
|
|
// @Bind id path
|
|
// @Bind user local
|
|
func (ctl *posts) Play(ctx fiber.Ctx, id int64, user *model.Users) (*PlayUrl, error) {
|
|
log := log.WithField("PlayPostID", strconv.FormatInt(id, 10))
|
|
// return &PlayUrl{
|
|
// Url: "https://github.com/mediaelement/mediaelement-files/raw/refs/heads/master/big_buck_bunny.mp4",
|
|
// }, nil
|
|
|
|
log.Infof("Fetching play URL for post ID: %d", id)
|
|
post, err := models.Posts.GetByID(ctx.Context(), id)
|
|
if err != nil {
|
|
log.WithError(err).Errorf("GetByID err: %v", err)
|
|
return nil, err
|
|
}
|
|
go models.Posts.IncrViewCount(ctx.Context(), post.ID)
|
|
|
|
preview := false
|
|
bought, err := models.Users.HasBought(ctx.Context(), user.ID, post.ID)
|
|
if !bought || err != nil {
|
|
preview = true
|
|
}
|
|
|
|
for _, asset := range post.Assets.Data {
|
|
if asset.Type == "video/mp4" && asset.Metas != nil && asset.Metas.Short == preview {
|
|
media, err := models.Medias.GetByID(ctx.Context(), asset.Media)
|
|
if err != nil {
|
|
log.WithError(err).Errorf("medias GetByID err: %v", err)
|
|
return nil, err
|
|
}
|
|
url, err := ctl.oss.GetSignedUrl(ctx.Context(), media.Path)
|
|
if err != nil {
|
|
log.WithError(err).Errorf("media GetSignedUrl err: %v", err)
|
|
return nil, err
|
|
}
|
|
return &PlayUrl{Url: url}, nil
|
|
}
|
|
}
|
|
return nil, errors.New("视频不存在")
|
|
}
|
|
|
|
// Mine posts
|
|
//
|
|
// @Router /posts/mine [get]
|
|
// @Bind pagination query
|
|
// @Bind query query
|
|
// @Bind user local
|
|
func (ctl *posts) Mine(ctx fiber.Ctx, pagination *requests.Pagination, query *ListQuery, user *model.Users) (*requests.Pager, error) {
|
|
log.Infof("Fetching posts for user with pagination: %+v and keyword: %v", pagination, query.Keyword)
|
|
|
|
conds := []conds.Cond{
|
|
conds.Post_NotDeleted(),
|
|
conds.Post_Status(fields.PostStatusPublished),
|
|
conds.Post_Like(query.Keyword),
|
|
}
|
|
pager, err := models.Users.PostList(ctx.Context(), user.ID, pagination, conds...)
|
|
if err != nil {
|
|
log.WithError(err).Errorf("post list err: %v", err)
|
|
return nil, err
|
|
}
|
|
|
|
postIds := lo.Map(pager.Items.([]model.Posts), func(item model.Posts, _ int) int64 { return item.ID })
|
|
if len(postIds) > 0 {
|
|
items := lo.FilterMap(pager.Items.([]model.Posts), func(item model.Posts, _ int) (PostItem, bool) {
|
|
medias, err := models.Posts.GetMediaByIds(ctx.Context(), item.HeadImages.Data)
|
|
if err != nil {
|
|
log.Errorf("GetMediaByIds err: %v", err)
|
|
return PostItem{}, false
|
|
}
|
|
mediaUrls := lo.FilterMap(medias, func(item model.Medias, _ int) (string, bool) {
|
|
url, err := ctl.oss.GetSignedUrl(ctx.Context(), item.Path)
|
|
if err != nil {
|
|
log.WithError(err).Errorf("head image GetSignedUrl err: %v", err)
|
|
return "", false
|
|
}
|
|
|
|
return url, true
|
|
})
|
|
|
|
return PostItem{
|
|
ID: item.ID,
|
|
Title: item.Title,
|
|
Description: item.Description,
|
|
Price: item.Price,
|
|
Discount: item.Discount,
|
|
Views: item.Views,
|
|
Likes: item.Likes,
|
|
Tags: item.Tags.Data,
|
|
HeadImages: mediaUrls,
|
|
}, true
|
|
})
|
|
|
|
pager.Items = items
|
|
}
|
|
return pager, nil
|
|
}
|
|
|
|
// Buy
|
|
//
|
|
// @Router /posts/:id/buy [post]
|
|
// @Bind id path
|
|
// @Bind user local
|
|
func (ctl *posts) Buy(ctx fiber.Ctx, id int64, user *model.Users) (*wechat.JSAPIPayParams, error) {
|
|
post, err := models.Posts.GetByID(ctx.Context(), id)
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, " failed to get post: %d", id)
|
|
}
|
|
payPrice := post.Price * int64(post.Discount) / 100
|
|
|
|
order, err := models.Orders.Create(ctx.Context(), user.ID, post.ID)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "订单创建失败")
|
|
}
|
|
|
|
if user.Balance >= payPrice {
|
|
if err := models.Orders.SetMeta(ctx.Context(), order.ID, func(om fields.OrderMeta) fields.OrderMeta {
|
|
om.CostBalance = payPrice
|
|
return om
|
|
}); err != nil {
|
|
return nil, errors.Wrap(err, "订单创建失败")
|
|
}
|
|
|
|
if err := ctl.job.Add(&jobs.BalancePayNotify{OrderNo: order.OrderNo}); err != nil {
|
|
log.Errorf("add job error:%v", err)
|
|
return nil, errors.Wrap(err, "Failed to add job")
|
|
}
|
|
|
|
return &wechat.JSAPIPayParams{
|
|
AppId: "balance",
|
|
}, nil
|
|
}
|
|
|
|
payPrice = payPrice - user.Balance
|
|
if err := models.Orders.SetMeta(ctx.Context(), order.ID, func(om fields.OrderMeta) fields.OrderMeta {
|
|
om.CostBalance = user.Balance
|
|
return om
|
|
}); err != nil {
|
|
return nil, errors.Wrap(err, "订单创建失败")
|
|
}
|
|
prePayResp, err := ctl.wepay.V3TransactionJsapi(ctx.Context(), func(bm *wepay.BodyMap) {
|
|
bm.
|
|
Expire(30 * time.Minute).
|
|
Description(post.Title).
|
|
OutTradeNo(order.OrderNo).
|
|
Payer(user.OpenID).
|
|
CNYAmount(payPrice)
|
|
})
|
|
if err != nil {
|
|
log.Errorf("wepay.V3TransactionJsapi err: %v", err)
|
|
return nil, errors.Wrap(err, "微信支付失败")
|
|
}
|
|
|
|
return prePayResp.PaySignOfJSAPI()
|
|
}
|