Skip to content

Commit b58fca9

Browse files
authored
Merge pull request #119 from RealTeamRocket/113-settings-page
113 settings page
2 parents 4ffa135 + f6b424d commit b58fca9

26 files changed

Lines changed: 1587 additions & 24 deletions

rocket-backend/integration-tests/internal/database-tests/image_store_table_test.go

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@ import (
44
"bytes"
55
"time"
66

7+
"github.com/google/uuid"
78
. "github.com/onsi/ginkgo/v2"
89
. "github.com/onsi/gomega"
9-
"github.com/google/uuid"
1010
)
1111

1212
var _ = Describe("Image Store Table Integration", func() {
@@ -92,4 +92,61 @@ var _ = Describe("Image Store Table Integration", func() {
9292
err := row.Scan(&gotID, &gotName, &gotData)
9393
Expect(err).ToNot(BeNil())
9494
})
95+
96+
It("should save and delete a user image", func() {
97+
imageID := uuid.New()
98+
imgName := "todelete.png"
99+
imgData := []byte{0x89, 0x50, 0x4E, 0x47}
100+
101+
_, err := testDbInstance.Exec(`
102+
INSERT INTO image_store (id, image_name, image_data)
103+
VALUES ($1, $2, $3)
104+
`, imageID, imgName, imgData)
105+
Expect(err).To(BeNil())
106+
107+
// Link image to user in settings
108+
_, err = testDbInstance.Exec(`
109+
UPDATE settings SET image_id = $1 WHERE user_id = $2
110+
`, imageID, userID)
111+
Expect(err).To(BeNil())
112+
113+
// Delete user image
114+
_, err = testDbInstance.Exec(`
115+
UPDATE settings SET image_id = NULL WHERE user_id = $1
116+
`, userID)
117+
Expect(err).To(BeNil())
118+
_, err = testDbInstance.Exec(`
119+
DELETE FROM image_store WHERE id = $1
120+
`, imageID)
121+
Expect(err).To(BeNil())
122+
123+
// Confirm deletion
124+
row := testDbInstance.QueryRow(`
125+
SELECT COUNT(*) FROM image_store WHERE id = $1
126+
`, imageID)
127+
var count int
128+
err = row.Scan(&count)
129+
Expect(err).To(BeNil())
130+
Expect(count).To(Equal(0))
131+
})
132+
133+
It("should return error when getting image for user with no image", func() {
134+
// Remove image_id from settings if present
135+
_, err := testDbInstance.Exec(`
136+
UPDATE settings SET image_id = NULL WHERE user_id = $1
137+
`, userID)
138+
Expect(err).To(BeNil())
139+
140+
row := testDbInstance.QueryRow(`
141+
SELECT i.id, i.image_name, i.image_data
142+
FROM settings s
143+
JOIN image_store i ON s.image_id = i.id
144+
WHERE s.user_id = $1
145+
`, userID)
146+
var gotID uuid.UUID
147+
var gotName string
148+
var gotData []byte
149+
err = row.Scan(&gotID, &gotName, &gotData)
150+
Expect(err).ToNot(BeNil())
151+
})
95152
})

