초기커밋

This commit is contained in:
2025-05-01 07:20:41 +09:00
commit 98bb2e3c5c
2747 changed files with 646947 additions and 0 deletions

View File

@@ -0,0 +1,66 @@
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.Configuration;
using System.Globalization;
using StackExchange.Redis;
namespace UGQApiServer;
public class AllowWhenSingleLoginAttribute : TypeFilterAttribute
{
public AllowWhenSingleLoginAttribute() : base(typeof(AllowWhenSingleLoginFilter))
{
}
}
public class AllowWhenSingleLoginFilter : IAsyncAuthorizationFilter
{
readonly IConfiguration _configuration;
readonly IHttpContextAccessor _httpContextAccessor;
readonly IConnectionMultiplexer _redis;
bool EnableAllowWhenSingleLogin = true;
public AllowWhenSingleLoginFilter(IConfiguration configuration,
IHttpContextAccessor httpContextAccessor,
IConnectionMultiplexer redis)
{
_configuration = configuration;
_httpContextAccessor = httpContextAccessor;
_redis = redis;
EnableAllowWhenSingleLogin = _configuration.GetValue<bool>("EnableAllowWhenSingleLogin", true);
}
public async Task OnAuthorizationAsync(AuthorizationFilterContext context)
{
if (EnableAllowWhenSingleLogin == false)
return;
var user_guid = context.HttpContext.User.Claims.FirstOrDefault(c => c.Type == "Id")?.Value;
if (user_guid == null)
{
context.Result = new UnauthorizedObjectResult(string.Empty);
return;
}
var game_login_cache = await _redis.GetDatabase().StringGetAsync($"login:{user_guid}");
if (game_login_cache.HasValue == true)
{
context.Result = new BadRequestObjectResult(ApiResponseFactory.error(ServerErrorCode.UgqMetaverseOnline));
return;
}
var ugq_login_cache = await _redis.GetDatabase().StringGetAsync($"ugq_login:{user_guid}");
if(ugq_login_cache.HasValue == false)
{
context.Result = new BadRequestObjectResult(ApiResponseFactory.error(ServerErrorCode.UgqAuthRemoved));
return;
}
}
}

142
UGQApiServer/ApiFilter.cs Normal file
View File

@@ -0,0 +1,142 @@
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc;
namespace UGQApiServer;
public enum UgqApiType
{
None,
UgqAdmin,
UgqIngame,
UgqApi,
UgqAllInOne
}
public class ApiAllowSettings
{
public bool AllowInGameApi { get; init; } = false;
public bool AllowWebApi { get; init; } = false;
public bool AllowAdminApi { get; init; } = false;
public ServerType ServerType { get; init; } = ServerType.None;
public ApiAllowSettings(UgqApiType type)
{
switch (type)
{
case UgqApiType.UgqAdmin:
AllowAdminApi = true;
ServerType = ServerType.UgqAdmin;
break;
case UgqApiType.UgqApi:
AllowWebApi = true;
ServerType = ServerType.UgqApi;
break;
case UgqApiType.UgqIngame:
AllowInGameApi = true;
ServerType = ServerType.UgqIngame;
break;
case UgqApiType.UgqAllInOne:
AllowAdminApi = true;
AllowWebApi = true;
AllowInGameApi = true;
break;
}
}
}
public class UGQInGameApiAttribute : Attribute, IResourceFilter
{
public void OnResourceExecuted(ResourceExecutedContext context)
{
}
public void OnResourceExecuting(ResourceExecutingContext context)
{
var settings = context.HttpContext.RequestServices.GetService<ApiAllowSettings>();
if (settings == null)
{
context.Result = new NotFoundResult();
}
else
{
if(settings.AllowInGameApi == false)
context.Result = new NotFoundResult();
}
}
}
public class UGQWebApiAttribute : Attribute, IResourceFilter
{
public void OnResourceExecuted(ResourceExecutedContext context)
{
}
public void OnResourceExecuting(ResourceExecutingContext context)
{
var settings = context.HttpContext.RequestServices.GetService<ApiAllowSettings>();
if (settings == null)
{
context.Result = new NotFoundResult();
}
else
{
if (settings.AllowWebApi == false)
context.Result = new NotFoundResult();
}
}
}
public class UGQAdminApiAttribute : Attribute, IResourceFilter
{
public void OnResourceExecuted(ResourceExecutedContext context)
{
}
public void OnResourceExecuting(ResourceExecutingContext context)
{
var settings = context.HttpContext.RequestServices.GetService<ApiAllowSettings>();
if (settings == null)
{
context.Result = new NotFoundResult();
}
else
{
if (settings.AllowAdminApi == false)
context.Result = new NotFoundResult();
}
}
}
public class DevelopmentOnlyAttribute : Attribute, IResourceFilter
{
public void OnResourceExecuted(ResourceExecutedContext context)
{
}
public void OnResourceExecuting(ResourceExecutingContext context)
{
bool cmd_develop = false;
var cmdOptions = context.HttpContext.RequestServices.GetService<CommandLineOptions>();
if (cmdOptions != null)
{
cmd_develop = cmdOptions.m_develop;
}
bool isDevelopment = false;
var env = context.HttpContext.RequestServices.GetService<IWebHostEnvironment>();
if (env != null)
{
isDevelopment = env.IsDevelopment() || (env.EnvironmentName == "AWSDev") || cmd_develop;
}
if (isDevelopment == false)
context.Result = new NotFoundResult();
}
}

View File

@@ -0,0 +1,45 @@
using System.Globalization;
using UGQApiServer.Converter;
using UGQApiServer.Models;
using UGQDataAccess;
using ServerCommon;
using ServerCommon.UGQ.Models;
using UGQApiServer.UGQData;
namespace UGQApiServer;
public static class ApiResponseFactory
{
public static ApiErrorResponse error(ServerErrorCode errorCode)
{
LangEnum langEnum = Culture.ToLangEnum(CultureInfo.CurrentCulture.Name);
var textId = errorCode.ToString().Replace("Ugq", "UGQ_Error_");
var message = UGQDataHelper.getText(langEnum, textId);
return new ApiErrorResponse
{
ErrorCode = (int)errorCode,
ErrorMessage = message ?? errorCode.ToString(),
};
}
public static ValidationErrorResponse validationError(ServerErrorCode errorCode, List<string> errors)
{
LangEnum langEnum = Culture.ToLangEnum(CultureInfo.CurrentCulture.Name);
var textId = errorCode.ToString().Replace("Ugq", "UGQ_Error_");
var message = UGQDataHelper.getText(langEnum, textId);
return new ValidationErrorResponse
{
ErrorCode = (int)errorCode,
ErrorMessage = message ?? errorCode.ToString(),
ValidationErrorMessages = errors
};
}
}

View File

@@ -0,0 +1,13 @@
namespace UGQApiServer;
public class Default
{
public const int DefaultPort = 11000;
}
public class NamedPipeConf
{
public static string m_name { get; set; } = "NamedPipe";
[ConfigurationKeyName("enable")] public bool m_enable { get; set; } = false;
}

View File

@@ -0,0 +1,76 @@

using MySqlConnector;
using Google.Protobuf;
using Google.Protobuf.WellKnownTypes;
using ServerCore;
using ServerBase;
using ServerCommon;
using ServerCommon.BusinessLogDomain;
using MetaAssets;
namespace UGQApiServer.Auth
{
public class AuthSql
{
readonly string _ssoAccountDb;
public AuthSql(IConfiguration configuration)
{
_ssoAccountDb = configuration["SSOAccount:SsoAccountDb"] ?? "";
}
public async Task<ulong> getAccountIdFromMysql(string email)
{
var result = new Result();
var err_msg = string.Empty;
bool is_found_account_id = false;
ulong account_id = 0;
try
{
var read_func = delegate (MySqlDataReader dataReader)
{
is_found_account_id = true;
account_id = dataReader.GetUInt64("id");
return ServerErrorCode.Success;
};
var query = $"SELECT * FROM wallet_user WHERE email = '{email}'";
Console.WriteLine(query);
// 1. 계정 존재 여부와 AccessToken의 동일 여부를 통합인증DB의 정보 참조하여 체크 한다.
result = await MySqlConnectorHelper.simpleQueryExecuteForReaderAsync(
$"SELECT * FROM wallet_user WHERE email = '{email}'", read_func, _ssoAccountDb);
if (result.isFail())
{
err_msg = $"Failed to simpleQueryExecuteForReaderAsync() for SsoAccountDb !!! : email:{email}, {result.toBasicString()}";
Log.getLogger().error(err_msg);
return 0;
}
if (false == is_found_account_id)
{
err_msg = $"Not found Account ID in SsoAccountDb !!! : email:{email}";
result.setFail(ServerErrorCode.AccountIdNotFoundInSsoAccountDb, err_msg);
Log.getLogger().error(err_msg);
return 0;
}
}
catch (Exception e)
{
err_msg = $"Failed to query SsoAccountDb !!! : Exception:{e} - AccountId:{account_id}";
result.setFail(ServerErrorCode.MySqlDbQueryException, err_msg);
Log.getLogger().error(result.toBasicString());
return 0;
}
return account_id;
}
}
}

View File

@@ -0,0 +1,160 @@
using System.Text;
using System.IdentityModel.Tokens.Jwt;
using Microsoft.IdentityModel.Tokens;
using MySqlConnector;
using ServerCore; using ServerBase;
using ServerCommon;
namespace UGQApiServer.Auth;
public class WebPortalToken
{
public string AccountId { get; set; } = "";
public AccountType AccountType { get; set; }
public ulong AccessToken { get; set; }
}
public class WebPortalTokenAuth
{
readonly string _ssoAccountDb;
readonly string _webPortalTokenSecret;
public string WebPortalTokenSecret => _webPortalTokenSecret;
public WebPortalTokenAuth(IConfiguration configuration)
{
_ssoAccountDb = configuration["SSOAccount:SsoAccountDb"] ?? "";
_webPortalTokenSecret = configuration["SSOAccount:WebPortalTokenSecret"] ?? "";
}
public async Task<ServerErrorCode> mysqlAuth(WebPortalToken token)
{
// db <20>ּ<EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20>׻<EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> ó<><C3B3>
if (string.IsNullOrEmpty(_ssoAccountDb) == true)
return ServerErrorCode.Success;
var result = new Result();
var err_msg = string.Empty;
var account_id = token.AccountId;
var access_tocken = token.AccessToken;
try
{
var is_found_account_id = false;
string email = string.Empty;
UInt64 read_access_token = 0;
var read_func = delegate (MySqlDataReader dataReader)
{
is_found_account_id = true;
email = dataReader.GetString("email");
read_access_token = dataReader.GetUInt64("access_token");
return ServerErrorCode.Success;
};
// 1. <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD> <20><><EFBFBD>ο<EFBFBD> AccessToken<65><6E> <20><><EFBFBD><EFBFBD> <20><><EFBFBD>θ<EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>DB<44><42> <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD>Ͽ<EFBFBD> üũ <20>Ѵ<EFBFBD>.
result = await MySqlConnectorHelper.simpleQueryExecuteForReaderAsync(
$"SELECT * FROM wallet_user WHERE id = {account_id}", read_func, _ssoAccountDb);
if (result.isFail())
{
err_msg = $"Failed to simpleQueryExecuteForReaderAsync() for SsoAccountDb !!! : AccountId:{account_id}, {result.toBasicString()}";
Log.getLogger().error(err_msg);
return result.ErrorCode;
}
if (false == is_found_account_id)
{
err_msg = $"Not found Account ID in SsoAccountDb !!! : AccountId:{account_id}";
result.setFail(ServerErrorCode.AccountIdNotFoundInSsoAccountDb, err_msg);
Log.getLogger().error(err_msg);
return result.ErrorCode;
}
if (read_access_token != access_tocken)
{
err_msg = $"Not match AccessToken in SsoAccountDb !!! : JWT:{access_tocken} == SsoAccountDb:{read_access_token} - AccountId:{account_id}, email:{email}";
result.setFail(ServerErrorCode.AccessTokenNotMatchInSsoAccountDb, err_msg);
Log.getLogger().error(err_msg);
return result.ErrorCode;
}
}
catch (Exception e)
{
err_msg = $"Failed to query SsoAccountDb !!! : Exception:{e} - AccountId:{account_id}";
result.setFail(ServerErrorCode.MySqlDbQueryException, err_msg);
Log.getLogger().error(result.toBasicString());
return result.ErrorCode;
}
return ServerErrorCode.Success;
}
public async Task<ServerErrorCode> mysqlAuth_igm(WebPortalToken token)
{
// db <20>ּ<EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20>׻<EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> ó<><C3B3>
if (string.IsNullOrEmpty(_ssoAccountDb) == true)
return ServerErrorCode.Success;
var result = new Result();
var err_msg = string.Empty;
var account_id = token.AccountId;
var access_tocken = token.AccessToken;
try
{
var is_found_account_id = false;
string email = string.Empty;
UInt64 read_access_token = 0;
var read_func = delegate (MySqlDataReader dataReader)
{
is_found_account_id = true;
email = dataReader.GetString("email");
read_access_token = dataReader.GetUInt64("access_igm_token");
return ServerErrorCode.Success;
};
// 1. <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD> <20><><EFBFBD>ο<EFBFBD> AccessToken<65><6E> <20><><EFBFBD><EFBFBD> <20><><EFBFBD>θ<EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>DB<44><42> <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD>Ͽ<EFBFBD> üũ <20>Ѵ<EFBFBD>.
result = await MySqlConnectorHelper.simpleQueryExecuteForReaderAsync(
$"SELECT * FROM wallet_user WHERE id = {account_id}", read_func, _ssoAccountDb);
if (result.isFail())
{
err_msg = $"Failed to simpleQueryExecuteForReaderAsync() for SsoAccountDb !!! : AccountId:{account_id}, {result.toBasicString()}";
Log.getLogger().error(err_msg);
return result.ErrorCode;
}
if (false == is_found_account_id)
{
err_msg = $"Not found Account ID in SsoAccountDb !!! : AccountId:{account_id}";
result.setFail(ServerErrorCode.AccountIdNotFoundInSsoAccountDb, err_msg);
Log.getLogger().error(err_msg);
return result.ErrorCode;
}
if (read_access_token != access_tocken)
{
err_msg = $"Not match AccessToken in SsoAccountDb !!! : JWT:{access_tocken} == SsoAccountDb:{read_access_token} - AccountId:{account_id}, email:{email}";
result.setFail(ServerErrorCode.AccessTokenNotMatchInSsoAccountDb, err_msg);
Log.getLogger().error(err_msg);
return result.ErrorCode;
}
}
catch (Exception e)
{
err_msg = $"Failed to query SsoAccountDb !!! : Exception:{e} - AccountId:{account_id}";
result.setFail(ServerErrorCode.MySqlDbQueryException, err_msg);
Log.getLogger().error(result.toBasicString());
return result.ErrorCode;
}
return ServerErrorCode.Success;
}
}

View File

@@ -0,0 +1,97 @@
using Amazon.DynamoDBv2.Model;
using ServerCore; using ServerBase;
namespace UGQApiServer.BackGroundService;
public static class BackgroundServiceExtensions
{
public static IServiceCollection AddOnTimeBackgroundService(this IServiceCollection services)
{
services.AddSingleton<OnTimeBackGroundService>();
services.AddHostedService(provider => provider.GetRequiredService<OnTimeBackGroundService>());
return services;
}
public static IServiceCollection AddOnTimeTask(this IServiceCollection services, string taskName, DateTime onTime, Task task)
{
var provider = services.BuildServiceProvider();
var service = provider.GetRequiredService<OnTimeBackGroundService>();
service.addOnTimeTask(taskName, onTime, task);
return services;
}
}
public class OnTimeBackGroundService : BackgroundService
{
private const int BaseDelayMs = 1_000;
private PeriodicTimer? m_timer { get; set; }
private Dictionary<string, (Task task, DateTime time)> m_onTime_tasks { get; set; } = new();
public OnTimeBackGroundService() {}
public override void Dispose()
{
base.Dispose();
m_timer?.Dispose();
GC.SuppressFinalize(this);
}
public bool addOnTimeTask(string taskName, DateTime onTime, Task action)
{
return m_onTime_tasks.TryAdd(taskName, (action, onTime));
}
public bool deleteOnTimeTask(string taskName)
{
return m_onTime_tasks.Remove(taskName);
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
CreateTimer();
try
{
if (!await m_timer!.WaitForNextTickAsync(stoppingToken).ConfigureAwait(false))
{
return;
}
while (!stoppingToken.IsCancellationRequested)
{
await RunAsync().ConfigureAwait(false);
if (!await m_timer!.WaitForNextTickAsync(stoppingToken).ConfigureAwait(false))
{
break;
}
}
}
catch (Exception e)
{
Log.getLogger().error($"Background Exception : {e.Message}");
}
}
private async Task RunAsync()
{
var current = DateTime.UtcNow;
foreach (var task in m_onTime_tasks)
{
var on_time = task.Value.time;
if (current.Hour != on_time.Hour || current.Minute != on_time.Minute || current.Second != on_time.Second) continue;
// Fire and Forget
_ = Task.Run(() => task.Value.task);
}
await Task.CompletedTask;
}
private void CreateTimer() => m_timer = new PeriodicTimer(TimeSpan.FromMilliseconds(BaseDelayMs));
}

View File

@@ -0,0 +1,62 @@
using UGQDataAccess.Service;
using ServerCore; using ServerBase;
public class ReserveAccountGradeBackGroundService : BackgroundService
{
private const int BaseDelayMs = 1 * 60 * 1_000;
private PeriodicTimer? m_timer;
private AccountService _accountService;
public ReserveAccountGradeBackGroundService(AccountService accountService)
{
_accountService = accountService;
}
public override void Dispose()
{
base.Dispose();
m_timer?.Dispose();
GC.SuppressFinalize(this);
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
CreateTimer();
try
{
if (!await m_timer!.WaitForNextTickAsync(stoppingToken).ConfigureAwait(false))
{
return;
}
while (!stoppingToken.IsCancellationRequested)
{
await RunAsync().ConfigureAwait(false);
if (!await m_timer!.WaitForNextTickAsync(stoppingToken).ConfigureAwait(false))
{
break;
}
}
}
catch (Exception e)
{
Log.getLogger().error($"Background Exception : {e.Message}");
}
}
private void CreateTimer() => m_timer = new PeriodicTimer(TimeSpan.FromMilliseconds(BaseDelayMs));
protected async Task RunAsync()
{
var list = await _accountService.completeReserveAccountGrade();
foreach(var reserveAccount in list)
{
await _accountService.modifyAccountGrade(reserveAccount.UserGuid, reserveAccount.ReserveGradeType);
}
await Task.CompletedTask;
}
}

View File

@@ -0,0 +1,314 @@
using System.Text;
using Asp.Versioning;
using JwtRoleAuthentication.Services;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.Options;
using ServerCommon;
using ServerCommon.UGQ.Models;
using StackExchange.Redis;
using Swashbuckle.AspNetCore.Annotations;
using UGQApiServer.Auth;
using UGQApiServer.Models;
using UGQApiServer.Settings;
using UGQDataAccess.Service;
using UGQDatabase.Models;
using ServerCore; using ServerBase;
using Microsoft.AspNetCore.Authorization;
using UGQDataAccess.Logs;
using ServerCommon.BusinessLogDomain;
namespace UGQApiServer.Controllers
{
[ApiVersion("1.0")]
[Route("api/v{version:apiVersion}/[controller]")]
[ApiController]
public class AccountController : ControllerBase
{
AccountService _accountService;
TokenService _tokenService;
WebPortalTokenAuth _webPortalTokenAuth;
DynamoDbClient _dynamoDbClient;
IConnectionMultiplexer _redis;
JWTSettings _jwtSettings;
public AccountController(
AccountService accountService,
TokenService tokenService,
WebPortalTokenAuth webPortalTokenAuth,
DynamoDbClient dynamoDbClient,
IConnectionMultiplexer redis,
IOptions<JWTSettings> settings)
{
_accountService = accountService;
_tokenService = tokenService;
_webPortalTokenAuth = webPortalTokenAuth;
_dynamoDbClient = dynamoDbClient;
_redis = redis;
_jwtSettings = settings.Value;
}
string? userGuidFromToken()
{
return User.Claims.FirstOrDefault(c => c.Type == "Id")?.Value;
}
// #if DEBUG
/// <summary>
/// 게임DB와 연동된 버전 개발용 로그인
/// </summary>
/// <remarks>
/// 계정이 없으면 POST /api/v1/Admin/add-gamedb-account 로 테스트 계정을 만드십시오.
/// </remarks>
[HttpPost]
[Route("dev-login-v2")]
[Produces("application/json")]
[DevelopmentOnly]
[SwaggerResponse(StatusCodes.Status200OK, Type = typeof(LoginResponse))]
[SwaggerResponse(StatusCodes.Status400BadRequest, Type = typeof(ApiErrorResponse))]
public async Task<IResult> loginDev([FromBody] DevLoginRequest request)
{
(Result result, UserByAccountIdResult? gameAccount) =
await EntitySearchHelper.findUserByAccountId(_dynamoDbClient, request.LoginAccountId, "");
if (result.isFail())
return Results.BadRequest(ApiResponseFactory.error(ServerErrorCode.UgqRequireAccount));
if (gameAccount == null)
return Results.BadRequest(ApiResponseFactory.error(ServerErrorCode.UgqServerException));
AccountEntity accountEntity = await _accountService.getOrInsertAccount(
gameAccount.UserGuid, gameAccount.Nickname, request.LoginAccountId);
var accessToken = _tokenService.CreateToken(gameAccount.UserGuid, gameAccount.Nickname);
var refreshToken = _tokenService.GenerateRefreshToken();
DateTime refreshTokenExpryTime = DateTime.Now.AddDays(7);
if (await _accountService.saveRefreshToken(accountEntity.UserGuid, refreshToken, refreshTokenExpryTime) == null)
return Results.BadRequest(ApiResponseFactory.error(ServerErrorCode.UgqNullEntity));
{
var expiry = TimeSpan.FromMinutes(_jwtSettings.TokenValidityInMinutes);
await _redis.GetDatabase().StringSetAsync($"ugq_login:{accountEntity.UserGuid}", "online", expiry);
}
List<ILogInvoker> business_logs = [
new UgqApiLoginBusinessLog(UgqApiLoginType.DevelopmentApi),
];
var log_action = new LogActionEx(LogActionType.UgqApiLogin);
UgqApiBusinessLogger.collectLogs(log_action, accountEntity, business_logs);
return Results.Ok(new LoginResponse
{
UserGuid = accountEntity.UserGuid,
Nickname = accountEntity.Nickname,
AccessToken = accessToken,
RefreshToken = refreshToken,
});
}
// #endif
/// <summary>
/// 로그인
/// </summary>
[HttpPost]
[Route("login")]
[Produces("application/json")]
[SwaggerResponse(StatusCodes.Status200OK, Type = typeof(LoginResponse))]
[SwaggerResponse(StatusCodes.Status400BadRequest, Type = typeof(ApiErrorResponse))]
public async Task<IResult> login([FromBody] LoginRequest request)
{
WebPortalToken? webPortalToken = null;
{
Result result2 = AuthHelper.verifySsoAccountAuthJWT(request.WebPortalToken,
_webPortalTokenAuth.WebPortalTokenSecret,
out var account_id, out var account_type, out var access_token);
if (result2.isFail())
return Results.BadRequest(ApiResponseFactory.error(ServerErrorCode.UgqInvalidWebPortalToken));
webPortalToken = new WebPortalToken
{
AccountId = account_id,
AccountType = account_type,
AccessToken = access_token,
};
Log.getLogger().info($"webPortalToken. AccountId: {webPortalToken.AccountId}, AccountType: {webPortalToken.AccountType}");
}
if (webPortalToken == null)
return Results.BadRequest(ApiResponseFactory.error(ServerErrorCode.UgqInvalidWebPortalToken));
var errorCode = await _webPortalTokenAuth.mysqlAuth(webPortalToken);
if (errorCode != ServerErrorCode.Success)
return Results.BadRequest(ApiResponseFactory.error(ServerErrorCode.UgqInvalidWebPortalToken));
(Result result, UserByAccountIdResult? gameAccount) =
await EntitySearchHelper.findUserByAccountId(_dynamoDbClient, webPortalToken.AccountId, "");
if (result.isFail())
return Results.BadRequest(ApiResponseFactory.error(result.ErrorCode));
if (gameAccount == null)
return Results.BadRequest(ApiResponseFactory.error(ServerErrorCode.UgqServerException));
AccountEntity accountEntity = await _accountService.getOrInsertAccount(
gameAccount.UserGuid, gameAccount.Nickname, webPortalToken.AccountId);
var accessToken = _tokenService.CreateToken(gameAccount.UserGuid, gameAccount.Nickname);
var refreshToken = _tokenService.GenerateRefreshToken();
DateTime refreshTokenExpryTime = DateTime.Now.AddDays(7);
if (await _accountService.saveRefreshToken(accountEntity.UserGuid, refreshToken, refreshTokenExpryTime) == null)
return Results.BadRequest(ApiResponseFactory.error(ServerErrorCode.UgqNullEntity));
{
var expiry = TimeSpan.FromMinutes(_jwtSettings.TokenValidityInMinutes);
await _redis.GetDatabase().StringSetAsync($"ugq_login:{accountEntity.UserGuid}", "online", expiry);
}
List<ILogInvoker> business_logs = [
new UgqApiLoginBusinessLog(UgqApiLoginType.NormalApi),
];
var log_action = new LogActionEx(LogActionType.UgqApiLogin);
UgqApiBusinessLogger.collectLogs(log_action, accountEntity, business_logs);
return Results.Ok(new LoginResponse
{
UserGuid = accountEntity.UserGuid,
Nickname = accountEntity.Nickname,
AccessToken = accessToken,
RefreshToken = refreshToken
});
}
/// <summary>
/// img 로그인
/// </summary>
[HttpPost]
[Route("login-igm")]
[Produces("application/json")]
[SwaggerResponse(StatusCodes.Status200OK, Type = typeof(IgmLoginResponse))]
[SwaggerResponse(StatusCodes.Status400BadRequest, Type = typeof(ApiErrorResponse))]
public async Task<IResult> login_igm([FromBody] LoginRequest request)
{
WebPortalToken? webPortalToken = null;
{
Result result2 = AuthHelper.verifySsoAccountAuthJWT(request.WebPortalToken,
_webPortalTokenAuth.WebPortalTokenSecret,
out var account_id, out var account_type, out var access_token);
if (result2.isFail())
return Results.BadRequest(ApiResponseFactory.error(ServerErrorCode.UgqInvalidWebPortalToken));
webPortalToken = new WebPortalToken
{
AccountId = account_id,
AccountType = account_type,
AccessToken = access_token,
};
Log.getLogger().info($"webPortalToken. igm - AccountId: {webPortalToken.AccountId}, AccountType: {webPortalToken.AccountType}");
}
if (webPortalToken == null)
return Results.BadRequest(ApiResponseFactory.error(ServerErrorCode.UgqInvalidWebPortalToken));
var errorCode = await _webPortalTokenAuth.mysqlAuth_igm(webPortalToken);
if (errorCode != ServerErrorCode.Success)
return Results.BadRequest(ApiResponseFactory.error(ServerErrorCode.UgqInvalidWebPortalToken));
(Result result, UserByAccountIdResult? gameAccount) =
await EntitySearchHelper.findUserByAccountId(_dynamoDbClient, webPortalToken.AccountId, "");
if (result.isFail())
return Results.BadRequest(ApiResponseFactory.error(result.ErrorCode));
if (gameAccount == null)
return Results.BadRequest(ApiResponseFactory.error(ServerErrorCode.UgqServerException));
var accessToken = _tokenService.CreateToken(gameAccount.UserGuid, gameAccount.Nickname);
List<ILogInvoker> business_logs = [
new UgqApiLoginBusinessLog(UgqApiLoginType.NormalApi),
];
var log_action = new LogActionEx(LogActionType.igmApiLogin);
UgqApiBusinessLogger.collectLogs(log_action, gameAccount.UserGuid, gameAccount.Nickname, business_logs);
return Results.Ok(new IgmLoginResponse
{
UserGuid = gameAccount.UserGuid,
Nickname = gameAccount.Nickname,
AccessToken = accessToken,
});
}
/// <summary>
/// 토큰 리프레쉬
/// </summary>
[HttpPost]
[Route("refresh")]
[Produces("application/json")]
[SwaggerResponse(StatusCodes.Status200OK, Type = typeof(RefreshTokenResponse))]
[SwaggerResponse(StatusCodes.Status400BadRequest, Type = typeof(ApiErrorResponse))]
public async Task<IResult> Refresh([FromBody] RefreshTokenRequest request)
{
var principal = _tokenService.GetPrincipalFromExpiredToken(request.AccessToken);
var userGuid = principal.Claims.FirstOrDefault(c => c.Type == "Id")?.Value;
if (userGuid == null)
return Results.Unauthorized();
var accountEntity = await _accountService.getAccount(userGuid);
if (accountEntity is null)
return Results.BadRequest(ApiResponseFactory.error(ServerErrorCode.UgqNullEntity));
if (accountEntity.RefreshToken != request.RefreshToken || accountEntity.RefreshTokenExpiryTime <= DateTime.Now)
return Results.BadRequest(ApiResponseFactory.error(ServerErrorCode.UgqInvalidToken));
var accessToken = _tokenService.CreateToken(userGuid, accountEntity.Nickname);
var refreshToken = _tokenService.GenerateRefreshToken();
if (await _accountService.saveRefreshToken(accountEntity.UserGuid, refreshToken, null) == null)
return Results.BadRequest(ApiResponseFactory.error(ServerErrorCode.UgqNullEntity));
{
var expiry = TimeSpan.FromMinutes(_jwtSettings.TokenValidityInMinutes);
await _redis.GetDatabase().StringSetAsync($"ugq_login:{accountEntity.UserGuid}", "online", expiry);
}
return Results.Ok(new RefreshTokenResponse
{
AccessToken = accessToken,
RefreshToken = refreshToken,
});
}
[Authorize]
[HttpPost]
[Route("logout")]
[Produces("application/json")]
[SwaggerResponse(StatusCodes.Status200OK)]
[SwaggerResponse(StatusCodes.Status400BadRequest, Type = typeof(ApiErrorResponse))]
public async Task<IResult> Logout()
{
var userGuid = userGuidFromToken();
if (userGuid == null)
return Results.Unauthorized();
var accountEntity = await _accountService.getAccount(userGuid);
if (accountEntity is null)
return Results.BadRequest(ApiResponseFactory.error(ServerErrorCode.UgqNullEntity));
if (await _accountService.deleteRefreshToken(userGuid) == null)
return Results.BadRequest(ApiResponseFactory.error(ServerErrorCode.UgqNullEntity));
List<ILogInvoker> business_logs = [
new UgqApiLogoutBusinessLog(),
];
var log_action = new LogActionEx(LogActionType.UgqApiLogout);
UgqApiBusinessLogger.collectLogs(log_action, accountEntity, business_logs);
return Results.Ok();
}
}
}

View File

