From b0606a44bb643ffb3b236eccfb661282372a08c9 Mon Sep 17 00:00:00 2001 From: Seonkyu_Kim Date: Thu, 3 Apr 2025 17:53:54 +0900 Subject: [PATCH 1/6] =?UTF-8?q?[=E2=99=BB=EF=B8=8F]=20=EC=A0=84=EC=B2=B4?= =?UTF-8?q?=EC=A0=81=EC=9D=B8=20=EA=B5=AC=EC=A1=B0=20=EB=A6=AC=ED=8C=A9?= =?UTF-8?q?=ED=86=A0=EB=A7=81=20=EC=A7=84=ED=96=89=EC=A4=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Back.csproj | 4 + Program.cs | 36 +- Program/Common/Auth/APIHeaderMiddleware.cs | 110 ++-- Program/Common/Auth/HeaderConfigRepository.cs | 27 + .../Common/Auth/Interface/IHeaderConfig.cs | 7 + Program/Common/Auth/JwtTokenService.cs | 186 ++++--- Program/Common/Chat/ChatHub.cs | 61 ++- Program/Common/Data/AppDbContext.cs | 80 +-- .../Common/Middleware/ExceptionMiddleware.cs | 50 ++ Program/Common/Model/APISetting.cs | 21 +- Program/Common/Model/AcaException.cs | 74 +++ Program/Common/Model/JwtSettings.cs | 58 ++- Program/Common/Model/Status.cs | 113 ++-- Program/Controllers/V1/AppController.cs | 270 ++++++++++ Program/Controllers/V1/ErrorController.cs | 15 + Program/Controllers/V1/MemberController.cs | 47 ++ Program/Controllers/V1/PushController.cs | 475 +++++++++++++++++ Program/Controllers/V1/UserController.cs | 178 +++++++ Program/Models/Entities/APIResult.cs | 16 + Program/Models/Entities/Academy.cs | 32 ++ Program/Models/Entities/AuthKey.cs | 10 + Program/Models/Entities/Chatting.cs | 147 ++++++ Program/Models/Entities/Log.cs | 44 ++ Program/Models/Entities/PushPayload.cs | 177 +++++++ Program/Models/Entities/User.cs | 149 ++++++ Program/Models/Entities/Version.cs | 21 + .../V1/Interfaces/ILogRepository.cs | 8 + .../V1/Interfaces/IUserRepository.cs | 14 + Program/Repositories/V1/LogRepository.cs | 24 + Program/Repositories/V1/UserRepository.cs | 49 ++ Program/Services/V1/InMemoryPushQueue.cs | 60 +++ .../Services/V1/Interfaces/IUserService.cs | 16 + .../Services => Services/V1}/PushService.cs | 74 ++- Program/Services/V1/RepositoryService.cs | 231 +++++++++ Program/Services/V1/UserService.cs | 238 +++++++++ Program/V1/Controllers/AppController.cs | 271 ---------- Program/V1/Controllers/ErrorController.cs | 15 - Program/V1/Controllers/MemberController.cs | 51 -- Program/V1/Controllers/PushController.cs | 481 ------------------ Program/V1/Controllers/UserController.cs | 443 ---------------- Program/V1/Models/APIResult.cs | 15 - Program/V1/Models/AcaException.cs | 76 --- Program/V1/Models/Academy.cs | 31 -- Program/V1/Models/AuthKey.cs | 11 - Program/V1/Models/Chatting.cs | 147 ------ Program/V1/Models/Log.cs | 44 -- Program/V1/Models/PushPayload.cs | 178 ------- Program/V1/Models/User.cs | 149 ------ Program/V1/Models/Version.cs | 21 - Program/V1/Repositories/UserRepository.cs | 28 - Program/V1/Services/InMemoryPushQueue.cs | 63 --- Program/V1/Services/RepositoryService.cs | 238 --------- Program/V1/Services/UserService.cs | 7 - SwaggerConfigure.cs | 150 +++--- 54 files changed, 2820 insertions(+), 2721 deletions(-) create mode 100644 Program/Common/Auth/HeaderConfigRepository.cs create mode 100644 Program/Common/Auth/Interface/IHeaderConfig.cs create mode 100644 Program/Common/Middleware/ExceptionMiddleware.cs create mode 100644 Program/Common/Model/AcaException.cs create mode 100644 Program/Controllers/V1/AppController.cs create mode 100644 Program/Controllers/V1/ErrorController.cs create mode 100644 Program/Controllers/V1/MemberController.cs create mode 100644 Program/Controllers/V1/PushController.cs create mode 100644 Program/Controllers/V1/UserController.cs create mode 100644 Program/Models/Entities/APIResult.cs create mode 100644 Program/Models/Entities/Academy.cs create mode 100644 Program/Models/Entities/AuthKey.cs create mode 100644 Program/Models/Entities/Chatting.cs create mode 100644 Program/Models/Entities/Log.cs create mode 100644 Program/Models/Entities/PushPayload.cs create mode 100644 Program/Models/Entities/User.cs create mode 100644 Program/Models/Entities/Version.cs create mode 100644 Program/Repositories/V1/Interfaces/ILogRepository.cs create mode 100644 Program/Repositories/V1/Interfaces/IUserRepository.cs create mode 100644 Program/Repositories/V1/LogRepository.cs create mode 100644 Program/Repositories/V1/UserRepository.cs create mode 100644 Program/Services/V1/InMemoryPushQueue.cs create mode 100644 Program/Services/V1/Interfaces/IUserService.cs rename Program/{V1/Services => Services/V1}/PushService.cs (57%) create mode 100644 Program/Services/V1/RepositoryService.cs create mode 100644 Program/Services/V1/UserService.cs delete mode 100644 Program/V1/Controllers/AppController.cs delete mode 100644 Program/V1/Controllers/ErrorController.cs delete mode 100644 Program/V1/Controllers/MemberController.cs delete mode 100644 Program/V1/Controllers/PushController.cs delete mode 100644 Program/V1/Controllers/UserController.cs delete mode 100644 Program/V1/Models/APIResult.cs delete mode 100644 Program/V1/Models/AcaException.cs delete mode 100644 Program/V1/Models/Academy.cs delete mode 100644 Program/V1/Models/AuthKey.cs delete mode 100644 Program/V1/Models/Chatting.cs delete mode 100644 Program/V1/Models/Log.cs delete mode 100644 Program/V1/Models/PushPayload.cs delete mode 100644 Program/V1/Models/User.cs delete mode 100644 Program/V1/Models/Version.cs delete mode 100644 Program/V1/Repositories/UserRepository.cs delete mode 100644 Program/V1/Services/InMemoryPushQueue.cs delete mode 100644 Program/V1/Services/RepositoryService.cs delete mode 100644 Program/V1/Services/UserService.cs diff --git a/Back.csproj b/Back.csproj index f591412..b289ad8 100644 --- a/Back.csproj +++ b/Back.csproj @@ -18,5 +18,9 @@ + + + + diff --git a/Program.cs b/Program.cs index 468caf6..0d76037 100644 --- a/Program.cs +++ b/Program.cs @@ -1,6 +1,5 @@ using Pomelo.EntityFrameworkCore; using System.Text; -using AcaMate.Common.Chat; using Microsoft.EntityFrameworkCore; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.IdentityModel.Tokens; @@ -10,13 +9,18 @@ using Microsoft.Extensions.DependencyInjection; using System.Net.Http; using System.Security.Cryptography.X509Certificates; using System.Text.Json; - -using AcaMate.Common.Models; -using AcaMate.V1.Services; -using AcaMate.Common.Data; -using AcaMate.Common.Token; -using AcaMate.V1.Controllers; -using AcaMate.V1.Models; +using Back; +using Back.Program.Common.Auth; +using Back.Program.Common.Auth.Interface; +using Back.Program.Common.Chat; +using Back.Program.Common.Data; +using Back.Program.Common.Middleware; +using Back.Program.Common.Model; +using Back.Program.Models.Entities; +using Back.Program.Repositories.V1; +using Back.Program.Repositories.V1.Interfaces; +using Back.Program.Services.V1; +using Back.Program.Services.V1.Interfaces; var builder = WebApplication.CreateBuilder(args); @@ -24,6 +28,8 @@ var builder = WebApplication.CreateBuilder(args); // DB 설정부 시작 builder.Configuration.AddJsonFile("private/dbSetting.json", optional: true, reloadOnChange: true); + +builder.Services.AddHttpContextAccessor(); // var connectionString = builder.Configuration.GetConnectionString("MariaDbConnection"); // builder.Services.AddDbContext(options => options.UseMySql(connectionString, ServerVersion.AutoDetect(connectionString))); builder.Services.AddDbContext(optionsAction: (serviceProvider, options) => @@ -38,7 +44,7 @@ builder.Services.AddDbContext(optionsAction: (serviceProvider, opt options.UseMySql(baseConnectionString, ServerVersion.AutoDetect(baseConnectionString)); }); -builder.Services.AddHttpContextAccessor(); + // DB 설정부 끝 @@ -113,9 +119,12 @@ builder.Services.AddHostedService(); builder.Services.AddControllers(); // 여기다가 API 있는 컨트롤러들 AddScoped 하면 되는건가? -builder.Services.AddScoped(); -builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); // builder.Services.AddScoped(); // // builder.Services.AddScoped(); @@ -151,10 +160,6 @@ else builder.Logging.SetMinimumLevel(LogLevel.Warning); } -//헤더 부분 -builder.Services.AddScoped(); - - // 로컬 테스트 위한 부분 (올릴때는 꺼두기) // builder.WebHost.UseUrls("http://0.0.0.0:5144"); @@ -181,6 +186,7 @@ app.UseHttpsRedirection(); // 헤더 미들웨어 부분 app.UseMiddleware((object)new string[] { "iOS_AM_Connect_Key", "And_AM_Connect_Key", "Web_AM_Connect_Key" }); +// app.UseMiddleware(); // app.UseMiddleware("X-MyHeader"); diff --git a/Program/Common/Auth/APIHeaderMiddleware.cs b/Program/Common/Auth/APIHeaderMiddleware.cs index 1983e08..6b268a8 100644 --- a/Program/Common/Auth/APIHeaderMiddleware.cs +++ b/Program/Common/Auth/APIHeaderMiddleware.cs @@ -1,86 +1,60 @@ -using System.Threading.Tasks; -using AcaMate.Common.Data; -using Microsoft.AspNetCore.Http; +using Back.Program.Common.Auth.Interface; +using Back.Program.Common.Data; using Microsoft.EntityFrameworkCore; -namespace AcaMate.Common.Token; - -public interface IHeaderConfig +namespace Back.Program.Common.Auth { - Task GetExpectedHeaderValueAsync(string headerName); -} - -/// -/// DB에서 헤더 키값 찾아서 그 밸류 값 빼오기 위해서 사용 -/// -public class HeaderConfigRepository : IHeaderConfig -{ - private readonly AppDbContext _dbContext; - - public HeaderConfigRepository(AppDbContext dbContext) + /// + public class APIHeaderMiddleware { - _dbContext = dbContext; - } - - public async Task GetExpectedHeaderValueAsync(string headerValue) - { - var config = await _dbContext.APIHeader - .FirstOrDefaultAsync(h => h.h_value == headerValue); - return config?.h_key ?? string.Empty; - } -} + private readonly RequestDelegate _next; + private readonly string[] _headerNames; + // private readonly IHeaderConfig _headerConfig; -public class APIHeaderMiddleware -{ - - private readonly RequestDelegate _next; - private readonly string[] _headerNames; - // private readonly IHeaderConfig _headerConfig; - - public APIHeaderMiddleware(RequestDelegate next, string[] headerNames)//, IHeaderConfig headerConfig) - { - _next = next; - _headerNames = headerNames; - } - - public async Task Invoke(HttpContext context) - { - - if (context.Request.Path.Equals("/api/v1/in/app", StringComparison.OrdinalIgnoreCase)) + public APIHeaderMiddleware(RequestDelegate next, string[] headerNames)//, IHeaderConfig headerConfig) { - await _next(context); - return; + _next = next; + _headerNames = headerNames; } - - // Scoped 사용해서 값 가져오는 곳임 - var headerConfig = context.RequestServices.GetRequiredService(); - - bool valid = false; - foreach (var header in _headerNames) + public async Task Invoke(HttpContext context) { - /// context.Request.Headers.TryGetValue(header, out var headerValue) - /// header 를 찾는데 header - if (context.Request.Headers.TryGetValue(header, out var headerValue) && - !string.IsNullOrWhiteSpace(headerValue)) + if (context.Request.Path.Equals("/api/v1/in/app", StringComparison.OrdinalIgnoreCase)) { - var keyName = await headerConfig.GetExpectedHeaderValueAsync(headerValue); - if (keyName != string.Empty) + await _next(context); + return; + } + + // Scoped 사용해서 값 가져오는 곳임 + var headerConfig = context.RequestServices.GetRequiredService(); + + bool valid = false; + + foreach (var header in _headerNames) + { + /// context.Request.Headers.TryGetValue(header, out var headerValue) + /// header 를 찾는데 header + if (context.Request.Headers.TryGetValue(header, out var headerValue) && + !string.IsNullOrWhiteSpace(headerValue)) { - valid = true; - break; + var keyName = await headerConfig.GetExpectedHeaderValueAsync(headerValue); + if (keyName != string.Empty) + { + valid = true; + break; + } } } - } - if (!valid) - { - context.Response.StatusCode = StatusCodes.Status401Unauthorized; - await context.Response.WriteAsync($"Invalid header value"); - return; - } + if (!valid) + { + context.Response.StatusCode = StatusCodes.Status401Unauthorized; + await context.Response.WriteAsync($"Invalid header value"); + return; + } - await _next(context); + await _next(context); + } } } \ No newline at end of file diff --git a/Program/Common/Auth/HeaderConfigRepository.cs b/Program/Common/Auth/HeaderConfigRepository.cs new file mode 100644 index 0000000..91a045b --- /dev/null +++ b/Program/Common/Auth/HeaderConfigRepository.cs @@ -0,0 +1,27 @@ +using Back.Program.Common.Auth.Interface; +using Back.Program.Common.Data; +using Microsoft.EntityFrameworkCore; + +namespace Back.Program.Common.Auth +{ + /// + /// DB에서 헤더 키값 찾아서 그 밸류 값 빼오기 위해서 사용 + /// + public class HeaderConfigRepository : IHeaderConfig + { + private readonly AppDbContext _dbContext; + + public HeaderConfigRepository(AppDbContext dbContext) + { + _dbContext = dbContext; + } + + public async Task GetExpectedHeaderValueAsync(string headerValue) + { + var config = await _dbContext.APIHeader + .FirstOrDefaultAsync(h => h.h_value == headerValue); + return config?.h_key ?? string.Empty; + } + + } +} diff --git a/Program/Common/Auth/Interface/IHeaderConfig.cs b/Program/Common/Auth/Interface/IHeaderConfig.cs new file mode 100644 index 0000000..04e5587 --- /dev/null +++ b/Program/Common/Auth/Interface/IHeaderConfig.cs @@ -0,0 +1,7 @@ +namespace Back.Program.Common.Auth.Interface +{ + public interface IHeaderConfig + { + Task GetExpectedHeaderValueAsync(string headerName); + } +} diff --git a/Program/Common/Auth/JwtTokenService.cs b/Program/Common/Auth/JwtTokenService.cs index 909fb9e..caa0e27 100644 --- a/Program/Common/Auth/JwtTokenService.cs +++ b/Program/Common/Auth/JwtTokenService.cs @@ -1,109 +1,107 @@ -using System; using System.IdentityModel.Tokens.Jwt; using System.Security.Claims; -using System.Text; -using System.Collections.Generic; -using AcaMate.Common.Models; -using Microsoft.IdentityModel.Tokens; -using Microsoft.Extensions.Options; -using Microsoft.AspNetCore.Authentication; -using Microsoft.AspNetCore.Mvc; - - using System.Security.Cryptography; +using System.Text; +using Back.Program.Common.Model; +using Microsoft.Extensions.Options; +using Microsoft.IdentityModel.Tokens; -namespace AcaMate.Common.Token; - -public class JwtTokenService +namespace Back.Program.Common.Auth { - private readonly JwtSettings _jwtSettings; - private readonly ILogger _logger; - - public JwtTokenService(IOptions jwtSettings, ILogger logger) - { - _jwtSettings = jwtSettings.Value; - _logger = logger; - } - - public string GenerateJwtToken(string jwtKey)//, string role) - { - // 1. 클레임(Claim) 설정 - 필요에 따라 추가 정보도 포함 - var claims = new List - { - // 토큰 주체(sub) 생성을 위해 값으로 uid를 사용함 : 토큰이 대표하는 고유 식별자 - new Claim(JwtRegisteredClaimNames.Sub, jwtKey), - // Jti 는 토큰 식별자로 토큰의 고유 ID 이다. - new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()), - // jwt 토큰이 가지는 권한 - // new Claim(ClaimTypes.Role, role), - // 추가 클레임 예: new Claim(ClaimTypes.Role, "Admin") - }; - - // 2. 비밀 키와 SigningCredentials 생성 - var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_jwtSettings.SecretKey)); - var credentials = new SigningCredentials(key, SecurityAlgorithms.HmacSha256); - - // 3. 토큰 생성 (Issuer, Audience, 만료 시간, 클레임, 서명 정보 포함) - var token = new JwtSecurityToken( - issuer: _jwtSettings.Issuer, - audience: _jwtSettings.Audience, - claims: claims, - expires: DateTime.Now.AddMinutes(_jwtSettings.ExpiryMinutes), - signingCredentials: credentials - ); - - // 4. 토큰 객체를 문자열로 변환하여 반환 - return new JwtSecurityTokenHandler().WriteToken(token); - } - - public RefreshToken GenerateRefreshToken(string uid) - { - var randomNumber = new byte[32]; // 256비트 - using (var rng = RandomNumberGenerator.Create()) - { - rng.GetBytes(randomNumber); - } - - return new RefreshToken() - { - uid = uid, - refresh_token = Convert.ToBase64String(randomNumber), - create_Date = DateTime.UtcNow, - expire_date = DateTime.UtcNow.AddDays(_jwtSettings.RefreshTokenExpiryDays) - - }; - } - - /// - /// 여기는 엑세스 토큰의 확인을 위한 jwt 서비스 내의 인증 메서드 + /// 사용자의 정보를 바탕으로 JWT 생성 /// - public async Task ValidateToken(string token) + public class JwtTokenService { - if (string.IsNullOrWhiteSpace(token)) return null; - var tokenHandler = new JwtSecurityTokenHandler(); + private readonly JwtSettings _jwtSettings; + private readonly ILogger _logger; - try + public JwtTokenService(IOptions jwtSettings, ILogger logger) { - var key = Encoding.UTF8.GetBytes(_jwtSettings.SecretKey); - var validationParameters = new TokenValidationParameters - { - ValidateIssuerSigningKey = true, - IssuerSigningKey = new SymmetricSecurityKey(key), - ValidateIssuer = true, - ValidIssuer = _jwtSettings.Issuer, - ValidateAudience = true, - ValidAudience = _jwtSettings.Audience, - ValidateLifetime = true, - ClockSkew = TimeSpan.FromMinutes(_jwtSettings.ClockSkewMinutes) - }; - var principal = tokenHandler.ValidateToken(token, validationParameters, out var securityToken); - return principal; + _jwtSettings = jwtSettings.Value; + _logger = logger; } - catch (Exception ex) + + public string GenerateJwtToken(string jwtKey)//, string role) { - _logger.LogError($"엑세스 토큰 오류: {ex.Message}"); - return null; + // 1. 클레임(Claim) 설정 - 필요에 따라 추가 정보도 포함 + var claims = new List + { + // 토큰 주체(sub) 생성을 위해 값으로 uid를 사용함 : 토큰이 대표하는 고유 식별자 + new Claim(JwtRegisteredClaimNames.Sub, jwtKey), + // Jti 는 토큰 식별자로 토큰의 고유 ID 이다. + new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()), + // jwt 토큰이 가지는 권한 + // new Claim(ClaimTypes.Role, role), + // 추가 클레임 예: new Claim(ClaimTypes.Role, "Admin") + }; + + // 2. 비밀 키와 SigningCredentials 생성 + var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_jwtSettings.SecretKey)); + var credentials = new SigningCredentials(key, SecurityAlgorithms.HmacSha256); + + // 3. 토큰 생성 (Issuer, Audience, 만료 시간, 클레임, 서명 정보 포함) + var token = new JwtSecurityToken( + issuer: _jwtSettings.Issuer, + audience: _jwtSettings.Audience, + claims: claims, + expires: DateTime.Now.AddMinutes(_jwtSettings.ExpiryMinutes), + signingCredentials: credentials + ); + + // 4. 토큰 객체를 문자열로 변환하여 반환 + return new JwtSecurityTokenHandler().WriteToken(token); + } + + public RefreshToken GenerateRefreshToken(string uid) + { + var randomNumber = new byte[32]; // 256비트 + using (var rng = RandomNumberGenerator.Create()) + { + rng.GetBytes(randomNumber); + } + + return new RefreshToken() + { + uid = uid, + refresh_token = Convert.ToBase64String(randomNumber), + create_Date = DateTime.UtcNow, + expire_date = DateTime.UtcNow.AddDays(_jwtSettings.RefreshTokenExpiryDays) + + }; + } + + + /// + /// 여기는 엑세스 토큰의 확인을 위한 jwt 서비스 내의 인증 메서드 + /// + public async Task ValidateToken(string token) + { + if (string.IsNullOrWhiteSpace(token)) return null; + var tokenHandler = new JwtSecurityTokenHandler(); + + try + { + var key = Encoding.UTF8.GetBytes(_jwtSettings.SecretKey); + var validationParameters = new TokenValidationParameters + { + ValidateIssuerSigningKey = true, + IssuerSigningKey = new SymmetricSecurityKey(key), + ValidateIssuer = true, + ValidIssuer = _jwtSettings.Issuer, + ValidateAudience = true, + ValidAudience = _jwtSettings.Audience, + ValidateLifetime = true, + ClockSkew = TimeSpan.FromMinutes(_jwtSettings.ClockSkewMinutes) + }; + var principal = tokenHandler.ValidateToken(token, validationParameters, out var securityToken); + return principal; + } + catch (Exception ex) + { + _logger.LogError($"엑세스 토큰 오류: {ex.Message}"); + return null; + } } } } \ No newline at end of file diff --git a/Program/Common/Chat/ChatHub.cs b/Program/Common/Chat/ChatHub.cs index 9841c96..6e8a9c0 100644 --- a/Program/Common/Chat/ChatHub.cs +++ b/Program/Common/Chat/ChatHub.cs @@ -1,37 +1,42 @@ using Microsoft.AspNetCore.SignalR; -using System.Threading.Tasks; -namespace AcaMate.Common.Chat; - -public class ChatHub : Hub +namespace Back.Program.Common.Chat { - // 클라이언트에서 메시지를 보내면 모든 사용자에게 전송 - public async Task SendMessage(string user, string message) + public class ChatHub : Hub { - Console.WriteLine($"Message received: {user}: {message}"); - await Clients.All.SendAsync("ReceiveMessage", user, message); + // 클라이언트에서 메시지를 보내면 모든 사용자에게 전송 + public async Task SendMessage(string user, string message) + { + Console.WriteLine($"Message received: {user}: {message}"); + await Clients.All.SendAsync("ReceiveMessage", user, message); - } + } - // 특정 사용자에게 메시지를 보냄 - public async Task SendMessageToUser(string connectionId, string message) - { - await Clients.Client(connectionId).SendAsync("ReceiveMessage", message); - } + // 특정 사용자에게 메시지를 보냄 + public async Task SendMessageToUser(string connectionId, string message) + { + await Clients.Client(connectionId).SendAsync("ReceiveMessage", message); + } - // 클라이언트가 연결될 때 호출 - public override async Task OnConnectedAsync() - { - await Clients.Caller.SendAsync("ReceiveMessage", "System", $"Welcome! Your ID: {Context.ConnectionId}"); - Console.WriteLine("OnConnectedAsync"); - await base.OnConnectedAsync(); - } + // 클라이언트가 연결될 때 호출 + public override async Task OnConnectedAsync() + { + await Clients.Caller.SendAsync("ReceiveMessage", "System", $"Welcome! Your ID: {Context.ConnectionId}"); + Console.WriteLine("OnConnectedAsync"); + await base.OnConnectedAsync(); + } - // 클라이언트가 연결 해제될 때 호출 - public override async Task OnDisconnectedAsync(Exception? exception) - { - await Clients.All.SendAsync("ReceiveMessage", "System", $"{Context.ConnectionId} disconnected"); - Console.WriteLine("OnDisconnectedAsync"); - await base.OnDisconnectedAsync(exception); + // 클라이언트가 연결 해제될 때 호출 + public override async Task OnDisconnectedAsync(Exception? exception) + { + await Clients.All.SendAsync("ReceiveMessage", "System", $"{Context.ConnectionId} disconnected"); + Console.WriteLine("OnDisconnectedAsync"); + await base.OnDisconnectedAsync(exception); + } } -} \ No newline at end of file +} + +/* + + +*/ \ No newline at end of file diff --git a/Program/Common/Data/AppDbContext.cs b/Program/Common/Data/AppDbContext.cs index 17b4e16..92b653f 100644 --- a/Program/Common/Data/AppDbContext.cs +++ b/Program/Common/Data/AppDbContext.cs @@ -1,56 +1,60 @@ -using AcaMate.Common.Models; +using Back.Program.Common.Model; +using Back.Program.Models.Entities; using Microsoft.EntityFrameworkCore; -using AcaMate.V1.Models; -using Version = AcaMate.V1.Models.Version; +using Version = Back.Program.Models.Entities.Version; -namespace AcaMate.Common.Data; -//database=AcaMate; -public class AppDbContext: DbContext +namespace Back.Program.Common.Data { - public AppDbContext(DbContextOptions options) : base(options) + //database=AcaMate; + public class AppDbContext: DbContext { - } + public AppDbContext(DbContextOptions options) : base(options) + { + } - //MARK: API - public DbSet APIHeader { get; set; } + //MARK: API + public DbSet APIHeader { get; set; } - //MARK: Program - public DbSet Version { get; set; } - public DbSet Academy { get; set; } - public DbSet RefreshTokens { get; set; } + //MARK: Program + public DbSet Version { get; set; } + public DbSet Academy { get; set; } + public DbSet RefreshToken { get; set; } - //MARK: USER - public DbSet Login { get; set; } - public DbSet UserAcademy { get; set; } - public DbSet User { get; set; } - public DbSet Permission { get; set; } - // public DbSet Token { get; set; } - public DbSet Location { get; set; } - public DbSet Contact { get; set; } + //MARK: USER + public DbSet Login { get; set; } + public DbSet UserAcademy { get; set; } + public DbSet User { get; set; } + public DbSet Permission { get; set; } + // public DbSet Token { get; set; } + public DbSet Location { get; set; } + public DbSet Contact { get; set; } - //MARK: PUSH - public DbSet DBPayload { get; set; } - public DbSet PushCabinet { get; set; } + //MARK: PUSH + public DbSet DBPayload { get; set; } + public DbSet PushCabinet { get; set; } + //MARK: CHATTING + // public DbSet<> - //MARK: LOG - public DbSet LogPush { get; set; } - public DbSet LogUser { get; set; } - public DbSet LogProject { get; set; } + //MARK: LOG + public DbSet LogPush { get; set; } + public DbSet LogUser { get; set; } + public DbSet LogProject { get; set; } - protected override void OnModelCreating(ModelBuilder modelBuilder) - { - modelBuilder.Entity() - .HasKey(ua => new { ua.uid, ua.bid }); + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.Entity() + .HasKey(ua => new { ua.uid, ua.bid }); - // modelBuilder.Entity() - // .HasKey(c => new { c.uid, c.bid, c.pid }); + // modelBuilder.Entity() + // .HasKey(c => new { c.uid, c.bid, c.pid }); - modelBuilder.Entity() - .HasKey(p => new { p.bid, p.pid }); + modelBuilder.Entity() + .HasKey(p => new { p.bid, p.pid }); - // modelBuilder.Entity().HasNoKey(); + // modelBuilder.Entity().HasNoKey(); + } } } \ No newline at end of file diff --git a/Program/Common/Middleware/ExceptionMiddleware.cs b/Program/Common/Middleware/ExceptionMiddleware.cs new file mode 100644 index 0000000..f373d31 --- /dev/null +++ b/Program/Common/Middleware/ExceptionMiddleware.cs @@ -0,0 +1,50 @@ +using System.Text.Json; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Logging; +using System.Threading.Tasks; +using Back.Program.Common.Model; + +namespace Back.Program.Common.Middleware +{ + public class ExceptionMiddleware + { + private readonly RequestDelegate _next; + private readonly ILogger _logger; + + public ExceptionMiddleware(RequestDelegate next, ILogger logger) + { + _next = next; + _logger = logger; + } + + // public async Task Invoke(HttpContext context) + // { + // try + // { + // await _next(context); // 다음 미들웨어 호출 + // } + // catch (AcaException ex) + // { + // _logger.LogWarning(ex, "AcaException 발생"); + // context.Response.StatusCode = 400; + // + // var response = APIResponse.Send(ex.Code, ex.Message, ""); + // var json = JsonSerializer.Serialize(response); + // + // context.Response.ContentType = "application/json; charset=utf-8"; + // await context.Response.WriteAsync(json); + // } + // catch (Exception ex) + // { + // _logger.LogError(ex, "Unhandled Exception"); + // context.Response.StatusCode = 500; + // + // var response = APIResponse.InternalSeverError("서버 내부 오류가 발생했습니다."); + // var json = JsonSerializer.Serialize(response); + // + // context.Response.ContentType = "application/json; charset=utf-8"; + // await context.Response.WriteAsync(json); + // } + // } + } +} \ No newline at end of file diff --git a/Program/Common/Model/APISetting.cs b/Program/Common/Model/APISetting.cs index 2836748..d1d4ac3 100644 --- a/Program/Common/Model/APISetting.cs +++ b/Program/Common/Model/APISetting.cs @@ -1,17 +1,18 @@ -using System.ComponentModel.DataAnnotations.Schema; using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; -namespace AcaMate.Common.Models; - -[Table("api_header")] -public class APIHeader +namespace Back.Program.Common.Model { - [Key] - public string specific_id { get; set; } + [Table("api_header")] + public class APIHeader + { + [Key] + public string specific_id { get; set; } - public DateTime connect_date { get; set; } - public string h_key { get; set; } - public string h_value { get; set; } + public DateTime connect_date { get; set; } + public string h_key { get; set; } + public string h_value { get; set; } + } } /* diff --git a/Program/Common/Model/AcaException.cs b/Program/Common/Model/AcaException.cs new file mode 100644 index 0000000..918de6e --- /dev/null +++ b/Program/Common/Model/AcaException.cs @@ -0,0 +1,74 @@ +namespace Back.Program.Common.Model +{ + /// + /// 입력 받은 토큰들(Access & Refresh) 자체에 문제가 있는 경우 + /// + public class TokenException: Exception + { + public TokenException(string message) : base(message) + { + } + } + + /// + /// 리프레시 토큰이 만료가 나있는 경우 + /// + public class RefreshRevokeException: Exception + { + public RefreshRevokeException(string message) : base(message) + { + } + } + + /// + /// 참조해야 하는 파일에서 오류가 발생하는 경우 + /// + public class FileNotValidException : Exception + { + public FileNotValidException(string message) : base(message) + { + } + } + + /// + /// 파일 내부에 값을 읽을 때 오류가 발생하는 경우 + /// + public class FileContentNotFoundException : Exception + { + public FileContentNotFoundException(string message) : base(message) + { + } + } + + /// + /// 외부 서비스에 연결시 연결 실패시 + /// + public class ServiceConnectionFailedException : Exception + { + public ServiceConnectionFailedException(string message) : base(message) + { + + } + } + + /// + /// PUSH 서비스 중 데이터 사용에 문제가 발생했을시 + /// + public class PushInvalidException : Exception + { + public PushInvalidException(string message) : base(message) + { + + } + } + /// + /// 값이 있어야 하는데 NULL인 경우 + /// + public class OutNULLException : Exception + { + public OutNULLException(string message) : base(message) + { + + } + } +} \ No newline at end of file diff --git a/Program/Common/Model/JwtSettings.cs b/Program/Common/Model/JwtSettings.cs index 5836c3e..ae86424 100644 --- a/Program/Common/Model/JwtSettings.cs +++ b/Program/Common/Model/JwtSettings.cs @@ -1,39 +1,41 @@ -using System.ComponentModel.DataAnnotations.Schema; using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; -namespace AcaMate.Common.Models; - -public class JwtSettings +namespace Back.Program.Common.Model { - public string SecretKey { get; set; } - public string Issuer { get; set; } - public string Audience { get; set; } - public int ExpiryMinutes { get; set; } - public int ClockSkewMinutes { get; set; } - public int RefreshTokenExpiryDays { get; set; } -} + public class JwtSettings + { + public string SecretKey { get; set; } + public string Issuer { get; set; } + public string Audience { get; set; } + public int ExpiryMinutes { get; set; } + public int ClockSkewMinutes { get; set; } + public int RefreshTokenExpiryDays { get; set; } + } -[Table("refresh_token")] -public class RefreshToken -{ - [Key] - [Required(ErrorMessage = "필수 항목 누락")] - public string uid { get; set; } - public string refresh_token { get; set; } - public DateTime create_Date { get; set; } - public DateTime expire_date { get; set; } + [Table("refresh_token")] + public class RefreshToken + { + [Key] + [Required(ErrorMessage = "필수 항목 누락")] + public string uid { get; set; } + public string refresh_token { get; set; } + public DateTime create_Date { get; set; } + public DateTime expire_date { get; set; } - // 이건 로그아웃시에 폐기 시킬예정이니 그떄 변경하는걸로 합시다. - public DateTime? revoke_Date { get; set; } + // 이건 로그아웃시에 폐기 시킬예정이니 그떄 변경하는걸로 합시다. + public DateTime? revoke_Date { get; set; } + } + + public class ValidateToken + { + public string token { get; set; } + public string refresh { get; set; } + public string uid { get; set; } + } } -public class ValidateToken -{ - public string token { get; set; } - public string refresh { get; set; } - public string uid { get; set; } -} /* """ 토큰 동작 관련 diff --git a/Program/Common/Model/Status.cs b/Program/Common/Model/Status.cs index 75ab156..bcfc234 100644 --- a/Program/Common/Model/Status.cs +++ b/Program/Common/Model/Status.cs @@ -1,73 +1,74 @@ using System.Text.Json; -namespace AcaMate.Common.Models; - -public class APIResponseStatus +namespace Back.Program.Common.Model { - public Status status { get; set; } - public T? data { get; set; } + public class APIResponseStatus + { + public Status status { get; set; } + public T? data { get; set; } - public string JsonToString() - { - return JsonSerializer.Serialize(this); - } -} - -public class Status -{ - public string code { get; set; } - public string message { get; set; } - -} - -public static class APIResponse -{ - public static APIResponseStatus Send(string code, string message, T data) - { - return new APIResponseStatus + public string JsonToString() { - status = new Status() + return JsonSerializer.Serialize(this); + } + } + + public class Status + { + public string code { get; set; } + public string message { get; set; } + + } + + public static class APIResponse + { + public static APIResponseStatus Send(string code, string message, T data) + { + return new APIResponseStatus { - code = code, - message = message - }, - data = data - }; - } + status = new Status() + { + code = code, + message = message + }, + data = data + }; + } - /// - /// 반환값 없는 API 정상 동작시 - /// - public static APIResponseStatus Success (){ - return Send("000", "정상", ""); - } + /// + /// 반환값 없는 API 정상 동작시 + /// + public static APIResponseStatus Success (){ + return Send("000", "정상", string.Empty); + } - public static APIResponseStatus InvalidInputError(string? msg = null) - { - return Send("100", msg ?? "입력 값이 유효하지 않습니다.", ""); - } + public static APIResponseStatus InvalidInputError(string? msg = null) + { + return Send("100", msg ?? "입력 값이 유효하지 않습니다.", string.Empty); + } - public static APIResponseStatus AccessExpireError(string? msg = null) - { - return Send("101", msg ?? "엑세스 토큰이 유효하지 않습니다.", ""); - } + public static APIResponseStatus AccessExpireError(string? msg = null) + { + return Send("101", msg ?? "엑세스 토큰이 유효하지 않습니다.", string.Empty); + } - // -- -- -- OUTPUT ERROR -- -- -- // - public static APIResponseStatus NotFoundError(string? msg = null) - { - return Send("200", msg ?? "알맞은 값을 찾을 수 없습니다.", ""); - } + // -- -- -- OUTPUT ERROR -- -- -- // + public static APIResponseStatus NotFoundError(string? msg = null) + { + return Send("200", msg ?? "알맞은 값을 찾을 수 없습니다.", string.Empty); + } - public static APIResponseStatus InternalSeverError(string? msg = null) - { - return Send("300", msg ?? "통신에 오류가 발생하였습니다.", ""); - } + public static APIResponseStatus InternalSeverError(string? msg = null) + { + return Send("300", msg ?? "통신에 오류가 발생하였습니다.", string.Empty); + } - public static APIResponseStatus UnknownError(string? msg = null) - { - return Send("999", msg ?? "알 수 없는 오류가 발생하였습니다.", ""); + public static APIResponseStatus UnknownError(string? msg = null) + { + return Send("999", msg ?? "알 수 없는 오류가 발생하였습니다.", string.Empty); + } } } \ No newline at end of file diff --git a/Program/Controllers/V1/AppController.cs b/Program/Controllers/V1/AppController.cs new file mode 100644 index 0000000..c415f68 --- /dev/null +++ b/Program/Controllers/V1/AppController.cs @@ -0,0 +1,270 @@ +using System.Security.Cryptography; +using System.Text; +using System.Text.Json; +using Back.Program.Common.Auth; +using Back.Program.Common.Data; +using Back.Program.Common.Model; +using Back.Program.Models.Entities; +using Back.Program.Services.V1; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using Version = Back.Program.Models.Entities.Version; + +namespace Back.Program.Controllers.V1 +{ + [ApiController] + [Route("/api/v1/in/app")] + [ApiExplorerSettings(GroupName = "공통")] + public class AppController : ControllerBase + { + private readonly AppDbContext _dbContext; + private readonly ILogger _logger; + private readonly IRepositoryService _repositoryService; + private readonly JwtTokenService _jwtTokenService; + + public AppController(AppDbContext dbContext, ILogger logger, IRepositoryService repositoryService, JwtTokenService jwtTokenService) + { + _dbContext = dbContext; + _logger = logger; + _repositoryService = repositoryService; + _jwtTokenService = jwtTokenService; + } + + + // 이 키값의 제한 시간은 24h이다 + [HttpGet] + [CustomOperation("헤더 정보 생성", "헤더에 접근하기 위한 키 값 받아오기", "시스템")] + public async Task GetHeaderValue(string type, string specific, string project) + { + if (string.IsNullOrEmpty(specific) || string.IsNullOrEmpty(type) || string.IsNullOrEmpty(project)) + return BadRequest(APIResponse.InvalidInputError()); + if (!ModelState.IsValid) return BadRequest(APIResponse.InvalidInputError()); + string summary = String.Empty; + + try + { + summary = _repositoryService.ReadSummary(typeof(AppController), "GetHeaderValue"); + bool valid = false; + + switch (type) + { + case "I": + if (project == "me.myds.ipstein.acamate.AcaMate") valid = true; + break; + case "A": + break; + case "W": + break; + default: + return BadRequest(APIResponse.InvalidInputError($"[{summary}], 타입 에러")); + break; + } + + if (valid) + { + + var apiHeader = await _dbContext.APIHeader.FirstOrDefaultAsync(h => h.specific_id == specific); + + string nowTime = DateTime.Now.ToString("o"); + string combineText = $"{project}_{nowTime}_{specific}"; + string headerValue = KeyGenerator(combineText); + + if (apiHeader != null) + { + if (DateTime.Now - apiHeader.connect_date > TimeSpan.FromHours(24)) + { + _logger.LogInformation($"[{summary}] : 해당 키 유효기간 경과"); + apiHeader.h_value = headerValue; + apiHeader.connect_date = DateTime.Now; + + if (await _repositoryService.SaveData(apiHeader)) + { + string msg = "정상 - 로그 저장 실패"; + var logProject = new LogProject + { + create_date = DateTime.Now , + log = $"[{summary}] : 해당 키 유효시간 만료로 인한 새 키 부여" + }; + if (await _repositoryService.SaveData(logProject)) + msg = "정상"; + return Ok(APIResponse.Send("001", msg, new { header = headerValue })); + } + else + { + // 저장이 안된거니 서버 오류 + return StatusCode(500, APIResponse.InternalSeverError()); + } + } + else + { + // 유효기간 만료 이상 없이 다 잘 됨 + return Ok(APIResponse.Send("000", "정상", new { header = apiHeader.h_value })); + } + } + else + { + _logger.LogInformation($"[{summary}] : 저장 된게 없음"); + + var newHeader = new APIHeader + { + h_key = type == "I" ? "iOS_AM_Connect_Key" + : (type == "A" ? "And_AM_Connect_Key" + : (type == "W" ? "Web_AM_Connect_Key": throw new Exception("ERROR"))), + h_value = headerValue, + connect_date = DateTime.Now, + specific_id = specific + }; + + if (await _repositoryService.SaveData(newHeader)) + { + string msg = "정상 - 로그 저장 실패"; + var logProject = new LogProject + { + create_date = DateTime.Now , + log = $"[{summary}] : 새로운 등록으로 인한 새 키 부여" + }; + // 이거 로그 저장 안되는거 확인! + _logger.LogInformation($"[{summary}] : {logProject.log}"); + if (await _repositoryService.SaveData(logProject)) + msg = "정상"; + + return Ok(APIResponse.Send("001", msg, new { header = headerValue })); + } + else + { + // 저장이 안된거니 서버 오류 + return StatusCode(500, APIResponse.InternalSeverError()); + } + } + } + return BadRequest(APIResponse.InvalidInputError()); + + + // return Ok(APIResponse.Send("000", "정상", Empty)); + } + catch (Exception ex) + { + _logger.LogError($"[{summary}] : {ex.Message}"); + return StatusCode(500, APIResponse.UnknownError(ex.Message)); + } + } + + // 스웨거는 퍼블릭으로 선언된걸 죄다 API로 인식하는 경향이 있음 + // 방법은 private 같이 접근 제한자를 변경하거나 [NonAction]을 붙여주면 됨 + [NonAction] + private string KeyGenerator(string combineText) + { + using (SHA256 sha256 = SHA256.Create()) + { + byte[] hashBytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(combineText)); + return BitConverter.ToString(hashBytes).Replace("-", string.Empty).ToLowerInvariant(); + } + } + + [HttpGet("version")] + [CustomOperation("앱 버전 확인","앱 버전을 확인해서 업데이트 여부 판단", "시스템")] + public async Task GetVersionData(string type) + { + if (string.IsNullOrEmpty(type)) + { + return BadRequest(APIResponse.InvalidInputError()); + } + + try + { + var version = await _dbContext.Version.FirstOrDefaultAsync(v => v.os_type == (type == "I" ? "VO01" : "VO02")); + + if (version == null) + { + return NotFound(APIResponse.NotFoundError()); + } + + var response = new APIResponseStatus + { + status = new Status() + { + code = "000", + message = "정상" + }, + data = new Version() + { + os_type = (version.os_type == "VO01" ? "I" : (version.os_type == "VO02" ? "A" : "W")), + final_ver = version.final_ver, + force_ver = version.force_ver, + dev_ver = version.dev_ver, + choice_update_yn = version.choice_update_yn + } + }; + + string jsonString = JsonSerializer.Serialize(response); + + // return Ok(jsonString); + return Ok(response.JsonToString()); + } + catch (Exception ex) + { + Console.WriteLine($"{ex.Message}\n{ex.StackTrace}"); + return StatusCode(500, APIResponse.UnknownError()); + } + } + + + [HttpGet("auth")] + [CustomOperation("서버 접근 권한 확인", "서버 기능을 사용하기 위한 접근에 대해 권한 확인", "시스템")] + public async Task AuthProgram([FromBody] AuthKey keys) + { + string summary = String.Empty; + + try + { + summary = _repositoryService.ReadSummary(typeof(AppController), "AuthProgram"); + } + catch (Exception ex) + { + _logger.LogError($"[{summary}] : {ex.Message}"); + return StatusCode(500, APIResponse.UnknownError(ex.Message)); + } + + return Ok(APIResponse.Send("000", "OK", Empty)); + } + + + [HttpGet("retryAccess")] + [CustomOperation("엑세스 토큰 재발급", "액세스 토큰 재발급 동작 수행", "시스템")] + public async Task RetryAccessToken(string refresh) + { + string summary = String.Empty; + + try + { + summary = _repositoryService.ReadSummary(typeof(AppController), "AuthProgram"); + var refreshToken = await _dbContext.RefreshToken + .FirstOrDefaultAsync(t => t.refresh_token == refresh); + if (refreshToken == null) throw new TokenException($"[{summary}] : 리프레시 토큰의 문제"); + if (refreshToken.revoke_Date < DateTime.Now) throw new TokenException($"[{summary}] : 리프레시 토큰 만료"); + if (refreshToken.expire_date < DateTime.Now) throw new TokenException($"[{summary}] : 리프레시 토큰 폐기"); + string access = _jwtTokenService.GenerateJwtToken(refreshToken.uid); + return Ok(APIResponse.Send("000", $"[{summary}], 토큰 생성 완료", + new { + access = access + })); + } + catch (TokenException ex) + { + _logger.LogError($"[{summary}] : {ex.Message}"); + return Ok(APIResponse.InvalidInputError(ex.Message)); + } + catch (Exception ex) + { + _logger.LogError($"[{summary}] : {ex.Message}"); + return StatusCode(500, APIResponse.UnknownError(ex.Message)); + } + + + } + + + + } +} + diff --git a/Program/Controllers/V1/ErrorController.cs b/Program/Controllers/V1/ErrorController.cs new file mode 100644 index 0000000..9fbe92e --- /dev/null +++ b/Program/Controllers/V1/ErrorController.cs @@ -0,0 +1,15 @@ +using Microsoft.AspNetCore.Mvc; + +namespace Back.Program.Controllers.V1 +{ + [ApiController] + [Route("/api/error")] + public class ErrorController: ControllerBase + { + [HttpGet] + public IActionResult HandleError() + { + return Problem("오류가 발생하였습니다. 잠시후 다시 시도해주세요."); + } + } +} \ No newline at end of file diff --git a/Program/Controllers/V1/MemberController.cs b/Program/Controllers/V1/MemberController.cs new file mode 100644 index 0000000..7277842 --- /dev/null +++ b/Program/Controllers/V1/MemberController.cs @@ -0,0 +1,47 @@ +using Back.Program.Common.Auth; +using Back.Program.Common.Data; +using Back.Program.Services.V1; +using Microsoft.AspNetCore.Mvc; + +namespace Back.Program.Controllers.V1 +{ + [ApiController] + [Route("/api/v1/in/member")] + [ApiExplorerSettings(GroupName = "사업자 정보")] + public class MemberController: ControllerBase + { + private readonly ILogger _logger; + private readonly AppDbContext _dbContext; + private readonly IRepositoryService _repositoryService; + private readonly JwtTokenService _jwtTokenService; + + public MemberController(AppDbContext dbContext, ILogger logger, IRepositoryService repositoryService, JwtTokenService jwtTokenService) + { + _dbContext = dbContext; + _logger = logger; + _repositoryService = repositoryService; + _jwtTokenService = jwtTokenService; + } + + [HttpGet("business")] + public IActionResult GetBusinessData() + { + // return Ok("GOOD"); + return Ok("DB 참조"); + } + + + + + + + // -- -- -- -- -- -- -- -- -- -- -- -- // + + [HttpGet("/api/v1/out/member/business")] + public IActionResult SearchBusinessNo() + { + return Ok("외부 참조"); + } + + } +} \ No newline at end of file diff --git a/Program/Controllers/V1/PushController.cs b/Program/Controllers/V1/PushController.cs new file mode 100644 index 0000000..0ee4783 --- /dev/null +++ b/Program/Controllers/V1/PushController.cs @@ -0,0 +1,475 @@ +using System.Security.Claims; +using Back.Program.Common.Auth; +using Back.Program.Common.Data; +using Back.Program.Common.Model; +using Back.Program.Models.Entities; +using Back.Program.Services.V1; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; + +namespace Back.Program.Controllers.V1 +{ + [ApiController] + [Route("/api/v1/in/push")] + [ApiExplorerSettings(GroupName = "공통")] + public class PushController : ControllerBase + { + private readonly ILogger _logger; + private readonly IPushQueue _pushQueue; + private readonly AppDbContext _dbContext; + private readonly IRepositoryService _repositoryService; + private readonly JwtTokenService _jwtTokenService; + public PushController(ILogger logger, IPushQueue pushQueue, AppDbContext dbContext, IRepositoryService repositoryService, JwtTokenService jwtTokenService) + { + _logger = logger; + _pushQueue = pushQueue; + _dbContext = dbContext; + _repositoryService = repositoryService; + _jwtTokenService = jwtTokenService; + } + + // 추가 사항 + // 카테고리 별 조회 하는 부분도 추가를 할 지 고민을 해야 할 것 같음 + + [HttpGet()] + [CustomOperation("푸시 확인", "저장된 양식을 확인 할 수 있다.", "푸시")] + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(APIResponseStatus))] + public async Task GetPush(string bid, string? pid, string? category) + { + string summary = String.Empty; + + try + { + summary = _repositoryService.ReadSummary(typeof(PushController), "GetPush"); + + if (!(await _dbContext.Academy.AnyAsync(a=>a.bid == bid))) + return Ok(APIResponse.Send("100", $"[{summary}], 존재하지 않는 BID", Empty)); + + List pushData = new List(); + var pushQuery = _dbContext.DBPayload.Where(p => p.bid == bid); + if (pid != null) + pushQuery = pushQuery.Where(p=>p.pid == pid); + if (category != null) + pushQuery = pushQuery.Where(p=>p.category == category); + pushData = await pushQuery.ToListAsync(); + + if (pushData.Count > 0) + { + return Ok(APIResponse.Send("000", $"[{summary}, 정상", pushData)); + } + + return Ok(APIResponse.Send("001", $"[{summary}], PUSH 데이터 없음", Empty)); + } + catch (Exception ex) + { + _logger.LogError($"[{summary}] : {ex.Message}"); + return StatusCode(500, APIResponse.UnknownError()); + } + } + + + [HttpPost("send")] + [CustomOperation("푸시 발송", "저장된 양식으로, 사용자에게 푸시를 송신한다.", "푸시")] + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(APIResponseStatus))] + public async Task SendPush([FromBody] PushRequest pushRequest) + { + string summary = String.Empty; + try { + summary = _repositoryService.ReadSummary(typeof(PushController), "SendPush"); + + if (!ModelState.IsValid) return BadRequest(APIResponse.InvalidInputError()); + + var payload = await _dbContext.DBPayload + .Where(p => p.pid == pushRequest.pid && p.bid == pushRequest.bid) + .Select(p => new Payload + { + aps = new Aps + { + alert = new Alert { title = p.title, body = p.body, subtitle = p.subtitle ?? "" }, + category = p.category + }, + pid = pushRequest.pid, + bid = pushRequest.bid, + content = pushRequest.content ?? (p.content ?? ""), + }) + .FirstOrDefaultAsync(); + + await Task.Run(async () => + { + if (payload == null) + throw new PushInvalidException("payload is NULL"); + + foreach (var uid in pushRequest.uids) + { + // 학원 내부에 해당 uid의 일원이 존재하는지 확인 + if ( + await _dbContext.UserAcademy + .Where(ua => ua.uid == uid && ua.bid == pushRequest.bid) + .AnyAsync() + ) + { + // 유저한테 온 모든 푸시에 대해서 안 읽은 것에 대한 뱃지 갯수 확인 + var badge = await _dbContext.PushCabinet + .Where(c => c.uid == uid + && c.check_yn == false) + .CountAsync(); + payload.aps.badge = badge + 1; + + // 푸시를 보내야 하니 푸시 토큰 확인 + var pushToken = await _dbContext.User + .Where(u => u.uid == uid) + .Select(u => u.push_token) + .FirstOrDefaultAsync() ?? ""; + + var pushCabinet = new PushCabinet + { + uid = uid, + bid = pushRequest.bid, + pid = pushRequest.pid, + send_date = DateTime.Now, + content = payload.content != "" ? payload.content : null, + }; + + var pushData = new PushData + { + pushToken = pushToken, + payload = payload + }; + + if (await _repositoryService.SaveData(pushCabinet)) + { + var logPush = new LogPush + { + bid = pushRequest.bid, + pid = pushRequest.pid, + create_date = DateTime.Now, + create_uid = "System", + log = $"[{summary}] : 푸시 캐비닛 저장 성공" + }; + if (await _repositoryService.SaveData(logPush)) + _logger.LogInformation($"[{summary}] : 로그 추가"); + } + + _pushQueue.Enqueue(pushData); + } + else + { + // 존재하지 않는 경우에는 지나가서 다른 uid 로 확인 하겠지 + var logPush = new LogPush + { + bid = pushRequest.bid, + pid = pushRequest.pid, + create_date = DateTime.Now, + create_uid = "System", + log = $"[{summary}] : 푸시 전송 실패" + }; + if (await _repositoryService.SaveData(logPush)) + _logger.LogInformation($"[{summary}] : 로그 추가"); + } + + } + }); + return Ok(APIResponse.Send("000", $"[{summary}], 정상", Empty)); + } + catch (PushInvalidException ex) + { + _logger.LogError($"[{summary}] : {ex.Message}"); + return Ok(APIResponse.Send("001", $"[{summary}], 푸시 송신 중 문제 발생",Empty)); + } + catch (Exception ex) + { + _logger.LogError($"[{summary}] : {ex.Message}"); + return StatusCode(500, APIResponse.UnknownError(ex.Message)); + } + } + + [HttpPost("set")] + [CustomOperation("푸시 변경", "저장된 양식을 변경한다.", "푸시")] + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(APIResponseStatus))] + public async Task SetPush(string token, [FromBody] DBPayload request) + { + if (string.IsNullOrEmpty(token)) return BadRequest(APIResponse.InvalidInputError()); + if (!ModelState.IsValid) return BadRequest(APIResponse.InvalidInputError()); + string summary = String.Empty; + string uid = String.Empty; + + try + { + if (token == "System") uid = "System"; + else + { + var validateToken = await _jwtTokenService.ValidateToken(token); + if (validateToken == null) return Ok(APIResponse.AccessExpireError()); + uid = validateToken.FindFirst(ClaimTypes.NameIdentifier)?.Value ?? string.Empty; + } + + summary = _repositoryService.ReadSummary(typeof(PushController), "SetPush"); + var dbPayload = await _dbContext.DBPayload + .FirstOrDefaultAsync(p => p.pid == request.pid && p.bid == request.bid); + + + if (dbPayload != null) + { + var logPush = new LogPush + { + bid = dbPayload.bid, + pid = dbPayload.pid, + create_uid = uid, + create_date = DateTime.Now, + }; + + if (dbPayload.title != request.title && request.title != "") dbPayload.title = request.title; + if (dbPayload.body != request.body && request.body != "") dbPayload.body = request.body; + if (dbPayload.subtitle != request.subtitle) dbPayload.subtitle = request.subtitle; + if (dbPayload.alert_yn != request.alert_yn) dbPayload.alert_yn = request.alert_yn; + if (dbPayload.category != request.category && request.category != "") dbPayload.category = request.category; + if (dbPayload.content != request.content) dbPayload.content = request.content; + if (await _repositoryService.SaveData(dbPayload)) + { + logPush.log = $"[{summary} : 정상 변경"; + return Ok(APIResponse.Send("000", $"[{summary}], 정상", Empty)); + } + + // 로그를 이제 만들어서 추가를 해야 합니다. + if (await _repositoryService.SaveData(logPush)) + _logger.LogInformation($"[{summary}] : 로그 추가"); + } + + return Ok(APIResponse.Send("100", $"[{summary}], PID, BID 또는 Cabinet 오류", Empty)); + } + catch (Exception ex) + { + _logger.LogError($"[{summary}] : {ex.Message}"); + return StatusCode(500, APIResponse.UnknownError(ex.Message)); + } + } + + [HttpPost("create")] + [CustomOperation("푸시 생성", "새로운 푸시 양식을 생성한다.", "푸시")] + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(APIResponseStatus))] + public async Task CreatePush(string token, [FromBody] CreatePush request) + { + if (string.IsNullOrEmpty(token)) return BadRequest(APIResponse.InvalidInputError()); + if (!ModelState.IsValid) return BadRequest(APIResponse.InvalidInputError()); + string summary = String.Empty; + string uid = ""; + + Func randomLetter = (letters, count) => new string(Enumerable.Range(0, count).Select(_ => letters[new Random().Next(letters.Length)]).ToArray()); + var letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + var digits = "0123456789"; + var frontLetters = $"{randomLetter(letters, 1)}{randomLetter(digits, 1)}{randomLetter(letters, 1)}"; + var afterLetters = $"{randomLetter(letters, 1)}{randomLetter(digits, 1)}{randomLetter(letters, 1)}"; + + try + { + if (token == "System") uid = "System"; + else { + var validateToken = await _jwtTokenService.ValidateToken(token); + if (validateToken == null) return Ok(APIResponse.AccessExpireError()); + uid = validateToken.FindFirst(ClaimTypes.NameIdentifier)?.Value ?? string.Empty; + } + + summary = _repositoryService.ReadSummary(typeof(PushController), "CreatePush"); + + if (await _dbContext.Academy.AnyAsync(a => a.bid == request.bid)) + { + DBPayload payload = new DBPayload + { + bid = request.bid, + pid = $"AP{DateTime.Now:yyyyMMdd}{frontLetters}{DateTime.Now:HHmmss}{afterLetters}", + title = request.title, + subtitle = request.subtitle, + body = request.body, + alert_yn = request.alert_yn, + category = request.category, + content = request.content, + }; + + if (await _repositoryService.SaveData(payload)) + { + var logPush = new LogPush + { + bid = payload.bid, + pid = payload.pid, + create_uid = uid, + create_date = DateTime.Now, + log = $"[{summary}] : 정상 생성" + }; + + // 로그를 이제 만들어서 추가를 해야 합니다. + if (await _repositoryService.SaveData(logPush)) + _logger.LogInformation($"[{summary}] : 로그 추가"); + + return Ok(APIResponse.Send("000", $"[{summary}], 정상", Empty)); + } + + } + return Ok(APIResponse.Send("100", $"[{summary}], 학원 정보(BID) 확인 불가", Empty)); + } + catch (TokenException tokenEx) + { + _logger.LogInformation($"[{summary}] : {tokenEx}"); + return Ok(APIResponse.Send("001", $"[{summary}], 토큰에 문제가 있음",Empty)); + } + catch (RefreshRevokeException refreshEx) + { + _logger.LogInformation($"[{summary}] : {refreshEx}"); + return Ok(APIResponse.Send("001", $"[{summary}], 폐기된 리프레시 토큰",Empty)); + } + catch (Exception ex) + { + _logger.LogError($"[{summary}] : {ex.Message}"); + return StatusCode(500, APIResponse.UnknownError()); + } + } + + + + [HttpDelete("delete")] + [CustomOperation("푸시 삭제", "저장된 푸시 양식을 삭제 한다.", "푸시")] + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(APIResponseStatus))] + public async Task DeletePush(string token, string bid, string pid) + { + if (string.IsNullOrEmpty(token)) return BadRequest(APIResponse.InvalidInputError()); + if (!ModelState.IsValid) return BadRequest(APIResponse.InvalidInputError()); + string uid = ""; + string summary = String.Empty; + + try + { + if (token == "System") uid = "System"; + else { + var validateToken = await _jwtTokenService.ValidateToken(token); + if (validateToken == null) return Ok(APIResponse.AccessExpireError()); + uid = validateToken.FindFirst(ClaimTypes.NameIdentifier)?.Value ?? string.Empty; + } + + summary = _repositoryService.ReadSummary(typeof(PushController), "DeletePush"); + + var payload = await _dbContext.DBPayload.FirstOrDefaultAsync(p => p.bid == bid && p.pid == pid); + if (payload == null) return Ok(APIResponse.Send("001", $"[{summary}], 삭제 할 PUSH 없음", Empty)); + if (!await _repositoryService.DeleteData(payload)) return Ok(APIResponse.Send("002", $"[{summary}], PUSH 삭제 실패", Empty)); + + // 로그를 이제 만들어서 추가를 해야 합니다. + var logPush = new LogPush + { + bid = bid, + pid = pid, + create_uid = uid, + create_date = DateTime.Now, + log = $"[{summary}] : {pid} 삭제 - {uid}" + }; + + // 로그를 이제 만들어서 추가를 해야 합니다. + if (await _repositoryService.SaveData(logPush)) _logger.LogInformation($"[{summary}] : 로그 추가"); + + return Ok(APIResponse.Send("000", $"[{summary}], 정상", Empty)); + } + catch (Exception ex) + { + _logger.LogError($"[{summary}] : {ex.Message}"); + return BadRequest(APIResponse.UnknownError(ex.Message)); + } + } + + + [HttpDelete("delete/list")] + [CustomOperation("사용자 푸시 목록 삭제", "사용자가 받은 푸시목록에서 푸시를 삭제한다..", "푸시")] + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(APIResponseStatus))] + public async Task DeleteListPush(string token, int id) + { + if (string.IsNullOrEmpty(token)) return BadRequest(APIResponse.InvalidInputError()); + if (!ModelState.IsValid) return BadRequest(APIResponse.InvalidInputError()); + string uid = ""; + string summary = String.Empty; + try + { + if (token == "System") uid = "System"; + else { + var validateToken = await _jwtTokenService.ValidateToken(token); + if (validateToken == null) return Ok(APIResponse.AccessExpireError()); + uid = validateToken.FindFirst(ClaimTypes.NameIdentifier)?.Value ?? string.Empty; + } + + summary = _repositoryService.ReadSummary(typeof(PushController), "DeleteListPush"); + var cabinetPush = await _dbContext.PushCabinet.FirstOrDefaultAsync(c => c.id == id); + if (cabinetPush == null) return Ok(APIResponse.Send("001", $"[{summary}], 삭제 할 PUSH 없음", Empty)); + if (!await _repositoryService.DeleteData(cabinetPush)) + return Ok(APIResponse.Send("002", $"[{summary}], PUSH 삭제 실패", Empty)); + + // // 로그를 이제 만들어서 추가를 해야 합니다. + var logPush = new LogPush + { + bid = cabinetPush.bid, + pid = cabinetPush.pid, + create_uid = uid, + create_date = DateTime.Now, + log = $"[{summary}] : {cabinetPush.pid} 삭제 - {uid}" + }; + if (await _repositoryService.SaveData(logPush)) _logger.LogInformation($"[{summary}] : 로그 추가"); + + return Ok(APIResponse.Send("000", $"[{summary}], 정상", Empty)); + } + catch (Exception ex) + { + _logger.LogError($"[{summary}] : {ex.Message}"); + return BadRequest(APIResponse.UnknownError(ex.Message)); + } + } + + + + [HttpPost("list")] + [CustomOperation("사용자 푸시 목록 조회", "해당 사용자가 받은 푸시의 정보를 조회한다.", "푸시")] + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(APIResponseStatus))] + public async Task SearchToUserPush(string token, int size, [FromBody] PushCabinet? request) + { + if (string.IsNullOrEmpty(token)) return BadRequest(APIResponse.InvalidInputError()); + if (!ModelState.IsValid) return BadRequest(APIResponse.InvalidInputError()); + string uid = ""; + string summary = String.Empty; + + try + { + if (token == "System") uid = "System"; + else { + var validateToken = await _jwtTokenService.ValidateToken(token); + if (validateToken == null) return Ok(APIResponse.AccessExpireError()); + uid = validateToken.FindFirst(ClaimTypes.NameIdentifier)?.Value ?? string.Empty; + } + + summary = _repositoryService.ReadSummary(typeof(PushController), "SearchToUserPush"); + if (request == null) + { + var pagedData = await _dbContext.PushCabinet.Where(c => c.uid == uid) + .OrderBy(c=> c.send_date) + .Take(size) + .ToListAsync(); + + return Ok(APIResponse.Send("000", $"[{summary}], 정상", pagedData)); + } + else + { + var sort = await _dbContext.PushCabinet.Where(p=> p.id == request.id) + .Select(p => p.send_date).FirstOrDefaultAsync(); + var query = _dbContext.PushCabinet.OrderBy(c => c.send_date).AsQueryable(); + query = query.Where(c => c.send_date > sort); + var pagedData = await query.Take(size).ToListAsync(); + return Ok(APIResponse.Send("000", $"[{summary}], 정상", pagedData)); + } + } + catch (Exception ex) + { + _logger.LogError($"[{summary}] : {ex.Message}"); + return BadRequest(APIResponse.UnknownError(ex.Message)); + } + } + + + } +} // END PUSH CONTROLLER + + + + + diff --git a/Program/Controllers/V1/UserController.cs b/Program/Controllers/V1/UserController.cs new file mode 100644 index 0000000..e811a91 --- /dev/null +++ b/Program/Controllers/V1/UserController.cs @@ -0,0 +1,178 @@ +using System.Security.Claims; +using Back.Program.Common.Auth; +using Back.Program.Common.Data; +using Back.Program.Common.Model; +using Back.Program.Models.Entities; +using Back.Program.Services.V1.Interfaces; +using Back.Program.Services.V1; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; + +namespace Back.Program.Controllers.V1 +{ + /// + /// USER는 사용자가 자신의 데이터를 보거나 만들거나 하는 등 직접 사용하는 경우에 사용 + /// + [ApiController] + [Route("/api/v1/in/user")] + [ApiExplorerSettings(GroupName = "사용자")] + public class UserController : ControllerBase + { + private readonly AppDbContext _dbContext; + private readonly ILogger _logger; + private readonly JwtTokenService _jwtTokenService; + private readonly IRepositoryService _repositoryService; + private readonly IUserService _userService; + + public UserController(AppDbContext dbContext, ILogger logger, JwtTokenService jwtTokenService, + IRepositoryService repositoryService, IUserService userService) + { + _dbContext = dbContext; + _logger = logger; + _jwtTokenService = jwtTokenService; + _repositoryService = repositoryService; + _userService = userService; + } + + + /* + catch (Exception ex) + { + _logger.LogInformation($"[{summary}] : {ex.Message}"); + return BadRequest(APIResponse.UnknownError(ex.Message)); + } + */ + + [HttpGet] + [CustomOperation("회원 정보 조회", "회원 정보 조회 (자기자신)", "사용자")] + public async Task GetUserData(string token) + { + if (string.IsNullOrEmpty(token)) return BadRequest(APIResponse.InvalidInputError()); + if (!ModelState.IsValid) return BadRequest(APIResponse.InvalidInputError()); + string summary = _repositoryService.ReadSummary(typeof(UserController), "GetUserData"); + + var result = _userService.GetUser(summary, token); + return Ok(result); + } + + + [HttpGet("login")] + [CustomOperation("SNS 로그인", "로그인 후 회원이 있는지 확인", "사용자")] + public async Task Login(string accType, string snsId) + { + // API 동작 파라미터 입력 값 확인 + if (string.IsNullOrEmpty(accType) && string.IsNullOrEmpty(snsId)) return BadRequest(APIResponse.InvalidInputError()); + if (!ModelState.IsValid) return BadRequest(APIResponse.InvalidInputError()); + + string summary = _repositoryService.ReadSummary(typeof(UserController), "Login"); + var result = await _userService.Login(summary, accType, snsId); + return Ok(result); + + } + + [HttpPost("register")] + [CustomOperation("회원 가입", "사용자 회원 가입", "사용자")] + public async Task UserRegister([FromBody] UserAll request) + { + if (!ModelState.IsValid) return BadRequest(APIResponse.InvalidInputError()); + string summary = _repositoryService.ReadSummary(typeof(UserController), "UserRegister"); + + var result = _userService.Register(summary, request); + return Ok(result); + } + + [HttpGet("logout")] + [CustomOperation("로그아웃", "사용자 로그아웃", "사용자")] + public async Task Logout(string token) + { + if (string.IsNullOrEmpty(token)) return BadRequest(APIResponse.InvalidInputError()); + if (!ModelState.IsValid) return BadRequest(APIResponse.InvalidInputError()); + string summary = _repositoryService.ReadSummary(typeof(UserController), "Logout"); + + var result = await _userService.Logout(summary, token); + return Ok(result); + } + + + [HttpGet("cancel")] + [CustomOperation("회원 탈퇴", "사용자 탈퇴", "사용자")] + public async Task Cancel(string token) + { + if (string.IsNullOrEmpty(token)) return BadRequest(APIResponse.InvalidInputError()); + if (!ModelState.IsValid) return BadRequest(APIResponse.InvalidInputError()); + string summary = _repositoryService.ReadSummary(typeof(UserController), "Cancel"); + var result = await _userService.Cancel(summary, token); + return Ok(result); + } + + + [HttpGet("academy")] + [CustomOperation("학원 리스트 확인", "사용자가 등록된 학원 리스트 확인", "사용자")] + public async Task GetAcademyData(string token) + { + if (string.IsNullOrEmpty(token)) return BadRequest(APIResponse.InvalidInputError()); + if (!ModelState.IsValid) return BadRequest(APIResponse.InvalidInputError()); + string summary = _repositoryService.ReadSummary(typeof(UserController), "ReadAcademyInfo"); + + var result = _userService.GetAcademy(summary, token); + return Ok(result); + } + } +} + + +// 근데 회원 정보를 변경하는게 뭐뭐를 변경해야 하는지 아직 정해진게 없어서 이건 일단 보류 + /* + [HttpGet("set")] + [CustomOperation("회원 정보 변경", "회원 정보 변경", "사용자")] + public async Task SetUserData(string token, string refresh) //, [FromBody]) + { + if (string.IsNullOrEmpty(token) || string.IsNullOrEmpty(refresh)) + return BadRequest(APIResponse.InvalidInputError()); + if (!ModelState.IsValid) return BadRequest(APIResponse.InvalidInputError()); + string summary = String.Empty; + + try + { + summary = _repositoryService.ReadSummary(typeof(UserController), "Cancel"); + + + // 여기서 애초에 토큰 관련 에러가 2개가 나오게 만들어져 있음 + var validateToken = await _repositoryService.ValidateToken(token, refresh); + var user = await _dbContext.User.FirstOrDefaultAsync(u => u.uid == validateToken.uid); + + } + catch (TokenException tokenEx) + { + return Ok(APIResponse.Send("101", $"[{summary}], 입력 받은 토큰의 문제", Empty)); + } + catch (RefreshRevokeException refreshEx) + { + return Ok(APIResponse.Send("102", $"[{summary}], 폐기된 리프레시 토큰", Empty)); + } + catch (Exception ex) + { + return StatusCode(500, APIResponse.UnknownError($"[{summary}], {ex.Message}")); + } + } + + +} + +/* + string uid = ""; + if (token == "System") uid = "System"; + else { + if (string.IsNullOrEmpty(token) || string.IsNullOrEmpty(refresh)) return BadRequest(APIResponse.InvalidInputError()); + if(!ModelState.IsValid) return BadRequest(APIResponse.InvalidInputError()); + + var validateToken = await _repositoryService.ValidateToken(token, refresh); + uid = validateToken.uid; + } + + string summary = String.Empty; + try + { + summary = _repositoryService.ReadSummary(typeof(PushController), "GetUserData"); + } +*/ \ No newline at end of file diff --git a/Program/Models/Entities/APIResult.cs b/Program/Models/Entities/APIResult.cs new file mode 100644 index 0000000..f6c5ae3 --- /dev/null +++ b/Program/Models/Entities/APIResult.cs @@ -0,0 +1,16 @@ +using System.Text.Json; + +namespace Back.Program.Models.Entities +{ + public class APIResult + { + public bool Success { get; set; } + public string Code { get; set; } + public string Message { get; set; } + + public string JsonToString() + { + return JsonSerializer.Serialize(this); + } + } +} \ No newline at end of file diff --git a/Program/Models/Entities/Academy.cs b/Program/Models/Entities/Academy.cs new file mode 100644 index 0000000..1aa8de9 --- /dev/null +++ b/Program/Models/Entities/Academy.cs @@ -0,0 +1,32 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Back.Program.Models.Entities +{ + [Table("academy")] + public class Academy + { + [Key] + public string bid { get; set; } + public string business_name { get; set; } + public string business_owner { get; set; } + public string business_number { get; set; } + public DateTime business_date { get; set; } + public string business_address { get; set; } + public string business_contact { get; set; } + public string uid { get; set; } + } + +// -- -- -- -- -- DB 테이블 -- -- -- -- -- // + + public class AcademyName + { + public string bid { get; set; } + public string name { get; set; } + } + + public class RequestAcademy + { + public List bids { get; set; } + } +} \ No newline at end of file diff --git a/Program/Models/Entities/AuthKey.cs b/Program/Models/Entities/AuthKey.cs new file mode 100644 index 0000000..2058686 --- /dev/null +++ b/Program/Models/Entities/AuthKey.cs @@ -0,0 +1,10 @@ +using System.ComponentModel.DataAnnotations.Schema; + +namespace Back.Program.Models.Entities +{ + [Table("authkey")] + public class AuthKey + { + + } +} \ No newline at end of file diff --git a/Program/Models/Entities/Chatting.cs b/Program/Models/Entities/Chatting.cs new file mode 100644 index 0000000..33599f2 --- /dev/null +++ b/Program/Models/Entities/Chatting.cs @@ -0,0 +1,147 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Back.Program.Models.Entities +{ + [Table("chat_room")] + public class Chat_Room + { + [Required(ErrorMessage = "필수 항목 누락")] + [MaxLength(16)] + public required string cid { get; set; } // bid + yyyyMMdd + count + + [Required(ErrorMessage = "필수 항목 누락")] + [MaxLength(6)] + public required string bid { get; set; } + + [Required(ErrorMessage = "필수 항목 누락")] + public required string name { get; set; } + + [Required(ErrorMessage = "필수 항목 누락")] + [MaxLength(4)] + public required string type { get; set; } + + [Required(ErrorMessage = "필수 항목 누락")] + public required DateTime create_date { get; set; } + + [Required(ErrorMessage = "필수 항목 누락")] + public required DateTime open_date { get; set; } + + public DateTime? close_date { get; set; } + + public Chat_Room(string cid, string bid, string name, string type, DateTime create_date, DateTime open_date) + { + this.cid = cid; + this.bid = bid; + this.name = name; + this.type = type; + this.create_date = create_date; + this.open_date = open_date; + } + } + + [Table("chat_join")] + public class Chat_Join + { + [Required(ErrorMessage = "필수 항목 누락")] + [MaxLength(16)] + public required string cid { get; set; } + + [Required(ErrorMessage = "필수 항목 누락")] + [MaxLength(70)] + public required string uid { get; set; } + + public DateTime? join_date { get; set; } + public string? mid { get; set; } + + [Required(ErrorMessage = "필수 항목 누락")] + public required bool is_notice { get; set; } + + public Chat_Join(string cid, string uid, bool is_notice) + { + this.cid = cid; + this.uid = uid; + this.is_notice = is_notice; + } + } + + [Table("chat_message")] + public class Chat_Message + { + + [Required(ErrorMessage = "필수 항목 누락")] + [MaxLength(36)] + public required string mid { get; set; } // UUID + + [Required(ErrorMessage = "필수 항목 누락")] + [MaxLength(16)] + public required string cid { get; set; } + + [Required(ErrorMessage = "필수 항목 누락")] + [MaxLength(70)] + public required string uid { get; set; } + + [Required(ErrorMessage = "필수 항목 누락")] + public required DateTime create_date { get; set; } + [Required(ErrorMessage = "필수 항목 누락")] + public required string content { get; set; } + [Required(ErrorMessage = "필수 항목 누락")] + public required bool is_hidden { get; set; } + + public string? media_url { get; set; } + + [Required(ErrorMessage = "필수 항목 누락")] + public required int read_count { get; set; } + + [Required(ErrorMessage = "필수 항목 누락")] + public required bool is_blind { get; set; } + + [Required(ErrorMessage = "필수 항목 누락")] + public required bool check_report { get; set; } + + public Chat_Message(string mid, string cid, string uid, DateTime create_date, string content, bool is_hidden, + int read_count, bool is_blind, bool check_report) + { + this.mid = mid; + this.cid = cid; + this.uid = uid; + this.create_date = create_date; + this.content = content; + this.is_hidden = is_hidden; + this.read_count = read_count; + this.check_report = check_report; + this.is_blind = is_blind; + this.check_report = check_report; + + } + } + + + [Table("chat_report_list")] + public class Chat_Report_List + { + + [Required(ErrorMessage = "필수 항목 누락")] + [MaxLength(36)] + public required string mid { get; set; } // UUID + + [Required(ErrorMessage = "필수 항목 누락")] + [MaxLength(16)] + public required string cid { get; set; } + + [Required(ErrorMessage = "필수 항목 누락")] + [MaxLength(70)] + public required string uid { get; set; } + + [Required(ErrorMessage = "필수 항목 누락")] + public required string report { get; set; } + + public Chat_Report_List(string mid, string cid, string uid, string report) + { + this.mid = mid; + this.cid = cid; + this.uid = uid; + this.report = report; + } + } +} \ No newline at end of file diff --git a/Program/Models/Entities/Log.cs b/Program/Models/Entities/Log.cs new file mode 100644 index 0000000..43739c7 --- /dev/null +++ b/Program/Models/Entities/Log.cs @@ -0,0 +1,44 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Back.Program.Models.Entities +{ + [Table("log_project")] + public class LogProject + { + [Key] + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public int lid { get; set; } + public DateTime create_date {get; set;} + public string log { get; set; } + } + + + + [Table("log_push")] + public class LogPush + { + [Key] + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public int lid { get; set; } + public string bid {get; set;} + public string pid {get; set;} + public DateTime create_date {get; set;} + public string create_uid {get; set;} + public string log { get; set; } + } + + [Table("log_user")] + public class LogUser + { + [Key] + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public int lid { get; set; } + public string uid {get; set;} + public DateTime create_date {get; set;} + public string create_uid {get; set;} + public string log { get; set; } + + + } +} \ No newline at end of file diff --git a/Program/Models/Entities/PushPayload.cs b/Program/Models/Entities/PushPayload.cs new file mode 100644 index 0000000..bf0c0e9 --- /dev/null +++ b/Program/Models/Entities/PushPayload.cs @@ -0,0 +1,177 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Back.Program.Models.Entities +{ + /* + * iOS Payload 정의 + * aps 딕셔너리 구성 + * 1. alert : 실질적으로 사용자에게 노출되는 항목 + * 1.1. title : 제목 + * 1.2. subtitle : 부제목 + * 1.3. body : 본문 내용 + * 2. badge : 앱 아이콘에 표시할 숫자 + * 3. sound : 소리인데 "default" 가능 + * 4. content-available : 백그라운드 업데이트나 사일런트 푸시 송신시 사용한다. + * 1로 설정해 사용자가 직접 보지 않아도 앱이 업데이트 되게 할 수 있으며, + * UI에 알림이 표시되지 않고 데이터만 전달할 때 필요하다. + * 5. mutable-content : 값을 1로 설정해두면 Notification Service Extension을 통해 알림의 내용을 변경할 수 있다. + * 6. category : 사용자 인터렉션 구성시에 사용하는 식별자이다. + * 7. thread-id : 관련 알림들을 그룹화해 관리할 때 사용한다. + */ + +/* + * FCM Payload + * https://firebase.google.com/docs/reference/fcm/rest/v1/projects.messages?hl=ko&_gl=1*3awp3i*_up*MQ..*_ga*MTMyNDk4ODU5MC4xNzQxMTM1MzI3*_ga_CW55HF8NVT*MTc0MTEzNTMyNy4xLjAuMTc0MTEzNTM4My4wLjAuMA..#Notification + * { + "name": string, + "data": { // 입력 전용으로 임의의 가 + string: string, + ... + }, + "notification": { + object (Notification) + }, + "android": { + object (AndroidConfig) + }, + "webpush": { + object (WebpushConfig) + }, + "apns": { + object (ApnsConfig) + }, + "fcm_options": { + object (FcmOptions) + }, + + // Union field target can be only one of the following: + "token": string, + "topic": string, + "condition": string + // End of list of possible types for union field target. + } + * 1. Notification 영역 + * 1.1. title: 알림 제목 + * 1.2. body: 알림 내용 + * 1.3. image: 알림에 표시할 이미지 URL + * 1.4. sound: 알림 재생 시 사용할 사운드 + * 1.5. click_action: 알림 클릭 시 실행할 액션(인텐트 액션 문자열) + * 1.6. tag: 동일 태그를 가진 알림끼리 대체 또는 그룹화할 때 사용 + * 1.7. color: 알림 아이콘 배경색 (16진수 코드 등) + * 1.8. body_loc_key 및 body_loc_args: 본문에 사용할 지역화 키와 인자 배열 + * 1.9. title_loc_key 및 title_loc_args: 제목에 사용할 지역화 키와 인자 배열 + * 1.10. channel_id: Android Oreo 이상에서 알림 채널 식별자 + * 1.11. ticker, sticky, event_time, notification_priority, visibility, notification_count, light_settings, vibrate_timings 등: 사용자 경험이나 알림의 동작 방식을 세밀하게 제어할 때 사용 + * 2. Data 영역 : 임의의 키-값 쌍을 포함하고 UI에 표시되는 내용이 아닌 앱의 로직에 활용할 데이터를 보낼 때 사용한다. + * 3. 기타 전송 옵션 + * 3.1. priority: 메시지 전송 우선순위 (“high” 또는 “normal”) + * 3.2. time_to_live (ttl): 메시지 유효 시간(초 단위) + * 3.3. collapse_key: 동일 collapse_key를 가진 메시지는 최신 하나로 교체됨 + * 3.4. restricted_package_name: 메시지를 수신할 앱의 패키지 이름 (Android 전용) + */ + + [Table("payload")] + public class DBPayload + { + public string bid { get; set; } + public string pid { get; set; } + public string title {get; set;} + public string? subtitle {get; set;} + public string body {get; set;} + public bool alert_yn {get; set;} + public string category {get; set;} + public string? content {get; set;} + } + + [Table("push_cabinet")] + public class PushCabinet + { + [Key] + public int id { get; set; } + public string uid { get; set; } + public string pid { get; set; } + public string bid { get; set; } + public DateTime send_date { get; set; } + public bool check_yn { get; set; } + public string? content {get; set;} + } + + public class PushRequest + { + public string bid { get; set; } + public List uids { get; set; } + public string pid { get; set; } + + public string? content { get; set; } + } + + + public class Payload + { + public Aps aps { get; set; } + public string pid { get; set; } + public string bid { get; set; } + public string content { get; set; } + // public string customKey { get; set; } 이런식으로 추가도 가능 + + public string ToJson() + { + return System.Text.Json.JsonSerializer.Serialize(this); + } + } + + public class Aps + { + public Aps() + { + sound = "default"; + content_available = 1; + } + [Required(ErrorMessage = "필수 입력 누락 (alert)")] + public Alert alert { get; set; } + + [Required(ErrorMessage = "필수 입력 누락 (badge")] + public int badge { get; set; } // 앱 아이콘 표시 배지 숫자 설정 + public string sound { get; set; } // 사운드 파일 이름 default = "default" + public int content_available { get; set; } // 백그라운드 알림 활성화: 필수 (1) + public string? category { get; set; } // 알림에 대한 특정 액션을 정의 + } + + public class Alert + { + [Required(ErrorMessage = "필수 입력 누락 (title")] + public string title { get; set; } // 제목 + [Required(ErrorMessage = "필수 입력 누락 (body)")] + public string body { get; set; } // 내용 + public string? subtitle { get; set; } // 부제목 (선택) + } + + /// + /// 푸시 등록하기 위한 apns 여러 데이터 목록 + /// + public class PushFileSetting + { + public string uri { get; set; } + public string p12Path { get; set; } + public string p12PWPath { get; set; } + public string apnsTopic { get; set; } + } + + public class PushData + { + public string pushToken { get; set; } + public Payload payload { get; set; } + } + + public class CreatePush + { + public string bid { get; set; } + public string title { get; set; } + public string? subtitle { get; set; } + public string body { get; set; } + public bool alert_yn { get; set; } = true; + public string category { get; set; } + public string? content { get; set; } + } +} \ No newline at end of file diff --git a/Program/Models/Entities/User.cs b/Program/Models/Entities/User.cs new file mode 100644 index 0000000..c61d7e7 --- /dev/null +++ b/Program/Models/Entities/User.cs @@ -0,0 +1,149 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Back.Program.Models.Entities +{ + [Table("login")] + public class Login + { + [Key] + [Required(ErrorMessage = "필수 항목 누락")] + [MaxLength(100)] + public string sns_id {get; set;} + + [Required(ErrorMessage = "필수 항목 누락")] + [MaxLength(70)] + public string uid {get; set;} + + [Required(ErrorMessage = "필수 항목 누락")] + [MaxLength(4)] + public string sns_type {get; set;} + } + + [Table("user_academy")] + public class User_Academy + { + [Key] + [Required(ErrorMessage = "필수 항목 누락")] + public string uid { get; set; } + + [Required(ErrorMessage = "필수 항목 누락")] + public string bid { get; set; } + + [Required(ErrorMessage = "필수 항목 누락")] + public DateTime register_date { get; set; } + + [Required(ErrorMessage = "필수 항목 누락")] + public bool status { get; set; } + } + + [Table("user")] + public class User + { + [Key] + [Required(ErrorMessage = "필수 항목 누락")] + public string uid { get; set; } + + [Required(ErrorMessage = "필수 항목 누락")] + public string name { get; set; } + public DateTime? birth { get; set; } + + [Required(ErrorMessage = "필수 항목 누락")] + public string type { get; set; } + + public string? device_id { get; set; } + + [Required(ErrorMessage = "필수 항목 누락")] + public bool auto_login_yn { get; set; } + + [Required(ErrorMessage = "필수 항목 누락")] + public DateTime login_date { get; set; } + public string? push_token { get; set; } + } + + + [Table("permission")] + public class Permission + { + [Key] + + [Required(ErrorMessage = "필수 항목 누락")] + public string uid { get; set; } + + public bool location_yn {get; set;} + public bool camera_yn {get; set;} + public bool photo_yn {get; set;} + public bool push_yn {get; set;} + public bool market_app_yn {get; set;} + public bool market_sms_yn {get; set;} + public bool market_email_yn {get; set;} + } + + + [Table("location")] + public class Location + { + [Key] + + [Required(ErrorMessage = "필수 항목 누락")] + public string uid { get; set; } + + [Required(ErrorMessage = "필수 항목 누락")] + public string lat { get; set; } + + [Required(ErrorMessage = "필수 항목 누락")] + public string lng { get; set; } + } + + [Table("contact")] + public class Contact + { + [Key] + + [Required(ErrorMessage = "필수 항목 누락")] + public string uid { get; set; } + + [Required(ErrorMessage = "필수 항목 누락")] + public string email { get; set; } + public string? phone { get; set; } + public string? address { get; set; } + } + +// -- -- -- -- -- DB 테이블 -- -- -- -- -- // + + public class UserAll + { + [Required(ErrorMessage = "필수 항목 누락")] + public string name { get; set; } + + public DateTime? birth { get; set; } + + [Required(ErrorMessage = "필수 항목 누락")] + public string type { get; set; } + + public string? device_id { get; set; } + + public bool auto_login_yn { get; set; } + public DateTime login_date { get; set; } + public string? push_token { get; set; } + + [Required(ErrorMessage = "필수 항목 누락")] + public string email { get; set; } + public string? phone { get; set; } + public string? address { get; set; } + + public bool location_yn {get; set;} + public bool camera_yn {get; set;} + public bool photo_yn {get; set;} + public bool push_yn {get; set;} + public bool market_app_yn {get; set;} + public bool market_sms_yn {get; set;} + public bool market_email_yn {get; set;} + + [Required(ErrorMessage = "필수 항목 누락")] + public string sns_id {get; set;} + + [Required(ErrorMessage = "필수 항목 누락")] + public string sns_type {get; set;} + } +} \ No newline at end of file diff --git a/Program/Models/Entities/Version.cs b/Program/Models/Entities/Version.cs new file mode 100644 index 0000000..5f24d28 --- /dev/null +++ b/Program/Models/Entities/Version.cs @@ -0,0 +1,21 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Back.Program.Models.Entities +{ + [Table("version")] + public class Version + { + [Key] + [MaxLength(4)] + public string os_type { get; set; } + [MaxLength(8)] + public string final_ver { get; set; } + [MaxLength(8)] + public string dev_ver { get; set; } + [MaxLength(8)] + public string force_ver { get; set; } + public bool choice_update_yn { get; set; } + + } +} \ No newline at end of file diff --git a/Program/Repositories/V1/Interfaces/ILogRepository.cs b/Program/Repositories/V1/Interfaces/ILogRepository.cs new file mode 100644 index 0000000..d8d1ff8 --- /dev/null +++ b/Program/Repositories/V1/Interfaces/ILogRepository.cs @@ -0,0 +1,8 @@ +using Back.Program.Models.Entities; + +namespace Back.Program.Repositories.V1.Interfaces; + +public interface ILogRepository +{ + Task SaveLogUser(LogUser log); +} \ No newline at end of file diff --git a/Program/Repositories/V1/Interfaces/IUserRepository.cs b/Program/Repositories/V1/Interfaces/IUserRepository.cs new file mode 100644 index 0000000..6fc9422 --- /dev/null +++ b/Program/Repositories/V1/Interfaces/IUserRepository.cs @@ -0,0 +1,14 @@ +using Back.Program.Common.Model; +using Back.Program.Models.Entities; + +namespace Back.Program.Repositories.V1.Interfaces +{ + public interface IUserRepository + { + Task FindLogin(string accType, string snsId); + Task FindUser(string uid); + Task FindRefreshToken(string uid); + Task> FindAcademies(string uid); + Task SaveChanges(); + } +} \ No newline at end of file diff --git a/Program/Repositories/V1/LogRepository.cs b/Program/Repositories/V1/LogRepository.cs new file mode 100644 index 0000000..d8430e3 --- /dev/null +++ b/Program/Repositories/V1/LogRepository.cs @@ -0,0 +1,24 @@ +using Back.Program.Common.Data; +using Back.Program.Models.Entities; +using Back.Program.Repositories.V1.Interfaces; + +namespace Back.Program.Repositories.V1; + +public class LogRepository: ILogRepository +{ + private readonly ILogger _logger; + private readonly AppDbContext _context; + + + public LogRepository(ILogger logger, AppDbContext context) + { + _logger = logger; + _context = context; + } + + public async Task SaveLogUser(LogUser log) + { + _context.LogUser.Add(log); + return await _context.SaveChangesAsync() > 0; + } +} \ No newline at end of file diff --git a/Program/Repositories/V1/UserRepository.cs b/Program/Repositories/V1/UserRepository.cs new file mode 100644 index 0000000..b6b2d94 --- /dev/null +++ b/Program/Repositories/V1/UserRepository.cs @@ -0,0 +1,49 @@ +using Back.Program.Common.Data; +using Back.Program.Common.Model; +using Back.Program.Models.Entities; +using Back.Program.Repositories.V1.Interfaces; +using Microsoft.EntityFrameworkCore; + +namespace Back.Program.Repositories.V1 +{ + public class UserRepository: IUserRepository + { + private readonly ILogger _logger; + private readonly AppDbContext _context; + + public UserRepository(ILogger logger,AppDbContext context) { + _logger = logger; + _context = context; + } + + public Task FindLogin(string accType, string snsId) + { + return _context.Login.FirstOrDefaultAsync(l => l.sns_type == accType && l.sns_id == snsId); + } + + public Task FindUser(string uid) + { + return _context.User.FirstOrDefaultAsync(u => u.uid == uid); + } + + public Task FindRefreshToken(string uid) + { + return _context.RefreshToken.FirstOrDefaultAsync(r => r.uid == uid); + } + + public Task> FindAcademies(string uid) + { + var academyList = _context.UserAcademy + .Join(_context.Academy, ua => ua.uid, a => a.uid, (ua, a) => new { ua, a }) + .Where(s => s.ua.uid == uid) + .Select(s => new AcademyName { bid = s.a.bid, name = s.a.business_name }) + .ToListAsync(); + return academyList; + } + + public async Task SaveChanges() + { + return await _context.SaveChangesAsync() > 0; + } + } +} \ No newline at end of file diff --git a/Program/Services/V1/InMemoryPushQueue.cs b/Program/Services/V1/InMemoryPushQueue.cs new file mode 100644 index 0000000..6e6f351 --- /dev/null +++ b/Program/Services/V1/InMemoryPushQueue.cs @@ -0,0 +1,60 @@ +using System.Collections.Concurrent; +using Back.Program.Models.Entities; + +namespace Back.Program.Services.V1 +{ + public interface IPushQueue + { + void Enqueue(PushData pushData); + Task DequeueAsync(CancellationToken cancellationToken); + } + + public class InMemoryPushQueue: IPushQueue + { + private readonly ConcurrentQueue _queue = new ConcurrentQueue(); + private readonly SemaphoreSlim _signal = new SemaphoreSlim(0); + + public void Enqueue(PushData pushData) + { + if( pushData is null ) + throw new ArgumentNullException(nameof(pushData)); + _queue.Enqueue(pushData); + _signal.Release(); + } + + public async Task DequeueAsync(CancellationToken cancellationToken) + { + await _signal.WaitAsync(cancellationToken); + _queue.TryDequeue(out var pushData); + return pushData; + } + } + + public class PushBackgroundService : BackgroundService + { + private readonly IPushQueue _queue; + private readonly IApnsPushService _pushService; + + public PushBackgroundService(IPushQueue queue, IApnsPushService pushService) + { + _queue = queue; + _pushService = pushService; + } + + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { + while (!stoppingToken.IsCancellationRequested) + { + var pushData = await _queue.DequeueAsync(stoppingToken); + try + { + await _pushService.SendPushNotificationAsync(pushData.pushToken, pushData.payload); + } + catch (Exception ex) + { + Console.WriteLine($"푸시 전송 실패: {ex.Message}"); + } + } + } + } +} \ No newline at end of file diff --git a/Program/Services/V1/Interfaces/IUserService.cs b/Program/Services/V1/Interfaces/IUserService.cs new file mode 100644 index 0000000..ebe0df7 --- /dev/null +++ b/Program/Services/V1/Interfaces/IUserService.cs @@ -0,0 +1,16 @@ +using Back.Program.Common.Model; +using Back.Program.Models.Entities; + +namespace Back.Program.Services.V1.Interfaces +{ + public interface IUserService + { + Task> GetUser(string summary, string token); + Task> Login(string summary, string accType, string snsId); + Task> Register(string summary, UserAll request); + Task> Logout(string summary, string token); + Task> Cancel(string summary, string token); + Task> GetAcademy(string summary, string token); + + } +} \ No newline at end of file diff --git a/Program/V1/Services/PushService.cs b/Program/Services/V1/PushService.cs similarity index 57% rename from Program/V1/Services/PushService.cs rename to Program/Services/V1/PushService.cs index 984d6c7..d90d12d 100644 --- a/Program/V1/Services/PushService.cs +++ b/Program/Services/V1/PushService.cs @@ -1,49 +1,42 @@ -using System; -using System.Diagnostics; -using System.Net.Http; -using System.Security.Cryptography; -using System.Security.Cryptography.X509Certificates; using System.Text; using System.Text.Json; -using System.Threading.Tasks; +using Back.Program.Common.Model; +using Back.Program.Models.Entities; using Microsoft.Extensions.Options; using Polly; - -using AcaMate.V1.Models; -using Microsoft.Extensions.Options; using Version = System.Version; -namespace AcaMate.V1.Services; - -public interface IApnsPushService +namespace Back.Program.Services.V1 { - Task SendPushNotificationAsync(string deviceToken, Payload payload); -} -public class ApnsPushService: IApnsPushService -{ - private readonly HttpClient _httpClient; - private readonly PushFileSetting _setting; - - public ApnsPushService(HttpClient httpClient, IOptions options) + public interface IApnsPushService { - _httpClient = httpClient; - _setting = options.Value; + Task SendPushNotificationAsync(string deviceToken, Payload payload); } - - public async Task SendPushNotificationAsync(string deviceToken, Payload payload) + public class ApnsPushService: IApnsPushService { + private readonly HttpClient _httpClient; + private readonly PushFileSetting _setting; + + public ApnsPushService(HttpClient httpClient, IOptions options) + { + _httpClient = httpClient; + _setting = options.Value; + } + + public async Task SendPushNotificationAsync(string deviceToken, Payload payload) + { - // 존재 안하면 예외 던져 버리는거 - if (!File.Exists(_setting.p12Path) || !File.Exists(_setting.p12PWPath)) - throw new FileNotFoundException("[푸시] : p12 관련 파일 확인 필요"); + // 존재 안하면 예외 던져 버리는거 + if (!File.Exists(_setting.p12Path) || !File.Exists(_setting.p12PWPath)) + throw new FileNotFoundException("[푸시] : p12 관련 파일 확인 필요"); - var jsonPayload = JsonSerializer.Serialize(payload); - // var keys = - // JsonSerializer.Deserialize>(await File.ReadAllTextAsync(_setting.p12PWPath)) - // ?? throw new FileContentNotFoundException("[푸시] : 파일 내부의 값을 읽어오지 못함"); - // - // try - // { + var jsonPayload = JsonSerializer.Serialize(payload); + // var keys = + // JsonSerializer.Deserialize>(await File.ReadAllTextAsync(_setting.p12PWPath)) + // ?? throw new FileContentNotFoundException("[푸시] : 파일 내부의 값을 읽어오지 못함"); + // + // try + // { // var handler = new HttpClientHandler(); // handler.ClientCertificates // .Add(new X509Certificate2(_setting.p12Path, keys["Password"])); @@ -88,11 +81,12 @@ public class ApnsPushService: IApnsPushService // var errorContent = await response.Content.ReadAsStringAsync(); // throw new ServiceConnectionFailedException($"[푸시] : APNS 통신 실패 - {errorContent}"); // } - // } - // catch (HttpRequestException httpEx) - // { - // Console.WriteLine($"HttpRequestException: {httpEx.Message}"); - // throw new ServiceConnectionFailedException($"[푸시] : APNS 통신 실패 - {httpEx.Message}"); - // } + // } + // catch (HttpRequestException httpEx) + // { + // Console.WriteLine($"HttpRequestException: {httpEx.Message}"); + // throw new ServiceConnectionFailedException($"[푸시] : APNS 통신 실패 - {httpEx.Message}"); + // } + } } } \ No newline at end of file diff --git a/Program/Services/V1/RepositoryService.cs b/Program/Services/V1/RepositoryService.cs new file mode 100644 index 0000000..07fbaef --- /dev/null +++ b/Program/Services/V1/RepositoryService.cs @@ -0,0 +1,231 @@ +using System.Linq.Expressions; +using System.Reflection; +using Back.Program.Common.Auth; +using Back.Program.Common.Data; +using Back.Program.Common.Model; +using Back.Program.Models.Entities; +using Microsoft.EntityFrameworkCore; + +namespace Back.Program.Services.V1 +{ + public interface IRepositoryService + { + // Task ValidateToken(string token, string refresh); + Task SaveData(T entity, Expression> key = null) where T : class; + Task DeleteData(T entity, Expression> key = null) where T : class; + String ReadSummary(Type type, String name); + } + + public class RepositoryService: IRepositoryService + { + private readonly AppDbContext _dbContext; + private readonly ILogger _logger; + private readonly JwtTokenService _jwtTokenService; + + public RepositoryService(AppDbContext dbContext, ILogger logger, JwtTokenService jwtTokenService) + { + _dbContext = dbContext; + _logger = logger; + _jwtTokenService = jwtTokenService; + } + + + // public async Task ValidateToken(string token, string refresh) + // { + // var principalToken = await _jwtTokenService.ValidateToken(token); + // if (principalToken != null) + // { + // var uid = principalToken.FindFirst(ClaimTypes.NameIdentifier)?.Value ?? string.Empty; + // _logger.LogInformation($"토큰 변환 - {uid}"); + // return new ValidateToken + // { + // token = token, + // refresh = refresh, + // uid = uid + // }; + // } + // else + // { + // _logger.LogInformation("엑세스 토큰 만료"); + // // var refreshToken = await _dbContext.RefreshTokens + // // .FirstOrDefaultAsync(t => t.refresh_token == refresh); + // // if (refreshToken == null) + // // throw new TokenException("입력 받은 토큰 자체의 문제"); + // // + // // var uid = refreshToken.uid; + // // + // // if (refreshToken.revoke_Date < DateTime.Now) + // // throw new RefreshRevokeException("리프레시 토큰 해지"); + // // + // // if (refreshToken.expire_date > DateTime.Now) + // // { + // // _logger.LogInformation($"인증 완료 리프레시 : {uid}"); + // // var access = _jwtTokenService.GenerateJwtToken(uid); + // // + // // return new ValidateToken + // // { + // // token = access, + // // refresh = refreshToken.refresh_token, + // // uid = uid + // // }; + // // } + // // else + // // { + // // refreshToken = _jwtTokenService.GenerateRefreshToken(uid); + // // _logger.LogInformation("리프레시 토큰 만료"); + // // // await SaveData(refreshToken, rt => rt.uid); + // // return new ValidateToken + // // { + // // token = token, + // // refresh = refreshToken.refresh_token, + // // uid = uid + // // }; + // // } + // } + // } + + public async Task SaveData(T entity, Expression> key = null) where T : class + { + try + { + if (key != null) + { + // key를 가지고 EF 로 돌리는게 아니라 내가 조건을 넣어서 하는 경우에 사용함 + var value = key.Compile()(entity); + var parameter = Expression.Parameter(typeof(T), "x"); + var invokedExpr = Expression.Invoke(key, parameter); + var constantExpr = Expression.Constant(value, key.Body.Type); + var equalsExpr = Expression.Equal(invokedExpr, constantExpr); + var predicate = Expression.Lambda>(equalsExpr, parameter); + var entityData = await _dbContext.Set().FirstOrDefaultAsync(predicate); + + if (entityData != null) + { + _logger.LogInformation($"[{typeof(T)}] 해당 PK 존재 = [{value}]: 계속"); + _dbContext.Entry(entityData).CurrentValues.SetValues(entity); + if (!(_dbContext.Entry(entityData).Properties.Any(p => p.IsModified))) + { + _logger.LogInformation($"[{typeof(T)}] 변경 사항 없음"); + return true; + } + + _logger.LogInformation($"[{typeof(T)}] 변경 사항이 존재"); + } + else + { + _logger.LogInformation($"[{typeof(T)}] 처음등록"); + _dbContext.Set().Add(entity); + } + + } + else + { + // EF 로 직접 키를 잡아서 사용 (관계키나 이런거 할때도 노상관됨) + + // 모델이 존재하지 않거나 기본 키 정의가 되지 않은 오류가 발생할 건데 그건 운영 단계에서는 오류 나면 안되는거니 + var keyProperties = _dbContext.Model.FindEntityType(typeof(T)).FindPrimaryKey().Properties; + + // 각 키 속성에 대해, entity에서 실제 키 값을 추출 + var keyValues = keyProperties.Select(p => typeof(T).GetProperty(p.Name).GetValue(entity)).ToArray(); + + // 기본 키 값을 이용해서 기존 엔티티를 찾음 (복합 키도 자동으로 처리됨) + var existingEntity = await _dbContext.Set().FindAsync(keyValues); + + if (existingEntity != null) + { + _logger.LogInformation($"[{typeof(T)}] 기존 데이터 발견: 기본 키 값({string.Join(", ", keyValues)})"); + // 기존 엔티티를 업데이트: 새 entity의 값으로 교체 + _dbContext.Entry(existingEntity).CurrentValues.SetValues(entity); + } + else + { + _logger.LogInformation($"[{typeof(T)}] 신규 데이터 등록: 기본 키 값({string.Join(", ", keyValues)})"); + // 데이터가 없으면 새 엔티티 추가 + _dbContext.Set().Add(entity); + } + } + + + await _dbContext.SaveChangesAsync(); + _logger.LogInformation($"[{typeof(T)}] DB 저장 완료: 종료"); + return true; + } + catch (Exception ex) + { + _logger.LogInformation($"[{typeof(T)}] 저장 중 알 수 없는 오류 발생: {ex}"); + return false; + } + } + + + public async Task DeleteData(T entity, Expression> key = null) where T : class + { + try + { + if (key != null) + { + // key를 통해 조건식을 만들어 삭제할 엔티티를 찾는 경우 + var value = key.Compile()(entity); + var parameter = Expression.Parameter(typeof(T), "x"); + var invokedExpr = Expression.Invoke(key, parameter); + var constantExpr = Expression.Constant(value, key.Body.Type); + var equalsExpr = Expression.Equal(invokedExpr, constantExpr); + var predicate = Expression.Lambda>(equalsExpr, parameter); + + var entityData = await _dbContext.Set().FirstOrDefaultAsync(predicate); + if (entityData == null) + { + _logger.LogInformation($"[{typeof(T)}] 삭제 대상 데이터가 존재하지 않습니다. (값 = {value})"); + return false; + } + _logger.LogInformation($"[{typeof(T)}] 조건에 맞는 데이터 발견 (값 = {value}): 삭제 진행"); + _dbContext.Set().Remove(entityData); + } + else + { + // key가 없는 경우 EF Core 메타데이터를 사용하여 기본 키를 통한 삭제 + var entityType = _dbContext.Model.FindEntityType(typeof(T)); + if (entityType == null) + { + throw new InvalidOperationException($"Entity type '{typeof(T).Name}'이 모델에 존재하지 않습니다."); + } + + var primaryKey = entityType.FindPrimaryKey(); + if (primaryKey == null) + { + throw new InvalidOperationException($"Entity type '{typeof(T).Name}'에 기본 키가 정의되어 있지 않습니다."); + } + + var keyProperties = primaryKey.Properties; + var keyValues = keyProperties.Select(p => typeof(T).GetProperty(p.Name).GetValue(entity)).ToArray(); + + var existingEntity = await _dbContext.Set().FindAsync(keyValues); + if (existingEntity == null) + { + _logger.LogInformation($"[{typeof(T)}] 기본 키 값({string.Join(", ", keyValues)})에 해당하는 삭제 대상 데이터가 존재하지 않습니다."); + return false; + } + _logger.LogInformation($"[{typeof(T)}] 기본 키 값({string.Join(", ", keyValues)})에 해당하는 데이터 발견: 삭제 진행"); + _dbContext.Set().Remove(existingEntity); + } + + await _dbContext.SaveChangesAsync(); + _logger.LogInformation($"[{typeof(T)}] DB에서 삭제 완료"); + return true; + } + catch (Exception ex) + { + _logger.LogError($"[{typeof(T)}] 삭제 중 오류 발생: {ex}"); + return false; + } + } + + + public string ReadSummary(Type type, string name) + { + var method = type.GetMethod(name) ?? throw new OutNULLException("swagger summary Load ERROR: NULL"); + var att = method.GetCustomAttribute() ?? throw new OutNULLException("swagger summary Load ERROR: NULL"); + return att.Summary; + } + } +} diff --git a/Program/Services/V1/UserService.cs b/Program/Services/V1/UserService.cs new file mode 100644 index 0000000..a5056a9 --- /dev/null +++ b/Program/Services/V1/UserService.cs @@ -0,0 +1,238 @@ +using System.Security.Claims; +using Back.Program.Common.Auth; +using Back.Program.Common.Model; +using Back.Program.Models.Entities; +using Back.Program.Repositories.V1.Interfaces; +using Back.Program.Services.V1.Interfaces; + +namespace Back.Program.Services.V1 +{ + public class UserService : IUserService + { + private readonly ILogger _logger; + private readonly IUserRepository _userRepository; + private readonly JwtTokenService _jwtTokenService; + private readonly IRepositoryService _repositoryService; + private readonly ILogRepository _logRepository; + + public UserService(ILogger logger, IUserRepository userRepository, + JwtTokenService jwtTokenService, + IRepositoryService repositoryService, ILogRepository logRepository) + { + _logger = logger; + _userRepository = userRepository; + _jwtTokenService = jwtTokenService; + _repositoryService = repositoryService; + _logRepository = logRepository; + } + + public async Task> GetUser(string summary, string token) + { + var validToken = await _jwtTokenService.ValidateToken(token); + if (validToken == null) return APIResponse.AccessExpireError(); + + var uid = validToken.FindFirst(ClaimTypes.NameIdentifier)?.Value ?? string.Empty; + var user = await _userRepository.FindUser(uid); + + return APIResponse.Send("000", $"[{summary}], 정상", user); + // user 없는 경우가 없네? 그거도 만들것 + } + + public async Task> Login(string summary, string accType, string snsId) + { + var login = await _userRepository.FindLogin(accType, snsId); + if (login == null) + return APIResponse.Send("001", $"[{summary}], 로그인 정보 없음", string.Empty); + + var user = await _userRepository.FindUser(login.uid); + if (user == null) + return APIResponse.Send("002", $"[{summary}], 회원 정보 오류", string.Empty); + + user.login_date = DateTime.Now; + var token = _jwtTokenService.GenerateJwtToken(user.uid); + var refresh = _jwtTokenService.GenerateRefreshToken(user.uid); + + if (await _repositoryService.SaveData(refresh)) + { + return APIResponse.Send("000", $"[{summary}], 정상", + new { token = token, refresh = refresh.refresh_token }); + } + + // 토큰 저장에 실패 및 로그인도 실패 + return APIResponse.InternalSeverError($"[{summary}], 로그인 동작 실패"); + } + + public async Task> Register(string summary, UserAll request) + { + var localPartEmail = request.email.Substring(0, request.email.IndexOf('@')); + var uid = $"AM{localPartEmail}{DateTime.Now:yyyyMMdd}"; + + var user = new User + { + uid = uid, + name = request.name, + birth = request.birth, + type = request.type, + device_id = request.device_id, + auto_login_yn = request.auto_login_yn, + login_date = request.login_date, + push_token = request.push_token + }; + var login = new Login + { + uid = uid, + sns_id = request.sns_id, + sns_type = request.sns_type + }; + + var permission = new Permission + { + uid = uid, + location_yn = request.location_yn, + camera_yn = request.camera_yn, + photo_yn = request.photo_yn, + push_yn = request.push_yn, + market_app_yn = request.market_app_yn, + market_sms_yn = request.market_sms_yn, + market_email_yn = request.market_email_yn + }; + + var contact = new Contact + { + uid = uid, + email = request.email, + phone = request.phone, + address = request.address + }; + + var logUser = new LogUser + { + uid = login.uid, + create_date = DateTime.Now, + create_uid = "System", + log = "" + }; + + var saveUser = await _repositoryService.SaveData(user); + var saveLogin = await _repositoryService.SaveData(login); + var savePermission = await _repositoryService.SaveData(permission); + var saveContact = await _repositoryService.SaveData(contact); + + if (saveUser && saveLogin && savePermission && saveContact) + { + var token = _jwtTokenService.GenerateJwtToken(uid); + var refresh = _jwtTokenService.GenerateRefreshToken(uid); + + if (await _repositoryService.SaveData(refresh)) + { + logUser.log = $"[{summary}] : 정상"; + if (await _logRepository.SaveLogUser(logUser)) + { + _logger.LogInformation($"[{summary}]: 성공"); + } + else + { + _logger.LogInformation($"[{summary}]: 성공 - 로그 저장 실패"); + } + + return APIResponse.Send("000", $"[{summary}], 정상", new + { + token = token, + refresh = refresh.refresh_token + }); + } + else + { + logUser.log = $"[{summary}] : 실패"; + if (await _logRepository.SaveLogUser(logUser)) + { + _logger.LogInformation($"[{summary}]: 실패"); + } + else + { + _logger.LogInformation($"[{summary}]: 실패 - 로그 저장 실패"); + } + } + } + + return APIResponse.InternalSeverError($"[{summary}], 회원가입 동작 실패"); + } + + public async Task> Logout(string summary, string token) + { + var validToken = await _jwtTokenService.ValidateToken(token); + if (validToken == null) return APIResponse.AccessExpireError(); + + var uid = validToken.FindFirst(ClaimTypes.NameIdentifier)?.Value ?? string.Empty; + var refresh = await _userRepository.FindRefreshToken(uid); + if (refresh != null) + { + refresh.revoke_Date = DateTime.Now; + if (await _repositoryService.SaveData(refresh)) + { + return APIResponse.Send("000", $"[{summary}], 로그아웃 정상", string.Empty); + } + } + + return APIResponse.UnknownError(); + } + + public async Task> Cancel(string summary, string token) + { + var validToken = await _jwtTokenService.ValidateToken(token); + if (validToken == null) return APIResponse.AccessExpireError(); + + var uid = validToken.FindFirst(ClaimTypes.NameIdentifier)?.Value ?? string.Empty; + var user = await _userRepository.FindUser(uid); + if (user == null) return APIResponse.Send("001", $"[{summary}], 회원 정보 확인 오류", string.Empty); + + if (await _repositoryService.DeleteData(user)) + { + if (await _logRepository.SaveLogUser(new LogUser + { + uid = user.uid, create_date = DateTime.Now, create_uid = "System", log = $"[{summary}] : 정상" + })) + { + _logger.LogInformation($"[{summary}]: 성공"); + } + else + { + _logger.LogInformation($"[{summary}]: 성공 - 로그 저장 실패"); + } + + return APIResponse.Send("000", $"[{summary}], 정상", string.Empty); + } + else + { + if (await _logRepository.SaveLogUser(new LogUser + { + uid = user.uid, create_date = DateTime.Now, create_uid = "System", log = $"[{summary}] : 실패" + })) + { + _logger.LogInformation($"[{summary}]: 실패"); + } + else + { + _logger.LogInformation($"[{summary}]: 실패 - 로그 저장 실패"); + } + } + + return APIResponse.InternalSeverError(); + } + + public async Task> GetAcademy(string summary, string token) + { + var validToken = await _jwtTokenService.ValidateToken(token); + if (validToken == null) return APIResponse.AccessExpireError(); + + var uid = validToken.FindFirst(ClaimTypes.NameIdentifier)?.Value ?? string.Empty; + var user = await _userRepository.FindUser(uid); + if (user == null) return APIResponse.Send("001", $"[{summary}], 회원 정보 확인 오류", string.Empty); + + var academyList = await _userRepository.FindAcademies(uid); + + _logger.LogInformation($"[{summary}]: 성공"); + return APIResponse.Send("000", $"[{summary}], 정상.", academyList); + } + } +} \ No newline at end of file diff --git a/Program/V1/Controllers/AppController.cs b/Program/V1/Controllers/AppController.cs deleted file mode 100644 index d0e17bc..0000000 --- a/Program/V1/Controllers/AppController.cs +++ /dev/null @@ -1,271 +0,0 @@ -using System.Diagnostics; -using Microsoft.AspNetCore.Mvc; -using System.Text.Json; -using System.Security.Cryptography; -using System.Text; -using AcaMate.Common.Data; -using AcaMate.Common.Models; -using AcaMate.Common.Token; -using AcaMate.V1.Models; -using AcaMate.V1.Services; -using Microsoft.AspNetCore.Http.HttpResults; -using Microsoft.EntityFrameworkCore; -using Microsoft.IdentityModel.Tokens; -using Version = AcaMate.V1.Models.Version; - -namespace AcaMate.V1.Controllers; - -[ApiController] -[Route("/api/v1/in/app")] -[ApiExplorerSettings(GroupName = "공통")] -public class AppController : ControllerBase -{ - private readonly AppDbContext _dbContext; - private readonly ILogger _logger; - private readonly IRepositoryService _repositoryService; - private readonly JwtTokenService _jwtTokenService; - - public AppController(AppDbContext dbContext, ILogger logger, IRepositoryService repositoryService, JwtTokenService jwtTokenService) - { - _dbContext = dbContext; - _logger = logger; - _repositoryService = repositoryService; - _jwtTokenService = jwtTokenService; - } - - - // 이 키값의 제한 시간은 24h이다 - [HttpGet] - [CustomOperation("헤더 정보 생성", "헤더에 접근하기 위한 키 값 받아오기", "시스템")] - public async Task GetHeaderValue(string type, string specific, string project) - { - if (string.IsNullOrEmpty(specific) || string.IsNullOrEmpty(type) || string.IsNullOrEmpty(project)) - return BadRequest(APIResponse.InvalidInputError()); - if (!ModelState.IsValid) return BadRequest(APIResponse.InvalidInputError()); - string summary = String.Empty; - - try - { - summary = _repositoryService.ReadSummary(typeof(AppController), "GetHeaderValue"); - bool valid = false; - - switch (type) - { - case "I": - if (project == "me.myds.ipstein.acamate.AcaMate") valid = true; - break; - case "A": - break; - case "W": - break; - default: - return BadRequest(APIResponse.InvalidInputError($"[{summary}], 타입 에러")); - break; - } - - if (valid) - { - - var apiHeader = await _dbContext.APIHeader.FirstOrDefaultAsync(h => h.specific_id == specific); - - string nowTime = DateTime.Now.ToString("o"); - string combineText = $"{project}_{nowTime}_{specific}"; - string headerValue = KeyGenerator(combineText); - - if (apiHeader != null) - { - if (DateTime.Now - apiHeader.connect_date > TimeSpan.FromHours(24)) - { - _logger.LogInformation($"[{summary}] : 해당 키 유효기간 경과"); - apiHeader.h_value = headerValue; - apiHeader.connect_date = DateTime.Now; - - if (await _repositoryService.SaveData(apiHeader)) - { - string msg = "정상 - 로그 저장 실패"; - var logProject = new LogProject - { - create_date = DateTime.Now , - log = $"[{summary}] : 해당 키 유효시간 만료로 인한 새 키 부여" - }; - if (await _repositoryService.SaveData(logProject)) - msg = "정상"; - return Ok(APIResponse.Send("001", msg, new { header = headerValue })); - } - else - { - // 저장이 안된거니 서버 오류 - return StatusCode(500, APIResponse.InternalSeverError()); - } - } - else - { - // 유효기간 만료 이상 없이 다 잘 됨 - return Ok(APIResponse.Send("000", "정상", new { header = apiHeader.h_value })); - } - } - else - { - _logger.LogInformation($"[{summary}] : 저장 된게 없음"); - - var newHeader = new APIHeader - { - h_key = type == "I" ? "iOS_AM_Connect_Key" - : (type == "A" ? "And_AM_Connect_Key" - : (type == "W" ? "Web_AM_Connect_Key": throw new Exception("ERROR"))), - h_value = headerValue, - connect_date = DateTime.Now, - specific_id = specific - }; - - if (await _repositoryService.SaveData(newHeader)) - { - string msg = "정상 - 로그 저장 실패"; - var logProject = new LogProject - { - create_date = DateTime.Now , - log = $"[{summary}] : 새로운 등록으로 인한 새 키 부여" - }; - // 이거 로그 저장 안되는거 확인! - _logger.LogInformation($"[{summary}] : {logProject.log}"); - if (await _repositoryService.SaveData(logProject)) - msg = "정상"; - - return Ok(APIResponse.Send("001", msg, new { header = headerValue })); - } - else - { - // 저장이 안된거니 서버 오류 - return StatusCode(500, APIResponse.InternalSeverError()); - } - } - } - return BadRequest(APIResponse.InvalidInputError()); - - - // return Ok(APIResponse.Send("000", "정상", Empty)); - } - catch (Exception ex) - { - _logger.LogError($"[{summary}] : {ex.Message}"); - return StatusCode(500, APIResponse.UnknownError(ex.Message)); - } - } - - // 스웨거는 퍼블릭으로 선언된걸 죄다 API로 인식하는 경향이 있음 - // 방법은 private 같이 접근 제한자를 변경하거나 [NonAction]을 붙여주면 됨 - [NonAction] - private string KeyGenerator(string combineText) - { - using (SHA256 sha256 = SHA256.Create()) - { - byte[] hashBytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(combineText)); - return BitConverter.ToString(hashBytes).Replace("-", string.Empty).ToLowerInvariant(); - } - } - - [HttpGet("version")] - [CustomOperation("앱 버전 확인","앱 버전을 확인해서 업데이트 여부 판단", "시스템")] - public async Task GetVersionData(string type) - { - if (string.IsNullOrEmpty(type)) - { - return BadRequest(APIResponse.InvalidInputError()); - } - - try - { - var version = await _dbContext.Version.FirstOrDefaultAsync(v => v.os_type == (type == "I" ? "VO01" : "VO02")); - - if (version == null) - { - return NotFound(APIResponse.NotFoundError()); - } - - var response = new APIResponseStatus - { - status = new Status() - { - code = "000", - message = "정상" - }, - data = new Version() - { - os_type = (version.os_type == "VO01" ? "I" : (version.os_type == "VO02" ? "A" : "W")), - final_ver = version.final_ver, - force_ver = version.force_ver, - dev_ver = version.dev_ver, - choice_update_yn = version.choice_update_yn - } - }; - - string jsonString = JsonSerializer.Serialize(response); - - // return Ok(jsonString); - return Ok(response.JsonToString()); - } - catch (Exception ex) - { - Console.WriteLine($"{ex.Message}\n{ex.StackTrace}"); - return StatusCode(500, APIResponse.UnknownError()); - } - } - - - [HttpGet("auth")] - [CustomOperation("서버 접근 권한 확인", "서버 기능을 사용하기 위한 접근에 대해 권한 확인", "시스템")] - public async Task AuthProgram([FromBody] AuthKey keys) - { - string summary = String.Empty; - - try - { - summary = _repositoryService.ReadSummary(typeof(AppController), "AuthProgram"); - } - catch (Exception ex) - { - _logger.LogError($"[{summary}] : {ex.Message}"); - return StatusCode(500, APIResponse.UnknownError(ex.Message)); - } - - return Ok(APIResponse.Send("000", "OK", Empty)); - } - - - [HttpGet("retryAccess")] - [CustomOperation("엑세스 토큰 재발급", "액세스 토큰 재발급 동작 수행", "시스템")] - public async Task RetryAccessToken(string refresh) - { - string summary = String.Empty; - - try - { - summary = _repositoryService.ReadSummary(typeof(AppController), "AuthProgram"); - var refreshToken = await _dbContext.RefreshTokens - .FirstOrDefaultAsync(t => t.refresh_token == refresh); - if (refreshToken == null) throw new TokenException($"[{summary}] : 리프레시 토큰의 문제"); - if (refreshToken.revoke_Date < DateTime.Now) throw new TokenException($"[{summary}] : 리프레시 토큰 만료"); - if (refreshToken.expire_date < DateTime.Now) throw new TokenException($"[{summary}] : 리프레시 토큰 폐기"); - string access = _jwtTokenService.GenerateJwtToken(refreshToken.uid); - return Ok(APIResponse.Send("000", $"[{summary}], 토큰 생성 완료", - new { - access = access - })); - } - catch (TokenException ex) - { - _logger.LogError($"[{summary}] : {ex.Message}"); - return Ok(APIResponse.InvalidInputError(ex.Message)); - } - catch (Exception ex) - { - _logger.LogError($"[{summary}] : {ex.Message}"); - return StatusCode(500, APIResponse.UnknownError(ex.Message)); - } - - } - - - -} - diff --git a/Program/V1/Controllers/ErrorController.cs b/Program/V1/Controllers/ErrorController.cs deleted file mode 100644 index c1efbed..0000000 --- a/Program/V1/Controllers/ErrorController.cs +++ /dev/null @@ -1,15 +0,0 @@ -using Microsoft.AspNetCore.Mvc; - -namespace AcaMate.V1.Controllers; - - -[ApiController] -[Route("/api/error")] -public class ErrorController: ControllerBase -{ - [HttpGet] - public IActionResult HandleError() - { - return Problem("오류가 발생하였습니다. 잠시후 다시 시도해주세요."); - } -} \ No newline at end of file diff --git a/Program/V1/Controllers/MemberController.cs b/Program/V1/Controllers/MemberController.cs deleted file mode 100644 index ce1b2e2..0000000 --- a/Program/V1/Controllers/MemberController.cs +++ /dev/null @@ -1,51 +0,0 @@ - -using System.Text.Json; -using AcaMate.Common.Data; -using AcaMate.Common.Models; -using AcaMate.Common.Token; -using AcaMate.V1.Models; -using AcaMate.V1.Services; -using Microsoft.AspNetCore.Http.HttpResults; -using Microsoft.AspNetCore.Mvc; - -namespace AcaMate.V1.Controllers; - -[ApiController] -[Route("/api/v1/in/member")] -[ApiExplorerSettings(GroupName = "사업자 정보")] -public class MemberController: ControllerBase -{ - private readonly ILogger _logger; - private readonly AppDbContext _dbContext; - private readonly IRepositoryService _repositoryService; - private readonly JwtTokenService _jwtTokenService; - - public MemberController(AppDbContext dbContext, ILogger logger, IRepositoryService repositoryService, JwtTokenService jwtTokenService) - { - _dbContext = dbContext; - _logger = logger; - _repositoryService = repositoryService; - _jwtTokenService = jwtTokenService; - } - - [HttpGet("business")] - public IActionResult GetBusinessData() - { - // return Ok("GOOD"); - return Ok("DB 참조"); - } - - - - - - - // -- -- -- -- -- -- -- -- -- -- -- -- // - - [HttpGet("/api/v1/out/member/business")] - public IActionResult SearchBusinessNo() - { - return Ok("외부 참조"); - } - -} \ No newline at end of file diff --git a/Program/V1/Controllers/PushController.cs b/Program/V1/Controllers/PushController.cs deleted file mode 100644 index a7ec18f..0000000 --- a/Program/V1/Controllers/PushController.cs +++ /dev/null @@ -1,481 +0,0 @@ - -using System.Security.Claims; -using Microsoft.AspNetCore.Mvc; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Update.Internal; -using Microsoft.AspNetCore.Http.HttpResults; - - -using AcaMate.Common.Data; -using AcaMate.V1.Services; -using AcaMate.Common.Models; -using AcaMate.Common.Token; -using AcaMate.V1.Models; - - -namespace AcaMate.V1.Controllers; - - -[ApiController] -[Route("/api/v1/in/push")] -[ApiExplorerSettings(GroupName = "공통")] -public class PushController : ControllerBase -{ - private readonly ILogger _logger; - private readonly IPushQueue _pushQueue; - private readonly AppDbContext _dbContext; - private readonly IRepositoryService _repositoryService; - private readonly JwtTokenService _jwtTokenService; - public PushController(ILogger logger, IPushQueue pushQueue, AppDbContext dbContext, IRepositoryService repositoryService, JwtTokenService jwtTokenService) - { - _logger = logger; - _pushQueue = pushQueue; - _dbContext = dbContext; - _repositoryService = repositoryService; - _jwtTokenService = jwtTokenService; - } - - // 추가 사항 - // 카테고리 별 조회 하는 부분도 추가를 할 지 고민을 해야 할 것 같음 - - [HttpGet()] - [CustomOperation("푸시 확인", "저장된 양식을 확인 할 수 있다.", "푸시")] - [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(APIResponseStatus))] - public async Task GetPush(string bid, string? pid, string? category) - { - string summary = String.Empty; - - try - { - summary = _repositoryService.ReadSummary(typeof(PushController), "GetPush"); - - if (!(await _dbContext.Academy.AnyAsync(a=>a.bid == bid))) - return Ok(APIResponse.Send("100", $"[{summary}], 존재하지 않는 BID", Empty)); - - List pushData = new List(); - var pushQuery = _dbContext.DBPayload.Where(p => p.bid == bid); - if (pid != null) - pushQuery = pushQuery.Where(p=>p.pid == pid); - if (category != null) - pushQuery = pushQuery.Where(p=>p.category == category); - pushData = await pushQuery.ToListAsync(); - - if (pushData.Count > 0) - { - return Ok(APIResponse.Send("000", $"[{summary}, 정상", pushData)); - } - - return Ok(APIResponse.Send("001", $"[{summary}], PUSH 데이터 없음", Empty)); - } - catch (Exception ex) - { - _logger.LogError($"[{summary}] : {ex.Message}"); - return StatusCode(500, APIResponse.UnknownError()); - } - } - - - [HttpPost("send")] - [CustomOperation("푸시 발송", "저장된 양식으로, 사용자에게 푸시를 송신한다.", "푸시")] - [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(APIResponseStatus))] - public async Task SendPush([FromBody] PushRequest pushRequest) - { - string summary = String.Empty; - try { - summary = _repositoryService.ReadSummary(typeof(PushController), "SendPush"); - - if (!ModelState.IsValid) return BadRequest(APIResponse.InvalidInputError()); - - var payload = await _dbContext.DBPayload - .Where(p => p.pid == pushRequest.pid && p.bid == pushRequest.bid) - .Select(p => new Payload - { - aps = new Aps - { - alert = new Alert { title = p.title, body = p.body, subtitle = p.subtitle ?? "" }, - category = p.category - }, - pid = pushRequest.pid, - bid = pushRequest.bid, - content = pushRequest.content ?? (p.content ?? ""), - }) - .FirstOrDefaultAsync(); - - await Task.Run(async () => - { - if (payload == null) - throw new PushInvalidException("payload is NULL"); - - foreach (var uid in pushRequest.uids) - { - // 학원 내부에 해당 uid의 일원이 존재하는지 확인 - if ( - await _dbContext.UserAcademy - .Where(ua => ua.uid == uid && ua.bid == pushRequest.bid) - .AnyAsync() - ) - { - // 유저한테 온 모든 푸시에 대해서 안 읽은 것에 대한 뱃지 갯수 확인 - var badge = await _dbContext.PushCabinet - .Where(c => c.uid == uid - && c.check_yn == false) - .CountAsync(); - payload.aps.badge = badge + 1; - - // 푸시를 보내야 하니 푸시 토큰 확인 - var pushToken = await _dbContext.User - .Where(u => u.uid == uid) - .Select(u => u.push_token) - .FirstOrDefaultAsync() ?? ""; - - var pushCabinet = new PushCabinet - { - uid = uid, - bid = pushRequest.bid, - pid = pushRequest.pid, - send_date = DateTime.Now, - content = payload.content != "" ? payload.content : null, - }; - - var pushData = new PushData - { - pushToken = pushToken, - payload = payload - }; - - if (await _repositoryService.SaveData(pushCabinet)) - { - var logPush = new LogPush - { - bid = pushRequest.bid, - pid = pushRequest.pid, - create_date = DateTime.Now, - create_uid = "System", - log = $"[{summary}] : 푸시 캐비닛 저장 성공" - }; - if (await _repositoryService.SaveData(logPush)) - _logger.LogInformation($"[{summary}] : 로그 추가"); - } - - _pushQueue.Enqueue(pushData); - } - else - { - // 존재하지 않는 경우에는 지나가서 다른 uid 로 확인 하겠지 - var logPush = new LogPush - { - bid = pushRequest.bid, - pid = pushRequest.pid, - create_date = DateTime.Now, - create_uid = "System", - log = $"[{summary}] : 푸시 전송 실패" - }; - if (await _repositoryService.SaveData(logPush)) - _logger.LogInformation($"[{summary}] : 로그 추가"); - } - - } - }); - return Ok(APIResponse.Send("000", $"[{summary}], 정상", Empty)); - } - catch (PushInvalidException ex) - { - _logger.LogError($"[{summary}] : {ex.Message}"); - return Ok(APIResponse.Send("001", $"[{summary}], 푸시 송신 중 문제 발생",Empty)); - } - catch (Exception ex) - { - _logger.LogError($"[{summary}] : {ex.Message}"); - return StatusCode(500, APIResponse.UnknownError(ex.Message)); - } - } - - [HttpPost("set")] - [CustomOperation("푸시 변경", "저장된 양식을 변경한다.", "푸시")] - [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(APIResponseStatus))] - public async Task SetPush(string token, [FromBody] DBPayload request) - { - if (string.IsNullOrEmpty(token)) return BadRequest(APIResponse.InvalidInputError()); - if (!ModelState.IsValid) return BadRequest(APIResponse.InvalidInputError()); - string summary = String.Empty; - string uid = String.Empty; - - try - { - if (token == "System") uid = "System"; - else - { - var validateToken = await _jwtTokenService.ValidateToken(token); - if (validateToken == null) return Ok(APIResponse.AccessExpireError()); - uid = validateToken.FindFirst(ClaimTypes.NameIdentifier)?.Value ?? string.Empty; - } - - summary = _repositoryService.ReadSummary(typeof(PushController), "SetPush"); - var dbPayload = await _dbContext.DBPayload - .FirstOrDefaultAsync(p => p.pid == request.pid && p.bid == request.bid); - - - if (dbPayload != null) - { - var logPush = new LogPush - { - bid = dbPayload.bid, - pid = dbPayload.pid, - create_uid = uid, - create_date = DateTime.Now, - }; - - if (dbPayload.title != request.title && request.title != "") dbPayload.title = request.title; - if (dbPayload.body != request.body && request.body != "") dbPayload.body = request.body; - if (dbPayload.subtitle != request.subtitle) dbPayload.subtitle = request.subtitle; - if (dbPayload.alert_yn != request.alert_yn) dbPayload.alert_yn = request.alert_yn; - if (dbPayload.category != request.category && request.category != "") dbPayload.category = request.category; - if (dbPayload.content != request.content) dbPayload.content = request.content; - if (await _repositoryService.SaveData(dbPayload)) - { - logPush.log = $"[{summary} : 정상 변경"; - return Ok(APIResponse.Send("000", $"[{summary}], 정상", Empty)); - } - - // 로그를 이제 만들어서 추가를 해야 합니다. - if (await _repositoryService.SaveData(logPush)) - _logger.LogInformation($"[{summary}] : 로그 추가"); - } - - return Ok(APIResponse.Send("100", $"[{summary}], PID, BID 또는 Cabinet 오류", Empty)); - } - catch (Exception ex) - { - _logger.LogError($"[{summary}] : {ex.Message}"); - return StatusCode(500, APIResponse.UnknownError(ex.Message)); - } - } - - [HttpPost("create")] - [CustomOperation("푸시 생성", "새로운 푸시 양식을 생성한다.", "푸시")] - [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(APIResponseStatus))] - public async Task CreatePush(string token, [FromBody] CreatePush request) - { - if (string.IsNullOrEmpty(token)) return BadRequest(APIResponse.InvalidInputError()); - if (!ModelState.IsValid) return BadRequest(APIResponse.InvalidInputError()); - string summary = String.Empty; - string uid = ""; - - Func randomLetter = (letters, count) => new string(Enumerable.Range(0, count).Select(_ => letters[new Random().Next(letters.Length)]).ToArray()); - var letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; - var digits = "0123456789"; - var frontLetters = $"{randomLetter(letters, 1)}{randomLetter(digits, 1)}{randomLetter(letters, 1)}"; - var afterLetters = $"{randomLetter(letters, 1)}{randomLetter(digits, 1)}{randomLetter(letters, 1)}"; - - try - { - if (token == "System") uid = "System"; - else { - var validateToken = await _jwtTokenService.ValidateToken(token); - if (validateToken == null) return Ok(APIResponse.AccessExpireError()); - uid = validateToken.FindFirst(ClaimTypes.NameIdentifier)?.Value ?? string.Empty; - } - - summary = _repositoryService.ReadSummary(typeof(PushController), "CreatePush"); - - if (await _dbContext.Academy.AnyAsync(a => a.bid == request.bid)) - { - DBPayload payload = new DBPayload - { - bid = request.bid, - pid = $"AP{DateTime.Now:yyyyMMdd}{frontLetters}{DateTime.Now:HHmmss}{afterLetters}", - title = request.title, - subtitle = request.subtitle, - body = request.body, - alert_yn = request.alert_yn, - category = request.category, - content = request.content, - }; - - if (await _repositoryService.SaveData(payload)) - { - var logPush = new LogPush - { - bid = payload.bid, - pid = payload.pid, - create_uid = uid, - create_date = DateTime.Now, - log = $"[{summary}] : 정상 생성" - }; - - // 로그를 이제 만들어서 추가를 해야 합니다. - if (await _repositoryService.SaveData(logPush)) - _logger.LogInformation($"[{summary}] : 로그 추가"); - - return Ok(APIResponse.Send("000", $"[{summary}], 정상", Empty)); - } - - } - return Ok(APIResponse.Send("100", $"[{summary}], 학원 정보(BID) 확인 불가", Empty)); - } - catch (TokenException tokenEx) - { - _logger.LogInformation($"[{summary}] : {tokenEx}"); - return Ok(APIResponse.Send("001", $"[{summary}], 토큰에 문제가 있음",Empty)); - } - catch (RefreshRevokeException refreshEx) - { - _logger.LogInformation($"[{summary}] : {refreshEx}"); - return Ok(APIResponse.Send("001", $"[{summary}], 폐기된 리프레시 토큰",Empty)); - } - catch (Exception ex) - { - _logger.LogError($"[{summary}] : {ex.Message}"); - return StatusCode(500, APIResponse.UnknownError()); - } - } - - - - [HttpDelete("delete")] - [CustomOperation("푸시 삭제", "저장된 푸시 양식을 삭제 한다.", "푸시")] - [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(APIResponseStatus))] - public async Task DeletePush(string token, string bid, string pid) - { - if (string.IsNullOrEmpty(token)) return BadRequest(APIResponse.InvalidInputError()); - if (!ModelState.IsValid) return BadRequest(APIResponse.InvalidInputError()); - string uid = ""; - string summary = String.Empty; - - try - { - if (token == "System") uid = "System"; - else { - var validateToken = await _jwtTokenService.ValidateToken(token); - if (validateToken == null) return Ok(APIResponse.AccessExpireError()); - uid = validateToken.FindFirst(ClaimTypes.NameIdentifier)?.Value ?? string.Empty; - } - - summary = _repositoryService.ReadSummary(typeof(PushController), "DeletePush"); - - var payload = await _dbContext.DBPayload.FirstOrDefaultAsync(p => p.bid == bid && p.pid == pid); - if (payload == null) return Ok(APIResponse.Send("001", $"[{summary}], 삭제 할 PUSH 없음", Empty)); - if (!await _repositoryService.DeleteData(payload)) return Ok(APIResponse.Send("002", $"[{summary}], PUSH 삭제 실패", Empty)); - - // 로그를 이제 만들어서 추가를 해야 합니다. - var logPush = new LogPush - { - bid = bid, - pid = pid, - create_uid = uid, - create_date = DateTime.Now, - log = $"[{summary}] : {pid} 삭제 - {uid}" - }; - - // 로그를 이제 만들어서 추가를 해야 합니다. - if (await _repositoryService.SaveData(logPush)) _logger.LogInformation($"[{summary}] : 로그 추가"); - - return Ok(APIResponse.Send("000", $"[{summary}], 정상", Empty)); - } - catch (Exception ex) - { - _logger.LogError($"[{summary}] : {ex.Message}"); - return BadRequest(APIResponse.UnknownError(ex.Message)); - } - } - - - [HttpDelete("delete/list")] - [CustomOperation("사용자 푸시 목록 삭제", "사용자가 받은 푸시목록에서 푸시를 삭제한다..", "푸시")] - [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(APIResponseStatus))] - public async Task DeleteListPush(string token, int id) - { - if (string.IsNullOrEmpty(token)) return BadRequest(APIResponse.InvalidInputError()); - if (!ModelState.IsValid) return BadRequest(APIResponse.InvalidInputError()); - string uid = ""; - string summary = String.Empty; - try - { - if (token == "System") uid = "System"; - else { - var validateToken = await _jwtTokenService.ValidateToken(token); - if (validateToken == null) return Ok(APIResponse.AccessExpireError()); - uid = validateToken.FindFirst(ClaimTypes.NameIdentifier)?.Value ?? string.Empty; - } - - summary = _repositoryService.ReadSummary(typeof(PushController), "DeleteListPush"); - var cabinetPush = await _dbContext.PushCabinet.FirstOrDefaultAsync(c => c.id == id); - if (cabinetPush == null) return Ok(APIResponse.Send("001", $"[{summary}], 삭제 할 PUSH 없음", Empty)); - if (!await _repositoryService.DeleteData(cabinetPush)) - return Ok(APIResponse.Send("002", $"[{summary}], PUSH 삭제 실패", Empty)); - - // // 로그를 이제 만들어서 추가를 해야 합니다. - var logPush = new LogPush - { - bid = cabinetPush.bid, - pid = cabinetPush.pid, - create_uid = uid, - create_date = DateTime.Now, - log = $"[{summary}] : {cabinetPush.pid} 삭제 - {uid}" - }; - if (await _repositoryService.SaveData(logPush)) _logger.LogInformation($"[{summary}] : 로그 추가"); - - return Ok(APIResponse.Send("000", $"[{summary}], 정상", Empty)); - } - catch (Exception ex) - { - _logger.LogError($"[{summary}] : {ex.Message}"); - return BadRequest(APIResponse.UnknownError(ex.Message)); - } - } - - - - [HttpPost("list")] - [CustomOperation("사용자 푸시 목록 조회", "해당 사용자가 받은 푸시의 정보를 조회한다.", "푸시")] - [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(APIResponseStatus))] - public async Task SearchToUserPush(string token, int size, [FromBody] PushCabinet? request) - { - if (string.IsNullOrEmpty(token)) return BadRequest(APIResponse.InvalidInputError()); - if (!ModelState.IsValid) return BadRequest(APIResponse.InvalidInputError()); - string uid = ""; - string summary = String.Empty; - - try - { - if (token == "System") uid = "System"; - else { - var validateToken = await _jwtTokenService.ValidateToken(token); - if (validateToken == null) return Ok(APIResponse.AccessExpireError()); - uid = validateToken.FindFirst(ClaimTypes.NameIdentifier)?.Value ?? string.Empty; - } - - summary = _repositoryService.ReadSummary(typeof(PushController), "SearchToUserPush"); - if (request == null) - { - var pagedData = await _dbContext.PushCabinet.Where(c => c.uid == uid) - .OrderBy(c=> c.send_date) - .Take(size) - .ToListAsync(); - - return Ok(APIResponse.Send("000", $"[{summary}], 정상", pagedData)); - } - else - { - var sort = await _dbContext.PushCabinet.Where(p=> p.id == request.id) - .Select(p => p.send_date).FirstOrDefaultAsync(); - var query = _dbContext.PushCabinet.OrderBy(c => c.send_date).AsQueryable(); - query = query.Where(c => c.send_date > sort); - var pagedData = await query.Take(size).ToListAsync(); - return Ok(APIResponse.Send("000", $"[{summary}], 정상", pagedData)); - } - } - catch (Exception ex) - { - _logger.LogError($"[{summary}] : {ex.Message}"); - return BadRequest(APIResponse.UnknownError(ex.Message)); - } - } - - -}// END PUSH CONTROLLER - - - - - diff --git a/Program/V1/Controllers/UserController.cs b/Program/V1/Controllers/UserController.cs deleted file mode 100644 index 5e09004..0000000 --- a/Program/V1/Controllers/UserController.cs +++ /dev/null @@ -1,443 +0,0 @@ -using System.IdentityModel.Tokens.Jwt; -using System.Security.Claims; -using Microsoft.IdentityModel.Tokens; -using Microsoft.AspNetCore.Mvc; -using Microsoft.EntityFrameworkCore; -using System.Linq.Expressions; -using Microsoft.Extensions.Logging; -using Microsoft.EntityFrameworkCore.Query; -using MySqlConnector; -using System.Linq; - -using AcaMate.Common.Data; -using AcaMate.Common.Token; -using AcaMate.Common.Models; - -using AcaMate.V1.Models; -using AcaMate.V1.Services; - -namespace AcaMate.V1.Controllers; - -/// -/// USER는 사용자가 자신의 데이터를 보거나 만들거나 하는 등 직접 사용하는 경우에 사용 -/// -[ApiController] -[Route("/api/v1/in/user")] -[ApiExplorerSettings(GroupName = "사용자")] -public class UserController : ControllerBase -{ - private readonly AppDbContext _dbContext; - private readonly ILogger _logger; - private readonly JwtTokenService _jwtTokenService; - private readonly IRepositoryService _repositoryService; - - public UserController(AppDbContext dbContext, ILogger logger, JwtTokenService jwtTokenService, - IRepositoryService repositoryService) - { - _dbContext = dbContext; - _logger = logger; - _jwtTokenService = jwtTokenService; - _repositoryService = repositoryService; - } - - [HttpGet] - [CustomOperation("회원 정보 조회", "회원 정보 조회 (자기자신)", "사용자")] - public async Task GetUserData(string token) - { - if (string.IsNullOrEmpty(token)) return BadRequest(APIResponse.InvalidInputError()); - if (!ModelState.IsValid) return BadRequest(APIResponse.InvalidInputError()); - string summary = String.Empty; - try - { - var validateToken = await _jwtTokenService.ValidateToken(token); - if (validateToken == null) return Ok(APIResponse.AccessExpireError()); - var uid = validateToken.FindFirst(ClaimTypes.NameIdentifier)?.Value ?? string.Empty; - summary = _repositoryService.ReadSummary(typeof(UserController), "GetUserData"); - - var user = await _dbContext.User - .Where(u => u.uid == uid) - .Select(u => new User - { - uid = u.uid, - name = u.name, - auto_login_yn = u.auto_login_yn, - birth = u.birth, - device_id = u.device_id, - login_date = u.login_date, - type = u.type - }) - .FirstOrDefaultAsync(); - - return Ok(APIResponse.Send("000", $"[{summary}], 정상", user)); - } - catch (Exception ex) - { - return StatusCode(500, APIResponse.UnknownError(ex.Message)); - } - } - - [HttpGet("academy")] - [CustomOperation("학원 리스트 확인", "사용자가 등록된 학원 리스트 확인", "사용자")] - public async Task ReadAcademyInfo(string token) - { - if (string.IsNullOrEmpty(token)) return BadRequest(APIResponse.InvalidInputError()); - if (!ModelState.IsValid) return BadRequest(APIResponse.InvalidInputError()); - string summary = String.Empty; - try - { - var validateToken = await _jwtTokenService.ValidateToken(token); - if (validateToken == null) return Ok(APIResponse.AccessExpireError()); - var uid = validateToken.FindFirst(ClaimTypes.NameIdentifier)?.Value ?? string.Empty; - - summary = _repositoryService.ReadSummary(typeof(UserController), "ReadAcademyInfo"); - - var academies = await (from ua in _dbContext.UserAcademy - join a in _dbContext.Academy on ua.bid equals a.bid - where ua.uid == uid - select new AcademyName - { - bid = a.bid, - name = a.business_name - }).ToListAsync(); - - - return Ok(APIResponse.Send("000", $"[{summary}], 정상.", academies)); - } - catch (TokenException tokenEx) - { - _logger.LogInformation($"[{summary}] : {tokenEx}"); - return Ok(APIResponse.Send("001", $"[{summary}], 토큰에 문제가 있음", Empty)); - } - catch (RefreshRevokeException refreshEx) - { - _logger.LogInformation($"[{summary}] : {refreshEx}"); - return Ok(APIResponse.Send("001", $"[{summary}], 폐기된 리프레시 토큰", Empty)); - } - catch (Exception ex) - { - _logger.LogInformation($"[{summary}] : {ex}"); - return StatusCode(500, APIResponse.UnknownError(ex.Message)); - } - } - - - [HttpGet("login")] - [CustomOperation("SNS 로그인", "로그인 후 회원이 있는지 확인", "사용자")] - public async Task Login(string acctype, string snsId) - { - // API 동작 파라미터 입력 값 확인 - if (string.IsNullOrEmpty(acctype) && string.IsNullOrEmpty(snsId)) - return BadRequest(APIResponse.InvalidInputError()); - if (!ModelState.IsValid) return BadRequest(APIResponse.InvalidInputError()); - string summary = String.Empty; - - try - { - summary = _repositoryService.ReadSummary(typeof(UserController), "Login"); - - var login = await _dbContext.Login - .FirstOrDefaultAsync(l => l.sns_type == acctype && l.sns_id == snsId); - - if (login != null) - { - // 로그인 정보가 존재 하는 상황 - var user = await _dbContext.User - .FirstOrDefaultAsync(u => u.uid == login.uid); - - // 회원 정보 없음 - if (user == null) return Ok(APIResponse.Send("002", $"[{summary}], 회원 정보 오류", Empty)); - - // 정상적으로 User 테이블에도 있는것이 확인 됨 - user.login_date = DateTime.Now; - await _dbContext.SaveChangesAsync(); - - // 토큰 생성은 로그인이 이제 되고 나서 한다. - var accessToken = _jwtTokenService.GenerateJwtToken(login.uid); - var refreshToken = _jwtTokenService.GenerateRefreshToken(login.uid); - _logger.LogInformation($"[{summary}] : {login.uid} = {accessToken}, {refreshToken}"); - - if (await _repositoryService.SaveData(refreshToken)) - { - var logUser = new LogUser - { - uid = login.uid, - create_date = DateTime.Now, - create_uid = "System", - log = $"[{summary}] : 정상" - }; - await _repositoryService.SaveData(logUser); - return Ok(APIResponse.Send("000", $"[{summary}], 정상", - new { token = accessToken, refresh = refreshToken.refresh_token })); - } - else - { - var logUser = new LogUser - { - uid = login.uid, - create_date = DateTime.Now, - create_uid = "System", - log = $"[{summary}] : 실패" - }; - await _repositoryService.SaveData(logUser); - - return Ok(APIResponse.InternalSeverError($"[{summary}], 로그인 저장 실패")); - } - - } - - return Ok(APIResponse.Send("001", $"[{summary}], 로그인 정보 없음", Empty)); - } - catch (Exception ex) - { - _logger.LogInformation($"[{summary}] : {ex}"); - return StatusCode(500, APIResponse.UnknownError(ex.Message)); - } - } - - [HttpPost("register")] - [CustomOperation("회원 가입", "사용자 회원 가입", "사용자")] - public async Task UserRegister([FromBody] UserAll request) - { - if (!ModelState.IsValid) return BadRequest(APIResponse.InvalidInputError()); - string summary = String.Empty; - - try - { - summary = _repositoryService.ReadSummary(typeof(UserController), "UserRegister"); - var localPartEmail = request.email.Substring(0, request.email.IndexOf('@')); - var uid = $"AM{localPartEmail}{DateTime.Now:yyyyMMdd}"; - - var user = new User - { - uid = uid, - name = request.name, - birth = request.birth, - type = request.type, - device_id = request.device_id, - auto_login_yn = request.auto_login_yn, - login_date = request.login_date, - push_token = request.push_token - }; - var login = new Login - { - uid = uid, - sns_id = request.sns_id, - sns_type = request.sns_type - }; - - var permission = new Permission - { - uid = uid, - location_yn = request.location_yn, - camera_yn = request.camera_yn, - photo_yn = request.photo_yn, - push_yn = request.push_yn, - market_app_yn = request.market_app_yn, - market_sms_yn = request.market_sms_yn, - market_email_yn = request.market_email_yn - }; - - var contact = new Contact - { - uid = uid, - email = request.email, - phone = request.phone, - address = request.address - }; - - var logUser = new LogUser - { - uid = login.uid, - create_date = DateTime.Now, - create_uid = "System", - log = "" - }; - - var saveUser = await _repositoryService.SaveData(user); - var saveLogin = await _repositoryService.SaveData(login); - var savePermission = await _repositoryService.SaveData(permission); - var saveContact = await _repositoryService.SaveData(contact); - if (saveUser && saveLogin && savePermission && saveContact) - { - - var token = _jwtTokenService.GenerateJwtToken(uid); - var refreshToken = _jwtTokenService.GenerateRefreshToken(uid); - - if (await _repositoryService.SaveData(refreshToken)) - { - logUser.log = $"[{summary}] : 정상"; - - if (await _repositoryService.SaveData(logUser)) - _logger.LogInformation($"[{summary}] : 로그 저장 성공"); - - return Ok(APIResponse.Send("000", $"[{summary}], 정상", new - { - token = token, - refresh = refreshToken.refresh_token - })); - } - else - { - _logger.LogError($"[{summary}] : 토큰 저장 실패"); - } - } - - logUser.log = $"[{summary}] : 동작 실패"; - await _repositoryService.SaveData(logUser); - - return Ok(APIResponse.InternalSeverError()); - } - catch (Exception ex) - { - _logger.LogInformation($"[{summary}] : {ex.Message}"); - return BadRequest(APIResponse.UnknownError(ex.Message)); - } - - } - - [HttpGet("logout")] - [CustomOperation("로그아웃", "사용자 로그아웃", "사용자")] - public async Task Logout(string token) - { - if (string.IsNullOrEmpty(token)) return BadRequest(APIResponse.InvalidInputError()); - if (!ModelState.IsValid) return BadRequest(APIResponse.InvalidInputError()); - - string summary = String.Empty; - try - { - var validateToken = await _jwtTokenService.ValidateToken(token); - if (validateToken == null) return Ok(APIResponse.AccessExpireError()); - var uid = validateToken.FindFirst(ClaimTypes.NameIdentifier)?.Value ?? string.Empty; - - summary = _repositoryService.ReadSummary(typeof(UserController), "UserRegister"); - - var refreshToken = await _dbContext.RefreshTokens.FirstOrDefaultAsync(r => r.uid == uid); - - if (refreshToken != null) - { - refreshToken.revoke_Date = DateTime.Now; - await _repositoryService.SaveData(refreshToken); - return Ok(APIResponse.Send("000", $"[{summary}], 로그아웃 정상", Empty)); - } - - // 리프레시 토큰이 없다?? 그럼 이거 무조건 문제지 (이유를 알 수 없는) - return Ok(APIResponse.UnknownError()); - } - catch (Exception ex) - { - return StatusCode(500, APIResponse.UnknownError($"[{summary}], {ex.Message}")); - } - } - - - [HttpGet("cancel")] - [CustomOperation("회원 탈퇴", "사용자 탈퇴", "사용자")] - public async Task Cancel(string token) - { - if (string.IsNullOrEmpty(token)) return BadRequest(APIResponse.InvalidInputError()); - if (!ModelState.IsValid) return BadRequest(APIResponse.InvalidInputError()); - string summary = String.Empty; - - try - { - var validateToken = await _jwtTokenService.ValidateToken(token); - if (validateToken == null) return Ok(APIResponse.AccessExpireError()); - var uid = validateToken.FindFirst(ClaimTypes.NameIdentifier)?.Value ?? string.Empty; - - summary = _repositoryService.ReadSummary(typeof(UserController), "Cancel"); - - - // 여기서 애초에 토큰 관련 에러가 2개가 나오게 만들어져 있음 - var user = await _dbContext.User.FirstOrDefaultAsync(u => u.uid == uid); - - if (user == null) - return Ok(APIResponse.Send("001", $"[{summary}], 회원 정보 확인 오류", Empty)); - - - var logUser = new LogUser - { - uid = uid, - create_date = DateTime.Now, - create_uid = "System", - log = "" - }; - - string returnCode = "000"; - string returnMsg = $"[{summary}], 정상"; - if (await _repositoryService.DeleteData(user)) - logUser.log = $"[{summary}] : 정상"; - else - { - logUser.log = $"[{summary}] : 실패"; - returnMsg = $"[{summary}], 실패"; - returnCode = "001"; - } - - if (!(await _repositoryService.SaveData(logUser))) _logger.LogError($"[{summary}] : 로그 저장 실패"); - - return Ok(APIResponse.Send(returnCode, returnMsg, Empty)); - - } - catch (Exception ex) - { - return StatusCode(500, APIResponse.UnknownError($"[{summary}], {ex.Message}")); - } - } -} - - -// 근데 회원 정보를 변경하는게 뭐뭐를 변경해야 하는지 아직 정해진게 없어서 이건 일단 보류 - /* - [HttpGet("set")] - [CustomOperation("회원 정보 변경", "회원 정보 변경", "사용자")] - public async Task SetUserData(string token, string refresh) //, [FromBody]) - { - if (string.IsNullOrEmpty(token) || string.IsNullOrEmpty(refresh)) - return BadRequest(APIResponse.InvalidInputError()); - if (!ModelState.IsValid) return BadRequest(APIResponse.InvalidInputError()); - string summary = String.Empty; - - try - { - summary = _repositoryService.ReadSummary(typeof(UserController), "Cancel"); - - - // 여기서 애초에 토큰 관련 에러가 2개가 나오게 만들어져 있음 - var validateToken = await _repositoryService.ValidateToken(token, refresh); - var user = await _dbContext.User.FirstOrDefaultAsync(u => u.uid == validateToken.uid); - - } - catch (TokenException tokenEx) - { - return Ok(APIResponse.Send("101", $"[{summary}], 입력 받은 토큰의 문제", Empty)); - } - catch (RefreshRevokeException refreshEx) - { - return Ok(APIResponse.Send("102", $"[{summary}], 폐기된 리프레시 토큰", Empty)); - } - catch (Exception ex) - { - return StatusCode(500, APIResponse.UnknownError($"[{summary}], {ex.Message}")); - } - } - - -} - -/* - string uid = ""; - if (token == "System") uid = "System"; - else { - if (string.IsNullOrEmpty(token) || string.IsNullOrEmpty(refresh)) return BadRequest(APIResponse.InvalidInputError()); - if(!ModelState.IsValid) return BadRequest(APIResponse.InvalidInputError()); - - var validateToken = await _repositoryService.ValidateToken(token, refresh); - uid = validateToken.uid; - } - - string summary = String.Empty; - try - { - summary = _repositoryService.ReadSummary(typeof(PushController), "GetUserData"); - } -*/ \ No newline at end of file diff --git a/Program/V1/Models/APIResult.cs b/Program/V1/Models/APIResult.cs deleted file mode 100644 index 2c00628..0000000 --- a/Program/V1/Models/APIResult.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System.Text.Json; - -namespace AcaMate.V1.Models; - -public class APIResult -{ - public bool Success { get; set; } - public string Code { get; set; } - public string Message { get; set; } - - public string JsonToString() - { - return JsonSerializer.Serialize(this); - } -} \ No newline at end of file diff --git a/Program/V1/Models/AcaException.cs b/Program/V1/Models/AcaException.cs deleted file mode 100644 index c282b34..0000000 --- a/Program/V1/Models/AcaException.cs +++ /dev/null @@ -1,76 +0,0 @@ -using Microsoft.IdentityModel.Tokens; -using System; - -namespace AcaMate.V1.Models; - -/// -/// 입력 받은 토큰들(Access & Refresh) 자체에 문제가 있는 경우 -/// -public class TokenException: Exception -{ - public TokenException(string message) : base(message) - { - } -} - -/// -/// 리프레시 토큰이 만료가 나있는 경우 -/// -public class RefreshRevokeException: Exception -{ - public RefreshRevokeException(string message) : base(message) - { - } -} - -/// -/// 참조해야 하는 파일에서 오류가 발생하는 경우 -/// -public class FileNotValidException : Exception -{ - public FileNotValidException(string message) : base(message) - { - } -} - -/// -/// 파일 내부에 값을 읽을 때 오류가 발생하는 경우 -/// -public class FileContentNotFoundException : Exception -{ - public FileContentNotFoundException(string message) : base(message) - { - } -} - -/// -/// 외부 서비스에 연결시 연결 실패시 -/// -public class ServiceConnectionFailedException : Exception -{ - public ServiceConnectionFailedException(string message) : base(message) - { - - } -} - -/// -/// PUSH 서비스 중 데이터 사용에 문제가 발생했을시 -/// -public class PushInvalidException : Exception -{ - public PushInvalidException(string message) : base(message) - { - - } -} -/// -/// 값이 있어야 하는데 NULL인 경우 -/// -public class OutNULLException : Exception -{ - public OutNULLException(string message) : base(message) - { - - } -} \ No newline at end of file diff --git a/Program/V1/Models/Academy.cs b/Program/V1/Models/Academy.cs deleted file mode 100644 index 8f8cc02..0000000 --- a/Program/V1/Models/Academy.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System.ComponentModel.DataAnnotations; -using System.ComponentModel.DataAnnotations.Schema; - -namespace AcaMate.V1.Models; - -[Table("academy")] -public class Academy -{ - [Key] - public string bid { get; set; } - public string business_name { get; set; } - public string business_owner { get; set; } - public string business_number { get; set; } - public DateTime business_date { get; set; } - public string business_address { get; set; } - public string business_contact { get; set; } - public string uid { get; set; } -} - -// -- -- -- -- -- DB 테이블 -- -- -- -- -- // - -public class AcademyName -{ - public string bid { get; set; } - public string name { get; set; } -} - -public class RequestAcademy -{ - public List bids { get; set; } -} \ No newline at end of file diff --git a/Program/V1/Models/AuthKey.cs b/Program/V1/Models/AuthKey.cs deleted file mode 100644 index 2d35f7f..0000000 --- a/Program/V1/Models/AuthKey.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System.ComponentModel.DataAnnotations; -using System.ComponentModel.DataAnnotations.Schema; - - -namespace AcaMate.V1.Models; - -[Table("authkey")] -public class AuthKey -{ - -} \ No newline at end of file diff --git a/Program/V1/Models/Chatting.cs b/Program/V1/Models/Chatting.cs deleted file mode 100644 index 9450084..0000000 --- a/Program/V1/Models/Chatting.cs +++ /dev/null @@ -1,147 +0,0 @@ -using System.ComponentModel.DataAnnotations; -using System.ComponentModel.DataAnnotations.Schema; -using System.Diagnostics.CodeAnalysis; - -namespace AcaMate.V1.Models; - -[Table("chat_room")] -public class Chat_Room -{ - [Required(ErrorMessage = "필수 항목 누락")] - [MaxLength(16)] - public required string cid { get; set; } // bid + yyyyMMdd + count - - [Required(ErrorMessage = "필수 항목 누락")] - [MaxLength(6)] - public required string bid { get; set; } - - [Required(ErrorMessage = "필수 항목 누락")] - public required string name { get; set; } - - [Required(ErrorMessage = "필수 항목 누락")] - [MaxLength(4)] - public required string type { get; set; } - - [Required(ErrorMessage = "필수 항목 누락")] - public required DateTime create_date { get; set; } - - [Required(ErrorMessage = "필수 항목 누락")] - public required DateTime open_date { get; set; } - - public DateTime? close_date { get; set; } - - public Chat_Room(string cid, string bid, string name, string type, DateTime create_date, DateTime open_date) - { - this.cid = cid; - this.bid = bid; - this.name = name; - this.type = type; - this.create_date = create_date; - this.open_date = open_date; - } -} - -[Table("chat_join")] -public class Chat_Join -{ - [Required(ErrorMessage = "필수 항목 누락")] - [MaxLength(16)] - public required string cid { get; set; } - - [Required(ErrorMessage = "필수 항목 누락")] - [MaxLength(70)] - public required string uid { get; set; } - - public DateTime? join_date { get; set; } - public string? mid { get; set; } - - [Required(ErrorMessage = "필수 항목 누락")] - public required bool is_notice { get; set; } - - public Chat_Join(string cid, string uid, bool is_notice) - { - this.cid = cid; - this.uid = uid; - this.is_notice = is_notice; - } -} - -[Table("chat_message")] -public class Chat_Message -{ - - [Required(ErrorMessage = "필수 항목 누락")] - [MaxLength(36)] - public required string mid { get; set; } // UUID - - [Required(ErrorMessage = "필수 항목 누락")] - [MaxLength(16)] - public required string cid { get; set; } - - [Required(ErrorMessage = "필수 항목 누락")] - [MaxLength(70)] - public required string uid { get; set; } - - [Required(ErrorMessage = "필수 항목 누락")] - public required DateTime create_date { get; set; } - [Required(ErrorMessage = "필수 항목 누락")] - public required string content { get; set; } - [Required(ErrorMessage = "필수 항목 누락")] - public required bool is_hidden { get; set; } - - public string? media_url { get; set; } - - [Required(ErrorMessage = "필수 항목 누락")] - public required int read_count { get; set; } - - [Required(ErrorMessage = "필수 항목 누락")] - public required bool is_blind { get; set; } - - [Required(ErrorMessage = "필수 항목 누락")] - public required bool check_report { get; set; } - - public Chat_Message(string mid, string cid, string uid, DateTime create_date, string content, bool is_hidden, - int read_count, bool is_blind, bool check_report) - { - this.mid = mid; - this.cid = cid; - this.uid = uid; - this.create_date = create_date; - this.content = content; - this.is_hidden = is_hidden; - this.read_count = read_count; - this.check_report = check_report; - this.is_blind = is_blind; - this.check_report = check_report; - - } -} - - -[Table("chat_report_list")] -public class Chat_Report_List -{ - - [Required(ErrorMessage = "필수 항목 누락")] - [MaxLength(36)] - public required string mid { get; set; } // UUID - - [Required(ErrorMessage = "필수 항목 누락")] - [MaxLength(16)] - public required string cid { get; set; } - - [Required(ErrorMessage = "필수 항목 누락")] - [MaxLength(70)] - public required string uid { get; set; } - - [Required(ErrorMessage = "필수 항목 누락")] - public required string report { get; set; } - - public Chat_Report_List(string mid, string cid, string uid, string report) - { - this.mid = mid; - this.cid = cid; - this.uid = uid; - this.report = report; - } -} \ No newline at end of file diff --git a/Program/V1/Models/Log.cs b/Program/V1/Models/Log.cs deleted file mode 100644 index ee27de8..0000000 --- a/Program/V1/Models/Log.cs +++ /dev/null @@ -1,44 +0,0 @@ -using System.ComponentModel.DataAnnotations; -using System.ComponentModel.DataAnnotations.Schema; -using System.Security.Cryptography.X509Certificates; -using Microsoft.EntityFrameworkCore.Metadata.Internal; - -namespace AcaMate.V1.Models; - - -[Table("log_project")] -public class LogProject -{ - [Key] - [DatabaseGenerated(DatabaseGeneratedOption.Identity)] - public int lid { get; set; } - public DateTime create_date {get; set;} - public string log { get; set; } -} - - - -[Table("log_push")] -public class LogPush -{ - [Key] - [DatabaseGenerated(DatabaseGeneratedOption.Identity)] - public int lid { get; set; } - public string bid {get; set;} - public string pid {get; set;} - public DateTime create_date {get; set;} - public string create_uid {get; set;} - public string log { get; set; } -} - -[Table("log_user")] -public class LogUser -{ - [Key] - [DatabaseGenerated(DatabaseGeneratedOption.Identity)] - public int lid { get; set; } - public string uid {get; set;} - public DateTime create_date {get; set;} - public string create_uid {get; set;} - public string log { get; set; } -} \ No newline at end of file diff --git a/Program/V1/Models/PushPayload.cs b/Program/V1/Models/PushPayload.cs deleted file mode 100644 index 6ee16e1..0000000 --- a/Program/V1/Models/PushPayload.cs +++ /dev/null @@ -1,178 +0,0 @@ -using System.ComponentModel.DataAnnotations; -using System.ComponentModel.DataAnnotations.Schema; -using System.Security.Cryptography.X509Certificates; -using Microsoft.EntityFrameworkCore.Metadata.Internal; - -namespace AcaMate.V1.Models; - -/* - * iOS Payload 정의 - * aps 딕셔너리 구성 - * 1. alert : 실질적으로 사용자에게 노출되는 항목 - * 1.1. title : 제목 - * 1.2. subtitle : 부제목 - * 1.3. body : 본문 내용 - * 2. badge : 앱 아이콘에 표시할 숫자 - * 3. sound : 소리인데 "default" 가능 - * 4. content-available : 백그라운드 업데이트나 사일런트 푸시 송신시 사용한다. - * 1로 설정해 사용자가 직접 보지 않아도 앱이 업데이트 되게 할 수 있으며, - * UI에 알림이 표시되지 않고 데이터만 전달할 때 필요하다. - * 5. mutable-content : 값을 1로 설정해두면 Notification Service Extension을 통해 알림의 내용을 변경할 수 있다. - * 6. category : 사용자 인터렉션 구성시에 사용하는 식별자이다. - * 7. thread-id : 관련 알림들을 그룹화해 관리할 때 사용한다. -*/ - -/* - * FCM Payload - * https://firebase.google.com/docs/reference/fcm/rest/v1/projects.messages?hl=ko&_gl=1*3awp3i*_up*MQ..*_ga*MTMyNDk4ODU5MC4xNzQxMTM1MzI3*_ga_CW55HF8NVT*MTc0MTEzNTMyNy4xLjAuMTc0MTEzNTM4My4wLjAuMA..#Notification - * { - "name": string, - "data": { // 입력 전용으로 임의의 가 - string: string, - ... - }, - "notification": { - object (Notification) - }, - "android": { - object (AndroidConfig) - }, - "webpush": { - object (WebpushConfig) - }, - "apns": { - object (ApnsConfig) - }, - "fcm_options": { - object (FcmOptions) - }, - - // Union field target can be only one of the following: - "token": string, - "topic": string, - "condition": string - // End of list of possible types for union field target. - } - * 1. Notification 영역 - * 1.1. title: 알림 제목 - * 1.2. body: 알림 내용 - * 1.3. image: 알림에 표시할 이미지 URL - * 1.4. sound: 알림 재생 시 사용할 사운드 - * 1.5. click_action: 알림 클릭 시 실행할 액션(인텐트 액션 문자열) - * 1.6. tag: 동일 태그를 가진 알림끼리 대체 또는 그룹화할 때 사용 - * 1.7. color: 알림 아이콘 배경색 (16진수 코드 등) - * 1.8. body_loc_key 및 body_loc_args: 본문에 사용할 지역화 키와 인자 배열 - * 1.9. title_loc_key 및 title_loc_args: 제목에 사용할 지역화 키와 인자 배열 - * 1.10. channel_id: Android Oreo 이상에서 알림 채널 식별자 - * 1.11. ticker, sticky, event_time, notification_priority, visibility, notification_count, light_settings, vibrate_timings 등: 사용자 경험이나 알림의 동작 방식을 세밀하게 제어할 때 사용 - * 2. Data 영역 : 임의의 키-값 쌍을 포함하고 UI에 표시되는 내용이 아닌 앱의 로직에 활용할 데이터를 보낼 때 사용한다. - * 3. 기타 전송 옵션 - * 3.1. priority: 메시지 전송 우선순위 (“high” 또는 “normal”) - * 3.2. time_to_live (ttl): 메시지 유효 시간(초 단위) - * 3.3. collapse_key: 동일 collapse_key를 가진 메시지는 최신 하나로 교체됨 - * 3.4. restricted_package_name: 메시지를 수신할 앱의 패키지 이름 (Android 전용) - */ - -[Table("payload")] -public class DBPayload -{ - public string bid { get; set; } - public string pid { get; set; } - public string title {get; set;} - public string? subtitle {get; set;} - public string body {get; set;} - public bool alert_yn {get; set;} - public string category {get; set;} - public string? content {get; set;} -} - -[Table("push_cabinet")] -public class PushCabinet -{ - [Key] - public int id { get; set; } - public string uid { get; set; } - public string pid { get; set; } - public string bid { get; set; } - public DateTime send_date { get; set; } - public bool check_yn { get; set; } - public string? content {get; set;} -} - -public class PushRequest -{ - public string bid { get; set; } - public List uids { get; set; } - public string pid { get; set; } - - public string? content { get; set; } -} - - -public class Payload -{ - public Aps aps { get; set; } - public string pid { get; set; } - public string bid { get; set; } - public string content { get; set; } - // public string customKey { get; set; } 이런식으로 추가도 가능 - - public string ToJson() - { - return System.Text.Json.JsonSerializer.Serialize(this); - } -} - -public class Aps -{ - public Aps() - { - sound = "default"; - content_available = 1; - } - [Required(ErrorMessage = "필수 입력 누락 (alert)")] - public Alert alert { get; set; } - - [Required(ErrorMessage = "필수 입력 누락 (badge")] - public int badge { get; set; } // 앱 아이콘 표시 배지 숫자 설정 - public string sound { get; set; } // 사운드 파일 이름 default = "default" - public int content_available { get; set; } // 백그라운드 알림 활성화: 필수 (1) - public string? category { get; set; } // 알림에 대한 특정 액션을 정의 -} - -public class Alert -{ - [Required(ErrorMessage = "필수 입력 누락 (title")] - public string title { get; set; } // 제목 - [Required(ErrorMessage = "필수 입력 누락 (body)")] - public string body { get; set; } // 내용 - public string? subtitle { get; set; } // 부제목 (선택) -} - -/// -/// 푸시 등록하기 위한 apns 여러 데이터 목록 -/// -public class PushFileSetting -{ - public string uri { get; set; } - public string p12Path { get; set; } - public string p12PWPath { get; set; } - public string apnsTopic { get; set; } -} - -public class PushData -{ - public string pushToken { get; set; } - public Payload payload { get; set; } -} - -public class CreatePush -{ - public string bid { get; set; } - public string title { get; set; } - public string? subtitle { get; set; } - public string body { get; set; } - public bool alert_yn { get; set; } = true; - public string category { get; set; } - public string? content { get; set; } -} \ No newline at end of file diff --git a/Program/V1/Models/User.cs b/Program/V1/Models/User.cs deleted file mode 100644 index 8642229..0000000 --- a/Program/V1/Models/User.cs +++ /dev/null @@ -1,149 +0,0 @@ -using System.ComponentModel.DataAnnotations; -using System.ComponentModel.DataAnnotations.Schema; -using System.Runtime.InteropServices.JavaScript; - -namespace AcaMate.V1.Models; - -[Table("login")] -public class Login -{ - [Key] - [Required(ErrorMessage = "필수 항목 누락")] - [MaxLength(100)] - public string sns_id {get; set;} - - [Required(ErrorMessage = "필수 항목 누락")] - [MaxLength(70)] - public string uid {get; set;} - - [Required(ErrorMessage = "필수 항목 누락")] - [MaxLength(4)] - public string sns_type {get; set;} -} - -[Table("user_academy")] -public class User_Academy -{ - [Key] - [Required(ErrorMessage = "필수 항목 누락")] - public string uid { get; set; } - - [Required(ErrorMessage = "필수 항목 누락")] - public string bid { get; set; } - - [Required(ErrorMessage = "필수 항목 누락")] - public DateTime register_date { get; set; } - - [Required(ErrorMessage = "필수 항목 누락")] - public bool status { get; set; } -} - -[Table("user")] -public class User -{ - [Key] - [Required(ErrorMessage = "필수 항목 누락")] - public string uid { get; set; } - - [Required(ErrorMessage = "필수 항목 누락")] - public string name { get; set; } - public DateTime? birth { get; set; } - - [Required(ErrorMessage = "필수 항목 누락")] - public string type { get; set; } - - public string? device_id { get; set; } - - [Required(ErrorMessage = "필수 항목 누락")] - public bool auto_login_yn { get; set; } - - [Required(ErrorMessage = "필수 항목 누락")] - public DateTime login_date { get; set; } - public string? push_token { get; set; } -} - - -[Table("permission")] -public class Permission -{ - [Key] - - [Required(ErrorMessage = "필수 항목 누락")] - public string uid { get; set; } - - public bool location_yn {get; set;} - public bool camera_yn {get; set;} - public bool photo_yn {get; set;} - public bool push_yn {get; set;} - public bool market_app_yn {get; set;} - public bool market_sms_yn {get; set;} - public bool market_email_yn {get; set;} -} - - -[Table("location")] -public class Location -{ - [Key] - - [Required(ErrorMessage = "필수 항목 누락")] - public string uid { get; set; } - - [Required(ErrorMessage = "필수 항목 누락")] - public string lat { get; set; } - - [Required(ErrorMessage = "필수 항목 누락")] - public string lng { get; set; } -} - -[Table("contact")] -public class Contact -{ - [Key] - - [Required(ErrorMessage = "필수 항목 누락")] - public string uid { get; set; } - - [Required(ErrorMessage = "필수 항목 누락")] - public string email { get; set; } - public string? phone { get; set; } - public string? address { get; set; } -} - -// -- -- -- -- -- DB 테이블 -- -- -- -- -- // - -public class UserAll -{ - [Required(ErrorMessage = "필수 항목 누락")] - public string name { get; set; } - - public DateTime? birth { get; set; } - - [Required(ErrorMessage = "필수 항목 누락")] - public string type { get; set; } - - public string? device_id { get; set; } - - public bool auto_login_yn { get; set; } - public DateTime login_date { get; set; } - public string? push_token { get; set; } - - [Required(ErrorMessage = "필수 항목 누락")] - public string email { get; set; } - public string? phone { get; set; } - public string? address { get; set; } - - public bool location_yn {get; set;} - public bool camera_yn {get; set;} - public bool photo_yn {get; set;} - public bool push_yn {get; set;} - public bool market_app_yn {get; set;} - public bool market_sms_yn {get; set;} - public bool market_email_yn {get; set;} - - [Required(ErrorMessage = "필수 항목 누락")] - public string sns_id {get; set;} - - [Required(ErrorMessage = "필수 항목 누락")] - public string sns_type {get; set;} -} \ No newline at end of file diff --git a/Program/V1/Models/Version.cs b/Program/V1/Models/Version.cs deleted file mode 100644 index ef25105..0000000 --- a/Program/V1/Models/Version.cs +++ /dev/null @@ -1,21 +0,0 @@ - -using System.ComponentModel.DataAnnotations; -using System.ComponentModel.DataAnnotations.Schema; - -namespace AcaMate.V1.Models; - -[Table("version")] -public class Version -{ - [Key] - [MaxLength(4)] - public string os_type { get; set; } - [MaxLength(8)] - public string final_ver { get; set; } - [MaxLength(8)] - public string dev_ver { get; set; } - [MaxLength(8)] - public string force_ver { get; set; } - public bool choice_update_yn { get; set; } - -} \ No newline at end of file diff --git a/Program/V1/Repositories/UserRepository.cs b/Program/V1/Repositories/UserRepository.cs deleted file mode 100644 index b47a88c..0000000 --- a/Program/V1/Repositories/UserRepository.cs +++ /dev/null @@ -1,28 +0,0 @@ -using Microsoft.AspNetCore.Mvc; - -using System.Linq; -using System.Threading.Tasks; -using Microsoft.EntityFrameworkCore; -using AcaMate.Common.Data; -using AcaMate.Common.Models; -using AcaMate.V1.Models; - -namespace AcaMate.V1.Repositories; - -public class UserRepository -{ - - private readonly AppDbContext _context; - - - public UserRepository(AppDbContext context) { - _context = context; - - } - - /* - public async Task> GetUserAcademyInfoBySnsIdAsync(string snsId) - { - - }*/ -} \ No newline at end of file diff --git a/Program/V1/Services/InMemoryPushQueue.cs b/Program/V1/Services/InMemoryPushQueue.cs deleted file mode 100644 index ab7cbd8..0000000 --- a/Program/V1/Services/InMemoryPushQueue.cs +++ /dev/null @@ -1,63 +0,0 @@ - -using System.Collections.Concurrent; -using System.Threading; -using System.Threading.Tasks; -using AcaMate.V1.Models; -using Microsoft.Extensions.Hosting; - -namespace AcaMate.V1.Services; - -public interface IPushQueue -{ - void Enqueue(PushData pushData); - Task DequeueAsync(CancellationToken cancellationToken); -} - -public class InMemoryPushQueue: IPushQueue -{ - private readonly ConcurrentQueue _queue = new ConcurrentQueue(); - private readonly SemaphoreSlim _signal = new SemaphoreSlim(0); - - public void Enqueue(PushData pushData) - { - if( pushData is null ) - throw new ArgumentNullException(nameof(pushData)); - _queue.Enqueue(pushData); - _signal.Release(); - } - - public async Task DequeueAsync(CancellationToken cancellationToken) - { - await _signal.WaitAsync(cancellationToken); - _queue.TryDequeue(out var pushData); - return pushData; - } -} - -public class PushBackgroundService : BackgroundService -{ - private readonly IPushQueue _queue; - private readonly IApnsPushService _pushService; - - public PushBackgroundService(IPushQueue queue, IApnsPushService pushService) - { - _queue = queue; - _pushService = pushService; - } - - protected override async Task ExecuteAsync(CancellationToken stoppingToken) - { - while (!stoppingToken.IsCancellationRequested) - { - var pushData = await _queue.DequeueAsync(stoppingToken); - try - { - await _pushService.SendPushNotificationAsync(pushData.pushToken, pushData.payload); - } - catch (Exception ex) - { - Console.WriteLine($"푸시 전송 실패: {ex.Message}"); - } - } - } -} \ No newline at end of file diff --git a/Program/V1/Services/RepositoryService.cs b/Program/V1/Services/RepositoryService.cs deleted file mode 100644 index 6c21503..0000000 --- a/Program/V1/Services/RepositoryService.cs +++ /dev/null @@ -1,238 +0,0 @@ -using AcaMate.Common.Data; -using AcaMate.Common.Token; -using AcaMate.Common.Models; - - -using Microsoft.AspNetCore.Mvc; -using Microsoft.EntityFrameworkCore; -using Microsoft.IdentityModel.Tokens; -using Microsoft.VisualBasic; - -using System.Linq.Expressions; -using System.Security.Claims; -using System.Reflection; - -using AcaMate.V1.Models; - -namespace AcaMate.V1.Services; - -public interface IRepositoryService -{ - // Task ValidateToken(string token, string refresh); - Task SaveData(T entity, Expression> key = null) where T : class; - Task DeleteData(T entity, Expression> key = null) where T : class; - String ReadSummary(Type type, String name); -} - -public class RepositoryService: IRepositoryService -{ - private readonly AppDbContext _dbContext; - private readonly ILogger _logger; - private readonly JwtTokenService _jwtTokenService; - - public RepositoryService(AppDbContext dbContext, ILogger logger, JwtTokenService jwtTokenService) - { - _dbContext = dbContext; - _logger = logger; - _jwtTokenService = jwtTokenService; - } - - - // public async Task ValidateToken(string token, string refresh) - // { - // var principalToken = await _jwtTokenService.ValidateToken(token); - // if (principalToken != null) - // { - // var uid = principalToken.FindFirst(ClaimTypes.NameIdentifier)?.Value ?? string.Empty; - // _logger.LogInformation($"토큰 변환 - {uid}"); - // return new ValidateToken - // { - // token = token, - // refresh = refresh, - // uid = uid - // }; - // } - // else - // { - // _logger.LogInformation("엑세스 토큰 만료"); - // // var refreshToken = await _dbContext.RefreshTokens - // // .FirstOrDefaultAsync(t => t.refresh_token == refresh); - // // if (refreshToken == null) - // // throw new TokenException("입력 받은 토큰 자체의 문제"); - // // - // // var uid = refreshToken.uid; - // // - // // if (refreshToken.revoke_Date < DateTime.Now) - // // throw new RefreshRevokeException("리프레시 토큰 해지"); - // // - // // if (refreshToken.expire_date > DateTime.Now) - // // { - // // _logger.LogInformation($"인증 완료 리프레시 : {uid}"); - // // var access = _jwtTokenService.GenerateJwtToken(uid); - // // - // // return new ValidateToken - // // { - // // token = access, - // // refresh = refreshToken.refresh_token, - // // uid = uid - // // }; - // // } - // // else - // // { - // // refreshToken = _jwtTokenService.GenerateRefreshToken(uid); - // // _logger.LogInformation("리프레시 토큰 만료"); - // // // await SaveData(refreshToken, rt => rt.uid); - // // return new ValidateToken - // // { - // // token = token, - // // refresh = refreshToken.refresh_token, - // // uid = uid - // // }; - // // } - // } - // } - - public async Task SaveData(T entity, Expression> key = null) where T : class - { - try - { - if (key != null) - { - // key를 가지고 EF 로 돌리는게 아니라 내가 조건을 넣어서 하는 경우에 사용함 - var value = key.Compile()(entity); - var parameter = Expression.Parameter(typeof(T), "x"); - var invokedExpr = Expression.Invoke(key, parameter); - var constantExpr = Expression.Constant(value, key.Body.Type); - var equalsExpr = Expression.Equal(invokedExpr, constantExpr); - var predicate = Expression.Lambda>(equalsExpr, parameter); - var entityData = await _dbContext.Set().FirstOrDefaultAsync(predicate); - - if (entityData != null) - { - _logger.LogInformation($"[{typeof(T)}] 해당 PK 존재 = [{value}]: 계속"); - _dbContext.Entry(entityData).CurrentValues.SetValues(entity); - if (!(_dbContext.Entry(entityData).Properties.Any(p => p.IsModified))) - { - _logger.LogInformation($"[{typeof(T)}] 변경 사항 없음"); - return true; - } - - _logger.LogInformation($"[{typeof(T)}] 변경 사항이 존재"); - } - else - { - _logger.LogInformation($"[{typeof(T)}] 처음등록"); - _dbContext.Set().Add(entity); - } - - } - else - { - // EF 로 직접 키를 잡아서 사용 (관계키나 이런거 할때도 노상관됨) - - // 모델이 존재하지 않거나 기본 키 정의가 되지 않은 오류가 발생할 건데 그건 운영 단계에서는 오류 나면 안되는거니 - var keyProperties = _dbContext.Model.FindEntityType(typeof(T)).FindPrimaryKey().Properties; - - // 각 키 속성에 대해, entity에서 실제 키 값을 추출 - var keyValues = keyProperties.Select(p => typeof(T).GetProperty(p.Name).GetValue(entity)).ToArray(); - - // 기본 키 값을 이용해서 기존 엔티티를 찾음 (복합 키도 자동으로 처리됨) - var existingEntity = await _dbContext.Set().FindAsync(keyValues); - - if (existingEntity != null) - { - _logger.LogInformation($"[{typeof(T)}] 기존 데이터 발견: 기본 키 값({string.Join(", ", keyValues)})"); - // 기존 엔티티를 업데이트: 새 entity의 값으로 교체 - _dbContext.Entry(existingEntity).CurrentValues.SetValues(entity); - } - else - { - _logger.LogInformation($"[{typeof(T)}] 신규 데이터 등록: 기본 키 값({string.Join(", ", keyValues)})"); - // 데이터가 없으면 새 엔티티 추가 - _dbContext.Set().Add(entity); - } - } - - - await _dbContext.SaveChangesAsync(); - _logger.LogInformation($"[{typeof(T)}] DB 저장 완료: 종료"); - return true; - } - catch (Exception ex) - { - _logger.LogInformation($"[{typeof(T)}] 저장 중 알 수 없는 오류 발생: {ex}"); - return false; - } - } - - - public async Task DeleteData(T entity, Expression> key = null) where T : class - { - try - { - if (key != null) - { - // key를 통해 조건식을 만들어 삭제할 엔티티를 찾는 경우 - var value = key.Compile()(entity); - var parameter = Expression.Parameter(typeof(T), "x"); - var invokedExpr = Expression.Invoke(key, parameter); - var constantExpr = Expression.Constant(value, key.Body.Type); - var equalsExpr = Expression.Equal(invokedExpr, constantExpr); - var predicate = Expression.Lambda>(equalsExpr, parameter); - - var entityData = await _dbContext.Set().FirstOrDefaultAsync(predicate); - if (entityData == null) - { - _logger.LogInformation($"[{typeof(T)}] 삭제 대상 데이터가 존재하지 않습니다. (값 = {value})"); - return false; - } - _logger.LogInformation($"[{typeof(T)}] 조건에 맞는 데이터 발견 (값 = {value}): 삭제 진행"); - _dbContext.Set().Remove(entityData); - } - else - { - // key가 없는 경우 EF Core 메타데이터를 사용하여 기본 키를 통한 삭제 - var entityType = _dbContext.Model.FindEntityType(typeof(T)); - if (entityType == null) - { - throw new InvalidOperationException($"Entity type '{typeof(T).Name}'이 모델에 존재하지 않습니다."); - } - - var primaryKey = entityType.FindPrimaryKey(); - if (primaryKey == null) - { - throw new InvalidOperationException($"Entity type '{typeof(T).Name}'에 기본 키가 정의되어 있지 않습니다."); - } - - var keyProperties = primaryKey.Properties; - var keyValues = keyProperties.Select(p => typeof(T).GetProperty(p.Name).GetValue(entity)).ToArray(); - - var existingEntity = await _dbContext.Set().FindAsync(keyValues); - if (existingEntity == null) - { - _logger.LogInformation($"[{typeof(T)}] 기본 키 값({string.Join(", ", keyValues)})에 해당하는 삭제 대상 데이터가 존재하지 않습니다."); - return false; - } - _logger.LogInformation($"[{typeof(T)}] 기본 키 값({string.Join(", ", keyValues)})에 해당하는 데이터 발견: 삭제 진행"); - _dbContext.Set().Remove(existingEntity); - } - - await _dbContext.SaveChangesAsync(); - _logger.LogInformation($"[{typeof(T)}] DB에서 삭제 완료"); - return true; - } - catch (Exception ex) - { - _logger.LogError($"[{typeof(T)}] 삭제 중 오류 발생: {ex}"); - return false; - } - } - - - public string ReadSummary(Type type, string name) - { - var method = type.GetMethod(name) ?? throw new OutNULLException("swagger summary Load ERROR: NULL"); - var att = method.GetCustomAttribute() ?? throw new OutNULLException("swagger summary Load ERROR: NULL"); - return att.Summary; - } -} diff --git a/Program/V1/Services/UserService.cs b/Program/V1/Services/UserService.cs deleted file mode 100644 index 5d078bb..0000000 --- a/Program/V1/Services/UserService.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace AcaMate.V1.Services; - -public class UserService -{ - // priva - -} \ No newline at end of file diff --git a/SwaggerConfigure.cs b/SwaggerConfigure.cs index 6526728..b3b3a35 100644 --- a/SwaggerConfigure.cs +++ b/SwaggerConfigure.cs @@ -1,92 +1,92 @@ -using Microsoft.Extensions.DependencyInjection; using Microsoft.OpenApi.Models; -using Swashbuckle.AspNetCore.Annotations; using Swashbuckle.AspNetCore.SwaggerGen; - -[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] -public class CustomOperationAttribute : Attribute +namespace Back { - public string Summary { get; } - public string Description { get; } - public string[] Tags { get; } + [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] + public class CustomOperationAttribute : Attribute + { + public string Summary { get; } + public string Description { get; } + public string[] Tags { get; } - public CustomOperationAttribute(string summary, string description, params string[] tags) - { - Summary = summary; - Description = description; - Tags = tags; - } -} - - - -public class CustomSwaggerOperationFilter : IOperationFilter -{ - public void Apply(OpenApiOperation operation, OperationFilterContext context) - { - var customSwaggerAttribute = context.MethodInfo.GetCustomAttributes(typeof(CustomOperationAttribute), false) - .FirstOrDefault() as CustomOperationAttribute; - - if (customSwaggerAttribute != null) + public CustomOperationAttribute(string summary, string description, params string[] tags) { - operation.Summary = customSwaggerAttribute.Summary; - operation.Description = customSwaggerAttribute.Description; - operation.Tags = customSwaggerAttribute.Tags - .Select(tag => new OpenApiTag { Name = tag }) - .ToList(); + Summary = summary; + Description = description; + Tags = tags; } } -} -public static class SwaggerConfigure -{ - private static OpenApiInfo DocName(string title, string version) - { - return new OpenApiInfo - { - Title = title, - Version = version - }; - } - public static void AddCustomSwagger(this IServiceCollection services) - { - services.AddSwaggerGen(options => - { - options.EnableAnnotations(); - options.OperationFilter(); - options.SwaggerDoc("전체", DocName("AcaMate 전체 API","1.0.0")); - options.SwaggerDoc("공통",DocName("공통 API", "1.0.0")); - options.SwaggerDoc("사업자 정보", DocName("사업자 정보 API", "1.0.0")); - options.SwaggerDoc("사용자", DocName("사용자 API", "1.0.0")); - options.DocInclusionPredicate((docName, apiDesc) => + + public class CustomSwaggerOperationFilter : IOperationFilter + { + public void Apply(OpenApiOperation operation, OperationFilterContext context) + { + var customSwaggerAttribute = context.MethodInfo.GetCustomAttributes(typeof(CustomOperationAttribute), false) + .FirstOrDefault() as CustomOperationAttribute; + + if (customSwaggerAttribute != null) { - if (docName == "전체") return true; // 전체 문서에 모든 API 포함 - if (docName == "공통" && apiDesc.GroupName == "공통") return true; - if (docName == "사업자 정보" && apiDesc.GroupName == "사업자 정보") return true; - if (docName == "사용자" && apiDesc.GroupName == "사용자") return true; - return false; - }); - - options.TagActionsBy(apiDesc => new[] { apiDesc.GroupName ?? "기타" }); - - - // options.TagActionsBy(apiDesc => apiDesc.ActionDescriptor.EndpointMetadata - // .OfType() - // .FirstOrDefault()?.Tags ?? new[] { "기타" }); - }); + operation.Summary = customSwaggerAttribute.Summary; + operation.Description = customSwaggerAttribute.Description; + operation.Tags = customSwaggerAttribute.Tags + .Select(tag => new OpenApiTag { Name = tag }) + .ToList(); + } + } } - public static void UseCustomSwaggerUI(this IApplicationBuilder app) + public static class SwaggerConfigure { - app.UseSwagger(); - app.UseSwaggerUI(options => + private static OpenApiInfo DocName(string title, string version) { - options.SwaggerEndpoint("/swagger/전체/swagger.json", "전체 API"); - options.SwaggerEndpoint("/swagger/공통/swagger.json", "공통 API"); - options.SwaggerEndpoint("/swagger/사용자/swagger.json", "사용자 API"); - options.SwaggerEndpoint("/swagger/사업자 정보/swagger.json", "사업자 정보 API"); - }); + return new OpenApiInfo + { + Title = title, + Version = version + }; + } + public static void AddCustomSwagger(this IServiceCollection services) + { + services.AddSwaggerGen(options => + { + options.EnableAnnotations(); + options.OperationFilter(); + options.SwaggerDoc("전체", DocName("AcaMate 전체 API","1.0.0")); + options.SwaggerDoc("공통",DocName("공통 API", "1.0.0")); + options.SwaggerDoc("사업자 정보", DocName("사업자 정보 API", "1.0.0")); + options.SwaggerDoc("사용자", DocName("사용자 API", "1.0.0")); + + options.DocInclusionPredicate((docName, apiDesc) => + { + if (docName == "전체") return true; // 전체 문서에 모든 API 포함 + if (docName == "공통" && apiDesc.GroupName == "공통") return true; + if (docName == "사업자 정보" && apiDesc.GroupName == "사업자 정보") return true; + if (docName == "사용자" && apiDesc.GroupName == "사용자") return true; + return false; + }); + + options.TagActionsBy(apiDesc => new[] { apiDesc.GroupName ?? "기타" }); + + + // options.TagActionsBy(apiDesc => apiDesc.ActionDescriptor.EndpointMetadata + // .OfType() + // .FirstOrDefault()?.Tags ?? new[] { "기타" }); + }); + } + + public static void UseCustomSwaggerUI(this IApplicationBuilder app) + { + app.UseSwagger(); + app.UseSwaggerUI(options => + { + options.SwaggerEndpoint("/swagger/전체/swagger.json", "전체 API"); + options.SwaggerEndpoint("/swagger/공통/swagger.json", "공통 API"); + options.SwaggerEndpoint("/swagger/사용자/swagger.json", "사용자 API"); + options.SwaggerEndpoint("/swagger/사업자 정보/swagger.json", "사업자 정보 API"); + }); + } } } \ No newline at end of file -- 2.45.1 From 7f0121a1755891cc4dab35c39da1d5b3adb7734a Mon Sep 17 00:00:00 2001 From: Seonkyu_Kim Date: Fri, 4 Apr 2025 16:22:44 +0900 Subject: [PATCH 2/6] =?UTF-8?q?[=E2=99=BB=EF=B8=8F]=20=EC=A0=84=EC=B2=B4?= =?UTF-8?q?=EC=A0=81=EC=9D=B8=20=EA=B5=AC=EC=A1=B0=20=EB=A6=AC=ED=8C=A9?= =?UTF-8?q?=ED=86=A0=EB=A7=81=20=EC=A7=84=ED=96=89=EC=A4=912=20(AppControl?= =?UTF-8?q?ler=20part)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Program.cs | 4 + Program/Controllers/V1/AppController.cs | 230 ++---------------- Program/Controllers/V1/UserController.cs | 6 +- Program/Repositories/V1/AppRepository.cs | 35 +++ .../V1/Interfaces/IAppRepository.cs | 11 + .../V1/Interfaces/ILogRepository.cs | 1 + Program/Repositories/V1/LogRepository.cs | 6 + Program/Services/V1/AppService.cs | 167 +++++++++++++ Program/Services/V1/Interfaces/IAppService.cs | 11 + 9 files changed, 257 insertions(+), 214 deletions(-) create mode 100644 Program/Repositories/V1/AppRepository.cs create mode 100644 Program/Repositories/V1/Interfaces/IAppRepository.cs create mode 100644 Program/Services/V1/AppService.cs create mode 100644 Program/Services/V1/Interfaces/IAppService.cs diff --git a/Program.cs b/Program.cs index 0d76037..2b69b6f 100644 --- a/Program.cs +++ b/Program.cs @@ -123,8 +123,12 @@ builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); + builder.Services.AddScoped(); builder.Services.AddScoped(); + +builder.Services.AddScoped(); +builder.Services.AddScoped(); // builder.Services.AddScoped(); // // builder.Services.AddScoped(); diff --git a/Program/Controllers/V1/AppController.cs b/Program/Controllers/V1/AppController.cs index c415f68..4d53461 100644 --- a/Program/Controllers/V1/AppController.cs +++ b/Program/Controllers/V1/AppController.cs @@ -5,7 +5,9 @@ using Back.Program.Common.Auth; using Back.Program.Common.Data; using Back.Program.Common.Model; using Back.Program.Models.Entities; +using Back.Program.Repositories.V1.Interfaces; using Back.Program.Services.V1; +using Back.Program.Services.V1.Interfaces; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using Version = Back.Program.Models.Entities.Version; @@ -21,13 +23,17 @@ namespace Back.Program.Controllers.V1 private readonly ILogger _logger; private readonly IRepositoryService _repositoryService; private readonly JwtTokenService _jwtTokenService; + private readonly IAppService _appService; + private readonly IAppRepository _appRepository; - public AppController(AppDbContext dbContext, ILogger logger, IRepositoryService repositoryService, JwtTokenService jwtTokenService) + public AppController(AppDbContext dbContext, ILogger logger, IRepositoryService repositoryService, JwtTokenService jwtTokenService,IAppService appService, IAppRepository appRepository) { _dbContext = dbContext; _logger = logger; _repositoryService = repositoryService; _jwtTokenService = jwtTokenService; + _appService = appService; + _appRepository = appRepository; } @@ -39,228 +45,30 @@ namespace Back.Program.Controllers.V1 if (string.IsNullOrEmpty(specific) || string.IsNullOrEmpty(type) || string.IsNullOrEmpty(project)) return BadRequest(APIResponse.InvalidInputError()); if (!ModelState.IsValid) return BadRequest(APIResponse.InvalidInputError()); - string summary = String.Empty; + string summary = _repositoryService.ReadSummary(typeof(AppController), "GetHeaderValue"); - try - { - summary = _repositoryService.ReadSummary(typeof(AppController), "GetHeaderValue"); - bool valid = false; - - switch (type) - { - case "I": - if (project == "me.myds.ipstein.acamate.AcaMate") valid = true; - break; - case "A": - break; - case "W": - break; - default: - return BadRequest(APIResponse.InvalidInputError($"[{summary}], 타입 에러")); - break; - } - - if (valid) - { - - var apiHeader = await _dbContext.APIHeader.FirstOrDefaultAsync(h => h.specific_id == specific); - - string nowTime = DateTime.Now.ToString("o"); - string combineText = $"{project}_{nowTime}_{specific}"; - string headerValue = KeyGenerator(combineText); - - if (apiHeader != null) - { - if (DateTime.Now - apiHeader.connect_date > TimeSpan.FromHours(24)) - { - _logger.LogInformation($"[{summary}] : 해당 키 유효기간 경과"); - apiHeader.h_value = headerValue; - apiHeader.connect_date = DateTime.Now; - - if (await _repositoryService.SaveData(apiHeader)) - { - string msg = "정상 - 로그 저장 실패"; - var logProject = new LogProject - { - create_date = DateTime.Now , - log = $"[{summary}] : 해당 키 유효시간 만료로 인한 새 키 부여" - }; - if (await _repositoryService.SaveData(logProject)) - msg = "정상"; - return Ok(APIResponse.Send("001", msg, new { header = headerValue })); - } - else - { - // 저장이 안된거니 서버 오류 - return StatusCode(500, APIResponse.InternalSeverError()); - } - } - else - { - // 유효기간 만료 이상 없이 다 잘 됨 - return Ok(APIResponse.Send("000", "정상", new { header = apiHeader.h_value })); - } - } - else - { - _logger.LogInformation($"[{summary}] : 저장 된게 없음"); - - var newHeader = new APIHeader - { - h_key = type == "I" ? "iOS_AM_Connect_Key" - : (type == "A" ? "And_AM_Connect_Key" - : (type == "W" ? "Web_AM_Connect_Key": throw new Exception("ERROR"))), - h_value = headerValue, - connect_date = DateTime.Now, - specific_id = specific - }; - - if (await _repositoryService.SaveData(newHeader)) - { - string msg = "정상 - 로그 저장 실패"; - var logProject = new LogProject - { - create_date = DateTime.Now , - log = $"[{summary}] : 새로운 등록으로 인한 새 키 부여" - }; - // 이거 로그 저장 안되는거 확인! - _logger.LogInformation($"[{summary}] : {logProject.log}"); - if (await _repositoryService.SaveData(logProject)) - msg = "정상"; - - return Ok(APIResponse.Send("001", msg, new { header = headerValue })); - } - else - { - // 저장이 안된거니 서버 오류 - return StatusCode(500, APIResponse.InternalSeverError()); - } - } - } - return BadRequest(APIResponse.InvalidInputError()); - - - // return Ok(APIResponse.Send("000", "정상", Empty)); - } - catch (Exception ex) - { - _logger.LogError($"[{summary}] : {ex.Message}"); - return StatusCode(500, APIResponse.UnknownError(ex.Message)); - } - } - - // 스웨거는 퍼블릭으로 선언된걸 죄다 API로 인식하는 경향이 있음 - // 방법은 private 같이 접근 제한자를 변경하거나 [NonAction]을 붙여주면 됨 - [NonAction] - private string KeyGenerator(string combineText) - { - using (SHA256 sha256 = SHA256.Create()) - { - byte[] hashBytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(combineText)); - return BitConverter.ToString(hashBytes).Replace("-", string.Empty).ToLowerInvariant(); - } + var result = await _appService.GetHeader(summary, type, specific, project); + return Ok(result); } [HttpGet("version")] [CustomOperation("앱 버전 확인","앱 버전을 확인해서 업데이트 여부 판단", "시스템")] public async Task GetVersionData(string type) { - if (string.IsNullOrEmpty(type)) - { - return BadRequest(APIResponse.InvalidInputError()); - } - - try - { - var version = await _dbContext.Version.FirstOrDefaultAsync(v => v.os_type == (type == "I" ? "VO01" : "VO02")); - - if (version == null) - { - return NotFound(APIResponse.NotFoundError()); - } - - var response = new APIResponseStatus - { - status = new Status() - { - code = "000", - message = "정상" - }, - data = new Version() - { - os_type = (version.os_type == "VO01" ? "I" : (version.os_type == "VO02" ? "A" : "W")), - final_ver = version.final_ver, - force_ver = version.force_ver, - dev_ver = version.dev_ver, - choice_update_yn = version.choice_update_yn - } - }; - - string jsonString = JsonSerializer.Serialize(response); - - // return Ok(jsonString); - return Ok(response.JsonToString()); - } - catch (Exception ex) - { - Console.WriteLine($"{ex.Message}\n{ex.StackTrace}"); - return StatusCode(500, APIResponse.UnknownError()); - } + if (string.IsNullOrEmpty(type)) return BadRequest(APIResponse.InvalidInputError()); + string summary = _repositoryService.ReadSummary(typeof(AppController), "GetHeaderValue"); + var result = await _appService.GetVersion(summary, type); + return Ok(result); } - - - [HttpGet("auth")] - [CustomOperation("서버 접근 권한 확인", "서버 기능을 사용하기 위한 접근에 대해 권한 확인", "시스템")] - public async Task AuthProgram([FromBody] AuthKey keys) - { - string summary = String.Empty; - - try - { - summary = _repositoryService.ReadSummary(typeof(AppController), "AuthProgram"); - } - catch (Exception ex) - { - _logger.LogError($"[{summary}] : {ex.Message}"); - return StatusCode(500, APIResponse.UnknownError(ex.Message)); - } - - return Ok(APIResponse.Send("000", "OK", Empty)); - } - - + [HttpGet("retryAccess")] [CustomOperation("엑세스 토큰 재발급", "액세스 토큰 재발급 동작 수행", "시스템")] public async Task RetryAccessToken(string refresh) { - string summary = String.Empty; - - try - { - summary = _repositoryService.ReadSummary(typeof(AppController), "AuthProgram"); - var refreshToken = await _dbContext.RefreshToken - .FirstOrDefaultAsync(t => t.refresh_token == refresh); - if (refreshToken == null) throw new TokenException($"[{summary}] : 리프레시 토큰의 문제"); - if (refreshToken.revoke_Date < DateTime.Now) throw new TokenException($"[{summary}] : 리프레시 토큰 만료"); - if (refreshToken.expire_date < DateTime.Now) throw new TokenException($"[{summary}] : 리프레시 토큰 폐기"); - string access = _jwtTokenService.GenerateJwtToken(refreshToken.uid); - return Ok(APIResponse.Send("000", $"[{summary}], 토큰 생성 완료", - new { - access = access - })); - } - catch (TokenException ex) - { - _logger.LogError($"[{summary}] : {ex.Message}"); - return Ok(APIResponse.InvalidInputError(ex.Message)); - } - catch (Exception ex) - { - _logger.LogError($"[{summary}] : {ex.Message}"); - return StatusCode(500, APIResponse.UnknownError(ex.Message)); - } - - + if (string.IsNullOrEmpty(refresh)) return BadRequest(APIResponse.InvalidInputError()); + string summary = _repositoryService.ReadSummary(typeof(AppController), "RetryAccessToken"); + var result = await _appService.RetryAccess(summary, refresh); + return Ok(result); } diff --git a/Program/Controllers/V1/UserController.cs b/Program/Controllers/V1/UserController.cs index e811a91..c03420e 100644 --- a/Program/Controllers/V1/UserController.cs +++ b/Program/Controllers/V1/UserController.cs @@ -51,7 +51,7 @@ namespace Back.Program.Controllers.V1 if (!ModelState.IsValid) return BadRequest(APIResponse.InvalidInputError()); string summary = _repositoryService.ReadSummary(typeof(UserController), "GetUserData"); - var result = _userService.GetUser(summary, token); + var result = await _userService.GetUser(summary, token); return Ok(result); } @@ -77,7 +77,7 @@ namespace Back.Program.Controllers.V1 if (!ModelState.IsValid) return BadRequest(APIResponse.InvalidInputError()); string summary = _repositoryService.ReadSummary(typeof(UserController), "UserRegister"); - var result = _userService.Register(summary, request); + var result = await _userService.Register(summary, request); return Ok(result); } @@ -114,7 +114,7 @@ namespace Back.Program.Controllers.V1 if (!ModelState.IsValid) return BadRequest(APIResponse.InvalidInputError()); string summary = _repositoryService.ReadSummary(typeof(UserController), "ReadAcademyInfo"); - var result = _userService.GetAcademy(summary, token); + var result = await _userService.GetAcademy(summary, token); return Ok(result); } } diff --git a/Program/Repositories/V1/AppRepository.cs b/Program/Repositories/V1/AppRepository.cs new file mode 100644 index 0000000..d623422 --- /dev/null +++ b/Program/Repositories/V1/AppRepository.cs @@ -0,0 +1,35 @@ +using Back.Program.Common.Data; +using Back.Program.Common.Model; +using Back.Program.Models.Entities; +using Back.Program.Repositories.V1.Interfaces; + +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using Version = Back.Program.Models.Entities.Version; + +namespace Back.Program.Repositories.V1; + +public class AppRepository: IAppRepository +{ + private readonly AppDbContext _context; + + public AppRepository(AppDbContext context) + { + _context = context; + } + + public async Task FindHeader(string specific) + { + return await _context.APIHeader.FirstOrDefaultAsync(h => h.specific_id == specific); + } + + public async Task FindVersion(string type) + { + return await _context.Version.FirstOrDefaultAsync(v => v.os_type == (type =="I" ? "VO01" : "VO02")); + } + + public async Task FindRefreshToken(string refresh) + { + return await _context.RefreshToken.FirstOrDefaultAsync(r => r.refresh_token == refresh); + } +} \ No newline at end of file diff --git a/Program/Repositories/V1/Interfaces/IAppRepository.cs b/Program/Repositories/V1/Interfaces/IAppRepository.cs new file mode 100644 index 0000000..3e71f44 --- /dev/null +++ b/Program/Repositories/V1/Interfaces/IAppRepository.cs @@ -0,0 +1,11 @@ +using Back.Program.Common.Model; +using Version = Back.Program.Models.Entities.Version; + +namespace Back.Program.Repositories.V1.Interfaces; + +public interface IAppRepository +{ + Task FindHeader(string specific); + Task FindVersion(string type); + Task FindRefreshToken(string refresh); +} \ No newline at end of file diff --git a/Program/Repositories/V1/Interfaces/ILogRepository.cs b/Program/Repositories/V1/Interfaces/ILogRepository.cs index d8d1ff8..7b526af 100644 --- a/Program/Repositories/V1/Interfaces/ILogRepository.cs +++ b/Program/Repositories/V1/Interfaces/ILogRepository.cs @@ -5,4 +5,5 @@ namespace Back.Program.Repositories.V1.Interfaces; public interface ILogRepository { Task SaveLogUser(LogUser log); + Task SaveLogProjrct(LogProject log); } \ No newline at end of file diff --git a/Program/Repositories/V1/LogRepository.cs b/Program/Repositories/V1/LogRepository.cs index d8430e3..396e968 100644 --- a/Program/Repositories/V1/LogRepository.cs +++ b/Program/Repositories/V1/LogRepository.cs @@ -21,4 +21,10 @@ public class LogRepository: ILogRepository _context.LogUser.Add(log); return await _context.SaveChangesAsync() > 0; } + + public async Task SaveLogProjrct(LogProject log) + { + _context.LogProject.Add(log); + return await _context.SaveChangesAsync() > 0; + } } \ No newline at end of file diff --git a/Program/Services/V1/AppService.cs b/Program/Services/V1/AppService.cs new file mode 100644 index 0000000..ae6922b --- /dev/null +++ b/Program/Services/V1/AppService.cs @@ -0,0 +1,167 @@ +using System.Security.Cryptography; +using System.Text; +using Back.Program.Common.Auth; +using Back.Program.Common.Model; +using Back.Program.Models.Entities; +using Back.Program.Repositories.V1.Interfaces; +using Back.Program.Services.V1.Interfaces; +using Version = Back.Program.Models.Entities.Version; + +namespace Back.Program.Services.V1; + +public class AppService: IAppService +{ + private readonly ILogger _logger; + private readonly IRepositoryService _repositoryService; + private readonly JwtTokenService _jwtTokenService; + private readonly ILogRepository _logRepository; + private readonly IAppRepository _appRepository; + + public AppService(ILogger logger, IRepositoryService repositoryService, + JwtTokenService jwtTokenService, ILogRepository logRepository, IAppRepository appRepository) + { + _logger = logger; + _repositoryService = repositoryService; + _jwtTokenService = jwtTokenService; + _logRepository = logRepository; + _appRepository = appRepository; + } + + public async Task> GetHeader(string summary, string type, string specific, string project) + { + bool valid = false; + switch (type) + { + case "I": + if (project == "me.myds.ipstein.acamate.AcaMate") valid = true; + break; + case "A": + break; + case "W": + break; + default: + return APIResponse.InvalidInputError($"[{summary}], 타입 에러"); + break; + } + + if (valid) + { + var apiHeader = await _appRepository.FindHeader(specific); + + string nowTime = DateTime.Now.ToString("o"); + string combineText = $"{project}_{nowTime}_{specific}"; + string headerValue = KeyGenerator(combineText); + if (apiHeader != null) + { + if (DateTime.Now - apiHeader.connect_date > TimeSpan.FromHours(24)) + { + _logger.LogInformation($"[{summary}] : 해당 키 유효기간 경과"); + apiHeader.h_value = headerValue; + apiHeader.connect_date = DateTime.Now; + if (await _repositoryService.SaveData(apiHeader)) + { + // 새로 업뎃해서 저장 + if(await _logRepository.SaveLogProjrct( new LogProject { create_date = DateTime.Now , log = $"[{summary}] : 해당 키 유효시간 만료로 인한 새 키 부여"})) + { + _logger.LogInformation($"[{summary}] : 성공"); + } + else + { + _logger.LogInformation($"[{summary}] : 성공 - 로그 저장 실패 "); + } + return APIResponse.Send("000", $"[{summary}], 정상", new { header = headerValue }); + } + else + { + // 저장이 안되었다? == 서버 오류 + return APIResponse.InternalSeverError(); + } + } + else + { + return APIResponse.Send("000", $"[{summary}], 정상", new { header = apiHeader.h_value }); + // 유효기간 경과도 없고 정상적으로 잘 불러짐 + } + } + else + { + // API 저장된 거 없으니 저장 합시다. + var newHeader = new APIHeader + { + h_key = type == "I" ? "iOS_AM_Connect_Key" + : (type == "A" ? "And_AM_Connect_Key" + : (type == "W" ? "Web_AM_Connect_Key": throw new Exception("ERROR"))), + h_value = headerValue, + connect_date = DateTime.Now, + specific_id = specific + }; + + if (await _repositoryService.SaveData(newHeader)) + { + // 새로 업뎃해서 저장 + if(await _logRepository.SaveLogProjrct( new LogProject { create_date = DateTime.Now , log = $"[{summary}] : 새 기기 등록으로 인한 새 키 부여"})) + { + _logger.LogInformation($"[{summary}] : 성공"); + } + else + { + _logger.LogInformation($"[{summary}] : 성공 - 로그 저장 실패 "); + } + return APIResponse.Send("000", $"[{summary}], 정상", new { header = headerValue }); + } + else + { + // 저장이 안되었다? == 서버 오류 + return APIResponse.InternalSeverError(); + } + } + } + else + { + // 헤더 없단 소리 == 문제 있음 + return APIResponse.InvalidInputError(); + } + } + + private string KeyGenerator(string combineText) + { + using (SHA256 sha256 = SHA256.Create()) + { + byte[] hashBytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(combineText)); + return BitConverter.ToString(hashBytes).Replace("-", string.Empty).ToLowerInvariant(); + } + } + + + public async Task> GetVersion(string summary, string type) + { + var version = await _appRepository.FindVersion(type); + if (version != null) + { + var sendVersion = new Version { + os_type = (version.os_type == "VO01" ? "I" : (version.os_type == "VO02" ? "A" : "W")), + final_ver = version.final_ver, + force_ver = version.force_ver, + dev_ver = version.dev_ver, + choice_update_yn = version.choice_update_yn + }; + return APIResponse.Send("000", $"[{summary}], 정상", sendVersion); + } + else + { + return APIResponse.NotFoundError(); + } + } + + public async Task> RetryAccess(string summary, string refresh) + { + var refreshToken = await _appRepository.FindRefreshToken(refresh); + if (refreshToken == null) return APIResponse.InvalidInputError($"[{summary}] : 리프레시 토큰 문제"); + if (refreshToken.revoke_Date < DateTime.Now) return APIResponse.InvalidInputError($"[{summary}] : 리프레시 토큰 만료"); + if (refreshToken.expire_date < DateTime.Now) return APIResponse.InvalidInputError($"[{summary}] : 리프레시 토큰 폐기"); + + string access = _jwtTokenService.GenerateJwtToken(refreshToken.uid); + return APIResponse.Send("000", $"[{summary}], 토큰 생성 완료", new { accsee = access }); + } + +} \ No newline at end of file diff --git a/Program/Services/V1/Interfaces/IAppService.cs b/Program/Services/V1/Interfaces/IAppService.cs new file mode 100644 index 0000000..94215e3 --- /dev/null +++ b/Program/Services/V1/Interfaces/IAppService.cs @@ -0,0 +1,11 @@ +using Back.Program.Common.Model; + +namespace Back.Program.Services.V1.Interfaces; + +public interface IAppService +{ + Task> GetHeader(string summary, string type, string specific, string project); + Task> GetVersion(string summary, string type); + Task> RetryAccess(string summary, string refresh); + +} \ No newline at end of file -- 2.45.1 From 0eb58a1edf2c5eaf2d938be1dcef5756a294bfa4 Mon Sep 17 00:00:00 2001 From: Seonkyu_Kim Date: Fri, 4 Apr 2025 17:56:16 +0900 Subject: [PATCH 3/6] =?UTF-8?q?[=E2=99=BB=EF=B8=8F]=20=EC=A0=84=EC=B2=B4?= =?UTF-8?q?=EC=A0=81=EC=9D=B8=20=EA=B5=AC=EC=A1=B0=20=EB=A6=AC=ED=8C=A9?= =?UTF-8?q?=ED=86=A0=EB=A7=81=20=EC=A7=84=ED=96=89=EC=A4=912=20(PushContro?= =?UTF-8?q?ller=20part)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Program.cs | 2 +- .../V1/Interfaces/ILogRepository.cs | 5 ++- .../V1/Interfaces/IPushRepository.cs | 14 +++++++ Program/Repositories/V1/LogRepository.cs | 8 +++- Program/Repositories/V1/PushRepository.cs | 42 +++++++++++++++++++ Program/Services/V1/AppService.cs | 4 +- Program/Services/V1/InMemoryPushQueue.cs | 5 ++- .../Services/V1/Interfaces/IPushService.cs | 8 ++++ Program/Services/V1/PushService.cs | 41 ++---------------- 9 files changed, 85 insertions(+), 44 deletions(-) create mode 100644 Program/Repositories/V1/Interfaces/IPushRepository.cs create mode 100644 Program/Repositories/V1/PushRepository.cs create mode 100644 Program/Services/V1/Interfaces/IPushService.cs diff --git a/Program.cs b/Program.cs index 2b69b6f..a35dfbe 100644 --- a/Program.cs +++ b/Program.cs @@ -90,7 +90,7 @@ builder.Services.AddAuthentication(options => builder.Services.Configure(builder.Configuration.GetSection("PushFileSetting")); // HttpClientFactory를 이용한 ApnsPushService 등록 (핸들러에 인증서 추가) -builder.Services.AddHttpClient(client => +builder.Services.AddHttpClient(client => { var settings = builder.Configuration.GetSection("PushFileSetting").Get(); client.BaseAddress = new Uri(settings.uri); diff --git a/Program/Repositories/V1/Interfaces/ILogRepository.cs b/Program/Repositories/V1/Interfaces/ILogRepository.cs index 7b526af..413a6fd 100644 --- a/Program/Repositories/V1/Interfaces/ILogRepository.cs +++ b/Program/Repositories/V1/Interfaces/ILogRepository.cs @@ -5,5 +5,8 @@ namespace Back.Program.Repositories.V1.Interfaces; public interface ILogRepository { Task SaveLogUser(LogUser log); - Task SaveLogProjrct(LogProject log); + Task SaveLogProject(LogProject log); + Task SaveLogPush(LogPush log); + + } \ No newline at end of file diff --git a/Program/Repositories/V1/Interfaces/IPushRepository.cs b/Program/Repositories/V1/Interfaces/IPushRepository.cs new file mode 100644 index 0000000..1d3037e --- /dev/null +++ b/Program/Repositories/V1/Interfaces/IPushRepository.cs @@ -0,0 +1,14 @@ +using Back.Program.Models.Entities; + +namespace Back.Program.Repositories.V1.Interfaces; + +public interface IPushRepository +{ + Task FindAcademy(string bid); + Task FindPushPayload(string bid, string? pid, string? category); + Task FindUserAcademy(string uid, string bid); + Task FindPushCabinet(string uid); + Task FindUser(string uid); + + +} \ No newline at end of file diff --git a/Program/Repositories/V1/LogRepository.cs b/Program/Repositories/V1/LogRepository.cs index 396e968..4dead60 100644 --- a/Program/Repositories/V1/LogRepository.cs +++ b/Program/Repositories/V1/LogRepository.cs @@ -22,9 +22,15 @@ public class LogRepository: ILogRepository return await _context.SaveChangesAsync() > 0; } - public async Task SaveLogProjrct(LogProject log) + public async Task SaveLogProject(LogProject log) { _context.LogProject.Add(log); return await _context.SaveChangesAsync() > 0; } + + public async Task SaveLogPush(LogPush log) + { + _context.LogPush.Add(log); + return await _context.SaveChangesAsync() > 0; + } } \ No newline at end of file diff --git a/Program/Repositories/V1/PushRepository.cs b/Program/Repositories/V1/PushRepository.cs new file mode 100644 index 0000000..799cf61 --- /dev/null +++ b/Program/Repositories/V1/PushRepository.cs @@ -0,0 +1,42 @@ +using Back.Program.Common.Data; +using Back.Program.Models.Entities; +using Back.Program.Repositories.V1.Interfaces; +using Microsoft.EntityFrameworkCore; + +namespace Back.Program.Repositories.V1; + +public class PushRepository: IPushRepository +{ + private readonly AppDbContext _context; + + public PushRepository(AppDbContext context) + { + _context = context; + } + + + public async Task FindAcademy(string bid, string? pid, string? category) + { + return await _context.Academy.FirstOrDefaultAsync(a => a.bid == bid); + } + + public async Task FindPushPayload(string bid, string pid, string? category) + { + throw new NotImplementedException(); + } + + public async Task FindUserAcademy(string uid, string bid) + { + throw new NotImplementedException(); + } + + public async Task FindPushCabinet(string uid) + { + throw new NotImplementedException(); + } + + public async Task FindUser(string uid) + { + throw new NotImplementedException(); + } +} \ No newline at end of file diff --git a/Program/Services/V1/AppService.cs b/Program/Services/V1/AppService.cs index ae6922b..6bc33fb 100644 --- a/Program/Services/V1/AppService.cs +++ b/Program/Services/V1/AppService.cs @@ -61,7 +61,7 @@ public class AppService: IAppService if (await _repositoryService.SaveData(apiHeader)) { // 새로 업뎃해서 저장 - if(await _logRepository.SaveLogProjrct( new LogProject { create_date = DateTime.Now , log = $"[{summary}] : 해당 키 유효시간 만료로 인한 새 키 부여"})) + if(await _logRepository.SaveLogProject( new LogProject { create_date = DateTime.Now , log = $"[{summary}] : 해당 키 유효시간 만료로 인한 새 키 부여"})) { _logger.LogInformation($"[{summary}] : 성공"); } @@ -99,7 +99,7 @@ public class AppService: IAppService if (await _repositoryService.SaveData(newHeader)) { // 새로 업뎃해서 저장 - if(await _logRepository.SaveLogProjrct( new LogProject { create_date = DateTime.Now , log = $"[{summary}] : 새 기기 등록으로 인한 새 키 부여"})) + if(await _logRepository.SaveLogProject( new LogProject { create_date = DateTime.Now , log = $"[{summary}] : 새 기기 등록으로 인한 새 키 부여"})) { _logger.LogInformation($"[{summary}] : 성공"); } diff --git a/Program/Services/V1/InMemoryPushQueue.cs b/Program/Services/V1/InMemoryPushQueue.cs index 6e6f351..fc04058 100644 --- a/Program/Services/V1/InMemoryPushQueue.cs +++ b/Program/Services/V1/InMemoryPushQueue.cs @@ -1,5 +1,6 @@ using System.Collections.Concurrent; using Back.Program.Models.Entities; +using Back.Program.Services.V1.Interfaces; namespace Back.Program.Services.V1 { @@ -33,9 +34,9 @@ namespace Back.Program.Services.V1 public class PushBackgroundService : BackgroundService { private readonly IPushQueue _queue; - private readonly IApnsPushService _pushService; + private readonly IPushService _pushService; - public PushBackgroundService(IPushQueue queue, IApnsPushService pushService) + public PushBackgroundService(IPushQueue queue, IPushService pushService) { _queue = queue; _pushService = pushService; diff --git a/Program/Services/V1/Interfaces/IPushService.cs b/Program/Services/V1/Interfaces/IPushService.cs new file mode 100644 index 0000000..32546d5 --- /dev/null +++ b/Program/Services/V1/Interfaces/IPushService.cs @@ -0,0 +1,8 @@ +using Back.Program.Models.Entities; + +namespace Back.Program.Services.V1.Interfaces; + +public interface IPushService +{ + Task SendPushNotificationAsync(string deviceToken, Payload payload); +} \ No newline at end of file diff --git a/Program/Services/V1/PushService.cs b/Program/Services/V1/PushService.cs index d90d12d..bc578da 100644 --- a/Program/Services/V1/PushService.cs +++ b/Program/Services/V1/PushService.cs @@ -2,22 +2,19 @@ using System.Text; using System.Text.Json; using Back.Program.Common.Model; using Back.Program.Models.Entities; +using Back.Program.Services.V1.Interfaces; using Microsoft.Extensions.Options; using Polly; using Version = System.Version; namespace Back.Program.Services.V1 { - public interface IApnsPushService - { - Task SendPushNotificationAsync(string deviceToken, Payload payload); - } - public class ApnsPushService: IApnsPushService + public class PushService: IPushService { private readonly HttpClient _httpClient; private readonly PushFileSetting _setting; - public ApnsPushService(HttpClient httpClient, IOptions options) + public PushService(HttpClient httpClient, IOptions options) { _httpClient = httpClient; _setting = options.Value; @@ -31,23 +28,6 @@ namespace Back.Program.Services.V1 throw new FileNotFoundException("[푸시] : p12 관련 파일 확인 필요"); var jsonPayload = JsonSerializer.Serialize(payload); - // var keys = - // JsonSerializer.Deserialize>(await File.ReadAllTextAsync(_setting.p12PWPath)) - // ?? throw new FileContentNotFoundException("[푸시] : 파일 내부의 값을 읽어오지 못함"); - // - // try - // { - // var handler = new HttpClientHandler(); - // handler.ClientCertificates - // .Add(new X509Certificate2(_setting.p12Path, keys["Password"])); - // handler.SslProtocols = System.Security.Authentication.SslProtocols.Tls12; - // - // - // using var client = new HttpClient(handler) - // { - // BaseAddress = new Uri(_setting.uri), - // Timeout = TimeSpan.FromSeconds(60) - // }; var request = new HttpRequestMessage(HttpMethod.Post, $"/3/device/{deviceToken}") { @@ -73,20 +53,7 @@ namespace Back.Program.Services.V1 } }); - // var response = await client.SendAsync(request); - // - // if (response.IsSuccessStatusCode) return true; - // else - // { - // var errorContent = await response.Content.ReadAsStringAsync(); - // throw new ServiceConnectionFailedException($"[푸시] : APNS 통신 실패 - {errorContent}"); - // } - // } - // catch (HttpRequestException httpEx) - // { - // Console.WriteLine($"HttpRequestException: {httpEx.Message}"); - // throw new ServiceConnectionFailedException($"[푸시] : APNS 통신 실패 - {httpEx.Message}"); - // } } + } } \ No newline at end of file -- 2.45.1 From c1ee151a9cd9fd1f4ca1e79f3802bd4239a717a8 Mon Sep 17 00:00:00 2001 From: Seonkyu_Kim Date: Mon, 7 Apr 2025 17:57:47 +0900 Subject: [PATCH 4/6] =?UTF-8?q?[=E2=99=BB=EF=B8=8F]=20=EC=A0=84=EC=B2=B4?= =?UTF-8?q?=EC=A0=81=EC=9D=B8=20=EA=B5=AC=EC=A1=B0=20=EB=A6=AC=ED=8C=A9?= =?UTF-8?q?=ED=86=A0=EB=A7=81=20=EC=A7=84=ED=96=89=EC=A4=913=20(PushContro?= =?UTF-8?q?ller=20part)=20-=20=EC=98=A4=EB=A5=98=20=EB=82=98=EC=84=9C=20?= =?UTF-8?q?=EB=B9=8C=EB=93=9C=20=EC=95=88=EB=90=98=EB=8A=94=20=EC=A4=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Program.cs | 3 + Program/Controllers/V1/PushController.cs | 378 ++---------------- Program/Controllers/V1/UserController.cs | 15 +- .../V1/Interfaces/IPushRepository.cs | 12 +- Program/Repositories/V1/PushRepository.cs | 39 +- Program/Services/V1/InMemoryPushQueue.cs | 12 +- .../Services/V1/Interfaces/IPushService.cs | 12 +- Program/Services/V1/PushService.cs | 273 ++++++++++++- 8 files changed, 357 insertions(+), 387 deletions(-) diff --git a/Program.cs b/Program.cs index a35dfbe..8fb8c6a 100644 --- a/Program.cs +++ b/Program.cs @@ -129,6 +129,9 @@ builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); + +builder.Services.AddScoped(); +builder.Services.AddScoped(); // builder.Services.AddScoped(); // // builder.Services.AddScoped(); diff --git a/Program/Controllers/V1/PushController.cs b/Program/Controllers/V1/PushController.cs index 0ee4783..60a5435 100644 --- a/Program/Controllers/V1/PushController.cs +++ b/Program/Controllers/V1/PushController.cs @@ -4,6 +4,7 @@ using Back.Program.Common.Data; using Back.Program.Common.Model; using Back.Program.Models.Entities; using Back.Program.Services.V1; +using Back.Program.Services.V1.Interfaces; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; @@ -15,19 +16,19 @@ namespace Back.Program.Controllers.V1 public class PushController : ControllerBase { private readonly ILogger _logger; - private readonly IPushQueue _pushQueue; - private readonly AppDbContext _dbContext; private readonly IRepositoryService _repositoryService; - private readonly JwtTokenService _jwtTokenService; - public PushController(ILogger logger, IPushQueue pushQueue, AppDbContext dbContext, IRepositoryService repositoryService, JwtTokenService jwtTokenService) + private readonly IPushQueue _pushQueue; + private readonly IPushService _pushService; + + public PushController(ILogger logger, IRepositoryService repositoryService, + IPushQueue pushQueue, IPushService pushService) { _logger = logger; - _pushQueue = pushQueue; - _dbContext = dbContext; _repositoryService = repositoryService; - _jwtTokenService = jwtTokenService; + _pushQueue = pushQueue; + _pushService = pushService; } - + // 추가 사항 // 카테고리 별 조회 하는 부분도 추가를 할 지 고민을 해야 할 것 같음 @@ -36,35 +37,10 @@ namespace Back.Program.Controllers.V1 [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(APIResponseStatus))] public async Task GetPush(string bid, string? pid, string? category) { - string summary = String.Empty; - - try - { - summary = _repositoryService.ReadSummary(typeof(PushController), "GetPush"); - - if (!(await _dbContext.Academy.AnyAsync(a=>a.bid == bid))) - return Ok(APIResponse.Send("100", $"[{summary}], 존재하지 않는 BID", Empty)); - - List pushData = new List(); - var pushQuery = _dbContext.DBPayload.Where(p => p.bid == bid); - if (pid != null) - pushQuery = pushQuery.Where(p=>p.pid == pid); - if (category != null) - pushQuery = pushQuery.Where(p=>p.category == category); - pushData = await pushQuery.ToListAsync(); - - if (pushData.Count > 0) - { - return Ok(APIResponse.Send("000", $"[{summary}, 정상", pushData)); - } - - return Ok(APIResponse.Send("001", $"[{summary}], PUSH 데이터 없음", Empty)); - } - catch (Exception ex) - { - _logger.LogError($"[{summary}] : {ex.Message}"); - return StatusCode(500, APIResponse.UnknownError()); - } + if (string.IsNullOrEmpty(bid)) return BadRequest(APIResponse.InvalidInputError()); + string summary = _repositoryService.ReadSummary(typeof(PushController), "GetPush"); + var result = await _pushService.GetPush(summary, bid, pid, category); + return Ok(result); } @@ -73,114 +49,11 @@ namespace Back.Program.Controllers.V1 [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(APIResponseStatus))] public async Task SendPush([FromBody] PushRequest pushRequest) { - string summary = String.Empty; - try { - summary = _repositoryService.ReadSummary(typeof(PushController), "SendPush"); - - if (!ModelState.IsValid) return BadRequest(APIResponse.InvalidInputError()); - - var payload = await _dbContext.DBPayload - .Where(p => p.pid == pushRequest.pid && p.bid == pushRequest.bid) - .Select(p => new Payload - { - aps = new Aps - { - alert = new Alert { title = p.title, body = p.body, subtitle = p.subtitle ?? "" }, - category = p.category - }, - pid = pushRequest.pid, - bid = pushRequest.bid, - content = pushRequest.content ?? (p.content ?? ""), - }) - .FirstOrDefaultAsync(); - - await Task.Run(async () => - { - if (payload == null) - throw new PushInvalidException("payload is NULL"); - - foreach (var uid in pushRequest.uids) - { - // 학원 내부에 해당 uid의 일원이 존재하는지 확인 - if ( - await _dbContext.UserAcademy - .Where(ua => ua.uid == uid && ua.bid == pushRequest.bid) - .AnyAsync() - ) - { - // 유저한테 온 모든 푸시에 대해서 안 읽은 것에 대한 뱃지 갯수 확인 - var badge = await _dbContext.PushCabinet - .Where(c => c.uid == uid - && c.check_yn == false) - .CountAsync(); - payload.aps.badge = badge + 1; - - // 푸시를 보내야 하니 푸시 토큰 확인 - var pushToken = await _dbContext.User - .Where(u => u.uid == uid) - .Select(u => u.push_token) - .FirstOrDefaultAsync() ?? ""; - - var pushCabinet = new PushCabinet - { - uid = uid, - bid = pushRequest.bid, - pid = pushRequest.pid, - send_date = DateTime.Now, - content = payload.content != "" ? payload.content : null, - }; - - var pushData = new PushData - { - pushToken = pushToken, - payload = payload - }; - - if (await _repositoryService.SaveData(pushCabinet)) - { - var logPush = new LogPush - { - bid = pushRequest.bid, - pid = pushRequest.pid, - create_date = DateTime.Now, - create_uid = "System", - log = $"[{summary}] : 푸시 캐비닛 저장 성공" - }; - if (await _repositoryService.SaveData(logPush)) - _logger.LogInformation($"[{summary}] : 로그 추가"); - } - - _pushQueue.Enqueue(pushData); - } - else - { - // 존재하지 않는 경우에는 지나가서 다른 uid 로 확인 하겠지 - var logPush = new LogPush - { - bid = pushRequest.bid, - pid = pushRequest.pid, - create_date = DateTime.Now, - create_uid = "System", - log = $"[{summary}] : 푸시 전송 실패" - }; - if (await _repositoryService.SaveData(logPush)) - _logger.LogInformation($"[{summary}] : 로그 추가"); - } - - } - }); - return Ok(APIResponse.Send("000", $"[{summary}], 정상", Empty)); - } - catch (PushInvalidException ex) - { - _logger.LogError($"[{summary}] : {ex.Message}"); - return Ok(APIResponse.Send("001", $"[{summary}], 푸시 송신 중 문제 발생",Empty)); - } - catch (Exception ex) - { - _logger.LogError($"[{summary}] : {ex.Message}"); - return StatusCode(500, APIResponse.UnknownError(ex.Message)); - } + if (!ModelState.IsValid) return BadRequest(APIResponse.InvalidInputError()); + string summary = _repositoryService.ReadSummary(typeof(PushController), "SendPush"); + + var result = await _pushService.SendPush(summary, pushRequest); + return Ok(result); } [HttpPost("set")] @@ -190,58 +63,10 @@ namespace Back.Program.Controllers.V1 { if (string.IsNullOrEmpty(token)) return BadRequest(APIResponse.InvalidInputError()); if (!ModelState.IsValid) return BadRequest(APIResponse.InvalidInputError()); - string summary = String.Empty; - string uid = String.Empty; - - try - { - if (token == "System") uid = "System"; - else - { - var validateToken = await _jwtTokenService.ValidateToken(token); - if (validateToken == null) return Ok(APIResponse.AccessExpireError()); - uid = validateToken.FindFirst(ClaimTypes.NameIdentifier)?.Value ?? string.Empty; - } + string summary = _repositoryService.ReadSummary(typeof(PushController), "SetPush"); - summary = _repositoryService.ReadSummary(typeof(PushController), "SetPush"); - var dbPayload = await _dbContext.DBPayload - .FirstOrDefaultAsync(p => p.pid == request.pid && p.bid == request.bid); - - - if (dbPayload != null) - { - var logPush = new LogPush - { - bid = dbPayload.bid, - pid = dbPayload.pid, - create_uid = uid, - create_date = DateTime.Now, - }; - - if (dbPayload.title != request.title && request.title != "") dbPayload.title = request.title; - if (dbPayload.body != request.body && request.body != "") dbPayload.body = request.body; - if (dbPayload.subtitle != request.subtitle) dbPayload.subtitle = request.subtitle; - if (dbPayload.alert_yn != request.alert_yn) dbPayload.alert_yn = request.alert_yn; - if (dbPayload.category != request.category && request.category != "") dbPayload.category = request.category; - if (dbPayload.content != request.content) dbPayload.content = request.content; - if (await _repositoryService.SaveData(dbPayload)) - { - logPush.log = $"[{summary} : 정상 변경"; - return Ok(APIResponse.Send("000", $"[{summary}], 정상", Empty)); - } - - // 로그를 이제 만들어서 추가를 해야 합니다. - if (await _repositoryService.SaveData(logPush)) - _logger.LogInformation($"[{summary}] : 로그 추가"); - } - - return Ok(APIResponse.Send("100", $"[{summary}], PID, BID 또는 Cabinet 오류", Empty)); - } - catch (Exception ex) - { - _logger.LogError($"[{summary}] : {ex.Message}"); - return StatusCode(500, APIResponse.UnknownError(ex.Message)); - } + var result = await _pushService.SetPush(summary, token, request); + return Ok(result); } [HttpPost("create")] @@ -251,76 +76,9 @@ namespace Back.Program.Controllers.V1 { if (string.IsNullOrEmpty(token)) return BadRequest(APIResponse.InvalidInputError()); if (!ModelState.IsValid) return BadRequest(APIResponse.InvalidInputError()); - string summary = String.Empty; - string uid = ""; - - Func randomLetter = (letters, count) => new string(Enumerable.Range(0, count).Select(_ => letters[new Random().Next(letters.Length)]).ToArray()); - var letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; - var digits = "0123456789"; - var frontLetters = $"{randomLetter(letters, 1)}{randomLetter(digits, 1)}{randomLetter(letters, 1)}"; - var afterLetters = $"{randomLetter(letters, 1)}{randomLetter(digits, 1)}{randomLetter(letters, 1)}"; - - try - { - if (token == "System") uid = "System"; - else { - var validateToken = await _jwtTokenService.ValidateToken(token); - if (validateToken == null) return Ok(APIResponse.AccessExpireError()); - uid = validateToken.FindFirst(ClaimTypes.NameIdentifier)?.Value ?? string.Empty; - } - - summary = _repositoryService.ReadSummary(typeof(PushController), "CreatePush"); - - if (await _dbContext.Academy.AnyAsync(a => a.bid == request.bid)) - { - DBPayload payload = new DBPayload - { - bid = request.bid, - pid = $"AP{DateTime.Now:yyyyMMdd}{frontLetters}{DateTime.Now:HHmmss}{afterLetters}", - title = request.title, - subtitle = request.subtitle, - body = request.body, - alert_yn = request.alert_yn, - category = request.category, - content = request.content, - }; - - if (await _repositoryService.SaveData(payload)) - { - var logPush = new LogPush - { - bid = payload.bid, - pid = payload.pid, - create_uid = uid, - create_date = DateTime.Now, - log = $"[{summary}] : 정상 생성" - }; - - // 로그를 이제 만들어서 추가를 해야 합니다. - if (await _repositoryService.SaveData(logPush)) - _logger.LogInformation($"[{summary}] : 로그 추가"); - - return Ok(APIResponse.Send("000", $"[{summary}], 정상", Empty)); - } - - } - return Ok(APIResponse.Send("100", $"[{summary}], 학원 정보(BID) 확인 불가", Empty)); - } - catch (TokenException tokenEx) - { - _logger.LogInformation($"[{summary}] : {tokenEx}"); - return Ok(APIResponse.Send("001", $"[{summary}], 토큰에 문제가 있음",Empty)); - } - catch (RefreshRevokeException refreshEx) - { - _logger.LogInformation($"[{summary}] : {refreshEx}"); - return Ok(APIResponse.Send("001", $"[{summary}], 폐기된 리프레시 토큰",Empty)); - } - catch (Exception ex) - { - _logger.LogError($"[{summary}] : {ex.Message}"); - return StatusCode(500, APIResponse.UnknownError()); - } + string summary = _repositoryService.ReadSummary(typeof(PushController), "CreatePush"); + var result = await _pushService.CreatePush(summary, token, request); + return Ok(result); } @@ -332,44 +90,9 @@ namespace Back.Program.Controllers.V1 { if (string.IsNullOrEmpty(token)) return BadRequest(APIResponse.InvalidInputError()); if (!ModelState.IsValid) return BadRequest(APIResponse.InvalidInputError()); - string uid = ""; - string summary = String.Empty; - - try - { - if (token == "System") uid = "System"; - else { - var validateToken = await _jwtTokenService.ValidateToken(token); - if (validateToken == null) return Ok(APIResponse.AccessExpireError()); - uid = validateToken.FindFirst(ClaimTypes.NameIdentifier)?.Value ?? string.Empty; - } - - summary = _repositoryService.ReadSummary(typeof(PushController), "DeletePush"); - - var payload = await _dbContext.DBPayload.FirstOrDefaultAsync(p => p.bid == bid && p.pid == pid); - if (payload == null) return Ok(APIResponse.Send("001", $"[{summary}], 삭제 할 PUSH 없음", Empty)); - if (!await _repositoryService.DeleteData(payload)) return Ok(APIResponse.Send("002", $"[{summary}], PUSH 삭제 실패", Empty)); - - // 로그를 이제 만들어서 추가를 해야 합니다. - var logPush = new LogPush - { - bid = bid, - pid = pid, - create_uid = uid, - create_date = DateTime.Now, - log = $"[{summary}] : {pid} 삭제 - {uid}" - }; - - // 로그를 이제 만들어서 추가를 해야 합니다. - if (await _repositoryService.SaveData(logPush)) _logger.LogInformation($"[{summary}] : 로그 추가"); - - return Ok(APIResponse.Send("000", $"[{summary}], 정상", Empty)); - } - catch (Exception ex) - { - _logger.LogError($"[{summary}] : {ex.Message}"); - return BadRequest(APIResponse.UnknownError(ex.Message)); - } + string summary = _repositoryService.ReadSummary(typeof(PushController), "DeletePush"); + var result = await _pushService.DeletePush(summary,token,bid,pid); + return Ok(result); } @@ -380,41 +103,9 @@ namespace Back.Program.Controllers.V1 { if (string.IsNullOrEmpty(token)) return BadRequest(APIResponse.InvalidInputError()); if (!ModelState.IsValid) return BadRequest(APIResponse.InvalidInputError()); - string uid = ""; - string summary = String.Empty; - try - { - if (token == "System") uid = "System"; - else { - var validateToken = await _jwtTokenService.ValidateToken(token); - if (validateToken == null) return Ok(APIResponse.AccessExpireError()); - uid = validateToken.FindFirst(ClaimTypes.NameIdentifier)?.Value ?? string.Empty; - } - - summary = _repositoryService.ReadSummary(typeof(PushController), "DeleteListPush"); - var cabinetPush = await _dbContext.PushCabinet.FirstOrDefaultAsync(c => c.id == id); - if (cabinetPush == null) return Ok(APIResponse.Send("001", $"[{summary}], 삭제 할 PUSH 없음", Empty)); - if (!await _repositoryService.DeleteData(cabinetPush)) - return Ok(APIResponse.Send("002", $"[{summary}], PUSH 삭제 실패", Empty)); - - // // 로그를 이제 만들어서 추가를 해야 합니다. - var logPush = new LogPush - { - bid = cabinetPush.bid, - pid = cabinetPush.pid, - create_uid = uid, - create_date = DateTime.Now, - log = $"[{summary}] : {cabinetPush.pid} 삭제 - {uid}" - }; - if (await _repositoryService.SaveData(logPush)) _logger.LogInformation($"[{summary}] : 로그 추가"); - - return Ok(APIResponse.Send("000", $"[{summary}], 정상", Empty)); - } - catch (Exception ex) - { - _logger.LogError($"[{summary}] : {ex.Message}"); - return BadRequest(APIResponse.UnknownError(ex.Message)); - } + string summary = _repositoryService.ReadSummary(typeof(PushController), "DeleteListPush"); + var result = await _pushService.DeleteListPush(summary, token, id); + return Ok(result); } @@ -426,9 +117,11 @@ namespace Back.Program.Controllers.V1 { if (string.IsNullOrEmpty(token)) return BadRequest(APIResponse.InvalidInputError()); if (!ModelState.IsValid) return BadRequest(APIResponse.InvalidInputError()); - string uid = ""; - string summary = String.Empty; + string summary = _repositoryService.ReadSummary(typeof(PushController), "SearchToUserPush"); + var result = await _pushService.SearchToUserPush(summary, token, size, request); + return Ok(result); + /* try { if (token == "System") uid = "System"; @@ -439,6 +132,8 @@ namespace Back.Program.Controllers.V1 } summary = _repositoryService.ReadSummary(typeof(PushController), "SearchToUserPush"); + + // 여기서부터 리팩토링 진행 if (request == null) { var pagedData = await _dbContext.PushCabinet.Where(c => c.uid == uid) @@ -463,6 +158,7 @@ namespace Back.Program.Controllers.V1 _logger.LogError($"[{summary}] : {ex.Message}"); return BadRequest(APIResponse.UnknownError(ex.Message)); } + */ } diff --git a/Program/Controllers/V1/UserController.cs b/Program/Controllers/V1/UserController.cs index c03420e..700ab93 100644 --- a/Program/Controllers/V1/UserController.cs +++ b/Program/Controllers/V1/UserController.cs @@ -18,30 +18,17 @@ namespace Back.Program.Controllers.V1 [ApiExplorerSettings(GroupName = "사용자")] public class UserController : ControllerBase { - private readonly AppDbContext _dbContext; private readonly ILogger _logger; - private readonly JwtTokenService _jwtTokenService; private readonly IRepositoryService _repositoryService; private readonly IUserService _userService; - public UserController(AppDbContext dbContext, ILogger logger, JwtTokenService jwtTokenService, + public UserController(ILogger logger, IRepositoryService repositoryService, IUserService userService) { - _dbContext = dbContext; _logger = logger; - _jwtTokenService = jwtTokenService; _repositoryService = repositoryService; _userService = userService; } - - - /* - catch (Exception ex) - { - _logger.LogInformation($"[{summary}] : {ex.Message}"); - return BadRequest(APIResponse.UnknownError(ex.Message)); - } - */ [HttpGet] [CustomOperation("회원 정보 조회", "회원 정보 조회 (자기자신)", "사용자")] diff --git a/Program/Repositories/V1/Interfaces/IPushRepository.cs b/Program/Repositories/V1/Interfaces/IPushRepository.cs index 1d3037e..f0ed436 100644 --- a/Program/Repositories/V1/Interfaces/IPushRepository.cs +++ b/Program/Repositories/V1/Interfaces/IPushRepository.cs @@ -4,11 +4,13 @@ namespace Back.Program.Repositories.V1.Interfaces; public interface IPushRepository { - Task FindAcademy(string bid); - Task FindPushPayload(string bid, string? pid, string? category); - Task FindUserAcademy(string uid, string bid); - Task FindPushCabinet(string uid); - Task FindUser(string uid); + Task FindAcademy(string bid); + Task> FindPushList(string bid, string? pid, string? category); + Task FindPushPayload(string bid, string pid); + Task FindUserAcademy(string uid, string bid); + Task CountBadge(string uid); + Task FindPushToken(string uid); + Task FindPushCabinet(int id); } \ No newline at end of file diff --git a/Program/Repositories/V1/PushRepository.cs b/Program/Repositories/V1/PushRepository.cs index 799cf61..ac1ab50 100644 --- a/Program/Repositories/V1/PushRepository.cs +++ b/Program/Repositories/V1/PushRepository.cs @@ -15,28 +15,47 @@ public class PushRepository: IPushRepository } - public async Task FindAcademy(string bid, string? pid, string? category) + public async Task FindAcademy(string bid) { - return await _context.Academy.FirstOrDefaultAsync(a => a.bid == bid); + return await _context.Academy.AnyAsync(a => a.bid == bid); } - public async Task FindPushPayload(string bid, string pid, string? category) + public async Task> FindPushList(string bid, string? pid, string? category) { - throw new NotImplementedException(); + var pushQuery = _context.DBPayload.Where(p => p.bid == bid); + if (pid != null) + pushQuery = pushQuery.Where(p => p.pid == pid); + if (category != null) + pushQuery = pushQuery.Where(p=>p.category == category); + return await pushQuery.ToListAsync(); + } - public async Task FindUserAcademy(string uid, string bid) + public async Task FindPushPayload(string bid, string pid) { - throw new NotImplementedException(); + return await _context.DBPayload.FirstOrDefaultAsync(p => p.bid == bid && p.pid == pid); } - public async Task FindPushCabinet(string uid) + public async Task FindUserAcademy(string uid, string bid) { - throw new NotImplementedException(); + return await _context.UserAcademy.AnyAsync(ua => ua.uid == uid && ua.bid == bid); } - public async Task FindUser(string uid) + public async Task CountBadge(string uid) { - throw new NotImplementedException(); + return await _context.PushCabinet.CountAsync(c => c.uid == uid && c.check_yn == false); + } + + public async Task FindPushToken(string uid) + { + return await _context.User + .Where(u => u.uid == uid) + .Select(u => u.push_token) + .FirstOrDefaultAsync(); + } + + public async Task FindPushCabinet(int id) + { + return await _context.PushCabinet.FirstOrDefaultAsync(c => c.id == id); } } \ No newline at end of file diff --git a/Program/Services/V1/InMemoryPushQueue.cs b/Program/Services/V1/InMemoryPushQueue.cs index fc04058..6621244 100644 --- a/Program/Services/V1/InMemoryPushQueue.cs +++ b/Program/Services/V1/InMemoryPushQueue.cs @@ -31,16 +31,10 @@ namespace Back.Program.Services.V1 } } - public class PushBackgroundService : BackgroundService + public class PushBackgroundService(IPushQueue queue, IPushService pushService) : BackgroundService { - private readonly IPushQueue _queue; - private readonly IPushService _pushService; - - public PushBackgroundService(IPushQueue queue, IPushService pushService) - { - _queue = queue; - _pushService = pushService; - } + private readonly IPushQueue _queue = queue; + private readonly IPushService _pushService = pushService; protected override async Task ExecuteAsync(CancellationToken stoppingToken) { diff --git a/Program/Services/V1/Interfaces/IPushService.cs b/Program/Services/V1/Interfaces/IPushService.cs index 32546d5..5028504 100644 --- a/Program/Services/V1/Interfaces/IPushService.cs +++ b/Program/Services/V1/Interfaces/IPushService.cs @@ -1,8 +1,18 @@ +using Back.Program.Common.Model; using Back.Program.Models.Entities; namespace Back.Program.Services.V1.Interfaces; public interface IPushService { + Task SendPushNotificationAsync(string deviceToken, Payload payload); -} \ No newline at end of file + Task> GetPush(string summary, string bid, string? pid, string? category); + Task> SendPush(string summary, PushRequest pushRequest); + Task> SetPush(string summary, string token, DBPayload request); + Task> CreatePush(string summary, string token, CreatePush request); + Task> DeletePush(string summary, string token, string bid, string pid); + Task> DeleteListPush(string summary, string token, int id); + Task> SearchToUserPush(string summary, string token, int size, PushCabinet? request); + +} diff --git a/Program/Services/V1/PushService.cs b/Program/Services/V1/PushService.cs index bc578da..1ea5bd2 100644 --- a/Program/Services/V1/PushService.cs +++ b/Program/Services/V1/PushService.cs @@ -1,34 +1,53 @@ +using System.Security.Claims; using System.Text; using System.Text.Json; +using Back.Program.Common.Auth; using Back.Program.Common.Model; using Back.Program.Models.Entities; +using Back.Program.Repositories.V1; +using Back.Program.Repositories.V1.Interfaces; using Back.Program.Services.V1.Interfaces; +using Microsoft.AspNetCore.Http.HttpResults; using Microsoft.Extensions.Options; using Polly; using Version = System.Version; namespace Back.Program.Services.V1 { - public class PushService: IPushService + public class PushService : IPushService { + private readonly ILogger _logger; private readonly HttpClient _httpClient; private readonly PushFileSetting _setting; + private readonly JwtTokenService _jwtTokenService; + private readonly IRepositoryService _repositoryService; + private readonly IPushQueue _pushQueue; - public PushService(HttpClient httpClient, IOptions options) + private readonly ILogRepository _logRepository; + private readonly IPushRepository _pushRepository; + + public PushService(ILogger logger, HttpClient httpClient, IOptions options, + IPushRepository pushRepository, IRepositoryService repositoryService, IPushQueue pushQueue, + ILogRepository logRepository, JwtTokenService jwtTokenService) { + _logger = logger; _httpClient = httpClient; _setting = options.Value; + _pushRepository = pushRepository; + _repositoryService = repositoryService; + _pushQueue = pushQueue; + _logRepository = logRepository; + _jwtTokenService = jwtTokenService; } public async Task SendPushNotificationAsync(string deviceToken, Payload payload) { - // 존재 안하면 예외 던져 버리는거 if (!File.Exists(_setting.p12Path) || !File.Exists(_setting.p12PWPath)) throw new FileNotFoundException("[푸시] : p12 관련 파일 확인 필요"); - + var jsonPayload = JsonSerializer.Serialize(payload); - + var request = new HttpRequestMessage(HttpMethod.Post, $"/3/device/{deviceToken}") { Content = new StringContent(jsonPayload, Encoding.UTF8, "application/json"), @@ -38,9 +57,9 @@ namespace Back.Program.Services.V1 // 필수 헤더 추가 request.Headers.Add("apns-topic", _setting.apnsTopic); request.Headers.Add("apns-push-type", "alert"); - + var policy = Policy.Handle() - .WaitAndRetryAsync(3, retryAttempt => + .WaitAndRetryAsync(3, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt))); await policy.ExecuteAsync(async () => @@ -52,8 +71,248 @@ namespace Back.Program.Services.V1 throw new ServiceConnectionFailedException($"[푸시] : APNS 통신 실패 - {errorContent}"); } }); + } + + public async Task> GetPush(string summary, string bid, string? pid, string? category) + { + if (!(await _pushRepository.FindAcademy(bid))) + return APIResponse.Send("100", $"[{summary}], 존재하지 않는 BID", string.Empty); + + var pushData = await _pushRepository.FindPushList(bid, pid, category); + + if (pushData.Count > 0) + return APIResponse.Send("000", $"[{summary}, 정상", pushData); + return APIResponse.Send("001", $"[{summary}], PUSH 데이터 없음", string.Empty); + } + + public async Task> SendPush(string summary, PushRequest pushRequest) + { + var payload = await _pushRepository.FindPushPayload(pushRequest.bid, pushRequest.pid); + if (payload == null) return APIResponse.InvalidInputError($"[{summary}], 저장된 payload 없음"); + + var pushTasks = pushRequest.uids.Select(async uid => + { + if (!await _pushRepository.FindUserAcademy(uid, pushRequest.bid)) return; + var badge = await _pushRepository.CountBadge(uid); + var pushToken = await _pushRepository.FindPushToken(uid); + if (pushToken == null) return; + + var newPayload = new Payload + { + aps = new Aps + { + alert = new Alert + { title = payload.title, body = payload.body, subtitle = payload.subtitle ?? "" }, + category = payload.category, + badge = badge + 1 + }, + pid = pushRequest.pid, + bid = pushRequest.bid, + content = pushRequest.content ?? (payload.content ?? "") + }; + + var pushCabinet = new PushCabinet + { + uid = uid, + bid = pushRequest.bid, + pid = pushRequest.pid, + send_date = DateTime.Now, + content = newPayload.content != "" ? newPayload.content : null + }; + + var pushData = new PushData + { + pushToken = pushToken, + payload = newPayload + }; + + var log = new LogPush + { + bid = pushRequest.bid, + pid = pushRequest.pid, + create_date = DateTime.Now, + create_uid = "System", + log = "" + }; + + var saved = await _repositoryService.SaveData(pushCabinet); + log.log = saved ? $"[{summary}]: 푸시 캐비닛 저장 성공 및 푸시 전송 시도" : $"[{summary}]: 푸시 전송 실패"; + var logSaved = await _repositoryService.SaveData(log); + _logger.LogInformation($"[{summary}]: 캐비닛 저장 = {saved} : 로그 저장 = {logSaved}"); + + if(saved) _pushQueue.Enqueue(pushData); + }); + + await Task.WhenAll(pushTasks); + + return APIResponse.Send("000", $"[{summary}], 정상", string.Empty); + } + + + public async Task> SetPush(string summary, string token, DBPayload request) + { + string uid = String.Empty; + if (token == "System") uid = "System"; + else + { + var validToken = await _jwtTokenService.ValidateToken(token); + if (validToken == null) return APIResponse.AccessExpireError(); + uid = validToken.FindFirst(ClaimTypes.NameIdentifier)?.Value ?? string.Empty; + } + var payload = await _pushRepository.FindPushPayload(request.bid, request.pid); + if (payload == null) + return APIResponse.Send("100", $"[{summary}], PID, BID 또는 Cabinet 오류", string.Empty); + + var log = new LogPush + { + bid = request.bid, + pid = request.pid, + create_date = DateTime.Now, + create_uid = uid + }; + + if (payload.title != request.title && request.title != "") payload.title = request.title; + if (payload.body != request.body && request.body != "") payload.body = request.body; + if (payload.subtitle != request.subtitle) payload.subtitle = request.subtitle; + if (payload.alert_yn != request.alert_yn) payload.alert_yn = request.alert_yn; + if (payload.category != request.category) payload.category = request.category; + if (request.content != request.content) payload.content = request.content; + + var saved = (await _repositoryService.SaveData(payload)); + log.log = $"[{summary}] : 상태 = {saved}"; + var logSaved = await _repositoryService.SaveData(log); + _logger.LogInformation($"[{summary}]: 상태 = {saved} : 로그 저장 = {logSaved}"); + + if (!saved) return APIResponse.Send("001", $"[{summary}], 실패", string.Empty); + return APIResponse.Send("000", $"[{summary}], 정상", string.Empty); + } + + + public async Task> CreatePush(string summary, string token, CreatePush request) + { + Func randomLetter = (letters, count) => new string(Enumerable.Range(0, count).Select(_ => letters[new Random().Next(letters.Length)]).ToArray()); + var letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + var digits = "0123456789"; + var frontLetters = $"{randomLetter(letters, 1)}{randomLetter(digits, 1)}{randomLetter(letters, 1)}"; + var afterLetters = $"{randomLetter(letters, 1)}{randomLetter(digits, 1)}{randomLetter(letters, 1)}"; + + string uid = String.Empty; + if (token == "System") uid = "System"; + else + { + var validToken = await _jwtTokenService.ValidateToken(token); + if (validToken == null) return APIResponse.AccessExpireError(); + uid = validToken.FindFirst(ClaimTypes.NameIdentifier)?.Value ?? string.Empty; + } + + if (!(await _pushRepository.FindAcademy(request.bid))) + return APIResponse.Send("100", $"[{summary}], 학원 정보(BID) 확인 불가", string.Empty); + + DBPayload payload = new DBPayload + { + bid = request.bid, + pid = $"AP{DateTime.Now:yyyyMMdd}{frontLetters}{DateTime.Now:HHmmss}{afterLetters}", + title = request.title, + subtitle = request.subtitle, + body = request.body, + alert_yn = request.alert_yn, + category = request.category, + content = request.content, + }; + + var log = new LogPush + { + bid = payload.bid, + pid = payload.pid, + create_date = DateTime.Now, + create_uid = uid + }; + + var saved = await _repositoryService.SaveData(payload); + log.log = $"[{summary}] : 푸시 생성 = {saved}"; + var logSaved = await _repositoryService.SaveData(log); + _logger.LogInformation($"[{summary}]: 푸시 생성 = {saved} : 로그 저장 = {logSaved}"); + if (!saved) return APIResponse.Send("001", $"[{summary}], 실패", string.Empty); + return APIResponse.Send("000", $"[{summary}], 정상", string.Empty); + } + public async Task> DeletePush(string summary, string token, string bid, string pid) + { + string uid = String.Empty; + if (token == "System") uid = "System"; + else + { + var validToken = await _jwtTokenService.ValidateToken(token); + if (validToken == null) return APIResponse.AccessExpireError(); + uid = validToken.FindFirst(ClaimTypes.NameIdentifier)?.Value ?? string.Empty; + } + + var payload = await _pushRepository.FindPushPayload(bid, pid); + if (payload == null) return APIResponse.Send("001", $"[{summary}], 삭제 할 PUSH 없음", string.Empty); + + var log = new LogPush + { + bid = payload.bid, + pid = payload.pid, + create_date = DateTime.Now, + create_uid = uid + }; + var delete = await _repositoryService.DeleteData(payload); + + log.log = $"[{summary}] : 삭제 = {delete}"; + var logSaved = await _repositoryService.SaveData(log); + _logger.LogInformation($"[{summary}]: 삭제 = {delete} : 로그 저장 = {logSaved}"); + if (!delete) return APIResponse.Send("002", $"[{summary}], 실패", string.Empty); + return APIResponse.Send("000", $"[{summary}], 정상", string.Empty); + } + + public async Task> DeleteListPush(string summary, string token, int id) + { + string uid = String.Empty; + if (token == "System") uid = "System"; + else + { + var validToken = await _jwtTokenService.ValidateToken(token); + if (validToken == null) return APIResponse.AccessExpireError(); + uid = validToken.FindFirst(ClaimTypes.NameIdentifier)?.Value ?? string.Empty; + } + + var cabinet = await _pushRepository.FindPushCabinet(id); + if (cabinet == null) return APIResponse.Send("001", $"[{summary}], 삭제 할 PUSH 없음", string.Empty); + + + var log = new LogPush + { + bid = cabinet.bid, + pid = cabinet.pid, + create_date = DateTime.Now, + create_uid = uid + }; + + var delete = await _repositoryService.DeleteData(cabinet); + log.log = $"[{summary}] : {cabinet.pid} 삭제 = {delete}"; + var logSaved = await _repositoryService.SaveData(log); + _logger.LogInformation($"[{summary}]: {cabinet.pid} 삭제 = {delete} : 로그 저장 = {logSaved}"); + if (!delete) return APIResponse.Send("002", $"[{summary}], 실패", string.Empty); + return APIResponse.Send("000", $"[{summary}], 정상", string.Empty); + } + + + public async Task> SearchToUserPush(string summary, string token, int size, PushCabinet? request) + { + string uid = String.Empty; + if (token == "System") uid = "System"; + else + { + var validToken = await _jwtTokenService.ValidateToken(token); + if (validToken == null) return APIResponse.AccessExpireError(); + uid = validToken.FindFirst(ClaimTypes.NameIdentifier)?.Value ?? string.Empty; + } + + return APIResponse.Send("000", $"[{summary}], 정상", string.Empty); + } } + } \ No newline at end of file -- 2.45.1 From 04f94ca835fe0f4752aef3379ca03837805c6d04 Mon Sep 17 00:00:00 2001 From: Seonkyu_Kim Date: Tue, 8 Apr 2025 17:17:12 +0900 Subject: [PATCH 5/6] =?UTF-8?q?[=E2=99=BB=EF=B8=8F]=20=EC=A0=84=EC=B2=B4?= =?UTF-8?q?=EC=A0=81=EC=9D=B8=20=EA=B5=AC=EC=A1=B0=20=EB=A6=AC=ED=8C=A9?= =?UTF-8?q?=ED=86=A0=EB=A7=81=20=EC=A7=84=ED=96=89=EC=A4=91=204=20(PushCon?= =?UTF-8?q?troller=20part)=20-=20=ED=91=B8=EC=8B=9C=EA=B9=8C=EC=A7=80=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20=EC=99=84=EB=A3=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Program.cs | 2 +- Program/Controllers/V1/PushController.cs | 38 ------------------- .../V1/Interfaces/IPushRepository.cs | 2 + Program/Repositories/V1/PushRepository.cs | 24 ++++++++++++ Program/Services/V1/InMemoryPushQueue.cs | 19 ++++++++-- .../Services/V1/Interfaces/IPushService.cs | 1 + Program/Services/V1/PushService.cs | 35 ++++++++++++++--- 7 files changed, 73 insertions(+), 48 deletions(-) diff --git a/Program.cs b/Program.cs index 8fb8c6a..7221288 100644 --- a/Program.cs +++ b/Program.cs @@ -130,7 +130,7 @@ builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); -builder.Services.AddScoped(); +// builder.Services.AddScoped(); builder.Services.AddScoped(); // builder.Services.AddScoped(); // // builder.Services.AddScoped(); diff --git a/Program/Controllers/V1/PushController.cs b/Program/Controllers/V1/PushController.cs index 60a5435..d8581c8 100644 --- a/Program/Controllers/V1/PushController.cs +++ b/Program/Controllers/V1/PushController.cs @@ -121,44 +121,6 @@ namespace Back.Program.Controllers.V1 var result = await _pushService.SearchToUserPush(summary, token, size, request); return Ok(result); - /* - try - { - if (token == "System") uid = "System"; - else { - var validateToken = await _jwtTokenService.ValidateToken(token); - if (validateToken == null) return Ok(APIResponse.AccessExpireError()); - uid = validateToken.FindFirst(ClaimTypes.NameIdentifier)?.Value ?? string.Empty; - } - - summary = _repositoryService.ReadSummary(typeof(PushController), "SearchToUserPush"); - - // 여기서부터 리팩토링 진행 - if (request == null) - { - var pagedData = await _dbContext.PushCabinet.Where(c => c.uid == uid) - .OrderBy(c=> c.send_date) - .Take(size) - .ToListAsync(); - - return Ok(APIResponse.Send("000", $"[{summary}], 정상", pagedData)); - } - else - { - var sort = await _dbContext.PushCabinet.Where(p=> p.id == request.id) - .Select(p => p.send_date).FirstOrDefaultAsync(); - var query = _dbContext.PushCabinet.OrderBy(c => c.send_date).AsQueryable(); - query = query.Where(c => c.send_date > sort); - var pagedData = await query.Take(size).ToListAsync(); - return Ok(APIResponse.Send("000", $"[{summary}], 정상", pagedData)); - } - } - catch (Exception ex) - { - _logger.LogError($"[{summary}] : {ex.Message}"); - return BadRequest(APIResponse.UnknownError(ex.Message)); - } - */ } diff --git a/Program/Repositories/V1/Interfaces/IPushRepository.cs b/Program/Repositories/V1/Interfaces/IPushRepository.cs index f0ed436..5672ab4 100644 --- a/Program/Repositories/V1/Interfaces/IPushRepository.cs +++ b/Program/Repositories/V1/Interfaces/IPushRepository.cs @@ -11,6 +11,8 @@ public interface IPushRepository Task CountBadge(string uid); Task FindPushToken(string uid); Task FindPushCabinet(int id); + Task> FindPushCabinet(string uid, int size); + Task> FindPushCabinet(int id, int size); } \ No newline at end of file diff --git a/Program/Repositories/V1/PushRepository.cs b/Program/Repositories/V1/PushRepository.cs index ac1ab50..acc7944 100644 --- a/Program/Repositories/V1/PushRepository.cs +++ b/Program/Repositories/V1/PushRepository.cs @@ -58,4 +58,28 @@ public class PushRepository: IPushRepository { return await _context.PushCabinet.FirstOrDefaultAsync(c => c.id == id); } + + public async Task> FindPushCabinet(string uid, int size) + { + return await _context.PushCabinet.Where(c => c.uid == uid) + .OrderBy(c => c.send_date) + .Take(size) + .ToListAsync(); + } + + public async Task> FindPushCabinet(int id, int size) + { + var sort = await _context.PushCabinet + .Where(p=> p.id == id) + .Select(p => p.send_date) + .FirstOrDefaultAsync(); + + if (sort == default) return new List(); + + return await _context.PushCabinet + .Where(c => c.send_date > sort) + .OrderBy(c => c.send_date) + .Take(size) + .ToListAsync(); + } } \ No newline at end of file diff --git a/Program/Services/V1/InMemoryPushQueue.cs b/Program/Services/V1/InMemoryPushQueue.cs index 6621244..dffd869 100644 --- a/Program/Services/V1/InMemoryPushQueue.cs +++ b/Program/Services/V1/InMemoryPushQueue.cs @@ -31,19 +31,29 @@ namespace Back.Program.Services.V1 } } - public class PushBackgroundService(IPushQueue queue, IPushService pushService) : BackgroundService + public class PushBackgroundService : BackgroundService { - private readonly IPushQueue _queue = queue; - private readonly IPushService _pushService = pushService; + private readonly IPushQueue _queue; + private readonly IServiceScopeFactory _scopeFactory; + + public PushBackgroundService(IPushQueue queue, IServiceScopeFactory scopeFactory) + { + _queue = queue; + _scopeFactory = scopeFactory; + } protected override async Task ExecuteAsync(CancellationToken stoppingToken) { while (!stoppingToken.IsCancellationRequested) { var pushData = await _queue.DequeueAsync(stoppingToken); + + using var scope = _scopeFactory.CreateScope(); + var pushService = scope.ServiceProvider.GetRequiredService(); + try { - await _pushService.SendPushNotificationAsync(pushData.pushToken, pushData.payload); + await pushService.SendPushNotificationAsync(pushData.pushToken, pushData.payload); } catch (Exception ex) { @@ -52,4 +62,5 @@ namespace Back.Program.Services.V1 } } } + } \ No newline at end of file diff --git a/Program/Services/V1/Interfaces/IPushService.cs b/Program/Services/V1/Interfaces/IPushService.cs index 5028504..4c081b2 100644 --- a/Program/Services/V1/Interfaces/IPushService.cs +++ b/Program/Services/V1/Interfaces/IPushService.cs @@ -14,5 +14,6 @@ public interface IPushService Task> DeletePush(string summary, string token, string bid, string pid); Task> DeleteListPush(string summary, string token, int id); Task> SearchToUserPush(string summary, string token, int size, PushCabinet? request); + } diff --git a/Program/Services/V1/PushService.cs b/Program/Services/V1/PushService.cs index 1ea5bd2..ed7e257 100644 --- a/Program/Services/V1/PushService.cs +++ b/Program/Services/V1/PushService.cs @@ -64,11 +64,27 @@ namespace Back.Program.Services.V1 await policy.ExecuteAsync(async () => { - var response = await _httpClient.SendAsync(request); - if (!response.IsSuccessStatusCode) + // var response = await _httpClient.SendAsync(request); + try { - var errorContent = await response.Content.ReadAsStringAsync(); - throw new ServiceConnectionFailedException($"[푸시] : APNS 통신 실패 - {errorContent}"); + var response = await _httpClient.SendAsync(request); + var result = await response.Content.ReadAsStringAsync(); + // Console.WriteLine($"[APNs 응답] StatusCode: {response.StatusCode}"); + // Console.WriteLine($"[APNs 응답 본문]: {result}"); + + if (!response.IsSuccessStatusCode) + { + var errorContent = await response.Content.ReadAsStringAsync(); + throw new ServiceConnectionFailedException($"[푸시] : APNS 통신 실패 - {errorContent}"); + + } + } + catch (Exception ex) + { + Console.WriteLine($"[푸시 전송 예외 발생] {ex.GetType().Name}: {ex.Message}"); + if (ex.InnerException != null) + Console.WriteLine( + $"[InnerException] {ex.InnerException.GetType().Name}: {ex.InnerException.Message}"); } }); } @@ -311,7 +327,16 @@ namespace Back.Program.Services.V1 uid = validToken.FindFirst(ClaimTypes.NameIdentifier)?.Value ?? string.Empty; } - return APIResponse.Send("000", $"[{summary}], 정상", string.Empty); + if (request == null) + { + var data = await _pushRepository.FindPushCabinet(uid, size); + return APIResponse.Send("000", $"[{summary}], 정상", data); + } + else + { + var data = await _pushRepository.FindPushCabinet(request.id, size); + return APIResponse.Send("000", $"[{summary}], 정상", data); + } } } -- 2.45.1 From 306470ac750d60db822da883084615d0bcf059bd Mon Sep 17 00:00:00 2001 From: Seonkyu_Kim Date: Wed, 9 Apr 2025 12:55:59 +0900 Subject: [PATCH 6/6] =?UTF-8?q?[=E2=99=BB=EF=B8=8F]=20=EC=A0=84=EC=B2=B4?= =?UTF-8?q?=EC=A0=81=EC=9D=B8=20=EA=B5=AC=EC=A1=B0=20=EB=A6=AC=ED=8C=A9?= =?UTF-8?q?=ED=86=A0=EB=A7=81=20=EC=A7=84=ED=96=89=EC=99=84=EB=A3=8C=20(Ex?= =?UTF-8?q?ception=20part)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Program.cs | 13 ++- .../Common/Middleware/ExceptionMiddleware.cs | 71 ++++++++------- Program/Common/Model/AcaException.cs | 87 ++++--------------- Program/Services/V1/PushService.cs | 3 +- Program/Services/V1/RepositoryService.cs | 4 +- 5 files changed, 63 insertions(+), 115 deletions(-) diff --git a/Program.cs b/Program.cs index 7221288..5fa9a9c 100644 --- a/Program.cs +++ b/Program.cs @@ -191,22 +191,21 @@ else // 로컬 테스트 위한 부분 (올릴떄는 켜두기) app.UseHttpsRedirection(); +//예외처리 미들웨어 부분 +app.UseMiddleware(); // 헤더 미들웨어 부분 -app.UseMiddleware((object)new string[] { "iOS_AM_Connect_Key", "And_AM_Connect_Key", "Web_AM_Connect_Key" }); -// app.UseMiddleware(); -// app.UseMiddleware("X-MyHeader"); - +app.UseMiddleware( + (object)new string[] { "iOS_AM_Connect_Key", "And_AM_Connect_Key", "Web_AM_Connect_Key" } + ); app.UseRouting(); -// app.MapControllers(); - app.UseCors("CorsPolicy"); app.UseAuthorization(); app.UseWebSockets(); app.UseEndpoints(end => { - end.MapControllers(); + ControllerEndpointRouteBuilderExtensions.MapControllers(end); end.MapHub("/chatHub"); }); diff --git a/Program/Common/Middleware/ExceptionMiddleware.cs b/Program/Common/Middleware/ExceptionMiddleware.cs index f373d31..e1b1d58 100644 --- a/Program/Common/Middleware/ExceptionMiddleware.cs +++ b/Program/Common/Middleware/ExceptionMiddleware.cs @@ -4,47 +4,46 @@ using Microsoft.Extensions.Logging; using System.Threading.Tasks; using Back.Program.Common.Model; -namespace Back.Program.Common.Middleware +namespace Back.Program.Common.Middleware; + +public class ExceptionMiddleware { - public class ExceptionMiddleware + private readonly RequestDelegate _next; + private readonly ILogger _logger; + + public ExceptionMiddleware(RequestDelegate next, ILogger logger) { - private readonly RequestDelegate _next; - private readonly ILogger _logger; + _next = next; + _logger = logger; + } - public ExceptionMiddleware(RequestDelegate next, ILogger logger) + public async Task Invoke(HttpContext context) + { + try { - _next = next; - _logger = logger; + await _next(context); // 다음 미들웨어 호출 } + catch (AcaException ex) + { + _logger.LogWarning(ex, $"예외 발생 : {ex.Message}"); + // 400 : 이건 개발자가 직접 던지는 비즈니스 로직에 대한 예외 == 클라이언트의 오류 + context.Response.StatusCode = ex.HttpStatus; + context.Response.ContentType = "application/json; charset=utf-8"; + + var response = APIResponse.Send(ex.Code, ex.Message, string.Empty); + var json = JsonSerializer.Serialize(response); + await context.Response.WriteAsync(json); + } + catch (Exception ex) + { + _logger.LogError(ex, "Unhandled Exception"); + context.Response.StatusCode = 500; - // public async Task Invoke(HttpContext context) - // { - // try - // { - // await _next(context); // 다음 미들웨어 호출 - // } - // catch (AcaException ex) - // { - // _logger.LogWarning(ex, "AcaException 발생"); - // context.Response.StatusCode = 400; - // - // var response = APIResponse.Send(ex.Code, ex.Message, ""); - // var json = JsonSerializer.Serialize(response); - // - // context.Response.ContentType = "application/json; charset=utf-8"; - // await context.Response.WriteAsync(json); - // } - // catch (Exception ex) - // { - // _logger.LogError(ex, "Unhandled Exception"); - // context.Response.StatusCode = 500; - // - // var response = APIResponse.InternalSeverError("서버 내부 오류가 발생했습니다."); - // var json = JsonSerializer.Serialize(response); - // - // context.Response.ContentType = "application/json; charset=utf-8"; - // await context.Response.WriteAsync(json); - // } - // } + var response = APIResponse.InternalSeverError("서버 내부 오류가 발생했습니다."); + var json = JsonSerializer.Serialize(response); + + context.Response.ContentType = "application/json; charset=utf-8"; + await context.Response.WriteAsync(json); + } } } \ No newline at end of file diff --git a/Program/Common/Model/AcaException.cs b/Program/Common/Model/AcaException.cs index 918de6e..a6d069d 100644 --- a/Program/Common/Model/AcaException.cs +++ b/Program/Common/Model/AcaException.cs @@ -1,74 +1,25 @@ -namespace Back.Program.Common.Model +using System.ComponentModel; + +namespace Back.Program.Common.Model; + +public static class ResposeCode { - /// - /// 입력 받은 토큰들(Access & Refresh) 자체에 문제가 있는 경우 - /// - public class TokenException: Exception - { - public TokenException(string message) : base(message) - { - } - } + public const string Success = "000"; + public const string InputErr = "100"; + public const string OutputErr = "200"; + public const string NetworkErr = "300"; + public const string UnknownErr = "999"; +} +public class AcaException : Exception +{ + public string Code { get; } + public int HttpStatus { get; } - /// - /// 리프레시 토큰이 만료가 나있는 경우 - /// - public class RefreshRevokeException: Exception + public AcaException(string code, string message, int httpStatus = 400) : base(message) { - public RefreshRevokeException(string message) : base(message) - { - } + this.Code = code; + this.HttpStatus = httpStatus; } +} - /// - /// 참조해야 하는 파일에서 오류가 발생하는 경우 - /// - public class FileNotValidException : Exception - { - public FileNotValidException(string message) : base(message) - { - } - } - /// - /// 파일 내부에 값을 읽을 때 오류가 발생하는 경우 - /// - public class FileContentNotFoundException : Exception - { - public FileContentNotFoundException(string message) : base(message) - { - } - } - - /// - /// 외부 서비스에 연결시 연결 실패시 - /// - public class ServiceConnectionFailedException : Exception - { - public ServiceConnectionFailedException(string message) : base(message) - { - - } - } - - /// - /// PUSH 서비스 중 데이터 사용에 문제가 발생했을시 - /// - public class PushInvalidException : Exception - { - public PushInvalidException(string message) : base(message) - { - - } - } - /// - /// 값이 있어야 하는데 NULL인 경우 - /// - public class OutNULLException : Exception - { - public OutNULLException(string message) : base(message) - { - - } - } -} \ No newline at end of file diff --git a/Program/Services/V1/PushService.cs b/Program/Services/V1/PushService.cs index ed7e257..9177b7d 100644 --- a/Program/Services/V1/PushService.cs +++ b/Program/Services/V1/PushService.cs @@ -75,8 +75,7 @@ namespace Back.Program.Services.V1 if (!response.IsSuccessStatusCode) { var errorContent = await response.Content.ReadAsStringAsync(); - throw new ServiceConnectionFailedException($"[푸시] : APNS 통신 실패 - {errorContent}"); - + throw new AcaException(ResposeCode.NetworkErr, $"[푸시] : APNS 통신 실패 - {errorContent}"); } } catch (Exception ex) diff --git a/Program/Services/V1/RepositoryService.cs b/Program/Services/V1/RepositoryService.cs index 07fbaef..9313c4a 100644 --- a/Program/Services/V1/RepositoryService.cs +++ b/Program/Services/V1/RepositoryService.cs @@ -223,8 +223,8 @@ namespace Back.Program.Services.V1 public string ReadSummary(Type type, string name) { - var method = type.GetMethod(name) ?? throw new OutNULLException("swagger summary Load ERROR: NULL"); - var att = method.GetCustomAttribute() ?? throw new OutNULLException("swagger summary Load ERROR: NULL"); + var method = type.GetMethod(name) ?? throw new AcaException(ResposeCode.NetworkErr,"swagger summary Load ERROR: NULL"); + var att = method.GetCustomAttribute() ?? throw new AcaException(ResposeCode.NetworkErr,"swagger summary Load ERROR: NULL"); return att.Summary; } } -- 2.45.1