Merge pull request 'refactory' (#1) from refactory into main

Reviewed-on: https://git.ipstein.myds.me/seonkyu.kim/AcaMate_API/pulls/1
This commit is contained in:
김선규 2025-04-09 04:04:37 +00:00
commit 7f9811e1a3
62 changed files with 2912 additions and 2786 deletions

View File

@ -18,5 +18,9 @@
<PackageReference Include="Swashbuckle.AspNetCore.Annotations" Version="7.1.0" />
</ItemGroup>
<ItemGroup>
<Folder Include="Program\Controllers\V1\Interfaces\" />
</ItemGroup>
</Project>

View File

@ -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<AppDbContext>(options => options.UseMySql(connectionString, ServerVersion.AutoDetect(connectionString)));
builder.Services.AddDbContext<AppDbContext>(optionsAction: (serviceProvider, options) =>
@ -38,7 +44,7 @@ builder.Services.AddDbContext<AppDbContext>(optionsAction: (serviceProvider, opt
options.UseMySql(baseConnectionString, ServerVersion.AutoDetect(baseConnectionString));
});
builder.Services.AddHttpContextAccessor();
// DB 설정부 끝
@ -84,7 +90,7 @@ builder.Services.AddAuthentication(options =>
builder.Services.Configure<PushFileSetting>(builder.Configuration.GetSection("PushFileSetting"));
// HttpClientFactory를 이용한 ApnsPushService 등록 (핸들러에 인증서 추가)
builder.Services.AddHttpClient<IApnsPushService, ApnsPushService>(client =>
builder.Services.AddHttpClient<IPushService, PushService>(client =>
{
var settings = builder.Configuration.GetSection("PushFileSetting").Get<PushFileSetting>();
client.BaseAddress = new Uri(settings.uri);
@ -113,9 +119,19 @@ builder.Services.AddHostedService<PushBackgroundService>();
builder.Services.AddControllers();
// 여기다가 API 있는 컨트롤러들 AddScoped 하면 되는건가?
builder.Services.AddScoped<AcaMate.Common.Token.JwtTokenService>();
builder.Services.AddScoped<IRepositoryService, AcaMate.V1.Services.RepositoryService>();
builder.Services.AddScoped<JwtTokenService>();
builder.Services.AddScoped<ILogRepository, LogRepository>();
builder.Services.AddScoped<IRepositoryService, RepositoryService>();
builder.Services.AddScoped<IHeaderConfig, HeaderConfigRepository>();
builder.Services.AddScoped<IUserService, UserService>();
builder.Services.AddScoped<IUserRepository, UserRepository>();
builder.Services.AddScoped<IAppService, AppService>();
builder.Services.AddScoped<IAppRepository, AppRepository>();
// builder.Services.AddScoped<IPushService, PushService>();
builder.Services.AddScoped<IPushRepository, PushRepository>();
// builder.Services.AddScoped<UserService>(); //
// builder.Services.AddScoped<UserController>();
@ -151,10 +167,6 @@ else
builder.Logging.SetMinimumLevel(LogLevel.Warning);
}
//헤더 부분
builder.Services.AddScoped<IHeaderConfig, HeaderConfigRepository>();
// 로컬 테스트 위한 부분 (올릴때는 꺼두기)
// builder.WebHost.UseUrls("http://0.0.0.0:5144");
@ -179,21 +191,21 @@ else
// 로컬 테스트 위한 부분 (올릴떄는 켜두기)
app.UseHttpsRedirection();
//예외처리 미들웨어 부분
app.UseMiddleware<ExceptionMiddleware>();
// 헤더 미들웨어 부분
app.UseMiddleware<APIHeaderMiddleware>((object)new string[] { "iOS_AM_Connect_Key", "And_AM_Connect_Key", "Web_AM_Connect_Key" });
// app.UseMiddleware<CustomHeaderMiddleware>("X-MyHeader");
app.UseMiddleware<APIHeaderMiddleware>(
(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>("/chatHub");
});

View File

@ -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<string> GetExpectedHeaderValueAsync(string headerName);
}
/// <summary>
/// DB에서 헤더 키값 찾아서 그 밸류 값 빼오기 위해서 사용
/// </summary>
public class HeaderConfigRepository : IHeaderConfig
{
private readonly AppDbContext _dbContext;
public HeaderConfigRepository(AppDbContext dbContext)
///
public class APIHeaderMiddleware
{
_dbContext = dbContext;
}
public async Task<string> 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<IHeaderConfig>();
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<IHeaderConfig>();
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);
}
}
}

View File

@ -0,0 +1,27 @@
using Back.Program.Common.Auth.Interface;
using Back.Program.Common.Data;
using Microsoft.EntityFrameworkCore;
namespace Back.Program.Common.Auth
{
/// <summary>
/// DB에서 헤더 키값 찾아서 그 밸류 값 빼오기 위해서 사용
/// </summary>
public class HeaderConfigRepository : IHeaderConfig
{
private readonly AppDbContext _dbContext;
public HeaderConfigRepository(AppDbContext dbContext)
{
_dbContext = dbContext;
}
public async Task<string> GetExpectedHeaderValueAsync(string headerValue)
{
var config = await _dbContext.APIHeader
.FirstOrDefaultAsync(h => h.h_value == headerValue);
return config?.h_key ?? string.Empty;
}
}
}

View File

@ -0,0 +1,7 @@
namespace Back.Program.Common.Auth.Interface
{
public interface IHeaderConfig
{
Task<string> GetExpectedHeaderValueAsync(string headerName);
}
}

View File

@ -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<JwtTokenService> _logger;
public JwtTokenService(IOptions<JwtSettings> jwtSettings, ILogger<JwtTokenService> logger)
{
_jwtSettings = jwtSettings.Value;
_logger = logger;
}
public string GenerateJwtToken(string jwtKey)//, string role)
{
// 1. 클레임(Claim) 설정 - 필요에 따라 추가 정보도 포함
var claims = new List<Claim>
{
// 토큰 주체(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)
};
}
/// <summary>
/// 여기는 엑세스 토큰의 확인을 위한 jwt 서비스 내의 인증 메서드
/// 사용자의 정보를 바탕으로 JWT 생성
/// </summary>
public async Task<ClaimsPrincipal?> ValidateToken(string token)
public class JwtTokenService
{
if (string.IsNullOrWhiteSpace(token)) return null;
var tokenHandler = new JwtSecurityTokenHandler();
private readonly JwtSettings _jwtSettings;
private readonly ILogger<JwtTokenService> _logger;
try
public JwtTokenService(IOptions<JwtSettings> jwtSettings, ILogger<JwtTokenService> 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<Claim>
{
// 토큰 주체(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)
};
}
/// <summary>
/// 여기는 엑세스 토큰의 확인을 위한 jwt 서비스 내의 인증 메서드
/// </summary>
public async Task<ClaimsPrincipal?> 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;
}
}
}
}

