@vibecodiq/cli 0.4.0 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. package/dist/foundation/admin_basic/manifest.json +37 -0
  2. package/dist/foundation/admin_basic/migrations/004_user_roles.sql +117 -0
  3. package/dist/foundation/admin_basic/migrations/005_audit_log.sql +34 -0
  4. package/dist/foundation/admin_basic/migrations/006_impersonation_sessions.sql +22 -0
  5. package/dist/foundation/admin_basic/shared/audit.ts +97 -0
  6. package/dist/foundation/admin_basic/shared/guards.ts +58 -0
  7. package/dist/foundation/admin_basic/shared/impersonation.ts +165 -0
  8. package/dist/foundation/admin_basic/shared/permissions.ts +27 -0
  9. package/dist/foundation/admin_basic/shared/roles.ts +151 -0
  10. package/dist/foundation/admin_basic/slices/audit_log/handler.ts +34 -0
  11. package/dist/foundation/admin_basic/slices/audit_log/slice.contract.json +19 -0
  12. package/dist/foundation/admin_basic/slices/dashboard/handler.ts +51 -0
  13. package/dist/foundation/admin_basic/slices/dashboard/slice.contract.json +13 -0
  14. package/dist/foundation/admin_basic/slices/impersonation/handler.ts +61 -0
  15. package/dist/foundation/admin_basic/slices/impersonation/slice.contract.json +21 -0
  16. package/dist/foundation/admin_basic/slices/roles/handler.ts +90 -0
  17. package/dist/foundation/admin_basic/slices/roles/slice.contract.json +21 -0
  18. package/dist/foundation/admin_basic/slices/users/handler.ts +48 -0
  19. package/dist/foundation/admin_basic/slices/users/slice.contract.json +15 -0
  20. package/dist/foundation/auth_basic/manifest.json +32 -0
  21. package/dist/foundation/auth_basic/migrations/001_create_profiles.sql +36 -0
  22. package/dist/foundation/auth_basic/shared/guards.ts +89 -0
  23. package/dist/foundation/auth_basic/shared/hooks.ts +63 -0
  24. package/dist/foundation/auth_basic/shared/middleware.ts +46 -0
  25. package/dist/foundation/auth_basic/shared/server-user.ts +61 -0
  26. package/dist/foundation/auth_basic/shared/session.ts +38 -0
  27. package/dist/foundation/auth_basic/shared/types.ts +29 -0
  28. package/dist/foundation/auth_basic/slices/login/handler.ts +50 -0
  29. package/dist/foundation/auth_basic/slices/login/repository.ts +23 -0
  30. package/dist/foundation/auth_basic/slices/login/schemas.ts +22 -0
  31. package/dist/foundation/auth_basic/slices/login/slice.contract.json +19 -0
  32. package/dist/foundation/auth_basic/slices/login/ui/AuthLogin.tsx +107 -0
  33. package/dist/foundation/auth_basic/slices/login/ui/hook.ts +44 -0
  34. package/dist/foundation/auth_basic/slices/logout/handler.ts +19 -0
  35. package/dist/foundation/auth_basic/slices/logout/slice.contract.json +16 -0
  36. package/dist/foundation/auth_basic/slices/register/handler.ts +61 -0
  37. package/dist/foundation/auth_basic/slices/register/repository.ts +25 -0
  38. package/dist/foundation/auth_basic/slices/register/schemas.ts +29 -0
  39. package/dist/foundation/auth_basic/slices/register/slice.contract.json +21 -0
  40. package/dist/foundation/auth_basic/slices/register/ui/AuthRegister.tsx +118 -0
  41. package/dist/foundation/auth_basic/slices/register/ui/hook.ts +48 -0
  42. package/dist/foundation/auth_basic/slices/reset_password/handler.ts +47 -0
  43. package/dist/foundation/auth_basic/slices/reset_password/schemas.ts +21 -0
  44. package/dist/foundation/auth_basic/slices/reset_password/slice.contract.json +18 -0
  45. package/dist/foundation/auth_basic/slices/reset_password/ui/AuthResetPassword.tsx +79 -0
  46. package/dist/foundation/auth_basic/slices/reset_password/ui/hook.ts +48 -0
  47. package/dist/foundation/db_basic/manifest.json +33 -0
  48. package/dist/foundation/db_basic/shared/seed.ts +27 -0
  49. package/dist/foundation/db_basic/shared/supabase-client.ts +70 -0
  50. package/dist/foundation/db_basic/shared/types.ts +20 -0
  51. package/dist/foundation/db_basic/shared/utils.ts +43 -0
  52. package/dist/foundation/payments_basic/manifest.json +54 -0
  53. package/dist/foundation/payments_basic/migrations/002_create_subscriptions.sql +44 -0
  54. package/dist/foundation/payments_basic/migrations/003_create_entitlements.sql +54 -0
  55. package/dist/foundation/payments_basic/migrations/003b_create_webhook_events.sql +28 -0
  56. package/dist/foundation/payments_basic/shared/entitlement-hooks.ts +50 -0
  57. package/dist/foundation/payments_basic/shared/entitlement-types.ts +29 -0
  58. package/dist/foundation/payments_basic/shared/entitlements.ts +78 -0
  59. package/dist/foundation/payments_basic/shared/guards.ts +110 -0
  60. package/dist/foundation/payments_basic/shared/hooks.ts +45 -0
  61. package/dist/foundation/payments_basic/shared/plans.ts +54 -0
  62. package/dist/foundation/payments_basic/shared/reconciliation.ts +85 -0
  63. package/dist/foundation/payments_basic/shared/resolver.ts +61 -0
  64. package/dist/foundation/payments_basic/shared/stripe-client.ts +15 -0
  65. package/dist/foundation/payments_basic/shared/types.ts +84 -0
  66. package/dist/foundation/payments_basic/shared/webhook-handler.ts +198 -0
  67. package/dist/foundation/payments_basic/shared/webhook-processor.ts +174 -0
  68. package/dist/foundation/payments_basic/slices/cancel/handler.ts +55 -0
  69. package/dist/foundation/payments_basic/slices/cancel/slice.contract.json +17 -0
  70. package/dist/foundation/payments_basic/slices/cancel/ui/hook.ts +45 -0
  71. package/dist/foundation/payments_basic/slices/check_limits/handler.ts +33 -0
  72. package/dist/foundation/payments_basic/slices/check_limits/slice.contract.json +17 -0
  73. package/dist/foundation/payments_basic/slices/subscribe/handler.ts +79 -0
  74. package/dist/foundation/payments_basic/slices/subscribe/repository.ts +32 -0
  75. package/dist/foundation/payments_basic/slices/subscribe/schemas.ts +21 -0
  76. package/dist/foundation/payments_basic/slices/subscribe/slice.contract.json +20 -0
  77. package/dist/foundation/payments_basic/slices/subscribe/ui/BillingSubscribe.tsx +93 -0
  78. package/dist/foundation/payments_basic/slices/subscribe/ui/hook.ts +44 -0
  79. package/dist/foundation/payments_basic/slices/webhook/handler.ts +67 -0
  80. package/dist/foundation/payments_basic/slices/webhook/slice.contract.json +19 -0
  81. package/dist/index.js +19 -18
  82. package/package.json +11 -2
