diff --git a/Program/Common/Model/Status.cs b/Program/Common/Model/Status.cs index 55f7cdf..b6cc911 100644 --- a/Program/Common/Model/Status.cs +++ b/Program/Common/Model/Status.cs @@ -12,7 +12,7 @@ public class Status public string message { get; set; } } -public static class ErrorResponse +public static class DefaultResponse { // private static readonly Lazy _instance = new Lazy(); // public static ErrorResponse Instace => _instance.Value; @@ -22,6 +22,15 @@ public static class ErrorResponse // // 외부 초기화 방지 // } + public static readonly APIResponseStatus Success = new APIResponseStatus + { + status = new Status() + { + code = "000", + message = "정상" + } + }; + public static readonly APIResponseStatus InvalidInputError = new APIResponseStatus { status = new Status() diff --git a/Program/V1/Controllers/AppController.cs b/Program/V1/Controllers/AppController.cs index f5d4a32..56ca1e3 100644 --- a/Program/V1/Controllers/AppController.cs +++ b/Program/V1/Controllers/AppController.cs @@ -8,7 +8,7 @@ using Version = AcaMate.V1.Models.Version; namespace AcaMate.V1.Controllers; [ApiController] -[Route("/v1/in/app")] +[Route("/api/v1/in/app")] [ApiExplorerSettings(GroupName = "공통")] public class AppController : ControllerBase { @@ -25,7 +25,7 @@ public class AppController : ControllerBase { if (string.IsNullOrEmpty(type)) { - return BadRequest(ErrorResponse.InvalidInputError); + return BadRequest(DefaultResponse.InvalidInputError); } try @@ -34,7 +34,7 @@ public class AppController : ControllerBase if (version == null) { - return NotFound(ErrorResponse.NotFoundError); + return NotFound(DefaultResponse.NotFoundError); } var response = new APIResponseStatus @@ -60,7 +60,7 @@ public class AppController : ControllerBase } catch (Exception ex) { - return StatusCode(500, ErrorResponse.UnknownError); + 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 index cb5be62..c1efbed 100644 --- a/Program/V1/Controllers/ErrorController.cs +++ b/Program/V1/Controllers/ErrorController.cs @@ -4,7 +4,7 @@ namespace AcaMate.V1.Controllers; [ApiController] -[Route("error")] +[Route("/api/error")] public class ErrorController: ControllerBase { [HttpGet] diff --git a/Program/V1/Controllers/MemberController.cs b/Program/V1/Controllers/MemberController.cs index 00d048a..1bbfcd2 100644 --- a/Program/V1/Controllers/MemberController.cs +++ b/Program/V1/Controllers/MemberController.cs @@ -9,7 +9,7 @@ using Microsoft.AspNetCore.Mvc; namespace AcaMate.V1.Controllers; [ApiController] -[Route("v1/in/member")] +[Route("/api/v1/in/member")] [ApiExplorerSettings(GroupName = "사업자 정보")] public class MemberController: ControllerBase { diff --git a/Program/V1/Controllers/PushController.cs b/Program/V1/Controllers/PushController.cs index bdd2e1b..735057b 100644 --- a/Program/V1/Controllers/PushController.cs +++ b/Program/V1/Controllers/PushController.cs @@ -1,24 +1,59 @@ + using Microsoft.AspNetCore.Mvc; -using Swashbuckle.AspNetCore.Annotations; + +using AcaMate.V1.Services; + + namespace AcaMate.V1.Controllers; [ApiController] -[Route("v1/in/push")] +[Route("/api/v1/in/push")] [ApiExplorerSettings(GroupName = "공통")] -public class PushController: ControllerBase +public class PushController : ControllerBase { + + private readonly IWebHostEnvironment _env; + + public PushController(IWebHostEnvironment env) + { + _env = env; + } + [HttpGet()] - [CustomOperation("푸시 확인","저장된 양식을 확인 할 수 있다..", "푸시")] + [CustomOperation("푸시 확인", "저장된 양식을 확인 할 수 있다..", "푸시")] public IActionResult GetPushData() { return Ok("SEND"); } - - [HttpGet("send")] - [CustomOperation("푸시전송","저장된 양식으로, 사용자에게 푸시를 전송한다.", "푸시")] - public IActionResult SendPush() + + [HttpPost("send")] + [CustomOperation("푸시전송", "저장된 양식으로, 사용자에게 푸시를 전송한다.", "푸시")] + public async Task SendPush(string deviceToken, string title, string body, int badge) { - return Ok("SEND"); + var keysFilePath = "private/appleKeys.json"; + var uri = ""; + var p12FilePath = ""; + + if (_env.IsDevelopment()) + { + uri = "https://api.sandbox.push.apple.com"; + p12FilePath = "private/AM_Push_Sandbox.p12"; + } + else + { + uri = "https://api.push.apple.com"; + p12FilePath = "private/AM_Push.p12"; + } + var apnsTopic = "me.myds.ipstien.acamate.AcaMate"; + + + var pushService = new PushServiceWithP12(keysFilePath, p12FilePath, apnsTopic); + + // 푸시 알림 전송 + await pushService.SendPushAsync(uri, deviceToken, title, body, badge); + + return Ok("Push notification sent."); + } -} \ No newline at end of file +} diff --git a/Program/V1/Controllers/UserController.cs b/Program/V1/Controllers/UserController.cs index 3952323..cc44569 100644 --- a/Program/V1/Controllers/UserController.cs +++ b/Program/V1/Controllers/UserController.cs @@ -9,7 +9,7 @@ namespace AcaMate.V1.Controllers; [ApiController] -[Route("/v1/in/user")] +[Route("/api/v1/in/user")] [ApiExplorerSettings(GroupName = "사용자")] public class UserController: ControllerBase { diff --git a/Program/V1/Models/PushPayload.cs b/Program/V1/Models/PushPayload.cs new file mode 100644 index 0000000..4c5e63e --- /dev/null +++ b/Program/V1/Models/PushPayload.cs @@ -0,0 +1,35 @@ +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; + } + public Alert alert { get; set; } + 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 +{ + public string title { get; set; } // 제목 + public string body { get; set; } // 내용 + public string? subtitle { get; set; } // 부제목 (선택) +} \ No newline at end of file diff --git a/Program/V1/Services/PushService.cs b/Program/V1/Services/PushService.cs new file mode 100644 index 0000000..009a981 --- /dev/null +++ b/Program/V1/Services/PushService.cs @@ -0,0 +1,74 @@ +using System.Net.Http; +using System.Net.Http.Headers; +using System.Security.Cryptography.X509Certificates; +using System.Text; +using System.Text.Json; + +namespace AcaMate.V1.Services; +using AcaMate.V1.Models; + +public class PushServiceWithP12 +{ + private readonly string p12Path; + private readonly string p12Password; + private readonly string apnsTopic; + + public PushServiceWithP12(string keysFilePath, string p12Path, string apnsTopic) + { + var keys = JsonSerializer.Deserialize>(File.ReadAllText(keysFilePath)); + this.p12Password = keys["Password"]; + this.p12Path = p12Path; + this.apnsTopic = apnsTopic; + } + + public async Task SendPushAsync(string uri,string deviceToken, string title, string body, int badge) + { + var payload = new Payload() + { + aps = new Aps() + { + alert = new Alert() + { + title = title, + body = body + }, + badge = badge + } + }; + var jsonPayload = JsonSerializer.Serialize(payload); + + var certificate = new X509Certificate2(p12Path, p12Password); + + var handler = new HttpClientHandler(); + handler.ClientCertificates.Add(certificate); + + using var client = new HttpClient(handler) + { + BaseAddress = new Uri($"{uri}") + }; + + client.DefaultRequestHeaders.Add("apns-topic", apnsTopic); + client.DefaultRequestHeaders.Add("apns-priority", "10"); + + var content = new StringContent(jsonPayload, Encoding.UTF8, "application/json"); + + try + { + var response = await client.PostAsync($"/3/device/{deviceToken}", content); + + if (response.IsSuccessStatusCode) + { + Console.WriteLine("Push notification sent successfully."); + } + else + { + var errorContent = await response.Content.ReadAsStringAsync(); + Console.WriteLine($"Failed to send push notification. Status Code: {response.StatusCode}, Error: {errorContent}"); + } + } + catch (Exception ex) + { + Console.WriteLine($"An error occurred while sending the push notification: {ex.Message}"); + } + } +} \ No newline at end of file