@@ -0,0 +1,150 @@
|
||||
package scenebannermod
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"91porn-server/common/db"
|
||||
"91porn-server/models"
|
||||
|
||||
"go.mongodb.org/mongo-driver/bson"
|
||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||
"go.mongodb.org/mongo-driver/mongo"
|
||||
"go.mongodb.org/mongo-driver/mongo/options"
|
||||
)
|
||||
|
||||
var mdb *db.MongoDB
|
||||
|
||||
func Init() {
|
||||
mdb = db.Init(models.SceneBanner)
|
||||
// 旧版本通过 scene_1 唯一索引限制每个场景只能配置一条 Banner。
|
||||
// 多图轮播需要先移除该索引,已有 Banner 数据不受影响。
|
||||
if err := coll().DropIndexIfExists("scene_1"); err != nil {
|
||||
panic(fmt.Sprintf("%s model drop legacy index err ==>[%+v]", models.SceneBanner, err))
|
||||
}
|
||||
if _, err := coll().CreateIndex([]mongo.IndexModel{
|
||||
{
|
||||
Keys: bson.D{
|
||||
{Key: "scene", Value: 1},
|
||||
{Key: "sort", Value: -1},
|
||||
{Key: "updatedAt", Value: -1},
|
||||
},
|
||||
},
|
||||
{
|
||||
Keys: bson.D{
|
||||
{Key: "scene", Value: 1},
|
||||
{Key: "enable", Value: 1},
|
||||
{Key: "startAt", Value: 1},
|
||||
{Key: "endAt", Value: 1},
|
||||
},
|
||||
},
|
||||
}); err != nil {
|
||||
panic(fmt.Sprintf("%s model set index err ==>[%+v]", models.SceneBanner, err))
|
||||
}
|
||||
}
|
||||
|
||||
func coll() *db.MongoTool {
|
||||
return mdb.Coll(models.SceneBanner)
|
||||
}
|
||||
|
||||
func Insert(p *SceneBanner) error {
|
||||
p.Normalize()
|
||||
if err := p.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
now := time.Now()
|
||||
p.ID = primitive.NewObjectID()
|
||||
p.CreatedAt = now
|
||||
p.UpdatedAt = now
|
||||
_, err := coll().InsertOne(p)
|
||||
return err
|
||||
}
|
||||
|
||||
func Update(p *SceneBanner) error {
|
||||
if p.ID.IsZero() {
|
||||
return fmt.Errorf("id is required")
|
||||
}
|
||||
p.Normalize()
|
||||
if err := p.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
p.UpdatedAt = time.Now()
|
||||
set := bson.M{
|
||||
"scene": p.Scene,
|
||||
"imageUrl": p.ImageURL,
|
||||
"mediaType": p.MediaType,
|
||||
"linkType": p.LinkType,
|
||||
"linkValue": p.LinkValue,
|
||||
"sort": p.Sort,
|
||||
"enable": p.Enable,
|
||||
"updatedAt": p.UpdatedAt,
|
||||
}
|
||||
update := bson.M{"$set": set}
|
||||
unset := bson.M{}
|
||||
if p.StartAt == nil {
|
||||
unset["startAt"] = ""
|
||||
} else {
|
||||
set["startAt"] = *p.StartAt
|
||||
}
|
||||
if p.EndAt == nil {
|
||||
unset["endAt"] = ""
|
||||
} else {
|
||||
set["endAt"] = *p.EndAt
|
||||
}
|
||||
if len(unset) > 0 {
|
||||
update["$unset"] = unset
|
||||
}
|
||||
result, err := coll().UpdateOne(bson.M{"_id": p.ID}, update)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if result.MatchedCount == 0 {
|
||||
return mongo.ErrNoDocuments
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func Delete(id primitive.ObjectID) error {
|
||||
if id.IsZero() {
|
||||
return fmt.Errorf("id is required")
|
||||
}
|
||||
_, err := coll().DeleteById(id)
|
||||
return err
|
||||
}
|
||||
|
||||
func List(scene string) ([]SceneBanner, error) {
|
||||
filter := bson.M{}
|
||||
if scene != "" {
|
||||
filter["scene"] = scene
|
||||
}
|
||||
var list []SceneBanner
|
||||
err := coll().Find(&list, filter, bannerFindOptions())
|
||||
return list, err
|
||||
}
|
||||
|
||||
func FindActive(scene string, now time.Time) ([]SceneBanner, error) {
|
||||
filter := bson.M{
|
||||
"scene": scene,
|
||||
"enable": true,
|
||||
"$and": bson.A{
|
||||
bson.M{"$or": bson.A{
|
||||
bson.M{"startAt": bson.M{"$exists": false}},
|
||||
bson.M{"startAt": nil},
|
||||
bson.M{"startAt": bson.M{"$lte": now}},
|
||||
}},
|
||||
bson.M{"$or": bson.A{
|
||||
bson.M{"endAt": bson.M{"$exists": false}},
|
||||
bson.M{"endAt": nil},
|
||||
bson.M{"endAt": bson.M{"$gt": now}},
|
||||
}},
|
||||
},
|
||||
}
|
||||
var list []SceneBanner
|
||||
err := coll().Find(&list, filter, bannerFindOptions())
|
||||
return list, err
|
||||
}
|
||||
|
||||
func bannerFindOptions() *options.FindOptions {
|
||||
return options.Find().
|
||||
SetSort(bson.D{{Key: "sort", Value: -1}, {Key: "updatedAt", Value: -1}})
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
package scenebannermod
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||
)
|
||||
|
||||
const (
|
||||
SceneCommentTop = "COMMENT_TOP"
|
||||
|
||||
MediaTypeImage = "IMAGE"
|
||||
MediaTypeGIF = "GIF"
|
||||
|
||||
LinkTypeInternal = "INTERNAL"
|
||||
LinkTypeExternal = "EXTERNAL"
|
||||
LinkTypeNone = "NONE"
|
||||
)
|
||||
|
||||
type SceneBanner struct {
|
||||
ID primitive.ObjectID `json:"id" bson:"_id,omitempty"`
|
||||
Scene string `json:"scene" bson:"scene"`
|
||||
ImageURL string `json:"imageUrl" bson:"imageUrl"`
|
||||
MediaType string `json:"mediaType" bson:"mediaType"`
|
||||
LinkType string `json:"linkType" bson:"linkType"`
|
||||
LinkValue string `json:"linkValue" bson:"linkValue"`
|
||||
Sort int `json:"sort" bson:"sort"`
|
||||
Enable bool `json:"enable" bson:"enable"`
|
||||
StartAt *time.Time `json:"startAt,omitempty" bson:"startAt,omitempty"`
|
||||
EndAt *time.Time `json:"endAt,omitempty" bson:"endAt,omitempty"`
|
||||
CreatedAt time.Time `json:"createdAt" bson:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt" bson:"updatedAt"`
|
||||
}
|
||||
|
||||
func ValidScene(scene string) bool {
|
||||
return scene == SceneCommentTop
|
||||
}
|
||||
|
||||
func (p *SceneBanner) Normalize() {
|
||||
p.Scene = strings.TrimSpace(strings.ToUpper(p.Scene))
|
||||
p.ImageURL = strings.TrimSpace(p.ImageURL)
|
||||
p.MediaType = strings.TrimSpace(strings.ToUpper(p.MediaType))
|
||||
p.LinkType = strings.TrimSpace(strings.ToUpper(p.LinkType))
|
||||
p.LinkValue = strings.TrimSpace(p.LinkValue)
|
||||
}
|
||||
|
||||
func (p SceneBanner) Validate() error {
|
||||
if !ValidScene(p.Scene) {
|
||||
return fmt.Errorf("unsupported scene: %s", p.Scene)
|
||||
}
|
||||
if p.ImageURL == "" {
|
||||
return fmt.Errorf("imageUrl is required")
|
||||
}
|
||||
if p.MediaType != MediaTypeImage && p.MediaType != MediaTypeGIF {
|
||||
return fmt.Errorf("unsupported mediaType: %s", p.MediaType)
|
||||
}
|
||||
switch p.LinkType {
|
||||
case LinkTypeInternal, LinkTypeExternal:
|
||||
if p.LinkValue == "" {
|
||||
return fmt.Errorf("linkValue is required for %s", p.LinkType)
|
||||
}
|
||||
case LinkTypeNone:
|
||||
if p.LinkValue != "" {
|
||||
return fmt.Errorf("linkValue must be empty when linkType is NONE")
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("unsupported linkType: %s", p.LinkType)
|
||||
}
|
||||
if p.StartAt != nil && p.EndAt != nil && !p.EndAt.After(*p.StartAt) {
|
||||
return fmt.Errorf("endAt must be later than startAt")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p SceneBanner) ActiveAt(now time.Time) bool {
|
||||
if !p.Enable {
|
||||
return false
|
||||
}
|
||||
if p.StartAt != nil && p.StartAt.After(now) {
|
||||
return false
|
||||
}
|
||||
return p.EndAt == nil || p.EndAt.After(now)
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package scenebannermod
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"go.mongodb.org/mongo-driver/bson"
|
||||
)
|
||||
|
||||
func TestSceneBannerValidate(t *testing.T) {
|
||||
banner := SceneBanner{
|
||||
Scene: "comment_top",
|
||||
ImageURL: "https://example.com/banner.gif",
|
||||
MediaType: "gif",
|
||||
LinkType: "internal",
|
||||
LinkValue: "video://detail?id=1",
|
||||
}
|
||||
banner.Normalize()
|
||||
if err := banner.Validate(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if banner.Scene != SceneCommentTop || banner.MediaType != MediaTypeGIF {
|
||||
t.Fatalf("unexpected normalized banner: %+v", banner)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSceneBannerActiveAt(t *testing.T) {
|
||||
now := time.Now()
|
||||
start := now.Add(-time.Minute)
|
||||
end := now.Add(time.Minute)
|
||||
banner := SceneBanner{Enable: true, StartAt: &start, EndAt: &end}
|
||||
if !banner.ActiveAt(now) {
|
||||
t.Fatal("banner should be active")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBannerFindOptionsSupportsCarousel(t *testing.T) {
|
||||
opts := bannerFindOptions()
|
||||
if opts.Limit != nil {
|
||||
t.Fatalf("carousel query must not limit result count: %d", *opts.Limit)
|
||||
}
|
||||
wantSort := bson.D{{Key: "sort", Value: -1}, {Key: "updatedAt", Value: -1}}
|
||||
if !reflect.DeepEqual(opts.Sort, wantSort) {
|
||||
t.Fatalf("unexpected carousel sort: %#v", opts.Sort)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user