docit-ai 1.0.0

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 (81) hide show
  1. package/.env.example +1 -0
  2. package/INSTRUCTIONS.MD +70 -0
  3. package/dist/ai/engine.d.ts +24 -0
  4. package/dist/ai/engine.d.ts.map +1 -0
  5. package/dist/ai/engine.js +69 -0
  6. package/dist/ai/engine.js.map +1 -0
  7. package/dist/ai/prompts.d.ts +11 -0
  8. package/dist/ai/prompts.d.ts.map +1 -0
  9. package/dist/ai/prompts.js +116 -0
  10. package/dist/ai/prompts.js.map +1 -0
  11. package/dist/cli/index.d.ts +3 -0
  12. package/dist/cli/index.d.ts.map +1 -0
  13. package/dist/cli/index.js +384 -0
  14. package/dist/cli/index.js.map +1 -0
  15. package/dist/formatter/index.d.ts +13 -0
  16. package/dist/formatter/index.d.ts.map +1 -0
  17. package/dist/formatter/index.js +76 -0
  18. package/dist/formatter/index.js.map +1 -0
  19. package/dist/index.d.ts +7 -0
  20. package/dist/index.d.ts.map +1 -0
  21. package/dist/index.js +9 -0
  22. package/dist/index.js.map +1 -0
  23. package/dist/parser/extractor.d.ts +23 -0
  24. package/dist/parser/extractor.d.ts.map +1 -0
  25. package/dist/parser/extractor.js +148 -0
  26. package/dist/parser/extractor.js.map +1 -0
  27. package/dist/scanner/index.d.ts +11 -0
  28. package/dist/scanner/index.d.ts.map +1 -0
  29. package/dist/scanner/index.js +30 -0
  30. package/dist/scanner/index.js.map +1 -0
  31. package/package.json +31 -0
  32. package/server/.env.example +22 -0
  33. package/server/package-lock.json +4426 -0
  34. package/server/package.json +31 -0
  35. package/server/src/config/database.d.ts +18 -0
  36. package/server/src/config/database.d.ts.map +1 -0
  37. package/server/src/config/database.js +8 -0
  38. package/server/src/config/database.js.map +1 -0
  39. package/server/src/config/database.ts +31 -0
  40. package/server/src/index.d.ts +2 -0
  41. package/server/src/index.d.ts.map +1 -0
  42. package/server/src/index.js +33 -0
  43. package/server/src/index.js.map +1 -0
  44. package/server/src/index.ts +55 -0
  45. package/server/src/middleware/auth.d.ts +12 -0
  46. package/server/src/middleware/auth.d.ts.map +1 -0
  47. package/server/src/middleware/auth.js +35 -0
  48. package/server/src/middleware/auth.js.map +1 -0
  49. package/server/src/middleware/auth.ts +56 -0
  50. package/server/src/routes/auth.d.ts +3 -0
  51. package/server/src/routes/auth.d.ts.map +1 -0
  52. package/server/src/routes/auth.js +145 -0
  53. package/server/src/routes/auth.js.map +1 -0
  54. package/server/src/routes/auth.ts +185 -0
  55. package/server/src/routes/dashboard.ts +243 -0
  56. package/server/src/routes/generate.d.ts +3 -0
  57. package/server/src/routes/generate.d.ts.map +1 -0
  58. package/server/src/routes/generate.js +55 -0
  59. package/server/src/routes/generate.js.map +1 -0
  60. package/server/src/routes/generate.ts +75 -0
  61. package/server/src/routes/payment.ts +192 -0
  62. package/server/src/services/ai.d.ts +10 -0
  63. package/server/src/services/ai.d.ts.map +1 -0
  64. package/server/src/services/ai.js +75 -0
  65. package/server/src/services/ai.js.map +1 -0
  66. package/server/src/services/ai.ts +99 -0
  67. package/server/src/services/payment.ts +141 -0
  68. package/server/src/types/flutterwave.d.ts +60 -0
  69. package/server/supabase_payments.sql +35 -0
  70. package/server/supabase_schema.sql +90 -0
  71. package/server/tsconfig.json +17 -0
  72. package/server/vercel.json +15 -0
  73. package/server/verify_dashboard.ts +126 -0
  74. package/src/ai/engine.ts +103 -0
  75. package/src/ai/prompts.ts +123 -0
  76. package/src/cli/index.ts +552 -0
  77. package/src/formatter/index.ts +110 -0
  78. package/src/index.ts +11 -0
  79. package/src/parser/extractor.ts +211 -0
  80. package/src/scanner/index.ts +49 -0
  81. package/tsconfig.json +43 -0
