2026-08-03 05:52:02 +00:00
|
|
|
|
package repository
|
|
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
|
"context"
|
|
|
|
|
|
|
|
|
|
|
|
"apps/backend/internal/module/radar/domain"
|
|
|
|
|
|
|
|
|
|
|
|
"github.com/zeromicro/go-zero/core/stores/mon"
|
|
|
|
|
|
"go.mongodb.org/mongo-driver/bson"
|
|
|
|
|
|
"go.mongodb.org/mongo-driver/mongo/options"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
func (s *MonStore) SaveWatch(ctx context.Context, w *domain.RadarWatch) error {
|
2026-08-13 02:22:24 +00:00
|
|
|
|
if w == nil {
|
|
|
|
|
|
return domain.ErrValidation
|
|
|
|
|
|
}
|
|
|
|
|
|
if err := w.NormalizeContext(); err != nil {
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
var old domain.RadarWatch
|
|
|
|
|
|
if err := s.watches.FindOne(ctx, &old, bson.M{"_id": w.ID}); err == nil {
|
|
|
|
|
|
if old.ContextMode == domain.WatchContextProduct && (old.BrandID != w.BrandID || old.ProductID != w.ProductID) {
|
|
|
|
|
|
return domain.ErrValidation
|
|
|
|
|
|
}
|
|
|
|
|
|
} else if err != mon.ErrNotFound {
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
2026-08-03 05:52:02 +00:00
|
|
|
|
_, err := s.watches.ReplaceOne(ctx, bson.M{"_id": w.ID}, w, options.Replace().SetUpsert(true))
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func (s *MonStore) GetWatch(ctx context.Context, id string) (*domain.RadarWatch, error) {
|
|
|
|
|
|
var w domain.RadarWatch
|
|
|
|
|
|
err := s.watches.FindOne(ctx, &w, bson.M{"_id": id})
|
|
|
|
|
|
if err == mon.ErrNotFound {
|
|
|
|
|
|
return nil, domain.ErrNotFound
|
|
|
|
|
|
}
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return nil, err
|
|
|
|
|
|
}
|
|
|
|
|
|
return &w, nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-13 02:22:24 +00:00
|
|
|
|
func (s *MonStore) DeleteWatch(ctx context.Context, id string) error {
|
|
|
|
|
|
res, err := s.watches.DeleteOne(ctx, bson.M{"_id": id})
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
if res == 0 {
|
|
|
|
|
|
return domain.ErrNotFound
|
|
|
|
|
|
}
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-03 05:52:02 +00:00
|
|
|
|
func (s *MonStore) ListWatches(ctx context.Context, ownerUID int64, f domain.WatchListFilter) ([]*domain.RadarWatch, int64, error) {
|
|
|
|
|
|
q := bson.M{"owner_uid": ownerUID}
|
|
|
|
|
|
if f.Status != "" {
|
|
|
|
|
|
q["status"] = f.Status
|
|
|
|
|
|
}
|
2026-08-13 02:22:24 +00:00
|
|
|
|
if f.ContextMode == domain.WatchContextGeneric {
|
|
|
|
|
|
q["$or"] = bson.A{bson.M{"context_mode": domain.WatchContextGeneric}, bson.M{"context_mode": bson.M{"$exists": false}}, bson.M{"context_mode": ""}}
|
|
|
|
|
|
} else if f.ContextMode != "" {
|
|
|
|
|
|
q["context_mode"] = f.ContextMode
|
|
|
|
|
|
}
|
|
|
|
|
|
if f.BrandID != "" {
|
|
|
|
|
|
q["brand_id"] = f.BrandID
|
|
|
|
|
|
}
|
|
|
|
|
|
if f.ProductID != "" {
|
|
|
|
|
|
q["product_id"] = f.ProductID
|
|
|
|
|
|
}
|
2026-08-03 05:52:02 +00:00
|
|
|
|
total, err := s.watches.CountDocuments(ctx, q)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return nil, 0, err
|
|
|
|
|
|
}
|
|
|
|
|
|
page, ps := f.Page, f.PageSize
|
|
|
|
|
|
if page < 1 {
|
|
|
|
|
|
page = 1
|
|
|
|
|
|
}
|
|
|
|
|
|
if ps < 1 {
|
|
|
|
|
|
ps = 20
|
|
|
|
|
|
}
|
|
|
|
|
|
var list []*domain.RadarWatch
|
|
|
|
|
|
err = s.watches.Find(ctx, &list, q, options.Find().
|
|
|
|
|
|
SetSort(bson.D{{Key: "created_at", Value: -1}}).
|
|
|
|
|
|
SetSkip(int64((page-1)*ps)).
|
|
|
|
|
|
SetLimit(int64(ps)))
|
2026-08-13 02:22:24 +00:00
|
|
|
|
for _, w := range list {
|
|
|
|
|
|
if w != nil && w.ContextMode == "" {
|
|
|
|
|
|
w.ContextMode = domain.WatchContextGeneric
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-08-03 05:52:02 +00:00
|
|
|
|
return list, total, err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func (s *MonStore) ListActiveWatches(ctx context.Context, ownerUID int64) ([]*domain.RadarWatch, error) {
|
|
|
|
|
|
var list []*domain.RadarWatch
|
|
|
|
|
|
err := s.watches.Find(ctx, &list,
|
|
|
|
|
|
bson.M{"owner_uid": ownerUID, "status": domain.WatchActive},
|
|
|
|
|
|
options.Find().SetSort(bson.D{{Key: "created_at", Value: 1}}))
|
|
|
|
|
|
return list, err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func (s *MonStore) ListAllActiveWatches(ctx context.Context) ([]*domain.RadarWatch, error) {
|
|
|
|
|
|
var list []*domain.RadarWatch
|
|
|
|
|
|
err := s.watches.Find(ctx, &list,
|
|
|
|
|
|
bson.M{"status": domain.WatchActive},
|
|
|
|
|
|
options.Find().SetSort(bson.D{
|
|
|
|
|
|
{Key: "owner_uid", Value: 1},
|
|
|
|
|
|
{Key: "created_at", Value: 1},
|
|
|
|
|
|
}))
|
|
|
|
|
|
return list, err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func (s *MonStore) CountActiveWatches(ctx context.Context, ownerUID int64) (int64, error) {
|
|
|
|
|
|
return s.watches.CountDocuments(ctx, bson.M{"owner_uid": ownerUID, "status": domain.WatchActive})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// TouchWatchSweptAt 只動 last_swept_at,避免與使用者同時編輯關鍵字互相覆蓋。
|
|
|
|
|
|
func (s *MonStore) TouchWatchSweptAt(ctx context.Context, id string, at int64) error {
|
|
|
|
|
|
res, err := s.watches.UpdateOne(ctx, bson.M{"_id": id}, bson.M{"$set": bson.M{"last_swept_at": at}})
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
if res.MatchedCount == 0 {
|
|
|
|
|
|
return domain.ErrNotFound
|
|
|
|
|
|
}
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|