package repository import ( "context" "errors" "time" libmongo "gateway/internal/library/mongo" authdomain "gateway/internal/model/auth/domain" "gateway/internal/model/auth/domain/entity" domrepo "gateway/internal/model/auth/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" ) // InviteRepositoryParam configures the Mongo invite repository. type InviteRepositoryParam struct { Conf *libmongo.Conf } type inviteRepository struct { db libmongo.DocumentDBUseCase } // NewInviteRepository creates a Mongo-backed InviteRepository. func NewInviteRepository(param InviteRepositoryParam) domrepo.InviteRepository { documentDB, err := libmongo.NewDocumentDB(param.Conf, entity.InviteCode{}.CollectionName()) if err != nil { panic(err) } return &inviteRepository{db: documentDB} } func (r *inviteRepository) GetByTenantAndCodeHash(ctx context.Context, tenantID, codeHash string) (*entity.InviteCode, error) { var doc entity.InviteCode filter := bson.M{ authdomain.BSONFieldTenantID: tenantID, authdomain.BSONFieldCodeHash: codeHash, } if err := r.db.GetClient().FindOne(ctx, &doc, filter); err != nil { if errors.Is(err, mongodriver.ErrNoDocuments) { return nil, authdomain.ErrInviteNotFound } return nil, err } return &doc, nil } func (r *inviteRepository) ConsumeOne(ctx context.Context, id bson.ObjectID) (*entity.InviteCode, error) { now := time.Now().UTC().UnixMilli() filter := bson.M{ authdomain.BSONFieldID: id, "$expr": bson.M{ "$lt": bson.A{"$" + authdomain.BSONFieldUsedCount, "$" + authdomain.BSONFieldMaxUses}, }, "$or": bson.A{ bson.M{authdomain.BSONFieldExpiresAt: bson.M{"$lte": 0}}, bson.M{authdomain.BSONFieldExpiresAt: bson.M{"$gt": now}}, }, } update := bson.M{ "$inc": bson.M{authdomain.BSONFieldUsedCount: 1}, "$set": bson.M{authdomain.BSONFieldUpdateAt: now}, } opts := options.FindOneAndUpdate().SetReturnDocument(options.After) var doc entity.InviteCode if err := r.db.GetClient().FindOneAndUpdate(ctx, &doc, filter, update, opts); err != nil { if errors.Is(err, mongodriver.ErrNoDocuments) { return nil, authdomain.ErrInviteExhausted } return nil, err } return &doc, nil } // Index20260521001UP ensures invite_codes collection indexes exist. func (r *inviteRepository) Index20260521001UP(ctx context.Context) error { return r.db.PopulateMultiIndex(ctx, []string{authdomain.BSONFieldTenantID, authdomain.BSONFieldCodeHash}, []int32{1, 1}, true, ) } var _ domrepo.InviteRepository = (*inviteRepository)(nil)