webpeel 0.20.2 → 0.20.3

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 (86) hide show
  1. package/dist/server/app.d.ts +14 -0
  2. package/dist/server/app.js +384 -0
  3. package/dist/server/auth-store.d.ts +27 -0
  4. package/dist/server/auth-store.js +88 -0
  5. package/dist/server/email-service.d.ts +21 -0
  6. package/dist/server/email-service.js +79 -0
  7. package/dist/server/job-queue.d.ts +100 -0
  8. package/dist/server/job-queue.js +145 -0
  9. package/dist/server/logger.d.ts +10 -0
  10. package/dist/server/logger.js +37 -0
  11. package/dist/server/middleware/auth.d.ts +28 -0
  12. package/dist/server/middleware/auth.js +221 -0
  13. package/dist/server/middleware/rate-limit.d.ts +24 -0
  14. package/dist/server/middleware/rate-limit.js +167 -0
  15. package/dist/server/middleware/url-validator.d.ts +15 -0
  16. package/dist/server/middleware/url-validator.js +186 -0
  17. package/dist/server/openapi.yaml +6418 -0
  18. package/dist/server/pg-auth-store.d.ts +132 -0
  19. package/dist/server/pg-auth-store.js +472 -0
  20. package/dist/server/pg-job-queue.d.ts +59 -0
  21. package/dist/server/pg-job-queue.js +375 -0
  22. package/dist/server/premium/domain-intel.d.ts +16 -0
  23. package/dist/server/premium/domain-intel.js +133 -0
  24. package/dist/server/premium/index.d.ts +17 -0
  25. package/dist/server/premium/index.js +35 -0
  26. package/dist/server/premium/swr-cache.d.ts +14 -0
  27. package/dist/server/premium/swr-cache.js +34 -0
  28. package/dist/server/routes/activity.d.ts +6 -0
  29. package/dist/server/routes/activity.js +74 -0
  30. package/dist/server/routes/answer.d.ts +5 -0
  31. package/dist/server/routes/answer.js +125 -0
  32. package/dist/server/routes/ask.d.ts +28 -0
  33. package/dist/server/routes/ask.js +229 -0
  34. package/dist/server/routes/batch.d.ts +6 -0
  35. package/dist/server/routes/batch.js +493 -0
  36. package/dist/server/routes/cli-usage.d.ts +6 -0
  37. package/dist/server/routes/cli-usage.js +127 -0
  38. package/dist/server/routes/compat.d.ts +23 -0
  39. package/dist/server/routes/compat.js +652 -0
  40. package/dist/server/routes/deep-fetch.d.ts +8 -0
  41. package/dist/server/routes/deep-fetch.js +57 -0
  42. package/dist/server/routes/demo.d.ts +24 -0
  43. package/dist/server/routes/demo.js +517 -0
  44. package/dist/server/routes/do.d.ts +8 -0
  45. package/dist/server/routes/do.js +72 -0
  46. package/dist/server/routes/extract.d.ts +8 -0
  47. package/dist/server/routes/extract.js +235 -0
  48. package/dist/server/routes/fetch.d.ts +7 -0
  49. package/dist/server/routes/fetch.js +999 -0
  50. package/dist/server/routes/health.d.ts +7 -0
  51. package/dist/server/routes/health.js +19 -0
  52. package/dist/server/routes/jobs.d.ts +7 -0
  53. package/dist/server/routes/jobs.js +573 -0
  54. package/dist/server/routes/mcp.d.ts +14 -0
  55. package/dist/server/routes/mcp.js +141 -0
  56. package/dist/server/routes/oauth.d.ts +9 -0
  57. package/dist/server/routes/oauth.js +396 -0
  58. package/dist/server/routes/playground.d.ts +17 -0
  59. package/dist/server/routes/playground.js +283 -0
  60. package/dist/server/routes/screenshot.d.ts +22 -0
  61. package/dist/server/routes/screenshot.js +816 -0
  62. package/dist/server/routes/search.d.ts +6 -0
  63. package/dist/server/routes/search.js +303 -0
  64. package/dist/server/routes/session.d.ts +15 -0
  65. package/dist/server/routes/session.js +397 -0
  66. package/dist/server/routes/stats.d.ts +6 -0
  67. package/dist/server/routes/stats.js +71 -0
  68. package/dist/server/routes/stripe.d.ts +15 -0
  69. package/dist/server/routes/stripe.js +294 -0
  70. package/dist/server/routes/users.d.ts +8 -0
  71. package/dist/server/routes/users.js +1671 -0
  72. package/dist/server/routes/watch.d.ts +15 -0
  73. package/dist/server/routes/watch.js +309 -0
  74. package/dist/server/routes/webhooks.d.ts +26 -0
  75. package/dist/server/routes/webhooks.js +170 -0
  76. package/dist/server/routes/youtube.d.ts +6 -0
  77. package/dist/server/routes/youtube.js +130 -0
  78. package/dist/server/sentry.d.ts +13 -0
  79. package/dist/server/sentry.js +38 -0
  80. package/dist/server/types.d.ts +15 -0
  81. package/dist/server/types.js +7 -0
  82. package/dist/server/utils/response.d.ts +44 -0
  83. package/dist/server/utils/response.js +69 -0
  84. package/dist/server/utils/sse.d.ts +22 -0
  85. package/dist/server/utils/sse.js +38 -0
  86. package/package.json +2 -1