@@ -0,0 +1,335 @@
using System.Globalization;
using System.Security.Claims;
using Amazon.DynamoDBv2.Model;
using Asp.Versioning;
using MetaAssets;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using ServerCommon;
using ServerCommon.UGQ.Models;
using Swashbuckle.AspNetCore.Annotations;
using UGQApiServer.Controllers.Common;
using UGQApiServer.Converter;
using UGQApiServer.Models;
using UGQApiServer.Auth;
using UGQDataAccess.Repository.Models;
using UGQDataAccess.Service;
using UGQDatabase.Models;
using ServerCommon.BusinessLogDomain;
using UGQDataAccess.Repository;
using MySqlConnector;
using ServerBase;
namespace UGQApiServer.Controllers.Admin;
[ApiController]
[ApiVersion("1.0")]
[ApiExplorerSettings(GroupName = "admin")]
[Route("api/v{version:apiVersion}/Admin/Account")]
[ControllerName("Account")]
[UGQAdminApi]
[Authorize(Roles = "Admin,Service")]
public class AdminAccountController : ControllerBase
{
const int MAX_PAGE_SIZE = 100;
AccountService _accountService;
QuestEditorService _questEditorService;
MetaDataApi _metaDataApi;
QuestEditorApi _questEditorApi;
DynamoDbClient _dynamoDbClient;
AuthSql _authSql;
public AdminAccountController(
AccountService accountService,
QuestEditorService questEditorService, MetaDataApi metaDataApi, QuestEditorApi questEditorApi,
DynamoDbClient dynamoDbClient, AuthSql authSql)
{
_accountService = accountService;
_questEditorService = questEditorService;
_metaDataApi = metaDataApi;
_questEditorApi = questEditorApi;
_dynamoDbClient = dynamoDbClient;
_authSql = authSql;
}
string? adminUsernameFromToken()
{
return User.Claims.FirstOrDefault(c => c.Type == ClaimTypes.Name)?.Value;
}
/// <summary>
/// 모든 계정 리스트 얻기
/// </summary>
[HttpGet]
[Route("all-accounts")]
[Produces("application/json")]
public async Task<IResult> getAllAccounts([FromQuery] int pageNumber, [FromQuery] int pageSize, [FromQuery] string? searchText, [FromQuery] AccountSortType sortType)
{
pageSize = Math.Clamp(pageSize, 1, MAX_PAGE_SIZE);
var result = await _accountService.getAccounts(pageNumber, pageSize, searchText, sortType);
long totalCount = await _accountService.getAllAccountCount();
return Results.Ok(new UGQAllAccountItemResponse
{
TotalCount = totalCount,
PageNumber = result.PageNumber,
PageSize = result.PageSize,
TotalPages = result.TotalPages,
Accounts = result.Items.Select(x => x.ToDTO()).ToList()
});
}
/// <summary>
/// 계정 정보 얻기
/// </summary>
[HttpGet]
[Route("account")]
[Produces("application/json")]
public async Task<IResult> getAccount([FromQuery] string userGuid)
{
var accountEntity = await _accountService.getAccount(userGuid);
if (accountEntity == null)
return Results.BadRequest(ApiResponseFactory.error(ServerErrorCode.UgqNullEntity));
(Result result, MoneyAttrib? moneyAttrib) =
await EntitySearchHelper.findUserMoneyByUserGuid(_dynamoDbClient, accountEntity.UserGuid);
if (result.isFail())
return Results.BadRequest(ApiResponseFactory.error(result.ErrorCode));
if(moneyAttrib == null)
return Results.BadRequest(ApiResponseFactory.error(ServerErrorCode.UserMoneyDocEmpty));
return Results.Ok(new UGQAccountMoney
{
Sapphire = moneyAttrib.Sapphire,
UserGuid = accountEntity.UserGuid,
Nickname = accountEntity.Nickname,
AccountId = accountEntity.AccountId ?? "",
SlotCount = (MetaHelper.GameConfigMeta.UGQDefaultSlot + accountEntity.AdditionalSlotCount),
GradeType = accountEntity.GradeType,
CreatorPoint = accountEntity.CreatorPoint,
});
}
/// <summary>
/// 계정의 UGQ 슬롯 수 변경
/// </summary>
[HttpPost]
[Route("modify-account-slot")]
[Produces("application/json")]
public async Task<IResult> modifyAccountSlot([FromBody] UGQModifyAccountSlot model)
{
string? adminUsername = adminUsernameFromToken();
if (adminUsername == null)
return Results.Unauthorized();
(ServerErrorCode errorCode, var accountEntity) = await _accountService.modifySlotCount(model.UserGuid, model.Count, adminUsername);
if (errorCode != ServerErrorCode.Success)
return Results.BadRequest(ApiResponseFactory.error(errorCode));
if (accountEntity == null)
return Results.BadRequest(ApiResponseFactory.error(ServerErrorCode.UgqNullEntity));
return Results.Ok(accountEntity.ToDTO());
}
/// <summary>
/// 계정의 UGQ 등급 변경 예약
/// </summary>
[HttpPost]
[Route("reserve-account-grade")]
[Produces("application/json")]
public async Task<IResult> reserveAccountGrade([FromBody] UGQReserveAccountGrade model)
{
if (DateTime.UtcNow > model.ReserveTime)
return Results.BadRequest(ApiResponseFactory.error(ServerErrorCode.UgqInvalidReserveTime));
var accountEntity = await _accountService.getAccount(model.UserGuid);
if (accountEntity == null)
return Results.BadRequest(ApiResponseFactory.error(ServerErrorCode.UgqNullEntity));
if(accountEntity.GradeType == model.GradeType)
return Results.BadRequest(ApiResponseFactory.error(ServerErrorCode.UgqSameGradeReserve));
var reserveAccountGradeEntity = await _accountService.getReserveAccountGrade(model.UserGuid);
if(reserveAccountGradeEntity != null)
return Results.BadRequest(ApiResponseFactory.error(ServerErrorCode.UgqAlreadyExistsReserveGrade));
reserveAccountGradeEntity = await _accountService.reserveAccountGrade(model.UserGuid, accountEntity.GradeType, model.GradeType, model.ReserveTime);
if (reserveAccountGradeEntity == null)
return Results.BadRequest(ApiResponseFactory.error(ServerErrorCode.UgqNullEntity));
return Results.Ok(reserveAccountGradeEntity.ToDTO());
}
/// <summary>
/// 계정의 UGQ 등급 변경 예약 수정
/// </summary>
[HttpPost]
[Route("modify-reserve-account-grade")]
[Produces("application/json")]
public async Task<IResult> modifyReserveAccountGrade([FromBody] UGQModifyAccountGrade model)
{
if (DateTime.UtcNow > model.ReserveTime)
return Results.BadRequest(ApiResponseFactory.error(ServerErrorCode.UgqInvalidReserveTime));
var reserveAccountGradeEntity = await _accountService.modifyReserveAccountGrade(model.ReserveId, model.GradeType, model.ReserveTime);
if (reserveAccountGradeEntity == null)
return Results.BadRequest(ApiResponseFactory.error(ServerErrorCode.UgqNullEntity));
return Results.Ok(reserveAccountGradeEntity.ToDTO());
}
/// <summary>
/// 계정의 UGQ 등급 변경 예약 취소
/// </summary>
[HttpPost]
[Route("delete-reserve-account-grade")]
[Produces("application/json")]
public async Task<IResult> deleteReserveAccountGrade([FromQuery] string reserveId)
{
var errorCode = await _accountService.deleteReserveAccountGrade(reserveId);
if (errorCode != ServerErrorCode.Success)
return Results.BadRequest(ApiResponseFactory.error(errorCode));
return Results.Ok();
}
/// <summary>
/// 계정의 UGQ 등급 변경 예약 조회
/// </summary>
[HttpPost]
[Route("all-reserve-account-grade")]
[Produces("application/json")]
public async Task<IResult> getAllReserveAccountGrade([FromQuery] int pageNumber, [FromQuery] int pageSize, [FromQuery] AccountGradeSortType sortType, [FromQuery] AccountGradeProcessType processType, [FromQuery] string? accountId)
{
string userGuid = string.Empty;
if(accountId != null)
{
var accountEntity = await _accountService.getAccountByAccountId(accountId);
if (accountEntity == null || accountEntity.AccountId == null)
return Results.BadRequest(ApiResponseFactory.error(ServerErrorCode.UgqNullEntity));
userGuid = accountEntity.UserGuid;
}
var result = await _accountService.getReserveAccountGrades(pageNumber, pageSize, sortType, processType, userGuid);
if (result == null)
return Results.BadRequest(ApiResponseFactory.error(ServerErrorCode.UgqNullEntity));
long totalCount = await _accountService.getAllReserveAccountGradeCount();
if (accountId != null)
totalCount = result.Items.Count;
return Results.Ok(new UGQAllReserveAccountGradeItemResponse
{
TotalCount = totalCount,
PageNumber = result.PageNumber,
PageSize = result.PageSize,
TotalPages = result.TotalPages,
Accounts = result.Items.Select(x => x.ToDTO()).ToList()
});
}
/// <summary>
/// 계정의 UGQ 등급 변경 예약 조회
/// </summary>
[HttpPost]
[Route("get-reserve-account-grade")]
[Produces("application/json")]
public async Task<IResult> getReserveAccountGrade([FromQuery] string accountId, [FromQuery] AccountGradeProcessType processType)
{
var accountEntity = await _accountService.getAccountByAccountId(accountId);
if(accountEntity == null || accountEntity.AccountId == null)
return Results.BadRequest(ApiResponseFactory.error(ServerErrorCode.UgqNullEntity));
var reserveAccountGradeEntityList = await _accountService.getReserveAccountGrade(accountEntity.UserGuid, processType);
if (reserveAccountGradeEntityList == null || reserveAccountGradeEntityList.Count == 0)
return Results.BadRequest(ApiResponseFactory.error(ServerErrorCode.UgqNullEntity));
return Results.Ok(new UGQReserveAccountGradeItemResponse
{
Accounts = reserveAccountGradeEntityList.Select(x => x.ToDTO()).ToList()
});
}
/// <summary>
/// 계정의 Creator Point 변경
/// </summary>
[HttpPost]
[Route("modify-creator-point")]
[Produces("application/json")]
public async Task<IResult> modifyCreatorPoint([FromBody] UGQModifyCreatorPoint model)
{
string? adminUsername = adminUsernameFromToken();
if (adminUsername == null)
return Results.Unauthorized();
(var errorCode, var accountEntity) = await _accountService.modifyCreatorPoint(model.UserGuid, model.Amount, model.Reason, adminUsername);
if (errorCode != ServerErrorCode.Success)
return Results.BadRequest(ApiResponseFactory.error(errorCode));
if (accountEntity == null)
return Results.BadRequest(ApiResponseFactory.error(ServerErrorCode.UgqNullEntity));
return Results.Ok(accountEntity.ToDTO());
}
/// <summary>
/// 메타버스 계정 정보 얻기
/// </summary>
/// <remarks>
/// email을 입력하면 email로 accountId를 검색, email을 입력 안 하면 accountId 입력 값을 사용
/// </remarks>
[HttpGet]
[Route("metaverse-account")]
[Produces("application/json")]
public async Task<IResult> getMetaverseAccount([FromQuery] string? email, [FromQuery] ulong? accountId)
{
if (email != null)
{
accountId = await _authSql.getAccountIdFromMysql(email);
if (accountId == 0)
{
return Results.Ok(new UGQMetaverseAccount
{
});
}
}
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 UGQMetaverseAccount
{
LoginAccountId = loginAccountId,
});
}
else
{
return Results.Ok(new UGQMetaverseAccount
{
LoginAccountId = loginAccountId,
UserGuid = gameAccount.UserGuid,
Nickname = gameAccount.Nickname,
});
}
}
}

View File

@@ -0,0 +1,200 @@
using System.Globalization;
using System.Security.Claims;
using Amazon.Auth.AccessControlPolicy;
using Asp.Versioning;
using JwtRoleAuthentication.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using ServerCommon;
using ServerCommon.BusinessLogDomain;
using ServerCommon.UGQ.Models;
using Swashbuckle.AspNetCore.Annotations;
using UGQApiServer.Controllers.Common;
using UGQApiServer.Converter;
using UGQApiServer.Models;
using UGQDataAccess.Repository.Models;
using UGQDataAccess.Service;
using UGQDatabase.Models;
using UGQDataAccess.Logs;
using ServerBase;
namespace UGQApiServer.Controllers.Admin;
[ApiController]
[ApiVersion("1.0")]
[ApiExplorerSettings(GroupName = "admin")]
[Route("api/v{version:apiVersion}/[controller]")]
[UGQAdminApi]
public class AdminController : ControllerBase
{
AdminService _adminService;
TokenService _tokenService;
public AdminController(AdminService adminService, TokenService tokenService)
{
_adminService = adminService;
_tokenService = tokenService;
}
/// <summary>
/// 어드민 계정 추가
/// </summary>
// [Authorize(Roles = "Admin")]
[HttpPost]
[Route("signup")]
[Produces("application/json")]
[SwaggerResponse(StatusCodes.Status200OK, Type = typeof(AdminSignupResponse))]
[SwaggerResponse(StatusCodes.Status400BadRequest, Type = typeof(ApiErrorResponse))]
public async Task<IResult> signup([FromBody] AdminSignupRequest request)
{
var entity = await _adminService.signup(request.Username, request.Password, UGQAccountRole.Service);
if (entity == null)
return Results.BadRequest(ApiResponseFactory.error(ServerErrorCode.UgqNullEntity));
return Results.Ok(new AdminSignupResponse
{
Username = entity.Username,
Role = entity.Role,
});
}
/// <summary>
/// 어드민 로그인
/// </summary>
[HttpPost]
[Route("login")]
[Produces("application/json")]
[SwaggerResponse(StatusCodes.Status200OK, Type = typeof(AdminLoginResponse))]
[SwaggerResponse(StatusCodes.Status400BadRequest, Type = typeof(ApiErrorResponse))]
public async Task<IResult> login([FromBody] AdminLoginRequest request)
{
var entity = await _adminService.login(request.Username, request.Password);
if(entity == null)
return Results.BadRequest(ApiResponseFactory.error(ServerErrorCode.UgqNullEntity));
var accessToken = _tokenService.CreateAdminToken(entity.Username, entity.Role);
var refreshToken = _tokenService.GenerateRefreshToken();
DateTime refreshTokenExpryTime = DateTime.Now.AddDays(7);
if (await _adminService.saveRefreshToken(entity.Id, refreshToken, refreshTokenExpryTime) == null)
return Results.BadRequest(ApiResponseFactory.error(ServerErrorCode.UgqNullEntity));
List<ILogInvoker> business_logs = [
new UgqApiAdminLoginBusinessLog(entity.Username),
];
var log_action = new LogActionEx(LogActionType.UgqApiAdminLogin);
UgqApiBusinessLogger.collectLogs(log_action, entity, business_logs);
return Results.Ok(new AdminLoginResponse
{
Username = entity.Username,
Role = entity.Role,
AccessToken = accessToken,
RefreshToken = refreshToken
});
}
/// <summary>
/// 어드민 패스워드 변경
/// </summary>
[Authorize]
[HttpPost]
[Route("change-password")]
[Produces("application/json")]
[SwaggerResponse(StatusCodes.Status200OK, Type = typeof(AdminChangePaswordResponse))]
[SwaggerResponse(StatusCodes.Status400BadRequest, Type = typeof(ApiErrorResponse))]
public async Task<IResult> changePassowrd([FromBody] AdminChangePaswordRequest request)
{
var username = User.Claims.FirstOrDefault(c => c.Type == ClaimTypes.Name)?.Value;
if (username == null)
return Results.Unauthorized();
var entity = await _adminService.changePassword(username, request.NewPassword);
if (entity == null)
return Results.BadRequest(ApiResponseFactory.error(ServerErrorCode.UgqNullEntity));
return Results.Ok(new AdminChangePaswordResponse
{
});
}
/// <summary>
/// 어드민 패스워드 변경
/// </summary>
[HttpPost]
[Route("update-password")]
[Produces("application/json")]
[SwaggerResponse(StatusCodes.Status200OK, Type = typeof(AdminUpdatePaswordResponse))]
[SwaggerResponse(StatusCodes.Status400BadRequest, Type = typeof(ApiErrorResponse))]
public async Task<IResult> updatePassowrd([FromBody] AdminUpdatePaswordRequest request)
{
var entity = await _adminService.changePassword(request.Username, request.NewPassword);
if (entity == null)
return Results.BadRequest(ApiResponseFactory.error(ServerErrorCode.UgqNullEntity));
return Results.Ok(new AdminUpdatePaswordResponse
{
});
}
/// <summary>
/// 토큰 리프레쉬
/// </summary>
[HttpPost]
[Route("refresh")]
[Produces("application/json")]
[SwaggerResponse(StatusCodes.Status200OK, Type = typeof(RefreshTokenResponse))]
[SwaggerResponse(StatusCodes.Status400BadRequest, Type = typeof(ApiErrorResponse))]
public async Task<IResult> Refresh([FromBody] RefreshTokenRequest request)
{
var principal = _tokenService.GetPrincipalFromExpiredToken(request.AccessToken);
var username = principal.Claims.FirstOrDefault(c => c.Type == ClaimTypes.Name)?.Value;
if (username == null)
return Results.Unauthorized();
var entity = await _adminService.get(username);
if (entity is null)
return Results.BadRequest(ApiResponseFactory.error(ServerErrorCode.UgqNullEntity));
if (entity.RefreshToken != request.RefreshToken || entity.RefreshTokenExpiryTime <= DateTime.Now)
return Results.BadRequest(ApiResponseFactory.error(ServerErrorCode.UgqInvalidToken));
var accessToken = _tokenService.CreateAdminToken(entity.Username, entity.Role);
var refreshToken = _tokenService.GenerateRefreshToken();
if (await _adminService.saveRefreshToken(entity.Id, refreshToken, null) == null)
return Results.BadRequest(ApiResponseFactory.error(ServerErrorCode.UgqNullEntity));
return Results.Ok(new RefreshTokenResponse
{
AccessToken = accessToken,
RefreshToken = refreshToken,
});
}
[Authorize]
[HttpPost]
[Route("logout")]
[Produces("application/json")]
[SwaggerResponse(StatusCodes.Status200OK)]
[SwaggerResponse(StatusCodes.Status400BadRequest, Type = typeof(ApiErrorResponse))]
public async Task<IResult> Logout()
{
var username = User.Claims.FirstOrDefault(c => c.Type == ClaimTypes.Name)?.Value;
if (username == null)
return Results.Unauthorized();
if (await _adminService.deleteRefreshToken(username) == null)
return Results.BadRequest(ApiResponseFactory.error(ServerErrorCode.UgqNullEntity));
return Results.Ok();
}
}

View File

@@ -0,0 +1,164 @@
using System.Globalization;
using Asp.Versioning;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using ServerCommon;
using ServerCommon.UGQ.Models;
using Swashbuckle.AspNetCore.Annotations;
using UGQApiServer.Controllers.Common;
using UGQApiServer.Converter;
using UGQApiServer.Models;
using UGQDataAccess.Repository.Models;
using UGQDataAccess.Service;
using UGQApiServer.UGQData;
using ServerBase;
namespace UGQApiServer.Controllers.Admin;
[Authorize]
[ApiController]
[ApiVersion("1.0")]
[ApiExplorerSettings(GroupName = "admin")]
[Route("api/v{version:apiVersion}/Admin/MetaData")]
[ControllerName("MetaData")]
[UGQAdminApi]
[Authorize(Roles = "Admin,Service")]
public class AdminMetaDataController : ControllerBase
{
AccountService _accountService;
QuestEditorService _questEditorService;
MetaDataApi _metaDataApi;
QuestEditorApi _questEditorApi;
DynamoDbClient _dynamoDbClient;
public AdminMetaDataController(
AccountService accountService,
QuestEditorService questEditorService, MetaDataApi metaDataApi, QuestEditorApi questEditorApi, DynamoDbClient dynamoDbClient)
{
_accountService = accountService;
_questEditorService = questEditorService;
_metaDataApi = metaDataApi;
_questEditorApi = questEditorApi;
_dynamoDbClient = dynamoDbClient;
}
/////////////////////////////////////////////////////////////////////////////
// 메타데이터 어드민
/////////////////////////////////////////////////////////////////////////////
/// <summary>
/// 사용가능한 시작 npc 가져오기
/// </summary>
/// <param name="userGuid">유저 Guid</param>
/// <param name="pageNumber">페이지</param>
/// <param name="pageSize">pageSize (최대 100)</param>
[HttpGet]
[Route("assignable-npcs")]
[Produces("application/json")]
[SwaggerResponse(StatusCodes.Status200OK, Type = typeof(UGQNpcMetaDataList))]
[SwaggerResponse(StatusCodes.Status400BadRequest, Type = typeof(ApiErrorResponse))]
public async Task<IResult> getAssignableNpcs([FromQuery] string userGuid, [FromQuery] int pageNumber, [FromQuery] int pageSize)
{
return await _metaDataApi.getAssignableNpcs(userGuid, pageNumber, pageSize);
}
/// <summary>
/// 사용가능한 task action 가져오기
/// </summary>
/// <param name="userGuid">유저 Guid</param>
/// <param name="pageNumber">페이지</param>
/// <param name="pageSize">pageSize (최대 100)</param>
[HttpGet]
[Route("available-task-actions")]
[Produces("application/json")]
[SwaggerResponse(StatusCodes.Status200OK, Type = typeof(TaskActionList))]
[SwaggerResponse(StatusCodes.Status400BadRequest, Type = typeof(ApiErrorResponse))]
public async Task<IResult> getAvailableTaskActions([FromQuery] string userGuid, [FromQuery] int pageNumber, [FromQuery] int pageSize)
{
return await _metaDataApi.getAvailableTaskActions(pageNumber, pageSize);
}
/// <summary>
/// 액션의 입력가능한 값 리스트 얻어오기
/// </summary>
/// <remarks>
/// Task Action이 의상장착인 경우에 사용
/// </remarks>
/// <param name="userGuid">유저 Guid</param>
/// <param name="taskActionId">TaskAction의 아이디</param>
/// <param name="pageNumber">페이지</param>
/// <param name="pageSize">pageSize (최대 100)</param>
[HttpGet]
[Route("task-action-values")]
[Produces("application/json")]
[SwaggerResponse(StatusCodes.Status200OK, Type = typeof(TaskActionValueList))]
[SwaggerResponse(StatusCodes.Status400BadRequest, Type = typeof(ApiErrorResponse))]
public async Task<IResult> getActionValues([FromQuery] string userGuid, [FromQuery] int taskActionId, [FromQuery] int pageNumber, [FromQuery] int pageSize)
{
return await _metaDataApi.getActionValues(userGuid, taskActionId, pageNumber, pageSize);
}
/// <summary>
/// 사용가능한 dialog 유형 가져오기
/// </summary>
/// <param name="userGuid">유저 Guid</param>
/// <param name="pageNumber">페이지</param>
/// <param name="pageSize">pageSize (최대 100)</param>
[HttpGet]
[Route("dialog-types")]
[Produces("application/json")]
[SwaggerResponse(StatusCodes.Status200OK, Type = typeof(DialogTypeList))]
[SwaggerResponse(StatusCodes.Status400BadRequest, Type = typeof(ApiErrorResponse))]
public async Task<IResult> getDialogTypes([FromQuery] string userGuid, [FromQuery] int pageNumber, [FromQuery] int pageSize)
{
return await _metaDataApi.getDialogTypes(pageNumber, pageSize);
}
/// <summary>
/// dialog 유형에 따라 사용가능한 dialog 액션 가져오기
/// </summary>
/// <param name="userGuid">유저 Guid</param>
/// <param name="dialogType">dialog 유형</param>
/// <param name="pageNumber">페이지</param>
/// <param name="pageSize">pageSize (최대 100)</param>
[HttpGet]
[Route("dialog-actions")]
[Produces("application/json")]
[SwaggerResponse(StatusCodes.Status200OK, Type = typeof(DialogActionList))]
[SwaggerResponse(StatusCodes.Status400BadRequest, Type = typeof(ApiErrorResponse))]
public async Task<IResult> getDialogActions([FromQuery] string userGuid, [FromQuery] int dialogType, [FromQuery] int pageNumber, [FromQuery] int pageSize)
{
return await _metaDataApi.getDialogActions(dialogType, pageNumber, pageSize);
}
/// <summary>
/// dialog 액션에 따라 입력 가능한 값 가져오기
/// </summary>
/// <param name="userGuid">유저 Guid</param>
/// <param name="dialogType">다이얼로그 타입</param>
/// <param name="dialogActionId">조건 종류</param>
/// <param name="pageNumber">페이지</param>
/// <param name="pageSize">pageSize (최대 100)</param>
[HttpGet]
[Route("dialog-action-values")]
[Produces("application/json")]
[SwaggerResponse(StatusCodes.Status200OK, Type = typeof(DialogActionValueList))]
[SwaggerResponse(StatusCodes.Status400BadRequest, Type = typeof(ApiErrorResponse))]
public async Task<IResult> getDialogConditionValues([FromQuery] string userGuid, [FromQuery] int dialogType, [FromQuery] int dialogActionId, [FromQuery] int pageNumber, [FromQuery] int pageSize)
{
return await _metaDataApi.getDialogConditionValues(dialogType, dialogActionId, pageNumber, pageSize);
}
[HttpGet]
[Route("preset-images")]
[Produces("application/json")]
[SwaggerResponse(StatusCodes.Status200OK, Type = typeof(DialogActionValueList))]
[SwaggerResponse(StatusCodes.Status400BadRequest, Type = typeof(ApiErrorResponse))]
public async Task<IResult> getPresetImages([FromQuery] string userGuid, [FromQuery] PresetKind kind, [FromQuery] PresetCategory category)
{
return await _metaDataApi.getPresetImages(kind, category);
}
}

View File

