Skip to content

Commit 4ffa135

Browse files
authored
Merge pull request #118 from RealTeamRocket/Highscore
Highscore
2 parents 71f4f4e + 8d298ed commit 4ffa135

11 files changed

Lines changed: 1203 additions & 61 deletions

File tree

rocket-backend/integration-tests/internal/server-tests/ranking_routes_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,9 @@ var _ = Describe("Ranking Handlers API", func() {
6565
Expect(resp.StatusCode).To(Equal(200))
6666
var friendsRanking []map[string]any
6767
_ = json.NewDecoder(resp.Body).Decode(&friendsRanking)
68-
Expect(len(friendsRanking)).To(Equal(2))
68+
Expect(len(friendsRanking)).To(Equal(3))
6969
Expect(friendsRanking[0]["username"]).To(Equal(userB))
7070
Expect(friendsRanking[1]["username"]).To(Equal(userC))
71+
Expect(friendsRanking[2]["username"]).To(Equal(userA))
7172
})
7273
})

rocket-backend/internal/database/friends_table.go

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,10 @@ func (s *service) GetFriends(userID uuid.UUID) ([]types.User, error) {
8383
return friends[i].Username < friends[j].Username
8484
})
8585

86+
if friends == nil {
87+
friends = []types.User{}
88+
}
89+
8690
return friends, nil
8791
}
8892

