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
68 changes: 58 additions & 10 deletions backend/cmd/server/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,57 +2,105 @@ package main

import (
"log"
"os"
"strconv"

"arguehub/config"
"arguehub/db"
"arguehub/middlewares"
"arguehub/routes"
"arguehub/services"
"arguehub/utils"
"arguehub/websocket"

"github.com/gin-contrib/cors"
"github.com/gin-gonic/gin"
)

func main() {
// Load the configuration from the specified YAML file
cfg, err := config.LoadConfig("./config/config.prod.yml")
Copy link

Choose a reason for hiding this comment

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

πŸ’‘ Verification agent

🧩 Analysis chain

Verify that the configuration file exists at the specified path.

The code attempts to load ./config/config.prod.yml, but according to the AI summary, the production version of this file was removed. This could cause a runtime failure.


🏁 Script executed:

#!/bin/bash
# Description: Check if the configuration file exists and examine its structure

# Check if the config file exists
if [ -f "./config/config.prod.yml" ]; then
    echo "Config file exists"
    cat "./config/config.prod.yml" | head -20
else
    echo "Config file NOT found at ./config/config.prod.yml"
    # Check for alternative config files
    fd -e yml -e yaml . config/
fi

Length of output: 271


Missing production config file and hardcoded path

The code calls:

cfg, err := config.LoadConfig("./config/config.prod.yml")

but there is no config/ directory or config.prod.yml file in the repo. This will cause a runtime failure and offers no flexibility for other environments.

Please:

  • Ensure the production config file is checked into the expected path, or
  • Parameterize the config file location (for example via a CLI flag or environment variable) instead of using a hardcoded relative path.
  • Update config.LoadConfig to default to a sane fallback or fail with a clear error message.
  • Document the new configuration-loading behavior in your README or deployment scripts.
πŸ€– Prompt for AI Agents
In backend/cmd/server/main.go at line 22, the code loads the production config
from a hardcoded path "./config/config.prod.yml" which does not exist, causing
runtime failure. Modify the code to accept the config file path as a CLI flag or
environment variable instead of a fixed string. Update config.LoadConfig to
handle missing files gracefully by providing a clear error or default fallback.
Finally, document the new config loading approach in the README or deployment
instructions.

if err != nil {
log.Fatalf("Failed to load config: %v", err)
}

router := setupRouter(cfg)
services.InitDebateVsBotService(cfg)
services.InitCoachService()
// Connect to MongoDB using the URI from the configuration
if err := db.ConnectMongoDB(cfg.Database.URI); err != nil {
log.Fatalf("Failed to connect to MongoDB: %v", err)
}
log.Println("Connected to MongoDB")

// Seed initial debate-related data
utils.SeedDebateData()
utils.PopulateTestUsers()
Comment on lines +36 to +37
Copy link

Choose a reason for hiding this comment

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

πŸ’‘ Verification agent

🧩 Analysis chain

Ensure data seeding is idempotent to prevent duplicates.

The SeedDebateData() and PopulateTestUsers() functions are called unconditionally on every server start. This could lead to duplicate data if these functions don't check for existing data.


🏁 Script executed:

#!/bin/bash
# Description: Examine the seed functions to verify idempotency

# Check SeedDebateData implementation
echo "=== Checking SeedDebateData ==="
ast-grep --pattern 'func SeedDebateData() {
  $$$
}'

# Check PopulateTestUsers implementation
echo -e "\n=== Checking PopulateTestUsers ==="
ast-grep --pattern 'func PopulateTestUsers() {
  $$$
}'

Length of output: 4848


Add idempotency guard to PopulateTestUsers

While SeedDebateData already skips seeding when the debates collection is non-empty, PopulateTestUsers unconditionally inserts the same users on every server startβ€”leading to duplicates.

β€’ File: backend/utils/user.go
Lines: 14–49

Suggested fix (before looping inserts):

// Skip if users collection already has data
count, err := collection.CountDocuments(context.Background(), bson.M{})
if err != nil || count > 0 {
    return
}

