using System.Linq.Expressions; using MongoDB.Bson; using MongoDB.Driver; using UGQDataAccess.Repository.Models; using UGQDatabase.Models; namespace UGQDataAccess.Repository.Query; public static class QuestContentQuery { public class QuestBoardQueryJoin : QuestContentEntity { public IEnumerable Likes { get; set; } = null!; public int LikeCount { get; set; } = 0; public IEnumerable Bookmarks { get; set; } = null!; public int BookmarkCount { get; set; } = 0; public List QuestAccepteds { get; set; } = null!; public int QuestAcceptedCount { get; set; } = 0; public List QuestCompleteds { get; set; } = null!; public int QuestCompletedCount { get; set; } = 0; } public class QuestSummariesQueryJoin : QuestContentEntity { public IEnumerable Likes { get; set; } = null!; public int LikeCount { get; set; } = 0; public IEnumerable Bookmarks { get; set; } = null!; public int BookmarkCount { get; set; } = 0; public List QuestAccepteds { get; set; } = null!; public int QuestAcceptedCount { get; set; } = 0; public List QuestCompleteds { get; set; } = null!; public int QuestCompletedCount { get; set; } = 0; public List QuestAborteds { get; set; } = null!; public int QuestAbortedCount { get; set; } = 0; public List Reports { get; set; } = null!; public int ReporCount { get; set; } = 0; public List ProfitHistories { get; set; } = null!; public double TotalProfit { get; set; } = 0; } public class QuestProfitStatsQueryJoin : QuestContentEntity { public List QuestCompleteds { get; set; } = null!; public int QuestCompletedCount { get; set; } = 0; public List ProfitHistories { get; set; } = null!; public double TotalProfit { get; set; } = 0; } public class QuestAcceptedLookup : QuestAcceptedEntity { } public static Expression> QuestBoardItemResultProjection = x => new QuestBoardItemResult { QuestId = x.QuestId, Revision = x.Revision, Author = x.Author, Title = x.Title, Languages = x.Langs, Cost = x.Cost, GradeType = x.GradeType, TitleImagePath = x.TitleImagePath, BannerImagePath = x.BannerImagePath, UpdatedAt = x.UpdatedAt, LikeCount = x.LikeCount, BookmarkCount = x.BookmarkCount, }; public static Expression> SummaryItemResultProjection = x => new SummaryItemResult { QuestContentId = x.Id.ToString(), QuestId = x.QuestId, Revision = x.Revision, BeaconId = x.BeaconId, UserGuid = x.UserGuid, Author = x.Author, UgcBeaconGuid = x.UgcBeaconGuid, UgcBeaconNickname = x.UgcBeaconNickname, GradeType = x.GradeType, Title = x.Title, Languages = x.Langs, Description = x.Description, Cost = x.Cost, TitleImagePath = x.TitleImagePath, BannerImagePath = x.BannerImagePath, State = x.State, UpdatedAt = x.UpdatedAt, CreatedAt = x.CreatedAt, AcceptedCount = x.QuestAcceptedCount, CompletedCount = x.QuestCompletedCount, LikeCount = x.LikeCount, BookmarkCount = x.BookmarkCount, ReportCount = x.ReporCount, TotalProfit = x.TotalProfit, Savelanguage = x.Savelanguage, }; public static Expression> QuestProfitStatsResultProjection = x => new QuestProfitStatsItemResult { QuestContentId = x.Id.ToString(), QuestId = x.QuestId, Revision = x.Revision, Title = x.Title, Languages = x.Langs, TitleImagePath = x.TitleImagePath, BannerImagePath = x.BannerImagePath, CompletedCount = x.QuestCompletedCount, TotalProfit = x.TotalProfit, }; static List getGradeTypes(UgqUICategoryGradeType gradeType) { switch (gradeType) { case UgqUICategoryGradeType.Amateur: return [UgqGradeType.Amature]; case UgqUICategoryGradeType.RisingStar: return [UgqGradeType.RisingStar]; case UgqUICategoryGradeType.Master: return [UgqGradeType.Master1, UgqGradeType.Master2, UgqGradeType.Master3]; } return []; } static async Task> getBookmarks(IMongoCollection bookmarkCollection, string userGuid) { var filterBuilder = Builders.Filter; var filter = filterBuilder.Eq(x => x.UserGuid, userGuid); var list = await bookmarkCollection.Find(filter).ToListAsync(); return list.Select(x => x.QuestId).ToList(); } static async Task> searchNpcs(IMongoCollection npcNameCollection, string searchText) { var filterBuilder = Builders.Filter; var filter = filterBuilder.Empty; if (string.IsNullOrEmpty(searchText) == false) { filter &= (filterBuilder.Regex(x => x.NpcName.Kr, searchText) | filterBuilder.Regex(x => x.NpcName.En, searchText) | filterBuilder.Regex(x => x.NpcName.Jp, searchText)); } var list = await npcNameCollection.Find(filter).ToListAsync(); return list.Select(x => x.NpcId).ToList(); } static FilterDefinition searchTitle(FilterDefinitionBuilder filterBuilder, string searchText) { return filterBuilder.Regex(x => x.Title.Kr, searchText) | filterBuilder.Regex(x => x.Title.En, searchText) | filterBuilder.Regex(x => x.Title.Jp, searchText); } public static async Task> summariesFilter(string? userGuid, UgqSearchType searchType, string? searchText, QuestContentState state, IMongoCollection accountCollection, IMongoCollection npcNameCollection) { var filterBuilder = Builders.Filter; var filter = filterBuilder.Eq(x => x.IsDeleted, false); if (state != QuestContentState.None) { if(state == QuestContentState.Uncomplate || state == QuestContentState.Editable) { var UncomplateOrFilter = filterBuilder.Eq(x => x.State, QuestContentState.Uncomplate); var EditableOrFilter = filterBuilder.Eq(x => x.State, QuestContentState.Editable); filter &= filterBuilder.Or(UncomplateOrFilter, EditableOrFilter); } else { filter &= filterBuilder.Eq(x => x.State, state); } } if (string.IsNullOrEmpty(userGuid) == false) filter &= filterBuilder.Eq(x => x.UserGuid, userGuid); if (string.IsNullOrEmpty(searchText) == false) { switch (searchType) { case UgqSearchType.Title: filter &= searchTitle(filterBuilder, searchText); break; case UgqSearchType.Beacon: var npcIds = await searchNpcs(npcNameCollection, searchText); filter &= (filterBuilder.In(x => x.BeaconId, npcIds) | filterBuilder.Regex(x => x.UgcBeaconNickname, searchText)); break; } } return filter; } public static async Task> questBoardFilter(string? userGuid, UgqUICategoryGradeType gradeType, UgqSearchType searchType, string? searchText, IMongoCollection accountCollection, IMongoCollection npcNameCollection) { var filterBuilder = Builders.Filter; var filter = filterBuilder.Eq(x => x.IsDeleted, false); filter &= filterBuilder.Eq(x => x.State, QuestContentState.Live); if (string.IsNullOrEmpty(userGuid) == false) filter &= filterBuilder.Eq(x => x.UserGuid, userGuid); if(gradeType != UgqUICategoryGradeType.None) { var gradeTypes = getGradeTypes(gradeType); filter &= filterBuilder.In(x => x.GradeType, gradeTypes); } if (string.IsNullOrEmpty(searchText) == false) { switch (searchType) { case UgqSearchType.Title: filter &= searchTitle(filterBuilder, searchText); break; case UgqSearchType.Beacon: var npcIds = await searchNpcs(npcNameCollection, searchText); filter &= (filterBuilder.In(x => x.BeaconId, npcIds) | filterBuilder.Regex(x => x.UgcBeaconNickname, searchText)); break; } } return filter; } public static async Task> questBoardBookmarkFilter(string userGuid, UgqUICategoryGradeType gradeType, UgqSearchType searchType, string? searchText, IMongoCollection npcNameCollection, IMongoCollection bookmarkCollection) { var filterBuilder = Builders.Filter; var filter = filterBuilder.Eq(x => x.IsDeleted, false); filter &= filterBuilder.Eq(x => x.State, QuestContentState.Live); if (gradeType != UgqUICategoryGradeType.None) { var gradeTypes = getGradeTypes(gradeType); filter &= filterBuilder.In(x => x.GradeType, gradeTypes); } var bookmarkQuestIds = await getBookmarks(bookmarkCollection, userGuid); filter &= filterBuilder.In(x => x.QuestId, bookmarkQuestIds); if (string.IsNullOrEmpty(searchText) == false) { switch (searchType) { case UgqSearchType.Title: filter &= searchTitle(filterBuilder, searchText); break; case UgqSearchType.Beacon: var npcIds = await searchNpcs(npcNameCollection, searchText); filter &= (filterBuilder.In(x => x.BeaconId, npcIds) | filterBuilder.Regex(x => x.UgcBeaconNickname, searchText)); break; } } return filter; } public static SortDefinition questBoardSort(UgqSortType sortType) { var sortBuilder = Builders.Sort; SortDefinition sort; switch (sortType) { case UgqSortType.New: sort = Builders.Sort.Descending("UpdatedAt"); break; case UgqSortType.Like: sort = Builders.Sort.Descending("LikeCount"); break; case UgqSortType.Bookmark: sort = Builders.Sort.Descending("BookmarkCount"); break; default: sort = Builders.Sort.Descending("UpdatedAt"); break; } return sort; } public static PipelineDefinition summariesPipeline( IMongoCollection likeCollection, IMongoCollection bookmarkCollection, FilterDefinition filter, Expression> projection) { var lookupStage1 = new BsonDocument("$lookup", new BsonDocument { { "from", "QuestAccepted" }, { "localField", "QuestId" }, { "foreignField", "QuestId" }, { "pipeline", new BsonArray { new BsonDocument("$match", new BsonDocument("Reason", "Player")) } }, { "as", "QuestAccepteds" } }); var addFields1 = new BsonDocument("$addFields", new BsonDocument("QuestAcceptedCount", new BsonDocument("$size", "$QuestAccepteds"))); var lookupStage2 = new BsonDocument("$lookup", new BsonDocument { { "from", "QuestCompleted" }, { "localField", "QuestId" }, { "foreignField", "QuestId" }, { "as", "QuestCompleteds" } }); var addFields2 = new BsonDocument("$addFields", new BsonDocument("QuestCompletedCount", new BsonDocument("$size", "$QuestCompleteds"))); var lookupStage3 = new BsonDocument("$lookup", new BsonDocument { { "from", "QuestAborted" }, { "localField", "QuestId" }, { "foreignField", "QuestId" }, { "as", "QuestAborteds" } }); var addFields3 = new BsonDocument("$addFields", new BsonDocument("QuestAbortedCount", new BsonDocument("$size", "$QuestAborteds"))); var lookupStage4 = new BsonDocument("$lookup", new BsonDocument { { "from", "CreatorPointHistory" }, { "localField", "QuestId" }, { "foreignField", "QuestId" }, { "pipeline", new BsonArray { new BsonDocument("$match", new BsonDocument("Kind", "QuestProfit")) } }, { "as", "ProfitHistories" } }); var addFields4 = new BsonDocument("$addFields", new BsonDocument("TotalProfit", new BsonDocument("$sum", "$ProfitHistories.Amount"))); var lookupStage5 = new BsonDocument("$lookup", new BsonDocument { { "from", "Report" }, { "localField", "QuestId" }, { "foreignField", "QuestId" }, { "as", "Reports" } }); var addFields5 = new BsonDocument("$addFields", new BsonDocument("ReporCount", new BsonDocument("$size", "$Reports"))); var pipeline = new EmptyPipelineDefinition() .Match(filter) .Lookup( likeCollection, x => x.QuestId, x => x.QuestId, x => x.Likes) .Lookup( bookmarkCollection, x => x.QuestId, x => x.QuestId, x => x.Bookmarks) .AppendStage("{$addFields: {LikeCount: {$size:'$Likes'}}}") .AppendStage("{$addFields: {BookmarkCount: {$size:'$Bookmarks'}}}") .AppendStage(lookupStage1) .AppendStage(addFields1) .AppendStage(lookupStage2) .AppendStage(addFields2) .AppendStage(lookupStage3) .AppendStage(addFields3) .AppendStage(lookupStage4) .AppendStage(addFields4) .AppendStage(lookupStage5) .AppendStage(addFields5) .Project(projection); return pipeline; } public static PipelineDefinition questProfitStatsPipeline( FilterDefinition filter, Expression> projection) { var lookupStage1 = new BsonDocument("$lookup", new BsonDocument { { "from", "QuestCompleted" }, { "localField", "QuestId" }, { "foreignField", "QuestId" }, { "as", "QuestCompleteds" } }); var addFields1 = new BsonDocument("$addFields", new BsonDocument("QuestCompletedCount", new BsonDocument("$size", "$QuestCompleteds"))); var lookupStage2 = new BsonDocument("$lookup", new BsonDocument { { "from", "CreatorPointHistory" }, { "localField", "QuestId" }, { "foreignField", "QuestId" }, { "pipeline", new BsonArray { new BsonDocument("$match", new BsonDocument("Kind", "QuestProfit")) } }, { "as", "ProfitHistories" } }); var addFields2 = new BsonDocument("$addFields", new BsonDocument("TotalProfit", new BsonDocument("$sum", "$ProfitHistories.Amount"))); var pipeline = new EmptyPipelineDefinition() .Match(filter) .AppendStage(lookupStage1) .AppendStage(addFields1) .AppendStage(lookupStage2) .AppendStage(addFields2) .Project(projection); return pipeline; } public static PipelineDefinition questBoardPipeline( IMongoCollection likeCollection, IMongoCollection bookmarkCollection, FilterDefinition filter, Expression> projection) { var pipeline = new EmptyPipelineDefinition() .Match(filter) .Lookup( likeCollection, x => x.QuestId, x => x.QuestId, x => x.Likes) .Lookup( bookmarkCollection, x => x.QuestId, x => x.QuestId, x => x.Bookmarks) .AppendStage("{$addFields: {LikeCount: {$size:'$Likes'}}}") .AppendStage("{$addFields: {BookmarkCount: {$size:'$Bookmarks'}}}") .Project(projection); return pipeline; } public static PipelineDefinition questBoardDetailPipeline( IMongoCollection likeCollection, IMongoCollection bookmarkCollection, FilterDefinition filter, Expression> projection) { var lookupStage1 = new BsonDocument("$lookup", new BsonDocument { { "from", "QuestAccepted" }, { "localField", "QuestId" }, { "foreignField", "QuestId" }, { "pipeline", new BsonArray { new BsonDocument("$match", new BsonDocument("Reason", "Player")) } }, { "as", "QuestAccepteds" } }); var lookupStage2 = new BsonDocument("$lookup", new BsonDocument { { "from", "QuestCompleted" }, { "localField", "QuestId" }, { "foreignField", "QuestId" }, { "as", "QuestCompleteds" } }); var pipeline = new EmptyPipelineDefinition() .Match(filter) .Lookup( likeCollection, x => x.QuestId, x => x.QuestId, x => x.Likes) .Lookup( bookmarkCollection, x => x.QuestId, x => x.QuestId, x => x.Bookmarks) .AppendStage(lookupStage1) .AppendStage(lookupStage2) .AppendStage("{$addFields: {LikeCount: {$size:'$Likes'}}}") .AppendStage("{$addFields: {BookmarkCount: {$size:'$Bookmarks'}}}") .AppendStage("{$addFields: {QuestAcceptedCount: {$size:'$QuestAccepteds'}}}") .AppendStage("{$addFields: {QuestCompletedCount: {$size:'$QuestCompleteds'}}}") .Project(projection); return pipeline; } }