package devices import ( "context" "time" ) // ctxKey is a private type for context keys to avoid collisions. type ctxKey string // CtxKeyUserID is the context key under which the authenticated user's // internal int64 ID is stored by the JWT auth middleware (module #2). // // It mirrors the key used by the codes module so that, once the auth // middleware lands, a single canonical key can be reconciled across modules. const CtxKeyUserID ctxKey = "user_id" // ctxKeyPlan is the context key under which the resolved subscription Plan is // stored by SubscriptionMiddleware. const ctxKeyPlan ctxKey = "plan" // WithUserID returns a copy of ctx carrying the authenticated user ID. // Primarily used in tests and by the auth middleware. func WithUserID(ctx context.Context, userID int64) context.Context { return context.WithValue(ctx, CtxKeyUserID, userID) } // UserIDFromContext extracts the authenticated user ID from ctx. // ok is false when no (valid) user ID is present. func UserIDFromContext(ctx context.Context) (int64, bool) { v, ok := ctx.Value(CtxKeyUserID).(int64) if !ok || v == 0 { return 0, false } return v, true } // Plan is the resolved effective subscription for a user, injected into the // request context by SubscriptionMiddleware and consumed by nodes (catalogue // filtering, connect), usage (daily caps), and the /v1/me summary. type Plan struct { // PlanCode is one of "free", "pro", "team". PlanCode string // ExpiresAt is the effective subscription expiry (UTC); nil for the free // fallback when the user has no active paid/trial subscription. ExpiresAt *time.Time // MaxDevices is the per-plan device cap (free 1 / pro 5 / team 10). MaxDevices int // DailyMinutes is the free-plan daily time cap in minutes; nil = unlimited. DailyMinutes *int // AdGate is true when the user must watch a rewarded ad to unlock daily use. AdGate bool // Source is the subscription source: "trial", "code", "admin", or "free" // for the no-subscription fallback. Source string } // WithPlan returns a copy of ctx carrying the resolved Plan. func WithPlan(ctx context.Context, p Plan) context.Context { return context.WithValue(ctx, ctxKeyPlan, p) } // PlanFromCtx extracts the resolved Plan from ctx. ok is false when // SubscriptionMiddleware has not run. func PlanFromCtx(ctx context.Context) (Plan, bool) { p, ok := ctx.Value(ctxKeyPlan).(Plan) return p, ok } // planTier maps a plan code to a numeric tier (higher = better) for comparison. func planTier(code string) int { switch code { case "team": return 3 case "pro": return 2 default: // "free" or unknown return 1 } }