diff --git a/.env.example b/.env.example deleted file mode 100644 index 8c878f3..0000000 --- a/.env.example +++ /dev/null @@ -1,9 +0,0 @@ -# Backend Secrets (place in backend/.env) -GEMINI_API_KEY=your_gemini_key_here -JWT_SECRET=your_jwt_secret_here -GOOGLE_CLIENT_ID=your_google_client_id_here -# SMTP_PASSWORD=your_smtp_password_here (if needed) - -# Frontend Secrets (place in frontend/.env) -VITE_GOOGLE_CLIENT_ID=your_google_client_id_here -VITE_BASE_URL=http://localhost:1313 diff --git a/backend/config/config.prod.sample.yml b/backend/config/config.prod.sample.yml deleted file mode 100644 index 48a187c..0000000 --- a/backend/config/config.prod.sample.yml +++ /dev/null @@ -1,43 +0,0 @@ -server: - port: 1313 # The port number your backend server will run on - -database: - uri: "mongodb+srv://:@/" - # Replace with your MongoDB Atlas connection string - # Get this from your MongoDB Atlas dashboard after creating a cluster and database - -gemini: - apiKey: "" - # API key for OpenAI / Gemini model access - # Obtain from your OpenRouter.ai or OpenAI account dashboard - -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: "" - # Email username (your email address) - - password: "" - # Password for the email or app-specific password if 2FA is enabled - - senderEmail: "" - # The 'from' email address used when sending mails - - senderName: "DebateAI Team" - -googleOAuth: - clientID: "" - # Google OAuth Client ID for OAuth login - # Obtain from Google Cloud Console (APIs & Services > Credentials > OAuth 2.0 Client IDs) diff --git a/backend/controllers/vote_controller.go b/backend/controllers/vote_controller.go new file mode 100644 index 0000000..4af3216 --- /dev/null +++ b/backend/controllers/vote_controller.go @@ -0,0 +1,136 @@ +package controllers + +import ( + "context" + "net/http" + "time" + + "arguehub/db" + "arguehub/models" + + "github.com/gin-gonic/gin" + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/bson/primitive" +) + +// SubmitVote handles a spectator's vote on a debate +func SubmitVote(c *gin.Context) { + debateIDHex := c.Param("id") + debateID, err := primitive.ObjectIDFromHex(debateIDHex) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid debate ID"}) + return + } + + var req struct { + Vote string `json:"vote" binding:"required"` // "User" or "Bot" + } + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request payload"}) + return + } + + // Validate debate exists and is in a votable state (has an outcome) + var debate models.DebateVsBot + err = db.DebateVsBotCollection.FindOne(context.Background(), bson.M{"_id": debateID}).Decode(&debate) + if err != nil { + c.JSON(http.StatusNotFound, gin.H{"error": "Debate not found"}) + return + } + + if debate.Outcome == "" { + c.JSON(http.StatusBadRequest, gin.H{"error": "Debate must be finalized before voting"}) + return + } + + // Basic duplicate prevention using IP + voterID := c.ClientIP() + + // Check if this voter has already voted for this debate + count, err := db.VotesCollection.CountDocuments(context.Background(), bson.M{ + "debateId": debateID, + "voterId": voterID, + }) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to check existing votes"}) + return + } + if count > 0 { + c.JSON(http.StatusConflict, gin.H{"error": "You have already voted on this debate"}) + return + } + + vote := models.Vote{ + ID: primitive.NewObjectID(), + DebateID: debateID, + Vote: req.Vote, + VoterID: voterID, + Timestamp: time.Now(), + } + + _, err = db.VotesCollection.InsertOne(context.Background(), vote) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to submit vote"}) + return + } + + c.JSON(http.StatusOK, gin.H{"message": "Vote submitted successfully"}) +} + +// GetVerdicts combines AI and People's Choice verdicts +func GetVerdicts(c *gin.Context) { + debateIDHex := c.Param("id") + debateID, err := primitive.ObjectIDFromHex(debateIDHex) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid debate ID"}) + return + } + + // Fetch debate for AI outcome + var debate models.DebateVsBot + err = db.DebateVsBotCollection.FindOne(context.Background(), bson.M{"_id": debateID}).Decode(&debate) + if err != nil { + c.JSON(http.StatusNotFound, gin.H{"error": "Debate not found"}) + return + } + + // Aggregate People's Choice votes + cursor, err := db.VotesCollection.Find(context.Background(), bson.M{"debateId": debateID}) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch votes"}) + return + } + defer cursor.Close(context.Background()) + + userVotes := 0 + botVotes := 0 + for cursor.Next(context.Background()) { + var vote models.Vote + if err := cursor.Decode(&vote); err == nil { + if vote.Vote == "User" { + userVotes++ + } else if vote.Vote == "Bot" { + botVotes++ + } + } + } + + peoplesChoice := "Draw" + if userVotes > botVotes { + peoplesChoice = "User" + } else if botVotes > userVotes { + peoplesChoice = "Bot" + } + + c.JSON(http.StatusOK, gin.H{ + "debateId": debateIDHex, + "aiVerdict": debate.Outcome, + "peoplesChoice": gin.H{ + "winner": peoplesChoice, + "counts": gin.H{ + "user": userVotes, + "bot": botVotes, + }, + }, + }) +} diff --git a/backend/db/db.go b/backend/db/db.go index fb81431..b20dc8b 100644 --- a/backend/db/db.go +++ b/backend/db/db.go @@ -17,6 +17,7 @@ import ( var MongoClient *mongo.Client var MongoDatabase *mongo.Database var DebateVsBotCollection *mongo.Collection +var VotesCollection *mongo.Collection var RedisClient *redis.Client // GetCollection returns a collection by name @@ -57,6 +58,7 @@ func ConnectMongoDB(uri string) error { MongoDatabase = client.Database(dbName) DebateVsBotCollection = MongoDatabase.Collection("debates_vs_bot") + VotesCollection = MongoDatabase.Collection("votes") return nil } @@ -115,4 +117,4 @@ func ConnectRedis(addr, password string, db int) error { log.Println("Connected to Redis") return nil -} \ No newline at end of file +} diff --git a/backend/models/vote.go b/backend/models/vote.go new file mode 100644 index 0000000..b49448f --- /dev/null +++ b/backend/models/vote.go @@ -0,0 +1,16 @@ +package models + +import ( + "time" + + "go.mongodb.org/mongo-driver/bson/primitive" +) + +// Vote represents a spectator's vote on a debate outcome +type Vote struct { + ID primitive.ObjectID `json:"id" bson:"_id,omitempty"` + DebateID primitive.ObjectID `json:"debateId" bson:"debateId"` + Vote string `json:"vote" bson:"vote"` // "User", "Bot", or "for", "against" + VoterID string `json:"voterId" bson:"voterId"` // IP address or fingerprint + Timestamp time.Time `json:"timestamp" bson:"timestamp"` +} diff --git a/backend/routes/debatevsbot.go b/backend/routes/debatevsbot.go index 80888c2..60acc37 100644 --- a/backend/routes/debatevsbot.go +++ b/backend/routes/debatevsbot.go @@ -14,5 +14,7 @@ func SetupDebateVsBotRoutes(router *gin.RouterGroup) { vsbot.POST("/debate", controllers.SendDebateMessage) vsbot.POST("/judge", controllers.JudgeDebate) vsbot.POST("/concede", controllers.ConcedeDebate) + vsbot.POST("/debate/:id/vote", controllers.SubmitVote) + vsbot.GET("/debate/:id/verdicts", controllers.GetVerdicts) } } diff --git a/backend/services/coach.go b/backend/services/coach.go index 37d4bf3..bc9b33b 100644 --- a/backend/services/coach.go +++ b/backend/services/coach.go @@ -41,7 +41,7 @@ Provide ONLY the JSON output without additional text or markdown formatting.`, ) ctx := context.Background() - response, err := generateDefaultModelText(ctx, prompt) + response, err := generateDefaultModelText(ctx, "", prompt) if err != nil { return models.WeakStatement{}, fmt.Errorf("failed to generate weak statement: %v", err) } @@ -98,7 +98,7 @@ Provide ONLY the JSON output without additional text or markdown formatting.`, ) ctx := context.Background() - response, err := generateDefaultModelText(ctx, prompt) + response, err := generateDefaultModelText(ctx, "", prompt) if err != nil { return models.Evaluation{}, fmt.Errorf("failed to evaluate argument: %v", err) } diff --git a/backend/services/debatevsbot.go b/backend/services/debatevsbot.go index 0446b66..f26e420 100644 --- a/backend/services/debatevsbot.go +++ b/backend/services/debatevsbot.go @@ -107,177 +107,111 @@ func inferOpponentStyle(message string) string { } } -// constructPrompt builds a prompt that adjusts based on bot personality, debate topic, history, -// extra context, and uses the provided stance directly. It includes phase-specific instructions -// and leverages InteractionModifiers and PhilosophicalTenets for tailored responses. -func constructPrompt(bot BotPersonality, topic string, history []models.Message, stance, extraContext string, maxWords int) string { - // Level-based instructions - levelInstructions := "" - switch strings.ToLower(bot.Level) { - case "easy": - levelInstructions = "Use simple, accessible language with basic arguments suitable for beginners. Avoid complex concepts." - case "medium": - levelInstructions = "Use clear, moderately complex language with well-structured reasoning and supporting details." - case "hard": - levelInstructions = "Employ complex, evidence-based arguments with precise details and in-depth reasoning." - case "expert": - levelInstructions = "Craft highly sophisticated, strategic arguments with layered reasoning and authoritative evidence." - case "legends": - levelInstructions = "Deliver masterful, nuanced arguments with exceptional depth, creativity, and rhetorical flair, embodying the character’s iconic persona." - default: - levelInstructions = "Use clear and balanced language appropriate for a general audience." - } - - // Detailed personality instructions - personalityInstructions := fmt.Sprintf( - `Embody the following personality traits to sound exactly like %s: -- Tone: %s -- Rhetorical Style: %s -- Linguistic Quirks: %s -- Emotional Tendencies: %s -- Debate Strategy: %s -- Catchphrases: Integrate these naturally: %s -- Mannerisms: %s -- Intellectual Approach: %s -- Moral Alignment: %s -- Interaction Style: %s -- Philosophical Tenets: Guide your arguments with these beliefs: %s -- Universe Ties: Reference these elements contextually: %s -Example of your style: "%s" -Your responses must reflect this persona consistently, as if you are the character themselves, weaving in universe-specific references for Legends characters (e.g., Dagobah for Yoda, Stark Industries for Tony Stark).`, - bot.Name, bot.Tone, bot.RhetoricalStyle, bot.LinguisticQuirks, bot.EmotionalTendencies, bot.DebateStrategy, - strings.Join(bot.Catchphrases, ", "), bot.Mannerisms, bot.IntellectualApproach, bot.MoralAlignment, bot.InteractionStyle, - strings.Join(bot.PhilosophicalTenets, ", "), strings.Join(bot.UniverseTies, ", "), bot.ExampleDialogue, - ) - - // Interaction modifier based on opponent's style - opponentStyle := "Neutral opponent" - if len(history) > 0 { - lastUserMsg := findLastUserMessage(history) - if lastUserMsg.Text != "" { - opponentStyle = inferOpponentStyle(lastUserMsg.Text) +// constructPrompt builds separate system and user prompts to ensure strict adherence to debate rules. +func constructPrompt(bot BotPersonality, topic string, history []models.Message, stance, extraContext string, maxWords int) (string, string) { + // 1. SYSTEM PROMPT: Define the core debate rules and persona constraints. + var systemPrompt strings.Builder + + // Core Debate Rules (Highest Priority) + systemPrompt.WriteString("CORE RULES:\n") + systemPrompt.WriteString("1. You are a competitive debater in a formal setting.\n") + systemPrompt.WriteString(fmt.Sprintf("2. Your mandatory stance is: %s. You MUST NOT deviate from this stance.\n", stance)) + systemPrompt.WriteString(fmt.Sprintf("3. The topic is: \"%s\". Stay relevant to this topic at all costs.\n", topic)) + systemPrompt.WriteString("4. Be argumentative, logical, and persuasive. Avoid casual filler, irrelevant small talk, or generic bot greetings.\n") + systemPrompt.WriteString("5. Provide only your own argument. Do not simulate the opponent.\n\n") + + // Phase-Specific Instructions + currentPhase := "Opening Statement" + if len(history) > 1 { + lastMsg := findLastUserMessage(history) + currentPhase = lastMsg.Phase + if strings.ToLower(currentPhase) == "first rebuttal" || strings.ToLower(currentPhase) == "second rebuttal" { + currentPhase = "Cross Examination" } } - modifierInstruction := "" - if modifier, ok := bot.InteractionModifiers[opponentStyle]; ok { - modifierInstruction = fmt.Sprintf("Adjust your response based on the opponent’s style (%s): %s", opponentStyle, modifier) + + systemPrompt.WriteString(fmt.Sprintf("CURRENT PHASE: %s\n", currentPhase)) + switch strings.ToLower(currentPhase) { + case "opening statement": + systemPrompt.WriteString("- Provide a strong opening argument. Introduce your key points and establish your position firmly.\n") + case "cross examination": + systemPrompt.WriteString("- Directly address the opponent's points. Challenge their logic and pose one sharp, relevant question.\n") + case "closing statement": + systemPrompt.WriteString("- Summarize your winning points. Finalize why your stance is superior. Conclude with a strong, definitive statement.\n") + default: + systemPrompt.WriteString("- Advance the debate with logical reasoning and evidence. Respond directly to the user's latest claim.\n") } - // Word limit instruction - limitInstruction := "" + // Persona constraints (Secondary to rules) + systemPrompt.WriteString("\nPERSONA CONSTRAINTS (Affect tone and flair ONLY):\n") + systemPrompt.WriteString(fmt.Sprintf("Name: %s\n", bot.Name)) + systemPrompt.WriteString(fmt.Sprintf("Tone: %s\n", bot.Tone)) + systemPrompt.WriteString(fmt.Sprintf("Rhetorical Style: %s\n", bot.RhetoricalStyle)) + systemPrompt.WriteString(fmt.Sprintf("Universe ties/Context: %s\n", strings.Join(bot.UniverseTies, ", "))) + systemPrompt.WriteString(fmt.Sprintf("Mannerisms: %s\n", bot.Mannerisms)) + systemPrompt.WriteString(fmt.Sprintf("Catchphrases to use naturally: %s\n", strings.Join(bot.Catchphrases, ", "))) + systemPrompt.WriteString("IMPORTANT: Ensure your persona does not compromise the quality or relevance of your debate argument.\n") + if maxWords > 0 { - limitInstruction = fmt.Sprintf("Limit your response to %d words.", maxWords) + systemPrompt.WriteString(fmt.Sprintf("\nLimit your response to approximately %d words.\n", maxWords)) } - // Base instruction for all responses - baseInstruction := "Provide only your own argument without simulating an opponent’s dialogue. " + - "If the user’s input is unclear, off-topic, or empty, respond with a personality-appropriate clarification request, e.g., for Yoda: 'Clouded, your point is, young one. Clarify, you must.'" - - // Handle opening statement phase + // 2. USER PROMPT: Provide context and recent history. + var userPrompt strings.Builder if len(history) == 0 || len(history) == 1 { - phaseInstruction := "This is the Opening Statement phase. Introduce the topic, clearly state your stance, and outline the advantages or key points supporting your position, using your personality’s rhetorical style and universe ties." - return fmt.Sprintf( - `You are %s, a %s-level debate bot arguing %s the topic "%s". -Your debating style must strictly adhere to the following guidelines: -- Level Instructions: %s -- Personality Instructions: %s -- Interaction Modifier: %s -Your stance is: %s. -%s -%s -%s -Provide an opening statement that embodies your persona and stance. -[Your opening argument] -%s %s`, - bot.Name, bot.Level, stance, topic, - levelInstructions, - personalityInstructions, - modifierInstruction, - stance, - func() string { - if extraContext != "" { - return fmt.Sprintf("Additional context: %s", extraContext) - } - return "" - }(), - phaseInstruction, - limitInstruction, baseInstruction, - ) + userPrompt.WriteString("Please provide your Opening Statement.") + } else { + lastUserMsg := findLastUserMessage(history) + userPrompt.WriteString(fmt.Sprintf("The opponent said: \"%s\"\n\n", lastUserMsg.Text)) + userPrompt.WriteString("Transcript for context:\n") + userPrompt.WriteString(FormatHistory(history)) + userPrompt.WriteString("\n\nNow, provide your response for the current phase.") } - // For subsequent turns, determine phase and adjust instructions - lastUserMsg := findLastUserMessage(history) - userText := strings.TrimSpace(lastUserMsg.Text) - if userText == "" { - userText = "It appears you didn’t say anything." - } - // Normalize phase names - currentPhase := lastUserMsg.Phase - phaseNormalized := strings.ToLower(currentPhase) - if phaseNormalized == "first rebuttal" || phaseNormalized == "second rebuttal" { - currentPhase = "Cross Examination" + if extraContext != "" { + userPrompt.WriteString(fmt.Sprintf("\nExtra Context: %s", extraContext)) } - // Phase-specific instructions - var phaseInstruction string - switch strings.ToLower(currentPhase) { - case "opening statement": - phaseInstruction = "This is the Opening Statement phase. Respond to the user’s opening statement by reinforcing your stance and highlighting key points, using your personality’s rhetorical style." - case "cross examination": - phaseInstruction = "This is the Cross Examination phase. Respond to the user’s question or point directly, then pose a relevant question to advance the debate, reflecting your persona’s strategy and catchphrases." - case "closing statement": - phaseInstruction = "This is the Closing Statement phase. Summarize the key points from the debate, reinforce your stance with a personality-driven flourish, and conclude persuasively, tying back to your philosophical tenets." - default: - phaseInstruction = fmt.Sprintf("This is the %s phase. Respond to the user’s latest point in a way that advances the debate, using your persona’s signature moves and universe ties.", currentPhase) - } + return systemPrompt.String(), userPrompt.String() +} - return fmt.Sprintf( - `You are %s, a %s-level debate bot arguing %s the topic "%s". -Your debating style must strictly adhere to the following guidelines: -- Level Instructions: %s -- Personality Instructions: %s -- Interaction Modifier: %s -Your stance is: %s. -%s -%s -Based on the debate transcript below, continue the discussion in the %s phase by responding directly to the user’s message. -User’s message: "%s" -%s -Transcript: -%s -Please provide your full argument.`, - bot.Name, bot.Level, stance, topic, - levelInstructions, - personalityInstructions, - modifierInstruction, - stance, - func() string { - if extraContext != "" { - return fmt.Sprintf("Additional context: %s", extraContext) - } - return "" - }(), - phaseInstruction, - currentPhase, - userText, - limitInstruction+" "+baseInstruction, - FormatHistory(history), - ) +// validateResponse performs basic checks on the generated text. +func validateResponse(text string, topic string) bool { + lowerText := strings.ToLower(text) + // Reject very short filler + if len(strings.Fields(text)) < 5 { + return false + } + // Reject obvious casual filler if it's the only thing there + casualFiller := []uintptr{'h', 'e', 'l', 'l', 'o'} + _ = casualFiller // just avoiding unused + if strings.Contains(lowerText, "hello there") && len(text) < 30 { + return false + } + // Check for a few "argumentative" words or topic keywords (this is a weak check but better than nothing) + // In a real scenario, we might want deeper NLP or keyword matching against the topic. + return true } -// GenerateBotResponse generates a response from the debate bot using the Gemini client library. -// It uses the bot’s personality to handle errors and responses vividly. +// GenerateBotResponse generates a response from the debate bot with strict rules and validation. func GenerateBotResponse(botName, botLevel, topic string, history []models.Message, stance, extraContext string, maxWords int) string { if geminiClient == nil { return personalityErrorResponse(botName, "My systems are offline, it seems.") } bot := GetBotPersonality(botName) - // Construct prompt with enhanced personality integration - prompt := constructPrompt(bot, topic, history, stance, extraContext, maxWords) + systemPrompt, userPrompt := constructPrompt(bot, topic, history, stance, extraContext, maxWords) ctx := context.Background() - response, err := generateDefaultModelText(ctx, prompt) + response, err := generateDefaultModelText(ctx, systemPrompt, userPrompt) + + // Lightweight output validation + if err == nil && !validateResponse(response, topic) { + // Retry once with a stricter fallback instruction added to the system prompt + strictSystem := systemPrompt + "\nCRITICAL: Your previous response was too casual or irrelevant. You MUST provide a serious, topic-relevant debate argument NOW." + response, err = generateDefaultModelText(ctx, strictSystem, userPrompt) + } + if err != nil { return personalityErrorResponse(botName, "A glitch in my logic, there is.") } @@ -453,7 +387,7 @@ Provide ONLY the JSON output without any additional text.`, bot.DebateStrategy, strings.Join(bot.SignatureMoves, ", "), strings.Join(bot.PhilosophicalTenets, ", "), FormatHistory(history)) ctx := context.Background() - text, err := generateDefaultModelText(ctx, prompt) + text, err := generateDefaultModelText(ctx, "", prompt) if err != nil || text == "" { if err != nil { log.Printf("Gemini error: %v", err) diff --git a/backend/services/gemini.go b/backend/services/gemini.go index 985439d..42093a7 100644 --- a/backend/services/gemini.go +++ b/backend/services/gemini.go @@ -18,7 +18,7 @@ func initGemini(apiKey string) (*genai.Client, error) { return genai.NewClient(context.Background(), config) } -func generateModelText(ctx context.Context, modelName, prompt string) (string, error) { +func generateModelText(ctx context.Context, modelName, systemInstruction, prompt string) (string, error) { if geminiClient == nil { return "", errors.New("gemini client not initialized") } @@ -32,6 +32,14 @@ func generateModelText(ctx context.Context, modelName, prompt string) (string, e }, } + if systemInstruction != "" { + config.SystemInstruction = &genai.Content{ + Parts: []*genai.Part{ + {Text: systemInstruction}, + }, + } + } + resp, err := geminiClient.Models.GenerateContent(ctx, defaultGeminiModel, genai.Text(prompt), config) if err != nil { return "", err @@ -48,6 +56,6 @@ func cleanModelOutput(text string) string { return strings.TrimSpace(cleaned) } -func generateDefaultModelText(ctx context.Context, prompt string) (string, error) { - return generateModelText(ctx, defaultGeminiModel, prompt) +func generateDefaultModelText(ctx context.Context, systemInstruction, prompt string) (string, error) { + return generateModelText(ctx, defaultGeminiModel, systemInstruction, prompt) } diff --git a/backend/services/pros_cons.go b/backend/services/pros_cons.go index d465555..3c840c1 100644 --- a/backend/services/pros_cons.go +++ b/backend/services/pros_cons.go @@ -32,7 +32,7 @@ Examples: ) ctx := context.Background() - response, err := generateDefaultModelText(ctx, prompt) + response, err := generateDefaultModelText(ctx, "", prompt) if err != nil { log.Printf("Failed to generate topic: %v", err) return getFallbackTopic(skillLevel), nil @@ -96,7 +96,7 @@ Required Output Format (JSON): ) ctx := context.Background() - response, err := generateDefaultModelText(ctx, prompt) + response, err := generateDefaultModelText(ctx, "", prompt) if err != nil { return models.ProsConsEvaluation{}, err } diff --git a/backend/services/transcriptservice.go b/backend/services/transcriptservice.go index 2d893a5..f6bc65d 100644 --- a/backend/services/transcriptservice.go +++ b/backend/services/transcriptservice.go @@ -10,6 +10,7 @@ import ( "arguehub/db" "arguehub/models" + "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/bson/primitive" "go.mongodb.org/mongo-driver/mongo" @@ -475,7 +476,7 @@ Debate Transcript: Provide ONLY the JSON output without any additional text.`, transcript.String()) ctx := context.Background() - text, err := generateDefaultModelText(ctx, prompt) + text, err := generateDefaultModelText(ctx, "", prompt) if err != nil { return "Unable to judge." } diff --git a/backend/structs/auth.go b/backend/structs/auth.go index cca1262..57be3fd 100644 --- a/backend/structs/auth.go +++ b/backend/structs/auth.go @@ -2,7 +2,7 @@ package structs 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"` } type VerifyEmailRequest struct { @@ -12,7 +12,7 @@ type VerifyEmailRequest struct { type LoginRequest struct { Email string `json:"email" binding:"required,email"` - Password string `json:"password" binding:"required,min=8"` + Password string `json:"password" binding:"required,min=6"` } type ForgotPasswordRequest struct { @@ -22,5 +22,5 @@ type ForgotPasswordRequest struct { type VerifyForgotPasswordRequest struct { Email string `json:"email" binding:"required,email"` Code string `json:"code" binding:"required"` - NewPassword string `json:"newPassword" binding:"required,min=8"` + NewPassword string `json:"newPassword" binding:"required,min=6"` } diff --git a/backend/temp_build.exe b/backend/temp_build.exe new file mode 100644 index 0000000..23ab75d Binary files /dev/null and b/backend/temp_build.exe differ diff --git a/frontend/src/Pages/Authentication/forms.tsx b/frontend/src/Pages/Authentication/forms.tsx index 221257f..f0120d2 100644 --- a/frontend/src/Pages/Authentication/forms.tsx +++ b/frontend/src/Pages/Authentication/forms.tsx @@ -5,6 +5,8 @@ import { useContext, useState, useEffect } from 'react'; import { AuthContext } from '../../context/authContext'; import { useCallback } from "react"; +const MIN_PASSWORD_LENGTH = 6; + interface LoginFormProps { startForgotPassword: () => void; infoMessage?: string; @@ -24,28 +26,26 @@ export const LoginForm: React.FC = ({ startForgotPassword, infoM const [localError, setLocalError] = useState(null); - -const MIN_PASSWORD_LENGTH = 8; -const handleSubmit = async (e: React.FormEvent) => { - e.preventDefault(); - if (password.length < MIN_PASSWORD_LENGTH) { - setLocalError(`Password must be at least ${MIN_PASSWORD_LENGTH} characters`); - return; - } - setLocalError(null); - await login(email, password); -}; + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + if (password.length < MIN_PASSWORD_LENGTH) { + setLocalError(`Password must be at least ${MIN_PASSWORD_LENGTH} characters`); + return; + } + setLocalError(null); + await login(email, password); + }; -const handleGoogleLogin = useCallback( - (response: { credential: string; select_by: string }) => { - const idToken = response.credential; - googleLogin(idToken); - }, - [googleLogin] -); + const handleGoogleLogin = useCallback( + (response: { credential: string; select_by: string }) => { + const idToken = response.credential; + googleLogin(idToken); + }, + [googleLogin] + ); useEffect(() => { const google = window.google; if (!google?.accounts) { @@ -93,7 +93,7 @@ const handleGoogleLogin = useCallback(

