package repository import ( "context" "strings" "haixun-backend/internal/library/clock" app "haixun-backend/internal/library/errors" "haixun-backend/internal/library/errors/code" "haixun-backend/internal/model/placement_topic/domain/entity" domrepo "haixun-backend/internal/model/placement_topic/domain/repository" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo/options" ) type mongoRepository struct { collection *mongo.Collection } func NewMongoRepository(db *mongo.Database) domrepo.Repository { if db == nil { return &mongoRepository{} } return &mongoRepository{collection: db.Collection(entity.CollectionName)} } func (r *mongoRepository) EnsureIndexes(ctx context.Context) error { if r.collection == nil { return nil } _, err := r.collection.Indexes().CreateMany(ctx, []mongo.IndexModel{ {Keys: bson.D{{Key: "tenant_id", Value: 1}, {Key: "owner_uid", Value: 1}, {Key: "update_at", Value: -1}}}, {Keys: bson.D{{Key: "tenant_id", Value: 1}, {Key: "owner_uid", Value: 1}, {Key: "brand_id", Value: 1}}}, {Keys: bson.D{{Key: "tenant_id", Value: 1}, {Key: "owner_uid", Value: 1}, {Key: "_id", Value: 1}}, Options: options.Index().SetUnique(true)}, }) return err } func (r *mongoRepository) Create(ctx context.Context, topic *entity.Topic) (*entity.Topic, error) { if r.collection == nil { return nil, app.For(code.Brand).DBUnavailable("Mongo is not configured") } now := clock.NowUnixNano() topic.CreateAt = now topic.UpdateAt = now if topic.Status == "" { topic.Status = entity.StatusOpen } _, err := r.collection.InsertOne(ctx, topic) if err != nil { return nil, err } return topic, nil } func (r *mongoRepository) FindByID(ctx context.Context, tenantID, ownerUID, topicID string) (*entity.Topic, error) { return r.findOne(ctx, bson.M{ "_id": strings.TrimSpace(topicID), "tenant_id": tenantID, "owner_uid": ownerUID, "status": entity.StatusOpen, }) } func (r *mongoRepository) ListByOwner(ctx context.Context, tenantID, ownerUID string) ([]*entity.Topic, error) { if r.collection == nil { return nil, app.For(code.Brand).DBUnavailable("Mongo is not configured") } cursor, err := r.collection.Find( ctx, bson.M{"tenant_id": tenantID, "owner_uid": ownerUID, "status": entity.StatusOpen}, options.Find().SetSort(bson.D{{Key: "update_at", Value: -1}}), ) if err != nil { return nil, err } defer cursor.Close(ctx) var items []*entity.Topic if err := cursor.All(ctx, &items); err != nil { return nil, err } return items, nil } func (r *mongoRepository) ListByBrand(ctx context.Context, tenantID, ownerUID, brandID string) ([]*entity.Topic, error) { if r.collection == nil { return nil, app.For(code.Brand).DBUnavailable("Mongo is not configured") } cursor, err := r.collection.Find( ctx, bson.M{ "tenant_id": tenantID, "owner_uid": ownerUID, "brand_id": strings.TrimSpace(brandID), "status": entity.StatusOpen, }, options.Find().SetSort(bson.D{{Key: "update_at", Value: -1}}), ) if err != nil { return nil, err } defer cursor.Close(ctx) var items []*entity.Topic if err := cursor.All(ctx, &items); err != nil { return nil, err } return items, nil } func (r *mongoRepository) Update(ctx context.Context, tenantID, ownerUID, topicID string, patch map[string]interface{}) (*entity.Topic, error) { if r.collection == nil { return nil, app.For(code.Brand).DBUnavailable("Mongo is not configured") } if len(patch) == 0 { return r.FindByID(ctx, tenantID, ownerUID, topicID) } patch["update_at"] = clock.NowUnixNano() var out entity.Topic err := r.collection.FindOneAndUpdate( ctx, bson.M{"_id": topicID, "tenant_id": tenantID, "owner_uid": ownerUID, "status": entity.StatusOpen}, bson.M{"$set": patch}, options.FindOneAndUpdate().SetReturnDocument(options.After), ).Decode(&out) if err == mongo.ErrNoDocuments { return nil, app.For(code.Brand).ResNotFound("placement topic not found") } return &out, err } func (r *mongoRepository) SoftDelete(ctx context.Context, tenantID, ownerUID, topicID string) error { if r.collection == nil { return app.For(code.Brand).DBUnavailable("Mongo is not configured") } res, err := r.collection.UpdateOne( ctx, bson.M{"_id": topicID, "tenant_id": tenantID, "owner_uid": ownerUID, "status": entity.StatusOpen}, bson.M{"$set": bson.M{"status": entity.StatusDeleted, "update_at": clock.NowUnixNano()}}, ) if err != nil { return err } if res.MatchedCount == 0 { return app.For(code.Brand).ResNotFound("placement topic not found") } return nil } func (r *mongoRepository) findOne(ctx context.Context, filter bson.M) (*entity.Topic, error) { if r.collection == nil { return nil, app.For(code.Brand).DBUnavailable("Mongo is not configured") } var out entity.Topic err := r.collection.FindOne(ctx, filter).Decode(&out) if err == mongo.ErrNoDocuments { return nil, app.For(code.Brand).ResNotFound("placement topic not found") } if err != nil { return nil, err } return &out, nil }