Compare commits

...

No commits in common. "main" and "dev" have entirely different histories.
main ... dev

86 changed files with 1436 additions and 5887 deletions

78
.gitignore vendored Executable file → Normal file
View File

@ -1,12 +1,8 @@
# 특정 환경에 따라 추가
/private/
/publish/
/bin/
/obj/
./private/
./privacy/
./publish/
publish/
./bin/
@ -19,7 +15,6 @@
*.swp
# macOS 관련 파일 제외
._
._*
.DS_Store
.AppleDouble
@ -54,11 +49,6 @@ obj/
# Blazor 관련
**/wwwroot/_framework/
./wwwroot
**/wwwroot
**/publish
./publish
# Docker 관련
docker-compose.override.yml
@ -66,68 +56,4 @@ Dockerfile
# 기타 캐시 파일
**/*.cache
**/*.tmp# 특정 환경에 따라 추가
/private/
/publish/
/bin/
/obj/
./private/
./privacy/
./publish/
./bin/
# 기본 파일 및 폴더 제외
*.log
*.env
*.bak
*.tmp
*.swp
# macOS 관련 파일 제외
._
._*
.DS_Store
.AppleDouble
.LSOverride
.Spotlight-V100
.Trashes
# Windows 관련
Thumbs.db
ehthumbs.db
desktop.ini
# Visual Studio 관련
.vscode/
.vs/
*.suo
*.user
*.userosscache
*.sln.docstates
# Rider 관련
.idea/
*.sln.iml
# .NET 관련
bin/
obj/
*.pdb
*.dll
*.exe
*.nuget/
# Blazor 관련
**/wwwroot/_framework/
# Docker 관련
docker-compose.override.yml
Dockerfile
# 기타 캐시 파일
**/*.cache
**/*.tmp
**/*.tmp

View File

@ -12,21 +12,13 @@
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.10" />
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="8.0.8"/>
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.10" />
<PackageReference Include="Polly" Version="8.5.2" />
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="8.0.2" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="7.1.0" />
<PackageReference Include="Swashbuckle.AspNetCore.Annotations" Version="7.1.0" />
</ItemGroup>
<ItemGroup>
<Folder Include="Program\Controllers\V1\Interfaces\" />
<Folder Include="publish\debug\" />
<Folder Include="wwwroot\" />
</ItemGroup>
<ItemGroup>
<_ContentIncludedByDefault Remove="wwwroot\css\app.css" />
<_ContentIncludedByDefault Remove="wwwroot\css\tailwind.css" />
<Folder Include="Program\Common\Interfaces\" />
</ItemGroup>

View File

@ -1,77 +0,0 @@
# 2025년 3월 To-do
## 6일 (목)
### 1. PUSH API 만들기
1. [X] 푸시 목록 확인 : [./push]
2. [X] 푸시 만들기 [./push/create]
3. [ ] 푸시 삭제하기 [./push/delete]
4. [ ] 사용자가 받은 전체 푸시 확인 [./push/list]
### 2. 학원 구분이 가능하게 하는 방법으로 바꾸기
1. [x] 푸시 전송
2. [x] 푸시 변경
---
## 7일(금)
### 1. PUSH API 만들기
1. [X] 푸시 삭제하기 [./push/delete]
2. [ ] 사용자가 받은 전체 푸시 확인 [./push/list]
### 2. log 기록 남게 만들기
1. [ ] 유저 관련 테이블들 로그 기록 만들기
2. [ ] 푸시 관련 테이블들 로그 기록 만들기
### 3. 출력 및 오류 메세지 알아보기 쉽게 변경하기
1. [X] 메세지 출력에 summary 추가하기
### 4. DB 저장 & 삭제 로직 변경하기
1. [X] 저장 로직 통일하기
2. [X] 삭제 로직 통일하기
---
## 10일(월)
### 1. PUSH API 만들기
1. [X] 사용자가 받은 전체 푸시 확인 [./push/list]
2. [X] 사용자가 받은 푸시 목록 삭제 [./push/delete/list]
### 2. log 기록 남게 만들기
1. [ ] 유저 관련 테이블들 로그 기록 만들기
2. [X] 푸시 관련 테이블들 로그 기록 만들기
### 3. PUSH API 로직 변경
1. [X] 전송 로직 변경
2. [X] 케비닛 저장 로직 변경
---
## 11일(화)
### 1. USER API 점검 및 수정
1. [X] 회원 정보 조회 [./user]
2. [X] 회원 가입 [./user/register]
3. [X] 로그인 [./user/login]
4. [X] 로그아웃 [./user/logout]
5. [X] 학원 조회 [./user/academy]
### 2. USER API 로그 기록 만들기
1. [X] 필요한 위치에 등록하기
### 3. USER API 만들기
1. [X] 회원 탈퇴 [./user/cancel]
2. [ ] [보류] 회원 정보 변경 [./user/set]
- 근데 회원 정보를 변경하는게 뭐뭐를 변경해야 하는지 아직 정해진게 없어서 이건 일단 보류
---
## 19일(수)
- 모바일(iOS) 앱 작업을 진행하다가 문제를 발견해서 해결중
### 1. Header 및 접근 보안 이슈
1. [X] 헤더 접근 방식 반영하기
2. [ ] 헤더 접근 위한 고유한 값 생성하기
- 로그 저장 안되는 문제 발생
---
## 20일(수)
### 1. Header 및 접근 보안 이슈
1. [X] 헤더 접근 위한 고유값 생성 후 로그 저장에서 발생하는 이슈 확인
- dbSet에 등록을 안했던 문제였음
2. [ ] 이거 헤더 필터 제대로 안걸림
### 2. iOS 앱 과 연결
1. [ ] 앱에서 데이터 받아 사용자 정보 관리 정상적으로 작동되는지 확인

View File

@ -1,63 +0,0 @@
# 2025년 4월 To-do
## 3일 (목)
### 1. 전체적인 구조 재 정립
1. [ ] Controller, Model, Repository, Service, DTO 확립
2. [ ] 확립된 구조에 맞게 폴더 구조 변경 및 네이밍 정의
3. [ ] 변경된 구조에 맞게 코드 리팩토링
4. [ ] 응답이나 예외에 맞는 일관되게 코드 통일화
## 리팩토링
### 리팩토링의 필요성
1. 현재 C,R,S 등 의 폴더를 만들어는 뒀으나 해당 구조에 맞게 작업이 올바르게 되지 않음
2. 제대로 구분되지 않다보니 하나의 Controller 에서 다양한 역할과 책임을 맡고 있음
3. 그러다보니 명확한 확장과 구조의 파악이 어려움
### 목표
- 책임과 역할에 맞게 명확한 구분을 한다.
#### 원칙
1. Common, Controller, Model, Repository, Service, DTO 등 역할별 책임에 맞게 계층 분리
2. 도메인 중심으로 각 단위별로 묶기
### 구조 정의
```
/Controllers
└─ /V1
└─ /Interfaces
└─ I{Domain}.cs
└─ {Domain}Contreoller.cs
/Services
└─ /V1
└─ /Interfaces
└─ I{Domain}Service.cs
└─ {Domain}Service.cs
/Repositories
└─ /V1
└─ /Interfaces
└─ I{Domain}Repository.cs
└─ {Domain}Repository.cs
/Models
└─ /Entities
└─ {Domain}.cs
└─ /DTOs
└─ /V1
└─ {Domain}Dto.cs
/Common
└─ /{공통기능}
└─ {공통기능관련}.cs
SwaggerConfigure.cs
Program.cs
```
---
## 14일 (월)
### 1. 회원가입 이후 동작할 기능 구현하기
1. [ ] 학원 목록 관련 기능 추가
### 2. Blazor를 활용한 Admin 페이지 생성해보기

View File

@ -1,26 +0,0 @@
# AcaMate API 문서
## 개요
## 프로젝트 구조
### 각 폴더 간 관계
#### 역할
- Controller: API 요청을 처리하고 응답을 반환하는 역할
- Service: 비즈니스 로직을 처리하는 역할
- Repository: 데이터베이스와의 상호작용을 처리하는 역할
- Model: 데이터 구조를 정의하는 역할
#### 폴더 관계
- Controller 는 Service 를 참조하고, Service 는 Repository 를 참조한다.
- Controller 는 Service 와 1:N 관계를 가진다.
- Service 는 Repository 와 1:N 관계를 가진다.
- Controller에서 Repository를 직접 참조하지 않는다.
- Repository 와 Service 는 모두 Interface 를 통해 의존성을 주입받는다.
- Common 폴더는 모든 계층에서 공통적으로 사용되는 유틸리티나 헬퍼 클래스를 포함한다.
### 오류 코드
- 0xx : 성공
- 1xx : 입력 오류
- 2xx : 출력 오류
- 3xx : 통신 오류
- 999 : 알 수 없는 오류

16
Jenkinsfile vendored
View File

@ -9,22 +9,6 @@ pipeline {
APP_VOLUME = '/src'
}
stages {
stage('Clear Repository') {
steps {
script {
sh """
echo 'Clearing Front directory'
docker run --rm -v /volume1/AcaMate/PROJECT/Application/Back:/back alpine \
sh -c "find /back -mindepth 1 -maxdepth 1 \\
! -name 'private' \\
! -name 'publish' \\
! -name 'wwwroot' \\
-exec rm -rf {} +"
echo 'Clean complete'
"""
}
}
}
stage('Clone Repository') {
steps {
git url: 'https://git.ipstein.myds.me/AcaMate/AcaMate_API.git', branch: env.GIT_BRANCH

View File

@ -1,40 +1,23 @@
using Pomelo.EntityFrameworkCore;
using System.Text;
using AcaMate.Common.Chat;
using Microsoft.EntityFrameworkCore;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
using Microsoft.AspNetCore.SignalR;
using Microsoft.Extensions.FileProviders;
using AcaMate.Common.Models;
using AcaMate.V1.Services;
using AcaMate.Common.Data;
using AcaMate.V1.Controllers;
using Microsoft.Extensions.DependencyInjection;
using System.Net.Http;
using System.Security.Cryptography.X509Certificates;
using System.Text.Json;
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;
Boolean isLocal = false;
// 로컬 테스트 할 때는 이거 키고 아니면 끄기
// isLocal = true;
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) =>
{
var httpContextAccessor = serviceProvider.GetRequiredService<IHttpContextAccessor>();
@ -47,7 +30,7 @@ builder.Services.AddDbContext<AppDbContext>(optionsAction: (serviceProvider, opt
options.UseMySql(baseConnectionString, ServerVersion.AutoDetect(baseConnectionString));
});
builder.Services.AddHttpContextAccessor();
// DB 설정부 끝
@ -88,68 +71,11 @@ builder.Services.AddAuthentication(options =>
});
// JWT 설정부 끝
// PUSH 설정부
// 설정 바인딩 (appsettings.json의 "ApnsPushService" 섹션)
builder.Services.Configure<PushFileSetting>(builder.Configuration.GetSection("PushFileSetting"));
// HttpClientFactory를 이용한 ApnsPushService 등록 (핸들러에 인증서 추가)
builder.Services.AddHttpClient<IPushService, PushService>(client =>
{
var settings = builder.Configuration.GetSection("PushFileSetting").Get<PushFileSetting>();
client.BaseAddress = new Uri(settings.uri);
client.Timeout = TimeSpan.FromSeconds(60);
})
.ConfigurePrimaryHttpMessageHandler(() =>
{
var config = builder.Configuration.GetSection("PushFileSetting").Get<PushFileSetting>();
var handler = new HttpClientHandler();
// p12PWPath 파일에서 비밀번호 읽어오기 (예시: JSON {"Password": "비밀번호"})
var json = File.ReadAllText(config.p12PWPath);
var keys = JsonSerializer.Deserialize<Dictionary<string, string>>(json);
var certificate = new X509Certificate2(config.p12Path, keys["Password"]);
handler.ClientCertificates.Add(certificate);
handler.SslProtocols = System.Security.Authentication.SslProtocols.Tls12;
return handler;
});
// InMemoryPushQueue와 백그라운드 서비스 등록
builder.Services.AddSingleton<IPushQueue, InMemoryPushQueue>();
builder.Services.AddHostedService<PushBackgroundService>();
// PUSH 설정부 끝
builder.Services.AddControllers();
// 세션 설정
// IN-MEMORY 캐시
builder.Services.AddDistributedMemoryCache();
builder.Services.AddSession();
// ==== SCOPED 으로 등록 할 서비스 ==== //
// 여기다가 API 있는 컨트롤러들 AddScoped 하면 되는건가?
builder.Services.AddScoped<JwtTokenService>();
builder.Services.AddScoped<ILogRepository, LogRepository>();
builder.Services.AddScoped<IRepositoryService, RepositoryService>();
builder.Services.AddScoped<ISessionService, SessionService>();
builder.Services.AddScoped<IHeaderConfig, HeaderConfigRepository>();
builder.Services.AddScoped<IUserService, UserService>();
builder.Services.AddScoped<IKakaoService, KakaoService>();
builder.Services.AddScoped<IUserRepository, UserRepository>();
builder.Services.AddScoped<IAppService, AppService>();
builder.Services.AddScoped<IAppRepository, AppRepository>();
builder.Services.AddScoped<IClassService, ClassService>();
builder.Services.AddScoped<IClassRepository, ClassRepository>();
// builder.Services.AddScoped<IPushService, PushService>();
builder.Services.AddScoped<IPushRepository, PushRepository>();
builder.Services.AddScoped<SessionManager>();
builder.Services.AddScoped<DedicateWeb>();
builder.Services.AddScoped<AcaMate.Common.Token.JwtTokenService>();
builder.Services.AddScoped<IRepositoryService, AcaMate.V1.Services.RepositoryService>();
// builder.Services.AddScoped<UserService>(); //
// builder.Services.AddScoped<UserController>();
@ -177,36 +103,27 @@ builder.Services.AddCors(option =>
// 로그 설정 부분
builder.Logging.ClearProviders();
builder.Logging.AddConsole();
builder.Logging.SetMinimumLevel(builder.Environment.IsDevelopment() ? LogLevel.Trace : LogLevel.Warning);
if (isLocal)
{
builder.WebHost.UseUrls("http://0.0.0.0:5144");
if (builder.Environment.IsDevelopment()) {
builder.Logging.SetMinimumLevel(LogLevel.Trace);
}
else
{
builder.WebHost.UseUrls(builder.Environment.IsDevelopment()? "http://0.0.0.0:7004":"http://0.0.0.0:7003");
builder.Logging.SetMinimumLevel(LogLevel.Warning);
}
// 로컬 테스트 위한 부분 (올릴때는 꺼두기)
// builder.WebHost.UseUrls("http://0.0.0.0:5144");
///// ===== builder 설정 부 ===== /////
var app = builder.Build();
string staticRoot;
if (isLocal)
{
staticRoot = app.Environment.IsDevelopment() ? //"/publish/debug/wwwroot" : "/publish/release/wwwroot" ;
Path.Combine(Directory.GetCurrentDirectory(), "publish", "debug", "wwwroot") : Path.Combine(Directory.GetCurrentDirectory(), "publish", "release", "wwwroot") ;
}
else
{
staticRoot = app.Environment.IsDevelopment() ?
"/src/publish/debug/wwwroot" : "/src/publish/release/wwwroot" ;
}
if (app.Environment.IsDevelopment())
{
// app.UseSwagger();
// app.UseSwaggerUI();
app.UseCustomSwaggerUI();
app.UseDeveloperExceptionPage(); // 좀더 자세한 예외 정보 제공
}
@ -217,39 +134,20 @@ else
}
// 로컬 테스트 위한 부분 (올릴떄는 켜두기)
// app.UseHttpsRedirection();
app.UseStaticFiles(new StaticFileOptions
{
FileProvider = new PhysicalFileProvider(staticRoot),
RequestPath = ""
});
app.UseHttpsRedirection();
app.UseRouting();
app.UseSession();
// app.MapControllers();
app.UseCors("CorsPolicy");
app.UseAuthentication();
app.UseAuthorization();
// 헤더 미들웨어 부분
app.UseMiddleware<APIHeaderMiddleware>
((object)new string[] { "iOS_AM_Connect_Key", "And_AM_Connect_Key", "Web-AM-Connect-Key" });
app.UseWebSockets();
Console.WriteLine($"[정적 파일 경로] {staticRoot}");
app.UseEndpoints(end =>
{
ControllerEndpointRouteBuilderExtensions.MapControllers(end);
end.MapControllers();
end.MapHub<ChatHub>("/chatHub");
end.MapFallback(context => { return context.Response.SendFileAsync(Path.Combine(staticRoot, "index.html")); });
});
//예외처리 미들웨어 부분
app.UseMiddleware<ExceptionMiddleware>();
app.Run();

View File

@ -1,76 +0,0 @@
using Back.Program.Common.Auth.Interface;
using Back.Program.Common.Data;
using Microsoft.EntityFrameworkCore;
namespace Back.Program.Common.Auth
{
///
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))
{
await _next(context);
return;
}
if (context.Request.Path.Value != null && context.Request.Path.Value.Contains("/out/"))
{
await _next(context);
return;
}
// 정적 파일 요청은 미들웨어 건너뜀
var path = context.Request.Path.Value;
if (path != null && (path.StartsWith("/api")))
{
// 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))
{
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;
}
await _next(context);
return;
}
await _next(context);
}
}
}

View File

@ -1,78 +0,0 @@
using System.Security.Claims;
using System.Text.Json;
using Back.Program.Common.Data;
using Back.Program.Common.Model;
using Back.Program.Services.V1.Interfaces;
namespace Back.Program.Common.Auth;
public class DedicateWeb(
ILogger<DedicateWeb> _logger,
SessionManager _sessionManager,
IRepositoryService _repositoryService,
JwtTokenService _jwtTokenService,
IAppService _appService)
{
public async Task<(string code, string result)> GetAuthToken()
{
var summary = "GetAuthToken";
try
{
// 1. 세션에서 토큰 가져오기
var (result, token) = await _sessionManager.GetString("token");
_logger.LogInformation($"세션에서 토큰 가져오기 결과: {result}, 토큰: {token}");
if (!result || string.IsNullOrEmpty(token))
{
_logger.LogWarning($"세션에 토큰이 없습니다");
return ("200", "세션에 토큰 없음");
}
// 2. 토큰 검증
var validToken = await _jwtTokenService.ValidateToken(token);
_logger.LogInformation($"토큰 검증 결과: {validToken != null}");
if (validToken == null)
{
// 3. 토큰이 유효하지 않으면 리프레시 토큰으로 새 토큰 발급 시도
var (refreshResult, refreshToken) = await _sessionManager.GetString("refresh");
_logger.LogInformation($"리프레시 토큰 가져오기 결과: {refreshResult}, 토큰: {refreshToken}");
if (!refreshResult || string.IsNullOrEmpty(refreshToken))
{
_logger.LogWarning($"리프레시 토큰이 없습니다");
return ("200", "리프레시 토큰 없음");
}
// 4. 리프레시 토큰으로 새 토큰 발급
var retryResult = await _appService.RetryAccess(summary, refreshToken);
_logger.LogInformation($"토큰 재발급 결과: {retryResult.status.code}");
if (retryResult.status.code == "000")
{
// 5. 새 토큰을 세션에 저장
var data = JsonSerializer.Deserialize<JsonElement>(JsonSerializer.Serialize(retryResult.data));
var newToken = data.GetProperty("access").GetString();
await _sessionManager.SetString("token", newToken);
_logger.LogInformation($"[{summary}] 새 토큰 세션 저장 완료");
return ("000", newToken);
}
else
{
_logger.LogWarning($"[{summary}] 토큰 갱신 실패: {retryResult.status.message}");
return ("102", "토큰 갱신 실패");
}
}
return ("000", token);
}
catch (Exception ex)
{
_logger.LogError($"[{summary}] 세션 데이터 조회 중 오류: {ex.Message}");
_logger.LogError($"[{summary}] 스택 트레이스: {ex.StackTrace}");
return ("100", "세션 데이터 조회 중 오류");
}
}
}

View File

@ -1,27 +0,0 @@
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

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

View File

@ -1,110 +0,0 @@
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Security.Cryptography;
using System.Text;
using Back.Program.Common.Model;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
namespace Back.Program.Common.Auth
{
/// <summary>
/// 사용자의 정보를 바탕으로 JWT 생성
/// </summary>
public class JwtTokenService
{
private readonly JwtSettings _jwtSettings;
private readonly ILogger<JwtTokenService> _logger;
public JwtTokenService(IOptions<JwtSettings> jwtSettings, ILogger<JwtTokenService> logger)
{
_jwtSettings = jwtSettings.Value;
_logger = logger;
}
// JWT 토큰 생성
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 서비스 내의 인증 메서드
/// </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,25 +1,16 @@
using Back.Program.Services.V1.Interfaces;
using Microsoft.AspNetCore.SignalR;
using System.Threading.Tasks;
namespace Back.Program.Common.Chat;
namespace AcaMate.Common.Chat;
public class ChatHub : Hub
{
private readonly ILogger<ChatHub> _logger;
private readonly IChatService _chatService;
public ChatHub(ILogger<ChatHub> logger, IChatService chatService)
{
_logger = logger;
_chatService = chatService;
}
// 클라이언트에서 메시지를 보내면 모든 사용자에게 전송
public async Task SendMessage(string user, string message)
{
Console.WriteLine($"Message received: {user}: {message}");
await Clients.All.SendAsync("ReceiveMessage", user, message);
}
// 특정 사용자에게 메시지를 보냄
@ -43,15 +34,4 @@ public class ChatHub : Hub
Console.WriteLine("OnDisconnectedAsync");
await base.OnDisconnectedAsync(exception);
}
public async Task JoinRoom(string cid)
{
await Groups.AddToGroupAsync(Context.ConnectionId, cid);
}
public async Task JoinGroup(string cid, string groupName)
{
await Groups.AddToGroupAsync(Context.ConnectionId, groupName);
}
}

View File

@ -1,70 +1,35 @@
using Back.Program.Common.Model;
using Back.Program.Models.Entities;
using AcaMate.Common.Models;
using Microsoft.EntityFrameworkCore;
using Version = Back.Program.Models.Entities.Version;
using AcaMate.V1.Models;
using Version = AcaMate.V1.Models.Version;
namespace Back.Program.Common.Data
namespace AcaMate.Common.Data;
//database=AcaMate;
public class AppDbContext: DbContext
{
//database=AcaMate;
public class AppDbContext: DbContext
public AppDbContext(DbContextOptions<AppDbContext> options) : base(options)
{
public AppDbContext(DbContextOptions<AppDbContext> options) : base(options)
{
}
}
//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: PUSH
public DbSet<DBPayload> DBPayload { get; set; }
public DbSet<PushCabinet> PushCabinet { 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: CLASS
public DbSet<Class_Info> Class_Info { get; set; }
public DbSet<Class_Attendance> Class_Attendance { get; set; }
public DbSet<Class_Map> Class_Map { 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; }
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<DBPayload>()
.HasKey(p => new { p.bid, p.pid });
// modelBuilder.Entity<LogPush>().HasNoKey();
modelBuilder.Entity<Class_Attendance>()
.HasKey(ca => new { ca.cid, ca.uid, ca.attendace_date});
modelBuilder.Entity<Class_Map>()
.HasKey(ca => new { ca.cid, ca.uid});
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<User_Academy>()
.HasKey(ua => new { ua.uid, ua.bid });
}
}

View File

@ -1,53 +0,0 @@
namespace Back.Program.Common.Data;
public class SessionManager
{
private readonly IHttpContextAccessor _http;
public SessionManager(IHttpContextAccessor http)
{
_http = http;
}
public Task<bool> SetString(string key, string value)
{
try
{
_http.HttpContext.Session.SetString(key, value);
return Task.FromResult(true);
}
catch
{
return Task.FromResult(false);
}
}
public Task<(bool result, string data)> GetString(string key)
{
try
{
var value = _http.HttpContext.Session.GetString(key);
return Task.FromResult((true, value ?? string.Empty));
}
catch
{
return Task.FromResult((false, ""));
}
}
public Task<bool> Remove(string key)
{
try
{
_http.HttpContext.Session.Remove(key);
return Task.FromResult(true);
}
catch
{
return Task.FromResult(false);
}
}
}

View File

@ -0,0 +1,116 @@
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;
namespace AcaMate.Common.Token;
public class JwtTokenService
{
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 uid)//, string role)
{
// 1. 클레임(Claim) 설정 - 필요에 따라 추가 정보도 포함
var claims = new List<Claim>
{
// 토큰 주체(sub) 생성을 위해 값으로 uid를 사용함 : 토큰이 대표하는 고유 식별자
new Claim(JwtRegisteredClaimNames.Sub, uid),
// 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 Convert.ToBase64String(randomNumber);
return new RefreshToken()
{
uid = uid,
refresh_token = Convert.ToBase64String(randomNumber),
create_Date = DateTime.UtcNow,
expire_date = DateTime.UtcNow.AddDays(_jwtSettings.RefreshTokenExpiryDays)
};
}
public 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 e)
{
Console.WriteLine($"검증 실패 {e}");
return null;
}
}
}

View File

@ -1,49 +0,0 @@
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,28 +0,0 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace Back.Program.Common.Model
{
[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 class SessionData
{
public string key { get; set; }
public string value { get; set; }
}
}
/*
h_key : h_value
iOS_AM_Connect_Key
And_AM_Connect_Key
Web-AM-Connect-Key
*/

