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,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "docit-server",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Docit Backend API Server",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"build": "tsc",
|
|
9
|
+
"start": "node dist/index.js",
|
|
10
|
+
"dev": "tsx watch src/index.ts"
|
|
11
|
+
},
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"@google/genai": "^1.38.0",
|
|
14
|
+
"@supabase/supabase-js": "^2.49.1",
|
|
15
|
+
"bcrypt": "^6.0.0",
|
|
16
|
+
"cors": "^2.8.5",
|
|
17
|
+
"dotenv": "^17.2.3",
|
|
18
|
+
"express": "^5.0.1",
|
|
19
|
+
"flutterwave-node-v3": "^1.3.0",
|
|
20
|
+
"jsonwebtoken": "^9.0.2"
|
|
21
|
+
},
|
|
22
|
+
"devDependencies": {
|
|
23
|
+
"@types/bcrypt": "^5.0.2",
|
|
24
|
+
"@types/cors": "^2.8.17",
|
|
25
|
+
"@types/express": "^5.0.1",
|
|
26
|
+
"@types/jsonwebtoken": "^9.0.9",
|
|
27
|
+
"@types/node": "^22.0.0",
|
|
28
|
+
"tsx": "^4.19.0",
|
|
29
|
+
"typescript": "^5.9.3"
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export declare const supabase: import("@supabase/supabase-js").SupabaseClient<any, "public", "public", any, any>;
|
|
2
|
+
export interface User {
|
|
3
|
+
id: string;
|
|
4
|
+
email: string;
|
|
5
|
+
password_hash: string;
|
|
6
|
+
credits: number;
|
|
7
|
+
created_at: string;
|
|
8
|
+
updated_at: string;
|
|
9
|
+
}
|
|
10
|
+
export interface CreditTransaction {
|
|
11
|
+
id: string;
|
|
12
|
+
user_id: string;
|
|
13
|
+
amount: number;
|
|
14
|
+
type: "purchase" | "usage" | "refund" | "bonus";
|
|
15
|
+
description: string;
|
|
16
|
+
created_at: string;
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=database.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"database.d.ts","sourceRoot":"","sources":["database.ts"],"names":[],"mappings":"AASA,eAAO,MAAM,QAAQ,mFAAyC,CAAC;AAG/D,MAAM,WAAW,IAAI;IACnB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,aAAa,EAAE,MAAM,CAAC;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,UAAU,GAAG,OAAO,GAAG,QAAQ,GAAG,OAAO,CAAC;IAChD,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;CACpB"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { createClient } from "@supabase/supabase-js";
|
|
2
|
+
const supabaseUrl = process.env.SUPABASE_URL ?? "";
|
|
3
|
+
const supabaseKey = process.env.SUPABASE_SERVICE_ROLE_KEY ?? "";
|
|
4
|
+
if (!supabaseUrl || !supabaseKey) {
|
|
5
|
+
throw new Error("Missing Supabase environment variables");
|
|
6
|
+
}
|
|
7
|
+
export const supabase = createClient(supabaseUrl, supabaseKey);
|
|
8
|
+
//# sourceMappingURL=database.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"database.js","sourceRoot":"","sources":["database.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAErD,MAAM,WAAW,GAAG,OAAO,CAAC,GAAG,CAAC,YAAY,IAAI,EAAE,CAAC;AACnD,MAAM,WAAW,GAAG,OAAO,CAAC,GAAG,CAAC,yBAAyB,IAAI,EAAE,CAAC;AAEhE,IAAI,CAAC,WAAW,IAAI,CAAC,WAAW,EAAE,CAAC;IACjC,MAAM,IAAI,KAAK,CAAC,wCAAwC,CAAC,CAAC;AAC5D,CAAC;AAED,MAAM,CAAC,MAAM,QAAQ,GAAG,YAAY,CAAC,WAAW,EAAE,WAAW,CAAC,CAAC"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { createClient } from "@supabase/supabase-js";
|
|
2
|
+
|
|
3
|
+
const supabaseUrl = process.env.SUPABASE_URL ?? "";
|
|
4
|
+
const supabaseKey = process.env.SUPABASE_SERVICE_ROLE_KEY ?? "";
|
|
5
|
+
|
|
6
|
+
if (!supabaseUrl || !supabaseKey) {
|
|
7
|
+
throw new Error("Missing Supabase environment variables");
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const supabase = createClient(supabaseUrl, supabaseKey);
|
|
11
|
+
|
|
12
|
+
// Database types
|
|
13
|
+
export interface User {
|
|
14
|
+
id: string;
|
|
15
|
+
email: string;
|
|
16
|
+
password_hash: string;
|
|
17
|
+
name?: string;
|
|
18
|
+
country?: string;
|
|
19
|
+
credits: number;
|
|
20
|
+
created_at: string;
|
|
21
|
+
updated_at: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface CreditTransaction {
|
|
25
|
+
id: string;
|
|
26
|
+
user_id: string;
|
|
27
|
+
amount: number;
|
|
28
|
+
type: "purchase" | "usage" | "refund" | "bonus";
|
|
29
|
+
description: string;
|
|
30
|
+
created_at: string;
|
|
31
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":"AAAA,OAAO,eAAe,CAAC"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import "dotenv/config";
|
|
2
|
+
import express from "express";
|
|
3
|
+
import cors from "cors";
|
|
4
|
+
import authRoutes from "./routes/auth.js";
|
|
5
|
+
import generateRoutes from "./routes/generate.js";
|
|
6
|
+
const app = express();
|
|
7
|
+
const PORT = process.env.PORT ?? 3001;
|
|
8
|
+
// Middleware
|
|
9
|
+
app.use(cors());
|
|
10
|
+
app.use(express.json({ limit: "10mb" }));
|
|
11
|
+
// Health check
|
|
12
|
+
app.get("/health", (_req, res) => {
|
|
13
|
+
res.json({ status: "ok", timestamp: new Date().toISOString() });
|
|
14
|
+
});
|
|
15
|
+
// Routes
|
|
16
|
+
app.use("/api/auth", authRoutes);
|
|
17
|
+
app.use("/api/generate", generateRoutes);
|
|
18
|
+
// Error handler
|
|
19
|
+
app.use((err, _req, res, _next) => {
|
|
20
|
+
console.error("Unhandled error:", err);
|
|
21
|
+
res.status(500).json({ error: "Internal server error" });
|
|
22
|
+
});
|
|
23
|
+
// Start server
|
|
24
|
+
app.listen(PORT, () => {
|
|
25
|
+
console.log(`🚀 Docit API server running on http://localhost:${PORT}`);
|
|
26
|
+
console.log(`📚 Endpoints:`);
|
|
27
|
+
console.log(` POST /api/auth/register - Create account`);
|
|
28
|
+
console.log(` POST /api/auth/login - Login`);
|
|
29
|
+
console.log(` GET /api/auth/me - Get current user`);
|
|
30
|
+
console.log(` GET /api/auth/credits - Get credit balance`);
|
|
31
|
+
console.log(` POST /api/generate - Generate documentation`);
|
|
32
|
+
});
|
|
33
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":"AAAA,OAAO,eAAe,CAAC;AACvB,OAAO,OAAO,MAAM,SAAS,CAAC;AAC9B,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,UAAU,MAAM,kBAAkB,CAAC;AAC1C,OAAO,cAAc,MAAM,sBAAsB,CAAC;AAElD,MAAM,GAAG,GAAG,OAAO,EAAE,CAAC;AACtB,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,IAAI,IAAI,CAAC;AAEtC,aAAa;AACb,GAAG,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC;AAChB,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC;AAEzC,eAAe;AACf,GAAG,CAAC,GAAG,CAAC,SAAS,EAAE,CAAC,IAAI,EAAE,GAAG,EAAE,EAAE;IAC/B,GAAG,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC;AAClE,CAAC,CAAC,CAAC;AAEH,SAAS;AACT,GAAG,CAAC,GAAG,CAAC,WAAW,EAAE,UAAU,CAAC,CAAC;AACjC,GAAG,CAAC,GAAG,CAAC,eAAe,EAAE,cAAc,CAAC,CAAC;AAEzC,gBAAgB;AAChB,GAAG,CAAC,GAAG,CACL,CACE,GAAU,EACV,IAAqB,EACrB,GAAqB,EACrB,KAA2B,EAC3B,EAAE;IACF,OAAO,CAAC,KAAK,CAAC,kBAAkB,EAAE,GAAG,CAAC,CAAC;IACvC,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,uBAAuB,EAAE,CAAC,CAAC;AAC3D,CAAC,CACF,CAAC;AAEF,eAAe;AACf,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,GAAG,EAAE;IACpB,OAAO,CAAC,GAAG,CAAC,mDAAmD,IAAI,EAAE,CAAC,CAAC;IACvE,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC;IAC7B,OAAO,CAAC,GAAG,CAAC,6CAA6C,CAAC,CAAC;IAC3D,OAAO,CAAC,GAAG,CAAC,oCAAoC,CAAC,CAAC;IAClD,OAAO,CAAC,GAAG,CAAC,+CAA+C,CAAC,CAAC;IAC7D,OAAO,CAAC,GAAG,CAAC,iDAAiD,CAAC,CAAC;IAC/D,OAAO,CAAC,GAAG,CAAC,qDAAqD,CAAC,CAAC;AACrE,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import "dotenv/config";
|
|
2
|
+
import express from "express";
|
|
3
|
+
import cors from "cors";
|
|
4
|
+
import authRoutes from "./routes/auth.js";
|
|
5
|
+
import generateRoutes from "./routes/generate.js";
|
|
6
|
+
import paymentRoutes from "./routes/payment.js";
|
|
7
|
+
import dashboardRoutes from "./routes/dashboard.js";
|
|
8
|
+
|
|
9
|
+
const app = express();
|
|
10
|
+
const PORT = process.env.PORT ?? 3001;
|
|
11
|
+
|
|
12
|
+
// Middleware
|
|
13
|
+
app.use(cors());
|
|
14
|
+
app.use(express.json({ limit: "10mb" }));
|
|
15
|
+
|
|
16
|
+
// Health check
|
|
17
|
+
app.get("/health", (_req, res) => {
|
|
18
|
+
res.json({ status: "ok", timestamp: new Date().toISOString() });
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
// Routes
|
|
22
|
+
app.use("/api/auth", authRoutes);
|
|
23
|
+
app.use("/api/generate", generateRoutes);
|
|
24
|
+
app.use("/api/payment", paymentRoutes);
|
|
25
|
+
app.use("/api/dashboard", dashboardRoutes);
|
|
26
|
+
|
|
27
|
+
// Error handler
|
|
28
|
+
app.use(
|
|
29
|
+
(
|
|
30
|
+
err: Error,
|
|
31
|
+
_req: express.Request,
|
|
32
|
+
res: express.Response,
|
|
33
|
+
_next: express.NextFunction,
|
|
34
|
+
) => {
|
|
35
|
+
console.error("Unhandled error:", err);
|
|
36
|
+
res.status(500).json({ error: "Internal server error" });
|
|
37
|
+
},
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
// Start server
|
|
41
|
+
if (process.env.VERCEL !== "1") {
|
|
42
|
+
app.listen(PORT, () => {
|
|
43
|
+
console.log(`🚀 Docit API server running on http://localhost:${PORT}`);
|
|
44
|
+
console.log(`📚 Endpoints:`);
|
|
45
|
+
console.log(` POST /api/auth/register - Create account`);
|
|
46
|
+
console.log(` POST /api/auth/login - Login`);
|
|
47
|
+
console.log(` GET /api/auth/me - Get current user`);
|
|
48
|
+
console.log(` POST /api/generate - Generate documentation`);
|
|
49
|
+
console.log(` GET /api/payment/plans - Get subscription plans`);
|
|
50
|
+
console.log(` POST /api/payment/initiate - Start payment`);
|
|
51
|
+
console.log(` GET /api/dashboard/* - Dashboard stats & data`);
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export default app;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Request, Response, NextFunction } from "express";
|
|
2
|
+
import { type User } from "../config/database.js";
|
|
3
|
+
export interface AuthRequest extends Request {
|
|
4
|
+
user?: User;
|
|
5
|
+
userId?: string;
|
|
6
|
+
}
|
|
7
|
+
export interface JWTPayload {
|
|
8
|
+
userId: string;
|
|
9
|
+
email: string;
|
|
10
|
+
}
|
|
11
|
+
export declare function authMiddleware(req: AuthRequest, res: Response, next: NextFunction): Promise<void>;
|
|
12
|
+
//# sourceMappingURL=auth.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["auth.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAE/D,OAAO,EAAY,KAAK,IAAI,EAAE,MAAM,uBAAuB,CAAC;AAE5D,MAAM,WAAW,WAAY,SAAQ,OAAO;IAC1C,IAAI,CAAC,EAAE,IAAI,CAAC;IACZ,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;CACf;AAED,wBAAsB,cAAc,CAClC,GAAG,EAAE,WAAW,EAChB,GAAG,EAAE,QAAQ,EACb,IAAI,EAAE,YAAY,GACjB,OAAO,CAAC,IAAI,CAAC,CAqCf"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import jwt from "jsonwebtoken";
|
|
2
|
+
import { supabase } from "../config/database.js";
|
|
3
|
+
export async function authMiddleware(req, res, next) {
|
|
4
|
+
const authHeader = req.headers.authorization;
|
|
5
|
+
if (!authHeader?.startsWith("Bearer ")) {
|
|
6
|
+
res.status(401).json({ error: "Missing or invalid authorization header" });
|
|
7
|
+
return;
|
|
8
|
+
}
|
|
9
|
+
const token = authHeader.slice(7);
|
|
10
|
+
try {
|
|
11
|
+
const jwtSecret = process.env.JWT_SECRET;
|
|
12
|
+
if (!jwtSecret) {
|
|
13
|
+
res.status(500).json({ error: "Server configuration error" });
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
const decoded = jwt.verify(token, jwtSecret);
|
|
17
|
+
// Fetch user from database
|
|
18
|
+
const { data: user, error } = await supabase
|
|
19
|
+
.from("users")
|
|
20
|
+
.select("*")
|
|
21
|
+
.eq("id", decoded.userId)
|
|
22
|
+
.single();
|
|
23
|
+
if (error || !user) {
|
|
24
|
+
res.status(401).json({ error: "User not found" });
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
req.user = user;
|
|
28
|
+
req.userId = decoded.userId;
|
|
29
|
+
next();
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
res.status(401).json({ error: "Invalid or expired token" });
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=auth.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["auth.ts"],"names":[],"mappings":"AACA,OAAO,GAAG,MAAM,cAAc,CAAC;AAC/B,OAAO,EAAE,QAAQ,EAAa,MAAM,uBAAuB,CAAC;AAY5D,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,GAAgB,EAChB,GAAa,EACb,IAAkB;IAElB,MAAM,UAAU,GAAG,GAAG,CAAC,OAAO,CAAC,aAAa,CAAC;IAE7C,IAAI,CAAC,UAAU,EAAE,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;QACvC,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,yCAAyC,EAAE,CAAC,CAAC;QAC3E,OAAO;IACT,CAAC;IAED,MAAM,KAAK,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IAElC,IAAI,CAAC;QACH,MAAM,SAAS,GAAG,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC;QACzC,IAAI,CAAC,SAAS,EAAE,CAAC;YACf,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,4BAA4B,EAAE,CAAC,CAAC;YAC9D,OAAO;QACT,CAAC;QAED,MAAM,OAAO,GAAG,GAAG,CAAC,MAAM,CAAC,KAAK,EAAE,SAAS,CAAe,CAAC;QAE3D,2BAA2B;QAC3B,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,MAAM,QAAQ;aACzC,IAAI,CAAC,OAAO,CAAC;aACb,MAAM,CAAC,GAAG,CAAC;aACX,EAAE,CAAC,IAAI,EAAE,OAAO,CAAC,MAAM,CAAC;aACxB,MAAM,EAAE,CAAC;QAEZ,IAAI,KAAK,IAAI,CAAC,IAAI,EAAE,CAAC;YACnB,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,gBAAgB,EAAE,CAAC,CAAC;YAClD,OAAO;QACT,CAAC;QAED,GAAG,CAAC,IAAI,GAAG,IAAY,CAAC;QACxB,GAAG,CAAC,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;QAC5B,IAAI,EAAE,CAAC;IACT,CAAC;IAAC,MAAM,CAAC;QACP,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,0BAA0B,EAAE,CAAC,CAAC;IAC9D,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { Request, Response, NextFunction } from "express";
|
|
2
|
+
import jwt from "jsonwebtoken";
|
|
3
|
+
import { supabase, type User } from "../config/database.js";
|
|
4
|
+
|
|
5
|
+
export interface AuthRequest extends Request {
|
|
6
|
+
user?: User;
|
|
7
|
+
userId?: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface JWTPayload {
|
|
11
|
+
userId: string;
|
|
12
|
+
email: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export async function authMiddleware(
|
|
16
|
+
req: AuthRequest,
|
|
17
|
+
res: Response,
|
|
18
|
+
next: NextFunction,
|
|
19
|
+
): Promise<void> {
|
|
20
|
+
const authHeader = req.headers.authorization;
|
|
21
|
+
|
|
22
|
+
if (!authHeader?.startsWith("Bearer ")) {
|
|
23
|
+
res.status(401).json({ error: "Missing or invalid authorization header" });
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const token = authHeader.slice(7);
|
|
28
|
+
|
|
29
|
+
try {
|
|
30
|
+
const jwtSecret = process.env.JWT_SECRET;
|
|
31
|
+
if (!jwtSecret) {
|
|
32
|
+
res.status(500).json({ error: "Server configuration error" });
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const decoded = jwt.verify(token, jwtSecret) as JWTPayload;
|
|
37
|
+
|
|
38
|
+
// Fetch user from database
|
|
39
|
+
const { data: user, error } = await supabase
|
|
40
|
+
.from("users")
|
|
41
|
+
.select("*")
|
|
42
|
+
.eq("id", decoded.userId)
|
|
43
|
+
.single();
|
|
44
|
+
|
|
45
|
+
if (error || !user) {
|
|
46
|
+
res.status(401).json({ error: "User not found" });
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
req.user = user as User;
|
|
51
|
+
req.userId = decoded.userId;
|
|
52
|
+
next();
|
|
53
|
+
} catch {
|
|
54
|
+
res.status(401).json({ error: "Invalid or expired token" });
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["auth.ts"],"names":[],"mappings":"AAMA,QAAA,MAAM,MAAM,4CAAW,CAAC;AA0KxB,eAAe,MAAM,CAAC"}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { Router } from "express";
|
|
2
|
+
import bcrypt from "bcrypt";
|
|
3
|
+
import jwt from "jsonwebtoken";
|
|
4
|
+
import { supabase } from "../config/database.js";
|
|
5
|
+
import { authMiddleware } from "../middleware/auth.js";
|
|
6
|
+
const router = Router();
|
|
7
|
+
// Register
|
|
8
|
+
router.post("/register", async (req, res) => {
|
|
9
|
+
try {
|
|
10
|
+
const { email, password, name } = req.body;
|
|
11
|
+
if (!email || !password) {
|
|
12
|
+
res.status(400).json({ error: "Email and password are required" });
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
// Check if user exists
|
|
16
|
+
const { data: existingUser } = await supabase
|
|
17
|
+
.from("users")
|
|
18
|
+
.select("id")
|
|
19
|
+
.eq("email", email)
|
|
20
|
+
.single();
|
|
21
|
+
if (existingUser) {
|
|
22
|
+
res.status(409).json({ error: "Email already registered" });
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
// Hash password
|
|
26
|
+
const passwordHash = await bcrypt.hash(password, 12);
|
|
27
|
+
// Get free credits from env
|
|
28
|
+
const freeCredits = parseInt(process.env.FREE_CREDITS_ON_SIGNUP ?? "10", 10);
|
|
29
|
+
// Create user
|
|
30
|
+
const { data: user, error } = await supabase
|
|
31
|
+
.from("users")
|
|
32
|
+
.insert({
|
|
33
|
+
email,
|
|
34
|
+
password_hash: passwordHash,
|
|
35
|
+
name: name ?? email.split("@")[0],
|
|
36
|
+
credits: freeCredits,
|
|
37
|
+
})
|
|
38
|
+
.select()
|
|
39
|
+
.single();
|
|
40
|
+
if (error) {
|
|
41
|
+
console.error("Registration error:", error);
|
|
42
|
+
res.status(500).json({ error: "Failed to create user" });
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
// Log credit transaction
|
|
46
|
+
await supabase.from("credit_transactions").insert({
|
|
47
|
+
user_id: user.id,
|
|
48
|
+
amount: freeCredits,
|
|
49
|
+
type: "bonus",
|
|
50
|
+
description: "Welcome bonus credits",
|
|
51
|
+
});
|
|
52
|
+
// Generate token
|
|
53
|
+
const jwtSecret = process.env.JWT_SECRET ?? "default_secret";
|
|
54
|
+
const token = jwt.sign({ userId: user.id, email: user.email }, jwtSecret, {
|
|
55
|
+
expiresIn: "30d",
|
|
56
|
+
});
|
|
57
|
+
res.status(201).json({
|
|
58
|
+
message: "Registration successful",
|
|
59
|
+
token,
|
|
60
|
+
user: {
|
|
61
|
+
id: user.id,
|
|
62
|
+
email: user.email,
|
|
63
|
+
name: user.name,
|
|
64
|
+
credits: user.credits,
|
|
65
|
+
},
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
catch (error) {
|
|
69
|
+
console.error("Registration error:", error);
|
|
70
|
+
res.status(500).json({ error: "Internal server error" });
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
// Login
|
|
74
|
+
router.post("/login", async (req, res) => {
|
|
75
|
+
try {
|
|
76
|
+
const { email, password } = req.body;
|
|
77
|
+
if (!email || !password) {
|
|
78
|
+
res.status(400).json({ error: "Email and password are required" });
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
// Find user
|
|
82
|
+
const { data: user, error } = await supabase
|
|
83
|
+
.from("users")
|
|
84
|
+
.select("*")
|
|
85
|
+
.eq("email", email)
|
|
86
|
+
.single();
|
|
87
|
+
if (error || !user) {
|
|
88
|
+
res.status(401).json({ error: "Invalid email or password" });
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
// Verify password
|
|
92
|
+
const validPassword = await bcrypt.compare(password, user.password_hash);
|
|
93
|
+
if (!validPassword) {
|
|
94
|
+
res.status(401).json({ error: "Invalid email or password" });
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
// Generate token
|
|
98
|
+
const jwtSecret = process.env.JWT_SECRET ?? "default_secret";
|
|
99
|
+
const token = jwt.sign({ userId: user.id, email: user.email }, jwtSecret, {
|
|
100
|
+
expiresIn: "30d",
|
|
101
|
+
});
|
|
102
|
+
res.json({
|
|
103
|
+
message: "Login successful",
|
|
104
|
+
token,
|
|
105
|
+
user: {
|
|
106
|
+
id: user.id,
|
|
107
|
+
email: user.email,
|
|
108
|
+
name: user.name,
|
|
109
|
+
credits: user.credits,
|
|
110
|
+
},
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
catch (error) {
|
|
114
|
+
console.error("Login error:", error);
|
|
115
|
+
res.status(500).json({ error: "Internal server error" });
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
// Get current user
|
|
119
|
+
router.get("/me", authMiddleware, async (req, res) => {
|
|
120
|
+
const user = req.user;
|
|
121
|
+
res.json({
|
|
122
|
+
user: {
|
|
123
|
+
id: user.id,
|
|
124
|
+
email: user.email,
|
|
125
|
+
credits: user.credits,
|
|
126
|
+
},
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
// Get credit balance
|
|
130
|
+
router.get("/credits", authMiddleware, async (req, res) => {
|
|
131
|
+
const user = req.user;
|
|
132
|
+
// Get transaction history
|
|
133
|
+
const { data: transactions } = await supabase
|
|
134
|
+
.from("credit_transactions")
|
|
135
|
+
.select("*")
|
|
136
|
+
.eq("user_id", user.id)
|
|
137
|
+
.order("created_at", { ascending: false })
|
|
138
|
+
.limit(10);
|
|
139
|
+
res.json({
|
|
140
|
+
credits: user.credits,
|
|
141
|
+
transactions: transactions ?? [],
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
export default router;
|
|
145
|
+
//# sourceMappingURL=auth.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["auth.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import { Router } from "express";
|
|
2
|
+
import bcrypt from "bcrypt";
|
|
3
|
+
import jwt from "jsonwebtoken";
|
|
4
|
+
import { supabase } from "../config/database.js";
|
|
5
|
+
import { authMiddleware, type AuthRequest } from "../middleware/auth.js";
|
|
6
|
+
|
|
7
|
+
const router = Router();
|
|
8
|
+
|
|
9
|
+
// Register
|
|
10
|
+
router.post("/register", async (req, res) => {
|
|
11
|
+
try {
|
|
12
|
+
const { email, password, name, country } = req.body as {
|
|
13
|
+
email?: string;
|
|
14
|
+
password?: string;
|
|
15
|
+
name?: string;
|
|
16
|
+
country?: string;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
if (!email || !password) {
|
|
20
|
+
res.status(400).json({ error: "Email and password are required" });
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Check if user exists
|
|
25
|
+
const { data: existingUser } = await supabase
|
|
26
|
+
.from("users")
|
|
27
|
+
.select("id")
|
|
28
|
+
.eq("email", email)
|
|
29
|
+
.single();
|
|
30
|
+
|
|
31
|
+
if (existingUser) {
|
|
32
|
+
res.status(409).json({ error: "Email already registered" });
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Hash password
|
|
37
|
+
const passwordHash = await bcrypt.hash(password, 12);
|
|
38
|
+
|
|
39
|
+
// Get free credits from env
|
|
40
|
+
const freeCredits = parseInt(
|
|
41
|
+
process.env.FREE_CREDITS_ON_SIGNUP ?? "10",
|
|
42
|
+
10,
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
// Create user
|
|
46
|
+
const { data: user, error } = await supabase
|
|
47
|
+
.from("users")
|
|
48
|
+
.insert({
|
|
49
|
+
email,
|
|
50
|
+
password_hash: passwordHash,
|
|
51
|
+
name: name ?? email.split("@")[0],
|
|
52
|
+
country,
|
|
53
|
+
credits: freeCredits,
|
|
54
|
+
})
|
|
55
|
+
.select()
|
|
56
|
+
.single();
|
|
57
|
+
|
|
58
|
+
if (error) {
|
|
59
|
+
console.error("Registration error:", error);
|
|
60
|
+
res.status(500).json({ error: "Failed to create user" });
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Log credit transaction
|
|
65
|
+
await supabase.from("credit_transactions").insert({
|
|
66
|
+
user_id: user.id,
|
|
67
|
+
amount: freeCredits,
|
|
68
|
+
type: "bonus",
|
|
69
|
+
description: "Welcome bonus credits",
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
// Generate token
|
|
73
|
+
const jwtSecret = process.env.JWT_SECRET ?? "default_secret";
|
|
74
|
+
const token = jwt.sign({ userId: user.id, email: user.email }, jwtSecret, {
|
|
75
|
+
expiresIn: "30d",
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
res.status(201).json({
|
|
79
|
+
message: "Registration successful",
|
|
80
|
+
token,
|
|
81
|
+
user: {
|
|
82
|
+
id: user.id,
|
|
83
|
+
email: user.email,
|
|
84
|
+
name: user.name,
|
|
85
|
+
country: user.country,
|
|
86
|
+
credits: user.credits,
|
|
87
|
+
},
|
|
88
|
+
});
|
|
89
|
+
} catch (error) {
|
|
90
|
+
console.error("Registration error:", error);
|
|
91
|
+
res.status(500).json({ error: "Internal server error" });
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
// Login
|
|
96
|
+
router.post("/login", async (req, res) => {
|
|
97
|
+
try {
|
|
98
|
+
const { email, password } = req.body as {
|
|
99
|
+
email?: string;
|
|
100
|
+
password?: string;
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
if (!email || !password) {
|
|
104
|
+
res.status(400).json({ error: "Email and password are required" });
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Find user
|
|
109
|
+
const { data: user, error } = await supabase
|
|
110
|
+
.from("users")
|
|
111
|
+
.select("*")
|
|
112
|
+
.eq("email", email)
|
|
113
|
+
.single();
|
|
114
|
+
|
|
115
|
+
if (error || !user) {
|
|
116
|
+
res.status(401).json({ error: "Invalid email or password" });
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Verify password
|
|
121
|
+
const validPassword = await bcrypt.compare(password, user.password_hash);
|
|
122
|
+
if (!validPassword) {
|
|
123
|
+
res.status(401).json({ error: "Invalid email or password" });
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Generate token
|
|
128
|
+
const jwtSecret = process.env.JWT_SECRET ?? "default_secret";
|
|
129
|
+
const token = jwt.sign({ userId: user.id, email: user.email }, jwtSecret, {
|
|
130
|
+
expiresIn: "30d",
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
res.json({
|
|
134
|
+
message: "Login successful",
|
|
135
|
+
token,
|
|
136
|
+
user: {
|
|
137
|
+
id: user.id,
|
|
138
|
+
email: user.email,
|
|
139
|
+
name: user.name,
|
|
140
|
+
country: user.country,
|
|
141
|
+
credits: user.credits,
|
|
142
|
+
},
|
|
143
|
+
});
|
|
144
|
+
} catch (error) {
|
|
145
|
+
console.error("Login error:", error);
|
|
146
|
+
res.status(500).json({ error: "Internal server error" });
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
// Get current user
|
|
151
|
+
router.get("/me", authMiddleware, async (req: AuthRequest, res) => {
|
|
152
|
+
const user = req.user!;
|
|
153
|
+
|
|
154
|
+
res.json({
|
|
155
|
+
user: {
|
|
156
|
+
id: user.id,
|
|
157
|
+
email: user.email,
|
|
158
|
+
name: user.name,
|
|
159
|
+
country: user.country,
|
|
160
|
+
credits: user.credits,
|
|
161
|
+
createdAt: user.created_at,
|
|
162
|
+
updatedAt: user.updated_at,
|
|
163
|
+
},
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
// Get credit balance
|
|
168
|
+
router.get("/credits", authMiddleware, async (req: AuthRequest, res) => {
|
|
169
|
+
const user = req.user!;
|
|
170
|
+
|
|
171
|
+
// Get transaction history
|
|
172
|
+
const { data: transactions } = await supabase
|
|
173
|
+
.from("credit_transactions")
|
|
174
|
+
.select("*")
|
|
175
|
+
.eq("user_id", user.id)
|
|
176
|
+
.order("created_at", { ascending: false })
|
|
177
|
+
.limit(10);
|
|
178
|
+
|
|
179
|
+
res.json({
|
|
180
|
+
credits: user.credits,
|
|
181
|
+
transactions: transactions ?? [],
|
|
182
|
+
});
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
export default router;
|