diff --git a/api/src/controller/controller.go b/api/src/controller/controller.go index c8b1873..28697e0 100644 --- a/api/src/controller/controller.go +++ b/api/src/controller/controller.go @@ -342,6 +342,33 @@ func (pg *PgController) Serve() *gin.Engine { c.JSON(http.StatusOK, giftAddedCollection) }) + r.POST("/removeCustomerGiftCollection/:collectionName/:customerId", func(c *gin.Context) { + var input model.Gift + + collectionName := c.Param("collectionName") + customerId := c.Param("customerId") + + intId, err := strconv.Atoi(customerId) + if err != nil { + panic(err) + } + + if err := c.BindJSON(&input); err != nil { + c.JSON(http.StatusBadRequest, "Failed to unmarshal gift") + fmt.Print(err) + return + } + + giftRemovedCollection, err := pg.DeleteGiftFromCustomerCollection(input, collectionName, int64(intId)) + + if err != nil { + c.JSON(http.StatusBadRequest, input) + panic(err) + } + + c.JSON(http.StatusOK, giftRemovedCollection) + }) + // Delete Gift to Gift Collection r.DELETE("/removeGiftFromGiftCollection/:giftID/:giftCollectionID", func(c *gin.Context) { diff --git a/api/src/main.go b/api/src/main.go index 898dbcc..917aa76 100644 --- a/api/src/main.go +++ b/api/src/main.go @@ -4,12 +4,13 @@ import ( "CaitsCurates/backend/src/controller" "CaitsCurates/backend/src/model" "fmt" - "github.com/lib/pq" - "gorm.io/gorm/logger" "log" "os" "time" + "github.com/lib/pq" + "gorm.io/gorm/logger" + "gorm.io/driver/postgres" "gorm.io/gorm" ) @@ -183,9 +184,15 @@ func main() { CollectionName: "Decor Enhancers", Gifts: []*model.Gift{&decorativeGift3, &decorativeGift4, &decorativeGift2, &decorativeGift1}, } + giftCollectionFavorites := model.GiftCollection{ + CustomerID: &customer1.UserID, + CollectionName: "Favorites", + Gifts: []*model.Gift{}, + } err = db.Create(&giftCollectionToy).Error err = db.Create(&giftCollectionFall).Error err = db.Create(&giftCollectionDecor).Error + err = db.Create(&giftCollectionFavorites).Error err = db.Create(&randomGift1).Error err = db.Create(&randomGift2).Error diff --git a/api/src/model/model.go b/api/src/model/model.go index b591926..e138730 100644 --- a/api/src/model/model.go +++ b/api/src/model/model.go @@ -32,6 +32,7 @@ type Model interface { AddGiftToGiftCollection(Gift, int64) (GiftCollection, error) AddGiftToCustomerCollection(Gift, string, int64) (GiftCollection, error) DeleteGiftFromGiftCollection(int64, int64) (GiftCollection, error) + DeleteGiftFromCustomerCollection(Gift, string, int64) (GiftCollection, error) } @@ -242,6 +243,17 @@ func (m *PgModel) AddGiftToCustomerCollection(gift Gift, collectionName string, return giftAddedCollection, nil } +func (m *PgModel) DeleteGiftFromCustomerCollection(gift Gift, collectionName string, customerId int64) (GiftCollection, error) { + + giftDeletedCollection, err := DeleteGiftFromCustomerCollectionFromDB(m.Conn, gift, collectionName, customerId); + + if err != nil { + return GiftCollection{}, err + } + + return giftDeletedCollection, nil +} + func (m *PgModel) DeleteGiftFromGiftCollection(giftID int64, giftCollectionID int64) (GiftCollection, error) { giftDeletedCollection, err := DeleteGiftFromCollectionFromDB(m.Conn, giftID, giftCollectionID) diff --git a/api/src/model/transactions.go b/api/src/model/transactions.go index 32347f9..6cb2cba 100644 --- a/api/src/model/transactions.go +++ b/api/src/model/transactions.go @@ -263,7 +263,7 @@ func GetAllCollectionsFromDB(db *gorm.DB) ([]GiftCollection, error) { // GetAllCustomerCollectionsFromDB fetches all GiftCollections that associated with the customer ID or none func GetAllCustomerCollectionsFromDB(db *gorm.DB, id int64) ([]GiftCollection, error) { var collections []GiftCollection - if err := db.Where("customer_id = ? OR customer_id IS NULL", id).Preload("Gifts").Find(&collections).Error; err != nil { + if err := db.Preload("Gifts").Preload("Gifts.GiftCollections").Where("customer_id = ? OR customer_id IS NULL", id).Find(&collections).Error; err != nil { return nil, err } return collections, nil @@ -299,6 +299,25 @@ func AddGiftToCustomerCollectionFromDB(db *gorm.DB, gift Gift, collectionName st return collection, nil } +func DeleteGiftFromCustomerCollectionFromDB(db *gorm.DB, gift Gift, collectionName string, customerId int64) (GiftCollection, error) { + var collection GiftCollection + if err := db.Preload("Gifts").Where("collection_name = ? AND customer_id = ?", collectionName, customerId).First(&collection).Error; err != nil { + return GiftCollection{}, err + } + + var giftRemovedCollection []*Gift + for _, collectionGift := range collection.Gifts { + if collectionGift.Name != gift.Name { + giftRemovedCollection = append(giftRemovedCollection, collectionGift) + } + } + if err := db.Model(&collection).Association("Gifts").Replace(giftRemovedCollection); err != nil { + return GiftCollection{}, err + } + + return collection, nil +} + func DeleteGiftFromCollectionFromDB(db *gorm.DB, giftID int64, giftCollectionID int64) (GiftCollection, error) { var collection GiftCollection if err := db.Preload("Gifts").First(&collection, giftCollectionID).Error; err != nil { diff --git a/api/tests/api_test.go b/api/tests/api_test.go index a79a9c8..3e7b673 100644 --- a/api/tests/api_test.go +++ b/api/tests/api_test.go @@ -1148,7 +1148,7 @@ func TestGetAllCustomerGiftCollection(t *testing.T) { // Test code w := httptest.NewRecorder() - + // Create a Customer user := model.User{} err = tx.Create(&user).Error @@ -1294,7 +1294,7 @@ func TestAddGiftToCustomerGiftCollection(t *testing.T) { req, err := http.NewRequest( "POST", - fmt.Sprintf("/addCustomerGiftCollection/%s/%d", retrievedCollection.CollectionName, retrievedCustomer.ID), + fmt.Sprintf("/addCustomerGiftCollection/%s/%d", retrievedCollection.CollectionName, retrievedCustomer.ID), bytes.NewBuffer(giftJSON), ) router.ServeHTTP(w, req) @@ -1507,7 +1507,7 @@ func TestSearchGift(t *testing.T) { assert.GreaterOrEqual(t, len(retrievedOneCategoryGift), 1) - // Test Empty + // Test Empty req9, err := http.NewRequest("GET", fmt.Sprintf("/search/%d", collection.ID), nil) if err != nil { t.Fatalf("Error creating request: %v", err) @@ -1522,3 +1522,95 @@ func TestSearchGift(t *testing.T) { assert.GreaterOrEqual(t, len(retrievedAllGifts), 3) } + + +func TestDeleteGiftFromCustomerGiftCollection(t *testing.T) { + // Database setup + dsn := "user=testuser password=testpwd host=localhost port=5433 dbname=testdb sslmode=disable" + if dbURL, exists := os.LookupEnv("TEST_DATABASE_URL"); exists { + dsn = dbURL + } + db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{}) + if err != nil { + t.Fatalf("Unable to connect to database: %v", err) + } + // Put auto migrations here + err = db.AutoMigrate(&model.GiftCollection{}, &model.Gift{}, &model.Customer{}, &model.User{}) + if err != nil { + panic("failed to migrate test database schema") + } + // Wrap the DB connection in a transaction + tx := db.Begin() + defer tx.Rollback() + + // Create Model and Controller + m := &model.PgModel{Conn: tx} + c := &c.PgController{Model: m} + router := c.Serve() + + // Test code + w := httptest.NewRecorder() + + // Create a Customer + user := model.User{} + err = tx.Create(&user).Error + assert.NoError(t, err) + var retrievedUser model.User + err = tx.First(&retrievedUser).Error + assert.NoError(t, err) + customer := model.Customer{ + User: retrievedUser, + } + err = tx.Create(&customer).Error + assert.NoError(t, err) + var retrievedCustomer model.Customer + err = tx.First(&retrievedCustomer).Error + assert.NoError(t, err) + + // Create gifts + giftToRemove := model.Gift{ + Name: "gift to remove", + Price: 25, + } + giftToStay := model.Gift{ + Name: "gift to stay", + Price: 25, + } + giftJSON, err := json.Marshal(giftToRemove) + if err != nil { + t.Fatalf("Error marshaling JSON: %v", err) + } + assert.NoError(t, err) + + // Create a collection + collection := model.GiftCollection{ + CustomerID: &retrievedCustomer.ID, + CollectionName: "test name", + Gifts: []*model.Gift{&giftToRemove, &giftToStay}, + } + + err = tx.Create(&collection).Error + assert.NoError(t, err) + var retrievedCollection model.GiftCollection + err = tx.Preload("Gifts").First(&retrievedCollection).Error; + assert.NoError(t, err) + + req, err := http.NewRequest( + "POST", + fmt.Sprintf("/removeCustomerGiftCollection/%s/%d", retrievedCollection.CollectionName, retrievedCustomer.ID), + bytes.NewBuffer(giftJSON), + ) + router.ServeHTTP(w, req) + assert.Equal(t, 200, w.Code) + + var collectionResponse model.GiftCollection + if e := json.Unmarshal(w.Body.Bytes(), &collectionResponse); e != nil { + t.Fatalf("Error unmarshaling JSON: %v", e) + } + + assert.Equal(t, &retrievedCustomer.ID, collectionResponse.CustomerID) + assert.Equal(t, collection.CollectionName, collectionResponse.CollectionName) + assert.Equal(t, 1, len(collectionResponse.Gifts)) + assert.Equal(t, giftToStay.Name, collectionResponse.Gifts[0].Name) + assert.Equal(t, giftToStay.Price, collectionResponse.Gifts[0].Price) +} \ No newline at end of file diff --git a/client/src/components/CollectionForm.tsx b/client/src/components/CollectionForm.tsx index 04af297..ea4e6a0 100644 --- a/client/src/components/CollectionForm.tsx +++ b/client/src/components/CollectionForm.tsx @@ -41,7 +41,7 @@ function CollectionForm({ collection, allGifts, onSave, onClose }: EditFormProps CollectionName: editedName, Gifts: editedGifts, Customer: collection?.Customer, - CustomerId: collection?.CustomerId + CustomerID: collection?.CustomerID }; try { diff --git a/client/src/components/UpdatedGiftItem.tsx b/client/src/components/UpdatedGiftItem.tsx index cee2e8b..e587514 100644 --- a/client/src/components/UpdatedGiftItem.tsx +++ b/client/src/components/UpdatedGiftItem.tsx @@ -1,19 +1,23 @@ +import { Gift } from "../types"; + type GiftItemProps = { - name: string; - price: number; + gift: Gift; + isSaved: boolean; + onFavoriteClick: (gift: Gift, isSaved: boolean) => void; }; -function UpdatedGiftItem({ name, price }: GiftItemProps) { +function UpdatedGiftItem({ gift, isSaved, onFavoriteClick }: GiftItemProps) { return (
onFavoriteClick(gift, isSaved)} >
-

