using Asp.Versioning; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using ServerCommon; using UGQApiServer.Models; using UGQDataAccess.Repository; using UGQDataAccess.Service; using UGQDatabase.Models; using MySqlConnector; using ServerCore; using ServerBase; using UGQApiServer.UGQData; using Amazon.DynamoDBv2; using Amazon.DynamoDBv2.DataModel; using Amazon.DynamoDBv2.DocumentModel; using Amazon.DynamoDBv2.Model; using MongoDB.Bson; using System.Text.Json.Nodes; using StackExchange.Redis; using UGQApiServer.Validation; using ServerCommon.BusinessLogDomain; using UGQApiServer.Auth; namespace UGQApiServer.Controllers { #if DEBUG [ApiController] [ApiVersion("1.0")] [ApiExplorerSettings(GroupName = "development")] [Route("api/v{version:apiVersion}/[controller]")] [DevelopmentOnly] public class DevelopmentController : ControllerBase { DynamoDbClient _dynamoDbClient; AccountService _accountService; QuestEditorService _questEditorService; InGameService _inGameService; readonly string _ssoAccountDb; UGQBannerImageList _ugqbannerImageList; IConnectionMultiplexer _redis; UGQMetaData _ugqMetaData; AuthSql _authSql; public DevelopmentController(DynamoDbClient dynamoDbClient, AccountService accountService, QuestEditorService questEditorService, InGameService inGameService, IConfiguration configuration, UGQBannerImageList ugqbannerImageList, IConnectionMultiplexer redis, UGQMetaData ugqMetaData, AuthSql authSql) { _dynamoDbClient = dynamoDbClient; _accountService = accountService; _questEditorService = questEditorService; _inGameService = inGameService; _ssoAccountDb = configuration["SSOAccount:SsoAccountDb"] ?? ""; _ugqbannerImageList = ugqbannerImageList; _redis = redis; _ugqMetaData = ugqMetaData; _authSql = authSql; } string? userGuidFromToken() { return User.Claims.FirstOrDefault(c => c.Type == "Id")?.Value; } async Task<(ServerErrorCode, AccountBaseAttrib?)> insertAccount(string loginAccountId) { var account_doc = new AccountBaseDoc(loginAccountId); string newUserGuid = System.Guid.NewGuid().ToString("N"); var account_base_attrib = account_doc.getAttrib(); NullReferenceCheckHelper.throwIfNull(account_base_attrib, () => $"account_base_attrib is null !!!"); account_base_attrib.AccountId = loginAccountId; account_base_attrib.UserGuid = newUserGuid; account_base_attrib.Password = "1234"; account_base_attrib.LanguageType = LanguageType.Ko; account_base_attrib.AuthAdminLevelType = AuthAdminLevelType.None; account_base_attrib.AccountCreationType = AccountCreationType.None; account_base_attrib.AccountCreationMetaId = 0; account_base_attrib.LoginDateTime = DateTime.MinValue; account_base_attrib.LogoutDateTime = DateTime.MinValue; account_base_attrib.CreatedDateTime = DateTimeHelper.Current; var result = await account_doc.newDoc4Query(); if(result.isFail()) { return (result.getErrorCode(), null); } (result, var account_document) = await account_doc.onCopyToDocument(); if (result.isFail()) { return (result.getErrorCode(), null); } var table = _dynamoDbClient.getTableByDoc(); (result, _) = await table.simpleUpsertDocument(account_document); if (result.isFail()) { return (result.getErrorCode(), null); } return (ServerErrorCode.Success, account_base_attrib); } async Task<(ServerErrorCode, NicknameAttrib?)> insertNickname(string userGuid, string nickname) { var nickname_doc = new NicknameDoc(OwnerEntityType.User, userGuid, string.Empty); var nickname_doc_doc_attrib = nickname_doc.getAttrib(); NullReferenceCheckHelper.throwIfNull(nickname_doc_doc_attrib, () => $"nickname_doc_doc_attrib is null !!!"); nickname_doc_doc_attrib.Nickname = nickname; var result = await nickname_doc.newDoc4Query(); if (result.isFail()) { return (result.getErrorCode(), null); } (result, var document) = await nickname_doc.onCopyToDocument(); if (result.isFail()) { return (result.getErrorCode(), null); } var table = _dynamoDbClient.getTableByDoc(); (result, _) = await table.simpleUpsertDocument(document); if (result.isFail()) { return (result.getErrorCode(), null); } return (ServerErrorCode.Success, nickname_doc_doc_attrib); } async Task<(ServerErrorCode, MoneyAttrib?)> insertMoney(string userGuid) { var money_doc = new MoneyDoc(OwnerEntityType.User, userGuid); var money_doc_doc_attrib = money_doc.getAttrib(); NullReferenceCheckHelper.throwIfNull(money_doc_doc_attrib, () => $"money_doc_doc_attrib is null !!!"); money_doc_doc_attrib.Gold = 0; money_doc_doc_attrib.Sapphire = 0; money_doc_doc_attrib.Calium = 0; money_doc_doc_attrib.Ruby = 0; var result = await money_doc.newDoc4Query(); if (result.isFail()) { return (result.getErrorCode(), null); } (result, var money_document) = await money_doc.onCopyToDocument(); if (result.isFail()) { return (result.getErrorCode(), null); } var table = _dynamoDbClient.getTableByDoc(); (result, _) = await table.simpleUpsertDocument(money_document); if (result.isFail()) { return (result.getErrorCode(), null); } return (ServerErrorCode.Success, money_doc_doc_attrib); } /// /// email로 계정 정보 얻기 /// [HttpGet] [Route("get-gamedb-account-by-email")] public async Task getGameDBAccountByEmail([FromQuery] string email) { ulong accountId = await _authSql.getAccountIdFromMysql(email); if (accountId == 0) { return Results.Ok(new GetGameDBAccountByEmail { Message = $"{email}이 인증 DB에 없음" }); } string loginAccountId = accountId.ToString(); (Result result, UserByAccountIdResult? gameAccount) = await EntitySearchHelper.findUserByAccountId(_dynamoDbClient, loginAccountId, ""); if (result.isFail() && result.ErrorCode != ServerErrorCode.DynamoDbQueryNoMatchAttribute) return Results.BadRequest(ApiResponseFactory.error(result.ErrorCode)); if (gameAccount == null) { return Results.Ok(new GetGameDBAccountByEmail { Message = $"{email}이 게임 DB에 없음", LoginAccountId = loginAccountId, }); } else { return Results.Ok(new GetGameDBAccountByEmail { Message = $"성공", LoginAccountId = loginAccountId, UserGuid = gameAccount.UserGuid, Nickname = gameAccount.Nickname, }); } } /// /// email로 DynamoDB에 AccountBaseDoc, NicknameDoc을 생성 /// /// /// 주의! - 게임 클라이언트를 사용할 수 없는 경우에만 api로 계정을 만드십시오! /// UGQ api를 사용하기 위한 최소한의 세팅으로 계정을 만드는 거라 /// 이 api로 만든 계정으로 게임 접속 시에는 정상적으로 작동하지 않습니다. /// [HttpPost] [Route("add-gamedb-account-by-email")] public async Task addGameDBAccountByEmail(AddGameDBAccountByEmailRequest request) { ulong accountId = await _authSql.getAccountIdFromMysql(request.Email); if (accountId == 0) return Results.BadRequest($"{request.Email} not found"); string loginAccountId = accountId.ToString(); (Result result, UserByAccountIdResult? gameAccount) = await EntitySearchHelper.findUserByAccountId(_dynamoDbClient, loginAccountId, ""); if (result.isFail() && result.ErrorCode != ServerErrorCode.DynamoDbQueryNoMatchAttribute) return Results.BadRequest(ApiResponseFactory.error(result.ErrorCode)); if (gameAccount != null) { return Results.Ok(new AddGameDBAccountResponse { LoginAccountId = loginAccountId, UserGuid = gameAccount.UserGuid, Nickname = gameAccount.Nickname, }); } (var errrorCode, AccountBaseAttrib? account_doc_doc_attrib) = await insertAccount(loginAccountId); if (account_doc_doc_attrib == null) return Results.BadRequest(ApiResponseFactory.error(errrorCode)); (errrorCode, NicknameAttrib? nickname_doc_doc_attrib) = await insertNickname(account_doc_doc_attrib.UserGuid, request.Nickname); if (nickname_doc_doc_attrib == null) return Results.BadRequest(ApiResponseFactory.error(errrorCode)); return Results.Ok(new AddGameDBAccountResponse { LoginAccountId = account_doc_doc_attrib.AccountId, UserGuid = account_doc_doc_attrib.UserGuid, Nickname = nickname_doc_doc_attrib.Nickname, }); } /// /// loginAccountId로 DynamoDB에 AccountBaseDoc, NicknameDoc을 생성 /// /// /// 주의! - 게임 클라이언트를 사용할 수 없는 경우에만 api로 계정을 만드십시오! /// UGQ api를 사용하기 위한 최소한의 세팅으로 계정을 만드는 거라 /// 이 api로 만든 계정으로 게임 접속 시에는 정상적으로 작동하지 않습니다. /// [HttpPost] [Route("add-gamedb-account")] public async Task addGameDBAccount(AddGameDBAccountRequest request) { (Result result, UserByAccountIdResult? gameAccount) = await EntitySearchHelper.findUserByAccountId(_dynamoDbClient, request.LoginAccountId, ""); if (result.isFail() && result.ErrorCode != ServerErrorCode.DynamoDbQueryNoMatchAttribute) return Results.BadRequest(ApiResponseFactory.error(result.ErrorCode)); if (gameAccount != null) { return Results.Ok(new AddGameDBAccountResponse { LoginAccountId = request.LoginAccountId, UserGuid = gameAccount.UserGuid, Nickname = gameAccount.Nickname, }); } (var errrorCode, AccountBaseAttrib? account_doc_doc_attrib) = await insertAccount(request.LoginAccountId); if (account_doc_doc_attrib == null) return Results.BadRequest(ApiResponseFactory.error(errrorCode)); (errrorCode, NicknameAttrib? nickname_doc_doc_attrib) = await insertNickname(account_doc_doc_attrib.UserGuid, request.Nickname); if (nickname_doc_doc_attrib == null) return Results.BadRequest(ApiResponseFactory.error(errrorCode)); return Results.Ok(new AddGameDBAccountResponse { LoginAccountId = account_doc_doc_attrib.AccountId, UserGuid = account_doc_doc_attrib.UserGuid, Nickname = nickname_doc_doc_attrib.Nickname, }); } /// /// DynamoDB에 UgcNpcDoc을 생성 /// /// /// 주의! - 게임 클라이언트를 사용할 수 없는 경우에만 api로 UgcNpc를 만드십시오! /// UGQ api를 사용하기 위한 최소한의 세팅으로 UgcNpc를 만드는 거라 /// 이 api로 만든 UgcNpc는 게임 접속 시에는 정상적으로 작동하지 않을 수 잇습니다. /// [HttpPost] [Route("add-fake-ugc-npc")] public async Task addFakeUgcNpc(AddFakeUgcNpcReqeust request) { string newUgcNpcGUid = System.Guid.NewGuid().ToString("N"); var ugc_npc_doc = new UgcNpcDoc(OwnerEntityType.User, request.UserGuid, newUgcNpcGUid); var ugc_npc_attrib = ugc_npc_doc.getAttrib(); if (ugc_npc_attrib == null) return Results.BadRequest(ApiResponseFactory.error(ServerErrorCode.UgqServerException)); ugc_npc_attrib.Nickname = request.UgcNpcNickname; ugc_npc_attrib.Title = "empty"; ugc_npc_attrib.Greeting = "empty"; ugc_npc_attrib.Introduction = "empty"; ugc_npc_attrib.Description = "empty"; ugc_npc_attrib.WorldScenario = "empty"; ugc_npc_attrib.DefaultSocialActionMetaId = 0; ugc_npc_attrib.HabitSocialActionMetaIds = []; ugc_npc_attrib.DialogueSocialActionMetaIds = []; ugc_npc_attrib.BodyItemMetaId = 0; ugc_npc_attrib.HashTagMetaIds = []; ugc_npc_attrib.IsRegisteredAiChatServer = false; var result = await ugc_npc_doc.newDoc4Query(); if (result.isFail()) { return Results.BadRequest(ApiResponseFactory.error(result.getErrorCode())); } (result, var ugq_npc_document) = await ugc_npc_doc.onCopyToDocument(); if (result.isFail()) { return Results.BadRequest(ApiResponseFactory.error(result.getErrorCode())); } var table = _dynamoDbClient.getTableByDoc(); (result, _) = await table.simpleUpsertDocument(ugq_npc_document); if (result.isFail()) { return Results.BadRequest(ApiResponseFactory.error(result.getErrorCode())); } return Results.Ok(); } /// /// Creator Point를 증가 /// [HttpPost] [Route("add-creator-point")] public async Task addCreatorPoint(AddCreatorPointReqeust request) { var errorCode = await _accountService.addCreatorPoint(request.UserGuid, request.QuestId, request.Revision, request.Amount, UgqCreatorPointReason.Development); if(errorCode != ServerErrorCode.Success) return Results.BadRequest(errorCode); return Results.Ok(); } /// /// Creator Point를 감소 /// [HttpPost] [Route("settle-up-creator-point")] public async Task settleUpCreatorPoint(SettleUpCreatorPointReqeust request) { var errorCode = await _accountService.decCreatorPoint(request.UserGuid, request.Amount, UgqCreatorPointReason.Development); if (errorCode != ServerErrorCode.Success) return Results.BadRequest(errorCode); return Results.Ok(); } /// /// GameQuestData를 새로 쓰기 /// [HttpPost] [Route("ugq-game-quest-data-fake-update")] public async Task gameQuestDataFakeUpdate(GameQuestDataFakeUpdateReqeust request) { await _questEditorService.gameQuestDataFakeUpdate(request.UserGuid, request.QuestContentId); return Results.Ok(); } /// /// s3에 올라간 preset 파일 리스트 얻기 /// [HttpGet] [Route("get-s3-preset")] public async Task getS3Preset() { return Results.Ok(await _ugqbannerImageList.getFiles()); } /// /// DynamoDB sacn을 사용해서 userGuid로 AccountBaseDoc을 얻기 /// [HttpGet] [Route("find-account-id")] public async Task findAccountId([FromQuery] string userGuid) { var client = _dynamoDbClient.getDbClient(); if (client == null) return Results.BadRequest(); JsonNode? found_account = null; Dictionary? lastEvaluatedKey = null; do { var request = new ScanRequest() { TableName = _dynamoDbClient.getTableFullName(ServerBase.DynamoDbDefine.TableNames.Main), FilterExpression = "DocType = :v_docType", ExpressionAttributeValues = new Dictionary() { { ":v_docType", new AttributeValue("AccountBaseDoc") } }, Limit = 100, ExclusiveStartKey = lastEvaluatedKey, }; var response = await client.ScanAsync(request); lastEvaluatedKey = response.LastEvaluatedKey; foreach (var item in response.Items) { if (item.TryGetValue("AccountBaseAttrib", out var attr) == false) continue; JsonNode node = JsonNode.Parse(attr.S)!; string doc_user_guid = node["user_guid"]!.GetValue(); if(doc_user_guid == userGuid) { found_account = node; break; } } if (found_account != null) break; } while (lastEvaluatedKey.Count != 0); return Results.Ok(new { UserGuid = userGuid, AccountBaseAttrib = found_account, }); } /// /// 레디스에 저장된 ugq 로그인 정보를 모두 삭제 /// [HttpPost] [Route("clear-all-login")] public async Task clearAllLogin() { var server = _redis.GetServer(_redis.GetEndPoints().First()); var enumerable = server.KeysAsync(pattern: "ugq_login:*"); var enumerator = enumerable.GetAsyncEnumerator(); var dataList = new List(); while (await enumerator.MoveNextAsync()) { dataList.Add(enumerator.Current); } Log.getLogger().info($"delete keys. [{string.Join(",", dataList.Select(x => x.ToString()))}]"); await _redis.GetDatabase().KeyDeleteAsync(dataList.ToArray()); return Results.Ok(); } async Task<(Result, MoneyDoc?)> findMondyDoc(DynamoDbClient dynamo_db_client, string userGuid) { var result = new Result(); var err_msg = string.Empty; (result, var make_primary_key) = await DynamoDBDocBaseHelper.makePrimaryKey(userGuid); if (result.isFail()) { return (result, null); } var query_config = dynamo_db_client.makeQueryConfigForReadByPKOnly(make_primary_key!.PK); (result, var found_money_doc) = await dynamo_db_client.simpleQueryDocTypeWithQueryOperationConfig(query_config); if (result.isFail()) { err_msg = $"Failed to simpleQueryDocTypeWithQueryOperationConfig() !!! : {result.toBasicString()}, {make_primary_key.toBasicString()}"; Log.getLogger().error(err_msg); return (result, null); } return (result, found_money_doc); } /// /// DynamoDB에 저장된 재화를 변경 /// [HttpPost] [Route("change-currency-amount")] public async Task changeCurrencyAmount(ChangeCurrencyAmountRequest request) { var result = new Result(); var db_connector = CurrencyControlHelper.getDbConnector(); (result, var is_login) = await ServerCommon.EntityHelper.isUserLoggedIn(db_connector, request.UserGuid); if (result.isFail()) return Results.BadRequest(ApiResponseFactory.error(ServerErrorCode.UgqServerException)); if(true == is_login) return Results.BadRequest(ApiResponseFactory.error(ServerErrorCode.UgqServerException)); (result, var moneyDoc) = await findMondyDoc(_dynamoDbClient, request.UserGuid); if (moneyDoc == null) await insertMoney(request.UserGuid); if (Enum.TryParse(MetaHelper.GameConfigMeta.UGQAddSlotType, true, out CurrencyType currencyType) == false) return Results.BadRequest(ApiResponseFactory.error(ServerErrorCode.UgqServerException)); (result, double updatedAmount) = await CurrencyControlHelper.changeMoneyByUserGuid(request.UserGuid, currencyType, request.Amount); if (result.isFail()) return Results.BadRequest(ApiResponseFactory.error(ServerErrorCode.UgqCurrencyError)); return Results.Ok(new ChangeCurrencyAmountResponse { UserGuid = request.UserGuid, CurrencyType = currencyType, CurrentAmount = updatedAmount, }); } /// /// 계정이 가지고 있는 모든 UGQ 데이터를 검증 /// [HttpPost] [Route("validate-quest")] public async Task validateQuest(ValidateQuestRequest request) { List items = new(); var quests = await _questEditorService.getAll(request.UserGuid); foreach (var quest in quests) { var questDialogIds = quest.Tasks .Where(x => string.IsNullOrEmpty(x.DialogId) == false) .Select(x => x.DialogId!) .ToList(); var questDialogs = await _questEditorService.getQuestDialogs(questDialogIds); UGQValidator validator = new UGQValidator(_ugqMetaData); var errors = validator.Validate(quest, questDialogs); items.Add(new ValidateQuestItem { QuestContent = quest, QuestDialogs = questDialogs, Errors = errors.Select(x => x.ToString()).ToList() }); } return Results.Ok(items); } [HttpPost] [Route("set-author")] public async Task setAuthor() { await _inGameService.setAuthor(); return Results.Ok(); } } #endif }