Skip to content

Commit

Permalink
Change how to return card array
Browse files Browse the repository at this point in the history
  • Loading branch information
yasuflatland-lf committed Aug 21, 2024
1 parent 3d93d5a commit 01cada1
Show file tree
Hide file tree
Showing 13 changed files with 256 additions and 43 deletions.
2 changes: 1 addition & 1 deletion backend/graph/schema.resolvers.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

81 changes: 59 additions & 22 deletions backend/graph/services/card.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,14 @@ type CardService interface {
GetCardsByIDs(ctx context.Context, ids []int64) ([]*model.Card, error)
FetchAllCardsByCardGroup(ctx context.Context, cardGroupID int64, first *int) ([]*model.Card, error)
AddNewCards(ctx context.Context, targetCards []model.Card, cardGroupID int64) ([]*model.Card, error)
GetCardsByUserAndCardGroup(ctx context.Context, cardGroupID int64, order string, limit int) ([]repository.Card, error)
GetRandomCardsFromRecentUpdates(ctx context.Context, cardGroupID int64, limit int, updatedSortOrder string, intervalDaysSortOrder string) ([]model.Card, error)
GetCardsByDefaultLogic(ctx context.Context, cardGroupID int64, limit int) ([]repository.Card, error)
GetCardsByUserAndCardGroup(ctx context.Context, cardGroupID int64,
order string, limit int) ([]*repository.Card, error)
ShuffleCards(cards []repository.Card, limit int) []*model.Card
GetRandomCardsFromRecentUpdates(ctx context.Context, cardGroupID int64,
limit int, updatedSortOrder string, intervalDaysSortOrder string) ([]*model.Card, error)
GetCardsByDefaultLogic(ctx context.Context, cardGroupID int64,
limit int) ([]*repository.Card, error)
GetRandomRecentCards(ctx context.Context, fromDate time.Time, limit int, sortOrder string) ([]*model.Card, error)
}

func NewCardService(db *gorm.DB, defaultLimit int) CardService {
Expand Down Expand Up @@ -70,11 +75,11 @@ func ConvertToCard(card repository.Card) *model.Card {
}
}

