diff --git a/api/go.mod b/api/go.mod index 2980558..a8cdb08 100644 --- a/api/go.mod +++ b/api/go.mod @@ -38,8 +38,9 @@ require ( github.com/ugorji/go/codec v1.2.11 // indirect golang.org/x/arch v0.3.0 // indirect golang.org/x/crypto v0.9.0 // indirect + golang.org/x/exp v0.0.0-20231006140011-7918f672742d golang.org/x/net v0.10.0 // indirect - golang.org/x/sys v0.8.0 // indirect + golang.org/x/sys v0.13.0 // indirect golang.org/x/text v0.9.0 // indirect google.golang.org/protobuf v1.30.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/api/go.sum b/api/go.sum index 5b639a3..31d676b 100644 --- a/api/go.sum +++ b/api/go.sum @@ -105,6 +105,8 @@ golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g= golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= +golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI= +golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= @@ -116,6 +118,7 @@ golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= diff --git a/api/src/model/types.go b/api/src/model/types.go index c203c05..29c69b6 100644 --- a/api/src/model/types.go +++ b/api/src/model/types.go @@ -1,9 +1,13 @@ package model import ( + "errors" + "regexp" + "time" + "github.com/lib/pq" + "golang.org/x/exp/slices" "gorm.io/gorm" - "time" ) type Gift struct { @@ -13,6 +17,8 @@ type Gift struct { Link string Description string Demographic string + Category pq.StringArray `gorm:"type:text[]"` + Occasion string GiftCollections []*GiftCollection `gorm:"many2many:gift_request_gifts;"` } @@ -66,3 +72,226 @@ type Admin struct { UserID uint User User } + +func (gift *Gift) BeforeSave(tx *gorm.DB) (err error) { + if len(gift.Name) == 0 { + err = errors.New("gift name cannot be empty") + return err + } + + if gift.Price < 0 { + err = errors.New("gift must have a price") + return err + } + + // if a gift has no link + if len(gift.Link) == 0 { + err = errors.New("gift must have a link") + return err + } + + Demographics := []string{ + "For her", + "For him", + "For kids", + "For mom", + "For dad", + "For women", + "For men", + } + + for _, demographic := range Demographics { + if !slices.Contains(Demographics, demographic) { + err = errors.New("gift must have a valid demographic") + return err + } + } + + Occasions := []string{ + "Birthday", + "Bridal", + "Get well soon", + "New baby", + "Thinking of you", + "Thank you", + } + + for _, occasion := range Occasions { + if !slices.Contains(Occasions, occasion) { + err = errors.New("gift must have a valid occasion") + return err + } + } + + Categories := []string{ + "Best selling", + "Fun", + "Gadgets", + "Home", + "Jewelry", + "Kitchen & bar", + "Warm and cozy", + } + + for _, category := range Categories { + if !slices.Contains(Categories, category) { + err = errors.New("gift must have a valid category") + return err + } + } + + // if a gift has no description + if len(gift.Description) == 0 { + err = errors.New("gift must have a description") + return err + } + + return +} + +func (gc *GiftCollection) BeforeSave(tx *gorm.DB) (err error) { + // if collection name is not set + if len(gc.CollectionName) == 0 { + err = errors.New("giftCollection must have a name") + return err + } + + // if customer is not found + if gc.Customer == nil { + err = errors.New("giftCollection must have a customer") + return err + } + + return +} + +func (user *User) BeforeSave(tx *gorm.DB) (err error) { + if len(user.FirstName) == 0 { + err = errors.New("user must have a first name") + return err + } + + if len(user.LastName) == 0 { + err = errors.New("user must have a last name") + return err + } + + if len(user.Password) == 0 { + err = errors.New("Please enter a password") + return err + } + + // use regex to validate email + pattern := `^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$` + regex := regexp.MustCompile(pattern) + + // Use the MatchString function to check if the text matches the pattern + if regex.MatchString(user.Email) == false { + err = errors.New("user email must be a valid email") + return err + } + + return +} + +func (customer *Customer) BeforeSave(tx *gorm.DB) (err error) { + // if giftRequests is not empty or not populated + if customer.GiftRequests == nil { + err = errors.New("customer must have a giftRequests") + return err + } + + return +} + +func (giftRequest *GiftRequest) BeforeSave(tx *gorm.DB) (err error) { + // if recipient name is not set + if len(giftRequest.RecipientName) == 0 { + err = errors.New("giftRequest must have a recipient name") + return err + } + + // if recipient age is not set + if giftRequest.RecipientAge < 1 || giftRequest.RecipientAge > 150 { + err = errors.New("giftRequest must have a valid recipient age") + return err + } + + GiftOccasions := []string{ + "Birthday", + "Bridal", + "Get well soon", + "New baby", + "Thinking of you", + "Thank you", + } + + // if occasion is not in GiftOccasions + if giftRequest.Occasion != nil { + for _, occasion := range giftRequest.Occasion { + if !slices.Contains(GiftOccasions, occasion) { + err = errors.New("giftRequest must have a valid occasion") + return err + } + } + } else { + err = errors.New("giftRequest must have an occasion") + return err + } + + Interests := []string{ + "Best selling", + "Fun", + "Gadgets", + "Home", + "Jewelry", + "Kitchen & bar", + "Warm and cozy", + } + + // if interests is not in Interests + if giftRequest.RecipientInterests != nil { + for _, interest := range giftRequest.RecipientInterests { + if !slices.Contains(Interests, interest) { + err = errors.New("giftRequest must have a valid interest") + return err + } + } + } else { + err = errors.New("giftRequest must have an interest") + return err + } + + // if either budget is below negative + if giftRequest.BudgetMax < 0 || giftRequest.BudgetMin < 0 { + err = errors.New("giftRequest budget cannot be negative") + return err + } + + // if max budget is less than min budget + if giftRequest.BudgetMax <= giftRequest.BudgetMin { + err = errors.New("giftRequest max budget must be greater than min budget") + return err + } + + // if date needed is not set + if giftRequest.DateNeeded.IsZero() { + err = errors.New("giftRequest must have a date needed") + return err + } else if giftRequest.DateNeeded.Before(time.Now()) { + // if date needed is in the past + err = errors.New("giftRequest date needed must be in the future") + return err + } + + return +} + +func (giftResponse *GiftResponse) BeforeSave(tx *gorm.DB) (err error) { + if giftResponse.CustomMessage == "" { + err = errors.New("giftResponse must have a custom message") + return err + } + + return +} diff --git a/api/tests/db_test.go b/api/tests/db_test.go index 5719ba9..f6913d4 100644 --- a/api/tests/db_test.go +++ b/api/tests/db_test.go @@ -2,11 +2,12 @@ package tests import ( "CaitsCurates/backend/src/model" - "github.com/stretchr/testify/assert" "os" "testing" "time" + "github.com/stretchr/testify/assert" + "gorm.io/driver/postgres" "gorm.io/gorm" ) @@ -274,6 +275,7 @@ func TestGiftResponseModel(t *testing.T) { CollectionName: "Cool Toys", Gifts: []*model.Gift{&gift1, &gift2}, } + // Save the gifts tx.Save(&gift1) tx.Save(&gift2) diff --git a/client/src/App.tsx b/client/src/App.tsx index b8e23cd..3c07d8e 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -1,19 +1,21 @@ import { - BrowserRouter as Router, - Route, - Routes, + BrowserRouter as Router, + Route, + Routes, } from 'react-router-dom'; +import GiftManagementPage from './pages/GiftManagementPage'; import HomePage from './pages/HomePage'; function App() { - return ( - - - } /> - - - ); + return ( + + + } /> + } /> + + + ); } export default App; \ No newline at end of file diff --git a/client/src/components/GiftItem.tsx b/client/src/components/GiftItem.tsx new file mode 100644 index 0000000..360f6fb --- /dev/null +++ b/client/src/components/GiftItem.tsx @@ -0,0 +1,37 @@ +import React from 'react' + +interface GiftProps { + name: string + description: string + price: number + link: string + demographic: string + giftCollections: string +} + +const GiftItem = (props: GiftProps) => { + return ( +
+

{props.name}

+ +
+ + {/** some container that holds the prics and link */} +
+

Price: ${props.price}

+ Buy Now +
+

Demographic: {props.demographic}

+

Description: {props.description}

+

Collections: {props.giftCollections}

+
+ +
+ + +
+
+ ) +} + +export default GiftItem \ No newline at end of file diff --git a/client/src/components/MiniTab.tsx b/client/src/components/MiniTab.tsx new file mode 100644 index 0000000..3a98e36 --- /dev/null +++ b/client/src/components/MiniTab.tsx @@ -0,0 +1,9 @@ +import React from 'react' + +const MiniTab = () => { + return ( +
MiniTab
+ ) +} + +export default MiniTab \ No newline at end of file diff --git a/client/src/components/NavBar.tsx b/client/src/components/NavBar.tsx new file mode 100644 index 0000000..1d98561 --- /dev/null +++ b/client/src/components/NavBar.tsx @@ -0,0 +1,20 @@ +import React from 'react' + +const NavBar = () => { + return ( +
+

Caits Curates

+ +
+ + +
+
+ ) +} + +export default NavBar \ No newline at end of file diff --git a/client/src/mock_db/gifts.json b/client/src/mock_db/gifts.json new file mode 100644 index 0000000..904e37e --- /dev/null +++ b/client/src/mock_db/gifts.json @@ -0,0 +1,26 @@ +[ + { + name: 'Gift 1', + description: 'Gift 1 description', + price: 1, + link: 'Gift 1 link', + demographic: 'Gift 1 demographic', + giftCollections: 'Gift 1 giftCollections' + }, + { + name: 'Gift 2', + description: 'Gift 2 description', + price: 2, + link: 'Gift 2 link', + demographic: 'Gift 2 demographic', + giftCollections: 'Gift 2 giftCollections' + }, + { + name: 'Gift 3', + description: 'Gift 3 description', + price: 3, + link: 'Gift 3 link', + demographic: 'Gift 3 demographic', + giftCollections: 'Gift 3 giftCollections' + } +] \ No newline at end of file diff --git a/client/src/pages/GiftManagementPage.tsx b/client/src/pages/GiftManagementPage.tsx new file mode 100644 index 0000000..6da7d86 --- /dev/null +++ b/client/src/pages/GiftManagementPage.tsx @@ -0,0 +1,61 @@ +import React from 'react' +import GiftItem from '../components/GiftItem' +import NavBar from '../components/NavBar' + +const gifts = [ + { + name: 'Gift 1', + description: 'Gift 1 description', + price: 1, + link: 'Gift 1 link', + demographic: 'Gift 1 demographic', + giftCollections: 'Gift 1 giftCollections' + }, + { + name: 'Gift 2', + description: 'Gift 2 description', + price: 2, + link: 'Gift 2 link', + demographic: 'Gift 2 demographic', + giftCollections: 'Gift 2 giftCollections' + }, + { + name: 'Gift 3', + description: 'Gift 3 description', + price: 3, + link: 'Gift 3 link', + demographic: 'Gift 3 demographic', + giftCollections: 'Gift 3 giftCollections' + } +] + +const GiftManagementPage = () => { + return ( +
+ +

Gift Management

+ +
+
+ + +
+ +
+ + + + {gifts.map((gift) => ( + + ))} +
+
+
+ ) +} + +export default GiftManagementPage \ No newline at end of file