Auspicious14 commited on
Commit
77610ec
·
verified ·
1 Parent(s): 3b61d2e

Upload 94 files

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. controllers/ai/index.ts +219 -0
  2. controllers/analytics/index.ts +72 -0
  3. controllers/auth/index.ts +448 -0
  4. controllers/code-review/index.ts +71 -0
  5. controllers/community/index.ts +152 -0
  6. controllers/events/index.ts +221 -0
  7. controllers/gamification/index.ts +69 -0
  8. controllers/github/index.ts +109 -0
  9. controllers/notifications/index.ts +75 -0
  10. controllers/paths/index.ts +82 -0
  11. controllers/projects/index.ts +706 -0
  12. controllers/social/index.ts +187 -0
  13. controllers/teams/index.ts +80 -0
  14. dist/controllers/ai/index.js +146 -0
  15. dist/controllers/analytics/index.js +74 -0
  16. dist/controllers/auth/index.js +397 -0
  17. dist/controllers/code-review/index.js +70 -0
  18. dist/controllers/community/index.js +156 -0
  19. dist/controllers/events/index.js +202 -0
  20. dist/controllers/gamification/index.js +71 -0
  21. dist/controllers/notifications/index.js +78 -0
  22. dist/controllers/paths/index.js +87 -0
  23. dist/controllers/projects/index.js +416 -0
  24. dist/controllers/social/index.js +181 -0
  25. dist/controllers/teams/index.js +71 -0
  26. dist/index.js +70 -0
  27. dist/middlewares/auth.js +32 -0
  28. dist/middlewares/email.js +36 -0
  29. dist/middlewares/errorHandler.js +51 -0
  30. dist/middlewares/file.js +32 -0
  31. dist/middlewares/generateOTP.js +13 -0
  32. dist/models/auth/index.js +26 -0
  33. dist/models/project/index.js +21 -0
  34. dist/prisma.config.js +16 -0
  35. dist/prisma/client.js +5 -0
  36. dist/prisma/migrations/generate-slugs.js +34 -0
  37. dist/prisma/seed.js +303 -0
  38. dist/routes/ai.js +11 -0
  39. dist/routes/analytics.js +9 -0
  40. dist/routes/auth.js +30 -0
  41. dist/routes/code-review.js +9 -0
  42. dist/routes/community.js +12 -0
  43. dist/routes/events.js +17 -0
  44. dist/routes/gamification.js +9 -0
  45. dist/routes/notifications.js +14 -0
  46. dist/routes/paths.js +10 -0
  47. dist/routes/project.js +22 -0
  48. dist/routes/social.js +16 -0
  49. dist/routes/teams.js +10 -0
  50. dist/routes/user.js +11 -0
