Skip to content
Merged
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
12 changes: 7 additions & 5 deletions backend/cmd/server/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,9 @@ func main() {
log.Println("Casbin RBAC initialized")

// Connect to Redis if configured
if cfg.Redis.URL != "" {
redisURL := cfg.Redis.URL
// FIX: Changed .URL to .Addr to match config.go definition
if cfg.Redis.Addr != "" {
redisURL := cfg.Redis.Addr
if redisURL == "" {
redisURL = "localhost:6379"
}
Comment on lines +46 to 50
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Dead code: unreachable default assignment.

The inner check if redisURL == "" on line 48 is unreachable because line 46 already ensures cfg.Redis.Addr != "", and line 47 assigns that non-empty value to redisURL. The default "localhost:6379" will never be applied.

Apply this diff to remove the dead code:

 	if cfg.Redis.Addr != "" {
 		redisURL := cfg.Redis.Addr
-		if redisURL == "" {
-			redisURL = "localhost:6379"
-		}
 		if err := debate.InitRedis(redisURL, cfg.Redis.Password, cfg.Redis.DB); err != nil {

Alternatively, if a default Redis address is desired, restructure the logic:

-	if cfg.Redis.Addr != "" {
-		redisURL := cfg.Redis.Addr
-		if redisURL == "" {
-			redisURL = "localhost:6379"
-		}
+	redisURL := cfg.Redis.Addr
+	if redisURL == "" {
+		redisURL = "localhost:6379"
+	}
+	if redisURL != "" {
 		if err := debate.InitRedis(redisURL, cfg.Redis.Password, cfg.Redis.DB); err != nil {
 			log.Printf("⚠️ Warning: Failed to initialize Redis: %v", err)
 			log.Printf("⚠️ Some realtime features will be unavailable until Redis is reachable")
 		} else {
 			log.Println("Connected to Redis")
 		}
 	} else {
 		log.Println("Redis Addr not configured; continuing without Redis-backed features")
 	}
📝 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.

Suggested change
if cfg.Redis.Addr != "" {
redisURL := cfg.Redis.Addr
if redisURL == "" {
redisURL = "localhost:6379"
}
if cfg.Redis.Addr != "" {
redisURL := cfg.Redis.Addr
if err := debate.InitRedis(redisURL, cfg.Redis.Password, cfg.Redis.DB); err != nil {
🤖 Prompt for AI Agents
In backend/cmd/server/main.go around lines 46-50, the inner check `if redisURL
== ""` is unreachable because you already gated with `if cfg.Redis.Addr != ""`
and then assigned that value to `redisURL`; remove the redundant inner check and
simply use cfg.Redis.Addr or, if you want a fallback default, restructure by
removing the outer `if` and assign redisURL from cfg.Redis.Addr with a
subsequent check to set `"localhost:6379"` when cfg.Redis.Addr is empty.

Expand All @@ -54,7 +55,7 @@ func main() {
log.Println("Connected to Redis")
}
} else {
log.Println("Redis URL not configured; continuing without Redis-backed features")
log.Println("Redis Addr not configured; continuing without Redis-backed features")
}
// Start the room watching service for matchmaking after DB connection
go websocket.WatchForNewRooms()
Expand Down Expand Up @@ -162,7 +163,8 @@ func setupRouter(cfg *config.Config) *gin.Engine {
log.Println("Admin routes registered")

// Debate spectator WebSocket handler (no auth required for anonymous spectators)
router.GET("/ws/debate/:debateID", DebateWebsocketHandler)
// FIX: Use websocket.DebateWebsocketHandler (moved to websocket package)
router.GET("/ws/debate/:debateID", websocket.DebateWebsocketHandler)

return router
}
}
31 changes: 14 additions & 17 deletions backend/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package config

import (
"fmt"
"io/ioutil"
"os"

"gopkg.in/yaml.v3"
)
Expand Down Expand Up @@ -40,29 +40,26 @@ type Config struct {
JWT struct {
Secret string `yaml:"secret"`
Expiry int `yaml:"expiry"`
}
} `yaml:"jwt"`

SMTP struct {
Host string `yaml:"host"`
Port int `yaml:"port"`
Username string `yaml:"username"` // Gmail address
Password string `yaml:"password"` // App Password
SenderEmail string `yaml:"senderEmail"` // Same as Username for Gmail
SenderName string `yaml:"senderName"`
} `yaml:"smtp"`
Comment on lines +45 to +52
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Potential scope creep and security concern.

The SMTP configuration block appears to add new functionality rather than fix the compilation errors described in PR #128. Consider whether this belongs in a separate feature PR.

Additionally, storing the SMTP password in plain text in the YAML file poses a security risk. Consider using environment variables or a secrets manager for sensitive credentials.

Example using environment variables:

SMTP struct {
    Host        string `yaml:"host"`
    Port        int    `yaml:"port"`
    Username    string `yaml:"username"`
    Password    string // Load from environment variable instead
    SenderEmail string `yaml:"senderEmail"`
    SenderName  string `yaml:"senderName"`
} `yaml:"smtp"`

Then in your initialization code, load the password from an environment variable:

cfg.SMTP.Password = os.Getenv("SMTP_PASSWORD")
🤖 Prompt for AI Agents
In backend/config/config.go around lines 45–52, the new SMTP struct addition
introduces potential scope creep for PR #128 and stores the SMTP password in
plain text via YAML; remove or revert this SMTP block from this PR (move it to a
dedicated feature/security PR) and, if keeping it, remove the yaml tag from the
Password field and load the password from an environment variable or secrets
manager during config initialization (do not persist secrets in the YAML file).


SMTP struct { // Add SMTP configuration
Host string
Port int
Username string // Gmail address
Password string // App Password
SenderEmail string // Same as Username for Gmail
SenderName string
}
GoogleOAuth struct {
ClientID string `yaml:"clientID"`
}
Redis struct {
URL string `yaml:"url"`
Password string `yaml:"password"`
DB int `yaml:"db"`
}
} `yaml:"googleOAuth"`
}

// LoadConfig reads the configuration file
func LoadConfig(path string) (*Config, error) {
data, err := ioutil.ReadFile(path)

data, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("failed to read config file: %w", err)
}
Expand Down
43 changes: 43 additions & 0 deletions backend/config/config.prod.sample.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+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

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)
18 changes: 0 additions & 18 deletions backend/config/config.prod.yml

This file was deleted.

11 changes: 7 additions & 4 deletions backend/controllers/admin_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -349,7 +349,8 @@ func GetComments(ctx *gin.Context) {
dbCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()

var comments []models.Comment
// Changed to AdminComment to match fields
var comments []models.AdminComment

// Get team debate messages
teamDebateCollection := db.MongoDatabase.Collection("team_debate_messages")
Expand All @@ -359,7 +360,8 @@ func GetComments(ctx *gin.Context) {
var messages []models.TeamDebateMessage
if err := cursor1.All(dbCtx, &messages); err == nil {
for _, msg := range messages {
comments = append(comments, models.Comment{
// Using AdminComment struct
comments = append(comments, models.AdminComment{
ID: msg.ID,
Type: "team_debate_message",
Content: msg.Message,
Expand All @@ -383,7 +385,8 @@ func GetComments(ctx *gin.Context) {
var chatMessages []models.TeamChatMessage
if err := cursor2.All(dbCtx, &chatMessages); err == nil {
for _, msg := range chatMessages {
comments = append(comments, models.Comment{
// Using AdminComment struct
comments = append(comments, models.AdminComment{
ID: msg.ID,
Type: "team_chat_message",
Content: msg.Message,
Expand Down Expand Up @@ -509,4 +512,4 @@ func BulkDeleteComments(ctx *gin.Context) {
"message": "Comments deleted successfully",
"deletedCount": result.DeletedCount,
})
}
}
15 changes: 8 additions & 7 deletions backend/controllers/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package controllers
import (
"context"
"fmt"
"log"
"math"
"net/http"
"os"
Expand Down Expand Up @@ -84,9 +85,9 @@ func GoogleLogin(ctx *gin.Context) {
LastRatingUpdate: now,
AvatarURL: avatarURL,
IsVerified: true,
Score: 0, // Initialize gamification score
Score: 0, // Initialize gamification score
Badges: []string{}, // Initialize badges array
CurrentStreak: 0, // Initialize streak
CurrentStreak: 0, // Initialize streak
CreatedAt: now,
UpdatedAt: now,
}
Expand Down Expand Up @@ -171,9 +172,9 @@ func SignUp(ctx *gin.Context) {
Password: string(hashedPassword),
IsVerified: false,
VerificationCode: verificationCode,
Score: 0, // Initialize gamification score
Score: 0, // Initialize gamification score
Badges: []string{}, // Initialize badges array
CurrentStreak: 0, // Initialize streak
CurrentStreak: 0, // Initialize streak
CreatedAt: now,
UpdatedAt: now,
}
Expand Down Expand Up @@ -541,9 +542,9 @@ func VerifyToken(ctx *gin.Context) {
func generateJWT(email, secret string, expiryMinutes int) (string, error) {
now := time.Now()
expirationTime := now.Add(time.Minute * time.Duration(expiryMinutes))

log.Printf("JWT Generation - Email: %s, Now: %s, Expiry: %s (in %d minutes)", email, now.Format(time.RFC3339), expirationTime.Format(time.RFC3339), expiryMinutes)

claims := jwt.MapClaims{
"sub": email,
"exp": expirationTime.Unix(),
Expand All @@ -555,7 +556,7 @@ func generateJWT(email, secret string, expiryMinutes int) (string, error) {
log.Printf("JWT signing error: %v", err)
return "", err
}

log.Printf("JWT Generated successfully - Expiration Unix: %d", expirationTime.Unix())
return signedToken, nil
}
Expand Down
16 changes: 0 additions & 16 deletions backend/controllers/team_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -366,15 +366,7 @@ func JoinTeam(c *gin.Context) {
for _, member := range team.Members {
totalElo += member.Elo
}
<<<<<<< HEAD
if len(team.Members) >= team.MaxSize {
=======
capacity := team.MaxSize
if capacity <= 0 {
capacity = 4
}
if len(team.Members) >= capacity {
>>>>>>> main
c.JSON(http.StatusBadRequest, gin.H{"error": "Team is already full"})
return
}
Expand Down Expand Up @@ -630,19 +622,11 @@ func GetAvailableTeams(c *gin.Context) {
collection := db.GetCollection("teams")
cursor, err := collection.Find(context.Background(), bson.M{
"$expr": bson.M{
<<<<<<< HEAD
"$lt": []interface{}{
bson.M{"$size": "$members"},
"$maxSize",
},
},
=======
"$lt": bson.A{
bson.M{"$size": "$members"},
"$maxSize",
},
},
>>>>>>> main
})
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to retrieve teams"})
Expand Down
7 changes: 4 additions & 3 deletions backend/controllers/transcript_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -405,13 +405,14 @@ func UpdatePendingTranscriptsHandler(c *gin.Context) {
}

token = strings.TrimPrefix(token, "Bearer ")
valid, _, err := utils.ValidateTokenAndFetchEmail("./config/config.prod.yml", token, c)
// FIX: Assign email to variable
valid, email, err := utils.ValidateTokenAndFetchEmail("./config/config.prod.yml", token, c)
if err != nil || !valid {
c.JSON(401, gin.H{"error": "Invalid or expired token"})
return
}

_ = email
_ = email // Keep compiler happy if email is indeed not used below

err = services.UpdatePendingTranscripts()
if err != nil {
Expand Down Expand Up @@ -499,4 +500,4 @@ func UpdateTranscriptResultHandler(c *gin.Context) {
}

c.JSON(200, gin.H{"message": "Transcript result updated successfully"})
}
}
5 changes: 3 additions & 2 deletions backend/db/db.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,14 @@ import (
"arguehub/models"
"context"
"fmt"
"log" // Added missing import
"net/url"
"time"

"github.com/redis/go-redis/v9"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
"github.com/redis/go-redis/v9"
)

var MongoClient *mongo.Client
Expand Down Expand Up @@ -114,4 +115,4 @@ func ConnectRedis(addr, password string, db int) error {

log.Println("Connected to Redis")
return nil
}
}
10 changes: 4 additions & 6 deletions backend/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ go 1.24
toolchain go1.24.4

require (
github.com/casbin/casbin/v2 v2.132.0
github.com/casbin/mongodb-adapter/v3 v3.7.0
github.com/gin-contrib/cors v1.7.2
github.com/gin-gonic/gin v1.10.0
github.com/golang-jwt/jwt/v5 v5.2.2
Expand All @@ -14,7 +16,7 @@ require (
go.mongodb.org/mongo-driver v1.17.3
golang.org/x/crypto v0.36.0
google.golang.org/api v0.228.0
google.golang.org/genai v1.34.0
google.golang.org/genai v1.37.0
gopkg.in/yaml.v3 v3.0.1
)

Expand All @@ -23,13 +25,10 @@ require (
cloud.google.com/go/auth v0.15.0 // indirect
cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
cloud.google.com/go/compute/metadata v0.6.0 // indirect
cloud.google.com/go/longrunning v0.5.7 // indirect
github.com/bmatcuk/doublestar/v4 v4.6.1 // indirect
github.com/bytedance/sonic v1.11.6 // indirect
github.com/bytedance/sonic/loader v0.1.1 // indirect
github.com/casbin/casbin/v2 v2.132.0 // indirect
github.com/casbin/govaluate v1.3.0 // indirect
github.com/casbin/mongodb-adapter/v3 v3.7.0 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/cloudwego/base64x v0.1.4 // indirect
github.com/cloudwego/iasm v0.2.0 // indirect
Expand Down Expand Up @@ -57,7 +56,6 @@ require (
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/montanaflynn/stats v0.7.1 // indirect
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
github.com/redis/go-redis/v9 v9.16.0 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.12 // indirect
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
Expand All @@ -70,7 +68,7 @@ require (
go.opentelemetry.io/otel/metric v1.34.0 // indirect
go.opentelemetry.io/otel/trace v1.34.0 // indirect
golang.org/x/arch v0.8.0 // indirect
golang.org/x/net v0.37.0 // indirect
golang.org/x/net v0.38.0 // indirect
golang.org/x/oauth2 v0.28.0 // indirect
golang.org/x/sync v0.12.0 // indirect
golang.org/x/sys v0.31.0 // indirect
Expand Down
Loading