-
Notifications
You must be signed in to change notification settings - Fork 0
add entitlements and quotas architecture for future stripe integration #131
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
48e245d
add entitlements and quotas architecture for future stripe integration
InfinityBowman c1e8e3e
Apply Prettier formatting
actions-user d4edcde
use drizzle transaction
InfinityBowman 33468de
simplify time parsing, simplify admin dashboard
InfinityBowman 9e20a80
turn entitlement logic into a package
InfinityBowman 6f4cac8
Apply Prettier formatting
actions-user 92b0e85
better handling unlimited values, update tests to reflect new subscri…
InfinityBowman 8908ef2
Merge branch '130-gated-feature-access' of https://github.com/Infinit…
InfinityBowman File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,51 @@ | ||
| --- | ||
| alwaysApply: true | ||
| scope: packages/workers | ||
| --- | ||
|
|
||
| # Workers Package Rules | ||
|
|
||
| ## Drizzle Transactions | ||
|
|
||
| **ALWAYS use `db.batch()` for multiple related database operations** to ensure atomicity: | ||
|
|
||
| ```javascript | ||
| // ✅ DO: Use batch for related operations | ||
| const batchOps = [ | ||
| db.insert(projects).values({ id, name, createdBy }), | ||
| db.insert(projectMembers).values({ projectId: id, userId, role: 'owner' }), | ||
| ]; | ||
| await db.batch(batchOps); | ||
|
|
||
| // ❌ DON'T: Separate operations | ||
| await db.insert(projects).values({ id, name }); | ||
| await db.insert(projectMembers).values({ projectId: id, userId }); | ||
| ``` | ||
|
|
||
| Use batch when operations must be atomic (all succeed or all fail). Single independent operations don't need batch. | ||
|
|
||
| ## Zod Validation | ||
|
|
||
| **ALWAYS validate request bodies** using `validateRequest` middleware: | ||
|
|
||
| ```javascript | ||
| // ✅ DO: Use middleware | ||
| import { validateRequest, projectSchemas } from '../config/validation.js'; | ||
|
|
||
| projectRoutes.post('/', validateRequest(projectSchemas.create), async c => { | ||
| const data = c.get('validatedBody'); // Already validated | ||
| }); | ||
|
|
||
| // ❌ DON'T: Manual validation | ||
| projectRoutes.post('/', async c => { | ||
| const body = await c.req.json(); | ||
| // Manual checks... | ||
| }); | ||
| ``` | ||
|
|
||
| **Add new schemas to `config/validation.js`** and reuse `commonFields` when possible. Use `validateQueryParams` for query parameters. | ||
|
|
||
| ## Examples | ||
|
|
||
| - See `src/routes/account-merge.js` for batch usage | ||
| - See `src/routes/projects.js` for validation patterns |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,7 @@ | ||
| /** | ||
| * Main entry point for @corates/shared package | ||
| * Re-exports everything from errors module | ||
| * Re-exports everything from errors and plans modules | ||
| */ | ||
|
|
||
| export * from './errors/index.js'; | ||
| export * from './plans/index.js'; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| /** | ||
| * Public API exports for plans module | ||
| * This is the main entry point for consuming plan configuration | ||
| */ | ||
|
|
||
| // Types | ||
| export type { | ||
| PlanId, | ||
| EntitlementKey, | ||
| QuotaKey, | ||
| Entitlements, | ||
| Quotas, | ||
| Plan, | ||
| Plans, | ||
| } from './types.js'; | ||
|
|
||
| // Plan configuration | ||
| export { PLANS, DEFAULT_PLAN, getPlan, isUnlimitedQuota } from './plans.js'; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,81 @@ | ||
| /** | ||
| * Plan configuration | ||
| * Maps plans to entitlements (boolean capabilities) and quotas (numeric limits) | ||
| * Plans are static configuration - not stored in database | ||
| */ | ||
|
|
||
| import type { Plans, PlanId } from './types.js'; | ||
|
|
||
| /** | ||
| * Plan configurations for all subscription tiers | ||
| */ | ||
| export const PLANS: Plans = { | ||
| free: { | ||
| name: 'Free', | ||
| entitlements: { | ||
| 'project.create': false, | ||
| 'checklist.edit': true, | ||
| 'export.pdf': false, | ||
| 'ai.run': false, | ||
| }, | ||
| quotas: { | ||
| 'projects.max': 0, | ||
| 'storage.project.maxMB': 10, | ||
| 'ai.tokens.monthly': 0, | ||
| }, | ||
| }, | ||
| pro: { | ||
| name: 'Pro', | ||
| entitlements: { | ||
| 'project.create': true, | ||
| 'checklist.edit': true, | ||
| 'export.pdf': true, | ||
| 'ai.run': true, | ||
| }, | ||
| quotas: { | ||
| 'projects.max': 10, | ||
| 'storage.project.maxMB': 1000, | ||
| 'ai.tokens.monthly': 100000, | ||
| }, | ||
| }, | ||
| unlimited: { | ||
| name: 'Unlimited', | ||
| entitlements: { | ||
| 'project.create': true, | ||
| 'checklist.edit': true, | ||
| 'export.pdf': true, | ||
| 'ai.run': true, | ||
| }, | ||
| quotas: { | ||
| 'projects.max': -1, | ||
| 'storage.project.maxMB': -1, | ||
| 'ai.tokens.monthly': -1, | ||
| }, | ||
| }, | ||
| }; | ||
|
|
||
| /** | ||
| * Default plan ID for users without an active subscription | ||
| */ | ||
| export const DEFAULT_PLAN: PlanId = 'free'; | ||
|
|
||
| /** | ||
| * Get plan configuration by plan ID | ||
| * @param planId - Plan ID (e.g., 'free', 'pro', 'unlimited') | ||
| * @returns Plan configuration, or default plan if planId is invalid | ||
| */ | ||
| export function getPlan(planId: PlanId | string): Plans[PlanId] { | ||
| if (planId in PLANS) { | ||
| return PLANS[planId as PlanId]; | ||
| } | ||
| return PLANS[DEFAULT_PLAN]; | ||
| } | ||
|
|
||
| /** | ||
| * Check if a quota value represents unlimited quota | ||
| * @param quota - Quota value to check | ||
| * @returns True if quota is unlimited (value is -1) | ||
| */ | ||
| export function isUnlimitedQuota(quota: number): boolean { | ||
| return quota === -1; | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,53 @@ | ||
| /** | ||
| * Type definitions for plan configuration | ||
| * Defines types for plan IDs, entitlement keys, quota keys, and plan structure | ||
| */ | ||
|
|
||
| /** | ||
| * Plan ID - identifies a subscription tier | ||
| */ | ||
| export type PlanId = 'free' | 'pro' | 'unlimited'; | ||
|
|
||
| /** | ||
| * Entitlement keys - boolean capabilities that can be enabled/disabled per plan | ||
| */ | ||
| export type EntitlementKey = 'project.create' | 'checklist.edit' | 'export.pdf' | 'ai.run'; | ||
|
|
||
| /** | ||
| * Quota keys - numeric limits that can be set per plan | ||
| */ | ||
| export type QuotaKey = 'projects.max' | 'storage.project.maxMB' | 'ai.tokens.monthly'; | ||
|
|
||
| /** | ||
| * Entitlements - mapping of entitlement keys to boolean values | ||
| */ | ||
| export interface Entitlements { | ||
| 'project.create': boolean; | ||
| 'checklist.edit': boolean; | ||
| 'export.pdf': boolean; | ||
| 'ai.run': boolean; | ||
| } | ||
|
|
||
| /** | ||
| * Quotas - mapping of quota keys to numeric values | ||
| * A value of -1 indicates unlimited quota (JSON-safe alternative to Infinity) | ||
| */ | ||
| export interface Quotas { | ||
| 'projects.max': number; | ||
| 'storage.project.maxMB': number; | ||
| 'ai.tokens.monthly': number; | ||
| } | ||
|
|
||
| /** | ||
| * Plan configuration - defines entitlements and quotas for a subscription tier | ||
| */ | ||
| export interface Plan { | ||
| name: string; | ||
| entitlements: Entitlements; | ||
| quotas: Quotas; | ||
| } | ||
|
|
||
| /** | ||
| * Plans - record of all plan configurations indexed by plan ID | ||
| */ | ||
| export type Plans = Record<PlanId, Plan>; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.