rocket-backend/integration-tests/internal/database-tests/user_table_test.go

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,4 +82,114 @@ var _ = Describe("Users Table Integration", func() {
8282
Expect(err).ToNot(BeNil())
8383
Expect(err.Error()).To(ContainSubstring("violates foreign key constraint"))
8484
})
85+
86+
It("should update and retrieve user name", func() {
87+
username := "useruser"
88+
email := "useruser@example.com"
89+
rocketpoints := 42
90+
91+
_, err := testDbInstance.Exec(`
92+
INSERT INTO users (id, username, email, rocketpoints)
93+
VALUES ($1, $2, $3, $4)
94+
`, userID, username, email, rocketpoints)
95+
Expect(err).To(BeNil())
96+
97+
_, err = testDbInstance.Exec(`
98+
UPDATE users SET username = $1 WHERE id = $2
99+
`, "newname", userID)
100+
Expect(err).To(BeNil())
101+
102+
row := testDbInstance.QueryRow(`
103+
SELECT username FROM users WHERE id = $1
104+
`, userID)
105+
var gotUsername string
106+
err = row.Scan(&gotUsername)
107+
Expect(err).To(BeNil())
108+
Expect(gotUsername).To(Equal("newname"))
109+
})
110+
111+
It("should update and retrieve user email in both users and credentials", func() {
112+
username := "useruser"
113+
email := "useruser@example.com"
114+
rocketpoints := 42
115+
116+
_, err := testDbInstance.Exec(`
117+
INSERT INTO users (id, username, email, rocketpoints)
118+
VALUES ($1, $2, $3, $4)
119+
`, userID, username, email, rocketpoints)
120+
Expect(err).To(BeNil())
121+
122+
_, err = testDbInstance.Exec(`
123+
UPDATE users SET email = $1 WHERE id = $2
124+
`, "newemail@example.com", userID)
125+
Expect(err).To(BeNil())
126+
_, err = testDbInstance.Exec(`
127+
UPDATE credentials SET email = $1 WHERE id = $2
128+
`, "newemail@example.com", userID)
129+
Expect(err).To(BeNil())
130+
131+
row := testDbInstance.QueryRow(`
132+
SELECT email FROM users WHERE id = $1
133+
`, userID)
134+
var gotEmail string
135+
err = row.Scan(&gotEmail)
136+
Expect(err).To(BeNil())
137+
Expect(gotEmail).To(Equal("newemail@example.com"))
138+
139+
row = testDbInstance.QueryRow(`
140+
SELECT email FROM credentials WHERE id = $1
141+
`, userID)
142+
err = row.Scan(&gotEmail)
143+
Expect(err).To(BeNil())
144+
Expect(gotEmail).To(Equal("newemail@example.com"))
145+
})
146+
147+
It("should get user ID by username", func() {
148+
username := "useruser"
149+
email := "useruser@example.com"
150+
rocketpoints := 42
151+
152+
_, err := testDbInstance.Exec(`
153+
INSERT INTO users (id, username, email, rocketpoints)
154+
VALUES ($1, $2, $3, $4)
155+
`, userID, username, email, rocketpoints)
156+
Expect(err).To(BeNil())
157+
158+
row := testDbInstance.QueryRow(`
159+
SELECT id FROM users WHERE username = $1
160+
`, username)
161+
var gotID uuid.UUID
162+
err = row.Scan(&gotID)
163+
Expect(err).To(BeNil())
164+
Expect(gotID).To(Equal(userID))
165+
})
166+
167+
It("should delete a user and its credentials", func() {
168+
username := "useruser"
169+
email := "useruser@example.com"
170+
rocketpoints := 42
171+
172+
_, err := testDbInstance.Exec(`
173+
INSERT INTO users (id, username, email, rocketpoints)
174+
VALUES ($1, $2, $3, $4)
175+
`, userID, username, email, rocketpoints)
176+
Expect(err).To(BeNil())
177+
178+
_, err = testDbInstance.Exec(`
179+
DELETE FROM users WHERE id = $1
180+
`, userID)
181+
Expect(err).To(BeNil())
182+
_, err = testDbInstance.Exec(`
183+
DELETE FROM credentials WHERE id = $1
184+
`, userID)
185+
Expect(err).To(BeNil())
186+
187+
row := testDbInstance.QueryRow(`
188+
SELECT COUNT(*) FROM users WHERE id = $1
189+
`, userID)
190+
var count int
191+
err = row.Scan(&count)
192+
Expect(err).To(BeNil())
193+
Expect(count).To(Equal(0))
194+
})
85195
})

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

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,4 +95,91 @@ var _ = Describe("Settings Handlers API", func() {
9595
_ = json.NewDecoder(resp.Body).Decode(&result)
9696
Expect(result["message"]).To(Equal("Image updated successfully"))
9797
})
98+
99+
It("should delete user image", func() {
100+
// First, upload an image so there is something to delete
101+
imgPath := filepath.Join(os.TempDir(), "testimg_del.png")
102+
os.WriteFile(imgPath, []byte{0x89, 0x50, 0x4E, 0x47}, 0644)
103+
file, _ := os.Open(imgPath)
104+
defer file.Close()
105+
106+
var b bytes.Buffer
107+
w := multipart.NewWriter(&b)
108+
fw, _ := w.CreateFormFile("image", "testimg_del.png")
109+
file.Seek(0, 0)
110+
_, _ = file.WriteTo(fw)
111+
w.Close()
112+
113+
req, _ := http.NewRequest("POST", baseURL+"/protected/settings/image", &b)
114+
req.Header.Set("Authorization", "Bearer "+token)
115+
req.Header.Set("Content-Type", w.FormDataContentType())
116+
resp, err := http.DefaultClient.Do(req)
117+
Expect(err).To(BeNil())
118+
resp.Body.Close()
119+
120+
// Now, delete the image
121+
req, _ = http.NewRequest("DELETE", baseURL+"/protected/settings/image", nil)
122+
req.Header.Set("Authorization", "Bearer "+token)
123+
resp, err = http.DefaultClient.Do(req)
124+
Expect(err).To(BeNil())
125+
defer resp.Body.Close()
126+
Expect(resp.StatusCode).To(Equal(200))
127+
var result map[string]any
128+
_ = json.NewDecoder(resp.Body).Decode(&result)
129+
Expect(result["message"]).To(Equal("Image deleted successfully"))
130+
})
131+
132+
It("should update user info (name and email)", func() {
133+
payload := map[string]any{
134+
"name": "New Name",
135+
"email": "newemail@example.com",
136+
}
137+
body, _ := json.Marshal(payload)
138+
req, _ := http.NewRequest("POST", baseURL+"/protected/settings/userinfo", bytes.NewReader(body))
139+
req.Header.Set("Authorization", "Bearer "+token)
140+
req.Header.Set("Content-Type", "application/json")
141+
resp, err := http.DefaultClient.Do(req)
142+
Expect(err).To(BeNil())
143+
defer resp.Body.Close()
144+
Expect(resp.StatusCode).To(Equal(200))
145+
var result map[string]any
146+
_ = json.NewDecoder(resp.Body).Decode(&result)
147+
Expect(result["message"]).To(Equal("User info updated successfully"))
148+
})
149+
150+
It("should update user password with correct current password", func() {
151+
payload := map[string]any{
152+
"currentPassword": "password123",
153+
"newPassword": "newpass456",
154+
}
155+
body, _ := json.Marshal(payload)
156+
req, _ := http.NewRequest("POST", baseURL+"/protected/settings/userinfo", bytes.NewReader(body))
157+
req.Header.Set("Authorization", "Bearer "+token)
158+
req.Header.Set("Content-Type", "application/json")
159+
resp, err := http.DefaultClient.Do(req)
160+
Expect(err).To(BeNil())
161+
defer resp.Body.Close()
162+
Expect(resp.StatusCode).To(Equal(200))
163+
var result map[string]any
164+
_ = json.NewDecoder(resp.Body).Decode(&result)
165+
Expect(result["message"]).To(Equal("User info updated successfully"))
166+
})
167+
168+
It("should reject password update with wrong current password", func() {
169+
payload := map[string]any{
170+
"currentPassword": "wrongpassword",
171+
"newPassword": "newpass456",
172+
}
173+
body, _ := json.Marshal(payload)
174+
req, _ := http.NewRequest("POST", baseURL+"/protected/settings/userinfo", bytes.NewReader(body))
175+
req.Header.Set("Authorization", "Bearer "+token)
176+
req.Header.Set("Content-Type", "application/json")
177+
resp, err := http.DefaultClient.Do(req)
178+
Expect(err).To(BeNil())
179+
defer resp.Body.Close()
180+
Expect(resp.StatusCode).To(Equal(401))
181+
var result map[string]any
182+
_ = json.NewDecoder(resp.Body).Decode(&result)
183+
Expect(result["error"]).To(Equal("Current password incorrect"))
184+
})
98185
})

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

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,4 +118,26 @@ var _ = Describe("Protected Handlers API", func() {
118118
_ = json.NewDecoder(resp.Body).Decode(&users)
119119
Expect(users).To(BeNil())
120120
})
121+
122+
It("should delete the user account", func() {
123+
delToken := registerAndLogin("deleteuser@example.com", "password123", "deleteuser")
124+
125+
req, _ := http.NewRequest("DELETE", baseURL+"/protected/user", nil)
126+
req.Header.Set("Authorization", "Bearer "+delToken)
127+
resp, err := http.DefaultClient.Do(req)
128+
Expect(err).To(BeNil())
129+
defer resp.Body.Close()
130+
Expect(resp.StatusCode).To(Equal(200))
131+
var result map[string]any
132+
_ = json.NewDecoder(resp.Body).Decode(&result)
133+
Expect(result["message"]).To(Equal("User deleted successfully"))
134+
135+
// Try to access a protected endpoint with the same token, should be unauthorized
136+
req2, _ := http.NewRequest("GET", baseURL+"/protected/user", nil)
137+
req2.Header.Set("Authorization", "Bearer "+delToken)
138+
resp2, err := http.DefaultClient.Do(req2)
139+
Expect(err).To(BeNil())
140+
defer resp2.Body.Close()
141+
Expect(resp2.StatusCode).To(Equal(401))
142+
})
121143
})

