mirror of
https://github.com/PrivateCaptcha/PrivateCaptcha.git
synced 2026-02-11 00:08:47 -06:00
186 lines
7.0 KiB
Go
186 lines
7.0 KiB
Go
package db
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"log/slog"
|
|
|
|
"github.com/PrivateCaptcha/PrivateCaptcha/pkg/billing"
|
|
"github.com/PrivateCaptcha/PrivateCaptcha/pkg/common"
|
|
dbgen "github.com/PrivateCaptcha/PrivateCaptcha/pkg/db/generated"
|
|
)
|
|
|
|
type SubscriptionLimits interface {
|
|
CheckOrgsLimit(ctx context.Context, userID int32, subscr *dbgen.Subscription) (bool, int, error)
|
|
CheckOrgMembersLimit(ctx context.Context, orgID int32, subscr *dbgen.Subscription) (bool, int, error)
|
|
CheckPropertiesLimit(ctx context.Context, userID int32, subscr *dbgen.Subscription) (bool, int, error)
|
|
RequestsLimit(ctx context.Context, subscr *dbgen.Subscription) (int64, error)
|
|
PropertiesLimit(ctx context.Context, subscr *dbgen.Subscription) (int, error)
|
|
OrgsLimit(ctx context.Context, subscr *dbgen.Subscription) (int, error)
|
|
}
|
|
|
|
var (
|
|
ErrNoActiveSubscription = errors.New("subscription is not active or nil")
|
|
)
|
|
|
|
type SubscriptionLimitsImpl struct {
|
|
Stage string
|
|
store Implementor
|
|
planService billing.PlanService
|
|
}
|
|
|
|
func NewSubscriptionLimits(stage string, store Implementor, planService billing.PlanService) *SubscriptionLimitsImpl {
|
|
return &SubscriptionLimitsImpl{
|
|
Stage: stage,
|
|
store: store,
|
|
planService: planService,
|
|
}
|
|
}
|
|
|
|
var _ SubscriptionLimits = (*SubscriptionLimitsImpl)(nil)
|
|
|
|
func (sl *SubscriptionLimitsImpl) CheckOrgsLimit(ctx context.Context, userID int32, subscr *dbgen.Subscription) (bool, int, error) {
|
|
if (subscr == nil) || !sl.planService.IsSubscriptionActive(subscr.Status) {
|
|
return false, 0, ErrNoActiveSubscription
|
|
}
|
|
|
|
isInternalSubscription := IsInternalSubscription(subscr.Source)
|
|
plan, err := sl.planService.FindPlan(subscr.ExternalProductID, subscr.ExternalPriceID, sl.Stage, isInternalSubscription)
|
|
if err != nil {
|
|
slog.ErrorContext(ctx, "Failed to find billing plan for subscription", "subscriptionID", subscr.ID,
|
|
"priceID", subscr.ExternalPriceID, "productID", subscr.ExternalProductID, common.ErrAttr(err))
|
|
return false, 0, err
|
|
}
|
|
|
|
count := 0
|
|
// NOTE: this should be freshly cached as we should have just rendered the dashboard
|
|
if orgs, err := sl.store.Impl().RetrieveUserOrganizations(ctx, userID); err == nil {
|
|
for _, org := range orgs {
|
|
if org.Level == dbgen.AccessLevelOwner {
|
|
count++
|
|
}
|
|
}
|
|
} else {
|
|
slog.ErrorContext(ctx, "Failed to retrieve user orgs", "userID", userID, common.ErrAttr(err))
|
|
return false, 0, err
|
|
}
|
|
|
|
ok := (plan.OrgsLimit() == 0) || (count < plan.OrgsLimit())
|
|
|
|
return ok, count - plan.OrgsLimit(), nil
|
|
}
|
|
|
|
func (sl *SubscriptionLimitsImpl) CheckOrgMembersLimit(ctx context.Context, orgID int32, subscr *dbgen.Subscription) (bool, int, error) {
|
|
if (subscr == nil) || !sl.planService.IsSubscriptionActive(subscr.Status) {
|
|
return false, 0, ErrNoActiveSubscription
|
|
}
|
|
|
|
isInternalSubscription := IsInternalSubscription(subscr.Source)
|
|
plan, err := sl.planService.FindPlan(subscr.ExternalProductID, subscr.ExternalPriceID, sl.Stage, isInternalSubscription)
|
|
if err != nil {
|
|
slog.ErrorContext(ctx, "Failed to find billing plan for subscription", "subscriptionID", subscr.ID, common.ErrAttr(err))
|
|
return false, 0, err
|
|
}
|
|
|
|
members, err := sl.store.Impl().RetrieveOrganizationUsers(ctx, orgID)
|
|
if err != nil {
|
|
slog.ErrorContext(ctx, "Failed to retrieve org users", common.ErrAttr(err))
|
|
return false, 0, err
|
|
}
|
|
|
|
ok := (plan.OrgMembersLimit() == 0) || (len(members) < plan.OrgMembersLimit())
|
|
|
|
return ok, len(members) - plan.OrgMembersLimit(), nil
|
|
}
|
|
|
|
func (sl *SubscriptionLimitsImpl) CheckPropertiesLimit(ctx context.Context, userID int32, subscr *dbgen.Subscription) (bool, int, error) {
|
|
if (subscr == nil) || !sl.planService.IsSubscriptionActive(subscr.Status) {
|
|
return false, 0, ErrNoActiveSubscription
|
|
}
|
|
|
|
isInternalSubscription := IsInternalSubscription(subscr.Source)
|
|
plan, err := sl.planService.FindPlan(subscr.ExternalProductID, subscr.ExternalPriceID, sl.Stage, isInternalSubscription)
|
|
if err != nil {
|
|
slog.ErrorContext(ctx, "Failed to find billing plan for subscription", "subscriptionID", subscr.ID, common.ErrAttr(err))
|
|
return false, 0, err
|
|
}
|
|
|
|
count, err := sl.store.Impl().RetrieveUserPropertiesCount(ctx, userID)
|
|
if err != nil {
|
|
slog.ErrorContext(ctx, "Failed to retrieve properties count", "userID", userID, common.ErrAttr(err))
|
|
return false, 0, err
|
|
}
|
|
|
|
ok := (plan.PropertiesLimit() == 0) || (count < int64(plan.PropertiesLimit()))
|
|
|
|
return ok, int(count) - plan.PropertiesLimit(), nil
|
|
}
|
|
|
|
func (sl *SubscriptionLimitsImpl) RequestsLimit(ctx context.Context, subscr *dbgen.Subscription) (int64, error) {
|
|
if (subscr == nil) || !sl.planService.IsSubscriptionActive(subscr.Status) {
|
|
return 0, ErrNoActiveSubscription
|
|
}
|
|
|
|
plan, err := sl.planService.FindPlan(subscr.ExternalProductID, subscr.ExternalPriceID, sl.Stage,
|
|
IsInternalSubscription(subscr.Source))
|
|
if err != nil {
|
|
slog.ErrorContext(ctx, "Failed to find billing plan", "productID", subscr.ExternalProductID, "priceID", subscr.ExternalPriceID, common.ErrAttr(err))
|
|
return 0, err
|
|
|
|
}
|
|
return plan.RequestsLimit(), nil
|
|
}
|
|
|
|
func (sl *SubscriptionLimitsImpl) PropertiesLimit(ctx context.Context, subscr *dbgen.Subscription) (int, error) {
|
|
if (subscr == nil) || !sl.planService.IsSubscriptionActive(subscr.Status) {
|
|
return 0, ErrNoActiveSubscription
|
|
}
|
|
|
|
plan, err := sl.planService.FindPlan(subscr.ExternalProductID, subscr.ExternalPriceID, sl.Stage,
|
|
IsInternalSubscription(subscr.Source))
|
|
if err != nil {
|
|
slog.ErrorContext(ctx, "Failed to find billing plan", "productID", subscr.ExternalProductID, "priceID", subscr.ExternalPriceID, common.ErrAttr(err))
|
|
return 0, err
|
|
|
|
}
|
|
return plan.PropertiesLimit(), nil
|
|
}
|
|
|
|
func (sl *SubscriptionLimitsImpl) OrgsLimit(ctx context.Context, subscr *dbgen.Subscription) (int, error) {
|
|
if (subscr == nil) || !sl.planService.IsSubscriptionActive(subscr.Status) {
|
|
return 0, ErrNoActiveSubscription
|
|
}
|
|
|
|
plan, err := sl.planService.FindPlan(subscr.ExternalProductID, subscr.ExternalPriceID, sl.Stage,
|
|
IsInternalSubscription(subscr.Source))
|
|
if err != nil {
|
|
slog.ErrorContext(ctx, "Failed to find billing plan", "productID", subscr.ExternalProductID, "priceID", subscr.ExternalPriceID, common.ErrAttr(err))
|
|
return 0, err
|
|
|
|
}
|
|
return plan.OrgsLimit(), nil
|
|
}
|
|
|
|
type StubSubscriptionLimits struct{}
|
|
|
|
func (StubSubscriptionLimits) CheckOrgsLimit(ctx context.Context, userID int32, subscr *dbgen.Subscription) (_ bool, _ int, _ error) {
|
|
return true, 0, nil
|
|
}
|
|
func (StubSubscriptionLimits) CheckOrgMembersLimit(ctx context.Context, orgID int32, subscr *dbgen.Subscription) (_ bool, _ int, _ error) {
|
|
return true, 0, nil
|
|
}
|
|
func (StubSubscriptionLimits) CheckPropertiesLimit(ctx context.Context, userID int32, subscr *dbgen.Subscription) (_ bool, _ int, _ error) {
|
|
return true, 0, nil
|
|
}
|
|
func (StubSubscriptionLimits) RequestsLimit(ctx context.Context, subscr *dbgen.Subscription) (int64, error) {
|
|
return 0, nil
|
|
}
|
|
func (StubSubscriptionLimits) PropertiesLimit(ctx context.Context, subscr *dbgen.Subscription) (int, error) {
|
|
return 0, nil
|
|
}
|
|
func (StubSubscriptionLimits) OrgsLimit(ctx context.Context, subscr *dbgen.Subscription) (int, error) {
|
|
return 0, nil
|
|
}
|
|
|
|
var _ SubscriptionLimits = (*StubSubscriptionLimits)(nil)
|