Example #1
0
        public async Task <ResultModel> RunAsync(Request request)
        {
            await request.CheckValidityAsync(dbContext);

            var card = await dbContext.Cards
                       .Include(card => card.Images)
                       .ThenInclude(img => img.Image)
                       .ThenInclude(img => img.Owner)
                       .Include(card => card.CardLanguage)
                       .Include(card => card.TagsInCards)
                       .ThenInclude(tagInCard => tagInCard.Tag)
                       .Include(card => card.UsersWithView)
                       .Where(card => card.Id == request.CardId)
                       .AsSingleQuery()
                       .SingleOrDefaultAsync();

            if (card == null)
            {
                throw new RequestInputException("Card not found in database");
            }

            var ratings = CardRatings.Load(dbContext, request.CurrentUserId, ImmutableHashSet.Create(request.CardId));

            var ownersOfDecksWithThisCard = dbContext.CardsInDecks
                                            .Where(cardInDeck => cardInDeck.CardId == request.CardId)
                                            .Select(cardInDeck => cardInDeck.Deck.Owner.UserName)
                                            .Distinct();

            return(new ResultModel(
                       card.FrontSide,
                       card.BackSide,
                       card.AdditionalInfo,
                       card.CardLanguage.Id,
                       card.TagsInCards.Select(tagInCard => new ResultTagModel(tagInCard.TagId, tagInCard.Tag.Name)),
                       card.UsersWithView.Select(userWithView => new ResultUserModel(userWithView.UserId, userWithView.User.UserName)),
                       card.InitialCreationUtcDate,
                       card.VersionUtcDate,
                       ownersOfDecksWithThisCard,
                       card.Images.Select(img => new ResultImageModel(img)),
                       ratings.User(request.CardId),
                       ratings.Average(request.CardId),
                       ratings.Count(request.CardId)
                       ));
        }
Example #2
0
        private IEnumerable <ResultCard> GetCardsToRepeat(Guid userId, Guid deckId, IEnumerable <Guid> excludedCardIds, IEnumerable <Guid> excludedTagIds, HeapingAlgorithm heapingAlgorithm, ImmutableDictionary <Guid, string> userNames, ImmutableDictionary <Guid, ImageDetails> imagesDetails, ImmutableDictionary <Guid, string> tagNames, int cardCount)
        {
            var result = new List <ResultCard>();

            for (var heap = MoveCardToHeap.MaxTargetHeapId; heap > 0 && result.Count < cardCount; heap--)
            {
                var cardsOfHeap = dbContext.CardsInDecks.AsNoTracking().Where(cardInDeck => cardInDeck.DeckId == deckId && cardInDeck.CurrentHeap == heap);

                var withoutExcuded = cardsOfHeap.Where(cardInDeck => !excludedCardIds.Contains(cardInDeck.CardId));
                withoutExcuded = withoutExcuded.Where(cardInDeck => !cardInDeck.Card.TagsInCards.Any(tag => excludedTagIds.Contains(tag.TagId)));

                var ordered = withoutExcuded.OrderBy(cardInDeck => cardInDeck.LastLearnUtcTime);
                var oldest  = ordered.Take(cardCount);

                var withInfoToComputeExpiration = oldest.Select(cardInDeck => new
                {
                    cardInDeck.CardId,
                    cardInDeck.CurrentHeap,
                    cardInDeck.LastLearnUtcTime,
                }).ToList();

                var expired = withInfoToComputeExpiration.Where(resultCard => heapingAlgorithm.HasExpired(resultCard.CurrentHeap, resultCard.LastLearnUtcTime)).Select(card => card.CardId).ToList();

                var withDetails = dbContext.CardsInDecks.AsNoTracking().Where(cardInDeck => cardInDeck.DeckId == deckId && expired.Contains(cardInDeck.CardId))
                                  .Include(cardInDeck => cardInDeck.Card).AsSingleQuery()
                                  .Select(cardInDeck => new
                {
                    cardInDeck.CardId,
                    cardInDeck.CurrentHeap,
                    cardInDeck.LastLearnUtcTime,
                    cardInDeck.AddToDeckUtcTime,
                    cardInDeck.BiggestHeapReached,
                    cardInDeck.NbTimesInNotLearnedHeap,
                    cardInDeck.Card.FrontSide,
                    cardInDeck.Card.BackSide,
                    cardInDeck.Card.AdditionalInfo,
                    cardInDeck.Card.VersionUtcDate,
                    VersionCreator      = cardInDeck.Card.VersionCreator.Id,
                    tagIds              = cardInDeck.Card.TagsInCards.Select(tag => tag.TagId),
                    userWithViewIds     = cardInDeck.Card.UsersWithView.Select(u => u.UserId),
                    imageIdAndCardSides = cardInDeck.Card.Images.Select(img => new { img.ImageId, img.CardSide })
                }).ToList();

                var cardIds       = expired.ToImmutableHashSet();
                var ratings       = CardRatings.Load(dbContext, userId, cardIds);
                var notifications = GetNotifications(userId, cardIds);

                var thisHeapResult = withDetails.Select(oldestCard => new ResultCard(oldestCard.CardId, oldestCard.CurrentHeap, oldestCard.LastLearnUtcTime, oldestCard.AddToDeckUtcTime,
                                                                                     oldestCard.BiggestHeapReached, oldestCard.NbTimesInNotLearnedHeap,
                                                                                     oldestCard.FrontSide, oldestCard.BackSide, oldestCard.AdditionalInfo,
                                                                                     oldestCard.VersionUtcDate,
                                                                                     userNames[oldestCard.VersionCreator],
                                                                                     oldestCard.tagIds.Select(tagId => tagNames[tagId]),
                                                                                     oldestCard.userWithViewIds.Select(userWithView => userNames[userWithView]),
                                                                                     oldestCard.imageIdAndCardSides.Select(imageIdAndCardSide => new ResultImageModel(imagesDetails[imageIdAndCardSide.ImageId], imageIdAndCardSide.CardSide)),
                                                                                     heapingAlgorithm,
                                                                                     ratings.User(oldestCard.CardId),
                                                                                     ratings.Average(oldestCard.CardId),
                                                                                     ratings.Count(oldestCard.CardId),
                                                                                     notifications[oldestCard.CardId]
                                                                                     )
                                                        );

                result.AddRange(thisHeapResult);
            }

            return(result);
        }
