Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ database:
uri: "<YOUR_MONGODB_URI>"
```

Replace `<YOUR_MONGODB_URI>` with your actual connection string including the database name and any required credentials (for example: `mongodb://localhost:27017/your_database_name`). Do not commit real connection strings or credentials to version control.

Without a valid MongoDB URI, the backend will fail to start.

---
Expand Down
6 changes: 3 additions & 3 deletions backend/config/config.prod.sample.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ server:
port: 1313 # The port number your backend server will run on

database:
uri: "mongodb+srv://<username>:<password>@<cluster-url>/<database-name>"
# Replace with your MongoDB Atlas connection string
# Get this from your MongoDB Atlas dashboard after creating a cluster and database
uri: "<YOUR_MONGODB_URI>"
# Replace with your MongoDB connection string (include DB name/credentials)
# Example: mongodb://localhost:27017/your_database_name or a MongoDB Atlas URI

gemini:
apiKey: "<YOUR_GEMINI_API_KEY>"
Expand Down
43 changes: 43 additions & 0 deletions backend/config/config.prod.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
server:
port: 1313 # The port number your backend server will run on

database:
uri: "mongodb://localhost:27017"
# Replace with your MongoDB Atlas connection string
# Get this from your MongoDB Atlas dashboard after creating a cluster and database

gemini:
apiKey: "<YOUR_GEMINI_API_KEY>"
# API key for OpenAI / Gemini model access
# Obtain from your OpenRouter.ai or OpenAI account dashboard

jwt:
secret: "<YOUR_JWT_SECRET>"
# A secret string used to sign JWT tokens
# Generate a strong random string (e.g. use `openssl rand -hex 32`)

expiry: 1440
# Token expiry time in minutes (e.g. 1440 = 24 hours)

smtp:
host: "smtp.gmail.com"
# SMTP server host for sending emails (example is Gmail SMTP)

port: 587
# SMTP server port (587 for TLS)

username: "<YOUR_EMAIL_ADDRESS>"
# Email username (your email address)

password: "<YOUR_EMAIL_PASSWORD_OR_APP_PASSWORD>"
# Password for the email or app-specific password if 2FA is enabled

senderEmail: "<YOUR_EMAIL_ADDRESS>"
# The 'from' email address used when sending mails

senderName: "DebateAI Team"

googleOAuth:
clientID: "<YOUR_GOOGLE_OAUTH_CLIENT_ID>"
# Google OAuth Client ID for OAuth login
# Obtain from Google Cloud Console (APIs & Services > Credentials > OAuth 2.0 Client IDs)
11 changes: 6 additions & 5 deletions frontend/src/Pages/Admin/AdminDashboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
type Admin,
} from "@/services/adminService";
import { Button } from "@/components/ui/button";
import { getLocalString, getLocalJSON } from "@/utils/storage";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import {
Expand Down Expand Up @@ -364,15 +365,15 @@ export default function AdminDashboard() {
}, []);

useEffect(() => {
const adminToken = localStorage.getItem("adminToken");
const adminData = localStorage.getItem("admin");
const adminToken = getLocalString("adminToken");
const adminData = getLocalJSON<Admin>("admin");

if (!adminToken || !adminData) {
navigate("/admin/login");
return;
}
setToken(adminToken);
setAdmin(JSON.parse(adminData));
setAdmin(adminData);
loadData(adminToken);
}, [loadData, navigate]);

