forked from AcaMate/AcaMate_API
[👷🏻] git 초기화 진행 중
This commit is contained in:
parent
16d38d6975
commit
de92fe3a7f
25
Back.csproj
Normal file
25
Back.csproj
Normal file
|
@ -0,0 +1,25 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<BlazorWebAssemblyLoadAllGlobalizationData>false</BlazorWebAssemblyLoadAllGlobalizationData>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Dapper" Version="2.1.35" />
|
||||
<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="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\Common\Interfaces\" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
</Project>
|
6
Back.http
Normal file
6
Back.http
Normal file
|
@ -0,0 +1,6 @@
|
|||
@Back_HostAddress = http://localhost:5144
|
||||
|
||||
GET {{Back_HostAddress}}/weatherforecast/
|
||||
Accept: application/json
|
||||
|
||||
###
|
59
Jenkinsfile
vendored
Normal file
59
Jenkinsfile
vendored
Normal file
|
@ -0,0 +1,59 @@
|
|||
pipeline {
|
||||
agent any
|
||||
environment {
|
||||
DOCKER_RELEASE_CONTAINER = 'acamate-back-build-release'
|
||||
DOCKER_DEBUG_CONTAINER = 'acamate-back-build-debug'
|
||||
DOCKER_RELEASE_RUN_CONTAINER = 'acamate-run-release'
|
||||
DOCKER_DEBUG_RUN_CONTAINER = 'acamate-run-debug'
|
||||
|
||||
APP_VOLUME = '/src'
|
||||
}
|
||||
stages {
|
||||
stage('Clone Repository') {
|
||||
steps {
|
||||
git url: 'https://git.ipstein.myds.me/AcaMate/AcaMate_API.git', branch: env.GIT_BRANCH
|
||||
}
|
||||
}
|
||||
stage('Deploy') {
|
||||
steps {
|
||||
script {
|
||||
if (env.GIT_BRANCH == 'main') {
|
||||
// main 브랜치용 작업
|
||||
def containerId = sh(script: "docker ps -qf 'name=${DOCKER_RELEASE_RUN_CONTAINER}'", returnStdout: true).trim()
|
||||
if (containerId) {
|
||||
sh "docker cp ${WORKSPACE}/. ${containerId}:${APP_VOLUME}"
|
||||
sh "docker start ${DOCKER_RELEASE_CONTAINER}"
|
||||
} else {
|
||||
error "Docker container ${DOCKER_RELEASE_RUN_CONTAINER} not found"
|
||||
}
|
||||
} else if (env.GIT_BRANCH == 'debug') {
|
||||
// debug 브랜치용 작업
|
||||
def containerId = sh(script: "docker ps -qf 'name=${DOCKER_DEBUG_RUN_CONTAINER}'", returnStdout: true).trim()
|
||||
if (containerId) {
|
||||
sh "docker cp ${WORKSPACE}/. ${containerId}:${APP_VOLUME}"
|
||||
sh "docker start ${DOCKER_DEBUG_CONTAINER}"
|
||||
} else {
|
||||
error "Docker container ${DOCKER_DEBUG_RUN_CONTAINER} not found"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
post {
|
||||
always {
|
||||
script {
|
||||
def containerId = sh(script: "docker ps -qf 'name=${env.GIT_BRANCH == 'main' ? DOCKER_RELEASE_CONTAINER : DOCKER_DEBUG_CONTAINER}'", returnStdout: true).trim()
|
||||
if (containerId) {
|
||||
sh "docker logs ${containerId}"
|
||||
} else {
|
||||
echo "Docker container not found"
|
||||
}
|
||||
}
|
||||
}
|
||||
failure {
|
||||
echo "Build failed. Check the console output for details."
|
||||
}
|
||||
}
|
||||
}
|
153
Program.cs
Normal file
153
Program.cs
Normal file
|
@ -0,0 +1,153 @@
|
|||
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 AcaMate.Common.Models;
|
||||
using AcaMate.V1.Services;
|
||||
using AcaMate.Common.Data;
|
||||
using AcaMate.V1.Controllers;
|
||||
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
|
||||
// DB 설정부 시작
|
||||
builder.Configuration.AddJsonFile("private/dbSetting.json", optional: true, reloadOnChange: true);
|
||||
// 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>();
|
||||
var dbName = httpContextAccessor.HttpContext?.Request.Query["aca_code"].ToString();
|
||||
var baseConnectionString = builder.Configuration.GetConnectionString("MariaDbConnection");
|
||||
if (!string.IsNullOrEmpty(dbName))
|
||||
{
|
||||
baseConnectionString = baseConnectionString.Replace("database=AcaMate", $"database={dbName}");
|
||||
}
|
||||
|
||||
options.UseMySql(baseConnectionString, ServerVersion.AutoDetect(baseConnectionString));
|
||||
});
|
||||
builder.Services.AddHttpContextAccessor();
|
||||
// DB 설정부 끝
|
||||
|
||||
|
||||
var dbString = builder.Configuration.GetConnectionString("MariaDbConnection");
|
||||
var userString = builder.Configuration.GetConnectionString("DBAccount");
|
||||
|
||||
// JWT 설정부 시작
|
||||
if (builder.Environment.IsDevelopment())
|
||||
{
|
||||
builder.Configuration.AddJsonFile("private/jwtSetting.Development.json", optional: true, reloadOnChange: true);
|
||||
}
|
||||
else
|
||||
{
|
||||
builder.Configuration.AddJsonFile("private/jwtSetting.json", optional: true, reloadOnChange: true);
|
||||
}
|
||||
|
||||
builder.Services.Configure<JwtSettings>(builder.Configuration.GetSection("JwtSettings"));
|
||||
|
||||
builder.Services.AddAuthentication(options =>
|
||||
{
|
||||
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
|
||||
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
|
||||
})
|
||||
.AddJwtBearer(options =>
|
||||
{
|
||||
var jwtSettings = builder.Configuration.GetSection("JwtSettings").Get<JwtSettings>();
|
||||
options.TokenValidationParameters = new TokenValidationParameters
|
||||
{
|
||||
ValidateIssuer = true,
|
||||
ValidateAudience = true,
|
||||
ValidateLifetime = true,
|
||||
ValidateIssuerSigningKey = true,
|
||||
ValidIssuer = jwtSettings.Issuer,
|
||||
ValidAudience = jwtSettings.Audience,
|
||||
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtSettings.SecretKey)),
|
||||
ClockSkew = TimeSpan.FromMinutes(jwtSettings.ClockSkewMinutes)
|
||||
};
|
||||
});
|
||||
// JWT 설정부 끝
|
||||
|
||||
builder.Services.AddControllers();
|
||||
|
||||
// 여기다가 API 있는 컨트롤러들 AddScoped 하면 되는건가?
|
||||
builder.Services.AddScoped<AcaMate.Common.Token.JwtTokenService>();
|
||||
builder.Services.AddScoped<IRepositoryService, AcaMate.V1.Services.RepositoryService>();
|
||||
// builder.Services.AddScoped<UserService>(); //
|
||||
// builder.Services.AddScoped<UserController>();
|
||||
|
||||
builder.Services.AddEndpointsApiExplorer();
|
||||
|
||||
// 스웨거 설정 추가 부분
|
||||
// builder.Services.AddSwaggerGen();
|
||||
builder.Services.AddCustomSwagger();
|
||||
|
||||
// SignalR 설정 추가 부분
|
||||
builder.Services.AddSignalR();
|
||||
builder.Services.AddCors(option =>
|
||||
{
|
||||
option.AddPolicy("CorsPolicy", builder =>
|
||||
{
|
||||
builder
|
||||
.WithOrigins("https://devacamate.ipstein.myds.me", "https://acamate.ipstein.myds.me") // 특정 도메인만 허용
|
||||
.AllowAnyMethod()
|
||||
.AllowAnyHeader()
|
||||
.AllowCredentials();
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
// 로그 설정 부분
|
||||
builder.Logging.ClearProviders();
|
||||
builder.Logging.AddConsole();
|
||||
if (builder.Environment.IsDevelopment()) {
|
||||
builder.Logging.SetMinimumLevel(LogLevel.Trace);
|
||||
}
|
||||
else
|
||||
{
|
||||
builder.Logging.SetMinimumLevel(LogLevel.Warning);
|
||||
}
|
||||
|
||||
|
||||
|
||||
// 로컬 테스트 위한 부분 (올릴때는 꺼두기)
|
||||
// builder.WebHost.UseUrls("http://0.0.0.0:5144");
|
||||
|
||||
///// ===== builder 설정 부 ===== /////
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
if (app.Environment.IsDevelopment())
|
||||
{
|
||||
// app.UseSwagger();
|
||||
// app.UseSwaggerUI();
|
||||
app.UseCustomSwaggerUI();
|
||||
app.UseDeveloperExceptionPage(); // 좀더 자세한 예외 정보 제공
|
||||
}
|
||||
else
|
||||
{
|
||||
app.UseExceptionHandler("/error");
|
||||
app.UseHsts();
|
||||
}
|
||||
|
||||
// 로컬 테스트 위한 부분 (올릴떄는 켜두기)
|
||||
app.UseHttpsRedirection();
|
||||
|
||||
app.UseRouting();
|
||||
// app.MapControllers();
|
||||
|
||||
app.UseCors("CorsPolicy");
|
||||
app.UseAuthorization();
|
||||
|
||||
app.UseWebSockets();
|
||||
app.UseEndpoints(end =>
|
||||
{
|
||||
end.MapControllers();
|
||||
end.MapHub<ChatHub>("/chatHub");
|
||||
});
|
||||
|
||||
|
||||
app.Run();
|
37
Program/Common/Chat/ChatHub.cs
Normal file
37
Program/Common/Chat/ChatHub.cs
Normal file
|
@ -0,0 +1,37 @@
|
|||
using Microsoft.AspNetCore.SignalR;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace AcaMate.Common.Chat;
|
||||
|
||||
public class ChatHub : Hub
|
||||
{
|
||||
// 클라이언트에서 메시지를 보내면 모든 사용자에게 전송
|
||||
public async Task SendMessage(string user, string message)
|
||||
{
|
||||
Console.WriteLine($"Message received: {user}: {message}");
|
||||
await Clients.All.SendAsync("ReceiveMessage", user, message);
|
||||
|
||||
}
|
||||
|
||||
// 특정 사용자에게 메시지를 보냄
|
||||
public async Task SendMessageToUser(string connectionId, string message)
|
||||
{
|
||||
await Clients.Client(connectionId).SendAsync("ReceiveMessage", message);
|
||||
}
|
||||
|
||||
// 클라이언트가 연결될 때 호출
|
||||
public override async Task OnConnectedAsync()
|
||||
{
|
||||
await Clients.Caller.SendAsync("ReceiveMessage", "System", $"Welcome! Your ID: {Context.ConnectionId}");
|
||||
Console.WriteLine("OnConnectedAsync");
|
||||
await base.OnConnectedAsync();
|
||||
}
|
||||
|
||||
// 클라이언트가 연결 해제될 때 호출
|
||||
public override async Task OnDisconnectedAsync(Exception? exception)
|
||||
{
|
||||
await Clients.All.SendAsync("ReceiveMessage", "System", $"{Context.ConnectionId} disconnected");
|
||||
Console.WriteLine("OnDisconnectedAsync");
|
||||
await base.OnDisconnectedAsync(exception);
|
||||
}
|
||||
}
|
35
Program/Common/Data/AppDbContext.cs
Normal file
35
Program/Common/Data/AppDbContext.cs
Normal file
|
@ -0,0 +1,35 @@
|
|||
using AcaMate.Common.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using AcaMate.V1.Models;
|
||||
using Version = AcaMate.V1.Models.Version;
|
||||
|
||||
namespace AcaMate.Common.Data;
|
||||
//database=AcaMate;
|
||||
public class AppDbContext: DbContext
|
||||
{
|
||||
public AppDbContext(DbContextOptions<AppDbContext> options) : base(options)
|
||||
{
|
||||
}
|
||||
|
||||
//MARK: Program
|
||||
public DbSet<Version> Version { get; set; }
|
||||
public DbSet<Academy> Academy { get; set; }
|
||||
public DbSet<RefreshToken> RefreshTokens { 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; }
|
||||
|
||||
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
{
|
||||
modelBuilder.Entity<User_Academy>()
|
||||
.HasKey(ua => new { ua.uid, ua.bid });
|
||||
}
|
||||
}
|
116
Program/Common/JWTToken/JwtTokenService.cs
Normal file
116
Program/Common/JWTToken/JwtTokenService.cs
Normal 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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
52
Program/Common/Model/JwtSettings.cs
Normal file
52
Program/Common/Model/JwtSettings.cs
Normal file
|
@ -0,0 +1,52 @@
|
|||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace AcaMate.Common.Models;
|
||||
|
||||
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; }
|
||||
}
|
||||
/*
|
||||
"""
|
||||
토큰 동작 관련
|
||||
다시 물어보자 토큰 로직 관련해서 일단은 로그인을 예로 들면
|
||||
1. 로그인을 진행한다.
|
||||
2. 회원이 DB에 정상적으로 존재한다.
|
||||
3. 엑세스 토큰과 리프레시 토큰을 생성한다.
|
||||
4. 엑세스 토큰은 클라이언트로 리프래시 토큰은 서버에 저장하고 클라이언트로도 보낸다.
|
||||
5. (상황1) 시간이 경과해 엑세스 토큰의 시간이 경과했다.
|
||||
6. (상황2) 회원 정보에 대한 접근이 필요한 동작을 수행한다.
|
||||
7. 엑세스 토큰과 리프레시 토큰을 서버로 전송한다.
|
||||
8. 엑세스 토큰이 만료가 되었음이 확인이 되면 리프레시 토큰으로 새 엑세스를 만들기 위해 리프레시 토큰을 확인한다.
|
||||
9. 리프레시 토큰이 만료가 되지 않았다면 리프레시 토큰을 토대로 엑세스 토큰을 생성한다.
|
||||
10. 생성된 엑세스 토큰을 가지고 상황2의 동작을 수행하고 엑세스 토큰을 반환한다.
|
||||
"""
|
||||
*/
|
78
Program/Common/Model/Status.cs
Normal file
78
Program/Common/Model/Status.cs
Normal file
|
@ -0,0 +1,78 @@
|
|||
using System.Text.Json;
|
||||
|
||||
namespace AcaMate.Common.Models;
|
||||
|
||||
public class APIResponseStatus<T>
|
||||
{
|
||||
public Status status { get; set; }
|
||||
public T? data { get; set; }
|
||||
|
||||
public string JsonToString()
|
||||
{
|
||||
return JsonSerializer.Serialize(this);
|
||||
}
|
||||
}
|
||||
|
||||
public class Status
|
||||
{
|
||||
public string code { get; set; }
|
||||
public string message { get; set; }
|
||||
|
||||
}
|
||||
|
||||
public static class 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>
|
||||
{
|
||||
status = new Status()
|
||||
{
|
||||
code = "000",
|
||||
message = "정상"
|
||||
}
|
||||
};
|
||||
|
||||
public static APIResponseStatus<string> InvalidInputError = new APIResponseStatus<string>
|
||||
{
|
||||
status = new Status()
|
||||
{
|
||||
code = "001",
|
||||
message = "입력 값이 유효하지 않습니다."
|
||||
}
|
||||
};
|
||||
|
||||
public static APIResponseStatus<string> NotFoundError = new APIResponseStatus<string>
|
||||
{
|
||||
status = new Status()
|
||||
{
|
||||
code = "002",
|
||||
message = "알맞은 값을 찾을 수 없습니다."
|
||||
}
|
||||
};
|
||||
|
||||
public static APIResponseStatus<string> InternalSeverError = new APIResponseStatus<string>
|
||||
{
|
||||
status = new Status
|
||||
{
|
||||
code = "003",
|
||||
message = "통신에 오류가 발생하였습니다."
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
public static APIResponseStatus<string> UnknownError = new APIResponseStatus<string>
|
||||
{
|
||||
status = new Status()
|
||||
{
|
||||
code = "999",
|
||||
message = "알 수 없는 오류가 발생하였습니다.."
|
||||
}
|
||||
};
|
||||
}
|
68
Program/V1/Controllers/AppController.cs
Normal file
68
Program/V1/Controllers/AppController.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
15
Program/V1/Controllers/ErrorController.cs
Normal file
15
Program/V1/Controllers/ErrorController.cs
Normal 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("오류가 발생하였습니다. 잠시후 다시 시도해주세요.");
|
||||
}
|
||||
}
|
43
Program/V1/Controllers/MemberController.cs
Normal file
43
Program/V1/Controllers/MemberController.cs
Normal 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("외부 참조");
|
||||
}
|
||||
|
||||
}
|
105
Program/V1/Controllers/PushController.cs
Normal file
105
Program/V1/Controllers/PushController.cs
Normal 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)
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
}
|
311
Program/V1/Controllers/UserController.cs
Normal file
311
Program/V1/Controllers/UserController.cs
Normal 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("로그아웃");
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
15
Program/V1/Models/APIResult.cs
Normal file
15
Program/V1/Models/APIResult.cs
Normal 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);
|
||||
}
|
||||
}
|
31
Program/V1/Models/Academy.cs
Normal file
31
Program/V1/Models/Academy.cs
Normal 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; }
|
||||
}
|
41
Program/V1/Models/PushPayload.cs
Normal file
41
Program/V1/Models/PushPayload.cs
Normal 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
150
Program/V1/Models/User.cs
Normal 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;}
|
||||
}
|
21
Program/V1/Models/Version.cs
Normal file
21
Program/V1/Models/Version.cs
Normal 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; }
|
||||
|
||||
}
|
28
Program/V1/Repositories/UserRepository.cs
Normal file
28
Program/V1/Repositories/UserRepository.cs
Normal 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)
|
||||
{
|
||||
|
||||
}*/
|
||||
}
|
126
Program/V1/Services/PushService.cs
Normal file
126
Program/V1/Services/PushService.cs
Normal 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}"
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
136
Program/V1/Services/RepositoryService.cs
Normal file
136
Program/V1/Services/RepositoryService.cs
Normal 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
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
7
Program/V1/Services/UserService.cs
Normal file
7
Program/V1/Services/UserService.cs
Normal file
|
@ -0,0 +1,7 @@
|
|||
namespace AcaMate.V1.Services;
|
||||
|
||||
public class UserService
|
||||
{
|
||||
// priva
|
||||
|
||||
}
|
41
Properties/launchSettings.json
Normal file
41
Properties/launchSettings.json
Normal file
|
@ -0,0 +1,41 @@
|
|||
{
|
||||
"$schema": "http://json.schemastore.org/launchsettings.json",
|
||||
"iisSettings": {
|
||||
"windowsAuthentication": false,
|
||||
"anonymousAuthentication": true,
|
||||
"iisExpress": {
|
||||
"applicationUrl": "http://localhost:57683",
|
||||
"sslPort": 44307
|
||||
}
|
||||
},
|
||||
"profiles": {
|
||||
"http": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": true,
|
||||
"launchUrl": "swagger",
|
||||
"applicationUrl": "http://localhost:5144",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
},
|
||||
"https": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": true,
|
||||
"launchUrl": "swagger",
|
||||
"applicationUrl": "https://localhost:7086;http://localhost:5144",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
},
|
||||
"IIS Express": {
|
||||
"commandName": "IISExpress",
|
||||
"launchBrowser": true,
|
||||
"launchUrl": "swagger",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
16
README.md
Normal file
16
README.md
Normal file
|
@ -0,0 +1,16 @@
|
|||
# API Server
|
||||
|
||||
## Development Environment
|
||||
### Skill
|
||||
- .NET Web API
|
||||
### IDE
|
||||
- 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 | |
|
||||
| 5 |Dapper|2.1.35|SQL 직접 작성|
|
92
SwaggerConfigure.cs
Normal file
92
SwaggerConfigure.cs
Normal file
|
@ -0,0 +1,92 @@
|
|||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.OpenApi.Models;
|
||||
using Swashbuckle.AspNetCore.Annotations;
|
||||
using Swashbuckle.AspNetCore.SwaggerGen;
|
||||
|
||||
|
||||
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
|
||||
public class CustomOperationAttribute : Attribute
|
||||
{
|
||||
public string Summary { get; }
|
||||
public string Description { get; }
|
||||
public string[] Tags { get; }
|
||||
|
||||
public CustomOperationAttribute(string summary, string description, params string[] tags)
|
||||
{
|
||||
Summary = summary;
|
||||
Description = description;
|
||||
Tags = tags;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
public class CustomSwaggerOperationFilter : IOperationFilter
|
||||
{
|
||||
public void Apply(OpenApiOperation operation, OperationFilterContext context)
|
||||
{
|
||||
var customSwaggerAttribute = context.MethodInfo.GetCustomAttributes(typeof(CustomOperationAttribute), false)
|
||||
.FirstOrDefault() as CustomOperationAttribute;
|
||||
|
||||
if (customSwaggerAttribute != null)
|
||||
{
|
||||
operation.Summary = customSwaggerAttribute.Summary;
|
||||
operation.Description = customSwaggerAttribute.Description;
|
||||
operation.Tags = customSwaggerAttribute.Tags
|
||||
.Select(tag => new OpenApiTag { Name = tag })
|
||||
.ToList();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class SwaggerConfigure
|
||||
{
|
||||
private static OpenApiInfo DocName(string title, string version)
|
||||
{
|
||||
return new OpenApiInfo
|
||||
{
|
||||
Title = title,
|
||||
Version = version
|
||||
};
|
||||
}
|
||||
public static void AddCustomSwagger(this IServiceCollection services)
|
||||
{
|
||||
services.AddSwaggerGen(options =>
|
||||
{
|
||||
options.EnableAnnotations();
|
||||
options.OperationFilter<CustomSwaggerOperationFilter>();
|
||||
options.SwaggerDoc("전체", DocName("AcaMate 전체 API","1.0.0"));
|
||||
options.SwaggerDoc("공통",DocName("공통 API", "1.0.0"));
|
||||
options.SwaggerDoc("사업자 정보", DocName("사업자 정보 API", "1.0.0"));
|
||||
options.SwaggerDoc("사용자", DocName("사용자 API", "1.0.0"));
|
||||
|
||||
options.DocInclusionPredicate((docName, apiDesc) =>
|
||||
{
|
||||
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");
|
||||
});
|
||||
}
|
||||
}
|
8
appsettings.Development.json
Normal file
8
appsettings.Development.json
Normal file
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
}
|
||||
}
|
9
appsettings.json
Normal file
9
appsettings.json
Normal file
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Warning",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
},
|
||||
"AllowedHosts": "*"
|
||||
}
|
Loading…
Reference in New Issue
Block a user