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/member/domain/entity" domrepo "haixun-backend/internal/model/member/domain/repository" "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" ) 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: "uid", Value: 1}}, Options: options.Index().SetUnique(true)}, {Keys: bson.D{{Key: "tenant_id", Value: 1}, {Key: "email", Value: 1}}, Options: options.Index().SetUnique(true)}, }) return err } func (r *mongoRepository) Create(ctx context.Context, member *entity.Member) (*entity.Member, error) { if r.collection == nil { return nil, app.For(code.Member).DBUnavailable("Mongo is not configured") } now := clock.NowUnixNano() member.Email = normalizeEmail(member.Email) member.CreateAt = now member.UpdateAt = now if member.ID.IsZero() { member.ID = primitive.NewObjectID() } if member.Status == "" { member.Status = entity.StatusOpen } if member.Origin == "" { member.Origin = entity.OriginNative } _, err := r.collection.InsertOne(ctx, member) if mongo.IsDuplicateKeyError(err) { return nil, app.For(code.Member).ResConflict("member already exists") } if err != nil { return nil, err } return member, nil } func (r *mongoRepository) FindByUID(ctx context.Context, tenantID, uid string) (*entity.Member, error) { return r.findOne(ctx, bson.M{"tenant_id": tenantID, "uid": uid}) } func (r *mongoRepository) FindByEmail(ctx context.Context, tenantID, email string) (*entity.Member, error) { return r.findOne(ctx, bson.M{"tenant_id": tenantID, "email": normalizeEmail(email)}) } func (r *mongoRepository) SetRoles(ctx context.Context, tenantID, uid string, roles []string) error { if r.collection == nil { return app.For(code.Member).DBUnavailable("Mongo is not configured") } if tenantID == "" || uid == "" { return app.For(code.Member).InputMissingRequired("tenant_id and uid are required") } res, err := r.collection.UpdateOne( ctx, bson.M{"tenant_id": tenantID, "uid": uid}, bson.M{"$set": bson.M{"roles": roles, "update_at": clock.NowUnixNano()}}, ) if err != nil { return err } if res.MatchedCount == 0 { return app.For(code.Member).ResNotFound("member not found") } return nil } func (r *mongoRepository) UpdateProfile(ctx context.Context, tenantID, uid string, update domrepo.ProfileUpdate) (*entity.Member, error) { if r.collection == nil { return nil, app.For(code.Member).DBUnavailable("Mongo is not configured") } set := bson.M{"update_at": clock.NowUnixNano()} if update.DisplayName != nil { set["display_name"] = *update.DisplayName } if update.Avatar != nil { set["avatar"] = *update.Avatar } if update.Language != nil { set["language"] = *update.Language } if update.Currency != nil { set["currency"] = *update.Currency } if update.Phone != nil { set["phone"] = *update.Phone } var out entity.Member err := r.collection.FindOneAndUpdate( ctx, bson.M{"tenant_id": tenantID, "uid": uid}, bson.M{"$set": set}, options.FindOneAndUpdate().SetReturnDocument(options.After), ).Decode(&out) if err == mongo.ErrNoDocuments { return nil, app.For(code.Member).ResNotFound("member not found") } return &out, err } func (r *mongoRepository) findOne(ctx context.Context, filter bson.M) (*entity.Member, error) { if r.collection == nil { return nil, app.For(code.Member).DBUnavailable("Mongo is not configured") } var out entity.Member err := r.collection.FindOne(ctx, filter).Decode(&out) if err == mongo.ErrNoDocuments { return nil, app.For(code.Member).ResNotFound("member not found") } if err != nil { return nil, err } return &out, nil } func normalizeEmail(email string) string { return strings.ToLower(strings.TrimSpace(email)) }