Skip to content

Commit a64c823

Browse files
authored
Merge branch 'dev' into DASOMBE-17-refactor-로그인-로직-분리
2 parents e07eb79 + 0f7e3ce commit a64c823

File tree

50 files changed

+1794
-62
lines changed

Some content is hidden

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

50 files changed

+1794
-62
lines changed
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
name: "🍀다솜-이슈-템플릿"
2+
description: "해당 이슈 생성 템플릿으로 작성해주세요! (2025.08.08 update)"
3+
labels: [FEAT]
4+
title: "[feat] 개발 타이틀"
5+
body:
6+
- type: input
7+
id: parentKey
8+
attributes:
9+
label: "🎟️ 상위 작업 (Ticket Number)"
10+
description: "상위 작업의 Ticket Number를 기입해주세요"
11+
placeholder: "DASOMBE-00"
12+
validations:
13+
required: true
14+
15+
- type: dropdown
16+
id: projectKey
17+
attributes:
18+
label: "📦 프로젝트"
19+
description: "이슈를 생성할 Jira 프로젝트를 선택하세요"
20+
options:
21+
- DASOMFE
22+
- DASOMBE
23+
validations:
24+
required: true
25+
26+
- type: textarea
27+
id: purpose
28+
attributes:
29+
label: "🎯 목적(Purpose)"
30+
description: "해당 Issue가 발생한 원인과 배경을 설명한다."
31+
placeholder: "예: 회원 기능 구현을 위한 로그인 기능 추가 필요"
32+
validations:
33+
required: true
34+
35+
- type: textarea
36+
id: description
37+
attributes:
38+
label: "📝 상세 내용(Description)"
39+
description: "구현할 기능 또는 수정 사항에 대한 구체적인 설명"
40+
placeholder: "예: 이메일 및 비밀번호 입력값 검증 로직 추가, OAuth 연동 검토"
41+
validations:
42+
required: true

