using Microsoft.Extensions.Options; using MongoDB.Driver; using UGQDatabase.Models; using UGQDataAccess.Settings; using UGQDataAccess.Repository.Models; using Amazon.SecurityToken.Model; using Microsoft.AspNetCore.Mvc.RazorPages; using UGQDataAccess.Repository.Query; using System.Linq.Expressions; using ServerCommon.UGQ; using ServerCommon.UGQ.Models; using UGQDataAccess; using MongoDB.Bson; using MongoDB.Bson.Serialization; using static System.Net.Mime.MediaTypeNames; namespace UGQDataAccess.Repository; public class QuestContentRepository : BaseRepository { internal enum SpotlightSort { MostLiked, MostBookmarked, } internal class SportlightQuery { internal SpotlightSort Sort { get; set; } internal DateRange DateRange { get; set; } internal List Items { get; set; } = new(); internal SportlightQuery(SpotlightSort sort, DateRange dateRange) { Sort = sort; DateRange = dateRange; } } private const string CollectionName = "QuestContent"; public QuestContentRepository(IMongoClient mongoClient, IOptions settings) : base(mongoClient, settings.Value.DatabaseName, CollectionName) { } public async Task insert(QuestContentEntity entity) { await Collection.InsertOneAsync(entity); return entity; } public async Task> getAll(string userGuid) { var builder = Builders.Filter; var filter = builder.Eq(x => x.IsDeleted, false); filter &= builder.Eq(x => x.UserGuid, userGuid); var result = await Collection.Find(filter).ToListAsync(); return result; } public async Task getAllCount() { var builder = Builders.Filter; var filter = builder.Eq(x => x.IsDeleted, false); var result = await Collection.Find(filter).CountDocumentsAsync(); return result; } public async Task> getUserQuests(string userGuid, IEnumerable states) { var builder = Builders.Filter; var filter = builder.Eq(x => x.IsDeleted, false); filter = builder.Eq(x => x.UserGuid, userGuid) & builder.In(x => x.State, states); return await Collection.Find(filter).ToListAsync(); } public async Task getSummaries(int pageNumber, int pageSize, string userGuid, QuestContentState state, string? searchText) { var accountCollection = _mongoDatabase.GetCollection("Account"); var likeCollection = _mongoDatabase.GetCollection("Like"); var bookmarkCollection = _mongoDatabase.GetCollection("Bookmark"); var npcNameCollection = _mongoDatabase.GetCollection("NpcName"); pageNumber = pageNumber < 1 ? 1 : pageNumber; var filter = await QuestContentQuery.summariesFilter(userGuid, UgqSearchType.Title, searchText, state, accountCollection, npcNameCollection); var countFacet = AggregateFacet.Create("count", new EmptyPipelineDefinition() .Count() ); var pagingFacet = AggregateFacet.Create("paging", new EmptyPipelineDefinition() .Skip((pageNumber - 1) * pageSize) .Limit(pageSize) ); var pipeline = QuestContentQuery.summariesPipeline( likeCollection, bookmarkCollection, filter, QuestContentQuery.SummaryItemResultProjection) .Facet(countFacet, pagingFacet); var aggregation = await (await Collection.AggregateAsync(pipeline)).ToListAsync(); var count = aggregation.First() .Facets.First(x => x.Name == "count") .Output() ?.FirstOrDefault() ?.Count; var paging = aggregation.First() .Facets.First(x => x.Name == "paging") .Output(); int totalPages = 0; if (count != null) totalPages = (int)Math.Ceiling((double)count / pageSize); var result = await (await Collection.AggregateAsync(pipeline)).ToListAsync(); return new SummaryQueryResult { PageNumber = pageNumber, PageSize = pageSize, TotalPages = totalPages, Items = paging.ToList(), }; } public async Task getAllSummaries(int pageNumber, int pageSize, QuestContentState state, string? searchText) { var accountCollection = _mongoDatabase.GetCollection("Account"); var likeCollection = _mongoDatabase.GetCollection("Like"); var bookmarkCollection = _mongoDatabase.GetCollection("Bookmark"); var npcNameCollection = _mongoDatabase.GetCollection("NpcName"); pageNumber = pageNumber < 1 ? 1 : pageNumber; var filter = await QuestContentQuery.summariesFilter(null, UgqSearchType.Title, searchText, state, accountCollection, npcNameCollection); var countFacet = AggregateFacet.Create("count", new EmptyPipelineDefinition() .Count() ); var pagingFacet = AggregateFacet.Create("paging", new EmptyPipelineDefinition() .Skip((pageNumber - 1) * pageSize) .Limit(pageSize) ); var pipeline = QuestContentQuery.summariesPipeline( likeCollection, bookmarkCollection, filter, QuestContentQuery.SummaryItemResultProjection) .Facet(countFacet, pagingFacet); var aggregation = await (await Collection.AggregateAsync(pipeline)).ToListAsync(); var count = aggregation.First() .Facets.First(x => x.Name == "count") .Output() ?.FirstOrDefault() ?.Count; var paging = aggregation.First() .Facets.First(x => x.Name == "paging") .Output(); int totalPages = 0; if (count != null) totalPages = (int)Math.Ceiling((double)count / pageSize); return new AllSummaryQueryResult { PageNumber = pageNumber, PageSize = pageSize, TotalPages = totalPages, Items = paging.ToList(), }; } public async Task get(string id) { var builder = Builders.Filter; var filter = builder.Eq(x => x.IsDeleted, false); filter &= builder.Eq(x => x.Id, id); return await Collection.Find(filter).FirstOrDefaultAsync(); } public async Task getByQuestId(long questId) { var builder = Builders.Filter; var filter = builder.Eq(x => x.IsDeleted, false); filter &= builder.Eq(x => x.QuestId, questId); return await Collection.Find(filter).FirstOrDefaultAsync(); } public async Task getByQuestIdRevision(long questId, long revision) { var builder = Builders.Filter; var filter = builder.Eq(x => x.IsDeleted, false); filter &= builder.Eq(x => x.QuestId, questId) & builder.Eq(x => x.Revision, revision); return await Collection.Find(filter).FirstOrDefaultAsync(); } public async Task updateContent(string id, UGQSaveQuestModel model, int? contentVersion) { var builder = Builders.Filter; var filter = builder.Eq(x => x.IsDeleted, false); filter &= builder.Eq(x => x.Id, id) & builder.Eq(x => x.ContentVersion, contentVersion); var State = QuestContentState.Editable; foreach (var task in model.Tasks) { if (task.ActionId == 0) { State = QuestContentState.Uncomplate; break; } } var update = Builders.Update .Set(x => x.BeaconId, model.BeaconId) .Set(x => x.UgcBeaconGuid, model.UgcBeaconGuid) .Set(x => x.UgcBeaconNickname, model.UgcBeaconNickname) .Set(x => x.Title, model.Title) .Set(x => x.Langs, model.Languages) .Set(x => x.Savelanguage, model.Savelanguage) .Set(x => x.Description, model.Description) .Set(x => x.Cost, model.Cost) .Set(x => x.Tasks, model.Tasks) .Set(x => x.State, State) .Inc(x => x.ContentVersion, 1) .Set(x => x.UpdatedAt, DateTime.UtcNow); var options = new FindOneAndUpdateOptions { ReturnDocument = ReturnDocument.After, }; return await Collection.FindOneAndUpdateAsync(filter, update, options); } public async Task updateTasks(string id, List tasks, int? contentVersion) { var builder = Builders.Filter; var filter = builder.Eq(x => x.IsDeleted, false); filter &= builder.Eq(x => x.Id, id) & builder.Eq(x => x.ContentVersion, contentVersion); var update = Builders.Update .Set(x => x.Tasks, tasks) .Inc(x => x.ContentVersion, 1) .Set(x => x.UpdatedAt, DateTime.UtcNow); var options = new FindOneAndUpdateOptions { ReturnDocument = ReturnDocument.After, }; return await Collection.FindOneAndUpdateAsync(filter, update, options); } public async Task updateImages(string id, string? titleImage, string? bannerImage) { var builder = Builders.Filter; var filter = builder.Eq(x => x.IsDeleted, false); filter &= builder.Eq(x => x.Id, id); var updateBuilder = Builders.Update; var update = updateBuilder.Set(x => x.UpdatedAt, DateTime.UtcNow); if (titleImage != null) update = updateBuilder.Combine(update, updateBuilder.Set(x => x.TitleImagePath, titleImage)); if (bannerImage != null) update = updateBuilder.Combine(update, updateBuilder.Set(x => x.BannerImagePath, bannerImage)); var options = new FindOneAndUpdateOptions { ReturnDocument = ReturnDocument.After, }; return await Collection.FindOneAndUpdateAsync(filter, update, options); } public async Task updateState(string id, long questId, long revision, IEnumerable before, QuestContentState after, UgqGradeType? gradeType = null) { var builder = Builders.Filter; var filter = builder.Eq(x => x.IsDeleted, false); filter &= builder.Eq(x => x.Id, id) & builder.In(x => x.State, before); // Test->Standby °¥ ¶§ toNextRevisionÀ» true·Î ÇØ¼­ Live °¥ ¶§ Revision + 1 ó¸® ÇÑ´Ù. bool toNextRevision = before.Any(x => x == QuestContentState.Test) && (after == QuestContentState.Standby); var updateBuilder = Builders.Update; var update = updateBuilder .Set(x => x.UpdatedAt, DateTime.UtcNow) .Set(x => x.QuestId, questId) .Set(x => x.Revision, revision) .Set(x => x.State, after) .Set(x => x.ToNextRevision, toNextRevision); if (gradeType != null) update = updateBuilder.Combine(update, updateBuilder.Set(x => x.GradeType, gradeType)); var options = new FindOneAndUpdateOptions { ReturnDocument = ReturnDocument.After, }; return await Collection.FindOneAndUpdateAsync(filter, update, options); } public async Task<(ServerErrorCode, int)> incUploadCounter(string questContentId) { var builder = Builders.Filter; var filter = builder.Eq(x => x.IsDeleted, false); filter &= builder.Eq(x => x.Id, questContentId); var update = Builders.Update .Inc(x => x.UploadCounter, 1) .Set(x => x.UpdatedAt, DateTime.UtcNow); var options = new FindOneAndUpdateOptions() { ReturnDocument = ReturnDocument.After }; var updated = await Collection.FindOneAndUpdateAsync(filter, update, options); return (ServerErrorCode.Success, updated.UploadCounter); } public async Task deleteQuest(string userGuid, string questContentId) { var builder = Builders.Filter; var filter = builder.Eq(x => x.IsDeleted, false); filter &= builder.Eq(x => x.UserGuid, userGuid) & builder.Eq(x => x.Id, questContentId); var update = Builders.Update .Set(x => x.UpdatedAt, DateTime.UtcNow) .Set(x => x.DeletedAt, DateTime.UtcNow) .Set(x => x.IsDeleted, true); var result = await Collection.UpdateOneAsync(filter, update); if(result.ModifiedCount == 0) return ServerErrorCode.UgqNullEntity; return ServerErrorCode.Success; } public async Task getQuestBoard(string? userGuid, UgqQuestBoardRequest request, int pageSize) { var accountCollection = _mongoDatabase.GetCollection("Account"); var likeCollection = _mongoDatabase.GetCollection("Like"); var bookmarkCollection = _mongoDatabase.GetCollection("Bookmark"); var npcNameCollection = _mongoDatabase.GetCollection("NpcName"); request.PageNumber = request.PageNumber < 1 ? 1 : request.PageNumber; var filter = await QuestContentQuery.questBoardFilter(userGuid, request.GradeType, request.SearchType, request.SearchText, accountCollection, npcNameCollection); var sort = QuestContentQuery.questBoardSort(request.SortType); var countFacet = AggregateFacet.Create("count", new EmptyPipelineDefinition() .Count() ); var pagingFacet = AggregateFacet.Create("paging", new EmptyPipelineDefinition() .Sort(sort) .Skip((request.PageNumber - 1) * pageSize) .Limit(pageSize) ); var pipeline = QuestContentQuery.questBoardPipeline( likeCollection, bookmarkCollection, filter, QuestContentQuery.QuestBoardItemResultProjection) .Facet(countFacet, pagingFacet); var aggregation = await (await Collection.AggregateAsync(pipeline)).ToListAsync(); var count = aggregation.First() .Facets.First(x => x.Name == "count") .Output() ?.FirstOrDefault() ?.Count; var paging = aggregation.First() .Facets.First(x => x.Name == "paging") .Output(); int totalPages = 0; if(count != null) totalPages = (int)Math.Ceiling((double)count / pageSize); return new QuestBoardQueryResult { PageNumber = request.PageNumber, PageSize = pageSize, TotalPages = totalPages, Items = paging.ToList(), }; } public async Task getQuestBoardDetail(string userGuid, long questId, long revision) { var likeCollection = _mongoDatabase.GetCollection("Like"); var bookmarkCollection = _mongoDatabase.GetCollection("Bookmark"); var filterBuilder = Builders.Filter; var filter = filterBuilder.Eq(x => x.IsDeleted, false); filter &= filterBuilder.Eq(x => x.QuestId, questId); filter &= filterBuilder.Eq(x => x.Revision, revision); filter &= filterBuilder.Eq(x => x.State, QuestContentState.Live); Expression> questBoardDetailItemProjection = x => new QuestBoardDetailItemResult { QuestId = x.QuestId, Revision = x.Revision, Author = x.Author, Title = x.Title, Languages = x.Langs, Description = x.Description, Cost = x.Cost, GradeType = x.GradeType, TitleImagePath = x.TitleImagePath, BannerImagePath = x.BannerImagePath, UpdatedAt = x.UpdatedAt, LikeCount = x.LikeCount, BookmarkCount = x.BookmarkCount, QuestAcceptedCount = x.QuestAcceptedCount, QuestCompletedCount = x.QuestCompletedCount, BeaconId = x.BeaconId, UgcBeaconGuid = x.UgcBeaconGuid, UgcBeaconNickname = x.UgcBeaconNickname, Liked = x.Likes.Any(x => x.UserGuid == userGuid), Bookmarked = x.Bookmarks.Any(x => x.UserGuid == userGuid), }; var pipeline = QuestContentQuery.questBoardDetailPipeline( likeCollection, bookmarkCollection, filter, questBoardDetailItemProjection); return await (await Collection.AggregateAsync(pipeline)).FirstOrDefaultAsync(); } public async Task getBookmarkQuests(string userGuid, UgqQuestBoardRequest request, int pageSize) { var likeCollection = _mongoDatabase.GetCollection("Like"); var bookmarkCollection = _mongoDatabase.GetCollection("Bookmark"); var npcNameCollection = _mongoDatabase.GetCollection("NpcName"); request.PageNumber = request.PageNumber < 1 ? 1 : request.PageNumber; var filter = await QuestContentQuery.questBoardBookmarkFilter(userGuid, request.GradeType, request.SearchType, request.SearchText, npcNameCollection, bookmarkCollection); var sort = QuestContentQuery.questBoardSort(request.SortType); var countFacet = AggregateFacet.Create("count", new EmptyPipelineDefinition() .Count() ); var pagingFacet = AggregateFacet.Create("paging", new EmptyPipelineDefinition() .Sort(sort) .Skip((request.PageNumber - 1) * pageSize) .Limit(pageSize) ); var pipeline = QuestContentQuery.questBoardPipeline( likeCollection, bookmarkCollection, filter, QuestContentQuery.QuestBoardItemResultProjection) .Facet(countFacet, pagingFacet); var aggregation = await (await Collection.AggregateAsync(pipeline)).ToListAsync(); var count = aggregation.First() .Facets.First(x => x.Name == "count") .Output() ?.FirstOrDefault() ?.Count; var paging = aggregation.First() .Facets.First(x => x.Name == "paging") .Output(); int totalPages = 0; if (count != null) totalPages = (int)Math.Ceiling((double)count / pageSize); return new QuestBoardQueryResult { PageNumber = request.PageNumber, PageSize = pageSize, TotalPages = totalPages, Items = paging.ToList(), }; } public async Task getQuestBoardSpotlight() { var accountCollection = _mongoDatabase.GetCollection("Account"); var likeCollection = _mongoDatabase.GetCollection("Like"); var bookmarkCollection = _mongoDatabase.GetCollection("Bookmark"); SportlightQuery[] queries = [ new SportlightQuery(SpotlightSort.MostLiked, DataRangeHelper.Yesterday(DateTime.Now)), new SportlightQuery(SpotlightSort.MostBookmarked, DataRangeHelper.Yesterday(DateTime.Now)), new SportlightQuery(SpotlightSort.MostLiked, DataRangeHelper.LastWeek(DateTime.Now)), new SportlightQuery(SpotlightSort.MostBookmarked, DataRangeHelper.LastWeek(DateTime.Now)), new SportlightQuery(SpotlightSort.MostLiked, DataRangeHelper.LastMonth(DateTime.Now)), new SportlightQuery(SpotlightSort.MostBookmarked, DataRangeHelper.LastMonth(DateTime.Now)), ]; var filterBuilder = Builders.Filter; var filter = filterBuilder.Eq(x => x.IsDeleted, false); foreach (var (value, i) in queries.Select((value, i) => (value, i))) { int limit = 1; switch(value.Sort) { case SpotlightSort.MostLiked: { var sort = Builders.Sort.Descending("LikeCount").Descending("CreatedAt"); var pipeline = QuestContentQuery.questBoardPipeline( likeCollection, bookmarkCollection, filter, QuestContentQuery.QuestBoardItemResultProjection) .Sort(sort) .Limit(limit); value.Items = await (await Collection.AggregateAsync(pipeline)).ToListAsync(); } break; case SpotlightSort.MostBookmarked: { var sort = Builders.Sort.Descending("BookmarkCount").Descending("CreatedAt"); var pipeline = QuestContentQuery.questBoardPipeline( likeCollection, bookmarkCollection, filter, QuestContentQuery.QuestBoardItemResultProjection) .Sort(sort) .Limit(limit); value.Items = await (await Collection.AggregateAsync(pipeline)).ToListAsync(); } break; } } /* // Áߺ¹ Á¦°Å foreach (var (value, i) in queries.Select((value, i) => (value, i))) { if (value.Items.Count == 0) continue; long questId = value.Items[0].QuestId; long revision = value.Items[0].Revision; for (int s = i + 1; s < queries.Length; s++) { queries[s].Items.RemoveAll(x => x.QuestId == questId && x.Revision == revision); } } */ return new QuestBoardSportlightQueryResult { MostLiked = new() { Today = queries[0].Items.FirstOrDefault(), ThisWeek = queries[2].Items.FirstOrDefault(), ThisMonth = queries[4].Items.FirstOrDefault(), }, MostBookmarked = new() { Today = queries[1].Items.FirstOrDefault(), ThisWeek = queries[3].Items.FirstOrDefault(), ThisMonth = queries[5].Items.FirstOrDefault(), }, }; } public async Task getQuestProfitStats(string userGuid, int pageNumber, int pageSize) { var accountCollection = _mongoDatabase.GetCollection("Account"); var likeCollection = _mongoDatabase.GetCollection("Like"); var bookmarkCollection = _mongoDatabase.GetCollection("Bookmark"); var npcNameCollection = _mongoDatabase.GetCollection("NpcName"); var questAcceptedCollection = _mongoDatabase.GetCollection("QuestAccepted"); var questCompletedCollection = _mongoDatabase.GetCollection("QuestCompleted"); var questAbortedCollection = _mongoDatabase.GetCollection("QuestAborted"); var creatorPointHistoryCollection = _mongoDatabase.GetCollection("CreatorPointHistory"); pageNumber = pageNumber < 1 ? 1 : pageNumber; var filterBuilder = Builders.Filter; var filter = filterBuilder.Eq(x => x.IsDeleted, false); filter &= filterBuilder.Eq(x => x.UserGuid, userGuid); var countFacet = AggregateFacet.Create("count", new EmptyPipelineDefinition() .Count() ); var pagingFacet = AggregateFacet.Create("paging", new EmptyPipelineDefinition() .Skip((pageNumber - 1) * pageSize) .Limit(pageSize) ); var pipeline = QuestContentQuery.questProfitStatsPipeline( filter, QuestContentQuery.QuestProfitStatsResultProjection) .Facet(countFacet, pagingFacet); var aggregation = await (await Collection.AggregateAsync(pipeline)).ToListAsync(); var count = aggregation.First() .Facets.First(x => x.Name == "count") .Output() ?.FirstOrDefault() ?.Count; var paging = aggregation.First() .Facets.First(x => x.Name == "paging") .Output(); int totalPages = 0; if (count != null) totalPages = (int)Math.Ceiling((double)count / pageSize); return new QuestProfitStatsQueryResult { PageNumber = pageNumber, PageSize = pageSize, TotalPages = totalPages, Items = paging.ToList(), }; } }