Skip to content

Commit dca6046

Browse files
authored
Merge pull request #2182 from BCSDLab/develop
release: 2026.03.17 배포
2 parents 1ef166c + e41a0cf commit dca6046

65 files changed

Lines changed: 2669 additions & 129 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.env.example

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
# JWT
2+
JWT_SECRET_KEY=your-jwt-secret-key-example-32chars!!
3+
JWT_ACCESS_TOKEN_EXPIRATION_TIME=6000000
4+
5+
# Spring
6+
SPRING_PROFILES_ACTIVE=dev
7+
8+
# Database
9+
DATASOURCE_URL=jdbc:mysql://127.0.0.1:3306/koin?characterEncoding=utf8&useUnicode=true&mysqlEncoding=utf8&zeroDateTimeBehavior=convertToNull&allowMultiQueries=true
10+
DATASOURCE_USERNAME=koin
11+
DATASOURCE_PASSWORD=your-db-password
12+
13+
# Redis
14+
REDIS_HOST=127.0.0.1
15+
REDIS_PORT=6379
16+
REDIS_PASSWORD=your-redis-password
17+
18+
# MongoDB
19+
MONGODB_URI=mongodb://koin:your-mongo-password@127.0.0.1:27017/koin?authSource=admin
20+
21+
# Swagger
22+
SWAGGER_SERVER_URL=http://localhost:8080
23+
24+
# AWS SES
25+
AWS_SES_ACCESS_KEY=test
26+
AWS_SES_SECRET_KEY=test
27+
28+
# S3
29+
S3_BUCKET=your-bucket-name
30+
S3_CUSTOM_DOMAIN=https://static.example.com/
31+
32+
# Slack
33+
SLACK_KOIN_EVENT_NOTIFY_URL=https://hooks.slack.com/services/YOUR/WEBHOOK/URL
34+
SLACK_KOIN_OWNER_EVENT_NOTIFY_URL=https://hooks.slack.com/services/YOUR/WEBHOOK/URL
35+
SLACK_KOIN_SHOP_REVIEW_NOTIFY_URL=https://hooks.slack.com/services/YOUR/WEBHOOK/URL
36+
SLACK_KOIN_LOST_ITEM_NOTIFY_URL=https://hooks.slack.com/services/YOUR/WEBHOOK/URL
37+
SLACK_LOGGING_ERROR_URL=https://hooks.slack.com/services/YOUR/WEBHOOK/URL
38+
39+
# Koin Admin
40+
KOIN_ADMIN_SHOP_URL=https://admin.example.com/store
41+
KOIN_ADMIN_REVIEW_URL=https://admin.example.com/review
42+
43+
# Open API
44+
OPEN_API_KEY_PUBLIC=your-open-api-key-public
45+
OPEN_API_KEY_TMONEY=your-open-api-key-tmoney
46+
47+
# FCM
48+
FCM_KOIN_URL=example://
49+
50+
# Naver SMS
51+
NAVER_ACCESS_KEY=your-naver-access-key
52+
NAVER_SECRET_KEY=your-naver-secret-key
53+
NAVER_SMS_API_URL=https://sens.apigw.ntruss.com
54+
NAVER_SMS_SERVICE_ID=your-service-id
55+
NAVER_SMS_FROM_NUMBER=01000000000
56+
57+
# KOIN VERIFICATION
58+
MAX_VERIFICATION_COUNT=5
59+
60+
# CORS (콤마로 구분)
61+
CORS_ALLOWED_ORIGINS=http://localhost:3000,http://localhost:8080,https://example.com
62+
63+
# CloudFront
64+
CLOUDFRONT_DISTRIBUTION_ID=test
65+
CLOUDFRONT_REGION=ap-northeast-2
66+
67+
# Address API
68+
ADDRESS_API_URL=https://business.juso.go.kr/addrlink/addrLinkApi.do
69+
ADDRESS_API_KEY=your-address-api-key
70+
71+
# Toss Payment
72+
TOSS_PAYMENT_SECRET_KEY=test_sk_your-toss-secret-key
73+
TOSS_PAYMENT_API_BASE_URL=https://api.tosspayments.com/v1/payments
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
name: KOIN_API_V2 CD (develop)
2+
3+
on:
4+
push:
5+
branches:
6+
- develop
7+
8+
jobs:
9+
build-and-deploy:
10+
runs-on: ubuntu-latest
11+
12+
steps:
13+
- name: Checkout
14+
uses: actions/checkout@v4
15+
16+
- name: Record start time
17+
run: echo "START_TIME=$(date +%s)" >> $GITHUB_ENV
18+
19+
- name: Notify Slack - Deploy Start
20+
env:
21+
ACTIONS_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
22+
run: |
23+
COMMIT_MSG=$(git log -1 --pretty=%s HEAD)
24+
curl -X POST ${{ secrets.SLACK_DEPLOY_WEBHOOK_URL }} \
25+
-H 'Content-Type: application/json' \
26+
-d "{
27+
\"text\": \":rocket: *[Develop] 배포 시작*\n• Repo: ${{ github.repository }}\n• Branch: develop\n• Author: @${{ github.actor }}\n• Commit: ${COMMIT_MSG}\n• <${ACTIONS_URL}|Actions 보기>\"
28+
}"
29+
30+
- name: Set up JDK 17
31+
uses: actions/setup-java@v4
32+
with:
33+
java-version: '17'
34+
distribution: 'temurin'
35+
36+
- name: Setup Gradle
37+
uses: gradle/actions/setup-gradle@v3
38+
with:
39+
cache-read-only: false
40+
cache-encryption-key: ${{ secrets.GRADLE_CACHE_ENCRYPTION_KEY }}
41+
42+
- name: Create Firebase Admin SDK JSON
43+
run: echo '${{ secrets.FCM_ADMIN_SDK_JSON_DEVELOP }}' > src/main/resources/koin-firebase-adminsdk.json
44+
45+
- name: Build JAR
46+
run: |
47+
set -a
48+
source .env.example
49+
set +a
50+
./gradlew clean build -x test -Dspring.profiles.active=dev
51+
52+
- name: SCP JAR to develop server
53+
uses: appleboy/scp-action@v0.1.7
54+
with:
55+
host: ${{ secrets.DEVELOP_SERVER_HOST }}
56+
username: ${{ secrets.DEVELOP_SERVER_USER }}
57+
key: ${{ secrets.DEVELOP_SSH_PRIVATE_KEY }}
58+
port: ${{ secrets.DEVELOP_SERVER_PORT }}
59+
source: ${{ secrets.SOURCE_JAR_PATH }}
60+
target: ${{ secrets.DEVELOP_SERVER_JAR_PATH }}
61+
strip_components: 2
62+
63+
- name: Run deploy script on develop server
64+
uses: appleboy/ssh-action@v1.0.3
65+
with:
66+
host: ${{ secrets.DEVELOP_SERVER_HOST }}
67+
username: ${{ secrets.DEVELOP_SERVER_USER }}
68+
key: ${{ secrets.DEVELOP_SSH_PRIVATE_KEY }}
69+
port: ${{ secrets.DEVELOP_SERVER_PORT }}
70+
script: ${{ secrets.DEVELOP_DEPLOY_SCRIPT_PATH }}
71+
72+
- name: Notify Slack - Deploy Result
73+
if: always()
74+
env:
75+
ACTIONS_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
76+
run: |
77+
COMMIT_MSG=$(git log -1 --pretty=%s HEAD)
78+
DURATION_SEC=$(( $(date +%s) - START_TIME ))
79+
DURATION="${DURATION_SEC}초 (약 $(( DURATION_SEC / 60 ))분)"
80+
if [ "${{ job.status }}" = "success" ]; then
81+
ICON=":white_check_mark:"
82+
STATUS="배포 성공"
83+
else
84+
ICON=":x:"
85+
STATUS="배포 실패"
86+
fi
87+
curl -X POST ${{ secrets.SLACK_DEPLOY_WEBHOOK_URL }} \
88+
-H 'Content-Type: application/json' \
89+
-d "{
90+
\"text\": \"${ICON} *[Develop] ${STATUS}*\n• Repo: ${{ github.repository }}\n• Branch: develop\n• Author: @${{ github.actor }}\n• Commit: ${COMMIT_MSG}\n• Duration: ${DURATION}\n• <${ACTIONS_URL}|Actions 보기>\"
91+
}"
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
name: KOIN_API_V2 CD (main)
2+
3+
on:
4+
workflow_dispatch:
5+
6+
jobs:
7+
build-and-deploy:
8+
runs-on: ubuntu-latest
9+
10+
steps:
11+
- name: Checkout
12+
uses: actions/checkout@v4
13+
14+
- name: Record start time
15+
run: echo "START_TIME=$(date +%s)" >> $GITHUB_ENV
16+
17+
- name: Notify Slack - Deploy Start
18+
env:
19+
ACTIONS_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
20+
run: |
21+
COMMIT_MSG=$(git log -1 --pretty=%s HEAD)
22+
curl -X POST ${{ secrets.SLACK_DEPLOY_WEBHOOK_URL }} \
23+
-H 'Content-Type: application/json' \
24+
-d "{
25+
\"text\": \":rocket: *[Production] 배포 시작*\n• Repo: ${{ github.repository }}\n• Branch: main\n• Author: @${{ github.actor }}\n• Commit: ${COMMIT_MSG}\n• <${ACTIONS_URL}|Actions 보기>\"
26+
}"
27+
28+
- name: Set up JDK 17
29+
uses: actions/setup-java@v4
30+
with:
31+
java-version: '17'
32+
distribution: 'temurin'
33+
34+
- name: Setup Gradle
35+
uses: gradle/actions/setup-gradle@v3
36+
with:
37+
cache-read-only: false
38+
cache-encryption-key: ${{ secrets.GRADLE_CACHE_ENCRYPTION_KEY }}
39+
40+
- name: Create Firebase Admin SDK JSON
41+
run: echo '${{ secrets.FCM_ADMIN_SDK_JSON_MAIN }}' > src/main/resources/koin-firebase-adminsdk.json
42+
43+
- name: Build JAR
44+
run: |
45+
set -a
46+
source .env.example
47+
set +a
48+
./gradlew clean build -x test -Dspring.profiles.active=prod
49+
50+
- name: SCP JAR to main server
51+
uses: appleboy/scp-action@v0.1.7
52+
with:
53+
host: ${{ secrets.MAIN_SERVER_HOST }}
54+
username: ${{ secrets.MAIN_SERVER_USER }}
55+
key: ${{ secrets.MAIN_SSH_PRIVATE_KEY }}
56+
port: ${{ secrets.MAIN_SERVER_PORT }}
57+
source: ${{ secrets.SOURCE_JAR_PATH }}
58+
target: ${{ secrets.MAIN_SERVER_JAR_PATH }}
59+
strip_components: 2
60+
61+
- name: Run deploy script on main server
62+
uses: appleboy/ssh-action@v1.0.3
63+
with:
64+
host: ${{ secrets.MAIN_SERVER_HOST }}
65+
username: ${{ secrets.MAIN_SERVER_USER }}
66+
key: ${{ secrets.MAIN_SSH_PRIVATE_KEY }}
67+
port: ${{ secrets.MAIN_SERVER_PORT }}
68+
script: ${{ secrets.MAIN_DEPLOY_SCRIPT_PATH }}
69+
70+
- name: Notify Slack - Deploy Result
71+
if: always()
72+
env:
73+
ACTIONS_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
74+
run: |
75+
COMMIT_MSG=$(git log -1 --pretty=%s HEAD)
76+
DURATION_SEC=$(( $(date +%s) - START_TIME ))
77+
DURATION="${DURATION_SEC}초 (약 $(( DURATION_SEC / 60 ))분)"
78+
if [ "${{ job.status }}" = "success" ]; then
79+
ICON=":white_check_mark:"
80+
STATUS="배포 성공"
81+
else
82+
ICON=":x:"
83+
STATUS="배포 실패"
84+
fi
85+
curl -X POST ${{ secrets.SLACK_DEPLOY_WEBHOOK_URL }} \
86+
-H 'Content-Type: application/json' \
87+
-d "{
88+
\"text\": \"${ICON} *[Production] ${STATUS}*\n• Repo: ${{ github.repository }}\n• Branch: main\n• Author: @${{ github.actor }}\n• Commit: ${COMMIT_MSG}\n• Duration: ${DURATION}\n• <${ACTIONS_URL}|Actions 보기>\"
89+
}"

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,8 @@ out/
3737
.vscode/
3838
.DS_STORE
3939