@@ -0,0 +1,243 @@
1
+ import { Router } from "express";
2
+ import { supabase } from "../config/database.js";
3
+ import { authMiddleware, type AuthRequest } from "../middleware/auth.js";
4
+ import { PLANS, getPlanById } from "../services/payment.js";
5
+
6
+ const router = Router();
7
+
8
+ // GET /dashboard/stats - Dashboard statistics
9
+ router.get("/stats", authMiddleware, async (req: AuthRequest, res) => {
10
+ try {
11
+ const user = req.user!;
12
+
13
+ // Get total documents generated count
14
+ const { count: documentsGenerated } = await supabase
15
+ .from("document_generations")
16
+ .select("*", { count: "exact", head: true })
17
+ .eq("user_id", user.id);
18
+
19
+ // Get total files processed
20
+ const { data: filesData } = await supabase
21
+ .from("document_generations")
22
+ .select("files_count")
23
+ .eq("user_id", user.id);
24
+
25
+ const filesProcessed =
26
+ filesData?.reduce((sum, doc) => sum + (doc.files_count ?? 0), 0) ?? 0;
27
+
28
+ // Get last sync (last document generation time)
29
+ const { data: lastDoc } = await supabase
30
+ .from("document_generations")
31
+ .select("created_at")
32
+ .eq("user_id", user.id)
33
+ .order("created_at", { ascending: false })
34
+ .limit(1)
35
+ .single();
36
+
37
+ res.json({
38
+ creditsAvailable: user.credits,
39
+ documentsGenerated: documentsGenerated ?? 0,
40
+ filesProcessed,
41
+ lastSync: lastDoc?.created_at ?? null,
42
+ });
43
+ } catch (error) {
44
+ console.error("Stats error:", error);
45
+ res.status(500).json({ error: "Failed to fetch stats" });
46
+ }
47
+ });
48
+
49
+ // GET /dashboard/activity - Recent user activity
50
+ router.get("/activity", authMiddleware, async (req: AuthRequest, res) => {
51
+ try {
52
+ const user = req.user!;
53
+ const limit = Math.min(parseInt(String(req.query.limit ?? "10"), 10), 50);
54
+
55
+ // Get recent credit transactions
56
+ const { data: transactions } = await supabase
57
+ .from("credit_transactions")
58
+ .select("*")
59
+ .eq("user_id", user.id)
60
+ .order("created_at", { ascending: false })
61
+ .limit(limit);
62
+
63
+ const activities = (transactions ?? []).map((tx) => {
64
+ let activityType:
65
+ | "document_generated"
66
+ | "credits_purchased"
67
+ | "files_processed";
68
+
69
+ if (tx.type === "purchase") {
70
+ activityType = "credits_purchased";
71
+ } else if (tx.type === "usage") {
72
+ activityType = tx.description?.includes("file")
73
+ ? "files_processed"
74
+ : "document_generated";
75
+ } else {
76
+ activityType = "document_generated";
77
+ }
78
+
79
+ return {
80
+ id: tx.id,
81
+ type: activityType,
82
+ description: tx.description,
83
+ timestamp: tx.created_at,
84
+ creditsUsed: tx.type === "usage" ? Math.abs(tx.amount) : null,
85
+ };
86
+ });
87
+
88
+ res.json({ activities });
89
+ } catch (error) {
90
+ console.error("Activity error:", error);
91
+ res.status(500).json({ error: "Failed to fetch activity" });
92
+ }
93
+ });
94
+
95
+ // GET /dashboard/usage - Credit usage over time
96
+ router.get("/usage", authMiddleware, async (req: AuthRequest, res) => {
97
+ try {
98
+ const user = req.user!;
99
+ const period = (req.query.period as string) ?? "30d";
100
+
101
+ // Calculate date range
102
+ const days = period === "7d" ? 7 : period === "90d" ? 90 : 30;
103
+ const startDate = new Date();
104
+ startDate.setDate(startDate.getDate() - days);
105
+
106
+ // Get usage transactions
107
+ const { data: transactions } = await supabase
108
+ .from("credit_transactions")
109
+ .select("amount, created_at")
110
+ .eq("user_id", user.id)
111
+ .eq("type", "usage")
112
+ .gte("created_at", startDate.toISOString())
113
+ .order("created_at", { ascending: true });
114
+
115
+ // Group by date
116
+ const usageByDate = new Map<string, number>();
117
+ (transactions ?? []).forEach((tx) => {
118
+ const date = tx.created_at.split("T")[0];
119
+ usageByDate.set(date, (usageByDate.get(date) ?? 0) + Math.abs(tx.amount));
120
+ });
121
+
122
+ const data = Array.from(usageByDate.entries()).map(([date, credits]) => ({
123
+ date,
124
+ credits,
125
+ }));
126
+
127
+ const totalCreditsUsed = data.reduce((sum, d) => sum + d.credits, 0);
128
+
129
+ res.json({
130
+ period,
131
+ data,
132
+ totalCreditsUsed,
133
+ });
134
+ } catch (error) {
135
+ console.error("Usage error:", error);
136
+ res.status(500).json({ error: "Failed to fetch usage" });
137
+ }
138
+ });
139
+
140
+ // GET /dashboard/documents - Document generation history
141
+ router.get("/documents", authMiddleware, async (req: AuthRequest, res) => {
142
+ try {
143
+ const user = req.user!;
144
+ const page = Math.max(parseInt(String(req.query.page ?? "1"), 10), 1);
145
+ const limit = Math.min(parseInt(String(req.query.limit ?? "20"), 10), 100);
146
+ const offset = (page - 1) * limit;
147
+
148
+ // Get total count
149
+ const { count: total } = await supabase
150
+ .from("document_generations")
151
+ .select("*", { count: "exact", head: true })
152
+ .eq("user_id", user.id);
153
+
154
+ // Get paginated documents
155
+ const { data: documents } = await supabase
156
+ .from("document_generations")
157
+ .select("*")
158
+ .eq("user_id", user.id)
159
+ .order("created_at", { ascending: false })
160
+ .range(offset, offset + limit - 1);
161
+
162
+ res.json({
163
+ documents: (documents ?? []).map((doc) => ({
164
+ id: doc.id,
165
+ project: doc.project_name,
166
+ files: doc.files_count,
167
+ credits: doc.credits_used,
168
+ createdAt: doc.created_at,
169
+ })),
170
+ pagination: {
171
+ page,
172
+ limit,
173
+ total: total ?? 0,
174
+ hasMore: offset + limit < (total ?? 0),
175
+ },
176
+ });
177
+ } catch (error) {
178
+ console.error("Documents error:", error);
179
+ res.status(500).json({ error: "Failed to fetch documents" });
180
+ }
181
+ });
182
+
183
+ // GET /dashboard/billing - Current billing info
184
+ router.get("/billing", authMiddleware, async (req: AuthRequest, res) => {
185
+ try {
186
+ const user = req.user!;
187
+
188
+ // Get most recent successful payment
189
+ const { data: lastPayment } = await supabase
190
+ .from("payment_transactions")
191
+ .select("*")
192
+ .eq("user_id", user.id)
193
+ .eq("status", "successful")
194
+ .order("created_at", { ascending: false })
195
+ .limit(1)
196
+ .single();
197
+
198
+ let subscriptionStatus: "active" | "cancelled" | "past_due" | "none" =
199
+ "none";
200
+ let currentPlan: string | null = null;
201
+ let planName: string | null = null;
202
+ let planAmount: number | null = null;
203
+ let planCurrency: string | null = null;
204
+ let nextBillingDate: string | null = null;
205
+ let maxCredits = 10; // Default for free tier
206
+
207
+ if (lastPayment) {
208
+ const plan = getPlanById(lastPayment.plan_id);
209
+ if (plan) {
210
+ currentPlan = plan.id;
211
+ planName = plan.name;
212
+ planAmount = plan.amount;
213
+ planCurrency = plan.currency;
214
+ maxCredits = plan.credits;
215
+
216
+ // Calculate next billing date (30 days from last payment)
217
+ const lastPaymentDate = new Date(lastPayment.created_at);
218
+ const nextBilling = new Date(lastPaymentDate);
219
+ nextBilling.setDate(nextBilling.getDate() + 30);
220
+ nextBillingDate = nextBilling.toISOString();
221
+
222
+ // Check if subscription is still active (within 30 days)
223
+ subscriptionStatus = nextBilling > new Date() ? "active" : "past_due";
224
+ }
225
+ }
226
+
227
+ res.json({
228
+ currentPlan,
229
+ planName,
230
+ planAmount,
231
+ planCurrency,
232
+ nextBillingDate,
233
+ creditBalance: user.credits,
234
+ maxCredits,
235
+ subscriptionStatus,
236
+ });
237
+ } catch (error) {
238
+ console.error("Billing error:", error);
239
+ res.status(500).json({ error: "Failed to fetch billing info" });
240
+ }
241
+ });
242
+
243
+ export default router;
@@ -0,0 +1,3 @@
1
+ declare const router: import("express-serve-static-core").Router;
2
+ export default router;
3
+ //# sourceMappingURL=generate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generate.d.ts","sourceRoot":"","sources":["generate.ts"],"names":[],"mappings":"AAKA,QAAA,MAAM,MAAM,4CAAW,CAAC;AA2DxB,eAAe,MAAM,CAAC"}
@@ -0,0 +1,55 @@
1
+ import { Router } from "express";
2
+ import { supabase } from "../config/database.js";
3
+ import { authMiddleware } from "../middleware/auth.js";
4
+ import { generateDocumentation } from "../services/ai.js";
5
+ const router = Router();
6
+ // Generate documentation
7
+ router.post("/", authMiddleware, async (req, res) => {
8
+ try {
9
+ const user = req.user;
10
+ const { codeSkeleton, views, fileCount } = req.body;
11
+ if (!codeSkeleton) {
12
+ res.status(400).json({ error: "Code skeleton is required" });
13
+ return;
14
+ }
15
+ // Calculate credits needed
16
+ const creditsPerFile = parseInt(process.env.CREDITS_PER_FILE ?? "1", 10);
17
+ const filesProcessed = fileCount ?? 1;
18
+ const creditsNeeded = filesProcessed * creditsPerFile;
19
+ // Check if user has enough credits
20
+ if (user.credits < creditsNeeded) {
21
+ res.status(402).json({
22
+ error: "Insufficient credits",
23
+ required: creditsNeeded,
24
+ available: user.credits,
25
+ });
26
+ return;
27
+ }
28
+ // Generate documentation
29
+ const results = await generateDocumentation(codeSkeleton, views);
30
+ // Deduct credits
31
+ const newBalance = user.credits - creditsNeeded;
32
+ await supabase
33
+ .from("users")
34
+ .update({ credits: newBalance })
35
+ .eq("id", user.id);
36
+ // Log transaction
37
+ await supabase.from("credit_transactions").insert({
38
+ user_id: user.id,
39
+ amount: -creditsNeeded,
40
+ type: "usage",
41
+ description: `Generated documentation for ${filesProcessed} file(s)`,
42
+ });
43
+ res.json({
44
+ results,
45
+ creditsUsed: creditsNeeded,
46
+ creditsRemaining: newBalance,
47
+ });
48
+ }
49
+ catch (error) {
50
+ console.error("Generation error:", error);
51
+ res.status(500).json({ error: "Failed to generate documentation" });
52
+ }
53
+ });
54
+ export default router;
55
+ //# sourceMappingURL=generate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generate.js","sourceRoot":"","sources":["generate.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACjC,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AACjD,OAAO,EAAE,cAAc,EAAoB,MAAM,uBAAuB,CAAC;AACzE,OAAO,EAAE,qBAAqB,EAAwB,MAAM,mBAAmB,CAAC;AAEhF,MAAM,MAAM,GAAG,MAAM,EAAE,CAAC;AAExB,yBAAyB;AACzB,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,cAAc,EAAE,KAAK,EAAE,GAAgB,EAAE,GAAG,EAAE,EAAE;IAC/D,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,GAAG,CAAC,IAAK,CAAC;QACvB,MAAM,EAAE,YAAY,EAAE,KAAK,EAAE,SAAS,EAAE,GAAG,GAAG,CAAC,IAE9C,CAAC;QAEF,IAAI,CAAC,YAAY,EAAE,CAAC;YAClB,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,2BAA2B,EAAE,CAAC,CAAC;YAC7D,OAAO;QACT,CAAC;QAED,2BAA2B;QAC3B,MAAM,cAAc,GAAG,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,gBAAgB,IAAI,GAAG,EAAE,EAAE,CAAC,CAAC;QACzE,MAAM,cAAc,GAAG,SAAS,IAAI,CAAC,CAAC;QACtC,MAAM,aAAa,GAAG,cAAc,GAAG,cAAc,CAAC;QAEtD,mCAAmC;QACnC,IAAI,IAAI,CAAC,OAAO,GAAG,aAAa,EAAE,CAAC;YACjC,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC;gBACnB,KAAK,EAAE,sBAAsB;gBAC7B,QAAQ,EAAE,aAAa;gBACvB,SAAS,EAAE,IAAI,CAAC,OAAO;aACxB,CAAC,CAAC;YACH,OAAO;QACT,CAAC;QAED,yBAAyB;QACzB,MAAM,OAAO,GAAG,MAAM,qBAAqB,CAAC,YAAY,EAAE,KAAK,CAAC,CAAC;QAEjE,iBAAiB;QACjB,MAAM,UAAU,GAAG,IAAI,CAAC,OAAO,GAAG,aAAa,CAAC;QAChD,MAAM,QAAQ;aACX,IAAI,CAAC,OAAO,CAAC;aACb,MAAM,CAAC,EAAE,OAAO,EAAE,UAAU,EAAE,CAAC;aAC/B,EAAE,CAAC,IAAI,EAAE,IAAI,CAAC,EAAE,CAAC,CAAC;QAErB,kBAAkB;QAClB,MAAM,QAAQ,CAAC,IAAI,CAAC,qBAAqB,CAAC,CAAC,MAAM,CAAC;YAChD,OAAO,EAAE,IAAI,CAAC,EAAE;YAChB,MAAM,EAAE,CAAC,aAAa;YACtB,IAAI,EAAE,OAAO;YACb,WAAW,EAAE,+BAA+B,cAAc,UAAU;SACrE,CAAC,CAAC;QAEH,GAAG,CAAC,IAAI,CAAC;YACP,OAAO;YACP,WAAW,EAAE,aAAa;YAC1B,gBAAgB,EAAE,UAAU;SAC7B,CAAC,CAAC;IACL,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,CAAC,KAAK,CAAC,mBAAmB,EAAE,KAAK,CAAC,CAAC;QAC1C,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,kCAAkC,EAAE,CAAC,CAAC;IACtE,CAAC;AACH,CAAC,CAAC,CAAC;AAEH,eAAe,MAAM,CAAC"}
@@ -0,0 +1,75 @@
1
+ import { Router } from "express";
2
+ import { supabase } from "../config/database.js";
3
+ import { authMiddleware, type AuthRequest } from "../middleware/auth.js";
4
+ import { generateDocumentation, type GenerateRequest } from "../services/ai.js";
5
+
6
+ const router = Router();
7
+
8
+ // Generate documentation
9
+ router.post("/", authMiddleware, async (req: AuthRequest, res) => {
10
+ try {
11
+ const user = req.user!;
12
+ const { codeSkeleton, views, fileCount, projectName } =
13
+ req.body as GenerateRequest & {
14
+ fileCount?: number;
15
+ projectName?: string;
16
+ };
17
+
18
+ if (!codeSkeleton) {
19
+ res.status(400).json({ error: "Code skeleton is required" });
20
+ return;
21
+ }
22
+
23
+ // Calculate credits needed
24
+ const creditsPerFile = parseInt(process.env.CREDITS_PER_FILE ?? "1", 10);
25
+ const filesProcessed = fileCount ?? 1;
26
+ const creditsNeeded = filesProcessed * creditsPerFile;
27
+
28
+ // Check if user has enough credits
29
+ if (user.credits < creditsNeeded) {
30
+ res.status(402).json({
31
+ error: "Insufficient credits",
32
+ required: creditsNeeded,
33
+ available: user.credits,
34
+ });
35
+ return;
36
+ }
37
+
38
+ // Generate documentation
39
+ const results = await generateDocumentation(codeSkeleton, views);
40
+
41
+ // Deduct credits
42
+ const newBalance = user.credits - creditsNeeded;
43
+ await supabase
44
+ .from("users")
45
+ .update({ credits: newBalance })
46
+ .eq("id", user.id);
47
+
48
+ // Log credit transaction
49
+ await supabase.from("credit_transactions").insert({
50
+ user_id: user.id,
51
+ amount: -creditsNeeded,
52
+ type: "usage",
53
+ description: `Generated documentation for ${filesProcessed} file(s)`,
54
+ });
55
+
56
+ // Log document generation
57
+ await supabase.from("document_generations").insert({
58
+ user_id: user.id,
59
+ project_name: projectName ?? "Untitled Project",
60
+ files_count: filesProcessed,
61
+ credits_used: creditsNeeded,
62
+ });
63
+
64
+ res.json({
65
+ results,
66
+ creditsUsed: creditsNeeded,
67
+ creditsRemaining: newBalance,
68
+ });
69
+ } catch (error) {
70
+ console.error("Generation error:", error);
71
+ res.status(500).json({ error: "Failed to generate documentation" });
72
+ }
73
+ });
74
+
75
+ export default router;
@@ -0,0 +1,192 @@
1
+ import { Router } from "express";
2
+ import { supabase } from "../config/database.js";
3
+ import { authMiddleware, type AuthRequest } from "../middleware/auth.js";
4
+ import {
5
+ PLANS,
6
+ getPlanById,
7
+ initiatePayment,
8
+ verifyTransaction,
9
+ } from "../services/payment.js";
10
+
11
+ const router = Router();
12
+
13
+ // Get available plans
14
+ router.get("/plans", (_req, res) => {
15
+ res.json({
16
+ plans: PLANS.map((p) => ({
17
+ id: p.id,
18
+ name: p.name,
19
+ amount: p.amount,
20
+ currency: p.currency,
21
+ credits: p.credits,
22
+ description: p.description,
23
+ })),
24
+ });
25
+ });
26
+
27
+ // Initiate payment
28
+ router.post("/initiate", authMiddleware, async (req: AuthRequest, res) => {
29
+ try {
30
+ const user = req.user!;
31
+ const { planId, redirectUrl } = req.body as {
32
+ planId?: string;
33
+ redirectUrl?: string;
34
+ };
35
+
36
+ if (!planId) {
37
+ res.status(400).json({ error: "Plan ID is required" });
38
+ return;
39
+ }
40
+
41
+ const plan = getPlanById(planId);
42
+ if (!plan) {
43
+ res.status(400).json({ error: "Invalid plan ID" });
44
+ return;
45
+ }
46
+
47
+ const result = await initiatePayment({
48
+ email: user.email,
49
+ userId: user.id,
50
+ planId,
51
+ redirectUrl:
52
+ redirectUrl ??
53
+ `${process.env.FRONTEND_URL ?? "http://localhost:3000"}/payment/callback`,
54
+ });
55
+
56
+ // Store pending transaction
57
+ await supabase.from("payment_transactions").insert({
58
+ user_id: user.id,
59
+ tx_ref: result.tx_ref,
60
+ plan_id: planId,
61
+ amount: plan.amount,
62
+ currency: plan.currency,
63
+ credits: plan.credits,
64
+ status: "pending",
65
+ });
66
+
67
+ res.json({
68
+ paymentLink: result.link,
69
+ txRef: result.tx_ref,
70
+ });
71
+ } catch (error) {
72
+ console.error("Payment initiation error:", error);
73
+ res.status(500).json({ error: "Failed to initiate payment" });
74
+ }
75
+ });
76
+
77
+ // Webhook for Flutterwave
78
+ router.post("/webhook", async (req, res) => {
79
+ try {
80
+ // Verify webhook signature
81
+ const secretHash = process.env.FLUTTERWAVE_SECRET_HASH;
82
+ const signature = req.headers["verif-hash"];
83
+
84
+ if (secretHash && signature !== secretHash) {
85
+ res.status(401).json({ error: "Invalid signature" });
86
+ return;
87
+ }
88
+
89
+ const { event, data } = req.body as {
90
+ event?: string;
91
+ data?: { id?: number; tx_ref?: string; status?: string };
92
+ };
93
+
94
+ if (event === "charge.completed" && data?.status === "successful") {
95
+ const transactionId = data.id;
96
+ if (!transactionId) {
97
+ res.status(400).json({ error: "Missing transaction ID" });
98
+ return;
99
+ }
100
+
101
+ // Verify the transaction
102
+ const verification = await verifyTransaction(String(transactionId));
103
+
104
+ if (verification.status === "successful" && verification.userId) {
105
+ // Update transaction status
106
+ await supabase
107
+ .from("payment_transactions")
108
+ .update({ status: "successful", flutterwave_id: transactionId })
109
+ .eq("tx_ref", data.tx_ref);
110
+
111
+ // Add credits to user
112
+ const { data: user } = await supabase
113
+ .from("users")
114
+ .select("credits")
115
+ .eq("id", verification.userId)
116
+ .single();
117
+
118
+ if (user) {
119
+ const newCredits = (user.credits ?? 0) + (verification.credits ?? 0);
120
+
121
+ await supabase
122
+ .from("users")
123
+ .update({ credits: newCredits })
124
+ .eq("id", verification.userId);
125
+
126
+ // Log credit transaction
127
+ await supabase.from("credit_transactions").insert({
128
+ user_id: verification.userId,
129
+ amount: verification.credits,
130
+ type: "purchase",
131
+ description: `Subscription payment - ${verification.credits} credits`,
132
+ reference_id: data.tx_ref,
133
+ });
134
+ }
135
+ }
136
+ }
137
+
138
+ res.json({ status: "ok" });
139
+ } catch (error) {
140
+ console.error("Webhook error:", error);
141
+ res.status(500).json({ error: "Webhook processing failed" });
142
+ }
143
+ });
144
+
145
+ // Verify payment (for redirect callback)
146
+ router.get("/verify/:txRef", authMiddleware, async (req: AuthRequest, res) => {
147
+ try {
148
+ const { txRef } = req.params;
149
+ const user = req.user!;
150
+
151
+ // Get transaction from database
152
+ const { data: transaction } = await supabase
153
+ .from("payment_transactions")
154
+ .select("*")
155
+ .eq("tx_ref", txRef)
156
+ .eq("user_id", user.id)
157
+ .single();
158
+
159
+ if (!transaction) {
160
+ res.status(404).json({ error: "Transaction not found" });
161
+ return;
162
+ }
163
+
164
+ res.json({
165
+ status: transaction.status,
166
+ credits: transaction.credits,
167
+ amount: transaction.amount,
168
+ currency: transaction.currency,
169
+ });
170
+ } catch (error) {
171
+ console.error("Verify error:", error);
172
+ res.status(500).json({ error: "Verification failed" });
173
+ }
174
+ });
175
+
176
+ // Get payment history
177
+ router.get("/history", authMiddleware, async (req: AuthRequest, res) => {
178
+ const user = req.user!;
179
+
180
+ const { data: transactions } = await supabase
181
+ .from("payment_transactions")
182
+ .select("*")
183
+ .eq("user_id", user.id)
184
+ .order("created_at", { ascending: false })
185
+ .limit(20);
186
+
187
+ res.json({
188
+ transactions: transactions ?? [],
189
+ });
190
+ });
191
+
192
+ export default router;
@@ -0,0 +1,10 @@
1
+ export interface GenerateRequest {
2
+ codeSkeleton: string;
3
+ views?: string[];
4
+ }
5
+ export interface GenerateResult {
6
+ view: string;
7
+ content: string;
8
+ }
9
+ export declare function generateDocumentation(codeSkeleton: string, views?: string[]): Promise<GenerateResult[]>;
10
+ //# sourceMappingURL=ai.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ai.d.ts","sourceRoot":"","sources":["ai.ts"],"names":[],"mappings":"AA2BA,MAAM,WAAW,eAAe;IAC9B,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;CAClB;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AAMD,wBAAsB,qBAAqB,CACzC,YAAY,EAAE,MAAM,EACpB,KAAK,GAAE,MAAM,EAA2C,GACvD,OAAO,CAAC,cAAc,EAAE,CAAC,CAsD3B"}
@@ -0,0 +1,75 @@
1
+ import { GoogleGenAI } from "@google/genai";
2
+ const STRATEGIC_VIEW_PROMPT = `You are a technical writer creating documentation for Project Managers.
3
+ Focus on business value and project status. NO code snippets. Use bullet points and bold text.
4
+ Include "What this feature enables" and a "Completeness Score" (0-100%).`;
5
+ const TECHNICAL_VIEW_PROMPT = `You are a technical writer creating documentation for Developers.
6
+ Focus on architecture and integration. Include function signatures, dependency maps, and code examples.
7
+ Use strict Markdown with syntax highlighting.`;
8
+ const PERSONAL_VIEW_PROMPT = `You are a technical writer creating documentation for Solo Developers.
9
+ Focus on productivity. Generate To-Do lists from TODO comments and Technical Debt summaries.
10
+ Use concise, checklist-style formatting.`;
11
+ function getPromptForView(view) {
12
+ switch (view) {
13
+ case "strategic":
14
+ return STRATEGIC_VIEW_PROMPT;
15
+ case "technical":
16
+ return TECHNICAL_VIEW_PROMPT;
17
+ case "personal":
18
+ return PERSONAL_VIEW_PROMPT;
19
+ default:
20
+ return TECHNICAL_VIEW_PROMPT;
21
+ }
22
+ }
23
+ async function sleep(ms) {
24
+ return new Promise((resolve) => setTimeout(resolve, ms));
25
+ }
26
+ export async function generateDocumentation(codeSkeleton, views = ["strategic", "technical", "personal"]) {
27
+ const apiKey = process.env.GEMINI_API_KEY;
28
+ if (!apiKey) {
29
+ throw new Error("Gemini API key not configured");
30
+ }
31
+ const ai = new GoogleGenAI({ apiKey });
32
+ const model = "gemini-2.0-flash";
33
+ const results = [];
34
+ for (const view of views) {
35
+ const systemPrompt = getPromptForView(view);
36
+ let lastError;
37
+ for (let attempt = 0; attempt < 5; attempt++) {
38
+ try {
39
+ const response = await ai.models.generateContent({
40
+ model,
41
+ contents: [
42
+ {
43
+ role: "user",
44
+ parts: [
45
+ {
46
+ text: `${systemPrompt}\n\n---\n\nAnalyze the following code skeleton and generate documentation:\n\n${codeSkeleton}`,
47
+ },
48
+ ],
49
+ },
50
+ ],
51
+ });
52
+ results.push({
53
+ view,
54
+ content: response.text ?? "",
55
+ });
56
+ break;
57
+ }
58
+ catch (error) {
59
+ lastError = error;
60
+ const status = error.status;
61
+ if (status === 503 || status === 429) {
62
+ const waitTime = Math.pow(2, attempt) * 1000;
63
+ await sleep(waitTime);
64
+ continue;
65
+ }
66
+ throw error;
67
+ }
68
+ }
69
+ if (results.length <= views.indexOf(view)) {
70
+ throw lastError ?? new Error("Failed to generate documentation");
71
+ }
72
+ }
73
+ return results;
74
+ }
75
+ //# sourceMappingURL=ai.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ai.js","sourceRoot":"","sources":["ai.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAE5C,MAAM,qBAAqB,GAAG;;yEAE2C,CAAC;AAE1E,MAAM,qBAAqB,GAAG;;8CAEgB,CAAC;AAE/C,MAAM,oBAAoB,GAAG;;yCAEY,CAAC;AAE1C,SAAS,gBAAgB,CAAC,IAAY;IACpC,QAAQ,IAAI,EAAE,CAAC;QACb,KAAK,WAAW;YACd,OAAO,qBAAqB,CAAC;QAC/B,KAAK,WAAW;YACd,OAAO,qBAAqB,CAAC;QAC/B,KAAK,UAAU;YACb,OAAO,oBAAoB,CAAC;QAC9B;YACE,OAAO,qBAAqB,CAAC;IACjC,CAAC;AACH,CAAC;AAYD,KAAK,UAAU,KAAK,CAAC,EAAU;IAC7B,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC;AAC3D,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,qBAAqB,CACzC,YAAoB,EACpB,QAAkB,CAAC,WAAW,EAAE,WAAW,EAAE,UAAU,CAAC;IAExD,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC;IAC1C,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,MAAM,IAAI,KAAK,CAAC,+BAA+B,CAAC,CAAC;IACnD,CAAC;IAED,MAAM,EAAE,GAAG,IAAI,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC;IACvC,MAAM,KAAK,GAAG,kBAAkB,CAAC;IACjC,MAAM,OAAO,GAAqB,EAAE,CAAC;IAErC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,YAAY,GAAG,gBAAgB,CAAC,IAAI,CAAC,CAAC;QAE5C,IAAI,SAA4B,CAAC;QACjC,KAAK,IAAI,OAAO,GAAG,CAAC,EAAE,OAAO,GAAG,CAAC,EAAE,OAAO,EAAE,EAAE,CAAC;YAC7C,IAAI,CAAC;gBACH,MAAM,QAAQ,GAAG,MAAM,EAAE,CAAC,MAAM,CAAC,eAAe,CAAC;oBAC/C,KAAK;oBACL,QAAQ,EAAE;wBACR;4BACE,IAAI,EAAE,MAAM;4BACZ,KAAK,EAAE;gCACL;oCACE,IAAI,EAAE,GAAG,YAAY,iFAAiF,YAAY,EAAE;iCACrH;6BACF;yBACF;qBACF;iBACF,CAAC,CAAC;gBAEH,OAAO,CAAC,IAAI,CAAC;oBACX,IAAI;oBACJ,OAAO,EAAE,QAAQ,CAAC,IAAI,IAAI,EAAE;iBAC7B,CAAC,CAAC;gBACH,MAAM;YACR,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,SAAS,GAAG,KAAc,CAAC;gBAC3B,MAAM,MAAM,GAAI,KAA6B,CAAC,MAAM,CAAC;gBAErD,IAAI,MAAM,KAAK,GAAG,IAAI,MAAM,KAAK,GAAG,EAAE,CAAC;oBACrC,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;oBAC7C,MAAM,KAAK,CAAC,QAAQ,CAAC,CAAC;oBACtB,SAAS;gBACX,CAAC;gBACD,MAAM,KAAK,CAAC;YACd,CAAC;QACH,CAAC;QAED,IAAI,OAAO,CAAC,MAAM,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;YAC1C,MAAM,SAAS,IAAI,IAAI,KAAK,CAAC,kCAAkC,CAAC,CAAC;QACnE,CAAC;IACH,CAAC;IAED,OAAO,OAAO,CAAC;AACjB,CAAC"}