View File

@ -1,25 +0,0 @@
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,41 +1,39 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.ComponentModel.DataAnnotations;
namespace Back.Program.Common.Model
namespace AcaMate.Common.Models;
public class JwtSettings
{
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; }
// 이건 로그아웃시에 폐기 시킬예정이니 그떄 변경하는걸로 합시다.
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 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; }
// 이건 로그아웃시에 폐기 시킬예정이니 그떄 변경하는걸로 합시다.
public DateTime? revoke_Date { get; set; }
}
public class ValidateToken
{
public string token { get; set; }
public string refresh { get; set; }
public string uid { get; set; }
}
/*
"""

View File

@ -1,74 +1,78 @@
using System.Text.Json;
namespace Back.Program.Common.Model
namespace AcaMate.Common.Models;
public class APIResponseStatus<T>
{
public class APIResponseStatus<T>
public Status status { get; set; }
public T? data { get; set; }
public string JsonToString()
{
public Status status { get; set; }
public T? data { get; set; }
public string JsonToString()
{
return JsonSerializer.Serialize(this);
}
return JsonSerializer.Serialize(this);
}
}
public class Status
public class Status
{
public string code { get; set; }
public string message { get; set; }
}
public static class DefaultResponse
{
// private static readonly Lazy<ErrorResponse> _instance = new Lazy<ErrorResponse>();
// public static ErrorResponse Instace => _instance.Value;
// private ErrorResponse()
// {
// // 외부 초기화 방지
// }
public static APIResponseStatus<string> Success = new APIResponseStatus<string>
{
public string code { get; set; }
public string message { get; set; }
status = new Status()
{
code = "000",
message = "정상"
}
};
}
public static class APIResponse
public static APIResponseStatus<string> InvalidInputError = new APIResponseStatus<string>
{
public static APIResponseStatus<T> Send<T>(string code, string message, T data)
status = new Status()
{
return new APIResponseStatus<T>
{
status = new Status()
{
code = code,
message = message
},
data = data
};
}
/// <summary>
/// 반환값 없는 API 정상 동작시
/// </summary>
public static APIResponseStatus<object> Success (){
return Send<object>("000", "정상", new {});
code = "001",
message = "입력 값이 유효하지 않습니다."
}
};
public static APIResponseStatus<object> InvalidInputError(string? msg = null)
public static APIResponseStatus<string> NotFoundError = new APIResponseStatus<string>
{
status = new Status()
{
return Send<object>("100", msg ?? "입력 값이 유효하지 않습니다.", new {});
code = "002",
message = "알맞은 값을 찾을 수 없습니다."
}
};
public static APIResponseStatus<object> AccessExpireError(string? msg = null)
public static APIResponseStatus<string> InternalSeverError = new APIResponseStatus<string>
{
status = new Status
{
return Send<object>("101", msg ?? "엑세스 토큰이 유효하지 않습니다.", new {});
code = "003",
message = "통신에 오류가 발생하였습니다."
}
};
// -- -- -- OUTPUT ERROR -- -- -- //
public static APIResponseStatus<object> NotFoundError(string? msg = null)
public static APIResponseStatus<string> UnknownError = new APIResponseStatus<string>
{
status = new Status()
{
return Send<object>("200", msg ?? "알맞은 값을 찾을 수 없습니다.", new {});
code = "999",
message = "알 수 없는 오류가 발생하였습니다.."
}
public static APIResponseStatus<object> InternalSeverError(string? msg = null)
{
return Send<object>("300", msg ?? "통신에 오류가 발생하였습니다.", new {});
}
public static APIResponseStatus<object> UnknownError(string? msg = null)
{
return Send<object>("999", msg ?? "알 수 없는 오류가 발생하였습니다.", new {});
}
}
}
};
}

View File

@ -1,123 +0,0 @@
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;
private readonly SessionManager _sessionManager;
public AppController(AppDbContext dbContext, ILogger<AppController> logger, IRepositoryService repositoryService,
JwtTokenService jwtTokenService, IAppService appService, IAppRepository appRepository, SessionManager sessionManager)
{
_dbContext = dbContext;
_logger = logger;
_repositoryService = repositoryService;
_jwtTokenService = jwtTokenService;
_appService = appService;
_appRepository = appRepository;
_sessionManager = sessionManager;
}
// 이 키값의 제한 시간은 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);
}
[HttpGet("session/get")]
[CustomOperation("세션 정보 읽어오기", "세션 정보를 읽어오는 동작 수행", "시스템")]
public async Task<IActionResult> GetSessionData(string key)
{
if (string.IsNullOrEmpty(key))
{
return BadRequest(APIResponse.InvalidInputError());
}
var (success, value) = await _sessionManager.GetString(key);
if (!success)
{
return BadRequest(APIResponse.InvalidInputError());
}
string summary = _repositoryService.ReadSummary(typeof(AppController), "GetSessionData");
return Ok(APIResponse.Send("000", $"[{summary}], 정상", new { data = value }));
}
[HttpPost("session/set")]
[CustomOperation("세션 정보 저장하기", "세션 정보에 저장하는 동작 수행", "시스템")]
public async Task<IActionResult> SetSessionData([FromBody] SessionData[] requests)
{
if(requests == null || requests.Length == 0)
{
return BadRequest(APIResponse.InvalidInputError());
}
Console.WriteLine($"받은 세션 데이터: {JsonSerializer.Serialize(requests)}");
foreach(var request in requests)
{
Console.WriteLine($"세션 저장 시도 - key: {request.key}, value: {request.value}");
var success = await _sessionManager.SetString(request.key, request.value);
if (!success)
{
Console.WriteLine($"세션 저장 실패 - key: {request.key}");
return BadRequest(APIResponse.InvalidInputError());
}
Console.WriteLine($"세션 저장 성공 - key: {request.key}");
}
return Ok(APIResponse.Send("000", $"[세션 저장]: 정상", new { }));
}
}
}

View File

@ -1,6 +0,0 @@
namespace Back.Program.Controllers.V1;
public class ChatController
{
}

View File

@ -1,31 +0,0 @@
using Back.Program.Common.Model;
using Microsoft.AspNetCore.Mvc;
using Back.Program.Services.V1.Interfaces;
using Back.Program.Repositories.V1.Interfaces;
using Microsoft.AspNetCore.Http.HttpResults;
namespace Back.Program.Controllers.V1;
[ApiController]
[Route("/api/v1/in/class")]
[ApiExplorerSettings(GroupName = "수업 관리")]
public class ClassController(
ILogger<ClassController> logger,
// SessionManager sessionManager,
// DedicateWeb dedicateWeb,
IRepositoryService repositoryService,
IClassService classService)
: ControllerBase
{
// [HttpGet("info")]
[HttpGet]
[CustomOperation("수업 정보 조회", "수업 정보 조회", "수업관리")]
public async Task<IActionResult> GetClassInfo(string cid)
{
if (string.IsNullOrEmpty(cid)) return BadRequest(APIResponse.InvalidInputError());
if (!ModelState.IsValid) return BadRequest(APIResponse.InvalidInputError());
string summary = repositoryService.ReadSummary(typeof(ClassController), "GetClassInfo");
var result = await classService.GetClassInfo(summary, cid);
return Ok(result);
}
}

View File

@ -1,15 +0,0 @@
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

@ -1,48 +0,0 @@
using Back.Program.Common.Auth;
using Back.Program.Common.Data;
using Back.Program.Services.V1;
using Back.Program.Services.V1.Interfaces;
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

@ -1,148 +0,0 @@
using System.Text.Json;
using Back.Program.Common.Data;
using Microsoft.AspNetCore.Http.HttpResults;
using Microsoft.AspNetCore.Mvc;
using Back.Program.Common.Model;
using Back.Program.Controllers.V1;
using Back.Program.Services.V1;
using Back.Program.Services.V1.Interfaces;
using Back.Program.Models.APIResponses;
namespace Back.Program.Controllers;
// TO-DO: 여기 controller, service, repository 분리 필요
[ApiController]
[Route("/api/v1/out/user")]
[ApiExplorerSettings(GroupName = "외부 동작(사용자)")]
public class OutController: ControllerBase
{
private readonly ILogger<OutController> _logger;
private readonly IRepositoryService _repositoryService;
private readonly IUserService _userService;
private readonly IKakaoService _kakaoService;
private readonly SessionManager _sessionManager;
public OutController(ILogger<OutController> logger,
IRepositoryService repositoryService, IUserService userService, IKakaoService kakaoService, SessionManager sessionManager)
{
_logger = logger;
_repositoryService = repositoryService;
_userService = userService;
_kakaoService = kakaoService;
_sessionManager = sessionManager;
}
[HttpGet("kakao/auth")]
[CustomOperation("카카오 로그인", "카카오 로그인 동작", "사용자")]
public async Task<IActionResult> KakaoLogin([FromQuery] string? scope, [FromQuery] string? redirectPath)
{
if (!string.IsNullOrEmpty(redirectPath))
{
await _sessionManager.SetString("redirectPath", redirectPath);
}
var url = await _kakaoService.GetAuthorizationUrl(scope ?? "");
Console.WriteLine($"카카오 로그인 API: {url}");
return Ok(new { url });
}
[HttpGet("kakao/redirect")]
public async Task<IActionResult> RedirectFromKakao([FromQuery] string code)
{
_logger.LogInformation("카카오 리다이렉트 시작");
var (success, response) = await _kakaoService.Redirect(code);
_logger.LogInformation($"리다이렉트 결과: {success}, 응답: {response}");
if (success)
{
var (idSuccess, idResponse) = await _kakaoService.UserMe(response);
_logger.LogInformation($"사용자 정보 조회 결과: {idSuccess}, 응답: {idResponse}");
if (idSuccess)
{
var json = JsonDocument.Parse(idResponse);
if (json.RootElement.TryGetProperty("id", out var idElement))
{
var snsId = idElement.ToString();
_logger.LogInformation($"카카오 ID: {snsId}");
var loginResult = await _userService.Login("SNS Login", "ST01", snsId);
_logger.LogInformation($"로그인 결과: {loginResult.JsonToString()}");
if (loginResult.status.code == "000")
{
var data = JsonSerializer.Deserialize<LoginAPIResponse>(JsonSerializer.Serialize(loginResult.data));
_logger.LogInformation($"로그인 데이터: {JsonSerializer.Serialize(data)}");
if (data != null)
{
string token = data.token;
string refresh = data.refresh;
_logger.LogInformation($"토큰 저장 시도 - token: {token}, refresh: {refresh}");
if (await _sessionManager.SetString("token", token) &&
await _sessionManager.SetString("refresh", refresh))
{
_logger.LogInformation("세션 저장 성공");
var (hasPath, redirectPath) = await _sessionManager.GetString("redirectPath");
await _sessionManager.Remove("redirectPath"); // 사용 후 세션에서 제거
var redirectUrl = hasPath && !string.IsNullOrEmpty(redirectPath)
? $"{redirectPath}?auth=true"
: "/about?auth=true";
_logger.LogInformation($"리다이렉트 URL: {redirectUrl}");
return Redirect(redirectUrl);
}
else
{
_logger.LogError("세션 저장 실패");
}
}
else
{
_logger.LogError("로그인 데이터가 null입니다");
}
}
else if (loginResult.status.code == "001")
{
_logger.LogInformation("회원가입 필요");
if (await _sessionManager.SetString("snsId", snsId))
{
return Redirect("/auth/register");
}
}
else
{
_logger.LogError($"로그인 실패: {loginResult.status.message}");
return BadRequest(new { error = "로그인 실패", message = loginResult.status.message });
}
}
else
{
_logger.LogError("카카오 ID를 찾을 수 없습니다");
}
}
else
{
_logger.LogError($"사용자 정보 조회 실패: {idResponse}");
}
}
else
{
_logger.LogError($"카카오 리다이렉트 실패: {response}");
}
return BadRequest();
}
// // 로그아웃 API 예시 (이미 있다면 해당 위치에 추가)
// [HttpGet("logout")]
// public IActionResult Logout()
// {
// // 세션/쿠키 등 로그아웃 처리
// Response.Cookies.Delete("IsLogin");
// // 기타 로그아웃 처리 로직...
// return Redirect("/");
// }
}

View File

@ -1,133 +0,0 @@
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

@ -1,37 +0,0 @@
using Back.Program.Common.Data;
using Back.Program.Services.V1.Interfaces;
using Microsoft.AspNetCore.Mvc;
namespace Back.Program.Controllers.V1;
/// <summary>
/// USER는 사용자가 자신의 데이터를 보거나 만들거나 하는 등 직접 사용하는 경우에 사용
/// </summary>
[ApiController]
[Route("/api/v1/in/user")]
[ApiExplorerSettings(GroupName = "")]
public class SessionController : ControllerBase
{
private readonly ILogger<SessionController> _logger;
private readonly IRepositoryService _repositoryService;
private readonly SessionManager _sessionManager;
private readonly ISessionService _sessionService;
private SessionController(ILogger<SessionController> logger,
IRepositoryService repositoryService, SessionManager sessionManager, ISessionService sessionService)
{
_logger = logger;
_repositoryService = repositoryService;
_sessionManager = sessionManager;
_sessionService = sessionService;
}
[HttpGet("session/user")]
[CustomOperation("세션 정보 확인", "세션 정보 확인", "사용자")]
public async Task<IActionResult> GetSessionData()
{
string summary = _repositoryService.ReadSummary(typeof(UserController), "GetSessionData");
var result = await _sessionService.GetSessionData(summary);
return Ok(result);
}
}

View File

@ -1,193 +0,0 @@
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.Http.HttpResults;
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(
ILogger<UserController> logger,
SessionManager sessionManager,
DedicateWeb dedicateWeb,
IRepositoryService repositoryService,
IUserService userService)
: ControllerBase
{
private readonly ILogger<UserController> _logger = logger;
private readonly SessionManager _sessionManager = sessionManager;
[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");
if (token == "VO00")
{
var (code, WebAuthResult) = await dedicateWeb.GetAuthToken();
if (code != "000") return Ok(APIResponse.Send(code, $"{WebAuthResult}", new { }));
token = WebAuthResult;
}
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");
if (token == "VO00")
{
var (code, WebAuthResult) = await dedicateWeb.GetAuthToken();
if (code != "000") return Ok(APIResponse.Send(code, $"{WebAuthResult}", new { }));
token = WebAuthResult;
}
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");
if (token == "VO00")
{
var (code, WebAuthResult) = await dedicateWeb.GetAuthToken();
if (code != "000") return Ok(APIResponse.Send(code, $"{WebAuthResult}", new { }));
token = WebAuthResult;
}
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), "GetAcademyData");
if (token == "VO00")
{
var (code, WebAuthResult) = await dedicateWeb.GetAuthToken();
if (code != "000") return Ok(APIResponse.Send(code, $"{WebAuthResult}", new { }));
token = WebAuthResult;
}
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

@ -1,7 +0,0 @@
namespace Back.Program.Models.APIResponses;
public class LoginAPIResponse
{
public string token { get; set; } = string.Empty;
public string refresh { get; set; } = string.Empty;
}

View File

@ -1,16 +0,0 @@
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

@ -1,32 +0,0 @@
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

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

View File

@ -1,147 +0,0 @@
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

@ -1,68 +0,0 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace Back.Program.Models.Entities;
[Table("class_info")]
public class Class_Info
{
[Required(ErrorMessage = "필수 항목 누락")]
[MaxLength(10)]
public required string id { get; set; } // AMC + 4숫자 + 3대문자
[Required(ErrorMessage = "필수 항목 누락")]
[MaxLength(100)]
public required string name { get; set; }
[Required(ErrorMessage = "필수 항목 누락")]
[MaxLength(70)]
public required string uid { get; set; } // 담당 선생님 구분 코드
[Required(ErrorMessage = "필수 항목 누락")]
public required DateTime start_date { get; set; }
public DateTime? end_date { get; set; }
[Required(ErrorMessage = "필수 항목 누락")]
public required byte day { get; set; } // 수업 요일 비트 (월요일부터 가장 좌측 비트)
[Required(ErrorMessage = "필수 항목 누락")]
[MaxLength(4)]
public required string start_time { get; set; } // 수업 시작 시간
[Required(ErrorMessage = "필수 항목 누락")]
[MaxLength(4)]
public required string end_time { get; set; } // 수업 종료 시간
}
[Table("class_map")]
public class Class_Map
{
[Key]
[Required(ErrorMessage = "필수 항목 누락")]
[MaxLength(10)]
public required string cid { get; set; } // 강의 구분 코드
[Required(ErrorMessage = "필수 항목 누락")]
[MaxLength(70)]
public required string uid { get; set; } // 학생(유저) 구분 코드
}
[Table("class_attendance")]
public class Class_Attendance
{
[Key]
[Required(ErrorMessage = "필수 항목 누락")]
[MaxLength(10)]
public required string cid { get; set; } // 강의 구분 코드
[Required(ErrorMessage = "필수 항목 누락")]
[MaxLength(70)]
public required string uid { get; set; } // 학생(유저) 구분 코드
[Required(ErrorMessage = "필수 항목 누락")]
public required DateTime attendace_date { get; set; } // 출석 일자
[Required(ErrorMessage = "필수 항목 누락")]
public required byte attendance_state { get; set; } // 출석 상태 (0=출석, 1=결석, 2=지각, 3=조퇴, 4=기타)
}

View File

@ -1,44 +0,0 @@
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

@ -1,177 +0,0 @@
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

@ -1,149 +0,0 @@
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

@ -1,21 +0,0 @@
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

@ -1,35 +0,0 @@
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

@ -1,25 +0,0 @@
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 ClassRepository(AppDbContext context): IClassRepository
{
public async Task<Class_Info?> FindClassInfo(string cid)
{
return await context.Class_Info.FirstOrDefaultAsync(c => c.id == cid);
}
//return _context.Login.FirstOrDefaultAsync(l => l.sns_type == accType && l.sns_id == snsId);
public async Task<Class_Attendance?> FindClassAttendance(string cid, string uid)
{
throw new NotImplementedException();
}
public async Task<Class_Map?> FindClassMap(string cid)
{
throw new NotImplementedException();
}
}

View File

@ -1,11 +0,0 @@
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

@ -1,19 +0,0 @@
using Back.Program.Models.Entities;
namespace Back.Program.Repositories.V1.Interfaces;
public interface IClassRepository
{
// 클래스 정보
// Task<Class_Info> GetClassInfo(string cid);
// //학생이 클래스에 참여했는지 여부
// Task<List<string>> GetIncludeStudents(string cid);
// // 학생이 클래스에서 했던 모든 출석
// Task<List<(DateTime date, int state)>> GetAttendanceOfClass(string cid, string uid);
// // 학생이 특정 날짜에 했던 출석
// Task<List<(string cid, int state)>> GetAttendanceByDate(string uid, DateTime date);
Task<Class_Info?> FindClassInfo(string cid);
Task<Class_Attendance?> FindClassAttendance(string cid, string uid);
Task<Class_Map?> FindClassMap(string cid);
}

View File

@ -1,12 +0,0 @@
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

@ -1,18 +0,0 @@
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

@ -1,14 +0,0 @@
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

@ -1,36 +0,0 @@
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

@ -1,85 +0,0 @@
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

@ -1,49 +0,0 @@
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.bid, a => a.bid, (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

@ -1,168 +0,0 @@
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":
if (project == "AcaMate") valid = true;
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 != null) 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 { access = access });
}
}

View File

@ -1,8 +0,0 @@
using Back.Program.Services.V1.Interfaces;
namespace Back.Program.Services.V1;
public class ChatService: IChatService
{
}

View File

@ -1,21 +0,0 @@
using Back.Program.Common.Model;
using Back.Program.Repositories.V1;
using Back.Program.Repositories.V1.Interfaces;
using Back.Program.Services.V1.Interfaces;
namespace Back.Program.Services.V1;
public class ClassService(IClassRepository classRepository): IClassService
{
public async Task<APIResponseStatus<object>> GetClassInfo(string summary, string cid)
{
var data = await classRepository.FindClassInfo(cid);
return APIResponse.Send<object>("000", "수업 정보 조회 성공", new
{
summary = summary,
data = data
}
);
}
}

View File

@ -1,66 +0,0 @@
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

@ -1,11 +0,0 @@
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

@ -1,6 +0,0 @@
namespace Back.Program.Services.V1.Interfaces;
public interface IChatService
{
}

View File

@ -1,8 +0,0 @@
using Back.Program.Common.Model;
namespace Back.Program.Services.V1.Interfaces;
public interface IClassService
{
Task<APIResponseStatus<object>> GetClassInfo(string summary, string cid);
}

View File

@ -1,13 +0,0 @@
using Back.Program.Common.Model;
namespace Back.Program.Services.V1.Interfaces;
public interface IKakaoService
{
Task<string> GetAccessToken(string code);
Task<string> GetAuthorizationUrl(string scope);
Task<(bool Success, string Response)> Redirect(string code);
Task<(bool Success, string Response)> Logout(string accessToken);
Task<(bool Success, string Response)> Unlink(string accessToken);
Task<(bool Success, string Response)> UserMe(string accessToken);
}

View File

@ -1,19 +0,0 @@
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

@ -1,12 +0,0 @@
using System.Linq.Expressions;
namespace Back.Program.Services.V1.Interfaces;
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);
Task SendFrontData<T>(T data, string url);
}

View File

@ -1,9 +0,0 @@
using Back.Program.Common.Model;
using Back.Program.Models.Entities;
namespace Back.Program.Services.V1.Interfaces;
public interface ISessionService
{
Task<APIResponseStatus<object>> GetSessionData(string summary);
}

View File

@ -1,15 +0,0 @@
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

@ -1,157 +0,0 @@
using System.Text.Json;
using System.Net.Http.Headers;
using Back.Program.Services.V1.Interfaces;
namespace Back.Program.Services.V1;
public class KakaoService: IKakaoService
{
private readonly HttpClient _httpClient;
private const string KAKAO_API_BASE_URL = "https://kapi.kakao.com";
private const string KAKAO_AUTH_BASE_URL = "https://kauth.kakao.com";
private readonly string _clientId;
private readonly string _clientSecret;
private readonly string _redirectUri;
public KakaoService(HttpClient httpClient, IConfiguration configuration)
{
_httpClient = httpClient;
_clientId = configuration["Kakao:ClientId"] ?? throw new InvalidOperationException("Kakao:ClientId not configured");
_redirectUri = configuration["Kakao:RedirectUri"] ?? throw new InvalidOperationException("Kakao:RedirectUri not configured");
_clientSecret = configuration["Kakao:ClientSecret"] ?? throw new InvalidOperationException("Kakao:ClientSecret not configured");
}
private void SetHeaders(string accessToken)
{
_httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
_httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
}
private async Task<string> Call(HttpMethod method, string url, HttpContent? content = null)
{
try
{
var request = new HttpRequestMessage(method, url);
if (content != null)
{
request.Content = content;
}
var response = await _httpClient.SendAsync(request);
var responseContent = await response.Content.ReadAsStringAsync();
if (!response.IsSuccessStatusCode)
{
return JsonSerializer.Serialize(new { error = $"HTTP {(int)response.StatusCode}: {responseContent}" });
}
return responseContent;
}
catch (Exception ex)
{
return JsonSerializer.Serialize(new { error = ex.Message });
}
}
/// <summary>
/// 인가 받은 코드로 토큰 받기를 실행하고 이 토큰으로 사용자 정보 가져오기를 할 수 있다.
/// </summary>
/// <param name="code"></param>
/// <returns></returns>
public async Task<string> GetAccessToken(string code)
{
var content = new FormUrlEncodedContent(new[]
{
new KeyValuePair<string, string>("grant_type", "authorization_code"),
new KeyValuePair<string, string>("client_id", _clientId),
new KeyValuePair<string, string>("redirect_uri", _redirectUri),
new KeyValuePair<string, string>("code", code),
new KeyValuePair<string, string>("client_secret", _clientSecret)
});
content.Headers.ContentType = new MediaTypeHeaderValue("application/x-www-form-urlencoded");
var response = await Call(HttpMethod.Post, $"{KAKAO_AUTH_BASE_URL}/oauth/token", content);
var responseData = JsonSerializer.Deserialize<JsonElement>(response);
if (responseData.TryGetProperty("error", out var error))
{
return response;
}
if (!responseData.TryGetProperty("access_token", out var accessToken))
{
return JsonSerializer.Serialize(new { error = "Access token is missing from response" });
}
return JsonSerializer.Serialize(new { access_token = accessToken.GetString() });
}
/// <summary>
/// 인가코드를 받는다 이 인가코드를 사용해 토큰 받기를 요청 할 수 있다.
/// 이게 리다이렉트에 포함되어있음
/// </summary>
/// <param name="scope"></param>
/// <returns></returns>
public Task<string> GetAuthorizationUrl(string scope)
{
var authUrl = $"{KAKAO_AUTH_BASE_URL}/oauth/authorize?client_id={_clientId}&redirect_uri={_redirectUri}&response_type=code";
if (!string.IsNullOrEmpty(scope))
{
authUrl += $"&scope={scope}";
}
return Task.FromResult(authUrl);
}
public async Task<(bool Success, string Response)> Redirect(string code)
{
if (string.IsNullOrEmpty(code))
return (false, JsonSerializer.Serialize(new { error = "Authorization code not found" }));
var response = await GetAccessToken(code);
var responseData = JsonSerializer.Deserialize<JsonElement>(response);
if (responseData.TryGetProperty("error", out var error))
{
return (false, response);
}
var accessToken = responseData.GetProperty("access_token").GetString();
if (string.IsNullOrEmpty(accessToken))
{
return (false, response);
}
return (true, accessToken);
}
public async Task<(bool Success, string Response)> Logout(string accessToken)
{
if (string.IsNullOrEmpty(accessToken))
return (false, JsonSerializer.Serialize(new { error = "Not logged in" }));
SetHeaders(accessToken);
var response = await Call(HttpMethod.Post, $"{KAKAO_API_BASE_URL}/v1/user/logout");
return (true, response);
}
public async Task<(bool Success, string Response)> Unlink(string accessToken)
{
if (string.IsNullOrEmpty(accessToken))
return (false, JsonSerializer.Serialize(new { error = "Not logged in" }));
SetHeaders(accessToken);
var response = await Call(HttpMethod.Post, $"{KAKAO_API_BASE_URL}/v1/user/unlink");
return (true, response);
}
public async Task<(bool Success, string Response)> UserMe(string accessToken)
{
if (string.IsNullOrEmpty(accessToken))
return (false, JsonSerializer.Serialize(new { error = "Not logged in" }));
SetHeaders(accessToken);
var response = await Call(HttpMethod.Get, $"{KAKAO_API_BASE_URL}/v2/user/me");
return (true, response);
}
}

View File

@ -1,342 +0,0 @@
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", new {});
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 데이터 없음", new {});
}
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}], 정상", new {});
}
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 오류", new {});
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}], 실패", new {});
return APIResponse.Send<object>("000", $"[{summary}], 정상", new {});
}
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) 확인 불가", new {});
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}], 실패", new {});
return APIResponse.Send<object>("000", $"[{summary}], 정상", new {});
}
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 없음", new {});
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}], 실패", new {});
return APIResponse.Send<object>("000", $"[{summary}], 정상", new {});
}
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 없음", new {});
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}], 실패", new {});
return APIResponse.Send<object>("000", $"[{summary}], 정상", new {});
}
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

@ -1,180 +0,0 @@
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
using System.Text.Json;
using Back.Program.Common.Auth;
using Back.Program.Common.Data;
using Back.Program.Common.Model;
using Back.Program.Models.Entities;
using Back.Program.Services.V1.Interfaces;
using Microsoft.EntityFrameworkCore;
namespace Back.Program.Services.V1
{
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<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;
}
public async Task SendFrontData<T>(T data, string url)
{
using var httpClient = new HttpClient();
var json = JsonSerializer.Serialize(data);
var content = new StringContent(json, Encoding.UTF8, "application/json");
var response = await httpClient.PostAsync(url, content);
response.EnsureSuccessStatusCode();
}
}
}

View File

@ -1,111 +0,0 @@
using System.Security.Claims;
using System.Text.Json;
using Back.Program.Common.Auth;
using Back.Program.Common.Data;
using Back.Program.Common.Model;
using Back.Program.Repositories.V1.Interfaces;
using Back.Program.Services.V1.Interfaces;
namespace Back.Program.Services.V1;
public class SessionService: ISessionService
{
private readonly ILogger<ISessionService> _logger;
private readonly IUserRepository _userRepository;
private readonly JwtTokenService _jwtTokenService;
private readonly IRepositoryService _repositoryService;
private readonly ILogRepository _logRepository;
private readonly SessionManager _sessionManager;
private readonly IAppService _appService;
public SessionService(ILogger<ISessionService> logger, IUserRepository userRepository,
JwtTokenService jwtTokenService,
IRepositoryService repositoryService, ILogRepository logRepository,
SessionManager sessionManager, IAppService appService)
{
_logger = logger;
_userRepository = userRepository;
_jwtTokenService = jwtTokenService;
_repositoryService = repositoryService;
_logRepository = logRepository;
_sessionManager = sessionManager;
_appService = appService;
}
public async Task<APIResponseStatus<object>> GetSessionData(string summary)
{
try
{
_logger.LogInformation($"[{summary}] 세션 데이터 조회 시작");
// 1. 세션에서 토큰 가져오기
var (result, token) = await _sessionManager.GetString("token");
_logger.LogInformation($"[{summary}] 세션에서 토큰 가져오기 결과: {result}, 토큰: {token}");
if (!result || string.IsNullOrEmpty(token))
{
_logger.LogWarning($"[{summary}] 세션에 토큰이 없습니다");
return APIResponse.Send<object>("200", "세션에 토큰이 없습니다", new { });
}
// 2. 토큰 검증
var validToken = await _jwtTokenService.ValidateToken(token);
_logger.LogInformation($"[{summary}] 토큰 검증 결과: {validToken != null}");
if (validToken == null)
{
// 3. 토큰이 유효하지 않으면 리프레시 토큰으로 새 토큰 발급 시도
var (refreshResult, refreshToken) = await _sessionManager.GetString("refresh");
_logger.LogInformation($"[{summary}] 리프레시 토큰 가져오기 결과: {refreshResult}, 토큰: {refreshToken}");
if (!refreshResult || string.IsNullOrEmpty(refreshToken))
{
_logger.LogWarning($"[{summary}] 리프레시 토큰이 없습니다");
return APIResponse.Send<object>("201", "리프레시 토큰이 없습니다", new { });
}
// 4. 리프레시 토큰으로 새 토큰 발급
var retryResult = await _appService.RetryAccess(summary, refreshToken);
_logger.LogInformation($"[{summary}] 토큰 재발급 결과: {retryResult.status.code}");
if (retryResult.status.code == "000")
{
// 5. 새 토큰을 세션에 저장
var data = JsonSerializer.Deserialize<JsonElement>(JsonSerializer.Serialize(retryResult.data));
var newToken = data.GetProperty("access").GetString();
await _sessionManager.SetString("token", newToken);
_logger.LogInformation($"[{summary}] 새 토큰 세션 저장 완료");
// 6. 새 토큰으로 사용자 정보 조회
validToken = await _jwtTokenService.ValidateToken(newToken);
}
else
{
_logger.LogWarning($"[{summary}] 토큰 갱신 실패: {retryResult.status.message}");
return APIResponse.Send<object>("202", "토큰 갱신 실패", new { });
}
}
// 7. 최종적으로 유효한 토큰으로 사용자 정보 조회
var uid = validToken.FindFirst(ClaimTypes.NameIdentifier)?.Value ?? string.Empty;
_logger.LogInformation($"[{summary}] 사용자 ID: {uid}");
var user = await _userRepository.FindUser(uid);
_logger.LogInformation($"[{summary}] 사용자 정보 조회 결과: {user != null}");
if (user == null)
{
_logger.LogWarning($"[{summary}] 사용자 정보를 찾을 수 없습니다");
return APIResponse.Send<object>("203", "사용자 정보를 찾을 수 없습니다", new { });
}
_logger.LogInformation($"[{summary}] 세션 데이터 조회 성공: {user.name}");
return APIResponse.Send<object>("000", $"[{summary}], 정상", user);
}
catch (Exception ex)
{
_logger.LogError($"[{summary}] 세션 데이터 조회 중 오류: {ex.Message}");
_logger.LogError($"[{summary}] 스택 트레이스: {ex.StackTrace}");
return APIResponse.InternalSeverError($"[{summary}], 세션 데이터 조회 실패");
}
}
}

View File

@ -1,244 +0,0 @@
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;
using System.Text.Json;
using Back.Program.Common.Data;
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;
private readonly IAppService _appService;
private readonly SessionManager _sessionManager;
public UserService(ILogger<IUserService> logger, IUserRepository userRepository,
JwtTokenService jwtTokenService,
IRepositoryService repositoryService, ILogRepository logRepository,
IAppService appService, SessionManager sessionManager)
{
_logger = logger;
_userRepository = userRepository;
_jwtTokenService = jwtTokenService;
_repositoryService = repositoryService;
_logRepository = logRepository;
_appService = appService;
_sessionManager = sessionManager;
}
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}], 로그인 정보 없음", new { });
var user = await _userRepository.FindUser(login.uid);
if (user == null)
return APIResponse.Send<object>("002", $"[{summary}], 회원 정보 오류", new { });
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}], 로그아웃 정상", new { });
}
}
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}], 회원 정보 확인 오류", new { });
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}], 정상", new { });
}
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}], 회원 정보 확인 오류", new { });
var academyList = await _userRepository.FindAcademies(uid);
_logger.LogInformation($"[{summary}]: 성공 - {System.Text.Json.JsonSerializer.Serialize(academyList)}");
return APIResponse.Send<object>("000", $"[{summary}], 정상.", academyList);
}
}
}

View File

@ -0,0 +1,68 @@
using Microsoft.AspNetCore.Mvc;
using System.Text.Json;
using AcaMate.Common.Data;
using AcaMate.Common.Models;
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;
public AppController(AppDbContext dbContext)
{
_dbContext = dbContext;
}
[HttpGet("version")]
[CustomOperation("앱 버전 확인","앱 버전을 확인해서 업데이트 여부 판단", "시스템")]
public IActionResult GetVersionData(string type)
{
if (string.IsNullOrEmpty(type))
{
return BadRequest(DefaultResponse.InvalidInputError);
}
try
{
var version = _dbContext.Version.FirstOrDefault(v => v.os_type == (type == "I" ? "VO01" : "VO02"));
if (version == null)
{
return NotFound(DefaultResponse.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, DefaultResponse.UnknownError);
}
}
}

View File

@ -0,0 +1,15 @@
using Microsoft.AspNetCore.Mvc;
namespace AcaMate.V1.Controllers;
[ApiController]
[Route("/api/error")]
public class ErrorController: ControllerBase
{
[HttpGet]
public IActionResult HandleError()
{
return Problem("오류가 발생하였습니다. 잠시후 다시 시도해주세요.");
}
}

View File

@ -0,0 +1,43 @@
using System.Text.Json;
using AcaMate.Common.Data;
using AcaMate.Common.Models;
using AcaMate.V1.Models;
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 AppDbContext _dbContext;
public MemberController(AppDbContext dbContext)
{
_dbContext = dbContext;
}
[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,105 @@
using AcaMate.Common.Models;
using AcaMate.V1.Models;
using Microsoft.AspNetCore.Mvc;
using AcaMate.V1.Services;
namespace AcaMate.V1.Controllers;
[ApiController]
[Route("/api/v1/in/push")]
[ApiExplorerSettings(GroupName = "공통")]
public class PushController : ControllerBase
{
private readonly IWebHostEnvironment _env;
public PushController(IWebHostEnvironment env)
{
_env = env;
}
[HttpGet()]
[CustomOperation("푸시 확인", "저장된 양식을 확인 할 수 있다..", "푸시")]
public IActionResult GetPushData()
{
return Ok("SEND");
}
/// <summary>
/// Sends a push notification to the specified device token with the provided payload.
/// </summary>
/// <param name="deviceToken">The device token to send the notification to.</param>
/// <param name="payload">The payload of the push notification.</param>
/// <returns>An IActionResult indicating the result of the operation.</returns>
/// <response code="200">Push notification sent successfully.</response>
/// <response code="400">Invalid input parameters.</response>
/// <response code="500">Internal server error occurred.</response>
/// <response code="999">Service unavailable.</response>
[HttpPost("send")]
[CustomOperation("푸시전송", "저장된 양식으로, 사용자에게 푸시를 전송한다.", "푸시")]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(APIResponseStatus<object>))]
public async Task<IActionResult> SendPush(string deviceToken, [FromBody] Payload? payload)
{
if (string.IsNullOrWhiteSpace(deviceToken))
{
var inputError = DefaultResponse.InvalidInputError;
inputError.status.message = "Deviece Toekn 오류";
return StatusCode(500,inputError);
}
if (payload == null)
{
var inputError = DefaultResponse.InvalidInputError;
inputError.status.message = "payload 입력 오류";
return StatusCode(500,inputError);
}
string uri = "";
string p12Path = "";
string p12PWPath = "/src/private/appleKeys.json";
// string p12PWPath = "private/appleKeys.json";
string apnsTopic = "me.myds.ipstein.acamate.AcaMate";
if (_env.IsDevelopment())
{
uri = "https://api.sandbox.push.apple.com/";
p12Path = "/src/private/AM_Push_Sandbox.p12";
// p12Path = "private/AM_Push_Sandbox.p12";
}
else
{
uri = "https://api.push.apple.com/";
p12Path = "/src/private/AM_Push.p12";
// p12Path = "private/AM_Push.p12";
}
// ApnsPushService 인스턴스 생성
var pushService = new ApnsPushService(uri, p12Path, p12PWPath, apnsTopic);
// 푸시 알림 전송
var result = await pushService.SendPushNotificationAsync(deviceToken, payload);
if (result.Success)
{
return Ok(DefaultResponse.Success.JsonToString());
}
else
{
var apnsError = DefaultResponse.InternalSeverError;
apnsError.status.message = $"{result.Message}";
return result.Code switch
{
"002" => StatusCode(002, apnsError),
"003" => StatusCode(003, apnsError),
"999" => StatusCode(999, apnsError)
};
}
}
}

View File

@ -0,0 +1,311 @@
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;
[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 IActionResult GetUserData(string uid)
{
if (string.IsNullOrEmpty(uid)) return BadRequest(DefaultResponse.InvalidInputError);
try
{
var user = _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
})
.FirstOrDefault();
var response = new APIResponseStatus<User>
{
status = new Status
{
code = "000",
message = "정상"
},
data = user
};
return Ok(response.JsonToString());
}
catch (Exception ex)
{
return StatusCode(500, DefaultResponse.UnknownError);
}
}
[HttpGet("login")]
[CustomOperation("SNS 로그인", "로그인 후 회원이 있는지 확인", "사용자")]
public async Task<IActionResult> Login(string acctype, string sns_id)
{
// API 동작 파라미터 입력 값 확인
if (string.IsNullOrEmpty(acctype) && string.IsNullOrEmpty(sns_id))
return BadRequest(DefaultResponse.InvalidInputError);
try
{
var login = await _dbContext.Login
.FirstOrDefaultAsync(l => l.sns_type == acctype && l.sns_id == sns_id);
if (login != null)
{
// 로그인 정보가 존재 하는 상황
var uid = login.uid;
var user = await _dbContext.User
.FirstOrDefaultAsync(u => u.uid == uid);
if (user != null)
{
// 정상적으로 User 테이블에도 있는것이 확인 됨
user.login_date = DateTime.Now;
await _dbContext.SaveChangesAsync();
// 토큰 생성은 로그인이 이제 되고 나서 한다.
var accessToken = _jwtTokenService.GenerateJwtToken(uid);//, "Normal");
var refreshToken = _jwtTokenService.GenerateRefreshToken(uid);
_logger.LogInformation($"{uid}: {accessToken}, {refreshToken}");
await _repositoryService.SaveData<RefreshToken, string>(refreshToken, rt => rt.uid);
var response = new APIResponseStatus<dynamic>
{
status = new Status
{
code = "000",
message = "정상"
},
data = new
{
token = accessToken,
refresh = refreshToken.refresh_token
}
};
return Ok(response.JsonToString());
}
}
// case 1: Login 테이블에 값이 없다 == 로그인이 처음
// case 2: User 테이블에 값이 없다 == 이건 문제가 있는 상황 -> 해결은 회원가입 재 진행 시도
// Login에는 있는데 User 테이블에 없다? 말이 안되긴 하는데...
return Ok(new APIResponseStatus<dynamic>
{
status = new Status
{
code = "010",
message = "로그인 정보 없음 > 회원 가입 진행"
},
data = new
{
token = "",
refresh = ""
// bidList = new string[] { }
}
}.JsonToString());
}
catch (Exception ex)
{
_logger.LogInformation($"[로그인] 에러 발생 : {ex}");
return StatusCode(500, DefaultResponse.UnknownError);
}
}
[HttpGet("academy")]
[CustomOperation("학원 리스트 확인", "사용자가 등록된 학원 리스트 확인", "사용자")]
public async Task<IActionResult> ReadAcademyInfo(string token, string refresh)
{
_logger.LogInformation($"토큰 : {token}, {refresh}");
if (string.IsNullOrEmpty(token) || string.IsNullOrEmpty(refresh))
{
var error = DefaultResponse.InvalidInputError;
return Ok(error);
}
try
{
var validateToken = await _repositoryService.ValidateToken(token, refresh);
var uid = validateToken.uid;
var userAcademy = await _dbContext.UserAcademy
.Where(ua => ua.uid == uid)
.Select(ua => ua.bid)
.ToListAsync();
var academies = await _dbContext.Academy
.Where(a => userAcademy.Contains(a.bid))
.Select(a => new AcademyName
{
bid = a.bid,
name = a.business_name
})
.ToListAsync();
var response = new APIResponseStatus<List<AcademyName>>
{
status = new Status
{
code = "000",
message = "정상"
},
data = academies
};
return Ok(response);
}
catch (SecurityTokenException tokenEx)
{
_logger.LogInformation($"[로그인][오류] 토큰 검증 : {tokenEx}");
return StatusCode(500, DefaultResponse.InvalidInputError);
}
catch (Exception ex)
{
_logger.LogInformation($"[로그인][오류] 에러 발생 : {ex}");
return StatusCode(500, DefaultResponse.UnknownError);
}
}
[HttpPost("register")]
[CustomOperation("회원 가입", "사용자 회원 가입", "사용자")]
public async Task<IActionResult> UserRegister([FromBody] UserAll request)
{
if (!ModelState.IsValid) return BadRequest(DefaultResponse.InvalidInputError);
var atIndext = request.email.IndexOf('@');
var localPart_email = request.email.Substring(0, atIndext);
var uid = $"AM{localPart_email}{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
};
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
};
if (await _repositoryService.SaveData<User, string>(user, u => u.uid))
{
await _repositoryService.SaveData<Login, string>(login, l => l.sns_id);
await _repositoryService.SaveData<Permission, string>(permission, p => p.uid);
await _repositoryService.SaveData<Contact, string>(contact, c => c.uid);
}
// TO-DO: jwt 토큰 만들어서 여기서 보내는 작업을 해야 함
var token = _jwtTokenService.GenerateJwtToken(uid);//, "admin");
var refreshToken = _jwtTokenService.GenerateRefreshToken(uid);
await _repositoryService.SaveData<RefreshToken, string>(refreshToken, rt => rt.uid);
var result = new APIResponseStatus<dynamic>()
{
status = new Status()
{
code = "000",
message = "정상"
},
data = new
{
accessToken = token,
refreshToken = refreshToken.refresh_token
}
};
return Ok(result.JsonToString());
}
[HttpGet("logout")]
[CustomOperation("로그아웃", "사용자 로그아웃", "사용자")]
public async Task<IActionResult> LogOut(string token, string refresh) //([FromBody] UserAll request)
{
/* */
// var value = await ValidateToken(token, refresh);
// _logger.LogInformation(value.uid);
// _logger.LogInformation(value.refresh);
// _logger.LogInformation(value.token);
/* */
return Ok("로그아웃");
}
}

