Files
quyun/backend_v1/app/http/posts.go
Rogee 49072ddd79
Some checks failed
build quyun / Build (push) Failing after 1m30s
migrate controllers
2025-12-19 23:33:02 +08:00

338 lines
8.8 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package http
import (
_ "embed"
"strconv"
"time"
"quyun/v2/app/jobs"
"quyun/v2/app/requests"
"quyun/v2/app/services"
"quyun/v2/database"
"quyun/v2/database/models"
"quyun/v2/pkg/fields"
"quyun/v2/providers/ali"
"quyun/v2/providers/app"
"quyun/v2/providers/job"
"quyun/v2/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"
"go.ipao.vip/gen"
)
type ListQuery struct {
Keyword *string `query:"keyword"`
}
// @provider
type posts struct {
wepay *wepay.Client
oss *ali.OSSClient
job *job.Job
app *app.Config
}
// 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 *models.User,
) (*requests.Pager, error) {
tbl, _ := models.PostQuery.QueryContext(ctx)
conds := []gen.Condition{
tbl.Status.Eq(fields.PostStatusPublished),
tbl.Title.Like(database.WrapLike(*query.Keyword)),
}
pager, err := services.Posts.List(ctx, pagination, conds...)
if err != nil {
log.WithError(err).Errorf("post list err: %v", err)
return nil, err
}
postIds := lo.Map(pager.Items.([]models.Post), func(item models.Post, _ int) int64 { return item.ID })
if len(postIds) > 0 {
userBoughtIds, err := services.Users.BatchCheckHasBought(ctx, user.ID, postIds)
if err != nil {
log.WithError(err).Errorf("BatchCheckHasBought err: %v", err)
}
items := lo.FilterMap(pager.Items.([]models.Post), func(item models.Post, _ int) (PostItem, bool) {
medias, err := services.Posts.GetMediasByIds(ctx, item.HeadImages.Data())
if err != nil {
log.Errorf("GetMediaByIds err: %v", err)
return PostItem{}, false
}
mediaUrls := lo.FilterMap(medias, func(item *models.Media, _ int) (string, bool) {
url, err := ctl.oss.GetSignedUrl(ctx, 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,
RechargeWechat: ctl.app.RechargeWechat,
}, 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"`
RechargeWechat string `json:"recharge_wechat,omitempty"`
}
// Show
//
// @Router /posts/:id/show [get]
// @Bind id path
// @Bind user local
func (ctl *posts) Show(ctx fiber.Ctx, id int64, user *models.User) (*PostItem, error) {
log.Infof("Fetching post with ID: %d", id)
post, err := services.Posts.FindByID(
ctx,
id,
models.PostQuery.Status.Eq(fields.PostStatusPublished),
)
if err != nil {
log.WithError(err).Errorf("GetByID err: %v", err)
return nil, err
}
bought, err := services.Users.HasBought(ctx, user.ID, post.ID)
if err != nil {
return nil, err
}
medias, err := services.Posts.GetMediasByIds(ctx, post.HeadImages.Data())
if err != nil {
return nil, err
}
mediaUrls := lo.FilterMap(medias, func(item *models.Media, _ int) (string, bool) {
url, err := ctl.oss.GetSignedUrl(ctx, 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,
RechargeWechat: ctl.app.RechargeWechat,
}, 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 *models.User) (*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
preview := false
bought, err := services.Users.HasBought(ctx, user.ID, id)
if !bought || err != nil {
preview = true
}
log.Infof("Fetching play URL for post ID: %d", id)
post, err := services.Posts.FindByID(ctx, id)
if err != nil {
log.WithError(err).Errorf("GetByID err: %v", err)
return nil, err
}
go services.Posts.IncrViewCount(ctx, post.ID)
for _, asset := range post.Assets.Data() {
if asset.Type == "video/mp4" && asset.Metas != nil && asset.Metas.Short == preview {
media, err := services.Medias.FindByID(ctx, asset.Media)
if err != nil {
log.WithError(err).Errorf("medias GetByID err: %v", err)
return nil, err
}
duration := 2*asset.Metas.Duration + 30
if asset.Metas.Duration == 0 {
duration = 60 * 5
}
url, err := ctl.oss.GetSignedUrl(
ctx,
media.Path,
ali.WithExpire(time.Second*time.Duration(duration)),
)
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 *models.User,
) (*requests.Pager, error) {
log.Infof("Fetching posts for user with pagination: %+v and keyword: %v", pagination, query.Keyword)
conds := []gen.Condition{
models.PostQuery.Status.Eq(fields.PostStatusPublished),
models.PostQuery.Title.Like(database.WrapLike(*query.Keyword)),
}
pager, err := services.Users.PostList(ctx, user.ID, pagination, conds...)
if err != nil {
log.WithError(err).Errorf("post list err: %v", err)
return nil, err
}
postIds := lo.Map(pager.Items.([]*models.Post), func(item *models.Post, _ int) int64 { return item.ID })
if len(postIds) > 0 {
items := lo.FilterMap(pager.Items.([]*models.Post), func(item *models.Post, _ int) (PostItem, bool) {
medias, err := services.Medias.GetByIds(ctx, item.HeadImages.Data())
if err != nil {
log.Errorf("GetMediaByIds err: %v", err)
return PostItem{}, false
}
mediaUrls := lo.FilterMap(medias, func(item *models.Media, _ int) (string, bool) {
url, err := ctl.oss.GetSignedUrl(ctx, 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,
RechargeWechat: ctl.app.RechargeWechat,
}, 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 *models.User) (*wechat.JSAPIPayParams, error) {
bought, err := services.Users.HasBought(ctx, user.ID, id)
if err != nil {
return nil, errors.New("查询购买失败")
}
if bought {
return nil, errors.New("已经购买过了")
}
post, err := services.Posts.FindByID(ctx, id)
if err != nil {
return nil, errors.Wrapf(err, " failed to get post: %d", id)
}
// payPrice := post.PayPrice()
order, err := services.Orders.CreateFromUserPostID(ctx, user.ID, post.ID)
if err != nil {
return nil, errors.Wrap(err, "订单创建失败")
}
if user.Balance >= post.PayPrice() {
if err := services.Orders.SetMeta(ctx, order.ID, func(om fields.OrderMeta) fields.OrderMeta {
om.CostBalance = post.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
}
return nil, errors.Errorf("账户余额不足, 当前余额:%0.2f 请联系管理员购买或充值", float64(user.Balance)/100)
}