@@ -92,9 +96,7 @@ func (s *service) GetFriendsRankedByPoints(userID uuid.UUID) ([]types.User, erro
9296
FROM friends f
9397
JOIN users u ON f.friend_id = u.id
9498
WHERE f.user_id = $1
95-
ORDER BY u.rocketpoints DESC
9699
`
97-
98100
rows, err := s.db.Query(query, userID)
99101
if err != nil {
100102
logger.Error("Failed to get friends", err)
@@ -112,6 +114,29 @@ func (s *service) GetFriendsRankedByPoints(userID uuid.UUID) ([]types.User, erro
112114
friends = append(friends, friend)
113115
}
114116

117+
// Also include the user themselves
118+
var user types.User
119+
userQuery := `
120+
SELECT id, username, email, rocketpoints
121+
FROM users
122+
WHERE id = $1
123+
`
124+
err = s.db.QueryRow(userQuery, userID).Scan(&user.ID, &user.Username, &user.Email, &user.RocketPoints)
125+
if err != nil {
126+
logger.Error("Failed to get user for self-inclusion in ranking", err)
127+
} else {
128+
friends = append(friends, user)
129+
}
130+
131+
// Sort by rocketpoints descending
132+
sort.Slice(friends, func(i, j int) bool {
133+
return friends[i].RocketPoints > friends[j].RocketPoints
134+
})
135+
136+
if friends == nil {
137+
friends = []types.User{}
138+
}
139+
115140
return friends, nil
116141
}
117142

@@ -144,5 +169,9 @@ func (s *service) GetFollowers(userID uuid.UUID) ([]types.User, error) {
144169
return followers[i].Username < followers[j].Username
145170
})
146171

172+
if followers == nil {
173+
followers = []types.User{}
174+
}
175+
147176
return followers, nil
148177
}

rocket-backend/internal/server/ranking_handlers.go

Lines changed: 41 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
package server
22

33
import (
4+
"encoding/base64"
45
"errors"
56
"net/http"
67

78
"rocket-backend/internal/custom_error"
9+
"rocket-backend/internal/types"
810
"github.com/gin-gonic/gin"
911
"github.com/google/uuid"
1012
)
@@ -32,12 +34,26 @@ func (s *Server) GetFriendsRankedHandler(c *gin.Context) {
3234
return
3335
}
3436

35-
if len(friends) == 0 {
36-
c.JSON(http.StatusOK, []interface{}{})
37-
return
37+
var friendsWithImages []types.UserWithImageDTO
38+
for _, user := range friends {
39+
userImage, err := s.db.GetUserImage(user.ID)
40+
var imageName, imageData string
41+
if err == nil && userImage != nil {
42+
imageName = userImage.Name
43+
imageData = base64.StdEncoding.EncodeToString(userImage.Data)
44+
}
45+
friendsWithImages = append(friendsWithImages, types.UserWithImageDTO{
46+
ID: user.ID,
47+
Username: user.Username,
48+
Email: user.Email,
49+
RocketPoints: user.RocketPoints,
50+
ImageName: imageName,
51+
ImageData: imageData,
52+
Steps: 0,
53+
})
3854
}
3955

40-
c.JSON(http.StatusOK, friends)
56+
c.JSON(http.StatusOK, friendsWithImages)
4157
}
4258

4359
func (s *Server) GetUserRankingHandler(c *gin.Context) {
@@ -51,5 +67,25 @@ func (s *Server) GetUserRankingHandler(c *gin.Context) {
5167
return
5268
}
5369

54-
c.JSON(http.StatusOK, ranking)
70+
var usersWithImages []types.UserWithImageDTO
71+
for _, user := range ranking {
72+
userImage, err := s.db.GetUserImage(user.ID)
73+
var imageName, imageData string
74+
if err == nil && userImage != nil {
75+
imageName = userImage.Name
76+
imageData = base64.StdEncoding.EncodeToString(userImage.Data)
77+
}
78+
79+
usersWithImages = append(usersWithImages, types.UserWithImageDTO{
80+
ID: user.ID,
81+
Username: user.Username,
82+
Email: user.Email,
83+
RocketPoints: user.RocketPoints,
84+
ImageName: imageName,
85+
ImageData: imageData,
86+
Steps: 0, // no steps needed for ranking
87+
})
88+
}
89+
90+
c.JSON(http.StatusOK, usersWithImages)
5591
}

website/src/api/backend-api.ts

Lines changed: 71 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
1-
import axios, { type AxiosResponse } from 'axios';
1+
import axios, { type AxiosResponse } from 'axios'
22

33
const publicAxiosApi = axios.create({
44
baseURL: '/api/v1',
55
timeout: 10000,
6-
headers: {'content-type': 'application/json'}
6+
headers: { 'content-type': 'application/json' }
77
})
88

99
const protectedAxiosApi = axios.create({
1010
baseURL: '/api/v1/protected',
1111
timeout: 10000,
12-
headers: {'content-type': 'application/json'}
12+
headers: { 'content-type': 'application/json' }
1313
})
1414

1515
export default {
@@ -23,75 +23,99 @@ export default {
2323
return protectedAxiosApi.get('/', { withCredentials: true })
2424
},
2525
logout(): Promise<AxiosResponse> {
26-
console.log("Logging out")
26+
console.log('Logging out')
2727
return publicAxiosApi.post('/logout', {}, { withCredentials: true })
2828
},
2929

3030
getUserStatistics(id?: string): Promise<AxiosResponse> {
31-
const data = id ? { id } : {};
32-
const config = { withCredentials: true };
33-
return protectedAxiosApi.post('/user/statistics', data, config);
31+
const data = id ? { id } : {}
32+
const config = { withCredentials: true }
33+
return protectedAxiosApi.post('/user/statistics', data, config)
3434
},
3535
getActivityFeed(): Promise<AxiosResponse> {
36-
return protectedAxiosApi.get('/activites', { withCredentials: true });
36+
return protectedAxiosApi.get('/activites', { withCredentials: true })
3737
},
3838
getUserImage(id?: string): Promise<AxiosResponse> {
39-
const config = { withCredentials: true };
40-
const data = id ? { user_id: id } : {};
41-
return protectedAxiosApi.post('/user/image', data, config);
39+
const config = { withCredentials: true }
40+
const data = id ? { user_id: id } : {}
41+
return protectedAxiosApi.post('/user/image', data, config)
4242
},
4343
getUser(username: string): Promise<AxiosResponse> {
44-
return protectedAxiosApi.get(`/user/${username}`, { withCredentials: true });
44+
return protectedAxiosApi.get(`/user/${username}`, { withCredentials: true })
4545
},
4646
getMyself(): Promise<AxiosResponse> {
47-
return protectedAxiosApi.get('/user', { withCredentials: true });
47+
return protectedAxiosApi.get('/user', { withCredentials: true })
4848
},
4949
getChatHistory(): Promise<AxiosResponse> {
50-
return protectedAxiosApi.get('/chat/history', { withCredentials: true });
50+
return protectedAxiosApi.get('/chat/history', { withCredentials: true })
5151
},
5252
getPastRuns(): Promise<AxiosResponse> {
53-
return protectedAxiosApi.get('/runs', { withCredentials: true });
53+
return protectedAxiosApi.get('/runs', { withCredentials: true })
5454
},
5555
deletePastRun(id: string): Promise<AxiosResponse> {
56-
return protectedAxiosApi.delete(`/runs/${id}`, { withCredentials: true });
56+
return protectedAxiosApi.delete(`/runs/${id}`, { withCredentials: true })
5757
},
5858
savePlannedRun(route: string, name: string, distance: number): Promise<AxiosResponse> {
59-
return protectedAxiosApi.post('/runs/plan', { route, name, distance }, { withCredentials: true });
59+
return protectedAxiosApi.post(
60+
'/runs/plan',
61+
{ route, name, distance },
62+
{ withCredentials: true }
63+
)
6064
},
6165
getPlannedRuns(): Promise<AxiosResponse> {
62-
return protectedAxiosApi.get('/runs/plan', { withCredentials: true });
66+
return protectedAxiosApi.get('/runs/plan', { withCredentials: true })
6367
},
6468
deletePlannedRun(id: string): Promise<AxiosResponse> {
65-
return protectedAxiosApi.delete(`/runs/plan/${id}`, { withCredentials: true });
66-
},
67-
getChallenges(): Promise<AxiosResponse> {
68-
return protectedAxiosApi.get('/challenges/new', { withCredentials: true });
69-
},
70-
completeChallenge(challengeId: string, rocketPoints: number): Promise<AxiosResponse> {
71-
return protectedAxiosApi.post('/challenges/complete', { challenge_id: challengeId, rocket_points: rocketPoints }, { withCredentials: true });
72-
},
73-
getChallengeProgress(): Promise<AxiosResponse> {
74-
return protectedAxiosApi.get('/challenges/progress', { withCredentials: true });
75-
},
76-
getFriends(): Promise<AxiosResponse> {
77-
return protectedAxiosApi.get('/friends', { withCredentials: true });
78-
},
79-
inviteFriendToChallenge(challengeId: string, friendId: string): Promise<AxiosResponse> {
80-
return protectedAxiosApi.post('/challenges/invite', { challenge_id: challengeId, friend_id: friendId }, { withCredentials: true });
81-
},
69+
return protectedAxiosApi.delete(`/runs/plan/${id}`, { withCredentials: true })
70+
},
71+
getChallenges(): Promise<AxiosResponse> {
72+
return protectedAxiosApi.get('/challenges/new', { withCredentials: true })
73+
},
74+
completeChallenge(challengeId: string, rocketPoints: number): Promise<AxiosResponse> {
75+
return protectedAxiosApi.post(
76+
'/challenges/complete',
77+
{ challenge_id: challengeId, rocket_points: rocketPoints },
78+
{ withCredentials: true }
79+
)
80+
},
81+
getChallengeProgress(): Promise<AxiosResponse> {
82+
return protectedAxiosApi.get('/challenges/progress', { withCredentials: true })
83+
},
84+
getFriends(): Promise<AxiosResponse> {
85+
return protectedAxiosApi.get('/friends', { withCredentials: true })
86+
},
87+
inviteFriendToChallenge(challengeId: string, friendId: string): Promise<AxiosResponse> {
88+
return protectedAxiosApi.post(
89+
'/challenges/invite',
90+
{ challenge_id: challengeId, friend_id: friendId },
91+
{ withCredentials: true }
92+
)
93+
},
8294
getFollowing(id: string): Promise<AxiosResponse> {
83-
return protectedAxiosApi.get(`/following/${id}`, { withCredentials: true });
95+
return protectedAxiosApi.get(`/following/${id}`, { withCredentials: true })
8496
},
8597
getFollowers(id: string): Promise<AxiosResponse> {
86-
return protectedAxiosApi.get(`/followers/${id}`, { withCredentials: true });
87-
},
88-
addFriend(friendName: string): Promise<AxiosResponse> {
89-
return protectedAxiosApi.post('/friends/add', { friend_name: friendName }, { withCredentials: true });
90-
},
91-
deleteFriend(friendName: string): Promise<AxiosResponse> {
92-
return protectedAxiosApi.delete(`/friends/${encodeURIComponent(friendName)}`, { withCredentials: true });
93-
},
94-
getAllUsers(): Promise<AxiosResponse> {
95-
return protectedAxiosApi.get('/users', { withCredentials: true });
96-
},
98+
return protectedAxiosApi.get(`/followers/${id}`, { withCredentials: true })
99+
},
100+
getRankedUsers(): Promise<AxiosResponse> {
101+
return protectedAxiosApi.get('/ranking/users', { withCredentials: true })
102+
},
103+
getRankedFriends(): Promise<AxiosResponse> {
104+
return protectedAxiosApi.get('/ranking/friends', { withCredentials: true })
105+
},
106+
addFriend(friendName: string): Promise<AxiosResponse> {
107+
return protectedAxiosApi.post(
108+
'/friends/add',
109+
{ friend_name: friendName },
110+
{ withCredentials: true }
111+
)
112+
},
113+
deleteFriend(friendName: string): Promise<AxiosResponse> {
114+
return protectedAxiosApi.delete(`/friends/${encodeURIComponent(friendName)}`, {
115+
withCredentials: true
116+
})
117+
},
118+
getAllUsers(): Promise<AxiosResponse> {
119+
return protectedAxiosApi.get('/users', { withCredentials: true })
120+
}
97121
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<script setup lang="ts">
2+
defineProps<{
3+
isFriend: boolean,
4+
username: string
5+
}>()
6+
defineEmits(['add-friend'])
7+
</script>
8+
9+
<template>
10+
<button
11+
v-if="!isFriend"
12+
@click="$emit('add-friend', username)"
13+
class="add-friend-btn"
14+
>
15+
Freund hinzufügen
16+
</button>
17+
<span v-else class="friend-icon">
18+
<img src="/src/assets/icons/user.svg" alt="Friend Icon" />
19+
</span>
20+
</template>
21+
22+
<style scoped>
23+
24+
</style>

0 commit comments

Comments
 (0)