View File

@ -0,0 +1,15 @@
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

@ -0,0 +1,31 @@
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

@ -0,0 +1,41 @@
using System.ComponentModel.DataAnnotations;
using System.Security.Cryptography.X509Certificates;
namespace AcaMate.V1.Models;
public class Payload
{
public Aps aps { 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; } // 부제목 (선택)
}

150
Program/V1/Models/User.cs Normal file
View File

@ -0,0 +1,150 @@
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; }
}
[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 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; }
public bool auto_login_yn { get; set; }
public DateTime login_date { 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 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

@ -0,0 +1,28 @@
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

@ -0,0 +1,126 @@
using System;
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 AcaMate.V1.Models;
using Version = System.Version;
public class ApnsPushService
{
private readonly string _uri;
private readonly string _p12Path;
private readonly string _p12PWPath;
private readonly string _apnsTopic;
public ApnsPushService(string uri,string p12Path, string p12PWPath, string apnsTopic)
{
_uri = uri ?? throw new ArgumentNullException(nameof(uri));
_p12Path = p12Path ?? throw new ArgumentNullException(nameof(p12Path));
_p12PWPath = p12PWPath ?? throw new ArgumentNullException(nameof(p12PWPath));
_apnsTopic = apnsTopic ?? throw new ArgumentNullException(nameof(apnsTopic));
}
public async Task<APIResult> SendPushNotificationAsync(string deviceToken, Payload payload)
{
// 존재 안하면 예외 던져 버리는거
if (!File.Exists(_p12PWPath))
{
Console.WriteLine($"File not found: {_p12PWPath}");
return new APIResult
{
Success = false ,
Code = "003",
Message = "서버 오류 : p12 PW 파일 확인 필요"
};
}
var jsonPayload = JsonSerializer.Serialize(payload);
var keys = JsonSerializer.Deserialize<Dictionary<string, string>>(File.ReadAllText(_p12PWPath));
var p12Password = keys["Password"];
try
{
var certificate = new X509Certificate2(_p12Path, p12Password);
Console.WriteLine($"Certificate Subject: {certificate.Subject}");
Console.WriteLine($"Certificate Issuer: {certificate.Issuer}");
var handler = new HttpClientHandler();
handler.ClientCertificates.Add(certificate);
handler.SslProtocols = System.Security.Authentication.SslProtocols.Tls12;
using var client = new HttpClient(handler)
{
BaseAddress = new Uri(_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", _apnsTopic);
request.Headers.Add("apns-push-type", "alert");
Console.WriteLine($"Send -> Payload: {jsonPayload}");
var response = await client.SendAsync(request);
if (response.IsSuccessStatusCode)
{
return new APIResult
{
Success = true
};
}
else
{
var errorContent = await response.Content.ReadAsStringAsync();
return new APIResult
{
Success = false,
Code = "003",
Message = $"APN 통신 실패 = {response.StatusCode} : {errorContent}"
};
}
}
catch (HttpRequestException httpEx)
{
Console.WriteLine($"HttpRequestException: {httpEx.Message}");
return new APIResult
{
Success = false,
Code = "003",
Message = $"통신 실패 : {httpEx.Message}"
};
}
catch (CryptographicException ex)
{
return new APIResult
{
Success = false,
Code = "999",
Message = $"오류 발생 : {ex.Message}"
};
}
catch (Exception ex)
{
return new APIResult
{
Success = false,
Code = "999",
Message = $"오류 발생 : {ex.Message}"
};
}
}
}

View File

@ -0,0 +1,136 @@
using AcaMate.Common.Data;
using AcaMate.Common.Token;
using AcaMate.Common.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using System.Linq.Expressions;
using System.Security.Claims;
using Microsoft.IdentityModel.Tokens;
namespace AcaMate.V1.Services;
public interface IRepositoryService
{
Task<bool> SaveData<T, K>(T entity, Expression<Func<T, K>> key) where T : class;
Task<ValidateToken> ValidateToken(string token, string refresh);
}
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<bool> SaveData<T, K> (T entity, Expression<Func<T, K>> key) where T : class
{
try
{
var value = key.Compile()(entity);
// x 라 함은 Expression 으로 생성되는 트리에서 T 타입으의 매개변수를 지칭함
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 dbSet = _dbContext.Set<T>();
var entityData = await dbSet.FirstOrDefaultAsync(predicate);
if (entityData != null)
{
_logger.LogInformation($"[{typeof(T)}] 해당 PK 존재 [{value}]: 계속");
var entry = _dbContext.Entry(entityData);
entry.CurrentValues.SetValues(entity);
if (entry.Properties.Any(p => p.IsModified))
{
_logger.LogInformation($"[{typeof(T)}] 변경사항 존재: 계속");
}
else
{
_logger.LogInformation($"[{typeof(T)}] 변경사항 없음: 종료");
return true;
}
}
else
{
_logger.LogInformation($"[{typeof(T)}] 처음등록");
dbSet.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<ValidateToken> ValidateToken(string token, string refresh)
{
var principalToken = _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 SecurityTokenException("리프레시 토큰도 잘못되었음");
}
var uid = refreshToken.uid;
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
};
}
}
}
}

View File

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

View File

@ -7,9 +7,10 @@
- JetBrains Rider
### 추가 패키지
| No. | Name | Version | Description |
|:---:|:---------------------------------------------:|:-------:|:---------------------------|
| 1 | Microsoft.AspNetCore.OpenApi | 8.0.10 | OpenAPI 를 지원하기 위해 사용 |
| 2 | Microsoft.EntityFrameworkCore | 8.0.10 | 데이터베이스 작업을 간편하게 수행하기 위해 사용 |
| 3 | Pomelo.EntityFrameworkCore.MySql | 8.0.2 | MariaDB 연결 |
| 4 | Microsoft.AspNetCore.Authentication.JwtBearer | 8.0.10 | |
| No. | Name | Version | Description |
|:---:|:-------------------------------------------:|:-------:|:---------------------------|
| 1 | Microsoft.AspNetCore.OpenApi | 8.0.10 | OpenAPI 를 지원하기 위해 사용 |
| 2 | Microsoft.EntityFrameworkCore | 8.0.10 | 데이터베이스 작업을 간편하게 수행하기 위해 사용 |
| 3 | Pomelo.EntityFrameworkCore.MySql | 8.0.2 | MariaDB 연결 |
| 4 |Microsoft.AspNetCore.Authentication.JwtBearer| 8.0.10 | |
| 5 |Dapper|2.1.35|SQL 직접 작성|

View File

@ -1,92 +1,92 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.Annotations;
using Swashbuckle.AspNetCore.SwaggerGen;
namespace Back
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
public class CustomOperationAttribute : Attribute
{
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
public class CustomOperationAttribute : Attribute
{
public string Summary { get; }
public string Description { get; }
public string[] Tags { get; }
public string Summary { get; }
public string Description { get; }
public string[] Tags { get; }
public CustomOperationAttribute(string summary, string description, params string[] tags)
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)
{
Summary = summary;
Description = description;
Tags = tags;
operation.Summary = customSwaggerAttribute.Summary;
operation.Description = customSwaggerAttribute.Description;
operation.Tags = customSwaggerAttribute.Tags
.Select(tag => new OpenApiTag { Name = tag })
.ToList();
}
}
}
public class CustomSwaggerOperationFilter : IOperationFilter
public static class SwaggerConfigure
{
private static OpenApiInfo DocName(string title, string version)
{
public void Apply(OpenApiOperation operation, OperationFilterContext context)
return new OpenApiInfo
{
var customSwaggerAttribute = context.MethodInfo.GetCustomAttributes(typeof(CustomOperationAttribute), false)
.FirstOrDefault() as CustomOperationAttribute;
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"));
if (customSwaggerAttribute != null)
options.DocInclusionPredicate((docName, apiDesc) =>
{
operation.Summary = customSwaggerAttribute.Summary;
operation.Description = customSwaggerAttribute.Description;
operation.Tags = customSwaggerAttribute.Tags
.Select(tag => new OpenApiTag { Name = tag })
.ToList();
}
}
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 class SwaggerConfigure
public static void UseCustomSwaggerUI(this IApplicationBuilder app)
{
private static OpenApiInfo DocName(string title, string version)
app.UseSwagger();
app.UseSwaggerUI(options =>
{
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");
});
}
options.SwaggerEndpoint("/swagger/전체/swagger.json", "전체 API");
options.SwaggerEndpoint("/swagger/공통/swagger.json", "공통 API");
options.SwaggerEndpoint("/swagger/사용자/swagger.json", "사용자 API");
options.SwaggerEndpoint("/swagger/사업자 정보/swagger.json", "사업자 정보 API");
});
}
}