Then proceed with defining and inserting users.

πŸ€– Prompt for AI Agents
In backend/cmd/server/main.go around lines 36 to 37, the call to
utils.PopulateTestUsers() leads to duplicate user entries on every server start
because it lacks an idempotency check. To fix this, modify the PopulateTestUsers
function in backend/utils/user.go (lines 14–49) by adding a guard at the start
that queries the users collection count; if the count is greater than zero or an
error occurs, return early without inserting users. This prevents duplicate
inserts by ensuring seeding only happens when the collection is empty.


// Create uploads directory
os.MkdirAll("uploads", os.ModePerm)
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Add error handling for directory creation.

The error from os.MkdirAll is not checked, which could hide permission or disk space issues.

Apply this diff to add error handling:

-	os.MkdirAll("uploads", os.ModePerm)
+	if err := os.MkdirAll("uploads", os.ModePerm); err != nil {
+		log.Fatalf("Failed to create uploads directory: %v", err)
+	}
πŸ€– Prompt for AI Agents
In backend/cmd/server/main.go at line 40, the call to os.MkdirAll("uploads",
os.ModePerm) lacks error handling. Modify the code to capture the error returned
by os.MkdirAll and handle it appropriately, such as logging the error and
exiting the program if directory creation fails, to ensure issues like
permission or disk space errors are not ignored.


// Set up the Gin router and configure routes
router := setupRouter(cfg)
port := strconv.Itoa(cfg.Server.Port)
log.Printf("Server starting on port %s", port)

if err := router.Run(":" + port); err != nil {
log.Fatalf("Failed to start server: %v", err)
}
}