@@ -0,0 +1,341 @@
using System.Globalization;
using System.Security.Claims;
using Asp.Versioning;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using ServerCommon;
using ServerCommon.UGQ.Models;
using Swashbuckle.AspNetCore.Annotations;
using UGQApiServer.Controllers.Common;
using UGQApiServer.Converter;
using UGQApiServer.Models;
using UGQDataAccess.Repository.Models;
using UGQDataAccess.Service;
using UGQDatabase.Models;
using ServerBase;
namespace UGQApiServer.Controllers.Admin;
[Authorize]
[ApiController]
[ApiVersion("1.0")]
[ApiExplorerSettings(GroupName = "admin")]
[Route("api/v{version:apiVersion}/Admin/QuestEditor")]
[ControllerName("QuestEditor")]
[UGQAdminApi]
[Authorize(Roles = "Admin,Service")]
public class AdminQuestEditorController : ControllerBase
{
const int MAX_PAGE_SIZE = 100;
AccountService _accountService;
QuestEditorService _questEditorService;
MetaDataApi _metaDataApi;
QuestEditorApi _questEditorApi;
DynamoDbClient _dynamoDbClient;
public AdminQuestEditorController(
AccountService accountService,
QuestEditorService questEditorService, MetaDataApi metaDataApi, QuestEditorApi questEditorApi, DynamoDbClient dynamoDbClient)
{
_accountService = accountService;
_questEditorService = questEditorService;
_metaDataApi = metaDataApi;
_questEditorApi = questEditorApi;
_dynamoDbClient = dynamoDbClient;
}
string? adminUsernameFromToken()
{
return User.Claims.FirstOrDefault(c => c.Type == ClaimTypes.Name)?.Value;
}
/// <summary>
/// 퀘스트 생성
/// </summary>
/// <remarks>
/// 빈 슬롯에 퀘스트 추가하는 경우에 호출
/// </remarks>
[HttpPost]
[Route("quest")]
[Produces("application/json")]
[SwaggerResponse(StatusCodes.Status200OK, Type = typeof(UGQContent))]
[SwaggerResponse(StatusCodes.Status400BadRequest, Type = typeof(ApiErrorResponse))]
public async Task<IResult> addQuest([FromQuery] string userGuid, [FromBody] UGQSaveQuestModel saveQuestModel)
{
string? adminUsername = adminUsernameFromToken();
if(adminUsername == null)
return Results.Unauthorized();
return await _questEditorApi.addQuest(userGuid, saveQuestModel, adminUsername);
}
/// <summary>
/// 퀘스트 내용 가져오기
/// </summary>
/// <param name="questContentId">UGQSummary의 QuestContentId 값으로 api 호출</param>
/// <param name="userGuid">유저 Guid</param>
[HttpGet]
[Route("quest/{questContentId}")]
[Produces("application/json")]
[SwaggerResponse(StatusCodes.Status200OK, Type = typeof(UGQContent))]
[SwaggerResponse(StatusCodes.Status400BadRequest, Type = typeof(ApiErrorResponse))]
public async Task<IResult> getQuest(string questContentId, [FromQuery] string userGuid)
{
return await _questEditorApi.getQuest(userGuid, questContentId);
}
/// <summary>
/// 편집 가능한 경우 퀘스트 내용 가져오기
/// </summary>
/// <remarks>
/// GET api와는 다르게 편집 불가능한 state인 경우에는 실패 리턴
/// </remarks>
/// <param name="questContentId">UGQSummary의 QuestContentId 값으로 api 호출</param>
/// <param name="userGuid">유저 Guid</param>
[HttpPost]
[Route("quest/{questContentId}/edit")]
[Produces("application/json")]
[SwaggerResponse(StatusCodes.Status200OK, Type = typeof(UGQContent))]
[SwaggerResponse(StatusCodes.Status400BadRequest, Type = typeof(ApiErrorResponse))]
public async Task<IResult> editQuest(string questContentId, [FromQuery] string userGuid)
{
return await _questEditorApi.editQuest(userGuid, questContentId);
}
/// <summary>
/// 퀘스트 내용 업데이트
/// </summary>
/// <remarks>
/// Task의 DialogId는 업데이트하지 않음.
/// 편집 불가능 state인 경우 에러 리턴
/// </remarks>
/// <param name="questContentId">GET 또는 edit 에서 얻은 questContentId 사용</param>
/// <param name="userGuid">유저 Guid</param>
/// <param name="saveQuestModel">웹에서 입력한 값</param>
[HttpPut]
[Route("quest/{questContentId}")]
[Produces("application/json")]
[SwaggerResponse(StatusCodes.Status200OK, Type = typeof(UGQContent))]
[SwaggerResponse(StatusCodes.Status400BadRequest, Type = typeof(ApiErrorResponse))]
public async Task<IResult> saveQuest(string questContentId, [FromQuery] string userGuid, [FromBody] UGQSaveQuestModel saveQuestModel)
{
return await _questEditorApi.saveQuest(userGuid, questContentId, saveQuestModel);
}
/// <summary>
/// 퀘스트 삭제
/// </summary>
/// <param name="questContentId">GET또는 edit 에서 얻은 questContentId 사용</param>
/// <param name="userGuid">유저 Guid</param>
[HttpDelete]
[Route("quest/{questContentId}")]
[Produces("application/json")]
[SwaggerResponse(StatusCodes.Status200OK)]
[SwaggerResponse(StatusCodes.Status400BadRequest, Type = typeof(ApiErrorResponse))]
public async Task<IResult> deleteQuest(string questContentId, [FromQuery] string userGuid)
{
return await _questEditorApi.deleteQuest(userGuid, questContentId);
}
/// <summary>
/// 퀘스트 이미지 업로드
/// </summary>
/// <remarks>
/// 세부사항은 정리 예정
/// </remarks>
/// <param name="questContentId">GET 또는 edit 에서 얻은 questContentId 사용</param>
/// <param name="userGuid">유저 Guid</param>
/// <param name="titleImageId">기본 이미지 인덱스</param>
/// <param name="bannerImageId">기본 이미지 인덱스</param>
/// <param name="title">업로드 파일</param>
/// <param name="banner">업로드 파일</param>
[HttpPost]
[Route("quest-image/{questContentId}")]
[Produces("application/json")]
public async Task<IResult> uploadQuestImage(string questContentId, [FromQuery] string userGuid, [FromQuery] int? titleImageId, [FromQuery] int? bannerImageId, IFormFile? title, IFormFile? banner)
{
return await _questEditorApi.uploadQuestImage(userGuid, questContentId, titleImageId ?? 0, bannerImageId ?? 0, title, banner);
}
/// <summary>
/// 퀘스트 타이틀 이미지 업로드
/// </summary>
/// <remarks>
/// 세부사항은 정리 예정
/// </remarks>
/// <param name="userGuid">유저 Guid</param>
/// <param name="questContentId">GET 또는 edit 에서 얻은 questContentId 사용</param>
/// <param name="titleImageId">기본 이미지 인덱스</param>
/// <param name="title">업로드 파일</param>
[HttpPost]
[Route("quest-title-image/{questContentId}")]
[Produces("application/json")]
public async Task<IResult> uploadQuestTitleImage(string questContentId, [FromQuery] string userGuid, [FromQuery] int? titleImageId, IFormFile? title)
{
return await _questEditorApi.uploadQuestImage(userGuid, questContentId, titleImageId ?? 0, 0, title, null);
}
/// <summary>
/// 퀘스트 배너 이미지 업로드
/// </summary>
/// <remarks>
/// 세부사항은 정리 예정
/// </remarks>
/// <param name="userGuid">유저 Guid</param>
/// <param name="questContentId">GET 또는 edit 에서 얻은 questContentId 사용</param>
/// <param name="bannerImageId">기본 이미지 인덱스</param>
/// <param name="banner">업로드 파일</param>
[HttpPost]
[Route("quest-banner-image/{questContentId}")]
[Produces("application/json")]
public async Task<IResult> uploadQuestBannerImage(string questContentId, [FromQuery] string userGuid, [FromQuery] int? bannerImageId, IFormFile? banner)
{
return await _questEditorApi.uploadQuestImage(userGuid, questContentId, 0, bannerImageId ?? 0, null, banner);
}
/// <summary>
/// 퀘스트 다이얼로그 가져오기
/// </summary>
/// <param name="questContentId">GET 또는 edit 에서 얻은 questContentId 사용</param>
/// <param name="dialogId">UGQContent.Tasks에 있는 DialogId 사용</param>
/// <param name="userGuid">유저 Guid</param>
[HttpGet]
[Route("quest-dialog/{questContentId}/{dialogId}")]
[Produces("application/json")]
[SwaggerResponse(StatusCodes.Status200OK, Type = typeof(UGQDialog))]
[SwaggerResponse(StatusCodes.Status400BadRequest, Type = typeof(ApiErrorResponse))]
public async Task<IResult> getQuestDialog(string questContentId, string dialogId, [FromQuery] string userGuid)
{
return await _questEditorApi.getQuestDialog(userGuid, questContentId, dialogId);
}
/// <summary>
/// 퀘스트 다이얼로그 새로 생성
/// </summary>
/// <remarks>
/// Dialog 를 추가하고, UGQContent.Tasks의 DialogId를 저장
/// </remarks>
/// <param name="questContentId">GET 또는 edit 에서 얻은 questContentId 사용</param>
/// <param name="userGuid">유저 Guid</param>
/// <param name="taskIndex">몇번째 Task에 추가되는 다이얼로그인지. 0부터 시작</param>
/// <param name="questDialog">questDialog</param>
[HttpPost]
[Route("quest-dialog/{questContentId}")]
[Produces("application/json")]
[SwaggerResponse(StatusCodes.Status200OK, Type = typeof(UGQAddQuestDialogResponse))]
[SwaggerResponse(StatusCodes.Status400BadRequest, Type = typeof(ApiErrorResponse))]
public async Task<IResult> addQuestDialog(string questContentId, [FromQuery] string userGuid, [FromQuery] int taskIndex, [FromBody] UGQSaveDialogModel questDialog)
{
return await _questEditorApi.addQuestDialog(userGuid, questContentId, taskIndex, questDialog);
}
/// <summary>
/// 퀘스트 다이얼로그 업데이트
/// </summary>
/// <param name="questContentId">GET 또는 edit 에서 얻은 questContentId 사용</param>
/// <param name="dialogId">UGQContent.Tasks에 있는 DialogId 사용</param>
/// <param name="userGuid">유저 Guid</param>
/// /// <param name="questDialog">questDialog</param>
[HttpPut]
[Route("quest-dialog/{questContentId}/{dialogId}")]
[Produces("application/json")]
[SwaggerResponse(StatusCodes.Status200OK, Type = typeof(UGQDialog))]
[SwaggerResponse(StatusCodes.Status400BadRequest, Type = typeof(ApiErrorResponse))]
public async Task<IResult> saveQuestDialog(string questContentId, string dialogId, [FromQuery] string userGuid, [FromBody] UGQSaveDialogModel questDialog)
{
return await _questEditorApi.saveQuestDialog(userGuid, questContentId, dialogId, questDialog);
}
/// <summary>
/// 퀘스트 state 변경
/// </summary>
/// <remarks>
/// Test 로 변경 실패 시 ValidationErrorResponse로 응답
/// </remarks>
/// <param name="questContentId">GET또는 edit 에서 얻은 questContentId 사용</param>
/// <param name="userGuid">유저 Guid</param>
/// <param name="state">UGQState</param>
[HttpPatch]
[Route("quest-state/{questContentId}")]
[Produces("application/json")]
[SwaggerResponse(StatusCodes.Status200OK, Type = typeof(UGQContent))]
[SwaggerResponse(StatusCodes.Status400BadRequest, Type = typeof(ApiErrorResponse))]
[SwaggerResponse(StatusCodes.Status400BadRequest, Type = typeof(ValidationErrorResponse))]
public async Task<IResult> changeQuestState(string questContentId, [FromQuery] string userGuid, [FromQuery] QuestContentState state)
{
string? adminUsername = adminUsernameFromToken();
if (adminUsername == null)
return Results.Unauthorized();
return await _questEditorApi.changeQuestState(userGuid, questContentId, state, adminUsername);
}
[HttpGet]
[Route("creator-point-history")]
[Produces("application/json")]
[SwaggerResponse(StatusCodes.Status200OK, Type = typeof(UGQCreatorPointHistoryResult))]
[SwaggerResponse(StatusCodes.Status400BadRequest, Type = typeof(ApiErrorResponse))]
public async Task<IResult> getCreatorPointHistory([FromQuery] string userGuid, [FromQuery] int pageNumber, [FromQuery] int pageSize, [FromQuery] CreatorPointHistoryKind kind, DateTimeOffset startDate, DateTimeOffset endDate)
{
return await _questEditorApi.getCreatorPointHistory(userGuid, pageNumber, pageSize, kind, startDate, endDate);
}
/// <summary>
/// 모든 퀘스트 보기
/// </summary>
[HttpGet]
[Route("all-quests")]
[Produces("application/json")]
public async Task<IResult> getAllQuests([FromQuery] QuestContentState state, [FromQuery] int pageNumber, [FromQuery] int pageSize, [FromQuery] string? searchText)
{
pageSize = Math.Clamp(pageSize, 1, MAX_PAGE_SIZE);
var result = await _questEditorService.getAllQuests(pageNumber, pageSize, state, searchText);
long allCount = await _questEditorService.getAllCount();
LangEnum langEnum = Culture.ToLangEnum(CultureInfo.CurrentCulture.Name);
return Results.Ok(new UQGAllQuestResponse
{
TotalCount = allCount,
PageNumber = result.PageNumber,
PageSize = result.PageSize,
TotalPages = result.TotalPages,
Summaries = result.Items.Select(x => x.ToDTO(langEnum)).ToList()
});
}
/// <summary>
/// 계정의 모든 퀘스트 보기
/// </summary>
[HttpGet]
[Route("account-quest")]
[Produces("application/json")]
[SwaggerResponse(StatusCodes.Status200OK, Type = typeof(List<UGQSummary>))]
[SwaggerResponse(StatusCodes.Status400BadRequest, Type = typeof(ApiErrorResponse))]
public async Task<IResult> getQuests([FromQuery] int pageNumber, [FromQuery] int pageSize, [FromQuery] string userGuid, [FromQuery] QuestContentState state, [FromQuery] string? searchText)
{
pageSize = Math.Clamp(pageSize, 1, MAX_PAGE_SIZE);
List<QuestContentEntity> all = await _questEditorService.getAll(userGuid);
var result = await _questEditorService.getSummaries(pageNumber, pageSize, userGuid, state, searchText);
LangEnum langEnum = Culture.ToLangEnum(CultureInfo.CurrentCulture.Name);
return Results.Ok(new UGQSummaryResponse
{
TotalCount = all.Count,
PageNumber = result.PageNumber,
PageSize = result.PageSize,
TotalPages = result.TotalPages,
Summaries = result.Items.Select(x => x.ToDTO(langEnum)).ToList(),
});
}
}

View File

@@ -0,0 +1,320 @@
using System.Globalization;
using Google.Protobuf.WellKnownTypes;
using MetaAssets;
using ServerBase;
using ServerCommon;
using UGQApiServer.Converter;
using UGQApiServer.Models;
using UGQApiServer.UGQData;
using UGQDataAccess;
namespace UGQApiServer.Controllers.Common;
public class MetaDataApi
{
UGQMetaData _ugqMetaData;
DynamoDbClient _dynamoDbClient;
public MetaDataApi(UGQMetaData ugqMetaData, DynamoDbClient dynamoDbClient)
{
_ugqMetaData = ugqMetaData;
_dynamoDbClient = dynamoDbClient;
}
public async Task<IResult> getAssignableNpcs(string userGuid, int pageNumber, int pageSize)
{
if (pageNumber < 1)
pageNumber = 1;
pageSize = Math.Clamp(pageSize, 1, UGQConstants.MAX_INPUT_PAGE_SIZE);
(Result result, List<UgcNpcAttrib>? ugcNpcs) =
await EntitySearchHelper.findUgcNpcAttribAllByUserGuid(_dynamoDbClient, userGuid, "");
if (result.isFail())
return Results.BadRequest(ApiResponseFactory.error(result.ErrorCode));
var UserNpcPos = MetaHelper.GameConfigMeta.UGQNormalBeaconQuestMarkerPos;
List<UGQNpcMetaData> value = new List<UGQNpcMetaData>();
if (ugcNpcs != null)
{
value.AddRange(ugcNpcs.Select(x => new UGQNpcMetaData
{
UgcNpcGuid = x.UgcNpcMetaGuid,
NpcName = x.Nickname,
NpcTitle = x.Title,
NpcGender = convertGenderTypeFromItem((int)x.BodyItemMetaId),
Location = new Locaion() { x = (int)UserNpcPos.X, y = (int)UserNpcPos.Y },
}));
}
value.AddRange(_ugqMetaData.getAssignableNpcs().Select(x => new UGQNpcMetaData
{
NpcId = x.npc_id,
NpcName = x.name,
NpcTitle = x.NpcTitle,
NpcGender = x.Gender,
Location = new Locaion() { x = x.UGQmap_x, y = x.UGQmap_y },
}));
var paging = value.Skip((pageNumber - 1) * pageSize).Take(pageSize + 1).ToList();
bool hasNextPage = false;
if (paging.Count > pageSize)
hasNextPage = true;
LangEnum langEnum = Culture.ToLangEnum(CultureInfo.CurrentCulture.Name);
return Results.Ok(new UGQNpcMetaDataList
{
PageNumber = pageNumber,
PageSize = pageSize,
HasNextPage = hasNextPage,
Npcs = paging.Take(pageSize).Select(x => x.ToNpcLocale(langEnum)).ToList(),
});
}
private EGenderType convertGenderTypeFromItem(int itemId)
{
MetaData.Instance._ItemTable.TryGetValue(itemId, out var item);
if(item == null)
return EGenderType.ALL;
return item.Gender;
}
public async Task<IResult> getAvailableTaskActions(int pageNumber, int pageSize)
{
await Task.CompletedTask;
pageSize = Math.Clamp(pageSize, 1, UGQConstants.MAX_INPUT_PAGE_SIZE);
var paging = _ugqMetaData.TackActions.Skip((pageNumber - 1) * pageSize).Take(pageSize + 1).ToList();
bool hasNextPage = false;
if (paging.Count > pageSize)
hasNextPage = true;
LangEnum langEnum = Culture.ToLangEnum(CultureInfo.CurrentCulture.Name);
return Results.Ok(new TaskActionList
{
PageNumber = pageNumber,
PageSize = pageSize,
HasNextPage = hasNextPage,
TaskActions = paging.Take(pageSize).Select(x => x.Value.ToTaskAction(langEnum)).ToList(),
});
}
public async Task<IResult> getActionValues(string userGuid, int taskActionId, int pageNumber, int pageSize)
{
await Task.CompletedTask;
if (pageNumber < 1)
pageNumber = 1;
pageSize = Math.Clamp(pageSize, 1, UGQConstants.MAX_INPUT_PAGE_SIZE);
List<UGQActionValue> taskActions = [];
{
_ugqMetaData.TackActions.TryGetValue(taskActionId, out var value);
if (value == null)
return Results.BadRequest(ApiResponseFactory.error(ServerErrorCode.UgqInvalidTaskAction));
if (value.Disabled == true)
return Results.BadRequest(ApiResponseFactory.error(ServerErrorCode.UgqTaskActionDisabled));
taskActions = value.TaskActions;
}
// taskActionId<49><64> talk<6C><6B> <20><><EFBFBD><EFBFBD> dynamodb<64><62><EFBFBD><EFBFBD> <20><> <20><><EFBFBD><EFBFBD> npc <20><><EFBFBD><EFBFBD><EFBFBD>;<EFBFBD> <20>Ѵ<EFBFBD>
if (taskActionId == UGQMetaData.TALK_ACTION_ID)
{
(Result result, List<UgcNpcAttrib>? ugcNpcs) =
await EntitySearchHelper.findUgcNpcAttribAllByUserGuid(_dynamoDbClient, userGuid, "");
if (result.isFail())
return Results.BadRequest(ApiResponseFactory.error(result.ErrorCode));
if (ugcNpcs != null)
{
List<UGQActionValue> npcValues = new List<UGQActionValue>();
npcValues.AddRange(ugcNpcs.Select(x => new UGQActionValue
{
UgcValueGuid = x.UgcNpcMetaGuid,
ValueName = x.Nickname,
}));
npcValues.AddRange(taskActions);
taskActions = npcValues;
}
}
var paging = taskActions.Skip((pageNumber - 1) * pageSize).Take(pageSize + 1).ToList();
bool hasNextPage = false;
if (paging.Count > pageSize)
hasNextPage = true;
LangEnum langEnum = Culture.ToLangEnum(CultureInfo.CurrentCulture.Name);
return Results.Ok(new TaskActionValueList
{
PageNumber = pageNumber,
PageSize = pageSize,
HasNextPage = hasNextPage,
Values = paging.Take(pageSize).Select(x => x.ToTaskActionValue(langEnum)).ToList(),
});
}
public async Task<IResult> getDialogTypes(int pageNumber, int pageSize)
{
await Task.CompletedTask;
pageSize = Math.Clamp(pageSize, 1, UGQConstants.MAX_INPUT_PAGE_SIZE);
var paging = _ugqMetaData.DialogActions.Skip((pageNumber - 1) * pageSize).Take(pageSize + 1).ToList();
bool hasNextPage = false;
if (paging.Count > pageSize)
hasNextPage = true;
LangEnum langEnum = Culture.ToLangEnum(CultureInfo.CurrentCulture.Name);
return Results.Ok(new DialogTypeList
{
PageNumber = pageNumber,
PageSize = pageSize,
HasNextPage = hasNextPage,
DialogTypes = _ugqMetaData.DialogActions.Select(x => x.Value.ToDialogType(langEnum)).ToList(),
});
}
public async Task<IResult> getDialogActions(int dialogType, int pageNumber, int pageSize)
{
await Task.CompletedTask;
if (pageNumber < 1)
pageNumber = 1;
pageSize = Math.Clamp(pageSize, 1, UGQConstants.MAX_INPUT_PAGE_SIZE);
_ugqMetaData.DialogActions.TryGetValue(dialogType, out var value);
if (value == null)
return Results.BadRequest(ApiResponseFactory.error(ServerErrorCode.UgqInvalidDialogType));
var paging = value.DialogConditions.Skip((pageNumber - 1) * pageSize).Take(pageSize + 1).ToList();
bool hasNextPage = false;
if (paging.Count > pageSize)
hasNextPage = true;
LangEnum langEnum = Culture.ToLangEnum(CultureInfo.CurrentCulture.Name);
return Results.Ok(new DialogActionList
{
PageNumber = pageNumber,
PageSize = pageSize,
HasNextPage = hasNextPage,
DialogActions = paging.Take(pageSize).Select(x => x.ToDialogAction(langEnum)).ToList(),
});
}
public async Task<IResult> getDialogConditionValues(int dialogType, int dialogActionId, int pageNumber, int pageSize)
{
await Task.CompletedTask;
if (pageNumber < 1)
pageNumber = 1;
pageSize = Math.Clamp(pageSize, 1, UGQConstants.MAX_INPUT_PAGE_SIZE);
_ugqMetaData.DialogActions.TryGetValue(dialogType, out var value);
if (value == null)
return Results.BadRequest(ApiResponseFactory.error(ServerErrorCode.UgqInvalidDialogType));
var dialogAction = value.DialogConditions.Find(x => x.ConditionId == dialogActionId);
if (dialogAction == null)
return Results.BadRequest(ApiResponseFactory.error(ServerErrorCode.UgqInvalidDialogCondition));
var paging = dialogAction.DialogConditionValues.Skip((pageNumber - 1) * pageSize).Take(pageSize + 1).ToList();
bool hasNextPage = false;
if (paging.Count > pageSize)
hasNextPage = true;
LangEnum langEnum = Culture.ToLangEnum(CultureInfo.CurrentCulture.Name);
return Results.Ok(new DialogActionValueList
{
PageNumber = pageNumber,
PageSize = pageSize,
HasNextPage = hasNextPage,
Values = paging.Take(pageSize).Select(x => x.ToDialogActionValue(langEnum)).ToList(),
});
}
public async Task<IResult> getNpcActionValues(int pageNumber, int pageSize, EGenderType gender)
{
await Task.CompletedTask;
if (pageNumber < 1)
pageNumber = 1;
pageSize = Math.Clamp(pageSize, 1, UGQConstants.MAX_INPUT_PAGE_SIZE);
var npcActionMetaDatas = MetaData.Instance.UGQBeaconActionDataListbyId;
if(gender != EGenderType.ALL)
{
npcActionMetaDatas = npcActionMetaDatas.Where(x => x.Value.Gender == gender || x.Value.Gender == EGenderType.ALL).ToDictionary(x => x.Key, x => x.Value);
}
var paging = npcActionMetaDatas.Skip((pageNumber - 1) * pageSize).Take(pageSize + 1).ToList();
bool hasNextPage = false;
if (paging.Count > pageSize)
hasNextPage = true;
LangEnum langEnum = Culture.ToLangEnum(CultureInfo.CurrentCulture.Name);
return Results.Ok(new NpcActionValueList
{
PageNumber = pageNumber,
PageSize = pageSize,
HasNextPage = hasNextPage,
Values = paging.Take(pageSize).Select(x => x.Value.ToNpcActionValue(langEnum)).ToList(),
});
}
public async Task<IResult> getMapDataValues(int pageNumber, int pageSize)
{
await Task.CompletedTask;
if (pageNumber < 1)
pageNumber = 1;
pageSize = Math.Clamp(pageSize, 1, UGQConstants.MAX_INPUT_PAGE_SIZE);
var zoneMetaDatas = MetaData.Instance.ZoneMetaDataListbyId;
var paging = zoneMetaDatas.Skip((pageNumber - 1) * pageSize).Take(pageSize + 1).ToList();
bool hasNextPage = false;
if (paging.Count > pageSize)
hasNextPage = true;
LangEnum langEnum = Culture.ToLangEnum(CultureInfo.CurrentCulture.Name);
return Results.Ok(new MapDataValueList
{
PageNumber = pageNumber,
PageSize = pageSize,
HasNextPage = hasNextPage,
Values = paging.Take(pageSize).Select(x => x.Value.ToMapDataValue(langEnum)).ToList(),
});
}
public async Task<IResult> getPresetImages(PresetKind kind, PresetCategory category)
{
await Task.CompletedTask;
if (_ugqMetaData.PresetImages.TryGetValue((kind, category), out var presetImages) == false)
return Results.BadRequest();
return Results.Ok(presetImages.Select(x => x.ToDTO()).ToList());
}
}

View File

@@ -0,0 +1,475 @@
using System.Globalization;
using Asp.Versioning;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using MongoDB.Bson.Serialization.Attributes;
using Swashbuckle.AspNetCore.Annotations;
using Swashbuckle.AspNetCore.Filters;
using UGQApiServer.Models;
using UGQDatabase.Models;
using UGQDataAccess.Service;
using UGQApiServer.Converter;
using UGQApiServer.Storage;
using UGQDataAccess.Repository.Models;
using Microsoft.AspNetCore.Authorization;
using UGQDataAccess;
using UGQApiServer.Validation;
using UGQApiServer.UGQData;
using UGQApiServer.Auth;
using JwtRoleAuthentication.Services;
using System.Security.Claims;
using Newtonsoft.Json.Linq;
using ServerCommon;
using ServerBase;
using ServerCommon.UGQ;
using ServerCommon.UGQ.Models;
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.IdentityModel.Tokens;
using System.Collections.Generic;
namespace UGQApiServer.Controllers.Common;
public class QuestEditorApi
{
QuestEditorService _questEditorService;
UGQMetaData _ugqMetaData;
DynamoDbClient _dynamoDbClient;
IStorageService _storageService;
public QuestEditorApi(QuestEditorService questEditorService,
UGQMetaData ugqMetaData, DynamoDbClient dynamoDbClient,
IStorageService storageService)
{
_questEditorService = questEditorService;
_ugqMetaData = ugqMetaData;
_dynamoDbClient = dynamoDbClient;
_storageService = storageService;
}
public async Task<IResult> addQuest(string userGuid, UGQSaveQuestModel saveQuestModel, string adminUsername = "")
{
if (string.IsNullOrEmpty(saveQuestModel.UgcBeaconGuid) == false)
{
(Result result, List<UgcNpcAttrib>? ugcNpcs) =
await EntitySearchHelper.findUgcNpcAttribAllByUserGuid(_dynamoDbClient, userGuid, "");
if (result.isFail())
return Results.BadRequest(ApiResponseFactory.error(ServerErrorCode.UgqGameDbaccessError));
if (ugcNpcs == null)
return Results.BadRequest(ApiResponseFactory.error(ServerErrorCode.UgqGameDbaccessError));
UgcNpcAttrib? found = ugcNpcs.Where(x => x.UgcNpcMetaGuid == saveQuestModel.UgcBeaconGuid).FirstOrDefault();
if (found == null)
return Results.BadRequest(ApiResponseFactory.error(ServerErrorCode.UgqNotOwnUgcNpc));
saveQuestModel.UgcBeaconNickname = found.Nickname;
}
foreach (var task in saveQuestModel.Tasks)
{
if (string.IsNullOrEmpty(task.UgcActionValueGuid) == false)
{
(Result result, List<UgcNpcAttrib>? ugcNpcs) =
await EntitySearchHelper.findUgcNpcAttribAllByUserGuid(_dynamoDbClient, userGuid, "");
if (result.isFail())
return Results.BadRequest(ApiResponseFactory.error(ServerErrorCode.UgqGameDbaccessError));
if (ugcNpcs == null)
return Results.BadRequest(ApiResponseFactory.error(ServerErrorCode.UgqGameDbaccessError));
UgcNpcAttrib? found = ugcNpcs.Where(x => x.UgcNpcMetaGuid == task.UgcActionValueGuid).FirstOrDefault();
if (found == null)
return Results.BadRequest(ApiResponseFactory.error(ServerErrorCode.UgqNotOwnUgcNpc));
task.UgcActionValueName = found.Nickname;
}
}
(ServerErrorCode errorCode, var entity) = await _questEditorService.addQuest(userGuid, saveQuestModel, adminUsername);
if (errorCode != ServerErrorCode.Success)
return Results.BadRequest(ApiResponseFactory.error(errorCode));
if (entity == null)
return Results.BadRequest(ApiResponseFactory.error(ServerErrorCode.UgqNullEntity));
LangEnum langEnum = Culture.ToLangEnum(CultureInfo.CurrentCulture.Name);
return Results.Ok(entity.ToDTO(langEnum));
}
public async Task<IResult> getQuest(string userGuid, string questContentId)
{
(ServerErrorCode errorCode, var entity) = await _questEditorService.getQuest(userGuid, questContentId);
if (errorCode != ServerErrorCode.Success)
return Results.BadRequest(ApiResponseFactory.error(errorCode));
if (entity == null)
return Results.BadRequest(ApiResponseFactory.error(ServerErrorCode.UgqNullEntity));
LangEnum langEnum = Culture.ToLangEnum(CultureInfo.CurrentCulture.Name);
return Results.Ok(entity.ToDTO(langEnum));
}
public async Task<IResult> editQuest(string userGuid, string questContentId)
{
(ServerErrorCode errorCode, var entity) = await _questEditorService.editQuest(userGuid, questContentId);
if (errorCode != ServerErrorCode.Success)
return Results.BadRequest(ApiResponseFactory.error(errorCode));
if (entity == null)
return Results.BadRequest(ApiResponseFactory.error(ServerErrorCode.UgqNullEntity));
LangEnum langEnum = Culture.ToLangEnum(CultureInfo.CurrentCulture.Name);
return Results.Ok(entity.ToDTO(langEnum));
}
public async Task<IResult> saveQuest(string userGuid, string questContentId, UGQSaveQuestModel saveQuestModel)
{
if (string.IsNullOrEmpty(saveQuestModel.UgcBeaconGuid) == false)
{
(Result result, List<UgcNpcAttrib>? ugcNpcs) =
await EntitySearchHelper.findUgcNpcAttribAllByUserGuid(_dynamoDbClient, userGuid, "");
if (result.isFail())
return Results.BadRequest(ApiResponseFactory.error(ServerErrorCode.UgqGameDbaccessError));
if (ugcNpcs == null)
return Results.BadRequest(ApiResponseFactory.error(ServerErrorCode.UgqGameDbaccessError));
UgcNpcAttrib? found = ugcNpcs.Where(x => x.UgcNpcMetaGuid == saveQuestModel.UgcBeaconGuid).FirstOrDefault();
if (found == null)
return Results.BadRequest(ApiResponseFactory.error(ServerErrorCode.UgqNotOwnUgcNpc));
saveQuestModel.UgcBeaconNickname = found.Nickname;
}
foreach (var task in saveQuestModel.Tasks)
{
if (string.IsNullOrEmpty(task.UgcActionValueGuid) == false)
{
(Result result, List<UgcNpcAttrib>? ugcNpcs) =
await EntitySearchHelper.findUgcNpcAttribAllByUserGuid(_dynamoDbClient, userGuid, "");
if (result.isFail())
return Results.BadRequest(ApiResponseFactory.error(ServerErrorCode.UgqGameDbaccessError));
if (ugcNpcs == null)
return Results.BadRequest(ApiResponseFactory.error(ServerErrorCode.UgqGameDbaccessError));
UgcNpcAttrib? found = ugcNpcs.Where(x => x.UgcNpcMetaGuid == task.UgcActionValueGuid).FirstOrDefault();
if (found == null)
return Results.BadRequest(ApiResponseFactory.error(ServerErrorCode.UgqNotOwnUgcNpc));
task.UgcActionValueName = found.Nickname;
}
}
(ServerErrorCode errorCode, var updated) = await _questEditorService.saveQuest(userGuid, questContentId, saveQuestModel);
if (errorCode != ServerErrorCode.Success)
return Results.BadRequest(ApiResponseFactory.error(errorCode));
if (updated == null)
return Results.BadRequest(ApiResponseFactory.error(ServerErrorCode.UgqNullEntity));
LangEnum langEnum = Culture.ToLangEnum(CultureInfo.CurrentCulture.Name);
return Results.Ok(updated.ToDTO(langEnum));
}
public async Task<IResult> deleteQuest(string userGuid, string questContentId)
{
var errorCode = await _questEditorService.deleteQuest(userGuid, questContentId);
if (errorCode != ServerErrorCode.Success)
return Results.BadRequest(ApiResponseFactory.error(errorCode));
return Results.Ok();
}
public async Task<IResult> uploadQuestImage(string userGuid, string questContentId,
int titleImageId, int bannerImageId,
IFormFile? title, IFormFile? banner)
{
(ServerErrorCode errorCode, var entity) = await _questEditorService.editQuest(userGuid, questContentId);
if (errorCode != ServerErrorCode.Success)
return Results.BadRequest(ApiResponseFactory.error(errorCode));
if (entity == null)
return Results.BadRequest(ApiResponseFactory.error(ServerErrorCode.UgqNullEntity));
string titleFile = entity.TitleImagePath;
string bannerFile = entity.BannerImagePath;
List<string> deleteFiles = new();
if (titleImageId != 0)
{
var data = _ugqMetaData.getPresetImage(titleImageId);
if (data != null)
titleFile = data.FileName;
}
else
{
if (title != null)
{
FileInfo fileInfo = new FileInfo(title.FileName);
(errorCode, int uploadCounter) = await _questEditorService.incUploadCounter(userGuid, questContentId);
if (errorCode != ServerErrorCode.Success)
return Results.BadRequest(ApiResponseFactory.error(errorCode));
string filename = $"{questContentId}_title_{uploadCounter}{fileInfo.Extension}";
await _storageService.uploadFile(filename, title);
if (string.IsNullOrEmpty(entity.TitleImagePath) == false &&
entity.TitleImagePath.StartsWith("preset/") == false)
deleteFiles.Add(entity.TitleImagePath);
titleFile = filename;
}
}
if (bannerImageId != 0)
{
var data = _ugqMetaData.getPresetImage(bannerImageId);
if (data != null)
bannerFile = data.FileName;
}
else
{
if (banner != null)
{
FileInfo fileInfo = new FileInfo(banner.FileName);
(errorCode, int uploadCounter) = await _questEditorService.incUploadCounter(userGuid, questContentId);
if (errorCode != ServerErrorCode.Success)
return Results.BadRequest(ApiResponseFactory.error(errorCode));
string filename = $"{questContentId}_banner_{uploadCounter}{fileInfo.Extension}";
await _storageService.uploadFile(filename, banner);
if (string.IsNullOrEmpty(entity.BannerImagePath) == false &&
entity.BannerImagePath.StartsWith("preset/") == false)
deleteFiles.Add(entity.BannerImagePath);
bannerFile = filename;
}
}
(errorCode, var updated) = await _questEditorService.updateQuestImages(questContentId, titleFile, bannerFile);
if (errorCode != ServerErrorCode.Success)
return Results.BadRequest(ApiResponseFactory.error(errorCode));
if (updated == null)
return Results.BadRequest(ApiResponseFactory.error(ServerErrorCode.UgqNullEntity));
foreach (var file in deleteFiles)
await _storageService.deleteFile(file);
LangEnum langEnum = Culture.ToLangEnum(CultureInfo.CurrentCulture.Name);
return Results.Ok(updated.ToDTO(langEnum));
}
public async Task<IResult> getQuestDialog(string userGuid, string questContentId, string dialogId)
{
(ServerErrorCode errorCode, var entity) = await _questEditorService.getQuestDialog(userGuid, questContentId, dialogId);
if (errorCode != ServerErrorCode.Success)
return Results.BadRequest(ApiResponseFactory.error(errorCode));
if (entity == null)
return Results.BadRequest(ApiResponseFactory.error(ServerErrorCode.UgqNullEntity));
LangEnum langEnum = Culture.ToLangEnum(CultureInfo.CurrentCulture.Name);
return Results.Ok(entity.ToDTO(langEnum));
}
public async Task<IResult> addQuestDialog(string userGuid, string questContentId, int taskIndex, UGQSaveDialogModel questDialog)
{
var sequences = questDialog.Sequences.Select(x => new DialogSequenceEntity
{
SequenceId = x.SequenceId,
Actions = x.Actions.Select(x => new DialogSequenceActionEntity
{
Talker = x.Talker,
Type = x.Type,
Talk = new TextEntity
{
Kr = x.Talk.Kr,
En = x.Talk.En,
Jp = x.Talk.Jp,
},
NpcAction = x.NpcAction,
Condition = x.Condition,
ConditionValue = x.ConditionValue,
NextSequence = x.NextSequence,
}).ToList(),
}).ToList();
(ServerErrorCode errorCode, var updated, var inserted) = await _questEditorService.addQuestDialog(userGuid, questContentId, taskIndex, sequences);
if (errorCode != ServerErrorCode.Success)
return Results.BadRequest(ApiResponseFactory.error(errorCode));
if (updated == null || inserted == null)
return Results.BadRequest(ApiResponseFactory.error(ServerErrorCode.UgqNullEntity));
LangEnum langEnum = Culture.ToLangEnum(CultureInfo.CurrentCulture.Name);
return Results.Ok(new UGQAddQuestDialogResponse
{
QuestContent = updated.ToDTO(langEnum),
QuestDialog = inserted.ToDTO(langEnum),
});
}
public async Task<IResult> saveQuestDialog(string userGuid, string questContentId, string dialogId, [FromBody] UGQSaveDialogModel questDialog)
{
var sequences = questDialog.Sequences.Select(x => new DialogSequenceEntity
{
SequenceId = x.SequenceId,
Actions = x.Actions.Select(x => new DialogSequenceActionEntity
{
Talker = x.Talker,
Type = x.Type,
Talk = new TextEntity
{
Kr = x.Talk.Kr,
En = x.Talk.En,
Jp = x.Talk.Jp,
},
NpcAction = x.NpcAction,
Condition = x.Condition,
ConditionValue = x.ConditionValue,
NextSequence = x.NextSequence,
}).ToList(),
}).ToList();
(ServerErrorCode errorCode, var updated) = await _questEditorService.saveQuestDialog(userGuid, questContentId, dialogId, sequences);
if (errorCode != ServerErrorCode.Success)
return Results.BadRequest(ApiResponseFactory.error(errorCode));
if (updated == null)
return Results.BadRequest(ApiResponseFactory.error(ServerErrorCode.UgqNullEntity));
LangEnum langEnum = Culture.ToLangEnum(CultureInfo.CurrentCulture.Name);
return Results.Ok(updated.ToDTO(langEnum));
}
List<QuestContentState> getAvailablesState(QuestContentState state)
{
List<QuestContentState> availables = [];
switch (state)
{
case QuestContentState.Editable:
availables = [
QuestContentState.Test,
QuestContentState.Standby,
QuestContentState.Shutdown
];
break;
case QuestContentState.Uncomplate:
case QuestContentState.Test:
availables = [
QuestContentState.Editable
];
break;
case QuestContentState.Standby:
availables = [
QuestContentState.Live
];
break;
case QuestContentState.Live:
availables = [
QuestContentState.Standby
];
break;
case QuestContentState.Shutdown:
availables = [];
break;
}
return availables;
}
public async Task<IResult> changeQuestState(string userGuid, string questContentId, QuestContentState state, string adminUsername = "")
{
(ServerErrorCode errorCode, var questContentEntity) = await _questEditorService.getQuest(userGuid, questContentId);
if (errorCode != ServerErrorCode.Success)
return Results.BadRequest(ApiResponseFactory.error(errorCode));
if (questContentEntity == null)
return Results.BadRequest(ApiResponseFactory.error(ServerErrorCode.UgqNullEntity));
// admin<69><6E> <20><><EFBFBD><EFBFBD> <20><><EFBFBD>Ѿ<EFBFBD><D1BE><EFBFBD> <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD>ϵ<EFBFBD><CFB5><EFBFBD> ó<><C3B3> - Uncomplate <20><><EFBFBD><EFBFBD><C2B6><EFBFBD> <20><><EFBFBD><EFBFBD> <20>Ұ<EFBFBD><D2B0><EFBFBD>
List<QuestContentState> before = [];
if (string.IsNullOrEmpty(adminUsername) == false)
{
before = [
QuestContentState.Editable,
QuestContentState.Test,
QuestContentState.Standby,
QuestContentState.Live,
QuestContentState.Shutdown,
];
}
else
{
before = getAvailablesState(state);
}
if (before.Any(x => x == questContentEntity.State) == false)
return Results.BadRequest(ApiResponseFactory.error(ServerErrorCode.UgqStateChangeError));
QuestContentEntity? updated = null;
var questDialogIds = questContentEntity.Tasks
.Where(x => string.IsNullOrEmpty(x.DialogId) == false)
.Select(x => x.DialogId!)
.ToList();
var questDialogs = await _questEditorService.getQuestDialogs(questDialogIds);
if (state == QuestContentState.Test)
{
UGQValidator validator = new UGQValidator(_ugqMetaData);
var errors = validator.Validate(questContentEntity, questDialogs);
if (errors.Count > 0)
{
return Results.BadRequest(
ApiResponseFactory.validationError(ServerErrorCode.UgqValidationError,
errors.Select(x => x.ToString()).ToList()));
}
}
(errorCode, updated) = await _questEditorService.changeQuestStateForEditor(userGuid,
questContentEntity, questDialogs, before, state, adminUsername);
if (errorCode != ServerErrorCode.Success)
return Results.BadRequest(ApiResponseFactory.error(errorCode));
if (updated == null)
return Results.BadRequest(ApiResponseFactory.error(ServerErrorCode.UgqNullEntity));
LangEnum langEnum = Culture.ToLangEnum(CultureInfo.CurrentCulture.Name);
return Results.Ok(updated.ToDTO(langEnum));
}
public async Task<IResult> getCreatorPointHistory(string userGuid, int pageNumber, int pageSize, CreatorPointHistoryKind kind, DateTimeOffset startDate, DateTimeOffset endDate)
{
pageSize = Math.Clamp(pageSize, 1, UGQConstants.MAX_INPUT_PAGE_SIZE);
var result = await _questEditorService.getCreatorPointHistories(
userGuid, pageNumber, pageSize, kind, startDate, endDate);
LangEnum langEnum = Culture.ToLangEnum(CultureInfo.CurrentCulture.Name);
return Results.Ok(result.ToDTO(langEnum));
}
public async Task<IResult> getQuestProfitStats(string userGuid, int pageNumber, int pageSize)
{
pageSize = Math.Clamp(pageSize, 1, UGQConstants.MAX_INPUT_PAGE_SIZE);
var result = await _questEditorService.getQuestProfitStats(userGuid, pageNumber, pageSize);
LangEnum langEnum = Culture.ToLangEnum(CultureInfo.CurrentCulture.Name);
return Results.Ok(new UGQQuestProfitStatsResult
{
PageNumber = result.PageNumber,
PageSize = result.PageSize,
TotalPages = result.TotalPages,
Items = result.Items.Select(x => x.ToDTO(langEnum)).ToList()
});
}
}

