Compare commits
126 Commits
Author | SHA1 | Date | |
---|---|---|---|
82d8afcfb7 | |||
6ef2a32d24 | |||
4e7130ea63 | |||
39359eea7a | |||
6c8c9972b2 | |||
2766b3489d | |||
e1aaf91a3b | |||
c15813804d | |||
9f6a5b882c | |||
5baa549695 | |||
669f22d365 | |||
a9462ca9b5 | |||
6a543945e7 | |||
80197c7775 | |||
1738878093 | |||
3ebb7137c0 | |||
65962c01c2 | |||
bc4658afca | |||
bb4de76af0 | |||
ff108ac299 | |||
c58092c048 | |||
0eaf1eae79 | |||
197e2007ce | |||
6580ac27b8 | |||
4ef3f3dc23 | |||
8f01f57c91 | |||
28ad68bfe6 | |||
d9166469f7 | |||
5ab4940b5f | |||
3d185179e2 | |||
3b71565733 | |||
5d79cde27f | |||
2b37dc2746 | |||
7f9811e1a3 | |||
306470ac75 | |||
04f94ca835 | |||
c1ee151a9c | |||
0eb58a1edf | |||
7f0121a175 | |||
b0606a44bb | |||
d7a3703e29 | |||
ff714ed09f | |||
9898b8364d | |||
a3bf37746d | |||
5994c9dc2f | |||
fc428d5c85 | |||
719c19e1e5 | |||
7fc316520c | |||
b2eec9c5a9 | |||
3c70f767a1 | |||
8593620059 | |||
3b9954f4eb | |||
992ec0fa77 | |||
079bb4bc29 | |||
6914bba007 | |||
090e03c6b3 | |||
643708627a | |||
1f8ac2cff7 | |||
50f740aa4b | |||
cb159397af | |||
0b65855e15 | |||
96d8999317 | |||
3e3a644203 | |||
2321980ee8 | |||
6963c5eadb | |||
19fb34bc32 | |||
d537ad7a25 | |||
166fe3dd5d | |||
a0beed0415 | |||
e6672ed630 | |||
012e7231db | |||
9acda11556 | |||
af0d5e21c1 | |||
d8c16fee29 | |||
aec03b9df7 | |||
968bd33b9d | |||
f2df6cb1e6 | |||
07900147c6 | |||
9e004cb265 | |||
ccd880e7e0 | |||
8cb4207ef6 | |||
289fe748a9 | |||
ecddaa2575 | |||
76d989a4fa | |||
f443f3410c | |||
9413dbbea8 | |||
242f1a48df | |||
59fa5bd014 | |||
29072037a0 | |||
e8a2f3d7ee | |||
e20ac8bdbc | |||
47f3b30010 | |||
806541e4d7 | |||
41c7e3a814 | |||
56ea832b8c | |||
b5021eaaa4 | |||
0e2452207c | |||
f1a901820f | |||
55f40e56cf | |||
cebf5a42cb | |||
b2e72a2f9c | |||
![]() |
41c8d94001 | ||
9cdb486785 | |||
4b7308f9d6 | |||
6f199389f1 | |||
6f376a5d29 | |||
c58373488d | |||
ab045e6eb8 | |||
![]() |
6f06f3b39c | ||
![]() |
e48eb6f76b | ||
a8e2a63db0 | |||
846f5c91a3 | |||
6f9880b9d3 | |||
f4efd70507 | |||
8f7f1c4894 | |||
aee9a805e0 | |||
a5e1cbc347 | |||
06507eca1f | |||
0634f8f156 | |||
d52ef4ff02 | |||
39beefcd37 | |||
818721f11d | |||
522432455c | |||
da0e142a79 | |||
73cfac228d | |||
ca3a9a7408 |
78
.gitignore
vendored
78
.gitignore
vendored
|
@ -1,8 +1,12 @@
|
||||||
# 특정 환경에 따라 추가
|
# 특정 환경에 따라 추가
|
||||||
|
/private/
|
||||||
|
/publish/
|
||||||
|
/bin/
|
||||||
|
/obj/
|
||||||
|
|
||||||
./private/
|
./private/
|
||||||
./privacy/
|
./privacy/
|
||||||
./publish/
|
./publish/
|
||||||
publish/
|
|
||||||
./bin/
|
./bin/
|
||||||
|
|
||||||
|
|
||||||
|
@ -15,6 +19,7 @@ publish/
|
||||||
*.swp
|
*.swp
|
||||||
|
|
||||||
# macOS 관련 파일 제외
|
# macOS 관련 파일 제외
|
||||||
|
._
|
||||||
._*
|
._*
|
||||||
.DS_Store
|
.DS_Store
|
||||||
.AppleDouble
|
.AppleDouble
|
||||||
|
@ -49,6 +54,11 @@ obj/
|
||||||
|
|
||||||
# Blazor 관련
|
# Blazor 관련
|
||||||
**/wwwroot/_framework/
|
**/wwwroot/_framework/
|
||||||
|
./wwwroot
|
||||||
|
**/wwwroot
|
||||||
|
**/publish
|
||||||
|
./publish
|
||||||
|
|
||||||
|
|
||||||
# Docker 관련
|
# Docker 관련
|
||||||
docker-compose.override.yml
|
docker-compose.override.yml
|
||||||
|
@ -56,4 +66,68 @@ Dockerfile
|
||||||
|
|
||||||
# 기타 캐시 파일
|
# 기타 캐시 파일
|
||||||
**/*.cache
|
**/*.cache
|
||||||
**/*.tmp
|
**/*.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
|
19
Back.csproj
19
Back.csproj
|
@ -8,8 +8,25 @@
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<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.AspNetCore.OpenApi" Version="8.0.8"/>
|
||||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0"/>
|
<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" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|
||||||
|
|
77
Diary/25.03.md
Normal file
77
Diary/25.03.md
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
# 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. [ ] 앱에서 데이터 받아 사용자 정보 관리 정상적으로 작동되는지 확인
|
63
Diary/25.04.md
Normal file
63
Diary/25.04.md
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
# 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 페이지 생성해보기
|
||||||
|
|
26
Document/Rule.md
Normal file
26
Document/Rule.md
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
# 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
16
Jenkinsfile
vendored
|
@ -9,6 +9,22 @@ pipeline {
|
||||||
APP_VOLUME = '/src'
|
APP_VOLUME = '/src'
|
||||||
}
|
}
|
||||||
stages {
|
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') {
|
stage('Clone Repository') {
|
||||||
steps {
|
steps {
|
||||||
git url: 'https://git.ipstein.myds.me/AcaMate/AcaMate_API.git', branch: env.GIT_BRANCH
|
git url: 'https://git.ipstein.myds.me/AcaMate/AcaMate_API.git', branch: env.GIT_BRANCH
|
||||||
|
|
288
Program.cs
288
Program.cs
|
@ -1,73 +1,255 @@
|
||||||
//var builder = WebApplication.CreateBuilder(args);
|
using Pomelo.EntityFrameworkCore;
|
||||||
/*
|
using System.Text;
|
||||||
var options = new WebApplicationOptions
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||||
|
using Microsoft.IdentityModel.Tokens;
|
||||||
|
using Microsoft.AspNetCore.SignalR;
|
||||||
|
using Microsoft.Extensions.FileProviders;
|
||||||
|
|
||||||
|
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();
|
||||||
|
builder.Services.AddDbContext<AppDbContext>(optionsAction: (serviceProvider, options) =>
|
||||||
{
|
{
|
||||||
WebRootPath = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") == "Development"
|
var httpContextAccessor = serviceProvider.GetRequiredService<IHttpContextAccessor>();
|
||||||
? "/src/publish/Debug/wwwroot"
|
var dbName = httpContextAccessor.HttpContext?.Request.Query["aca_code"].ToString();
|
||||||
: "/src/publish/Release/wwwroot"
|
var baseConnectionString = builder.Configuration.GetConnectionString("MariaDbConnection");
|
||||||
};
|
if (!string.IsNullOrEmpty(dbName))
|
||||||
*/
|
{
|
||||||
|
baseConnectionString = baseConnectionString.Replace("database=AcaMate", $"database={dbName}");
|
||||||
|
}
|
||||||
|
|
||||||
// var currentDirectory = Directory.GetCurrentDirectory();
|
options.UseMySql(baseConnectionString, ServerVersion.AutoDetect(baseConnectionString));
|
||||||
// var webRootPath = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") == "Development"
|
});
|
||||||
// ? Path.Combine(currentDirectory, "publish/Debug/wwwroot")
|
|
||||||
// : Path.Combine(currentDirectory, "publish/Release/wwwroot");
|
|
||||||
|
|
||||||
var webRootPath = Environment.GetEnvironmentVariable("ASPNETCORE_WEBROOT");
|
// DB 설정부 끝
|
||||||
// builder.WebHost.UseWebRoot(webRootPath);
|
|
||||||
|
|
||||||
var options = new WebApplicationOptions { WebRootPath = webRootPath };
|
|
||||||
|
|
||||||
var builder = WebApplication.CreateBuilder(options);
|
var dbString = builder.Configuration.GetConnectionString("MariaDbConnection");
|
||||||
|
var userString = builder.Configuration.GetConnectionString("DBAccount");
|
||||||
|
|
||||||
// var env = builder.Environment.EnvironmentName;
|
// JWT 설정부 시작
|
||||||
// string wwwrootPath = env == "Development" ? "/src/publish/Debug/wwwroot" : "/src/publish/Release/wwwroot";
|
if (builder.Environment.IsDevelopment())
|
||||||
// builder.WebHost.UseWebRoot(wwwrootPath);
|
{
|
||||||
|
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 설정부 끝
|
||||||
|
|
||||||
|
// 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<UserService>(); //
|
||||||
|
// builder.Services.AddScoped<UserController>();
|
||||||
|
|
||||||
builder.Services.AddEndpointsApiExplorer();
|
builder.Services.AddEndpointsApiExplorer();
|
||||||
builder.Services.AddSwaggerGen();
|
|
||||||
|
// 스웨거 설정 추가 부분
|
||||||
|
// 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();
|
||||||
|
builder.Logging.SetMinimumLevel(builder.Environment.IsDevelopment() ? LogLevel.Trace : LogLevel.Warning);
|
||||||
|
|
||||||
|
if (isLocal)
|
||||||
|
{
|
||||||
|
builder.WebHost.UseUrls("http://0.0.0.0:5144");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
builder.WebHost.UseUrls(builder.Environment.IsDevelopment()? "http://0.0.0.0:7004":"http://0.0.0.0:7003");
|
||||||
|
}
|
||||||
|
|
||||||
|
///// ===== builder 설정 부 ===== /////
|
||||||
|
|
||||||
var app = builder.Build();
|
var app = builder.Build();
|
||||||
|
|
||||||
// Configure the HTTP request pipeline.
|
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())
|
if (app.Environment.IsDevelopment())
|
||||||
{
|
{
|
||||||
app.UseSwagger();
|
app.UseCustomSwaggerUI();
|
||||||
app.UseSwaggerUI();
|
app.UseDeveloperExceptionPage(); // 좀더 자세한 예외 정보 제공
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
app.UseExceptionHandler("/error");
|
||||||
|
app.UseHsts();
|
||||||
}
|
}
|
||||||
|
|
||||||
app.UseExceptionHandler("/Error");
|
// 로컬 테스트 위한 부분 (올릴떄는 켜두기)
|
||||||
// .UseStaticFiles()
|
// app.UseHttpsRedirection();
|
||||||
|
|
||||||
|
|
||||||
app.UseStaticFiles(new StaticFileOptions
|
app.UseStaticFiles(new StaticFileOptions
|
||||||
{
|
|
||||||
ServeUnknownFileTypes = true
|
|
||||||
});
|
|
||||||
app.UseRouting();
|
|
||||||
app.MapFallbackToFile("index.html");
|
|
||||||
|
|
||||||
var summaries = new[]
|
|
||||||
{
|
{
|
||||||
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
|
FileProvider = new PhysicalFileProvider(staticRoot),
|
||||||
};
|
RequestPath = ""
|
||||||
|
});
|
||||||
|
|
||||||
app.MapGet("/weatherforecast", () =>
|
app.UseRouting();
|
||||||
{
|
app.UseSession();
|
||||||
var forecast = Enumerable.Range(1, 5).Select(index =>
|
app.UseCors("CorsPolicy");
|
||||||
new WeatherForecast
|
app.UseAuthentication();
|
||||||
(
|
app.UseAuthorization();
|
||||||
DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
|
|
||||||
Random.Shared.Next(-20, 55),
|
|
||||||
summaries[Random.Shared.Next(summaries.Length)]
|
// 헤더 미들웨어 부분
|
||||||
))
|
app.UseMiddleware<APIHeaderMiddleware>
|
||||||
.ToArray();
|
((object)new string[] { "iOS_AM_Connect_Key", "And_AM_Connect_Key", "Web-AM-Connect-Key" });
|
||||||
return forecast;
|
|
||||||
})
|
|
||||||
.WithName("GetWeatherForecast")
|
|
||||||
.WithOpenApi();
|
app.UseWebSockets();
|
||||||
|
Console.WriteLine($"[정적 파일 경로] {staticRoot}");
|
||||||
|
app.UseEndpoints(end =>
|
||||||
|
{
|
||||||
|
ControllerEndpointRouteBuilderExtensions.MapControllers(end);
|
||||||
|
end.MapHub<ChatHub>("/chatHub");
|
||||||
|
end.MapFallback(context => { return context.Response.SendFileAsync(Path.Combine(staticRoot, "index.html")); });
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
//예외처리 미들웨어 부분
|
||||||
|
app.UseMiddleware<ExceptionMiddleware>();
|
||||||
|
|
||||||
app.Run();
|
app.Run();
|
||||||
|
|
||||||
record WeatherForecast(DateOnly Date, int TemperatureC, string? Summary)
|
|
||||||
{
|
|
||||||
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
|
|
||||||
}
|
|
76
Program/Common/Auth/APIHeaderMiddleware.cs
Normal file
76
Program/Common/Auth/APIHeaderMiddleware.cs
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
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);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
78
Program/Common/Auth/DedicateWeb.cs
Normal file
78
Program/Common/Auth/DedicateWeb.cs
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
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", "세션 데이터 조회 중 오류");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
27
Program/Common/Auth/HeaderConfigRepository.cs
Normal file
27
Program/Common/Auth/HeaderConfigRepository.cs
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
using Back.Program.Common.Auth.Interface;
|
||||||
|
using Back.Program.Common.Data;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace Back.Program.Common.Auth
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// DB에서 헤더 키값 찾아서 그 밸류 값 빼오기 위해서 사용
|
||||||
|
/// </summary>
|
||||||
|
public class HeaderConfigRepository : IHeaderConfig
|
||||||
|
{
|
||||||
|
private readonly AppDbContext _dbContext;
|
||||||
|
|
||||||
|
public HeaderConfigRepository(AppDbContext dbContext)
|
||||||
|
{
|
||||||
|
_dbContext = dbContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<string> GetExpectedHeaderValueAsync(string headerValue)
|
||||||
|
{
|
||||||
|
var config = await _dbContext.APIHeader
|
||||||
|
.FirstOrDefaultAsync(h => h.h_value == headerValue);
|
||||||
|
return config?.h_key ?? string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
7
Program/Common/Auth/Interface/IHeaderConfig.cs
Normal file
7
Program/Common/Auth/Interface/IHeaderConfig.cs
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
namespace Back.Program.Common.Auth.Interface
|
||||||
|
{
|
||||||
|
public interface IHeaderConfig
|
||||||
|
{
|
||||||
|
Task<string> GetExpectedHeaderValueAsync(string headerName);
|
||||||
|
}
|
||||||
|
}
|
110
Program/Common/Auth/JwtTokenService.cs
Normal file
110
Program/Common/Auth/JwtTokenService.cs
Normal file
|
@ -0,0 +1,110 @@
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
57
Program/Common/Chat/ChatHub.cs
Normal file
57
Program/Common/Chat/ChatHub.cs
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
using Back.Program.Services.V1.Interfaces;
|
||||||
|
using Microsoft.AspNetCore.SignalR;
|
||||||
|
|
||||||
|
namespace Back.Program.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);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 특정 사용자에게 메시지를 보냄
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
70
Program/Common/Data/AppDbContext.cs
Normal file
70
Program/Common/Data/AppDbContext.cs
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
using Back.Program.Common.Model;
|
||||||
|
using Back.Program.Models.Entities;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Version = Back.Program.Models.Entities.Version;
|
||||||
|
|
||||||
|
namespace Back.Program.Common.Data
|
||||||
|
{
|
||||||
|
//database=AcaMate;
|
||||||
|
public class AppDbContext: DbContext
|
||||||
|
{
|
||||||
|
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> 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: 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});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
53
Program/Common/Data/SessionManager.cs
Normal file
53
Program/Common/Data/SessionManager.cs
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
49
Program/Common/Middleware/ExceptionMiddleware.cs
Normal file
49
Program/Common/Middleware/ExceptionMiddleware.cs
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
using System.Text.Json;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Back.Program.Common.Model;
|
||||||
|
|
||||||
|
namespace Back.Program.Common.Middleware;
|
||||||
|
|
||||||
|
public class ExceptionMiddleware
|
||||||
|
{
|
||||||
|
private readonly RequestDelegate _next;
|
||||||
|
private readonly ILogger<ExceptionMiddleware> _logger;
|
||||||
|
|
||||||
|
public ExceptionMiddleware(RequestDelegate next, ILogger<ExceptionMiddleware> logger)
|
||||||
|
{
|
||||||
|
_next = next;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Invoke(HttpContext context)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await _next(context); // 다음 미들웨어 호출
|
||||||
|
}
|
||||||
|
catch (AcaException ex)
|
||||||
|
{
|
||||||
|
_logger.LogWarning(ex, $"예외 발생 : {ex.Message}");
|
||||||
|
// 400 : 이건 개발자가 직접 던지는 비즈니스 로직에 대한 예외 == 클라이언트의 오류
|
||||||
|
context.Response.StatusCode = ex.HttpStatus;
|
||||||
|
context.Response.ContentType = "application/json; charset=utf-8";
|
||||||
|
|
||||||
|
var response = APIResponse.Send<string>(ex.Code, ex.Message, string.Empty);
|
||||||
|
var json = JsonSerializer.Serialize(response);
|
||||||
|
await context.Response.WriteAsync(json);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Unhandled Exception");
|
||||||
|
context.Response.StatusCode = 500;
|
||||||
|
|
||||||
|
var response = APIResponse.InternalSeverError("서버 내부 오류가 발생했습니다.");
|
||||||
|
var json = JsonSerializer.Serialize(response);
|
||||||
|
|
||||||
|
context.Response.ContentType = "application/json; charset=utf-8";
|
||||||
|
await context.Response.WriteAsync(json);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
28
Program/Common/Model/APISetting.cs
Normal file
28
Program/Common/Model/APISetting.cs
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
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
|
||||||
|
*/
|
25
Program/Common/Model/AcaException.cs
Normal file
25
Program/Common/Model/AcaException.cs
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
using System.ComponentModel;
|
||||||
|
|
||||||
|
namespace Back.Program.Common.Model;
|
||||||
|
|
||||||
|
public static class ResposeCode
|
||||||
|
{
|
||||||
|
public const string Success = "000";
|
||||||
|
public const string InputErr = "100";
|
||||||
|
public const string OutputErr = "200";
|
||||||
|
public const string NetworkErr = "300";
|
||||||
|
public const string UnknownErr = "999";
|
||||||
|
}
|
||||||
|
public class AcaException : Exception
|
||||||
|
{
|
||||||
|
public string Code { get; }
|
||||||
|
public int HttpStatus { get; }
|
||||||
|
|
||||||
|
public AcaException(string code, string message, int httpStatus = 400) : base(message)
|
||||||
|
{
|
||||||
|
this.Code = code;
|
||||||
|
this.HttpStatus = httpStatus;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
54
Program/Common/Model/JwtSettings.cs
Normal file
54
Program/Common/Model/JwtSettings.cs
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
|
|
||||||
|
namespace Back.Program.Common.Model
|
||||||
|
{
|
||||||
|
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의 동작을 수행하고 엑세스 토큰을 반환한다.
|
||||||
|
"""
|
||||||
|
*/
|
74
Program/Common/Model/Status.cs
Normal file
74
Program/Common/Model/Status.cs
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
using System.Text.Json;
|
||||||
|
|
||||||
|
namespace Back.Program.Common.Model
|
||||||
|
{
|
||||||
|
public class APIResponseStatus<T>
|
||||||
|
{
|
||||||
|
public Status status { get; set; }
|
||||||
|
public T? data { get; set; }
|
||||||
|
|
||||||
|
public string JsonToString()
|
||||||
|
{
|
||||||
|
return JsonSerializer.Serialize(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Status
|
||||||
|
{
|
||||||
|
public string code { get; set; }
|
||||||
|
public string message { get; set; }
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class APIResponse
|
||||||
|
{
|
||||||
|
public static APIResponseStatus<T> Send<T>(string code, string message, T data)
|
||||||
|
{
|
||||||
|
return new APIResponseStatus<T>
|
||||||
|
{
|
||||||
|
status = new Status()
|
||||||
|
{
|
||||||
|
code = code,
|
||||||
|
message = message
|
||||||
|
},
|
||||||
|
data = data
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 반환값 없는 API 정상 동작시
|
||||||
|
/// </summary>
|
||||||
|
public static APIResponseStatus<object> Success (){
|
||||||
|
return Send<object>("000", "정상", new {});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static APIResponseStatus<object> InvalidInputError(string? msg = null)
|
||||||
|
{
|
||||||
|
return Send<object>("100", msg ?? "입력 값이 유효하지 않습니다.", new {});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static APIResponseStatus<object> AccessExpireError(string? msg = null)
|
||||||
|
{
|
||||||
|
return Send<object>("101", msg ?? "엑세스 토큰이 유효하지 않습니다.", new {});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// -- -- -- OUTPUT ERROR -- -- -- //
|
||||||
|
public static APIResponseStatus<object> NotFoundError(string? msg = null)
|
||||||
|
{
|
||||||
|
return Send<object>("200", msg ?? "알맞은 값을 찾을 수 없습니다.", new {});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
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 {});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
123
Program/Controllers/V1/AppController.cs
Normal file
123
Program/Controllers/V1/AppController.cs
Normal file
|
@ -0,0 +1,123 @@
|
||||||
|
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 { }));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
6
Program/Controllers/V1/ChatController.cs
Normal file
6
Program/Controllers/V1/ChatController.cs
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
namespace Back.Program.Controllers.V1;
|
||||||
|
|
||||||
|
public class ChatController
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
31
Program/Controllers/V1/ClassController.cs
Normal file
31
Program/Controllers/V1/ClassController.cs
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
15
Program/Controllers/V1/ErrorController.cs
Normal file
15
Program/Controllers/V1/ErrorController.cs
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
|
namespace Back.Program.Controllers.V1
|
||||||
|
{
|
||||||
|
[ApiController]
|
||||||
|
[Route("/api/error")]
|
||||||
|
public class ErrorController: ControllerBase
|
||||||
|
{
|
||||||
|
[HttpGet]
|
||||||
|
public IActionResult HandleError()
|
||||||
|
{
|
||||||
|
return Problem("오류가 발생하였습니다. 잠시후 다시 시도해주세요.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
48
Program/Controllers/V1/MemberController.cs
Normal file
48
Program/Controllers/V1/MemberController.cs
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
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("외부 참조");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
148
Program/Controllers/V1/OutController.cs
Normal file
148
Program/Controllers/V1/OutController.cs
Normal file
|
@ -0,0 +1,148 @@
|
||||||
|
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("/");
|
||||||
|
// }
|
||||||
|
}
|
133
Program/Controllers/V1/PushController.cs
Normal file
133
Program/Controllers/V1/PushController.cs
Normal file
|
@ -0,0 +1,133 @@
|
||||||
|
using System.Security.Claims;
|
||||||
|
using Back.Program.Common.Auth;
|
||||||
|
using Back.Program.Common.Data;
|
||||||
|
using Back.Program.Common.Model;
|
||||||
|
using Back.Program.Models.Entities;
|
||||||
|
using Back.Program.Services.V1;
|
||||||
|
using Back.Program.Services.V1.Interfaces;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace Back.Program.Controllers.V1
|
||||||
|
{
|
||||||
|
[ApiController]
|
||||||
|
[Route("/api/v1/in/push")]
|
||||||
|
[ApiExplorerSettings(GroupName = "공통")]
|
||||||
|
public class PushController : ControllerBase
|
||||||
|
{
|
||||||
|
private readonly ILogger<PushController> _logger;
|
||||||
|
private readonly IRepositoryService _repositoryService;
|
||||||
|
private readonly IPushQueue _pushQueue;
|
||||||
|
private readonly IPushService _pushService;
|
||||||
|
|
||||||
|
public PushController(ILogger<PushController> logger, IRepositoryService repositoryService,
|
||||||
|
IPushQueue pushQueue, IPushService pushService)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
_repositoryService = repositoryService;
|
||||||
|
_pushQueue = pushQueue;
|
||||||
|
_pushService = pushService;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 추가 사항
|
||||||
|
// 카테고리 별 조회 하는 부분도 추가를 할 지 고민을 해야 할 것 같음
|
||||||
|
|
||||||
|
[HttpGet()]
|
||||||
|
[CustomOperation("푸시 확인", "저장된 양식을 확인 할 수 있다.", "푸시")]
|
||||||
|
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(APIResponseStatus<object>))]
|
||||||
|
public async Task<IActionResult> GetPush(string bid, string? pid, string? category)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(bid)) return BadRequest(APIResponse.InvalidInputError());
|
||||||
|
string summary = _repositoryService.ReadSummary(typeof(PushController), "GetPush");
|
||||||
|
var result = await _pushService.GetPush(summary, bid, pid, category);
|
||||||
|
return Ok(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
[HttpPost("send")]
|
||||||
|
[CustomOperation("푸시 발송", "저장된 양식으로, 사용자에게 푸시를 송신한다.", "푸시")]
|
||||||
|
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(APIResponseStatus<object>))]
|
||||||
|
public async Task<IActionResult> SendPush([FromBody] PushRequest pushRequest)
|
||||||
|
{
|
||||||
|
if (!ModelState.IsValid) return BadRequest(APIResponse.InvalidInputError());
|
||||||
|
string summary = _repositoryService.ReadSummary(typeof(PushController), "SendPush");
|
||||||
|
|
||||||
|
var result = await _pushService.SendPush(summary, pushRequest);
|
||||||
|
return Ok(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("set")]
|
||||||
|
[CustomOperation("푸시 변경", "저장된 양식을 변경한다.", "푸시")]
|
||||||
|
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(APIResponseStatus<object>))]
|
||||||
|
public async Task<IActionResult> SetPush(string token, [FromBody] DBPayload request)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(token)) return BadRequest(APIResponse.InvalidInputError());
|
||||||
|
if (!ModelState.IsValid) return BadRequest(APIResponse.InvalidInputError());
|
||||||
|
string summary = _repositoryService.ReadSummary(typeof(PushController), "SetPush");
|
||||||
|
|
||||||
|
var result = await _pushService.SetPush(summary, token, request);
|
||||||
|
return Ok(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("create")]
|
||||||
|
[CustomOperation("푸시 생성", "새로운 푸시 양식을 생성한다.", "푸시")]
|
||||||
|
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(APIResponseStatus<object>))]
|
||||||
|
public async Task<IActionResult> CreatePush(string token, [FromBody] CreatePush request)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(token)) return BadRequest(APIResponse.InvalidInputError());
|
||||||
|
if (!ModelState.IsValid) return BadRequest(APIResponse.InvalidInputError());
|
||||||
|
string summary = _repositoryService.ReadSummary(typeof(PushController), "CreatePush");
|
||||||
|
var result = await _pushService.CreatePush(summary, token, request);
|
||||||
|
return Ok(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
[HttpDelete("delete")]
|
||||||
|
[CustomOperation("푸시 삭제", "저장된 푸시 양식을 삭제 한다.", "푸시")]
|
||||||
|
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(APIResponseStatus<object>))]
|
||||||
|
public async Task<IActionResult> DeletePush(string token, string bid, string pid)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(token)) return BadRequest(APIResponse.InvalidInputError());
|
||||||
|
if (!ModelState.IsValid) return BadRequest(APIResponse.InvalidInputError());
|
||||||
|
string summary = _repositoryService.ReadSummary(typeof(PushController), "DeletePush");
|
||||||
|
var result = await _pushService.DeletePush(summary,token,bid,pid);
|
||||||
|
return Ok(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
[HttpDelete("delete/list")]
|
||||||
|
[CustomOperation("사용자 푸시 목록 삭제", "사용자가 받은 푸시목록에서 푸시를 삭제한다..", "푸시")]
|
||||||
|
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(APIResponseStatus<object>))]
|
||||||
|
public async Task<IActionResult> DeleteListPush(string token, int id)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(token)) return BadRequest(APIResponse.InvalidInputError());
|
||||||
|
if (!ModelState.IsValid) return BadRequest(APIResponse.InvalidInputError());
|
||||||
|
string summary = _repositoryService.ReadSummary(typeof(PushController), "DeleteListPush");
|
||||||
|
var result = await _pushService.DeleteListPush(summary, token, id);
|
||||||
|
return Ok(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
[HttpPost("list")]
|
||||||
|
[CustomOperation("사용자 푸시 목록 조회", "해당 사용자가 받은 푸시의 정보를 조회한다.", "푸시")]
|
||||||
|
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(APIResponseStatus<object>))]
|
||||||
|
public async Task<IActionResult> SearchToUserPush(string token, int size, [FromBody] PushCabinet? request)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(token)) return BadRequest(APIResponse.InvalidInputError());
|
||||||
|
if (!ModelState.IsValid) return BadRequest(APIResponse.InvalidInputError());
|
||||||
|
string summary = _repositoryService.ReadSummary(typeof(PushController), "SearchToUserPush");
|
||||||
|
|
||||||
|
var result = await _pushService.SearchToUserPush(summary, token, size, request);
|
||||||
|
return Ok(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
} // END PUSH CONTROLLER
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
37
Program/Controllers/V1/SessionController.cs
Normal file
37
Program/Controllers/V1/SessionController.cs
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
193
Program/Controllers/V1/UserController.cs
Normal file
193
Program/Controllers/V1/UserController.cs
Normal file
|
@ -0,0 +1,193 @@
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
*/
|
7
Program/Models/APIResponses/API_User.cs
Normal file
7
Program/Models/APIResponses/API_User.cs
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
namespace Back.Program.Models.APIResponses;
|
||||||
|
|
||||||
|
public class LoginAPIResponse
|
||||||
|
{
|
||||||
|
public string token { get; set; } = string.Empty;
|
||||||
|
public string refresh { get; set; } = string.Empty;
|
||||||
|
}
|
16
Program/Models/Entities/APIResult.cs
Normal file
16
Program/Models/Entities/APIResult.cs
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
using System.Text.Json;
|
||||||
|
|
||||||
|
namespace Back.Program.Models.Entities
|
||||||
|
{
|
||||||
|
public class APIResult
|
||||||
|
{
|
||||||
|
public bool Success { get; set; }
|
||||||
|
public string Code { get; set; }
|
||||||
|
public string Message { get; set; }
|
||||||
|
|
||||||
|
public string JsonToString()
|
||||||
|
{
|
||||||
|
return JsonSerializer.Serialize(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
32
Program/Models/Entities/Academy.cs
Normal file
32
Program/Models/Entities/Academy.cs
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
|
|
||||||
|
namespace Back.Program.Models.Entities
|
||||||
|
{
|
||||||
|
[Table("academy")]
|
||||||
|
public class Academy
|
||||||
|
{
|
||||||
|
[Key]
|
||||||
|
public string bid { get; set; }
|
||||||
|
public string business_name { get; set; }
|
||||||
|
public string business_owner { get; set; }
|
||||||
|
public string business_number { get; set; }
|
||||||
|
public DateTime business_date { get; set; }
|
||||||
|
public string business_address { get; set; }
|
||||||
|
public string business_contact { get; set; }
|
||||||
|
public string uid { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
// -- -- -- -- -- DB 테이블 -- -- -- -- -- //
|
||||||
|
|
||||||
|
public class AcademyName
|
||||||
|
{
|
||||||
|
public string bid { get; set; }
|
||||||
|
public string name { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class RequestAcademy
|
||||||
|
{
|
||||||
|
public List<string> bids { get; set; }
|
||||||
|
}
|
||||||
|
}
|
10
Program/Models/Entities/AuthKey.cs
Normal file
10
Program/Models/Entities/AuthKey.cs
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
|
|
||||||
|
namespace Back.Program.Models.Entities
|
||||||
|
{
|
||||||
|
[Table("authkey")]
|
||||||
|
public class AuthKey
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
147
Program/Models/Entities/Chatting.cs
Normal file
147
Program/Models/Entities/Chatting.cs
Normal file
|
@ -0,0 +1,147 @@
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
|
|
||||||
|
namespace Back.Program.Models.Entities
|
||||||
|
{
|
||||||
|
[Table("chat_room")]
|
||||||
|
public class Chat_Room
|
||||||
|
{
|
||||||
|
[Required(ErrorMessage = "필수 항목 누락")]
|
||||||
|
[MaxLength(16)]
|
||||||
|
public required string cid { get; set; } // bid + yyyyMMdd + count
|
||||||
|
|
||||||
|
[Required(ErrorMessage = "필수 항목 누락")]
|
||||||
|
[MaxLength(6)]
|
||||||
|
public required string bid { get; set; }
|
||||||
|
|
||||||
|
[Required(ErrorMessage = "필수 항목 누락")]
|
||||||
|
public required string name { get; set; }
|
||||||
|
|
||||||
|
[Required(ErrorMessage = "필수 항목 누락")]
|
||||||
|
[MaxLength(4)]
|
||||||
|
public required string type { get; set; }
|
||||||
|
|
||||||
|
[Required(ErrorMessage = "필수 항목 누락")]
|
||||||
|
public required DateTime create_date { get; set; }
|
||||||
|
|
||||||
|
[Required(ErrorMessage = "필수 항목 누락")]
|
||||||
|
public required DateTime open_date { get; set; }
|
||||||
|
|
||||||
|
public DateTime? close_date { get; set; }
|
||||||
|
|
||||||
|
public Chat_Room(string cid, string bid, string name, string type, DateTime create_date, DateTime open_date)
|
||||||
|
{
|
||||||
|
this.cid = cid;
|
||||||
|
this.bid = bid;
|
||||||
|
this.name = name;
|
||||||
|
this.type = type;
|
||||||
|
this.create_date = create_date;
|
||||||
|
this.open_date = open_date;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Table("chat_join")]
|
||||||
|
public class Chat_Join
|
||||||
|
{
|
||||||
|
[Required(ErrorMessage = "필수 항목 누락")]
|
||||||
|
[MaxLength(16)]
|
||||||
|
public required string cid { get; set; }
|
||||||
|
|
||||||
|
[Required(ErrorMessage = "필수 항목 누락")]
|
||||||
|
[MaxLength(70)]
|
||||||
|
public required string uid { get; set; }
|
||||||
|
|
||||||
|
public DateTime? join_date { get; set; }
|
||||||
|
public string? mid { get; set; }
|
||||||
|
|
||||||
|
[Required(ErrorMessage = "필수 항목 누락")]
|
||||||
|
public required bool is_notice { get; set; }
|
||||||
|
|
||||||
|
public Chat_Join(string cid, string uid, bool is_notice)
|
||||||
|
{
|
||||||
|
this.cid = cid;
|
||||||
|
this.uid = uid;
|
||||||
|
this.is_notice = is_notice;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Table("chat_message")]
|
||||||
|
public class Chat_Message
|
||||||
|
{
|
||||||
|
|
||||||
|
[Required(ErrorMessage = "필수 항목 누락")]
|
||||||
|
[MaxLength(36)]
|
||||||
|
public required string mid { get; set; } // UUID
|
||||||
|
|
||||||
|
[Required(ErrorMessage = "필수 항목 누락")]
|
||||||
|
[MaxLength(16)]
|
||||||
|
public required string cid { get; set; }
|
||||||
|
|
||||||
|
[Required(ErrorMessage = "필수 항목 누락")]
|
||||||
|
[MaxLength(70)]
|
||||||
|
public required string uid { get; set; }
|
||||||
|
|
||||||
|
[Required(ErrorMessage = "필수 항목 누락")]
|
||||||
|
public required DateTime create_date { get; set; }
|
||||||
|
[Required(ErrorMessage = "필수 항목 누락")]
|
||||||
|
public required string content { get; set; }
|
||||||
|
[Required(ErrorMessage = "필수 항목 누락")]
|
||||||
|
public required bool is_hidden { get; set; }
|
||||||
|
|
||||||
|
public string? media_url { get; set; }
|
||||||
|
|
||||||
|
[Required(ErrorMessage = "필수 항목 누락")]
|
||||||
|
public required int read_count { get; set; }
|
||||||
|
|
||||||
|
[Required(ErrorMessage = "필수 항목 누락")]
|
||||||
|
public required bool is_blind { get; set; }
|
||||||
|
|
||||||
|
[Required(ErrorMessage = "필수 항목 누락")]
|
||||||
|
public required bool check_report { get; set; }
|
||||||
|
|
||||||
|
public Chat_Message(string mid, string cid, string uid, DateTime create_date, string content, bool is_hidden,
|
||||||
|
int read_count, bool is_blind, bool check_report)
|
||||||
|
{
|
||||||
|
this.mid = mid;
|
||||||
|
this.cid = cid;
|
||||||
|
this.uid = uid;
|
||||||
|
this.create_date = create_date;
|
||||||
|
this.content = content;
|
||||||
|
this.is_hidden = is_hidden;
|
||||||
|
this.read_count = read_count;
|
||||||
|
this.check_report = check_report;
|
||||||
|
this.is_blind = is_blind;
|
||||||
|
this.check_report = check_report;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
[Table("chat_report_list")]
|
||||||
|
public class Chat_Report_List
|
||||||
|
{
|
||||||
|
|
||||||
|
[Required(ErrorMessage = "필수 항목 누락")]
|
||||||
|
[MaxLength(36)]
|
||||||
|
public required string mid { get; set; } // UUID
|
||||||
|
|
||||||
|
[Required(ErrorMessage = "필수 항목 누락")]
|
||||||
|
[MaxLength(16)]
|
||||||
|
public required string cid { get; set; }
|
||||||
|
|
||||||
|
[Required(ErrorMessage = "필수 항목 누락")]
|
||||||
|
[MaxLength(70)]
|
||||||
|
public required string uid { get; set; }
|
||||||
|
|
||||||
|
[Required(ErrorMessage = "필수 항목 누락")]
|
||||||
|
public required string report { get; set; }
|
||||||
|
|
||||||
|
public Chat_Report_List(string mid, string cid, string uid, string report)
|
||||||
|
{
|
||||||
|
this.mid = mid;
|
||||||
|
this.cid = cid;
|
||||||
|
this.uid = uid;
|
||||||
|
this.report = report;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
68
Program/Models/Entities/Class.cs
Normal file
68
Program/Models/Entities/Class.cs
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
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=기타)
|
||||||
|
}
|
44
Program/Models/Entities/Log.cs
Normal file
44
Program/Models/Entities/Log.cs
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
|
|
||||||
|
namespace Back.Program.Models.Entities
|
||||||
|
{
|
||||||
|
[Table("log_project")]
|
||||||
|
public class LogProject
|
||||||
|
{
|
||||||
|
[Key]
|
||||||
|
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
|
||||||
|
public int lid { get; set; }
|
||||||
|
public DateTime create_date {get; set;}
|
||||||
|
public string log { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
[Table("log_push")]
|
||||||
|
public class LogPush
|
||||||
|
{
|
||||||
|
[Key]
|
||||||
|
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
|
||||||
|
public int lid { get; set; }
|
||||||
|
public string bid {get; set;}
|
||||||
|
public string pid {get; set;}
|
||||||
|
public DateTime create_date {get; set;}
|
||||||
|
public string create_uid {get; set;}
|
||||||
|
public string log { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[Table("log_user")]
|
||||||
|
public class LogUser
|
||||||
|
{
|
||||||
|
[Key]
|
||||||
|
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
|
||||||
|
public int lid { get; set; }
|
||||||
|
public string uid {get; set;}
|
||||||
|
public DateTime create_date {get; set;}
|
||||||
|
public string create_uid {get; set;}
|
||||||
|
public string log { get; set; }
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
177
Program/Models/Entities/PushPayload.cs
Normal file
177
Program/Models/Entities/PushPayload.cs
Normal file
|
@ -0,0 +1,177 @@
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
|
|
||||||
|
namespace Back.Program.Models.Entities
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* iOS Payload 정의
|
||||||
|
* aps 딕셔너리 구성
|
||||||
|
* 1. alert : 실질적으로 사용자에게 노출되는 항목
|
||||||
|
* 1.1. title : 제목
|
||||||
|
* 1.2. subtitle : 부제목
|
||||||
|
* 1.3. body : 본문 내용
|
||||||
|
* 2. badge : 앱 아이콘에 표시할 숫자
|
||||||
|
* 3. sound : 소리인데 "default" 가능
|
||||||
|
* 4. content-available : 백그라운드 업데이트나 사일런트 푸시 송신시 사용한다.
|
||||||
|
* 1로 설정해 사용자가 직접 보지 않아도 앱이 업데이트 되게 할 수 있으며,
|
||||||
|
* UI에 알림이 표시되지 않고 데이터만 전달할 때 필요하다.
|
||||||
|
* 5. mutable-content : 값을 1로 설정해두면 Notification Service Extension을 통해 알림의 내용을 변경할 수 있다.
|
||||||
|
* 6. category : 사용자 인터렉션 구성시에 사용하는 식별자이다.
|
||||||
|
* 7. thread-id : 관련 알림들을 그룹화해 관리할 때 사용한다.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* FCM Payload
|
||||||
|
* https://firebase.google.com/docs/reference/fcm/rest/v1/projects.messages?hl=ko&_gl=1*3awp3i*_up*MQ..*_ga*MTMyNDk4ODU5MC4xNzQxMTM1MzI3*_ga_CW55HF8NVT*MTc0MTEzNTMyNy4xLjAuMTc0MTEzNTM4My4wLjAuMA..#Notification
|
||||||
|
* {
|
||||||
|
"name": string,
|
||||||
|
"data": { // 입력 전용으로 임의의 가
|
||||||
|
string: string,
|
||||||
|
...
|
||||||
|
},
|
||||||
|
"notification": {
|
||||||
|
object (Notification)
|
||||||
|
},
|
||||||
|
"android": {
|
||||||
|
object (AndroidConfig)
|
||||||
|
},
|
||||||
|
"webpush": {
|
||||||
|
object (WebpushConfig)
|
||||||
|
},
|
||||||
|
"apns": {
|
||||||
|
object (ApnsConfig)
|
||||||
|
},
|
||||||
|
"fcm_options": {
|
||||||
|
object (FcmOptions)
|
||||||
|
},
|
||||||
|
|
||||||
|
// Union field target can be only one of the following:
|
||||||
|
"token": string,
|
||||||
|
"topic": string,
|
||||||
|
"condition": string
|
||||||
|
// End of list of possible types for union field target.
|
||||||
|
}
|
||||||
|
* 1. Notification 영역
|
||||||
|
* 1.1. title: 알림 제목
|
||||||
|
* 1.2. body: 알림 내용
|
||||||
|
* 1.3. image: 알림에 표시할 이미지 URL
|
||||||
|
* 1.4. sound: 알림 재생 시 사용할 사운드
|
||||||
|
* 1.5. click_action: 알림 클릭 시 실행할 액션(인텐트 액션 문자열)
|
||||||
|
* 1.6. tag: 동일 태그를 가진 알림끼리 대체 또는 그룹화할 때 사용
|
||||||
|
* 1.7. color: 알림 아이콘 배경색 (16진수 코드 등)
|
||||||
|
* 1.8. body_loc_key 및 body_loc_args: 본문에 사용할 지역화 키와 인자 배열
|
||||||
|
* 1.9. title_loc_key 및 title_loc_args: 제목에 사용할 지역화 키와 인자 배열
|
||||||
|
* 1.10. channel_id: Android Oreo 이상에서 알림 채널 식별자
|
||||||
|
* 1.11. ticker, sticky, event_time, notification_priority, visibility, notification_count, light_settings, vibrate_timings 등: 사용자 경험이나 알림의 동작 방식을 세밀하게 제어할 때 사용
|
||||||
|
* 2. Data 영역 : 임의의 키-값 쌍을 포함하고 UI에 표시되는 내용이 아닌 앱의 로직에 활용할 데이터를 보낼 때 사용한다.
|
||||||
|
* 3. 기타 전송 옵션
|
||||||
|
* 3.1. priority: 메시지 전송 우선순위 (“high” 또는 “normal”)
|
||||||
|
* 3.2. time_to_live (ttl): 메시지 유효 시간(초 단위)
|
||||||
|
* 3.3. collapse_key: 동일 collapse_key를 가진 메시지는 최신 하나로 교체됨
|
||||||
|
* 3.4. restricted_package_name: 메시지를 수신할 앱의 패키지 이름 (Android 전용)
|
||||||
|
*/
|
||||||
|
|
||||||
|
[Table("payload")]
|
||||||
|
public class DBPayload
|
||||||
|
{
|
||||||
|
public string bid { get; set; }
|
||||||
|
public string pid { get; set; }
|
||||||
|
public string title {get; set;}
|
||||||
|
public string? subtitle {get; set;}
|
||||||
|
public string body {get; set;}
|
||||||
|
public bool alert_yn {get; set;}
|
||||||
|
public string category {get; set;}
|
||||||
|
public string? content {get; set;}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Table("push_cabinet")]
|
||||||
|
public class PushCabinet
|
||||||
|
{
|
||||||
|
[Key]
|
||||||
|
public int id { get; set; }
|
||||||
|
public string uid { get; set; }
|
||||||
|
public string pid { get; set; }
|
||||||
|
public string bid { get; set; }
|
||||||
|
public DateTime send_date { get; set; }
|
||||||
|
public bool check_yn { get; set; }
|
||||||
|
public string? content {get; set;}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class PushRequest
|
||||||
|
{
|
||||||
|
public string bid { get; set; }
|
||||||
|
public List<string> uids { get; set; }
|
||||||
|
public string pid { get; set; }
|
||||||
|
|
||||||
|
public string? content { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public class Payload
|
||||||
|
{
|
||||||
|
public Aps aps { get; set; }
|
||||||
|
public string pid { get; set; }
|
||||||
|
public string bid { get; set; }
|
||||||
|
public string content { get; set; }
|
||||||
|
// public string customKey { get; set; } 이런식으로 추가도 가능
|
||||||
|
|
||||||
|
public string ToJson()
|
||||||
|
{
|
||||||
|
return System.Text.Json.JsonSerializer.Serialize(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Aps
|
||||||
|
{
|
||||||
|
public Aps()
|
||||||
|
{
|
||||||
|
sound = "default";
|
||||||
|
content_available = 1;
|
||||||
|
}
|
||||||
|
[Required(ErrorMessage = "필수 입력 누락 (alert)")]
|
||||||
|
public Alert alert { get; set; }
|
||||||
|
|
||||||
|
[Required(ErrorMessage = "필수 입력 누락 (badge")]
|
||||||
|
public int badge { get; set; } // 앱 아이콘 표시 배지 숫자 설정
|
||||||
|
public string sound { get; set; } // 사운드 파일 이름 default = "default"
|
||||||
|
public int content_available { get; set; } // 백그라운드 알림 활성화: 필수 (1)
|
||||||
|
public string? category { get; set; } // 알림에 대한 특정 액션을 정의
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Alert
|
||||||
|
{
|
||||||
|
[Required(ErrorMessage = "필수 입력 누락 (title")]
|
||||||
|
public string title { get; set; } // 제목
|
||||||
|
[Required(ErrorMessage = "필수 입력 누락 (body)")]
|
||||||
|
public string body { get; set; } // 내용
|
||||||
|
public string? subtitle { get; set; } // 부제목 (선택)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 푸시 등록하기 위한 apns 여러 데이터 목록
|
||||||
|
/// </summary>
|
||||||
|
public class PushFileSetting
|
||||||
|
{
|
||||||
|
public string uri { get; set; }
|
||||||
|
public string p12Path { get; set; }
|
||||||
|
public string p12PWPath { get; set; }
|
||||||
|
public string apnsTopic { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class PushData
|
||||||
|
{
|
||||||
|
public string pushToken { get; set; }
|
||||||
|
public Payload payload { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class CreatePush
|
||||||
|
{
|
||||||
|
public string bid { get; set; }
|
||||||
|
public string title { get; set; }
|
||||||
|
public string? subtitle { get; set; }
|
||||||
|
public string body { get; set; }
|
||||||
|
public bool alert_yn { get; set; } = true;
|
||||||
|
public string category { get; set; }
|
||||||
|
public string? content { get; set; }
|
||||||
|
}
|
||||||
|
}
|
149
Program/Models/Entities/User.cs
Normal file
149
Program/Models/Entities/User.cs
Normal file
|
@ -0,0 +1,149 @@
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
|
|
||||||
|
namespace Back.Program.Models.Entities
|
||||||
|
{
|
||||||
|
[Table("login")]
|
||||||
|
public class Login
|
||||||
|
{
|
||||||
|
[Key]
|
||||||
|
[Required(ErrorMessage = "필수 항목 누락")]
|
||||||
|
[MaxLength(100)]
|
||||||
|
public string sns_id {get; set;}
|
||||||
|
|
||||||
|
[Required(ErrorMessage = "필수 항목 누락")]
|
||||||
|
[MaxLength(70)]
|
||||||
|
public string uid {get; set;}
|
||||||
|
|
||||||
|
[Required(ErrorMessage = "필수 항목 누락")]
|
||||||
|
[MaxLength(4)]
|
||||||
|
public string sns_type {get; set;}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Table("user_academy")]
|
||||||
|
public class User_Academy
|
||||||
|
{
|
||||||
|
[Key]
|
||||||
|
[Required(ErrorMessage = "필수 항목 누락")]
|
||||||
|
public string uid { get; set; }
|
||||||
|
|
||||||
|
[Required(ErrorMessage = "필수 항목 누락")]
|
||||||
|
public string bid { get; set; }
|
||||||
|
|
||||||
|
[Required(ErrorMessage = "필수 항목 누락")]
|
||||||
|
public DateTime register_date { get; set; }
|
||||||
|
|
||||||
|
[Required(ErrorMessage = "필수 항목 누락")]
|
||||||
|
public bool status { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[Table("user")]
|
||||||
|
public class User
|
||||||
|
{
|
||||||
|
[Key]
|
||||||
|
[Required(ErrorMessage = "필수 항목 누락")]
|
||||||
|
public string uid { get; set; }
|
||||||
|
|
||||||
|
[Required(ErrorMessage = "필수 항목 누락")]
|
||||||
|
public string name { get; set; }
|
||||||
|
public DateTime? birth { get; set; }
|
||||||
|
|
||||||
|
[Required(ErrorMessage = "필수 항목 누락")]
|
||||||
|
public string type { get; set; }
|
||||||
|
|
||||||
|
public string? device_id { get; set; }
|
||||||
|
|
||||||
|
[Required(ErrorMessage = "필수 항목 누락")]
|
||||||
|
public bool auto_login_yn { get; set; }
|
||||||
|
|
||||||
|
[Required(ErrorMessage = "필수 항목 누락")]
|
||||||
|
public DateTime login_date { get; set; }
|
||||||
|
public string? push_token { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
[Table("permission")]
|
||||||
|
public class Permission
|
||||||
|
{
|
||||||
|
[Key]
|
||||||
|
|
||||||
|
[Required(ErrorMessage = "필수 항목 누락")]
|
||||||
|
public string uid { get; set; }
|
||||||
|
|
||||||
|
public bool location_yn {get; set;}
|
||||||
|
public bool camera_yn {get; set;}
|
||||||
|
public bool photo_yn {get; set;}
|
||||||
|
public bool push_yn {get; set;}
|
||||||
|
public bool market_app_yn {get; set;}
|
||||||
|
public bool market_sms_yn {get; set;}
|
||||||
|
public bool market_email_yn {get; set;}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
[Table("location")]
|
||||||
|
public class Location
|
||||||
|
{
|
||||||
|
[Key]
|
||||||
|
|
||||||
|
[Required(ErrorMessage = "필수 항목 누락")]
|
||||||
|
public string uid { get; set; }
|
||||||
|
|
||||||
|
[Required(ErrorMessage = "필수 항목 누락")]
|
||||||
|
public string lat { get; set; }
|
||||||
|
|
||||||
|
[Required(ErrorMessage = "필수 항목 누락")]
|
||||||
|
public string lng { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[Table("contact")]
|
||||||
|
public class Contact
|
||||||
|
{
|
||||||
|
[Key]
|
||||||
|
|
||||||
|
[Required(ErrorMessage = "필수 항목 누락")]
|
||||||
|
public string uid { get; set; }
|
||||||
|
|
||||||
|
[Required(ErrorMessage = "필수 항목 누락")]
|
||||||
|
public string email { get; set; }
|
||||||
|
public string? phone { get; set; }
|
||||||
|
public string? address { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
// -- -- -- -- -- DB 테이블 -- -- -- -- -- //
|
||||||
|
|
||||||
|
public class UserAll
|
||||||
|
{
|
||||||
|
[Required(ErrorMessage = "필수 항목 누락")]
|
||||||
|
public string name { get; set; }
|
||||||
|
|
||||||
|
public DateTime? birth { get; set; }
|
||||||
|
|
||||||
|
[Required(ErrorMessage = "필수 항목 누락")]
|
||||||
|
public string type { get; set; }
|
||||||
|
|
||||||
|
public string? device_id { get; set; }
|
||||||
|
|
||||||
|
public bool auto_login_yn { get; set; }
|
||||||
|
public DateTime login_date { get; set; }
|
||||||
|
public string? push_token { get; set; }
|
||||||
|
|
||||||
|
[Required(ErrorMessage = "필수 항목 누락")]
|
||||||
|
public string email { get; set; }
|
||||||
|
public string? phone { get; set; }
|
||||||
|
public string? address { get; set; }
|
||||||
|
|
||||||
|
public bool location_yn {get; set;}
|
||||||
|
public bool camera_yn {get; set;}
|
||||||
|
public bool photo_yn {get; set;}
|
||||||
|
public bool push_yn {get; set;}
|
||||||
|
public bool market_app_yn {get; set;}
|
||||||
|
public bool market_sms_yn {get; set;}
|
||||||
|
public bool market_email_yn {get; set;}
|
||||||
|
|
||||||
|
[Required(ErrorMessage = "필수 항목 누락")]
|
||||||
|
public string sns_id {get; set;}
|
||||||
|
|
||||||
|
[Required(ErrorMessage = "필수 항목 누락")]
|
||||||
|
public string sns_type {get; set;}
|
||||||
|
}
|
||||||
|
}
|
21
Program/Models/Entities/Version.cs
Normal file
21
Program/Models/Entities/Version.cs
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
|
|
||||||
|
namespace Back.Program.Models.Entities
|
||||||
|
{
|
||||||
|
[Table("version")]
|
||||||
|
public class Version
|
||||||
|
{
|
||||||
|
[Key]
|
||||||
|
[MaxLength(4)]
|
||||||
|
public string os_type { get; set; }
|
||||||
|
[MaxLength(8)]
|
||||||
|
public string final_ver { get; set; }
|
||||||
|
[MaxLength(8)]
|
||||||
|
public string dev_ver { get; set; }
|
||||||
|
[MaxLength(8)]
|
||||||
|
public string force_ver { get; set; }
|
||||||
|
public bool choice_update_yn { get; set; }
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
35
Program/Repositories/V1/AppRepository.cs
Normal file
35
Program/Repositories/V1/AppRepository.cs
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
using Back.Program.Common.Data;
|
||||||
|
using Back.Program.Common.Model;
|
||||||
|
using Back.Program.Models.Entities;
|
||||||
|
using Back.Program.Repositories.V1.Interfaces;
|
||||||
|
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Version = Back.Program.Models.Entities.Version;
|
||||||
|
|
||||||
|
namespace Back.Program.Repositories.V1;
|
||||||
|
|
||||||
|
public class AppRepository: IAppRepository
|
||||||
|
{
|
||||||
|
private readonly AppDbContext _context;
|
||||||
|
|
||||||
|
public AppRepository(AppDbContext context)
|
||||||
|
{
|
||||||
|
_context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<APIHeader?> FindHeader(string specific)
|
||||||
|
{
|
||||||
|
return await _context.APIHeader.FirstOrDefaultAsync(h => h.specific_id == specific);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Version?> FindVersion(string type)
|
||||||
|
{
|
||||||
|
return await _context.Version.FirstOrDefaultAsync(v => v.os_type == (type =="I" ? "VO01" : "VO02"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<RefreshToken?> FindRefreshToken(string refresh)
|
||||||
|
{
|
||||||
|
return await _context.RefreshToken.FirstOrDefaultAsync(r => r.refresh_token == refresh);
|
||||||
|
}
|
||||||
|
}
|
25
Program/Repositories/V1/ClassRepository.cs
Normal file
25
Program/Repositories/V1/ClassRepository.cs
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
11
Program/Repositories/V1/Interfaces/IAppRepository.cs
Normal file
11
Program/Repositories/V1/Interfaces/IAppRepository.cs
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
using Back.Program.Common.Model;
|
||||||
|
using Version = Back.Program.Models.Entities.Version;
|
||||||
|
|
||||||
|
namespace Back.Program.Repositories.V1.Interfaces;
|
||||||
|
|
||||||
|
public interface IAppRepository
|
||||||
|
{
|
||||||
|
Task<APIHeader?> FindHeader(string specific);
|
||||||
|
Task<Version?> FindVersion(string type);
|
||||||
|
Task<RefreshToken?> FindRefreshToken(string refresh);
|
||||||
|
}
|
19
Program/Repositories/V1/Interfaces/IClassRepository.cs
Normal file
19
Program/Repositories/V1/Interfaces/IClassRepository.cs
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
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);
|
||||||
|
}
|
12
Program/Repositories/V1/Interfaces/ILogRepository.cs
Normal file
12
Program/Repositories/V1/Interfaces/ILogRepository.cs
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
using Back.Program.Models.Entities;
|
||||||
|
|
||||||
|
namespace Back.Program.Repositories.V1.Interfaces;
|
||||||
|
|
||||||
|
public interface ILogRepository
|
||||||
|
{
|
||||||
|
Task<bool> SaveLogUser(LogUser log);
|
||||||
|
Task<bool> SaveLogProject(LogProject log);
|
||||||
|
Task<bool> SaveLogPush(LogPush log);
|
||||||
|
|
||||||
|
|
||||||
|
}
|
18
Program/Repositories/V1/Interfaces/IPushRepository.cs
Normal file
18
Program/Repositories/V1/Interfaces/IPushRepository.cs
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
using Back.Program.Models.Entities;
|
||||||
|
|
||||||
|
namespace Back.Program.Repositories.V1.Interfaces;
|
||||||
|
|
||||||
|
public interface IPushRepository
|
||||||
|
{
|
||||||
|
Task<bool> FindAcademy(string bid);
|
||||||
|
Task<List<DBPayload>> FindPushList(string bid, string? pid, string? category);
|
||||||
|
Task<DBPayload?> FindPushPayload(string bid, string pid);
|
||||||
|
Task<bool> FindUserAcademy(string uid, string bid);
|
||||||
|
Task<int> CountBadge(string uid);
|
||||||
|
Task<string?> FindPushToken(string uid);
|
||||||
|
Task<PushCabinet?> FindPushCabinet(int id);
|
||||||
|
Task<List<PushCabinet>> FindPushCabinet(string uid, int size);
|
||||||
|
Task<List<PushCabinet>> FindPushCabinet(int id, int size);
|
||||||
|
|
||||||
|
|
||||||
|
}
|
14
Program/Repositories/V1/Interfaces/IUserRepository.cs
Normal file
14
Program/Repositories/V1/Interfaces/IUserRepository.cs
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
using Back.Program.Common.Model;
|
||||||
|
using Back.Program.Models.Entities;
|
||||||
|
|
||||||
|
namespace Back.Program.Repositories.V1.Interfaces
|
||||||
|
{
|
||||||
|
public interface IUserRepository
|
||||||
|
{
|
||||||
|
Task<Login?> FindLogin(string accType, string snsId);
|
||||||
|
Task<User?> FindUser(string uid);
|
||||||
|
Task<RefreshToken?> FindRefreshToken(string uid);
|
||||||
|
Task<List<AcademyName>> FindAcademies(string uid);
|
||||||
|
Task<bool> SaveChanges();
|
||||||
|
}
|
||||||
|
}
|
36
Program/Repositories/V1/LogRepository.cs
Normal file
36
Program/Repositories/V1/LogRepository.cs
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
using Back.Program.Common.Data;
|
||||||
|
using Back.Program.Models.Entities;
|
||||||
|
using Back.Program.Repositories.V1.Interfaces;
|
||||||
|
|
||||||
|
namespace Back.Program.Repositories.V1;
|
||||||
|
|
||||||
|
public class LogRepository: ILogRepository
|
||||||
|
{
|
||||||
|
private readonly ILogger<LogRepository> _logger;
|
||||||
|
private readonly AppDbContext _context;
|
||||||
|
|
||||||
|
|
||||||
|
public LogRepository(ILogger<LogRepository> logger, AppDbContext context)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
_context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> SaveLogUser(LogUser log)
|
||||||
|
{
|
||||||
|
_context.LogUser.Add(log);
|
||||||
|
return await _context.SaveChangesAsync() > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> SaveLogProject(LogProject log)
|
||||||
|
{
|
||||||
|
_context.LogProject.Add(log);
|
||||||
|
return await _context.SaveChangesAsync() > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> SaveLogPush(LogPush log)
|
||||||
|
{
|
||||||
|
_context.LogPush.Add(log);
|
||||||
|
return await _context.SaveChangesAsync() > 0;
|
||||||
|
}
|
||||||
|
}
|
85
Program/Repositories/V1/PushRepository.cs
Normal file
85
Program/Repositories/V1/PushRepository.cs
Normal file
|
@ -0,0 +1,85 @@
|
||||||
|
using Back.Program.Common.Data;
|
||||||
|
using Back.Program.Models.Entities;
|
||||||
|
using Back.Program.Repositories.V1.Interfaces;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace Back.Program.Repositories.V1;
|
||||||
|
|
||||||
|
public class PushRepository: IPushRepository
|
||||||
|
{
|
||||||
|
private readonly AppDbContext _context;
|
||||||
|
|
||||||
|
public PushRepository(AppDbContext context)
|
||||||
|
{
|
||||||
|
_context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public async Task<bool> FindAcademy(string bid)
|
||||||
|
{
|
||||||
|
return await _context.Academy.AnyAsync(a => a.bid == bid);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<List<DBPayload>> FindPushList(string bid, string? pid, string? category)
|
||||||
|
{
|
||||||
|
var pushQuery = _context.DBPayload.Where(p => p.bid == bid);
|
||||||
|
if (pid != null)
|
||||||
|
pushQuery = pushQuery.Where(p => p.pid == pid);
|
||||||
|
if (category != null)
|
||||||
|
pushQuery = pushQuery.Where(p=>p.category == category);
|
||||||
|
return await pushQuery.ToListAsync();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<DBPayload?> FindPushPayload(string bid, string pid)
|
||||||
|
{
|
||||||
|
return await _context.DBPayload.FirstOrDefaultAsync(p => p.bid == bid && p.pid == pid);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> FindUserAcademy(string uid, string bid)
|
||||||
|
{
|
||||||
|
return await _context.UserAcademy.AnyAsync(ua => ua.uid == uid && ua.bid == bid);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<int> CountBadge(string uid)
|
||||||
|
{
|
||||||
|
return await _context.PushCabinet.CountAsync(c => c.uid == uid && c.check_yn == false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<string?> FindPushToken(string uid)
|
||||||
|
{
|
||||||
|
return await _context.User
|
||||||
|
.Where(u => u.uid == uid)
|
||||||
|
.Select(u => u.push_token)
|
||||||
|
.FirstOrDefaultAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<PushCabinet?> FindPushCabinet(int id)
|
||||||
|
{
|
||||||
|
return await _context.PushCabinet.FirstOrDefaultAsync(c => c.id == id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<List<PushCabinet>> FindPushCabinet(string uid, int size)
|
||||||
|
{
|
||||||
|
return await _context.PushCabinet.Where(c => c.uid == uid)
|
||||||
|
.OrderBy(c => c.send_date)
|
||||||
|
.Take(size)
|
||||||
|
.ToListAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<List<PushCabinet>> FindPushCabinet(int id, int size)
|
||||||
|
{
|
||||||
|
var sort = await _context.PushCabinet
|
||||||
|
.Where(p=> p.id == id)
|
||||||
|
.Select(p => p.send_date)
|
||||||
|
.FirstOrDefaultAsync();
|
||||||
|
|
||||||
|
if (sort == default) return new List<PushCabinet>();
|
||||||
|
|
||||||
|
return await _context.PushCabinet
|
||||||
|
.Where(c => c.send_date > sort)
|
||||||
|
.OrderBy(c => c.send_date)
|
||||||
|
.Take(size)
|
||||||
|
.ToListAsync();
|
||||||
|
}
|
||||||
|
}
|
49
Program/Repositories/V1/UserRepository.cs
Normal file
49
Program/Repositories/V1/UserRepository.cs
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
using Back.Program.Common.Data;
|
||||||
|
using Back.Program.Common.Model;
|
||||||
|
using Back.Program.Models.Entities;
|
||||||
|
using Back.Program.Repositories.V1.Interfaces;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace Back.Program.Repositories.V1
|
||||||
|
{
|
||||||
|
public class UserRepository: IUserRepository
|
||||||
|
{
|
||||||
|
private readonly ILogger<UserRepository> _logger;
|
||||||
|
private readonly AppDbContext _context;
|
||||||
|
|
||||||
|
public UserRepository(ILogger<UserRepository> logger,AppDbContext context) {
|
||||||
|
_logger = logger;
|
||||||
|
_context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<Login?> FindLogin(string accType, string snsId)
|
||||||
|
{
|
||||||
|
return _context.Login.FirstOrDefaultAsync(l => l.sns_type == accType && l.sns_id == snsId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<User?> FindUser(string uid)
|
||||||
|
{
|
||||||
|
return _context.User.FirstOrDefaultAsync(u => u.uid == uid);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<RefreshToken?> FindRefreshToken(string uid)
|
||||||
|
{
|
||||||
|
return _context.RefreshToken.FirstOrDefaultAsync(r => r.uid == uid);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<List<AcademyName>> FindAcademies(string uid)
|
||||||
|
{
|
||||||
|
var academyList = _context.UserAcademy
|
||||||
|
.Join(_context.Academy, ua => ua.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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
168
Program/Services/V1/AppService.cs
Normal file
168
Program/Services/V1/AppService.cs
Normal file
|
@ -0,0 +1,168 @@
|
||||||
|
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 });
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
8
Program/Services/V1/ChatService.cs
Normal file
8
Program/Services/V1/ChatService.cs
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
using Back.Program.Services.V1.Interfaces;
|
||||||
|
|
||||||
|
namespace Back.Program.Services.V1;
|
||||||
|
|
||||||
|
public class ChatService: IChatService
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
21
Program/Services/V1/ClassService.cs
Normal file
21
Program/Services/V1/ClassService.cs
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
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
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
66
Program/Services/V1/InMemoryPushQueue.cs
Normal file
66
Program/Services/V1/InMemoryPushQueue.cs
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
using System.Collections.Concurrent;
|
||||||
|
using Back.Program.Models.Entities;
|
||||||
|
using Back.Program.Services.V1.Interfaces;
|
||||||
|
|
||||||
|
namespace Back.Program.Services.V1
|
||||||
|
{
|
||||||
|
public interface IPushQueue
|
||||||
|
{
|
||||||
|
void Enqueue(PushData pushData);
|
||||||
|
Task<PushData> DequeueAsync(CancellationToken cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class InMemoryPushQueue: IPushQueue
|
||||||
|
{
|
||||||
|
private readonly ConcurrentQueue<PushData> _queue = new ConcurrentQueue<PushData>();
|
||||||
|
private readonly SemaphoreSlim _signal = new SemaphoreSlim(0);
|
||||||
|
|
||||||
|
public void Enqueue(PushData pushData)
|
||||||
|
{
|
||||||
|
if( pushData is null )
|
||||||
|
throw new ArgumentNullException(nameof(pushData));
|
||||||
|
_queue.Enqueue(pushData);
|
||||||
|
_signal.Release();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<PushData> DequeueAsync(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
await _signal.WaitAsync(cancellationToken);
|
||||||
|
_queue.TryDequeue(out var pushData);
|
||||||
|
return pushData;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class PushBackgroundService : BackgroundService
|
||||||
|
{
|
||||||
|
private readonly IPushQueue _queue;
|
||||||
|
private readonly IServiceScopeFactory _scopeFactory;
|
||||||
|
|
||||||
|
public PushBackgroundService(IPushQueue queue, IServiceScopeFactory scopeFactory)
|
||||||
|
{
|
||||||
|
_queue = queue;
|
||||||
|
_scopeFactory = scopeFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||||||
|
{
|
||||||
|
while (!stoppingToken.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
var pushData = await _queue.DequeueAsync(stoppingToken);
|
||||||
|
|
||||||
|
using var scope = _scopeFactory.CreateScope();
|
||||||
|
var pushService = scope.ServiceProvider.GetRequiredService<IPushService>();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await pushService.SendPushNotificationAsync(pushData.pushToken, pushData.payload);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"푸시 전송 실패: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
11
Program/Services/V1/Interfaces/IAppService.cs
Normal file
11
Program/Services/V1/Interfaces/IAppService.cs
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
using Back.Program.Common.Model;
|
||||||
|
|
||||||
|
namespace Back.Program.Services.V1.Interfaces;
|
||||||
|
|
||||||
|
public interface IAppService
|
||||||
|
{
|
||||||
|
Task<APIResponseStatus<object>> GetHeader(string summary, string type, string specific, string project);
|
||||||
|
Task<APIResponseStatus<object>> GetVersion(string summary, string type);
|
||||||
|
Task<APIResponseStatus<object>> RetryAccess(string summary, string refresh);
|
||||||
|
|
||||||
|
}
|
6
Program/Services/V1/Interfaces/IChatService.cs
Normal file
6
Program/Services/V1/Interfaces/IChatService.cs
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
namespace Back.Program.Services.V1.Interfaces;
|
||||||
|
|
||||||
|
public interface IChatService
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
8
Program/Services/V1/Interfaces/IClassService.cs
Normal file
8
Program/Services/V1/Interfaces/IClassService.cs
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
using Back.Program.Common.Model;
|
||||||
|
|
||||||
|
namespace Back.Program.Services.V1.Interfaces;
|
||||||
|
|
||||||
|
public interface IClassService
|
||||||
|
{
|
||||||
|
Task<APIResponseStatus<object>> GetClassInfo(string summary, string cid);
|
||||||
|
}
|
13
Program/Services/V1/Interfaces/IKakaoService.cs
Normal file
13
Program/Services/V1/Interfaces/IKakaoService.cs
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
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);
|
||||||
|
}
|
19
Program/Services/V1/Interfaces/IPushService.cs
Normal file
19
Program/Services/V1/Interfaces/IPushService.cs
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
using Back.Program.Common.Model;
|
||||||
|
using Back.Program.Models.Entities;
|
||||||
|
|
||||||
|
namespace Back.Program.Services.V1.Interfaces;
|
||||||
|
|
||||||
|
public interface IPushService
|
||||||
|
{
|
||||||
|
|
||||||
|
Task SendPushNotificationAsync(string deviceToken, Payload payload);
|
||||||
|
Task<APIResponseStatus<object>> GetPush(string summary, string bid, string? pid, string? category);
|
||||||
|
Task<APIResponseStatus<object>> SendPush(string summary, PushRequest pushRequest);
|
||||||
|
Task<APIResponseStatus<object>> SetPush(string summary, string token, DBPayload request);
|
||||||
|
Task<APIResponseStatus<object>> CreatePush(string summary, string token, CreatePush request);
|
||||||
|
Task<APIResponseStatus<object>> DeletePush(string summary, string token, string bid, string pid);
|
||||||
|
Task<APIResponseStatus<object>> DeleteListPush(string summary, string token, int id);
|
||||||
|
Task<APIResponseStatus<object>> SearchToUserPush(string summary, string token, int size, PushCabinet? request);
|
||||||
|
|
||||||
|
|
||||||
|
}
|
12
Program/Services/V1/Interfaces/IRepositoryService.cs
Normal file
12
Program/Services/V1/Interfaces/IRepositoryService.cs
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
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);
|
||||||
|
}
|
9
Program/Services/V1/Interfaces/ISessionService.cs
Normal file
9
Program/Services/V1/Interfaces/ISessionService.cs
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
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);
|
||||||
|
}
|
15
Program/Services/V1/Interfaces/IUserService.cs
Normal file
15
Program/Services/V1/Interfaces/IUserService.cs
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
157
Program/Services/V1/KakaoService.cs
Normal file
157
Program/Services/V1/KakaoService.cs
Normal file
|
@ -0,0 +1,157 @@
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
342
Program/Services/V1/PushService.cs
Normal file
342
Program/Services/V1/PushService.cs
Normal file
|
@ -0,0 +1,342 @@
|
||||||
|
using System.Security.Claims;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.Json;
|
||||||
|
using Back.Program.Common.Auth;
|
||||||
|
using Back.Program.Common.Model;
|
||||||
|
using Back.Program.Models.Entities;
|
||||||
|
using Back.Program.Repositories.V1;
|
||||||
|
using Back.Program.Repositories.V1.Interfaces;
|
||||||
|
using Back.Program.Services.V1.Interfaces;
|
||||||
|
using Microsoft.AspNetCore.Http.HttpResults;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
using Polly;
|
||||||
|
using Version = System.Version;
|
||||||
|
|
||||||
|
namespace Back.Program.Services.V1
|
||||||
|
{
|
||||||
|
public class PushService : IPushService
|
||||||
|
{
|
||||||
|
private readonly ILogger<PushService> _logger;
|
||||||
|
private readonly HttpClient _httpClient;
|
||||||
|
private readonly PushFileSetting _setting;
|
||||||
|
private readonly JwtTokenService _jwtTokenService;
|
||||||
|
private readonly IRepositoryService _repositoryService;
|
||||||
|
private readonly IPushQueue _pushQueue;
|
||||||
|
|
||||||
|
private readonly ILogRepository _logRepository;
|
||||||
|
private readonly IPushRepository _pushRepository;
|
||||||
|
|
||||||
|
public PushService(ILogger<PushService> logger, HttpClient httpClient, IOptions<PushFileSetting> options,
|
||||||
|
IPushRepository pushRepository, IRepositoryService repositoryService, IPushQueue pushQueue,
|
||||||
|
ILogRepository logRepository, JwtTokenService jwtTokenService)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
_httpClient = httpClient;
|
||||||
|
_setting = options.Value;
|
||||||
|
_pushRepository = pushRepository;
|
||||||
|
_repositoryService = repositoryService;
|
||||||
|
_pushQueue = pushQueue;
|
||||||
|
_logRepository = logRepository;
|
||||||
|
_jwtTokenService = jwtTokenService;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task SendPushNotificationAsync(string deviceToken, Payload payload)
|
||||||
|
{
|
||||||
|
// 존재 안하면 예외 던져 버리는거
|
||||||
|
if (!File.Exists(_setting.p12Path) || !File.Exists(_setting.p12PWPath))
|
||||||
|
throw new FileNotFoundException("[푸시] : p12 관련 파일 확인 필요");
|
||||||
|
|
||||||
|
var jsonPayload = JsonSerializer.Serialize(payload);
|
||||||
|
|
||||||
|
var request = new HttpRequestMessage(HttpMethod.Post, $"/3/device/{deviceToken}")
|
||||||
|
{
|
||||||
|
Content = new StringContent(jsonPayload, Encoding.UTF8, "application/json"),
|
||||||
|
Version = new Version(2, 0) // HTTP/2 사용
|
||||||
|
};
|
||||||
|
|
||||||
|
// 필수 헤더 추가
|
||||||
|
request.Headers.Add("apns-topic", _setting.apnsTopic);
|
||||||
|
request.Headers.Add("apns-push-type", "alert");
|
||||||
|
|
||||||
|
var policy = Policy.Handle<HttpRequestException>()
|
||||||
|
.WaitAndRetryAsync(3, retryAttempt =>
|
||||||
|
TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)));
|
||||||
|
|
||||||
|
await policy.ExecuteAsync(async () =>
|
||||||
|
{
|
||||||
|
// var response = await _httpClient.SendAsync(request);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var response = await _httpClient.SendAsync(request);
|
||||||
|
var result = await response.Content.ReadAsStringAsync();
|
||||||
|
// Console.WriteLine($"[APNs 응답] StatusCode: {response.StatusCode}");
|
||||||
|
// Console.WriteLine($"[APNs 응답 본문]: {result}");
|
||||||
|
|
||||||
|
if (!response.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
var errorContent = await response.Content.ReadAsStringAsync();
|
||||||
|
throw new AcaException(ResposeCode.NetworkErr, $"[푸시] : APNS 통신 실패 - {errorContent}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"[푸시 전송 예외 발생] {ex.GetType().Name}: {ex.Message}");
|
||||||
|
if (ex.InnerException != null)
|
||||||
|
Console.WriteLine(
|
||||||
|
$"[InnerException] {ex.InnerException.GetType().Name}: {ex.InnerException.Message}");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<APIResponseStatus<object>> GetPush(string summary, string bid, string? pid, string? category)
|
||||||
|
{
|
||||||
|
if (!(await _pushRepository.FindAcademy(bid)))
|
||||||
|
return APIResponse.Send<object>("100", $"[{summary}], 존재하지 않는 BID", 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
180
Program/Services/V1/RepositoryService.cs
Normal file
180
Program/Services/V1/RepositoryService.cs
Normal file
|
@ -0,0 +1,180 @@
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
111
Program/Services/V1/SessionService.cs
Normal file
111
Program/Services/V1/SessionService.cs
Normal file
|
@ -0,0 +1,111 @@
|
||||||
|
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}], 세션 데이터 조회 실패");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
244
Program/Services/V1/UserService.cs
Normal file
244
Program/Services/V1/UserService.cs
Normal file
|
@ -0,0 +1,244 @@
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,3 +5,11 @@
|
||||||
- .NET Web API
|
- .NET Web API
|
||||||
### IDE
|
### IDE
|
||||||
- JetBrains Rider
|
- 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 | |
|
||||||
|
|
92
SwaggerConfigure.cs
Normal file
92
SwaggerConfigure.cs
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
using Microsoft.OpenApi.Models;
|
||||||
|
using Swashbuckle.AspNetCore.SwaggerGen;
|
||||||
|
|
||||||
|
namespace Back
|
||||||
|
{
|
||||||
|
[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");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,5 +4,19 @@
|
||||||
"Default": "Information",
|
"Default": "Information",
|
||||||
"Microsoft.AspNetCore": "Warning"
|
"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"
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,20 @@
|
||||||
{
|
{
|
||||||
"Logging": {
|
"Logging": {
|
||||||
"LogLevel": {
|
"LogLevel": {
|
||||||
"Default": "Information",
|
"Default": "Warning",
|
||||||
"Microsoft.AspNetCore": "Warning"
|
"Microsoft.AspNetCore": "Warning"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"AllowedHosts": "*"
|
"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"
|
||||||
|
}
|
||||||
}
|
}
|
1669
package-lock.json
generated
Normal file
1669
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user