.github/pull_request_template.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# [태그] 제목
2+
3+
(예: [feat] 사용자 인증 기능 추가)
4+
5+
## Issue
6+
7+
- 예시: #111, #112
8+
(해당 PR과 관련된 이슈 번호를 명시하여 추적 용이성을 확보)
9+
10+
## 변경 내용
11+
12+
- 이번 PR에서 어떤 변경이 이루어졌는지 간략하게 기술합니다.
13+
(예: 기존 로그인 API에 JWT 기반 인증 로직 추가)
14+
15+
## 체크리스트
16+
17+
- PR을 제출하기 전에 다음 사항들을 확인해주세요:
18+
19+
- [ ] 커밋 메시지가 컨벤션을 따르는가?
20+
- [ ] 린트 검사를 통과했는가? (`npm run lint` or `npm run lint:fix`)
21+
- [ ] 로컬에서 빌드가 성공하는가? (`npm run build`)
22+
23+
## 테스트
24+
25+
- [ ] 로컬에서 테스트 완료
26+
- [ ] 기존 기능에 영향 없음 확인
27+
- [ ] 다양한 브라우저에서 테스트 (필요시)
28+
29+
## 리뷰 요구사항(필요시)
30+
31+
- 특별히 리뷰해주길 원하는 부분
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
name: Close Jira issue
2+
on:
3+
issues:
4+
types:
5+
- closed
6+
7+
jobs:
8+
close-issue:
9+
name: Close Jira issue
10+
runs-on: ubuntu-latest
11+
12+
steps:
13+
- name: Login to Jira
14+
uses: atlassian/gajira-login@v3
15+
env:
16+
JIRA_BASE_URL: ${{ secrets.JIRA_URL }}
17+
JIRA_API_TOKEN: ${{ secrets.JIRA_API_TOKEN }}
18+
JIRA_USER_EMAIL: ${{ secrets.JIRA_EMAIL }}
19+
20+
- name: Extract Jira issue key from GitHub issue title
21+
id: extract-key
22+
run: |
23+
ISSUE_TITLE="${{ github.event.issue.title }}"
24+
JIRA_KEY=$(echo "$ISSUE_TITLE" | grep -oE '[A-Z]+-[0-9]+')
25+
echo "JIRA_KEY=$JIRA_KEY" >> $GITHUB_ENV
26+
27+
- name: Close Jira issue
28+
if: env.JIRA_KEY != ''
29+
uses: atlassian/gajira-transition@v3
30+
with:
31+
issue: ${{ env.JIRA_KEY }}
32+
transition: 완료
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
name: Create Jira issue
2+
on:
3+
issues:
4+
types:
5+
- opened
6+
jobs:
7+
create-issue:
8+
name: Create Jira issue
9+
runs-on: ubuntu-latest
10+
steps:
11+
- name: Login
12+
uses: atlassian/gajira-login@v3
13+
env:
14+
JIRA_BASE_URL: ${{ secrets.JIRA_URL }}
15+
JIRA_API_TOKEN: ${{ secrets.JIRA_API_TOKEN }}
16+
JIRA_USER_EMAIL: ${{ secrets.JIRA_EMAIL }}
17+
18+
- name: Checkout main code
19+
uses: actions/checkout@v4
20+
with:
21+
ref: main
22+
23+
- name: Issue Parser
24+
uses: stefanbuck/github-issue-parser@v3
25+
id: issue-parser
26+
with:
27+
template-path: .github/ISSUE_TEMPLATE/🍀다솜-이슈-템플릿.yml
28+
29+
- name: Log Issue Parser
30+
run: |
31+
echo '${{ steps.issue-parser.outputs.issueparser_parentKey }}'
32+
echo '${{ steps.issue-parser.outputs.__ticket_number }}'
33+
echo '${{ steps.issue-parser.outputs.jsonString }}'
34+
35+
- name: Convert markdown to Jira Syntax
36+
uses: peter-evans/jira2md@v1
37+
id: md2jira
38+
with:
39+
input-text: |
40+
### Github Issue Link
41+
- ${{ github.event.issue.html_url }}
42+
43+
${{ github.event.issue.body }}
44+
mode: md2jira
45+
46+
- name: Create Issue
47+
id: create
48+
uses: atlassian/gajira-create@v3
49+
with:
50+
project: DASOMBE
51+
issuetype: Task
52+
summary: "${{ github.event.issue.title }}"
53+
description: "${{ steps.md2jira.outputs.output-text }}"
54+
fields: |
55+
{
56+
"parent": {
57+
"key": "${{ steps.issue-parser.outputs.issueparser_parentKey}}"
58+
}
59+
}
60+
61+
- name: Log created issue
62+
run: echo "Jira Issue ${{ steps.issue-parser.outputs.parentKey }}/${{ steps.create.outputs.issue }} was created"
63+
64+
- name: Update issue title
65+
uses: actions-cool/issues-helper@v3
66+
with:
67+
actions: "update-issue"
68+
token: ${{ secrets.GITHUB_TOKEN }}
69+
title: "[${{ steps.create.outputs.issue }}] ${{ github.event.issue.title }}"
70+
71+
- name: Add comment with Jira issue link
72+
uses: actions-cool/issues-helper@v3
73+
with:
74+
actions: "create-comment"
75+
token: ${{ secrets.GITHUB_TOKEN }}
76+
issue-number: ${{ github.event.issue.number }}
77+
body: "Jira Issue Created: [${{ steps.create.outputs.issue }}](${{ secrets.JIRA_URL }}/browse/${{ steps.create.outputs.issue }})"

build.gradle

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@ dependencies {
5757
implementation 'io.jsonwebtoken:jjwt-api:0.12.6'
5858
runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.12.6'
5959
runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.12.6'
60+
61+
implementation 'software.amazon.awssdk:s3:2.28.23'
6062
}
6163

