Skip to content
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
cbaf2f3
feat(schema): add subscriptions and plans support
steebchen Jun 6, 2025
df5a24d
feat(api): add helper to resolve organization in Stripe events
steebchen Jun 6, 2025
a6b3fd3
feat(api): track subscription events in PostHog
steebchen Jun 6, 2025
79bf37f
feat(api): add plan and mode fields to test data
steebchen Jun 6, 2025
d981f28
feat(api): add plan field to test organization data
steebchen Jun 6, 2025
e2e5593
chore: migrations
steebchen Jun 6, 2025
1e3875d
feat(api): add plan and mode fields to test data
steebchen Jun 6, 2025
560c849
feat(api): add resume Pro subscription endpoint
steebchen Jun 6, 2025
93c2eb1
feat(api): add strict Stripe event type checking
steebchen Jun 6, 2025
1a3c016
fix(api): handle optional fields in Stripe event data
steebchen Jun 6, 2025
7755285
fix(api): handle undefined period end in Stripe events
steebchen Jun 6, 2025
8097410
feat(api): track subscription cancellation state
steebchen Jun 6, 2025
73c915f
feat(api): remove cancelAtPeriodEnd and track cancellation status
steebchen Jun 6, 2025
de7aceb
fix(api): validate STRIPE_PRO_PRICE_ID env variable
steebchen Jun 6, 2025
205e822
fix(api): update org on subscription reactivation
steebchen Jun 6, 2025
876c6d3
refactor(api): remove redundant timestamps in DB updates
steebchen Jun 6, 2025
3f59434
feat(billing): enhance payment status handling
steebchen Jun 6, 2025
92ba70e
refactor(db): simplify schema defaults for mode fields
steebchen Jun 6, 2025
637a7ab
chore: squash migrations
steebchen Jun 6, 2025
e05c6d6
feat(db): add mode fields for logs testing
steebchen Jun 6, 2025
b54c3c6
feat(db): add unique constraints on Stripe IDs
steebchen Jun 6, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -94,3 +94,4 @@ NEXT_PUBLIC_POSTHOG_HOST=https://app.posthog.com

STRIPE_SECRET_KEY=sk_test_your_stripe_secret_key
STRIPE_WEBHOOK_SECRET=whsec_your_webhook_secret
STRIPE_PRO_PRICE_ID=price_your_pro_plan_price_id
1 change: 1 addition & 0 deletions apps/api/.env.example
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
STRIPE_SECRET_KEY=sk_test_123
STRIPE_WEBHOOK_SECRET=whsec_123
STRIPE_PRO_PRICE_ID=price_12345678

VITE_POSTHOG_KEY=phc_123
VITE_POSTHOG_HOST=https://app.posthog.com
2 changes: 2 additions & 0 deletions apps/api/src/routes/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { logs } from "./logs";
import organization from "./organization";
import { payments } from "./payments";
import projects from "./projects";
import { subscriptions } from "./subscriptions";
import { user } from "./user";

import type { ServerTypes } from "../vars";
Expand Down Expand Up @@ -40,3 +41,4 @@ routes.route("/projects", projects);

routes.route("/orgs", organization);
routes.route("/payments", payments);
routes.route("/subscriptions", subscriptions);
2 changes: 2 additions & 0 deletions apps/api/src/routes/keys-provider.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ describe("e2e tests for provider keys", () => {
await db.insert(tables.organization).values({
id: "test-org-id",
name: "Test Organization",
plan: "pro",
});

await db.insert(tables.userOrganization).values({
Expand All @@ -32,6 +33,7 @@ describe("e2e tests for provider keys", () => {
id: "test-project-id",
name: "Test Project",
organizationId: "test-org-id",
mode: "api-keys",
});
});

Expand Down
1 change: 1 addition & 0 deletions apps/api/src/routes/keys-provider.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ describe("provider keys route", () => {
await db.insert(tables.organization).values({
id: "test-org-id",
name: "Test Organization",
plan: "pro",
});

// Associate user with organization
Expand Down
10 changes: 10 additions & 0 deletions apps/api/src/routes/keys-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,16 @@ keysProvider.openapi(create, async (c) => {
});
}

const organization = userOrgs[0].organization;

// Check if organization has pro plan for provider keys
if (organization?.plan !== "pro") {
throw new HTTPException(403, {
message:
"Provider keys are only available on the Pro plan. Please upgrade to use your own API keys.",
});
}

// Check if a provider key already exists for this provider and organization
const existingKey = await db.query.providerKey.findFirst({
where: {
Expand Down
2 changes: 2 additions & 0 deletions apps/api/src/routes/organization.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ const organizationSchema = z.object({
updatedAt: z.date(),
name: z.string(),
credits: z.string(),
plan: z.enum(["free", "pro"]),
planExpiresAt: z.date().nullable(),
status: z.enum(["active", "inactive", "deleted"]).nullable(),
});

Expand Down
2 changes: 0 additions & 2 deletions apps/api/src/routes/payments.ts
Original file line number Diff line number Diff line change
Expand Up @@ -330,15 +330,13 @@ payments.openapi(setDefaultPaymentMethod, async (c) => {
.update(tables.paymentMethod)
.set({
isDefault: false,
updatedAt: new Date(),
})
.where(eq(tables.paymentMethod.organizationId, organizationId));

await db
.update(tables.paymentMethod)
.set({
isDefault: true,
updatedAt: new Date(),
})
.where(eq(tables.paymentMethod.id, paymentMethodId));

Expand Down
Loading