View File

@ -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);
}
}
}
}
/*
*/

View File

@ -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<AppDbContext> options) : base(options)
//database=AcaMate;
public class AppDbContext: DbContext
{
}
public AppDbContext(DbContextOptions<AppDbContext> options) : base(options)
{
}
//MARK: API
public DbSet<APIHeader> APIHeader { get; set; }
//MARK: API
public DbSet<APIHeader> APIHeader { get; set; }
//MARK: Program
public DbSet<Version> Version { get; set; }
public DbSet<Academy> Academy { get; set; }
public DbSet<RefreshToken> RefreshTokens { get; set; }
//MARK: Program
public DbSet<Version> Version { get; set; }
public DbSet<Academy> Academy { get; set; }
public DbSet<RefreshToken> RefreshToken { get; set; }
//MARK: USER
public DbSet<Login> Login { get; set; }
public DbSet<User_Academy> UserAcademy { get; set; }
public DbSet<User> User { get; set; }
public DbSet<Permission> Permission { get; set; }
// public DbSet<Token> Token { get; set; }
public DbSet<Location> Location { get; set; }
public DbSet<Contact> Contact { get; set; }
//MARK: USER
public DbSet<Login> Login { get; set; }
public DbSet<User_Academy> UserAcademy { get; set; }
public DbSet<User> User { get; set; }
public DbSet<Permission> Permission { get; set; }
// public DbSet<Token> Token { get; set; }
public DbSet<Location> Location { get; set; }
public DbSet<Contact> Contact { get; set; }
//MARK: PUSH
public DbSet<DBPayload> DBPayload { get; set; }
public DbSet<PushCabinet> PushCabinet { get; set; }
//MARK: PUSH
public DbSet<DBPayload> DBPayload { get; set; }
public DbSet<PushCabinet> PushCabinet { get; set; }
//MARK: CHATTING
// public DbSet<>
//MARK: LOG
public DbSet<LogPush> LogPush { get; set; }
public DbSet<LogUser> LogUser { get; set; }
public DbSet<LogProject> LogProject { get; set; }
//MARK: LOG
public DbSet<LogPush> LogPush { get; set; }
public DbSet<LogUser> LogUser { get; set; }
public DbSet<LogProject> LogProject { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<User_Academy>()
.HasKey(ua => new { ua.uid, ua.bid });
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<User_Academy>()
.HasKey(ua => new { ua.uid, ua.bid });
// modelBuilder.Entity<PushCabinet>()
// .HasKey(c => new { c.uid, c.bid, c.pid });
// modelBuilder.Entity<PushCabinet>()
// .HasKey(c => new { c.uid, c.bid, c.pid });
modelBuilder.Entity<DBPayload>()
.HasKey(p => new { p.bid, p.pid });
modelBuilder.Entity<DBPayload>()
.HasKey(p => new { p.bid, p.pid });
// modelBuilder.Entity<LogPush>().HasNoKey();
// modelBuilder.Entity<LogPush>().HasNoKey();
}
}
}

View File

@ -0,0 +1,49 @@
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<ExceptionMiddleware> _logger;
public ExceptionMiddleware(RequestDelegate next, ILogger<ExceptionMiddleware> logger)
{
_next = next;
_logger = logger;
}
public async Task Invoke(HttpContext context)
{
try
{
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<string>(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;
var response = APIResponse.InternalSeverError("서버 내부 오류가 발생했습니다.");
var json = JsonSerializer.Serialize(response);
context.Response.ContentType = "application/json; charset=utf-8";
await context.Response.WriteAsync(json);
}
}
}

View File

