2026-05-20 23:51:22 +00:00
|
|
|
package repository
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"context"
|
|
|
|
|
"errors"
|
|
|
|
|
"time"
|
|
|
|
|
|
|
|
|
|
libmongo "gateway/internal/library/mongo"
|
|
|
|
|
member "gateway/internal/model/member/domain"
|
|
|
|
|
"gateway/internal/model/member/domain/entity"
|
|
|
|
|
"gateway/internal/model/member/domain/enum"
|
|
|
|
|
domrepo "gateway/internal/model/member/domain/repository"
|
|
|
|
|
|
|
|
|
|
"go.mongodb.org/mongo-driver/v2/bson"
|
|
|
|
|
mongodriver "go.mongodb.org/mongo-driver/v2/mongo"
|
|
|
|
|
"go.mongodb.org/mongo-driver/v2/mongo/options"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// MemberRepositoryParam configures the Mongo member repository.
|
|
|
|
|
type MemberRepositoryParam struct {
|
|
|
|
|
Conf *libmongo.Conf
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type memberRepository struct {
|
|
|
|
|
db libmongo.DocumentDBUseCase
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// NewMemberRepository creates a Mongo-backed MemberRepository.
|
|
|
|
|
func NewMemberRepository(param MemberRepositoryParam) domrepo.MemberRepository {
|
|
|
|
|
documentDB, err := libmongo.NewDocumentDB(param.Conf, entity.Member{}.CollectionName())
|
|
|
|
|
if err != nil {
|
|
|
|
|
panic(err)
|
|
|
|
|
}
|
|
|
|
|
return &memberRepository{db: documentDB}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (r *memberRepository) Insert(ctx context.Context, rec *entity.Member) error {
|
|
|
|
|
now := time.Now().UTC().UnixMilli()
|
|
|
|
|
if rec.ID.IsZero() {
|
|
|
|
|
rec.ID = bson.NewObjectID()
|
|
|
|
|
}
|
|
|
|
|
if rec.CreateAt == 0 {
|
|
|
|
|
rec.CreateAt = now
|
|
|
|
|
}
|
|
|
|
|
if rec.UpdateAt == 0 {
|
|
|
|
|
rec.UpdateAt = now
|
|
|
|
|
}
|
|
|
|
|
_, err := r.db.GetClient().InsertOne(ctx, rec)
|
|
|
|
|
if err != nil {
|
|
|
|
|
if mongodriver.IsDuplicateKeyError(err) {
|
|
|
|
|
return member.ErrDuplicateMember
|
|
|
|
|
}
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (r *memberRepository) GetByUID(ctx context.Context, tenantID, uid string) (*entity.Member, error) {
|
|
|
|
|
var doc entity.Member
|
|
|
|
|
filter := bson.M{
|
|
|
|
|
member.BSONFieldTenantID: tenantID,
|
|
|
|
|
member.BSONFieldUID: uid,
|
|
|
|
|
}
|
|
|
|
|
if err := r.db.GetClient().FindOne(ctx, &doc, filter); err != nil {
|
|
|
|
|
if errors.Is(err, mongodriver.ErrNoDocuments) {
|
|
|
|
|
return nil, member.ErrNotFound
|
|
|
|
|
}
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
return &doc, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (r *memberRepository) GetByZitadelUserID(ctx context.Context, tenantID, zitadelUserID string) (*entity.Member, error) {
|
|
|
|
|
var doc entity.Member
|
|
|
|
|
filter := bson.M{
|
|
|
|
|
member.BSONFieldTenantID: tenantID,
|
|
|
|
|
member.BSONFieldZitadelUserID: zitadelUserID,
|
|
|
|
|
}
|
|
|
|
|
if err := r.db.GetClient().FindOne(ctx, &doc, filter); err != nil {
|
|
|
|
|
if errors.Is(err, mongodriver.ErrNoDocuments) {
|
|
|
|
|
return nil, member.ErrNotFound
|
|
|
|
|
}
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
return &doc, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (r *memberRepository) UpdateProfile(ctx context.Context, tenantID, uid string, update *domrepo.MemberUpdate) (*entity.Member, error) {
|
|
|
|
|
set := bson.M{member.BSONFieldUpdateAt: time.Now().UTC().UnixMilli()}
|
|
|
|
|
if update.DisplayName != nil {
|
|
|
|
|
set[member.BSONFieldDisplayName] = *update.DisplayName
|
|
|
|
|
}
|
|
|
|
|
if update.Avatar != nil {
|
|
|
|
|
set[member.BSONFieldAvatar] = *update.Avatar
|
|
|
|
|
}
|
|
|
|
|
if update.Language != nil {
|
|
|
|
|
set[member.BSONFieldLanguage] = *update.Language
|
|
|
|
|
}
|
|
|
|
|
if update.Currency != nil {
|
|
|
|
|
set[member.BSONFieldCurrency] = *update.Currency
|
|
|
|
|
}
|
|
|
|
|
if update.Phone != nil {
|
|
|
|
|
set[member.BSONFieldPhone] = *update.Phone
|
|
|
|
|
}
|
|
|
|
|
filter := bson.M{member.BSONFieldTenantID: tenantID, member.BSONFieldUID: uid}
|
|
|
|
|
opts := options.FindOneAndUpdate().SetReturnDocument(options.After)
|
|
|
|
|
var doc entity.Member
|
2026-05-21 06:45:35 +00:00
|
|
|
if err := r.db.GetClient().FindOneAndUpdate(ctx, &doc, filter, bson.M{bsonOpSet: set}, opts); err != nil {
|
2026-05-20 23:51:22 +00:00
|
|
|
if errors.Is(err, mongodriver.ErrNoDocuments) {
|
|
|
|
|
return nil, member.ErrNotFound
|
|
|
|
|
}
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
return &doc, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (r *memberRepository) UpdateStatus(ctx context.Context, tenantID, uid string, status enum.MemberStatus, suspendReason string) error {
|
|
|
|
|
set := bson.M{
|
|
|
|
|
member.BSONFieldMemberStatus: status,
|
|
|
|
|
member.BSONFieldUpdateAt: time.Now().UTC().UnixMilli(),
|
|
|
|
|
}
|
|
|
|
|
if status == enum.MemberStatusSuspended {
|
|
|
|
|
set[member.BSONFieldSuspendReason] = suspendReason
|
|
|
|
|
}
|
|
|
|
|
if status == enum.MemberStatusDeleted {
|
|
|
|
|
set[member.BSONFieldDeletedAt] = time.Now().UTC().UnixMilli()
|
|
|
|
|
}
|
|
|
|
|
filter := bson.M{member.BSONFieldTenantID: tenantID, member.BSONFieldUID: uid}
|
2026-05-21 06:45:35 +00:00
|
|
|
res, err := r.db.GetClient().UpdateOne(ctx, filter, bson.M{bsonOpSet: set})
|
2026-05-20 23:51:22 +00:00
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
if res.MatchedCount == 0 {
|
|
|
|
|
return member.ErrNotFound
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (r *memberRepository) List(ctx context.Context, filter domrepo.ListMembersFilter) ([]*entity.Member, int64, error) {
|
|
|
|
|
q := bson.M{member.BSONFieldTenantID: filter.TenantID}
|
|
|
|
|
if filter.Status != "" {
|
|
|
|
|
q[member.BSONFieldMemberStatus] = filter.Status
|
|
|
|
|
}
|
|
|
|
|
total, err := r.db.GetClient().CountDocuments(ctx, q)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, 0, err
|
|
|
|
|
}
|
|
|
|
|
limit := filter.Limit
|
|
|
|
|
if limit <= 0 {
|
|
|
|
|
limit = 20
|
|
|
|
|
}
|
|
|
|
|
if limit > 100 {
|
|
|
|
|
limit = 100
|
|
|
|
|
}
|
|
|
|
|
opts := options.Find().
|
|
|
|
|
SetSkip(filter.Offset).
|
|
|
|
|
SetLimit(limit).
|
|
|
|
|
SetSort(bson.D{{Key: member.BSONFieldCreateAt, Value: -1}})
|
|
|
|
|
var docs []*entity.Member
|
|
|
|
|
if err := r.db.GetClient().Find(ctx, &docs, q, opts); err != nil {
|
|
|
|
|
return nil, 0, err
|
|
|
|
|
}
|
|
|
|
|
return docs, total, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (r *memberRepository) SetBusinessEmailVerified(ctx context.Context, tenantID, uid, email string) error {
|
|
|
|
|
now := time.Now().UTC().UnixMilli()
|
|
|
|
|
set := bson.M{
|
|
|
|
|
member.BSONFieldBusinessEmail: email,
|
|
|
|
|
member.BSONFieldBusinessEmailVerified: true,
|
|
|
|
|
member.BSONFieldBusinessEmailVerifiedAt: now,
|
|
|
|
|
member.BSONFieldUpdateAt: now,
|
|
|
|
|
}
|
|
|
|
|
filter := bson.M{member.BSONFieldTenantID: tenantID, member.BSONFieldUID: uid}
|
2026-05-21 06:45:35 +00:00
|
|
|
res, err := r.db.GetClient().UpdateOne(ctx, filter, bson.M{bsonOpSet: set})
|
2026-05-20 23:51:22 +00:00
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
if res.MatchedCount == 0 {
|
|
|
|
|
return member.ErrNotFound
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (r *memberRepository) SetBusinessPhoneVerified(ctx context.Context, tenantID, uid, phone string) error {
|
|
|
|
|
now := time.Now().UTC().UnixMilli()
|
|
|
|
|
set := bson.M{
|
|
|
|
|
member.BSONFieldBusinessPhone: phone,
|
|
|
|
|
member.BSONFieldBusinessPhoneVerified: true,
|
|
|
|
|
member.BSONFieldBusinessPhoneVerifiedAt: now,
|
|
|
|
|
member.BSONFieldUpdateAt: now,
|
|
|
|
|
}
|
|
|
|
|
filter := bson.M{member.BSONFieldTenantID: tenantID, member.BSONFieldUID: uid}
|
2026-05-21 06:45:35 +00:00
|
|
|
res, err := r.db.GetClient().UpdateOne(ctx, filter, bson.M{bsonOpSet: set})
|
2026-05-20 23:51:22 +00:00
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
if res.MatchedCount == 0 {
|
|
|
|
|
return member.ErrNotFound
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Index20260520001UP ensures members collection indexes exist.
|
|
|
|
|
func (r *memberRepository) Index20260520001UP(ctx context.Context) error {
|
|
|
|
|
if err := r.db.PopulateMultiIndex(ctx,
|
|
|
|
|
[]string{member.BSONFieldTenantID, member.BSONFieldUID},
|
|
|
|
|
[]int32{1, 1}, true); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
if err := r.db.PopulateMultiIndex(ctx,
|
|
|
|
|
[]string{member.BSONFieldTenantID, member.BSONFieldZitadelUserID},
|
|
|
|
|
[]int32{1, 1}, true); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
return r.db.PopulateMultiIndex(ctx,
|
|
|
|
|
[]string{member.BSONFieldTenantID, member.BSONFieldMemberStatus, member.BSONFieldCreateAt},
|
|
|
|
|
[]int32{1, 1, -1}, false)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var _ domrepo.MemberRepository = (*memberRepository)(nil)
|