Skip to content

Commit 28ada75

Browse files
authored
Merge pull request #101 from RealTeamRocket/98-chat-room
98 chat room
2 parents 2791d6f + b88fb14 commit 28ada75

33 files changed

+944
-43
lines changed

rocket-backend/go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ require (
5757
github.com/goccy/go-json v0.10.5 // indirect
5858
github.com/gogo/protobuf v1.3.2 // indirect
5959
github.com/google/uuid v1.6.0
60+
github.com/gorilla/websocket v1.5.3
6061
github.com/jackc/pgpassfile v1.0.0 // indirect
6162
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
6263
github.com/jackc/puddle/v2 v2.2.2 // indirect

rocket-backend/go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,8 @@ github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J
8282
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
8383
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
8484
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
85+
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
86+
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
8587
github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rHAxPBD8KFhJpmcqms=
8688
github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg=
8789
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=

rocket-backend/integration-tests/mocks/database_mock.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,11 @@ type MockDB struct {
5252
GetChallengeByIDFunc func(challengeID uuid.UUID) (*types.Challenge, error)
5353
SaveActivityFunc func(userID uuid.UUID, message string) error
5454
GetActivitiesForUserAndFriendsFunc func(userID uuid.UUID) ([]types.ActivityWithUser, error)
55+
SaveChatMessageFunc func(userID uuid.UUID, message string, timestamp string) (uuid.UUID, error)
56+
GetChatMessagesFunc func(userID uuid.UUID) ([]types.ChatMessage, error)
57+
AddReactionToChatMessageFunc func(userID uuid.UUID, messageID uuid.UUID) error
58+
CountReactionsForMessageFunc func(messageID uuid.UUID) (int, error)
59+
GetIDByMessageIDFunc func(messageID uuid.UUID) (uuid.UUID, error)
5560
}
5661

5762
func (m *MockDB) ExecuteRawSQL(query string) (sql.Result, error) {
@@ -347,3 +352,38 @@ func (m *MockDB) DeleteRun(runID uuid.UUID) error {
347352
}
348353
return nil
349354
}
355+
356+
func (m *MockDB) SaveChatMessage(userID uuid.UUID, message string, timestamp string) (uuid.UUID, error) {
357+
if m.SaveChatMessageFunc != nil {
358+
return m.SaveChatMessageFunc(userID, message, timestamp)
359+
}
360+
return uuid.Nil, nil
361+
}
362+
363+
func (m *MockDB) GetChatMessages(userID uuid.UUID) ([]types.ChatMessage, error) {
364+
if m.GetChatMessagesFunc != nil {
365+
return m.GetChatMessagesFunc(userID)
366+
}
367+
return nil, nil
368+
}
369+
370+
func (m *MockDB) AddReactionToChatMessage(userID uuid.UUID, messageID uuid.UUID) error {
371+
if m.AddReactionToChatMessageFunc != nil {
372+
return m.AddReactionToChatMessageFunc(userID, messageID)
373+
}
374+
return nil
375+
}
376+
377+
func (m *MockDB) CountReactionsForMessage(messageID uuid.UUID) (int, error) {
378+
if m.CountReactionsForMessageFunc != nil {
379+
return m.CountReactionsForMessageFunc(messageID)
380+
}
381+
return 0, nil
382+
}
383+
384+
func (m *MockDB) GetIDByMessageID(messageID uuid.UUID) (uuid.UUID, error) {
385+
if m.GetIDByMessageIDFunc != nil {
386+
return m.GetIDByMessageIDFunc(messageID)
387+
}
388+
return uuid.Nil, nil
389+
}

rocket-backend/internal/custom_error/database_error.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,6 @@ var (
1212
ErrChallengeNotFound = errors.New("challenge not found")
1313
ErrImageNotFound = errors.New("image not found")
1414
ErrSettingsNotFound = errors.New("settings not found")
15-
ErrFailedToDelete = errors.New("failed to delete data")
15+
ErrFailedToDelete = errors.New("failed to delete data")
16+
ErrFailedToLoad = errors.New("failed to load data")
1617
)
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
package database
2+
3+
import (
4+
"fmt"
5+
"rocket-backend/internal/custom_error"
6+
"rocket-backend/internal/types"
7+
"rocket-backend/pkg/logger"
8+
9+
"github.com/google/uuid"
10+
)
11+
12+
func (s *service) SaveChatMessage(userID uuid.UUID, message string, timestamp string) (uuid.UUID, error) {
13+
query := `
14+
INSERT INTO chat_messages (user_id, message, timestamp)
15+
VALUES ($1, $2, $3)
16+
RETURNING id
17+
`
18+
var id uuid.UUID
19+
err := s.db.QueryRow(query, userID, message, timestamp).Scan(&id)
20+
if err != nil {
21+
logger.Error("Failed to save chat message and return ID", err)
22+
return uuid.Nil, fmt.Errorf("%w: %v", custom_error.ErrFailedToSave, err)
23+
}
24+
return id, nil
25+
}
26+
27+
func (s *service) GetChatMessages(userID uuid.UUID) ([]types.ChatMessage, error) {
28+
query := `
29+
SELECT cm.id, cm.user_id, u.username, cm.message, cm.timestamp
30+
FROM chat_messages cm
31+
JOIN users u ON cm.user_id = u.id
32+
ORDER BY cm.timestamp ASC
33+
`
34+
rows, err := s.db.Query(query)
35+
if err != nil {
36+
logger.Error("Failed to load chat messages", err)
37+
return nil, fmt.Errorf("%w: %v", custom_error.ErrFailedToLoad, err)
38+
}
39+
defer rows.Close()
40+
41+
var messages []types.ChatMessage
42+
for rows.Next() {
43+
var msg types.ChatMessage
44+
var dbUserID uuid.UUID
45+
if err := rows.Scan(&msg.ID, &dbUserID, &msg.Username, &msg.Message, &msg.Timestamp); err != nil {
46+
logger.Error("Failed to scan chat message", err)
47+
continue
48+
}
49+
if dbUserID == userID {
50+
msg.Username = "You"
51+
}
52+
// Count reactions for this message
53+
count, err := s.CountReactionsForMessage(msg.ID)
54+
if err != nil {
55+
logger.Error("Failed to count reactions for message", err)
56+
msg.Reactions = 0
57+
} else {
58+
msg.Reactions = count
59+
}
60+
// Check if the current user has reacted to this message
61+
hasReacted := false
62+
checkQuery := `SELECT 1 FROM chat_messages_reactions WHERE message_id = $1 AND user_id = $2 LIMIT 1`
63+
row := s.db.QueryRow(checkQuery, msg.ID, userID)
64+
var dummy int
65+
if err := row.Scan(&dummy); err == nil {
66+
hasReacted = true
67+
}
68+
msg.HasReacted = hasReacted
69+
messages = append(messages, msg)
70+
}
71+
if err := rows.Err(); err != nil {
72+
logger.Error("Error iterating chat messages", err)
73+
return nil, fmt.Errorf("%w: %v", custom_error.ErrFailedToLoad, err)
74+
}
75+
return messages, nil
76+
}
77+
78+
func (s *service) AddReactionToChatMessage(userID uuid.UUID, messageID uuid.UUID) error {
79+
query := `
80+
INSERT INTO chat_messages_reactions (message_id, user_id)
81+
VALUES ($1, $2)
82+
ON CONFLICT DO NOTHING
83+
`
84+
_, err := s.db.Exec(query, messageID, userID)
85+
return err
86+
}
87+
88+
func (s *service) CountReactionsForMessage(messageID uuid.UUID) (int, error) {
89+
var count int
90+
query := `SELECT COUNT(*) FROM chat_messages_reactions WHERE message_id = $1`
91+
err := s.db.QueryRow(query, messageID).Scan(&count)
92+
return count, err
93+
}
94+
95+
func (s *service) GetIDByMessageID(messageID uuid.UUID) (uuid.UUID, error) {
96+
query := `SELECT user_id FROM chat_messages WHERE id = $1`
97+
var userID uuid.UUID
98+
err := s.db.QueryRow(query, messageID).Scan(&userID)
99+
if err != nil {
100+
logger.Error("Failed to get user ID by message ID", err)
101+
return uuid.Nil, fmt.Errorf("%w: %v", custom_error.ErrFailedToRetrieveData, err)
102+
}
103+
return userID, nil
104+
}

rocket-backend/internal/database/daily_steps_table.go

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,32 +9,37 @@ import (
99
"github.com/google/uuid"
1010
)
1111

12+
func (s *service) saveStepMilestoneActivities(userID uuid.UUID, oldSteps, newSteps int) {
13+
milestone := 2000
14+
for threshold := ((oldSteps/milestone)+1)*milestone; threshold <= newSteps; threshold += milestone {
15+
message := fmt.Sprintf("🎉 Has reached %d steps today! 🚀", threshold)
16+
_ = s.SaveActivity(userID, message)
17+
}
18+
}
19+
1220
func (s *service) UpdateDailySteps(userID uuid.UUID, steps int) error {
1321
currentDate := time.Now().Format("2006-01-02")
1422

15-
// Check if the user already has a record for today
1623
var existingSteps int
1724
queryCheck := `SELECT steps_taken FROM daily_steps WHERE user_id = $1 AND date = $2`
1825
err := s.db.QueryRow(queryCheck, userID, currentDate).Scan(&existingSteps)
1926

2027
if err != nil {
21-
// No record for today, insert new record and award rocket points
2228
id := uuid.New()
2329
queryInsert := `INSERT INTO daily_steps (id, user_id, steps_taken, date) VALUES ($1, $2, $3, $4)`
2430
_, err := s.db.Exec(queryInsert, id, userID, steps, currentDate)
2531
if err != nil {
26-
// Log and return error if the insert fails
2732
logger.Error("Error inserting daily steps: %v\n", err)
2833
return fmt.Errorf("failed to insert daily steps: %w", err)
2934
}
30-
// Award rocket points for all steps (steps/10)
3135
rocketPoints := steps / 10
3236
if rocketPoints > 0 {
3337
if err := s.UpdateRocketPoints(userID, rocketPoints); err != nil {
3438
logger.Error("Error updating rocket points: %v\n", err)
3539
return fmt.Errorf("failed to update rocket points: %w", err)
3640
}
3741
}
42+
s.saveStepMilestoneActivities(userID, 0, steps)
3843
} else {
3944
// If an entry exists, update the steps and rocket points only if the new steps are greater than the existing steps
4045
if steps > existingSteps {
@@ -43,18 +48,17 @@ func (s *service) UpdateDailySteps(userID uuid.UUID, steps int) error {
4348
queryUpdate := `UPDATE daily_steps SET steps_taken = $1 WHERE user_id = $2 AND date = $3`
4449
_, err := s.db.Exec(queryUpdate, steps, userID, currentDate)
4550
if err != nil {
46-
// Log and return error if the update fails
4751
logger.Error("Error updating daily steps: %v\n", err)
4852
return fmt.Errorf("failed to update daily steps: %w", err)
4953
}
50-
// Award rocket points for the difference in steps (stepDiff/10)
5154
rocketPoints := stepDiff / 10
5255
if rocketPoints > 0 {
5356
if err := s.UpdateRocketPoints(userID, rocketPoints); err != nil {
5457
logger.Error("Error updating rocket points: %v\n", err)
5558
return fmt.Errorf("failed to update rocket points: %w", err)
5659
}
5760
}
61+
s.saveStepMilestoneActivities(userID, existingSteps, steps)
5862
}
5963
}
6064
}

rocket-backend/internal/database/database.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,13 @@ type Service interface {
8484
// activities
8585
SaveActivity(userID uuid.UUID, message string) error
8686
GetActivitiesForUserAndFriends(userID uuid.UUID) ([]types.ActivityWithUser, error)
87+
88+
// chat
89+
SaveChatMessage(userID uuid.UUID, message string, timestamp string) (uuid.UUID, error)
90+
GetChatMessages(userID uuid.UUID) ([]types.ChatMessage, error)
91+
AddReactionToChatMessage(userID uuid.UUID, messageID uuid.UUID) error
92+
CountReactionsForMessage(messageID uuid.UUID) (int, error)
93+
GetIDByMessageID(messageID uuid.UUID) (uuid.UUID, error)
8794
}
8895

8996
type service struct {
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package server
2+
3+
import (
4+
"net/http"
5+
6+
"github.com/gin-gonic/gin"
7+
"github.com/google/uuid"
8+
)
9+
10+
func (s *Server) GetChatHistoryHandler(c *gin.Context) {
11+
userID, exists := c.Get("userID")
12+
if !exists {
13+
c.JSON(http.StatusUnauthorized, gin.H{"error": "User not authenticated"})
14+
return
15+
}
16+
17+
userUUID, err := uuid.Parse(userID.(string))
18+
if err != nil {
19+
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid user ID format"})
20+
return
21+
}
22+
23+
messages, err := s.db.GetChatMessages(userUUID)
24+
if err != nil {
25+
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch chat messages"})
26+
return
27+
}
28+
29+
c.JSON(http.StatusOK, gin.H{"messages": messages})
30+
}

0 commit comments

Comments
 (0)