func setupRouter(cfg *config.Config) *gin.Engine {
// gin.SetMode(gin.ReleaseMode) // Uncomment this line for production

router := gin.Default()

// Set trusted proxies (adjust as needed)
router.SetTrustedProxies([]string{"127.0.0.1", "localhost"})

// Configure CORS for your frontend (e.g., localhost:5173 for Vite)
router.Use(cors.New(cors.Config{
AllowOrigins: []string{"http://localhost:5173"},
AllowMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
AllowHeaders: []string{"Origin", "Content-Type", "Authorization"},
ExposeHeaders: []string{"Content-Length"},
AllowCredentials: true,
}))
Comment on lines 59 to 65
Copy link

Choose a reason for hiding this comment

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

πŸ› οΈ Refactor suggestion

Make CORS origins configurable for different environments.

The CORS configuration has a hardcoded origin http://localhost:5173, which is not suitable for production deployments.

Consider loading allowed origins from the configuration:

 	router.Use(cors.New(cors.Config{
-		AllowOrigins:     []string{"http://localhost:5173"},
+		AllowOrigins:     cfg.CORS.AllowedOrigins,
 		AllowMethods:     []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
 		AllowHeaders:     []string{"Origin", "Content-Type", "Authorization"},
 		ExposeHeaders:    []string{"Content-Length"},
 		AllowCredentials: true,
 	}))
πŸ“ 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
router.Use(cors.New(cors.Config{
AllowOrigins: []string{"http://localhost:5173"},
AllowMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
AllowHeaders: []string{"Origin", "Content-Type", "Authorization"},
ExposeHeaders: []string{"Content-Length"},
AllowCredentials: true,
}))
router.Use(cors.New(cors.Config{
- AllowOrigins: []string{"http://localhost:5173"},
+ AllowOrigins: cfg.CORS.AllowedOrigins,
AllowMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
AllowHeaders: []string{"Origin", "Content-Type", "Authorization"},
ExposeHeaders: []string{"Content-Length"},
AllowCredentials: true,
}))
πŸ€– Prompt for AI Agents
In backend/cmd/server/main.go around lines 59 to 65, the CORS middleware is
configured with a hardcoded origin "http://localhost:5173", which is not
flexible for different environments. Modify the code to load the allowed origins
from a configuration source such as environment variables or a config file.
Replace the hardcoded origin with the dynamically loaded list of allowed origins
to support multiple environments including production.

router.OPTIONS("/*path", func(c *gin.Context) { c.Status(204) })

router.OPTIONS("/*path", func(c *gin.Context) {
c.Status(204)
})

// Public routes for authentication
router.POST("/signup", routes.SignUpRouteHandler)
router.POST("/verifyEmail", routes.VerifyEmailRouteHandler)
router.POST("/login", routes.LoginRouteHandler)
router.POST("/googleLogin", routes.GoogleLoginRouteHandler)
router.POST("/forgotPassword", routes.ForgotPasswordRouteHandler)
router.POST("/confirmForgotPassword", routes.VerifyForgotPasswordRouteHandler)
router.POST("/verifyToken", routes.VerifyTokenRouteHandler)

router.GET("/ws", websocket.WebsocketHandler)

// Protected routes (JWT auth)
auth := router.Group("/")
auth.Use(middlewares.AuthMiddleware("./config/config.prod.yml"))
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Potential issue with hardcoded config path in middleware.

The auth middleware is initialized with the same hardcoded config path that might not exist.

Consider passing the already loaded config object instead:

-	auth.Use(middlewares.AuthMiddleware("./config/config.prod.yml"))
+	auth.Use(middlewares.AuthMiddleware(cfg))

This requires updating the AuthMiddleware function signature to accept a config object instead of a file path.

πŸ€– Prompt for AI Agents
In backend/cmd/server/main.go at line 79, the AuthMiddleware is called with a
hardcoded config file path which may not exist in all environments. To fix this,
modify the AuthMiddleware function to accept a config object instead of a file
path, then pass the already loaded config object to it here. This avoids relying
on a fixed file path and makes the middleware more flexible and testable.

{
auth.GET("/user/fetchprofile", routes.GetProfileRouteHandler)
auth.PUT("/user/updateprofile", routes.UpdateProfileRouteHandler)
auth.GET("/leaderboard", routes.GetLeaderboardRouteHandler)
auth.POST("/debate/result", routes.UpdateEloAfterDebateRouteHandler)
routes.SetupDebateVsBotRoutes(auth)

// WebSocket signaling endpoint
auth.GET("/ws", websocket.WebsocketHandler)

routes.SetupTranscriptRoutes(auth)
auth.GET("/coach/strengthen-argument/weak-statement", routes.GetWeakStatement)
auth.POST("/coach/strengthen-argument/evaluate", routes.EvaluateStrengthenedArgument)

// Add Room routes.
auth.GET("/rooms", routes.GetRoomsHandler)
auth.POST("/rooms", routes.CreateRoomHandler)
auth.POST("/rooms/:id/join", routes.JoinRoomHandler)

auth.GET("/chat/:roomId", websocket.RoomChatHandler)

auth.GET("/coach/pros-cons/topic", routes.GetProsConsTopic)
auth.POST("/coach/pros-cons/submit", routes.SubmitProsCons)
}

return router
}
}
76 changes: 51 additions & 25 deletions backend/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,37 +2,63 @@ package config

import (
"fmt"
"io/ioutil"
"io/ioutil"
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Replace deprecated io/ioutil import.

The io/ioutil package has been deprecated since Go 1.19. Use os.ReadFile instead.

-	"io/ioutil"
+	"os"

And update the LoadConfig function:

-	data, err := ioutil.ReadFile(path)
+	data, err := os.ReadFile(path)
πŸ“ 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
"io/ioutil"
package config
import (
"os"
"gopkg.in/yaml.v2"
)
func LoadConfig(path string) (*Config, error) {
data, err := os.ReadFile(path)
if err != nil {
return nil, err
}
var cfg Config
if err := yaml.Unmarshal(data, &cfg); err != nil {
return nil, err
}
return &cfg, nil
}
🧰 Tools
πŸͺ› golangci-lint (1.64.8)

5-5: SA1019: "io/ioutil" has been deprecated since Go 1.19: As of Go 1.16, the same functionality is now provided by package [io] or package [os], and those implementations should be preferred in new code. See the specific function documentation for details.

(staticcheck)

πŸ€– Prompt for AI Agents
In backend/config/config.go at line 5, replace the deprecated import of
"io/ioutil" with "os" to use the updated file reading functions. Then, update
the LoadConfig function to use os.ReadFile instead of ioutil.ReadFile for
reading configuration files, ensuring compatibility with Go 1.19 and later.


"gopkg.in/yaml.v3"
"gopkg.in/yaml.v3"
)