View File

@@ -0,0 +1,598 @@
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<AccountBaseAttrib>();
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<AccountBaseDoc>();
(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<NicknameAttrib>();
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<NicknameDoc>();
(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<MoneyAttrib>();
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<MoneyDoc>();
(result, _) = await table.simpleUpsertDocument(money_document);
if (result.isFail())
{
return (result.getErrorCode(), null);
}
return (ServerErrorCode.Success, money_doc_doc_attrib);
}
/// <summary>
/// email로 계정 정보 얻기
/// </summary>
[HttpGet]
[Route("get-gamedb-account-by-email")]
public async Task<IResult> 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,
});
}
}
/// <summary>
/// email로 DynamoDB에 AccountBaseDoc, NicknameDoc을 생성
/// </summary>
/// <remarks>
/// 주의! - 게임 클라이언트를 사용할 수 없는 경우에만 api로 계정을 만드십시오!
/// UGQ api를 사용하기 위한 최소한의 세팅으로 계정을 만드는 거라
/// 이 api로 만든 계정으로 게임 접속 시에는 정상적으로 작동하지 않습니다.
/// </remarks>
[HttpPost]
[Route("add-gamedb-account-by-email")]
public async Task<IResult> 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,
});
}
/// <summary>
/// loginAccountId로 DynamoDB에 AccountBaseDoc, NicknameDoc을 생성
/// </summary>
/// <remarks>
/// 주의! - 게임 클라이언트를 사용할 수 없는 경우에만 api로 계정을 만드십시오!
/// UGQ api를 사용하기 위한 최소한의 세팅으로 계정을 만드는 거라
/// 이 api로 만든 계정으로 게임 접속 시에는 정상적으로 작동하지 않습니다.
/// </remarks>
[HttpPost]
[Route("add-gamedb-account")]
public async Task<IResult> 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,
});
}
/// <summary>
/// DynamoDB에 UgcNpcDoc을 생성
/// </summary>
/// <remarks>
/// 주의! - 게임 클라이언트를 사용할 수 없는 경우에만 api로 UgcNpc를 만드십시오!
/// UGQ api를 사용하기 위한 최소한의 세팅으로 UgcNpc를 만드는 거라
/// 이 api로 만든 UgcNpc는 게임 접속 시에는 정상적으로 작동하지 않을 수 잇습니다.
/// </remarks>
[HttpPost]
[Route("add-fake-ugc-npc")]
public async Task<IResult> 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<UgcNpcAttrib>();
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<UgcNpcDoc>();
(result, _) = await table.simpleUpsertDocument(ugq_npc_document);
if (result.isFail())
{
return Results.BadRequest(ApiResponseFactory.error(result.getErrorCode()));
}
return Results.Ok();
}
/// <summary>
/// Creator Point를 증가
/// </summary>
[HttpPost]
[Route("add-creator-point")]
public async Task<IResult> 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();
}
/// <summary>
/// Creator Point를 감소
/// </summary>
[HttpPost]
[Route("settle-up-creator-point")]
public async Task<IResult> 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();
}
/// <summary>
/// GameQuestData를 새로 쓰기
/// </summary>
[HttpPost]
[Route("ugq-game-quest-data-fake-update")]
public async Task<IResult> gameQuestDataFakeUpdate(GameQuestDataFakeUpdateReqeust request)
{
await _questEditorService.gameQuestDataFakeUpdate(request.UserGuid, request.QuestContentId);
return Results.Ok();
}
/// <summary>
/// s3에 올라간 preset 파일 리스트 얻기
/// </summary>
[HttpGet]
[Route("get-s3-preset")]
public async Task<IResult> getS3Preset()
{
return Results.Ok(await _ugqbannerImageList.getFiles());
}
/// <summary>
/// DynamoDB sacn을 사용해서 userGuid로 AccountBaseDoc을 얻기
/// </summary>
[HttpGet]
[Route("find-account-id")]
public async Task<IResult> findAccountId([FromQuery] string userGuid)
{
var client = _dynamoDbClient.getDbClient();
if (client == null)
return Results.BadRequest();
JsonNode? found_account = null;
Dictionary<string, AttributeValue>? lastEvaluatedKey = null;
do
{
var request = new ScanRequest()
{
TableName = _dynamoDbClient.getTableFullName(ServerBase.DynamoDbDefine.TableNames.Main),
FilterExpression = "DocType = :v_docType",
ExpressionAttributeValues = new Dictionary<string, AttributeValue>()
{
{ ":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<string>();
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,
});
}
/// <summary>
/// 레디스에 저장된 ugq 로그인 정보를 모두 삭제
/// </summary>
[HttpPost]
[Route("clear-all-login")]
public async Task<IResult> clearAllLogin()
{
var server = _redis.GetServer(_redis.GetEndPoints().First());
var enumerable = server.KeysAsync(pattern: "ugq_login:*");
var enumerator = enumerable.GetAsyncEnumerator();
var dataList = new List<RedisKey>();
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<MoneyDoc>(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<MoneyDoc>(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);
}
/// <summary>
/// DynamoDB에 저장된 재화를 변경
/// </summary>
[HttpPost]
[Route("change-currency-amount")]
public async Task<IResult> 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,
});
}
/// <summary>
/// 계정이 가지고 있는 모든 UGQ 데이터를 검증
/// </summary>
[HttpPost]
[Route("validate-quest")]
public async Task<IResult> validateQuest(ValidateQuestRequest request)
{
List<ValidateQuestItem> 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<IResult> setAuthor()
{
await _inGameService.setAuthor();
return Results.Ok();
}
}
#endif
}

View File

@@ -0,0 +1,371 @@
using Asp.Versioning;
using MetaAssets;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using StackExchange.Redis;
using UGQDataAccess.Repository;
using UGQDataAccess.Service;
using ServerCommon.UGQ.Models;
using System.Globalization;
using MongoDB.Driver.Linq;
using UGQApiServer.Converter;
using UGQDataAccess;
using ServerCommon.UGQ;
using UGQDatabase.Models;
using Amazon.Runtime.Internal;
using System.Security.Principal;
using Amazon.S3.Model;
using Microsoft.Extensions.Caching.Distributed;
using ServerCommon.BusinessLogDomain;
using ServerCommon;
namespace UGQApiServer.Controllers
{
[ApiController]
[ApiVersion("1.0")]
[ApiExplorerSettings(GroupName = "ingame")]
[Route("api/v{version:apiVersion}/[controller]")]
[UGQInGameApi]
public class InGameController : ControllerBase
{
InGameService _inGameService;
IConnectionMultiplexer _redis;
public InGameController(InGameService inGameService, IConnectionMultiplexer redis)
{
_inGameService = inGameService;
_redis = redis;
}
/// <summary>
/// userGuid의 UGQ 계정 정보 얻기
/// </summary>
[HttpGet]
[Route("account/{userGuid}")]
public async Task<IResult> getAccount(string userGuid)
{
(ServerErrorCode errorCode, var accountEntity) = await _inGameService.getAccount(userGuid);
if (errorCode != ServerErrorCode.Success)
return Results.BadRequest(ApiResponseFactory.error(errorCode));
if (accountEntity == null)
return Results.BadRequest(ApiResponseFactory.error(ServerErrorCode.UgqNullEntity));
return Results.Ok(new UgqAccount
{
GradeType = accountEntity.GradeType,
CreatorPoint = accountEntity.CreatorPoint,
});
}
/// <summary>
/// 직접 작성한 퀘스트를 수락해서 완료까지 진행해보기 위해서 Test 상태인 퀘스트 리스트
/// </summary>
[HttpGet]
[Route("user-quests-in-test-state/{userGuid}")]
public async Task<IResult> getUserQuestsInTestState(string userGuid)
{
var result = await _inGameService.getUserQuests(userGuid, [QuestContentState.Test]);
LangEnum langEnum = Culture.ToLangEnum(CultureInfo.CurrentCulture.Name);
return Results.Ok(result.Select(x => x.ToProtoUgqQuestInTestState(langEnum, ImageUrlType.BannerImg)).ToList());
}
/// <summary>
/// userGuid가 작성해서 Live 상태로 보낸 퀘스트 리스트
/// </summary>
[HttpGet]
[Route("quest-board/{userGuid}")]
public async Task<IResult> getUserQuestBoard(string userGuid, [FromQuery] UgqQuestBoardRequest request)
{
int pageSize = Math.Clamp(request.PageSize, 1, UGQConstants.MAX_INPUT_PAGE_SIZE);
var result = await _inGameService.getQuestBoard(userGuid, request, pageSize);
LangEnum langEnum = Culture.ToLangEnum(CultureInfo.CurrentCulture.Name);
return Results.Ok(result.ToProto(langEnum, ImageUrlType.BannerImg));
}
/// <summary>
/// 전체 Live 상태인 퀘스트 리스트
/// </summary>
[HttpGet]
[Route("quest-board")]
public async Task<IResult> getQuestBoard([FromQuery] UgqQuestBoardRequest request)
{
int pageSize = Math.Clamp(request.PageSize, 1, UGQConstants.MAX_INPUT_PAGE_SIZE);
var result = await _inGameService.getQuestBoard(null, request, pageSize);
LangEnum langEnum = Culture.ToLangEnum(CultureInfo.CurrentCulture.Name);
return Results.Ok(result.ToProto(langEnum, ImageUrlType.BannerImg));
}
/// <summary>
/// userGuid가 북마크한 Live 상태 퀘스트 리스트
/// </summary>
[HttpGet]
[Route("bookmark-quest-board/{userGuid}")]
public async Task<IResult> getBookmarkQuestBoard(string userGuid, [FromQuery] UgqQuestBoardRequest request)
{
int pageSize = Math.Clamp(request.PageSize, 1, UGQConstants.MAX_INPUT_PAGE_SIZE);
var result = await _inGameService.getBookmarkQuests(userGuid, request, pageSize);
LangEnum langEnum = Culture.ToLangEnum(CultureInfo.CurrentCulture.Name);
return Results.Ok(result.ToProto(langEnum, ImageUrlType.BannerImg));
}
/// <summary>
/// 퀘스트 스포트라이트
/// </summary>
[HttpGet]
[Route("quest-board-spotlight")]
public async Task<IResult> getQuestBoardSpotlight()
{
var result = await _inGameService.getQuestBoardSpotlight();
LangEnum langEnum = Culture.ToLangEnum(CultureInfo.CurrentCulture.Name);
return Results.Ok(result.ToProto(langEnum, ImageUrlType.TitleImg));
}
/// <summary>
/// 퀘스트 자세한 정보
/// </summary>
[HttpGet]
[Route("quest-board-detail/{userGuid}")]
public async Task<IResult> getQuestBoardDetail(string userGuid, [FromQuery] UgqQuestIdRevisionRequest request)
{
var result = await _inGameService.getQuestBoardDetail(userGuid, request.QuestId, request.Revision);
if (result == null)
return Results.BadRequest(ApiResponseFactory.error(ServerErrorCode.UgqNullEntity));
LangEnum langEnum = Culture.ToLangEnum(CultureInfo.CurrentCulture.Name);
return Results.Ok(result.ToProto(langEnum, ImageUrlType.BannerImg));
}
/// <summary>
/// Test 상태인 퀘스트를 정상적으로 완료시켰을 때 호출해서 Standby 상태로 변경
/// </summary>
[HttpPost]
[Route("set-test-completed/{userGuid}")]
public async Task<IResult> setTestCompleted(string userGuid, [FromQuery] long questId, [FromQuery] long revision)
{
// 여기에서만 Test -> Standby 가능
(var errorCode, var updated) = await _inGameService.changeQuestStateForInGame(userGuid,
questId, revision, [QuestContentState.Test], QuestContentState.Standby);
if (errorCode != ServerErrorCode.Success)
return Results.BadRequest(ApiResponseFactory.error(errorCode));
return Results.Ok();
}
/// <summary>
/// Test 상태인 퀘스트를 포기했을 때 호출해서 Editable 상태로 변경
/// </summary>
[HttpPost]
[Route("set-test-aborted/{userGuid}")]
public async Task<IResult> setTestAborted(string userGuid, [FromQuery] long questId, [FromQuery] long revision)
{
// 여기에서만 Test -> Editable 가능
(var errorCode, var updated) = await _inGameService.changeQuestStateForInGame(userGuid,
questId, revision, [QuestContentState.Test], QuestContentState.Editable);
if (errorCode != ServerErrorCode.Success)
return Results.BadRequest(ApiResponseFactory.error(errorCode));
return Results.Ok();
}
/// <summary>
/// 퀘스트를 수락했을 때 호출
/// </summary>
[HttpPost]
[Route("set-quest-accepted/{userGuid}")]
public async Task<IResult> setQuestAccepted(string userGuid, UgqQuestAcceptedRequest request)
{
var errorCode = await _inGameService.setQuestAccepted(userGuid, request);
if (errorCode != ServerErrorCode.Success)
return Results.BadRequest(ApiResponseFactory.error(errorCode));
return Results.Ok();
}
/// <summary>
/// 퀘스트를 완료했을 때 호출
/// </summary>
[HttpPost]
[Route("set-quest-completed/{userGuid}")]
public async Task<IResult> setQuestCompleted(string userGuid, UgqQuestCompletedRequest request)
{
var errorCode = await _inGameService.setQuestCompleted(userGuid, request);
if (errorCode != ServerErrorCode.Success)
return Results.BadRequest(ApiResponseFactory.error(errorCode));
return Results.Ok();
}
/// <summary>
/// 퀘스트를 포기했을 때 호출
/// </summary>
[HttpPost]
[Route("set-quest-aborted/{userGuid}")]
public async Task<IResult> setQuestAbort(string userGuid, UgqQuestAbortedRequest request)
{
var errorCode = await _inGameService.setQuestAborted(userGuid, request);
if (errorCode != ServerErrorCode.Success)
return Results.BadRequest(ApiResponseFactory.error(errorCode));
return Results.Ok();
}
/// <summary>
/// 퀘스트를 북마크
/// </summary>
[HttpPost]
[Route("bookmark")]
public async Task<IResult> bookmark(UgqBookmarkRequest request)
{
var errorCode = await _inGameService.bookmark(request.QuestId, request.UserGuid);
if(errorCode != ServerErrorCode.Success)
return Results.BadRequest(ApiResponseFactory.error(errorCode));
return Results.Ok();
}
/// <summary>
/// 퀘스트를 북마크 취소
/// </summary>
[HttpPost]
[Route("unbookmark")]
public async Task<IResult> unbookmark(UgqUnbookmarkRequest request)
{
var errorCode = await _inGameService.unbookmark(request.QuestId, request.UserGuid);
if (errorCode != ServerErrorCode.Success)
return Results.BadRequest(ApiResponseFactory.error(errorCode));
return Results.Ok();
}
/// <summary>
/// 퀘스트를 좋아요
/// </summary>
[HttpPost]
[Route("like")]
public async Task<IResult> like(UgqLikeRequest request)
{
var errorCode = await _inGameService.like(request.QuestId, request.Revision, request.UserGuid);
if (errorCode != ServerErrorCode.Success)
return Results.BadRequest(ApiResponseFactory.error(errorCode));
return Results.Ok();
}
/// <summary>
/// 퀘스트를 좋아요 취소
/// </summary>
[HttpPost]
[Route("unlike")]
public async Task<IResult> unlike(UgqUnlikeRequest request)
{
var errorCode = await _inGameService.unlike(request.QuestId, request.Revision, request.UserGuid);
if (errorCode != ServerErrorCode.Success)
return Results.BadRequest(ApiResponseFactory.error(errorCode));
return Results.Ok();
}
/// <summary>
/// 퀘스트를 신고
/// </summary>
[HttpPost]
[Route("report")]
public async Task<IResult> report(UgqReportRequest request)
{
var errorCode = await _inGameService.report(request.UserGuid, request.QuestId, request.Revision, request.Contents);
if (errorCode != ServerErrorCode.Success)
return Results.BadRequest(ApiResponseFactory.error(errorCode));
return Results.Ok();
}
/// <summary>
/// Test 상태인 UGQ QuestScript 정보 얻기 (직접 작성한 퀘스트만 획득 가능)
/// </summary>
[HttpGet]
[Route("test-game-quest-data/{userGuid}")]
public async Task<IResult> getTestGameQuestData(string userGuid, [FromQuery] long questId, [FromQuery] long revision)
{
(ServerErrorCode errorCode, var gameQuestDataEntity) = await _inGameService.getTestGameQuestData(userGuid, questId, revision);
if (errorCode != ServerErrorCode.Success)
return Results.BadRequest(ApiResponseFactory.error(errorCode));
if (gameQuestDataEntity == null)
return Results.BadRequest(ApiResponseFactory.error(ServerErrorCode.UgqNullEntity));
return Results.Ok(gameQuestDataEntity);
}
/// <summary>
/// Live 상태인 UGQ QuestScript 정보 얻기
/// </summary>
[HttpGet]
[Route("game-quest-data")]
public async Task<IResult> getGameQuestData([FromQuery] long questId, [FromQuery] long revision)
{
(ServerErrorCode errorCode, var gameQuestDataEntity) = await _inGameService.getGameQuestData(questId, revision, QuestContentState.Live);
if (errorCode != ServerErrorCode.Success)
return Results.BadRequest(ApiResponseFactory.error(errorCode));
if (gameQuestDataEntity == null)
return Results.BadRequest(ApiResponseFactory.error(ServerErrorCode.UgqNullEntity));
return Results.Ok(gameQuestDataEntity);
}
/// <summary>
/// QuestScript 데이터 중 일부만 얻기
/// </summary>
[HttpGet]
[Route("game-quest-data-simple")]
public async Task<IResult> getGameQuestDataSimple([FromQuery] long questId, [FromQuery] long revision, [FromQuery] QuestContentState state)
{
(ServerErrorCode errorCode, var gameQuestDataEntity) = await _inGameService.getGameQuestData(questId, revision, state);
if (errorCode != ServerErrorCode.Success)
return Results.BadRequest(ApiResponseFactory.error(errorCode));
if (gameQuestDataEntity == null)
return Results.BadRequest(ApiResponseFactory.error(ServerErrorCode.UgqNullEntity));
return Results.Ok(gameQuestDataEntity.ToSimpleDTO());
}
/// <summary>
/// 최신 리비전의 QuestScript 정보 얻기
/// </summary>
[HttpGet]
[Route("latest-revision-game-quest-data")]
public async Task<IResult> getLatestRevisionGameQuestData([FromQuery] long questId)
{
(ServerErrorCode errorCode, var gameQuestDataEntity) = await _inGameService.getLatestRevisionGameQuestData(questId);
if (errorCode != ServerErrorCode.Success)
return Results.BadRequest(ApiResponseFactory.error(errorCode));
if (gameQuestDataEntity == null)
return Results.BadRequest(ApiResponseFactory.error(ServerErrorCode.UgqNullEntity));
return Results.Ok(gameQuestDataEntity);
}
/// <summary>
/// UGQ 로그인 상태를 레디스에서 삭제
/// </summary>
[HttpPost]
[Route("clear-login/{userGuid}")]
public async Task<IResult> clearLogin(string userGuid)
{
// ugq 로그인 상태를 삭제 시킨다.
await _redis.GetDatabase().KeyDeleteAsync($"ugq_login:{userGuid}");
return Results.Ok();
}
}
}

View File

@@ -0,0 +1,198 @@
using System.Globalization;
using Asp.Versioning;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Swashbuckle.AspNetCore.Annotations;
using UGQApiServer.Converter;
using UGQApiServer.Models;
using UGQApiServer.UGQData;
using UGQDataAccess;
using ServerCommon.UGQ;
using ServerCommon.UGQ.Models;
using ServerCommon;
using UGQApiServer.Auth;
using System.Collections.Generic;
using System.Text.Json;
using System.Text.Json.Serialization;
using UGQApiServer.Controllers.Common;
using MetaAssets;
using ServerBase;
namespace UGQApiServer.Controllers
{
[Authorize]
[ApiController]
[ApiVersion("1.0")]
[Route("api/v{version:apiVersion}/[controller]")]
[UGQWebApi]
[AllowWhenSingleLogin]
public class MetaDataController : ControllerBase
{
const int PAGE_SIZE = 30;
UGQMetaData _ugqMetaData;
DynamoDbClient _dynamoDbClient;
MetaDataApi _metaDataApi;
public MetaDataController(UGQMetaData ugqMetaData,
DynamoDbClient dynamoDbClient,
MetaDataApi metaDataApi)
{
_ugqMetaData = ugqMetaData;
_dynamoDbClient = dynamoDbClient;
_metaDataApi = metaDataApi;
}
string? userGuidFromToken()
{
return User.Claims.FirstOrDefault(c => c.Type == "Id")?.Value;
}
/// <summary>
/// <20><><EFBFBD><EFBFBD><EBB0A1><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD> npc <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
/// </summary>
/// <param name="pageNumber"><3E><><EFBFBD><EFBFBD><EFBFBD><EFBFBD></param>
/// <param name="pageSize">pageSize (<28>ִ<EFBFBD> 100)</param>
[HttpGet]
[Route("assignable-npcs")]
[Produces("application/json")]
[SwaggerResponse(StatusCodes.Status200OK, Type = typeof(UGQNpcMetaDataList))]
[SwaggerResponse(StatusCodes.Status400BadRequest, Type = typeof(ApiErrorResponse))]
public async Task<IResult> getAssignableNpcs([FromQuery] int pageNumber, [FromQuery] int pageSize)
{
var userGuid = userGuidFromToken();
if (userGuid == null)
return Results.Unauthorized();
return await _metaDataApi.getAssignableNpcs(userGuid, pageNumber, pageSize);
}
/// <summary>
/// <20><><EFBFBD><EFBFBD><EBB0A1><EFBFBD><EFBFBD> task action <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
/// </summary>
/// <param name="pageNumber"><3E><><EFBFBD><EFBFBD><EFBFBD><EFBFBD></param>
/// <param name="pageSize">pageSize (<28>ִ<EFBFBD> 100)</param>
[HttpGet]
[Route("available-task-actions")]
[Produces("application/json")]
[SwaggerResponse(StatusCodes.Status200OK, Type = typeof(TaskActionList))]
[SwaggerResponse(StatusCodes.Status400BadRequest, Type = typeof(ApiErrorResponse))]
public async Task<IResult> getAvailableTaskActions([FromQuery] int pageNumber, [FromQuery] int pageSize)
{
return await _metaDataApi.getAvailableTaskActions(pageNumber, pageSize);
}
/// <summary>
/// <20>׼<EFBFBD><D7BC><EFBFBD> <20>Է°<D4B7><C2B0><EFBFBD><EFBFBD><EFBFBD> <20><> <20><><EFBFBD><EFBFBD>Ʈ <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
/// </summary>
/// <remarks>
/// Task Action<6F><6E> <20>ǻ<EFBFBD><C7BB><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><20><><EFBFBD><EFBFBD>
/// </remarks>
/// <param name="taskActionId">TaskAction<6F><6E> <20><><EFBFBD>̵<EFBFBD></param>
/// <param name="pageNumber"><3E><><EFBFBD><EFBFBD><EFBFBD><EFBFBD></param>
/// <param name="pageSize">pageSize (<28>ִ<EFBFBD> 100)</param>
[HttpGet]
[Route("task-action-values")]
[Produces("application/json")]
[SwaggerResponse(StatusCodes.Status200OK, Type = typeof(TaskActionValueList))]
[SwaggerResponse(StatusCodes.Status400BadRequest, Type = typeof(ApiErrorResponse))]
public async Task<IResult> getActionValues([FromQuery] int taskActionId, [FromQuery] int pageNumber, [FromQuery] int pageSize)
{
var userGuid = userGuidFromToken();
if (userGuid == null)
return Results.Unauthorized();
return await _metaDataApi.getActionValues(userGuid, taskActionId, pageNumber, pageSize);
}
/// <summary>
/// <20><><EFBFBD><EFBFBD><EBB0A1><EFBFBD><EFBFBD> dialog <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
/// </summary>
/// <param name="pageNumber"><3E><><EFBFBD><EFBFBD><EFBFBD><EFBFBD></param>
/// <param name="pageSize">pageSize (<28>ִ<EFBFBD> 100)</param>
[HttpGet]
[Route("dialog-types")]
[Produces("application/json")]
[SwaggerResponse(StatusCodes.Status200OK, Type = typeof(DialogTypeList))]
[SwaggerResponse(StatusCodes.Status400BadRequest, Type = typeof(ApiErrorResponse))]
public async Task<IResult> getDialogTypes([FromQuery] int pageNumber, [FromQuery] int pageSize)
{
return await _metaDataApi.getDialogTypes(pageNumber, pageSize);
}
/// <summary>
/// dialog <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EBB0A1><EFBFBD><EFBFBD> dialog <20>׼<EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
/// </summary>
/// <param name="dialogType">dialog <20><><EFBFBD><EFBFBD></param>
/// <param name="pageNumber"><3E><><EFBFBD><EFBFBD><EFBFBD><EFBFBD></param>
/// <param name="pageSize">pageSize (<28>ִ<EFBFBD> 100)</param>
[HttpGet]
[Route("dialog-actions")]
[Produces("application/json")]
[SwaggerResponse(StatusCodes.Status200OK, Type = typeof(DialogActionList))]
[SwaggerResponse(StatusCodes.Status400BadRequest, Type = typeof(ApiErrorResponse))]
public async Task<IResult> getDialogActions([FromQuery] int dialogType, [FromQuery] int pageNumber, [FromQuery] int pageSize)
{
return await _metaDataApi.getDialogActions(dialogType, pageNumber, pageSize);
}
/// <summary>
/// dialog <20>׼ǿ<D7BC> <20><><EFBFBD><EFBFBD> <20>Է<EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
/// </summary>
/// <param name="dialogType"><3E><><EFBFBD>̾<EFBFBD><CCBE>α<EFBFBD> Ÿ<><C5B8></param>
/// <param name="dialogActionId"><3E><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD></param>
/// <param name="pageNumber"><3E><><EFBFBD><EFBFBD><EFBFBD><EFBFBD></param>
/// <param name="pageSize">pageSize (<28>ִ<EFBFBD> 100)</param>
[HttpGet]
[Route("dialog-action-values")]
[Produces("application/json")]
[SwaggerResponse(StatusCodes.Status200OK, Type = typeof(DialogActionValueList))]
[SwaggerResponse(StatusCodes.Status400BadRequest, Type = typeof(ApiErrorResponse))]
public async Task<IResult> getDialogConditionValues([FromQuery] int dialogType, [FromQuery] int dialogActionId, [FromQuery] int pageNumber, [FromQuery] int pageSize)
{
return await _metaDataApi.getDialogConditionValues(dialogType, dialogActionId, pageNumber, pageSize);
}
/// <summary>
/// NPC <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
/// </summary>
/// <param name="gender">npc gender</param>
/// <param name="pageNumber"><3E><><EFBFBD><EFBFBD><EFBFBD><EFBFBD></param>
/// <param name="pageSize">pageSize (<28>ִ<EFBFBD> 100)</param>
[HttpGet]
[Route("npc-action-values")]
[Produces("application/json")]
[SwaggerResponse(StatusCodes.Status200OK, Type = typeof(NpcActionValueList))]
[SwaggerResponse(StatusCodes.Status400BadRequest, Type = typeof(ApiErrorResponse))]
public async Task<IResult> getNpcActionValues([FromQuery] EGenderType gender, [FromQuery] int pageNumber, [FromQuery] int pageSize)
{
return await _metaDataApi.getNpcActionValues(pageNumber, pageSize, gender);
}
/// <summary>
/// Map <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
/// </summary>
/// <param name="pageNumber"><3E><><EFBFBD><EFBFBD><EFBFBD><EFBFBD></param>
/// <param name="pageSize">pageSize (<28>ִ<EFBFBD> 100)</param>
[HttpGet]
[Route("map-data-values")]
[Produces("application/json")]
[SwaggerResponse(StatusCodes.Status200OK, Type = typeof(MapDataValueList))]
[SwaggerResponse(StatusCodes.Status400BadRequest, Type = typeof(ApiErrorResponse))]
public async Task<IResult> getNpcActionValues([FromQuery] int pageNumber, [FromQuery] int pageSize)
{
return await _metaDataApi.getMapDataValues(pageNumber, pageSize);
}
[HttpGet]
[Route("preset-images")]
[Produces("application/json")]
[SwaggerResponse(StatusCodes.Status200OK, Type = typeof(DialogActionValueList))]
[SwaggerResponse(StatusCodes.Status400BadRequest, Type = typeof(ApiErrorResponse))]
public async Task<IResult> getPresetImages([FromQuery] PresetKind kind, [FromQuery] PresetCategory category)
{
return await _metaDataApi.getPresetImages(kind, category);
}
}
}

