diff --git a/Back.csproj b/Back.csproj
new file mode 100644
index 0000000..f7dec04
--- /dev/null
+++ b/Back.csproj
@@ -0,0 +1,25 @@
+
+
+
+ net8.0
+ enable
+ enable
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Back.http b/Back.http
new file mode 100644
index 0000000..ebc2035
--- /dev/null
+++ b/Back.http
@@ -0,0 +1,6 @@
+@Back_HostAddress = http://localhost:5144
+
+GET {{Back_HostAddress}}/weatherforecast/
+Accept: application/json
+
+###
diff --git a/Jenkinsfile b/Jenkinsfile
new file mode 100644
index 0000000..399a767
--- /dev/null
+++ b/Jenkinsfile
@@ -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."
+ }
+ }
+}
\ No newline at end of file
diff --git a/Program.cs b/Program.cs
new file mode 100644
index 0000000..3b7eed2
--- /dev/null
+++ b/Program.cs
@@ -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(options => options.UseMySql(connectionString, ServerVersion.AutoDetect(connectionString)));
+builder.Services.AddDbContext(optionsAction: (serviceProvider, options) =>
+{
+ var httpContextAccessor = serviceProvider.GetRequiredService();
+ 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(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();
+ 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();
+builder.Services.AddScoped();
+// builder.Services.AddScoped(); //
+// builder.Services.AddScoped();
+
+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");
+});
+
+
+app.Run();
\ No newline at end of file
diff --git a/Program/Common/Chat/ChatHub.cs b/Program/Common/Chat/ChatHub.cs
new file mode 100644
index 0000000..9841c96
--- /dev/null
+++ b/Program/Common/Chat/ChatHub.cs
@@ -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);
+ }
+}
\ No newline at end of file
diff --git a/Program/Common/Data/AppDbContext.cs b/Program/Common/Data/AppDbContext.cs
new file mode 100644
index 0000000..3fddb4f
--- /dev/null
+++ b/Program/Common/Data/AppDbContext.cs
@@ -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 options) : base(options)
+ {
+ }
+
+ //MARK: Program
+ public DbSet Version { get; set; }
+ public DbSet Academy { get; set; }
+ public DbSet RefreshTokens { get; set; }
+
+ //MARK: USER
+ public DbSet Login { get; set; }
+ public DbSet UserAcademy { get; set; }
+ public DbSet User { get; set; }
+ public DbSet Permission { get; set; }
+ // public DbSet Token { get; set; }
+ public DbSet Location { get; set; }
+ public DbSet Contact { get; set; }
+
+
+
+ protected override void OnModelCreating(ModelBuilder modelBuilder)
+ {
+ modelBuilder.Entity()
+ .HasKey(ua => new { ua.uid, ua.bid });
+ }
+}
\ No newline at end of file
diff --git a/Program/Common/JWTToken/JwtTokenService.cs b/Program/Common/JWTToken/JwtTokenService.cs
new file mode 100644
index 0000000..250fc0e
--- /dev/null
+++ b/Program/Common/JWTToken/JwtTokenService.cs
@@ -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 _logger;
+
+ public JwtTokenService(IOptions jwtSettings, ILogger logger)
+ {
+ _jwtSettings = jwtSettings.Value;
+ _logger = logger;
+ }
+
+ public string GenerateJwtToken(string uid)//, string role)
+ {
+ // 1. 클레임(Claim) 설정 - 필요에 따라 추가 정보도 포함
+ var claims = new List
+ {
+ // 토큰 주체(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;
+ }
+
+
+ }
+
+
+
+
+
+
+}
\ No newline at end of file
diff --git a/Program/Common/Model/JwtSettings.cs b/Program/Common/Model/JwtSettings.cs
new file mode 100644
index 0000000..5836c3e
--- /dev/null
+++ b/Program/Common/Model/JwtSettings.cs
@@ -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의 동작을 수행하고 엑세스 토큰을 반환한다.
+"""
+*/
\ No newline at end of file
diff --git a/Program/Common/Model/Status.cs b/Program/Common/Model/Status.cs
new file mode 100644
index 0000000..593d759
--- /dev/null
+++ b/Program/Common/Model/Status.cs
@@ -0,0 +1,78 @@
+using System.Text.Json;
+
+namespace AcaMate.Common.Models;
+
+public class APIResponseStatus
+{
+ public Status status { get; set; }
+ public T? data { get; set; }
+
+ public string JsonToString()
+ {
+ return JsonSerializer.Serialize(this);
+ }
+}
+
+public class Status
+{
+ public string code { get; set; }
+ public string message { get; set; }
+
+}
+
+public static class DefaultResponse
+{
+ // private static readonly Lazy _instance = new Lazy();
+ // public static ErrorResponse Instace => _instance.Value;
+
+ // private ErrorResponse()
+ // {
+ // // 외부 초기화 방지
+ // }
+
+ public static APIResponseStatus Success = new APIResponseStatus
+ {
+ status = new Status()
+ {
+ code = "000",
+ message = "정상"
+ }
+ };
+
+ public static APIResponseStatus InvalidInputError = new APIResponseStatus
+ {
+ status = new Status()
+ {
+ code = "001",
+ message = "입력 값이 유효하지 않습니다."
+ }
+ };
+
+ public static APIResponseStatus NotFoundError = new APIResponseStatus
+ {
+ status = new Status()
+ {
+ code = "002",
+ message = "알맞은 값을 찾을 수 없습니다."
+ }
+ };
+
+ public static APIResponseStatus InternalSeverError = new APIResponseStatus
+ {
+ status = new Status
+ {
+ code = "003",
+ message = "통신에 오류가 발생하였습니다."
+ }
+ };
+
+
+ public static APIResponseStatus UnknownError = new APIResponseStatus
+ {
+ status = new Status()
+ {
+ code = "999",
+ message = "알 수 없는 오류가 발생하였습니다.."
+ }
+ };
+}
diff --git a/Program/V1/Controllers/AppController.cs b/Program/V1/Controllers/AppController.cs
new file mode 100644
index 0000000..f202268
--- /dev/null
+++ b/Program/V1/Controllers/AppController.cs
@@ -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
+ {
+ 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);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Program/V1/Controllers/ErrorController.cs b/Program/V1/Controllers/ErrorController.cs
new file mode 100644
index 0000000..c1efbed
--- /dev/null
+++ b/Program/V1/Controllers/ErrorController.cs
@@ -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("오류가 발생하였습니다. 잠시후 다시 시도해주세요.");
+ }
+}
\ No newline at end of file
diff --git a/Program/V1/Controllers/MemberController.cs b/Program/V1/Controllers/MemberController.cs
new file mode 100644
index 0000000..bf8d442
--- /dev/null
+++ b/Program/V1/Controllers/MemberController.cs
@@ -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("외부 참조");
+ }
+
+}
\ No newline at end of file
diff --git a/Program/V1/Controllers/PushController.cs b/Program/V1/Controllers/PushController.cs
new file mode 100644
index 0000000..7cc0d10
--- /dev/null
+++ b/Program/V1/Controllers/PushController.cs
@@ -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");
+ }
+
+
+ ///
+ /// Sends a push notification to the specified device token with the provided payload.
+ ///
+ /// The device token to send the notification to.
+ /// The payload of the push notification.
+ /// An IActionResult indicating the result of the operation.
+ /// Push notification sent successfully.
+ /// Invalid input parameters.
+ /// Internal server error occurred.
+ /// Service unavailable.
+ [HttpPost("send")]
+ [CustomOperation("푸시전송", "저장된 양식으로, 사용자에게 푸시를 전송한다.", "푸시")]
+ [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(APIResponseStatus