@@ -0,0 +1,294 @@
1
+ /**
2
+ * Stripe webhook handler for subscription management
3
+ */
4
+ import { Router } from 'express';
5
+ import Stripe from 'stripe';
6
+ import pg from 'pg';
7
+ import { createLogger } from '../logger.js';
8
+ const log = createLogger('stripe');
9
+ const { Pool } = pg;
10
+ /**
11
+ * Tier configuration (weekly usage model)
12
+ */
13
+ const TIER_LIMITS = {
14
+ free: { weekly_limit: 500, burst_limit: 50, rate_limit: 10 },
15
+ pro: { weekly_limit: 1250, burst_limit: 100, rate_limit: 60 },
16
+ max: { weekly_limit: 6250, burst_limit: 500, rate_limit: 200 },
17
+ };
18
+ /**
19
+ * Create Stripe Billing Portal router
20
+ * POST /v1/billing/portal — create a Stripe Customer Portal session
21
+ * Requires global auth middleware to already have run (req.user or req.auth set).
22
+ */
23
+ export function createBillingPortalRouter(pool) {
24
+ const router = Router();
25
+ const stripeSecretKey = process.env.STRIPE_SECRET_KEY;
26
+ if (!stripeSecretKey) {
27
+ log.warn('STRIPE_SECRET_KEY not configured - billing portal disabled');
28
+ return router;
29
+ }
30
+ const stripe = new Stripe(stripeSecretKey);
31
+ router.post('/v1/billing/portal', async (req, res) => {
32
+ try {
33
+ const userId = req.user?.userId || req.auth?.keyInfo?.accountId;
34
+ if (!userId) {
35
+ res.status(401).json({ success: false, error: { type: 'unauthorized', message: 'Authentication required.', docs: 'https://webpeel.dev/docs/authentication' }, requestId: req.requestId });
36
+ return;
37
+ }
38
+ if (!pool) {
39
+ res.status(503).json({
40
+ success: false,
41
+ error: {
42
+ type: 'db_unavailable',
43
+ message: 'Database not configured',
44
+ docs: 'https://webpeel.dev/docs/errors#db_unavailable',
45
+ },
46
+ requestId: req.requestId,
47
+ });
48
+ return;
49
+ }
50
+ // Get user's stripe_customer_id from DB
51
+ const result = await pool.query('SELECT stripe_customer_id FROM users WHERE id = $1', [userId]);
52
+ const stripeCustomerId = result.rows[0]?.stripe_customer_id;
53
+ if (!stripeCustomerId) {
54
+ res.status(400).json({ success: false, error: { type: 'no_subscription', message: 'No active subscription found. Upgrade to Pro or Max to manage billing.', hint: 'Upgrade at https://webpeel.dev/pricing', docs: 'https://webpeel.dev/docs/errors#no_subscription' }, requestId: req.requestId });
55
+ return;
56
+ }
57
+ // Create portal session
58
+ const session = await stripe.billingPortal.sessions.create({
59
+ customer: stripeCustomerId,
60
+ return_url: 'https://app.webpeel.dev/billing',
61
+ });
62
+ res.json({ url: session.url });
63
+ }
64
+ catch (err) {
65
+ log.error('Failed to create portal session:', err);
66
+ res.status(500).json({
67
+ success: false,
68
+ error: {
69
+ type: 'portal_failed',
70
+ message: 'Failed to create billing portal session',
71
+ docs: 'https://webpeel.dev/docs/errors#portal_failed',
72
+ },
73
+ requestId: req.requestId,
74
+ });
75
+ }
76
+ });
77
+ return router;
78
+ }
79
+ /**
80
+ * Create Stripe webhook router
81
+ */
82
+ export function createStripeRouter() {
83
+ const router = Router();
84
+ const stripeSecretKey = process.env.STRIPE_SECRET_KEY;
85
+ const webhookSecret = process.env.STRIPE_WEBHOOK_SECRET;
86
+ const dbUrl = process.env.DATABASE_URL;
87
+ if (!stripeSecretKey) {
88
+ log.warn('STRIPE_SECRET_KEY not configured - Stripe webhooks disabled');
89
+ return router;
90
+ }
91
+ if (!webhookSecret) {
92
+ log.warn('STRIPE_WEBHOOK_SECRET not configured - Stripe webhooks disabled');
93
+ return router;
94
+ }
95
+ if (!dbUrl) {
96
+ throw new Error('DATABASE_URL environment variable is required');
97
+ }
98
+ const stripe = new Stripe(stripeSecretKey);
99
+ const pool = new Pool({
100
+ connectionString: dbUrl,
101
+ // TLS: enabled when DATABASE_URL contains sslmode=require.
102
+ // Secure by default (rejectUnauthorized: true); set PG_REJECT_UNAUTHORIZED=false
103
+ // only for managed DBs (Render/Neon/Supabase) that use self-signed certs.
104
+ ssl: process.env.DATABASE_URL?.includes('sslmode=require')
105
+ ? { rejectUnauthorized: process.env.PG_REJECT_UNAUTHORIZED !== 'false' }
106
+ : undefined,
107
+ });
108
+ /**
109
+ * POST /v1/webhooks/stripe
110
+ * Handle Stripe webhook events
111
+ * SECURITY: Verifies webhook signature
112
+ */
113
+ router.post('/', async (req, res) => {
114
+ try {
115
+ const sig = req.headers['stripe-signature'];
116
+ if (!sig || typeof sig !== 'string') {
117
+ res.status(400).json({ success: false, error: { type: 'missing_signature', message: 'Stripe signature header missing', hint: 'Ensure the request includes the stripe-signature header', docs: 'https://webpeel.dev/docs/errors#missing_signature' }, requestId: req.requestId });
118
+ return;
119
+ }
120
+ // SECURITY: Verify webhook signature
121
+ let event;
122
+ try {
123
+ event = stripe.webhooks.constructEvent(req.body, sig, webhookSecret);
124
+ }
125
+ catch (err) {
126
+ log.error('Webhook signature verification failed', { message: err.message });
127
+ res.status(400).json({ success: false, error: { type: 'invalid_signature', message: 'Webhook signature verification failed', hint: 'Verify your STRIPE_WEBHOOK_SECRET matches the Stripe dashboard', docs: 'https://webpeel.dev/docs/errors#invalid_signature' }, requestId: req.requestId });
128
+ return;
129
+ }
130
+ // Handle different event types
131
+ switch (event.type) {
132
+ case 'checkout.session.completed': {
133
+ const session = event.data.object;
134
+ await handleCheckoutCompleted(pool, session);
135
+ break;
136
+ }
137
+ case 'customer.subscription.updated': {
138
+ const subscription = event.data.object;
139
+ await handleSubscriptionUpdated(pool, subscription);
140
+ break;
141
+ }
142
+ case 'customer.subscription.deleted': {
143
+ const subscription = event.data.object;
144
+ await handleSubscriptionDeleted(pool, subscription);
145
+ break;
146
+ }
147
+ case 'invoice.payment_failed': {
148
+ const invoice = event.data.object;
149
+ await handlePaymentFailed(pool, invoice);
150
+ break;
151
+ }
152
+ default:
153
+ log.warn(`Unhandled Stripe event type: ${event.type}`);
154
+ }
155
+ res.json({ received: true });
156
+ }
157
+ catch (error) {
158
+ log.error('Webhook error', { error: error instanceof Error ? error.message : String(error) });
159
+ res.status(500).json({
160
+ success: false,
161
+ error: {
162
+ type: 'webhook_failed',
163
+ message: 'Failed to process webhook',
164
+ docs: 'https://webpeel.dev/docs/errors#webhook_failed',
165
+ },
166
+ requestId: req.requestId,
167
+ });
168
+ }
169
+ });
170
+ return router;
171
+ }
172
+ /**
173
+ * Handle checkout.session.completed
174
+ * Upgrade user tier and set limits
175
+ */
176
+ async function handleCheckoutCompleted(pool, session) {
177
+ try {
178
+ const customerId = session.customer;
179
+ const subscriptionId = session.subscription;
180
+ // Get subscription to determine tier
181
+ const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);
182
+ const subscription = await stripe.subscriptions.retrieve(subscriptionId);
183
+ // Determine tier from price ID (you'll need to configure these)
184
+ const priceId = subscription.items.data[0]?.price.id;
185
+ const tier = getTierFromPriceId(priceId);
186
+ const limits = TIER_LIMITS[tier];
187
+ // Update user
188
+ await pool.query(`UPDATE users
189
+ SET
190
+ stripe_customer_id = $1,
191
+ stripe_subscription_id = $2,
192
+ tier = $3,
193
+ weekly_limit = $4,
194
+ burst_limit = $5,
195
+ rate_limit = $6,
196
+ updated_at = now()
197
+ WHERE stripe_customer_id = $1 OR email = $7`, [
198
+ customerId,
199
+ subscriptionId,
200
+ tier,
201
+ limits.weekly_limit,
202
+ limits.burst_limit,
203
+ limits.rate_limit,
204
+ session.customer_email,
205
+ ]);
206
+ log.info(`Checkout completed for customer ${customerId}: upgraded to ${tier}`);
207
+ }
208
+ catch (error) {
209
+ log.error('Failed to handle checkout completion', { error: error instanceof Error ? error.message : String(error) });
210
+ throw error;
211
+ }
212
+ }
213
+ /**
214
+ * Handle customer.subscription.updated
215
+ * Update user tier based on subscription changes
216
+ */
217
+ async function handleSubscriptionUpdated(pool, subscription) {
218
+ try {
219
+ const customerId = subscription.customer;
220
+ const priceId = subscription.items.data[0]?.price.id;
221
+ const tier = getTierFromPriceId(priceId);
222
+ const limits = TIER_LIMITS[tier];
223
+ await pool.query(`UPDATE users
224
+ SET
225
+ tier = $1,
226
+ weekly_limit = $2,
227
+ burst_limit = $3,
228
+ rate_limit = $4,
229
+ stripe_subscription_id = $5,
230
+ updated_at = now()
231
+ WHERE stripe_customer_id = $6`, [tier, limits.weekly_limit, limits.burst_limit, limits.rate_limit, subscription.id, customerId]);
232
+ log.info(`Subscription updated for customer ${customerId}: tier=${tier}`);
233
+ }
234
+ catch (error) {
235
+ log.error('Failed to handle subscription update', { error: error instanceof Error ? error.message : String(error) });
236
+ throw error;
237
+ }
238
+ }
239
+ /**
240
+ * Handle customer.subscription.deleted
241
+ * Downgrade user to free tier
242
+ */
243
+ async function handleSubscriptionDeleted(pool, subscription) {
244
+ try {
245
+ const customerId = subscription.customer;
246
+ const limits = TIER_LIMITS.free;
247
+ await pool.query(`UPDATE users
248
+ SET
249
+ tier = 'free',
250
+ weekly_limit = $1,
251
+ burst_limit = $2,
252
+ rate_limit = $3,
253
+ stripe_subscription_id = NULL,
254
+ updated_at = now()
255
+ WHERE stripe_customer_id = $4`, [limits.weekly_limit, limits.burst_limit, limits.rate_limit, customerId]);
256
+ log.info(`Subscription deleted for customer ${customerId}: downgraded to free`);
257
+ }
258
+ catch (error) {
259
+ log.error('Failed to handle subscription deletion', { error: error instanceof Error ? error.message : String(error) });
260
+ throw error;
261
+ }
262
+ }
263
+ /**
264
+ * Handle invoice.payment_failed
265
+ * Log payment failure (could add email notification here)
266
+ */
267
+ async function handlePaymentFailed(pool, invoice) {
268
+ try {
269
+ const customerId = invoice.customer;
270
+ // Get user email for logging
271
+ const result = await pool.query('SELECT email FROM users WHERE stripe_customer_id = $1', [customerId]);
272
+ if (result.rows.length > 0) {
273
+ log.warn(`Payment failed for customer ${customerId}`, { email: result.rows[0].email });
274
+ // Note: Email notification not implemented. Log only for now.
275
+ }
276
+ }
277
+ catch (error) {
278
+ log.error('Failed to handle payment failure', { error: error instanceof Error ? error.message : String(error) });
279
+ throw error;
280
+ }
281
+ }
282
+ /**
283
+ * Map Stripe price ID to tier
284
+ * Maps Stripe price IDs to tiers (configured via STRIPE_PRICE_PRO and STRIPE_PRICE_MAX env vars)
285
+ */
286
+ function getTierFromPriceId(priceId) {
287
+ // Map price IDs to tiers
288
+ const priceMap = {
289
+ // Add your Stripe price IDs here
290
+ [process.env.STRIPE_PRICE_PRO || '']: 'pro',
291
+ [process.env.STRIPE_PRICE_MAX || '']: 'max',
292
+ };
293
+ return priceMap[priceId] || 'free';
294
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * User authentication and API key management routes
3
+ */
4
+ import { Router } from 'express';
5
+ /**
6
+ * Create user routes
7
+ */
8
+ export declare function createUserRouter(): Router;