View File

@@ -0,0 +1,624 @@
using System.Globalization;
using Asp.Versioning;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using MongoDB.Bson.Serialization.Attributes;
using Swashbuckle.AspNetCore.Annotations;
using Swashbuckle.AspNetCore.Filters;
using UGQApiServer.Models;
using UGQDatabase.Models;
using UGQDataAccess.Service;
using UGQApiServer.Converter;
using UGQApiServer.Storage;
using UGQDataAccess.Repository.Models;
using Microsoft.AspNetCore.Authorization;
using UGQDataAccess;
using UGQApiServer.Validation;
using UGQApiServer.UGQData;
using UGQApiServer.Auth;
using JwtRoleAuthentication.Services;
using System.Security.Claims;
using Newtonsoft.Json.Linq;
using ServerCommon;
using ServerCommon.UGQ;
using ServerCommon.UGQ.Models;
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.IdentityModel.Tokens;
using System.Collections.Generic;
using UGQApiServer.Controllers.Common;
using UGQDataAccess.Repository;
using ServerCore; using ServerBase;
using StackExchange.Redis;
using UGQDataAccess.Logs;
namespace UGQApiServer.Controllers
{
[Authorize]
[ApiController]
[ApiVersion("1.0")]
[Route("api/v{version:apiVersion}/[controller]")]
[UGQWebApi]
[AllowWhenSingleLogin]
public class QuestEditorController : ControllerBase
{
AccountService _accountService;
QuestEditorService _questEditorService;
IStorageService _storageService;
UGQMetaData _ugqMetaData;
TokenService _tokenService;
WebPortalTokenAuth _webPortalTokenAuth;
DynamoDbClient _dynamoDbClient;
readonly IConnectionMultiplexer _redis;
QuestEditorApi _questEditorApi;
public QuestEditorController(
AccountService accountService,
QuestEditorService questEditorService, IStorageService storageService,
UGQMetaData uqgMetadata, TokenService tokenService,
WebPortalTokenAuth webPortalTokenAuth,
DynamoDbClient dynamoDbClient,
QuestEditorApi questEditorApi,
IConnectionMultiplexer redis)
{
_accountService = accountService;
_questEditorService = questEditorService;
_storageService = storageService;
_ugqMetaData = uqgMetadata;
_tokenService = tokenService;
_webPortalTokenAuth = webPortalTokenAuth;
_dynamoDbClient = dynamoDbClient;
_questEditorApi = questEditorApi;
_redis = redis;
}
string? userGuidFromToken()
{
return User.Claims.FirstOrDefault(c => c.Type == "Id")?.Value;
}
/// <summary>
/// 퀘스트 랩 시작 페이지
/// </summary>
/// <param name="pageNumber">pageNumber</param>
/// <param name="pageSize">pageSize (최대 100)</param>
/// <param name="state">검색할 상태</param>
/// <param name="searchText">검색할 텍스트</param>
[HttpGet]
[Route("summaries")]
[Produces("application/json")]
[SwaggerResponse(StatusCodes.Status200OK, Type = typeof(UGQSummaryList))]
[SwaggerResponse(StatusCodes.Status400BadRequest, Type = typeof(ApiErrorResponse))]
public async Task<IResult> getSummaries([FromQuery] int pageNumber, [FromQuery] int pageSize, [FromQuery] QuestContentState state, [FromQuery] string? searchText)
{
var userGuid = userGuidFromToken();
if (userGuid == null)
return Results.Unauthorized();
pageSize = Math.Clamp(pageSize, 1, UGQConstants.MAX_INPUT_PAGE_SIZE);
// TODO: state만 얻어오게 수정
List<QuestContentEntity> all = await _questEditorService.getAll(userGuid);
var result = await _questEditorService.getSummaries(pageNumber, pageSize, userGuid, state, searchText);
var query = all.GroupBy(
p => p.State,
p => p.State,
(state, states) => new
{
Key = state,
Count = states.Count(),
});
var stateGroups = query.Select(x => new UGQStateGroup
{
State = x.Key,
Count = x.Count,
}).ToList();
LangEnum langEnum = Culture.ToLangEnum(CultureInfo.CurrentCulture.Name);
return Results.Ok(new UGQSummaryResponse
{
TotalCount = all.Count,
StateGroups = stateGroups,
PageNumber = result.PageNumber,
PageSize = result.PageSize,
TotalPages = result.TotalPages,
Summaries = result.Items.Select(x => x.ToDTO(langEnum)).ToList(),
});
}
/// <summary>
/// 유저 정보 얻기
/// </summary>
[HttpGet]
[Route("account")]
[Produces("application/json")]
[SwaggerResponse(StatusCodes.Status200OK, Type = typeof(UGQAccountDetail))]
[SwaggerResponse(StatusCodes.Status400BadRequest, Type = typeof(ApiErrorResponse))]
public async Task<IResult> getAccount()
{
var userGuid = userGuidFromToken();
if (userGuid == null)
return Results.Unauthorized();
var accountEntity = await _accountService.getAccount(userGuid);
if(accountEntity == null)
return Results.BadRequest(ApiResponseFactory.error(ServerErrorCode.UgqNullEntity));
// TODO: count만 얻어오게 수정
List<QuestContentEntity> all = await _questEditorService.getAll(userGuid);
return Results.Ok(new UGQAccountDetail
{
UserGuid = accountEntity.UserGuid,
Nickname = accountEntity.Nickname,
AccountId = accountEntity.AccountId ?? "",
SlotCount = (MetaHelper.GameConfigMeta.UGQDefaultSlot + accountEntity.AdditionalSlotCount),
GradeType = accountEntity.GradeType,
CreatorPoint = accountEntity.CreatorPoint,
TotalQuests = all.Count,
});
}
/// <summary>
/// 유저 재화 정보 얻기
/// </summary>
[HttpGet]
[Route("account-currency")]
[Produces("application/json")]
[SwaggerResponse(StatusCodes.Status200OK, Type = typeof(UGQAccount))]
[SwaggerResponse(StatusCodes.Status400BadRequest, Type = typeof(ApiErrorResponse))]
public async Task<IResult> getAccountCurrency()
{
var userGuid = userGuidFromToken();
if (userGuid == null)
return Results.Unauthorized();
if (Enum.TryParse(MetaHelper.GameConfigMeta.UGQAddSlotType, true, out CurrencyType currencyType) == false)
return Results.BadRequest(ApiResponseFactory.error(ServerErrorCode.UgqServerException));
double slotPrice = MetaHelper.GameConfigMeta.UGQAddSlotAmount;
(var result, double currentAmount) = await CurrencyControlHelper.getMoneyByUserGuid(userGuid, currencyType);
if (result.isFail())
return Results.BadRequest(ApiResponseFactory.error(ServerErrorCode.UgqCurrencyError));
return Results.Ok(new UGQAccountCurrency
{
CurrencyType = currencyType,
Amount = currentAmount,
});
}
/// <summary>
/// 유저의 메타버스 로그인 상태 얻기
/// </summary>
[HttpGet]
[Route("metaverse-online-status")]
[Produces("application/json")]
[SwaggerResponse(StatusCodes.Status200OK, Type = typeof(UGQAccount))]
[SwaggerResponse(StatusCodes.Status400BadRequest, Type = typeof(ApiErrorResponse))]
public async Task<IResult> getMetaverseOnlineStatus()
{
var userGuid = userGuidFromToken();
if (userGuid == null)
return Results.Unauthorized();
bool online = false;
var game_login_cache = await _redis.GetDatabase().StringGetAsync($"login:{userGuid}");
if (game_login_cache.HasValue == true)
{
online = true;
}
return Results.Ok(new {
Online = online
});
}
/// <summary>
/// 퀘스트 슬롯을 추가하는데 필요한 재화와 소모량 얻기
/// </summary>
[HttpGet]
[Route("slot-price")]
[Produces("application/json")]
[SwaggerResponse(StatusCodes.Status200OK, Type = typeof(UGQSlotPrice))]
[SwaggerResponse(StatusCodes.Status400BadRequest, Type = typeof(ApiErrorResponse))]
public async Task<IResult> getSlotPrice()
{
await Task.CompletedTask;
var userGuid = userGuidFromToken();
if (userGuid == null)
return Results.Unauthorized();
// MetaHelper.GameConfigMeta.UGQDefaultSlot
if (Enum.TryParse(MetaHelper.GameConfigMeta.UGQAddSlotType, true, out CurrencyType currencyType) == false)
return Results.BadRequest(ApiResponseFactory.error(ServerErrorCode.UgqServerException));
double slotPrice = MetaHelper.GameConfigMeta.UGQAddSlotAmount;
return Results.Ok(new UGQSlotPrice
{
CurrencyType = currencyType,
Price = slotPrice
});
}
/// <summary>
/// 퀘스트 슬롯 추가
/// </summary>
[HttpPost]
[Route("slot")]
[Produces("application/json")]
[SwaggerResponse(StatusCodes.Status200OK, Type = typeof(UGQAccount))]
[SwaggerResponse(StatusCodes.Status400BadRequest, Type = typeof(ApiErrorResponse))]
public async Task<IResult> addSlot()
{
var userGuid = userGuidFromToken();
if (userGuid == null)
return Results.Unauthorized();
if (Enum.TryParse(MetaHelper.GameConfigMeta.UGQAddSlotType, true, out CurrencyType currencyType) == false)
return Results.BadRequest(ApiResponseFactory.error(ServerErrorCode.UgqServerException));
double slotPrice = MetaHelper.GameConfigMeta.UGQAddSlotAmount;
var accountEntity = await _accountService.getAccount(userGuid);
if (accountEntity == null)
return Results.BadRequest(ApiResponseFactory.error(ServerErrorCode.UgqNullEntity));
int finalCount = MetaHelper.GameConfigMeta.UGQDefaultSlot + accountEntity.AdditionalSlotCount + 1;
if (finalCount > MetaHelper.GameConfigMeta.UGQMaximumSlot)
return Results.BadRequest(ApiResponseFactory.error(ServerErrorCode.UgqSlotLimit));
var result = new Result();
var db_connector = CurrencyControlHelper.getDbConnector();
(result, var is_login) = await ServerCommon.EntityHelper.isUserLoggedIn(db_connector, userGuid);
if (result.isFail())
return Results.BadRequest(ApiResponseFactory.error(ServerErrorCode.UgqServerException));
if (true == is_login)
return Results.BadRequest(ApiResponseFactory.error(ServerErrorCode.UgqServerException));
// 일단 슬롯 추가할 수 있음
// 게임 DB 재화 체크, 소모
(result, double currentAmount) = await CurrencyControlHelper.getMoneyByUserGuid(userGuid, currencyType);
if (result.isFail())
return Results.BadRequest(ApiResponseFactory.error(ServerErrorCode.UgqCurrencyError));
if (currentAmount < slotPrice)
return Results.BadRequest(ApiResponseFactory.error(ServerErrorCode.UgqNotEnoughCurrency));
(result, double updatedAmount) = await CurrencyControlHelper.changeMoneyByUserGuid(userGuid, currencyType, slotPrice);
if (result.isFail())
return Results.BadRequest(ApiResponseFactory.error(ServerErrorCode.UgqCurrencyError));
(ServerErrorCode errorCode, var updatedAccountEntity) = await _accountService.addSlotCount(userGuid, 1, accountEntity.SlotCountVersion);
if (errorCode != ServerErrorCode.Success)
return Results.BadRequest(ApiResponseFactory.error(errorCode));
if (updatedAccountEntity == null)
{
// 슬롯 추가 실패
// 슬롯수 획인, 재화 소모 처리후 SlotCountVersion이 변경되었으면 슬롯 수 변경이 저장되지 않고 실패함.
// 재화는 돌려준다.
(result, double rollbackAmount) = await CurrencyControlHelper.earnMoneyByUserGuid(userGuid, currencyType, slotPrice);
if (result.isFail())
{
Log.getLogger().error($"currency rollback error. userGuid: {userGuid}, currencyType: {currencyType}, slotPrice: {slotPrice}");
return Results.BadRequest(ApiResponseFactory.error(ServerErrorCode.UgqCurrencyError));
}
return Results.BadRequest(ApiResponseFactory.error(ServerErrorCode.UgqNullEntity));
}
List<ILogInvoker> business_logs = [
new UgqApiAddSlotBusinessLog(updatedAccountEntity.AdditionalSlotCount, ""),
];
var log_action = new LogActionEx(LogActionType.UgqApiAddSlot);
UgqApiBusinessLogger.collectLogs(log_action, userGuid, business_logs);
return Results.Ok(updatedAccountEntity.ToDTO());
}
/// <summary>
/// 퀘스트 생성
/// </summary>
/// <remarks>
/// 빈 슬롯에 퀘스트 추가하는 경우에 호출
/// </remarks>
[HttpPost]
[Route("quest")]
[Produces("application/json")]
[SwaggerResponse(StatusCodes.Status200OK, Type = typeof(UGQContent))]
[SwaggerResponse(StatusCodes.Status400BadRequest, Type = typeof(ApiErrorResponse))]
public async Task<IResult> addQuest([FromBody] UGQSaveQuestModel saveQuestModel)
{
var userGuid = userGuidFromToken();
if (userGuid == null)
return Results.Unauthorized();
return await _questEditorApi.addQuest(userGuid, saveQuestModel);
}
/// <summary>
/// 퀘스트 내용 가져오기
/// </summary>
/// <param name="questContentId">UGQSummary의 QuestContentId 값으로 api 호출</param>
[HttpGet]
[Route("quest/{questContentId}")]
[Produces("application/json")]
[SwaggerResponse(StatusCodes.Status200OK, Type = typeof(UGQContent))]
[SwaggerResponse(StatusCodes.Status400BadRequest, Type = typeof(ApiErrorResponse))]
public async Task<IResult> getQuest(string questContentId)
{
var userGuid = userGuidFromToken();
if (userGuid == null)
return Results.Unauthorized();
return await _questEditorApi.getQuest(userGuid, questContentId);
}
/// <summary>
/// 편집 가능한 경우 퀘스트 내용 가져오기
/// </summary>
/// <remarks>
/// GET api와는 다르게 편집 불가능한 state인 경우에는 실패 리턴
/// </remarks>
/// <param name="questContentId">UGQSummary의 QuestContentId 값으로 api 호출</param>
[HttpPost]
[Route("quest/{questContentId}/edit")]
[Produces("application/json")]
[SwaggerResponse(StatusCodes.Status200OK, Type = typeof(UGQContent))]
[SwaggerResponse(StatusCodes.Status400BadRequest, Type = typeof(ApiErrorResponse))]
public async Task<IResult> editQuest(string questContentId)
{
var userGuid = userGuidFromToken();
if (userGuid == null)
return Results.Unauthorized();
return await _questEditorApi.editQuest(userGuid, questContentId);
}
/// <summary>
/// 퀘스트 내용 업데이트
/// </summary>
/// <remarks>
/// Task의 DialogId는 업데이트하지 않음.
/// 편집 불가능 state인 경우 에러 리턴
/// </remarks>
/// <param name="questContentId">GET 또는 edit 에서 얻은 questContentId 사용</param>
/// <param name="saveQuestModel">웹에서 입력한 값</param>
[HttpPut]
[Route("quest/{questContentId}")]
[Produces("application/json")]
[SwaggerResponse(StatusCodes.Status200OK, Type = typeof(UGQContent))]
[SwaggerResponse(StatusCodes.Status400BadRequest, Type = typeof(ApiErrorResponse))]
public async Task<IResult> saveQuest(string questContentId, [FromBody] UGQSaveQuestModel saveQuestModel)
{
var userGuid = userGuidFromToken();
if (userGuid == null)
return Results.Unauthorized();
return await _questEditorApi.saveQuest(userGuid, questContentId, saveQuestModel);
}
/// <summary>
/// 퀘스트 삭제
/// </summary>
/// <param name="questContentId">GET또는 edit 에서 얻은 questContentId 사용</param>
[HttpDelete]
[Route("quest/{questContentId}")]
[Produces("application/json")]
[SwaggerResponse(StatusCodes.Status200OK)]
[SwaggerResponse(StatusCodes.Status400BadRequest, Type = typeof(ApiErrorResponse))]
public async Task<IResult> deleteQuest(string questContentId)
{
var userGuid = userGuidFromToken();
if (userGuid == null)
return Results.Unauthorized();
return await _questEditorApi.deleteQuest(userGuid, questContentId);
}
/// <summary>
/// 퀘스트 이미지 업로드
/// </summary>
/// <remarks>
/// 세부사항은 정리 예정
/// </remarks>
/// <param name="questContentId">GET 또는 edit 에서 얻은 questContentId 사용</param>
/// <param name="titleImageId">기본 이미지 인덱스</param>
/// <param name="bannerImageId">기본 이미지 인덱스</param>
/// <param name="title">업로드 파일</param>
/// <param name="banner">업로드 파일</param>
[HttpPost]
[Route("quest-image/{questContentId}")]
[Produces("application/json")]
public async Task<IResult> uploadQuestImage(string questContentId, [FromQuery] int? titleImageId, [FromQuery] int? bannerImageId, IFormFile? title, IFormFile? banner)
{
var userGuid = userGuidFromToken();
if (userGuid == null)
return Results.Unauthorized();
return await _questEditorApi.uploadQuestImage(userGuid, questContentId, titleImageId ?? 0, bannerImageId ?? 0, title, banner);
}
/// <summary>
/// 퀘스트 타이틀 이미지 업로드
/// </summary>
/// <remarks>
/// 세부사항은 정리 예정
/// </remarks>
/// <param name="questContentId">GET 또는 edit 에서 얻은 questContentId 사용</param>
/// <param name="titleImageId">기본 이미지 인덱스</param>
/// <param name="title">업로드 파일</param>
[HttpPost]
[Route("quest-title-image/{questContentId}")]
[Produces("application/json")]
public async Task<IResult> uploadQuestTitleImage(string questContentId, [FromQuery] int? titleImageId, IFormFile? title)
{
var userGuid = userGuidFromToken();
if (userGuid == null)
return Results.Unauthorized();
return await _questEditorApi.uploadQuestImage(userGuid, questContentId, titleImageId ?? 0, 0, title, null);
}
/// <summary>
/// 퀘스트 배너 이미지 업로드
/// </summary>
/// <remarks>
/// 세부사항은 정리 예정
/// </remarks>
/// <param name="questContentId">GET 또는 edit 에서 얻은 questContentId 사용</param>
/// <param name="bannerImageId">기본 이미지 인덱스</param>
/// <param name="banner">업로드 파일</param>
[HttpPost]
[Route("quest-banner-image/{questContentId}")]
[Produces("application/json")]
public async Task<IResult> uploadQuestBannerImage(string questContentId, [FromQuery] int? bannerImageId, IFormFile? banner)
{
var userGuid = userGuidFromToken();
if (userGuid == null)
return Results.Unauthorized();
return await _questEditorApi.uploadQuestImage(userGuid, questContentId, 0, bannerImageId ?? 0, null, banner);
}
/// <summary>
/// 퀘스트 다이얼로그 가져오기
/// </summary>
/// <param name="questContentId">GET 또는 edit 에서 얻은 questContentId 사용</param>
/// <param name="dialogId">UGQContent.Tasks에 있는 DialogId 사용</param>
[HttpGet]
[Route("quest-dialog/{questContentId}/{dialogId}")]
[Produces("application/json")]
[SwaggerResponse(StatusCodes.Status200OK, Type = typeof(UGQDialog))]
[SwaggerResponse(StatusCodes.Status400BadRequest, Type = typeof(ApiErrorResponse))]
public async Task<IResult> getQuestDialog(string questContentId, string dialogId)
{
var userGuid = userGuidFromToken();
if (userGuid == null)
return Results.Unauthorized();
return await _questEditorApi.getQuestDialog(userGuid, questContentId, dialogId);
}
/// <summary>
/// 퀘스트 다이얼로그 새로 생성
/// </summary>
/// <remarks>
/// Dialog 를 추가하고, UGQContent.Tasks의 DialogId를 저장
/// </remarks>
/// <param name="questContentId">GET 또는 edit 에서 얻은 questContentId 사용</param>
/// <param name="taskIndex">몇번째 Task에 추가되는 다이얼로그인지. 0부터 시작</param>
/// <param name="questDialog">questDialog</param>
[HttpPost]
[Route("quest-dialog/{questContentId}")]
[Produces("application/json")]
[SwaggerResponse(StatusCodes.Status200OK, Type = typeof(UGQAddQuestDialogResponse))]
[SwaggerResponse(StatusCodes.Status400BadRequest, Type = typeof(ApiErrorResponse))]
public async Task<IResult> addQuestDialog(string questContentId, [FromQuery] int taskIndex, [FromBody] UGQSaveDialogModel questDialog)
{
var userGuid = userGuidFromToken();
if (userGuid == null)
return Results.Unauthorized();
return await _questEditorApi.addQuestDialog(userGuid, questContentId, taskIndex, questDialog);
}
/// <summary>
/// 퀘스트 다이얼로그 업데이트
/// </summary>
/// <param name="questContentId">GET 또는 edit 에서 얻은 questContentId 사용</param>
/// <param name="dialogId">UGQContent.Tasks에 있는 DialogId 사용</param>
/// /// <param name="questDialog">questDialog</param>
[HttpPut]
[Route("quest-dialog/{questContentId}/{dialogId}")]
[Produces("application/json")]
[SwaggerResponse(StatusCodes.Status200OK, Type = typeof(UGQDialog))]
[SwaggerResponse(StatusCodes.Status400BadRequest, Type = typeof(ApiErrorResponse))]
public async Task<IResult> saveQuestDialog(string questContentId, string dialogId, [FromBody] UGQSaveDialogModel questDialog)
{
var userGuid = userGuidFromToken();
if (userGuid == null)
return Results.Unauthorized();
return await _questEditorApi.saveQuestDialog(userGuid, questContentId, dialogId, questDialog);
}
/// <summary>
/// 퀘스트 state 변경
/// </summary>
/// <remarks>
/// Test 로 변경 실패 시 ValidationErrorResponse로 응답
/// </remarks>
/// <param name="questContentId">GET또는 edit 에서 얻은 questContentId 사용</param>
/// <param name="state">QuestContentState</param>
[HttpPatch]
[Route("quest-state/{questContentId}")]
[Produces("application/json")]
[SwaggerResponse(StatusCodes.Status200OK, Type = typeof(UGQContent))]
[SwaggerResponse(StatusCodes.Status400BadRequest, Type = typeof(ApiErrorResponse))]
[SwaggerResponse(StatusCodes.Status400BadRequest, Type = typeof(ValidationErrorResponse))]
public async Task<IResult> changeQuestState(string questContentId, [FromQuery] QuestContentState state)
{
var userGuid = userGuidFromToken();
if (userGuid == null)
return Results.Unauthorized();
return await _questEditorApi.changeQuestState(userGuid, questContentId, state);
}
/// <summary>
/// CreatorPoint 내역 보기
/// </summary>
/// <param name="pageNumber">페이지</param>
/// <param name="pageSize">페이지 사이즈 (최대 100)</param>
/// <param name="kind">kind</param>
/// <param name="startDate">검색할 시작 시간</param>
/// <param name="endDate">검색할 종료 시간</param>
[HttpGet]
[Route("creator-point-history")]
[Produces("application/json")]
[SwaggerResponse(StatusCodes.Status200OK, Type = typeof(UGQCreatorPointHistoryResult))]
[SwaggerResponse(StatusCodes.Status400BadRequest, Type = typeof(ApiErrorResponse))]
public async Task<IResult> getCreatorPointHistory([FromQuery] int pageNumber, [FromQuery] int pageSize, CreatorPointHistoryKind kind, DateTimeOffset startDate, DateTimeOffset endDate)
{
var userGuid = userGuidFromToken();
if (userGuid == null)
return Results.Unauthorized();
return await _questEditorApi.getCreatorPointHistory(userGuid, pageNumber, pageSize, kind, startDate, endDate);
}
/// <summary>
/// 퀘스트별 수익 보기
/// </summary>
/// <param name="pageNumber">페이지</param>
/// <param name="pageSize">페이지 사이즈 (최대 100)</param>
[HttpGet]
[Route("quest-profit-stats")]
[Produces("application/json")]
[SwaggerResponse(StatusCodes.Status200OK, Type = typeof(UGQQuestProfitStatsResult))]
[SwaggerResponse(StatusCodes.Status400BadRequest, Type = typeof(ApiErrorResponse))]
public async Task<IResult> getQuestProfitStats([FromQuery] int pageNumber, [FromQuery] int pageSize)
{
var userGuid = userGuidFromToken();
if (userGuid == null)
return Results.Unauthorized();
return await _questEditorApi.getQuestProfitStats(userGuid, pageNumber, pageSize);
}
}
}

View File

@@ -0,0 +1,26 @@
using UGQDatabase.Models;
namespace UGQApiServer.Converter;
public static class ConverterCommon
{
public static List<string> buildLanguages(TextEntity titleText)
{
List<string> languages = new List<string>();
if (string.IsNullOrEmpty(titleText.En) == false)
languages.Add("en");
if (string.IsNullOrEmpty(titleText.Kr) == false)
languages.Add("kr");
if (string.IsNullOrEmpty(titleText.Jp) == false)
languages.Add("jp");
if (languages.Count == 0)
languages.Add("en");
return languages;
}
}

View File

