/// <summary>
        /// 创建 <see cref="DiscoveryPage"/>
        /// </summary>
        /// <param name="currentUserId">当前登录用户 ID</param>
        /// <param name="dbContext"><see cref="KeylolDbContext"/></param>
        /// <param name="cachedData"><see cref="CachedDataProvider"/></param>
        /// <returns><see cref="DiscoveryPage"/></returns>
        public static async Task <DiscoveryPage> CreateAsync(string currentUserId, KeylolDbContext dbContext,
                                                             CachedDataProvider cachedData)
        {
            var onSalePoints = await OnSalePointList.CreateAsync(currentUserId, 1, true, true, dbContext, cachedData);

            var latestArticles = await LatestArticleList.CreateAsync(1, true, true, dbContext, cachedData);

            return(new DiscoveryPage
            {
                SlideshowEntries = await SlideshowEntryList.CreateAsync(1, 4, dbContext),
                SpotlightPoints = await SpotlightPointList.CreateAsync(currentUserId, 1, 30, dbContext, cachedData),
                SpotlightReviews = await SpotlightArticleList.CreateAsync(currentUserId, 1, 4,
                                                                          SpotlightArticleStream.ArticleCategory.Review, dbContext, cachedData),
                SpotlightStudies = await SpotlightArticleList.CreateAsync(currentUserId, 1, 4,
                                                                          SpotlightArticleStream.ArticleCategory.Study, dbContext, cachedData),
                OnSalePointHeaderImage = onSalePoints.Item3,
                OnSalePointPageCount = onSalePoints.Item2,
                OnSalePoints = onSalePoints.Item1,
                SpotlightStories = await SpotlightArticleList.CreateAsync(currentUserId, 1, 4,
                                                                          SpotlightArticleStream.ArticleCategory.Story, dbContext, cachedData),
                LatestArticleHeaderImage = latestArticles.Item3,
                LatestArticlePageCount = latestArticles.Item2,
                LatestArticles = latestArticles.Item1,
                LatestActivityHeaderImage = onSalePoints.Item4,
                LatestActivities = await TimelineCardList.CreateAsync(LatestActivityStream.Name, currentUserId,
                                                                      12, false, dbContext, cachedData)
            });
        }
        /// <summary>
        /// 创建 <see cref="SpotlightPointList"/>
        /// </summary>
        /// <param name="currentUserId">当前登录用户 ID</param>
        /// <param name="page">分页页码</param>
        /// <param name="recordPerPage">每页个数</param>
        /// <param name="dbContext"><see cref="KeylolDbContext"/></param>
        /// <param name="cachedData"><see cref="CachedDataProvider"/></param>
        /// <returns><see cref="SpotlightPointList"/></returns>
        public static async Task <SpotlightPointList> CreateAsync(string currentUserId, int page, int recordPerPage,
                                                                  KeylolDbContext dbContext, CachedDataProvider cachedData)
        {
            var queryResult = await(from feed in dbContext.Feeds
                                    where feed.StreamName == SpotlightPointStream.Name
                                    join point in dbContext.Points on feed.Entry equals point.Id
                                    orderby feed.Id descending
                                    select new
            {
                FeedId = feed.Id,
                point.Id,
                point.Type,
                point.IdCode,
                point.AvatarImage,
                point.EnglishName,
                point.ChineseName,
                point.SteamAppId,
                point.SteamPrice,
                point.SteamDiscountedPrice,
                point.SonkwoProductId,
                point.SonkwoPrice,
                point.SonkwoDiscountedPrice,
                point.UplayLink,
                point.UplayPrice,
                point.XboxLink,
                point.XboxPrice,
                point.PlayStationLink,
                point.PlayStationPrice,
                point.OriginLink,
                point.OriginPrice,
                point.WindowsStoreLink,
                point.WindowsStorePrice,
                point.AppStoreLink,
                point.AppStorePrice,
                point.GooglePlayLink,
                point.GooglePlayPrice,
                point.GogLink,
                point.GogPrice,
                point.BattleNetLink,
                point.BattleNetPrice
            }).TakePage(page, recordPerPage).ToListAsync();
            var result = new SpotlightPointList(queryResult.Count);

            foreach (var p in queryResult)
            {
                result.Add(new PointBasicInfo
                {
                    FeedId                = p.FeedId,
                    Id                    = p.Id,
                    Type                  = p.Type,
                    IdCode                = p.IdCode,
                    AvatarImage           = p.AvatarImage,
                    EnglishName           = p.EnglishName,
                    ChineseName           = p.ChineseName,
                    AverageRating         = (await cachedData.Points.GetRatingsAsync(p.Id)).AverageRating,
                    SteamAppId            = p.SteamAppId,
                    SteamPrice            = p.SteamPrice,
                    SteamDiscountedPrice  = p.SteamDiscountedPrice,
                    SonkwoProductId       = p.SonkwoProductId,
                    SonkwoPrice           = p.SonkwoPrice,
                    SonkwoDiscountedPrice = p.SonkwoDiscountedPrice,
                    UplayLink             = p.UplayLink,
                    UplayPrice            = p.UplayPrice,
                    XboxLink              = p.XboxLink,
                    XboxPrice             = p.XboxPrice,
                    PlayStationLink       = p.PlayStationLink,
                    PlayStationPrice      = p.PlayStationPrice,
                    OriginLink            = p.OriginLink,
                    OriginPrice           = p.OriginPrice,
                    WindowsStoreLink      = p.WindowsStoreLink,
                    WindowsStorePrice     = p.WindowsStorePrice,
                    AppStoreLink          = p.AppStoreLink,
                    AppStorePrice         = p.AppStorePrice,
                    GooglePlayLink        = p.GooglePlayLink,
                    GooglePlayPrice       = p.GooglePlayPrice,
                    GogLink               = p.GogLink,
                    GogPrice              = p.GogPrice,
                    BattleNetLink         = p.BattleNetLink,
                    BattleNetPrice        = p.BattleNetPrice,
                    Subscribed            = string.IsNullOrWhiteSpace(currentUserId)
                        ? (bool?)null
                        : await cachedData.Subscriptions.IsSubscribedAsync(currentUserId, p.Id,
                                                                           SubscriptionTargetType.Point),
                    InLibrary = string.IsNullOrWhiteSpace(currentUserId) || p.SteamAppId == null
                        ? (bool?)null
                        : await cachedData.Users.IsSteamAppInLibraryAsync(currentUserId, p.SteamAppId.Value)
                });
            }
            return(result);
        }