초기커밋

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,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);
}
}
}