@@ -0,0 +1,17 @@
1
+ {
2
+ "domain": "billing",
3
+ "slice": "check-limits",
4
+ "type": "route",
5
+ "has_ui": true,
6
+ "has_repository": true,
7
+ "description": "Check current plan entitlements and usage limits. Returns what the user can and cannot do based on their subscription.",
8
+ "input": {
9
+ "feature": "string (optional)"
10
+ },
11
+ "output": {
12
+ "plan": "string",
13
+ "entitlements": "object",
14
+ "can_access": "boolean"
15
+ },
16
+ "side_effects": []
17
+ }
@@ -0,0 +1,79 @@
1
+ import { NextRequest, NextResponse } from 'next/server';
2
+ import { requireAuth } from '@/shared/auth/guards';
3
+ import { stripe } from '@/shared/billing/stripe-client';
4
+ import { createAdminClient } from '@/shared/db/supabase-client';
5
+ import { subscribeInputSchema } from './schemas';
6
+ import type { SubscribeOutput } from './schemas';
7
+
8
+ // --- ASA GENERATED START ---
9
+ // Route handler for billing/subscribe slice.
10
+ // Creates Stripe Checkout Session for subscription upgrade.
11
+ // Security: requires authenticated user, creates Stripe Customer if needed.
12
+ // --- ASA GENERATED END ---
13
+
14
+ // --- USER CODE START ---
15
+
16
+ export async function POST(request: NextRequest): Promise<NextResponse<SubscribeOutput>> {
17
+ try {
18
+ const { user, response } = await requireAuth(request);
19
+ if (response) return response as NextResponse<SubscribeOutput>;
20
+
21
+ const body = await request.json();
22
+ const parsed = subscribeInputSchema.safeParse(body);
23
+
24
+ if (!parsed.success) {
25
+ return NextResponse.json(
26
+ { checkout_url: null, error: parsed.error.errors[0]?.message ?? 'Invalid input' },
27
+ { status: 400 },
28
+ );
29
+ }
30
+
31
+ const { price_id } = parsed.data;
32
+ const supabase = createAdminClient();
33
+
34
+ // Get or create Stripe customer
35
+ const { data: subscription } = await supabase
36
+ .from('subscriptions')
37
+ .select('stripe_customer_id')
38
+ .eq('user_id', user!.id)
39
+ .single();
40
+
41
+ let customerId = subscription?.stripe_customer_id;
42
+
43
+ if (!customerId) {
44
+ const customer = await stripe.customers.create({
45
+ email: user!.email,
46
+ metadata: { supabase_user_id: user!.id },
47
+ });
48
+ customerId = customer.id;
49
+
50
+ // Upsert subscription record with customer ID
51
+ await supabase.from('subscriptions').upsert({
52
+ user_id: user!.id,
53
+ stripe_customer_id: customerId,
54
+ plan: 'free',
55
+ status: 'active',
56
+ }, { onConflict: 'user_id' });
57
+ }
58
+
59
+ // Create Checkout Session
60
+ const session = await stripe.checkout.sessions.create({
61
+ customer: customerId,
62
+ mode: 'subscription',
63
+ line_items: [{ price: price_id, quantity: 1 }],
64
+ success_url: `${process.env.NEXT_PUBLIC_APP_URL}/billing?success=true`,
65
+ cancel_url: `${process.env.NEXT_PUBLIC_APP_URL}/billing?canceled=true`,
66
+ metadata: { supabase_user_id: user!.id },
67
+ });
68
+
69
+ return NextResponse.json({ checkout_url: session.url, error: null });
70
+ } catch (err) {
71
+ console.error('[billing/subscribe] Error:', err);
72
+ return NextResponse.json(
73
+ { checkout_url: null, error: 'Failed to create checkout session' },
74
+ { status: 500 },
75
+ );
76
+ }
77
+ }
78
+
79
+ // --- USER CODE END ---
@@ -0,0 +1,32 @@
1
+ import { createAdminClient } from '@/shared/db/supabase-client';
2
+
3
+ // --- ASA GENERATED START ---
4
+ // Repository for billing/subscribe slice.
5
+ // --- ASA GENERATED END ---
6
+
7
+ // --- USER CODE START ---
8
+
9
+ export async function getSubscription(userId: string) {
10
+ const supabase = createAdminClient();
11
+ const { data, error } = await supabase
12
+ .from('subscriptions')
13
+ .select('*')
14
+ .eq('user_id', userId)
15
+ .single();
16
+
17
+ if (error) return null;
18
+ return data;
19
+ }
20
+
21
+ export async function getEntitlements(userId: string) {
22
+ const supabase = createAdminClient();
23
+ const { data, error } = await supabase
24
+ .from('entitlements')
25
+ .select('*')
26
+ .eq('user_id', userId);
27
+
28
+ if (error) return [];
29
+ return data ?? [];
30
+ }
31
+
32
+ // --- USER CODE END ---
@@ -0,0 +1,21 @@
1
+ import { z } from 'zod';
2
+
3
+ // --- ASA GENERATED START ---
4
+ // Validation schemas for billing/subscribe slice.
5
+ // --- ASA GENERATED END ---
6
+
7
+ // --- USER CODE START ---
8
+
9
+ export const subscribeInputSchema = z.object({
10
+ price_id: z.string().min(1, 'Price ID is required'),
11
+ });
12
+
13
+ export const subscribeOutputSchema = z.object({
14
+ checkout_url: z.string().nullable(),
15
+ error: z.string().nullable(),
16
+ });
17
+
18
+ export type SubscribeInput = z.infer<typeof subscribeInputSchema>;
19
+ export type SubscribeOutput = z.infer<typeof subscribeOutputSchema>;
20
+
21
+ // --- USER CODE END ---
@@ -0,0 +1,20 @@
1
+ {
2
+ "domain": "billing",
3
+ "slice": "subscribe",
4
+ "type": "route",
5
+ "has_ui": true,
6
+ "has_repository": true,
7
+ "description": "Creates Stripe Checkout session for subscription upgrade. Redirects to Stripe hosted checkout.",
8
+ "input": {
9
+ "price_id": "string"
10
+ },
11
+ "output": {
12
+ "checkout_url": "string | null",
13
+ "error": "string | null"
14
+ },
15
+ "side_effects": [
16
+ "Creates Stripe Checkout Session",
17
+ "Creates Stripe Customer if not exists",
18
+ "Redirects user to Stripe hosted checkout"
19
+ ]
20
+ }
@@ -0,0 +1,93 @@
1
+ 'use client';
2
+
3
+ import { useSubscribe } from './hook';
4
+ import { PLANS } from '@/shared/billing/plans';
5
+
6
+ // --- ASA GENERATED START ---
7
+ // UI component for billing/subscribe slice.
8
+ // Shows available plans with pricing and upgrade buttons.
9
+ // --- ASA GENERATED END ---
10
+
11
+ // --- USER CODE START ---
12
+
13
+ interface BillingSubscribeProps {
14
+ currentPlan?: string;
15
+ }
16
+
17
+ export default function BillingSubscribe({ currentPlan = 'free' }: BillingSubscribeProps) {
18
+ const { subscribe, loading, error } = useSubscribe();
19
+
20
+ return (
21
+ <div className="mx-auto max-w-4xl px-4 py-12">
22
+ <h2 className="text-2xl font-bold text-neutral-900">Choose your plan</h2>
23
+ <p className="mt-2 text-sm text-neutral-500">
24
+ Upgrade to unlock more features and higher limits.
25
+ </p>
26
+
27
+ {error && (
28
+ <div className="mt-4 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700">
29
+ {error}
30
+ </div>
31
+ )}
32
+
33
+ <div className="mt-8 grid gap-6 sm:grid-cols-2 lg:grid-cols-3">
34
+ {PLANS.map((plan) => {
35
+ const isCurrent = plan.id === currentPlan;
36
+ return (
37
+ <div
38
+ key={plan.id}
39
+ className={`rounded-xl border p-6 ${
40
+ isCurrent
41
+ ? 'border-neutral-900 bg-neutral-50'
42
+ : 'border-neutral-200 bg-white'
43
+ }`}
44
+ >
45
+ <h3 className="text-lg font-semibold text-neutral-900">{plan.name}</h3>
46
+ <p className="mt-1 text-sm text-neutral-500">{plan.description}</p>
47
+
48
+ <div className="mt-4">
49
+ <span className="text-3xl font-bold text-neutral-900">
50
+ ${plan.price}
51
+ </span>
52
+ {plan.price > 0 && (
53
+ <span className="text-sm text-neutral-500">/month</span>
54
+ )}
55
+ </div>
56
+
57
+ <ul className="mt-6 space-y-2">
58
+ {plan.features.map((feature) => (
59
+ <li key={feature} className="flex items-start gap-2 text-sm text-neutral-600">
60
+ <span className="mt-0.5 text-neutral-400">&#10003;</span>
61
+ {feature}
62
+ </li>
63
+ ))}
64
+ </ul>
65
+
66
+ <div className="mt-6">
67
+ {isCurrent ? (
68
+ <div className="rounded-lg bg-neutral-100 px-4 py-2.5 text-center text-sm font-medium text-neutral-500">
69
+ Current plan
70
+ </div>
71
+ ) : plan.stripe_price_id ? (
72
+ <button
73
+ onClick={() => subscribe(plan.stripe_price_id!)}
74
+ disabled={loading}
75
+ className="w-full rounded-lg bg-neutral-900 px-4 py-2.5 text-sm font-medium text-white shadow-sm hover:bg-neutral-800 focus:outline-none focus:ring-2 focus:ring-neutral-500 focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50"
76
+ >
77
+ {loading ? 'Processing...' : `Upgrade to ${plan.name}`}
78
+ </button>
79
+ ) : (
80
+ <div className="rounded-lg bg-neutral-100 px-4 py-2.5 text-center text-sm font-medium text-neutral-500">
81
+ Free forever
82
+ </div>
83
+ )}
84
+ </div>
85
+ </div>
86
+ );
87
+ })}
88
+ </div>
89
+ </div>
90
+ );
91
+ }
92
+
93
+ // --- USER CODE END ---
@@ -0,0 +1,44 @@
1
+ import { useState } from 'react';
2
+
3
+ // --- ASA GENERATED START ---
4
+ // React hook for billing/subscribe slice.
5
+ // --- ASA GENERATED END ---
6
+
7
+ // --- USER CODE START ---
8
+
9
+ export function useSubscribe() {
10
+ const [loading, setLoading] = useState(false);
11
+ const [error, setError] = useState<string | null>(null);
12
+
13
+ async function subscribe(priceId: string): Promise<void> {
14
+ setLoading(true);
15
+ setError(null);
16
+
17
+ try {
18
+ const res = await fetch('/api/billing/subscribe', {
19
+ method: 'POST',
20
+ headers: { 'Content-Type': 'application/json' },
21
+ body: JSON.stringify({ price_id: priceId }),
22
+ });
23
+
24
+ const data = await res.json();
25
+
26
+ if (data.error) {
27
+ setError(data.error);
28
+ return;
29
+ }
30
+
31
+ if (data.checkout_url) {
32
+ window.location.href = data.checkout_url;
33
+ }
34
+ } catch {
35
+ setError('Failed to start checkout. Please try again.');
36
+ } finally {
37
+ setLoading(false);
38
+ }
39
+ }
40
+
41
+ return { subscribe, loading, error };
42
+ }
43
+
44
+ // --- USER CODE END ---
@@ -0,0 +1,67 @@
1
+ import { NextRequest, NextResponse } from 'next/server';
2
+ import { stripe } from '@/shared/billing/stripe-client';
3
+ import { createAdminClient } from '@/shared/db/supabase-client';
4
+
5
+ // --- ASA GENERATED START ---
6
+ // Webhook handler for billing/webhook slice.
7
+ // ENQUEUE-ONLY PATTERN: verify signature → store event → return 200.
8
+ // No inline business logic processing.
9
+ // Security: uses req.text() for raw body (NOT req.json()) to preserve Stripe signature.
10
+ // --- ASA GENERATED END ---
11
+
12
+ // --- USER CODE START ---
13
+
14
+ export async function POST(request: NextRequest): Promise<NextResponse> {
15
+ const signature = request.headers.get('stripe-signature');
16
+
17
+ if (!signature) {
18
+ return NextResponse.json({ error: 'Missing stripe-signature header' }, { status: 400 });
19
+ }
20
+
21
+ let event;
22
+
23
+ try {
24
+ // CRITICAL: Use request.text() — NOT request.json().
25
+ // Stripe signature verification requires the raw body string.
26
+ // Using request.json() parses and re-serializes, breaking the signature.
27
+ const rawBody = await request.text();
28
+
29
+ event = stripe.webhooks.constructEvent(
30
+ rawBody,
31
+ signature,
32
+ process.env.STRIPE_WEBHOOK_SECRET!,
33
+ );
34
+ } catch (err) {
35
+ console.error('[billing/webhook] Signature verification failed:', err);
36
+ return NextResponse.json({ error: 'Invalid signature' }, { status: 400 });
37
+ }
38
+
39
+ // ENQUEUE: Insert into webhook_events table with status 'pending'.
40
+ // A scheduled processor will handle business logic asynchronously.
41
+ const supabase = createAdminClient();
42
+
43
+ const { error: insertError } = await supabase
44
+ .from('webhook_events')
45
+ .upsert(
46
+ {
47
+ stripe_event_id: event.id,
48
+ type: event.type,
49
+ payload: event.data,
50
+ status: 'pending',
51
+ attempt_count: 0,
52
+ received_at: new Date().toISOString(),
53
+ },
54
+ { onConflict: 'stripe_event_id', ignoreDuplicates: true },
55
+ );
56
+
57
+ if (insertError) {
58
+ console.error('[billing/webhook] Failed to store event:', insertError);
59
+ // Still return 200 — Stripe will retry if we return error
60
+ // and we don't want to process the same event twice
61
+ }
62
+
63
+ // Return 200 immediately — no inline processing
64
+ return NextResponse.json({ received: true });
65
+ }
66
+
67
+ // --- USER CODE END ---
@@ -0,0 +1,19 @@
1
+ {
2
+ "domain": "billing",
3
+ "slice": "webhook",
4
+ "type": "webhook",
5
+ "has_ui": false,
6
+ "has_repository": true,
7
+ "description": "Stripe webhook endpoint. Enqueue-only: verify signature, store event, return 200. Processing happens asynchronously.",
8
+ "input": {
9
+ "stripe_signature": "string (header)",
10
+ "raw_body": "string"
11
+ },
12
+ "output": {
13
+ "received": "boolean"
14
+ },
15
+ "side_effects": [
16
+ "Inserts event into webhook_events table with status 'pending'",
17
+ "Returns 200 immediately — no inline business logic"
18
+ ]
19
+ }