@@ -0,0 +1,179 @@
using ServerCommon;
using ServerCommon.UGQ.Models;
using UGQApiServer.Storage;
using UGQApiServer.UGQData;
using UGQDataAccess.Repository.Models;
using UGQDatabase.Models;
namespace UGQApiServer.Converter;
public enum ImageUrlType
{
TitleImg,
BannerImg,
}
public static class InGameConverter
{
static IStorageService _storageService = null!;
static UGQMetaData _ugqMetadata = null!;
public static void init(IStorageService storageService, UGQMetaData ugqMetadata)
{
_storageService = storageService;
_ugqMetadata = ugqMetadata;
}
static BoolType ToProto(this bool value)
{
return (value == true) ? BoolType.True : BoolType.False;
}
public static string GetLangText(this TextEntity entity, LangEnum langEnum)
{
string text = string.Empty;
switch(langEnum)
{
case LangEnum.Ko:
text = entity.Kr;
break;
case LangEnum.Jp:
text = entity.Jp;
break;
case LangEnum.En:
text = entity.En;
break;
}
if (text == string.Empty)
text = entity.En;
return text;
}
public static UgqQuestInTestState ToProtoUgqQuestInTestState(this QuestContentEntity result, LangEnum langEnum, ImageUrlType ImgUrlType)
{
string ImagePath = "";
if (ImgUrlType == ImageUrlType.TitleImg)
ImagePath = _storageService.fileUrl(result.TitleImagePath);
else if (ImgUrlType == ImageUrlType.BannerImg)
ImagePath = _storageService.fileUrl(result.BannerImagePath);
return new UgqQuestInTestState
{
//ComposedQuestId = result.QuestId << 32 | result.Revision,
ComposedQuestId = QuestHelper.convertQuestIdAndRevisionToUgqQuestId((UInt32)result.QuestId, (UInt32)result.Revision),
Title = result.Title.GetLangText(langEnum),
TitleImagePath = ImagePath,
};
}
public static UgqBoardSearchResult ToProto(this QuestBoardQueryResult result, LangEnum langEnum, ImageUrlType ImgUrlType)
{
return new UgqBoardSearchResult
{
PageNumber = result.PageNumber,
PageSize = result.PageSize,
TotalPages = result.TotalPages,
Items = { result.Items.Select(x => x.ToProto(langEnum, ImgUrlType)).ToList() }
};
}
public static UgqBoardSportlightResult ToProto(this QuestBoardSportlightQueryResult result, LangEnum langEnum, ImageUrlType ImgUrlType)
{
return new UgqBoardSportlightResult
{
MostLiked = new DateRangeUgqBoardItem
{
Today = result.MostLiked.Today?.ToProto(langEnum, ImgUrlType),
ThisWeek = result.MostLiked.ThisWeek?.ToProto(langEnum, ImgUrlType),
ThisMonth = result.MostLiked.ThisMonth?.ToProto(langEnum, ImgUrlType),
},
MostBookmarked = new DateRangeUgqBoardItem
{
Today = result.MostBookmarked.Today?.ToProto(langEnum, ImgUrlType),
ThisWeek = result.MostBookmarked.ThisWeek?.ToProto(langEnum, ImgUrlType),
ThisMonth = result.MostBookmarked.ThisMonth?.ToProto(langEnum, ImgUrlType),
},
};
}
public static UgqBoardItem ToProto(this QuestBoardItemResult result, LangEnum langEnum, ImageUrlType ImgUrlType)
{
var compoesed_quest_id = QuestHelper.convertQuestIdAndRevisionToUgqQuestId((UInt32)result.QuestId, (UInt32)result.Revision);
string ImagePath = "";
if (ImgUrlType == ImageUrlType.TitleImg)
ImagePath = _storageService.fileUrl(result.TitleImagePath);
else if (ImgUrlType == ImageUrlType.BannerImg)
ImagePath = _storageService.fileUrl(result.BannerImagePath);
return new UgqBoardItem
{
//ComposedQuestId = result.QuestId << 32 | result.Revision,
ComposedQuestId = compoesed_quest_id,
Title = result.Title.GetLangText(langEnum),
TitleImagePath = ImagePath,
LikeCount = result.LikeCount,
BookmarkCount = result.BookmarkCount,
Author = result.Author ?? "",
GradeType = result.GradeType,
Cost = result.Cost,
};
}
public static UgqBoardItemDetail ToProto(this QuestBoardDetailItemResult result, LangEnum langEnum, ImageUrlType ImgUrlType)
{
string beaconName = "";
if (result.BeaconId != 0)
beaconName = _ugqMetadata.getBeaconName((int)result.BeaconId, langEnum);
else if (result.UgcBeaconNickname != null)
beaconName = result.UgcBeaconNickname;
string ImagePath = "";
if (ImgUrlType == ImageUrlType.TitleImg)
ImagePath = _storageService.fileUrl(result.TitleImagePath);
else if (ImgUrlType == ImageUrlType.BannerImg)
ImagePath = _storageService.fileUrl(result.BannerImagePath);
var compoesed_quest_id = QuestHelper.convertQuestIdAndRevisionToUgqQuestId((UInt32)result.QuestId, (UInt32)result.Revision);
return new UgqBoardItemDetail
{
ComposedQuestId = compoesed_quest_id,
Title = result.Title.GetLangText(langEnum),
TitleImagePath = ImagePath,
LikeCount = result.LikeCount,
BookmarkCount = result.BookmarkCount,
AcceptCount = result.QuestAcceptedCount,
CompleteCount = result.QuestCompletedCount,
Author = result.Author ?? "",
Description = result.Description.GetLangText(langEnum),
Langs = { ConverterCommon.buildLanguages(result.Title) },
Beacon = beaconName,
Liked = result.Liked.ToProto(),
Bookmarked = result.Bookmarked.ToProto(),
GradeType = result.GradeType,
Cost = result.Cost,
};
}
public static GameQuestDataSimple ToSimpleDTO(this GameQuestDataEntity entity)
{
return new GameQuestDataSimple
{
QuestId = entity.QuestId,
Revision = entity.Revision,
UserGuid = entity.UserGuid,
Author = entity.Author ?? "",
Title = entity.Title,
GradeType = entity.GradeType,
State = entity.State,
Cost = entity.Cost,
Shutdown = entity.Shutdown,
};
}
}

View File

@@ -0,0 +1,282 @@
using Microsoft.AspNetCore.Http.HttpResults;
using Amazon.S3.Model;
using ServerCommon;
using MetaAssets;
using UGQDataAccess.Repository.Models;
using UGQApiServer.Models;
using UGQDatabase.Models;
using UGQApiServer.Storage;
using UGQApiServer.UGQData;
using UGQDataAccess.Service;
namespace UGQApiServer.Converter;
public static class UGQConverter
{
static IStorageService _storageService = null!;
static UGQMetaData _ugqMetadata = null!;
public static void init(IStorageService storageService, UGQMetaData ugqMetadata)
{
_storageService = storageService;
_ugqMetadata = ugqMetadata;
}
public static UGQText ToDTO(this TextEntity entity)
{
return new UGQText
{
Kr = entity.Kr,
En = entity.En,
Jp = entity.Jp,
};
}
public static UGQTask ToDTO(this TaskEntity entity, LangEnum langEnum)
{
return new UGQTask
{
GoalText = entity.GoalText.ToDTO(),
ActionId = entity.ActionId,
ActionValue = entity.ActionValue,
IsShowNpcLocation = entity.IsShowNpcLocation,
UgcActionValueGuid = entity.UgcActionValueGuid,
UgcActionValueName = entity.UgcActionValueName,
DialogId = entity.DialogId,
ActionName = _ugqMetadata.getTaskActionName(entity.ActionId, langEnum),
ActionValueName = _ugqMetadata.getTaskActionValueName(entity.ActionId, entity.ActionValue, langEnum),
};
}
public static UGQAccount ToDTO(this AccountEntity entity)
{
return new UGQAccount
{
UserGuid = entity.UserGuid,
Nickname = entity.Nickname,
AccountId = entity.AccountId ?? "",
SlotCount = (MetaHelper.GameConfigMeta.UGQDefaultSlot + entity.AdditionalSlotCount),
GradeType = entity.GradeType,
CreatorPoint = entity.CreatorPoint,
};
}
public static UGQAccountItem ToDTO(this AccountItemResult entity)
{
return new UGQAccountItem
{
UserGuid = entity.UserGuid,
Nickname = entity.Nickname,
AccountId = entity.AccountId ?? "",
SlotCount = (MetaHelper.GameConfigMeta.UGQDefaultSlot + entity.AdditionalSlotCount),
GradeType = entity.GradeType,
CreatorPoint = entity.CreatorPoint,
QuestCount = entity.QuestCount,
CreatedAt = entity.CreatedAt,
};
}
public static UGQQuestProfitStats ToDTO(this QuestProfitStatsItemResult result, LangEnum langEnum)
{
return new UGQQuestProfitStats
{
QuestContentId = result.QuestContentId,
QuestId = result.QuestId,
Revision = result.Revision,
Title = result.Title.GetLangText(langEnum),
TitleImagePath = _storageService.fileUrl(result.TitleImagePath),
BannerImagePath = _storageService.fileUrl(result.BannerImagePath),
Completed = result.CompletedCount,
TotalProfit = result.TotalProfit,
};
}
public static UGQDialogSequenceAction ToDTO(this DialogSequenceActionEntity entity, LangEnum langEnum)
{
return new UGQDialogSequenceAction
{
Talker = entity.Talker,
Type = entity.Type,
Talk = entity.Talk.ToDTO(),
Condition = entity.Condition,
ConditionValue = entity.ConditionValue,
NextSequence = entity.NextSequence,
NpcAction = entity.NpcAction,
TypeName = _ugqMetadata.getDialogTypeName(entity.Type, langEnum),
ConditioneName = _ugqMetadata.getDialogConditionName(entity.Type, entity.Condition, langEnum),
ConditioneValueName = _ugqMetadata.getDialogConditionValueName(entity.Type, entity.Condition, entity.ConditionValue, langEnum),
};
}
public static UGQDialogSequence ToDTO(this DialogSequenceEntity entity, LangEnum langEnum)
{
return new UGQDialogSequence
{
SequenceId = entity.SequenceId,
Actions = entity.Actions.Select(x => x.ToDTO(langEnum)).ToList(),
};
}
public static UGQPresetImage ToDTO(this UGQPresetImageMetaData data)
{
return new UGQPresetImage
{
Id = data.Id,
ImagePath = _storageService.fileUrl(data.FileName),
};
}
public static UGQSummary ToDTO(this SummaryItemResult result, LangEnum langEnum)
{
string beaconName = "";
if (result.BeaconId != 0)
beaconName = _ugqMetadata.getBeaconName(result.BeaconId, langEnum);
else if (result.UgcBeaconNickname != null)
beaconName = result.UgcBeaconNickname;
return new UGQSummary
{
QuestContentId = result.QuestContentId,
QuestId = result.QuestId,
Revision = result.Revision,
UserGuid = result.UserGuid,
Author = result.Author,
BeaconId = result.BeaconId,
UgcBeaconGuid = result.UgcBeaconGuid,
GradeType = result.GradeType,
Title = result.Title.GetLangText(Culture.ToLangEnum(result.Savelanguage)),
Languages = ConverterCommon.buildLanguages(result.Title),
TitleImagePath = _storageService.fileUrl(result.TitleImagePath),
BannerImagePath = _storageService.fileUrl(result.BannerImagePath),
Description = result.Description.GetLangText(Culture.ToLangEnum(result.Savelanguage)),
State = result.State,
Cost = result.Cost,
UpdatedAt = result.UpdatedAt,
CreatedAt = result.CreatedAt,
Savelanguage = result.Savelanguage,
Stats = new UGQStats
{
Accepted = result.AcceptedCount,
Completed = result.CompletedCount,
Like = result.LikeCount,
Bookmark = result.BookmarkCount,
Report = result.ReportCount,
TotalProfit = result.TotalProfit,
},
BeaconName = beaconName,
};
}
public static UGQContent ToDTO(this QuestContentEntity entity, LangEnum langEnum)
{
string beaconName = "";
if (entity.BeaconId != 0)
beaconName = _ugqMetadata.getBeaconName(entity.BeaconId, langEnum);
else if (entity.UgcBeaconNickname != null)
beaconName = entity.UgcBeaconNickname;
return new UGQContent
{
QuestContentId = entity.Id,
QuestId = entity.QuestId,
Revision = entity.Revision,
BeaconId = entity.BeaconId,
UgcBeaconGuid = entity.UgcBeaconGuid,
GradeType = entity.GradeType,
Title = entity.Title.ToDTO(),
Languages = ConverterCommon.buildLanguages(entity.Title),
TitleImagePath = _storageService.fileUrl(entity.TitleImagePath),
BannerImagePath = _storageService.fileUrl(entity.BannerImagePath),
Description = entity.Description.ToDTO(),
State = entity.State,
Cost = entity.Cost,
Tasks = entity.Tasks.Select(x => x.ToDTO(langEnum)).ToList(),
LastUpdated = entity.UpdatedAt,
BeaconName = beaconName,
};
}
public static UGQDialog ToDTO(this QuestDialogEntity entity, LangEnum langEnum)
{
return new UGQDialog
{
DialogId = entity.Id,
Sequences = entity.Sequences.Select(x => x.ToDTO(langEnum)).ToList(),
};
}
public static UGQCreatorPointHistory ToDTO(this CreatorPointHistoryEntity entity, LangEnum langEnum)
{
return new UGQCreatorPointHistory
{
Kind = entity.Kind,
Amount = entity.Amount,
TotalAmount = entity.TotalAmount,
CreatedAt = entity.CreatedAt,
};
}
public static UGQCreatorPointHistoryResult ToDTO(this CreatorPointHistoryQueryResult result, LangEnum langEnum)
{
return new UGQCreatorPointHistoryResult
{
PageNumber = result.PageNumber,
PageSize = result.PageSize,
TotalPages = result.TotalPages,
Items = result.Items.Select(x => x.ToDTO(langEnum)).ToList(),
};
}
public static UGQReserveAccountGradeResult ToDTO(this ReserveAccountGradeEntity entity)
{
return new UGQReserveAccountGradeResult
{
ReserveId = entity.Id.ToString(),
UserGuid = entity.UserGuid,
CurrentGradeType = entity.BeforeGradeType,
ReserveGradeType = entity.ReserveGradeType,
ReserveTime = entity.ReserveTime,
UpdatedAt = entity.UpdatedAt,
IsCompleted = entity.IsCompleted,
};
}
public static UGQReserveAccountGradeResult ToDTO(this ReserveAccountGradeItemResult entity)
{
return new UGQReserveAccountGradeResult
{
ReserveId = entity.ReserveId,
UserGuid = entity.UserGuid,
AccountId = entity.AccountId,
CurrentGradeType = entity.CurrentGradeType,
ReserveGradeType = entity.ReserveGradeType,
ReserveTime = entity.ReserveTime,
UpdatedAt = entity.UpdatedAt,
IsCompleted = entity.IsCompleted,
};
}
}

View File

@@ -0,0 +1,154 @@
using ServerCommon;
using MetaAssets;
using UGQApiServer.Models;
using UGQApiServer.UGQData;
namespace UGQApiServer.Converter;
public static class UGQMetaDataConverter
{
public static UGQNpcMetaData ToNpcLocale(this UGQNpcMetaData npc, LangEnum langEnum)
{
if (npc.NpcId != null)
{
var text = UGQDataHelper.getText(langEnum, npc.NpcName);
if (text != null)
npc.NpcName = text;
var titleText = UGQDataHelper.getText(langEnum, npc.NpcTitle);
if (titleText != null)
npc.NpcTitle = titleText;
}
return npc;
}
public static TaskAction ToTaskAction(this UGQTaskAction action, LangEnum langEnum)
{
string actionName = action.ActionName;
if (action.TextId != string.Empty)
{
var text = UGQDataHelper.getText(langEnum, action.TextId);
if (text != null)
actionName = text;
}
return new TaskAction
{
ActionId = action.ActionId,
ActionName = actionName,
};
}
public static TaskActionValue ToTaskActionValue(this UGQActionValue actionValue, LangEnum langEnum)
{
string valueName = actionValue.ValueName;
if (actionValue.TextId != string.Empty)
{
var text = UGQDataHelper.getText(langEnum, actionValue.TextId);
if (text != null)
valueName = text;
}
return new TaskActionValue
{
ValueId = actionValue.ValueId,
UgcValueGuid = actionValue.UgcValueGuid,
ValueName = valueName,
};
}
public static DialogType ToDialogType(this UGQDialogType dialogType, LangEnum langEnum)
{
string typeName = dialogType.TypeName;
if (dialogType.TextId != string.Empty)
{
var text = UGQDataHelper.getText(langEnum, dialogType.TextId);
if (text != null)
typeName = text;
}
return new DialogType
{
TypeId = dialogType.TypeId,
TypeName = typeName,
};
}
public static DialogAction ToDialogAction(this UGQDialogCondition action, LangEnum langEnum)
{
string actionName = action.ConditionName;
if (action.TextId != string.Empty)
{
var text = UGQDataHelper.getText(langEnum, action.TextId);
if (text != null)
actionName = text;
}
return new DialogAction
{
ActionId = action.ConditionId,
ActionName = actionName,
};
}
public static DialogActionValue ToDialogActionValue(this UGQActionValue actionValue, LangEnum langEnum)
{
string valueName = actionValue.ValueName;
if (actionValue.TextId != string.Empty)
{
var text = UGQDataHelper.getText(langEnum, actionValue.TextId);
if (text != null)
valueName = text;
}
return new DialogActionValue
{
ValueId = actionValue.ValueId,
ValueName = valueName,
};
}
public static NpcActionValue ToNpcActionValue(this UGQBeaconActionData npcAction, LangEnum langEnum)
{
string valueName = npcAction.Name;
if (npcAction.Name != string.Empty)
{
var text = UGQDataHelper.getText(langEnum, npcAction.Name);
if (text != null)
valueName = text;
}
return new NpcActionValue
{
Id = npcAction.Id,
Name = valueName,
FileName = npcAction.FileName,
};
}
public static MapDataValue ToMapDataValue(this ZoneMetaData zoneMetaData, LangEnum langEnum)
{
//string valueName = npcAction.zone_name;
//if (npcAction.zone_name != string.Empty)
//{
// var text = UGQDataHelper.getText(langEnum, npcAction.zone_name);
// if (text != null)
// valueName = text;
//}
MetaData.Instance.UGQMapImageMetaDataListbyId.TryGetValue(zoneMetaData.Id, out var mapImageData);
return new MapDataValue
{
Id = zoneMetaData.Id,
Name = zoneMetaData.zone_name,
MapSize_X = zoneMetaData.mapDistance_x,
MapSize_Y = zoneMetaData.mapDistance_y,
ImageFileName = mapImageData == null ? string.Empty : mapImageData.FileName,
};
}
}

31
UGQApiServer/Culture.cs Normal file
View File

@@ -0,0 +1,31 @@
namespace UGQApiServer;
public enum LangEnum
{
En,
Ko,
Jp,
}
public static class Culture
{
public static LangEnum ToLangEnum(string lang)
{
switch (lang)
{
case "ko-KR":
case "ko":
case "kr":
return LangEnum.Ko;
case "ja-JP":
case "ja":
case "jp":
return LangEnum.Jp;
default:
return LangEnum.En;
}
}
}

View File

@@ -0,0 +1,8 @@
<Project>
<PropertyGroup>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
<BaseIntermediateOutputPath>..\..\obj\AnyCPU\$(MSBuildProjectName)\</BaseIntermediateOutputPath>
<BaseOutputPath>..\..\bin\</BaseOutputPath>
</PropertyGroup>
</Project>

View File

@@ -0,0 +1,281 @@
using System.Globalization;
using System.Reflection;
using System.Text;
using System.Text.Json.Serialization;
using Amazon.DynamoDBv2.DocumentModel;
using Asp.Versioning;
using JwtRoleAuthentication.Services;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Localization;
using Microsoft.IdentityModel.Tokens;
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.Filters;
using Swashbuckle.AspNetCore.SwaggerGen;
using Microsoft.Extensions.Configuration;
using Microsoft.AspNetCore.HttpLogging;
using StackExchange.Redis;
using ServerCore; using ServerBase;
using ServerCommon;
using UGQApiServer.Auth;
using UGQApiServer.Settings;
using UGQApiServer.Storage;
using UGQApiServer.UGQData;
using UGQDataAccess.Settings;
using UGQDataAccess.Extensions;
using UGQApiServer.Controllers.Common;
using UGQDataAccess.Service;
namespace UGQApiServer.Extensions;
public class AcceptLanguageHeaderFilter : IOperationFilter
{
public void Apply(OpenApiOperation operation, OperationFilterContext context)
{
if (operation == null)
{
throw new Exception("Invalid operation");
}
operation.Parameters.Add(new OpenApiParameter
{
In = ParameterLocation.Header,
Name = "accept-language",
Description = "pass the locale here: examples like => en-US,en,ko-KR,ko,ja-JP,ja",
Schema = new OpenApiSchema
{
Type = "String"
},
});
}
}
public static class UGQApiExtentions
{
public static async Task AddUGQApiExtention(this IServiceCollection services, IConfiguration configuration)
{
// Add services to the container.
services.AddControllers().AddJsonOptions(x =>
{
// serialize enums as strings in api responses (e.g. Role)
x.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter());
x.JsonSerializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull;
});
Log.getLogger().info("AddJsonOptions");
services.ConfigureHttpJsonOptions(options =>
{
// options.SerializerOptions.Converters.Add(new JsonStringEnumConverter());
options.SerializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull;
});
Log.getLogger().info("ConfigureHttpJsonOptions");
services.Configure<RequestLocalizationOptions>(options =>
{
var supportedCultures = new[]
{
new CultureInfo("en-US"),
new CultureInfo("en"),
new CultureInfo("ko-KR"),
new CultureInfo("ko"),
new CultureInfo("ja-JP"),
new CultureInfo("ja"),
};
options.SupportedCultures = supportedCultures;
options.SupportedUICultures = supportedCultures;
options.DefaultRequestCulture = new RequestCulture(culture: "en-US", uiCulture: "en-US");
options.RequestCultureProviders.Insert(0, new AcceptLanguageHeaderRequestCultureProvider());
});
Log.getLogger().info("RequestLocalizationOptions");
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
services.AddEndpointsApiExplorer();
services.AddSwaggerGen(options =>
{
options.OperationFilter<AcceptLanguageHeaderFilter>();
options.AddSecurityDefinition("Bearer", new Microsoft.OpenApi.Models.OpenApiSecurityScheme
{
Name = "Authorization",
Type = Microsoft.OpenApi.Models.SecuritySchemeType.Http,
Scheme = "Bearer",
BearerFormat = "JWT",
In = Microsoft.OpenApi.Models.ParameterLocation.Header,
Description = "JWT Authorization header using the Bearer scheme."
});
options.AddSecurityRequirement(new Microsoft.OpenApi.Models.OpenApiSecurityRequirement
{
{
new Microsoft.OpenApi.Models.OpenApiSecurityScheme
{
Reference = new Microsoft.OpenApi.Models.OpenApiReference
{
Type = Microsoft.OpenApi.Models.ReferenceType.SecurityScheme,
Id = "Bearer"
}
},
new string [] {}
}
});
// using System.Reflection;
// var xmlFilename = $"{Assembly.GetEntryAssembly()?.GetName().Name ?? string.Empty}.xml";
// <20><> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ʈ <20≯<EFBFBD><CCB8><EFBFBD><EFBFBD><EFBFBD> <20>ؾ<EFBFBD> Comment<6E><74> <20>ε<EFBFBD> <20>ȴ<EFBFBD>.
var xmlFilename = "UGQApiServer.xml";
options.IncludeXmlComments(Path.Combine(AppContext.BaseDirectory, xmlFilename));
options.OperationFilter<AppendAuthorizeToSummaryOperationFilter>();
options.SwaggerDoc("v1", new OpenApiInfo { Title = "UGQApiServer", Version = "v1" });
options.SwaggerDoc("ingame", new OpenApiInfo { Title = "InGame Api", Version = "ingame" });
options.SwaggerDoc("admin", new OpenApiInfo { Title = "Admin Api", Version = "admin" });
options.SwaggerDoc("development", new OpenApiInfo { Title = "Development Api", Version = "development" });
});
Log.getLogger().info("AddSwaggerGen");
services.AddApiVersioning(x =>
{
x.DefaultApiVersion = new ApiVersion(1, 0);
x.AssumeDefaultVersionWhenUnspecified = true;
x.ReportApiVersions = true;
})
.AddApiExplorer(x =>
{
x.GroupNameFormat = "'v'VVV";
x.SubstituteApiVersionInUrl = true;
});
Log.getLogger().info("AddApiVersioning");
services.Configure<JWTSettings>(configuration.GetSection("JWT"));
var jwtSettings = new JWTSettings();
configuration.Bind("JWT", jwtSettings);
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.IncludeErrorDetails = true;
options.TokenValidationParameters = new TokenValidationParameters()
{
ClockSkew = TimeSpan.Zero,
ValidateIssuer = false,
ValidateAudience = false,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = jwtSettings.ValidIssuer,
ValidAudience = jwtSettings.ValidAudience,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtSettings.Secret)
),
};
});
Log.getLogger().info("JWT");
MetaData.Instance.LoadTableAll();
Log.getLogger().info("LoadTableAll");
await services.AddUGQDataAccess(configuration);
Log.getLogger().info("AddUGQDataAccess");
services.AddSingleton<UGQMetaData>(provider =>
{
var inGameService = provider.GetRequiredService<InGameService>();
var ugqMetaData = new UGQMetaData(inGameService);
ugqMetaData.init();
return ugqMetaData;
});
Log.getLogger().info("UGQMetaData");
services.Configure<S3Settings>(configuration.GetSection("S3"));
services.AddSingleton<IStorageService, S3StorageService>();
services.AddSingleton<UGQBannerImageList>();
Log.getLogger().info("S3StorageService");
services.AddSingleton<TokenService>();
services.AddSingleton<WebPortalTokenAuth>();
services.AddSingleton<IConnectionMultiplexer>(ConnectionMultiplexer.Connect(configuration["Redis"]!));
Log.getLogger().info("Redis");
services.AddSingleton<DynamoDbClient>(_ =>
{
var dynamoDbClient = new DynamoDbClient();
var settings = configuration.GetSection("DynamoDb").Get<DynamoDbSettings>();
(var error_code, var table_names) = ServerConfigHelper.getDynamoDbTableNamesWithServiceType(settings?.TablePostfix ?? string.Empty);
ConditionValidCheckHelper.throwIfFalseWithCondition(() => ServerErrorCode.Success == error_code, () => $"Failed to ServerConfigHelper.getDynamoDbTableNamesWithServiceType() !!! : errorCode:{error_code}");
if (settings != null)
{
var local = !string.IsNullOrEmpty(settings.Url);
dynamoDbClient.connectToDb( table_names, local, settings.Url ?? string.Empty
, settings.AccessKey, settings.SecretKey, settings.Region );
}
dynamoDbClient.createDBIfNotExists(false).GetAwaiter().GetResult();
CurrencyControlHelper.setDbConnector(dynamoDbClient);
return dynamoDbClient;
});
Log.getLogger().info("DynamoDbClient");
services.AddSingleton<MetaDataApi>();
services.AddSingleton<QuestEditorApi>();
services.AddCors(options => options.AddPolicy("Everything", policy =>
{
policy
.AllowAnyOrigin()
.AllowAnyHeader()
.AllowAnyMethod();
}));
Log.getLogger().info("AddCors");
services.AddHealthChecks();
Log.getLogger().info("AddHealthChecks");
/*
services.AddHttpLogging(o => {
o.LoggingFields = HttpLoggingFields.RequestBody | HttpLoggingFields.ResponseBody;
o.RequestBodyLogLimit = 4096;
o.ResponseBodyLogLimit = 4096;
});
Log.getLogger().info("AddHttpLogging");
*/
}
}

View File

@@ -0,0 +1,150 @@
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Security.Cryptography;
using System.Text;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
using UGQApiServer.Settings;
using UGQDatabase.Models;
namespace JwtRoleAuthentication.Services;
public class TokenService
{
readonly JWTSettings _jwtSettings;
public TokenService(IOptions<JWTSettings> settings)
{
_jwtSettings = settings.Value;
}
public string CreateToken(string userGuid, string nickname)
{
var token = CreateJwtToken(CreateClaims(userGuid, nickname));
var tokenHandler = new JwtSecurityTokenHandler();
return tokenHandler.WriteToken(token);
}
public string CreateAdminToken(string username, UGQAccountRole role)
{
var token = CreateJwtToken(CreateAdminClaims(username, role));
var tokenHandler = new JwtSecurityTokenHandler();
return tokenHandler.WriteToken(token);
}
public string GenerateRefreshToken()
{
var randomNumber = new byte[32];
using var rng = RandomNumberGenerator.Create();
rng.GetBytes(randomNumber);
return Convert.ToBase64String(randomNumber);
}
public ClaimsPrincipal GetPrincipalFromExpiredToken(string token)
{
var tokenValidationParameters = new TokenValidationParameters
{
ValidateAudience = false, //you might want to validate the audience and issuer depending on your use case
ValidateIssuer = false,
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_jwtSettings.Secret)),
ValidateLifetime = false //here we are saying that we don't care about the token's expiration date
};
var tokenHandler = new JwtSecurityTokenHandler();
SecurityToken securityToken;
var principal = tokenHandler.ValidateToken(token, tokenValidationParameters, out securityToken);
var jwtSecurityToken = securityToken as JwtSecurityToken;
if (jwtSecurityToken == null || !jwtSecurityToken.Header.Alg.Equals(SecurityAlgorithms.HmacSha256, StringComparison.InvariantCultureIgnoreCase))
throw new SecurityTokenException("Invalid token");
return principal;
}
public string GenerateAccessTokenFromRefreshToken(string refreshToken, string secret)
{
// Implement logic to generate a new access token from the refresh token
// Verify the refresh token and extract necessary information (e.g., user ID)
// Then generate a new access token
// For demonstration purposes, return a new token with an extended expiry
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes(secret);
var tokenDescriptor = new SecurityTokenDescriptor
{
Expires = DateTime.UtcNow.AddMinutes(15), // Extend expiration time
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
};
var token = tokenHandler.CreateToken(tokenDescriptor);
return tokenHandler.WriteToken(token);
}
private JwtSecurityToken CreateJwtToken(List<Claim> claims)
{
var symmetricSecurityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_jwtSettings.Secret));
DateTime expiration = DateTime.UtcNow.AddMinutes(_jwtSettings.TokenValidityInMinutes);
return new(
_jwtSettings.ValidIssuer,
_jwtSettings.ValidAudience,
claims,
expires: expiration,
signingCredentials: new SigningCredentials(symmetricSecurityKey, SecurityAlgorithms.HmacSha256)
);
}
private List<Claim> CreateAdminClaims(string username, UGQAccountRole role)
{
try
{
var claims = new List<Claim>
{
// new Claim(JwtRegisteredClaimNames.Sub, jwtSub),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
new Claim(JwtRegisteredClaimNames.Iat, DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString()),
new Claim(ClaimTypes.Name, username),
new Claim(ClaimTypes.Role, role.ToString())
};
return claims;
}
catch (Exception e)
{
Console.WriteLine(e);
throw;
}
}
private List<Claim> CreateClaims(string userGuid, string nickname)
{
try
{
var claims = new List<Claim>
{
// new Claim(JwtRegisteredClaimNames.Sub, jwtSub),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
new Claim(JwtRegisteredClaimNames.Iat, DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString()),
new Claim("Id", userGuid),
new Claim(ClaimTypes.Name, nickname),
// new Claim(ClaimTypes.Email, user.Email),
// new Claim(ClaimTypes.Role, user.Role.ToString())
};
return claims;
}
catch (Exception e)
{
Console.WriteLine(e);
throw;
}
}
}

View File