@ -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; }
}
}
/*

View File

@ -0,0 +1,25 @@
using System.ComponentModel;
namespace Back.Program.Common.Model;
public static class ResposeCode
{
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 AcaException(string code, string message, int httpStatus = 400) : base(message)
{
this.Code = code;
this.HttpStatus = httpStatus;
}
}

View File

@ -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; }
}
/*
"""

View File

@ -1,73 +1,74 @@
using System.Text.Json;
namespace AcaMate.Common.Models;
public class APIResponseStatus<T>
namespace Back.Program.Common.Model
{
public Status status { get; set; }
public T? data { get; set; }
public class APIResponseStatus<T>
{
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<T> Send<T>(string code, string message, T data)
{
return new APIResponseStatus<T>
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<T> Send<T>(string code, string message, T data)
{
return new APIResponseStatus<T>
{
code = code,
message = message
},
data = data
};
}
status = new Status()
{
code = code,
message = message
},
data = data
};
}
/// <summary>
/// 반환값 없는 API 정상 동작시
/// </summary>
public static APIResponseStatus<string> Success (){
return Send("000", "정상", "");
}
/// <summary>
/// 반환값 없는 API 정상 동작시
/// </summary>
public static APIResponseStatus<object> Success (){
return Send<object>("000", "정상", string.Empty);
}
public static APIResponseStatus<string> InvalidInputError(string? msg = null)
{
return Send("100", msg ?? "입력 값이 유효하지 않습니다.", "");
}
public static APIResponseStatus<object> InvalidInputError(string? msg = null)
{
return Send<object>("100", msg ?? "입력 값이 유효하지 않습니다.", string.Empty);
}
public static APIResponseStatus<string> AccessExpireError(string? msg = null)
{
return Send("101", msg ?? "엑세스 토큰이 유효하지 않습니다.", "");
}
public static APIResponseStatus<object> AccessExpireError(string? msg = null)
{
return Send<object>("101", msg ?? "엑세스 토큰이 유효하지 않습니다.", string.Empty);
}
// -- -- -- OUTPUT ERROR -- -- -- //
public static APIResponseStatus<string> NotFoundError(string? msg = null)
{
return Send("200", msg ?? "알맞은 값을 찾을 수 없습니다.", "");
}
// -- -- -- OUTPUT ERROR -- -- -- //
public static APIResponseStatus<object> NotFoundError(string? msg = null)
{
return Send<object>("200", msg ?? "알맞은 값을 찾을 수 없습니다.", string.Empty);
}
public static APIResponseStatus<string> InternalSeverError(string? msg = null)
{
return Send("300", msg ?? "통신에 오류가 발생하였습니다.", "");
}
public static APIResponseStatus<object> InternalSeverError(string? msg = null)
{
return Send<object>("300", msg ?? "통신에 오류가 발생하였습니다.", string.Empty);
}
public static APIResponseStatus<string> UnknownError(string? msg = null)
{
return Send("999", msg ?? "알 수 없는 오류가 발생하였습니다.", "");
public static APIResponseStatus<object> UnknownError(string? msg = null)
{
return Send<object>("999", msg ?? "알 수 없는 오류가 발생하였습니다.", string.Empty);
}
}
}

View File

@ -0,0 +1,78 @@
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.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;
namespace Back.Program.Controllers.V1
{
[ApiController]
[Route("/api/v1/in/app")]
[ApiExplorerSettings(GroupName = "공통")]
public class AppController : ControllerBase
{
private readonly AppDbContext _dbContext;
private readonly ILogger<AppController> _logger;
private readonly IRepositoryService _repositoryService;
private readonly JwtTokenService _jwtTokenService;
private readonly IAppService _appService;
private readonly IAppRepository _appRepository;
public AppController(AppDbContext dbContext, ILogger<AppController> logger, IRepositoryService repositoryService, JwtTokenService jwtTokenService,IAppService appService, IAppRepository appRepository)
{
_dbContext = dbContext;
_logger = logger;
_repositoryService = repositoryService;
_jwtTokenService = jwtTokenService;
_appService = appService;
_appRepository = appRepository;
}
// 이 키값의 제한 시간은 24h이다
[HttpGet]
[CustomOperation("헤더 정보 생성", "헤더에 접근하기 위한 키 값 받아오기", "시스템")]
public async Task<IActionResult> 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 = _repositoryService.ReadSummary(typeof(AppController), "GetHeaderValue");
var result = await _appService.GetHeader(summary, type, specific, project);
return Ok(result);
}
[HttpGet("version")]
[CustomOperation("앱 버전 확인","앱 버전을 확인해서 업데이트 여부 판단", "시스템")]
public async Task<IActionResult> GetVersionData(string type)
{
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("retryAccess")]
[CustomOperation("엑세스 토큰 재발급", "액세스 토큰 재발급 동작 수행", "시스템")]
public async Task<IActionResult> RetryAccessToken(string refresh)
{
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);
}
}
}

View File

@ -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("오류가 발생하였습니다. 잠시후 다시 시도해주세요.");
}
}
}

View File

@ -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<MemberController> _logger;
private readonly AppDbContext _dbContext;
private readonly IRepositoryService _repositoryService;
private readonly JwtTokenService _jwtTokenService;
public MemberController(AppDbContext dbContext, ILogger<MemberController> 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("외부 참조");
}
}
}

View File

@ -0,0 +1,133 @@
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 Back.Program.Services.V1.Interfaces;
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<PushController> _logger;
private readonly IRepositoryService _repositoryService;
private readonly IPushQueue _pushQueue;
private readonly IPushService _pushService;
public PushController(ILogger<PushController> logger, IRepositoryService repositoryService,
IPushQueue pushQueue, IPushService pushService)
{
_logger = logger;
_repositoryService = repositoryService;
_pushQueue = pushQueue;
_pushService = pushService;
}
// 추가 사항
// 카테고리 별 조회 하는 부분도 추가를 할 지 고민을 해야 할 것 같음
[HttpGet()]
[CustomOperation("푸시 확인", "저장된 양식을 확인 할 수 있다.", "푸시")]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(APIResponseStatus<object>))]
public async Task<IActionResult> GetPush(string bid, string? pid, string? category)
{
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);
}
[HttpPost("send")]
[CustomOperation("푸시 발송", "저장된 양식으로, 사용자에게 푸시를 송신한다.", "푸시")]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(APIResponseStatus<object>))]
public async Task<IActionResult> SendPush([FromBody] PushRequest pushRequest)
{
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")]
[CustomOperation("푸시 변경", "저장된 양식을 변경한다.", "푸시")]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(APIResponseStatus<object>))]
public async Task<IActionResult> SetPush(string token, [FromBody] DBPayload request)
{
if (string.IsNullOrEmpty(token)) return BadRequest(APIResponse.InvalidInputError());
if (!ModelState.IsValid) return BadRequest(APIResponse.InvalidInputError());
string summary = _repositoryService.ReadSummary(typeof(PushController), "SetPush");
var result = await _pushService.SetPush(summary, token, request);
return Ok(result);
}
[HttpPost("create")]
[CustomOperation("푸시 생성", "새로운 푸시 양식을 생성한다.", "푸시")]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(APIResponseStatus<object>))]
public async Task<IActionResult> CreatePush(string token, [FromBody] CreatePush request)
{
if (string.IsNullOrEmpty(token)) return BadRequest(APIResponse.InvalidInputError());
if (!ModelState.IsValid) return BadRequest(APIResponse.InvalidInputError());
string summary = _repositoryService.ReadSummary(typeof(PushController), "CreatePush");
var result = await _pushService.CreatePush(summary, token, request);
return Ok(result);
}
[HttpDelete("delete")]
[CustomOperation("푸시 삭제", "저장된 푸시 양식을 삭제 한다.", "푸시")]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(APIResponseStatus<object>))]
public async Task<IActionResult> DeletePush(string token, string bid, string pid)
{
if (string.IsNullOrEmpty(token)) return BadRequest(APIResponse.InvalidInputError());
if (!ModelState.IsValid) return BadRequest(APIResponse.InvalidInputError());
string summary = _repositoryService.ReadSummary(typeof(PushController), "DeletePush");
var result = await _pushService.DeletePush(summary,token,bid,pid);
return Ok(result);
}
[HttpDelete("delete/list")]
[CustomOperation("사용자 푸시 목록 삭제", "사용자가 받은 푸시목록에서 푸시를 삭제한다..", "푸시")]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(APIResponseStatus<object>))]
public async Task<IActionResult> DeleteListPush(string token, int id)
{
if (string.IsNullOrEmpty(token)) return BadRequest(APIResponse.InvalidInputError());
if (!ModelState.IsValid) return BadRequest(APIResponse.InvalidInputError());
string summary = _repositoryService.ReadSummary(typeof(PushController), "DeleteListPush");
var result = await _pushService.DeleteListPush(summary, token, id);
return Ok(result);
}
[HttpPost("list")]
[CustomOperation("사용자 푸시 목록 조회", "해당 사용자가 받은 푸시의 정보를 조회한다.", "푸시")]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(APIResponseStatus<object>))]
public async Task<IActionResult> SearchToUserPush(string token, int size, [FromBody] PushCabinet? request)
{
if (string.IsNullOrEmpty(token)) return BadRequest(APIResponse.InvalidInputError());
if (!ModelState.IsValid) return BadRequest(APIResponse.InvalidInputError());
string summary = _repositoryService.ReadSummary(typeof(PushController), "SearchToUserPush");
var result = await _pushService.SearchToUserPush(summary, token, size, request);
return Ok(result);
}
}
} // END PUSH CONTROLLER

View File

@ -0,0 +1,165 @@
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
{
/// <summary>
/// USER는 사용자가 자신의 데이터를 보거나 만들거나 하는 등 직접 사용하는 경우에 사용
/// </summary>
[ApiController]
[Route("/api/v1/in/user")]
[ApiExplorerSettings(GroupName = "사용자")]
public class UserController : ControllerBase
{
private readonly ILogger<UserController> _logger;
private readonly IRepositoryService _repositoryService;
private readonly IUserService _userService;
public UserController(ILogger<UserController> logger,
IRepositoryService repositoryService, IUserService userService)
{
_logger = logger;
_repositoryService = repositoryService;
_userService = userService;
}
[HttpGet]
[CustomOperation("회원 정보 조회", "회원 정보 조회 (자기자신)", "사용자")]
public async Task<IActionResult> 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 = await _userService.GetUser(summary, token);
return Ok(result);
}
[HttpGet("login")]
[CustomOperation("SNS 로그인", "로그인 후 회원이 있는지 확인", "사용자")]
public async Task<IActionResult> 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<IActionResult> UserRegister([FromBody] UserAll request)
{
if (!ModelState.IsValid) return BadRequest(APIResponse.InvalidInputError());
string summary = _repositoryService.ReadSummary(typeof(UserController), "UserRegister");
var result = await _userService.Register(summary, request);
return Ok(result);
}
[HttpGet("logout")]
[CustomOperation("로그아웃", "사용자 로그아웃", "사용자")]
public async Task<IActionResult> 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<IActionResult> 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<IActionResult> 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 = await _userService.GetAcademy(summary, token);
return Ok(result);
}
}
}
// 근데 회원 정보를 변경하는게 뭐뭐를 변경해야 하는지 아직 정해진게 없어서 이건 일단 보류
/*
[HttpGet("set")]
[CustomOperation("회원 정보 변경", "회원 정보 변경", "사용자")]
public async Task<IActionResult> 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");
}
*/