func ConvertToCards(cards []repository.Card) []model.Card {
var result []model.Card
func ConvertToCards(cards []repository.Card) []*model.Card {
var result []*model.Card
for _, card := range cards {
convertedCard := ConvertToCard(card)
result = append(result, *convertedCard)
result = append(result, convertedCard)
}
return result
}
Expand Down Expand Up @@ -318,8 +323,9 @@ func (s *cardService) AddNewCards(ctx context.Context, targetCards []model.Card,
}

func (s *cardService) GetCardsByUserAndCardGroup(
ctx context.Context, cardGroupID int64, order string, limit int) ([]repository.Card, error) {
var cards []repository.Card
ctx context.Context, cardGroupID int64, order string,
limit int) ([]*repository.Card, error) {
var cards []*repository.Card

// Query to find the latest cards with matching user_id and cardgroup_id
err := s.db.WithContext(ctx).
Expand All @@ -335,7 +341,22 @@ func (s *cardService) GetCardsByUserAndCardGroup(
return cards, nil
}

func (s *cardService) GetRandomCardsFromRecentUpdates(ctx context.Context, cardGroupID int64, limit int, updatedSortOrder string, intervalDaysSortOrder string) ([]model.Card, error) {
// Shuffle cards
func (s *cardService) ShuffleCards(cards []repository.Card,
limit int) []*model.Card {
rng := rand.New(rand.NewSource(time.Now().UnixNano()))
rng.Shuffle(len(cards), func(i, j int) { cards[i], cards[j] = cards[j], cards[i] })

if len(cards) <= limit {
return ConvertToCards(cards)
}

selectedCards := cards[:limit]
return ConvertToCards(selectedCards)
}

func (s *cardService) GetRandomCardsFromRecentUpdates(ctx context.Context,
cardGroupID int64, limit int, updatedSortOrder string, intervalDaysSortOrder string) ([]*model.Card, error) {
var cards []repository.Card

// Validate sortOrder for updated and intervalDays
Expand All @@ -358,21 +379,13 @@ func (s *cardService) GetRandomCardsFromRecentUpdates(ctx context.Context, cardG
return nil, goerr.Wrap(err, "Failed to retrieve recent cards")
}

// Shuffle the cards if necessary
rng := rand.New(rand.NewSource(time.Now().UnixNano()))
rng.Shuffle(len(cards), func(i, j int) { cards[i], cards[j] = cards[j], cards[i] })

if len(cards) <= limit {
return ConvertToCards(cards), nil
}

randomCards := cards[:limit]

return ConvertToCards(randomCards), nil
// Shuffle the cards
return s.ShuffleCards(cards, limit), nil
}

func (s *cardService) GetCardsByDefaultLogic(ctx context.Context, cardGroupID int64, limit int) ([]repository.Card, error) {
var cards []repository.Card
func (s *cardService) GetCardsByDefaultLogic(ctx context.Context,
cardGroupID int64, limit int) ([]*repository.Card, error) {
var cards []*repository.Card

err := s.db.WithContext(ctx).
Where("cardgroup_id = ?", cardGroupID).
Expand All @@ -387,3 +400,27 @@ func (s *cardService) GetCardsByDefaultLogic(ctx context.Context, cardGroupID in

return cards, nil
}

func (s *cardService) GetRandomRecentCards(
ctx context.Context, fromDate time.Time, limit int, sortOrder string) ([]*model.Card, error) {
var cards []repository.Card

// Validate sortOrder, default to "desc" if not valid
if sortOrder != repo.ASC && sortOrder != repo.DESC {
sortOrder = repo.DESC
}

// Create the query with the fromDate filter, ordering by created, and limit
err := s.db.WithContext(ctx).
Where("created >= ?", fromDate).
Order(fmt.Sprintf("created %s", sortOrder)).
Limit(limit).
Find(&cards).Error

if err != nil {
return nil, goerr.Wrap(err, "Failed to retrieve recent cards")
}

// Shuffle the cards
return s.ShuffleCards(cards, limit), nil
}
150 changes: 150 additions & 0 deletions backend/graph/services/card_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -607,6 +607,156 @@ func (suite *CardTestSuite) TestCardService() {
}
})

suite.Run("Normal_ShuffleCards", func() {
// Arrange
createdGroup, _, _ := testutils.CreateUserAndCardGroup(ctx, userService, cardGroupService, roleService)

// Create 10 dummy cards and associate them with the created card group
limit := 10
cards := []repository.Card{}
for i := 0; i < limit; i++ {
card := repository.Card{
Front: "Front " + strconv.Itoa(i),
Back: "Back " + strconv.Itoa(i),
ReviewDate: time.Now().UTC(),
IntervalDays: 1,
Created: time.Now().UTC(),
Updated: time.Now().UTC(),
CardGroupID: createdGroup.ID,
CardGroup: repository.Cardgroup{
ID: createdGroup.ID,
},
}
cards = append(cards, card)
}

// Act
shuffledCards := cardService.ShuffleCards(cards, limit)

// Assert
assert.Len(suite.T(), shuffledCards, len(cards))
assert.NotEqual(suite.T(), cards, shuffledCards, "Shuffled cards should not be in the same order as the original")

// Check if all original cards are still present after shuffle
cardMap := make(map[int64]*model.Card)
for _, card := range shuffledCards {
cardMap[card.ID] = card
}
for _, card := range cards {
_, exists := cardMap[card.ID]
assert.True(suite.T(), exists, "All original cards should be present after shuffle")
}
})

suite.Run("Normal_ShuffleCards_Randomness", func() {
// Arrange
createdGroup, _, _ := testutils.CreateUserAndCardGroup(ctx, userService, cardGroupService, roleService)

// Create 10 dummy cards and associate them with the created card group
limit := 10
cards := []repository.Card{}
for i := 0; i < limit; i++ {
card := repository.Card{
Front: "Front " + strconv.Itoa(i),
Back: "Back " + strconv.Itoa(i),
ReviewDate: time.Now().UTC(),
IntervalDays: 1,
Created: time.Now().UTC(),
Updated: time.Now().UTC(),
CardGroupID: createdGroup.ID,
CardGroup: repository.Cardgroup{
ID: createdGroup.ID,
},
}
cards = append(cards, card)
}

// Shuffle twice
shuffledCards1 := cardService.ShuffleCards(cards, limit)
shuffledCards2 := cardService.ShuffleCards(cards, limit)

// Assert that the order is different in at least one shuffle
assert.NotEqual(suite.T(), shuffledCards1, shuffledCards2, "Different shuffles should result in different orders")
})

suite.Run("Normal_GetRecentCards", func() {
// Arrange
cardService := suite.sv.(services.CardService)
userService := suite.sv.(services.UserService)
cardGroupService := suite.sv.(services.CardGroupService)
roleService := suite.sv.(services.RoleService)
ctx := context.Background()

// Create a user and card group using testutils
createdGroup, _, _ := testutils.CreateUserAndCardGroup(ctx, userService, cardGroupService, roleService)

// Add some cards with different creation times
for i := 0; i < 10; i++ {
input := model.NewCard{
Front: "Recent Front " + strconv.Itoa(i),
Back: "Recent Back " + strconv.Itoa(i),
ReviewDate: time.Now().UTC().Add(-time.Duration(i) * time.Hour),
CardgroupID: createdGroup.ID,
}
_, err := cardService.CreateCard(ctx, input)
assert.NoError(suite.T(), err)
}

// Define the date from which to fetch recent cards
fromDate := time.Now().UTC().Add(-5 * time.Hour) // fetch cards created in the last 5 hours
limit := 5

// Act
recentCards, err := cardService.GetRandomRecentCards(ctx, fromDate, limit,
repo.DESC)

// Assert
assert.NoError(suite.T(), err)
assert.Len(suite.T(), recentCards, limit)
for _, card := range recentCards {
assert.True(suite.T(), card.Created.After(fromDate), "Card should be created after the fromDate")
}
})

suite.Run("Normal_GetRecentCards_LimitExceeds", func() {
// Arrange
cardService := suite.sv.(services.CardService)
userService := suite.sv.(services.UserService)
cardGroupService := suite.sv.(services.CardGroupService)
roleService := suite.sv.(services.RoleService)
ctx := context.Background()

// Reuse the same method to create a user and card group
createdGroup, _, _ := testutils.CreateUserAndCardGroup(ctx, userService, cardGroupService, roleService)

// Add some cards with different creation times
for i := 0; i < 10; i++ {
input := model.NewCard{
Front: "Recent Front " + strconv.Itoa(i),
Back: "Recent Back " + strconv.Itoa(i),
ReviewDate: time.Now().UTC().Add(-time.Duration(i) * time.Hour),
CardgroupID: createdGroup.ID,
}
_, err := cardService.CreateCard(ctx, input)
assert.NoError(suite.T(), err)
}

// Define the date from which to fetch recent cards
fromDate := time.Now().UTC().Add(-24 * time.Hour)
limit := 15 // Exceeds the number of available cards

// Act
recentCards, err := cardService.GetRandomRecentCards(ctx, fromDate, limit,
repo.DESC)

// Assert
assert.NoError(suite.T(), err)
assert.True(suite.T(), len(recentCards) <= limit, "Number of cards should not exceed the limit")
for _, card := range recentCards {
assert.True(suite.T(), card.Created.After(fromDate), "Card should be created after the fromDate")
}
})

}

func TestCardTestSuite(t *testing.T) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package usecases
package dictionary_manager

import (
"backend/graph/model"
Expand Down Expand Up @@ -41,7 +41,6 @@ func (dmu *dictionaryManagerUsecase) UpsertCards(ctx context.Context, encodedDic
return nil, goerr.Wrap(fmt.Errorf("failed to process dictionary: %+v", errs))
}

// Convert nodes to []model.Card
var cards []model.Card
for _, node := range nodes {
card := model.Card{
Expand Down
4 changes: 3 additions & 1 deletion backend/pkg/usecases/swipe_manager/default_state_strategy.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"backend/pkg/config"
"backend/pkg/logger"
repo "backend/pkg/repository"

"github.com/m-mizutani/goerr"
"golang.org/x/net/context"
)
Expand All @@ -27,7 +28,8 @@ func NewDefaultStateStrategy(
}
}

func (d *defaultStateStrategy) Run(ctx context.Context, newSwipeRecord model.NewSwipeRecord) ([]model.Card, error) {
func (d *defaultStateStrategy) Run(ctx context.Context,
newSwipeRecord model.NewSwipeRecord) ([]*model.Card, error) {

// Fetch random recent added words
cards, err := d.swipeManagerUsecase.Srv().GetRandomCardsFromRecentUpdates(ctx, newSwipeRecord.CardGroupID, config.Cfg.PGQueryLimit, repo.DESC, repo.ASC)
Expand Down
18 changes: 16 additions & 2 deletions backend/pkg/usecases/swipe_manager/difficult_state_strategy.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@ import (
"backend/graph/services"
"backend/pkg/config"
"backend/pkg/logger"
repo "backend/pkg/repository"
"github.com/m-mizutani/goerr"
"golang.org/x/net/context"
"time"
)

type difficultStateStrategy struct {
Expand All @@ -25,9 +28,20 @@ func NewDifficultStateStrategy(swipeManagerUsecase SwipeManagerUsecase) Difficul
}
}

func (d *difficultStateStrategy) Run(ctx context.Context, newSwipeRecord model.NewSwipeRecord) ([]model.Card, error) {
func (d *difficultStateStrategy) Run(ctx context.Context,
newSwipeRecord model.NewSwipeRecord) ([]*model.Card, error) {
// Past 1 week
fromDate := time.Now().AddDate(0, 0, -7)

return nil, nil
// Fetch random recent created card within a week.
cards, err := d.swipeManagerUsecase.Srv().GetRandomRecentCards(
ctx, fromDate, d.amountOfSwipes, repo.DESC)

if err != nil {
return nil, goerr.Wrap(err)
}

return cards, nil
}

func (d *difficultStateStrategy) IsApplicable(ctx context.Context, newSwipeRecord model.NewSwipeRecord, latestSwipeRecords []*repository.SwipeRecord) bool {
Expand Down
3 changes: 2 additions & 1 deletion backend/pkg/usecases/swipe_manager/easy_state_strategy.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ func NewEasyStateStrategy(swipeManagerUsecase SwipeManagerUsecase) EasyStateStra
}
}

func (e *easyStateStrategy) Run(ctx context.Context, newSwipeRecord model.NewSwipeRecord) ([]model.Card, error) {
func (e *easyStateStrategy) Run(ctx context.Context,
newSwipeRecord model.NewSwipeRecord) ([]*model.Card, error) {

// Fetch random unknown words
cards, err := e.swipeManagerUsecase.Srv().GetRandomCardsFromRecentUpdates(ctx, newSwipeRecord.CardGroupID, config.Cfg.PGQueryLimit, repo.ASC, repo.ASC)
Expand Down
3 changes: 2 additions & 1 deletion backend/pkg/usecases/swipe_manager/good_state_strategy.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ func NewGoodStateStrategy(swipeManagerUsecase SwipeManagerUsecase) GoodStateStra
}

// Run ChangeState changes the state of the given swipe records to GOOD
func (g *goodStateStrategy) Run(ctx context.Context, newSwipeRecord model.NewSwipeRecord) ([]model.Card, error) {
func (g *goodStateStrategy) Run(ctx context.Context,
newSwipeRecord model.NewSwipeRecord) ([]*model.Card, error) {
return nil, nil
}

Expand Down
3 changes: 2 additions & 1 deletion backend/pkg/usecases/swipe_manager/inwhile_state_strategy.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ func NewInWhileStateStrategy(swipeManagerUsecase SwipeManagerUsecase) InWhileSta
}
}

func (d *inWhileStateStrategy) Run(ctx context.Context, newSwipeRecord model.NewSwipeRecord) ([]model.Card, error) {
func (d *inWhileStateStrategy) Run(ctx context.Context,
newSwipeRecord model.NewSwipeRecord) ([]*model.Card, error) {
// Fetch random known words, sorting by the most recent updates
cards, err := d.swipeManagerUsecase.Srv().GetRandomCardsFromRecentUpdates(ctx, newSwipeRecord.CardGroupID, config.Cfg.PGQueryLimit, repo.DESC, repo.DESC)
if err != nil {
Expand Down
Loading

0 comments on commit 01cada1

Please sign in to comment.