Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
fc276d2
feat : 명부 전체 조회 service, dto, repository 추가 완료 및 임시저장
marianddi May 23, 2026
e30feea
Merge remote-tracking branch 'origin/develop' into web-36
marianddi May 23, 2026
1f926c2
feat : 관리자 전용 명부 전체 조회 API 구현
marianddi May 23, 2026
6c5ad0d
feat : 관리자 부원 등록 API 구현
marianddi May 23, 2026
1dbf2d8
feat : 관리자 부원 상세 조회 api 구현
marianddi May 23, 2026
09eda7b
feat : 관리자 부원 상세 조회 api 구현 중 (request dto추가)
marianddi May 25, 2026
e2c74f5
fix : develop과 머지 후 컨플릭트 해결
marianddi May 25, 2026
fe0ad2a
fix : Member entity형태 반영하여 수정
marianddi May 25, 2026
24da152
fix : develop 최신 코드 반영 및 컨플릭트 해결
marianddi May 25, 2026
93f63c3
Merge branch 'feature/web-37-1' into feature/web-37
marianddi May 25, 2026
dcbfe30
docs : api설명 주석 등을 정확한 설명으로 수정
marianddi May 25, 2026
78b8847
Merge branch 'develop' into feature/web-37-1
marianddi May 25, 2026
037c276
Merge branch 'develop' into feature/web-36
marianddi May 25, 2026
2d6f485
Merge branch 'develop' into feature/web-37
marianddi May 25, 2026
5517fb1
Merge branch 'feature/web-37-1' into feature/web-37-2
marianddi May 25, 2026
acd9b94
feat : 관리자 부원 정보 수정 api 구현
marianddi May 25, 2026
463e40f
feat : 관리자 부원 삭제 api 구현
marianddi May 26, 2026
1bb5cf2
feat : 관리자 신청 부원 조회 api 구현
marianddi May 27, 2026
32ed707
refactor : api 경로명 수정
marianddi May 27, 2026
56e8eba
feat : 관리자 신청 부원 상세 조회 api구현
marianddi May 28, 2026
fbac18c
feat : 관리자 신청 정보 불러오기 api구현
marianddi May 28, 2026
cebeab1
refator : 주석 설명 수정 및 메서드명 변경
marianddi May 28, 2026
a514c8a
docs : 주석 위치 변경
marianddi May 28, 2026
f482458
refactor : MemberListResponseDto 스웨거 추가 및 memberId필드 추가, 이 외 주석 수정
marianddi May 28, 2026
316cc80
refactor : MemberController 누락된 Valid어노테이션 추가
marianddi May 28, 2026
8988833
fix : develop pull받은 후 컨플릭트 해결
marianddi May 30, 2026
c8d46de
refactor : 명부 전체 조회 api 리뷰 반영 및 구조 정리
marianddi May 30, 2026
6627c57
fix : 최신 코드 반영(web-36 pull) 후 컨플릭트 해결
marianddi May 30, 2026
da91a0a
refactor : 리뷰 반영 및 구조 수정
marianddi May 30, 2026
fef5591
refactor : 최신 코드 반영(web-37-1 pull) 후 컨플릭트 해결
marianddi May 30, 2026
2884093
refactor : 구조 변경 임시저장
marianddi May 31, 2026
b83bafb
fix : 최신코드(web-37 pull) 반영 및 컨플릭트 해결
marianddi May 31, 2026
98877a8
refactor : 구조 변경 및 리뷰 반영
marianddi May 31, 2026
d4dbb39
docs : ApiErrorEceptions어노테이션에 에러 추가
marianddi May 31, 2026
4718f63
Merge branch 'feature/web-37-1' of https://github.com/DongyangOne/web…
marianddi May 31, 2026
aa918de
refactor : pagination > dto 필드 수정 및 리뷰 반영
marianddi May 31, 2026
19c28d9
refactor : web-36과 병합 후 컨플릭트 해결
marianddi May 31, 2026
da36d70
refactor : 리뷰 반영하여 수정
marianddi May 31, 2026
8dad793
fix : web-37-1 pull 받아 병합 후 컨플릭트 해결
marianddi May 31, 2026
6e552bb
refactor : 리뷰 반영 하여 수정
marianddi May 31, 2026
ed407b5
fix : feature/web-37 pull 받아 병합 후 컨플릭트 해결
marianddi May 31, 2026
f81bacf
refactor : 구조 수정 및 리뷰 반영
marianddi May 31, 2026
a1ef850
fix : feature/web-37-2 pull 병합 후 컨플릭트 해결
marianddi May 31, 2026
0721d05
refactor : 구조 변경 및 리뷰 반영
marianddi May 31, 2026
cab6d18
refactor : feature/web-38-2 pull 병합 후 컨플릭트 해결
marianddi May 31, 2026
9233027
refactor : 구조 수정 및 리뷰 반영
marianddi May 31, 2026
e8035c8
fix : feature/web-39 pull 병합 후 컨플릭트 해결
marianddi May 31, 2026
3b184a0
refactor : 구조 수정 및 리뷰 반영
marianddi May 31, 2026
68f0288
fix : feature/web-40 pull 병합 후 컨플릭트 해결
marianddi May 31, 2026
dbbfd34
refactor : 구조 변경 및 리뷰 반영
marianddi May 31, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ dependencies {
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
testRuntimeOnly 'com.h2database:h2'
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
}