Expand Down Expand Up @@ -504,8 +505,8 @@ export default function AdminDashboard() {
No analytics data available yet.
</p>
<Button
onClick={() => {
const currentToken = token || localStorage.getItem("adminToken");
onClick={() => {
const currentToken = token || getLocalString("adminToken");
if (currentToken) {
loadData(currentToken);
}
Expand Down
7 changes: 4 additions & 3 deletions frontend/src/Pages/CommunityFeed.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React, { useState, useEffect, useCallback } from "react";
import { useUser } from "../hooks/useUser";
import { getLocalString } from '@/utils/storage';
import CommentTree from "../components/CommentTree";
import ProfileHover from "../components/ProfileHover";
import { Button } from "../components/ui/button";
Expand Down Expand Up @@ -278,7 +279,7 @@ const CommunityFeed: React.FC = () => {
const fetchFeed = useCallback(async () => {
try {
setLoading(true);
const token = localStorage.getItem("token");
const token = getLocalString("token");
const userId = currentUserId;
const response = await fetch(`${baseURL}/posts/feed`, {
method: "GET",
Expand Down Expand Up @@ -356,7 +357,7 @@ const CommunityFeed: React.FC = () => {
}, [fetchFeed]);

const handleFollow = async (userId: string, isFollowing: boolean) => {
const token = localStorage.getItem("token");
const token = getLocalString("token");
if (!token) {
alert("Please log in to follow users");
return;
Expand Down Expand Up @@ -402,7 +403,7 @@ const CommunityFeed: React.FC = () => {
};

const handleDeletePost = async (postId: string) => {
const token = localStorage.getItem("token");
const token = getLocalString("token");
if (!token) {
alert("Please log in to delete posts");
return;
Expand Down
63 changes: 40 additions & 23 deletions frontend/src/Pages/DebateRoom.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import React, { useState, useEffect, useRef } from "react";
import { safeParse } from '@/utils/safeParse';
import { useLocation } from "react-router-dom";
import { getLocalString } from '@/utils/storage';
import { Button } from "../components/ui/button";
import { Input } from "../components/ui/input";
import { sendDebateMessage, judgeDebate } from "@/services/vsbot";
Expand Down Expand Up @@ -224,19 +226,38 @@ const DebateRoom: React.FC = () => {
const [user] = useAtom(userAtom);

const [state, setState] = useState<DebateState>(() => {
const savedState = localStorage.getItem(debateKey);
return savedState
? JSON.parse(savedState)
: {
messages: [],
currentPhase: 0,
phaseStep: 0,
isBotTurn: false,
userStance: "",
botStance: "",
timer: phases[0].time,
isDebateEnded: false,
};
const savedState = getLocalString(debateKey);
if (savedState) {
try {
const parsed = JSON.parse(savedState) as Partial<DebateState> | null;
if (parsed && typeof parsed === 'object') {
return {
messages: parsed.messages ?? [],
currentPhase: parsed.currentPhase ?? 0,
phaseStep: parsed.phaseStep ?? 0,
isBotTurn: parsed.isBotTurn ?? false,
userStance: parsed.userStance ?? "",
botStance: parsed.botStance ?? "",
timer: parsed.timer ?? phases[0].time,
isDebateEnded: parsed.isDebateEnded ?? false,
} as DebateState;
}
} catch (err) {
console.warn('Failed to parse saved debate state, clearing corrupt value', err);
try { localStorage.removeItem(debateKey); } catch {}
}
}

return {
messages: [],
currentPhase: 0,
phaseStep: 0,
isBotTurn: false,
userStance: "",
botStance: "",
timer: phases[0].time,
isDebateEnded: false,
};
});
const [finalInput, setFinalInput] = useState("");
const [interimInput, setInterimInput] = useState("");
Expand Down Expand Up @@ -561,21 +582,17 @@ const DebateRoom: React.FC = () => {
const jsonString = extractJSON(result);
console.log("Extracted JSON string:", jsonString);

let judgment: JudgmentData;
try {
judgment = JSON.parse(jsonString);
} catch (parseError) {
console.error("JSON parse error:", parseError, "Trying to fix JSON...");
// Try to fix common JSON issues
let judgment: JudgmentData | null = safeParse<JudgmentData>(jsonString, null);
if (!judgment) {
console.warn('Initial safe parse failed, attempting to fix JSON...');
const fixedJson = jsonString
.replace(/'/g, '"') // Replace single quotes with double quotes
.replace(/(\w+):/g, '"$1":') // Add quotes to keys
.replace(/,\s*}/g, '}') // Remove trailing commas
.replace(/,\s*]/g, ']'); // Remove trailing commas in arrays
try {
judgment = JSON.parse(fixedJson);
} catch (e) {
throw new Error(`Failed to parse JSON: ${e}`);
judgment = safeParse<JudgmentData>(fixedJson, null);
if (!judgment) {
throw new Error('Failed to parse judgment JSON after attempts');
}
}

Expand Down
12 changes: 10 additions & 2 deletions frontend/src/Pages/Game.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -282,9 +282,17 @@ const Game: React.FC = () => {
ws.onopen = () => console.log("WebSocket connection established");
ws.onmessage = (event) => {
try {
handleWebSocketMessage(JSON.parse(event.data));
const raw = event.data;
let parsed: any = null;
try {
parsed = JSON.parse(typeof raw === 'string' ? raw : String(raw));
} catch (err) {
console.warn('Game: failed to parse WS message', err);
return;
}
handleWebSocketMessage(parsed);
} catch (error) {
console.error("Failed to parse WebSocket message:", error);
console.error("Failed to handle WebSocket message:", error);
}
Comment on lines 283 to 296
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Use the safeParse utility for consistency.

This manual parsing logic duplicates the functionality of the safeParse utility introduced in this PR. Using the utility would:

  • Improve consistency across the codebase
  • Reduce duplication
  • Centralize parsing logic
  • Make the code more maintainable
🔎 Recommended refactor
+import { safeParse } from '@/utils/safeParse';
+
 ws.onmessage = (event) => {
   try {
-    const raw = event.data;
-    let parsed: any = null;
-    try {
-      parsed = JSON.parse(typeof raw === 'string' ? raw : String(raw));
-    } catch (err) {
-      console.warn('Game: failed to parse WS message', err);
-      return;
-    }
-    handleWebSocketMessage(parsed);
+    const parsed = safeParse(event.data, null);
+    if (!parsed) return;
+    handleWebSocketMessage(parsed);
   } catch (error) {
     console.error("Failed to handle WebSocket message:", error);
   }
 };
🤖 Prompt for AI Agents
In frontend/src/Pages/Game.tsx around lines 283 to 296, the WebSocket onmessage
handler manually JSON.parses with nested try/catch which duplicates existing
safeParse utility; replace the manual parsing with a call to safeParse(typeof
raw === 'string' ? raw : String(raw)), import safeParse if not already imported,
check the result for success/failure (log a warning on parse failure and
return), and pass the parsed value to handleWebSocketMessage on success so
parsing logic is centralized and consistent.

};
ws.onerror = (error) => console.error("WebSocket error:", error);
Expand Down
15 changes: 8 additions & 7 deletions frontend/src/Pages/Leaderboard.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"use client";

import React, { useState, useEffect, useRef } from "react";
import { getLocalString } from "@/utils/storage";
import {
Table,
TableHeader,
Expand Down Expand Up @@ -93,7 +94,7 @@ const Leaderboard: React.FC = () => {
const loadData = async () => {
try {
setLoading(true);
const token = localStorage.getItem("token");
const token = getLocalString("token");
if (!token) return;

// Try to fetch from gamification endpoint first, fallback to old endpoint
Expand Down Expand Up @@ -121,7 +122,7 @@ const Leaderboard: React.FC = () => {

// Set up WebSocket connection for live updates
useEffect(() => {
const token = localStorage.getItem("token");
const token = getLocalString("token");
if (!token || !user) return;

// Clean up existing connection
Expand Down Expand Up @@ -172,11 +173,11 @@ const Leaderboard: React.FC = () => {
// Reload full leaderboard periodically to ensure accuracy
const reloadTimer = setTimeout(async () => {
try {
const token = localStorage.getItem("token");
if (token) {
const data = await fetchGamificationLeaderboard(token);
setDebaters(data.debaters);
}
const token = getLocalString("token");
if (token) {
const data = await fetchGamificationLeaderboard(token);
setDebaters(data.debaters);
}
} catch (err) {
console.error("Error reloading leaderboard:", err);
}
Expand Down
27 changes: 20 additions & 7 deletions frontend/src/Pages/OnlineDebateRoom.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import JudgmentPopup from "@/components/JudgementPopup";
import SpeechTranscripts from "@/components/SpeechTranscripts";
import { useUser } from "@/hooks/useUser";
import { getAuthToken } from "@/utils/auth";
import { safeParse } from "@/utils/safeParse";
import ReconnectingWebSocket from "reconnecting-websocket";
import { useAtom } from "jotai";
import {
Expand Down Expand Up @@ -555,10 +556,15 @@ const OnlineDebateRoom = (): JSX.Element => {
judgePollRef.current = null;
}
const jsonString = extractJSON(pollData.result);
const judgment: JudgmentData = JSON.parse(jsonString);
setJudgmentData(judgment);
setPopup({ show: false, message: "" });
setShowJudgment(true);
const judgment = safeParse<JudgmentData>(jsonString, null);
if (judgment) {
setJudgmentData(judgment);
setPopup({ show: false, message: "" });
setShowJudgment(true);
} else {
console.warn('Failed to parse judgment JSON from pollData.result');
setPopup({ show: false, message: 'Error parsing judgment result' });
}
submissionStartedRef.current = false;
}
} catch (error) {
Expand Down Expand Up @@ -653,7 +659,10 @@ const OnlineDebateRoom = (): JSX.Element => {
result.message === "Debate already judged"
) {
const jsonString = extractJSON(result.result);
const judgment: JudgmentData = JSON.parse(jsonString);
const judgment = safeParse<JudgmentData>(jsonString, null);
if (!judgment) {
console.warn('Failed to parse judgment JSON from result.result');
}
return judgment;
}
} catch (error) {
Expand Down Expand Up @@ -715,7 +724,7 @@ const OnlineDebateRoom = (): JSX.Element => {

return rolePhases.reduce((acc, phase) => {
const storageKey = `${roomId}_${phase}_${role}`;
const stored = storageKey ? localStorage.getItem(storageKey) : null;
const stored = storageKey ? getLocalString(storageKey) : null;
const fromState = speechTranscripts[phase] ?? "";
const combined =
(typeof fromState === "string" && fromState.trim().length > 0
Expand Down Expand Up @@ -1114,7 +1123,11 @@ const OnlineDebateRoom = (): JSX.Element => {
};

rws.onmessage = async (event) => {
const data: WSMessage = JSON.parse(event.data);
const data = safeParse<WSMessage>(event.data, null);
if (!data) {
console.warn('Received invalid WS message in OnlineDebateRoom');
return;
}
switch (data.type) {
case "topicChange":
if (data.topic !== undefined) setTopic(data.topic);
Expand Down
26 changes: 21 additions & 5 deletions frontend/src/Pages/StrengthenArgument.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ interface WeakStatement {
}

interface Evaluation {
pointsEarned: number;
score: number;
feedback: string;
}

Expand Down Expand Up @@ -125,8 +125,13 @@ const StrengthenArgument: React.FC = () => {
if (!response.ok) {
throw new Error(`Server error (Status: ${response.status}): ${text || "Unknown error"}`);
}
const data: WeakStatement = JSON.parse(text);
if (!data.id || !data.text || !data.topic || !data.stance) {
let data: WeakStatement | null = null;
try {
data = JSON.parse(text) as WeakStatement;
} catch (err) {
console.warn('Failed to parse weak statement response', err);
}
if (!data || !data.id || !data.text || !data.topic || !data.stance) {
throw new Error("Invalid response format: missing fields");
}
setWeakStatement(data);
Expand Down Expand Up @@ -170,9 +175,20 @@ const StrengthenArgument: React.FC = () => {
if (!response.ok) {
throw new Error(`Server error (Status: ${response.status}): ${text || "Unknown error"}`);
}
const data: Evaluation = JSON.parse(text);
let data: Evaluation | null = null;
try {
data = JSON.parse(text) as Evaluation;
} catch (err) {
console.warn("Failed to parse evaluation response", err);
}

// Validate parsed response
if (!data || typeof data.score !== "number" || !data.feedback) {
throw new Error("Invalid evaluation result: missing score or feedback");
}

setFeedback(data.feedback);
setScore(data.pointsEarned);
setScore(data.score);
setShowModal(true);
setCurrentStep(3);
} catch (err: any) {
Expand Down
Loading