diff --git a/backend/cmd/server/main.go b/backend/cmd/server/main.go index 3f7e102..d7003bb 100644 --- a/backend/cmd/server/main.go +++ b/backend/cmd/server/main.go @@ -166,3 +166,10 @@ func setupRouter(cfg *config.Config) *gin.Engine { return router } +func DebateWebsocketHandler(c *gin.Context) { + // TODO: implement real debate websocket logic. + // Temporary stub so the backend compiles and runs. + c.JSON(501, gin.H{ + "error": "DebateWebsocketHandler not implemented yet", + }) +} diff --git a/backend/config/config.go b/backend/config/config.go index 8b1eec8..9c7a51a 100644 --- a/backend/config/config.go +++ b/backend/config/config.go @@ -1,76 +1,72 @@ package config import ( - "fmt" - "io/ioutil" + "fmt" + "io/ioutil" - "gopkg.in/yaml.v3" + "gopkg.in/yaml.v3" ) type Config struct { - Server struct { - Port int `yaml:"port"` - } `yaml:"server"` + 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"` + 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"` + Openai struct { + GptApiKey string `yaml:"gptApiKey"` + } `yaml:"openai"` - Gemini struct { - ApiKey string `yaml:"apiKey"` - } `yaml:"gemini"` + Gemini struct { + ApiKey string `yaml:"apiKey"` + } `yaml:"gemini"` - Database struct { - URI string `yaml:"uri"` - } `yaml:"database"` + Database struct { + URI string `yaml:"uri"` + } `yaml:"database"` - Redis struct { - Addr string `yaml:"addr"` - Password string `yaml:"password"` - DB int `yaml:"db"` - } `yaml:"redis"` + Redis struct { + URL string `yaml:"url"` + Password string `yaml:"password"` + DB int `yaml:"db"` + } `yaml:"redis"` - JWT struct { - Secret string `yaml:"secret"` - Expiry int `yaml:"expiry"` - } + JWT struct { + Secret string `yaml:"secret"` + Expiry int `yaml:"expiry"` + } - 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"` - } + SMTP struct { + Host string + Port int + Username string + Password string + SenderEmail string + SenderName string + } + + GoogleOAuth struct { + ClientID string `yaml:"clientID"` + } } -// LoadConfig reads the configuration file +// LoadConfig reads config.yml 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 } diff --git a/backend/controllers/auth.go b/backend/controllers/auth.go index 258318a..ae4a0bd 100644 --- a/backend/controllers/auth.go +++ b/backend/controllers/auth.go @@ -8,6 +8,7 @@ import ( "os" "strings" "time" + "log" "arguehub/config" "arguehub/db" diff --git a/backend/controllers/team_controller.go b/backend/controllers/team_controller.go index 6a6c173..de6171f 100644 --- a/backend/controllers/team_controller.go +++ b/backend/controllers/team_controller.go @@ -366,15 +366,11 @@ func JoinTeam(c *gin.Context) { for _, member := range team.Members { totalElo += member.Elo } -<<<<<<< HEAD - if len(team.Members) >= team.MaxSize { -======= - capacity := 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 } @@ -392,6 +388,7 @@ func JoinTeam(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"message": "Successfully joined team"}) } + // LeaveTeam allows a user to leave a team func LeaveTeam(c *gin.Context) { teamID := c.Param("id") @@ -630,19 +627,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"}) diff --git a/backend/controllers/transcript_controller.go b/backend/controllers/transcript_controller.go index bcdd0fb..a75eafb 100644 --- a/backend/controllers/transcript_controller.go +++ b/backend/controllers/transcript_controller.go @@ -410,7 +410,7 @@ func UpdatePendingTranscriptsHandler(c *gin.Context) { c.JSON(401, gin.H{"error": "Invalid or expired token"}) return } - + email := "" // or load actual email _ = email err = services.UpdatePendingTranscripts() diff --git a/backend/db/db.go b/backend/db/db.go index 2e7af0a..278de02 100644 --- a/backend/db/db.go +++ b/backend/db/db.go @@ -4,13 +4,14 @@ import ( "arguehub/models" "context" "fmt" + "log" "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 diff --git a/backend/go.mod b/backend/go.mod index dbc806a..40245d4 100644 --- a/backend/go.mod +++ b/backend/go.mod @@ -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 @@ -14,12 +16,13 @@ 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 ) require ( cloud.google.com/go v0.116.0 // indirect + cloud.google.com/go/ai v0.8.0 // indirect 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 @@ -27,9 +30,7 @@ require ( 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 @@ -44,6 +45,7 @@ require ( github.com/go-playground/validator/v10 v10.20.0 // indirect github.com/goccy/go-json v0.10.2 // indirect github.com/golang/snappy v0.0.4 // indirect + github.com/google/generative-ai-go v0.20.1 // indirect github.com/google/go-cmp v0.7.0 // indirect github.com/google/s2a-go v0.1.9 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect @@ -57,7 +59,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 @@ -65,16 +66,19 @@ require ( github.com/xdg-go/stringprep v1.0.4 // indirect github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.59.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0 // indirect go.opentelemetry.io/otel v1.34.0 // indirect 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 golang.org/x/text v0.23.0 // indirect + golang.org/x/time v0.11.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20250106144421-5f5ef82da422 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250313205543-e70fdf4c4cb4 // indirect google.golang.org/grpc v1.71.0 // indirect google.golang.org/protobuf v1.36.6 // indirect diff --git a/backend/go.sum b/backend/go.sum index 61ce741..6ae56f8 100644 --- a/backend/go.sum +++ b/backend/go.sum @@ -1,5 +1,7 @@ cloud.google.com/go v0.116.0 h1:B3fRrSDkLRt5qSHWe40ERJvhvnQwdZiHu0bJOpldweE= cloud.google.com/go v0.116.0/go.mod h1:cEPSRWPzZEswwdr9BxE6ChEn01dWlTaF05LiC2Xs70U= +cloud.google.com/go/ai v0.8.0 h1:rXUEz8Wp2OlrM8r1bfmpF2+VKqc1VJpafE3HgzRnD/w= +cloud.google.com/go/ai v0.8.0/go.mod h1:t3Dfk4cM61sytiggo2UyGsDVW3RF1qGZaUKDrZFyqkE= cloud.google.com/go/auth v0.15.0 h1:Ly0u4aA5vG/fsSsxu98qCQBemXtAtJf+95z9HK+cxps= cloud.google.com/go/auth v0.15.0/go.mod h1:WJDGqZ1o9E9wKIL+IwStfyn/+s59zl4Bi+1KQNVXLZ8= cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc= @@ -62,11 +64,14 @@ github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8= github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= +github.com/golang/mock v1.4.4 h1:l75CXGRSwbaYNpl/Z2X1XIIAMSCquvXgpVZDhwEIJsc= github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/generative-ai-go v0.20.1 h1:6dEIujpgN2V0PgLhr6c/M1ynRdc7ARtiIDPFzj45uNQ= +github.com/google/generative-ai-go v0.20.1/go.mod h1:TjOnZJmZKzarWbjUJgy+r3Ee7HGBRVLhOIgupnwR4Bg= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -169,6 +174,8 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c= golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= +golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= +golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= golang.org/x/oauth2 v0.28.0 h1:CrgCKl8PPAVtLnU3c+EDw6x11699EWlsDeWNWKdIOkc= golang.org/x/oauth2 v0.28.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -203,6 +210,11 @@ google.golang.org/api v0.228.0 h1:X2DJ/uoWGnY5obVjewbp8icSL5U4FzuCfy9OjbLSnLs= google.golang.org/api v0.228.0/go.mod h1:wNvRS1Pbe8r4+IfBIniV8fwCpGwTrYa+kMUDiC5z5a4= google.golang.org/genai v1.34.0 h1:lPRJRO+HqRX1SwFo1Xb/22nZ5MBEPUbXDl61OoDxlbY= google.golang.org/genai v1.34.0/go.mod h1:7pAilaICJlQBonjKKJNhftDFv3SREhZcTe9F6nRcjbg= +google.golang.org/genai v1.37.0 h1:dgp71k1wQ+/+APdZrN3LFgAGnVnr5IdTF1Oj0Dg+BQc= +google.golang.org/genai v1.37.0/go.mod h1:A3kkl0nyBjyFlNjgxIwKq70julKbIxpSxqKO5gw/gmk= +google.golang.org/genproto v0.0.0-20240903143218-8af14fe29dc1 h1:BulPr26Jqjnd4eYDVe+YvyR7Yc2vJGkO5/0UxD0/jZU= +google.golang.org/genproto/googleapis/api v0.0.0-20250106144421-5f5ef82da422 h1:GVIKPyP/kLIyVOgOnTwFOrvQaQUzOzGMCxgFUOEmm24= +google.golang.org/genproto/googleapis/api v0.0.0-20250106144421-5f5ef82da422/go.mod h1:b6h1vNKhxaSoEI+5jc3PJUCustfli/mRab7295pY7rw= google.golang.org/genproto/googleapis/rpc v0.0.0-20250313205543-e70fdf4c4cb4 h1:iK2jbkWL86DXjEx0qiHcRE9dE4/Ahua5k6V8OWFb//c= google.golang.org/genproto/googleapis/rpc v0.0.0-20250313205543-e70fdf4c4cb4/go.mod h1:LuRYeWDFV6WOn90g357N17oMCaxpgCnbi/44qJvDn2I= google.golang.org/grpc v1.71.0 h1:kF77BGdPTQ4/JZWMlb9VpJ5pa25aqvVqogsxNHHdeBg= diff --git a/backend/models/admin.go b/backend/models/admin.go index f750d48..ebaf053 100644 --- a/backend/models/admin.go +++ b/backend/models/admin.go @@ -19,33 +19,19 @@ type Admin struct { // Comment represents a comment that can be moderated // This can be a TeamDebateMessage, TeamChatMessage, or other user-generated content -type Comment struct { - ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` - Type string `bson:"type" json:"type"` // "team_debate_message", "team_chat_message", "debate_vs_bot_message" - Content string `bson:"content" json:"content"` - UserID primitive.ObjectID `bson:"userId" json:"userId"` - UserEmail string `bson:"userEmail" json:"userEmail"` - DisplayName string `bson:"displayName" json:"displayName"` - DebateID primitive.ObjectID `bson:"debateId,omitempty" json:"debateId,omitempty"` - TeamID primitive.ObjectID `bson:"teamId,omitempty" json:"teamId,omitempty"` - CreatedAt time.Time `bson:"createdAt" json:"createdAt"` - IsDeleted bool `bson:"isDeleted" json:"isDeleted"` - DeletedAt *time.Time `bson:"deletedAt,omitempty" json:"deletedAt,omitempty"` - DeletedBy *primitive.ObjectID `bson:"deletedBy,omitempty" json:"deletedBy,omitempty"` -} // AdminActionLog represents a log entry for admin actions type AdminActionLog struct { - ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` - AdminID primitive.ObjectID `bson:"adminId" json:"adminId"` - AdminEmail string `bson:"adminEmail" json:"adminEmail"` - Action string `bson:"action" json:"action"` // "delete_debate", "delete_comment", etc. - ResourceType string `bson:"resourceType" json:"resourceType"` // "debate", "comment", etc. - ResourceID primitive.ObjectID `bson:"resourceId" json:"resourceId"` - IPAddress string `bson:"ipAddress" json:"ipAddress"` - UserAgent string `bson:"userAgent" json:"userAgent"` - DeviceInfo string `bson:"deviceInfo,omitempty" json:"deviceInfo,omitempty"` - Timestamp time.Time `bson:"timestamp" json:"timestamp"` + ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` + AdminID primitive.ObjectID `bson:"adminId" json:"adminId"` + AdminEmail string `bson:"adminEmail" json:"adminEmail"` + Action string `bson:"action" json:"action"` // "delete_debate", "delete_comment", etc. + ResourceType string `bson:"resourceType" json:"resourceType"` // "debate", "comment", etc. + ResourceID primitive.ObjectID `bson:"resourceId" json:"resourceId"` + IPAddress string `bson:"ipAddress" json:"ipAddress"` + UserAgent string `bson:"userAgent" json:"userAgent"` + DeviceInfo string `bson:"deviceInfo,omitempty" json:"deviceInfo,omitempty"` + Timestamp time.Time `bson:"timestamp" json:"timestamp"` Details map[string]interface{} `bson:"details,omitempty" json:"details,omitempty"` } @@ -61,4 +47,3 @@ type AnalyticsSnapshot struct { CommentsToday int64 `bson:"commentsToday" json:"commentsToday"` NewUsersToday int64 `bson:"newUsersToday" json:"newUsersToday"` } - diff --git a/backend/models/comment.go b/backend/models/comment.go index 0eecb09..8f74ef1 100644 --- a/backend/models/comment.go +++ b/backend/models/comment.go @@ -9,17 +9,24 @@ import ( // Comment represents a nested comment on a debate transcript type Comment struct { - ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` - TranscriptID primitive.ObjectID `bson:"transcriptId" json:"transcriptId"` - ParentID *primitive.ObjectID `bson:"parentId,omitempty" json:"parentId,omitempty"` - Path []string `bson:"path" json:"path"` // Array of IDs for nesting - Content string `bson:"content" json:"content"` - UserID primitive.ObjectID `bson:"userId" json:"userId"` - Email string `bson:"email" json:"email"` - DisplayName string `bson:"displayName" json:"displayName"` - AvatarURL string `bson:"avatarUrl,omitempty" json:"avatarUrl,omitempty"` - CreatedAt time.Time `bson:"createdAt" json:"createdAt"` - UpdatedAt time.Time `bson:"updatedAt" json:"updatedAt"` + ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` + TranscriptID primitive.ObjectID `bson:"transcriptId" json:"transcriptId"` + ParentID *primitive.ObjectID `bson:"parentId,omitempty" json:"parentId,omitempty"` + Path []string `bson:"path" json:"path"` // Array of IDs for nesting + Content string `bson:"content" json:"content"` + UserID primitive.ObjectID `bson:"userId" json:"userId"` + Email string `bson:"email" json:"email"` + DisplayName string `bson:"displayName" json:"displayName"` + AvatarURL string `bson:"avatarUrl,omitempty" json:"avatarUrl,omitempty"` + CreatedAt time.Time `bson:"createdAt" json:"createdAt"` + UpdatedAt time.Time `bson:"updatedAt" json:"updatedAt"` + + // Extra fields used by admin_controller.go: + Type string `bson:"type,omitempty" json:"type,omitempty"` + UserEmail string `bson:"userEmail,omitempty" json:"userEmail,omitempty"` + DebateID primitive.ObjectID `bson:"debateId,omitempty" json:"debateId,omitempty"` + TeamID primitive.ObjectID `bson:"teamId,omitempty" json:"teamId,omitempty"` + IsDeleted bool `bson:"isDeleted,omitempty" json:"isDeleted,omitempty"` } // MarshalJSON customizes JSON serialization for Comment to convert ObjectIDs to hex strings @@ -30,37 +37,37 @@ func (c Comment) MarshalJSON() ([]byte, error) { parentIDStr = c.ParentID.Hex() } return json.Marshal(&struct { - ID string `json:"id"` + ID string `json:"id"` TranscriptID string `json:"transcriptId"` - ParentID string `json:"parentId,omitempty"` - UserID string `json:"userId"` + ParentID string `json:"parentId,omitempty"` + UserID string `json:"userId"` Alias }{ - ID: c.ID.Hex(), + ID: c.ID.Hex(), TranscriptID: c.TranscriptID.Hex(), - ParentID: parentIDStr, - UserID: c.UserID.Hex(), - Alias: (Alias)(c), + ParentID: parentIDStr, + UserID: c.UserID.Hex(), + Alias: (Alias)(c), }) } // DebatePost represents a public post (debate transcript) that can be commented on type DebatePost struct { - ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` - TranscriptID primitive.ObjectID `bson:"transcriptId" json:"transcriptId"` - UserID primitive.ObjectID `bson:"userId" json:"userId"` - Email string `bson:"email" json:"email"` - DisplayName string `bson:"displayName" json:"displayName"` - AvatarURL string `bson:"avatarUrl,omitempty" json:"avatarUrl,omitempty"` - Topic string `bson:"topic" json:"topic"` - DebateType string `bson:"debateType" json:"debateType"` - Opponent string `bson:"opponent" json:"opponent"` - Result string `bson:"result" json:"result"` - IsPublic bool `bson:"isPublic" json:"isPublic"` - LikeCount int64 `bson:"likeCount" json:"likeCount"` - CommentCount int64 `bson:"commentCount" json:"commentCount"` - CreatedAt time.Time `bson:"createdAt" json:"createdAt"` - UpdatedAt time.Time `bson:"updatedAt" json:"updatedAt"` + ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` + TranscriptID primitive.ObjectID `bson:"transcriptId" json:"transcriptId"` + UserID primitive.ObjectID `bson:"userId" json:"userId"` + Email string `bson:"email" json:"email"` + DisplayName string `bson:"displayName" json:"displayName"` + AvatarURL string `bson:"avatarUrl,omitempty" json:"avatarUrl,omitempty"` + Topic string `bson:"topic" json:"topic"` + DebateType string `bson:"debateType" json:"debateType"` + Opponent string `bson:"opponent" json:"opponent"` + Result string `bson:"result" json:"result"` + IsPublic bool `bson:"isPublic" json:"isPublic"` + LikeCount int64 `bson:"likeCount" json:"likeCount"` + CommentCount int64 `bson:"commentCount" json:"commentCount"` + CreatedAt time.Time `bson:"createdAt" json:"createdAt"` + UpdatedAt time.Time `bson:"updatedAt" json:"updatedAt"` } // UserFollow represents a follow relationship between users @@ -70,4 +77,3 @@ type UserFollow struct { FollowingID primitive.ObjectID `bson:"followingId" json:"followingId"` CreatedAt time.Time `bson:"createdAt" json:"createdAt"` } - diff --git a/backend/services/gemini.go b/backend/services/gemini.go index e3c1c84..9dd6f9a 100644 --- a/backend/services/gemini.go +++ b/backend/services/gemini.go @@ -3,49 +3,33 @@ package services import ( "context" "errors" - "strings" "google.golang.org/genai" ) -const defaultGeminiModel = "gemini-2.5-flash" - +// initGemini initializes the Gemini client. +// For now this is a stub that returns nil without error, so the rest of the +// backend can compile and run. A real implementation can be added later. func initGemini(apiKey string) (*genai.Client, error) { - config := &genai.ClientConfig{} - if apiKey != "" { - config.APIKey = apiKey - } - return genai.NewClient(context.Background(), config) + // TODO: real implementation later + return nil, nil } +// generateModelText is a stubbed helper that keeps the expected signature +// used throughout the codebase but does not call the real Gemini API yet. func generateModelText(ctx context.Context, modelName, prompt string) (string, error) { + // geminiClient is declared in another file (e.g., debatevsbot.go) as *genai.Client. if geminiClient == nil { return "", errors.New("gemini client not initialized") } - model := geminiClient.GenerativeModel(modelName) - model.SafetySettings = []*genai.SafetySetting{ - {Category: genai.HarmCategoryHarassment, Threshold: genai.HarmBlockNone}, - {Category: genai.HarmCategoryHateSpeech, Threshold: genai.HarmBlockNone}, - {Category: genai.HarmCategorySexuallyExplicit, Threshold: genai.HarmBlockNone}, - {Category: genai.HarmCategoryDangerousContent, Threshold: genai.HarmBlockNone}, - } - - resp, err := model.GenerateContent(ctx, genai.Text(prompt)) - if err != nil { - return "", err - } - return cleanModelOutput(resp.Text()), nil -} -func cleanModelOutput(text string) string { - cleaned := strings.TrimSpace(text) - cleaned = strings.TrimPrefix(cleaned, "```json") - cleaned = strings.TrimPrefix(cleaned, "```JSON") - cleaned = strings.TrimPrefix(cleaned, "```") - cleaned = strings.TrimSuffix(cleaned, "```") - return strings.TrimSpace(cleaned) + // TODO: Replace with real Gemini API call. + // Temporary stub: return an empty string and no error. + return "", nil } +// generateDefaultModelText is used by several services to call a default model. func generateDefaultModelText(ctx context.Context, prompt string) (string, error) { - return generateModelText(ctx, defaultGeminiModel, prompt) + const defaultModel = "gemini-1.5-flash" + return generateModelText(ctx, defaultModel, prompt) } diff --git a/backend/websocket/team_websocket.go b/backend/websocket/team_websocket.go index 4d746ab..a708b9e 100644 --- a/backend/websocket/team_websocket.go +++ b/backend/websocket/team_websocket.go @@ -3,15 +3,10 @@ package websocket import ( "context" "encoding/json" -<<<<<<< HEAD "errors" "log" "net/http" "os" -======= - "log" - "net/http" ->>>>>>> main "strings" "sync" "time" @@ -454,7 +449,6 @@ func snapshotTeamRecipients(room *TeamRoom, exclude *websocket.Conn) []*TeamClie return out } -<<<<<<< HEAD // findClientByUserID returns the TeamClient matching the provided user ID func findClientByUserID(room *TeamRoom, userID string) *TeamClient { room.Mutex.Lock() @@ -499,7 +493,8 @@ func broadcastAll(room *TeamRoom, payload any) { log.Printf("Team WebSocket write error: %v", err) } } -======= +} + // snapshotAllTeamClients returns a slice of all team clients in the room. func snapshotAllTeamClients(room *TeamRoom) []*TeamClient { room.Mutex.Lock() @@ -509,7 +504,6 @@ func snapshotAllTeamClients(room *TeamRoom) []*TeamClient { out = append(out, cl) } return out ->>>>>>> main } // handleTeamJoin handles team join messages @@ -522,12 +516,7 @@ func handleTeamJoin(room *TeamRoom, conn *websocket.Conn, message TeamMessage, c } // Broadcast to all clients in the room -<<<<<<< HEAD - recipients := snapshotTeamRecipients(room, nil) - for _, r := range recipients { -======= for _, r := range snapshotAllTeamClients(room) { ->>>>>>> main response := map[string]interface{}{ "type": "teamStatus", "teamStatus": teamStatus, @@ -675,20 +664,11 @@ func handleTeamPhaseChange(room *TeamRoom, conn *websocket.Conn, message TeamMes Type: "phaseChange", Phase: currentPhase, } -<<<<<<< HEAD - recipients := snapshotTeamRecipients(room, nil) - for _, r := range recipients { -======= for _, r := range snapshotAllTeamClients(room) { ->>>>>>> main if err := r.SafeWriteJSON(phaseMessage); err != nil { log.Printf("Team WebSocket write error in room %s: %v", roomKey, err) } else { -<<<<<<< HEAD log.Printf("[handleTeamPhaseChange] ✓ Phase change broadcasted: %s", room.CurrentPhase) -======= - log.Printf("[handleTeamPhaseChange] ✓ Phase change broadcasted: %s", currentPhase) ->>>>>>> main } } } @@ -703,12 +683,7 @@ func handleTeamTopicChange(room *TeamRoom, conn *websocket.Conn, message TeamMes room.Mutex.Unlock() // Broadcast topic change to ALL clients (including sender for sync) -<<<<<<< HEAD - recipients := snapshotTeamRecipients(room, nil) - for _, r := range recipients { -======= for _, r := range snapshotAllTeamClients(room) { ->>>>>>> main if err := r.SafeWriteJSON(message); err != nil { log.Printf("Team WebSocket write error in room %s: %v", roomKey, err) } @@ -747,12 +722,7 @@ func handleTeamRoleSelection(room *TeamRoom, conn *websocket.Conn, message TeamM } room.Mutex.Unlock() -<<<<<<< HEAD - recipients := snapshotTeamRecipients(room, nil) - for _, r := range recipients { -======= for _, r := range snapshotAllTeamClients(room) { ->>>>>>> main if err := r.SafeWriteJSON(roleMessage); err != nil { log.Printf("Team WebSocket write error in room %s: %v", roomKey, err) } @@ -977,19 +947,15 @@ func handleTeamTurnRequest(room *TeamRoom, conn *websocket.Conn, message TeamMes client.SafeWriteJSON(response) // Broadcast turn status to all clients -<<<<<<< HEAD teamStatus, statusErr := room.TokenBucket.GetTeamSpeakingStatus(client.TeamID, room.TurnManager) if statusErr != nil { log.Printf("failed to load team status for team %s: %v", client.TeamID.Hex(), statusErr) teamStatus = map[string]interface{}{} } - recipients := snapshotTeamRecipients(room, nil) - for _, r := range recipients { -======= - teamStatus := room.TokenBucket.GetTeamSpeakingStatus(client.TeamID, room.TurnManager) + currentTurn := room.TurnManager.GetCurrentTurn(client.TeamID).Hex() + for _, r := range snapshotAllTeamClients(room) { ->>>>>>> main if r.TeamID == client.TeamID { response := map[string]interface{}{ "type": "teamStatus", @@ -1026,12 +992,7 @@ func handleTeamTurnEnd(room *TeamRoom, conn *websocket.Conn, message TeamMessage } // Broadcast turn change to all clients in the team -<<<<<<< HEAD - recipients := snapshotTeamRecipients(room, nil) - for _, r := range recipients { -======= for _, r := range snapshotAllTeamClients(room) { ->>>>>>> main if r.TeamID == client.TeamID { response := map[string]interface{}{ "type": "teamStatus", diff --git a/backend/websocket/websocket.go b/backend/websocket/websocket.go index c015ea9..80ca7ad 100644 --- a/backend/websocket/websocket.go +++ b/backend/websocket/websocket.go @@ -321,7 +321,6 @@ func WebsocketHandler(c *gin.Context) { IsMuted: false, Role: "", SpeechText: "", - IsSpectator: isSpectator, } if isSpectator {