feat(subscription): temporarily disable subscription gating
Subscriptions aren't a shipping feature for now. Make
GET /api/subscription/status/ return a "limitations disabled" / pro-tier
stub at the top of the function with no DB or Redis work:
- tier="pro"
- is_active=true
- limitations_enabled=false (master kill switch in SubscriptionHelper.kt;
every canCreate* check short-circuits true)
- usage=0 across the board
- limits map present with empty entries (all-nil = unlimited per the KMM
model convention) so client tier-lookups don't NPE
The original implementation is preserved verbatim as the unexported
getSubscriptionStatusFromDB method. Re-enabling is a one-line change:
swap GetSubscriptionStatus's body to call s.getSubscriptionStatusFromDB.
Two integration tests in subscription_is_free_test.go assert the original
"limitations actually apply based on settings/IsFree" behavior. They now
t.Skip with the same TEMPORARILY DISABLED marker pointing back to the
service comment. CheckLimit-based tests in the same file still pass
because that codepath is unchanged.
Perf side effect: POST/GET on this route drops to ~1ms (just JSON marshal),
removing 4-5 serial Neon RTTs from every cold call. Was the slowest endpoint
in the live dashboard (~213ms p95 / ~480ms after the pod roll).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -171,6 +171,11 @@ func (app *SubscriptionTestApp) makeAuthenticatedRequest(t *testing.T, method, p
|
|||||||
// TestIntegration_IsFreeBypassesLimitations tests that users with IsFree=true
|
// TestIntegration_IsFreeBypassesLimitations tests that users with IsFree=true
|
||||||
// see limitations_enabled=false regardless of global settings
|
// see limitations_enabled=false regardless of global settings
|
||||||
func TestIntegration_IsFreeBypassesLimitations(t *testing.T) {
|
func TestIntegration_IsFreeBypassesLimitations(t *testing.T) {
|
||||||
|
// TEMPORARILY DISABLED — Subscriptions: GetSubscriptionStatus now returns
|
||||||
|
// a limitations_enabled=false stub for everyone, so the assertion that a
|
||||||
|
// normal user sees limitations_enabled=true (per EnableLimitations setting)
|
||||||
|
// no longer holds. Remove this skip when GetSubscriptionStatus is restored.
|
||||||
|
t.Skip("subscription feature disabled — see subscription_service.go TEMPORARILY DISABLED")
|
||||||
app := setupSubscriptionTest(t)
|
app := setupSubscriptionTest(t)
|
||||||
|
|
||||||
// Register and login a user
|
// Register and login a user
|
||||||
@@ -289,6 +294,10 @@ func TestIntegration_IsFreeBypassesCheckLimit(t *testing.T) {
|
|||||||
// TestIntegration_IsFreeIndependentOfTier tests that IsFree works regardless of
|
// TestIntegration_IsFreeIndependentOfTier tests that IsFree works regardless of
|
||||||
// the user's subscription tier
|
// the user's subscription tier
|
||||||
func TestIntegration_IsFreeIndependentOfTier(t *testing.T) {
|
func TestIntegration_IsFreeIndependentOfTier(t *testing.T) {
|
||||||
|
// TEMPORARILY DISABLED — Subscriptions: GetSubscriptionStatus is stubbed,
|
||||||
|
// so the Pro+!IsFree case (which would normally return limitations_enabled=true)
|
||||||
|
// is no longer reachable. Remove this skip when the feature is restored.
|
||||||
|
t.Skip("subscription feature disabled — see subscription_service.go TEMPORARILY DISABLED")
|
||||||
app := setupSubscriptionTest(t)
|
app := setupSubscriptionTest(t)
|
||||||
|
|
||||||
// Register and login a user
|
// Register and login a user
|
||||||
|
|||||||
@@ -121,7 +121,51 @@ func (s *SubscriptionService) GetSubscription(ctx context.Context, userID uint)
|
|||||||
// 5-minute TTL; mutation paths (residence/task/contractor/document/sub CRUD)
|
// 5-minute TTL; mutation paths (residence/task/contractor/document/sub CRUD)
|
||||||
// invalidate via cache.InvalidateSubscriptionStatusForUsers, fanning out to
|
// invalidate via cache.InvalidateSubscriptionStatusForUsers, fanning out to
|
||||||
// every member of a shared residence.
|
// every member of a shared residence.
|
||||||
func (s *SubscriptionService) GetSubscriptionStatus(ctx context.Context, userID uint) (*SubscriptionStatusResponse, error) {
|
//
|
||||||
|
// TEMPORARILY DISABLED — Subscriptions feature off.
|
||||||
|
//
|
||||||
|
// Returns a "limitations disabled" / pro-tier stub without any DB or Redis
|
||||||
|
// work. The KMM client (SubscriptionHelper.kt) treats limitations_enabled=false
|
||||||
|
// as a master kill switch — every canCreate* check short-circuits to true
|
||||||
|
// without ever consulting tier or limits. tier="pro" is belt-and-suspenders in
|
||||||
|
// case a future client path inspects tier directly.
|
||||||
|
//
|
||||||
|
// To re-enable: change this body to call s.getSubscriptionStatusFromDB(ctx, userID).
|
||||||
|
// The original implementation is preserved verbatim below as an unexported
|
||||||
|
// method so this is a one-line revert.
|
||||||
|
//
|
||||||
|
// Findable by searching: "TEMPORARILY DISABLED — Subscriptions"
|
||||||
|
func (s *SubscriptionService) GetSubscriptionStatus(ctx context.Context, _ uint) (*SubscriptionStatusResponse, error) {
|
||||||
|
_ = ctx
|
||||||
|
return &SubscriptionStatusResponse{
|
||||||
|
Tier: "pro",
|
||||||
|
IsActive: true,
|
||||||
|
AutoRenew: false,
|
||||||
|
LimitationsEnabled: false,
|
||||||
|
Usage: &UsageResponse{
|
||||||
|
PropertiesCount: 0,
|
||||||
|
TasksCount: 0,
|
||||||
|
ContractorsCount: 0,
|
||||||
|
DocumentsCount: 0,
|
||||||
|
},
|
||||||
|
Limits: map[string]*TierLimitsClientResponse{
|
||||||
|
// Empty TierLimitsClientResponse → all fields nil → "unlimited"
|
||||||
|
// per the KMM model's `null = unlimited` convention. Keys present
|
||||||
|
// so client Map[tier] lookups don't NPE if they ever run.
|
||||||
|
"free": {},
|
||||||
|
"pro": {},
|
||||||
|
},
|
||||||
|
SubscriptionSource: "none",
|
||||||
|
TrialActive: false,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getSubscriptionStatusFromDB is the original implementation, preserved verbatim
|
||||||
|
// while the subscription feature is disabled. See the TEMPORARILY DISABLED note
|
||||||
|
// on GetSubscriptionStatus above for the re-enable procedure.
|
||||||
|
//
|
||||||
|
//nolint:unused // intentional — re-enabled by switching GetSubscriptionStatus to call this.
|
||||||
|
func (s *SubscriptionService) getSubscriptionStatusFromDB(ctx context.Context, userID uint) (*SubscriptionStatusResponse, error) {
|
||||||
// Cache fast path — only used on warm reads. Cold reads, trial-start
|
// Cache fast path — only used on warm reads. Cold reads, trial-start
|
||||||
// branch, and the actual mutation paths below all populate fresh.
|
// branch, and the actual mutation paths below all populate fresh.
|
||||||
if s.cache != nil {
|
if s.cache != nil {
|
||||||
|
|||||||
Reference in New Issue
Block a user