tasks.named('test') {
Expand Down
6 changes: 5 additions & 1 deletion src/main/java/org/one/global/enums/ErrorCode.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,11 @@ public enum ErrorCode {
INVALID_DATE_RANGE(HttpStatus.BAD_REQUEST, "INVALID_DATE_RANGE", "날짜 범위가 올바르지 않습니다."),
PHOTO_LIMIT_EXCEEDED(HttpStatus.BAD_REQUEST, "PHOTO_LIMIT_EXCEEDED", "사진은 최대 3장까지 업로드할 수 있습니다."),
INVALID_OBJECT_KEY(HttpStatus.BAD_REQUEST, "INVALID_OBJECT_KEY", "유효하지 않은 파일 경로입니다."),
INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "INTERNAL_SERVER_ERROR", "서버 오류가 발생했습니다.");
INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "INTERNAL_SERVER_ERROR", "서버 오류가 발생했습니다."),
DUPLICATE_STUDENT_ID(HttpStatus.CONFLICT, "DUPLICATE_STUDENT_ID", "이미 존재하는 학번입니다."),
DUPLICATE_PHONE_NUMBER(HttpStatus.CONFLICT, "DUPLICATE_PHONE_NUMBER", "이미 존재하는 전화번호입니다."),
MEMBER_NOT_FOUND(HttpStatus.NOT_FOUND, "MEMBER_NOT_FOUND", "존재하지 않는 부원입니다.");


private final HttpStatus status;
private final String code;
Expand Down
28 changes: 28 additions & 0 deletions src/main/java/org/one/global/pagination/RequestPagingDto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package org.one.global.pagination;

import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;

@Setter
@Getter
@NoArgsConstructor
@AllArgsConstructor
public class RequestPagingDto {
private Integer page = 0;
private Integer size = 10;
private String sort = "id";
private String direction = "DESC";

public Pageable toPageable() {
Sort.Direction dir = Sort.Direction.fromString(direction);
return PageRequest.of(page, size, Sort.by(dir, sort));
}
}
30 changes: 30 additions & 0 deletions src/main/java/org/one/global/pagination/ResponsePagingDto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package org.one.global.pagination;

import lombok.Builder;
import lombok.Getter;
import org.springframework.data.domain.Page;

import java.util.List;