View File

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

View File

@ -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<string> bids { get; set; }
}
}

View File

@ -0,0 +1,10 @@
using System.ComponentModel.DataAnnotations.Schema;
namespace Back.Program.Models.Entities
{
[Table("authkey")]
public class AuthKey
{
}
}

View File

@ -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;
}
}
}

View File

@ -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; }
}
}

View File

@ -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<string> 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; } // 부제목 (선택)
}
/// <summary>
/// 푸시 등록하기 위한 apns 여러 데이터 목록
/// </summary>
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; }
}
}

View File

@ -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;}
}
}

View File

@ -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; }
}
}

View File

@ -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<APIHeader?> FindHeader(string specific)
{
return await _context.APIHeader.FirstOrDefaultAsync(h => h.specific_id == specific);
}
public async Task<Version?> FindVersion(string type)
{
return await _context.Version.FirstOrDefaultAsync(v => v.os_type == (type =="I" ? "VO01" : "VO02"));
}
public async Task<RefreshToken?> FindRefreshToken(string refresh)
{
return await _context.RefreshToken.FirstOrDefaultAsync(r => r.refresh_token == refresh);
}
}

View File

@ -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<APIHeader?> FindHeader(string specific);
Task<Version?> FindVersion(string type);
Task<RefreshToken?> FindRefreshToken(string refresh);
}

View File

@ -0,0 +1,12 @@
using Back.Program.Models.Entities;
namespace Back.Program.Repositories.V1.Interfaces;
public interface ILogRepository
{
Task<bool> SaveLogUser(LogUser log);
Task<bool> SaveLogProject(LogProject log);
Task<bool> SaveLogPush(LogPush log);
}

View File

@ -0,0 +1,18 @@
using Back.Program.Models.Entities;
namespace Back.Program.Repositories.V1.Interfaces;
public interface IPushRepository
{
Task<bool> FindAcademy(string bid);
Task<List<DBPayload>> FindPushList(string bid, string? pid, string? category);
Task<DBPayload?> FindPushPayload(string bid, string pid);
Task<bool> FindUserAcademy(string uid, string bid);
Task<int> CountBadge(string uid);
Task<string?> FindPushToken(string uid);
Task<PushCabinet?> FindPushCabinet(int id);
Task<List<PushCabinet>> FindPushCabinet(string uid, int size);
Task<List<PushCabinet>> FindPushCabinet(int id, int size);
}

View File

@ -0,0 +1,14 @@
using Back.Program.Common.Model;
using Back.Program.Models.Entities;
namespace Back.Program.Repositories.V1.Interfaces
{
public interface IUserRepository
{
Task<Login?> FindLogin(string accType, string snsId);
Task<User?> FindUser(string uid);
Task<RefreshToken?> FindRefreshToken(string uid);
Task<List<AcademyName>> FindAcademies(string uid);
Task<bool> SaveChanges();
}
}

View File

@ -0,0 +1,36 @@
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<LogRepository> _logger;
private readonly AppDbContext _context;
public LogRepository(ILogger<LogRepository> logger, AppDbContext context)
{
_logger = logger;
_context = context;
}
public async Task<bool> SaveLogUser(LogUser log)
{
_context.LogUser.Add(log);
return await _context.SaveChangesAsync() > 0;
}
public async Task<bool> SaveLogProject(LogProject log)
{
_context.LogProject.Add(log);
return await _context.SaveChangesAsync() > 0;
}
public async Task<bool> SaveLogPush(LogPush log)
{
_context.LogPush.Add(log);
return await _context.SaveChangesAsync() > 0;
}
}

View File

@ -0,0 +1,85 @@
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<bool> FindAcademy(string bid)
{
return await _context.Academy.AnyAsync(a => a.bid == bid);
}
public async Task<List<DBPayload>> FindPushList(string bid, string? pid, string? category)
{
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<DBPayload?> FindPushPayload(string bid, string pid)
{
return await _context.DBPayload.FirstOrDefaultAsync(p => p.bid == bid && p.pid == pid);
}
public async Task<bool> FindUserAcademy(string uid, string bid)
{
return await _context.UserAcademy.AnyAsync(ua => ua.uid == uid && ua.bid == bid);
}
public async Task<int> CountBadge(string uid)
{
return await _context.PushCabinet.CountAsync(c => c.uid == uid && c.check_yn == false);
}
public async Task<string?> FindPushToken(string uid)
{
return await _context.User
.Where(u => u.uid == uid)
.Select(u => u.push_token)
.FirstOrDefaultAsync();
}
public async Task<PushCabinet?> FindPushCabinet(int id)
{
return await _context.PushCabinet.FirstOrDefaultAsync(c => c.id == id);
}
public async Task<List<PushCabinet>> 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<List<PushCabinet>> 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<PushCabinet>();
return await _context.PushCabinet
.Where(c => c.send_date > sort)
.OrderBy(c => c.send_date)
.Take(size)
.ToListAsync();
}
}

View File

@ -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<UserRepository> _logger;
private readonly AppDbContext _context;
public UserRepository(ILogger<UserRepository> logger,AppDbContext context) {
_logger = logger;
_context = context;
}
public Task<Login?> FindLogin(string accType, string snsId)
{
return _context.Login.FirstOrDefaultAsync(l => l.sns_type == accType && l.sns_id == snsId);
}
public Task<User?> FindUser(string uid)
{
return _context.User.FirstOrDefaultAsync(u => u.uid == uid);
}
public Task<RefreshToken?> FindRefreshToken(string uid)
{
return _context.RefreshToken.FirstOrDefaultAsync(r => r.uid == uid);
}
public Task<List<AcademyName>> 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<bool> SaveChanges()
{
return await _context.SaveChangesAsync() > 0;
}
}
}

