Skip to content

Commit 999c525

Browse files
Merge pull request #144 from prgrms-web-devcourse-final-project/develop
chore: develop → main 브랜치 머지
2 parents a4b7a13 + 2ffba36 commit 999c525

File tree

56 files changed

+867
-201
lines changed

Some content is hidden

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

56 files changed

+867
-201
lines changed
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
package grep.neogul_coder.domain.admin.controller;
2+
3+
import grep.neogul_coder.domain.admin.controller.dto.response.AdminRecruitmentPostResponse;
4+
import grep.neogul_coder.domain.admin.controller.dto.response.AdminStudyResponse;
5+
import grep.neogul_coder.domain.admin.controller.dto.response.AdminUserResponse;
6+
import grep.neogul_coder.domain.admin.service.AdminService;
7+
import grep.neogul_coder.domain.study.enums.Category;
8+
import grep.neogul_coder.global.response.ApiResponse;
9+
import lombok.RequiredArgsConstructor;
10+
import org.springframework.data.domain.Page;
11+
import org.springframework.data.domain.PageRequest;
12+
import org.springframework.data.domain.Pageable;
13+
import org.springframework.security.access.prepost.PreAuthorize;
14+
import org.springframework.web.bind.annotation.DeleteMapping;
15+
import org.springframework.web.bind.annotation.GetMapping;
16+
import org.springframework.web.bind.annotation.PathVariable;
17+
import org.springframework.web.bind.annotation.RequestMapping;
18+
import org.springframework.web.bind.annotation.RequestParam;
19+
import org.springframework.web.bind.annotation.RestController;
20+
21+
@RestController
22+
@RequiredArgsConstructor
23+
@RequestMapping("/admin")
24+
@PreAuthorize("hasRole('ADMIN')")
25+
public class AdminController implements AdminSpecification {
26+
27+
private final AdminService adminService;
28+
29+
@GetMapping("/users")
30+
public ApiResponse<Page<AdminUserResponse>> getUsers(
31+
@RequestParam(defaultValue = "0") int page,
32+
@RequestParam(required = false) String email) {
33+
int size = 10;
34+
Pageable pageable = PageRequest.of(page, size);
35+
return ApiResponse.success(adminService.getAllUsers(pageable, email));
36+
}
37+
38+
@GetMapping("/studies")
39+
public ApiResponse<Page<AdminStudyResponse>> getStudies(
40+
@RequestParam(defaultValue = "0") int page,
41+
@RequestParam(required = false) String name,
42+
@RequestParam(required = false) Category category) {
43+
return ApiResponse.success(adminService.getAllStudies(page,name,category));
44+
}
45+
46+
@GetMapping("/recruitment-posts")
47+
public ApiResponse<Page<AdminRecruitmentPostResponse>> getRecruitmentPosts(
48+
@RequestParam(defaultValue = "0") int page,
49+
@RequestParam(required = false) String subject) {
50+
51+
return ApiResponse.success(adminService.getAllRecruitmentPosts(page, subject));
52+
}
53+
54+
@DeleteMapping("/users/{userId}")
55+
public ApiResponse<Void> deleteUser(@PathVariable Long userId) {
56+
adminService.deleteUser(userId);
57+
return ApiResponse.noContent();
58+
}
59+
60+
@DeleteMapping("/studies/{studyId}")
61+
public ApiResponse<Void> deleteStudy(@PathVariable Long studyId) {
62+
adminService.deleteStudy(studyId);
63+
return ApiResponse.noContent();
64+
}
65+
66+
@DeleteMapping("/recruitment-posts/{postId}")
67+
public ApiResponse<Void> deleteRecruitmentPost(@PathVariable Long postId) {
68+
adminService.deleteRecruitmentPost(postId);
69+
return ApiResponse.noContent();
70+
}
71+
72+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
package grep.neogul_coder.domain.admin.controller;
2+
3+
import grep.neogul_coder.domain.admin.controller.dto.response.AdminRecruitmentPostResponse;
4+
import grep.neogul_coder.domain.admin.controller.dto.response.AdminStudyResponse;
5+
import grep.neogul_coder.domain.admin.controller.dto.response.AdminUserResponse;
6+
import grep.neogul_coder.domain.study.enums.Category;
7+
import grep.neogul_coder.global.response.ApiResponse;
8+
import io.swagger.v3.oas.annotations.Operation;
9+
import io.swagger.v3.oas.annotations.Parameter;
10+
import io.swagger.v3.oas.annotations.tags.Tag;
11+
import org.springframework.data.domain.Page;
12+
import org.springframework.web.bind.annotation.DeleteMapping;
13+
import org.springframework.web.bind.annotation.GetMapping;
14+
import org.springframework.web.bind.annotation.PathVariable;
15+
import org.springframework.web.bind.annotation.RequestParam;
16+
17+
@Tag(name = "관리자 API", description = "관리자 기능 관련 API")
18+
public interface AdminSpecification {
19+
20+
@Operation(summary = "전체 사용자 조회", description = "관리자가 전체 유저 목록을 페이지 단위로 조회합니다.")
21+
@GetMapping("/admin/users")
22+
ApiResponse<Page<AdminUserResponse>> getUsers(
23+
@Parameter(description = "페이지 번호 (0부터 시작)", example = "0")
24+
@RequestParam(defaultValue = "0") int page, @RequestParam(required = false) String email
25+
);
26+
27+
@Operation(summary = "전체 스터디 조회", description = "관리자가 전체 스터디 목록을 페이지 단위로 조회합니다.")
28+
@GetMapping("/admin/studies")
29+
ApiResponse<Page<AdminStudyResponse>> getStudies(
30+
@Parameter(description = "페이지 번호 (0부터 시작)", example = "0")
31+
@RequestParam(defaultValue = "0") int page,
32+
@RequestParam(required = false) String name,
33+
@RequestParam(required = false) Category category
34+
);
35+
36+
@Operation(summary = "전체 모집글 조회", description = "관리자가 전체 모집글 목록을 페이지 단위로 조회합니다.")
37+
@GetMapping("/admin/recruitment-posts")
38+
ApiResponse<Page<AdminRecruitmentPostResponse>> getRecruitmentPosts(
39+
@Parameter(description = "페이지 번호 (0부터 시작)", example = "0")
40+
@RequestParam(defaultValue = "0") int page,
41+
@RequestParam(required = false) String subject
42+
);
43+
44+
@Operation(summary = "사용자 강제 탈퇴", description = "관리자가 특정 사용자를 탈퇴(삭제) 처리합니다.")
45+
@DeleteMapping("/admin/users/{userId}")
46+
ApiResponse<Void> deleteUser(
47+
@Parameter(description = "삭제할 사용자 ID", example = "1")
48+
@PathVariable Long userId
49+
);
50+
51+
@Operation(summary = "스터디 비활성화", description = "관리자가 특정 스터디를 비활성화(삭제) 처리합니다.")
52+
@DeleteMapping("/admin/studies/{studyId}")
53+
ApiResponse<Void> deleteStudy(
54+
@Parameter(description = "삭제할 스터디 ID", example = "1")
55+
@PathVariable Long studyId
56+
);
57+
58+
@Operation(summary = "모집글 삭제", description = "관리자가 특정 모집글을 삭제 처리합니다.")
59+
@DeleteMapping("/admin/recruitment-posts/{postId}")
60+
ApiResponse<Void> deleteRecruitmentPost(
61+
@Parameter(description = "삭제할 모집글 ID", example = "1")
62+
@PathVariable Long postId
63+
);
64+
65+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package grep.neogul_coder.domain.admin.controller.dto.response;
2+
3+
import grep.neogul_coder.domain.recruitment.post.RecruitmentPost;
4+
import io.swagger.v3.oas.annotations.media.Schema;
5+
import java.time.LocalDate;
6+
import java.time.LocalDateTime;
7+
import lombok.Builder;
8+
import lombok.Data;
9+
10+
@Data
11+
@Schema(description = "관리자용 모집글 응답 DTO")
12+
public class AdminRecruitmentPostResponse {
13+
14+
@Schema(description = "모집글 ID", example = "123")
15+
private Long id;
16+
17+
@Schema(description = "모집글 제목", example = "자바 스터디 모집")
18+
private String subject;
19+
20+
@Schema(description = "모집 마감일", example = "2025-08-01")
21+
private LocalDateTime expiredDate;
22+
23+
@Schema(description = "활성화 여부", example = "true")
24+
private boolean activated;
25+
26+
public static AdminRecruitmentPostResponse from(RecruitmentPost post) {
27+
return AdminRecruitmentPostResponse
28+
.builder()
29+
.id(post.getId())
30+
.subject(post.getSubject())
31+
.expiredDate(post.getExpiredDate())
32+
.activated(post.getActivated())
33+
.build();
34+
}
35+
36+
@Builder
37+
private AdminRecruitmentPostResponse(Long id, String subject, LocalDateTime expiredDate, boolean activated) {
38+
this.id = id;
39+
this.subject = subject;
40+
this.expiredDate = expiredDate;
41+
this.activated = activated;
42+
}
43+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package grep.neogul_coder.domain.admin.controller.dto.response;
2+
3+
import grep.neogul_coder.domain.study.Study;
4+
import grep.neogul_coder.domain.study.enums.Category;
5+
import io.swagger.v3.oas.annotations.media.Schema;
6+
import lombok.Builder;
7+
import lombok.Data;
8+
9+
@Data
10+
@Schema(description = "관리자용 스터디 응답 DTO")
11+
public class AdminStudyResponse {
12+
13+
@Schema(description = "스터디 ID", example = "101")
14+
private Long id;
15+
16+
@Schema(description = "스터디 이름", example = "Spring Boot 스터디")
17+
private String name;
18+
19+
@Schema(description = "스터디 카테고리", example = "BACKEND")
20+
private Category category;
21+
22+
@Schema(description = "스터디 종료 여부", example = "false")
23+
private boolean isFinished;
24+
25+
@Schema(description = "활성화 여부", example = "true")
26+
private boolean activated;
27+
28+
@Builder
29+
private AdminStudyResponse(Long id, String name, Category category, boolean isFinished,
30+
boolean activated) {
31+
this.id = id;
32+
this.name = name;
33+
this.category = category;
34+
this.isFinished = isFinished;
35+
this.activated = activated;
36+
}
37+
38+
public static AdminStudyResponse from(Study study) {
39+
return AdminStudyResponse.builder()
40+
.id(study.getId())
41+
.name(study.getName())
42+
.category(study.getCategory())
43+
.isFinished(study.isFinished())
44+
.activated(study.getActivated())
45+
.build();
46+
}
47+
48+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package grep.neogul_coder.domain.admin.controller.dto.response;
2+
3+
import grep.neogul_coder.domain.users.entity.User;
4+
import io.swagger.v3.oas.annotations.media.Schema;
5+
import lombok.Builder;
6+
import lombok.Data;
7+
8+
@Data
9+
@Schema(description = "관리자용 사용자 응답 DTO")
10+
public class AdminUserResponse {
11+
12+
@Schema(description = "사용자 ID", example = "10")
13+
private Long id;
14+
15+
@Schema(description = "이메일 주소", example = "[email protected]")
16+
private String email;
17+
18+
@Schema(description = "사용자 닉네임", example = "관리자")
19+
private String nickname;
20+
21+
@Schema(description = "활성화 여부", example = "true")
22+
private Boolean activated;
23+
24+
@Builder
25+
private AdminUserResponse(Long id, String email, String nickname, Boolean activated) {
26+
this.id = id;
27+
this.email = email;
28+
this.nickname = nickname;
29+
this.activated = activated;
30+
}
31+
32+
public static AdminUserResponse from(User user) {
33+
return new AdminUserResponse(
34+
user.getId(),
35+
user.getEmail(),
36+
user.getNickname(),
37+
user.getActivated()
38+
);
39+
}
40+
41+
}
42+
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
package grep.neogul_coder.domain.admin.service;
2+
3+
import grep.neogul_coder.domain.admin.controller.dto.response.AdminRecruitmentPostResponse;
4+
import grep.neogul_coder.domain.admin.controller.dto.response.AdminStudyResponse;
5+
import grep.neogul_coder.domain.admin.controller.dto.response.AdminUserResponse;
6+
import grep.neogul_coder.domain.recruitment.post.repository.RecruitmentPostRepository;
7+
import grep.neogul_coder.domain.study.Study;
8+
import grep.neogul_coder.domain.study.enums.Category;
9+
import grep.neogul_coder.domain.study.repository.StudyQueryRepository;
10+
import grep.neogul_coder.domain.study.repository.StudyRepository;
11+
import grep.neogul_coder.domain.users.repository.UserRepository;
12+
import grep.neogul_coder.domain.users.service.UserService;
13+
import grep.neogul_coder.global.exception.business.NotFoundException;
14+
import lombok.RequiredArgsConstructor;
15+
import org.springframework.data.domain.Page;
16+
import org.springframework.data.domain.PageRequest;
17+
import org.springframework.data.domain.Pageable;
18+
import org.springframework.stereotype.Service;
19+
import org.springframework.transaction.annotation.Transactional;
20+
21+
import static grep.neogul_coder.domain.study.exception.code.StudyErrorCode.STUDY_NOT_FOUND;
22+
23+
@Service
24+
@RequiredArgsConstructor
25+
public class AdminService {
26+
27+
private final UserRepository userRepository;
28+
private final UserService userService;
29+
private final StudyQueryRepository studyQueryRepository;
30+
private final StudyRepository studyRepository;
31+
private final RecruitmentPostRepository recruitmentPostRepository;
32+
33+
@Transactional(readOnly = true)
34+
public Page<AdminUserResponse> getAllUsers(Pageable pageable, String email) {
35+
if (isContainEmail(email)) {
36+
return userRepository.findByEmailContainingIgnoreCase(email, pageable)
37+
.map(AdminUserResponse::from);
38+
}
39+
return userRepository.findAll(pageable)
40+
.map(AdminUserResponse::from);
41+
}
42+
43+
@Transactional(readOnly = true)
44+
public Page<AdminStudyResponse> getAllStudies(int page, String name, Category category) {
45+
Pageable pageable = PageRequest.of(page, 10);
46+
return studyQueryRepository.adminSearchStudy(name, category, pageable)
47+
.map(AdminStudyResponse::from);
48+
}
49+
50+
@Transactional(readOnly = true)
51+
public Page<AdminRecruitmentPostResponse> getAllRecruitmentPosts(int page, String subject) {
52+
Pageable pageable = PageRequest.of(page, 10);
53+
if (isContainSubject(subject)) {
54+
return recruitmentPostRepository.findBySubjectContainingIgnoreCase(subject, pageable)
55+
.map(AdminRecruitmentPostResponse::from);
56+
}
57+
return recruitmentPostRepository.findAll(pageable)
58+
.map(AdminRecruitmentPostResponse::from);
59+
}
60+
61+
@Transactional
62+
public void deleteUser(Long userId) {
63+
userService.deleteUser(userId);
64+
}
65+
66+
@Transactional
67+
public void deleteStudy(Long studyId) {
68+
Study study = studyRepository.findById(studyId)
69+
.orElseThrow(() -> new NotFoundException(STUDY_NOT_FOUND));
70+
study.delete();
71+
}
72+
73+
@Transactional
74+
public void deleteRecruitmentPost(Long recruitmentPostId) {
75+
recruitmentPostRepository.deleteById(recruitmentPostId);
76+
}
77+
78+
private Boolean isContainEmail(String email) {
79+
return email != null && !email.isEmpty();
80+
}
81+
82+
private Boolean isContainSubject(String subject) {
83+
return subject != null && !subject.isEmpty();
84+
}
85+
86+
}

src/main/java/grep/neogul_coder/domain/calender/repository/PersonalCalendarQueryRepository.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ public List<PersonalCalendar> findByUserIdAndDate(Long userId, LocalDate date) {
3737
.join(pc.calendar, calendar).fetchJoin()
3838
.where(
3939
pc.userId.eq(userId),
40+
pc.activated.eq(true),
4041
calendar.scheduledStart.loe(end),
4142
calendar.scheduledEnd.goe(start)
4243
)

src/main/java/grep/neogul_coder/domain/calender/repository/TeamCalendarQueryRepository.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ public List<TeamCalendar> findByStudyIdAndDate(Long studyId, LocalDate date) {
3636
.join(tc.calendar, calendar).fetchJoin()
3737
.where(
3838
tc.studyId.eq(studyId),
39+
tc.activated.eq(true),
3940
calendar.scheduledStart.loe(end),
4041
calendar.scheduledEnd.goe(start)
4142
)

src/main/java/grep/neogul_coder/domain/main/controller/MainController.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
import grep.neogul_coder.domain.main.controller.dto.response.MainResponse;
44
import grep.neogul_coder.domain.recruitment.post.controller.dto.response.RecruitmentPostPagingInfo;
55
import grep.neogul_coder.domain.recruitment.post.service.RecruitmentPostService;
6-
import grep.neogul_coder.domain.study.controller.StudySpecification;
76
import grep.neogul_coder.domain.study.controller.dto.response.StudyItemResponse;
87
import grep.neogul_coder.domain.study.service.StudyService;
98
import grep.neogul_coder.global.auth.Principal;
@@ -28,11 +27,11 @@ public class MainController implements MainSpecification {
2827

2928
@GetMapping
3029
public ApiResponse<MainResponse> getMain(@PageableDefault(size = 10) Pageable pageable,
31-
@AuthenticationPrincipal Principal userDetails) {
30+
@AuthenticationPrincipal Principal userDetails) {
3231
Long userId = userDetails.getUserId();
3332

3433
List<StudyItemResponse> myStudies = studyService.getMyStudies(userId);
35-
RecruitmentPostPagingInfo recruitingStudies = recruitmentPostService.getPagingInfo(pageable);
34+
RecruitmentPostPagingInfo recruitingStudies = recruitmentPostService.getPagingInfo(pageable, null);
3635
MainResponse response = MainResponse.from(myStudies, recruitingStudies);
3736

3837
return ApiResponse.success(response);

0 commit comments

Comments
 (0)