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.
- package/.env.example +1 -0
- package/INSTRUCTIONS.MD +70 -0
- package/dist/ai/engine.d.ts +24 -0
- package/dist/ai/engine.d.ts.map +1 -0
- package/dist/ai/engine.js +69 -0
- package/dist/ai/engine.js.map +1 -0
- package/dist/ai/prompts.d.ts +11 -0
- package/dist/ai/prompts.d.ts.map +1 -0
- package/dist/ai/prompts.js +116 -0
- package/dist/ai/prompts.js.map +1 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +384 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/formatter/index.d.ts +13 -0
- package/dist/formatter/index.d.ts.map +1 -0
- package/dist/formatter/index.js +76 -0
- package/dist/formatter/index.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -0
- package/dist/parser/extractor.d.ts +23 -0
- package/dist/parser/extractor.d.ts.map +1 -0
- package/dist/parser/extractor.js +148 -0
- package/dist/parser/extractor.js.map +1 -0
- package/dist/scanner/index.d.ts +11 -0
- package/dist/scanner/index.d.ts.map +1 -0
- package/dist/scanner/index.js +30 -0
- package/dist/scanner/index.js.map +1 -0
- package/package.json +31 -0
- package/server/.env.example +22 -0
- package/server/package-lock.json +4426 -0
- package/server/package.json +31 -0
- package/server/src/config/database.d.ts +18 -0
- package/server/src/config/database.d.ts.map +1 -0
- package/server/src/config/database.js +8 -0
- package/server/src/config/database.js.map +1 -0
- package/server/src/config/database.ts +31 -0
- package/server/src/index.d.ts +2 -0
- package/server/src/index.d.ts.map +1 -0
- package/server/src/index.js +33 -0
- package/server/src/index.js.map +1 -0
- package/server/src/index.ts +55 -0
- package/server/src/middleware/auth.d.ts +12 -0
- package/server/src/middleware/auth.d.ts.map +1 -0
- package/server/src/middleware/auth.js +35 -0
- package/server/src/middleware/auth.js.map +1 -0
- package/server/src/middleware/auth.ts +56 -0
- package/server/src/routes/auth.d.ts +3 -0
- package/server/src/routes/auth.d.ts.map +1 -0
- package/server/src/routes/auth.js +145 -0
- package/server/src/routes/auth.js.map +1 -0
- package/server/src/routes/auth.ts +185 -0
- package/server/src/routes/dashboard.ts +243 -0
- package/server/src/routes/generate.d.ts +3 -0
- package/server/src/routes/generate.d.ts.map +1 -0
- package/server/src/routes/generate.js +55 -0
- package/server/src/routes/generate.js.map +1 -0
- package/server/src/routes/generate.ts +75 -0
- package/server/src/routes/payment.ts +192 -0
- package/server/src/services/ai.d.ts +10 -0
- package/server/src/services/ai.d.ts.map +1 -0
- package/server/src/services/ai.js +75 -0
- package/server/src/services/ai.js.map +1 -0
- package/server/src/services/ai.ts +99 -0
- package/server/src/services/payment.ts +141 -0
- package/server/src/types/flutterwave.d.ts +60 -0
- package/server/supabase_payments.sql +35 -0
- package/server/supabase_schema.sql +90 -0
- package/server/tsconfig.json +17 -0
- package/server/vercel.json +15 -0
- package/server/verify_dashboard.ts +126 -0
- package/src/ai/engine.ts +103 -0
- package/src/ai/prompts.ts +123 -0
- package/src/cli/index.ts +552 -0
- package/src/formatter/index.ts +110 -0
- package/src/index.ts +11 -0
- package/src/parser/extractor.ts +211 -0
- package/src/scanner/index.ts +49 -0
- package/tsconfig.json +43 -0
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { GoogleGenAI } from "@google/genai";
|
|
2
|
+
|
|
3
|
+
const STRATEGIC_VIEW_PROMPT = `You are a technical writer creating documentation for Project Managers.
|
|
4
|
+
Focus on business value and project status. NO code snippets. Use bullet points and bold text.
|
|
5
|
+
Include "What this feature enables" and a "Completeness Score" (0-100%).`;
|
|
6
|
+
|
|
7
|
+
const TECHNICAL_VIEW_PROMPT = `You are a technical writer creating documentation for Developers.
|
|
8
|
+
Focus on architecture and integration. Include function signatures, dependency maps, and code examples.
|
|
9
|
+
Use strict Markdown with syntax highlighting.`;
|
|
10
|
+
|
|
11
|
+
const PERSONAL_VIEW_PROMPT = `You are a technical writer creating documentation for Solo Developers.
|
|
12
|
+
Focus on productivity. Generate To-Do lists from TODO comments and Technical Debt summaries.
|
|
13
|
+
Use concise, checklist-style formatting.`;
|
|
14
|
+
|
|
15
|
+
function getPromptForView(view: string): string {
|
|
16
|
+
switch (view) {
|
|
17
|
+
case "strategic":
|
|
18
|
+
return STRATEGIC_VIEW_PROMPT;
|
|
19
|
+
case "technical":
|
|
20
|
+
return TECHNICAL_VIEW_PROMPT;
|
|
21
|
+
case "personal":
|
|
22
|
+
return PERSONAL_VIEW_PROMPT;
|
|
23
|
+
default:
|
|
24
|
+
return TECHNICAL_VIEW_PROMPT;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface GenerateRequest {
|
|
29
|
+
codeSkeleton: string;
|
|
30
|
+
views?: string[];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface GenerateResult {
|
|
34
|
+
view: string;
|
|
35
|
+
content: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async function sleep(ms: number): Promise<void> {
|
|
39
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export async function generateDocumentation(
|
|
43
|
+
codeSkeleton: string,
|
|
44
|
+
views: string[] = ["strategic", "technical", "personal"],
|
|
45
|
+
): Promise<GenerateResult[]> {
|
|
46
|
+
const apiKey = process.env.GEMINI_API_KEY;
|
|
47
|
+
if (!apiKey) {
|
|
48
|
+
throw new Error("Gemini API key not configured");
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const ai = new GoogleGenAI({ apiKey });
|
|
52
|
+
const model = "gemini-2.0-flash";
|
|
53
|
+
const results: GenerateResult[] = [];
|
|
54
|
+
|
|
55
|
+
for (const view of views) {
|
|
56
|
+
const systemPrompt = getPromptForView(view);
|
|
57
|
+
|
|
58
|
+
let lastError: Error | undefined;
|
|
59
|
+
for (let attempt = 0; attempt < 5; attempt++) {
|
|
60
|
+
try {
|
|
61
|
+
const response = await ai.models.generateContent({
|
|
62
|
+
model,
|
|
63
|
+
contents: [
|
|
64
|
+
{
|
|
65
|
+
role: "user",
|
|
66
|
+
parts: [
|
|
67
|
+
{
|
|
68
|
+
text: `${systemPrompt}\n\n---\n\nAnalyze the following code skeleton and generate documentation:\n\n${codeSkeleton}`,
|
|
69
|
+
},
|
|
70
|
+
],
|
|
71
|
+
},
|
|
72
|
+
],
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
results.push({
|
|
76
|
+
view,
|
|
77
|
+
content: response.text ?? "",
|
|
78
|
+
});
|
|
79
|
+
break;
|
|
80
|
+
} catch (error) {
|
|
81
|
+
lastError = error as Error;
|
|
82
|
+
const status = (error as { status?: number }).status;
|
|
83
|
+
|
|
84
|
+
if (status === 503 || status === 429) {
|
|
85
|
+
const waitTime = Math.pow(2, attempt) * 1000;
|
|
86
|
+
await sleep(waitTime);
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
throw error;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (results.length <= views.indexOf(view)) {
|
|
94
|
+
throw lastError ?? new Error("Failed to generate documentation");
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return results;
|
|
99
|
+
}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import Flutterwave from "flutterwave-node-v3";
|
|
2
|
+
|
|
3
|
+
const flw = new Flutterwave(
|
|
4
|
+
process.env.FLUTTERWAVE_PUBLIC_KEY ?? "",
|
|
5
|
+
process.env.FLUTTERWAVE_SECRET_KEY ?? "",
|
|
6
|
+
);
|
|
7
|
+
|
|
8
|
+
export interface PaymentPlan {
|
|
9
|
+
id: string;
|
|
10
|
+
name: string;
|
|
11
|
+
amount: number;
|
|
12
|
+
currency: "NGN" | "USD";
|
|
13
|
+
credits: number;
|
|
14
|
+
description: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// Subscription plans
|
|
18
|
+
export const PLANS: PaymentPlan[] = [
|
|
19
|
+
{
|
|
20
|
+
id: "nigerian_dev",
|
|
21
|
+
name: "Nigerian Developer",
|
|
22
|
+
amount: 8000,
|
|
23
|
+
currency: "NGN",
|
|
24
|
+
credits: 100, // Monthly credits
|
|
25
|
+
description: "₦8,000/month - 100 credits for Nigerian developers",
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
id: "global_company",
|
|
29
|
+
name: "Global Company",
|
|
30
|
+
amount: 20,
|
|
31
|
+
currency: "USD",
|
|
32
|
+
credits: 100, // Monthly credits
|
|
33
|
+
description: "$20/month - 100 credits for global companies",
|
|
34
|
+
},
|
|
35
|
+
];
|
|
36
|
+
|
|
37
|
+
export function getPlanById(planId: string): PaymentPlan | undefined {
|
|
38
|
+
return PLANS.find((p) => p.id === planId);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface InitiatePaymentParams {
|
|
42
|
+
email: string;
|
|
43
|
+
userId: string;
|
|
44
|
+
planId: string;
|
|
45
|
+
redirectUrl: string;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export async function initiatePayment(
|
|
49
|
+
params: InitiatePaymentParams,
|
|
50
|
+
): Promise<{ link: string; tx_ref: string }> {
|
|
51
|
+
const { email, userId, planId, redirectUrl } = params;
|
|
52
|
+
const plan = getPlanById(planId);
|
|
53
|
+
|
|
54
|
+
if (!plan) {
|
|
55
|
+
throw new Error("Invalid plan ID");
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const tx_ref = `docit_${userId}_${Date.now()}`;
|
|
59
|
+
|
|
60
|
+
const payload = {
|
|
61
|
+
tx_ref,
|
|
62
|
+
amount: plan.amount,
|
|
63
|
+
currency: plan.currency,
|
|
64
|
+
redirect_url: redirectUrl,
|
|
65
|
+
customer: {
|
|
66
|
+
email,
|
|
67
|
+
},
|
|
68
|
+
customizations: {
|
|
69
|
+
title: "Docit Subscription",
|
|
70
|
+
description: plan.description,
|
|
71
|
+
},
|
|
72
|
+
meta: {
|
|
73
|
+
user_id: userId,
|
|
74
|
+
plan_id: planId,
|
|
75
|
+
credits: plan.credits,
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
try {
|
|
80
|
+
const response = await fetch("https://api.flutterwave.com/v3/payments", {
|
|
81
|
+
method: "POST",
|
|
82
|
+
headers: {
|
|
83
|
+
Authorization: `Bearer ${process.env.FLUTTERWAVE_SECRET_KEY}`,
|
|
84
|
+
"Content-Type": "application/json",
|
|
85
|
+
},
|
|
86
|
+
body: JSON.stringify(payload),
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
const data = (await response.json()) as {
|
|
90
|
+
status: string;
|
|
91
|
+
message: string;
|
|
92
|
+
data: { link: string };
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
if (data.status === "success") {
|
|
96
|
+
return {
|
|
97
|
+
link: data.data.link,
|
|
98
|
+
tx_ref,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
throw new Error(data.message || "Failed to initiate payment");
|
|
103
|
+
} catch (error: any) {
|
|
104
|
+
console.error("Payment initiation error:", error);
|
|
105
|
+
throw new Error(error.message || "Payment initiation failed");
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export async function verifyTransaction(transactionId: string): Promise<{
|
|
110
|
+
status: "successful" | "failed" | "pending";
|
|
111
|
+
userId?: string;
|
|
112
|
+
planId?: string;
|
|
113
|
+
credits?: number;
|
|
114
|
+
amount?: number;
|
|
115
|
+
currency?: string;
|
|
116
|
+
}> {
|
|
117
|
+
const response = await flw.Transaction.verify({ id: transactionId });
|
|
118
|
+
|
|
119
|
+
if (response.status === "success" && response.data.status === "successful") {
|
|
120
|
+
const meta = response.data.meta as {
|
|
121
|
+
user_id?: string;
|
|
122
|
+
plan_id?: string;
|
|
123
|
+
credits?: number;
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
return {
|
|
127
|
+
status: "successful",
|
|
128
|
+
userId: meta.user_id,
|
|
129
|
+
planId: meta.plan_id,
|
|
130
|
+
credits: meta.credits,
|
|
131
|
+
amount: response.data.amount,
|
|
132
|
+
currency: response.data.currency,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return {
|
|
137
|
+
status: response.data?.status === "pending" ? "pending" : "failed",
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export { flw };
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
declare module "flutterwave-node-v3" {
|
|
2
|
+
interface FlutterwaveConfig {
|
|
3
|
+
public_key: string;
|
|
4
|
+
secret_key: string;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
interface ChargePayload {
|
|
8
|
+
tx_ref: string;
|
|
9
|
+
amount: number;
|
|
10
|
+
currency: string;
|
|
11
|
+
redirect_url: string;
|
|
12
|
+
payment_options?: string;
|
|
13
|
+
customer: {
|
|
14
|
+
email: string;
|
|
15
|
+
name?: string;
|
|
16
|
+
phone_number?: string;
|
|
17
|
+
};
|
|
18
|
+
customizations?: {
|
|
19
|
+
title?: string;
|
|
20
|
+
description?: string;
|
|
21
|
+
logo?: string;
|
|
22
|
+
};
|
|
23
|
+
meta?: Record<string, unknown>;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
interface ChargeResponse {
|
|
27
|
+
status: string;
|
|
28
|
+
message: string;
|
|
29
|
+
data: {
|
|
30
|
+
link: string;
|
|
31
|
+
[key: string]: unknown;
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
interface VerifyResponse {
|
|
36
|
+
status: string;
|
|
37
|
+
message: string;
|
|
38
|
+
data: {
|
|
39
|
+
id: number;
|
|
40
|
+
tx_ref: string;
|
|
41
|
+
amount: number;
|
|
42
|
+
currency: string;
|
|
43
|
+
status: string;
|
|
44
|
+
meta: Record<string, unknown>;
|
|
45
|
+
[key: string]: unknown;
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
class Flutterwave {
|
|
50
|
+
constructor(publicKey: string, secretKey: string);
|
|
51
|
+
Charge: {
|
|
52
|
+
card(payload: ChargePayload): Promise<ChargeResponse>;
|
|
53
|
+
};
|
|
54
|
+
Transaction: {
|
|
55
|
+
verify(payload: { id: string }): Promise<VerifyResponse>;
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export = Flutterwave;
|
|
60
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
-- Additional Supabase Schema for Payments
|
|
2
|
+
-- Run this AFTER the initial schema
|
|
3
|
+
|
|
4
|
+
-- Payment transactions table
|
|
5
|
+
CREATE TABLE IF NOT EXISTS payment_transactions (
|
|
6
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
7
|
+
user_id UUID REFERENCES users(id) ON DELETE CASCADE,
|
|
8
|
+
tx_ref TEXT UNIQUE NOT NULL,
|
|
9
|
+
flutterwave_id TEXT,
|
|
10
|
+
plan_id TEXT NOT NULL,
|
|
11
|
+
amount DECIMAL(10, 2) NOT NULL,
|
|
12
|
+
currency TEXT NOT NULL CHECK (currency IN ('NGN', 'USD')),
|
|
13
|
+
credits INTEGER NOT NULL,
|
|
14
|
+
status TEXT NOT NULL DEFAULT 'pending' CHECK (status IN ('pending', 'successful', 'failed', 'cancelled')),
|
|
15
|
+
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
16
|
+
updated_at TIMESTAMPTZ DEFAULT NOW()
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
-- Indexes
|
|
20
|
+
CREATE INDEX IF NOT EXISTS idx_payment_transactions_user_id ON payment_transactions(user_id);
|
|
21
|
+
CREATE INDEX IF NOT EXISTS idx_payment_transactions_tx_ref ON payment_transactions(tx_ref);
|
|
22
|
+
CREATE INDEX IF NOT EXISTS idx_payment_transactions_status ON payment_transactions(status);
|
|
23
|
+
|
|
24
|
+
-- Updated at trigger
|
|
25
|
+
DROP TRIGGER IF EXISTS payment_transactions_updated_at ON payment_transactions;
|
|
26
|
+
CREATE TRIGGER payment_transactions_updated_at
|
|
27
|
+
BEFORE UPDATE ON payment_transactions
|
|
28
|
+
FOR EACH ROW
|
|
29
|
+
EXECUTE FUNCTION update_updated_at();
|
|
30
|
+
|
|
31
|
+
-- RLS
|
|
32
|
+
ALTER TABLE payment_transactions ENABLE ROW LEVEL SECURITY;
|
|
33
|
+
|
|
34
|
+
CREATE POLICY "Service role full access on payment_transactions" ON payment_transactions
|
|
35
|
+
FOR ALL USING (true);
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
-- Supabase Schema for Docit
|
|
2
|
+
-- Run this in your Supabase SQL Editor
|
|
3
|
+
|
|
4
|
+
-- Users table
|
|
5
|
+
CREATE TABLE IF NOT EXISTS users (
|
|
6
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
7
|
+
email TEXT UNIQUE NOT NULL,
|
|
8
|
+
password_hash TEXT NOT NULL,
|
|
9
|
+
name TEXT,
|
|
10
|
+
country TEXT,
|
|
11
|
+
credits INTEGER DEFAULT 10,
|
|
12
|
+
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
13
|
+
updated_at TIMESTAMPTZ DEFAULT NOW()
|
|
14
|
+
);
|
|
15
|
+
|
|
16
|
+
-- Credit transactions table
|
|
17
|
+
CREATE TABLE IF NOT EXISTS credit_transactions (
|
|
18
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
19
|
+
user_id UUID REFERENCES users(id) ON DELETE CASCADE,
|
|
20
|
+
amount INTEGER NOT NULL,
|
|
21
|
+
type TEXT NOT NULL CHECK (type IN ('purchase', 'usage', 'refund', 'bonus')),
|
|
22
|
+
description TEXT,
|
|
23
|
+
reference_id TEXT,
|
|
24
|
+
created_at TIMESTAMPTZ DEFAULT NOW()
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
-- Payment transactions table (Flutterwave)
|
|
28
|
+
CREATE TABLE IF NOT EXISTS payment_transactions (
|
|
29
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
30
|
+
user_id UUID REFERENCES users(id) ON DELETE CASCADE,
|
|
31
|
+
tx_ref TEXT UNIQUE NOT NULL,
|
|
32
|
+
flutterwave_id TEXT,
|
|
33
|
+
plan_id TEXT NOT NULL,
|
|
34
|
+
amount DECIMAL(10, 2) NOT NULL,
|
|
35
|
+
currency TEXT NOT NULL,
|
|
36
|
+
credits INTEGER NOT NULL,
|
|
37
|
+
status TEXT NOT NULL DEFAULT 'pending' CHECK (status IN ('pending', 'successful', 'failed')),
|
|
38
|
+
created_at TIMESTAMPTZ DEFAULT NOW()
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
-- Document generations table
|
|
42
|
+
CREATE TABLE IF NOT EXISTS document_generations (
|
|
43
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
44
|
+
user_id UUID REFERENCES users(id) ON DELETE CASCADE,
|
|
45
|
+
project_name VARCHAR(255) NOT NULL,
|
|
46
|
+
files_count INTEGER NOT NULL,
|
|
47
|
+
credits_used INTEGER NOT NULL,
|
|
48
|
+
created_at TIMESTAMPTZ DEFAULT NOW()
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
-- Indexes
|
|
52
|
+
CREATE INDEX IF NOT EXISTS idx_users_email ON users(email);
|
|
53
|
+
CREATE INDEX IF NOT EXISTS idx_transactions_user_id ON credit_transactions(user_id);
|
|
54
|
+
CREATE INDEX IF NOT EXISTS idx_transactions_created_at ON credit_transactions(created_at DESC);
|
|
55
|
+
CREATE INDEX IF NOT EXISTS idx_payment_tx_user_id ON payment_transactions(user_id);
|
|
56
|
+
CREATE INDEX IF NOT EXISTS idx_payment_tx_ref ON payment_transactions(tx_ref);
|
|
57
|
+
CREATE INDEX IF NOT EXISTS idx_payment_tx_status ON payment_transactions(status);
|
|
58
|
+
CREATE INDEX IF NOT EXISTS idx_doc_gen_user_id ON document_generations(user_id);
|
|
59
|
+
CREATE INDEX IF NOT EXISTS idx_doc_gen_created_at ON document_generations(created_at DESC);
|
|
60
|
+
|
|
61
|
+
-- Updated at trigger
|
|
62
|
+
CREATE OR REPLACE FUNCTION update_updated_at()
|
|
63
|
+
RETURNS TRIGGER AS $$
|
|
64
|
+
BEGIN
|
|
65
|
+
NEW.updated_at = NOW();
|
|
66
|
+
RETURN NEW;
|
|
67
|
+
END;
|
|
68
|
+
$$ LANGUAGE plpgsql;
|
|
69
|
+
|
|
70
|
+
DROP TRIGGER IF EXISTS users_updated_at ON users;
|
|
71
|
+
CREATE TRIGGER users_updated_at
|
|
72
|
+
BEFORE UPDATE ON users
|
|
73
|
+
FOR EACH ROW
|
|
74
|
+
EXECUTE FUNCTION update_updated_at();
|
|
75
|
+
|
|
76
|
+
-- Row Level Security (optional, for direct client access)
|
|
77
|
+
ALTER TABLE users ENABLE ROW LEVEL SECURITY;
|
|
78
|
+
ALTER TABLE credit_transactions ENABLE ROW LEVEL SECURITY;
|
|
79
|
+
ALTER TABLE payment_transactions ENABLE ROW LEVEL SECURITY;
|
|
80
|
+
|
|
81
|
+
-- Service role can access all rows
|
|
82
|
+
CREATE POLICY "Service role full access on users" ON users
|
|
83
|
+
FOR ALL USING (true);
|
|
84
|
+
|
|
85
|
+
CREATE POLICY "Service role full access on transactions" ON credit_transactions
|
|
86
|
+
FOR ALL USING (true);
|
|
87
|
+
|
|
88
|
+
CREATE POLICY "Service role full access on payments" ON payment_transactions
|
|
89
|
+
FOR ALL USING (true);
|
|
90
|
+
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"rootDir": "./src",
|
|
4
|
+
"outDir": "./dist",
|
|
5
|
+
"module": "nodenext",
|
|
6
|
+
"target": "esnext",
|
|
7
|
+
"lib": ["esnext"],
|
|
8
|
+
"types": ["node"],
|
|
9
|
+
"strict": true,
|
|
10
|
+
"esModuleInterop": true,
|
|
11
|
+
"skipLibCheck": true,
|
|
12
|
+
"declaration": true,
|
|
13
|
+
"sourceMap": true
|
|
14
|
+
},
|
|
15
|
+
"include": ["src/**/*"],
|
|
16
|
+
"exclude": ["node_modules", "dist"]
|
|
17
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import fetch from "node-fetch";
|
|
2
|
+
|
|
3
|
+
const BASE_URL = "http://localhost:3001/api";
|
|
4
|
+
|
|
5
|
+
async function test() {
|
|
6
|
+
console.log("🚀 Starting verification...");
|
|
7
|
+
|
|
8
|
+
// 1. Health check
|
|
9
|
+
const health = await fetch(`http://localhost:3001/health`).then((r) =>
|
|
10
|
+
r.json(),
|
|
11
|
+
);
|
|
12
|
+
console.log("✅ Health:", health);
|
|
13
|
+
|
|
14
|
+
// 2. Register/Login
|
|
15
|
+
const email = `test_${Date.now()}@example.com`;
|
|
16
|
+
const password = "password123";
|
|
17
|
+
|
|
18
|
+
console.log(`👤 Registering user ${email}...`);
|
|
19
|
+
const registerRes = await fetch(`${BASE_URL}/auth/register`, {
|
|
20
|
+
method: "POST",
|
|
21
|
+
headers: { "Content-Type": "application/json" },
|
|
22
|
+
body: JSON.stringify({
|
|
23
|
+
email,
|
|
24
|
+
password,
|
|
25
|
+
name: "Test User",
|
|
26
|
+
country: "Nigeria",
|
|
27
|
+
}),
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
const authBody: any = await registerRes.json();
|
|
31
|
+
if (!registerRes.ok) {
|
|
32
|
+
console.error("❌ Registration failed:", authBody);
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const token = authBody.token;
|
|
37
|
+
const headers = { Authorization: `Bearer ${token}` };
|
|
38
|
+
console.log("✅ Registered & Logged in");
|
|
39
|
+
|
|
40
|
+
// 3. Test Auth Me (Check for new fields)
|
|
41
|
+
const me: any = await fetch(`${BASE_URL}/auth/me`, { headers }).then((r) =>
|
|
42
|
+
r.json(),
|
|
43
|
+
);
|
|
44
|
+
if (me.user.name && me.user.createdAt) {
|
|
45
|
+
console.log("✅ /auth/me returns new fields");
|
|
46
|
+
} else {
|
|
47
|
+
console.error("❌ /auth/me missing fields:", me);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// 4. Test Dashboard Stats
|
|
51
|
+
const stats = await fetch(`${BASE_URL}/dashboard/stats`, { headers }).then(
|
|
52
|
+
(r) => r.json(),
|
|
53
|
+
);
|
|
54
|
+
console.log("📊 Stats:", stats);
|
|
55
|
+
|
|
56
|
+
// 5. Test Generate (to create data for dashboard)
|
|
57
|
+
console.log("📝 Generating fake document...");
|
|
58
|
+
const genRes = await fetch(`${BASE_URL}/generate`, {
|
|
59
|
+
method: "POST",
|
|
60
|
+
headers: { ...headers, "Content-Type": "application/json" },
|
|
61
|
+
body: JSON.stringify({
|
|
62
|
+
codeSkeleton: "console.log('hello')",
|
|
63
|
+
views: ["technical"],
|
|
64
|
+
fileCount: 2,
|
|
65
|
+
projectName: "Test Project",
|
|
66
|
+
}),
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
// Note: Generate calls main AI service. If it fails due to keys/etc, we handle logic,
|
|
70
|
+
// but importantly we want to see if it hits our database logic if successful.
|
|
71
|
+
// Actually, without AI keys configured this might fail.
|
|
72
|
+
// Let's manually insert data via direct DB call or just expect empty stats first.
|
|
73
|
+
|
|
74
|
+
if (genRes.ok) {
|
|
75
|
+
console.log("✅ Generation successful");
|
|
76
|
+
// Check stats again to see update
|
|
77
|
+
const stats2 = await fetch(`${BASE_URL}/dashboard/stats`, { headers }).then(
|
|
78
|
+
(r) => r.json(),
|
|
79
|
+
);
|
|
80
|
+
console.log("📊 Stats after generation:", stats2);
|
|
81
|
+
} else {
|
|
82
|
+
console.log(
|
|
83
|
+
"⚠️ Generation skipped/failed (expected if AI keys missing):",
|
|
84
|
+
await genRes.json(),
|
|
85
|
+
);
|
|
86
|
+
// We can't easily verify the DB write if generation fails, but we can verify the other endpoints return empty structures
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// 6. Test Dashboard Activity
|
|
90
|
+
const activity: any = await fetch(`${BASE_URL}/dashboard/activity`, {
|
|
91
|
+
headers,
|
|
92
|
+
}).then((r) => r.json());
|
|
93
|
+
console.log("activity:", activity);
|
|
94
|
+
if (Array.isArray(activity.activities)) {
|
|
95
|
+
console.log("✅ Activity endpoint returns array");
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// 7. Test Dashboard Usage
|
|
99
|
+
const usage: any = await fetch(`${BASE_URL}/dashboard/usage`, {
|
|
100
|
+
headers,
|
|
101
|
+
}).then((r) => r.json());
|
|
102
|
+
console.log("usage:", usage);
|
|
103
|
+
if (Array.isArray(usage.data)) {
|
|
104
|
+
console.log("✅ Usage endpoint returns data array");
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// 8. Test Dashboard Documents
|
|
108
|
+
const docs: any = await fetch(`${BASE_URL}/dashboard/documents`, {
|
|
109
|
+
headers,
|
|
110
|
+
}).then((r) => r.json());
|
|
111
|
+
console.log("documents:", docs);
|
|
112
|
+
if (Array.isArray(docs.documents)) {
|
|
113
|
+
console.log("✅ Documents endpoint returns array");
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// 9. Test Dashboard Billing
|
|
117
|
+
const billing: any = await fetch(`${BASE_URL}/dashboard/billing`, {
|
|
118
|
+
headers,
|
|
119
|
+
}).then((r) => r.json());
|
|
120
|
+
console.log("billing:", billing);
|
|
121
|
+
if (billing.creditBalance !== undefined) {
|
|
122
|
+
console.log("✅ Billing endpoint returns info");
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
test().catch(console.error);
|
package/src/ai/engine.ts
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { GoogleGenAI } from "@google/genai";
|
|
2
|
+
import { getPromptForView, type ViewType } from "./prompts.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* AI Engine Module
|
|
6
|
+
* Orchestrates calls to the Gemini LLM.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export interface DocumentationResult {
|
|
10
|
+
view: ViewType;
|
|
11
|
+
content: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface EngineConfig {
|
|
15
|
+
apiKey: string;
|
|
16
|
+
model?: string | undefined;
|
|
17
|
+
maxRetries?: number | undefined;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async function sleep(ms: number): Promise<void> {
|
|
21
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export class DocitEngine {
|
|
25
|
+
private ai: GoogleGenAI;
|
|
26
|
+
private model: string;
|
|
27
|
+
private maxRetries: number;
|
|
28
|
+
|
|
29
|
+
constructor(config: EngineConfig) {
|
|
30
|
+
this.ai = new GoogleGenAI({ apiKey: config.apiKey });
|
|
31
|
+
this.model = config.model ?? "gemini-2.0-flash";
|
|
32
|
+
this.maxRetries = config.maxRetries ?? 5;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async generateDocumentation(
|
|
36
|
+
codeSkeleton: string,
|
|
37
|
+
view: ViewType,
|
|
38
|
+
): Promise<DocumentationResult> {
|
|
39
|
+
const systemPrompt = getPromptForView(view);
|
|
40
|
+
|
|
41
|
+
let lastError: Error | undefined;
|
|
42
|
+
|
|
43
|
+
for (let attempt = 0; attempt < this.maxRetries; attempt++) {
|
|
44
|
+
try {
|
|
45
|
+
const response = await this.ai.models.generateContent({
|
|
46
|
+
model: this.model,
|
|
47
|
+
contents: [
|
|
48
|
+
{
|
|
49
|
+
role: "user",
|
|
50
|
+
parts: [
|
|
51
|
+
{
|
|
52
|
+
text: `${systemPrompt}\n\n---\n\nAnalyze the following code skeleton and generate documentation:\n\n${codeSkeleton}`,
|
|
53
|
+
},
|
|
54
|
+
],
|
|
55
|
+
},
|
|
56
|
+
],
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
const content = response.text ?? "";
|
|
60
|
+
|
|
61
|
+
return {
|
|
62
|
+
view,
|
|
63
|
+
content,
|
|
64
|
+
};
|
|
65
|
+
} catch (error) {
|
|
66
|
+
lastError = error as Error;
|
|
67
|
+
const status = (error as { status?: number }).status;
|
|
68
|
+
|
|
69
|
+
// Retry on 503 (overloaded) or 429 (rate limit)
|
|
70
|
+
if (status === 503 || status === 429) {
|
|
71
|
+
const waitTime = Math.pow(2, attempt) * 1000; // Exponential backoff
|
|
72
|
+
console.log(
|
|
73
|
+
` ⏳ Model overloaded, retrying in ${waitTime / 1000}s...`,
|
|
74
|
+
);
|
|
75
|
+
await sleep(waitTime);
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Don't retry other errors
|
|
80
|
+
throw error;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
throw lastError ?? new Error("Max retries exceeded");
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
async generateAllViews(codeSkeleton: string): Promise<DocumentationResult[]> {
|
|
88
|
+
const views: ViewType[] = ["strategic", "technical", "personal"];
|
|
89
|
+
const results: DocumentationResult[] = [];
|
|
90
|
+
|
|
91
|
+
for (const view of views) {
|
|
92
|
+
console.log(` 📄 Generating ${view} view...`);
|
|
93
|
+
const result = await this.generateDocumentation(codeSkeleton, view);
|
|
94
|
+
results.push(result);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return results;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function createEngine(apiKey: string): DocitEngine {
|
|
102
|
+
return new DocitEngine({ apiKey });
|
|
103
|
+
}
|