View File

@ -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<IAppService> _logger;
private readonly IRepositoryService _repositoryService;
private readonly JwtTokenService _jwtTokenService;
private readonly ILogRepository _logRepository;
private readonly IAppRepository _appRepository;
public AppService(ILogger<IAppService> logger, IRepositoryService repositoryService,
JwtTokenService jwtTokenService, ILogRepository logRepository, IAppRepository appRepository)
{
_logger = logger;
_repositoryService = repositoryService;
_jwtTokenService = jwtTokenService;
_logRepository = logRepository;
_appRepository = appRepository;
}
public async Task<APIResponseStatus<object>> 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>(apiHeader))
{
// 새로 업뎃해서 저장
if(await _logRepository.SaveLogProject( new LogProject { create_date = DateTime.Now , log = $"[{summary}] : 해당 키 유효시간 만료로 인한 새 키 부여"}))
{
_logger.LogInformation($"[{summary}] : 성공");
}
else
{
_logger.LogInformation($"[{summary}] : 성공 - 로그 저장 실패 ");
}
return APIResponse.Send<object>("000", $"[{summary}], 정상", new { header = headerValue });
}
else
{
// 저장이 안되었다? == 서버 오류
return APIResponse.InternalSeverError();
}
}
else
{
return APIResponse.Send<object>("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<APIHeader>(newHeader))
{
// 새로 업뎃해서 저장
if(await _logRepository.SaveLogProject( new LogProject { create_date = DateTime.Now , log = $"[{summary}] : 새 기기 등록으로 인한 새 키 부여"}))
{
_logger.LogInformation($"[{summary}] : 성공");
}
else
{
_logger.LogInformation($"[{summary}] : 성공 - 로그 저장 실패 ");
}
return APIResponse.Send<object>("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<APIResponseStatus<object>> 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<object>("000", $"[{summary}], 정상", sendVersion);
}
else
{
return APIResponse.NotFoundError();
}
}
public async Task<APIResponseStatus<object>> 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<object>("000", $"[{summary}], 토큰 생성 완료", new { accsee = access });
}
}

View File

@ -0,0 +1,66 @@
using System.Collections.Concurrent;
using Back.Program.Models.Entities;
using Back.Program.Services.V1.Interfaces;
namespace Back.Program.Services.V1
{
public interface IPushQueue
{
void Enqueue(PushData pushData);
Task<PushData> DequeueAsync(CancellationToken cancellationToken);
}
public class InMemoryPushQueue: IPushQueue
{
private readonly ConcurrentQueue<PushData> _queue = new ConcurrentQueue<PushData>();
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<PushData> DequeueAsync(CancellationToken cancellationToken)
{
await _signal.WaitAsync(cancellationToken);
_queue.TryDequeue(out var pushData);
return pushData;
}
}
public class PushBackgroundService : BackgroundService
{
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<IPushService>();
try
{
await pushService.SendPushNotificationAsync(pushData.pushToken, pushData.payload);
}
catch (Exception ex)
{
Console.WriteLine($"푸시 전송 실패: {ex.Message}");
}
}
}
}
}

View File

@ -0,0 +1,11 @@
using Back.Program.Common.Model;
namespace Back.Program.Services.V1.Interfaces;
public interface IAppService
{
Task<APIResponseStatus<object>> GetHeader(string summary, string type, string specific, string project);
Task<APIResponseStatus<object>> GetVersion(string summary, string type);
Task<APIResponseStatus<object>> RetryAccess(string summary, string refresh);
}

View File

@ -0,0 +1,19 @@
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);
Task<APIResponseStatus<object>> GetPush(string summary, string bid, string? pid, string? category);
Task<APIResponseStatus<object>> SendPush(string summary, PushRequest pushRequest);
Task<APIResponseStatus<object>> SetPush(string summary, string token, DBPayload request);
Task<APIResponseStatus<object>> CreatePush(string summary, string token, CreatePush request);
Task<APIResponseStatus<object>> DeletePush(string summary, string token, string bid, string pid);
Task<APIResponseStatus<object>> DeleteListPush(string summary, string token, int id);
Task<APIResponseStatus<object>> SearchToUserPush(string summary, string token, int size, PushCabinet? request);
}

View File

@ -0,0 +1,16 @@
using Back.Program.Common.Model;
using Back.Program.Models.Entities;
namespace Back.Program.Services.V1.Interfaces
{
public interface IUserService
{
Task<APIResponseStatus<object>> GetUser(string summary, string token);
Task<APIResponseStatus<object>> Login(string summary, string accType, string snsId);
Task<APIResponseStatus<object>> Register(string summary, UserAll request);
Task<APIResponseStatus<object>> Logout(string summary, string token);
Task<APIResponseStatus<object>> Cancel(string summary, string token);
Task<APIResponseStatus<object>> GetAcademy(string summary, string token);
}
}

View File