controllers/ai/index.ts ADDED
@@ -0,0 +1,219 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Request, Response } from "express";
2
+ import prisma from "../../prisma/client";
3
+ import { getGeminiResponse } from "../../utils/gemini";
4
+
5
+ export const chatWithAI = async (req: Request, res: Response) => {
6
+ const userId = (req as any).user?.id;
7
+ const { message, projectId } = req.body;
8
+
9
+ try {
10
+ let projectContext = "";
11
+ let difficultyMode: string | null = null;
12
+
13
+ // 1. Fetch project context if projectId is provided
14
+ if (projectId) {
15
+ const project = await prisma.project.findUnique({
16
+ where: { id: projectId },
17
+ include: {
18
+ milestones: true,
19
+ userProjects: { where: { userId } },
20
+ },
21
+ });
22
+
23
+ if (project) {
24
+ difficultyMode =
25
+ project.userProjects?.[0]?.difficultyModeChosen || "STANDARD";
26
+ projectContext = `
27
+ You are helping a student with the following project:
28
+ Project Title: ${project.title}
29
+ Description: ${project.description}
30
+ Difficulty Level: ${project.difficultyLevel}
31
+ Technologies: ${project.technologies.join(", ")}
32
+ Estimated Time: ${project.estimatedTime}
33
+ Chosen Difficulty Mode: ${difficultyMode}
34
+
35
+ Milestones:
36
+ ${project.milestones
37
+ .sort((a, b) => a.milestoneNumber - b.milestoneNumber)
38
+ .map((m) => `${m.milestoneNumber}. ${m.title}: ${m.description}`)
39
+ .join("\n")}
40
+
41
+ Learning Objectives:
42
+ ${project.learningObjectives.map((obj, i) => `${i + 1}. ${obj}`).join("\n")}
43
+ `;
44
+ }
45
+ }
46
+
47
+ // 2. Define mode-specific instructions
48
+ let modeInstruction = "";
49
+ if (difficultyMode === "GUIDED") {
50
+ modeInstruction =
51
+ "The user is in GUIDED mode. Be very helpful, provide detailed explanations, and don't hesitate to give small code snippets if they are stuck.";
52
+ } else if (difficultyMode === "HARDCORE") {
53
+ modeInstruction =
54
+ "The user is in HARDCORE mode. Be very brief, provide only high-level conceptual hints, and never provide code solutions.";
55
+ } else {
56
+ modeInstruction =
57
+ "The user is in STANDARD mode. Provide balanced guidance, focus on concepts, and only provide code as a last resort.";
58
+ }
59
+
60
+ // 3. Build System Prompt
61
+ const systemPrompt = `You are an AI Guide for a project-based learning platform.
62
+ Your goal is to help students learn by providing guidance, not just giving away answers.
63
+ ${projectContext}
64
+ ${modeInstruction}
65
+ Keep your responses concise and educational. Always respond in markdown format.`;
66
+
67
+ // 4. Fetch chat history
68
+ const history = await prisma.chatMessage.findMany({
69
+ where: { userId, projectId: projectId || null },
70
+ orderBy: { createdAt: "asc" },
71
+ take: 20,
72
+ });
73
+
74
+ const geminiHistory = history.map((msg) => ({
75
+ role: msg.role === "user" ? "user" : "model",
76
+ parts: [{ text: msg.content }],
77
+ }));
78
+
79
+ // 5. Get AI Response
80
+ const aiResponse = await getGeminiResponse(
81
+ `${systemPrompt}\n\nUser Question: ${message}`,
82
+ geminiHistory
83
+ );
84
+
85
+ // 6. Persist Conversation
86
+ await prisma.chatMessage.createMany({
87
+ data: [
88
+ {
89
+ userId,
90
+ projectId: projectId || null,
91
+ role: "user",
92
+ content: message,
93
+ },
94
+ {
95
+ userId,
96
+ projectId: projectId || null,
97
+ role: "assistant",
98
+ content: aiResponse,
99
+ },
100
+ ],
101
+ });
102
+
103
+ // 7. Stream response (simulated streaming)
104
+ res.setHeader("Content-Type", "text/plain");
105
+ res.setHeader("Transfer-Encoding", "chunked");
106
+ res.setHeader("Cache-Control", "no-cache");
107
+ res.setHeader("Connection", "keep-alive");
108
+
109
+ const words = aiResponse.split(" ");
110
+ for (let i = 0; i < words.length; i++) {
111
+ res.write(words[i] + " ");
112
+ await new Promise((resolve) => setTimeout(resolve, 20));
113
+ }
114
+
115
+ res.end();
116
+ } catch (error: any) {
117
+ console.error("Chat error:", error);
118
+ if (!res.headersSent) {
119
+ res.status(500).json({ success: false, message: error.message });
120
+ } else {
121
+ res.end();
122
+ }
123
+ }
124
+ };
125
+
126
+ export const getAllChatMessages = async (req: Request, res: Response) => {
127
+ const userId = (req as any).user?.id;
128
+
129
+ try {
130
+ const messages = await prisma.chatMessage.findMany({
131
+ where: { userId },
132
+ orderBy: { createdAt: "asc" },
133
+ });
134
+
135
+ res.json({ success: true, data: messages });
136
+ } catch (error: any) {
137
+ res.status(500).json({ success: false, message: error.message });
138
+ }
139
+ };
140
+
141
+ export const requestHint = async (req: Request, res: Response) => {
142
+ const userId = (req as any).user?.id;
143
+ const { projectId, milestoneNumber, difficultyMode } = req.body;
144
+
145
+ try {
146
+ const milestone = await prisma.projectMilestone.findUnique({
147
+ where: { projectId_milestoneNumber: { projectId, milestoneNumber } },
148
+ include: { project: true },
149
+ });
150
+
151
+ if (!milestone)
152
+ return res
153
+ .status(404)
154
+ .json({ success: false, message: "Milestone not found" });
155
+
156
+ let modeInstruction = "";
157
+ if (difficultyMode === "GUIDED") {
158
+ modeInstruction =
159
+ "The user is in GUIDED mode. Be very helpful and detailed.";
160
+ } else {
161
+ modeInstruction =
162
+ "The user is in STANDARD mode. Provide a balanced, conceptual hint.";
163
+ }
164
+
165
+ const modeHintsForPrompt = Array.isArray(milestone.hints)
166
+ ? milestone.hints
167
+ : (milestone.hints as any)?.[difficultyMode] || [];
168
+
169
+ const prompt = `The user is stuck on Milestone ${milestoneNumber} ("${
170
+ milestone.title
171
+ }") of the project "${milestone.project.title}".
172
+ Milestone description: ${milestone.description}.
173
+ Difficulty Mode: ${difficultyMode}.
174
+ ${modeInstruction}
175
+ Existing hints: ${modeHintsForPrompt.join(", ")}.
176
+ Please provide a new, progressive hint that helps them move forward without revealing the full solution.
177
+ Format the response as a single, clear hint string.`;
178
+
179
+ const hint = await getGeminiResponse(prompt);
180
+
181
+ // Persist the hint by mode in the JSON object
182
+ const existingHints = (milestone.hints as any) || {};
183
+ const modeHints = existingHints[difficultyMode] || [];
184
+ modeHints.push(hint);
185
+
186
+ await prisma.projectMilestone.update({
187
+ where: { id: milestone.id },
188
+ data: {
189
+ hints: {
190
+ ...existingHints,
191
+ [difficultyMode]: modeHints,
192
+ },
193
+ },
194
+ });
195
+
196
+ res.json({ success: true, data: hint });
197
+ } catch (error: any) {
198
+ res.status(500).json({ success: false, message: error.message });
199
+ }
200
+ };
201
+
202
+ export const getChatHistory = async (req: Request, res: Response) => {
203
+ const { projectId } = req.params;
204
+ const userId = (req as any).user?.id;
205
+
206
+ try {
207
+ const messages = await prisma.chatMessage.findMany({
208
+ where: {
209
+ userId,
210
+ projectId: projectId || null,
211
+ },
212
+ orderBy: { createdAt: "asc" },
213
+ });
214
+
215
+ res.json({ success: true, data: messages });
216
+ } catch (error: any) {
217
+ res.status(500).json({ success: false, message: error.message });
218
+ }
219
+ };
controllers/analytics/index.ts ADDED
@@ -0,0 +1,72 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Request, Response, NextFunction } from "express";
2
+ import prisma from "../../prisma/client";
3
+
4
+ export const getUserAnalytics = async (req: Request, res: Response, next: NextFunction) => {
5
+ const userId = (req as any).user?.id;
6
+
7
+ try {
8
+ const user = await prisma.user.findUnique({
9
+ where: { id: userId },
10
+ select: {
11
+ xp: true,
12
+ streak: true,
13
+ _count: {
14
+ select: {
15
+ projects: true,
16
+ submissions: true,
17
+ achievements: true
18
+ }
19
+ }
20
+ }
21
+ });
22
+
23
+ const projectCompletion = await prisma.userProject.groupBy({
24
+ by: ["status"],
25
+ where: { userId },
26
+ _count: true
27
+ });
28
+
29
+ res.json({
30
+ success: true,
31
+ data: {
32
+ stats: user,
33
+ projectCompletion
34
+ }
35
+ });
36
+ } catch (error: any) {
37
+ next(error);
38
+ }
39
+ };
40
+
41
+ export const getAdminAnalytics = async (req: Request, res: Response, next: NextFunction) => {
42
+ const userId = (req as any).user?.id;
43
+
44
+ try {
45
+ const user = await prisma.user.findUnique({ where: { id: userId } });
46
+ if (user?.role !== "ADMIN") {
47
+ return res.status(403).json({ success: false, message: "Forbidden" });
48
+ }
49
+
50
+ const totalUsers = await prisma.user.count();
51
+ const totalProjects = await prisma.project.count();
52
+ const totalSubmissions = await prisma.submission.count();
53
+
54
+ const popularProjects = await prisma.project.findMany({
55
+ orderBy: { submissionCount: "desc" },
56
+ take: 5,
57
+ select: { title: true, submissionCount: true }
58
+ });
59
+
60
+ res.json({
61
+ success: true,
62
+ data: {
63
+ totalUsers,
64
+ totalProjects,
65
+ totalSubmissions,
66
+ popularProjects
67
+ }
68
+ });
69
+ } catch (error: any) {
70
+ next(error);
71
+ }
72
+ };
controllers/auth/index.ts ADDED
@@ -0,0 +1,448 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Request, Response } from "express";
2
+ import prisma from "../../prisma/client";
3
+ import * as argon2 from "argon2";
4
+ import { sendEmail } from "../../middlewares/email";
5
+ import { generateOTP } from "../../middlewares/generateOTP";
6
+ import jwt from "jsonwebtoken";
7
+ import dotenv from "dotenv";
8
+ import redis from "../../utils/redis";
9
+ import { Difficulty, Role } from "@prisma/client";
10
+
11
+ dotenv.config();
12
+ const JWT_SECRET: string = process.env.JWT_SECRET || "default_secret";
13
+
14
+ export const signUp = async (req: Request, res: Response) => {
15
+ const { firstName, lastName, skillLevel, email, password, role } = req.body;
16
+ try {
17
+ const existingUser = await prisma.user.findUnique({ where: { email } });
18
+ if (existingUser) {
19
+ return res
20
+ .status(400)
21
+ .json({ success: false, message: "Email already registered" });
22
+ }
23
+
24
+ const hashedPassword = await argon2.hash(password);
25
+ const user = await prisma.user.create({
26
+ data: {
27
+ firstName,
28
+ lastName,
29
+ email,
30
+ skillLevel: (skillLevel as Difficulty) || "BEGINNER",
31
+ password: hashedPassword,
32
+ role: (role as Role) || "STUDENT",
33
+ },
34
+ });
35
+
36
+ res.json({
37
+ success: true,
38
+ message: "User registered successfully",
39
+ data: { id: user.id },
40
+ });
41
+ } catch (error: any) {
42
+ res.status(500).json({ success: false, message: error.message });
43
+ }
44
+ };
45
+
46
+ export const login = async (req: Request, res: Response) => {
47
+ const { email, password } = req.body;
48
+ try {
49
+ const user = await prisma.user.findUnique({ where: { email } });
50
+ if (!user || !user.password) {
51
+ return res
52
+ .status(401)
53
+ .json({ success: false, message: "Invalid Email or Password" });
54
+ }
55
+
56
+ const verifyPassword = await argon2.verify(user.password, password);
57
+ if (!verifyPassword) {
58
+ return res
59
+ .status(401)
60
+ .json({ success: false, message: "Invalid Email or Password" });
61
+ }
62
+
63
+ const token = jwt.sign({ id: user.id, role: user.role }, JWT_SECRET, {
64
+ expiresIn: "7d",
65
+ });
66
+
67
+ res.cookie("token", token, {
68
+ httpOnly: true,
69
+ secure: process.env.NODE_ENV === "production",
70
+ sameSite: "strict",
71
+ maxAge: 7 * 24 * 60 * 60 * 1000, // 7 days
72
+ });
73
+
74
+ res.json({
75
+ success: true,
76
+ message: "Login successful",
77
+ data: {
78
+ id: user.id,
79
+ firstName: user.firstName,
80
+ lastName: user.lastName,
81
+ email: user.email,
82
+ role: user.role,
83
+ token,
84
+ },
85
+ });
86
+ } catch (error: any) {
87
+ res.status(500).json({ success: false, message: error.message });
88
+ }
89
+ };
90
+
91
+ export const logout = async (req: Request, res: Response) => {
92
+ res.clearCookie("token");
93
+ res.json({ success: true, message: "Logged out successfully" });
94
+ };
95
+
96
+ export const me = async (req: Request, res: Response) => {
97
+ const userId = (req as any).user?.id;
98
+ if (!userId) {
99
+ return res.status(401).json({ success: false, message: "Unauthorized" });
100
+ }
101
+
102
+ try {
103
+ const user = await prisma.user.findUnique({
104
+ where: { id: userId },
105
+ select: {
106
+ id: true,
107
+ email: true,
108
+ firstName: true,
109
+ lastName: true,
110
+ skillLevel: true,
111
+ bio: true,
112
+ portfolioLinks: true,
113
+ xp: true,
114
+ streak: true,
115
+ role: true,
116
+ avatarUrl: true,
117
+ createdAt: true,
118
+ projects: {
119
+ include: {
120
+ project: true,
121
+ },
122
+ },
123
+ achievements: {
124
+ include: {
125
+ achievement: true,
126
+ },
127
+ },
128
+ },
129
+ });
130
+
131
+ if (!user) {
132
+ return res
133
+ .status(404)
134
+ .json({ success: false, message: "User not found" });
135
+ }
136
+
137
+ res.json({ success: true, data: user });
138
+ } catch (error: any) {
139
+ res.status(500).json({ success: false, message: error.message });
140
+ }
141
+ };
142
+
143
+ export const forgetPassword = async (req: Request, res: Response) => {
144
+ const { email } = req.body;
145
+ const appName = "Dev Drill";
146
+
147
+ try {
148
+ const user = await prisma.user.findUnique({ where: { email } });
149
+ if (!user) {
150
+ return res
151
+ .status(404)
152
+ .json({ success: false, message: "Account is not registered" });
153
+ }
154
+
155
+ const { otp, otpDate } = generateOTP();
156
+ // Store OTP in Redis with 1 hour expiry
157
+ await redis.setex(`otp:${email}`, 3600, otp.toString());
158
+
159
+ const htmlMessage = `
160
+ <div style="font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto;">
161
+ <div style="background: #f0f7ff; padding: 30px; border-radius: 10px;">
162
+ <h1 style="color: #1a237e; margin-bottom: 25px;">${appName}</h1>
163
+ <p style="font-size: 16px; color: #333;">
164
+ Hello ${user.lastName},<br><br>
165
+ We received a request to reset your password for your ${appName} account.
166
+ </p>
167
+ <div style="background: #fff; padding: 20px; border-radius: 5px; margin: 20px 0; text-align: center;">
168
+ <p style="margin: 0; font-weight: bold; font-size: 24px; letter-spacing: 2px;">${otp}</p>
169
+ </div>
170
+ <p style="font-size: 14px; color: #666;">
171
+ This verification code will expire in 1 hour.<br>
172
+ If you didn't request this password reset, please ignore this email.
173
+ </p>
174
+ </div>
175
+ <p style="font-size: 12px; color: #999; text-align: center; margin-top: 20px;">
176
+ © ${new Date().getFullYear()} ${appName}. All rights reserved.
177
+ </p>
178
+ </div>
179
+ `;
180
+
181
+ const textMessage =
182
+ `${appName} Password Reset\n\n` +
183
+ `Hello ${user.lastName},\n\n` +
184
+ `Use this OTP to reset your password: ${otp}\n` +
185
+ `This code expires in 1 hour.`;
186
+
187
+ sendEmail(
188
+ user.email,
189
+ `Password Reset Request - ${appName}`,
190
+ htmlMessage,
191
+ textMessage
192
+ );
193
+
194
+ res.json({
195
+ success: true,
196
+ message: `An OTP has been sent to your mail. Check your mail for your verification code`,
197
+ });
198
+ } catch (error: any) {
199
+ res.status(500).json({ success: false, message: error.message });
200
+ }
201
+ };
202
+
203
+ export const verifyOTP = async (req: Request, res: Response) => {
204
+ const { email, otp: userOtp } = req.body;
205
+
206
+ try {
207
+ const storedOtp = await redis.get(`otp:${email}`);
208
+ if (!storedOtp) {
209
+ return res
210
+ .status(400)
211
+ .json({ success: false, message: "OTP expired or not found" });
212
+ }
213
+
214
+ if (storedOtp !== userOtp.toString()) {
215
+ return res
216
+ .status(400)
217
+ .json({ success: false, message: "Invalid Verification code!" });
218
+ }
219
+
220
+ // OTP verified, can remove it now
221
+ await redis.del(`otp:${email}`);
222
+ // Optionally set a flag in Redis that OTP was verified for this email to allow password reset
223
+ await redis.setex(`otp_verified:${email}`, 600, "true");
224
+
225
+ res.json({ success: true, message: "Verification successful" });
226
+ } catch (error: any) {
227
+ res.status(500).json({ success: false, message: error.message });
228
+ }
229
+ };
230
+
231
+ export const resetPassword = async (req: Request, res: Response) => {
232
+ const { email, newPassword } = req.body;
233
+
234
+ try {
235
+ const isVerified = await redis.get(`otp_verified:${email}`);
236
+ if (!isVerified) {
237
+ return res
238
+ .status(400)
239
+ .json({ success: false, message: "OTP not verified" });
240
+ }
241
+
242
+ const user = await prisma.user.findUnique({ where: { email } });
243
+ if (!user)
244
+ return res
245
+ .status(404)
246
+ .json({ success: false, message: "User not found" });
247
+
248
+ if (user.password) {
249
+ const verifyPassword = await argon2.verify(user.password, newPassword);
250
+ if (verifyPassword) {
251
+ return res
252
+ .status(400)
253
+ .json({ success: false, message: "You entered your old password" });
254
+ }
255
+ }
256
+
257
+ const newPass = await argon2.hash(newPassword);
258
+ await prisma.user.update({
259
+ where: { email },
260
+ data: { password: newPass },
261
+ });
262
+
263
+ await redis.del(`otp_verified:${email}`);
264
+
265
+ res.json({ success: true, message: "Password successfully reset" });
266
+ } catch (error: any) {
267
+ res.status(500).json({ success: false, message: error.message });
268
+ }
269
+ };
270
+
271
+ export const getUser = async (req: Request, res: Response) => {
272
+ const { id } = req.params;
273
+ try {
274
+ const user = await prisma.user.findUnique({
275
+ where: { id },
276
+ select: {
277
+ id: true,
278
+ email: true,
279
+ firstName: true,
280
+ lastName: true,
281
+ skillLevel: true,
282
+ bio: true,
283
+ portfolioLinks: true,
284
+ xp: true,
285
+ streak: true,
286
+ role: true,
287
+ avatarUrl: true,
288
+ createdAt: true,
289
+ projects: {
290
+ include: {
291
+ project: true,
292
+ },
293
+ },
294
+ achievements: {
295
+ include: {
296
+ achievement: true,
297
+ },
298
+ },
299
+ },
300
+ });
301
+ if (!user)
302
+ return res
303
+ .status(404)
304
+ .json({ success: false, message: "User not found" });
305
+ res.json({ success: true, data: user });
306
+ } catch (error: any) {
307
+ res.status(500).json({ success: false, message: error.message });
308
+ }
309
+ };
310
+
311
+ export const updateUser = async (req: Request, res: Response) => {
312
+ const { id } = req.params;
313
+ const authenticatedUserId = (req as any).user?.id;
314
+
315
+ if (authenticatedUserId !== id) {
316
+ return res.status(403).json({ success: false, message: "Forbidden" });
317
+ }
318
+
319
+ let {
320
+ firstName,
321
+ lastName,
322
+ email,
323
+ password,
324
+ bio,
325
+ skillLevel,
326
+ portfolioLinks,
327
+ avatarUrl,
328
+ } = req.body;
329
+ try {
330
+ const user = await prisma.user.findUnique({ where: { id } });
331
+ if (!user) {
332
+ return res
333
+ .status(404)
334
+ .json({ success: false, message: "User not found" });
335
+ }
336
+
337
+ const data: any = {
338
+ firstName,
339
+ lastName,
340
+ bio,
341
+ skillLevel,
342
+ portfolioLinks,
343
+ avatarUrl,
344
+ };
345
+
346
+ if (email && email !== user.email) {
347
+ const existingUser = await prisma.user.findUnique({ where: { email } });
348
+ if (existingUser) {
349
+ return res
350
+ .status(400)
351
+ .json({ success: false, message: "Email already in use" });
352
+ }
353
+ data.email = email;
354
+ }
355
+
356
+ if (password) {
357
+ data.password = await argon2.hash(password);
358
+ }
359
+
360
+ const updatedUser = await prisma.user.update({
361
+ where: { id },
362
+ data,
363
+ select: {
364
+ id: true,
365
+ email: true,
366
+ firstName: true,
367
+ lastName: true,
368
+ skillLevel: true,
369
+ bio: true,
370
+ portfolioLinks: true,
371
+ xp: true,
372
+ streak: true,
373
+ role: true,
374
+ avatarUrl: true,
375
+ createdAt: true,
376
+ projects: {
377
+ include: {
378
+ project: true,
379
+ },
380
+ },
381
+ achievements: {
382
+ include: {
383
+ achievement: true,
384
+ },
385
+ },
386
+ },
387
+ });
388
+
389
+ res.json({
390
+ success: true,
391
+ data: updatedUser,
392
+ });
393
+ } catch (error: any) {
394
+ res.status(500).json({ success: false, message: error.message });
395
+ }
396
+ };
397
+
398
+ export const googleAuthCallback = async (req: Request, res: Response) => {
399
+ try {
400
+ const user = req.user as any;
401
+ if (!user) {
402
+ return res.redirect(
403
+ `${process.env.CLIENT_URL}/auth/signin?error=auth_failed`
404
+ );
405
+ }
406
+
407
+ const token = jwt.sign({ id: user.id, role: user.role }, JWT_SECRET, {
408
+ expiresIn: "7d",
409
+ });
410
+
411
+ res.cookie("token", token, {
412
+ httpOnly: true,
413
+ secure: process.env.NODE_ENV === "production",
414
+ sameSite: "lax",
415
+ maxAge: 7 * 24 * 60 * 60 * 1000,
416
+ });
417
+
418
+ res.redirect(`${process.env.CLIENT_URL}/dashboard`);
419
+ } catch (error: any) {
420
+ res.redirect(`${process.env.CLIENT_URL}/auth/signin?error=server_error`);
421
+ }
422
+ };
423
+
424
+ export const githubAuthCallback = async (req: Request, res: Response) => {
425
+ try {
426
+ const user = req.user as any;
427
+ if (!user) {
428
+ return res.redirect(
429
+ `${process.env.CLIENT_URL}/auth/signin?error=auth_failed`
430
+ );
431
+ }
432
+
433
+ const token = jwt.sign({ id: user.id, role: user.role }, JWT_SECRET, {
434
+ expiresIn: "7d",
435
+ });
436
+
437
+ res.cookie("token", token, {
438
+ httpOnly: true,
439
+ secure: process.env.NODE_ENV === "production",
440
+ sameSite: "lax",
441
+ maxAge: 7 * 24 * 60 * 60 * 1000,
442
+ });
443
+
444
+ res.redirect(`${process.env.CLIENT_URL}/dashboard`);
445
+ } catch (error: any) {
446
+ res.redirect(`${process.env.CLIENT_URL}/auth/signin?error=server_error`);
447
+ }
448
+ };
controllers/code-review/index.ts ADDED
@@ -0,0 +1,71 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Request, Response } from "express";
2
+ import prisma from "../../prisma/client";
3
+ import { getGeminiResponse } from "../../utils/gemini";
4
+ import axios from "axios";
5
+
6
+ export const analyzeCode = async (req: Request, res: Response) => {
7
+ const userId = (req as any).user?.id;
8
+ const { repoUrl, projectId } = req.body;
9
+
10
+ try {
11
+ // 1. Fetch code from GitHub (simplified: fetch README or a few files for demo)
12
+ // In a real app, you'd use a GitHub App or User Token to clone or fetch via API
13
+ const repoPath = repoUrl.replace("https://github.com/", "");
14
+ const [owner, repo] = repoPath.split("/");
15
+
16
+ // Fetch repo info as a placeholder for analysis
17
+ const githubApiUrl = `https://api.github.com/repos/${owner}/${repo}`;
18
+ const repoInfo = await axios.get(githubApiUrl);
19
+
20
+ // 2. Automated analysis (simulated)
21
+ const metrics = {
22
+ complexity: "Medium",
23
+ securityIssues: 0,
24
+ lintErrors: 0
25
+ };
26
+
27
+ // 3. AI-powered feedback
28
+ const prompt = `Please review this GitHub repository: ${repoUrl}.
29
+ Owner: ${owner}
30
+ Repo: ${repo}
31
+ Description: ${repoInfo.data.description}
32
+ Provide constructive feedback on the project structure and suggest improvements for a student.`;
33
+
34
+ const feedback = await getGeminiResponse(prompt);
35
+
36
+ // 4. Store review results
37
+ const submission = await prisma.submission.create({
38
+ data: {
39
+ userId,
40
+ projectId,
41
+ repoUrl,
42
+ feedback,
43
+ score: 85 // Simulated score
44
+ }
45
+ });
46
+
47
+ res.json({ success: true, data: submission });
48
+ } catch (error: any) {
49
+ res.status(500).json({ success: false, message: error.message });
50
+ }
51
+ };
52
+
53
+ export const getSubmissionReview = async (req: Request, res: Response) => {
54
+ const { id } = req.params;
55
+
56
+ try {
57
+ const submission = await prisma.submission.findUnique({
58
+ where: { id },
59
+ include: {
60
+ project: { select: { title: true } },
61
+ user: { select: { firstName: true, lastName: true } }
62
+ }
63
+ });
64
+
65
+ if (!submission) return res.status(404).json({ success: false, message: "Submission not found" });
66
+
67
+ res.json({ success: true, data: submission });
68
+ } catch (error: any) {
69
+ res.status(500).json({ success: false, message: error.message });
70
+ }
71
+ };
controllers/community/index.ts ADDED
@@ -0,0 +1,152 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Request, Response, NextFunction } from "express";
2
+ import prisma from "../../prisma/client";
3
+
4
+ export const getPublicSubmissions = async (req: Request, res: Response, next: NextFunction) => {
5
+ try {
6
+ const submissions = await prisma.submission.findMany({
7
+ where: { isPublic: true },
8
+ include: {
9
+ user: {
10
+ select: {
11
+ id: true,
12
+ firstName: true,
13
+ lastName: true,
14
+ avatarUrl: true,
15
+ },
16
+ },
17
+ project: {
18
+ select: {
19
+ id: true,
20
+ title: true,
21
+ },
22
+ },
23
+ _count: {
24
+ select: {
25
+ comments: true,
26
+ votes: true,
27
+ },
28
+ },
29
+ },
30
+ orderBy: { createdAt: "desc" },
31
+ });
32
+
33
+ res.json({ success: true, data: submissions });
34
+ } catch (error: any) {
35
+ next(error);
36
+ }
37
+ };
38
+
39
+ export const commentOnSubmission = async (req: Request, res: Response, next: NextFunction) => {
40
+ const { id: submissionId } = req.params;
41
+ const { content } = req.body;
42
+ const userId = (req as any).user?.id;
43
+
44
+ try {
45
+ const comment = await prisma.comment.create({
46
+ data: {
47
+ content,
48
+ userId,
49
+ submissionId,
50
+ },
51
+ include: {
52
+ user: {
53
+ select: {
54
+ id: true,
55
+ firstName: true,
56
+ lastName: true,
57
+ avatarUrl: true,
58
+ },
59
+ },
60
+ },
61
+ });
62
+
63
+ res.status(201).json({ success: true, data: comment });
64
+ } catch (error: any) {
65
+ next(error);
66
+ }
67
+ };
68
+
69
+ export const voteOnSubmission = async (req: Request, res: Response, next: NextFunction) => {
70
+ const { id: submissionId } = req.params;
71
+ const { value } = req.body; // 1 for upvote, -1 for downvote
72
+ const userId = (req as any).user?.id;
73
+
74
+ try {
75
+ const vote = await prisma.vote.upsert({
76
+ where: {
77
+ userId_submissionId: {
78
+ userId,
79
+ submissionId,
80
+ },
81
+ },
82
+ update: { value },
83
+ create: {
84
+ value,
85
+ userId,
86
+ submissionId,
87
+ },
88
+ });
89
+
90
+ res.json({ success: true, data: vote });
91
+ } catch (error: any) {
92
+ next(error);
93
+ }
94
+ };
95
+
96
+ export const getForumThreads = async (req: Request, res: Response, next: NextFunction) => {
97
+ const { projectId } = req.query;
98
+
99
+ try {
100
+ const threads = await prisma.forumThread.findMany({
101
+ where: projectId ? { projectId: String(projectId) } : {},
102
+ include: {
103
+ user: {
104
+ select: {
105
+ id: true,
106
+ firstName: true,
107
+ lastName: true,
108
+ avatarUrl: true,
109
+ },
110
+ },
111
+ _count: {
112
+ select: { posts: true },
113
+ },
114
+ },
115
+ orderBy: { createdAt: "desc" },
116
+ });
117
+
118
+ res.json({ success: true, data: threads });
119
+ } catch (error: any) {
120
+ next(error);
121
+ }
122
+ };
123
+
124
+ export const createForumThread = async (req: Request, res: Response, next: NextFunction) => {
125
+ const { title, content, projectId } = req.body;
126
+ const userId = (req as any).user?.id;
127
+
128
+ try {
129
+ const thread = await prisma.forumThread.create({
130
+ data: {
131
+ title,
132
+ content,
133
+ userId,
134
+ projectId: projectId || null,
135
+ },
136
+ include: {
137
+ user: {
138
+ select: {
139
+ id: true,
140
+ firstName: true,
141
+ lastName: true,
142
+ avatarUrl: true,
143
+ },
144
+ },
145
+ },
146
+ });
147
+
148
+ res.status(201).json({ success: true, data: thread });
149
+ } catch (error: any) {
150
+ next(error);
151
+ }
152
+ };
controllers/events/index.ts ADDED
@@ -0,0 +1,221 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Request, Response } from "express";
2
+ import prisma from "../../prisma/client";
3
+ import { EventStatus } from "@prisma/client";
4
+
5
+ export const createEvent = async (req: Request, res: Response) => {
6
+ const {
7
+ title,
8
+ description,
9
+ type,
10
+ startDate,
11
+ endDate,
12
+ prizePool,
13
+ maxParticipants,
14
+ bannerUrl,
15
+ } = req.body;
16
+
17
+ try {
18
+ const event = await prisma.event.create({
19
+ data: {
20
+ title,
21
+ description,
22
+ type,
23
+ startDate: new Date(startDate),
24
+ endDate: new Date(endDate),
25
+ prizePool,
26
+ maxParticipants,
27
+ bannerUrl,
28
+ },
29
+ });
30
+
31
+ res.json({ success: true, data: event });
32
+ } catch (error: any) {
33
+ res.status(500).json({ success: false, message: error.message });
34
+ }
35
+ };
36
+
37
+ export const getEvents = async (req: Request, res: Response) => {
38
+ const { status, type, page = 1, limit = 10 } = req.query;
39
+
40
+ try {
41
+ const where: any = {};
42
+ if (status) where.status = status as EventStatus;
43
+ if (type) where.type = type;
44
+
45
+ const events = await prisma.event.findMany({
46
+ where,
47
+ include: {
48
+ _count: {
49
+ select: { participants: true },
50
+ },
51
+ },
52
+ orderBy: { startDate: "desc" },
53
+ take: Number(limit),
54
+ skip: (Number(page) - 1) * Number(limit),
55
+ });
56
+
57
+ res.json({ success: true, data: events });
58
+ } catch (error: any) {
59
+ res.status(500).json({ success: false, message: error.message });
60
+ }
61
+ };
62
+
63
+ export const getEvent = async (req: Request, res: Response) => {
64
+ const { id } = req.params;
65
+
66
+ try {
67
+ const event = await prisma.event.findUnique({
68
+ where: { id },
69
+ include: {
70
+ participants: {
71
+ include: {
72
+ user: {
73
+ select: {
74
+ id: true,
75
+ firstName: true,
76
+ lastName: true,
77
+ avatarUrl: true,
78
+ },
79
+ },
80
+ },
81
+ },
82
+ leaderboard: {
83
+ include: {
84
+ user: {
85
+ select: {
86
+ id: true,
87
+ firstName: true,
88
+ lastName: true,
89
+ avatarUrl: true,
90
+ },
91
+ },
92
+ },
93
+ orderBy: { score: "desc" },
94
+ take: 100,
95
+ },
96
+ },
97
+ });
98
+
99
+ if (!event) {
100
+ return res
101
+ .status(404)
102
+ .json({ success: false, message: "Event not found" });
103
+ }
104
+
105
+ res.json({ success: true, data: event });
106
+ } catch (error: any) {
107
+ res.status(500).json({ success: false, message: error.message });
108
+ }
109
+ };
110
+
111
+ export const joinEvent = async (req: Request, res: Response) => {
112
+ const userId = (req as any).user?.id;
113
+ const { eventId } = req.params;
114
+
115
+ try {
116
+ const event = await prisma.event.findUnique({
117
+ where: { id: eventId },
118
+ include: { _count: { select: { participants: true } } },
119
+ });
120
+
121
+ if (!event) {
122
+ return res
123
+ .status(404)
124
+ .json({ success: false, message: "Event not found" });
125
+ }
126
+
127
+ if (
128
+ event.maxParticipants &&
129
+ event._count.participants >= event.maxParticipants
130
+ ) {
131
+ return res.status(400).json({ success: false, message: "Event is full" });
132
+ }
133
+
134
+ const participant = await prisma.eventParticipant.create({
135
+ data: { eventId, userId },
136
+ });
137
+
138
+ await prisma.eventLeaderboard.create({
139
+ data: { eventId, userId, score: 0 },
140
+ });
141
+
142
+ res.json({ success: true, data: participant });
143
+ } catch (error: any) {
144
+ res.status(500).json({ success: false, message: error.message });
145
+ }
146
+ };
147
+
148
+ export const leaveEvent = async (req: Request, res: Response) => {
149
+ const userId = (req as any).user?.id;
150
+ const { eventId } = req.params;
151
+
152
+ try {
153
+ await prisma.eventParticipant.delete({
154
+ where: { eventId_userId: { eventId, userId } },
155
+ });
156
+
157
+ await prisma.eventLeaderboard.delete({
158
+ where: { eventId_userId: { eventId, userId } },
159
+ });
160
+
161
+ res.json({ success: true, message: "Left event successfully" });
162
+ } catch (error: any) {
163
+ res.status(500).json({ success: false, message: error.message });
164
+ }
165
+ };
166
+
167
+ export const updateEventScore = async (req: Request, res: Response) => {
168
+ const { eventId, userId } = req.params;
169
+ const { score } = req.body;
170
+
171
+ try {
172
+ const leaderboard = await prisma.eventLeaderboard.update({
173
+ where: { eventId_userId: { eventId, userId } },
174
+ data: { score },
175
+ });
176
+
177
+ const allScores = await prisma.eventLeaderboard.findMany({
178
+ where: { eventId },
179
+ orderBy: { score: "desc" },
180
+ });
181
+
182
+ for (let i = 0; i < allScores.length; i++) {
183
+ await prisma.eventLeaderboard.update({
184
+ where: { id: allScores[i].id },
185
+ data: { rank: i + 1 },
186
+ });
187
+ }
188
+
189
+ res.json({ success: true, data: leaderboard });
190
+ } catch (error: any) {
191
+ res.status(500).json({ success: false, message: error.message });
192
+ }
193
+ };
194
+
195
+ export const getLeaderboard = async (req: Request, res: Response) => {
196
+ const { eventId } = req.params;
197
+ const { limit = 100 } = req.query;
198
+
199
+ try {
200
+ const leaderboard = await prisma.eventLeaderboard.findMany({
201
+ where: { eventId },
202
+ include: {
203
+ user: {
204
+ select: {
205
+ id: true,
206
+ firstName: true,
207
+ lastName: true,
208
+ avatarUrl: true,
209
+ xp: true,
210
+ },
211
+ },
212
+ },
213
+ orderBy: { score: "desc" },
214
+ take: Number(limit),
215
+ });
216
+
217
+ res.json({ success: true, data: leaderboard });
218
+ } catch (error: any) {
219
+ res.status(500).json({ success: false, message: error.message });
220
+ }
221
+ };
controllers/gamification/index.ts ADDED
@@ -0,0 +1,69 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Request, Response, NextFunction } from "express";
2
+ import prisma from "../../prisma/client";
3
+ import redis from "../../utils/redis";
4
+
5
+ const LEADERBOARD_CACHE_KEY = "leaderboard:top20";
6
+ const CACHE_TTL = 3600; // 1 hour
7
+
8
+ export const getLeaderboard = async (req: Request, res: Response, next: NextFunction) => {
9
+ try {
10
+ // Try to get from cache
11
+ const cachedLeaderboard = await redis.get(LEADERBOARD_CACHE_KEY);
12
+ if (cachedLeaderboard) {
13
+ return res.json({ success: true, data: JSON.parse(cachedLeaderboard), cached: true });
14
+ }
15
+
16
+ const leaderboard = await prisma.user.findMany({
17
+ select: {
18
+ id: true,
19
+ firstName: true,
20
+ lastName: true,
21
+ avatarUrl: true,
22
+ xp: true,
23
+ streak: true
24
+ },
25
+ orderBy: { xp: "desc" },
26
+ take: 20
27
+ });
28
+
29
+ // Save to cache
30
+ await redis.setex(LEADERBOARD_CACHE_KEY, CACHE_TTL, JSON.stringify(leaderboard));
31
+
32
+ res.json({ success: true, data: leaderboard });
33
+ } catch (error: any) {
34
+ next(error);
35
+ }
36
+ };
37
+
38
+ export const getUserAchievements = async (req: Request, res: Response, next: NextFunction) => {
39
+ const userId = (req as any).user?.id;
40
+
41
+ try {
42
+ const achievements = await prisma.userAchievement.findMany({
43
+ where: { userId },
44
+ include: {
45
+ achievement: true
46
+ }
47
+ });
48
+
49
+ res.json({ success: true, data: achievements });
50
+ } catch (error: any) {
51
+ next(error);
52
+ }
53
+ };
54
+
55
+ export const awardXP = async (userId: string, amount: number) => {
56
+ try {
57
+ await prisma.user.update({
58
+ where: { id: userId },
59
+ data: {
60
+ xp: { increment: amount }
61
+ }
62
+ });
63
+
64
+ // Invalidate leaderboard cache when XP changes
65
+ await redis.del(LEADERBOARD_CACHE_KEY);
66
+ } catch (error) {
67
+ console.error("Error awarding XP:", error);
68
+ }
69
+ };
controllers/github/index.ts ADDED
@@ -0,0 +1,109 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Request, Response } from "express";
2
+ import prisma from "../../prisma/client";
3
+ import axios from "axios";
4
+
5
+ export const linkRepository = async (req: Request, res: Response) => {
6
+ const userId = (req as any).user?.id;
7
+ const { projectId, repoUrl } = req.body;
8
+
9
+ try {
10
+ if (!repoUrl.includes("github.com")) {
11
+ return res.status(400).json({
12
+ success: false,
13
+ message: "Invalid GitHub repository URL",
14
+ });
15
+ }
16
+
17
+ // Extract owner and repo from URL
18
+ const repoPath = repoUrl
19
+ .replace("https://github.com/", "")
20
+ .replace(".git", "");
21
+ const [owner, repo] = repoPath.split("/");
22
+
23
+ // Verify repository exists (optional - requires GitHub token)
24
+ try {
25
+ const githubApiUrl = `https://api.github.com/repos/${owner}/${repo}`;
26
+ await axios.get(githubApiUrl);
27
+ } catch (error) {
28
+ return res.status(404).json({
29
+ success: false,
30
+ message: "Repository not found or not accessible",
31
+ });
32
+ }
33
+
34
+ // Update user project with repo URL
35
+ const userProject = await prisma.userProject.findFirst({
36
+ where: {
37
+ userId,
38
+ projectId,
39
+ },
40
+ });
41
+
42
+ if (!userProject) {
43
+ return res.status(404).json({
44
+ success: false,
45
+ message: "Project not started by user",
46
+ });
47
+ }
48
+
49
+ const updated = await prisma.userProject.update({
50
+ where: { id: userProject.id },
51
+ data: { repoUrl },
52
+ });
53
+
54
+ res.json({ success: true, data: updated });
55
+ } catch (error: any) {
56
+ console.error("Link repository error:", error);
57
+ res.status(500).json({ success: false, message: error.message });
58
+ }
59
+ };
60
+
61
+ export const getRepositoryCommits = async (req: Request, res: Response) => {
62
+ const userId = (req as any).user?.id;
63
+ const { projectId } = req.params;
64
+
65
+ try {
66
+ const userProject = await prisma.userProject.findFirst({
67
+ where: {
68
+ userId,
69
+ projectId,
70
+ },
71
+ });
72
+
73
+ if (!userProject || !userProject.repoUrl) {
74
+ return res.status(404).json({
75
+ success: false,
76
+ message: "No repository linked",
77
+ });
78
+ }
79
+
80
+ // Extract owner and repo
81
+ const repoPath = userProject.repoUrl
82
+ .replace("https://github.com/", "")
83
+ .replace(".git", "");
84
+ const [owner, repo] = repoPath.split("/");
85
+
86
+ // Fetch commits from GitHub API
87
+ const githubApiUrl = `https://api.github.com/repos/${owner}/${repo}/commits`;
88
+ const response = await axios.get(githubApiUrl, {
89
+ params: { per_page: 10 },
90
+ headers: {
91
+ // Add GitHub token if available for higher rate limits
92
+ Authorization: `token ${process.env.GITHUB_TOKEN}`,
93
+ },
94
+ });
95
+
96
+ const commits = response.data.map((commit: any) => ({
97
+ id: commit.sha,
98
+ message: commit.commit.message,
99
+ author: commit.commit.author.name,
100
+ date: commit.commit.author.date,
101
+ url: commit.html_url,
102
+ }));
103
+
104
+ res.json({ success: true, data: commits });
105
+ } catch (error: any) {
106
+ console.error("Get commits error:", error);
107
+ res.status(500).json({ success: false, message: error.message });
108
+ }
109
+ };
controllers/notifications/index.ts ADDED
@@ -0,0 +1,75 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Request, Response } from "express";
2
+ import prisma from "../../prisma/client";
3
+
4
+ export const getNotifications = async (req: Request, res: Response) => {
5
+ const userId = (req as any).user?.id;
6
+ const { page = 1, limit = 20, unreadOnly = false } = req.query;
7
+
8
+ try {
9
+ const where: any = { userId };
10
+ if (unreadOnly === "true") {
11
+ where.read = false;
12
+ }
13
+
14
+ const notifications = await prisma.notification.findMany({
15
+ where,
16
+ orderBy: { createdAt: "desc" },
17
+ take: Number(limit),
18
+ skip: (Number(page) - 1) * Number(limit),
19
+ });
20
+
21
+ const unreadCount = await prisma.notification.count({
22
+ where: { userId, read: false },
23
+ });
24
+
25
+ res.json({ success: true, data: { notifications, unreadCount } });
26
+ } catch (error: any) {
27
+ res.status(500).json({ success: false, message: error.message });
28
+ }
29
+ };
30
+
31
+ export const markAsRead = async (req: Request, res: Response) => {
32
+ const userId = (req as any).user?.id;
33
+ const { notificationId } = req.params;
34
+
35
+ try {
36
+ await prisma.notification.updateMany({
37
+ where: { id: notificationId, userId },
38
+ data: { read: true },
39
+ });
40
+
41
+ res.json({ success: true, message: "Notification marked as read" });
42
+ } catch (error: any) {
43
+ res.status(500).json({ success: false, message: error.message });
44
+ }
45
+ };
46
+
47
+ export const markAllAsRead = async (req: Request, res: Response) => {
48
+ const userId = (req as any).user?.id;
49
+
50
+ try {
51
+ await prisma.notification.updateMany({
52
+ where: { userId, read: false },
53
+ data: { read: true },
54
+ });
55
+
56
+ res.json({ success: true, message: "All notifications marked as read" });
57
+ } catch (error: any) {
58
+ res.status(500).json({ success: false, message: error.message });
59
+ }
60
+ };
61
+
62
+ export const deleteNotification = async (req: Request, res: Response) => {
63
+ const userId = (req as any).user?.id;
64
+ const { notificationId } = req.params;
65
+
66
+ try {
67
+ await prisma.notification.deleteMany({
68
+ where: { id: notificationId, userId },
69
+ });
70
+
71
+ res.json({ success: true, message: "Notification deleted" });
72
+ } catch (error: any) {
73
+ res.status(500).json({ success: false, message: error.message });
74
+ }
75
+ };
controllers/paths/index.ts ADDED
@@ -0,0 +1,82 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Request, Response, NextFunction } from "express";
2
+ import prisma from "../../prisma/client";
3
+
4
+ export const getLearningPaths = async (req: Request, res: Response, next: NextFunction) => {
5
+ try {
6
+ const paths = await prisma.learningPath.findMany({
7
+ include: {
8
+ projects: {
9
+ include: {
10
+ project: {
11
+ select: {
12
+ title: true,
13
+ difficultyLevel: true,
14
+ },
15
+ },
16
+ },
17
+ orderBy: { orderIndex: "asc" },
18
+ },
19
+ },
20
+ });
21
+
22
+ res.json({ success: true, data: paths });
23
+ } catch (error: any) {
24
+ next(error);
25
+ }
26
+ };
27
+
28
+ export const getPathProgress = async (req: Request, res: Response, next: NextFunction) => {
29
+ const { id: pathId } = req.params;
30
+ const userId = (req as any).user?.id;
31
+
32
+ try {
33
+ const progress = await prisma.userPathProgress.findUnique({
34
+ where: {
35
+ userId_pathId: {
36
+ userId,
37
+ pathId,
38
+ },
39
+ },
40
+ include: {
41
+ path: {
42
+ include: {
43
+ projects: {
44
+ include: { project: true },
45
+ orderBy: { orderIndex: "asc" },
46
+ },
47
+ },
48
+ },
49
+ },
50
+ });
51
+
52
+ res.json({ success: true, data: progress });
53
+ } catch (error: any) {
54
+ next(error);
55
+ }
56
+ };
57
+
58
+ export const startPath = async (req: Request, res: Response, next: NextFunction) => {
59
+ const { id: pathId } = req.params;
60
+ const userId = (req as any).user?.id;
61
+
62
+ try {
63
+ const progress = await prisma.userPathProgress.upsert({
64
+ where: {
65
+ userId_pathId: {
66
+ userId,
67
+ pathId
68
+ }
69
+ },
70
+ update: {},
71
+ create: {
72
+ userId,
73
+ pathId,
74
+ currentProjectIndex: 0,
75
+ },
76
+ });
77
+
78
+ res.status(201).json({ success: true, data: progress });
79
+ } catch (error: any) {
80
+ next(error);
81
+ }
82
+ };
controllers/projects/index.ts ADDED
@@ -0,0 +1,706 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Request, Response } from "express";
2
+ import prisma from "../../prisma/client";
3
+ import { Difficulty, DifficultyMode, ProjectStatus } from "@prisma/client";
4
+ import { checkAuth } from "../../middlewares/auth";
5
+ import { upLoadFiles, uploadBuffer } from "../../middlewares/file";
6
+ import { generateSlug, parseIfString } from "../../utils/helper";
7
+ import { getGeminiResponse } from "../../utils/gemini";
8
+
9
+ export const createProject = async (req: Request, res: Response) => {
10
+ const userId = (req as any).user?.id;
11
+ let {
12
+ title,
13
+ description,
14
+ difficultyLevel,
15
+ technologies,
16
+ categories,
17
+ estimatedTime,
18
+ learningObjectives,
19
+ resourceLinks,
20
+ starterRepoUrl,
21
+ difficultyModes,
22
+ coverImage,
23
+ milestones,
24
+ } = req.body;
25
+
26
+ // Parse JSON strings from FormData
27
+ technologies = parseIfString(technologies);
28
+ categories = parseIfString(categories);
29
+ learningObjectives = parseIfString(learningObjectives);
30
+ resourceLinks = parseIfString(resourceLinks);
31
+ difficultyModes = parseIfString(difficultyModes);
32
+ milestones = parseIfString(milestones);
33
+
34
+ try {
35
+ if (!userId)
36
+ return res.status(401).json({ success: false, message: "Unauthorized" });
37
+
38
+ const user = await prisma.user.findUnique({ where: { id: userId } });
39
+ if (!user || (user.role !== "ADMIN" && user.role !== "CONTRIBUTOR")) {
40
+ return res.status(403).json({
41
+ success: false,
42
+ message: "Only Admins or Contributors can create projects",
43
+ });
44
+ }
45
+
46
+ let imageUrl = coverImage;
47
+ if (req.file) {
48
+ imageUrl = await uploadBuffer(req.file.buffer, req.file.originalname);
49
+ } else if (coverImage && !coverImage.startsWith("http")) {
50
+ imageUrl = await upLoadFiles(coverImage);
51
+ }
52
+
53
+ const project = await prisma.project.create({
54
+ data: {
55
+ title,
56
+ slug: generateSlug(title),
57
+ description,
58
+ difficultyLevel: difficultyLevel as Difficulty,
59
+ technologies,
60
+ categories,
61
+ estimatedTime,
62
+ learningObjectives,
63
+ resourceLinks: resourceLinks || [],
64
+ starterRepoUrl,
65
+ difficultyModes: difficultyModes || ["GUIDED", "STANDARD", "HARDCORE"],
66
+ createdBy: { connect: { id: userId } },
67
+ coverImage: imageUrl,
68
+ milestones: {
69
+ create: milestones?.map((m: any, index: number) => ({
70
+ milestoneNumber: index + 1,
71
+ title: m.title,
72
+ description: m.description,
73
+ hints: Array.isArray(m.hints) ? { GUIDED: m.hints } : m.hints || {},
74
+ validationCriteria: m.validationCriteria,
75
+ })),
76
+ },
77
+ },
78
+ include: { milestones: true },
79
+ });
80
+
81
+ res.status(201).json({ success: true, data: project });
82
+ } catch (error: any) {
83
+ res.status(500).json({ success: false, message: error.message });
84
+ }
85
+ };
86
+
87
+ export const updateProject = async (req: Request, res: Response) => {
88
+ const { id } = req.params;
89
+ const userId = (req as any).user?.id;
90
+
91
+ let updateData = req.body;
92
+
93
+ updateData.technologies = parseIfString(updateData.technologies);
94
+ updateData.categories = parseIfString(updateData.categories);
95
+ updateData.learningObjectives = parseIfString(updateData.learningObjectives);
96
+ updateData.resourceLinks = parseIfString(updateData.resourceLinks);
97
+ updateData.difficultyModes = parseIfString(updateData.difficultyModes);
98
+
99
+ const milestonesData = parseIfString(updateData.milestones);
100
+ delete updateData.milestones;
101
+
102
+ try {
103
+ const project = await prisma.project.findUnique({
104
+ where: { id },
105
+ include: { milestones: true, userProjects: true },
106
+ });
107
+
108
+ if (!project) {
109
+ return res.status(404).json({
110
+ success: false,
111
+ message: "Project not found",
112
+ });
113
+ }
114
+
115
+ const user = await prisma.user.findUnique({ where: { id: userId } });
116
+
117
+ if (!user || (user.role !== "ADMIN" && project.createdById !== userId)) {
118
+ return res.status(403).json({
119
+ success: false,
120
+ message: "Unauthorized to update this project",
121
+ });
122
+ }
123
+
124
+ if (req.file) {
125
+ updateData.coverImage = await uploadBuffer(
126
+ req.file.buffer,
127
+ req.file.originalname
128
+ );
129
+ } else if (
130
+ updateData.coverImage &&
131
+ !updateData.coverImage.startsWith("http")
132
+ ) {
133
+ updateData.coverImage = await upLoadFiles(updateData.coverImage);
134
+ }
135
+
136
+ // Update slug if title changed
137
+ if (updateData.title && updateData.title !== project.title) {
138
+ updateData.slug = generateSlug(updateData.title);
139
+ }
140
+
141
+ // Clean up any undefined or empty string values
142
+ Object.keys(updateData).forEach((key) => {
143
+ if (updateData[key] === undefined || updateData[key] === "") {
144
+ delete updateData[key];
145
+ }
146
+ });
147
+
148
+ // Update project with milestones handled separately
149
+ const updatedProject = await prisma.project.update({
150
+ where: { id },
151
+ data: {
152
+ ...updateData,
153
+ ...(milestonesData && {
154
+ milestones: {
155
+ deleteMany: {},
156
+ create: milestonesData
157
+ .filter((m: any) => m.title && m.description)
158
+ .map((m: any, index: number) => ({
159
+ milestoneNumber: index + 1,
160
+ title: m.title,
161
+ description: m.description,
162
+ hints: typeof m.hints === "object" ? m.hints : {},
163
+ validationCriteria:
164
+ typeof m.validationCriteria === "string"
165
+ ? m.validationCriteria
166
+ : JSON.stringify(m.validationCriteria || {}),
167
+ })),
168
+ },
169
+ }),
170
+ },
171
+ include: {
172
+ milestones: {
173
+ orderBy: { milestoneNumber: "asc" },
174
+ },
175
+ createdBy: {
176
+ select: {
177
+ id: true,
178
+ firstName: true,
179
+ lastName: true,
180
+ email: true,
181
+ avatarUrl: true,
182
+ },
183
+ },
184
+ userProjects: { where: { userId: user.id } },
185
+ },
186
+ });
187
+
188
+ // Build progressByMode
189
+ let progressByMode: any = {};
190
+ let userProgress = null;
191
+
192
+ if (updatedProject.userProjects && updatedProject.userProjects.length > 0) {
193
+ for (const up of updatedProject.userProjects) {
194
+ const completedMilestones = await prisma.userMilestone.findMany({
195
+ where: {
196
+ userId: user.id,
197
+ userProjectId: up.id,
198
+ },
199
+ select: { milestoneId: true },
200
+ });
201
+
202
+ progressByMode[up.difficultyModeChosen] = {
203
+ status: up.status,
204
+ completedMilestones: completedMilestones.map((m) => m.milestoneId),
205
+ userProjectId: up.id,
206
+ startedAt: up.startedAt,
207
+ completedAt: up.completedAt,
208
+ };
209
+ }
210
+
211
+ userProgress =
212
+ updatedProject.userProjects[updatedProject.userProjects.length - 1];
213
+ }
214
+
215
+ res.json({
216
+ success: true,
217
+ data: {
218
+ ...updatedProject,
219
+ progressByMode,
220
+ userProgress,
221
+ },
222
+ });
223
+ } catch (error: any) {
224
+ console.error("Update project error:", error);
225
+ res.status(500).json({
226
+ success: false,
227
+ message: error.message,
228
+ });
229
+ }
230
+ };
231
+
232
+ export const deleteProject = async (req: Request, res: Response) => {
233
+ const { id } = req.params;
234
+ const userId = (req as any).user?.id;
235
+
236
+ try {
237
+ const project = await prisma.project.findUnique({ where: { id } });
238
+ if (!project)
239
+ return res
240
+ .status(404)
241
+ .json({ success: false, message: "Project not found" });
242
+
243
+ const user = await prisma.user.findUnique({ where: { id: userId } });
244
+ if (!user || user.role !== "ADMIN") {
245
+ return res
246
+ .status(403)
247
+ .json({ success: false, message: "Only Admins can delete projects" });
248
+ }
249
+
250
+ await prisma.project.delete({ where: { id } });
251
+ res.json({ success: true, message: "Project deleted successfully" });
252
+ } catch (error: any) {
253
+ res.status(500).json({ success: false, message: error.message });
254
+ }
255
+ };
256
+
257
+ export const getProjects = async (req: Request, res: Response) => {
258
+ const { difficulty, tech, category, title } = req.query;
259
+
260
+ try {
261
+ const where: any = {};
262
+ if (difficulty) where.difficultyLevel = difficulty as Difficulty;
263
+ if (tech) where.technologies = { has: tech as string };
264
+ if (category) where.categories = { has: category as string };
265
+ if (title) where.title = { contains: title as string, mode: "insensitive" };
266
+
267
+ const projects = await prisma.project.findMany({
268
+ where,
269
+ include: {
270
+ createdBy: {
271
+ select: { firstName: true, lastName: true },
272
+ },
273
+ },
274
+ orderBy: { createdAt: "desc" },
275
+ });
276
+
277
+ res.json({ success: true, data: projects });
278
+ } catch (error: any) {
279
+ res.status(500).json({ success: false, message: error.message });
280
+ }
281
+ };
282
+
283
+ export const getProjectById = async (req: Request, res: Response) => {
284
+ const { id } = req.params;
285
+
286
+ try {
287
+ const user = await checkAuth(req);
288
+ const project = await prisma.project.findUnique({
289
+ where: { id },
290
+ include: {
291
+ milestones: { orderBy: { milestoneNumber: "asc" } },
292
+ createdBy: {
293
+ select: {
294
+ id: true,
295
+ firstName: true,
296
+ lastName: true,
297
+ avatarUrl: true,
298
+ },
299
+ },
300
+ userProjects: user ? { where: { userId: user.id } } : false,
301
+ },
302
+ });
303
+
304
+ if (!project)
305
+ return res
306
+ .status(404)
307
+ .json({ success: false, message: "Project not found" });
308
+
309
+ // If user is authenticated, fetch their progress and completed milestones for each mode
310
+ let progressByMode: any = {};
311
+ let userProgress = null;
312
+
313
+ if (user && project.userProjects && project.userProjects.length > 0) {
314
+ for (const up of project.userProjects) {
315
+ const completedMilestones = await prisma.userMilestone.findMany({
316
+ where: {
317
+ userId: user.id,
318
+ userProjectId: up.id,
319
+ },
320
+ select: { milestoneId: true },
321
+ });
322
+
323
+ progressByMode[up.difficultyModeChosen] = {
324
+ status: up.status,
325
+ completedMilestones: completedMilestones.map((m) => m.milestoneId),
326
+ userProjectId: up.id,
327
+ startedAt: up.startedAt,
328
+ completedAt: up.completedAt,
329
+ };
330
+ }
331
+
332
+ // Set the most recent userProject as the default userProgress
333
+ userProgress = project.userProjects[project.userProjects.length - 1];
334
+ }
335
+
336
+ res.json({
337
+ success: true,
338
+ data: {
339
+ ...project,
340
+ progressByMode,
341
+ userProgress,
342
+ },
343
+ });
344
+ } catch (error: any) {
345
+ console.error("Get project error:", error);
346
+ res.status(500).json({ success: false, message: error.message });
347
+ }
348
+ };
349
+
350
+ // User Progress Endpoints
351
+ export const startProject = async (req: Request, res: Response) => {
352
+ const { id: projectId } = req.params;
353
+ const userId = (req as any).user?.id;
354
+ const { difficultyModeChosen } = req.body;
355
+
356
+ try {
357
+ const mode = (difficultyModeChosen as DifficultyMode) || "STANDARD";
358
+
359
+ const existingProgress = await prisma.userProject.findUnique({
360
+ where: {
361
+ userId_projectId_difficultyModeChosen: {
362
+ userId,
363
+ projectId,
364
+ difficultyModeChosen: mode,
365
+ },
366
+ },
367
+ });
368
+
369
+ if (existingProgress) {
370
+ return res.status(400).json({
371
+ success: false,
372
+ message: "Project already started in this mode",
373
+ });
374
+ }
375
+
376
+ const progress = await prisma.userProject.create({
377
+ data: {
378
+ userId,
379
+ projectId,
380
+ difficultyModeChosen: mode,
381
+ status: "IN_PROGRESS",
382
+ },
383
+ });
384
+
385
+ res.status(201).json({ success: true, data: progress });
386
+ } catch (error: any) {
387
+ res.status(500).json({ success: false, message: error.message });
388
+ }
389
+ };
390
+
391
+ export const updateProgress = async (req: Request, res: Response) => {
392
+ const { id: projectId } = req.params;
393
+ const userId = (req as any).user?.id;
394
+ const { status, repoUrl, difficultyMode } = req.body;
395
+
396
+ try {
397
+ const progress = await prisma.userProject.update({
398
+ where: {
399
+ userId_projectId_difficultyModeChosen: {
400
+ userId,
401
+ projectId,
402
+ difficultyModeChosen: difficultyMode as DifficultyMode,
403
+ },
404
+ },
405
+ data: { status: status as ProjectStatus, repoUrl },
406
+ });
407
+
408
+ res.json({ success: true, data: progress });
409
+ } catch (error: any) {
410
+ res.status(500).json({ success: false, message: error.message });
411
+ }
412
+ };
413
+
414
+ export const completeProject = async (req: Request, res: Response) => {
415
+ const { id: projectId } = req.params;
416
+ const userId = (req as any).user?.id;
417
+ const { repoUrl, difficultyMode } = req.body;
418
+
419
+ try {
420
+ const progress = await prisma.userProject.update({
421
+ where: {
422
+ userId_projectId_difficultyModeChosen: {
423
+ userId,
424
+ projectId,
425
+ difficultyModeChosen: difficultyMode as DifficultyMode,
426
+ },
427
+ },
428
+ data: {
429
+ status: "COMPLETED",
430
+ completedAt: new Date(),
431
+ repoUrl,
432
+ },
433
+ });
434
+
435
+ // Award XP (simple example)
436
+ await prisma.user.update({
437
+ where: { id: userId },
438
+ data: { xp: { increment: 100 } },
439
+ });
440
+
441
+ // Update project completion rate
442
+ const totalStarted = await prisma.userProject.count({
443
+ where: { projectId },
444
+ });
445
+ const totalCompleted = await prisma.userProject.count({
446
+ where: { projectId, status: "COMPLETED" },
447
+ });
448
+
449
+ await prisma.project.update({
450
+ where: { id: projectId },
451
+ data: {
452
+ completionRate: (totalCompleted / totalStarted) * 100,
453
+ submissionCount: { increment: 1 },
454
+ },
455
+ });
456
+
457
+ res.json({ success: true, data: progress });
458
+ } catch (error: any) {
459
+ res.status(500).json({ success: false, message: error.message });
460
+ }
461
+ };
462
+
463
+ export const getUserProgress = async (req: Request, res: Response) => {
464
+ const userId = (req as any).user?.id;
465
+
466
+ try {
467
+ const progress = await prisma.userProject.findMany({
468
+ where: { userId },
469
+ include: {
470
+ project: {
471
+ include: {
472
+ milestones: {
473
+ orderBy: { milestoneNumber: "asc" },
474
+ },
475
+ createdBy: {
476
+ select: { firstName: true, lastName: true, avatarUrl: true },
477
+ },
478
+ },
479
+ },
480
+ // Correct relation name from schema.prisma
481
+ milestoneProgress: {
482
+ select: { milestoneId: true, completedAt: true },
483
+ },
484
+ },
485
+ });
486
+
487
+ res.json({ success: true, data: progress });
488
+ } catch (error: any) {
489
+ res.status(500).json({ success: false, message: error.message });
490
+ }
491
+ };
492
+
493
+ // Submissions
494
+ export const submitProject = async (req: Request, res: Response) => {
495
+ const { id: projectId } = req.params;
496
+ const userId = (req as any).user?.id;
497
+ const { repoUrl } = req.body;
498
+
499
+ try {
500
+ const submission = await prisma.submission.create({
501
+ data: {
502
+ userId,
503
+ projectId,
504
+ repoUrl,
505
+ },
506
+ });
507
+
508
+ res.status(201).json({ success: true, data: submission });
509
+ } catch (error: any) {
510
+ res.status(500).json({ success: false, message: error.message });
511
+ }
512
+ };
513
+
514
+ export const completeMilestone = async (req: Request, res: Response) => {
515
+ const { id: projectId, milestoneId } = req.params;
516
+ const userId = (req as any).user?.id;
517
+ const { difficultyMode } = req.body;
518
+
519
+ try {
520
+ const userProject = await prisma.userProject.findUnique({
521
+ where: {
522
+ userId_projectId_difficultyModeChosen: {
523
+ userId,
524
+ projectId,
525
+ difficultyModeChosen: difficultyMode as DifficultyMode,
526
+ },
527
+ },
528
+ });
529
+
530
+ if (!userProject) {
531
+ return res.status(400).json({
532
+ success: false,
533
+ message: "Project not started by user in this mode",
534
+ });
535
+ }
536
+
537
+ const existingMilestone = await prisma.userMilestone.findUnique({
538
+ where: {
539
+ userId_milestoneId_userProjectId: {
540
+ userId,
541
+ milestoneId,
542
+ userProjectId: userProject.id,
543
+ },
544
+ },
545
+ });
546
+
547
+ if (existingMilestone) {
548
+ return res.status(400).json({
549
+ success: false,
550
+ message: "Milestone already completed in this mode",
551
+ });
552
+ }
553
+
554
+ // 1. Create the milestone completion record
555
+ const milestone = await prisma.userMilestone.create({
556
+ data: {
557
+ userId,
558
+ milestoneId,
559
+ userProjectId: userProject.id,
560
+ },
561
+ });
562
+
563
+ // 2. CHECK FOR PROJECT COMPLETION
564
+ const totalMilestones = await prisma.projectMilestone.count({
565
+ where: { projectId },
566
+ });
567
+
568
+ const completedCount = await prisma.userMilestone.count({
569
+ where: {
570
+ userId,
571
+ userProjectId: userProject.id,
572
+ },
573
+ });
574
+
575
+ // 3. AUTO-COMPLETE PROJECT if all milestones are done
576
+ if (completedCount === totalMilestones) {
577
+ await prisma.userProject.update({
578
+ where: { id: userProject.id },
579
+ data: {
580
+ status: "COMPLETED",
581
+ completedAt: new Date(),
582
+ },
583
+ });
584
+
585
+ await prisma.user.update({
586
+ where: { id: userId },
587
+ data: { xp: { increment: 50 } },
588
+ });
589
+ }
590
+
591
+ res.status(201).json({ success: true, data: milestone });
592
+ } catch (error: any) {
593
+ res.status(500).json({ success: false, message: error.message });
594
+ }
595
+ };
596
+
597
+ export const getFeaturedProjects = async (req: Request, res: Response) => {
598
+ try {
599
+ const featuredProjects = await prisma.project.findMany({
600
+ take: 4,
601
+ orderBy: { submissionCount: "desc" },
602
+ include: {
603
+ createdBy: {
604
+ select: { firstName: true, lastName: true },
605
+ },
606
+ },
607
+ });
608
+
609
+ res.json({ success: true, data: featuredProjects });
610
+ } catch (error: any) {
611
+ res.status(500).json({ success: false, message: error.message });
612
+ }
613
+ };
614
+
615
+ export const submitCode = async (req: Request, res: Response) => {
616
+ const userId = (req as any).user?.id;
617
+ const { projectId, milestoneId, code, language } = req.body;
618
+
619
+ try {
620
+ // Fetch milestone details for validation criteria
621
+ const milestone = await prisma.projectMilestone.findUnique({
622
+ where: { id: milestoneId },
623
+ include: { project: true },
624
+ });
625
+
626
+ if (!milestone) {
627
+ return res.status(404).json({
628
+ success: false,
629
+ message: "Milestone not found",
630
+ });
631
+ }
632
+
633
+ // AI-powered code review
634
+ const reviewPrompt = `
635
+ You are a code reviewer for a learning platform. Review this code submission:
636
+
637
+ Project: ${milestone.project.title}
638
+ Milestone: ${milestone.title}
639
+ Description: ${milestone.description}
640
+ Validation Criteria: ${milestone.validationCriteria || "General best practices"}
641
+
642
+ Code:
643
+ \`\`\`${language}
644
+ ${code}
645
+ \`\`\`
646
+
647
+ Provide:
648
+ 1. Does this code meet the milestone requirements? (YES/NO)
649
+ 2. Code quality score (0-100)
650
+ 3. Specific feedback on what's good
651
+ 4. Areas for improvement
652
+ 5. Security concerns (if any)
653
+
654
+ Format as JSON:
655
+ {
656
+ "meetsRequirements": boolean,
657
+ "score": number,
658
+ "strengths": string[],
659
+ "improvements": string[],
660
+ "securityIssues": string[]
661
+ }
662
+ `;
663
+
664
+ const reviewResult = await getGeminiResponse(reviewPrompt);
665
+
666
+ // Parse AI response
667
+ let parsedReview;
668
+ try {
669
+ // Extract JSON from potential markdown code blocks
670
+ const jsonMatch = reviewResult.match(/```json\n?([\s\S]*?)\n?```/);
671
+ const jsonStr = jsonMatch ? jsonMatch[1] : reviewResult;
672
+ parsedReview = JSON.parse(jsonStr);
673
+ } catch (e) {
674
+ parsedReview = {
675
+ meetsRequirements: false,
676
+ score: 0,
677
+ strengths: [],
678
+ improvements: ["Unable to parse AI review"],
679
+ securityIssues: [],
680
+ };
681
+ }
682
+
683
+ // Save code submission
684
+ const submission = await prisma.submission.create({
685
+ data: {
686
+ userId,
687
+ projectId,
688
+ repoUrl: `code-submission-${Date.now()}`, // Placeholder
689
+ feedback: JSON.stringify(parsedReview),
690
+ score: parsedReview.score,
691
+ aiReviewData: parsedReview,
692
+ },
693
+ });
694
+
695
+ res.json({
696
+ success: true,
697
+ data: {
698
+ submission,
699
+ review: parsedReview,
700
+ },
701
+ });
702
+ } catch (error: any) {
703
+ console.error("Code submission error:", error);
704
+ res.status(500).json({ success: false, message: error.message });
705
+ }
706
+ };
controllers/social/index.ts ADDED
@@ -0,0 +1,187 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Request, Response } from "express";
2
+ import prisma from "../../prisma/client";
3
+
4
+ export const followUser = async (req: Request, res: Response) => {
5
+ const userId = (req as any).user?.id;
6
+ const { followingId } = req.body;
7
+
8
+ try {
9
+ if (userId === followingId) {
10
+ return res
11
+ .status(400)
12
+ .json({ success: false, message: "Cannot follow yourself" });
13
+ }
14
+
15
+ const existingFollow = await prisma.follow.findUnique({
16
+ where: { followerId_followingId: { followerId: userId, followingId } },
17
+ });
18
+
19
+ if (existingFollow) {
20
+ return res
21
+ .status(400)
22
+ .json({ success: false, message: "Already following this user" });
23
+ }
24
+
25
+ const follow = await prisma.follow.create({
26
+ data: { followerId: userId, followingId },
27
+ });
28
+
29
+ await prisma.notification.create({
30
+ data: {
31
+ userId: followingId,
32
+ type: "FOLLOW",
33
+ title: "New Follower",
34
+ message: "Someone started following you",
35
+ link: `/profile/${userId}`,
36
+ },
37
+ });
38
+
39
+ res.json({ success: true, data: follow });
40
+ } catch (error: any) {
41
+ res.status(500).json({ success: false, message: error.message });
42
+ }
43
+ };
44
+
45
+ export const unfollowUser = async (req: Request, res: Response) => {
46
+ const userId = (req as any).user?.id;
47
+ const { followingId } = req.params;
48
+
49
+ try {
50
+ await prisma.follow.delete({
51
+ where: { followerId_followingId: { followerId: userId, followingId } },
52
+ });
53
+
54
+ res.json({ success: true, message: "Unfollowed successfully" });
55
+ } catch (error: any) {
56
+ res.status(500).json({ success: false, message: error.message });
57
+ }
58
+ };
59
+
60
+ export const getFollowers = async (req: Request, res: Response) => {
61
+ const { userId } = req.params;
62
+
63
+ try {
64
+ const followers = await prisma.follow.findMany({
65
+ where: { followingId: userId },
66
+ include: {
67
+ follower: {
68
+ select: {
69
+ id: true,
70
+ firstName: true,
71
+ lastName: true,
72
+ avatarUrl: true,
73
+ xp: true,
74
+ },
75
+ },
76
+ },
77
+ });
78
+
79
+ res.json({ success: true, data: followers });
80
+ } catch (error: any) {
81
+ res.status(500).json({ success: false, message: error.message });
82
+ }
83
+ };
84
+
85
+ export const getFollowing = async (req: Request, res: Response) => {
86
+ const { userId } = req.params;
87
+
88
+ try {
89
+ const following = await prisma.follow.findMany({
90
+ where: { followerId: userId },
91
+ include: {
92
+ following: {
93
+ select: {
94
+ id: true,
95
+ firstName: true,
96
+ lastName: true,
97
+ avatarUrl: true,
98
+ xp: true,
99
+ },
100
+ },
101
+ },
102
+ });
103
+
104
+ res.json({ success: true, data: following });
105
+ } catch (error: any) {
106
+ res.status(500).json({ success: false, message: error.message });
107
+ }
108
+ };
109
+
110
+ export const getActivityFeed = async (req: Request, res: Response) => {
111
+ const userId = (req as any).user?.id;
112
+ const { page = 1, limit = 20 } = req.query;
113
+
114
+ try {
115
+ const following = await prisma.follow.findMany({
116
+ where: { followerId: userId },
117
+ select: { followingId: true },
118
+ });
119
+
120
+ const followingIds = following.map((f) => f.followingId);
121
+
122
+ const activities = await prisma.userProject.findMany({
123
+ where: {
124
+ userId: { in: followingIds },
125
+ status: "COMPLETED",
126
+ },
127
+ include: {
128
+ user: {
129
+ select: {
130
+ id: true,
131
+ firstName: true,
132
+ lastName: true,
133
+ avatarUrl: true,
134
+ },
135
+ },
136
+ project: {
137
+ select: {
138
+ id: true,
139
+ title: true,
140
+ slug: true,
141
+ },
142
+ },
143
+ },
144
+ orderBy: { completedAt: "desc" },
145
+ take: Number(limit),
146
+ skip: (Number(page) - 1) * Number(limit),
147
+ });
148
+
149
+ res.json({ success: true, data: activities });
150
+ } catch (error: any) {
151
+ res.status(500).json({ success: false, message: error.message });
152
+ }
153
+ };
154
+
155
+ export const shareProject = async (req: Request, res: Response) => {
156
+ const userId = (req as any).user?.id;
157
+ const { projectId, platform } = req.body;
158
+
159
+ try {
160
+ const project = await prisma.project.findUnique({
161
+ where: { id: projectId },
162
+ });
163
+
164
+ if (!project) {
165
+ return res
166
+ .status(404)
167
+ .json({ success: false, message: "Project not found" });
168
+ }
169
+
170
+ const shareUrl = `${process.env.CLIENT_URL}/projects/${project.slug}`;
171
+ const ogImageUrl = `${process.env.CLIENT_URL}/api/og-image/${project.slug}`;
172
+
173
+ const share = await prisma.socialShare.create({
174
+ data: {
175
+ userId,
176
+ projectId,
177
+ platform,
178
+ shareUrl,
179
+ ogImageUrl,
180
+ },
181
+ });
182
+
183
+ res.json({ success: true, data: { shareUrl, ogImageUrl, share } });
184
+ } catch (error: any) {
185
+ res.status(500).json({ success: false, message: error.message });
186
+ }
187
+ };
controllers/teams/index.ts ADDED
@@ -0,0 +1,80 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Request, Response, NextFunction } from "express";
2
+ import prisma from "../../prisma/client";
3
+
4
+ export const createTeam = async (
5
+ req: Request,
6
+ res: Response,
7
+ next: NextFunction
8
+ ) => {
9
+ const { name, projectId } = req.body;
10
+ const userId = (req as any).user?.id;
11
+
12
+ try {
13
+ const team = await prisma.team.create({
14
+ data: {
15
+ name,
16
+ projectId,
17
+ members: {
18
+ create: {
19
+ userId,
20
+ },
21
+ },
22
+ },
23
+ include: {
24
+ members: true,
25
+ },
26
+ });
27
+
28
+ res.status(201).json({ success: true, data: team });
29
+ } catch (error: any) {
30
+ next(error);
31
+ }
32
+ };
33
+
34
+ export const joinTeam = async (
35
+ req: Request,
36
+ res: Response,
37
+ next: NextFunction
38
+ ) => {
39
+ const { id: teamId } = req.params;
40
+ const userId = (req as any).user?.id;
41
+
42
+ try {
43
+ const teamMember = await prisma.teamMember.create({
44
+ data: {
45
+ teamId,
46
+ userId,
47
+ },
48
+ });
49
+
50
+ res.status(201).json({ success: true, data: teamMember });
51
+ } catch (error: any) {
52
+ next(error);
53
+ }
54
+ };
55
+
56
+ export const getTeamProjects = async (
57
+ req: Request,
58
+ res: Response,
59
+ next: NextFunction
60
+ ) => {
61
+ const { id: teamId } = req.params;
62
+
63
+ try {
64
+ const team = await prisma.team.findUnique({
65
+ where: { id: teamId },
66
+ include: {
67
+ project: true,
68
+ },
69
+ });
70
+
71
+ if (!team)
72
+ return res
73
+ .status(404)
74
+ .json({ success: false, message: "Team not found" });
75
+
76
+ res.json({ success: true, data: team.project });
77
+ } catch (error: any) {
78
+ next(error);
79
+ }
80
+ };
dist/controllers/ai/index.js ADDED
@@ -0,0 +1,146 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.requestHint = exports.getChatHistory = exports.getAllChatMessages = exports.chatWithAI = void 0;
7
+ const client_1 = __importDefault(require("../../prisma/client"));
8
+ const gemini_1 = require("../../utils/gemini");
9
+ const chatWithAI = async (req, res) => {
10
+ var _a, _b, _c;
11
+ const userId = (_a = req.user) === null || _a === void 0 ? void 0 : _a.id;
12
+ const { message, projectId } = req.body;
13
+ try {
14
+ // Fetch project context if projectId is provided
15
+ let projectContext = "";
16
+ let difficultyMode = null;
17
+ if (projectId) {
18
+ const project = await client_1.default.project.findUnique({
19
+ where: { id: projectId },
20
+ include: {
21
+ milestones: true,
22
+ userProjects: { where: { userId } },
23
+ },
24
+ });
25
+ if (project) {
26
+ difficultyMode =
27
+ ((_c = (_b = project.userProjects) === null || _b === void 0 ? void 0 : _b[0]) === null || _c === void 0 ? void 0 : _c.difficultyModeChosen) || "STANDARD";
28
+ projectContext = `The user is working on the project: "${project.title}".
29
+ Description: ${project.description}.
30
+ Difficulty Level: ${project.difficultyLevel}.
31
+ User's chosen mode: ${difficultyMode}.
32
+ Milestones: ${project.milestones
33
+ .map((m) => `${m.milestoneNumber}. ${m.title}`)
34
+ .join(", ")}.`;
35
+ }
36
+ }
37
+ // Fetch chat history from DB
38
+ const history = await client_1.default.chatMessage.findMany({
39
+ where: { userId, projectId: projectId || null },
40
+ orderBy: { createdAt: "asc" },
41
+ take: 20,
42
+ });
43
+ const geminiHistory = history.map((msg) => ({
44
+ role: msg.role === "user" ? "user" : "model",
45
+ parts: [{ text: msg.content }],
46
+ }));
47
+ let modeInstruction = "";
48
+ if (difficultyMode === "GUIDED") {
49
+ modeInstruction =
50
+ "The user is in GUIDED mode. Be very helpful, provide detailed explanations, and don't hesitate to give small code snippets if they are stuck.";
51
+ }
52
+ else if (difficultyMode === "HARDCORE") {
53
+ modeInstruction =
54
+ "The user is in HARDCORE mode. Be very brief, provide only high-level conceptual hints, and never provide code solutions.";
55
+ }
56
+ else {
57
+ modeInstruction =
58
+ "The user is in STANDARD mode. Provide balanced guidance, focus on concepts, and only provide code as a last resort.";
59
+ }
60
+ const systemPrompt = `You are an AI Guide for a project-based learning platform.
61
+ Your goal is to help students learn by providing guidance, not just giving away answers.
62
+ ${projectContext}
63
+ ${modeInstruction}
64
+ Keep your responses concise and educational.`;
65
+ const fullPrompt = `${systemPrompt}\n\nUser: ${message}`;
66
+ const aiResponse = await (0, gemini_1.getGeminiResponse)(fullPrompt, geminiHistory);
67
+ // Save messages to DB
68
+ await client_1.default.chatMessage.createMany({
69
+ data: [
70
+ {
71
+ userId,
72
+ projectId: projectId || null,
73
+ role: "user",
74
+ content: message,
75
+ },
76
+ {
77
+ userId,
78
+ projectId: projectId || null,
79
+ role: "assistant",
80
+ content: aiResponse,
81
+ },
82
+ ],
83
+ });
84
+ res.json({ success: true, data: aiResponse });
85
+ }
86
+ catch (error) {
87
+ res.status(500).json({ success: false, message: error.message });
88
+ }
89
+ };
90
+ exports.chatWithAI = chatWithAI;
91
+ const getAllChatMessages = async (req, res) => {
92
+ var _a;
93
+ const userId = (_a = req.user) === null || _a === void 0 ? void 0 : _a.id;
94
+ try {
95
+ const messages = await client_1.default.chatMessage.findMany({
96
+ where: { userId },
97
+ orderBy: { createdAt: "asc" },
98
+ });
99
+ res.json({ success: true, data: messages });
100
+ }
101
+ catch (error) {
102
+ res.status(500).json({ success: false, message: error.message });
103
+ }
104
+ };
105
+ exports.getAllChatMessages = getAllChatMessages;
106
+ const getChatHistory = async (req, res) => {
107
+ var _a;
108
+ const userId = (_a = req.user) === null || _a === void 0 ? void 0 : _a.id;
109
+ const { projectId } = req.params;
110
+ try {
111
+ const history = await client_1.default.chatMessage.findMany({
112
+ where: { userId, projectId: projectId === "null" ? null : projectId },
113
+ orderBy: { createdAt: "asc" },
114
+ });
115
+ res.json({ success: true, data: history });
116
+ }
117
+ catch (error) {
118
+ res.status(500).json({ success: false, message: error.message });
119
+ }
120
+ };
121
+ exports.getChatHistory = getChatHistory;
122
+ const requestHint = async (req, res) => {
123
+ var _a;
124
+ const userId = (_a = req.user) === null || _a === void 0 ? void 0 : _a.id;
125
+ const { projectId, milestoneNumber } = req.body;
126
+ try {
127
+ const milestone = await client_1.default.projectMilestone.findUnique({
128
+ where: { projectId_milestoneNumber: { projectId, milestoneNumber } },
129
+ include: { project: true },
130
+ });
131
+ if (!milestone)
132
+ return res
133
+ .status(404)
134
+ .json({ success: false, message: "Milestone not found" });
135
+ const prompt = `The user is stuck on Milestone ${milestoneNumber} ("${milestone.title}") of the project "${milestone.project.title}".
136
+ Milestone description: ${milestone.description}.
137
+ Existing hints: ${milestone.hints.join(", ")}.
138
+ Please provide a new, progressive hint that helps them move forward without revealing the full solution.`;
139
+ const hint = await (0, gemini_1.getGeminiResponse)(prompt);
140
+ res.json({ success: true, data: hint });
141
+ }
142
+ catch (error) {
143
+ res.status(500).json({ success: false, message: error.message });
144
+ }
145
+ };
146
+ exports.requestHint = requestHint;
dist/controllers/analytics/index.js ADDED
@@ -0,0 +1,74 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.getAdminAnalytics = exports.getUserAnalytics = void 0;
7
+ const client_1 = __importDefault(require("../../prisma/client"));
8
+ const getUserAnalytics = async (req, res, next) => {
9
+ var _a;
10
+ const userId = (_a = req.user) === null || _a === void 0 ? void 0 : _a.id;
11
+ try {
12
+ const user = await client_1.default.user.findUnique({
13
+ where: { id: userId },
14
+ select: {
15
+ xp: true,
16
+ streak: true,
17
+ _count: {
18
+ select: {
19
+ projects: true,
20
+ submissions: true,
21
+ achievements: true
22
+ }
23
+ }
24
+ }
25
+ });
26
+ const projectCompletion = await client_1.default.userProject.groupBy({
27
+ by: ["status"],
28
+ where: { userId },
29
+ _count: true
30
+ });
31
+ res.json({
32
+ success: true,
33
+ data: {
34
+ stats: user,
35
+ projectCompletion
36
+ }
37
+ });
38
+ }
39
+ catch (error) {
40
+ next(error);
41
+ }
42
+ };
43
+ exports.getUserAnalytics = getUserAnalytics;
44
+ const getAdminAnalytics = async (req, res, next) => {
45
+ var _a;
46
+ const userId = (_a = req.user) === null || _a === void 0 ? void 0 : _a.id;
47
+ try {
48
+ const user = await client_1.default.user.findUnique({ where: { id: userId } });
49
+ if ((user === null || user === void 0 ? void 0 : user.role) !== "ADMIN") {
50
+ return res.status(403).json({ success: false, message: "Forbidden" });
51
+ }
52
+ const totalUsers = await client_1.default.user.count();
53
+ const totalProjects = await client_1.default.project.count();
54
+ const totalSubmissions = await client_1.default.submission.count();
55
+ const popularProjects = await client_1.default.project.findMany({
56
+ orderBy: { submissionCount: "desc" },
57
+ take: 5,
58
+ select: { title: true, submissionCount: true }
59
+ });
60
+ res.json({
61
+ success: true,
62
+ data: {
63
+ totalUsers,
64
+ totalProjects,
65
+ totalSubmissions,
66
+ popularProjects
67
+ }
68
+ });
69
+ }
70
+ catch (error) {
71
+ next(error);
72
+ }
73
+ };
74
+ exports.getAdminAnalytics = getAdminAnalytics;
dist/controllers/auth/index.js ADDED
@@ -0,0 +1,397 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.githubAuthCallback = exports.googleAuthCallback = exports.updateUser = exports.getUser = exports.resetPassword = exports.verifyOTP = exports.forgetPassword = exports.me = exports.logout = exports.login = exports.signUp = void 0;
40
+ const client_1 = __importDefault(require("../../prisma/client"));
41
+ const argon2 = __importStar(require("argon2"));
42
+ const email_1 = require("../../middlewares/email");
43
+ const generateOTP_1 = require("../../middlewares/generateOTP");
44
+ const jsonwebtoken_1 = __importDefault(require("jsonwebtoken"));
45
+ const dotenv_1 = __importDefault(require("dotenv"));
46
+ const redis_1 = __importDefault(require("../../utils/redis"));
47
+ dotenv_1.default.config();
48
+ const JWT_SECRET = process.env.JWT_SECRET || "default_secret";
49
+ const signUp = async (req, res) => {
50
+ const { firstName, lastName, skillLevel, email, password, role } = req.body;
51
+ try {
52
+ const existingUser = await client_1.default.user.findUnique({ where: { email } });
53
+ if (existingUser) {
54
+ return res
55
+ .status(400)
56
+ .json({ success: false, message: "Email already registered" });
57
+ }
58
+ const hashedPassword = await argon2.hash(password);
59
+ const user = await client_1.default.user.create({
60
+ data: {
61
+ firstName,
62
+ lastName,
63
+ email,
64
+ skillLevel: skillLevel || "BEGINNER",
65
+ password: hashedPassword,
66
+ role: role || "STUDENT",
67
+ },
68
+ });
69
+ res.json({
70
+ success: true,
71
+ message: "User registered successfully",
72
+ data: { id: user.id },
73
+ });
74
+ }
75
+ catch (error) {
76
+ res.status(500).json({ success: false, message: error.message });
77
+ }
78
+ };
79
+ exports.signUp = signUp;
80
+ const login = async (req, res) => {
81
+ const { email, password } = req.body;
82
+ try {
83
+ const user = await client_1.default.user.findUnique({ where: { email } });
84
+ if (!user || !user.password) {
85
+ return res
86
+ .status(401)
87
+ .json({ success: false, message: "Invalid Email or Password" });
88
+ }
89
+ const verifyPassword = await argon2.verify(user.password, password);
90
+ if (!verifyPassword) {
91
+ return res
92
+ .status(401)
93
+ .json({ success: false, message: "Invalid Email or Password" });
94
+ }
95
+ const token = jsonwebtoken_1.default.sign({ id: user.id, role: user.role }, JWT_SECRET, {
96
+ expiresIn: "7d",
97
+ });
98
+ res.cookie("token", token, {
99
+ httpOnly: true,
100
+ secure: process.env.NODE_ENV === "production",
101
+ sameSite: "strict",
102
+ maxAge: 7 * 24 * 60 * 60 * 1000, // 7 days
103
+ });
104
+ res.json({
105
+ success: true,
106
+ message: "Login successful",
107
+ data: {
108
+ id: user.id,
109
+ firstName: user.firstName,
110
+ lastName: user.lastName,
111
+ email: user.email,
112
+ role: user.role,
113
+ token,
114
+ },
115
+ });
116
+ }
117
+ catch (error) {
118
+ res.status(500).json({ success: false, message: error.message });
119
+ }
120
+ };
121
+ exports.login = login;
122
+ const logout = async (req, res) => {
123
+ res.clearCookie("token");
124
+ res.json({ success: true, message: "Logged out successfully" });
125
+ };
126
+ exports.logout = logout;
127
+ const me = async (req, res) => {
128
+ var _a;
129
+ const userId = (_a = req.user) === null || _a === void 0 ? void 0 : _a.id;
130
+ if (!userId) {
131
+ return res.status(401).json({ success: false, message: "Unauthorized" });
132
+ }
133
+ try {
134
+ const user = await client_1.default.user.findUnique({
135
+ where: { id: userId },
136
+ select: {
137
+ id: true,
138
+ email: true,
139
+ firstName: true,
140
+ lastName: true,
141
+ skillLevel: true,
142
+ bio: true,
143
+ portfolioLinks: true,
144
+ xp: true,
145
+ streak: true,
146
+ role: true,
147
+ avatarUrl: true,
148
+ createdAt: true,
149
+ },
150
+ });
151
+ if (!user) {
152
+ return res
153
+ .status(404)
154
+ .json({ success: false, message: "User not found" });
155
+ }
156
+ res.json({ success: true, data: user });
157
+ }
158
+ catch (error) {
159
+ res.status(500).json({ success: false, message: error.message });
160
+ }
161
+ };
162
+ exports.me = me;
163
+ const forgetPassword = async (req, res) => {
164
+ const { email } = req.body;
165
+ const appName = "Dev Drill";
166
+ try {
167
+ const user = await client_1.default.user.findUnique({ where: { email } });
168
+ if (!user) {
169
+ return res
170
+ .status(404)
171
+ .json({ success: false, message: "Account is not registered" });
172
+ }
173
+ const { otp, otpDate } = (0, generateOTP_1.generateOTP)();
174
+ // Store OTP in Redis with 1 hour expiry
175
+ await redis_1.default.setex(`otp:${email}`, 3600, otp.toString());
176
+ const htmlMessage = `
177
+ <div style="font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto;">
178
+ <div style="background: #f0f7ff; padding: 30px; border-radius: 10px;">
179
+ <h1 style="color: #1a237e; margin-bottom: 25px;">${appName}</h1>
180
+ <p style="font-size: 16px; color: #333;">
181
+ Hello ${user.lastName},<br><br>
182
+ We received a request to reset your password for your ${appName} account.
183
+ </p>
184
+ <div style="background: #fff; padding: 20px; border-radius: 5px; margin: 20px 0; text-align: center;">
185
+ <p style="margin: 0; font-weight: bold; font-size: 24px; letter-spacing: 2px;">${otp}</p>
186
+ </div>
187
+ <p style="font-size: 14px; color: #666;">
188
+ This verification code will expire in 1 hour.<br>
189
+ If you didn't request this password reset, please ignore this email.
190
+ </p>
191
+ </div>
192
+ <p style="font-size: 12px; color: #999; text-align: center; margin-top: 20px;">
193
+ © ${new Date().getFullYear()} ${appName}. All rights reserved.
194
+ </p>
195
+ </div>
196
+ `;
197
+ const textMessage = `${appName} Password Reset\n\n` +
198
+ `Hello ${user.lastName},\n\n` +
199
+ `Use this OTP to reset your password: ${otp}\n` +
200
+ `This code expires in 1 hour.`;
201
+ (0, email_1.sendEmail)(user.email, `Password Reset Request - ${appName}`, htmlMessage, textMessage);
202
+ res.json({
203
+ success: true,
204
+ message: `An OTP has been sent to your mail. Check your mail for your verification code`,
205
+ });
206
+ }
207
+ catch (error) {
208
+ res.status(500).json({ success: false, message: error.message });
209
+ }
210
+ };
211
+ exports.forgetPassword = forgetPassword;
212
+ const verifyOTP = async (req, res) => {
213
+ const { email, otp: userOtp } = req.body;
214
+ try {
215
+ const storedOtp = await redis_1.default.get(`otp:${email}`);
216
+ if (!storedOtp) {
217
+ return res
218
+ .status(400)
219
+ .json({ success: false, message: "OTP expired or not found" });
220
+ }
221
+ if (storedOtp !== userOtp.toString()) {
222
+ return res
223
+ .status(400)
224
+ .json({ success: false, message: "Invalid Verification code!" });
225
+ }
226
+ // OTP verified, can remove it now
227
+ await redis_1.default.del(`otp:${email}`);
228
+ // Optionally set a flag in Redis that OTP was verified for this email to allow password reset
229
+ await redis_1.default.setex(`otp_verified:${email}`, 600, "true");
230
+ res.json({ success: true, message: "Verification successful" });
231
+ }
232
+ catch (error) {
233
+ res.status(500).json({ success: false, message: error.message });
234
+ }
235
+ };
236
+ exports.verifyOTP = verifyOTP;
237
+ const resetPassword = async (req, res) => {
238
+ const { email, newPassword } = req.body;
239
+ try {
240
+ const isVerified = await redis_1.default.get(`otp_verified:${email}`);
241
+ if (!isVerified) {
242
+ return res
243
+ .status(400)
244
+ .json({ success: false, message: "OTP not verified" });
245
+ }
246
+ const user = await client_1.default.user.findUnique({ where: { email } });
247
+ if (!user)
248
+ return res
249
+ .status(404)
250
+ .json({ success: false, message: "User not found" });
251
+ if (user.password) {
252
+ const verifyPassword = await argon2.verify(user.password, newPassword);
253
+ if (verifyPassword) {
254
+ return res
255
+ .status(400)
256
+ .json({ success: false, message: "You entered your old password" });
257
+ }
258
+ }
259
+ const newPass = await argon2.hash(newPassword);
260
+ await client_1.default.user.update({
261
+ where: { email },
262
+ data: { password: newPass },
263
+ });
264
+ await redis_1.default.del(`otp_verified:${email}`);
265
+ res.json({ success: true, message: "Password successfully reset" });
266
+ }
267
+ catch (error) {
268
+ res.status(500).json({ success: false, message: error.message });
269
+ }
270
+ };
271
+ exports.resetPassword = resetPassword;
272
+ const getUser = async (req, res) => {
273
+ const { id } = req.params;
274
+ try {
275
+ const user = await client_1.default.user.findUnique({
276
+ where: { id },
277
+ select: {
278
+ id: true,
279
+ email: true,
280
+ firstName: true,
281
+ lastName: true,
282
+ skillLevel: true,
283
+ bio: true,
284
+ portfolioLinks: true,
285
+ xp: true,
286
+ streak: true,
287
+ role: true,
288
+ avatarUrl: true,
289
+ createdAt: true,
290
+ },
291
+ });
292
+ if (!user)
293
+ return res
294
+ .status(404)
295
+ .json({ success: false, message: "User not found" });
296
+ res.json({ success: true, data: user });
297
+ }
298
+ catch (error) {
299
+ res.status(500).json({ success: false, message: error.message });
300
+ }
301
+ };
302
+ exports.getUser = getUser;
303
+ const updateUser = async (req, res) => {
304
+ const { id } = req.params;
305
+ let { firstName, lastName, email, password, bio, skillLevel, portfolioLinks, avatarUrl, } = req.body;
306
+ try {
307
+ const user = await client_1.default.user.findUnique({ where: { id } });
308
+ if (!user) {
309
+ return res
310
+ .status(404)
311
+ .json({ success: false, message: "User not found" });
312
+ }
313
+ const data = {
314
+ firstName,
315
+ lastName,
316
+ bio,
317
+ skillLevel,
318
+ portfolioLinks,
319
+ avatarUrl,
320
+ };
321
+ if (email && email !== user.email) {
322
+ const existingUser = await client_1.default.user.findUnique({ where: { email } });
323
+ if (existingUser) {
324
+ return res
325
+ .status(400)
326
+ .json({ success: false, message: "Email already in use" });
327
+ }
328
+ data.email = email;
329
+ }
330
+ if (password) {
331
+ data.password = await argon2.hash(password);
332
+ }
333
+ const updatedUser = await client_1.default.user.update({
334
+ where: { googleId: user.googleId || undefined },
335
+ data,
336
+ });
337
+ res.json({
338
+ success: true,
339
+ data: {
340
+ id: updatedUser.id,
341
+ firstName: updatedUser.firstName,
342
+ lastName: updatedUser.lastName,
343
+ email: updatedUser.email,
344
+ role: updatedUser.role,
345
+ skillLevel: updatedUser.skillLevel,
346
+ },
347
+ });
348
+ }
349
+ catch (error) {
350
+ res.status(500).json({ success: false, message: error.message });
351
+ }
352
+ };
353
+ exports.updateUser = updateUser;
354
+ const googleAuthCallback = async (req, res) => {
355
+ try {
356
+ const user = req.user;
357
+ if (!user) {
358
+ return res.redirect(`${process.env.CLIENT_URL}/auth/signin?error=auth_failed`);
359
+ }
360
+ const token = jsonwebtoken_1.default.sign({ id: user.id, role: user.role }, JWT_SECRET, {
361
+ expiresIn: "7d",
362
+ });
363
+ res.cookie("token", token, {
364
+ httpOnly: true,
365
+ secure: process.env.NODE_ENV === "production",
366
+ sameSite: "lax",
367
+ maxAge: 7 * 24 * 60 * 60 * 1000,
368
+ });
369
+ res.redirect(`${process.env.CLIENT_URL}/dashboard`);
370
+ }
371
+ catch (error) {
372
+ res.redirect(`${process.env.CLIENT_URL}/auth/signin?error=server_error`);
373
+ }
374
+ };
375
+ exports.googleAuthCallback = googleAuthCallback;
376
+ const githubAuthCallback = async (req, res) => {
377
+ try {
378
+ const user = req.user;
379
+ if (!user) {
380
+ return res.redirect(`${process.env.CLIENT_URL}/auth/signin?error=auth_failed`);
381
+ }
382
+ const token = jsonwebtoken_1.default.sign({ id: user.id, role: user.role }, JWT_SECRET, {
383
+ expiresIn: "7d",
384
+ });
385
+ res.cookie("token", token, {
386
+ httpOnly: true,
387
+ secure: process.env.NODE_ENV === "production",
388
+ sameSite: "lax",
389
+ maxAge: 7 * 24 * 60 * 60 * 1000,
390
+ });
391
+ res.redirect(`${process.env.CLIENT_URL}/dashboard`);
392
+ }
393
+ catch (error) {
394
+ res.redirect(`${process.env.CLIENT_URL}/auth/signin?error=server_error`);
395
+ }
396
+ };
397
+ exports.githubAuthCallback = githubAuthCallback;
dist/controllers/code-review/index.js ADDED
@@ -0,0 +1,70 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.getSubmissionReview = exports.analyzeCode = void 0;
7
+ const client_1 = __importDefault(require("../../prisma/client"));
8
+ const gemini_1 = require("../../utils/gemini");
9
+ const axios_1 = __importDefault(require("axios"));
10
+ const analyzeCode = async (req, res) => {
11
+ var _a;
12
+ const userId = (_a = req.user) === null || _a === void 0 ? void 0 : _a.id;
13
+ const { repoUrl, projectId } = req.body;
14
+ try {
15
+ // 1. Fetch code from GitHub (simplified: fetch README or a few files for demo)
16
+ // In a real app, you'd use a GitHub App or User Token to clone or fetch via API
17
+ const repoPath = repoUrl.replace("https://github.com/", "");
18
+ const [owner, repo] = repoPath.split("/");
19
+ // Fetch repo info as a placeholder for analysis
20
+ const githubApiUrl = `https://api.github.com/repos/${owner}/${repo}`;
21
+ const repoInfo = await axios_1.default.get(githubApiUrl);
22
+ // 2. Automated analysis (simulated)
23
+ const metrics = {
24
+ complexity: "Medium",
25
+ securityIssues: 0,
26
+ lintErrors: 0
27
+ };
28
+ // 3. AI-powered feedback
29
+ const prompt = `Please review this GitHub repository: ${repoUrl}.
30
+ Owner: ${owner}
31
+ Repo: ${repo}
32
+ Description: ${repoInfo.data.description}
33
+ Provide constructive feedback on the project structure and suggest improvements for a student.`;
34
+ const feedback = await (0, gemini_1.getGeminiResponse)(prompt);
35
+ // 4. Store review results
36
+ const submission = await client_1.default.submission.create({
37
+ data: {
38
+ userId,
39
+ projectId,
40
+ repoUrl,
41
+ feedback,
42
+ score: 85 // Simulated score
43
+ }
44
+ });
45
+ res.json({ success: true, data: submission });
46
+ }
47
+ catch (error) {
48
+ res.status(500).json({ success: false, message: error.message });
49
+ }
50
+ };
51
+ exports.analyzeCode = analyzeCode;
52
+ const getSubmissionReview = async (req, res) => {
53
+ const { id } = req.params;
54
+ try {
55
+ const submission = await client_1.default.submission.findUnique({
56
+ where: { id },
57
+ include: {
58
+ project: { select: { title: true } },
59
+ user: { select: { firstName: true, lastName: true } }
60
+ }
61
+ });
62
+ if (!submission)
63
+ return res.status(404).json({ success: false, message: "Submission not found" });
64
+ res.json({ success: true, data: submission });
65
+ }
66
+ catch (error) {
67
+ res.status(500).json({ success: false, message: error.message });
68
+ }
69
+ };
70
+ exports.getSubmissionReview = getSubmissionReview;
dist/controllers/community/index.js ADDED
@@ -0,0 +1,156 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.createForumThread = exports.getForumThreads = exports.voteOnSubmission = exports.commentOnSubmission = exports.getPublicSubmissions = void 0;
7
+ const client_1 = __importDefault(require("../../prisma/client"));
8
+ const getPublicSubmissions = async (req, res, next) => {
9
+ try {
10
+ const submissions = await client_1.default.submission.findMany({
11
+ where: { isPublic: true },
12
+ include: {
13
+ user: {
14
+ select: {
15
+ id: true,
16
+ firstName: true,
17
+ lastName: true,
18
+ avatarUrl: true,
19
+ },
20
+ },
21
+ project: {
22
+ select: {
23
+ id: true,
24
+ title: true,
25
+ },
26
+ },
27
+ _count: {
28
+ select: {
29
+ comments: true,
30
+ votes: true,
31
+ },
32
+ },
33
+ },
34
+ orderBy: { createdAt: "desc" },
35
+ });
36
+ res.json({ success: true, data: submissions });
37
+ }
38
+ catch (error) {
39
+ next(error);
40
+ }
41
+ };
42
+ exports.getPublicSubmissions = getPublicSubmissions;
43
+ const commentOnSubmission = async (req, res, next) => {
44
+ var _a;
45
+ const { id: submissionId } = req.params;
46
+ const { content } = req.body;
47
+ const userId = (_a = req.user) === null || _a === void 0 ? void 0 : _a.id;
48
+ try {
49
+ const comment = await client_1.default.comment.create({
50
+ data: {
51
+ content,
52
+ userId,
53
+ submissionId,
54
+ },
55
+ include: {
56
+ user: {
57
+ select: {
58
+ id: true,
59
+ firstName: true,
60
+ lastName: true,
61
+ avatarUrl: true,
62
+ },
63
+ },
64
+ },
65
+ });
66
+ res.status(201).json({ success: true, data: comment });
67
+ }
68
+ catch (error) {
69
+ next(error);
70
+ }
71
+ };
72
+ exports.commentOnSubmission = commentOnSubmission;
73
+ const voteOnSubmission = async (req, res, next) => {
74
+ var _a;
75
+ const { id: submissionId } = req.params;
76
+ const { value } = req.body; // 1 for upvote, -1 for downvote
77
+ const userId = (_a = req.user) === null || _a === void 0 ? void 0 : _a.id;
78
+ try {
79
+ const vote = await client_1.default.vote.upsert({
80
+ where: {
81
+ userId_submissionId: {
82
+ userId,
83
+ submissionId,
84
+ },
85
+ },
86
+ update: { value },
87
+ create: {
88
+ value,
89
+ userId,
90
+ submissionId,
91
+ },
92
+ });
93
+ res.json({ success: true, data: vote });
94
+ }
95
+ catch (error) {
96
+ next(error);
97
+ }
98
+ };
99
+ exports.voteOnSubmission = voteOnSubmission;
100
+ const getForumThreads = async (req, res, next) => {
101
+ const { projectId } = req.query;
102
+ try {
103
+ const threads = await client_1.default.forumThread.findMany({
104
+ where: projectId ? { projectId: String(projectId) } : {},
105
+ include: {
106
+ user: {
107
+ select: {
108
+ id: true,
109
+ firstName: true,
110
+ lastName: true,
111
+ avatarUrl: true,
112
+ },
113
+ },
114
+ _count: {
115
+ select: { posts: true },
116
+ },
117
+ },
118
+ orderBy: { createdAt: "desc" },
119
+ });
120
+ res.json({ success: true, data: threads });
121
+ }
122
+ catch (error) {
123
+ next(error);
124
+ }
125
+ };
126
+ exports.getForumThreads = getForumThreads;
127
+ const createForumThread = async (req, res, next) => {
128
+ var _a;
129
+ const { title, content, projectId } = req.body;
130
+ const userId = (_a = req.user) === null || _a === void 0 ? void 0 : _a.id;
131
+ try {
132
+ const thread = await client_1.default.forumThread.create({
133
+ data: {
134
+ title,
135
+ content,
136
+ userId,
137
+ projectId: projectId || null,
138
+ },
139
+ include: {
140
+ user: {
141
+ select: {
142
+ id: true,
143
+ firstName: true,
144
+ lastName: true,
145
+ avatarUrl: true,
146
+ },
147
+ },
148
+ },
149
+ });
150
+ res.status(201).json({ success: true, data: thread });
151
+ }
152
+ catch (error) {
153
+ next(error);
154
+ }
155
+ };
156
+ exports.createForumThread = createForumThread;
dist/controllers/events/index.js ADDED
@@ -0,0 +1,202 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.getLeaderboard = exports.updateEventScore = exports.leaveEvent = exports.joinEvent = exports.getEvent = exports.getEvents = exports.createEvent = void 0;
7
+ const client_1 = __importDefault(require("../../prisma/client"));
8
+ const createEvent = async (req, res) => {
9
+ const { title, description, type, startDate, endDate, prizePool, maxParticipants, bannerUrl, } = req.body;
10
+ try {
11
+ const event = await client_1.default.event.create({
12
+ data: {
13
+ title,
14
+ description,
15
+ type,
16
+ startDate: new Date(startDate),
17
+ endDate: new Date(endDate),
18
+ prizePool,
19
+ maxParticipants,
20
+ bannerUrl,
21
+ },
22
+ });
23
+ res.json({ success: true, data: event });
24
+ }
25
+ catch (error) {
26
+ res.status(500).json({ success: false, message: error.message });
27
+ }
28
+ };
29
+ exports.createEvent = createEvent;
30
+ const getEvents = async (req, res) => {
31
+ const { status, type, page = 1, limit = 10 } = req.query;
32
+ try {
33
+ const where = {};
34
+ if (status)
35
+ where.status = status;
36
+ if (type)
37
+ where.type = type;
38
+ const events = await client_1.default.event.findMany({
39
+ where,
40
+ include: {
41
+ _count: {
42
+ select: { participants: true },
43
+ },
44
+ },
45
+ orderBy: { startDate: "desc" },
46
+ take: Number(limit),
47
+ skip: (Number(page) - 1) * Number(limit),
48
+ });
49
+ res.json({ success: true, data: events });
50
+ }
51
+ catch (error) {
52
+ res.status(500).json({ success: false, message: error.message });
53
+ }
54
+ };
55
+ exports.getEvents = getEvents;
56
+ const getEvent = async (req, res) => {
57
+ const { id } = req.params;
58
+ try {
59
+ const event = await client_1.default.event.findUnique({
60
+ where: { id },
61
+ include: {
62
+ participants: {
63
+ include: {
64
+ user: {
65
+ select: {
66
+ id: true,
67
+ firstName: true,
68
+ lastName: true,
69
+ avatarUrl: true,
70
+ },
71
+ },
72
+ },
73
+ },
74
+ leaderboard: {
75
+ include: {
76
+ user: {
77
+ select: {
78
+ id: true,
79
+ firstName: true,
80
+ lastName: true,
81
+ avatarUrl: true,
82
+ },
83
+ },
84
+ },
85
+ orderBy: { score: "desc" },
86
+ take: 100,
87
+ },
88
+ },
89
+ });
90
+ if (!event) {
91
+ return res
92
+ .status(404)
93
+ .json({ success: false, message: "Event not found" });
94
+ }
95
+ res.json({ success: true, data: event });
96
+ }
97
+ catch (error) {
98
+ res.status(500).json({ success: false, message: error.message });
99
+ }
100
+ };
101
+ exports.getEvent = getEvent;
102
+ const joinEvent = async (req, res) => {
103
+ var _a;
104
+ const userId = (_a = req.user) === null || _a === void 0 ? void 0 : _a.id;
105
+ const { eventId } = req.params;
106
+ try {
107
+ const event = await client_1.default.event.findUnique({
108
+ where: { id: eventId },
109
+ include: { _count: { select: { participants: true } } },
110
+ });
111
+ if (!event) {
112
+ return res
113
+ .status(404)
114
+ .json({ success: false, message: "Event not found" });
115
+ }
116
+ if (event.maxParticipants &&
117
+ event._count.participants >= event.maxParticipants) {
118
+ return res.status(400).json({ success: false, message: "Event is full" });
119
+ }
120
+ const participant = await client_1.default.eventParticipant.create({
121
+ data: { eventId, userId },
122
+ });
123
+ await client_1.default.eventLeaderboard.create({
124
+ data: { eventId, userId, score: 0 },
125
+ });
126
+ res.json({ success: true, data: participant });
127
+ }
128
+ catch (error) {
129
+ res.status(500).json({ success: false, message: error.message });
130
+ }
131
+ };
132
+ exports.joinEvent = joinEvent;
133
+ const leaveEvent = async (req, res) => {
134
+ var _a;
135
+ const userId = (_a = req.user) === null || _a === void 0 ? void 0 : _a.id;
136
+ const { eventId } = req.params;
137
+ try {
138
+ await client_1.default.eventParticipant.delete({
139
+ where: { eventId_userId: { eventId, userId } },
140
+ });
141
+ await client_1.default.eventLeaderboard.delete({
142
+ where: { eventId_userId: { eventId, userId } },
143
+ });
144
+ res.json({ success: true, message: "Left event successfully" });
145
+ }
146
+ catch (error) {
147
+ res.status(500).json({ success: false, message: error.message });
148
+ }
149
+ };
150
+ exports.leaveEvent = leaveEvent;
151
+ const updateEventScore = async (req, res) => {
152
+ const { eventId, userId } = req.params;
153
+ const { score } = req.body;
154
+ try {
155
+ const leaderboard = await client_1.default.eventLeaderboard.update({
156
+ where: { eventId_userId: { eventId, userId } },
157
+ data: { score },
158
+ });
159
+ const allScores = await client_1.default.eventLeaderboard.findMany({
160
+ where: { eventId },
161
+ orderBy: { score: "desc" },
162
+ });
163
+ for (let i = 0; i < allScores.length; i++) {
164
+ await client_1.default.eventLeaderboard.update({
165
+ where: { id: allScores[i].id },
166
+ data: { rank: i + 1 },
167
+ });
168
+ }
169
+ res.json({ success: true, data: leaderboard });
170
+ }
171
+ catch (error) {
172
+ res.status(500).json({ success: false, message: error.message });
173
+ }
174
+ };
175
+ exports.updateEventScore = updateEventScore;
176
+ const getLeaderboard = async (req, res) => {
177
+ const { eventId } = req.params;
178
+ const { limit = 100 } = req.query;
179
+ try {
180
+ const leaderboard = await client_1.default.eventLeaderboard.findMany({
181
+ where: { eventId },
182
+ include: {
183
+ user: {
184
+ select: {
185
+ id: true,
186
+ firstName: true,
187
+ lastName: true,
188
+ avatarUrl: true,
189
+ xp: true,
190
+ },
191
+ },
192
+ },
193
+ orderBy: { score: "desc" },
194
+ take: Number(limit),
195
+ });
196
+ res.json({ success: true, data: leaderboard });
197
+ }
198
+ catch (error) {
199
+ res.status(500).json({ success: false, message: error.message });
200
+ }
201
+ };
202
+ exports.getLeaderboard = getLeaderboard;
dist/controllers/gamification/index.js ADDED
@@ -0,0 +1,71 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.awardXP = exports.getUserAchievements = exports.getLeaderboard = void 0;
7
+ const client_1 = __importDefault(require("../../prisma/client"));
8
+ const redis_1 = __importDefault(require("../../utils/redis"));
9
+ const LEADERBOARD_CACHE_KEY = "leaderboard:top20";
10
+ const CACHE_TTL = 3600; // 1 hour
11
+ const getLeaderboard = async (req, res, next) => {
12
+ try {
13
+ // Try to get from cache
14
+ const cachedLeaderboard = await redis_1.default.get(LEADERBOARD_CACHE_KEY);
15
+ if (cachedLeaderboard) {
16
+ return res.json({ success: true, data: JSON.parse(cachedLeaderboard), cached: true });
17
+ }
18
+ const leaderboard = await client_1.default.user.findMany({
19
+ select: {
20
+ id: true,
21
+ firstName: true,
22
+ lastName: true,
23
+ avatarUrl: true,
24
+ xp: true,
25
+ streak: true
26
+ },
27
+ orderBy: { xp: "desc" },
28
+ take: 20
29
+ });
30
+ // Save to cache
31
+ await redis_1.default.setex(LEADERBOARD_CACHE_KEY, CACHE_TTL, JSON.stringify(leaderboard));
32
+ res.json({ success: true, data: leaderboard });
33
+ }
34
+ catch (error) {
35
+ next(error);
36
+ }
37
+ };
38
+ exports.getLeaderboard = getLeaderboard;
39
+ const getUserAchievements = async (req, res, next) => {
40
+ var _a;
41
+ const userId = (_a = req.user) === null || _a === void 0 ? void 0 : _a.id;
42
+ try {
43
+ const achievements = await client_1.default.userAchievement.findMany({
44
+ where: { userId },
45
+ include: {
46
+ achievement: true
47
+ }
48
+ });
49
+ res.json({ success: true, data: achievements });
50
+ }
51
+ catch (error) {
52
+ next(error);
53
+ }
54
+ };
55
+ exports.getUserAchievements = getUserAchievements;
56
+ const awardXP = async (userId, amount) => {
57
+ try {
58
+ await client_1.default.user.update({
59
+ where: { id: userId },
60
+ data: {
61
+ xp: { increment: amount }
62
+ }
63
+ });
64
+ // Invalidate leaderboard cache when XP changes
65
+ await redis_1.default.del(LEADERBOARD_CACHE_KEY);
66
+ }
67
+ catch (error) {
68
+ console.error("Error awarding XP:", error);
69
+ }
70
+ };
71
+ exports.awardXP = awardXP;
dist/controllers/notifications/index.js ADDED
@@ -0,0 +1,78 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.deleteNotification = exports.markAllAsRead = exports.markAsRead = exports.getNotifications = void 0;
7
+ const client_1 = __importDefault(require("../../prisma/client"));
8
+ const getNotifications = async (req, res) => {
9
+ var _a;
10
+ const userId = (_a = req.user) === null || _a === void 0 ? void 0 : _a.id;
11
+ const { page = 1, limit = 20, unreadOnly = false } = req.query;
12
+ try {
13
+ const where = { userId };
14
+ if (unreadOnly === "true") {
15
+ where.read = false;
16
+ }
17
+ const notifications = await client_1.default.notification.findMany({
18
+ where,
19
+ orderBy: { createdAt: "desc" },
20
+ take: Number(limit),
21
+ skip: (Number(page) - 1) * Number(limit),
22
+ });
23
+ const unreadCount = await client_1.default.notification.count({
24
+ where: { userId, read: false },
25
+ });
26
+ res.json({ success: true, data: { notifications, unreadCount } });
27
+ }
28
+ catch (error) {
29
+ res.status(500).json({ success: false, message: error.message });
30
+ }
31
+ };
32
+ exports.getNotifications = getNotifications;
33
+ const markAsRead = async (req, res) => {
34
+ var _a;
35
+ const userId = (_a = req.user) === null || _a === void 0 ? void 0 : _a.id;
36
+ const { notificationId } = req.params;
37
+ try {
38
+ await client_1.default.notification.updateMany({
39
+ where: { id: notificationId, userId },
40
+ data: { read: true },
41
+ });
42
+ res.json({ success: true, message: "Notification marked as read" });
43
+ }
44
+ catch (error) {
45
+ res.status(500).json({ success: false, message: error.message });
46
+ }
47
+ };
48
+ exports.markAsRead = markAsRead;
49
+ const markAllAsRead = async (req, res) => {
50
+ var _a;
51
+ const userId = (_a = req.user) === null || _a === void 0 ? void 0 : _a.id;
52
+ try {
53
+ await client_1.default.notification.updateMany({
54
+ where: { userId, read: false },
55
+ data: { read: true },
56
+ });
57
+ res.json({ success: true, message: "All notifications marked as read" });
58
+ }
59
+ catch (error) {
60
+ res.status(500).json({ success: false, message: error.message });
61
+ }
62
+ };
63
+ exports.markAllAsRead = markAllAsRead;
64
+ const deleteNotification = async (req, res) => {
65
+ var _a;
66
+ const userId = (_a = req.user) === null || _a === void 0 ? void 0 : _a.id;
67
+ const { notificationId } = req.params;
68
+ try {
69
+ await client_1.default.notification.deleteMany({
70
+ where: { id: notificationId, userId },
71
+ });
72
+ res.json({ success: true, message: "Notification deleted" });
73
+ }
74
+ catch (error) {
75
+ res.status(500).json({ success: false, message: error.message });
76
+ }
77
+ };
78
+ exports.deleteNotification = deleteNotification;
dist/controllers/paths/index.js ADDED
@@ -0,0 +1,87 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.startPath = exports.getPathProgress = exports.getLearningPaths = void 0;
7
+ const client_1 = __importDefault(require("../../prisma/client"));
8
+ const getLearningPaths = async (req, res, next) => {
9
+ try {
10
+ const paths = await client_1.default.learningPath.findMany({
11
+ include: {
12
+ projects: {
13
+ include: {
14
+ project: {
15
+ select: {
16
+ title: true,
17
+ difficultyLevel: true,
18
+ },
19
+ },
20
+ },
21
+ orderBy: { orderIndex: "asc" },
22
+ },
23
+ },
24
+ });
25
+ res.json({ success: true, data: paths });
26
+ }
27
+ catch (error) {
28
+ next(error);
29
+ }
30
+ };
31
+ exports.getLearningPaths = getLearningPaths;
32
+ const getPathProgress = async (req, res, next) => {
33
+ var _a;
34
+ const { id: pathId } = req.params;
35
+ const userId = (_a = req.user) === null || _a === void 0 ? void 0 : _a.id;
36
+ try {
37
+ const progress = await client_1.default.userPathProgress.findUnique({
38
+ where: {
39
+ userId_pathId: {
40
+ userId,
41
+ pathId,
42
+ },
43
+ },
44
+ include: {
45
+ path: {
46
+ include: {
47
+ projects: {
48
+ include: { project: true },
49
+ orderBy: { orderIndex: "asc" },
50
+ },
51
+ },
52
+ },
53
+ },
54
+ });
55
+ res.json({ success: true, data: progress });
56
+ }
57
+ catch (error) {
58
+ next(error);
59
+ }
60
+ };
61
+ exports.getPathProgress = getPathProgress;
62
+ const startPath = async (req, res, next) => {
63
+ var _a;
64
+ const { id: pathId } = req.params;
65
+ const userId = (_a = req.user) === null || _a === void 0 ? void 0 : _a.id;
66
+ try {
67
+ const progress = await client_1.default.userPathProgress.upsert({
68
+ where: {
69
+ userId_pathId: {
70
+ userId,
71
+ pathId
72
+ }
73
+ },
74
+ update: {},
75
+ create: {
76
+ userId,
77
+ pathId,
78
+ currentProjectIndex: 0,
79
+ },
80
+ });
81
+ res.status(201).json({ success: true, data: progress });
82
+ }
83
+ catch (error) {
84
+ next(error);
85
+ }
86
+ };
87
+ exports.startPath = startPath;
dist/controllers/projects/index.js ADDED
@@ -0,0 +1,416 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.getFeaturedProjects = exports.completeMilestone = exports.submitProject = exports.getUserProgress = exports.completeProject = exports.updateProgress = exports.startProject = exports.getProjectById = exports.getProjects = exports.deleteProject = exports.updateProject = exports.createProject = void 0;
7
+ const client_1 = __importDefault(require("../../prisma/client"));
8
+ const file_1 = require("../../middlewares/file");
9
+ const auth_1 = require("../../middlewares/auth");
10
+ function generateSlug(title) {
11
+ return title
12
+ .toLowerCase()
13
+ .replace(/[^a-z0-9]+/g, "-")
14
+ .replace(/^-+|-+$/g, "");
15
+ }
16
+ const createProject = async (req, res) => {
17
+ var _a;
18
+ const userId = (_a = req.user) === null || _a === void 0 ? void 0 : _a.id;
19
+ const { title, description, difficultyLevel, technologies, categories, estimatedTime, learningObjectives, resourceLinks, starterRepoUrl, difficultyModes, coverImage, milestones, } = req.body;
20
+ try {
21
+ if (!userId)
22
+ return res.status(401).json({ success: false, message: "Unauthorized" });
23
+ const user = await client_1.default.user.findUnique({ where: { id: userId } });
24
+ if (!user || (user.role !== "ADMIN" && user.role !== "CONTRIBUTOR")) {
25
+ return res.status(403).json({
26
+ success: false,
27
+ message: "Only Admins or Contributors can create projects",
28
+ });
29
+ }
30
+ let imageUrl = coverImage;
31
+ if (coverImage && !coverImage.startsWith("http")) {
32
+ imageUrl = await (0, file_1.upLoadFiles)(coverImage);
33
+ }
34
+ const project = await client_1.default.project.create({
35
+ data: {
36
+ title,
37
+ slug: generateSlug(title),
38
+ description,
39
+ difficultyLevel: difficultyLevel,
40
+ technologies,
41
+ categories,
42
+ estimatedTime,
43
+ learningObjectives,
44
+ resourceLinks: resourceLinks || [],
45
+ starterRepoUrl,
46
+ difficultyModes: difficultyModes || ["GUIDED", "STANDARD", "HARDCORE"],
47
+ createdBy: { connect: { id: userId } },
48
+ milestones: {
49
+ create: milestones === null || milestones === void 0 ? void 0 : milestones.map((m, index) => ({
50
+ milestoneNumber: index + 1,
51
+ title: m.title,
52
+ description: m.description,
53
+ hints: m.hints || [],
54
+ validationCriteria: m.validationCriteria,
55
+ })),
56
+ },
57
+ },
58
+ include: {
59
+ milestones: true,
60
+ },
61
+ });
62
+ res.status(201).json({ success: true, data: project });
63
+ }
64
+ catch (error) {
65
+ res.status(500).json({ success: false, message: error.message });
66
+ }
67
+ };
68
+ exports.createProject = createProject;
69
+ const updateProject = async (req, res) => {
70
+ var _a;
71
+ const { id } = req.params;
72
+ const userId = (_a = req.user) === null || _a === void 0 ? void 0 : _a.id;
73
+ const updateData = req.body;
74
+ try {
75
+ const project = await client_1.default.project.findUnique({ where: { id } });
76
+ if (!project)
77
+ return res
78
+ .status(404)
79
+ .json({ success: false, message: "Project not found" });
80
+ // Check permissions
81
+ const user = await client_1.default.user.findUnique({ where: { id: userId } });
82
+ if (!user || (user.role !== "ADMIN" && project.createdById !== userId)) {
83
+ return res.status(403).json({
84
+ success: false,
85
+ message: "Unauthorized to update this project",
86
+ });
87
+ }
88
+ const updatedProject = await client_1.default.project.update({
89
+ where: { id },
90
+ data: updateData,
91
+ include: { milestones: true },
92
+ });
93
+ res.json({ success: true, data: updatedProject });
94
+ }
95
+ catch (error) {
96
+ res.status(500).json({ success: false, message: error.message });
97
+ }
98
+ };
99
+ exports.updateProject = updateProject;
100
+ const deleteProject = async (req, res) => {
101
+ var _a;
102
+ const { id } = req.params;
103
+ const userId = (_a = req.user) === null || _a === void 0 ? void 0 : _a.id;
104
+ try {
105
+ const project = await client_1.default.project.findUnique({ where: { id } });
106
+ if (!project)
107
+ return res
108
+ .status(404)
109
+ .json({ success: false, message: "Project not found" });
110
+ const user = await client_1.default.user.findUnique({ where: { id: userId } });
111
+ if (!user || user.role !== "ADMIN") {
112
+ return res
113
+ .status(403)
114
+ .json({ success: false, message: "Only Admins can delete projects" });
115
+ }
116
+ await client_1.default.project.delete({ where: { id } });
117
+ res.json({ success: true, message: "Project deleted successfully" });
118
+ }
119
+ catch (error) {
120
+ res.status(500).json({ success: false, message: error.message });
121
+ }
122
+ };
123
+ exports.deleteProject = deleteProject;
124
+ const getProjects = async (req, res) => {
125
+ const { difficulty, tech, category, title } = req.query;
126
+ try {
127
+ const where = {};
128
+ if (difficulty)
129
+ where.difficultyLevel = difficulty;
130
+ if (tech)
131
+ where.technologies = { has: tech };
132
+ if (category)
133
+ where.categories = { has: category };
134
+ if (title)
135
+ where.title = { contains: title, mode: "insensitive" };
136
+ const projects = await client_1.default.project.findMany({
137
+ where,
138
+ include: {
139
+ createdBy: {
140
+ select: { firstName: true, lastName: true },
141
+ },
142
+ },
143
+ orderBy: { createdAt: "desc" },
144
+ });
145
+ res.json({ success: true, data: projects });
146
+ }
147
+ catch (error) {
148
+ res.status(500).json({ success: false, message: error.message });
149
+ }
150
+ };
151
+ exports.getProjects = getProjects;
152
+ const getProjectById = async (req, res) => {
153
+ var _a;
154
+ const { id } = req.params;
155
+ try {
156
+ const user = await (0, auth_1.checkAuth)(req);
157
+ const project = (await client_1.default.project.findUnique({
158
+ where: { id },
159
+ include: {
160
+ milestones: { orderBy: { milestoneNumber: "asc" } },
161
+ createdBy: { select: { firstName: true, lastName: true } },
162
+ userProjects: user ? { where: { userId: user.id } } : false,
163
+ },
164
+ }));
165
+ if (!project)
166
+ return res
167
+ .status(404)
168
+ .json({ success: false, message: "Project not found" });
169
+ // If user is authenticated, fetch their progress and completed milestones for each mode
170
+ let progressByMode = {};
171
+ if (user && project.userProjects) {
172
+ for (const up of project.userProjects) {
173
+ const completedMilestones = await client_1.default.userMilestone.findMany({
174
+ where: {
175
+ userId: user.id,
176
+ userProjectId: up.id,
177
+ },
178
+ select: { milestoneId: true },
179
+ });
180
+ progressByMode[up.difficultyModeChosen] = {
181
+ status: up.status,
182
+ completedMilestones: completedMilestones.map((m) => m.milestoneId),
183
+ userProjectId: up.id,
184
+ };
185
+ }
186
+ }
187
+ res.json({
188
+ success: true,
189
+ data: {
190
+ ...project,
191
+ progressByMode,
192
+ // For backward compatibility
193
+ userProgress: ((_a = project.userProjects) === null || _a === void 0 ? void 0 : _a[0]) || null,
194
+ },
195
+ });
196
+ }
197
+ catch (error) {
198
+ res.status(500).json({ success: false, message: error.message });
199
+ }
200
+ };
201
+ exports.getProjectById = getProjectById;
202
+ // User Progress Endpoints
203
+ const startProject = async (req, res) => {
204
+ var _a;
205
+ const { id: projectId } = req.params;
206
+ const userId = (_a = req.user) === null || _a === void 0 ? void 0 : _a.id;
207
+ const { difficultyModeChosen } = req.body;
208
+ try {
209
+ const mode = difficultyModeChosen || "STANDARD";
210
+ const existingProgress = await client_1.default.userProject.findUnique({
211
+ where: {
212
+ userId_projectId_difficultyModeChosen: {
213
+ userId,
214
+ projectId,
215
+ difficultyModeChosen: mode,
216
+ },
217
+ },
218
+ });
219
+ if (existingProgress) {
220
+ return res.status(400).json({
221
+ success: false,
222
+ message: "Project already started in this mode",
223
+ });
224
+ }
225
+ const progress = await client_1.default.userProject.create({
226
+ data: {
227
+ userId,
228
+ projectId,
229
+ difficultyModeChosen: mode,
230
+ status: "IN_PROGRESS",
231
+ },
232
+ });
233
+ res.status(201).json({ success: true, data: progress });
234
+ }
235
+ catch (error) {
236
+ res.status(500).json({ success: false, message: error.message });
237
+ }
238
+ };
239
+ exports.startProject = startProject;
240
+ const updateProgress = async (req, res) => {
241
+ var _a;
242
+ const { id: projectId } = req.params;
243
+ const userId = (_a = req.user) === null || _a === void 0 ? void 0 : _a.id;
244
+ const { status, repoUrl, difficultyMode } = req.body;
245
+ try {
246
+ const progress = await client_1.default.userProject.update({
247
+ where: {
248
+ userId_projectId_difficultyModeChosen: {
249
+ userId,
250
+ projectId,
251
+ difficultyModeChosen: difficultyMode,
252
+ },
253
+ },
254
+ data: { status: status, repoUrl },
255
+ });
256
+ res.json({ success: true, data: progress });
257
+ }
258
+ catch (error) {
259
+ res.status(500).json({ success: false, message: error.message });
260
+ }
261
+ };
262
+ exports.updateProgress = updateProgress;
263
+ const completeProject = async (req, res) => {
264
+ var _a;
265
+ const { id: projectId } = req.params;
266
+ const userId = (_a = req.user) === null || _a === void 0 ? void 0 : _a.id;
267
+ const { repoUrl, difficultyMode } = req.body;
268
+ try {
269
+ const progress = await client_1.default.userProject.update({
270
+ where: {
271
+ userId_projectId_difficultyModeChosen: {
272
+ userId,
273
+ projectId,
274
+ difficultyModeChosen: difficultyMode,
275
+ },
276
+ },
277
+ data: {
278
+ status: "COMPLETED",
279
+ completedAt: new Date(),
280
+ repoUrl,
281
+ },
282
+ });
283
+ // Award XP (simple example)
284
+ await client_1.default.user.update({
285
+ where: { id: userId },
286
+ data: { xp: { increment: 100 } },
287
+ });
288
+ // Update project completion rate
289
+ const totalStarted = await client_1.default.userProject.count({
290
+ where: { projectId },
291
+ });
292
+ const totalCompleted = await client_1.default.userProject.count({
293
+ where: { projectId, status: "COMPLETED" },
294
+ });
295
+ await client_1.default.project.update({
296
+ where: { id: projectId },
297
+ data: {
298
+ completionRate: (totalCompleted / totalStarted) * 100,
299
+ submissionCount: { increment: 1 },
300
+ },
301
+ });
302
+ res.json({ success: true, data: progress });
303
+ }
304
+ catch (error) {
305
+ res.status(500).json({ success: false, message: error.message });
306
+ }
307
+ };
308
+ exports.completeProject = completeProject;
309
+ const getUserProgress = async (req, res) => {
310
+ var _a;
311
+ const userId = (_a = req.user) === null || _a === void 0 ? void 0 : _a.id;
312
+ try {
313
+ const progress = await client_1.default.userProject.findMany({
314
+ where: { userId },
315
+ include: {
316
+ project: {
317
+ select: { title: true, difficultyLevel: true },
318
+ },
319
+ },
320
+ });
321
+ res.json({ success: true, data: progress });
322
+ }
323
+ catch (error) {
324
+ res.status(500).json({ success: false, message: error.message });
325
+ }
326
+ };
327
+ exports.getUserProgress = getUserProgress;
328
+ // Submissions
329
+ const submitProject = async (req, res) => {
330
+ var _a;
331
+ const { id: projectId } = req.params;
332
+ const userId = (_a = req.user) === null || _a === void 0 ? void 0 : _a.id;
333
+ const { repoUrl } = req.body;
334
+ try {
335
+ const submission = await client_1.default.submission.create({
336
+ data: {
337
+ userId,
338
+ projectId,
339
+ repoUrl,
340
+ },
341
+ });
342
+ res.status(201).json({ success: true, data: submission });
343
+ }
344
+ catch (error) {
345
+ res.status(500).json({ success: false, message: error.message });
346
+ }
347
+ };
348
+ exports.submitProject = submitProject;
349
+ const completeMilestone = async (req, res) => {
350
+ var _a;
351
+ const { id: projectId, milestoneId } = req.params;
352
+ const userId = (_a = req.user) === null || _a === void 0 ? void 0 : _a.id;
353
+ const { difficultyMode } = req.body;
354
+ try {
355
+ const userProject = await client_1.default.userProject.findUnique({
356
+ where: {
357
+ userId_projectId_difficultyModeChosen: {
358
+ userId,
359
+ projectId,
360
+ difficultyModeChosen: difficultyMode,
361
+ },
362
+ },
363
+ });
364
+ if (!userProject) {
365
+ return res.status(400).json({
366
+ success: false,
367
+ message: "Project not started by user in this mode",
368
+ });
369
+ }
370
+ const existingMilestone = await client_1.default.userMilestone.findUnique({
371
+ where: {
372
+ userId_milestoneId_userProjectId: {
373
+ userId,
374
+ milestoneId,
375
+ userProjectId: userProject.id,
376
+ },
377
+ },
378
+ });
379
+ if (existingMilestone) {
380
+ return res.status(400).json({
381
+ success: false,
382
+ message: "Milestone already completed in this mode",
383
+ });
384
+ }
385
+ const milestone = await client_1.default.userMilestone.create({
386
+ data: {
387
+ userId,
388
+ milestoneId,
389
+ userProjectId: userProject.id,
390
+ },
391
+ });
392
+ res.status(201).json({ success: true, data: milestone });
393
+ }
394
+ catch (error) {
395
+ res.status(500).json({ success: false, message: error.message });
396
+ }
397
+ };
398
+ exports.completeMilestone = completeMilestone;
399
+ const getFeaturedProjects = async (req, res) => {
400
+ try {
401
+ const featuredProjects = await client_1.default.project.findMany({
402
+ take: 4,
403
+ orderBy: { submissionCount: "desc" },
404
+ include: {
405
+ createdBy: {
406
+ select: { firstName: true, lastName: true },
407
+ },
408
+ },
409
+ });
410
+ res.json({ success: true, data: featuredProjects });
411
+ }
412
+ catch (error) {
413
+ res.status(500).json({ success: false, message: error.message });
414
+ }
415
+ };
416
+ exports.getFeaturedProjects = getFeaturedProjects;
dist/controllers/social/index.js ADDED
@@ -0,0 +1,181 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.shareProject = exports.getActivityFeed = exports.getFollowing = exports.getFollowers = exports.unfollowUser = exports.followUser = void 0;
7
+ const client_1 = __importDefault(require("../../prisma/client"));
8
+ const followUser = async (req, res) => {
9
+ var _a;
10
+ const userId = (_a = req.user) === null || _a === void 0 ? void 0 : _a.id;
11
+ const { followingId } = req.body;
12
+ try {
13
+ if (userId === followingId) {
14
+ return res
15
+ .status(400)
16
+ .json({ success: false, message: "Cannot follow yourself" });
17
+ }
18
+ const existingFollow = await client_1.default.follow.findUnique({
19
+ where: { followerId_followingId: { followerId: userId, followingId } },
20
+ });
21
+ if (existingFollow) {
22
+ return res
23
+ .status(400)
24
+ .json({ success: false, message: "Already following this user" });
25
+ }
26
+ const follow = await client_1.default.follow.create({
27
+ data: { followerId: userId, followingId },
28
+ });
29
+ await client_1.default.notification.create({
30
+ data: {
31
+ userId: followingId,
32
+ type: "FOLLOW",
33
+ title: "New Follower",
34
+ message: "Someone started following you",
35
+ link: `/profile/${userId}`,
36
+ },
37
+ });
38
+ res.json({ success: true, data: follow });
39
+ }
40
+ catch (error) {
41
+ res.status(500).json({ success: false, message: error.message });
42
+ }
43
+ };
44
+ exports.followUser = followUser;
45
+ const unfollowUser = async (req, res) => {
46
+ var _a;
47
+ const userId = (_a = req.user) === null || _a === void 0 ? void 0 : _a.id;
48
+ const { followingId } = req.params;
49
+ try {
50
+ await client_1.default.follow.delete({
51
+ where: { followerId_followingId: { followerId: userId, followingId } },
52
+ });
53
+ res.json({ success: true, message: "Unfollowed successfully" });
54
+ }
55
+ catch (error) {
56
+ res.status(500).json({ success: false, message: error.message });
57
+ }
58
+ };
59
+ exports.unfollowUser = unfollowUser;
60
+ const getFollowers = async (req, res) => {
61
+ const { userId } = req.params;
62
+ try {
63
+ const followers = await client_1.default.follow.findMany({
64
+ where: { followingId: userId },
65
+ include: {
66
+ follower: {
67
+ select: {
68
+ id: true,
69
+ firstName: true,
70
+ lastName: true,
71
+ avatarUrl: true,
72
+ xp: true,
73
+ },
74
+ },
75
+ },
76
+ });
77
+ res.json({ success: true, data: followers });
78
+ }
79
+ catch (error) {
80
+ res.status(500).json({ success: false, message: error.message });
81
+ }
82
+ };
83
+ exports.getFollowers = getFollowers;
84
+ const getFollowing = async (req, res) => {
85
+ const { userId } = req.params;
86
+ try {
87
+ const following = await client_1.default.follow.findMany({
88
+ where: { followerId: userId },
89
+ include: {
90
+ following: {
91
+ select: {
92
+ id: true,
93
+ firstName: true,
94
+ lastName: true,
95
+ avatarUrl: true,
96
+ xp: true,
97
+ },
98
+ },
99
+ },
100
+ });
101
+ res.json({ success: true, data: following });
102
+ }
103
+ catch (error) {
104
+ res.status(500).json({ success: false, message: error.message });
105
+ }
106
+ };
107
+ exports.getFollowing = getFollowing;
108
+ const getActivityFeed = async (req, res) => {
109
+ var _a;
110
+ const userId = (_a = req.user) === null || _a === void 0 ? void 0 : _a.id;
111
+ const { page = 1, limit = 20 } = req.query;
112
+ try {
113
+ const following = await client_1.default.follow.findMany({
114
+ where: { followerId: userId },
115
+ select: { followingId: true },
116
+ });
117
+ const followingIds = following.map((f) => f.followingId);
118
+ const activities = await client_1.default.userProject.findMany({
119
+ where: {
120
+ userId: { in: followingIds },
121
+ status: "COMPLETED",
122
+ },
123
+ include: {
124
+ user: {
125
+ select: {
126
+ id: true,
127
+ firstName: true,
128
+ lastName: true,
129
+ avatarUrl: true,
130
+ },
131
+ },
132
+ project: {
133
+ select: {
134
+ id: true,
135
+ title: true,
136
+ slug: true,
137
+ },
138
+ },
139
+ },
140
+ orderBy: { completedAt: "desc" },
141
+ take: Number(limit),
142
+ skip: (Number(page) - 1) * Number(limit),
143
+ });
144
+ res.json({ success: true, data: activities });
145
+ }
146
+ catch (error) {
147
+ res.status(500).json({ success: false, message: error.message });
148
+ }
149
+ };
150
+ exports.getActivityFeed = getActivityFeed;
151
+ const shareProject = async (req, res) => {
152
+ var _a;
153
+ const userId = (_a = req.user) === null || _a === void 0 ? void 0 : _a.id;
154
+ const { projectId, platform } = req.body;
155
+ try {
156
+ const project = await client_1.default.project.findUnique({
157
+ where: { id: projectId },
158
+ });
159
+ if (!project) {
160
+ return res
161
+ .status(404)
162
+ .json({ success: false, message: "Project not found" });
163
+ }
164
+ const shareUrl = `${process.env.CLIENT_URL}/projects/${project.slug}`;
165
+ const ogImageUrl = `${process.env.CLIENT_URL}/api/og-image/${project.slug}`;
166
+ const share = await client_1.default.socialShare.create({
167
+ data: {
168
+ userId,
169
+ projectId,
170
+ platform,
171
+ shareUrl,
172
+ ogImageUrl,
173
+ },
174
+ });
175
+ res.json({ success: true, data: { shareUrl, ogImageUrl, share } });
176
+ }
177
+ catch (error) {
178
+ res.status(500).json({ success: false, message: error.message });
179
+ }
180
+ };
181
+ exports.shareProject = shareProject;
dist/controllers/teams/index.js ADDED
@@ -0,0 +1,71 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.getTeamProjects = exports.joinTeam = exports.createTeam = void 0;
7
+ const client_1 = __importDefault(require("../../prisma/client"));
8
+ const createTeam = async (req, res, next) => {
9
+ var _a;
10
+ const { name, projectId } = req.body;
11
+ const userId = (_a = req.user) === null || _a === void 0 ? void 0 : _a.id;
12
+ try {
13
+ const team = await client_1.default.team.create({
14
+ data: {
15
+ name,
16
+ projectId,
17
+ members: {
18
+ create: {
19
+ userId,
20
+ },
21
+ },
22
+ },
23
+ include: {
24
+ members: true,
25
+ },
26
+ });
27
+ res.status(201).json({ success: true, data: team });
28
+ }
29
+ catch (error) {
30
+ next(error);
31
+ }
32
+ };
33
+ exports.createTeam = createTeam;
34
+ const joinTeam = async (req, res, next) => {
35
+ var _a;
36
+ const { id: teamId } = req.params;
37
+ const userId = (_a = req.user) === null || _a === void 0 ? void 0 : _a.id;
38
+ try {
39
+ const teamMember = await client_1.default.teamMember.create({
40
+ data: {
41
+ teamId,
42
+ userId,
43
+ },
44
+ });
45
+ res.status(201).json({ success: true, data: teamMember });
46
+ }
47
+ catch (error) {
48
+ next(error);
49
+ }
50
+ };
51
+ exports.joinTeam = joinTeam;
52
+ const getTeamProjects = async (req, res, next) => {
53
+ const { id: teamId } = req.params;
54
+ try {
55
+ const team = await client_1.default.team.findUnique({
56
+ where: { id: teamId },
57
+ include: {
58
+ project: true,
59
+ },
60
+ });
61
+ if (!team)
62
+ return res
63
+ .status(404)
64
+ .json({ success: false, message: "Team not found" });
65
+ res.json({ success: true, data: team.project });
66
+ }
67
+ catch (error) {
68
+ next(error);
69
+ }
70
+ };
71
+ exports.getTeamProjects = getTeamProjects;
dist/index.js ADDED
@@ -0,0 +1,70 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.appRoute = void 0;
7
+ const dotenv_1 = __importDefault(require("dotenv"));
8
+ const express_1 = __importDefault(require("express"));
9
+ const cors_1 = __importDefault(require("cors"));
10
+ const cookie_parser_1 = __importDefault(require("cookie-parser"));
11
+ exports.appRoute = (0, express_1.default)();
12
+ const auth_1 = __importDefault(require("./routes/auth"));
13
+ const project_1 = __importDefault(require("./routes/project"));
14
+ const user_1 = __importDefault(require("./routes/user"));
15
+ const ai_1 = __importDefault(require("./routes/ai"));
16
+ const code_review_1 = __importDefault(require("./routes/code-review"));
17
+ const community_1 = __importDefault(require("./routes/community"));
18
+ const teams_1 = __importDefault(require("./routes/teams"));
19
+ const gamification_1 = __importDefault(require("./routes/gamification"));
20
+ const paths_1 = __importDefault(require("./routes/paths"));
21
+ const analytics_1 = __importDefault(require("./routes/analytics"));
22
+ const social_1 = __importDefault(require("./routes/social"));
23
+ const notifications_1 = __importDefault(require("./routes/notifications"));
24
+ const events_1 = __importDefault(require("./routes/events"));
25
+ const errorHandler_1 = require("./middlewares/errorHandler");
26
+ const passport_1 = __importDefault(require("./utils/passport"));
27
+ dotenv_1.default.config();
28
+ const allowedOrigins = process.env.CLIENT_URL
29
+ ? process.env.CLIENT_URL.split(",").map((url) => url.trim())
30
+ : ["http://localhost:3002"];
31
+ console.log("CORS Allowed Origins:", allowedOrigins);
32
+ exports.appRoute.use((0, cors_1.default)({
33
+ origin: (origin, callback) => {
34
+ console.log("Incoming origin:", origin);
35
+ if (!origin ||
36
+ allowedOrigins.some((allowed) => origin.startsWith(allowed))) {
37
+ callback(null, true);
38
+ }
39
+ else {
40
+ console.warn("Blocked by CORS:", origin);
41
+ callback(new Error("Not allowed by CORS"));
42
+ }
43
+ },
44
+ credentials: true,
45
+ allowedHeaders: ["Content-Type", "Authorization"],
46
+ methods: ["GET", "POST", "PUT", "DELETE"],
47
+ exposedHeaders: ["Content-Disposition"],
48
+ }));
49
+ console.log("ENV:", process.env.CLIENT_URL);
50
+ exports.appRoute.use(express_1.default.json({ limit: "50mb" }));
51
+ exports.appRoute.use(express_1.default.urlencoded({ limit: "50mb", extended: true }));
52
+ exports.appRoute.use((0, cookie_parser_1.default)());
53
+ exports.appRoute.use(passport_1.default.initialize());
54
+ exports.appRoute.get("/", (req, res) => {
55
+ res.send("Backend is working!");
56
+ });
57
+ exports.appRoute.use("/api/auth", auth_1.default);
58
+ exports.appRoute.use("/api/projects", project_1.default);
59
+ exports.appRoute.use("/api/user", user_1.default);
60
+ exports.appRoute.use("/api/ai", ai_1.default);
61
+ exports.appRoute.use("/api/code-review", code_review_1.default);
62
+ exports.appRoute.use("/api/community", community_1.default);
63
+ exports.appRoute.use("/api/teams", teams_1.default);
64
+ exports.appRoute.use("/api/gamification", gamification_1.default);
65
+ exports.appRoute.use("/api/paths", paths_1.default);
66
+ exports.appRoute.use("/api/analytics", analytics_1.default);
67
+ exports.appRoute.use("/api/social", social_1.default);
68
+ exports.appRoute.use("/api/notifications", notifications_1.default);
69
+ exports.appRoute.use("/api/events", events_1.default);
70
+ exports.appRoute.use(errorHandler_1.errorHandler);
dist/middlewares/auth.js ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.checkAuth = exports.authenticateToken = void 0;
7
+ const jsonwebtoken_1 = __importDefault(require("jsonwebtoken"));
8
+ const authenticateToken = (req, res, next) => {
9
+ var _a;
10
+ const token = req.cookies.token || ((_a = req.headers["authorization"]) === null || _a === void 0 ? void 0 : _a.split(" ")[1]);
11
+ if (!token)
12
+ res.sendStatus(401);
13
+ jsonwebtoken_1.default.verify(token, process.env.JWT_SECRET, (err, user) => {
14
+ if (err)
15
+ res.sendStatus(403);
16
+ req.user = user;
17
+ next();
18
+ });
19
+ };
20
+ exports.authenticateToken = authenticateToken;
21
+ const checkAuth = async (req) => {
22
+ var _a;
23
+ let user = { id: "" };
24
+ const token = req.cookies.token || ((_a = req.headers["authorization"]) === null || _a === void 0 ? void 0 : _a.split(" ")[1]);
25
+ if (!token)
26
+ return null;
27
+ jsonwebtoken_1.default.verify(token, process.env.JWT_SECRET, (err, decoded) => {
28
+ user = decoded;
29
+ });
30
+ return user;
31
+ };
32
+ exports.checkAuth = checkAuth;
dist/middlewares/email.js ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.sendEmail = void 0;
7
+ const dotenv_1 = __importDefault(require("dotenv"));
8
+ const nodemailer_1 = __importDefault(require("nodemailer"));
9
+ dotenv_1.default.config();
10
+ const sendEmail = async (email, subject, html, text) => {
11
+ try {
12
+ const transporter = nodemailer_1.default.createTransport({
13
+ host: "smtp.gmail.com",
14
+ secure: true,
15
+ service: "gmail",
16
+ auth: {
17
+ user: process.env.EMAIL_USERNAME,
18
+ pass: process.env.EMAIL_PASSWORD,
19
+ },
20
+ });
21
+ const options = () => {
22
+ return {
23
+ from: `CV Builder <${process.env.EMAIL_USERNAME}>`,
24
+ to: email,
25
+ subject,
26
+ html: html,
27
+ text: text,
28
+ };
29
+ };
30
+ await transporter.sendMail(options());
31
+ }
32
+ catch (error) {
33
+ return error;
34
+ }
35
+ };
36
+ exports.sendEmail = sendEmail;
dist/middlewares/errorHandler.js ADDED
@@ -0,0 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.errorHandler = void 0;
4
+ const client_1 = require("@prisma/client");
5
+ const errorHandler = (err, req, res, next) => {
6
+ var _a, _b;
7
+ console.error("Error:", err);
8
+ let status = 500;
9
+ let message = "Something went wrong. Please try again.";
10
+ let errors = undefined;
11
+ // Prisma Error Handling
12
+ if (err instanceof client_1.Prisma.PrismaClientKnownRequestError) {
13
+ switch (err.code) {
14
+ case "P2002": // Unique constraint violation
15
+ status = 409;
16
+ const target = ((_a = err.meta) === null || _a === void 0 ? void 0 : _a.target) || [];
17
+ message = `${target.join(", ")} already exists.`;
18
+ break;
19
+ case "P2025": // Record not found
20
+ status = 404;
21
+ message = ((_b = err.meta) === null || _b === void 0 ? void 0 : _b.cause) || "Record not found.";
22
+ break;
23
+ case "P2003": // Foreign key constraint violation
24
+ status = 400;
25
+ message = "Foreign key constraint failed.";
26
+ break;
27
+ default:
28
+ status = 400;
29
+ message = `Database error: ${err.message}`;
30
+ }
31
+ }
32
+ else if (err instanceof client_1.Prisma.PrismaClientValidationError) {
33
+ status = 400;
34
+ message = "Validation error in database request.";
35
+ }
36
+ else if (err.name === "ValidationError") {
37
+ status = 400;
38
+ message = err.message;
39
+ }
40
+ else if (err.status) {
41
+ status = err.status;
42
+ message = err.message;
43
+ }
44
+ res.status(status).json({
45
+ success: false,
46
+ message,
47
+ errors,
48
+ stack: process.env.NODE_ENV === "development" ? err.stack : undefined,
49
+ });
50
+ };
51
+ exports.errorHandler = errorHandler;
dist/middlewares/file.js ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.upLoadFiles = exports.mapFiles = void 0;
4
+ const dotenv = require("dotenv");
5
+ dotenv.config();
6
+ const cloudinary = require("cloudinary").v2;
7
+ cloudinary.config({
8
+ cloud_name: process.env.CLOUDINARY_NAME,
9
+ api_key: process.env.CLOUDINARY_KEY,
10
+ api_secret: process.env.CLOUDINARY_SECRET,
11
+ });
12
+ const mapFiles = async (files) => {
13
+ let fls = [];
14
+ if (files && (files === null || files === void 0 ? void 0 : files.length) > 0) {
15
+ for await (let file of files) {
16
+ fls.push({
17
+ name: file === null || file === void 0 ? void 0 : file.name,
18
+ type: file === null || file === void 0 ? void 0 : file.type,
19
+ uri: (file === null || file === void 0 ? void 0 : file.uri.includes("res.cloudinary.com"))
20
+ ? file === null || file === void 0 ? void 0 : file.uri
21
+ : await (0, exports.upLoadFiles)(file === null || file === void 0 ? void 0 : file.uri, file === null || file === void 0 ? void 0 : file.name),
22
+ });
23
+ }
24
+ }
25
+ return fls;
26
+ };
27
+ exports.mapFiles = mapFiles;
28
+ const upLoadFiles = async (file, fileName) => {
29
+ const uri = await cloudinary.uploader.upload(file, { public_id: fileName });
30
+ return uri === null || uri === void 0 ? void 0 : uri.secure_url;
31
+ };
32
+ exports.upLoadFiles = upLoadFiles;
dist/middlewares/generateOTP.js ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.generateOTP = void 0;
4
+ const generateOTP = () => {
5
+ let otp;
6
+ const otpDate = Date.now();
7
+ const numbers = 123456;
8
+ // const length: number = Math.floor(Math.random() * 2)
9
+ const randomNumbers = Math.floor(numbers + Math.random() * 100000);
10
+ otp = randomNumbers;
11
+ return { otp, otpDate };
12
+ };
13
+ exports.generateOTP = generateOTP;
dist/models/auth/index.js ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const mongoose_1 = __importDefault(require("mongoose"));
7
+ const { isEmail } = require("validator");
8
+ const Schema = mongoose_1.default.Schema;
9
+ const auth = new Schema({
10
+ firstName: { type: String, required: true, min: 3 },
11
+ lastName: { type: String, required: true, min: 3 },
12
+ email: {
13
+ type: String,
14
+ required: true,
15
+ unique: true,
16
+ lowercase: true,
17
+ validate: isEmail,
18
+ },
19
+ password: { type: String, required: true, min: 6 },
20
+ manageOTP: {
21
+ otp: { type: Number },
22
+ otpDate: { type: Number },
23
+ },
24
+ }, { timestamps: true });
25
+ const userAuth = mongoose_1.default.model("user", auth);
26
+ exports.default = userAuth;
dist/models/project/index.js ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Project = void 0;
4
+ const mongoose_1 = require("mongoose");
5
+ const resourceSchema = new mongoose_1.Schema({
6
+ type: { type: String, enum: ["video", "article", "course"], required: true },
7
+ url: { type: String, required: true },
8
+ title: { type: String, required: true },
9
+ });
10
+ const projectSchema = new mongoose_1.Schema({
11
+ title: { type: String, required: true },
12
+ difficulty: {
13
+ type: String,
14
+ enum: ["beginner", "intermediate", "advanced"],
15
+ required: true,
16
+ },
17
+ description: { type: String, required: true },
18
+ requirements: [{ type: String }],
19
+ resources: [resourceSchema],
20
+ }, { timestamps: true });
21
+ exports.Project = (0, mongoose_1.model)("Project", projectSchema);
dist/prisma.config.js ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ // This file was generated by Prisma and assumes you have installed the following:
4
+ // npm install --save-dev prisma dotenv
5
+ require("dotenv/config");
6
+ const config_1 = require("prisma/config");
7
+ exports.default = (0, config_1.defineConfig)({
8
+ schema: "prisma/schema.prisma",
9
+ migrations: {
10
+ path: "prisma/migrations",
11
+ },
12
+ engine: "classic",
13
+ datasource: {
14
+ url: (0, config_1.env)("DATABASE_URL"),
15
+ },
16
+ });
dist/prisma/client.js ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const client_1 = require("@prisma/client");
4
+ const prisma = new client_1.PrismaClient();
5
+ exports.default = prisma;
dist/prisma/migrations/generate-slugs.js ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const client_1 = require("@prisma/client");
4
+ const prisma = new client_1.PrismaClient();
5
+ function generateSlug(title) {
6
+ return title
7
+ .toLowerCase()
8
+ .replace(/[^a-z0-9]+/g, "-")
9
+ .replace(/^-+|-+$/g, "");
10
+ }
11
+ async function generateSlugs() {
12
+ const projects = await prisma.project.findMany({
13
+ where: { slug: null },
14
+ });
15
+ console.log(`Generating slugs for ${projects.length} projects...`);
16
+ for (const project of projects) {
17
+ let slug = generateSlug(project.title);
18
+ let counter = 1;
19
+ // Handle duplicates
20
+ while (await prisma.project.findUnique({ where: { slug } })) {
21
+ slug = `${generateSlug(project.title)}-${counter}`;
22
+ counter++;
23
+ }
24
+ await prisma.project.update({
25
+ where: { id: project.id },
26
+ data: { slug },
27
+ });
28
+ console.log(`✓ ${project.title} -> ${slug}`);
29
+ }
30
+ console.log("Done!");
31
+ }
32
+ generateSlugs()
33
+ .catch(console.error)
34
+ .finally(() => prisma.$disconnect());
dist/prisma/seed.js ADDED
@@ -0,0 +1,303 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const client_1 = require("@prisma/client");
4
+ const prisma = new client_1.PrismaClient();
5
+ function generateSlug(title) {
6
+ return title
7
+ .toLowerCase()
8
+ .replace(/[^a-z0-9]+/g, "-")
9
+ .replace(/^-+|-+$/g, "");
10
+ }
11
+ const projects = [
12
+ {
13
+ title: "Landing Page Template",
14
+ slug: "landing-page-template",
15
+ difficultyLevel: client_1.Difficulty.BEGINNER,
16
+ description: "Build a responsive landing page with modern design principles",
17
+ technologies: ["HTML", "CSS"],
18
+ categories: ["Frontend", "Web Design"],
19
+ estimatedTime: "4-6 hours",
20
+ learningObjectives: [
21
+ "Responsive design",
22
+ "CSS Flexbox/Grid",
23
+ "Modern UI patterns",
24
+ ],
25
+ resourceLinks: [
26
+ { type: "video", url: "#", title: "HTML & CSS Crash Course" },
27
+ ],
28
+ featured: true,
29
+ },
30
+ {
31
+ title: "E-Commerce Dashboard",
32
+ slug: "ecommerce-dashboard",
33
+ difficultyLevel: client_1.Difficulty.INTERMEDIATE,
34
+ description: "Create dynamic admin dashboard with React and Chart.js",
35
+ technologies: ["React", "Chart.js"],
36
+ categories: ["Frontend", "Data Visualization"],
37
+ estimatedTime: "12-16 hours",
38
+ learningObjectives: [
39
+ "React state management",
40
+ "Data visualization",
41
+ "Dashboard UX",
42
+ ],
43
+ resourceLinks: [
44
+ { type: "article", url: "#", title: "React State Management Guide" },
45
+ ],
46
+ featured: true,
47
+ },
48
+ {
49
+ title: "E-Commerce REST API",
50
+ slug: "ecommerce-rest-api",
51
+ difficultyLevel: client_1.Difficulty.INTERMEDIATE,
52
+ description: "Develop a Node.js API with Express and MongoDB for product management",
53
+ technologies: ["Node.js", "Express", "MongoDB"],
54
+ categories: ["Backend", "API Development"],
55
+ estimatedTime: "16-20 hours",
56
+ learningObjectives: [
57
+ "RESTful API design",
58
+ "Database modeling",
59
+ "Authentication",
60
+ ],
61
+ resourceLinks: [
62
+ { type: "course", url: "#", title: "Node.js Fundamentals" },
63
+ ],
64
+ featured: false,
65
+ },
66
+ {
67
+ title: "Social Media Dashboard",
68
+ slug: "social-media-dashboard",
69
+ difficultyLevel: client_1.Difficulty.ADVANCED,
70
+ description: "Build real-time dashboard with React, GraphQL and WebSockets",
71
+ technologies: ["React", "GraphQL", "WebSockets"],
72
+ categories: ["Full-stack", "Real-time"],
73
+ estimatedTime: "24-32 hours",
74
+ learningObjectives: [
75
+ "GraphQL subscriptions",
76
+ "Real-time data",
77
+ "Advanced state management",
78
+ ],
79
+ resourceLinks: [
80
+ { type: "video", url: "#", title: "GraphQL Subscriptions" },
81
+ ],
82
+ featured: true,
83
+ },
84
+ {
85
+ title: "Portfolio Website",
86
+ slug: "portfolio-website",
87
+ difficultyLevel: client_1.Difficulty.BEGINNER,
88
+ description: "Create a personal portfolio with Next.js and Tailwind CSS",
89
+ technologies: ["Next.js", "Tailwind CSS"],
90
+ categories: ["Frontend", "Web Design"],
91
+ estimatedTime: "8-10 hours",
92
+ learningObjectives: [
93
+ "Next.js basics",
94
+ "Tailwind CSS",
95
+ "Portfolio best practices",
96
+ ],
97
+ resourceLinks: [
98
+ { type: "article", url: "#", title: "Next.js Deployment Guide" },
99
+ ],
100
+ featured: false,
101
+ },
102
+ {
103
+ title: "React Todo App with Firebase",
104
+ slug: "react-todo-firebase",
105
+ difficultyLevel: client_1.Difficulty.INTERMEDIATE,
106
+ description: "Create a collaborative todo application with real-time sync",
107
+ technologies: ["React", "Firebase"],
108
+ categories: ["Frontend", "Real-time"],
109
+ estimatedTime: "10-14 hours",
110
+ learningObjectives: [
111
+ "Firebase integration",
112
+ "Real-time sync",
113
+ "CRUD operations",
114
+ ],
115
+ resourceLinks: [
116
+ { type: "article", url: "#", title: "Firebase Realtime Database Guide" },
117
+ ],
118
+ featured: false,
119
+ },
120
+ {
121
+ title: "Authentication System",
122
+ slug: "authentication-system",
123
+ difficultyLevel: client_1.Difficulty.ADVANCED,
124
+ description: "Implement OAuth 2.0 flow with Node.js and Passport.js",
125
+ technologies: ["Node.js", "Passport.js"],
126
+ categories: ["Backend", "Security"],
127
+ estimatedTime: "20-24 hours",
128
+ learningObjectives: ["OAuth 2.0", "JWT tokens", "Security best practices"],
129
+ resourceLinks: [
130
+ { type: "course", url: "#", title: "Web Security Fundamentals" },
131
+ ],
132
+ featured: false,
133
+ },
134
+ {
135
+ title: "Mobile Recipe App",
136
+ slug: "mobile-recipe-app",
137
+ difficultyLevel: client_1.Difficulty.INTERMEDIATE,
138
+ description: "Build cross-platform recipe manager with React Native",
139
+ technologies: ["React Native"],
140
+ categories: ["Mobile", "Frontend"],
141
+ estimatedTime: "18-22 hours",
142
+ learningObjectives: [
143
+ "React Native basics",
144
+ "Mobile navigation",
145
+ "API integration",
146
+ ],
147
+ resourceLinks: [
148
+ { type: "video", url: "#", title: "React Native Navigation Tutorial" },
149
+ ],
150
+ featured: false,
151
+ },
152
+ {
153
+ title: "CMS Platform",
154
+ slug: "cms-platform",
155
+ difficultyLevel: client_1.Difficulty.ADVANCED,
156
+ description: "Headless CMS with Next.js and Sanity.io",
157
+ technologies: ["Next.js", "Sanity.io"],
158
+ categories: ["Full-stack", "CMS"],
159
+ estimatedTime: "28-36 hours",
160
+ learningObjectives: ["Headless CMS", "Content modeling", "Next.js ISR"],
161
+ resourceLinks: [
162
+ { type: "article", url: "#", title: "Sanity Studio Customization" },
163
+ ],
164
+ featured: true,
165
+ },
166
+ {
167
+ title: "AI Chat Interface",
168
+ slug: "ai-chat-interface",
169
+ difficultyLevel: client_1.Difficulty.ADVANCED,
170
+ description: "Build GPT-4 chatbot with streaming responses",
171
+ technologies: ["GPT-4", "Streaming"],
172
+ categories: ["AI/ML", "Full-stack"],
173
+ estimatedTime: "24-30 hours",
174
+ learningObjectives: ["LLM integration", "Streaming responses", "Chat UX"],
175
+ resourceLinks: [
176
+ { type: "course", url: "#", title: "LLM Integration Patterns" },
177
+ ],
178
+ featured: true,
179
+ },
180
+ {
181
+ title: "Real-time Chat Application",
182
+ slug: "realtime-chat-app",
183
+ difficultyLevel: client_1.Difficulty.INTERMEDIATE,
184
+ description: "Build chat app with Socket.io and React",
185
+ technologies: ["Socket.io", "React"],
186
+ categories: ["Full-stack", "Real-time"],
187
+ estimatedTime: "14-18 hours",
188
+ learningObjectives: [
189
+ "WebSocket protocol",
190
+ "Real-time messaging",
191
+ "Chat features",
192
+ ],
193
+ resourceLinks: [
194
+ { type: "video", url: "#", title: "WebSocket Fundamentals" },
195
+ ],
196
+ featured: false,
197
+ },
198
+ {
199
+ title: "Serverless API Gateway",
200
+ slug: "serverless-api-gateway",
201
+ difficultyLevel: client_1.Difficulty.ADVANCED,
202
+ description: "Create REST API using AWS Lambda & API Gateway",
203
+ technologies: ["AWS Lambda", "API Gateway"],
204
+ categories: ["Backend", "Cloud"],
205
+ estimatedTime: "20-26 hours",
206
+ learningObjectives: [
207
+ "Serverless architecture",
208
+ "AWS services",
209
+ "API design",
210
+ ],
211
+ resourceLinks: [
212
+ { type: "article", url: "#", title: "AWS Serverless Patterns" },
213
+ ],
214
+ featured: false,
215
+ },
216
+ {
217
+ title: "E-commerce Product Search",
218
+ slug: "ecommerce-product-search",
219
+ difficultyLevel: client_1.Difficulty.ADVANCED,
220
+ description: "Implement search functionality with Algolia",
221
+ technologies: ["Algolia"],
222
+ categories: ["Frontend", "Search"],
223
+ estimatedTime: "16-20 hours",
224
+ learningObjectives: [
225
+ "Search algorithms",
226
+ "Algolia integration",
227
+ "Search UX",
228
+ ],
229
+ resourceLinks: [
230
+ { type: "course", url: "#", title: "Search Engine Optimization" },
231
+ ],
232
+ featured: false,
233
+ },
234
+ {
235
+ title: "Mobile Payment Integration",
236
+ slug: "mobile-payment-integration",
237
+ difficultyLevel: client_1.Difficulty.ADVANCED,
238
+ description: "Add Stripe payments to React Native app",
239
+ technologies: ["React Native", "Stripe"],
240
+ categories: ["Mobile", "Payments"],
241
+ estimatedTime: "18-24 hours",
242
+ learningObjectives: ["Payment processing", "Stripe API", "Mobile security"],
243
+ resourceLinks: [
244
+ { type: "video", url: "#", title: "Mobile Payment Systems" },
245
+ ],
246
+ featured: false,
247
+ },
248
+ {
249
+ title: "Data Visualization Dashboard",
250
+ slug: "data-visualization-dashboard",
251
+ difficultyLevel: client_1.Difficulty.INTERMEDIATE,
252
+ description: "Create analytics dashboard with D3.js",
253
+ technologies: ["D3.js"],
254
+ categories: ["Frontend", "Data Visualization"],
255
+ estimatedTime: "14-18 hours",
256
+ learningObjectives: [
257
+ "D3.js fundamentals",
258
+ "Data visualization",
259
+ "Interactive charts",
260
+ ],
261
+ resourceLinks: [{ type: "article", url: "#", title: "D3.js Essentials" }],
262
+ featured: false,
263
+ },
264
+ ];
265
+ async function main() {
266
+ console.log("Start seeding...");
267
+ // Create a default user if not exists
268
+ const user = await prisma.user.upsert({
269
+ where: { email: "admin@devresource.com" },
270
+ update: {},
271
+ create: {
272
+ email: "admin@devresource.com",
273
+ firstName: "Admin",
274
+ lastName: "User",
275
+ password: "password123", // In a real app, this should be hashed
276
+ role: "ADMIN",
277
+ },
278
+ });
279
+ console.log(`Created user with id: ${user.id}`);
280
+ for (const project of projects) {
281
+ const createdProject = await prisma.project.upsert({
282
+ where: { slug: project.slug },
283
+ update: {
284
+ ...project,
285
+ createdById: user.id,
286
+ },
287
+ create: {
288
+ ...project,
289
+ createdById: user.id,
290
+ },
291
+ });
292
+ console.log(`Created project with id: ${createdProject.id}`);
293
+ }
294
+ console.log("Seeding finished.");
295
+ }
296
+ main()
297
+ .catch((e) => {
298
+ console.error(e);
299
+ process.exit(1);
300
+ })
301
+ .finally(async () => {
302
+ await prisma.$disconnect();
303
+ });
dist/routes/ai.js ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const express_1 = require("express");
4
+ const ai_1 = require("../controllers/ai");
5
+ const auth_1 = require("../middlewares/auth");
6
+ const router = (0, express_1.Router)();
7
+ router.post("/chat", auth_1.authenticateToken, ai_1.chatWithAI);
8
+ router.get("/chat", auth_1.authenticateToken, ai_1.getAllChatMessages);
9
+ router.get("/chat/history/:projectId", auth_1.authenticateToken, ai_1.getChatHistory);
10
+ router.post("/hint-request", auth_1.authenticateToken, ai_1.requestHint);
11
+ exports.default = router;
dist/routes/analytics.js ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const express_1 = require("express");
4
+ const analytics_1 = require("../controllers/analytics");
5
+ const auth_1 = require("../middlewares/auth");
6
+ const router = (0, express_1.Router)();
7
+ router.get("/user", auth_1.authenticateToken, analytics_1.getUserAnalytics);
8
+ router.get("/admin", auth_1.authenticateToken, analytics_1.getAdminAnalytics);
9
+ exports.default = router;
dist/routes/auth.js ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const express_1 = __importDefault(require("express"));
7
+ const auth_1 = require("../controllers/auth");
8
+ const auth_2 = require("../middlewares/auth");
9
+ const passport_1 = __importDefault(require("../utils/passport"));
10
+ const router = express_1.default.Router();
11
+ router.post("/register", auth_1.signUp);
12
+ router.post("/login", auth_1.login);
13
+ router.post("/logout", auth_1.logout);
14
+ router.get("/me", auth_2.authenticateToken, auth_1.me);
15
+ router.post("/update/:id", auth_2.authenticateToken, auth_1.updateUser);
16
+ router.get("/user/:id", auth_2.authenticateToken, auth_1.getUser);
17
+ router.post("/forgetPassword", auth_1.forgetPassword);
18
+ router.post("/verifyOtp", auth_1.verifyOTP);
19
+ router.post("/resetPassword", auth_1.resetPassword);
20
+ router.get("/google", passport_1.default.authenticate("google", { scope: ["profile", "email"] }));
21
+ router.get("/google/callback", passport_1.default.authenticate("google", {
22
+ session: false,
23
+ failureRedirect: `${process.env.CLIENT_URL}/auth/signin?error=auth_failed`,
24
+ }), auth_1.googleAuthCallback);
25
+ router.get("/github", passport_1.default.authenticate("github", { scope: ["user:email"] }));
26
+ router.get("/github/callback", passport_1.default.authenticate("github", {
27
+ session: false,
28
+ failureRedirect: `${process.env.CLIENT_URL}/auth/signin?error=auth_failed`,
29
+ }), auth_1.githubAuthCallback);
30
+ exports.default = router;
dist/routes/code-review.js ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const express_1 = require("express");
4
+ const code_review_1 = require("../controllers/code-review");
5
+ const auth_1 = require("../middlewares/auth");
6
+ const router = (0, express_1.Router)();
7
+ router.post("/analyze", auth_1.authenticateToken, code_review_1.analyzeCode);
8
+ router.get("/:id", auth_1.authenticateToken, code_review_1.getSubmissionReview);
9
+ exports.default = router;
dist/routes/community.js ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const express_1 = require("express");
4
+ const community_1 = require("../controllers/community");
5
+ const auth_1 = require("../middlewares/auth");
6
+ const router = (0, express_1.Router)();
7
+ router.get("/submissions/public", community_1.getPublicSubmissions);
8
+ router.post("/submissions/:id/comment", auth_1.authenticateToken, community_1.commentOnSubmission);
9
+ router.post("/submissions/:id/vote", auth_1.authenticateToken, community_1.voteOnSubmission);
10
+ router.get("/forum/threads", community_1.getForumThreads);
11
+ router.post("/forum/threads", auth_1.authenticateToken, community_1.createForumThread);
12
+ exports.default = router;
dist/routes/events.js ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const express_1 = __importDefault(require("express"));
7
+ const events_1 = require("../controllers/events");
8
+ const auth_1 = require("../middlewares/auth");
9
+ const router = express_1.default.Router();
10
+ router.post("/", auth_1.authenticateToken, events_1.createEvent);
11
+ router.get("/", events_1.getEvents);
12
+ router.get("/:id", events_1.getEvent);
13
+ router.post("/:eventId/join", auth_1.authenticateToken, events_1.joinEvent);
14
+ router.delete("/:eventId/leave", auth_1.authenticateToken, events_1.leaveEvent);
15
+ router.patch("/:eventId/score/:userId", auth_1.authenticateToken, events_1.updateEventScore);
16
+ router.get("/:eventId/leaderboard", events_1.getLeaderboard);
17
+ exports.default = router;
dist/routes/gamification.js ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const express_1 = require("express");
4
+ const gamification_1 = require("../controllers/gamification");
5
+ const auth_1 = require("../middlewares/auth");
6
+ const router = (0, express_1.Router)();
7
+ router.get("/leaderboard", gamification_1.getLeaderboard);
8
+ router.get("/achievements", auth_1.authenticateToken, gamification_1.getUserAchievements);
9
+ exports.default = router;
dist/routes/notifications.js ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const express_1 = __importDefault(require("express"));
7
+ const notifications_1 = require("../controllers/notifications");
8
+ const auth_1 = require("../middlewares/auth");
9
+ const router = express_1.default.Router();
10
+ router.get("/", auth_1.authenticateToken, notifications_1.getNotifications);
11
+ router.patch("/:notificationId/read", auth_1.authenticateToken, notifications_1.markAsRead);
12
+ router.patch("/read-all", auth_1.authenticateToken, notifications_1.markAllAsRead);
13
+ router.delete("/:notificationId", auth_1.authenticateToken, notifications_1.deleteNotification);
14
+ exports.default = router;
dist/routes/paths.js ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const express_1 = require("express");
4
+ const paths_1 = require("../controllers/paths");
5
+ const auth_1 = require("../middlewares/auth");
6
+ const router = (0, express_1.Router)();
7
+ router.get("/", paths_1.getLearningPaths);
8
+ router.get("/:id/progress", auth_1.authenticateToken, paths_1.getPathProgress);
9
+ router.post("/:id/start", auth_1.authenticateToken, paths_1.startPath);
10
+ exports.default = router;
dist/routes/project.js ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const express_1 = require("express");
4
+ const projects_1 = require("../controllers/projects");
5
+ const auth_1 = require("../middlewares/auth");
6
+ const router = (0, express_1.Router)();
7
+ router.get("/", projects_1.getProjects);
8
+ router.get("/progress", auth_1.authenticateToken, projects_1.getUserProgress);
9
+ router.get("/featured", projects_1.getFeaturedProjects);
10
+ router.get("/:id", projects_1.getProjectById);
11
+ router.post("/", auth_1.authenticateToken, projects_1.createProject);
12
+ router.put("/:id", auth_1.authenticateToken, projects_1.updateProject);
13
+ router.delete("/:id", auth_1.authenticateToken, projects_1.deleteProject);
14
+ // User Progress
15
+ router.post("/:id/start", auth_1.authenticateToken, projects_1.startProject);
16
+ router.put("/:id/progress", auth_1.authenticateToken, projects_1.updateProgress);
17
+ router.post("/:id/complete", auth_1.authenticateToken, projects_1.completeProject);
18
+ // Milestones
19
+ router.post("/:id/milestones/:milestoneId/complete", auth_1.authenticateToken, projects_1.completeMilestone);
20
+ // Submissions
21
+ router.post("/:id/submit", auth_1.authenticateToken, projects_1.submitProject);
22
+ exports.default = router;
dist/routes/social.js ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const express_1 = __importDefault(require("express"));
7
+ const social_1 = require("../controllers/social");
8
+ const auth_1 = require("../middlewares/auth");
9
+ const router = express_1.default.Router();
10
+ router.post("/follow", auth_1.authenticateToken, social_1.followUser);
11
+ router.delete("/unfollow/:followingId", auth_1.authenticateToken, social_1.unfollowUser);
12
+ router.get("/followers/:userId", auth_1.authenticateToken, social_1.getFollowers);
13
+ router.get("/following/:userId", auth_1.authenticateToken, social_1.getFollowing);
14
+ router.get("/activity-feed", auth_1.authenticateToken, social_1.getActivityFeed);
15
+ router.post("/share", auth_1.authenticateToken, social_1.shareProject);
16
+ exports.default = router;
dist/routes/teams.js ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const express_1 = require("express");
4
+ const teams_1 = require("../controllers/teams");
5
+ const auth_1 = require("../middlewares/auth");
6
+ const router = (0, express_1.Router)();
7
+ router.post("/create", auth_1.authenticateToken, teams_1.createTeam);
8
+ router.post("/:id/join", auth_1.authenticateToken, teams_1.joinTeam);
9
+ router.get("/:id/projects", auth_1.authenticateToken, teams_1.getTeamProjects);
10
+ exports.default = router;
dist/routes/user.js ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const express_1 = require("express");
4
+ const projects_1 = require("../controllers/projects");
5
+ const auth_1 = require("../middlewares/auth");
6
+ const router = (0, express_1.Router)();
7
+ router.get("/progress", auth_1.authenticateToken, projects_1.getUserProgress);
8
+ router.post("/projects/:id/start", auth_1.authenticateToken, projects_1.startProject);
9
+ router.put("/projects/:id/progress", auth_1.authenticateToken, projects_1.updateProgress);
10
+ router.post("/projects/:id/complete", auth_1.authenticateToken, projects_1.completeProject);
11
+ exports.default = router;