{name}

-

${price}

+

{gift.Name}

+

${gift.Price}

); diff --git a/client/src/pages/HomePage.tsx b/client/src/pages/HomePage.tsx index f4ef4a8..d51a87c 100644 --- a/client/src/pages/HomePage.tsx +++ b/client/src/pages/HomePage.tsx @@ -5,7 +5,7 @@ import GiftSortNavBar from "../components/GiftSortNavBar"; import UpdatedGiftItem from "../components/UpdatedGiftItem"; import axios from "axios"; import {useEffect, useState} from "react"; -import {GiftCollection} from "../types.tsx"; +import {Gift, GiftCollection} from "../types.tsx"; const HomePage = () => { @@ -74,7 +74,7 @@ const HomePage = () => { const exampleGiftCollection = { ID: 1, - CustomerId: 1, + CustomerID: 1, Customer: exampleCustomer, CollectionName: 'Default', Gifts: exampleGifts, @@ -88,15 +88,32 @@ const HomePage = () => { getCollection(); }, []); - const getCollection = async ()=> { + const getCollection = async (): Promise => { try { const response = await axios.get(`/api/collections/${customerID}`); setCollections(response.data); + return response.data; } catch (error) { console.error('An error occurred while fetching the collection:', error); } }; + const handleFavoriteClick = async (gift: Gift, isSaved: boolean) => { + const baseUrl = isSaved ? "/api/removeCustomerGiftCollection" : "/api/addCustomerGiftCollection" + try { + await axios.post(`${baseUrl}/Favorites/${customerID}`, gift) + // refetch customer gift collections + const updatedCollection = await getCollection(); + + if (updatedCollection) { + // on success set state for currently displayed collection + const currentCollection = updatedCollection.find((collection) => collection.ID === displayCollection.ID) ?? displayCollection; + setDisplayCollection(currentCollection); + } + } catch (error) { + console.error('An error occured while favoriting a gift:', error) + } + } return (
@@ -113,7 +130,7 @@ const HomePage = () => {
{collections.map((collection, index) => (
setDisplayCollection(collection)}>
@@ -126,11 +143,14 @@ const HomePage = () => {
- {displayCollection.Gifts.map((gift, index) => ( + {displayCollection.Gifts.map((gift, index) => { + const isSaved = gift.GiftCollections === null ? false : gift.GiftCollections.some((collection) => collection.CollectionName === "Favorites" && collection.CustomerID === customerID) + return (
- +
- ))} + ) + })}
diff --git a/client/src/types.tsx b/client/src/types.tsx index c647982..c2b4cc5 100644 --- a/client/src/types.tsx +++ b/client/src/types.tsx @@ -12,7 +12,7 @@ export interface Gift { export interface GiftRequest { ID: number; - CustomerId: number; + CustomerID: number; GiftResponseId: number | null; RecipientName: string; RecipientAge: number; @@ -26,7 +26,7 @@ export interface GiftRequest { export interface GiftCollection { ID: number; - CustomerId: number | null; + CustomerID: number | null; Customer: Customer; CollectionName: string; Gifts: Gift[];