Files
spsays-bff/internal/service/plan.go
T
2026-06-23 14:21:45 +05:30

64 lines
2.2 KiB
Go

package service
import (
"errors"
"fmt"
"github.com/prabhupadasays/spsays-bff/internal/model"
)
// Plan identifiers. The numeric caps live in model.ResolveLimits (so the self
// user payload and these enforcers agree), and per-user admin overrides take
// precedence over the plan default.
const (
PlanFree = "free"
PlanPremium = "premium"
)
// ErrPlanLimit signals that a free-plan limit blocks the requested action.
// Handlers surface this as HTTP 402 (Payment Required) so clients can show
// an upsell. The message tells the user what cap was hit.
var ErrPlanLimit = errors.New("plan limit reached")
// PlanLimitError carries detail about which limit was hit so the frontend
// can render a helpful message instead of a generic "upgrade to premium".
type PlanLimitError struct {
Resource string // e.g. "bookmarks", "private collections"
Limit int
Plan string
}
func (e *PlanLimitError) Error() string {
return fmt.Sprintf("%s limit reached (%d on %s plan)", e.Resource, e.Limit, e.Plan)
}
func (e *PlanLimitError) Is(target error) bool { return target == ErrPlanLimit }
// EnforceBookmarkLimit returns nil if the user is allowed to create one more
// bookmark, or a PlanLimitError otherwise. The cap is the user's effective
// bookmark limit (plan default, or an admin override). A negative cap means
// unlimited.
func EnforceBookmarkLimit(u *model.User, currentCount int) error {
limit := u.EffectiveLimits().Bookmarks
if limit >= 0 && currentCount >= limit {
return &PlanLimitError{Resource: "bookmarks", Limit: limit, Plan: u.Plan}
}
return nil
}
// EnforceCollectionLimit returns nil if the user is allowed to create one more
// collection of the given visibility. The cap is the user's effective
// public/private collection limit (plan default, or an admin override); a
// negative cap means unlimited.
func EnforceCollectionLimit(u *model.User, isPublic bool, currentCount int) error {
l := u.EffectiveLimits()
limit, resource := l.PrivateCollections, "private collections"
if isPublic {
limit, resource = l.PublicCollections, "public collections"
}
if limit >= 0 && currentCount >= limit {
return &PlanLimitError{Resource: resource, Limit: limit, Plan: u.Plan}
}
return nil
}