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
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ import (
"bytes"
"time"

"github.com/google/uuid"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/google/uuid"
)

var _ = Describe("Image Store Table Integration", func() {
Expand Down Expand Up @@ -92,4 +92,61 @@ var _ = Describe("Image Store Table Integration", func() {
err := row.Scan(&gotID, &gotName, &gotData)
Expect(err).ToNot(BeNil())
})

It("should save and delete a user image", func() {
imageID := uuid.New()
imgName := "todelete.png"
imgData := []byte{0x89, 0x50, 0x4E, 0x47}

_, err := testDbInstance.Exec(`
INSERT INTO image_store (id, image_name, image_data)
VALUES ($1, $2, $3)
`, imageID, imgName, imgData)
Expect(err).To(BeNil())

// Link image to user in settings
_, err = testDbInstance.Exec(`
UPDATE settings SET image_id = $1 WHERE user_id = $2
`, imageID, userID)
Expect(err).To(BeNil())

// Delete user image
_, err = testDbInstance.Exec(`
UPDATE settings SET image_id = NULL WHERE user_id = $1
`, userID)
Expect(err).To(BeNil())
_, err = testDbInstance.Exec(`
DELETE FROM image_store WHERE id = $1
`, imageID)
Expect(err).To(BeNil())

// Confirm deletion
row := testDbInstance.QueryRow(`
SELECT COUNT(*) FROM image_store WHERE id = $1
`, imageID)
var count int
err = row.Scan(&count)
Expect(err).To(BeNil())
Expect(count).To(Equal(0))
})

It("should return error when getting image for user with no image", func() {
// Remove image_id from settings if present
_, err := testDbInstance.Exec(`
UPDATE settings SET image_id = NULL WHERE user_id = $1
`, userID)
Expect(err).To(BeNil())

row := testDbInstance.QueryRow(`
SELECT i.id, i.image_name, i.image_data
FROM settings s
JOIN image_store i ON s.image_id = i.id
WHERE s.user_id = $1
`, userID)
var gotID uuid.UUID
var gotName string
var gotData []byte
err = row.Scan(&gotID, &gotName, &gotData)
Expect(err).ToNot(BeNil())
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -82,4 +82,114 @@ var _ = Describe("Users Table Integration", func() {
Expect(err).ToNot(BeNil())
Expect(err.Error()).To(ContainSubstring("violates foreign key constraint"))
})

It("should update and retrieve user name", func() {
username := "useruser"
email := "[email protected]"
rocketpoints := 42

_, err := testDbInstance.Exec(`
INSERT INTO users (id, username, email, rocketpoints)
VALUES ($1, $2, $3, $4)
`, userID, username, email, rocketpoints)
Expect(err).To(BeNil())

_, err = testDbInstance.Exec(`
UPDATE users SET username = $1 WHERE id = $2
`, "newname", userID)
Expect(err).To(BeNil())

row := testDbInstance.QueryRow(`
SELECT username FROM users WHERE id = $1
`, userID)
var gotUsername string
err = row.Scan(&gotUsername)
Expect(err).To(BeNil())
Expect(gotUsername).To(Equal("newname"))
})

It("should update and retrieve user email in both users and credentials", func() {
username := "useruser"
email := "[email protected]"
rocketpoints := 42

_, err := testDbInstance.Exec(`
INSERT INTO users (id, username, email, rocketpoints)
VALUES ($1, $2, $3, $4)
`, userID, username, email, rocketpoints)
Expect(err).To(BeNil())

_, err = testDbInstance.Exec(`
UPDATE users SET email = $1 WHERE id = $2
`, "[email protected]", userID)
Expect(err).To(BeNil())
_, err = testDbInstance.Exec(`
UPDATE credentials SET email = $1 WHERE id = $2
`, "[email protected]", userID)
Expect(err).To(BeNil())

row := testDbInstance.QueryRow(`
SELECT email FROM users WHERE id = $1
`, userID)
var gotEmail string
err = row.Scan(&gotEmail)
Expect(err).To(BeNil())
Expect(gotEmail).To(Equal("[email protected]"))

row = testDbInstance.QueryRow(`
SELECT email FROM credentials WHERE id = $1
`, userID)
err = row.Scan(&gotEmail)
Expect(err).To(BeNil())
Expect(gotEmail).To(Equal("[email protected]"))
})

It("should get user ID by username", func() {
username := "useruser"
email := "[email protected]"
rocketpoints := 42

_, err := testDbInstance.Exec(`
INSERT INTO users (id, username, email, rocketpoints)
VALUES ($1, $2, $3, $4)
`, userID, username, email, rocketpoints)
Expect(err).To(BeNil())

row := testDbInstance.QueryRow(`
SELECT id FROM users WHERE username = $1
`, username)
var gotID uuid.UUID
err = row.Scan(&gotID)
Expect(err).To(BeNil())
Expect(gotID).To(Equal(userID))
})

It("should delete a user and its credentials", func() {
username := "useruser"
email := "[email protected]"
rocketpoints := 42

_, err := testDbInstance.Exec(`
INSERT INTO users (id, username, email, rocketpoints)
VALUES ($1, $2, $3, $4)
`, userID, username, email, rocketpoints)
Expect(err).To(BeNil())

_, err = testDbInstance.Exec(`
DELETE FROM users WHERE id = $1
`, userID)
Expect(err).To(BeNil())
_, err = testDbInstance.Exec(`
DELETE FROM credentials WHERE id = $1
`, userID)
Expect(err).To(BeNil())

row := testDbInstance.QueryRow(`
SELECT COUNT(*) FROM users WHERE id = $1
`, userID)
var count int
err = row.Scan(&count)
Expect(err).To(BeNil())
Expect(count).To(Equal(0))
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -95,4 +95,91 @@ var _ = Describe("Settings Handlers API", func() {
_ = json.NewDecoder(resp.Body).Decode(&result)
Expect(result["message"]).To(Equal("Image updated successfully"))
})

It("should delete user image", func() {
// First, upload an image so there is something to delete
imgPath := filepath.Join(os.TempDir(), "testimg_del.png")
os.WriteFile(imgPath, []byte{0x89, 0x50, 0x4E, 0x47}, 0644)
file, _ := os.Open(imgPath)
defer file.Close()

var b bytes.Buffer
w := multipart.NewWriter(&b)
fw, _ := w.CreateFormFile("image", "testimg_del.png")
file.Seek(0, 0)
_, _ = file.WriteTo(fw)
w.Close()

req, _ := http.NewRequest("POST", baseURL+"/protected/settings/image", &b)
req.Header.Set("Authorization", "Bearer "+token)
req.Header.Set("Content-Type", w.FormDataContentType())
resp, err := http.DefaultClient.Do(req)
Expect(err).To(BeNil())
resp.Body.Close()

// Now, delete the image
req, _ = http.NewRequest("DELETE", baseURL+"/protected/settings/image", nil)
req.Header.Set("Authorization", "Bearer "+token)
resp, err = http.DefaultClient.Do(req)
Expect(err).To(BeNil())
defer resp.Body.Close()
Expect(resp.StatusCode).To(Equal(200))
var result map[string]any
_ = json.NewDecoder(resp.Body).Decode(&result)
Expect(result["message"]).To(Equal("Image deleted successfully"))
})

It("should update user info (name and email)", func() {
payload := map[string]any{
"name": "New Name",
"email": "[email protected]",
}
body, _ := json.Marshal(payload)
req, _ := http.NewRequest("POST", baseURL+"/protected/settings/userinfo", bytes.NewReader(body))
req.Header.Set("Authorization", "Bearer "+token)
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)
Expect(err).To(BeNil())
defer resp.Body.Close()
Expect(resp.StatusCode).To(Equal(200))
var result map[string]any
_ = json.NewDecoder(resp.Body).Decode(&result)
Expect(result["message"]).To(Equal("User info updated successfully"))
})

It("should update user password with correct current password", func() {
payload := map[string]any{
"currentPassword": "password123",
"newPassword": "newpass456",
}
body, _ := json.Marshal(payload)
req, _ := http.NewRequest("POST", baseURL+"/protected/settings/userinfo", bytes.NewReader(body))
req.Header.Set("Authorization", "Bearer "+token)
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)
Expect(err).To(BeNil())
defer resp.Body.Close()
Expect(resp.StatusCode).To(Equal(200))
var result map[string]any
_ = json.NewDecoder(resp.Body).Decode(&result)
Expect(result["message"]).To(Equal("User info updated successfully"))
})

It("should reject password update with wrong current password", func() {
payload := map[string]any{
"currentPassword": "wrongpassword",
"newPassword": "newpass456",
}
body, _ := json.Marshal(payload)
req, _ := http.NewRequest("POST", baseURL+"/protected/settings/userinfo", bytes.NewReader(body))
req.Header.Set("Authorization", "Bearer "+token)
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)
Expect(err).To(BeNil())
defer resp.Body.Close()
Expect(resp.StatusCode).To(Equal(401))
var result map[string]any
_ = json.NewDecoder(resp.Body).Decode(&result)
Expect(result["error"]).To(Equal("Current password incorrect"))
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -118,4 +118,26 @@ var _ = Describe("Protected Handlers API", func() {
_ = json.NewDecoder(resp.Body).Decode(&users)
Expect(users).To(BeNil())
})

It("should delete the user account", func() {
delToken := registerAndLogin("[email protected]", "password123", "deleteuser")

req, _ := http.NewRequest("DELETE", baseURL+"/protected/user", nil)
req.Header.Set("Authorization", "Bearer "+delToken)
resp, err := http.DefaultClient.Do(req)
Expect(err).To(BeNil())
defer resp.Body.Close()
Expect(resp.StatusCode).To(Equal(200))
var result map[string]any
_ = json.NewDecoder(resp.Body).Decode(&result)
Expect(result["message"]).To(Equal("User deleted successfully"))

// Try to access a protected endpoint with the same token, should be unauthorized
req2, _ := http.NewRequest("GET", baseURL+"/protected/user", nil)
req2.Header.Set("Authorization", "Bearer "+delToken)
resp2, err := http.DefaultClient.Do(req2)
Expect(err).To(BeNil())
defer resp2.Body.Close()
Expect(resp2.StatusCode).To(Equal(401))
})
})
6 changes: 6 additions & 0 deletions rocket-backend/internal/database/database.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,11 @@ type Service interface {
GetUserIDByName(name string) (uuid.UUID, error)
GetTopUsers(limit int) ([]types.User, error)
GetAllUsers(excludeUserID *uuid.UUID) ([]types.User, error)
DeleteUser(userID uuid.UUID) error
UpdateUserName(userID uuid.UUID, newName string) error
UpdateUserEmail(userID uuid.UUID, newEmail string) error
CheckUserPassword(userID uuid.UUID, currentPassword string) (bool, error)
UpdateUserPassword(userID uuid.UUID, newPassword string) error

// daily_steps
UpdateDailySteps(userID uuid.UUID, steps int) error
Expand All @@ -55,6 +60,7 @@ type Service interface {
UpdateSettingsImage(userId uuid.UUID, imageID uuid.UUID) error
UpdateStepGoal(userId uuid.UUID, stepGoal int) error
UpdateImage(userId uuid.UUID, imageID uuid.UUID) error
DeleteUserImage(userID uuid.UUID) error

// images
SaveImage(filename string, data []byte) (uuid.UUID, error)
Expand Down
36 changes: 36 additions & 0 deletions rocket-backend/internal/database/image_store_table.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,39 @@ func (s *service) GetUserImage(userID uuid.UUID) (*types.UserImage, error) {

return &img, nil
}

func (s *service) DeleteUserImage(userID uuid.UUID) error {
var imageID uuid.UUID
err := s.db.QueryRow(`
SELECT image_id FROM settings WHERE user_id = $1
`, userID).Scan(&imageID)
if err != nil {
if err == sql.ErrNoRows {
logger.Warn("No settings found for user:", userID)
return nil
}
logger.Error("Failed to get image_id from settings", err)
return fmt.Errorf("%w: %v", custom_error.ErrFailedToRetrieveData, err)
}
if imageID == uuid.Nil {
return nil
}

_, err = s.db.Exec(`
UPDATE settings SET image_id = NULL WHERE user_id = $1
`, userID)
if err != nil {
logger.Error("Failed to remove image reference from settings", err)
return fmt.Errorf("%w: %v", custom_error.ErrFailedToDelete, err)
}

_, err = s.db.Exec(`
DELETE FROM image_store WHERE id = $1
`, imageID)
if err != nil {
logger.Error("Failed to delete image", err)
return fmt.Errorf("%w: %v", custom_error.ErrFailedToDelete, err)
}

return nil
}
Loading
Loading