Example #3
0
            public ResultCardBeforeDeckInfo(Guid cardId, string frontSide, IEnumerable <string> tags, IEnumerable <string> visibleTo, CardRatings cardRatings)
            {
                CardId    = cardId;
                FrontSide = frontSide;
                Tags      = tags;
                VisibleTo = visibleTo;

                CurrentUserRating  = cardRatings.User(cardId);
                AverageRating      = cardRatings.Average(cardId);
                CountOfUserRatings = cardRatings.Count(cardId);
            }
Example #4
0
        private async Task <IEnumerable <ResultCard> > GetUnknownCardsAsync(Guid userId, Guid deckId, IEnumerable <Guid> excludedCardIds, IEnumerable <Guid> excludedTagIds, HeapingAlgorithm heapingAlgorithm, ImmutableDictionary <Guid, string> userNames, ImmutableDictionary <Guid, ImageDetails> imagesDetails, ImmutableDictionary <Guid, string> tagNames, int cardCount)
        {
            var cardsOfDeck = dbContext.CardsInDecks.AsNoTracking()
                              .Include(card => card.Card).AsSingleQuery()
                              .Where(card => card.DeckId.Equals(deckId) && !excludedCardIds.Contains(card.CardId));

            var onUnknownHeap        = cardsOfDeck.Where(cardInDeck => cardInDeck.CurrentHeap == 0);
            var withoutExcludedCards = onUnknownHeap;

            foreach (var tag in excludedTagIds)   //I tried to do better with an intersect between the two sets, but that failed
            {
                withoutExcludedCards = withoutExcludedCards.Where(cardInDeck => !cardInDeck.Card.TagsInCards.Where(tagInCard => tagInCard.TagId == tag).Any());
            }
            var oldest = withoutExcludedCards.OrderBy(cardInDeck => cardInDeck.LastLearnUtcTime).Take(cardCount * 3);   //we take more cards for shuffling accross more

            var withDetails = oldest.Select(cardInDeck => new
            {
                cardInDeck.CardId,
                cardInDeck.LastLearnUtcTime,
                cardInDeck.AddToDeckUtcTime,
                cardInDeck.BiggestHeapReached,
                cardInDeck.NbTimesInNotLearnedHeap,
                cardInDeck.Card.FrontSide,
                cardInDeck.Card.BackSide,
                cardInDeck.Card.AdditionalInfo,
                cardInDeck.Card.VersionUtcDate,
                VersionCreator      = cardInDeck.Card.VersionCreator.Id,
                tagIds              = cardInDeck.Card.TagsInCards.Select(tag => tag.TagId),
                userWithViewIds     = cardInDeck.Card.UsersWithView.Select(u => u.UserId),
                imageIdAndCardSides = cardInDeck.Card.Images.Select(img => new { img.ImageId, img.CardSide })
            });

            var listed = await withDetails.ToListAsync();

            var cardIds       = listed.Select(cardInDeck => cardInDeck.CardId).ToImmutableHashSet();
            var ratings       = CardRatings.Load(dbContext, userId, cardIds);
            var notifications = GetNotifications(userId, cardIds);

            var result = listed.Select(cardInDeck => new ResultCard(
                                           cardInDeck.CardId,
                                           0,
                                           cardInDeck.LastLearnUtcTime,
                                           cardInDeck.AddToDeckUtcTime,
                                           cardInDeck.BiggestHeapReached, cardInDeck.NbTimesInNotLearnedHeap,
                                           cardInDeck.FrontSide,
                                           cardInDeck.BackSide,
                                           cardInDeck.AdditionalInfo,
                                           cardInDeck.VersionUtcDate,
                                           userNames[cardInDeck.VersionCreator],
                                           cardInDeck.tagIds.Select(tagId => tagNames[tagId]),
                                           cardInDeck.userWithViewIds.Select(userWithView => userNames[userWithView]),
                                           cardInDeck.imageIdAndCardSides.Select(imageIdAndCardSide => new ResultImageModel(imagesDetails[imageIdAndCardSide.ImageId], imageIdAndCardSide.CardSide)),
                                           heapingAlgorithm,
                                           ratings.User(cardInDeck.CardId),
                                           ratings.Average(cardInDeck.CardId),
                                           ratings.Count(cardInDeck.CardId),
                                           notifications[cardInDeck.CardId]
                                           ));

            return(Shuffler.Shuffle(result).Take(cardCount));
        }
