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/threads_account/domain/entity" domrepo "haixun-backend/internal/model/threads_account/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: "_id", Value: 1}}, Options: options.Index().SetUnique(true)}, }) return err } func (r *mongoRepository) Create(ctx context.Context, account *entity.Account) (*entity.Account, error) { if r.collection == nil { return nil, app.For(code.ThreadsAccount).DBUnavailable("Mongo is not configured") } now := clock.NowUnixNano() account.CreateAt = now account.UpdateAt = now if account.Status == "" { account.Status = entity.StatusOpen } _, err := r.collection.InsertOne(ctx, account) if err != nil { return nil, err } return account, nil } func (r *mongoRepository) FindByID(ctx context.Context, tenantID, ownerUID, accountID string) (*entity.Account, error) { return r.findOne(ctx, bson.M{ "_id": strings.TrimSpace(accountID), "tenant_id": tenantID, "owner_uid": ownerUID, "status": entity.StatusOpen, }) } func (r *mongoRepository) ListByOwner(ctx context.Context, tenantID, ownerUID string) ([]*entity.Account, error) { if r.collection == nil { return nil, app.For(code.ThreadsAccount).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.Account if err := cursor.All(ctx, &items); err != nil { return nil, err } return items, nil } func (r *mongoRepository) UpdateShell(ctx context.Context, tenantID, ownerUID, accountID string, displayName, username, personaID *string) (*entity.Account, error) { if r.collection == nil { return nil, app.For(code.ThreadsAccount).DBUnavailable("Mongo is not configured") } set := bson.M{"update_at": clock.NowUnixNano()} if displayName != nil { set["display_name"] = strings.TrimSpace(*displayName) } if username != nil { set["username"] = strings.TrimPrefix(strings.TrimSpace(*username), "@") } if personaID != nil { set["persona_id"] = strings.TrimSpace(*personaID) } var out entity.Account err := r.collection.FindOneAndUpdate( ctx, bson.M{"_id": accountID, "tenant_id": tenantID, "owner_uid": ownerUID, "status": entity.StatusOpen}, bson.M{"$set": set}, options.FindOneAndUpdate().SetReturnDocument(options.After), ).Decode(&out) if err == mongo.ErrNoDocuments { return nil, app.For(code.ThreadsAccount).ResNotFound("threads account not found") } return &out, err } func (r *mongoRepository) SoftDelete(ctx context.Context, tenantID, ownerUID, accountID string) error { if r.collection == nil { return app.For(code.ThreadsAccount).DBUnavailable("Mongo is not configured") } res, err := r.collection.UpdateOne( ctx, bson.M{"_id": accountID, "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.ThreadsAccount).ResNotFound("threads account not found") } return nil } func (r *mongoRepository) findOne(ctx context.Context, filter bson.M) (*entity.Account, error) { if r.collection == nil { return nil, app.For(code.ThreadsAccount).DBUnavailable("Mongo is not configured") } var out entity.Account err := r.collection.FindOne(ctx, filter).Decode(&out) if err == mongo.ErrNoDocuments { return nil, app.For(code.ThreadsAccount).ResNotFound("threads account not found") } if err != nil { return nil, err } return &out, nil }