@Getter
@Builder
public class ResponsePagingDto<T> {

private List<T> content;
private Integer page;
private Integer size;
private Long totalElements;
private Integer totalPages;
private Boolean last;

public static <T> ResponsePagingDto<T> from(Page<T> page) {
return ResponsePagingDto.<T>builder()
.content(page.getContent())
.page(page.getNumber())
.size(page.getSize())
.totalElements(page.getTotalElements())
.totalPages(page.getTotalPages())
.last(page.isLast())
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package org.one.member.controller;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.one.global.annotation.ApiErrorExceptions;
import org.one.global.dto.ApiResponse;
import org.one.global.enums.ErrorCode;
import org.one.member.dto.ApplicantInfoResponseDto;
import org.one.member.dto.ApplicantMemberDetailResponseDto;
import org.one.member.dto.ApplicantMemberListRequestDto;
import org.one.member.dto.ApplicantMemberListResponseDto;
import org.one.member.service.ApplicantMemberService;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@Tag(name = "ApplicantMember", description = "신청 부원 관리 (관리자 전용)")
@RestController
@RequiredArgsConstructor
@RequestMapping("/api/v1/applicantMembers")
public class ApplicantMemberController {
private final ApplicantMemberService applicantMemberService;


/**
* 신청 부원 조회 api
*
* api 요청 예시 : GET /api/v1/applicantMembers
*
* 응답 데이터 : 신청 부원 리스트
*/
@ApiErrorExceptions({ErrorCode.INVALID_INPUT})
@Operation(summary = "신청 부원 조회", description = "관리자 권한(ADMIN)이 있는 계정만 신청 부원을 조회할 수 있습니다.")
@PreAuthorize("hasRole('ADMIN')")
@GetMapping
public ResponseEntity<ApiResponse<List<ApplicantMemberListResponseDto>>> getApplicantMemberList(@ModelAttribute @Valid ApplicantMemberListRequestDto requestDto){
List<ApplicantMemberListResponseDto> response = applicantMemberService.getApplicantList(requestDto);
return ResponseEntity.ok(ApiResponse.success(response));
}

/**
* 신청 부원 상세 정보 조회 API : 신청 부원의 상세 정보를 조회하기 위한 api
* 요청 시, applicantMemberId를 @PathVariable로 url을 통해 전달
*
* api 요청 예시 : GET /api/v1/applicantMembers/{applicantMemberId}
*
* 응답 데이터 : 특정 신청 부원에 대한 상세 정보
*/
@ApiErrorExceptions({ErrorCode.INVALID_INPUT, ErrorCode.MEMBER_NOT_FOUND})
@Operation(summary = "신청 부원 상세 정보 조회", description = "관리자 권한(ADMIN)이 있는 계정만 신청 부원의 상세 정보를 조회할 수 있습니다.")
@PreAuthorize("hasRole('ADMIN')")
@GetMapping("/{applicantMemberId}")
public ResponseEntity<ApiResponse<ApplicantMemberDetailResponseDto>> getApplicantMemberDetail(@PathVariable Long applicantMemberId){
ApplicantMemberDetailResponseDto responseDto= applicantMemberService.getApplicantMemberDetail(applicantMemberId);
return ResponseEntity.ok(ApiResponse.success(responseDto));
}

/**
* 부원(Member) 등록 시 신청 정보 불러오기 API : 신청 부원의 정보를 불러오기 위한 api
* 요청 시, applicantMemberId를 @PathVariable로 url을 통해 전달
*
* api 요청 예시 : GET /api/v1/applicantMembers/{applicantMemberId}/registration-form
*
* 응답 데이터 : 특정 신청 부원의 등록 시 사용할 정보
*/
@ApiErrorExceptions({ErrorCode.INVALID_INPUT, ErrorCode.MEMBER_NOT_FOUND})
@Operation(summary = "신청 정보 불러오기", description = "관리자 권한(ADMIN)이 있는 계정만 신청 정보를 불러올 수 있습니다.")
@PreAuthorize("hasRole('ADMIN')")
@GetMapping("/{applicantMemberId}/registration-form")
public ResponseEntity<ApiResponse<ApplicantInfoResponseDto>> getApplicantInfo(@PathVariable Long applicantMemberId){
ApplicantInfoResponseDto responseDto = applicantMemberService.getApplicantInfo(applicantMemberId);
return ResponseEntity.ok(ApiResponse.success(responseDto));
}

}
132 changes: 132 additions & 0 deletions src/main/java/org/one/member/controller/MemberController.java
Original file line number Diff line number Diff line change
@@ -1,4 +1,136 @@
package org.one.member.controller;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.one.global.annotation.ApiErrorExceptions;
import org.one.global.dto.ApiResponse;
import org.one.global.enums.ErrorCode;
import org.one.member.dto.*;
import org.one.member.service.MemberService;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@Tag(name = "Member", description = "부원 명부 관리 (관리자 전용)")
@RestController
@RequiredArgsConstructor
@RequestMapping("/api/v1/members")
public class MemberController {
private final MemberService memberService;

/**
* 명부 전체 조회 API
* 요청 시, 선택적으로 page관련 설정(정렬 등)
*
* api 요청 예시 : GET /api/v1/members?page=1&size=10&sort=...
*
* 응답 데이터 : 전체 member의 명부리스트
*/
@ApiErrorExceptions({ErrorCode.INVALID_INPUT})
@Operation(summary = "명부 전체 조회", description = "관리자 권한(ADMIN)이 있는 계정만 전체 부원 명부를 조회할 수 있습니다.")
@PreAuthorize("hasRole('ADMIN')")
@GetMapping
public ResponseEntity<ApiResponse<List<MemberListResponseDto>>> getMemberList(
@ModelAttribute @Valid MemberListRequestDto requestDto){

List<MemberListResponseDto> response = memberService.getMemberListByAdmin(requestDto);

return ResponseEntity.ok(ApiResponse.success(response));
}

/**
* 부원 등록 API
* 요청 시, requestBody를 이용 MemberRegisterRequestDto 필드 입력
*
* api 요청 예시 : POST /api/v1/members
*
* requestBody 예시
* "name" : "aa",
* "grade" : 1,
* "studentId" : "20991111",
* "age" : 22,
* "phoneNum" : "010-1111-2222"
*
* 응답 데이터 : x
*/
@ApiErrorExceptions({ErrorCode.DUPLICATE_PHONE_NUMBER, ErrorCode.DUPLICATE_STUDENT_ID, ErrorCode.INVALID_INPUT})
@Operation(summary = "부원 등록", description = "관리자 권한(ADMIN)이 있는 계정만 전체 부원 명부를 조회할 수 있습니다.")
@PreAuthorize("hasRole('ADMIN')")
@PostMapping
public ResponseEntity<ApiResponse<Void>> registerMember(@RequestBody @Valid MemberRegisterRequestDto requestDto){
//등록 정보를 service로 넘겨 부원 등록 진행
memberService.registerMember(requestDto);

//오류없이 넘어왔을 경우 성공 처리
return ResponseEntity.ok(ApiResponse.success(null));
}

/**
* 특정 부원 정보 조회 API : 부원 수정 시 정보를 불러오기 위한 api
* 요청 시, memberId를 @PathVariable로 url을 통해 전달
*
* api 요청 예시 : GET /api/members/{memberId}
*
* 응답 데이터 : 특정 부원에 대한 정보
*/
@ApiErrorExceptions({ErrorCode.MEMBER_NOT_FOUND, ErrorCode.INVALID_INPUT})
@Operation(summary = "부원 정보 가져오기", description = "관리자 권한(ADMIN)이 있는 계정만 부원 상세 정보를 조회할 수 있습니다.")
@PreAuthorize("hasRole('ADMIN')")
@GetMapping("/{memberId}")
public ResponseEntity<ApiResponse<MemberDetailResponseDto>> getMemberDetail(@PathVariable Long memberId){
MemberDetailResponseDto responseDto= memberService.getMemberDetail(memberId);
return ResponseEntity.ok(ApiResponse.success(responseDto));
}

/**
* 부원 정보 수정 api
* 요청 시,
* @PathVariable와 @requestBody를 통해 memberId와 MemberUpdateReqeustDto전달
*
* api 요청 예시 : PATCH /api/members/{memberId}
*
* 응답 데이터 : 수정 완료 메시지
*/
@ApiErrorExceptions({ErrorCode.MEMBER_NOT_FOUND, ErrorCode.INVALID_INPUT, ErrorCode.DUPLICATE_PHONE_NUMBER, ErrorCode.DUPLICATE_STUDENT_ID})
@Operation(summary = "부원 수정", description = "관리자 권한(ADMIN)이 있는 계정만 특정 부원의 정보를 일부 수정할 수 있습니다.")
@PreAuthorize("hasRole('ADMIN')")
@PatchMapping("/{memberId}")
public ResponseEntity<ApiResponse<Void>> updateMember(
@PathVariable Long memberId,
@Valid @RequestBody MemberUpdateRequestDto requestDto)
{
memberService.updateMember(memberId, requestDto);

return ResponseEntity.ok(null);
}

/**
* 부원 삭제 api
* 요청 시,
* @requestBody를 통해 memberId리스트를 전달
*
* api 요청 예시 : Delete /api/members
*
* requestBody 예시
* "memberIds" : [2, 7]
*
* 응답 데이터 : x
*/
@ApiErrorExceptions({ErrorCode.MEMBER_NOT_FOUND, ErrorCode.INVALID_INPUT})
@Operation(summary = "부원 삭제", description = "관리자 권한(ADMIN)이 있는 계정만 부원을 삭제할 수 있습니다.")
@PreAuthorize("hasRole('ADMIN')")
@DeleteMapping
public ResponseEntity<ApiResponse<Void>> deleteMembers(@RequestBody @Valid MemberDeleteListRequestDto requestDto){
memberService.deleteMembers(requestDto.getMemberIds());
return ResponseEntity.ok(ApiResponse.success(null));
}

}
9 changes: 8 additions & 1 deletion src/main/java/org/one/member/domain/Member.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,18 @@
import jakarta.persistence.Table;
import java.time.LocalDateTime;
import org.one.member.enums.MemberStatus;

import lombok.AllArgsConstructor;
import lombok.Builder;
import org.one.global.entity.BaseEntity;

/**
* 정규 부원의 기본 정보, 상태, 학년 승급 기준 시각을 저장하는 엔티티입니다.
*/
@Entity
@Table(name = "member")
@Builder
@AllArgsConstructor
public class Member extends BaseEntity {

@Id
Expand Down Expand Up @@ -144,12 +149,14 @@ public Member(MemberStatus status, String name, String studentId,
* 부원 기본 정보를 수정합니다.
*
* @param name 이름
* @param studentId 학번
* @param phoneNumber 연락처
* @param grade 학년
* @param age 나이
*/
public void updateInfo(String name, String phoneNumber, Integer grade, Integer age) {
public void updateInfo(String name,String studentId, String phoneNumber, Integer grade, Integer age) {
this.name = name;
this.studentId = studentId;
this.phoneNumber = phoneNumber;
this.grade = grade;
this.age = age;
Expand Down
44 changes: 44 additions & 0 deletions src/main/java/org/one/member/dto/ApplicantInfoResponseDto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package org.one.member.dto;

import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import org.one.member.domain.ApplicantMember;

import java.time.LocalDate;

@JsonPropertyOrder({"applicantId", "name","age", "studentId", "grade", "phoneNumber"})
@Schema(description = "신청 정보 불러오기 응답")
@Getter
public class ApplicantInfoResponseDto {
@Schema(description = "신청 부원 id", example = "3")
private Long applicantId;

@Schema(description = "신청 부원 이름", example = "홍길동")
private String name;

@Schema(description = "신청 부원 학번", example = "20991234")
private String studentId;

@Schema(description = "신청 부원 나이", example = "21")
private Integer age;

@Schema(description = "신청 부원 학년", example = "3")
private Integer grade;

@Schema(description = "신청 부원 전화번호", example = "010-1111-2222")
private String phoneNumber;

private ApplicantInfoResponseDto(ApplicantMember applicantMember){
this.applicantId = applicantMember.getApplicantId();
this.name = applicantMember.getName();
this.age = LocalDate.now().getYear() - applicantMember.getBirthday().getYear()+1; //한국식 나이 적용
this.studentId = applicantMember.getStudentId();
this.grade = applicantMember.getGrade();
this.phoneNumber = applicantMember.getPhoneNumber();
}

public static ApplicantInfoResponseDto from(ApplicantMember applicantMember){
return new ApplicantInfoResponseDto(applicantMember);
}
}
Loading