@ -0,0 +1,342 @@
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
{
private readonly ILogger<PushService> _logger;
private readonly HttpClient _httpClient;
private readonly PushFileSetting _setting;
private readonly JwtTokenService _jwtTokenService;
private readonly IRepositoryService _repositoryService;
private readonly IPushQueue _pushQueue;
private readonly ILogRepository _logRepository;
private readonly IPushRepository _pushRepository;
public PushService(ILogger<PushService> logger, HttpClient httpClient, IOptions<PushFileSetting> 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"),
Version = new Version(2, 0) // HTTP/2 사용
};
// 필수 헤더 추가
request.Headers.Add("apns-topic", _setting.apnsTopic);
request.Headers.Add("apns-push-type", "alert");
var policy = Policy.Handle<HttpRequestException>()
.WaitAndRetryAsync(3, retryAttempt =>
TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)));
await policy.ExecuteAsync(async () =>
{
// var response = await _httpClient.SendAsync(request);
try
{
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 AcaException(ResposeCode.NetworkErr, $"[푸시] : 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}");
}
});
}
public async Task<APIResponseStatus<object>> GetPush(string summary, string bid, string? pid, string? category)
{
if (!(await _pushRepository.FindAcademy(bid)))
return APIResponse.Send<object>("100", $"[{summary}], 존재하지 않는 BID", string.Empty);
var pushData = await _pushRepository.FindPushList(bid, pid, category);
if (pushData.Count > 0)
return APIResponse.Send<object>("000", $"[{summary}, 정상", pushData);
return APIResponse.Send<object>("001", $"[{summary}], PUSH 데이터 없음", string.Empty);
}
public async Task<APIResponseStatus<object>> 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>(pushCabinet);
log.log = saved ? $"[{summary}]: 푸시 캐비닛 저장 성공 및 푸시 전송 시도" : $"[{summary}]: 푸시 전송 실패";
var logSaved = await _repositoryService.SaveData<LogPush>(log);
_logger.LogInformation($"[{summary}]: 캐비닛 저장 = {saved} : 로그 저장 = {logSaved}");
if(saved) _pushQueue.Enqueue(pushData);
});
await Task.WhenAll(pushTasks);
return APIResponse.Send<object>("000", $"[{summary}], 정상", string.Empty);
}
public async Task<APIResponseStatus<object>> 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<object>("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<DBPayload>(payload));
log.log = $"[{summary}] : 상태 = {saved}";
var logSaved = await _repositoryService.SaveData<LogPush>(log);
_logger.LogInformation($"[{summary}]: 상태 = {saved} : 로그 저장 = {logSaved}");
if (!saved) return APIResponse.Send<object>("001", $"[{summary}], 실패", string.Empty);
return APIResponse.Send<object>("000", $"[{summary}], 정상", string.Empty);
}
public async Task<APIResponseStatus<object>> CreatePush(string summary, string token, CreatePush request)
{
Func<string, int, string> 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<object>("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<DBPayload>(payload);
log.log = $"[{summary}] : 푸시 생성 = {saved}";
var logSaved = await _repositoryService.SaveData<LogPush>(log);
_logger.LogInformation($"[{summary}]: 푸시 생성 = {saved} : 로그 저장 = {logSaved}");
if (!saved) return APIResponse.Send<object>("001", $"[{summary}], 실패", string.Empty);
return APIResponse.Send<object>("000", $"[{summary}], 정상", string.Empty);
}
public async Task<APIResponseStatus<object>> 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<object>("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<DBPayload>(payload);
log.log = $"[{summary}] : 삭제 = {delete}";
var logSaved = await _repositoryService.SaveData<LogPush>(log);
_logger.LogInformation($"[{summary}]: 삭제 = {delete} : 로그 저장 = {logSaved}");
if (!delete) return APIResponse.Send<object>("002", $"[{summary}], 실패", string.Empty);
return APIResponse.Send<object>("000", $"[{summary}], 정상", string.Empty);
}
public async Task<APIResponseStatus<object>> 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<object>("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<PushCabinet>(cabinet);
log.log = $"[{summary}] : {cabinet.pid} 삭제 = {delete}";
var logSaved = await _repositoryService.SaveData<LogPush>(log);
_logger.LogInformation($"[{summary}]: {cabinet.pid} 삭제 = {delete} : 로그 저장 = {logSaved}");
if (!delete) return APIResponse.Send<object>("002", $"[{summary}], 실패", string.Empty);
return APIResponse.Send<object>("000", $"[{summary}], 정상", string.Empty);
}
public async Task<APIResponseStatus<object>> 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;
}
if (request == null)
{
var data = await _pushRepository.FindPushCabinet(uid, size);
return APIResponse.Send<object>("000", $"[{summary}], 정상", data);
}
else
{
var data = await _pushRepository.FindPushCabinet(request.id, size);
return APIResponse.Send<object>("000", $"[{summary}], 정상", data);
}
}
}
}

View File

@ -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> ValidateToken(string token, string refresh);
Task<bool> SaveData<T>(T entity, Expression<Func<T, object>> key = null) where T : class;
Task<bool> DeleteData<T>(T entity, Expression<Func<T, object>> key = null) where T : class;
String ReadSummary(Type type, String name);
}
public class RepositoryService: IRepositoryService
{
private readonly AppDbContext _dbContext;
private readonly ILogger<RepositoryService> _logger;
private readonly JwtTokenService _jwtTokenService;
public RepositoryService(AppDbContext dbContext, ILogger<RepositoryService> logger, JwtTokenService jwtTokenService)
{
_dbContext = dbContext;
_logger = logger;
_jwtTokenService = jwtTokenService;
}
// public async Task<ValidateToken> 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, string>(refreshToken, rt => rt.uid);
// // return new ValidateToken
// // {
// // token = token,
// // refresh = refreshToken.refresh_token,
// // uid = uid
// // };
// // }
// }
// }
public async Task<bool> SaveData<T>(T entity, Expression<Func<T, object>> 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<Func<T, bool>>(equalsExpr, parameter);
var entityData = await _dbContext.Set<T>().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<T>().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<T>().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<T>().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<bool> DeleteData<T>(T entity, Expression<Func<T, object>> 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<Func<T, bool>>(equalsExpr, parameter);
var entityData = await _dbContext.Set<T>().FirstOrDefaultAsync(predicate);
if (entityData == null)
{
_logger.LogInformation($"[{typeof(T)}] 삭제 대상 데이터가 존재하지 않습니다. (값 = {value})");
return false;
}
_logger.LogInformation($"[{typeof(T)}] 조건에 맞는 데이터 발견 (값 = {value}): 삭제 진행");
_dbContext.Set<T>().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<T>().FindAsync(keyValues);
if (existingEntity == null)
{
_logger.LogInformation($"[{typeof(T)}] 기본 키 값({string.Join(", ", keyValues)})에 해당하는 삭제 대상 데이터가 존재하지 않습니다.");
return false;
}
_logger.LogInformation($"[{typeof(T)}] 기본 키 값({string.Join(", ", keyValues)})에 해당하는 데이터 발견: 삭제 진행");
_dbContext.Set<T>().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 AcaException(ResposeCode.NetworkErr,"swagger summary Load ERROR: NULL");
var att = method.GetCustomAttribute<CustomOperationAttribute>() ?? throw new AcaException(ResposeCode.NetworkErr,"swagger summary Load ERROR: NULL");
return att.Summary;
}
}
}

View File