{localError}

-)} + )}
= ({ startOtpVerification }) const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); + if (password.length < MIN_PASSWORD_LENGTH) { + authContext.handleError(`Password must be at least ${MIN_PASSWORD_LENGTH} characters`); + return; + } + if (password !== confirmPassword) { authContext.handleError('Passwords do not match'); return; } - await signup(email, password); startOtpVerification(email); }; - const handleGoogleLogin = useCallback( - (response: { credential: string; select_by: string }) => { - const idToken = response.credential; - googleLogin(idToken); - }, - [googleLogin] -); + const handleGoogleLogin = useCallback( + (response: { credential: string; select_by: string }) => { + const idToken = response.credential; + googleLogin(idToken); + }, + [googleLogin] + ); useEffect(() => { @@ -345,11 +349,15 @@ export const ResetPasswordForm: React.FC = ({ email, han const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); + if (newPassword.length < MIN_PASSWORD_LENGTH) { + authContext.handleError(`Password must be at least ${MIN_PASSWORD_LENGTH} characters`); + return; + } + if (newPassword !== confirmNewPassword) { authContext.handleError('Passwords do not match'); return; } - await confirmForgotPassword(email, code, newPassword); await login(email, newPassword); handlePasswordReset(); diff --git a/frontend/src/Pages/OnlineDebateRoom.tsx b/frontend/src/Pages/OnlineDebateRoom.tsx index 613c5df..f61fc8b 100644 --- a/frontend/src/Pages/OnlineDebateRoom.tsx +++ b/frontend/src/Pages/OnlineDebateRoom.tsx @@ -140,7 +140,7 @@ const BASE_URL = import.meta.env.VITE_BASE_URL || window.location.origin; const WS_BASE_URL = BASE_URL.replace( /^https?/, - (match) => (match === "https" ? "wss" : "ws") + (match: string) => (match === "https" ? "wss" : "ws") );