@@ -0,0 +1,144 @@
using System;
using System.Text.Json.Serialization;
using Amazon.S3.Model;
using UGQDatabase.Models;
namespace UGQApiServer.Models;
#pragma warning disable CS8618
public class AdminSignupRequest
{
public string Username { get; set; }
public string Password { get; set; }
}
public class AdminSignupResponse
{
public string Username { get; set; }
[JsonConverter(typeof(JsonStringEnumConverter))]
public UGQAccountRole Role { get; set; }
}
public class AdminLoginRequest
{
public string Username { get; set; }
public string Password { get; set; }
}
public class AdminLoginResponse
{
public string Username { get; set; }
[JsonConverter(typeof(JsonStringEnumConverter))]
public UGQAccountRole Role { get; set; }
public string AccessToken { get; set; }
public string RefreshToken { get; set; }
}
public class AdminChangePaswordRequest
{
public string NewPassword { get; set; }
}
public class AdminChangePaswordResponse
{
}
public class AdminUpdatePaswordRequest
{
public string Username { get; set; }
public string NewPassword { get; set; }
}
public class AdminUpdatePaswordResponse
{
}
public class UQGAllQuestResponse
{
public long TotalCount { get; set; }
public int PageNumber { get; set; }
public int PageSize { get; set; }
public int TotalPages { get; set; }
public List<UGQSummary> Summaries { get; set; }
}
public class UGQAllAccountItemResponse
{
public long TotalCount { get; set; }
public int PageNumber { get; set; }
public int PageSize { get; set; }
public int TotalPages { get; set; }
public List<UGQAccountItem> Accounts { get; set; }
}
public class UGQModifyAccountSlot
{
public string UserGuid { get; set; }
public int Count { get; set; }
}
public class UGQReserveAccountGrade
{
public string UserGuid { get; set; }
[JsonConverter(typeof(JsonStringEnumConverter))]
public UgqGradeType GradeType { get; set; }
public DateTime ReserveTime { get; set; }
}
public class UGQModifyAccountGrade
{
public string ReserveId { get; set; }
[JsonConverter(typeof(JsonStringEnumConverter))]
public UgqGradeType GradeType { get; set; }
public DateTime ReserveTime { get; set; }
}
public class UGQModifyCreatorPoint
{
public string UserGuid { get; set; }
public int Amount { get; set; }
public string Reason { get; set; }
}
public class UGQMetaverseAccount
{
public string? LoginAccountId { get; set; }
public string? UserGuid { get; set; }
public string? Nickname { get; set; }
}
public class UGQReserveAccountGradeResult
{
public string ReserveId { get; set; }
public string UserGuid { get; set; }
public string AccountId { get; set; }
[JsonConverter(typeof(JsonStringEnumConverter))]
public UgqGradeType CurrentGradeType { get; set; }
[JsonConverter(typeof(JsonStringEnumConverter))]
public UgqGradeType ReserveGradeType { get; set; }
public DateTime ReserveTime { get; set; }
public DateTime UpdatedAt { get; set; }
public bool IsCompleted { get; set; }
}
public class UGQAllReserveAccountGradeItemResponse
{
public long TotalCount { get; set; }
public int PageNumber { get; set; }
public int PageSize { get; set; }
public int TotalPages { get; set; }
public List<UGQReserveAccountGradeResult> Accounts { get; set; }
}
public class UGQReserveAccountGradeItemResponse
{
public List<UGQReserveAccountGradeResult> Accounts { get; set; }
}

View File

@@ -0,0 +1,100 @@
using System.Text.Json.Serialization;
using UGQDatabase.Models;
namespace UGQApiServer.Models;
#pragma warning disable CS8618
public class GetGameDBAccountByEmail
{
public string Message { get; set; }
public string? LoginAccountId { get; set; }
public string? UserGuid { get; set; }
public string? Nickname { get; set; }
}
public class AddGameDBAccountRequest
{
public string LoginAccountId { get; set; }
public string Nickname { get; set; }
}
public class AddGameDBAccountByEmailRequest
{
public string Email { get; set; }
public string Nickname { get; set; }
}
public class AddGameDBAccountResponse
{
public string LoginAccountId { get; set; }
public string UserGuid { get; set; }
public string Nickname { get; set; }
}
public class AddFakeUgcNpcReqeust
{
public string UserGuid { get; set; }
public string UgcNpcNickname { get; set; }
}
public class AddCreatorPointReqeust
{
public string UserGuid { get; set; }
public long QuestId { get; set; }
public long Revision { get; set; }
public int Amount { get; set; }
}
public class SettleUpCreatorPointReqeust
{
public string UserGuid { get; set; }
public int Amount { get; set; }
}
public class GameQuestDataFakeUpdateReqeust
{
public string UserGuid { get; set; }
public string QuestContentId { get; set; }
}
public class ChangeCurrencyAmountRequest
{
public string UserGuid { get; set; }
public double Amount { get; set; }
}
public class ChangeCurrencyAmountResponse
{
public string UserGuid { get; set; }
[JsonConverter(typeof(JsonStringEnumConverter))]
public CurrencyType CurrencyType { get; set; }
public double CurrentAmount { get; set; }
}
public class ValidateQuestRequest
{
public string UserGuid { get; set; }
}
public class ValidateQuestItem
{
public QuestContentEntity QuestContent { get; set; }
public List<QuestDialogEntity> QuestDialogs { get; set; }
public List<string> Errors { get; set; }
}
public class ValidateQuestResponse
{
public List<ValidateQuestItem> Items { get; set; }
}
public class CopyGameScriptDataRequest
{
public long QuestId { get; set; }
}

View File

@@ -0,0 +1,144 @@
using MetaAssets;
namespace UGQApiServer.Models;
#pragma warning disable CS8618
public class UGQNpcMetaData
{
public int? NpcId { get; set; }
public string? UgcNpcGuid { get; set; }
public string NpcName { get; set; }
public string NpcTitle { get; set; }
public EGenderType NpcGender { get; set; }
public Locaion Location { get; set; }
}
public class Locaion
{
public int x { get; set; }
public int y { get; set; }
}
public class UGQNpcActionMetaData
{
public int ActionId { get; set; }
public string ActionName { get; set; }
public string Url { get; set; }
}
public class UGQNpcMetaDataList
{
public int PageNumber { get; set; }
public int PageSize { get; set; }
public bool HasNextPage { get; set; }
public List<UGQNpcMetaData> Npcs { get; set; }
}
public class TaskAction
{
public int ActionId { get; set; }
public string ActionName { get; set; }
public bool Disabled { get; set; }
}
public class TaskActionList
{
public int PageNumber { get; set; }
public int PageSize { get; set; }
public bool HasNextPage { get; set; }
public List<TaskAction> TaskActions { get; set; }
}
public class DialogType
{
public int TypeId { get; set; }
public string TypeName { get; set; }
public bool Disabled { get; set; }
}
public class DialogTypeList
{
public int PageNumber { get; set; }
public int PageSize { get; set; }
public bool HasNextPage { get; set; }
public List<DialogType> DialogTypes { get; set; }
}
public class DialogAction
{
public int? ActionId { get; set; }
public string ActionName { get; set; }
public bool Disabled { get; set; }
}
public class DialogActionList
{
public int PageNumber { get; set; }
public int PageSize { get; set; }
public bool HasNextPage { get; set; }
public List<DialogAction> DialogActions { get; set; }
}
public class TaskActionValue
{
public int? ValueId { get; set; }
public string? UgcValueGuid { get; set; }
public string ValueName { get; set; }
}
public class TaskActionValueList
{
public int PageNumber { get; set; }
public int PageSize { get; set; }
public bool HasNextPage { get; set; }
public List<TaskActionValue> Values { get; set; }
}
public class DialogActionValue
{
public int? ValueId { get; set; }
public string ValueName { get; set; }
}
public class DialogActionValueList
{
public int PageNumber { get; set; }
public int PageSize { get; set; }
public bool HasNextPage { get; set; }
public List<DialogActionValue> Values { get; set; }
}
public class NpcActionValue
{
public int Id { get; set; }
public string Name { get; set; }
public string FileName { get; set; }
}
public class MapDataValue
{
public int Id { get; set; }
public string Name { get; set; }
public int MapSize_X { get; set; }
public int MapSize_Y { get; set; }
public string ImageFileName { get; set; }
}
public class NpcActionValueList
{
public int PageNumber { get; set; }
public int PageSize { get; set; }
public bool HasNextPage { get; set; }
public List<NpcActionValue> Values { get; set; }
}
public class MapDataValueList
{
public int PageNumber { get; set; }
public int PageSize { get; set; }
public bool HasNextPage { get; set; }
public List<MapDataValue> Values { get; set; }
}

View File

@@ -0,0 +1,124 @@
using System.Text.Json.Serialization;
using UGQDataAccess;
using UGQDatabase.Models;
namespace UGQApiServer.Models;
#pragma warning disable CS8618
public class OldDevLoginRequest
{
public string UserGuid { get; set; }
}
public class DevLoginRequest
{
public string LoginAccountId { get; set; }
}
public class LoginRequest
{
public string WebPortalToken { get; set; }
}
public class IgmLoginResponse
{
public string UserGuid { get; set; }
public string Nickname { get; set; }
public string AccessToken { get; set; }
}
public class LoginResponse
{
public string UserGuid { get; set; }
public string Nickname { get; set; }
public string AccessToken { get; set; }
public string RefreshToken { get; set; }
}
public class RefreshTokenRequest
{
public string AccessToken { get; set; }
public string RefreshToken { get; set; }
}
public class RefreshTokenResponse
{
public string AccessToken { get; set; }
public string RefreshToken { get; set; }
}
public class UGQAccount
{
public string UserGuid { get; set; }
public string Nickname { get; set; }
public string AccountId { get; set; }
public int SlotCount { get; set; }
[JsonConverter(typeof(JsonStringEnumConverter))]
public UgqGradeType GradeType { get; set; }
public double CreatorPoint { get; set; }
public DateTime CreatedAt { get; set; }
}
public class UGQAccountDetail : UGQAccount
{
public int TotalQuests { get; set; }
}
public class UGQAccountCurrency
{
[JsonConverter(typeof(JsonStringEnumConverter))]
public CurrencyType CurrencyType { get; set; }
public double Amount { get; set; }
}
public class UGQSlotPrice
{
[JsonConverter(typeof(JsonStringEnumConverter))]
public CurrencyType CurrencyType { get; set; }
public double Price { get; set; }
}
public class UGQAccountItem : UGQAccount
{
public int QuestCount { get; set; }
}
public class UGQAccountMoney : UGQAccount
{
public double Sapphire { get; set; }
}
public class UGQSummaryList
{
public List<UGQSummary> UGQSummaries { get; set; }
}
public class UGQAddQuestDialogResponse
{
public UGQContent QuestContent { get; set; }
public UGQDialog QuestDialog { get; set; }
}
public class TagIdResponse
{
public int TagId { get; set; }
public string TagName { get; set; }
}
public class UserBeacon
{
public string BeaconId { get; set; }
public string BeaconName { get; set; }
}
public class UserBeaconsResponse
{
public int PageNumber { get; set; }
public int PageSize { get; set; }
public bool HasNextPage { get; set; }
public List<UserBeacon> UserBeacons { get; set; }
}

View File

@@ -0,0 +1,208 @@
using System.Text.Json.Serialization;
using MongoDB.Bson.Serialization.Attributes;
using MongoDB.Bson;
using UGQDatabase.Models;
using UGQDataAccess.Service;
using UGQDataAccess.Repository.Models;
namespace UGQApiServer.Models;
#pragma warning disable CS8618
public class UGQStateGroup
{
[JsonConverter(typeof(JsonStringEnumConverter))]
public QuestContentState State { get; set; }
public int Count { get; set; }
}
public class UGQSummary
{
public string QuestContentId { get; set; }
public long QuestId { get; set; }
public long Revision { get; set; }
public string UserGuid { get; set; }
public string Author { get; set; }
public int BeaconId { get; set; }
public string? UgcBeaconGuid { get; set; }
[JsonConverter(typeof(JsonStringEnumConverter))]
public UgqGradeType GradeType { get; set; }
public string Title { get; set; }
public List<string> Languages { get; set; }
public string Description { get; set; }
public int Cost { get; set; }
public string TitleImagePath { get; set; }
public string BannerImagePath { get; set; }
[JsonConverter(typeof(JsonStringEnumConverter))]
public QuestContentState State { get; set; }
public DateTime UpdatedAt { get; set; }
public DateTime CreatedAt { get; set; }
public UGQStats Stats { get; set; }
public string BeaconName { get; set; }
public string Savelanguage { get; set; }
}
public class UGQQuestProfitStats
{
public string QuestContentId { get; set; }
public long QuestId { get; set; }
public long Revision { get; set; }
public string Title { get; set; }
public string TitleImagePath { get; set; }
public string BannerImagePath { get; set; }
public int Completed { get; set; }
public double TotalProfit { get; set; }
}
public class UGQText
{
public string Kr { get; set; }
public string En { get; set; }
public string Jp { get; set; }
}
public class UGQPresetImage
{
public int Id { get; set; }
public string ImagePath { get; set; }
}
public class UGQDialogSequenceAction
{
public DialogTalker Talker { get; set; }
public int Type { get; set; }
public UGQText Talk { get; set; }
public int Condition { get; set; }
public int ConditionValue { get; set; }
public int NextSequence { get; set; }
public int NpcAction { get; set; }
public string TypeName { get; set; }
public string ConditioneName { get; set; }
public string ConditioneValueName { get; set; }
}
public class UGQDialogSequence
{
public int SequenceId { get; set; }
public List<UGQDialogSequenceAction> Actions { get; set; }
}
public class UGQDialog
{
public string DialogId { get; set; }
public List<UGQDialogSequence> Sequences { get; set; }
}
public class UGQTask
{
public UGQText GoalText { get; set; }
public int ActionId { get; set; }
public int ActionValue { get; set; }
public bool IsShowNpcLocation { get; set; }
public string? UgcActionValueGuid { get; set; }
public string? UgcActionValueName { get; set; }
public string? DialogId { get; set; }
public string ActionName { get; set; }
public string ActionValueName { get; set; }
}
public class UGQContent
{
public string QuestContentId { get; set; }
public long QuestId { get; set; }
public long Revision { get; set; }
public int BeaconId { get; set; }
public string? UgcBeaconGuid { get; set; }
[JsonConverter(typeof(JsonStringEnumConverter))]
public UgqGradeType GradeType { get; set; }
public UGQText Title { get; set; }
public List<string> Languages { get; set; }
public string TitleImagePath { get; set; }
public string BannerImagePath { get; set; }
public UGQText Description { get; set; }
[JsonConverter(typeof(JsonStringEnumConverter))]
public QuestContentState State { get; set; }
public int Cost { get; set; }
public List<UGQTask> Tasks { get; set; }
public DateTimeOffset LastUpdated { get; set; }
public string BeaconName { get; set; }
}
public class UGQBoard
{
public int QuestContentId { get; set; }
public long QuestId { get; set; }
public long Revision { get; set; }
public string Title { get; set; }
public List<string> Languages { get; set; }
public string TitleImagePath { get; set; }
public string BannerImagePath { get; set; }
public string Description { get; set; }
public UGQStats Stats { get; set; }
[JsonConverter(typeof(JsonStringEnumConverter))]
public QuestContentState State { get; set; }
public int Cost { get; set; }
public DateTimeOffset LastUpdated { get; set; }
}
public class UGQCreatorPointHistory
{
[JsonConverter(typeof(JsonStringEnumConverter))]
public CreatorPointHistoryKind Kind { get; set; }
// public string DetailReason { get; set; }
public double Amount { get; set; }
public double TotalAmount { get; set; }
public DateTimeOffset CreatedAt { get; set; }
}
public class UGQCreatorPointHistoryResult
{
public int PageNumber { get; set; }
public int PageSize { get; set; }
public int TotalPages { get; set; }
public List<UGQCreatorPointHistory> Items { get; set; }
}
public class UGQSummaryResponse
{
public int TotalCount { get; set; }
public List<UGQStateGroup> StateGroups { get; set; }
public int PageNumber { get; set; }
public int PageSize { get; set; }
public int TotalPages { get; set; }
public List<UGQSummary> Summaries { get; set; }
}
public class UGQQuestProfitStatsResult
{
public int PageNumber { get; set; }
public int PageSize { get; set; }
public int TotalPages { get; set; }
public List<UGQQuestProfitStats> Items { get; set; }
}

View File

@@ -0,0 +1,29 @@
using ControlCenter.NamedPipeHost.Manager;
using ControlCenter.NamedPipeHost.NamedPipe;
using ServerControlCenter;
using ServerCore; using ServerBase;
namespace UGQApiServer.NamedPipePacketHandler;
public class ForceStopServerMessageReceiver : NamedPipeReceiver<A2S_REQ_FORCE_STOP_SERVER>
{
private readonly ServerInfoManager m_info_manager;
public ForceStopServerMessageReceiver(ServerInfoManager infoManager)
{
m_info_manager = infoManager;
}
public override async Task Handle(A2S_REQ_FORCE_STOP_SERVER message, string message_id)
{
Log.getLogger().debug($"{nameof(ForceStopServerMessageReceiver)}: Receive - message_id[{message_id}] / message[{message}]");
m_info_manager.setServerStatus(ServerStatus.Stop);
// 정보 전달 대기
await Task.Delay(1_000);
// process 종료
Environment.Exit(0);
}
}

View File

@@ -0,0 +1,29 @@
using ControlCenter.NamedPipeHost.Manager;
using ControlCenter.NamedPipeHost.NamedPipe;
using ServerControlCenter;
using ServerCore; using ServerBase;
namespace UGQApiServer.NamedPipePacketHandler;
public class StopServerMessageReceiver : NamedPipeReceiver<A2S_REQ_STOP_SERVER>
{
private readonly ServerInfoManager m_info_manager;
public StopServerMessageReceiver(ServerInfoManager infoManager)
{
m_info_manager = infoManager;
}
public override async Task Handle(A2S_REQ_STOP_SERVER message, string message_id)
{
Log.getLogger().debug($"{nameof(StopServerMessageReceiver)}: Receive - message_id[{message_id}] / message[{message}]");
m_info_manager.setServerStatus(ServerStatus.Stop);
// 정보 전달 대기
await Task.Delay(1_000);
// process 종료
Environment.Exit(0);
}
}

174
UGQApiServer/Program.cs Normal file
View File

@@ -0,0 +1,174 @@
using System.Net;
using System.Reflection;
using Amazon.DynamoDBv2.DocumentModel;
using CommandLine;
using ControlCenter.NamedPipeHost.Extensions;
using Google.Protobuf;
using Microsoft.AspNetCore.Server.Kestrel.Core;
using NLog.Web;
using ServerCommon;
using ServerCore; using ServerBase;
using UGQApiServer.Converter;
using UGQApiServer.Extensions;
using UGQApiServer.Settings;
using UGQApiServer.Storage;
using UGQApiServer.UGQData;
using UGQApiServer.Auth;
using UGQApiServer.BackGroundService;
using UGQDataAccess.Logs;
namespace UGQApiServer
{
public class CommandLineOptions
{
[Option('t', "serverType", Required = true, HelpText = "UGQ API Server Type: [UgqApi, UgqAdmin, UgqIngame, UgqAllInOne]")]
public UgqApiType m_type { get; set; }
[Option('p', "port", Required = true, HelpText = "UGQ API Server Port")]
public int m_port { get; set; }
[Option('d', "develop", Default = false, Required = false, HelpText = "UGQ API Server Development Mode")]
public bool m_develop { get; set; }
}
/*
message UgqGameTaskDataForClient
{
int32 taskNum = 1;
UgqGameTextDataForClient goalText = 2;
optional string dialogueId = 3;
}
*/
public class Program
{
public static async Task Main(string[] args)
{
var parsing = Parser.Default.ParseArguments<CommandLineOptions>(args);
if (parsing?.Value == null) return;
var options = parsing.Value;
var apiAllowSettings = new ApiAllowSettings(options.m_type);
var builder = WebApplication.CreateBuilder(args);
Console.WriteLine($"Env: {builder.Environment.EnvironmentName}");
// kestrel
options.m_port = usingKestrel(builder, options.m_port);
Log.NLogFileName = "./Config/nlog-UGQApiServer.config";
Log.initLog("UGQApiServer", "Developer");
UgqApiBusinessLogger.initBusinessLog();
Log.getLogger().info("startup");
builder.Logging.ClearProviders();
builder.Logging.SetMinimumLevel(LogLevel.Trace);
builder.Host.UseNLog();
builder.Services.AddSingleton(apiAllowSettings);
builder.Services.AddSingleton(options);
builder.Services.AddSingleton<AuthSql>();
builder.Services.AddSingleton<ReserveAccountGradeBackGroundService>();
builder.Services.AddHostedService(provider => provider.GetRequiredService<ReserveAccountGradeBackGroundService>());
builder.Services.AddOnTimeBackgroundService();
builder.Services.AddOnTimeTask("logging", new DateTime(1, 1, 1, 0, 1, 0), new Task(DailyEmptyLogging));
await builder.Services.AddUGQApiExtention(builder.Configuration);
var assemblies = new List<Assembly>(AppDomain.CurrentDomain.GetAssemblies());
var serverType = changeServerTypeFromUgcTypeToControlCenterType(options.m_type);
builder.Services.AddNamedPipelineClientManager(assemblies, serverType, ServiceCategory.Caliverse.ToString(), NetworkHelper.getEthernetLocalIPv4(), options.m_port);
var app = builder.Build();
// app.UseHttpLogging();
app.MapHealthChecks("/healthcheck");
UGQConverter.init(app.Services.GetRequiredService<IStorageService>(), app.Services.GetRequiredService<UGQMetaData>());
InGameConverter.init(app.Services.GetRequiredService<IStorageService>(), app.Services.GetRequiredService<UGQMetaData>());
bool isDevelopment = app.Environment.IsDevelopment() || options.m_develop ||
(builder.Environment.EnvironmentName == "AWSDev");
// Configure the HTTP request pipeline.
if (isDevelopment == true)
{
app.UseSwagger();
app.UseSwaggerUI(c =>
{
// Account api 때문에 모든 설정에서 보여야 함
// if (apiAllowSettings.AllowWebApi) c.SwaggerEndpoint("/swagger/v1/swagger.json", "v1");
c.SwaggerEndpoint("/swagger/v1/swagger.json", "v1");
if (apiAllowSettings.AllowInGameApi) c.SwaggerEndpoint("/swagger/ingame/swagger.json", "InGame");
if (apiAllowSettings.AllowAdminApi) c.SwaggerEndpoint("/swagger/admin/swagger.json", "Admin");
if (isDevelopment) c.SwaggerEndpoint("/swagger/development/swagger.json", "Development");
});
}
app.UseRequestLocalization();
app.UseCors("Everything");
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.UseNamedPipelineClientManager();
await app.RunAsync();
}
private static void DailyEmptyLogging()
{
List<ILogInvoker> business_logs = [
new UgqApiEmptyLoginBusinessLog(),
];
var log_action = new LogActionEx(LogActionType.TestBusinessLog);
var empty_business_refresh_with_log_actor = new EmptyBusinessWithLogActor();
BusinessLogger.collectLogs(log_action, empty_business_refresh_with_log_actor, business_logs);
}
private static int usingKestrel(WebApplicationBuilder builder, int port)
{
port = port <= 0 ? Default.DefaultPort : port;
builder.WebHost.ConfigureKestrel(options =>
{
// port 설정
options.Listen(IPAddress.Any, port, o =>
{
// https 사용 설정
//o.UseHttps("*.pfx", "pwd");
// protocols 설정
o.Protocols = HttpProtocols.Http1;
});
});
return port;
}
private static ServerControlCenter.ServerType changeServerTypeFromUgcTypeToControlCenterType(UgqApiType type)
{
return type switch
{
UgqApiType.UgqApi or UgqApiType.UgqAllInOne => ServerControlCenter.ServerType.UgqApi,
UgqApiType.UgqIngame => ServerControlCenter.ServerType.UgqIngame,
UgqApiType.UgqAdmin => ServerControlCenter.ServerType.UgqAdmin,
_ => ServerControlCenter.ServerType.None
};
}
}
}

View File

@@ -0,0 +1,33 @@
{
"profiles": {
"http": {
"commandName": "Project",
"commandLineArgs": "-t UgqAllInOne -p 11000",
"workingDirectory": "..\\..\\bin\\Debug",
"launchBrowser": true,
"launchUrl": "swagger",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"dotnetRunMessages": true,
"applicationUrl": "http://localhost:11000"
},
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"launchUrl": "swagger",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
},
"$schema": "https://json.schemastore.org/launchsettings.json",
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:42071",
"sslPort": 0
}
}
}

View File

@@ -0,0 +1,10 @@
namespace UGQApiServer.Settings;
public class DynamoDbSettings
{
public string Url { get; set; } = null!;
public string TablePostfix { get; set; } = null!;
public string AccessKey { get; set; } = null!;
public string SecretKey { get; set; } = null!;
public string Region { get; set; } = null!;
}

View File

@@ -0,0 +1,12 @@
namespace UGQApiServer.Settings;
#pragma warning disable CS8618
public class JWTSettings
{
public string ValidAudience { get; set; }
public string ValidIssuer { get; set; }
public string Secret { get; set; }
public int TokenValidityInMinutes { get; set; }
}

View File

@@ -0,0 +1,15 @@
namespace UGQDataAccess.Settings;
#pragma warning disable CS8618
public class S3Settings
{
public string AccessKey { get; set; }
public string SecretAccessKey { get; set; }
public string BucketName { get; set; }
public string RootFolder { get; set; }
public string DownloadUrl { get; set; }
}

View File

@@ -0,0 +1,12 @@
namespace UGQApiServer.Storage;
public interface IStorageService
{
string fileUrl(string filename);
Task<bool> uploadFile(string filename, IFormFile file);
Task<bool> deleteFile(string filename);
}

View File

@@ -0,0 +1,40 @@
namespace UGQApiServer.Storage;
public class LocalStorageService : IStorageService
{
public LocalStorageService()
{
}
public string fileUrl(string filename)
{
return filename;
}
public async Task<bool> uploadFile(string filename, IFormFile file)
{
await Task.CompletedTask;
string path = Path.Combine(Directory.GetCurrentDirectory(), "files");
if (!Directory.Exists(path))
Directory.CreateDirectory(path);
using (var stream = new FileStream(Path.Combine(path, filename), FileMode.Create))
{
file.CopyTo(stream);
}
return true;
}
public async Task<bool> deleteFile(string filename)
{
await Task.CompletedTask;
System.IO.File.Delete(filename);
return true;
}
}

View File

@@ -0,0 +1,63 @@
using Amazon.S3;
using UGQDataAccess.Settings;
using Microsoft.Extensions.Options;
using Amazon.S3.Transfer;
using Amazon.S3.Model;
using Microsoft.AspNetCore.DataProtection.KeyManagement;
namespace UGQApiServer.Storage;
public class S3StorageService : IStorageService
{
readonly AmazonS3Client _s3Client;
readonly S3Settings _settings;
public S3StorageService(IOptions<S3Settings> settings)
{
_settings = settings.Value;
_s3Client = new AmazonS3Client(_settings.AccessKey, _settings.SecretAccessKey, Amazon.RegionEndpoint.USEast1);
}
public string fileUrl(string filename)
{
if (string.IsNullOrEmpty(filename) == true)
return string.Empty;
return $"{_settings.DownloadUrl}/{_settings.RootFolder}/{filename}";
}
public async Task<bool> uploadFile(string filename, IFormFile file)
{
using (var newMemoryStream = new MemoryStream())
{
file.CopyTo(newMemoryStream);
var uploadRequest = new TransferUtilityUploadRequest
{
InputStream = newMemoryStream,
Key = $"{_settings.RootFolder}/{filename}",
BucketName = _settings.BucketName,
};
var fileTransferUtility = new TransferUtility(_s3Client);
await fileTransferUtility.UploadAsync(uploadRequest);
}
return true;
}
public async Task<bool> deleteFile(string filename)
{
var deleteObjectRequest = new DeleteObjectRequest
{
BucketName = _settings.BucketName,
Key = $"{_settings.RootFolder}/{filename}",
};
await _s3Client.DeleteObjectAsync(deleteObjectRequest);
return true;
}
}

View File

@@ -0,0 +1,51 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<OutputType>Exe</OutputType>
<ImplicitUsings>enable</ImplicitUsings>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<Deterministic>true</Deterministic>
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
<Configurations>Debug;Release;Shipping</Configurations>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<GenerateTargetFrameworkAttribute>false</GenerateTargetFrameworkAttribute>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<NoWarn>1591</NoWarn>
<DebugType>full</DebugType>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<NoWarn>1591</NoWarn>
<DebugType>full</DebugType>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Shipping|AnyCPU'">
<NoWarn>1591</NoWarn>
<DebugType>full</DebugType>
<Optimize>true</Optimize>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Asp.Versioning.Mvc" />
<PackageReference Include="Asp.Versioning.Mvc.ApiExplorer" />
<PackageReference Include="AWSSDK.S3" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" />
<PackageReference Include="Microsoft.AspNetCore.OpenApi" />
<PackageReference Include="Microsoft.Extensions.Caching.StackExchangeRedis" />
<PackageReference Include="NLog.Web.AspNetCore" />
<PackageReference Include="Swashbuckle.AspNetCore" />
<PackageReference Include="Swashbuckle.AspNetCore.Annotations" />
<PackageReference Include="Swashbuckle.AspNetCore.Filters" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Tools\ControlCenter\ControlCenter.NamedPipeHost\ControlCenter.NamedPipeHost.csproj" />
<ProjectReference Include="..\UGQDataAccess\UGQDataAccess.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,42 @@
using System.Text;
using System.Text.Json;
using System.Collections.Generic;
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.Options;
using Amazon.S3;
using MetaAssets;
using UGQApiServer.Models;
using UGQApiServer.Settings;
using UGQApiServer.Storage;
using UGQDatabase.Models;
using UGQDataAccess.Settings;
namespace UGQApiServer.UGQData;
public class UGQBannerImageList
{
readonly AmazonS3Client _s3Client;
readonly S3Settings _settings;
public UGQBannerImageList(IOptions<S3Settings> settings)
{
_settings = settings.Value;
_s3Client = new AmazonS3Client(_settings.AccessKey, _settings.SecretAccessKey, Amazon.RegionEndpoint.USEast1);
}
public async Task<List<string>> getFiles()
{
var res = await _s3Client.ListObjectsAsync(_settings.BucketName, "preset");
return res.S3Objects.Where(x => x.Key.EndsWith(".png"))
.Select(x => x.Key).ToList();
}
}

View File

@@ -0,0 +1,35 @@
using ServerCommon;
namespace UGQApiServer.UGQData;
public static class UGQDataHelper
{
public static string? getText(LangEnum langEnum, string textId)
{
MetaData.Instance._textTable.TryGetValue(textId, out var textData);
if (textData == null)
return null;
string text;
switch (langEnum)
{
case LangEnum.Ko:
text = textData.SourceString;
break;
case LangEnum.En:
text = textData.en;
break;
case LangEnum.Jp:
text = textData.ja;
break;
default:
text = textData.SourceString;
break;
}
return text;
}
}

View File