type Config struct {
Server struct {
Port int `yaml:"port"`
} `yaml:"server"`

Cognito struct {
AppClientId string `yaml:"appClientId"`
AppClientSecret string `yaml:"appClientSecret"`
UserPoolId string `yaml:"userPoolId"`
Region string `yaml:"region"`
} `yaml:"cognito"`

Openai struct {
GptApiKey string `yaml:"gptApiKey"`
} `yaml:"openai`
Server struct {
Port int `yaml:"port"`
} `yaml:"server"`

Cognito struct {
AppClientId string `yaml:"appClientId"`
AppClientSecret string `yaml:"appClientSecret"`
UserPoolId string `yaml:"userPoolId"`
Region string `yaml:"region"`
} `yaml:"cognito"`

Openai struct {
GptApiKey string `yaml:"gptApiKey"`
} `yaml:"openai"`

Gemini struct {
ApiKey string `yaml:"apiKey"`
} `yaml:"gemini"`

Database struct {
URI string `yaml:"uri"`
} `yaml:"database"`

JWT struct {
Secret string // Add JWT secret
Expiry int // Token expiry in minutes
}
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 { // Add Google OAuth configuration
ClientID string
}
Comment on lines +34 to +48
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Add missing YAML tags to configuration structs.

The JWT, SMTP, and GoogleOAuth structs are missing YAML tags, which means they won't be properly unmarshaled from YAML configuration files. This will cause these configurations to have zero values at runtime.

 JWT struct {
-	Secret string // Add JWT secret
-	Expiry int    // Token expiry in minutes
-}
+	Secret string `yaml:"secret"` // Add JWT secret
+	Expiry int    `yaml:"expiry"` // Token expiry in minutes
+} `yaml:"jwt"`
 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
-}
+	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"`
 GoogleOAuth struct { // Add Google OAuth configuration
-	ClientID string
-}
+	ClientID string `yaml:"clientId"`
+} `yaml:"googleOAuth"`
πŸ“ 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
JWT struct {
Secret string // Add JWT secret
Expiry int // Token expiry in minutes
}
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 { // Add Google OAuth configuration
ClientID string
}
JWT struct {
Secret string `yaml:"secret"` // Add JWT secret
Expiry int `yaml:"expiry"` // Token expiry in minutes
} `yaml:"jwt"`
SMTP struct { // Add SMTP configuration
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"`
GoogleOAuth struct { // Add Google OAuth configuration
ClientID string `yaml:"clientId"`
} `yaml:"googleOAuth"`
πŸ€– Prompt for AI Agents
In backend/config/config.go around lines 34 to 48, the JWT, SMTP, and
GoogleOAuth structs lack YAML tags, preventing proper unmarshaling from YAML
config files. Add appropriate YAML tags to each struct field matching the
expected YAML keys to ensure correct loading of configuration values at runtime.

}

// LoadConfig reads the configuration file
func LoadConfig(path string) (*Config, error) {
data, err := ioutil.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("failed to read config file: %w", err)
}
data, err := ioutil.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("failed to read config file: %w", err)
}

var cfg Config
if err := yaml.Unmarshal(data, &cfg); err != nil {
return nil, fmt.Errorf("failed to unmarshal yaml: %w", err)
}
var cfg Config
if err := yaml.Unmarshal(data, &cfg); err != nil {
return nil, fmt.Errorf("failed to unmarshal yaml: %w", err)
}

return &cfg, nil
return &cfg, nil
}
12 changes: 0 additions & 12 deletions backend/config/config.prod.yml

This file was deleted.

Loading