@@ -0,0 +1,55 @@
|
||||
package vidser
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"go.mongodb.org/mongo-driver/bson"
|
||||
)
|
||||
|
||||
func TestApplyLiaoBaTopSortAllowsDuplicateAndLargeValues(t *testing.T) {
|
||||
existingVideo := bson.M{"liaoBaTopSort": 1000}
|
||||
targetUpdate := bson.M{"title": "unchanged"}
|
||||
sortCode := 1000
|
||||
|
||||
applyLiaoBaTopSort(targetUpdate, &sortCode)
|
||||
|
||||
if got := targetUpdate["liaoBaTopSort"]; got != sortCode {
|
||||
t.Fatalf("liaoBaTopSort = %v, want %d", got, sortCode)
|
||||
}
|
||||
if got := targetUpdate["liaoBaTop"]; got != true {
|
||||
t.Fatalf("liaoBaTop = %v, want true", got)
|
||||
}
|
||||
if got := targetUpdate["title"]; got != "unchanged" {
|
||||
t.Fatalf("unrelated field changed: %v", got)
|
||||
}
|
||||
if got := existingVideo["liaoBaTopSort"]; got != sortCode {
|
||||
t.Fatalf("existing video sort changed: %v", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyLiaoBaTopSortZeroUnpinsCurrentVideo(t *testing.T) {
|
||||
set := bson.M{}
|
||||
sortCode := 0
|
||||
|
||||
applyLiaoBaTopSort(set, &sortCode)
|
||||
|
||||
if got := set["liaoBaTopSort"]; got != 0 {
|
||||
t.Fatalf("liaoBaTopSort = %v, want 0", got)
|
||||
}
|
||||
if got := set["liaoBaTop"]; got != false {
|
||||
t.Fatalf("liaoBaTop = %v, want false", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLiaoBaTopSortModeListHasStableTieBreakers(t *testing.T) {
|
||||
want := SortModeList{
|
||||
{Field: "liaoBaTopSort", Value: -1},
|
||||
{Field: "reviewAt", Value: -1},
|
||||
{Field: "_id", Value: -1},
|
||||
}
|
||||
|
||||
if got := LiaoBaTopSortModeList(); !reflect.DeepEqual(got, want) {
|
||||
t.Fatalf("sort = %#v, want %#v", got, want)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,260 @@
|
||||
package vidser
|
||||
|
||||
import (
|
||||
"91porn-server/app/service/modulevideoser"
|
||||
"91porn-server/models/v/modulevidmod"
|
||||
"91porn-server/models/v/usermod"
|
||||
"91porn-server/models/v/vidtimeonlinemod"
|
||||
"context"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||
|
||||
"91porn-server/common"
|
||||
"91porn-server/common/constant"
|
||||
"91porn-server/common/log"
|
||||
"91porn-server/common/mdrsdb"
|
||||
"91porn-server/models/v/vidmod"
|
||||
|
||||
"github.com/shopspring/decimal"
|
||||
)
|
||||
|
||||
func getMediaPathFromUrl(rawUrl string) string {
|
||||
url, err := url.Parse(rawUrl)
|
||||
if err != nil || url.Path == "" {
|
||||
return ""
|
||||
}
|
||||
return url.Path
|
||||
}
|
||||
|
||||
func getMediaFileInfoFromUrl(rawUrl string) (sourceID, fileName string) {
|
||||
url, err := url.Parse(rawUrl)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
path := url.Path
|
||||
sliX := strings.Split(path, "/")
|
||||
if len(sliX) > 0 {
|
||||
fileName = sliX[len(sliX)-1]
|
||||
}
|
||||
sliP := strings.Split(fileName, ".")
|
||||
if len(sliP) > 0 {
|
||||
sourceID = sliP[0]
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func getIMGPathFromUrl(rawUrl string) string {
|
||||
//新桶,无需加ims,可直接回源
|
||||
if strings.Contains(rawUrl, "cf230705") || strings.Contains(rawUrl, "bf211222") {
|
||||
return rawUrl
|
||||
}
|
||||
if rawUrl == "" {
|
||||
return ""
|
||||
}
|
||||
url, err := url.Parse(rawUrl)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return common.BindUrl(constant.ImageSourceIMS, url.Path)
|
||||
}
|
||||
|
||||
func GetMediaList(ctx context.Context, page, size, vloggerUserId int64, title, tagText, hashId string, vMia, videoType int, syncType, terminalCdo string, startTime, endTime time.Time, sortType, maxTotal int) (data []mdrsdb.MediaResource, count int64, err error) {
|
||||
data, count, err = mdrsdb.FindMediaList(ctx, page, size, vloggerUserId, title, tagText, hashId, vMia, videoType, syncType, terminalCdo, startTime, endTime, sortType, maxTotal)
|
||||
for i := range data {
|
||||
data[i].CoverImage = getIMGPathFromUrl(data[i].CoverImage)
|
||||
data[i].VerticalCover = getIMGPathFromUrl(data[i].VerticalCover)
|
||||
data[i].Src1080 = getMediaPathFromUrl(data[i].Src1080)
|
||||
data[i].Src720 = getMediaPathFromUrl(data[i].Src720)
|
||||
data[i].Src480 = getMediaPathFromUrl(data[i].Src480)
|
||||
data[i].VerticalCover = getIMGPathFromUrl(data[i].VerticalCover)
|
||||
tags := strings.Split(data[i].TagsText, ",")
|
||||
if len(tags) > 5 {
|
||||
tags = tags[:5]
|
||||
}
|
||||
data[i].TagList = tags
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func UseMedias(ctx context.Context, ids []int64, authorID uint64, sectionID primitive.ObjectID, inTags []string, videoType int, manager string, coins *int64, onlineTime time.Time) ([]SensitiveHit, error) {
|
||||
mbs, err := mdrsdb.FindMediaListWithIDs(ctx, ids, videoType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
terms := LoadEnabledSensitiveTerms()
|
||||
hits := make([]SensitiveHit, 0)
|
||||
useIds := make([]int64, 0, len(mbs))
|
||||
for i, v := range mbs {
|
||||
log.Info(fmt.Sprintf("Synchronize media library video information:video_id:%d up_id:%d authorId:%d", v.Id, v.VloggerUserId, authorID))
|
||||
mbs[i].CoverImage = getIMGPathFromUrl(mbs[i].CoverImage)
|
||||
mbs[i].VerticalCover = getIMGPathFromUrl(mbs[i].VerticalCover)
|
||||
mbs[i].Src1080 = getMediaPathFromUrl(mbs[i].Src1080)
|
||||
mbs[i].Src720 = getMediaPathFromUrl(mbs[i].Src720)
|
||||
mbs[i].Src480 = getMediaPathFromUrl(mbs[i].Src480)
|
||||
mbs[i].VerticalCover = getIMGPathFromUrl(mbs[i].VerticalCover)
|
||||
var m3u8 string
|
||||
var m3u8Size int64
|
||||
if strings.Contains(mbs[i].Src720, "/") {
|
||||
m3u8 = mbs[i].Src720
|
||||
m3u8Size = mbs[i].FileSize720
|
||||
}
|
||||
if m3u8 == "" {
|
||||
m3u8 = mbs[i].Src480
|
||||
m3u8Size = mbs[i].FileSize480
|
||||
}
|
||||
if m3u8 == "" {
|
||||
m3u8 = mbs[i].Src1080
|
||||
m3u8Size = mbs[i].FileSize1080
|
||||
}
|
||||
tagsList := strings.Split(mbs[i].TagsText, ",")
|
||||
tags := []string{}
|
||||
for _, tag := range tagsList {
|
||||
tag = strings.TrimSpace(tag)
|
||||
if tag != "" {
|
||||
tags = append(tags, tag)
|
||||
}
|
||||
}
|
||||
if len(tags) > 5 {
|
||||
tags = tags[:5]
|
||||
}
|
||||
tags = append(tags, inTags...)
|
||||
if authorID == 0 && v.VloggerUserId != 0 {
|
||||
authorID = v.VloggerUserId
|
||||
} else if authorID == 0 {
|
||||
authorID = 100001
|
||||
}
|
||||
author, _ := usermod.FindUserByUID(authorID)
|
||||
if author == nil {
|
||||
authorID = uint64(rand.Int63n(50000) + 100001)
|
||||
}
|
||||
var pics []string
|
||||
if len(v.Pics) > 0 {
|
||||
pics = strings.Split(v.Pics, ",")
|
||||
for i, p := range pics {
|
||||
pics[i] = getIMGPathFromUrl(p)
|
||||
}
|
||||
}
|
||||
// videoType : 0 长视频 1短视频 2图文
|
||||
// v.MediaType: 0视频 1 图文
|
||||
vt := vidmod.SP
|
||||
if videoType == 1 {
|
||||
vt = vidmod.SHORT
|
||||
} else if videoType == 2 && v.MediaType == 1 {
|
||||
vt = vidmod.COVER
|
||||
}
|
||||
|
||||
var ratio float64 = 0
|
||||
if v.Height > 0 && v.Width > 0 {
|
||||
ratio = float64(decimal.NewFromInt(int64(v.Width)).Div(decimal.NewFromInt(int64(v.Height))).Shift(2).IntPart()) / 100
|
||||
}
|
||||
temp := vidmod.WebSubmitReq{
|
||||
UID: authorID,
|
||||
NewsType: vt,
|
||||
Title: v.Title,
|
||||
Content: v.Content,
|
||||
Tags: tags,
|
||||
PlayTime: v.Length,
|
||||
Cover: mbs[i].CoverImage,
|
||||
VerticalCover: mbs[i].VerticalCover,
|
||||
SeriesCover: pics,
|
||||
Via: mbs[i].TerminalCdo,
|
||||
Coins: mbs[i].SaleCoin,
|
||||
Size: int(m3u8Size),
|
||||
Resolution: strconv.Itoa(v.Width) + "*" + strconv.Itoa(v.Height),
|
||||
Ratio: ratio,
|
||||
Width: v.Width,
|
||||
Height: v.Height,
|
||||
MimeType: "",
|
||||
Actor: "",
|
||||
SourceURL: m3u8,
|
||||
MD5: v.HashId,
|
||||
SourceID: strconv.FormatInt(v.Id, 10),
|
||||
MDSID: v.HashId,
|
||||
Account: manager,
|
||||
RichText: v.RichText,
|
||||
PreviewStart: v.PreviewStart,
|
||||
}
|
||||
if coins != nil {
|
||||
temp.Coins = *coins
|
||||
}
|
||||
if len(tags) > 0 {
|
||||
if tags[0] != "" {
|
||||
temp.Status = 1
|
||||
}
|
||||
|
||||
}
|
||||
if m3u8 != "" {
|
||||
temp.SourceID, temp.Filename = getMediaFileInfoFromUrl(m3u8)
|
||||
if temp.PlayTime > 60 {
|
||||
temp.FreeTime = 15
|
||||
} else {
|
||||
temp.FreeTime = int(temp.PlayTime / 4)
|
||||
}
|
||||
}
|
||||
if authorID > 0 {
|
||||
temp.UID = authorID
|
||||
}
|
||||
if !onlineTime.IsZero() {
|
||||
temp.Status = 0
|
||||
}
|
||||
if temp.NewsType == vidmod.COVER {
|
||||
temp.Status = 0
|
||||
}
|
||||
if !onlineTime.IsZero() {
|
||||
temp.Status = vidmod.OnlinePass
|
||||
}
|
||||
// 敏感词命中:强制为待审核,且不进入自动上架/专题上架链路
|
||||
hit := CheckTextHits(terms, temp.SourceID, temp.Title, temp.Content, temp.RichText)
|
||||
if hit != nil {
|
||||
temp.Status = vidmod.WaitingCheck
|
||||
log.Warn("vidser.UseMedias sensitive hit, forced offline & skip auto-online",
|
||||
log.Any("sourceId", hit.SourceID),
|
||||
log.Any("title", hit.Title),
|
||||
log.Any("detail", hit.Detail))
|
||||
hits = append(hits, *hit)
|
||||
}
|
||||
code, vid := SubmitBase(temp)
|
||||
if code != 200 {
|
||||
log.ErrorX(ctx, "vidser UseMedias is fail", log.Any("code", code), log.Any("vid", vid))
|
||||
continue
|
||||
}
|
||||
if !onlineTime.IsZero() && hit == nil {
|
||||
// 加入批量自动上架(命中过的资源不进入此路径)
|
||||
BatchAuditVidTimeOnlineInfo(vidtimeonlinemod.BatchAuditReq{
|
||||
Vids: []primitive.ObjectID{vid},
|
||||
SectionID: sectionID,
|
||||
OnlineTime: onlineTime,
|
||||
}, int(temp.Coins), manager)
|
||||
|
||||
} else if !sectionID.IsZero() && temp.Status == 1 {
|
||||
// 加入专题
|
||||
sv := modulevidmod.SectionVideo{
|
||||
SectionID: sectionID,
|
||||
VideoID: vid,
|
||||
VideoReviewedAt: time.Now(),
|
||||
SortCode: 50,
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
|
||||
_ = modulevideoser.AddVideo(&sv)
|
||||
}
|
||||
useIds = append(useIds, v.Id)
|
||||
}
|
||||
if len(useIds) > 0 {
|
||||
if err = mdrsdb.UseMedia(ctx, useIds, videoType); err != nil {
|
||||
return hits, err
|
||||
}
|
||||
}
|
||||
return hits, nil
|
||||
}
|
||||
|
||||
func IgnoreMedias(ctx context.Context, ids []int64, videoType int) (err error) {
|
||||
return mdrsdb.IgnoreMedia(ctx, ids, videoType)
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package vidser
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"go.mongodb.org/mongo-driver/bson"
|
||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||
)
|
||||
|
||||
func TestApplyScheduledVideoModuleID(t *testing.T) {
|
||||
moduleID := primitive.NewObjectID()
|
||||
update := bson.M{}
|
||||
|
||||
applyScheduledVideoModuleID(update, moduleID)
|
||||
|
||||
if got := update["mId"]; got != moduleID.Hex() {
|
||||
t.Fatalf("mId = %v, want %s", got, moduleID.Hex())
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyScheduledVideoModuleIDIgnoresEmptyID(t *testing.T) {
|
||||
update := bson.M{}
|
||||
applyScheduledVideoModuleID(update, primitive.NilObjectID)
|
||||
if _, ok := update["mId"]; ok {
|
||||
t.Fatal("empty module id must not be written")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
package vidser
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"91porn-server/common/log"
|
||||
"91porn-server/models/v/sensitivewordmod"
|
||||
"91porn-server/models/v/vidmod"
|
||||
|
||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||
)
|
||||
|
||||
// CheckShelvingFields 直接对一组文本做敏感词命中检测;用于单帖编辑场景,
|
||||
// 调用方负责把请求字段与 DB 现值合并后传入。
|
||||
// 命中规则与 CheckShelvingByIDs 一致:Title / Content / RichText 任一命中即视为违规。
|
||||
func CheckShelvingFields(id primitive.ObjectID, title, content, richText string) error {
|
||||
terms := loadEnabledSensitiveTerms()
|
||||
if len(terms) == 0 {
|
||||
return nil
|
||||
}
|
||||
titleHits := findSensitiveHits(title, terms)
|
||||
contentHits := findSensitiveHits(content, terms)
|
||||
richHits := findSensitiveHits(richText, terms)
|
||||
if len(titleHits) == 0 && len(contentHits) == 0 && len(richHits) == 0 {
|
||||
return nil
|
||||
}
|
||||
return errors.New(formatHitLine(id, title, titleHits, contentHits, richHits))
|
||||
}
|
||||
|
||||
// CheckShelvingByIDs 上架前敏感词命中检测。
|
||||
//
|
||||
// 与 skd/service/contentreviewser VIDEO 任务的扫描规则保持一致:
|
||||
// 对每条视频的 Title / Content / RichText 三个字段做 substring 命中检测,
|
||||
// 任一字段命中启用状态的敏感词词条即视为违规,不予上架。
|
||||
//
|
||||
// 返回值:
|
||||
// - nil —— 全部通过或敏感词库为空
|
||||
// - non-nil err —— 至少一条命中,err.Error() 已按视频聚合
|
||||
//
|
||||
// 多条命中时,每条视频独占一行,行内字段按"标题命中 / 内容命中 / 富文本命中"顺序拼接。
|
||||
func CheckShelvingByIDs(ids []primitive.ObjectID) error {
|
||||
if len(ids) == 0 {
|
||||
return nil
|
||||
}
|
||||
terms := loadEnabledSensitiveTerms()
|
||||
if len(terms) == 0 {
|
||||
return nil
|
||||
}
|
||||
videos, err := vidmod.GetVideoListByIDsNoStatus(ids)
|
||||
if err != nil {
|
||||
log.Error("vidser.CheckShelvingByIDs query videos fail", log.Any("ids", ids), log.E(err))
|
||||
return errors.New("查询视频信息失败")
|
||||
}
|
||||
lines := make([]string, 0)
|
||||
for _, v := range videos {
|
||||
titleHits := findSensitiveHits(v.Title, terms)
|
||||
contentHits := findSensitiveHits(v.Content, terms)
|
||||
richHits := findSensitiveHits(v.RichText, terms)
|
||||
if len(titleHits) == 0 && len(contentHits) == 0 && len(richHits) == 0 {
|
||||
continue
|
||||
}
|
||||
lines = append(lines, formatHitLine(v.ID, v.Title, titleHits, contentHits, richHits))
|
||||
}
|
||||
if len(lines) == 0 {
|
||||
return nil
|
||||
}
|
||||
return errors.New(strings.Join(lines, "\n"))
|
||||
}
|
||||
|
||||
// LoadEnabledSensitiveTerms 启用状态敏感词词条加载(代理 sensitivewordmod.LoadEnabledTerms)
|
||||
// 保留此处签名以兼容现有 laosijiser / tianyuctrl 等调用方
|
||||
func LoadEnabledSensitiveTerms() []string {
|
||||
return sensitivewordmod.LoadEnabledTerms()
|
||||
}
|
||||
|
||||
// loadEnabledSensitiveTerms 内部短名别名
|
||||
func loadEnabledSensitiveTerms() []string {
|
||||
return sensitivewordmod.LoadEnabledTerms()
|
||||
}
|
||||
|
||||
// SensitiveHit 单条命中结果,用于批量同步接口(老司机/天宇媒资库)返回给前端
|
||||
type SensitiveHit struct {
|
||||
SourceID string `json:"sourceId"` // 来源 id(如 laosiji 资源 id / tianyu fileId)
|
||||
Title string `json:"title"` // 标题
|
||||
Detail string `json:"detail"` // "标题命中 XX 内容命中 YY 富文本命中 ZZ"
|
||||
}
|
||||
|
||||
// CheckTextHits 对一组文本做敏感词命中检测,命中返回 *SensitiveHit,未命中返回 nil。
|
||||
// 与 skd 内容审查任务的匹配规则保持一致:任一字段命中即视为命中。
|
||||
// 调用方在入库前调用,命中后应将记录的 status 强制为"未上架"语义的值。
|
||||
//
|
||||
// 词条由外部传入,便于批量场景一次加载、多次匹配;传 nil 或空切片视为词库为空,直接返回 nil。
|
||||
func CheckTextHits(terms []string, sourceID, title, content, richText string) *SensitiveHit {
|
||||
if len(terms) == 0 {
|
||||
return nil
|
||||
}
|
||||
titleHits := findSensitiveHits(title, terms)
|
||||
contentHits := findSensitiveHits(content, terms)
|
||||
richHits := findSensitiveHits(richText, terms)
|
||||
if len(titleHits) == 0 && len(contentHits) == 0 && len(richHits) == 0 {
|
||||
return nil
|
||||
}
|
||||
parts := make([]string, 0, 3)
|
||||
if len(titleHits) > 0 {
|
||||
parts = append(parts, "标题命中 "+strings.Join(titleHits, "、"))
|
||||
}
|
||||
if len(contentHits) > 0 {
|
||||
parts = append(parts, "内容命中 "+strings.Join(contentHits, "、"))
|
||||
}
|
||||
if len(richHits) > 0 {
|
||||
parts = append(parts, "富文本命中 "+strings.Join(richHits, "、"))
|
||||
}
|
||||
return &SensitiveHit{
|
||||
SourceID: sourceID,
|
||||
Title: title,
|
||||
Detail: strings.Join(parts, " "),
|
||||
}
|
||||
}
|
||||
|
||||
// SensitiveForcedOfflineTip 写库后给管理员的统一提示文案
|
||||
const SensitiveForcedOfflineTip = "由于部分数据内容命中敏感词,状态强制为下架"
|
||||
|
||||
// findSensitiveHits substring 命中检测的内部别名,代理 sensitivewordmod.MatchHits
|
||||
func findSensitiveHits(input string, terms []string) []string {
|
||||
return sensitivewordmod.MatchHits(input, terms)
|
||||
}
|
||||
|
||||
// shouldCheckSensitiveOnUpdate 判断单帖编辑 UpdateVidInfo 是否需要做敏感词校验
|
||||
//
|
||||
// 1. status 被切到 上架(CheckPass=1) / 上架并免费(Free=3) —— 上架转换
|
||||
// 2. 当前已是上架态(1/3) 且本次更新涉及 Title/Content/RichText —— 已上架内容改动
|
||||
func shouldCheckSensitiveOnUpdate(vidInfo vidmod.VideoModel, req vidmod.EditReq) bool {
|
||||
if req.Status != nil && (*req.Status == vidmod.CheckPass || *req.Status == vidmod.Free) {
|
||||
return true
|
||||
}
|
||||
isCurrentlyShelved := vidInfo.Status == vidmod.CheckPass || vidInfo.Status == vidmod.Free
|
||||
touchesText := req.Title != nil || req.Content != nil || req.RichText != nil
|
||||
return isCurrentlyShelved && touchesText
|
||||
}
|
||||
|
||||
// parseObjectIDs 把十六进制 id 字符串数组转 ObjectID,非法 id 静默丢弃
|
||||
func parseObjectIDs(ids []string) []primitive.ObjectID {
|
||||
out := make([]primitive.ObjectID, 0, len(ids))
|
||||
for _, s := range ids {
|
||||
oid, err := primitive.ObjectIDFromHex(s)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
out = append(out, oid)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// formatHitLine 按需求格式拼接单条命中描述:
|
||||
// "id:xxxx 对象标题 XXXXXX。 标题命中 XX 内容命中 XX 富文本命中 XX,不予上架成功"
|
||||
func formatHitLine(id primitive.ObjectID, title string, titleHits, contentHits, richHits []string) string {
|
||||
parts := make([]string, 0, 3)
|
||||
if len(titleHits) > 0 {
|
||||
parts = append(parts, "标题命中 "+strings.Join(titleHits, "、"))
|
||||
}
|
||||
if len(contentHits) > 0 {
|
||||
parts = append(parts, "内容命中 "+strings.Join(contentHits, "、"))
|
||||
}
|
||||
if len(richHits) > 0 {
|
||||
parts = append(parts, "富文本命中 "+strings.Join(richHits, "、"))
|
||||
}
|
||||
return fmt.Sprintf("id:%s 对象标题 %s。 %s,不予上架成功", id.Hex(), title, strings.Join(parts, " "))
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package vidser
|
||||
|
||||
import (
|
||||
"sort"
|
||||
|
||||
"91porn-server/models/v/moduleconfmod"
|
||||
|
||||
"go.mongodb.org/mongo-driver/bson"
|
||||
)
|
||||
|
||||
var excludedTagSearchModuleIDs = moduleconfmod.ExcludedSearchModuleIDs
|
||||
|
||||
func tagSearchCacheKey(base string, excludedModuleIDs []string) string {
|
||||
moduleIDs := append([]string(nil), excludedModuleIDs...)
|
||||
sort.Strings(moduleIDs)
|
||||
for _, moduleID := range moduleIDs {
|
||||
base += ":" + moduleID
|
||||
}
|
||||
return base
|
||||
}
|
||||
|
||||
func applyTagSearchVisibilityFilter(filter bson.M, excludedModuleIDs []string) {
|
||||
if len(excludedModuleIDs) == 0 {
|
||||
return
|
||||
}
|
||||
filter["mId"] = bson.M{"$nin": excludedModuleIDs}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package vidser
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"go.mongodb.org/mongo-driver/bson"
|
||||
)
|
||||
|
||||
func TestTagSearchCacheKeyTracksExcludedModules(t *testing.T) {
|
||||
base := "tag-videos"
|
||||
first := tagSearchCacheKey(base, []string{"module-b", "module-a"})
|
||||
second := tagSearchCacheKey(base, []string{"module-a", "module-b"})
|
||||
if first != second {
|
||||
t.Fatalf("cache keys differ for the same module set: %q != %q", first, second)
|
||||
}
|
||||
if first == tagSearchCacheKey(base, []string{"module-a"}) {
|
||||
t.Fatal("cache key did not change when the excluded module set changed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyTagSearchVisibilityFilter(t *testing.T) {
|
||||
filter := bson.M{"status": 1}
|
||||
excluded := []string{"module-a", "module-b"}
|
||||
applyTagSearchVisibilityFilter(filter, excluded)
|
||||
|
||||
want := bson.M{
|
||||
"status": 1,
|
||||
"mId": bson.M{"$nin": excluded},
|
||||
}
|
||||
if !reflect.DeepEqual(filter, want) {
|
||||
t.Fatalf("filter = %#v, want %#v", filter, want)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package vidser
|
||||
|
||||
type SortMode struct {
|
||||
Field string
|
||||
Value int
|
||||
}
|
||||
|
||||
type SortModeList []SortMode
|
||||
|
||||
// LiaoBaTopSortModeList returns the deterministic order used by pinned-video
|
||||
// lists. Duplicate liaoBaTopSort values are allowed, so reviewAt and _id are
|
||||
// used as stable tie-breakers.
|
||||
func LiaoBaTopSortModeList() SortModeList {
|
||||
return SortModeList{
|
||||
{Field: "liaoBaTopSort", Value: -1},
|
||||
{Field: "reviewAt", Value: -1},
|
||||
{Field: "_id", Value: -1},
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user