@@ -0,0 +1,484 @@
using System.Runtime.CompilerServices;
using System.Xml.Linq;
using Amazon.DynamoDBv2.Model;
using ServerCommon;
using MetaAssets;
using UGQApiServer.Validation;
using UGQDataAccess.Service;
using UGQDatabase.Models;
namespace UGQApiServer.UGQData;
public enum PresetKind
{
None = 0,
Title = 1,
Banner = 2,
}
public enum PresetCategory
{
None = 0,
Comedy = 1,
Drama = 2,
Horror = 3,
Mystery = 4,
Romance = 5,
Thriller = 6,
All = 7,
}
#pragma warning disable CS8618
public class UGQActionValue
{
public int? ValueId { get; set; }
public string? UgcValueGuid { get; set; }
public string ValueName { get; set; } = string.Empty;
public string TextId { get; set; } = string.Empty;
}
public class UGQTaskAction
{
public int ActionId { get; set; }
public string ActionName { get; set; } = string.Empty;
public string TextId { get; set; } = string.Empty;
public bool Disabled { get; set; }
public List<UGQActionValue> TaskActions { get; set; }
}
public class UGQDialogCondition
{
public int? ConditionId { get; set; }
public string ConditionName { get; set; } = string.Empty;
public string TextId { get; set; } = string.Empty;
public List<UGQActionValue> DialogConditionValues { get; set; }
}
public class UGQDialogType
{
public int TypeId { get; set; }
public string TypeName { get; set; } = string.Empty;
public string TextId { get; set; } = string.Empty;
public List<UGQDialogCondition> DialogConditions { get; set; }
}
#pragma warning restore CS8618
public class UGQMetaData
{
public const int TALK_ACTION_ID = 1;
public Dictionary<int, UGQTaskAction> TackActions = new(); // key: TackActionType
public Dictionary<int, UGQDialogType> DialogActions = new(); // key: DialogType
public IReadOnlyDictionary<int, MetaAssets.UGQPresetImageMetaData> UGQPresetImageMetaDataListbyId =>
MetaData.Instance.UGQPresetImageMetaDataListbyId;
public Dictionary<(PresetKind, PresetCategory), List<MetaAssets.UGQPresetImageMetaData>> PresetImages = new();
InGameService _inGameService;
public UGQMetaData(InGameService inGameService)
{
_inGameService = inGameService;
}
List<UGQActionValue> getInputValues(MetaAssets.EUGQValueSource source)
{
List<UGQActionValue> values = new();
switch(source)
{
case MetaAssets.EUGQValueSource.UGQ_NPC:
values = MetaData.Instance._npcTable
.Where(x => x.Value.UGQ == true)
.Select(x => new UGQActionValue
{
ValueId = x.Value.npc_id,
TextId = x.Value.name,
}).ToList();
break;
case MetaAssets.EUGQValueSource.CLOTH_ITEM:
values = MetaData.Instance._ItemTable
.Where(x => x.Value.UGQAction == MetaAssets.EUGQAction.CLOTH)
.Select(x => new UGQActionValue
{
ValueId = x.Value.ItemId,
TextId = x.Value.Name,
}).ToList();
break;
case MetaAssets.EUGQValueSource.UGQ_ITEM:
values = MetaData.Instance._ItemTable
.Where(x => x.Value.UGQAction != MetaAssets.EUGQAction.NOTUSE)
.Select(x => new UGQActionValue
{
ValueId = x.Value.ItemId,
TextId = x.Value.Name,
}).ToList();
break;
case MetaAssets.EUGQValueSource.UGQ_ATTRIBUTE_DEFINITION:
values = MetaData.Instance._AttributeDefinitionMetaTable
.Where(x => x.Value.UGQ == true)
.Select(x => new UGQActionValue
{
ValueId = x.Value.ID,
TextId = x.Value.Name,
}).ToList();
break;
case MetaAssets.EUGQValueSource.RANGE_1_30:
for (int i = 1; i <= 30; i++)
{
values.Add(new UGQActionValue
{
ValueId = i,
ValueName = i.ToString(),
});
}
break;
case MetaAssets.EUGQValueSource.UGQ_SOCIAL_ACTION:
values = MetaData.Instance._SocialActionTable
.Where(x => x.Value.UGQ == true)
.Select(x => new UGQActionValue
{
ValueId = x.Value.SocialActionId,
TextId = x.Value.Content,
}).ToList();
break;
}
return values;
}
void initTaskActions()
{
foreach(var taskInput in MetaData.Instance.UGQInputTaskMetaDataList)
{
var action = new UGQTaskAction
{
ActionId = taskInput.Id,
ActionName = taskInput.Name,
TextId = taskInput.Name,
Disabled = !taskInput.Enabled,
TaskActions = getInputValues(taskInput.ValueSource),
};
TackActions.Add(taskInput.Id, action);
}
}
void initDialogTypes()
{
foreach (var dialogInput in MetaData.Instance.UGQInputDialogMetaDataList)
{
var conditionValues = getInputValues(dialogInput.ConditionValueSource);
List<UGQDialogCondition> conditions = new();
switch (dialogInput.ConditionSelection)
{
case MetaAssets.EConditionSelection.USE_STATIC:
MetaData.Instance.UGQInputDialogConditionMetaDataListbyName.TryGetValue(dialogInput.ConditionName, out var dialogCondition);
if(dialogCondition == null)
{
conditions.Add(new UGQDialogCondition
{
ConditionId = 0,
ConditionName = "UGQInput_Dialog_None",
TextId = "UGQInput_Dialog_None",
DialogConditionValues = conditionValues
});
}
else
{
conditions.Add(new UGQDialogCondition
{
ConditionId = dialogCondition.Id,
ConditionName = dialogCondition.Text,
TextId = dialogCondition.Text,
DialogConditionValues = conditionValues
});
}
break;
case MetaAssets.EConditionSelection.USE_SOURCE:
var source = getInputValues(dialogInput.ConditionSource);
conditions = source.Select(x => new UGQDialogCondition
{
ConditionId = x.ValueId,
ConditionName = x.ValueName,
TextId = x.TextId,
DialogConditionValues = conditionValues
}).ToList();
break;
}
var dialogType = new UGQDialogType
{
TypeId = dialogInput.TypeId,
TypeName = dialogInput.TypeName,
TextId = dialogInput.TypeName,
DialogConditions = conditions,
};
DialogActions.Add(dialogInput.TypeId, dialogType);
}
}
public TextEntity getNpcName(int npcId)
{
TextEntity getNpcNameEntity(string textId)
{
MetaData.Instance._textTable.TryGetValue(textId, out var textData);
if (textData == null)
return new TextEntity();
return new TextEntity
{
Kr = textData.SourceString,
En = textData.en,
Jp = textData.ja,
};
}
MetaData.Instance._npcTable.TryGetValue(npcId, out var data);
if(data != null)
{
return getNpcNameEntity(data.name);
}
return new TextEntity
{
Kr = "",
En = "",
Jp = "",
};
}
public void init()
{
initTaskActions();
initDialogTypes();
foreach (var preset in MetaData.Instance.UGQPresetImageMetaDataListbyId.Values)
{
var pathSplit = preset.FileName.Split('/');
if (Enum.TryParse(pathSplit[1], true, out PresetCategory category) == false)
continue;
PresetKind kind = PresetKind.None;
if (preset.FileName.EndsWith("normal.png") == true)
kind = PresetKind.Banner;
else if (preset.FileName.EndsWith("hot.png") == true)
kind = PresetKind.Title;
if (kind == PresetKind.None)
continue;
if (PresetImages.TryGetValue((kind, category), out var list) == false)
{
list = new();
PresetImages.Add((kind, category), list);
}
list.Add(preset);
if (PresetImages.TryGetValue((kind, PresetCategory.All), out var allList) == false)
{
allList = new();
PresetImages.Add((kind, PresetCategory.All), allList);
}
allList.Add(preset);
}
}
public List<MetaAssets.NpcMetaData> getAssignableNpcs()
{
return MetaData.Instance._npcTable
.Where(x => x.Value.UGQ == true)
.Select(x => x.Value).ToList();
}
public string getBeaconName(int beaconId, LangEnum langEnum)
{
MetaData.Instance._npcTable.TryGetValue(beaconId, out var npcData);
if (npcData == null)
return string.Empty;
return UGQDataHelper.getText(langEnum, npcData.name) ?? string.Empty;
}
public string getTaskActionName(int actionId, LangEnum langEnum)
{
TackActions.TryGetValue(actionId, out var taskAction);
if (taskAction == null)
return string.Empty;
return UGQDataHelper.getText(langEnum, taskAction.TextId) ?? string.Empty;
}
public string getTaskActionValueName(int actionId, int actionValueId, LangEnum langEnum)
{
TackActions.TryGetValue(actionId, out var taskAction);
if (taskAction == null)
return "";
var actionValue = taskAction.TaskActions.Where(x => x.ValueId == actionValueId).FirstOrDefault();
if (actionValue == null)
return "";
return UGQDataHelper.getText(langEnum, actionValue.TextId) ?? string.Empty;
}
public string getDialogTypeName(int typeId, LangEnum langEnum)
{
DialogActions.TryGetValue(typeId, out var dialogType);
if (dialogType == null)
return "";
return UGQDataHelper.getText(langEnum, dialogType.TextId) ?? string.Empty;
}
public string getDialogConditionName(int typeId, int conditionId, LangEnum langEnum)
{
DialogActions.TryGetValue(typeId, out var dialogType);
if (dialogType == null)
return "";
var dialogCondition = dialogType.DialogConditions.Where(x => x.ConditionId == conditionId).FirstOrDefault();
if (dialogCondition == null)
return "";
return UGQDataHelper.getText(langEnum, dialogCondition.TextId) ?? string.Empty;
}
public string getDialogConditionValueName(int typeId, int conditionId, int conditionValue, LangEnum langEnum)
{
DialogActions.TryGetValue(typeId, out var dialogType);
if (dialogType == null)
return "";
var dialogCondition = dialogType.DialogConditions.Where(x => x.ConditionId == conditionId).FirstOrDefault();
if (dialogCondition == null)
return "";
var dialogConditionValue = dialogCondition.DialogConditionValues.Where(x => x.ValueId == conditionValue).FirstOrDefault();
if (dialogConditionValue == null)
return "";
return UGQDataHelper.getText(langEnum, dialogConditionValue.TextId) ?? string.Empty;
}
public ValidationErrorKind isValidNpcId(int npcId)
{
if (MetaData.Instance._npcTable.TryGetValue(npcId, out var result) == false)
return ValidationErrorKind.NotFoundBeaconId;
return ValidationErrorKind.Success;
}
public ValidationErrorKind isValidDialogTypeId(int dialogTypeId)
{
DialogActions.TryGetValue((int)dialogTypeId, out var dialogType);
if (dialogType == null)
return ValidationErrorKind.InvalidDialogType;
return ValidationErrorKind.Success;
}
public ValidationErrorKind isValidDialogConditionId(int dialogTypeId, int conditionId)
{
// DialogActions<6E><73><EFBFBD><EFBFBD> dialogType <20>˻<EFBFBD>
DialogActions.TryGetValue((int)dialogTypeId, out var dialogType);
if (dialogType == null)
return ValidationErrorKind.InvalidDialogType;
var dialogCondition = dialogType.DialogConditions.Where(x => x.ConditionId == conditionId).FirstOrDefault();
if (dialogCondition == null)
return ValidationErrorKind.InvalidDialogConditionId;
return ValidationErrorKind.Success;
}
public ValidationErrorKind isValidDialogActionValue(int dialogTypeId, int conditionId, int conditionValue)
{
// DialogActions<6E><73><EFBFBD><EFBFBD> dialogType, dialogActionType <20>˻<EFBFBD>
// dialogActionId<49><64> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> üũ
DialogActions.TryGetValue((int)dialogTypeId, out var dialogType);
if (dialogType == null)
return ValidationErrorKind.InvalidDialogType;
var dialogCondition = dialogType.DialogConditions.Where(x => x.ConditionId == conditionId).FirstOrDefault();
if (dialogCondition == null)
return ValidationErrorKind.InvalidDialogConditionId;
// DialogConditionValues<65><73> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><> üũ
if (dialogCondition.DialogConditionValues.Count > 0)
{
var dialogConditionValue = dialogCondition.DialogConditionValues.Where(x => x.ValueId == conditionValue).FirstOrDefault();
if (dialogConditionValue == null)
return ValidationErrorKind.InvalidDialogConditionValue;
}
return ValidationErrorKind.Success;
}
public ValidationErrorKind isValidNpcActionId(int actionId)
{
// TackActionType
MetaData.Instance.UGQBeaconActionDataListbyId.TryGetValue(actionId, out var npcAction);
if (npcAction == null)
return ValidationErrorKind.InvalidNpcActionId;
return ValidationErrorKind.Success;
}
public ValidationErrorKind isValidTackActionId(int actionId)
{
// TackActionType
TackActions.TryGetValue(actionId, out var tackAction);
if (tackAction == null)
return ValidationErrorKind.InvalidTaskActionId;
if (tackAction.Disabled == true)
return ValidationErrorKind.InvalidTaskActionValue;
return ValidationErrorKind.Success;
}
public ValidationErrorKind isValidTackActionValue(int actionId, int actionValue)
{
// TackActions<6E><73><EFBFBD><EFBFBD> TackActionType <20>˻<EFBFBD>
TackActions.TryGetValue(actionId, out var tackAction);
if (tackAction == null)
return ValidationErrorKind.InvalidTaskActionId;
// actionValue<75><65> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> üũ
var taskActionValue = tackAction.TaskActions.Where(x => x.ValueId == actionValue).FirstOrDefault();
if (taskActionValue == null)
return ValidationErrorKind.InvalidTaskActionValue;
return ValidationErrorKind.Success;
}
public MetaAssets.UGQPresetImageMetaData? getPresetImage(int id)
{
UGQPresetImageMetaDataListbyId.TryGetValue(id, out var presetImage);
return presetImage;
}
}

View File

@@ -0,0 +1,478 @@
using System;
using System.Globalization;
using Microsoft.IdentityModel.Tokens;
using Pipelines.Sockets.Unofficial.Arenas;
using ServerCommon;
using UGQDataAccess;
using UGQDatabase.Models;
using UGQApiServer.Converter;
using UGQApiServer.Models;
using UGQApiServer.UGQData;
namespace UGQApiServer.Validation;
public enum ValidationErrorKind
{
Success,
RequireBeaconId, // <20><><EFBFBD><EFBFBD> <20>Է<EFBFBD> <20>ʿ<EFBFBD>
NotFoundBeaconId, // <20>Էµ<D4B7> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20>߸<EFBFBD><DFB8><EFBFBD> <20><><EFBFBD>̵<EFBFBD>
FirstTaskIsNotTalkAction, // ù<><C3B9><EFBFBD><EFBFBD> <20>½<EFBFBD>ũ <20><>ȭ <20>׼<EFBFBD><D7BC><EFBFBD> <20>ƴ<EFBFBD>
FirstTaskIsNotSameBeacon, // ù<><C3B9><EFBFBD><EFBFBD> <20>½<EFBFBD>ũ<EFBFBD><C5A9> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20>ٸ<EFBFBD>
InvalidCostAmount, // <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD>
InvalidDialogType, // <20>Է<EFBFBD> <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD>
InvalidDialogConditionId, // <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD>
InvalidDialogConditionValue, // <20><><EFBFBD><EFBFBD> <20><> <20><><EFBFBD><EFBFBD>
InvalidDialogNextSequence, // <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><> <20><><EFBFBD><EFBFBD>
InvalidSequenceCount, // <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> 3~30<33><30> <20><><EFBFBD><EFBFBD>
DontHaveEndSequence, // <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><20><><EFBFBD><EFBFBD>
NotSetPlayerTalker, // Player Talker<65><72> <20><><EFBFBD>õǾ<C3B5> <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD>
InvalidTalkerOrder, // Npc <20>ڿ<EFBFBD> Player<65><72> <20><> <20><> <20><><EFBFBD><EFBFBD>
InvalidTaskActionId, // Task <20>׼<EFBFBD> Ÿ<><C5B8> <20><><EFBFBD><EFBFBD>
InvalidTaskActionValue, // Task <20>׼<EFBFBD> <20><> <20><><EFBFBD><EFBFBD>
InvalidTaskCount, // Task <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> 3~20<32><30> <20><><EFBFBD><EFBFBD>
DontHaveTitleImage, // Ÿ<><C5B8>Ʋ <20>̹<EFBFBD><CCB9><EFBFBD> <20><><EFBFBD><EFBFBD>
DontHaveBannerImage, // <20><><EFBFBD><EFBFBD> <20>̹<EFBFBD><CCB9><EFBFBD> <20><><EFBFBD><EFBFBD>
InvalidNpcActionId, // Npc <20>׼<EFBFBD> Ÿ<><C5B8> <20><><EFBFBD><EFBFBD>
}
public class ValidationError
{
public ValidationErrorKind Kind { get; set; }
public override string ToString()
{
return "";
}
}
public class QuestDialogValidatorError : ValidationError
{
public int TaskIndex { get; set; } = -1;
public int SequenceId { get; set; } = -1; // <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20>߻<EFBFBD><DFBB><EFBFBD> SequenceId.
public int SequenceActionIndex { get; set; } = -1; // <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20>߻<EFBFBD><DFBB><EFBFBD> SequenceAction <20><>ġ. 0 <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD>. -1<><31> <20>׼<EFBFBD> <20><><EFBFBD><EFBFBD> <20>ƴ<EFBFBD>.
public override string ToString()
{
// Kind<6E><64> <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD>޽<EFBFBD><DEBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD>ؼ<EFBFBD> <20><><EFBFBD><EFBFBD>
LangEnum langEnum = Culture.ToLangEnum(CultureInfo.CurrentCulture.Name);
string position = "";
if (SequenceActionIndex != -1)
{
position = $"[TaskIndex: {TaskIndex}, SequenceId: {SequenceId}, SequenceActionIndex: {SequenceActionIndex}] ";
}
else if (SequenceId != -1)
{
position = $"[TaskIndex: {TaskIndex}, SequenceId: {SequenceId}] ";
}
else
{
position = $"[TaskIndex: {TaskIndex}] ";
}
var textId = $"UGQ_Validator_Error_{Kind.ToString()}";
var message = UGQDataHelper.getText(langEnum, textId);
return $"{position}{message}";
}
}
public class QuestContentValidatorError : ValidationError
{
public int TaskIndex { get; set; } = -1; // <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20>߻<EFBFBD><DFBB><EFBFBD> Task <20><>ġ. 0 <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD>
public override string ToString()
{
// Kind<6E><64> <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD>޽<EFBFBD><DEBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD>ؼ<EFBFBD> <20><><EFBFBD><EFBFBD>
LangEnum langEnum = Culture.ToLangEnum(CultureInfo.CurrentCulture.Name);
string position = "";
if (TaskIndex != -1)
{
position = $"[TaskIndex: {TaskIndex}] ";
}
else
{
position = "";
}
var textId = $"UGQ_Validator_Error_{Kind.ToString()}";
var message = UGQDataHelper.getText(langEnum, textId);
return $"{position}{message ?? "Unkonwn"}";
}
}
public class UGQValidator
{
UGQMetaData _ugqMetadata;
int MIN_SEQUENCE = 3;
int MAX_SEQUENCE = 30;
int MIN_TASK = 3;
int MAX_TASK = 20;
public UGQValidator(UGQMetaData ugqMetadata)
{
_ugqMetadata = ugqMetadata;
}
public List<ValidationError> Validate(QuestContentEntity questContent, List<QuestDialogEntity> questDialogs)
{
List<ValidationError> errors = new();
ValidateQuestContent(questContent, errors);
// <20><><EFBFBD>̾<EFBFBD><CCBE>α<EFBFBD> üũ
foreach (var (index, task) in questContent.Tasks.Select((task, index) => (index, task)))
{
var dialogIndex = questDialogs.FindIndex(x => x.Id == task.DialogId);
if (task.ActionId == UGQMetaData.TALK_ACTION_ID)
{
if (dialogIndex == -1)
{
// Talk <20>׼<EFBFBD><D7BC><EFBFBD> <20><><EFBFBD><EFBFBD> Dialog<6F><67> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD>
QuestContentValidatorError error = new QuestContentValidatorError
{
Kind = ValidationErrorKind.InvalidSequenceCount,
TaskIndex = index,
};
errors.Add(error);
continue;
}
var questDialog = questDialogs[dialogIndex];
// sequence <20><><EFBFBD><EFBFBD> üũ
if (questDialog.Sequences.Count < MIN_SEQUENCE || questDialog.Sequences.Count >= MAX_SEQUENCE)
{
QuestContentValidatorError error = new QuestContentValidatorError
{
Kind = ValidationErrorKind.InvalidSequenceCount,
TaskIndex = index,
};
errors.Add(error);
}
ValidateQuestDialog(index, questDialog, errors);
}
}
return errors;
}
int getCost(UgqGradeType gradeType)
{
switch (gradeType)
{
case UgqGradeType.Amature:
return MetaHelper.GameConfigMeta.UgqUsageFeeAmateur;
case UgqGradeType.RisingStar:
return MetaHelper.GameConfigMeta.UgqUsageFeeRisingStar;
case UgqGradeType.Master1:
return MetaHelper.GameConfigMeta.UgqUsageFeeMaster1;
case UgqGradeType.Master2:
return MetaHelper.GameConfigMeta.UgqUsageFeeMaster2;
case UgqGradeType.Master3:
return MetaHelper.GameConfigMeta.UgqUsageFeeMaster3;
}
return 0;
}
void ValidateQuestContent(QuestContentEntity questContent, List<ValidationError> errors)
{
if(questContent.BeaconId == 0 && string.IsNullOrEmpty(questContent.UgcBeaconGuid) == true)
{
QuestContentValidatorError error = new QuestContentValidatorError
{
Kind = ValidationErrorKind.RequireBeaconId,
};
errors.Add(error);
}
if(questContent.BeaconId != 0)
{
var errorKind = _ugqMetadata.isValidNpcId(questContent.BeaconId);
if (errorKind != ValidationErrorKind.Success)
{
QuestContentValidatorError error = new QuestContentValidatorError
{
Kind = errorKind,
};
errors.Add(error);
}
}
if (questContent.Tasks.Count < MIN_TASK || questContent.Tasks.Count >= MAX_TASK)
{
QuestContentValidatorError error = new QuestContentValidatorError
{
Kind = ValidationErrorKind.InvalidTaskCount,
};
errors.Add(error);
}
if (string.IsNullOrEmpty(questContent.TitleImagePath) == true)
{
QuestContentValidatorError error = new QuestContentValidatorError
{
Kind = ValidationErrorKind.DontHaveTitleImage,
};
errors.Add(error);
}
if (string.IsNullOrEmpty(questContent.BannerImagePath) == true)
{
QuestContentValidatorError error = new QuestContentValidatorError
{
Kind = ValidationErrorKind.DontHaveBannerImage,
};
errors.Add(error);
}
//if (questContent.Cost != getCost(questContent.GradeType))
//{
// QuestContentValidatorError error = new QuestContentValidatorError
// {
// Kind = ValidationErrorKind.InvalidCostAmount,
// };
// errors.Add(error);
//}
if (questContent.Tasks[0].ActionId != 1)
{
QuestContentValidatorError error = new QuestContentValidatorError
{
Kind = ValidationErrorKind.FirstTaskIsNotTalkAction,
TaskIndex = 0,
};
errors.Add(error);
}
bool validTalkBeacon = false;
if (questContent.BeaconId != 0 && questContent.Tasks[0].ActionValue == questContent.BeaconId)
validTalkBeacon = true;
if (string.IsNullOrEmpty(questContent.UgcBeaconGuid) == false && questContent.Tasks[0].UgcActionValueGuid == questContent.UgcBeaconGuid)
validTalkBeacon = true;
if(validTalkBeacon == false)
{
QuestContentValidatorError error = new QuestContentValidatorError
{
Kind = ValidationErrorKind.FirstTaskIsNotSameBeacon,
TaskIndex = 0,
};
errors.Add(error);
}
foreach (var (index, task) in questContent.Tasks.Select((task, index) => (index, task)))
{
if (string.IsNullOrEmpty(task.UgcActionValueGuid) == false)
{
var errorKind = _ugqMetadata.isValidTackActionId(task.ActionId);
if (errorKind != ValidationErrorKind.Success)
{
QuestContentValidatorError error = new QuestContentValidatorError
{
Kind = errorKind,
TaskIndex = index,
};
errors.Add(error);
}
}
else
{
var errorKind = _ugqMetadata.isValidTackActionValue(task.ActionId, task.ActionValue);
if (errorKind != ValidationErrorKind.Success)
{
QuestContentValidatorError error = new QuestContentValidatorError
{
Kind = errorKind,
TaskIndex = index,
};
errors.Add(error);
}
}
}
}
void ValidateQuestDialogSequenceAction(int taskIndex, List<int> sequenceIds, int sequenceId, int actionIdex, DialogSequenceActionEntity sequenceAction, List<ValidationError> errors)
{
if (sequenceAction.Talker == DialogTalker.Player)
{
// Talker<65><72> <20>÷<EFBFBD><C3B7>̾<EFBFBD> <20><> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> üũ
var errorKind = _ugqMetadata.isValidDialogActionValue(sequenceAction.Type, sequenceAction.Condition, sequenceAction.ConditionValue);
if (errorKind != ValidationErrorKind.Success)
{
QuestDialogValidatorError error = new QuestDialogValidatorError
{
TaskIndex = taskIndex,
Kind = errorKind,
SequenceId = sequenceId,
SequenceActionIndex = actionIdex,
};
errors.Add(error);
}
}
else if(sequenceAction.Talker == DialogTalker.Npc && sequenceAction.NpcAction != 0)
{
var errorKind = _ugqMetadata.isValidNpcActionId(sequenceAction.NpcAction);
if (errorKind != ValidationErrorKind.Success)
{
QuestDialogValidatorError error = new QuestDialogValidatorError
{
TaskIndex = taskIndex,
Kind = errorKind,
SequenceId = sequenceId,
SequenceActionIndex = actionIdex,
};
errors.Add(error);
}
}
if (sequenceId != -1)
{
if (sequenceIds.Any(x => x == sequenceId) == false)
{
QuestDialogValidatorError error = new QuestDialogValidatorError
{
TaskIndex = taskIndex,
Kind = ValidationErrorKind.InvalidDialogNextSequence,
SequenceId = sequenceId,
SequenceActionIndex = actionIdex,
};
errors.Add(error);
}
}
}
void ValidateQuestDialogSequence(int taskIndex, List<int> sequenceIds, DialogSequenceEntity sequence, List<ValidationError> errors)
{
foreach (var (index, action) in sequence.Actions.Select((action, index) => (index, action)))
{
ValidateQuestDialogSequenceAction(taskIndex, sequenceIds, sequence.SequenceId, index, action, errors);
}
int playerTalkerCount = 0;
int npcTalkerCount = 0;
bool invalidTalkerOrder = false;
foreach (var (index, action) in sequence.Actions.Select((action, index) => (index, action)))
{
if (action.Talker == DialogTalker.Player)
{
playerTalkerCount++;
}
else if (action.Talker == DialogTalker.Npc)
{
// Npc<70>ε<EFBFBD>, playerTalkerCount<6E><74> 0<><30><EFBFBD><EFBFBD> ũ<>ٸ<EFBFBD>, Player<65>ڿ<EFBFBD> Npc<70><63> <20><><EFBFBD>Դٴ<D4B4> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD> ó<><C3B3>
if (playerTalkerCount > 0)
invalidTalkerOrder = true;
npcTalkerCount++;
}
}
if (playerTalkerCount < 1)
{
QuestDialogValidatorError error = new QuestDialogValidatorError
{
TaskIndex = taskIndex,
Kind = ValidationErrorKind.NotSetPlayerTalker,
SequenceId = sequence.SequenceId,
};
}
if (invalidTalkerOrder == true)
{
QuestDialogValidatorError error = new QuestDialogValidatorError
{
TaskIndex = taskIndex,
Kind = ValidationErrorKind.InvalidTalkerOrder,
SequenceId = sequence.SequenceId,
};
}
}
void ValidateQuestDialog(int taskIndex, QuestDialogEntity questDialog, List<ValidationError> errors)
{
var sequenceIds = questDialog.Sequences.Select(x => x.SequenceId).ToList();
List<int> npcActionIds = new();
var dialogActions = questDialog.Sequences.Select(x => x.Actions).ToList();
foreach(var item in dialogActions)
npcActionIds.AddRange(item.Select(x => x.NpcAction).ToList());
bool hasEnd = false;
foreach (var sequence in questDialog.Sequences)
{
ValidateQuestDialogSequence(taskIndex, sequenceIds, sequence, errors);
foreach (var action in sequence.Actions)
{
if(action.NextSequence == -1)
hasEnd = true;
}
}
if (hasEnd == false)
{
QuestDialogValidatorError error = new QuestDialogValidatorError
{
TaskIndex = taskIndex,
Kind = ValidationErrorKind.DontHaveEndSequence,
};
errors.Add(error);
}
}
}

View File

@@ -0,0 +1,38 @@
{
"UGQDatabase": {
"ConnectionString": "mongodb://ip-172-50-167-39.us-west-2.compute.internal:27017",
"DatabaseName": "UGQ",
"MinConnectionPoolSize": 100,
"MaxConnectionPoolSize": 500,
"WaitQueueTimeoutSecs": 120
},
"SSOAccount": {
"WebPortalTokenSecret": "zgoRtipbFcgQp0VGP8VZW8QhW4ll1swfvASqwr78",
"SsoAccountDb": "Server=dev-caliverse-db.cluster-custom-czac0we0qoyx.us-west-2.rds.amazonaws.com;Port=3306;User ID=external_ro;Password=bQNEXbRWQTtV6bwlqktGyBiuf2KqYF;Database=caliverse"
},
"JWT": {
"ValidAudience": "",
"ValidIssuer": "",
"Secret": "5bb3238d7f19456329597a9cc57da0f19d57c8c8412c5978cf938396b82060253c706217719e748908cb87a228302c29240183caa63a1c3db20b94e638520740",
"TokenValidityInMinutes": 60
},
"S3": {
"AccessKey": "AKIA4G3CB4Z5Y3NL2ANF",
"SecretAccessKey": "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",
"BucketName": "metaverse-ugq-image",
"RootFolder": "Dev",
"DownloadUrl": "https://d11pmt9vsv778p.cloudfront.net"
},
"DynamoDb": {
"Url": "http://121.135.164.17:8000",
"TablePostfix": "Dev",
"AccessKey": "AKIA4G3CB4Z5T6JUPHJN",
"SecretKey": "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",
"Region": "us-west-2"
},
"Redis": "121.135.164.17:6379,password=KT-i5#i%-%LxKfZ5YJj6,AsyncTimeout=30000,SyncTimeout=30000,ssl=false,abortConnect=false",
"NamedPipe": {
"enable": false
},
"EnableAllowWhenSingleLogin": true
}

View File

@@ -0,0 +1,25 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"UGQDatabase": {
"ConnectionString": "mongodb://root:root@127.0.0.1:27017",
// "ConnectionString": "mongodb://127.0.0.1:27017",
"DatabaseName": "UGQ",
"MinConnectionPoolSize": 100,
"MaxConnectionPoolSize": 500,
"WaitQueueTimeoutSecs": 120
},
"JWT": {
"ValidAudience": "",
"ValidIssuer": "",
"Secret": "5bb3238d7f19456329597a9cc57da0f19d57c8c8412c5978cf938396b82060253c706217719e748908cb87a228302c29240183caa63a1c3db20b94e638520740",
"TokenValidityInMinutes": 3000
},
"NamedPipe": {
"enable": false
}
}

View File

@@ -0,0 +1,45 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"UGQDatabase": {
"ConnectionString": "mongodb://root:root@127.0.0.1:27018",
"DatabaseName": "UGQ",
"MinConnectionPoolSize": 100,
"MaxConnectionPoolSize": 500,
"WaitQueueTimeoutSecs": 120
},
"SSOAccount": {
"WebPortalTokenSecret": "Gudx7xjCbCKxZsLEWr7HL5auSkScVPTUaYnZEztN",
"SsoAccountDb": "Server=localhost;Port=13306;User ID=external_ro;Password=bQNEXbRWQTtV6bwlqktGyBiuf2KqYF;Database=caliverse"
},
"JWT": {
"ValidAudience": "",
"ValidIssuer": "",
"Secret": "5bb3238d7f19456329597a9cc57da0f19d57c8c8412c5978cf938396b82060253c706217719e748908cb87a228302c29240183caa63a1c3db20b94e638520740",
"TokenValidityInMinutes": 3000
},
"S3": {
"AccessKey": "AKIA4G3CB4Z5Y3NL2ANF",
"SecretAccessKey": "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",
"BucketName": "metaverse-ugq-image",
"RootFolder": "Dev",
"DownloadUrl": "https://d11pmt9vsv778p.cloudfront.net"
},
"DynamoDb": {
"Url": "http://localhost:8000",
"TablePostfix": "Dev",
"AccessKey": "AKIA4G3CB4Z5T6JUPHJN",
"SecretKey": "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",
"Region": "us-west-2"
},
"Redis": "127.0.0.1:6379,password=KT-i5#i%-%LxKfZ5YJj6,AsyncTimeout=30000,SyncTimeout=30000,ssl=false,abortConnect=false",
"NamedPipe": {
"enable": true
},
"EnableAllowWhenSingleLogin": true
}