diff --git a/internal/common/cache/invitation.go b/internal/common/cache/invitation.go index 890a076..19359ab 100644 --- a/internal/common/cache/invitation.go +++ b/internal/common/cache/invitation.go @@ -68,7 +68,7 @@ func GetInvitationCodeListByUserID(userID int32) ([]UserInvitation, error) { return nil, err } - var invitationList []UserInvitation + invitationList := make([]UserInvitation, 0, len(invitations)) for code, info := range invitations { var uim UserInvitationMapValue err := util.StringToStruct(info, &uim) diff --git a/internal/common/db/torrent.go b/internal/common/db/torrent.go index b775d8a..c87d45e 100644 --- a/internal/common/db/torrent.go +++ b/internal/common/db/torrent.go @@ -3,6 +3,7 @@ package db import ( "errors" "fmt" + "strconv" "github.com/TensoRaws/NuxBT-Backend/dal/model" "github.com/TensoRaws/NuxBT-Backend/dal/query" @@ -59,3 +60,71 @@ func GetTorrentByID(torrentID int32) (*model.Torrent, error) { torrent, err := q.Where(q.TorrentID.Eq(torrentID)).First() return torrent, err } + +// GetTorrentList 获取种子列表,返回种子列表和分页数量 +func GetTorrentList(zone TorrentZone, orderBy OrderByType, order OrderType, + page int, perPage int, search string) ([]*model.Torrent, int, error) { + if !order.Validate() || !orderBy.Validate() || !zone.Validate() { + return nil, 0, fmt.Errorf("invalid order: %v, %v", order, orderBy) + } + + q := query.Torrent + var torrents []*model.Torrent + var totalPages int + + // 分区 + var Query query.ITorrentDo + switch zone { + case TORRENT_ZONE_OFFICIAL: + Query = q.Where(q.Official) + case TORRENT_ZONE_GENERAL: + Query = q.Where(q.Status.Eq(STATUS_APPROVED)) + case TORRENT_ZONE_PENDING: + Query = q.Where(q.Status.Eq(STATUS_PENDING)).Or(q.Status.Eq(STATUS_REJECTED)) + default: + return nil, 0, fmt.Errorf("invalid zone: %v", zone) + } + + // 搜索 + var searchAnidbID int + searchAnidbID, err := strconv.Atoi(search) + if err != nil { + searchAnidbID = 0 + } + + if search != "" { + Query = Query.Where(q.Title.Like(search)).Or(q.Subtitle.Like(search)). + Or(q.Hash.Eq(search)).Or(q.AnidbID.Eq(int32(searchAnidbID))) + } + + // 获取总记录数 + count, err := Query.Count() + if err != nil { + return nil, 0, err + } + // 计算总页数 + totalPages = (int(count) + perPage - 1) / perPage + offset := (page - 1) * perPage + + // 排序 + if orderBy == ORDER_BY_TYPE_DATE { + if order == ORDER_TYPE_ASC { + Query = Query.Order(q.CreatedAt.Asc()) + } else if order == ORDER_TYPE_DESC { + Query = Query.Order(q.CreatedAt.Desc()) + } + } else if orderBy == ORDER_BY_TYPE_SIZE { + if order == ORDER_TYPE_ASC { + Query = Query.Order(q.Size.Asc()) + } else if order == ORDER_TYPE_DESC { + Query = Query.Order(q.Size.Desc()) + } + } + + torrents, err = Query.Limit(perPage).Offset(offset).Find() + if err != nil { + return nil, 0, err + } + + return torrents, totalPages, nil +} diff --git a/internal/common/db/torrent_type.go b/internal/common/db/torrent_type.go new file mode 100644 index 0000000..8a04190 --- /dev/null +++ b/internal/common/db/torrent_type.go @@ -0,0 +1,44 @@ +package db + +const ( + STATUS_PENDING = "pending" + STATUS_APPROVED = "approved" + STATUS_REJECTED = "rejected" +) + +// OrderType 用于排序的升降序类型 +type OrderType string + +const ( + ORDER_TYPE_ASC OrderType = "asc" + ORDER_TYPE_DESC OrderType = "desc" +) + +func (o OrderType) Validate() bool { + return o == "asc" || o == "desc" +} + +// OrderByType 用于排序的字段类型 +type OrderByType string + +const ( + ORDER_BY_TYPE_DATE OrderByType = "date" + ORDER_BY_TYPE_SIZE OrderByType = "size" +) + +func (o OrderByType) Validate() bool { + return o == "date" || o == "size" +} + +// TorrentZone 种子区域类型 +type TorrentZone string + +const ( + TORRENT_ZONE_OFFICIAL TorrentZone = "official" + TORRENT_ZONE_GENERAL TorrentZone = "general" + TORRENT_ZONE_PENDING TorrentZone = "pending" +) + +func (t TorrentZone) Validate() bool { + return t == "official" || t == "general" || t == "pending" +} diff --git a/internal/router/api/v1/torrent.go b/internal/router/api/v1/torrent.go index fd1b266..e077bf8 100644 --- a/internal/router/api/v1/torrent.go +++ b/internal/router/api/v1/torrent.go @@ -35,6 +35,11 @@ func TorrentRouterGroup(api *gin.RouterGroup) { jwt.RequireAuth(false), rbac.RABC(role.REVIEWER), torrent_service.Review) + // 种子首页官种 + torrent.GET("official", + jwt.RequireAuth(false), + middleware_cache.Response(6*time.Hour), + torrent_service.Official) // 种子详情 torrent.GET("detail", jwt.RequireAuth(false), diff --git a/internal/service/torrent/common.go b/internal/service/torrent/common.go index 25b0d73..73c5767 100644 --- a/internal/service/torrent/common.go +++ b/internal/service/torrent/common.go @@ -9,11 +9,16 @@ import ( "github.com/TensoRaws/NuxBT-Backend/module/util" ) -const ( - STATUS_PENDING = "pending" - STATUS_APPROVED = "approved" - STATUS_REJECTED = "rejected" -) +type OfficialInfo struct { + CreatedAt string `json:"created_at"` + Essay string `json:"essay"` + Img string `json:"img"` + Size string `json:"size"` + Subtitle string `json:"subtitle"` + Title string `json:"title"` + TorrentID int32 `json:"torrent_id"` + UpdateAt string `json:"update_at"` +} type Info struct { AnidbID int32 `json:"anidb_id"` diff --git a/internal/service/torrent/detail.go b/internal/service/torrent/detail.go index ab64f6d..10cec33 100644 --- a/internal/service/torrent/detail.go +++ b/internal/service/torrent/detail.go @@ -34,6 +34,6 @@ func Detail(c *gin.Context) { return } - resp.OKWithData(c, torrentInfo) + resp.OKWithData(c, &torrentInfo) log.Logger.Infof("get torrent detail success: %v", req.TorrentID) } diff --git a/internal/service/torrent/official.go b/internal/service/torrent/official.go index 10cbafc..dfa4e98 100644 --- a/internal/service/torrent/official.go +++ b/internal/service/torrent/official.go @@ -1 +1,67 @@ package torrent + +import ( + "github.com/TensoRaws/NuxBT-Backend/internal/common/db" + "github.com/TensoRaws/NuxBT-Backend/module/code" + "github.com/TensoRaws/NuxBT-Backend/module/log" + "github.com/TensoRaws/NuxBT-Backend/module/resp" + "github.com/TensoRaws/NuxBT-Backend/module/util" + "github.com/gin-gonic/gin" +) + +type OfficialRequest struct { + Order string `form:"order" binding:"required,oneof=asc desc"` + OrderBy string `form:"order_by" binding:"required,oneof=date size"` + Page int `form:"page" binding:"required,min=1"` + PerPage int `form:"per_page" binding:"required,min=10"` + Search *string `form:"search" binding:"omitempty"` +} + +type OfficialResponse struct { + Torrents []OfficialInfo `json:"torrents"` + TotalPage int `json:"total_page"` +} + +// Official 获取种子文件列表 (GET /official) +func Official(c *gin.Context) { + // 绑定参数 + var req OfficialRequest + if err := c.ShouldBindQuery(&req); err != nil { + resp.AbortWithMsg(c, code.RequestErrorInvalidParams, err.Error()) + return + } + + search := "" + if req.Search != nil { + search = *req.Search + } + + // 获取种子列表 + bts, totalPage, err := db.GetTorrentList( + db.TORRENT_ZONE_OFFICIAL, db.OrderByType(req.OrderBy), db.OrderType(req.Order), + req.Page, req.PerPage, search) + if err != nil { + resp.AbortWithMsg(c, code.DatabaseErrorRecordNotFound, err.Error()) + log.Logger.Error("failed to get official torrent list" + err.Error()) + return + } + + torrentsInfo := make([]OfficialInfo, 0, len(bts)) + for _, bt := range bts { + torrentsInfo = append(torrentsInfo, OfficialInfo{ + CreatedAt: bt.CreatedAt.Format("2006-01-02 15:04:05"), + Essay: bt.Essay, + Img: bt.Img, + Size: util.ByteCountBinary(uint64(bt.Size)), + Subtitle: bt.Subtitle, + Title: bt.Title, + TorrentID: bt.TorrentID, + UpdateAt: bt.UpdatedAt.Format("2006-01-02 15:04:05"), + }) + } + + resp.OKWithData(c, &OfficialResponse{ + Torrents: torrentsInfo, + TotalPage: totalPage, + }) +} diff --git a/internal/service/torrent/review.go b/internal/service/torrent/review.go index b2318aa..9bd2f23 100644 --- a/internal/service/torrent/review.go +++ b/internal/service/torrent/review.go @@ -30,7 +30,7 @@ func Review(c *gin.Context) { return } - if bt.Status == STATUS_APPROVED { + if bt.Status == db.STATUS_APPROVED { resp.AbortWithMsg(c, code.AuthErrorNoPermission, "torrent already approved") log.Logger.Errorf("torrent already approved, torrent_id: %d", req.TorrentID) return diff --git a/internal/service/torrent/upload.go b/internal/service/torrent/upload.go index 19db2c5..702119a 100644 --- a/internal/service/torrent/upload.go +++ b/internal/service/torrent/upload.go @@ -62,9 +62,9 @@ func Upload(c *gin.Context) { // 判断发布权限,是否可以直接发种 var status string if util.CheckStringInSlice(role.UPLOADER, roles) || util.CheckStringInSlice(role.ADMIN, roles) { - status = STATUS_APPROVED + status = db.STATUS_APPROVED } else { - status = STATUS_PENDING + status = db.STATUS_PENDING } // 开始解析种子文件 @@ -162,6 +162,6 @@ func Upload(c *gin.Context) { return } - resp.OKWithData(c, torrentInfo) + resp.OKWithData(c, &torrentInfo) log.Logger.Infof("upload torrent success, user ID: %v", userID) } diff --git a/internal/service/user/invitation.go b/internal/service/user/invitation.go index 66e78a2..e4308ab 100644 --- a/internal/service/user/invitation.go +++ b/internal/service/user/invitation.go @@ -73,10 +73,7 @@ func InvitationMe(c *gin.Context) { return } - if len(codeList) == 0 { - resp.OKWithData(c, InvitationMeResponse{}) - } else { - resp.OKWithData(c, InvitationMeResponse(codeList)) - } + resp.OKWithData(c, InvitationMeResponse(codeList)) + log.Logger.Infof("User %d got invitation code list successfully!", userID) }