6264
tasks.named('test') {
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package dmu.dasom.api.domain.activity.controller;
2+
3+
import dmu.dasom.api.domain.activity.dto.ActivityResponseDto;
4+
import dmu.dasom.api.domain.activity.service.ActivityService;
5+
import dmu.dasom.api.domain.common.exception.ErrorResponse;
6+
import io.swagger.v3.oas.annotations.Operation;
7+
import io.swagger.v3.oas.annotations.media.Content;
8+
import io.swagger.v3.oas.annotations.media.ExampleObject;
9+
import io.swagger.v3.oas.annotations.media.Schema;
10+
import io.swagger.v3.oas.annotations.responses.ApiResponse;
11+
import io.swagger.v3.oas.annotations.responses.ApiResponses;
12+
import io.swagger.v3.oas.annotations.tags.Tag;
13+
import lombok.RequiredArgsConstructor;
14+
import org.springframework.http.ResponseEntity;
15+
import org.springframework.web.bind.annotation.GetMapping;
16+
import org.springframework.web.bind.annotation.RequestMapping;
17+
import org.springframework.web.bind.annotation.RestController;
18+
19+
import java.util.List;
20+
21+
@RestController
22+
@RequestMapping("/api/activities")
23+
@RequiredArgsConstructor
24+
@Tag(name = "Activity API", description = "활동 연혁 조회 API")
25+
public class ActivityController {
26+
27+
private final ActivityService activityService;
28+
29+
@Operation(summary = "활동 연혁 전체 조회", description = "모든 활동 연혁을 연도별, 섹션별로 그룹화하여 조회합니다.")
30+
@ApiResponses(value = {
31+
@ApiResponse(responseCode = "200", description = "활동 연혁 전체 조회 성공"),
32+
@ApiResponse(responseCode = "405", description = "허용되지 않은 요청 방식",
33+
content = @Content(
34+
mediaType = "application/json",
35+
schema = @Schema(implementation = ErrorResponse.class),
36+
examples = {
37+
@ExampleObject(
38+
name = "허용되지 않은 메서드",
39+
value = "{ \"code\": \"C007\", \"message\": \"허용되지 않은 요청 방식입니다.\" }"
40+
)
41+
}
42+
)),
43+
@ApiResponse(responseCode = "500", description = "서버 내부 오류",
44+
content = @Content(
45+
mediaType = "application/json",
46+
schema = @Schema(implementation = ErrorResponse.class),
47+
examples = {
48+
@ExampleObject(
49+
name = "서버 문제 발생",
50+
value = "{ \"code\": \"C009\", \"message\": \"서버에 문제가 발생하였습니다.\" }"
51+
)
52+
}
53+
))
54+
})
55+
@GetMapping
56+
public ResponseEntity<List<ActivityResponseDto>> getActivities() {
57+
return ResponseEntity.ok(activityService.getActivities());
58+
}
59+
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
package dmu.dasom.api.domain.activity.controller;
2+
3+
import dmu.dasom.api.domain.activity.dto.ActivityRequestDto;
4+
import dmu.dasom.api.domain.activity.service.ActivityService;
5+
import dmu.dasom.api.domain.common.exception.ErrorResponse;
6+
import io.swagger.v3.oas.annotations.Operation;
7+
import io.swagger.v3.oas.annotations.media.Content;
8+
import io.swagger.v3.oas.annotations.media.ExampleObject;
9+
import io.swagger.v3.oas.annotations.media.Schema;
10+
import io.swagger.v3.oas.annotations.responses.ApiResponse;
11+
import io.swagger.v3.oas.annotations.responses.ApiResponses;
12+
import io.swagger.v3.oas.annotations.tags.Tag;
13+
import jakarta.validation.Valid;
14+
import jakarta.validation.constraints.Min;
15+
import lombok.RequiredArgsConstructor;
16+
import org.springframework.http.ResponseEntity;
17+
import org.springframework.web.bind.annotation.*;
18+
19+
@RestController
20+
@RequestMapping("/api/admin/activities")
21+
@RequiredArgsConstructor
22+
@Tag(name = "ADMIN - Activity API", description = "어드민 활동 연혁 관리 API")
23+
public class AdminActivityController {
24+
25+
private final ActivityService activityService;
26+
27+
@Operation(summary = "활동 연혁 생성")
28+
@ApiResponses(value = {
29+
@ApiResponse(responseCode = "201", description = "활동 연혁 생성 성공"),
30+
@ApiResponse(responseCode = "400", description = "요청 값 유효성 검사 실패", content = @Content(mediaType = "application/json", schema = @Schema(implementation = ErrorResponse.class), examples = @ExampleObject(name = "유효성 검사 실패", value = "{ \"code\": \"C007\", \"message\": \"요청한 값이 올바르지 않습니다.\" }"))),
31+
@ApiResponse(responseCode = "401", description = "인증되지 않은 사용자", content = @Content(mediaType = "application/json", schema = @Schema(implementation = ErrorResponse.class), examples = @ExampleObject(name = "인증 실패", value = "{ \"code\": \"C001\", \"message\": \"인증되지 않은 사용자입니다.\" }"))),
32+
@ApiResponse(responseCode = "403", description = "접근 권한 없음 (ADMIN이 아님)", content = @Content(mediaType = "application/json", schema = @Schema(implementation = ErrorResponse.class), examples = @ExampleObject(name = "권한 없음", value = "{ \"code\": \"C002\", \"message\": \"접근 권한이 없습니다.\" }"))),
33+
@ApiResponse(responseCode = "500", description = "서버 내부 오류", content = @Content(mediaType = "application/json", schema = @Schema(implementation = ErrorResponse.class), examples = @ExampleObject(name = "서버 문제 발생", value = "{ \"code\": \"C009\", \"message\": \"서버에 문제가 발생하였습니다.\" }")))
34+
})
35+
@PostMapping
36+
public ResponseEntity<Void> createActivity(@Valid @RequestBody ActivityRequestDto requestDto) {
37+
activityService.createActivity(requestDto);
38+
return ResponseEntity.status(201).build();
39+
}
40+
41+
@Operation(summary = "활동 연혁 수정")
42+
@ApiResponses(value = {
43+
@ApiResponse(responseCode = "200", description = "활동 연혁 수정 성공"),
44+
@ApiResponse(responseCode = "400", description = "요청 값 유효성 검사 실패", content = @Content(mediaType = "application/json", schema = @Schema(implementation = ErrorResponse.class), examples = @ExampleObject(name = "유효성 검사 실패", value = "{ \"code\": \"C007\", \"message\": \"요청한 값이 올바르지 않습니다.\" }"))),
45+
@ApiResponse(responseCode = "401", description = "인증되지 않은 사용자", content = @Content(mediaType = "application/json", schema = @Schema(implementation = ErrorResponse.class), examples = @ExampleObject(name = "인증 실패", value = "{ \"code\": \"C001\", \"message\": \"인증되지 않은 사용자입니다.\" }"))),
46+
@ApiResponse(responseCode = "403", description = "접근 권한 없음 (ADMIN이 아님)", content = @Content(mediaType = "application/json", schema = @Schema(implementation = ErrorResponse.class), examples = @ExampleObject(name = "권한 없음", value = "{ \"code\": \"C002\", \"message\": \"접근 권한이 없습니다.\" }"))),
47+
@ApiResponse(responseCode = "404", description = "수정할 리소스를 찾을 수 없음", content = @Content(mediaType = "application/json", schema = @Schema(implementation = ErrorResponse.class), examples = @ExampleObject(name = "조회 결과 없음", value = "{ \"code\": \"C010\", \"message\": \"해당 리소스를 찾을 수 없습니다.\" }"))),
48+
@ApiResponse(responseCode = "500", description = "서버 내부 오류", content = @Content(mediaType = "application/json", schema = @Schema(implementation = ErrorResponse.class), examples = @ExampleObject(name = "서버 문제 발생", value = "{ \"code\": \"C009\", \"message\": \"서버에 문제가 발생하였습니다.\" }")))
49+
})
50+
@PutMapping("/{activityId}")
51+
public ResponseEntity<Void> updateActivity(
52+
@PathVariable @Min(1) Long activityId,
53+
@Valid @RequestBody ActivityRequestDto requestDto
54+
) {
55+
activityService.updateActivity(activityId, requestDto);
56+
return ResponseEntity.ok().build();
57+
}
58+
59+
@Operation(summary = "활동 연혁 삭제")
60+
@ApiResponses(value = {
61+
@ApiResponse(responseCode = "204", description = "활동 연혁 삭제 성공"),
62+
@ApiResponse(responseCode = "401", description = "인증되지 않은 사용자", content = @Content(mediaType = "application/json", schema = @Schema(implementation = ErrorResponse.class), examples = @ExampleObject(name = "인증 실패", value = "{ \"code\": \"C001\", \"message\": \"인증되지 않은 사용자입니다.\" }"))),
63+
@ApiResponse(responseCode = "403", description = "접근 권한 없음 (ADMIN이 아님)", content = @Content(mediaType = "application/json", schema = @Schema(implementation = ErrorResponse.class), examples = @ExampleObject(name = "권한 없음", value = "{ \"code\": \"C002\", \"message\": \"접근 권한이 없습니다.\" }"))),
64+
@ApiResponse(responseCode = "404", description = "삭제할 리소스를 찾을 수 없음", content = @Content(mediaType = "application/json", schema = @Schema(implementation = ErrorResponse.class), examples = @ExampleObject(name = "조회 결과 없음", value = "{ \"code\": \"C010\", \"message\": \"해당 리소스를 찾을 수 없습니다.\" }"))),
65+
@ApiResponse(responseCode = "500", description = "서버 내부 오류", content = @Content(mediaType = "application/json", schema = @Schema(implementation = ErrorResponse.class), examples = @ExampleObject(name = "서버 문제 발생", value = "{ \"code\": \"C009\", \"message\": \"서버에 문제가 발생하였습니다.\" }")))
66+
})
67+
@DeleteMapping("/{activityId}")
68+
public ResponseEntity<Void> deleteActivity(@PathVariable Long activityId) {
69+
activityService.deleteActivity(activityId);
70+
return ResponseEntity.noContent().build();
71+
}
72+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package dmu.dasom.api.domain.activity.dto;
2+
3+
import dmu.dasom.api.domain.activity.entity.Activity;
4+
import io.swagger.v3.oas.annotations.media.Schema;
5+
import lombok.Builder;
6+
import lombok.Getter;
7+
import java.time.format.DateTimeFormatter;
8+
9+
@Getter
10+
@Builder
11+
@Schema(name = "ActivityItemDto", description = "개별 활동 목록")
12+
public class ActivityItemDto {
13+
14+
@Schema(description = "활동 고유 ID", example = "1")
15+
private final Long id;
16+
17+
@Schema(description = "활동 날짜", example = "05.10")
18+
private final String monthDay; // 날짜 필드 추가
19+
20+
@Schema(description = "활동 제목", example = "컴퓨터 공학부 경진대회")
21+
private final String title;
22+
23+
@Schema(description = "수상 내역", example = "최우수상")
24+
private final String award;
25+
26+
public static ActivityItemDto of(Activity activity) {
27+
String formattedMonthDay = activity.getActivityDate()
28+
.format(DateTimeFormatter.ofPattern("MM.dd"));
29+
30+
return ActivityItemDto.builder()
31+
.id(activity.getId())
32+
.monthDay(formattedMonthDay)
33+
.title(activity.getTitle())
34+
.award(activity.getAward())
35+
.build();
36+
}
37+
}

0 commit comments

Comments
 (0)