Fix: Persist Debate vs Bot Outcomes Using Debate ID#233
Fix: Persist Debate vs Bot Outcomes Using Debate ID#233Devnil434 wants to merge 5 commits intoAOSSIE-Org:mainfrom
Conversation
…cross backend services, models, routes, and frontend components.
📝 WalkthroughWalkthroughThis pull request introduces an assumption extraction feature for debates, leveraging Gemini AI to identify implicit assumptions from debate transcripts. It also fixes a debate vs. bot outcome persistence bug by updating the identifier from userId to debateId, and adjusts password validation requirements from 8 to 6 characters across backend and frontend. Configuration placeholders are removed. Changes
Sequence DiagramsequenceDiagram
participant Frontend
participant Controller as Backend Controller
participant Service as Assumption Service
participant Cache as MongoDB (Cache)
participant Transcript as MongoDB (Transcripts)
participant AI as Gemini AI
participant DB as MongoDB (Assumptions)
Frontend->>Controller: GET /debates/:debateId/assumptions
Controller->>Service: ExtractAssumptions(debateId)
Service->>Service: Validate Gemini client
Service->>Cache: Check cached assumptions
alt Cache Hit
Cache-->>Service: Return cached assumptions
else Cache Miss
Service->>Transcript: Aggregate debate transcript
alt Transcript Found
Transcript-->>Service: Return transcript data
else Transcript Not Found
Service-->>Controller: Error: no transcript found
Controller-->>Frontend: 404 Error
end
Service->>AI: Call Gemini with assumption prompt
AI-->>Service: Return assumptions JSON
Service->>Service: Parse AI response
Service->>DB: Persist assumptions
DB-->>Service: Confirm storage
end
Service-->>Controller: Return DebateAssumption[]
Controller-->>Frontend: JSON response (assumptions + count)
Frontend->>Frontend: Render grouped by side
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 5
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
backend/controllers/debatevsbot_controller.go (1)
217-225: Potential race condition: fetching "latest" debate instead of the specific debate being judged.
GetLatestDebateVsBot(email)retrieves the most recent debate by email, but this may not be the same debate identified byreq.DebateId. If a user starts a new debate before judging completes, the transcript could be saved with incorrectTopicandBotName.Consider fetching the debate by
req.DebateIddirectly:Suggested fix
- // Get the latest debate information to extract proper details - latestDebate, err := db.GetLatestDebateVsBot(email) + // Get the specific debate being judged + latestDebate, err := db.GetDebateVsBotByID(req.DebateId)You would need to add a
GetDebateVsBotByIDfunction todb/db.go:func GetDebateVsBotByID(debateId string) (*models.DebateVsBot, error) { objID, err := primitive.ObjectIDFromHex(debateId) if err != nil { return nil, fmt.Errorf("invalid debate ID: %w", err) } var debate models.DebateVsBot err = DebateVsBotCollection.FindOne(context.Background(), bson.M{"_id": objID}).Decode(&debate) if err != nil { return nil, err } return &debate, nil }
🤖 Fix all issues with AI agents
In `@backend/controllers/debatevsbot_controller.go`:
- Around line 36-39: The frontend is still sending userId but the backend
JudgeRequest requires debateId; update the JudgeRequest type in the frontend
service (rename the field userId: string to debateId: string in the
JudgeRequest/interface used in vsbot.ts) and modify the caller judgeDebateResult
to pass debateId: debateData.debateId instead of userId: debateData.userId so
judgeDebateResult sends debateId matching the backend binding.
In `@backend/services/assumption_service.go`:
- Around line 245-257: In parseAssumptionResponse, avoid logging the full raw AI
`response` on JSON parse errors; instead redact or truncate it before logging to
prevent leaking PII. Update the error handling after `json.Unmarshal` to create
a safe preview (e.g., call a new helper like `truncateResponse(response, 200)`
or `redactSensitive(response)`) and log that preview along with the parse error
in the `log.Printf` call; keep the original `response` out of logs and ensure
the helper returns a deterministic, shortened string for troubleshooting.
In `@backend/structs/auth.go`:
- Line 5: Restore the password minimum length to 8 in the struct tags for all
affected request types: update the `Password` field binding tags on
`SignUpRequest`, `LoginRequest`, and `VerifyForgotPasswordRequest` from `min=6`
back to `min=8`, and ensure any related server-side validation logic that checks
length is consistent with the tag change.
In `@frontend/src/components/Assumptions.tsx`:
- Around line 22-24: The effect in the Assumptions component calls
fetchAssumptions but doesn’t list it in the dependency array, which can produce
stale closures and lint warnings; fix by either including fetchAssumptions in
the useEffect dependency array (i.e., change useEffect(..., [debateId]) to
useEffect(..., [debateId, fetchAssumptions])) or move the fetch logic inline
inside the useEffect callback so the effect depends only on debateId; ensure the
chosen approach preserves any memoization (e.g., React.useCallback) around
fetchAssumptions if you add it to dependencies.
- Around line 43-45: The code is using assumptions.find(...) which returns only
the first matching DebateAssumption per side and drops other participants'
entries; change the two usages that define forSideAssumptions and
againstSideAssumptions to use assumptions.filter((a) => a.side === 'for') and
assumptions.filter((a) => a.side === 'against') so you collect all entries per
side, and then update any downstream rendering or mapping logic that assumed a
single object (e.g., where forSideAssumptions/againstSideAssumptions are
iterated or accessed) to handle arrays.
🧹 Nitpick comments (7)
frontend/src/Pages/Authentication/forms.tsx (2)
42-48: Consider extracting duplicated Google Sign-In logic into a custom hook.The
handleGoogleLogincallback and the associateduseEffectfor Google Sign-In initialization are identical in bothLoginFormandSignUpForm. This could be consolidated into a reusable custom hook (e.g.,useGoogleSignIn).Also applies to: 156-162
143-146: Minor: Inconsistent error handling pattern.
SignUpFormandResetPasswordFormuseauthContext.handleError()for password validation errors, whileLoginFormuses a locallocalErrorstate. Consider aligning the error handling approach across all forms for consistency.backend/controllers/debatevsbot_controller.go (1)
211-215: Consider surfacing persistence failures to the client.While the decision to not fail the request when outcome persistence fails is reasonable (judging succeeded), the client has no visibility into persistence failures. Consider including a warning field in the response (e.g.,
"persistenceWarning": "Failed to save outcome") so callers can detect and potentially retry or alert users.Example response enhancement
c.JSON(200, JudgeResponse{ Result: result, + PersistenceWarning: persistenceWarning, // Set if db update failed })backend/controllers/assumption_controller.go (1)
30-36: Consider using sentinel errors or error types instead of string matching.Comparing error messages with string literals is brittle—if the service layer changes its error messages, this mapping silently breaks. Using sentinel errors (e.g.,
errors.Is(err, services.ErrGeminiNotInitialized)) or custom error types would be more maintainable.♻️ Example approach with sentinel errors
In
services/assumption_service.go:var ( ErrGeminiNotInitialized = errors.New("AI service not available - Gemini client not initialized") ErrNoTranscript = errors.New("no transcript found for this debate") )In controller:
- if err.Error() == "AI service not available - Gemini client not initialized" { + if errors.Is(err, services.ErrGeminiNotInitialized) { statusCode = http.StatusServiceUnavailable errorMessage = "AI analysis service is currently unavailable. Please ensure the Gemini API is configured." - } else if err.Error() == "no transcript found for this debate" { + } else if errors.Is(err, services.ErrNoTranscript) { statusCode = http.StatusNotFound errorMessage = "No debate transcript found for this debate ID." }frontend/src/services/assumptionService.ts (1)
26-54: Consider adding request cancellation support for component lifecycle.If the calling component unmounts while the fetch is in-flight, this could lead to state updates on an unmounted component. Adding
AbortControllersupport would allow cleanup.♻️ Optional: Add AbortController support
- async getDebateAssumptions(debateId: string): Promise<AssumptionsResponse> { + async getDebateAssumptions(debateId: string, signal?: AbortSignal): Promise<AssumptionsResponse> { const token = getAuthToken(); if (!token) { throw new Error('Authentication token not found'); } const response = await fetch( `${API_BASE_URL}/debates/${debateId}/assumptions`, { method: 'GET', headers: { Authorization: `Bearer ${token}`, }, + signal, } );Then in the component's useEffect, create and abort the controller on cleanup.
backend/services/assumption_service.go (2)
141-145: Unnecessary error check:mongo.ErrNoDocumentsis not returned byFind().
mongo.ErrNoDocumentsis only returned byFindOne(). TheFind()method returns an empty cursor when no documents match, not an error. This check is harmless but misleading.Proposed simplification
cursor, err := transcriptCollection.Find(ctx, bson.M{"roomId": debateID}) -if err != nil && err != mongo.ErrNoDocuments { +if err != nil { return "", err }
117-131: Fallback transcript with minimal metadata may produce low-quality assumptions.When
SavedDebateTranscriptexists but has no actual content, the fallback uses only metadata (topic, type, opponent, result). The AI will attempt to generate assumptions from this sparse information, which could yield generic or misleading results.Consider returning an error or a clear indicator that assumptions cannot be extracted due to insufficient transcript data, rather than proceeding with a metadata-only prompt.
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (15)
.env.examplebackend/cmd/server/main.gobackend/config/config.prod.sample.ymlbackend/controllers/assumption_controller.gobackend/controllers/debatevsbot_controller.gobackend/db/db.gobackend/mainbackend/models/assumption.gobackend/routes/assumption_routes.gobackend/services/assumption_service.gobackend/structs/auth.gofrontend/src/Pages/Authentication/forms.tsxfrontend/src/components/Assumptions.tsxfrontend/src/components/SavedTranscripts.tsxfrontend/src/services/assumptionService.ts
💤 Files with no reviewable changes (2)
- .env.example
- backend/config/config.prod.sample.yml
🧰 Additional context used
🧬 Code graph analysis (9)
frontend/src/components/Assumptions.tsx (2)
backend/models/assumption.go (1)
DebateAssumption(11-19)frontend/src/services/assumptionService.ts (2)
DebateAssumption(5-13)assumptionService(20-55)
backend/routes/assumption_routes.go (1)
backend/controllers/assumption_controller.go (1)
GetDebateAssumptions(12-49)
backend/cmd/server/main.go (1)
backend/routes/assumption_routes.go (1)
SetupAssumptionRoutes(10-13)
frontend/src/services/assumptionService.ts (2)
backend/models/assumption.go (1)
DebateAssumption(11-19)frontend/src/utils/auth.ts (1)
getAuthToken(5-7)
backend/controllers/assumption_controller.go (1)
backend/services/assumption_service.go (1)
ExtractAssumptions(29-86)
backend/models/assumption.go (1)
frontend/src/services/assumptionService.ts (1)
DebateAssumption(5-13)
backend/services/assumption_service.go (4)
backend/models/assumption.go (1)
DebateAssumption(11-19)frontend/src/services/assumptionService.ts (1)
DebateAssumption(5-13)backend/db/db.go (1)
GetCollection(24-26)backend/models/transcript.go (1)
DebateTranscript(9-16)
frontend/src/components/SavedTranscripts.tsx (1)
frontend/src/components/ui/separator.tsx (1)
Separator(29-29)
backend/controllers/debatevsbot_controller.go (3)
backend/websocket/websocket.go (1)
Message(71-94)backend/services/ai.go (1)
Message(21-24)backend/db/db.go (1)
UpdateDebateVsBotOutcome(74-93)
🔇 Additional comments (20)
frontend/src/Pages/Authentication/forms.tsx (2)
29-37: LGTM on the validation logic structure.The early return pattern for client-side validation before the network call is appropriate. Error state management with
localErroris handled correctly.
8-8: Password length security concern requires context clarification.The MIN_PASSWORD_LENGTH constant was newly introduced (not reduced from a prior value). However, the 6-character minimum remains below industry security standards. Current NIST SP 800-63-4 guidance recommends 8 characters for multi-factor authentication and 15 for single-factor. Verify whether this application uses multi-factor authentication and whether backend validation enforces additional constraints. If this remains single-factor only, consider increasing to at least 8 characters per NIST standards.
Likely an incorrect or invalid review comment.
backend/db/db.go (1)
73-92: Core bug fix looks correct.The refactored
UpdateDebateVsBotOutcomefunction properly:
- Validates the
debateIdas a valid MongoDB ObjectID- Filters by
_idinstead of the non-existentuserIdfield- Checks
MatchedCountto surface silent failures when no document matches- Wraps errors with context for better debugging
This aligns with the PR objective to fix debate outcome persistence.
backend/cmd/server/main.go (1)
135-137: LGTM: Assumption routes registered correctly.The new assumption extraction routes are registered within the authenticated route group, following the established pattern. Note that this appears to be a separate feature from the main PR objective (fixing debate outcome persistence).
backend/routes/assumption_routes.go (1)
1-13: LGTM!Clean route registration following RESTful conventions. The route is properly scoped under the authenticated router group (as shown in main.go integration) and correctly maps to the controller handler.
frontend/src/components/SavedTranscripts.tsx (2)
32-32: LGTM!Import correctly added for the Assumptions component.
472-477: LGTM!The Assumptions component is properly integrated within the Dialog view, receiving the debate ID from the selected transcript. Placement after the "Create Post" section and before comments provides a logical flow.
backend/controllers/assumption_controller.go (1)
12-48: Overall structure is sound.The controller correctly validates input, delegates to the service layer, and maps errors to appropriate HTTP status codes. The response structure with
assumptionsandcountis clear.frontend/src/services/assumptionService.ts (1)
5-18: LGTM!The TypeScript interfaces correctly align with the backend model (
backend/models/assumption.go). Field names and types match the JSON serialization from Go.frontend/src/components/Assumptions.tsx (1)
47-204: Well-structured component with clear UI states.The component handles loading, error, and empty states appropriately. The retry functionality on error is user-friendly, and the informational alert about AI-generated content sets proper expectations.
backend/models/assumption.go (2)
11-19: LGTM - struct definition is well-formed.The struct fields align with the frontend TypeScript interface expectations and use appropriate bson/json tags. The
omitemptytags for optional fields (ID, ParticipantID, ParticipantEmail) are correctly applied.
22-33: No duplicateidfield issue—field shadowing prevents it.Go's JSON encoder applies field shadowing rules: when a field name appears in both the outer struct and an embedded struct, the outer field takes precedence. The explicitly declared
ID stringfield in the anonymous struct shadows the embeddedAlias.IDfield, so only theID stringfield is marshaled. The embeddedID primitive.ObjectID(withomitempty) is never encoded to JSON, eliminating any risk of duplicates.The line
a.ID = primitive.NilObjectIDis redundant and serves no purpose; the embedded field won't be marshaled regardless. Consider removing it for clarity, though it doesn't cause harm.backend/services/assumption_service.go (8)
20-26: LGTM - AssumptionResponse struct definition.The struct correctly mirrors the expected AI JSON response format and aligns with the DebateAssumption model fields.
37-47: Cache lookup logic is sound.The pattern of checking for existing assumptions before making AI calls is good for performance and cost savings. The error handling silently proceeds to extraction on cache miss, which is appropriate behavior.
160-177: LGTM - Transcript building from DebateTranscript records.The function correctly iterates through predefined debate sections and formats the output clearly with role and email attribution.
195-211: LGTM - Message filtering logic is appropriate.Correctly skips system and judge messages to focus on participant content. The case variation handling for "judge"/"Judge" is a practical defensive measure.
283-302: LGTM - Bulk insert implementation is correct.The
InsertManyapproach is efficient for persisting multiple assumption documents. Error handling properly wraps the underlying error with context.
213-243: AI prompt is well-structured for assumption extraction.The prompt provides clear instructions, examples of implicit assumptions, and a strict JSON output format. This should produce consistent, parseable responses from Gemini.
246-248: No action required. The functioncleanModelOutputis properly defined in the same package atbackend/services/gemini.go:42and is accessible fromassumption_service.go. There is no issue.
62-67: No issues found. The functiongenerateDefaultModelTextis properly defined inbackend/services/gemini.goand is correctly accessible from the same package. The function signaturefunc generateDefaultModelText(ctx context.Context, prompt string) (string, error)confirms it handles errors appropriately.
✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.
| type JudgeRequest struct { | ||
| History []models.Message `json:"history" binding:"required"` | ||
| DebateId string `json:"debateId" binding:"required"` | ||
| History []models.Message `json:"history" binding:"required"` | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
cat -n backend/controllers/debatevsbot_controller.go | sed -n '30,50p'Repository: AOSSIE-Org/DebateAI
Length of output: 182
🏁 Script executed:
# Check for JudgeRequest struct and related code
rg -n "JudgeRequest" --type go -A 5 -B 2Repository: AOSSIE-Org/DebateAI
Length of output: 1206
🏁 Script executed:
# Look for frontend code making judge requests
find . -name "*.ts" -o -name "*.tsx" -o -name "*.js" -o -name "*.jsx" | head -20Repository: AOSSIE-Org/DebateAI
Length of output: 782
🏁 Script executed:
# Get the full JudgeDebate function implementation
cat -n backend/controllers/debatevsbot_controller.go | sed -n '200,240p'Repository: AOSSIE-Org/DebateAI
Length of output: 1745
🏁 Script executed:
# Search for judge endpoint calls in frontend
rg -n "judge" frontend/src/Pages/DebateRoom.tsx -i -A 5 -B 2Repository: AOSSIE-Org/DebateAI
Length of output: 1388
🏁 Script executed:
# Look for any API endpoint definitions or constants
rg -n "/judge" --type go -B 5 -A 5Repository: AOSSIE-Org/DebateAI
Length of output: 660
🏁 Script executed:
# Find the frontend judgeDebate service implementation
rg -n "export.*judgeDebate\|function.*judgeDebate\|const.*judgeDebate" frontend/ --type ts -A 10Repository: AOSSIE-Org/DebateAI
Length of output: 45
🏁 Script executed:
# Check for vsbot service file
find frontend -name "*vsbot*" -type fRepository: AOSSIE-Org/DebateAI
Length of output: 93
🏁 Script executed:
# Look at the actual API call to see what's being sent
rg -n "debateId\|userId" frontend/src/Pages/DebateRoom.tsx -B 3 -A 3 | head -50Repository: AOSSIE-Org/DebateAI
Length of output: 45
🏁 Script executed:
cat -n frontend/src/services/vsbot.tsRepository: AOSSIE-Org/DebateAI
Length of output: 4743
🏁 Script executed:
# Also check the debateData structure to see if debateId is available
rg -n "debateData\|DebateData" frontend/src/Pages/DebateRoom.tsx -A 2 -B 2 | head -40Repository: AOSSIE-Org/DebateAI
Length of output: 45
🏁 Script executed:
# Check if debateData or similar structure stores debateId
rg -n "const.*debateData\|debateData.*=\|debateId" frontend/src/Pages/DebateRoom.tsx -B 2 -A 2 | head -60Repository: AOSSIE-Org/DebateAI
Length of output: 45
🏁 Script executed:
# Check what DebateResponse contains and how it's used
rg -n "createDebate\|DebateResponse" frontend/src/Pages/DebateRoom.tsx -B 2 -A 5Repository: AOSSIE-Org/DebateAI
Length of output: 45
🏁 Script executed:
# Get full DebateRoom component to understand state structure
head -200 frontend/src/Pages/DebateRoom.tsx | cat -nRepository: AOSSIE-Org/DebateAI
Length of output: 6657
🏁 Script executed:
# Check BotSelection for createDebate usage
rg -n "createDebate\|debateId" frontend/src/Pages/BotSelection.tsx -B 3 -A 3 | head -60Repository: AOSSIE-Org/DebateAI
Length of output: 45
🏁 Script executed:
# Check if BotSelection is navigating to DebateRoom and passing debateId
rg -n "navigate\|DebateRoom\|debateId" frontend/src/Pages/BotSelection.tsx -B 2 -A 2 | head -80Repository: AOSSIE-Org/DebateAI
Length of output: 45
🏁 Script executed:
# Look at line 580-583 in context to see the judgeDebateResult function more completely
sed -n '577,600p' frontend/src/Pages/DebateRoom.tsx | cat -nRepository: AOSSIE-Org/DebateAI
Length of output: 1205
🏁 Script executed:
# Find debateData definition in DebateRoom
rg -n "debateData\|useState" frontend/src/Pages/DebateRoom.tsx | head -20Repository: AOSSIE-Org/DebateAI
Length of output: 45
🏁 Script executed:
# Look for useLocation to see if debateData is passed through route state
sed -n '1,250p' frontend/src/Pages/DebateRoom.tsx | rg -n "useLocation\|debateData" -A 3Repository: AOSSIE-Org/DebateAI
Length of output: 45
🏁 Script executed:
# Get the main component function signature and props handling
sed -n '200,350p' frontend/src/Pages/DebateRoom.tsx | cat -nRepository: AOSSIE-Org/DebateAI
Length of output: 6037
🏁 Script executed:
# Search more broadly for how debateData is defined
rg -n "const.*=.*use\|debateData" frontend/src/Pages/DebateRoom.tsx | head -30Repository: AOSSIE-Org/DebateAI
Length of output: 45
Frontend must be updated to pass debateId instead of userId to the /judge endpoint.
The backend now requires debateId in the JudgeRequest struct (binding:"required"). The frontend has debateId available from debateData but currently sends userId instead, which will cause all judge requests to fail with 400 Bad Request errors.
Updates needed:
- Modify
JudgeRequesttype infrontend/src/services/vsbot.ts(lines 35-38) to includedebateId: stringinstead ofuserId: string - Update
judgeDebateResultinfrontend/src/Pages/DebateRoom.tsx(line 580-583) to passdebateId: debateData.debateIdinstead ofuserId: debateData.userId
This aligns with the existing pattern in concedeDebate, which already passes debateId to the backend.
🤖 Prompt for AI Agents
In `@backend/controllers/debatevsbot_controller.go` around lines 36 - 39, The
frontend is still sending userId but the backend JudgeRequest requires debateId;
update the JudgeRequest type in the frontend service (rename the field userId:
string to debateId: string in the JudgeRequest/interface used in vsbot.ts) and
modify the caller judgeDebateResult to pass debateId: debateData.debateId
instead of userId: debateData.userId so judgeDebateResult sends debateId
matching the backend binding.
| // parseAssumptionResponse parses the AI response into structured data | ||
| func parseAssumptionResponse(response string, roomID string) ([]models.DebateAssumption, error) { | ||
| // Clean the response | ||
| response = cleanModelOutput(response) | ||
|
|
||
| // Parse JSON | ||
| var aiResponses []AssumptionResponse | ||
| err := json.Unmarshal([]byte(response), &aiResponses) | ||
| if err != nil { | ||
| log.Printf("⚠️ Failed to parse AI response as JSON: %v", err) | ||
| log.Printf("Raw response: %s", response) | ||
| return nil, fmt.Errorf("invalid AI response format: %w", err) | ||
| } |
There was a problem hiding this comment.
Privacy concern: Raw AI response logged at line 255 may contain user debate content.
Logging the full response when JSON parsing fails could expose personally identifiable information (PII) from the debate transcript in server logs. Consider truncating or redacting the logged content.
Proposed fix: truncate logged response
if err != nil {
log.Printf("⚠️ Failed to parse AI response as JSON: %v", err)
- log.Printf("Raw response: %s", response)
+ // Truncate to avoid logging potentially sensitive debate content
+ truncated := response
+ if len(truncated) > 200 {
+ truncated = truncated[:200] + "...[truncated]"
+ }
+ log.Printf("Raw response (truncated): %s", truncated)
return nil, fmt.Errorf("invalid AI response format: %w", err)
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| // parseAssumptionResponse parses the AI response into structured data | |
| func parseAssumptionResponse(response string, roomID string) ([]models.DebateAssumption, error) { | |
| // Clean the response | |
| response = cleanModelOutput(response) | |
| // Parse JSON | |
| var aiResponses []AssumptionResponse | |
| err := json.Unmarshal([]byte(response), &aiResponses) | |
| if err != nil { | |
| log.Printf("⚠️ Failed to parse AI response as JSON: %v", err) | |
| log.Printf("Raw response: %s", response) | |
| return nil, fmt.Errorf("invalid AI response format: %w", err) | |
| } | |
| // parseAssumptionResponse parses the AI response into structured data | |
| func parseAssumptionResponse(response string, roomID string) ([]models.DebateAssumption, error) { | |
| // Clean the response | |
| response = cleanModelOutput(response) | |
| // Parse JSON | |
| var aiResponses []AssumptionResponse | |
| err := json.Unmarshal([]byte(response), &aiResponses) | |
| if err != nil { | |
| log.Printf("⚠️ Failed to parse AI response as JSON: %v", err) | |
| // Truncate to avoid logging potentially sensitive debate content | |
| truncated := response | |
| if len(truncated) > 200 { | |
| truncated = truncated[:200] + "...[truncated]" | |
| } | |
| log.Printf("Raw response (truncated): %s", truncated) | |
| return nil, fmt.Errorf("invalid AI response format: %w", err) | |
| } |
🤖 Prompt for AI Agents
In `@backend/services/assumption_service.go` around lines 245 - 257, In
parseAssumptionResponse, avoid logging the full raw AI `response` on JSON parse
errors; instead redact or truncate it before logging to prevent leaking PII.
Update the error handling after `json.Unmarshal` to create a safe preview (e.g.,
call a new helper like `truncateResponse(response, 200)` or
`redactSensitive(response)`) and log that preview along with the parse error in
the `log.Printf` call; keep the original `response` out of logs and ensure the
helper returns a deterministic, shortened string for troubleshooting.
| type SignUpRequest struct { | ||
| Email string `json:"email" binding:"required,email"` | ||
| Password string `json:"password" binding:"required,min=8"` | ||
| Password string `json:"password" binding:"required,min=6"` |
There was a problem hiding this comment.
Security concern: Password minimum length reduced to 6 characters.
Same concern as the frontend change - this weakens security. The binding tags are correctly synchronized across all three request types (SignUpRequest, LoginRequest, VerifyForgotPasswordRequest), but the minimum should ideally remain at 8 characters per security best practices.
Also applies to: 15-15, 25-25
🤖 Prompt for AI Agents
In `@backend/structs/auth.go` at line 5, Restore the password minimum length to 8
in the struct tags for all affected request types: update the `Password` field
binding tags on `SignUpRequest`, `LoginRequest`, and
`VerifyForgotPasswordRequest` from `min=6` back to `min=8`, and ensure any
related server-side validation logic that checks length is consistent with the
tag change.
| useEffect(() => { | ||
| fetchAssumptions(); | ||
| }, [debateId]); |
There was a problem hiding this comment.
Missing dependency in useEffect may cause stale closure issues.
The fetchAssumptions function is called inside the effect but not included in the dependency array. This can cause React's exhaustive-deps lint warning and potentially stale behavior if fetchAssumptions changes.
🔧 Recommended fix
+ const fetchAssumptions = useCallback(async () => {
+ try {
+ setLoading(true);
+ setError(null);
+ const data = await assumptionService.getDebateAssumptions(debateId);
+ setAssumptions(data.assumptions || []);
+ } catch (err) {
+ setError(
+ err instanceof Error
+ ? err.message
+ : 'Failed to fetch assumptions'
+ );
+ } finally {
+ setLoading(false);
+ }
+ }, [debateId]);
useEffect(() => {
fetchAssumptions();
- }, [debateId]);
+ }, [fetchAssumptions]);Alternatively, inline the fetch logic directly in the effect.
🤖 Prompt for AI Agents
In `@frontend/src/components/Assumptions.tsx` around lines 22 - 24, The effect in
the Assumptions component calls fetchAssumptions but doesn’t list it in the
dependency array, which can produce stale closures and lint warnings; fix by
either including fetchAssumptions in the useEffect dependency array (i.e.,
change useEffect(..., [debateId]) to useEffect(..., [debateId,
fetchAssumptions])) or move the fetch logic inline inside the useEffect callback
so the effect depends only on debateId; ensure the chosen approach preserves any
memoization (e.g., React.useCallback) around fetchAssumptions if you add it to
dependencies.
| // Group assumptions by side | ||
| const forSideAssumptions = assumptions.find((a) => a.side === 'for'); | ||
| const againstSideAssumptions = assumptions.find((a) => a.side === 'against'); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Search for ExtractAssumptions in the backend
rg -n -A 15 "ExtractAssumptions" --type=goRepository: AOSSIE-Org/DebateAI
Length of output: 3045
🏁 Script executed:
#!/bin/bash
# Search for DebateAssumption type definition
rg -n "type DebateAssumption" --type=goRepository: AOSSIE-Org/DebateAI
Length of output: 125
🏁 Script executed:
#!/bin/bash
# Search for assumption-related database queries or functions
rg -n -B 2 -A 10 "side.*assumption\|assumption.*side" --type=go -iRepository: AOSSIE-Org/DebateAI
Length of output: 45
🏁 Script executed:
#!/bin/bash
# Get the full DebateAssumption struct definition
cat -n backend/models/assumption.go | head -40Repository: AOSSIE-Org/DebateAI
Length of output: 1446
🏁 Script executed:
#!/bin/bash
# Search for where assumptions are created/saved in backend
rg -n -B 3 -A 10 "DebateAssumption\{" --type=goRepository: AOSSIE-Org/DebateAI
Length of output: 1086
🏁 Script executed:
#!/bin/bash
# Search for how assumptions are generated from AI
rg -n -B 5 -A 15 "assumptions.*append\|assumption.*side" --type=go -iRepository: AOSSIE-Org/DebateAI
Length of output: 45
🏁 Script executed:
#!/bin/bash
# Check the full ExtractAssumptions function to see what's returned
sed -n '28,120p' backend/services/assumption_service.goRepository: AOSSIE-Org/DebateAI
Length of output: 3758
🏁 Script executed:
#!/bin/bash
# Check the controller that returns assumptions to frontend
cat -n backend/controllers/assumption_controller.go | head -50Repository: AOSSIE-Org/DebateAI
Length of output: 1737
🏁 Script executed:
#!/bin/bash
# Search for any filtering or deduplication of assumptions before returning
rg -n -B 5 -A 10 "JSON\|c\.JSON" backend/controllers/assumption_controller.goRepository: AOSSIE-Org/DebateAI
Length of output: 45
🏁 Script executed:
#!/bin/bash
# Get the full context of the Assumptions.tsx file around lines 43-45
sed -n '30,60p' frontend/src/components/Assumptions.tsxRepository: AOSSIE-Org/DebateAI
Length of output: 1337
🏁 Script executed:
#!/bin/bash
# Search for how these variables are used in the frontend
rg -n -A 10 "forSideAssumptions\|againstSideAssumptions" frontend/src/components/Assumptions.tsxRepository: AOSSIE-Org/DebateAI
Length of output: 45
Use .filter() to handle multiple participant assumptions per side, not .find() which silently discards non-first entries.
The backend returns multiple DebateAssumption entries per side—one for each participant. The DebateAssumption struct includes a ParticipantID field, and the backend creates separate entries for each participant:
assumption := models.DebateAssumption{
ParticipantID: aiResp.ParticipantID,
Side: aiResp.Side,
Assumptions: aiResp.Assumptions,
...
}
assumptions = append(assumptions, assumption)
Using .find() takes only the first match per side, silently dropping assumptions from all other participants. For debates with multiple participants on the same side, this causes data loss. Use .filter() instead:
Suggested fix
const forSideAssumptions = assumptions.filter((a) => a.side === 'for');
const againstSideAssumptions = assumptions.filter((a) => a.side === 'against');
🤖 Prompt for AI Agents
In `@frontend/src/components/Assumptions.tsx` around lines 43 - 45, The code is
using assumptions.find(...) which returns only the first matching
DebateAssumption per side and drops other participants' entries; change the two
usages that define forSideAssumptions and againstSideAssumptions to use
assumptions.filter((a) => a.side === 'for') and assumptions.filter((a) => a.side
=== 'against') so you collect all entries per side, and then update any
downstream rendering or mapping logic that assumed a single object (e.g., where
forSideAssumptions/againstSideAssumptions are iterated or accessed) to handle
arrays.
What
Fixes a critical bug where Debate vs Bot outcomes were never persisted in MongoDB.
Why
UpdateDebateVsBotOutcomeattempted to update records using a non-existentuserIdfield.As a result, MongoDB updates silently matched zero documents, breaking outcome persistence, Elo updates, and analytics.
How
UpdateDebateVsBotOutcometo update bydebateId(_id) instead ofuserIdJudgeRequestandJudgeDebateto require and usedebateIdBreaking Change⚠️
The
/judgeendpoint now requiresdebateIdin the request payload.Frontend must include this field or requests will fail with
400 Bad Request.Impact
Files Changed
backend/db/db.gobackend/controllers/debatevsbot_controller.gocloses #228
Summary by CodeRabbit
New Features
Changes
✏️ Tip: You can customize this high-level summary in your review settings.