rocket-backend/internal/database/database.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,11 @@ type Service interface {
4242
GetUserIDByName(name string) (uuid.UUID, error)
4343
GetTopUsers(limit int) ([]types.User, error)
4444
GetAllUsers(excludeUserID *uuid.UUID) ([]types.User, error)
45+
DeleteUser(userID uuid.UUID) error
46+
UpdateUserName(userID uuid.UUID, newName string) error
47+
UpdateUserEmail(userID uuid.UUID, newEmail string) error
48+
CheckUserPassword(userID uuid.UUID, currentPassword string) (bool, error)
49+
UpdateUserPassword(userID uuid.UUID, newPassword string) error
4550

4651
// daily_steps
4752
UpdateDailySteps(userID uuid.UUID, steps int) error
@@ -55,6 +60,7 @@ type Service interface {
5560
UpdateSettingsImage(userId uuid.UUID, imageID uuid.UUID) error
5661
UpdateStepGoal(userId uuid.UUID, stepGoal int) error
5762
UpdateImage(userId uuid.UUID, imageID uuid.UUID) error
63+
DeleteUserImage(userID uuid.UUID) error
5864

5965
// images
6066
SaveImage(filename string, data []byte) (uuid.UUID, error)

rocket-backend/internal/database/image_store_table.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,3 +56,39 @@ func (s *service) GetUserImage(userID uuid.UUID) (*types.UserImage, error) {
5656

5757
return &img, nil
5858
}
59+
60+
func (s *service) DeleteUserImage(userID uuid.UUID) error {
61+
var imageID uuid.UUID
62+
err := s.db.QueryRow(`
63+
SELECT image_id FROM settings WHERE user_id = $1
64+
`, userID).Scan(&imageID)
65+
if err != nil {
66+
if err == sql.ErrNoRows {
67+
logger.Warn("No settings found for user:", userID)
68+
return nil
69+
}
70+
logger.Error("Failed to get image_id from settings", err)
71+
return fmt.Errorf("%w: %v", custom_error.ErrFailedToRetrieveData, err)
72+
}
73+
if imageID == uuid.Nil {
74+
return nil
75+
}
76+
77+
_, err = s.db.Exec(`
78+
UPDATE settings SET image_id = NULL WHERE user_id = $1
79+
`, userID)
80+
if err != nil {
81+
logger.Error("Failed to remove image reference from settings", err)
82+
return fmt.Errorf("%w: %v", custom_error.ErrFailedToDelete, err)
83+
}
84+
85+
_, err = s.db.Exec(`
86+
DELETE FROM image_store WHERE id = $1
87+
`, imageID)
88+
if err != nil {
89+
logger.Error("Failed to delete image", err)
90+
return fmt.Errorf("%w: %v", custom_error.ErrFailedToDelete, err)
91+
}
92+
93+
return nil
94+
}

0 commit comments

Comments
 (0)