JOB-EASY

Subscriptions

Storage limits, entitlement enforcement, and billing history.

The subscriptions slice owns platform access, subscription tiers, trial periods, and strict storage caps for resources (like Resumes and Email sends).

The Entitlement Guard

The hasPlatformAccess function is the single source of truth for platform lockouts. It evaluates the user's trial expiration, active subscription state, and admin toggles. If this guard returns false, the user is hard-redirected to the billing portal.

Storage Limit Enforcement

Beyond simple platform access, every tier defines strict numerical caps for resource creation. Paid tiers pull these from SubscriptionPlan, while users on a trial pull from trialMax* keys in SystemSettings.

The LimitMap Mechanism

The enforcement layer never sees a raw SubscriptionPlan model. It only sees a resolved LimitMap. This is because state.plan can be null in three scenarios: an active trial, an admin activation with no plan attached, or a plan deleted out from under a user (onDelete: SetNull). If the plan is null, the system safely maps the user to the trial limits, preventing a lapsed account from accidentally becoming unlimited.

The Central Registry

All limits are governed by a single registry in constants/resourceLimits.ts. A strict test (__tests__/resourceLimits.test.ts) parses schema.prisma as text to ensure the registry, the plan columns, and the system setting keys are perfectly aligned.

Generated Plan Cards

Because the registry is the source of truth, the UI's pricing cards and feature bullets are dynamically generated directly from it. Marketing copy cannot promise a ceiling that the server does not actually enforce.

Enforcement Rules

  • 0 means unlimited: Across the entire application, a limit of 0 strictly implies unlimited storage/actions.
  • Check before write: A limit check must run before any database write, including incidental ones (like uploading to Cloudinary or clearing a default flag). Rejecting afterwards leaves debris behind.
  • 409 Limit Reached: When a user hits a cap, the API returns a 409 Conflict (not a 429). A 429 implies a rate limit with a Retry-After window, but a hard storage cap never magically reopens until the user deletes resources or upgrades.

Downgrades are safe

If a user with 50 saved companies downgrades to a tier that only allows 10, nothing is deleted. The UI will simply display their usage as 50 / 10, and reject any new company creations.

Billing History

The system maintains a comprehensive timeline combining two interleaved streams:

  • PaymentRequest: Manual records of proof-of-payments submitted by users and the admin's approval/rejection verdict.
  • SubscriptionEvent: The actual entitlement timeline (trial started, activated, renewed, expired, cancelled). This prevents silent expirations where a user suddenly loses access without a paper trail.