40-
application.yml
4140
*adminsdk.json
41+
.env*
42+
!.env.example
4243

4344
logs
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
package in.koreatech.koin.admin.callvan.controller;
2+
3+
import static in.koreatech.koin.domain.user.model.UserType.ADMIN;
4+
import static in.koreatech.koin.global.code.ApiResponseCode.*;
5+
import static io.swagger.v3.oas.annotations.enums.ParameterIn.PATH;
6+
7+
import org.springframework.http.ResponseEntity;
8+
import org.springframework.web.bind.annotation.GetMapping;
9+
import org.springframework.web.bind.annotation.PathVariable;
10+
import org.springframework.web.bind.annotation.PostMapping;
11+
import org.springframework.web.bind.annotation.RequestBody;
12+
import org.springframework.web.bind.annotation.RequestMapping;
13+
import org.springframework.web.bind.annotation.RequestParam;
14+
15+
import in.koreatech.koin.admin.callvan.dto.AdminCallvanReportProcessRequest;
16+
import in.koreatech.koin.admin.callvan.dto.AdminCallvanReportsResponse;
17+
import in.koreatech.koin.global.auth.Auth;
18+
import in.koreatech.koin.global.code.ApiResponseCodes;
19+
import io.swagger.v3.oas.annotations.Operation;
20+
import io.swagger.v3.oas.annotations.Parameter;
21+
import io.swagger.v3.oas.annotations.tags.Tag;
22+
import jakarta.validation.Valid;
23+
24+
@Tag(name = "(Admin) Callvan: 신고 처리", description = "어드민 콜벤 사용자 신고 내역 관리")
25+
@RequestMapping("/admin/callvan/reports")
26+
public interface AdminCallvanReportApi {
27+
28+
@ApiResponseCodes({
29+
OK,
30+
UNAUTHORIZED_USER,
31+
FORBIDDEN_ADMIN
32+
})
33+
@Operation(summary = "콜벤 신고 목록 조회", description = """
34+
콜벤 사용자 신고 접수 목록을 조회합니다.
35+
- `only_pending=true` 이면 미처리 신고(PENDING)만 조회합니다.
36+
- 각 항목에는 피신고자 정보, 신고 사유, 첨부 이미지, 처리 유형, 누적 신고 이력이 포함됩니다.
37+
""")
38+
@GetMapping
39+
ResponseEntity<AdminCallvanReportsResponse> getCallvanReports(
40+
@RequestParam(name = "only_pending", required = false, defaultValue = "false") Boolean onlyPending,
41+
@RequestParam(name = "page", required = false) Integer page,
42+
@RequestParam(name = "limit", required = false) Integer limit,
43+
@Auth(permit = {ADMIN}) Integer adminId);
44+
45+
@ApiResponseCodes({
46+
OK,
47+
UNAUTHORIZED_USER,
48+
FORBIDDEN_ADMIN,
49+
INVALID_REQUEST_BODY,
50+
NOT_FOUND_CALLVAN_REPORT,
51+
CALLVAN_REPORT_ALREADY_PROCESSED
52+
})
53+
@Operation(summary = "콜벤 신고 처리", description = """
54+
콜벤 신고를 처리합니다.
55+
- `WARNING`: 신고 확정 후 주의 안내 알림을 발송합니다.
56+
- `TEMPORARY_RESTRICTION_14_DAYS`: 신고 확정 후 14일간 새 모집/참여를 제한하며, 제재 알림을 발송합니다.
57+
- `PERMANENT_RESTRICTION`: 신고 확정 후 콜벤 기능을 영구 제한하며, 제재 알림을 발송합니다.
58+
- `REJECT`: 신고를 반려하고 상태를 REJECTED로 변경합니다. 알림은 발송되지 않습니다.
59+
60+
#### 제재 유형별 알림 메시지
61+
| 처리 유형 | 알림 타입 | 메시지 |
62+
| :--- | :--- | :--- |
63+
| `WARNING` | `REPORT_WARNING` | 콜벤팟 이용 과정에서 신고가 접수되어 운영 검토 후 주의 안내가 전달되었습니다. 이후 동일한 문제가 반복될 경우 콜벤 기능 이용이 제한될 수 있습니다. |
64+
| `TEMPORARY_RESTRICTION_14_DAYS` | `REPORT_RESTRICTION_14_DAYS` | 콜벤팟 이용 과정에서 신고가 접수되어 운영 검토 후 14일간 콜벤 기능 이용이 제한되었습니다. |
65+
| `PERMANENT_RESTRICTION` | `REPORT_PERMANENT_RESTRICTION` | 콜벤팟 이용 과정에서 신고가 접수되어 운영 검토 후 콜벤 기능 이용이 영구적으로 제한되었습니다. |
66+
""")
67+
@PostMapping("/{reportId}/process")
68+
ResponseEntity<Void> processCallvanReport(
69+
@Parameter(in = PATH) @PathVariable Integer reportId,
70+
@RequestBody @Valid AdminCallvanReportProcessRequest request,
71+
@Auth(permit = {ADMIN}) Integer adminId);
72+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package in.koreatech.koin.admin.callvan.controller;
2+
3+
import static in.koreatech.koin.domain.user.model.UserType.ADMIN;
4+
import static io.swagger.v3.oas.annotations.enums.ParameterIn.PATH;
5+
6+
import org.springframework.http.ResponseEntity;
7+
import org.springframework.web.bind.annotation.GetMapping;
8+
import org.springframework.web.bind.annotation.PathVariable;
9+
import org.springframework.web.bind.annotation.PostMapping;
10+
import org.springframework.web.bind.annotation.RequestBody;
11+
import org.springframework.web.bind.annotation.RequestMapping;
12+
import org.springframework.web.bind.annotation.RequestParam;
13+
import org.springframework.web.bind.annotation.RestController;
14+
15+
import in.koreatech.koin.admin.callvan.dto.AdminCallvanReportProcessRequest;
16+
import in.koreatech.koin.admin.callvan.dto.AdminCallvanReportsResponse;
17+
import in.koreatech.koin.admin.callvan.service.AdminCallvanReportQueryService;
18+
import in.koreatech.koin.admin.callvan.service.AdminCallvanReportService;
19+
import in.koreatech.koin.global.auth.Auth;
20+
import io.swagger.v3.oas.annotations.Parameter;
21+
import jakarta.validation.Valid;
22+
import lombok.RequiredArgsConstructor;
23+
24+
@RestController
25+
@RequestMapping("/admin/callvan/reports")
26+
@RequiredArgsConstructor
27+
public class AdminCallvanReportController implements AdminCallvanReportApi {
28+
29+
private final AdminCallvanReportService adminCallvanReportService;
30+
private final AdminCallvanReportQueryService adminCallvanReportQueryService;
31+
32+
@GetMapping
33+
public ResponseEntity<AdminCallvanReportsResponse> getCallvanReports(
34+
@RequestParam(name = "only_pending", required = false, defaultValue = "false") Boolean onlyPending,
35+
@RequestParam(name = "page", required = false) Integer page,
36+
@RequestParam(name = "limit", required = false) Integer limit,
37+
@Auth(permit = {ADMIN}) Integer adminId
38+
) {
39+
return ResponseEntity.ok(adminCallvanReportQueryService.getReports(onlyPending, page, limit));
40+
}
41+
42+
@PostMapping("/{reportId}/process")
43+
public ResponseEntity<Void> processCallvanReport(
44+
@Parameter(in = PATH) @PathVariable Integer reportId,
45+
@RequestBody @Valid AdminCallvanReportProcessRequest request,
46+
@Auth(permit = {ADMIN}) Integer adminId
47+
) {
48+
adminCallvanReportService.processReport(reportId, adminId, request);
49+
return ResponseEntity.ok().build();
50+
}
51+
}

0 commit comments

Comments
 (0)