Example #5
0
        public Result Run(Request request, Guid userId)
        {
            var allCards = dbContext.Cards.AsNoTracking()
                           .Include(card => card.TagsInCards)
                           .Include(card => card.VersionCreator)
                           .Include(card => card.CardLanguage)
                           .Include(card => card.UsersWithView)
                           .AsSingleQuery();

            var cardsViewableByUser = allCards.Where(
                card =>
                card.VersionCreator.Id == userId ||
                !card.UsersWithView.Any() ||    //card is public
                card.UsersWithView.Where(userWithView => userWithView.UserId == userId).Any()
                );

            IQueryable <Card> cardsFilteredWithDeck;

            if (request.Deck != Guid.Empty)
            {
                if (request.DeckIsInclusive)
                {
                    cardsFilteredWithDeck = cardsViewableByUser.Where(card =>
                                                                      dbContext.CardsInDecks.AsNoTracking().Where(cardInDeck => cardInDeck.CardId == card.Id && cardInDeck.DeckId == request.Deck).Any() &&
                                                                      (request.Heap == -1 || dbContext.CardsInDecks.AsNoTracking().Single(cardInDeck => cardInDeck.CardId == card.Id && cardInDeck.DeckId == request.Deck).CurrentHeap == request.Heap)
                                                                      );
                }
                else
                {
                    cardsFilteredWithDeck = cardsViewableByUser.Where(card => !dbContext.CardsInDecks.AsNoTracking().Where(cardInDeck => cardInDeck.CardId == card.Id && cardInDeck.DeckId == request.Deck).Any());
                }
            }
            else
            {
                cardsFilteredWithDeck = cardsViewableByUser;
            }

            var cardsFilteredWithRequiredTags = cardsFilteredWithDeck;

            foreach (var tag in request.RequiredTags)   //I tried to do better with an intersect between the two sets, but that failed
            {
                cardsFilteredWithRequiredTags = cardsFilteredWithRequiredTags.Where(card => card.TagsInCards.Where(tagInCard => tagInCard.TagId == tag).Any());
            }

            var cardsFilteredWithExludedTags = cardsFilteredWithRequiredTags;

            if (request.ExcludedTags == null)
            {
                cardsFilteredWithExludedTags = cardsFilteredWithExludedTags.Where(card => !card.TagsInCards.Any());
            }
            else
            {
                foreach (var tag in request.ExcludedTags)   //I tried to do better with an intersect between the two sets, but that failed
                {
                    cardsFilteredWithExludedTags = cardsFilteredWithExludedTags.Where(card => !card.TagsInCards.Where(tagInCard => tagInCard.TagId == tag).Any());
                }
            }

            var cardsFilteredWithText = string.IsNullOrEmpty(request.RequiredText) ? cardsFilteredWithExludedTags :
                                        cardsFilteredWithExludedTags.Where(card =>
                                                                           EF.Functions.Like(card.FrontSide, $"%{request.RequiredText}%") ||
                                                                           EF.Functions.Like(card.BackSide, $"%{request.RequiredText}%") ||
                                                                           EF.Functions.Like(card.AdditionalInfo, $"%{request.RequiredText}%")
                                                                           );

            IQueryable <Card> cardsFilteredWithVisibility;

            if (request.Visibility == 2)
            {
                cardsFilteredWithVisibility = cardsFilteredWithText.Where(card => card.UsersWithView.Count() != 1);
            }
            else
            if (request.Visibility == 3)
            {
                cardsFilteredWithVisibility = cardsFilteredWithText.Where(card => card.UsersWithView.Count() == 1);
            }
            else
            {
                cardsFilteredWithVisibility = cardsFilteredWithText;
            }

            IQueryable <Card> cardsFilteredWithAverageRating;
            CardRatings?      cardRatings = null;

            if (request.RatingFilteringMode == 1)
            {
                cardsFilteredWithAverageRating = cardsFilteredWithVisibility;
            }
            else
            {
                if (cardsFilteredWithVisibility.Count() > 20000)
                {
                    throw new SearchResultTooBigForRatingException();
                }

                cardRatings = CardRatings.Load(dbContext, userId, cardsFilteredWithVisibility.Select(card => card.Id).ToImmutableHashSet());
                if (request.RatingFilteringMode == 4)
                {
                    cardsFilteredWithAverageRating = cardsFilteredWithVisibility.Where(card => cardRatings.CardsWithoutEval.Contains(card.Id));
                }
                else
                if (request.RatingFilteringMode == 2)
                {
                    cardsFilteredWithAverageRating = cardsFilteredWithVisibility.Where(card => cardRatings.CardsWithAverageRatingAtLeast(request.RatingFilteringValue).Contains(card.Id));
                }
                else
                {
                    cardsFilteredWithAverageRating = cardsFilteredWithVisibility.Where(card => cardRatings.CardsWithAverageRatingAtMost(request.RatingFilteringValue).Contains(card.Id));
                }
            }

            IQueryable <Card> cardsFilteredWithNotifications;

            if (request.NotificationFiltering == 1)
            {
                cardsFilteredWithNotifications = cardsFilteredWithAverageRating;
            }
            else
            {
                var notifMustExist = request.NotificationFiltering == 2;
                cardsFilteredWithNotifications = cardsFilteredWithAverageRating.Where(card => dbContext.CardNotifications.AsNoTracking().Where(cardNotif => cardNotif.CardId == card.Id && cardNotif.UserId == userId).Any() == notifMustExist);
            }

            var finalResult = cardsFilteredWithNotifications;

            finalResult = finalResult.OrderByDescending(card => card.VersionUtcDate); //For Take() and Skip(), just below, to work, we need to have an order. In future versions we will offer the user some sorting

            var totalNbCards   = finalResult.Count();
            var totalPageCount = (int)Math.Ceiling(((double)totalNbCards) / request.pageSize);

            var pageCards = finalResult.Skip((request.pageNo - 1) * request.pageSize).Take(request.pageSize);

            if (cardRatings == null)
            {
                cardRatings = CardRatings.Load(dbContext, userId, pageCards.Select(card => card.Id).ToImmutableHashSet());
            }

            var resultCards = pageCards.Select(card => new ResultCardBeforeDeckInfo(card.Id, card.FrontSide, card.TagsInCards.Select(tagInCard => tagInCard.Tag.Name), card.UsersWithView.Select(userWithView => userWithView.User.UserName), cardRatings)).ToList();

            var withUserDeckInfo = AddDeckInfo(userId, resultCards);

            return(new Result(totalNbCards, totalPageCount, withUserDeckInfo));
        }