@ -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<IUserService> _logger;
private readonly IUserRepository _userRepository;
private readonly JwtTokenService _jwtTokenService;
private readonly IRepositoryService _repositoryService;
private readonly ILogRepository _logRepository;
public UserService(ILogger<IUserService> logger, IUserRepository userRepository,
JwtTokenService jwtTokenService,
IRepositoryService repositoryService, ILogRepository logRepository)
{
_logger = logger;
_userRepository = userRepository;
_jwtTokenService = jwtTokenService;
_repositoryService = repositoryService;
_logRepository = logRepository;
}
public async Task<APIResponseStatus<object>> 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<object>("000", $"[{summary}], 정상", user);
// user 없는 경우가 없네? 그거도 만들것
}
public async Task<APIResponseStatus<object>> Login(string summary, string accType, string snsId)
{
var login = await _userRepository.FindLogin(accType, snsId);
if (login == null)
return APIResponse.Send<object>("001", $"[{summary}], 로그인 정보 없음", string.Empty);
var user = await _userRepository.FindUser(login.uid);
if (user == null)
return APIResponse.Send<object>("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<RefreshToken>(refresh))
{
return APIResponse.Send<object>("000", $"[{summary}], 정상",
new { token = token, refresh = refresh.refresh_token });
}
// 토큰 저장에 실패 및 로그인도 실패
return APIResponse.InternalSeverError($"[{summary}], 로그인 동작 실패");
}
public async Task<APIResponseStatus<object>> 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>(user);
var saveLogin = await _repositoryService.SaveData<Login>(login);
var savePermission = await _repositoryService.SaveData<Permission>(permission);
var saveContact = await _repositoryService.SaveData<Contact>(contact);
if (saveUser && saveLogin && savePermission && saveContact)
{
var token = _jwtTokenService.GenerateJwtToken(uid);
var refresh = _jwtTokenService.GenerateRefreshToken(uid);
if (await _repositoryService.SaveData<RefreshToken>(refresh))
{
logUser.log = $"[{summary}] : 정상";
if (await _logRepository.SaveLogUser(logUser))
{
_logger.LogInformation($"[{summary}]: 성공");
}
else
{
_logger.LogInformation($"[{summary}]: 성공 - 로그 저장 실패");
}
return APIResponse.Send<object>("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<APIResponseStatus<object>> 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<RefreshToken>(refresh))
{
return APIResponse.Send<object>("000", $"[{summary}], 로그아웃 정상", string.Empty);
}
}
return APIResponse.UnknownError();
}
public async Task<APIResponseStatus<object>> 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<object>("001", $"[{summary}], 회원 정보 확인 오류", string.Empty);
if (await _repositoryService.DeleteData<User>(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<object>("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<APIResponseStatus<object>> 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<object>("001", $"[{summary}], 회원 정보 확인 오류", string.Empty);
var academyList = await _userRepository.FindAcademies(uid);
_logger.LogInformation($"[{summary}]: 성공");
return APIResponse.Send<object>("000", $"[{summary}], 정상.", academyList);
}
}
}

View File

@ -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<AppController> _logger;
private readonly IRepositoryService _repositoryService;
private readonly JwtTokenService _jwtTokenService;
public AppController(AppDbContext dbContext, ILogger<AppController> logger, IRepositoryService repositoryService, JwtTokenService jwtTokenService)
{
_dbContext = dbContext;
_logger = logger;
_repositoryService = repositoryService;
_jwtTokenService = jwtTokenService;
}
// 이 키값의 제한 시간은 24h이다
[HttpGet]
[CustomOperation("헤더 정보 생성", "헤더에 접근하기 위한 키 값 받아오기", "시스템")]
public async Task<IActionResult> 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>(apiHeader))
{
string msg = "정상 - 로그 저장 실패";
var logProject = new LogProject
{
create_date = DateTime.Now ,
log = $"[{summary}] : 해당 키 유효시간 만료로 인한 새 키 부여"
};
if (await _repositoryService.SaveData<LogProject>(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<APIHeader>(newHeader))
{
string msg = "정상 - 로그 저장 실패";
var logProject = new LogProject
{
create_date = DateTime.Now ,
log = $"[{summary}] : 새로운 등록으로 인한 새 키 부여"
};
// 이거 로그 저장 안되는거 확인!
_logger.LogInformation($"[{summary}] : {logProject.log}");
if (await _repositoryService.SaveData<LogProject>(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<IActionResult> 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<Version>
{
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<IActionResult> 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<IActionResult> 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));
}
}
}

View File

@ -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("오류가 발생하였습니다. 잠시후 다시 시도해주세요.");
}
}

View File

@ -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<MemberController> _logger;
private readonly AppDbContext _dbContext;
private readonly IRepositoryService _repositoryService;
private readonly JwtTokenService _jwtTokenService;
public MemberController(AppDbContext dbContext, ILogger<MemberController> 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("외부 참조");
}
}

View File

@ -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<PushController> _logger;
private readonly IPushQueue _pushQueue;
private readonly AppDbContext _dbContext;
private readonly IRepositoryService _repositoryService;
private readonly JwtTokenService _jwtTokenService;
public PushController(ILogger<PushController> 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<object>))]
public async Task<IActionResult> 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<DBPayload> pushData = new List<DBPayload>();
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<object>))]
public async Task<IActionResult> 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>(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>(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>(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<object>))]
public async Task<IActionResult> 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>(dbPayload))
{
logPush.log = $"[{summary} : 정상 변경";
return Ok(APIResponse.Send("000", $"[{summary}], 정상", Empty));
}
// 로그를 이제 만들어서 추가를 해야 합니다.
if (await _repositoryService.SaveData<LogPush>(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<object>))]
public async Task<IActionResult> 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<string, int, string> 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<DBPayload>(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>(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<object>))]
public async Task<IActionResult> 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<DBPayload>(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>(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<object>))]
public async Task<IActionResult> 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<PushCabinet>(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>(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<object>))]
public async Task<IActionResult> 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

View File

@ -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;
/// <summary>
/// USER는 사용자가 자신의 데이터를 보거나 만들거나 하는 등 직접 사용하는 경우에 사용
/// </summary>
[ApiController]
[Route("/api/v1/in/user")]
[ApiExplorerSettings(GroupName = "사용자")]
public class UserController : ControllerBase
{
private readonly AppDbContext _dbContext;
private readonly ILogger<UserController> _logger;
private readonly JwtTokenService _jwtTokenService;
private readonly IRepositoryService _repositoryService;
public UserController(AppDbContext dbContext, ILogger<UserController> logger, JwtTokenService jwtTokenService,
IRepositoryService repositoryService)
{
_dbContext = dbContext;
_logger = logger;
_jwtTokenService = jwtTokenService;
_repositoryService = repositoryService;
}
[HttpGet]
[CustomOperation("회원 정보 조회", "회원 정보 조회 (자기자신)", "사용자")]
public async Task<IActionResult> 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<IActionResult> 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<IActionResult> 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>(refreshToken))
{
var logUser = new LogUser
{
uid = login.uid,
create_date = DateTime.Now,
create_uid = "System",
log = $"[{summary}] : 정상"
};
await _repositoryService.SaveData<LogUser>(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>(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<IActionResult> 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>(user);
var saveLogin = await _repositoryService.SaveData<Login>(login);
var savePermission = await _repositoryService.SaveData<Permission>(permission);
var saveContact = await _repositoryService.SaveData<Contact>(contact);
if (saveUser && saveLogin && savePermission && saveContact)
{
var token = _jwtTokenService.GenerateJwtToken(uid);
var refreshToken = _jwtTokenService.GenerateRefreshToken(uid);
if (await _repositoryService.SaveData<RefreshToken>(refreshToken))
{
logUser.log = $"[{summary}] : 정상";
if (await _repositoryService.SaveData<LogUser>(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>(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<IActionResult> 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>(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<IActionResult> 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>(user))
logUser.log = $"[{summary}] : 정상";
else
{
logUser.log = $"[{summary}] : 실패";
returnMsg = $"[{summary}], 실패";
returnCode = "001";
}
if (!(await _repositoryService.SaveData<LogUser>(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<IActionResult> 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");
}
*/

View File

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

View File

@ -1,76 +0,0 @@
using Microsoft.IdentityModel.Tokens;
using System;
namespace AcaMate.V1.Models;
/// <summary>
/// 입력 받은 토큰들(Access & Refresh) 자체에 문제가 있는 경우
/// </summary>
public class TokenException: Exception
{
public TokenException(string message) : base(message)
{
}
}
/// <summary>
/// 리프레시 토큰이 만료가 나있는 경우
/// </summary>
public class RefreshRevokeException: Exception
{
public RefreshRevokeException(string message) : base(message)
{
}
}
/// <summary>
/// 참조해야 하는 파일에서 오류가 발생하는 경우
/// </summary>
public class FileNotValidException : Exception
{
public FileNotValidException(string message) : base(message)
{
}
}
/// <summary>
/// 파일 내부에 값을 읽을 때 오류가 발생하는 경우
/// </summary>
public class FileContentNotFoundException : Exception
{
public FileContentNotFoundException(string message) : base(message)
{
}
}
/// <summary>
/// 외부 서비스에 연결시 연결 실패시
/// </summary>
public class ServiceConnectionFailedException : Exception
{
public ServiceConnectionFailedException(string message) : base(message)
{
}
}
/// <summary>
/// PUSH 서비스 중 데이터 사용에 문제가 발생했을시
/// </summary>
public class PushInvalidException : Exception
{
public PushInvalidException(string message) : base(message)
{
}
}
/// <summary>
/// 값이 있어야 하는데 NULL인 경우
/// </summary>
public class OutNULLException : Exception
{
public OutNULLException(string message) : base(message)
{
}
}

View File

@ -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<string> bids { get; set; }
}

View File

@ -1,11 +0,0 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace AcaMate.V1.Models;
[Table("authkey")]
public class AuthKey
{
}

View File

@ -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;
}
}

View File

@ -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; }
}

View File

@ -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<string> 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; } // 부제목 (선택)
}
/// <summary>
/// 푸시 등록하기 위한 apns 여러 데이터 목록
/// </summary>
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; }
}

View File

@ -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;}
}