View File

@ -4,19 +4,5 @@
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"PushFileSetting": {
"uri": "https://api.sandbox.push.apple.com/",
"p12Path": "./private/AM_Push_Sandbox.p12",
"p12PWPath": "./private/appleKeys.json",
"apnsTopic": "me.myds.ipstein.acamate.AcaMate"
},
"AllowedHosts": "*",
"Kakao": {
"ClientId": "a9632e6c14d8706ef6c8fe2ef52b721d",
"ClientSecret": " this is rest api secret key ",
// "RedirectUri": "http://0.0.0.0:5144/api/v1/out/user/kakao/redirect"
"RedirectUri": "https://devacamate.ipstein.myds.me/api/v1/out/user/kakao/redirect"
}
}

View File

@ -5,16 +5,5 @@
"Microsoft.AspNetCore": "Warning"
}
},
"PushFileSetting": {
"Uri": "https://api.push.apple.com/",
"P12Path": "/src/private/AM_Push.p12",
"P12PWPath": "/src/private/appleKeys.json",
"ApnsTopic": "me.myds.ipstein.acamate.AcaMate"
},
"AllowedHosts": "*",
"Kakao": {
"ClientId": "a9632e6c14d8706ef6c8fe2ef52b721d",
"ClientSecret": " this is rest api secret key ",
"RedirectUri": "https://acamate.ipstein.myds.me/api/v1/out/user/kakao/redirect"
}
"AllowedHosts": "*"
}

1669
package-lock.json generated

File diff suppressed because it is too large Load Diff