using MongoDB.Driver; using ServerCommon; using ServerCore; using ServerBase; using UGQDataAccess.Logs; using UGQDataAccess.Repository; using UGQDataAccess.Repository.Models; using UGQDatabase.Models; namespace UGQDataAccess.Service; public class QuestEditorService { readonly QuestIdSequenceRepository _questIdSequenceRepository; readonly QuestContentRepository _questContentRepository; readonly QuestDialogRepository _questDialogRepository; readonly GameQuestDataRepository _gameQuestDataRepository; readonly CreatorPointHistoryRepository _creatorPointHistoryRepository; readonly NpcNameRepository _npcNameRepository; readonly UgqMetaGenerateService _ugqMetaGenerateService; readonly AccountService _accountService; public QuestEditorService( AccountService accountService, QuestIdSequenceRepository questIdSequenceRepository, QuestContentRepository questContentRepository, QuestDialogRepository questDialogRepository, GameQuestDataRepository gameQuestDataRepository, CreatorPointHistoryRepository creatorPointHistoryRepository, NpcNameRepository npcNameRepository, UgqMetaGenerateService ugqMetaGenerateService) { _accountService = accountService; _questIdSequenceRepository = questIdSequenceRepository; _questContentRepository = questContentRepository; _questDialogRepository = questDialogRepository; _gameQuestDataRepository = gameQuestDataRepository; _creatorPointHistoryRepository = creatorPointHistoryRepository; _npcNameRepository = npcNameRepository; _ugqMetaGenerateService = ugqMetaGenerateService; } public async Task getAllQuests(int pageNumber, int pageSize, QuestContentState state, string? searchText) { return await _questContentRepository.getAllSummaries(pageNumber, pageSize, state, searchText); } public async Task> getAll(string userGuid) { return await _questContentRepository.getAll(userGuid); } public async Task getAllCount() { return await _questContentRepository.getAllCount(); } public async Task getSummaries(int pageNumber, int pageSize, string userGuid, QuestContentState state, string? searchText) { return await _questContentRepository.getSummaries(pageNumber, pageSize, userGuid, state, searchText); } public async Task<(ServerErrorCode, QuestContentEntity?)> addQuest(string userGuid, UGQSaveQuestModel saveQuestModel, string adminUsername) { var accountEntity = await _accountService.getAccount(userGuid); if (accountEntity == null) return (ServerErrorCode.UgqNullEntity, null); List quests = await _questContentRepository.getAll(userGuid); int slotCount = ServerCommon.MetaHelper.GameConfigMeta.UGQDefaultSlot + accountEntity.AdditionalSlotCount; if (slotCount <= quests.Count) return (ServerErrorCode.UgqNotEnoughQuestSlot, null); var State = QuestContentState.Editable; foreach(var task in saveQuestModel.Tasks) { if(task.ActionId == 0) { State = QuestContentState.Uncomplate; break; } } QuestContentEntity entity = new() { QuestId = 0, Revision = 0, UserGuid = userGuid, Author = accountEntity.Nickname, BeaconId = saveQuestModel.BeaconId, UgcBeaconGuid = saveQuestModel.UgcBeaconGuid, UgcBeaconNickname = saveQuestModel.UgcBeaconNickname, GradeType = accountEntity.GradeType, Title = new TextEntity { Kr = saveQuestModel.Title.Kr, En = saveQuestModel.Title.En, Jp = saveQuestModel.Title.Jp, }, Langs = saveQuestModel.Languages.ToList(), Description = new TextEntity { Kr = saveQuestModel.Description.Kr, En = saveQuestModel.Description.En, Jp = saveQuestModel.Description.Jp, }, UploadCounter = 0, TitleImagePath = string.Empty, BannerImagePath = string.Empty, State = State, Cost = saveQuestModel.Cost, Tasks = saveQuestModel.Tasks.Select(x => new TaskEntity { GoalText = new TextEntity { Kr = x.GoalText.Kr, En = x.GoalText.En, Jp = x.GoalText.Jp, }, ActionId = x.ActionId, ActionValue = x.ActionValue, IsShowNpcLocation = x.IsShowNpcLocation, UgcActionValueGuid = x.UgcActionValueGuid, UgcActionValueName = x.UgcActionValueName, }).ToList(), Savelanguage = saveQuestModel.Savelanguage, ContentVersion = 1, CreatedAt = DateTime.UtcNow, UpdatedAt = DateTime.UtcNow, }; await _questContentRepository.insert(entity); List business_logs = [ new UgqApiQuestCraeteBusinessLog(entity.Id, adminUsername), ]; var log_action = new LogActionEx(LogActionType.UgqApiQuestCraete); UgqApiBusinessLogger.collectLogs(log_action, userGuid, business_logs); return (ServerErrorCode.Success, entity); } public async Task deleteQuest(string userGuid, string questContentId) { return await _questContentRepository.deleteQuest(userGuid, questContentId); } public async Task<(ServerErrorCode, QuestContentEntity?)> getQuest(string userGuid, string questContentId) { var entity = await _questContentRepository.get(questContentId); if (entity == null) return (ServerErrorCode.UgqNullEntity, null); if (entity?.UserGuid != userGuid) return (ServerErrorCode.UgqNotOwnQuest, null); return (ServerErrorCode.Success, entity); } public async Task<(ServerErrorCode, QuestContentEntity?)> editQuest(string userGuid, string questContentId) { (ServerErrorCode errorCode, var entity) = await getQuest(userGuid, questContentId); if (errorCode != ServerErrorCode.Success) return (errorCode, null); if (entity == null) return (ServerErrorCode.UgqNullEntity, null); if (entity.State != QuestContentState.Editable && entity.State != QuestContentState.Uncomplate) return (ServerErrorCode.UgqNotAllowEdit, null); return (ServerErrorCode.Success, entity); } public async Task<(ServerErrorCode, QuestContentEntity?)> saveQuest(string userGuid, string questContentId, UGQSaveQuestModel saveQuestModel) { int retryCount = 0; while (retryCount < UGQConstants.MAX_UPDATE_RETRY_COUNT) { (ServerErrorCode errorCode, var questContentEntity) = await getQuest(userGuid, questContentId); if (errorCode != ServerErrorCode.Success) return (errorCode, null); if (questContentEntity == null) return (ServerErrorCode.UgqNullEntity, null); if (questContentEntity.State != QuestContentState.Editable && questContentEntity.State != QuestContentState.Uncomplate) return (ServerErrorCode.UgqNotAllowEdit, null); List newDialogIds = saveQuestModel.Tasks .Where(x => string.IsNullOrEmpty(x.DialogId) == false) .Select(x => x.DialogId!).ToList(); HashSet oldDialogIds = questContentEntity.Tasks .Where(x => string.IsNullOrEmpty(x.DialogId) == false) .Select(x => x.DialogId!).ToHashSet(); // »õ·Î ¾µ taskÀÇ dialogId¸¦ ³»°¡ °¡Áö°í ÀÖ´Ù¸é Åë°ú foreach (var dialogId in newDialogIds) { if (oldDialogIds.Contains(dialogId) == false) return (ServerErrorCode.UgqDialogIsNotInTask, null); } int? contentVersion = questContentEntity.ContentVersion; var updated = await _questContentRepository.updateContent(questContentId, saveQuestModel, contentVersion); if (updated == null) { retryCount++; Log.getLogger().warn($"questContent version missmatch. retryCount: {retryCount}"); continue; } return (ServerErrorCode.Success, updated); } return (ServerErrorCode.UgqExceedTransactionRetry, null); } public async Task<(ServerErrorCode, int)> incUploadCounter(string userGuid, string questContentId) { return await _questContentRepository.incUploadCounter(questContentId); } public async Task<(ServerErrorCode, QuestContentEntity?)> updateQuestImages(string questContentId, string? titleImage, string? bannerImage) { if (titleImage == null && bannerImage == null) return (ServerErrorCode.UgqRequireImage, null); var updated = await _questContentRepository.updateImages(questContentId, titleImage, bannerImage); if (updated == null) return (ServerErrorCode.UgqNullEntity, null); return (ServerErrorCode.Success, updated); } public async Task> getQuestDialogs(IEnumerable questDialogIds) { return await _questDialogRepository.get(questDialogIds); } public async Task<(ServerErrorCode, QuestDialogEntity?)> getQuestDialog(string userGuid, string questContentId, string questDialogId) { (ServerErrorCode errorCode, var questContentEntity) = await getQuest(userGuid, questContentId); if (errorCode != ServerErrorCode.Success) return (errorCode, null); if (questContentEntity == null) return (errorCode, null); var task = questContentEntity.Tasks.Where(x => x.DialogId == questDialogId).FirstOrDefault(); if (task == null) return (ServerErrorCode.UgqDialogIsNotInTask, null); QuestDialogEntity? entity = await _questDialogRepository.get(questDialogId); if (entity == null) return (ServerErrorCode.UgqNullEntity, null); return (ServerErrorCode.Success, entity); } public async Task<(ServerErrorCode, QuestContentEntity?, QuestDialogEntity?)> addQuestDialog(string userGuid, string questContentId, int taskIndex, List sequences) { QuestDialogEntity dialogEntity = new() { Sequences = sequences, CreatedAt = DateTime.UtcNow, UpdatedAt = DateTime.UtcNow, }; await _questDialogRepository.add(dialogEntity); int retryCount = 0; while (retryCount < UGQConstants.MAX_UPDATE_RETRY_COUNT) { (ServerErrorCode errorCode, var questContentEntity) = await getQuest(userGuid, questContentId); if (errorCode != ServerErrorCode.Success) return (errorCode, null, null); if (questContentEntity == null) return (errorCode, null, null); List tasks = questContentEntity.Tasks; if (tasks.Count < taskIndex + 1) { int addCount = (taskIndex + 1) - tasks.Count; for (int i = 0; i < addCount; i++) { TaskEntity emptyTask = new TaskEntity(); tasks.Add(emptyTask); } } tasks[taskIndex].DialogId = dialogEntity.Id; int? contentVersion = questContentEntity.ContentVersion; var updated = await _questContentRepository.updateTasks(questContentId, tasks, contentVersion); if (updated == null) { retryCount++; Log.getLogger().warn($"questContent version missmatch. retryCount: {retryCount}"); continue; } return (ServerErrorCode.Success, updated, dialogEntity); } return (ServerErrorCode.UgqExceedTransactionRetry, null, null); } public async Task<(ServerErrorCode, QuestDialogEntity?)> saveQuestDialog(string userGuid, string questContentId, string questDialogId, List sequences) { (ServerErrorCode errorCode, var questContentEntity) = await getQuest(userGuid, questContentId); if (errorCode != ServerErrorCode.Success) return (errorCode, null); if (questContentEntity == null) return (errorCode, null); var task = questContentEntity.Tasks.Where(x => x.DialogId == questDialogId).FirstOrDefault(); if (task == null) return (ServerErrorCode.UgqDialogIsNotInTask, null); return await _questDialogRepository.updateSequences(questDialogId, sequences); } public async Task<(ServerErrorCode, QuestContentEntity?)> changeQuestStateForEditor(string userGuid, QuestContentEntity content, List? dialogs, IEnumerable before, QuestContentState after, string adminUsername) { QuestContentState fromState = content.State; QuestContentState toState = after; ServerErrorCode errorCode = ServerErrorCode.Success; QuestContentEntity? updated = null; switch (after) { case QuestContentState.Editable: updated = await _questContentRepository.updateState(content.Id, content.QuestId, content.Revision, before, after); break; case QuestContentState.Standby: { var gameQuestEntity = await _gameQuestDataRepository.setShutdown(content.QuestId, content.Revision); if (gameQuestEntity == null) return (ServerErrorCode.UgqNullEntity, null); updated = await _questContentRepository.updateState(content.Id, content.QuestId, content.Revision, before, after); } break; case QuestContentState.Test: { if (dialogs == null) return (ServerErrorCode.UgqNullEntity, null); var accountEntity = await _accountService.getAccount(userGuid); if (accountEntity == null) return (ServerErrorCode.UgqNullEntity, null); var beforeRevision = content.Revision; if (content.QuestId == 0) { content.QuestId = await _questIdSequenceRepository.getNextSequence(); content.Revision = 1; } else { content.Revision++; } var tasks = content.Tasks; var game_quest_dialog_data_entities = dialogs.Select(x => new GameQuestDialogDataEntity { Id = x.Id, Sequences = x.Sequences, }).ToList(); (var result, var quest_metas) = _ugqMetaGenerateService.generateUgqMeta(content); var ugq_game_quest_data_for_client_string = _ugqMetaGenerateService.makeUgqGameQuestDataForClients(content, after, game_quest_dialog_data_entities); var gameQuestEntity = await _gameQuestDataRepository.insert(content, QuestContentState.Test, game_quest_dialog_data_entities, quest_metas, ugq_game_quest_data_for_client_string, accountEntity.GradeType); if (gameQuestEntity == null) return (ServerErrorCode.UgqNullEntity, null); updated = await _questContentRepository.updateState(content.Id, content.QuestId, content.Revision, before, after, accountEntity.GradeType); if (updated != null&& beforeRevision != 0) await _gameQuestDataRepository.delete(content.QuestId, beforeRevision, QuestContentState.Test); } break; case QuestContentState.Live: { if (dialogs == null) return (ServerErrorCode.UgqNullEntity, null); bool toNextRevision = content.ToNextRevision ?? true; long beforeRevision = content.Revision; content.Revision = toNextRevision == true ? content.Revision + 1 : content.Revision; var tasks = content.Tasks; var game_quest_dialog_data_entities = dialogs.Select(x => new GameQuestDialogDataEntity { Id = x.Id, Sequences = x.Sequences, }).ToList(); (var result, var quest_metas) = _ugqMetaGenerateService.generateUgqMeta(content); var ugq_game_quest_data_for_client_string = _ugqMetaGenerateService.makeUgqGameQuestDataForClients(content, after, game_quest_dialog_data_entities); var gameQuestEntity = await _gameQuestDataRepository.insert(content, QuestContentState.Live, game_quest_dialog_data_entities, quest_metas, ugq_game_quest_data_for_client_string); if (gameQuestEntity == null) return (ServerErrorCode.UgqNullEntity, null); updated = await _questContentRepository.updateState(content.Id, content.QuestId, content.Revision, before, after); if(updated != null) await _gameQuestDataRepository.delete(content.QuestId, beforeRevision, QuestContentState.Test); } break; case QuestContentState.Shutdown: { await _gameQuestDataRepository.setShutdown(content.QuestId, content.Revision); updated = await _questContentRepository.updateState(content.Id, content.QuestId, content.Revision, before, after); } break; default: errorCode = ServerErrorCode.UgqStateChangeError; break; } if (updated == null) return (ServerErrorCode.UgqStateChangeError, null); if(errorCode == ServerErrorCode.Success) { List business_logs = [ new UgqApiChangeStateBusinessLog(updated.Id, fromState.ToString(), toState.ToString(), updated.QuestId, updated.Revision, adminUsername), ]; var log_action = new LogActionEx(LogActionType.UgqApiChangeState); UgqApiBusinessLogger.collectLogs(log_action, userGuid, business_logs); } return (errorCode, updated); } public async Task getCreatorPointHistories(string userGuid, int pageNumber, int pageSize, CreatorPointHistoryKind kind, DateTimeOffset startDate, DateTimeOffset endDate) { return await _creatorPointHistoryRepository.getList(userGuid, pageNumber, pageSize, kind, startDate, endDate); } public async Task getQuestProfitStats(string userGuid, int pageNumber, int pageSize) { return await _questContentRepository.getQuestProfitStats(userGuid, pageNumber, pageSize); } public async Task gameQuestDataFakeUpdate(string userGuid, string questContentId) { (ServerErrorCode errorCode, var questContentEntity) = await getQuest(userGuid, questContentId); if (errorCode != ServerErrorCode.Success) return; if (questContentEntity == null) return; var questDialogIds = questContentEntity.Tasks .Where(x => string.IsNullOrEmpty(x.DialogId) == false) .Select(x => x.DialogId!) .ToList(); var questDialogs = await getQuestDialogs(questDialogIds); var tasks = questContentEntity.Tasks; var game_quest_dialog_data_entities = questDialogs.Select(x => new GameQuestDialogDataEntity { Id = x.Id, Sequences = x.Sequences, }).ToList(); (var result, var quest_metas) = _ugqMetaGenerateService.generateUgqMeta(questContentEntity); var ugq_game_quest_data_for_client = _ugqMetaGenerateService.makeUgqGameQuestDataForClients(questContentEntity, questContentEntity.State, game_quest_dialog_data_entities); var gameQuestEntity = await _gameQuestDataRepository.insert(questContentEntity, QuestContentState.Test, game_quest_dialog_data_entities, quest_metas, ugq_game_quest_data_for_client); } }