View File

@ -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; }
}

View File

@ -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<IEnumerable<UserAcademyResult>> GetUserAcademyInfoBySnsIdAsync(string snsId)
{
}*/
}

View File

@ -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<PushData> DequeueAsync(CancellationToken cancellationToken);
}
public class InMemoryPushQueue: IPushQueue
{
private readonly ConcurrentQueue<PushData> _queue = new ConcurrentQueue<PushData>();
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<PushData> 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}");
}
}
}
}

View File

@ -1,98 +0,0 @@
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 Microsoft.Extensions.Options;
using Polly;
using AcaMate.V1.Models;
using Microsoft.Extensions.Options;
using Version = System.Version;
namespace AcaMate.V1.Services;
public interface IApnsPushService
{
Task SendPushNotificationAsync(string deviceToken, Payload payload);
}
public class ApnsPushService: IApnsPushService
{
private readonly HttpClient _httpClient;
private readonly PushFileSetting _setting;
public ApnsPushService(HttpClient httpClient, IOptions<PushFileSetting> 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 관련 파일 확인 필요");
var jsonPayload = JsonSerializer.Serialize(payload);
// var keys =
// JsonSerializer.Deserialize<Dictionary<string, string>>(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}")
{
Content = new StringContent(jsonPayload, Encoding.UTF8, "application/json"),
Version = new Version(2, 0) // HTTP/2 사용
};
// 필수 헤더 추가
request.Headers.Add("apns-topic", _setting.apnsTopic);
request.Headers.Add("apns-push-type", "alert");
var policy = Policy.Handle<HttpRequestException>()
.WaitAndRetryAsync(3, retryAttempt =>
TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)));
await policy.ExecuteAsync(async () =>
{
var response = await _httpClient.SendAsync(request);
if (!response.IsSuccessStatusCode)
{
var errorContent = await response.Content.ReadAsStringAsync();
throw new ServiceConnectionFailedException($"[푸시] : APNS 통신 실패 - {errorContent}");
}
});
// 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}");
// }
}
}

View File

@ -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> ValidateToken(string token, string refresh);
Task<bool> SaveData<T>(T entity, Expression<Func<T, object>> key = null) where T : class;
Task<bool> DeleteData<T>(T entity, Expression<Func<T, object>> key = null) where T : class;
String ReadSummary(Type type, String name);
}
public class RepositoryService: IRepositoryService
{
private readonly AppDbContext _dbContext;
private readonly ILogger<RepositoryService> _logger;
private readonly JwtTokenService _jwtTokenService;
public RepositoryService(AppDbContext dbContext, ILogger<RepositoryService> logger, JwtTokenService jwtTokenService)
{
_dbContext = dbContext;
_logger = logger;
_jwtTokenService = jwtTokenService;
}
// public async Task<ValidateToken> 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, string>(refreshToken, rt => rt.uid);
// // return new ValidateToken
// // {
// // token = token,
// // refresh = refreshToken.refresh_token,
// // uid = uid
// // };
// // }
// }
// }
public async Task<bool> SaveData<T>(T entity, Expression<Func<T, object>> 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<Func<T, bool>>(equalsExpr, parameter);
var entityData = await _dbContext.Set<T>().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<T>().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<T>().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<T>().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<bool> DeleteData<T>(T entity, Expression<Func<T, object>> 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<Func<T, bool>>(equalsExpr, parameter);
var entityData = await _dbContext.Set<T>().FirstOrDefaultAsync(predicate);
if (entityData == null)
{
_logger.LogInformation($"[{typeof(T)}] 삭제 대상 데이터가 존재하지 않습니다. (값 = {value})");
return false;
}
_logger.LogInformation($"[{typeof(T)}] 조건에 맞는 데이터 발견 (값 = {value}): 삭제 진행");
_dbContext.Set<T>().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<T>().FindAsync(keyValues);
if (existingEntity == null)
{
_logger.LogInformation($"[{typeof(T)}] 기본 키 값({string.Join(", ", keyValues)})에 해당하는 삭제 대상 데이터가 존재하지 않습니다.");
return false;
}
_logger.LogInformation($"[{typeof(T)}] 기본 키 값({string.Join(", ", keyValues)})에 해당하는 데이터 발견: 삭제 진행");
_dbContext.Set<T>().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<CustomOperationAttribute>() ?? throw new OutNULLException("swagger summary Load ERROR: NULL");
return att.Summary;
}
}

View File

@ -1,7 +0,0 @@
namespace AcaMate.V1.Services;
public class UserService
{
// priva
}

View File

@ -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<CustomSwaggerOperationFilter>();
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<SwaggerOperationAttribute>()
// .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<CustomSwaggerOperationFilter>();
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<SwaggerOperationAttribute>()
// .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");
});
}
}
}