diff --git a/build.gradle b/build.gradle index 64e1acd..30ef1a3 100644 --- a/build.gradle +++ b/build.gradle @@ -22,12 +22,15 @@ repositories { } dependencies { + implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' implementation 'org.springframework.boot:spring-boot-starter-security' - implementation 'org.springframework.boot:spring-boot-starter-data-jpa' + implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.0.4' implementation 'com.mysql:mysql-connector-j' implementation 'io.jsonwebtoken:jjwt:0.9.1' compileOnly 'org.projectlombok:lombok' annotationProcessor 'org.projectlombok:lombok' + testImplementation 'org.springframework.boot:spring-boot-starter-test' + developmentOnly 'org.springframework.boot:spring-boot-devtools' } diff --git a/src/main/java/com/powersupply/PES/answer/controller/AnswerController.java b/src/main/java/com/powersupply/PES/answer/controller/AnswerController.java new file mode 100644 index 0000000..90c6307 --- /dev/null +++ b/src/main/java/com/powersupply/PES/answer/controller/AnswerController.java @@ -0,0 +1,75 @@ +package com.powersupply.PES.answer.controller; + +import com.powersupply.PES.answer.dto.AnswerDTO; +import com.powersupply.PES.answer.service.AnswerService; +import com.powersupply.PES.utils.ResponseUtil; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequiredArgsConstructor +@Tag(name = "Answer", description = "답변 관련 API") +public class AnswerController { + + private final AnswerService answerService; + + @Operation(summary = "답변 생성", description = "사용자가 답변을 생성합니다.") + @ApiResponses({ + @ApiResponse(responseCode = "201", description = "답변 생성 성공"), + @ApiResponse(responseCode = "400", description = "잘못된 요청"), + @ApiResponse(responseCode = "500", description = "서버 오류") + }) + @PostMapping("/api/answer") + public ResponseEntity createAnswer( + @Parameter(description = "회원 이메일", example = "user@example.com") @RequestParam("memberEmail") String email, + @Parameter(description = "문제 ID", example = "1") @RequestParam("problemId") Long problemId) { + return ResponseEntity.status(HttpStatus.CREATED).body(answerService.createAnswer(email, problemId)); + } + + @Operation(summary = "질문 및 답변 조회", description = "특정 답변 ID에 대한 질문과 답변을 조회합니다.") + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "조회 성공(토큰에 문제가 없고 유저를 DB에서 찾을 수 있는 경우)"), + @ApiResponse(responseCode = "404", description = "해당 answerId를 찾을 수 없는 경우"), + @ApiResponse(responseCode = "500", description = "서버 오류") + }) + @GetMapping("/api/answer/{answerId}") + public ResponseEntity getAnswer( + @Parameter(description = "답변 ID", example = "1") @PathVariable Long answerId) { + return ResponseEntity.ok().body(answerService.getAnswer(answerId)); + } + + @Operation(summary = "답변하기", description = "사용자가 특정 답변 ID에 대해 답변을 작성합니다.") + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "답변 작성 성공"), + @ApiResponse(responseCode = "400", description = "answer 내용이 null인 경우 or 이미 댓글이 있어 수정이 불가능 한 경우"), + @ApiResponse(responseCode = "404", description = "answer의 주인과 토큰이 다른 경우"), + @ApiResponse(responseCode = "500", description = "서버 오류") + }) + @PostMapping("/api/answer/{answerId}") + public ResponseEntity postAnswer( + @Parameter(description = "답변 ID", example = "1") @PathVariable Long answerId, + @RequestBody AnswerDTO.AnswerContent dto) { + answerService.postAnswer(answerId, dto); + return ResponseUtil.successResponse(""); + } + + @Operation(summary = "풀이 보기", description = "특정 문제 ID에 대한 풀이 목록을 조회합니다.") + @ApiResponses({ + @ApiResponse(responseCode = "201", description = "토큰에 문제가 없고 유저를 DB에서 찾을 수 있는 경우"), + @ApiResponse(responseCode = "204", description = "아직 푼 사람이 없는 경우"), + @ApiResponse(responseCode = "404", description = "problemId가 잘못된 경우"), + @ApiResponse(responseCode = "500", description = "서버 오류") + }) + @GetMapping("/api/answerlist/{problemId}") + public ResponseEntity getAnswerList( + @Parameter(description = "문제 ID", example = "1") @PathVariable Long problemId) { + return answerService.getAnswerList(problemId); + } +} \ No newline at end of file diff --git a/src/main/java/com/powersupply/PES/domain/dto/AnswerDTO.java b/src/main/java/com/powersupply/PES/answer/dto/AnswerDTO.java similarity index 95% rename from src/main/java/com/powersupply/PES/domain/dto/AnswerDTO.java rename to src/main/java/com/powersupply/PES/answer/dto/AnswerDTO.java index f6f27b9..df2a4d0 100644 --- a/src/main/java/com/powersupply/PES/domain/dto/AnswerDTO.java +++ b/src/main/java/com/powersupply/PES/answer/dto/AnswerDTO.java @@ -1,4 +1,4 @@ -package com.powersupply.PES.domain.dto; +package com.powersupply.PES.answer.dto; import lombok.Builder; import lombok.Getter; diff --git a/src/main/java/com/powersupply/PES/domain/entity/AnswerEntity.java b/src/main/java/com/powersupply/PES/answer/entity/AnswerEntity.java similarity index 77% rename from src/main/java/com/powersupply/PES/domain/entity/AnswerEntity.java rename to src/main/java/com/powersupply/PES/answer/entity/AnswerEntity.java index 55a25a0..581e7ca 100644 --- a/src/main/java/com/powersupply/PES/domain/entity/AnswerEntity.java +++ b/src/main/java/com/powersupply/PES/answer/entity/AnswerEntity.java @@ -1,5 +1,10 @@ -package com.powersupply.PES.domain.entity; +package com.powersupply.PES.answer.entity; +import com.powersupply.PES.comment.entity.CommentEntity; +import com.powersupply.PES.entity.BaseEntity; +import com.powersupply.PES.problem.entity.ProblemEntity; +import com.powersupply.PES.question.entity.QuestionEntity; +import com.powersupply.PES.member.entity.MemberEntity; import lombok.*; import javax.persistence.*; diff --git a/src/main/java/com/powersupply/PES/repository/AnswerRepository.java b/src/main/java/com/powersupply/PES/answer/repository/AnswerRepository.java similarity index 83% rename from src/main/java/com/powersupply/PES/repository/AnswerRepository.java rename to src/main/java/com/powersupply/PES/answer/repository/AnswerRepository.java index 5f347d7..3d1ce57 100644 --- a/src/main/java/com/powersupply/PES/repository/AnswerRepository.java +++ b/src/main/java/com/powersupply/PES/answer/repository/AnswerRepository.java @@ -1,7 +1,7 @@ -package com.powersupply.PES.repository; +package com.powersupply.PES.answer.repository; -import com.powersupply.PES.domain.entity.AnswerEntity; -import com.powersupply.PES.domain.entity.QuestionEntity; +import com.powersupply.PES.answer.entity.AnswerEntity; +import com.powersupply.PES.question.entity.QuestionEntity; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; diff --git a/src/main/java/com/powersupply/PES/service/AnswerService.java b/src/main/java/com/powersupply/PES/answer/service/AnswerService.java similarity index 90% rename from src/main/java/com/powersupply/PES/service/AnswerService.java rename to src/main/java/com/powersupply/PES/answer/service/AnswerService.java index 75845fa..1d3c4f2 100644 --- a/src/main/java/com/powersupply/PES/service/AnswerService.java +++ b/src/main/java/com/powersupply/PES/answer/service/AnswerService.java @@ -1,10 +1,18 @@ -package com.powersupply.PES.service; +package com.powersupply.PES.answer.service; -import com.powersupply.PES.domain.dto.AnswerDTO; -import com.powersupply.PES.domain.entity.*; +import com.powersupply.PES.answer.entity.AnswerEntity; +import com.powersupply.PES.answer.repository.AnswerRepository; +import com.powersupply.PES.comment.entity.CommentEntity; +import com.powersupply.PES.comment.repository.CommentRepository; +import com.powersupply.PES.answer.dto.AnswerDTO; import com.powersupply.PES.exception.AppException; import com.powersupply.PES.exception.ErrorCode; -import com.powersupply.PES.repository.*; +import com.powersupply.PES.member.entity.MemberEntity; +import com.powersupply.PES.member.repository.MemberRepository; +import com.powersupply.PES.problem.entity.ProblemEntity; +import com.powersupply.PES.problem.repository.ProblemRepository; +import com.powersupply.PES.question.entity.QuestionEntity; +import com.powersupply.PES.question.repository.QuestionRepository; import com.powersupply.PES.utils.JwtUtil; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; diff --git a/src/main/java/com/powersupply/PES/comment/controller/CommentController.java b/src/main/java/com/powersupply/PES/comment/controller/CommentController.java new file mode 100644 index 0000000..a6e41bf --- /dev/null +++ b/src/main/java/com/powersupply/PES/comment/controller/CommentController.java @@ -0,0 +1,47 @@ +package com.powersupply.PES.comment.controller; + +import com.powersupply.PES.comment.dto.CommentDTO; +import com.powersupply.PES.comment.service.CommentService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequiredArgsConstructor +@Tag(name = "Comment", description = "댓글 관련 API") +public class CommentController { + + private final CommentService commentService; + + @Operation(summary = "댓글 조회", description = "특정 답변 ID에 대한 댓글을 조회합니다.") + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "조회 성공"), + @ApiResponse(responseCode = "204", description = "댓글이 없는 경우"), + @ApiResponse(responseCode = "500", description = "서버 오류") + }) + @GetMapping("/api/comment/{answerId}") + public ResponseEntity getComment( + @Parameter(description = "답변 ID", example = "1") @PathVariable Long answerId) { + return commentService.getComment(answerId); + } + + @Operation(summary = "댓글 달기", description = "특정 답변 ID에 대해 댓글을 작성합니다.") + @ApiResponses({ + @ApiResponse(responseCode = "201", description = "토큰에 문제가 없고 유저를 DB에서 찾아 정상적으로 댓글을 생성한 경우"), + @ApiResponse(responseCode = "400", description = "이미 자신의 댓글이 있는 경우"), + @ApiResponse(responseCode = "403", description = "재학생이 아닌 경우 / jwt 문제 / 자신의 답변에 댓글을 단 경우 / 최대 댓글 수 도달"), + @ApiResponse(responseCode = "404", description = "answerId가 잘못된 경우"), + @ApiResponse(responseCode = "500", description = "서버 오류") + }) + @PostMapping("/api/comment/{answerId}") + public ResponseEntity createComment( + @Parameter(description = "답변 ID", example = "1") @PathVariable Long answerId, + @RequestBody CommentDTO.CreateComment dto) { + return commentService.createComment(answerId, dto); + } +} diff --git a/src/main/java/com/powersupply/PES/domain/dto/CommentDTO.java b/src/main/java/com/powersupply/PES/comment/dto/CommentDTO.java similarity index 92% rename from src/main/java/com/powersupply/PES/domain/dto/CommentDTO.java rename to src/main/java/com/powersupply/PES/comment/dto/CommentDTO.java index 0db6f5f..a39570c 100644 --- a/src/main/java/com/powersupply/PES/domain/dto/CommentDTO.java +++ b/src/main/java/com/powersupply/PES/comment/dto/CommentDTO.java @@ -1,4 +1,4 @@ -package com.powersupply.PES.domain.dto; +package com.powersupply.PES.comment.dto; import lombok.Builder; import lombok.Getter; diff --git a/src/main/java/com/powersupply/PES/domain/entity/CommentEntity.java b/src/main/java/com/powersupply/PES/comment/entity/CommentEntity.java similarity index 75% rename from src/main/java/com/powersupply/PES/domain/entity/CommentEntity.java rename to src/main/java/com/powersupply/PES/comment/entity/CommentEntity.java index b2eb2ec..3a86113 100644 --- a/src/main/java/com/powersupply/PES/domain/entity/CommentEntity.java +++ b/src/main/java/com/powersupply/PES/comment/entity/CommentEntity.java @@ -1,5 +1,8 @@ -package com.powersupply.PES.domain.entity; +package com.powersupply.PES.comment.entity; +import com.powersupply.PES.answer.entity.AnswerEntity; +import com.powersupply.PES.entity.BaseEntity; +import com.powersupply.PES.member.entity.MemberEntity; import lombok.*; import javax.persistence.*; diff --git a/src/main/java/com/powersupply/PES/repository/CommentRepository.java b/src/main/java/com/powersupply/PES/comment/repository/CommentRepository.java similarity index 87% rename from src/main/java/com/powersupply/PES/repository/CommentRepository.java rename to src/main/java/com/powersupply/PES/comment/repository/CommentRepository.java index cd8bc4d..9e459f6 100644 --- a/src/main/java/com/powersupply/PES/repository/CommentRepository.java +++ b/src/main/java/com/powersupply/PES/comment/repository/CommentRepository.java @@ -1,6 +1,6 @@ -package com.powersupply.PES.repository; +package com.powersupply.PES.comment.repository; -import com.powersupply.PES.domain.entity.CommentEntity; +import com.powersupply.PES.comment.entity.CommentEntity; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; diff --git a/src/main/java/com/powersupply/PES/service/CommentService.java b/src/main/java/com/powersupply/PES/comment/service/CommentService.java similarity index 92% rename from src/main/java/com/powersupply/PES/service/CommentService.java rename to src/main/java/com/powersupply/PES/comment/service/CommentService.java index cb777a4..c702133 100644 --- a/src/main/java/com/powersupply/PES/service/CommentService.java +++ b/src/main/java/com/powersupply/PES/comment/service/CommentService.java @@ -1,14 +1,14 @@ -package com.powersupply.PES.service; +package com.powersupply.PES.comment.service; -import com.powersupply.PES.domain.dto.CommentDTO; -import com.powersupply.PES.domain.entity.AnswerEntity; -import com.powersupply.PES.domain.entity.CommentEntity; -import com.powersupply.PES.domain.entity.MemberEntity; +import com.powersupply.PES.comment.dto.CommentDTO; +import com.powersupply.PES.answer.entity.AnswerEntity; +import com.powersupply.PES.comment.entity.CommentEntity; +import com.powersupply.PES.member.entity.MemberEntity; import com.powersupply.PES.exception.AppException; import com.powersupply.PES.exception.ErrorCode; -import com.powersupply.PES.repository.AnswerRepository; -import com.powersupply.PES.repository.CommentRepository; -import com.powersupply.PES.repository.MemberRepository; +import com.powersupply.PES.answer.repository.AnswerRepository; +import com.powersupply.PES.comment.repository.CommentRepository; +import com.powersupply.PES.member.repository.MemberRepository; import com.powersupply.PES.utils.JwtUtil; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; diff --git a/src/main/java/com/powersupply/PES/configuration/JwtFilter.java b/src/main/java/com/powersupply/PES/configuration/JwtFilter.java index e86e8aa..e19f82b 100644 --- a/src/main/java/com/powersupply/PES/configuration/JwtFilter.java +++ b/src/main/java/com/powersupply/PES/configuration/JwtFilter.java @@ -26,10 +26,25 @@ public class JwtFilter extends OncePerRequestFilter { private final String secretKey; // 인증이 필요없는 경로 URL 목록 - private final List skipUrls = Arrays.asList("/api/signin", "/api/signup", "/api/finduser"); + private final List skipUrls = Arrays.asList( + "/api/signin", + "/api/signup", + "/api/finduser", + "/swagger-ui/**", + "/v3/api-docs/**", + "/swagger-resources/**", + "/webjars/**" + ); // 인증이 필요 없는 GET 요청의 URL 목록 - private final List skipGetUrls = Arrays.asList("/api/problemlist/" , "/api/answer/**", "/api/comment/**", "/api/answerlist/**", "/api/rank/**", "/api/admin/**"); + private final List skipGetUrls = Arrays.asList( + "/api/problemlist/", + "/api/answer/**", + "/api/comment/**", + "/api/answerlist/**", + "/api/rank/**", + "/api/admin/**" + ); @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { @@ -46,7 +61,7 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse } // 인증이 필요없는 경로는 바로 통과 - if (skipUrls.contains(requestURI)) { + if (skipUrls.stream().anyMatch(uri -> pathMatcher.match(uri, requestURI))) { log.info("인증 필요 없음 : {}", requestURI); filterChain.doFilter(request, response); return; diff --git a/src/main/java/com/powersupply/PES/configuration/SecurityConfig.java b/src/main/java/com/powersupply/PES/configuration/SecurityConfig.java index 4cac12b..889f187 100644 --- a/src/main/java/com/powersupply/PES/configuration/SecurityConfig.java +++ b/src/main/java/com/powersupply/PES/configuration/SecurityConfig.java @@ -5,14 +5,14 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpMethod; -import org.springframework.security.access.expression.SecurityExpressionHandler; -import org.springframework.security.access.hierarchicalroles.RoleHierarchy; -import org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.core.userdetails.User; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.provisioning.InMemoryUserDetailsManager; import org.springframework.security.web.SecurityFilterChain; -import org.springframework.security.web.access.expression.DefaultWebSecurityExpressionHandler; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.CorsConfigurationSource; @@ -28,18 +28,25 @@ public class SecurityConfig { @Value("${jwt.secret}") private String secretKey; + @Value("${admin.username}") + private String adminUsername; + + @Value("${admin.password}") + private String adminPassword; + @Bean - public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception{ + public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception { return httpSecurity - .httpBasic().disable() // UI쪽 들어오는거 disable - .csrf().disable() // cross site 기능 - .cors().configurationSource(corsConfigurationSource()).and() // cross site 도메인 다른 경우 허용 + .httpBasic().and() // 기본 인증 활성화 + .csrf().disable() // CSRF 비활성화 + .cors().configurationSource(corsConfigurationSource()).and() // CORS 설정 .authorizeRequests() - .antMatchers("/api/signin","/api/signup").permitAll() // 기본 요청 언제나 접근 가능 - .antMatchers(HttpMethod.GET, "/api/problemlist" , "/api/answer/**", "/api/comment/**", "/api/answerlist/**", "/api/rank/**", "/api/notice/**", "/api/admin/**").permitAll() - .antMatchers(HttpMethod.POST,"/api/comment/**").hasRole("REGULAR_STUDENT") - .antMatchers(HttpMethod.POST,"/api/notice/**").hasRole("MANAGER") - .antMatchers(HttpMethod.PATCH,"/api/notice/**").hasRole("MANAGER") + .antMatchers("/api/signin", "/api/signup").permitAll() // 기본 요청 허용 + .antMatchers("/swagger-ui/**", "/v3/api-docs/**", "/swagger-resources/**", "/webjars/**").authenticated() // Swagger 경로에 대한 접근 제한 + .antMatchers(HttpMethod.GET, "/api/problemlist", "/api/answer/**", "/api/comment/**", "/api/answerlist/**", "/api/rank/**", "/api/notice/**", "/api/admin/**").permitAll() + .antMatchers(HttpMethod.POST, "/api/comment/**").hasRole("REGULAR_STUDENT") + .antMatchers(HttpMethod.POST, "/api/notice/**").hasRole("MANAGER") + .antMatchers(HttpMethod.PATCH, "/api/notice/**").hasRole("MANAGER") .anyRequest().hasRole("USER") .and() .sessionManagement() @@ -53,31 +60,38 @@ public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws @Bean public CorsConfigurationSource corsConfigurationSource() { CorsConfiguration configuration = new CorsConfiguration(); - configuration.setAllowedOrigins(Arrays.asList("https://www.pes23.com", "https://pes23.com")); // 여기에 IP 추가 + configuration.setAllowedOrigins(Arrays.asList("https://www.pes23.com", "https://pes23.com", "http://localhost:8080")); configuration.setAllowedMethods(Arrays.asList("POST", "GET", "OPTIONS", "PUT", "DELETE", "PATCH")); configuration.setAllowedHeaders(Arrays.asList("Authorization", "Cache-Control", "Content-Type")); - configuration.setAllowCredentials(true); // 필요한 경우, 쿠키와 함께 요청을 보내는 것을 허용 + configuration.setAllowCredentials(true); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); - source.registerCorsConfiguration("/**", configuration); // 모든 URL에 대해 설정 적용 + source.registerCorsConfiguration("/**", configuration); return source; } @Bean - public SecurityExpressionHandler customWebSecurityExpressionHandler() { - DefaultWebSecurityExpressionHandler defaultWebSecurityExpressionHandler = new DefaultWebSecurityExpressionHandler(); - defaultWebSecurityExpressionHandler.setRoleHierarchy(roleHierarchy()); - return defaultWebSecurityExpressionHandler; + public UserDetailsService userDetailsService() { + InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager(); + manager.createUser(User.withUsername(adminUsername).password("{noop}" + adminPassword).roles("ADMIN").build()); + return manager; } @Bean - public RoleHierarchy roleHierarchy() { - RoleHierarchyImpl roleHierarchy = new RoleHierarchyImpl(); - //roleHierarchy.setHierarchy("ROLE_MANAGER > ROLE_REGULAR_STUDENT > ROLE_USER and ROLE_NEW_STUDENT > ROLE_USER"); - String hierarchy = "ROLE_ADMIN > ROLE_MANAGER > ROLE_REGULAR_STUDENT > ROLE_USER\n" + - "ROLE_NEW_STUDENT > ROLE_USER"; - roleHierarchy.setHierarchy(hierarchy); - return roleHierarchy; + public PasswordEncoder passwordEncoder() { + return new NoOpPasswordEncoder(); + } +} + +class NoOpPasswordEncoder implements PasswordEncoder { + @Override + public String encode(CharSequence rawPassword) { + return rawPassword.toString(); + } + + @Override + public boolean matches(CharSequence rawPassword, String encodedPassword) { + return rawPassword.toString().equals(encodedPassword); } } diff --git a/src/main/java/com/powersupply/PES/controller/AnswerController.java b/src/main/java/com/powersupply/PES/controller/AnswerController.java deleted file mode 100644 index eab69a2..0000000 --- a/src/main/java/com/powersupply/PES/controller/AnswerController.java +++ /dev/null @@ -1,42 +0,0 @@ -package com.powersupply.PES.controller; - -import com.powersupply.PES.domain.dto.AnswerDTO; -import com.powersupply.PES.service.AnswerService; -import com.powersupply.PES.utils.ResponseUtil; -import lombok.RequiredArgsConstructor; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.*; - -@RestController -@RequiredArgsConstructor -public class AnswerController { - - private final AnswerService answerService; - - // answerEntity 만들기 - @PostMapping("/api/answer") - public ResponseEntity createAnswer(@RequestParam("memberEmail") String email, @RequestParam("problemId") Long problemId) { - return ResponseEntity.status(HttpStatus.CREATED).body(answerService.createAnswer(email, problemId)); - } - - // 질문, 답변 가져오기 - @GetMapping("/api/answer/{answerId}") - public ResponseEntity getAnswer(@PathVariable Long answerId) { - return ResponseEntity.ok().body(answerService.getAnswer(answerId)); - } - - // 답변하기 - @PostMapping("/api/answer/{answerId}") - public ResponseEntity postAnswer(@PathVariable Long answerId, - @RequestBody AnswerDTO.AnswerContent dto) { - answerService.postAnswer(answerId, dto); - return ResponseUtil.successResponse(""); - } - - // 풀이 보기 - @GetMapping("/api/answerlist/{problemId}") - public ResponseEntity getAnswerList(@PathVariable Long problemId) { - return answerService.getAnswerList(problemId); - } -} diff --git a/src/main/java/com/powersupply/PES/controller/CommentController.java b/src/main/java/com/powersupply/PES/controller/CommentController.java deleted file mode 100644 index 2aae365..0000000 --- a/src/main/java/com/powersupply/PES/controller/CommentController.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.powersupply.PES.controller; - -import com.powersupply.PES.domain.dto.CommentDTO; -import com.powersupply.PES.service.CommentService; -import lombok.RequiredArgsConstructor; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.*; - -@RestController -@RequiredArgsConstructor -public class CommentController { - - private final CommentService commentService; - - // 댓글 가져오기 - @GetMapping("/api/comment/{answerId}") - public ResponseEntity getComment(@PathVariable Long answerId) { - return commentService.getComment(answerId); - } - - // 댓글 달기 - @PostMapping("/api/comment/{answerId}") - public ResponseEntity createComment(@PathVariable Long answerId, - @RequestBody CommentDTO.CreateComment dto) { - return commentService.createComment(answerId, dto); - } -} diff --git a/src/main/java/com/powersupply/PES/controller/ManageController.java b/src/main/java/com/powersupply/PES/controller/ManageController.java deleted file mode 100644 index b2760f4..0000000 --- a/src/main/java/com/powersupply/PES/controller/ManageController.java +++ /dev/null @@ -1,104 +0,0 @@ -package com.powersupply.PES.controller; - -import com.powersupply.PES.domain.dto.ManageDTO; -import com.powersupply.PES.domain.entity.MemberEntity; -import com.powersupply.PES.exception.ExceptionManger; -import com.powersupply.PES.service.ManageService; -import lombok.RequiredArgsConstructor; -import org.springframework.http.ResponseEntity; -import org.springframework.security.core.parameters.P; -import org.springframework.web.bind.annotation.*; - -import java.io.IOException; -import java.util.List; - -@RestController -@RequiredArgsConstructor -@RequestMapping("/api/admin") -public class ManageController { - - private final ManageService manageService; - - /* ---------- 문제 관리 기능 ---------- */ - - // 전체 문제 리스트 불러오기 - @GetMapping("/problemlist") - public ResponseEntity> problemList() { - List problemList = manageService.problemList(); - return ResponseEntity.ok().body(problemList); - } - - // 특정 문제 detail 불러오기 - @GetMapping("/problem/{problemId}") - public ResponseEntity problemDetail(@PathVariable Long problemId) { - return ResponseEntity.ok().body(manageService.problemDetail(problemId)); - } - - // 문제 등록하기 - @PostMapping("/problem") - public ResponseEntity postProblem(@RequestBody ManageDTO.ProblemRequestDto requestDto) throws IOException { - return ResponseEntity.ok(manageService.postProblem(requestDto)); - } - - // 문제 수정하기 - @PatchMapping("/problem/{problemId}") - public ResponseEntity patchProblem(@PathVariable Long problemId, @RequestBody ManageDTO.ProblemRequestDto requestDto) { - return manageService.patchProblem(problemId, requestDto); - } - - /* ---------- 회원 관리 기능 ---------- */ - - // 전체 멤버 리스트 불러오기 - @GetMapping("/memberlist") - public ResponseEntity> list() { - List memberList = manageService.list(); - - return ResponseEntity.ok().body(memberList); - } - - // 특정 멤버 detail 불러오기 - @GetMapping("/member/{memberId}") - public ResponseEntity readDetail(@PathVariable String memberId) { - return ResponseEntity.ok().body(manageService.readDetail(memberId)); - } - - // 멤버 삭제하기 - @DeleteMapping("/member/{memberId}") - public ResponseEntity deleteMember(@PathVariable String memberId) { - return manageService.deleteMember(memberId); - } - - // 멤버 상태 수정하기 - @PutMapping("/member/{memberId}") - public ResponseEntity updateMemberStatus(@PathVariable String memberId, @RequestBody ManageDTO.MemberUpdateRequestDto requestDto) { - return ResponseEntity.ok(manageService.updateMemberStatus(memberId, requestDto)); - } - - /* ---------- 질문 관리 기능 ---------- */ - - // 문제 별 질문 목록 가져오기 - @GetMapping("/questionlist/{problemId}") - public ResponseEntity> questionList(@PathVariable Long problemId) { - List questionList = manageService.questionList(problemId); - - return ResponseEntity.ok().body(questionList); - } - - // 질문 등록하기 - @PostMapping("/question/{problemId}") - public ResponseEntity postQuestion(@PathVariable Long problemId, @RequestBody ManageDTO.QuestionRequestDto requestDto) { - return ResponseEntity.ok(manageService.postQuestion(problemId, requestDto)); - } - - // 질문 수정하기 - @PatchMapping("/question/{questionId}") - public ResponseEntity patchQuestion(@PathVariable Long questionId, @RequestBody ManageDTO.QuestionRequestDto requestDto) { - return ResponseEntity.ok(manageService.updateQuestion(questionId, requestDto)); - } - - // 질문 삭제하기 - @DeleteMapping("/question/{questionId}") - public ResponseEntity deleteQuestion(@PathVariable Long questionId) { - return ResponseEntity.ok(manageService.deleteQuestion(questionId)); - } -} diff --git a/src/main/java/com/powersupply/PES/controller/MemberController.java b/src/main/java/com/powersupply/PES/controller/MemberController.java deleted file mode 100644 index 0a64ed8..0000000 --- a/src/main/java/com/powersupply/PES/controller/MemberController.java +++ /dev/null @@ -1,106 +0,0 @@ -package com.powersupply.PES.controller; - -import com.powersupply.PES.domain.dto.MemberDTO; -import com.powersupply.PES.service.MemberService; -import com.powersupply.PES.utils.ResponseUtil; -import lombok.RequiredArgsConstructor; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.*; - -import javax.servlet.http.Cookie; -import javax.servlet.http.HttpServletResponse; -import java.time.LocalDate; -import java.util.List; - -@RestController -@RequiredArgsConstructor -public class MemberController { - - private final MemberService memberService; - - // 회원가입 정보 저장 - @PostMapping("/api/signup") - public ResponseEntity postSignUp(@RequestBody MemberDTO.MemberSignUpRequest dto) { - String name = memberService.signUp(dto); - - return ResponseUtil.createResponse(name + "님, 가입에 성공했습니다."); - } - - // 로그인 진행 - @PostMapping("/api/signin") - public ResponseEntity postSignIn(HttpServletResponse response, @RequestBody MemberDTO.MemberSignInRequest dto) { - String token = memberService.signIn(dto); - - Cookie cookie = new Cookie("Authorization", token); - cookie.setMaxAge(60 * 60); // 쿠키 유효 시간 (1시간) - cookie.setSecure(true); // HTTPS에서만 쿠키 사용 - cookie.setPath("/"); // 도메인 전체에서 사용 가능하도록 설정 - cookie.setHttpOnly(true); // JavaScript에서 쿠키에 접근할 수 없도록 설정 - response.addCookie(cookie); - - return ResponseUtil.successResponse("로그인에 성공했습니다."); - } - - // 로그아웃 진행 - @PostMapping("/api/logout") - public ResponseEntity postLogout(HttpServletResponse response) { - Cookie logoutCookie = new Cookie("Authorization", null); - logoutCookie.setMaxAge(0); // 쿠키 즉시 만료 - logoutCookie.setSecure(true); - logoutCookie.setPath("/"); - logoutCookie.setHttpOnly(true); - response.addCookie(logoutCookie); - - return ResponseUtil.successResponse("로그아웃 되었습니다."); - } - - // 마이페이지(정보) - @GetMapping("/api/mypage/information") - public ResponseEntity getMyPageInfo() { - - return ResponseEntity.ok().body(memberService.getMyPage()); - } - - // 마이페이지(내가 푼 문제) - @GetMapping("/api/mypage/mysolve") - public ResponseEntity getMySolveInfo() { - - return memberService.getMySolve(); - } - - // 마이페이지(나의 피드백) - @GetMapping("/api/mypage/myfeedback") - public ResponseEntity getMyFeedbackInfo() { - - return memberService.getMyFeedback(); - } - - // 상단 사용자 정보 - @GetMapping("/api/exp") - public ResponseEntity myUser() { - return ResponseEntity.ok().body(memberService.expVar()); - } - - // 랭킹 가져오기 - @GetMapping("/api/rank/junior") - public ResponseEntity> getRank(@RequestParam(value = "memberGen", required = false) Integer memberGen) { - // year이 null이면 현재 현재 기수로 설정 - if (memberGen == null) { - memberGen = LocalDate.now().getYear() - 1989; - } - - List rank = memberService.getRank(memberGen); - if (rank.isEmpty()) return ResponseEntity.noContent().build(); - return ResponseEntity.ok().body(rank); - } - - // 재학생 랭킹 가져오기 - @GetMapping("/api/rank/senior") - public ResponseEntity> getSeniorRank() { - List rank = memberService.getSeniorRank(); - if (rank.isEmpty()) return ResponseEntity.noContent().build(); - return ResponseEntity.ok().body(rank); - } -} - - diff --git a/src/main/java/com/powersupply/PES/controller/NoticeController.java b/src/main/java/com/powersupply/PES/controller/NoticeController.java deleted file mode 100644 index 9919d2d..0000000 --- a/src/main/java/com/powersupply/PES/controller/NoticeController.java +++ /dev/null @@ -1,57 +0,0 @@ -package com.powersupply.PES.controller; - -import com.powersupply.PES.domain.dto.NoticeDTO; -import com.powersupply.PES.service.NoticeService; -import lombok.RequiredArgsConstructor; -import org.springframework.http.ResponseEntity; -import org.springframework.security.core.parameters.P; -import org.springframework.web.bind.annotation.*; - -@RestController -@RequiredArgsConstructor -public class NoticeController { - - private final NoticeService noticeService; - - // 새로운 공지사항 존재 확인(비회원 전용) - @GetMapping("/api/notice/new") - public ResponseEntity checkNewNotice() { - return noticeService.checkNewNotice(); - } - - // 공지사항 등록 - @PostMapping("/api/notice") - public ResponseEntity postNotice(@RequestBody NoticeDTO.BaseNotice dto) { - return noticeService.postNotice(dto); - } - - // 공지사항 리스트 가져오기(state가 true시 삭제된 것 가져오기) - @GetMapping("/api/notice") - public ResponseEntity getNoticeList(@RequestParam(required = false) boolean state) { - return noticeService.getNoticeList(state); - } - - // 공지사항 내용 가져오기 - @GetMapping("/api/notice/{noticeId}") - public ResponseEntity getNotice(@PathVariable Long noticeId) { - return noticeService.getNotice(noticeId); - } - - // 공지사항 수정 - @PatchMapping("/api/notice/{noticeId}") - public ResponseEntity updateNotice(@PathVariable Long noticeId, @RequestBody NoticeDTO.BaseNotice dto) { - return noticeService.updateNotice(noticeId, dto); - } - - // 공지사항 복구 - @PatchMapping("/api/notice/restore/{noticeId}") - public ResponseEntity restoreNotice(@PathVariable Long noticeId) { - return noticeService.restoreNotice(noticeId); - } - - // 공지사항 삭제 - @DeleteMapping("/api/notice/{noticeId}") - public ResponseEntity deleteNotice(@PathVariable Long noticeId) { - return noticeService.deleteNotice(noticeId); - } -} diff --git a/src/main/java/com/powersupply/PES/controller/ProblemController.java b/src/main/java/com/powersupply/PES/controller/ProblemController.java deleted file mode 100644 index dd41be3..0000000 --- a/src/main/java/com/powersupply/PES/controller/ProblemController.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.powersupply.PES.controller; - -import com.powersupply.PES.service.ProblemService; -import lombok.RequiredArgsConstructor; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RestController; - -@RestController -@RequiredArgsConstructor -public class ProblemController { - - private final ProblemService problemService; - - // 문제 리스트 가져오기 - @GetMapping("/api/problemlist") - public ResponseEntity getProblemList() { - return problemService.getProblemList(); - } -} diff --git a/src/main/java/com/powersupply/PES/controller/QuestionController.java b/src/main/java/com/powersupply/PES/controller/QuestionController.java deleted file mode 100644 index 6711481..0000000 --- a/src/main/java/com/powersupply/PES/controller/QuestionController.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.powersupply.PES.controller; - -import com.powersupply.PES.domain.dto.QuestionDTO; -import com.powersupply.PES.service.QuestionService; -import lombok.RequiredArgsConstructor; -import org.springframework.http.ResponseEntity; -import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; - -import java.util.List; - -@Controller -@RequiredArgsConstructor -public class QuestionController { - - private final QuestionService questionService; - - // (재)질문 목록 보기 - @GetMapping("/api/questions/{problemId}") - public ResponseEntity> getQuestionList(@PathVariable Long problemId) { - return ResponseEntity.ok().body(questionService.getQeustionList(problemId)); - } -} diff --git a/src/main/java/com/powersupply/PES/domain/dto/MemberDTO.java b/src/main/java/com/powersupply/PES/domain/dto/MemberDTO.java deleted file mode 100644 index 4865b8c..0000000 --- a/src/main/java/com/powersupply/PES/domain/dto/MemberDTO.java +++ /dev/null @@ -1,78 +0,0 @@ -package com.powersupply.PES.domain.dto; - -import lombok.Builder; -import lombok.Getter; -import lombok.Setter; - -public class MemberDTO { - - @Getter - public static class MemberSignUpRequest { - private String memberId; // 아이디 - private String memberPw; // 멤버 비밀번호 - private String memberName; // 이름 - private int memberGen; // 기수 - private String memberMajor; // 학과 - private String memberPhone; // 전화번호 - private String memberEmail; // 이메일 - } - - @Getter - public static class MemberSignInRequest { - private String memberId; // 아이디 - private String memberPw; // 비밀번호 - } - - @Builder - @Getter - public static class MemberMyPageResponse { - private String memberName; // 이름 - private int memberGen; // 기수 - private String memberMajor; // 학과 - private String memberPhone; // 전화번호 - private String memberStatus; // 상태 - private String memberEmail; // 이메일 - private int memberScore; // 점수 - private String memberId; // 아이디 - } - - @Getter - @Builder - public static class NameScoreResponse { - private String memberName; - private String memberStatus; - private int memberScore; - private int memberGen; - private boolean hasNewNotices; - } - - @Getter - @Builder - public static class MemberMySolveResponse { - private Long problemId; - private String problemTitle; - private int problemScore; - private Long answerId; - private String answerState; - private int finalScore; - } - - @Getter - @Builder - public static class MemberMyFeedbackResponse { - private Long answerId; - private int memberGen; - private String memberName; - private int commentPassFail; - private String commentContent; - } - - @Getter - @Builder - @Setter - public static class Rank { - private int rank; - private String memberName; - private int score; - } -} diff --git a/src/main/java/com/powersupply/PES/domain/entity/BaseEntity.java b/src/main/java/com/powersupply/PES/entity/BaseEntity.java similarity index 92% rename from src/main/java/com/powersupply/PES/domain/entity/BaseEntity.java rename to src/main/java/com/powersupply/PES/entity/BaseEntity.java index 746b3e8..2373513 100644 --- a/src/main/java/com/powersupply/PES/domain/entity/BaseEntity.java +++ b/src/main/java/com/powersupply/PES/entity/BaseEntity.java @@ -1,4 +1,4 @@ -package com.powersupply.PES.domain.entity; +package com.powersupply.PES.entity; import lombok.Getter; import org.hibernate.annotations.CreationTimestamp; diff --git a/src/main/java/com/powersupply/PES/manage/controller/ManageController.java b/src/main/java/com/powersupply/PES/manage/controller/ManageController.java new file mode 100644 index 0000000..b639f9c --- /dev/null +++ b/src/main/java/com/powersupply/PES/manage/controller/ManageController.java @@ -0,0 +1,178 @@ +package com.powersupply.PES.manage.controller; + +import com.powersupply.PES.manage.dto.ManageDTO; +import com.powersupply.PES.member.entity.MemberEntity; +import com.powersupply.PES.manage.service.ManageService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.io.IOException; +import java.util.List; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/admin") +@Tag(name = "Manage", description = "관리 기능 API") +public class ManageController { + + private final ManageService manageService; + + /* ---------- 문제 관리 기능 ---------- */ + + @Operation(summary = "전체 문제 리스트 불러오기", description = "전체 문제 리스트를 불러옵니다.") + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "조회 성공"), + @ApiResponse(responseCode = "500", description = "서버 오류") + }) + @GetMapping("/problemlist") + public ResponseEntity> problemList() { + List problemList = manageService.problemList(); + return ResponseEntity.ok().body(problemList); + } + + @Operation(summary = "특정 문제 detail 불러오기", description = "특정 문제의 상세 정보를 불러옵니다.") + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "조회 성공"), + @ApiResponse(responseCode = "404", description = "해당 problemId가 없을 경우"), + @ApiResponse(responseCode = "500", description = "서버 오류") + }) + @GetMapping("/problem/{problemId}") + public ResponseEntity problemDetail( + @Parameter(description = "문제 ID", example = "1") @PathVariable Long problemId) { + return ResponseEntity.ok().body(manageService.problemDetail(problemId)); + } + + @Operation(summary = "문제 등록하기", description = "새로운 문제를 등록합니다.") + @ApiResponses({ + @ApiResponse(responseCode = "201", description = "등록 성공"), + @ApiResponse(responseCode = "403", description = "관리자 권한이 없는 경우"), + @ApiResponse(responseCode = "500", description = "서버 오류") + }) + @PostMapping("/problem") + public ResponseEntity postProblem(@RequestBody ManageDTO.ProblemRequestDto requestDto) throws IOException { + return ResponseEntity.ok(manageService.postProblem(requestDto)); + } + + @Operation(summary = "문제 수정하기", description = "기존 문제를 수정합니다.") + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "수정 성공"), + @ApiResponse(responseCode = "404", description = "해당 problemId가 없을 경우"), + @ApiResponse(responseCode = "500", description = "서버 오류") + }) + @PatchMapping("/problem/{problemId}") + public ResponseEntity patchProblem( + @Parameter(description = "문제 ID", example = "1") @PathVariable Long problemId, + @RequestBody ManageDTO.ProblemRequestDto requestDto) { + return manageService.patchProblem(problemId, requestDto); + } + + /* ---------- 회원 관리 기능 ---------- */ + + @Operation(summary = "전체 멤버 리스트 불러오기", description = "전체 멤버 리스트를 불러옵니다.") + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "조회 성공"), + @ApiResponse(responseCode = "500", description = "서버 오류") + }) + @GetMapping("/memberlist") + public ResponseEntity> list() { + List memberList = manageService.list(); + return ResponseEntity.ok().body(memberList); + } + + @Operation(summary = "특정 멤버 detail 불러오기", description = "특정 멤버의 상세 정보를 불러옵니다.") + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "조회 성공"), + @ApiResponse(responseCode = "404", description = "해당 memberId가 없을 경우"), + @ApiResponse(responseCode = "500", description = "서버 오류") + }) + @GetMapping("/member/{memberId}") + public ResponseEntity readDetail( + @Parameter(description = "멤버 ID", example = "user123") @PathVariable String memberId) { + return ResponseEntity.ok().body(manageService.readDetail(memberId)); + } + + @Operation(summary = "멤버 삭제하기", description = "특정 멤버를 삭제합니다.") + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "삭제 성공"), + @ApiResponse(responseCode = "403", description = "관리자 권한이 없는 경우"), + @ApiResponse(responseCode = "404", description = "해당 memberId가 없을 경우"), + @ApiResponse(responseCode = "500", description = "서버 오류") + }) + @DeleteMapping("/member/{memberId}") + public ResponseEntity deleteMember( + @Parameter(description = "멤버 ID", example = "user123") @PathVariable String memberId) { + return manageService.deleteMember(memberId); + } + + @Operation(summary = "멤버 상태 수정하기", description = "특정 멤버의 상태를 수정합니다.") + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "수정 성공"), + @ApiResponse(responseCode = "403", description = "관리자 권한이 없는 경우"), + @ApiResponse(responseCode = "404", description = "해당 memberId가 없을 경우"), + @ApiResponse(responseCode = "500", description = "서버 오류") + }) + @PutMapping("/member/{memberId}") + public ResponseEntity updateMemberStatus( + @Parameter(description = "멤버 ID", example = "user123") @PathVariable String memberId, + @RequestBody ManageDTO.MemberUpdateRequestDto requestDto) { + return ResponseEntity.ok(manageService.updateMemberStatus(memberId, requestDto)); + } + + /* ---------- 질문 관리 기능 ---------- */ + + @Operation(summary = "문제 별 질문 목록 가져오기", description = "특정 문제에 대한 질문 목록을 불러옵니다.") + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "조회 성공"), + @ApiResponse(responseCode = "500", description = "서버 오류") + }) + @GetMapping("/questionlist/{problemId}") + public ResponseEntity> questionList( + @Parameter(description = "문제 ID", example = "1") @PathVariable Long problemId) { + List questionList = manageService.questionList(problemId); + return ResponseEntity.ok().body(questionList); + } + + @Operation(summary = "질문 등록하기", description = "특정 문제에 대해 질문을 등록합니다.") + @ApiResponses({ + @ApiResponse(responseCode = "201", description = "등록 성공"), + @ApiResponse(responseCode = "400", description = "관리자 권한이 없는 경우"), + @ApiResponse(responseCode = "500", description = "서버 오류") + }) + @PostMapping("/question/{problemId}") + public ResponseEntity postQuestion( + @Parameter(description = "문제 ID", example = "1") @PathVariable Long problemId, + @RequestBody ManageDTO.QuestionRequestDto requestDto) { + return ResponseEntity.ok(manageService.postQuestion(problemId, requestDto)); + } + + @Operation(summary = "질문 수정하기", description = "기존 질문을 수정합니다.") + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "수정 성공"), + @ApiResponse(responseCode = "404", description = "해당 questionId가 없을 경우"), + @ApiResponse(responseCode = "500", description = "서버 오류") + }) + @PatchMapping("/question/{questionId}") + public ResponseEntity patchQuestion( + @Parameter(description = "질문 ID", example = "1") @PathVariable Long questionId, + @RequestBody ManageDTO.QuestionRequestDto requestDto) { + return ResponseEntity.ok(manageService.updateQuestion(questionId, requestDto)); + } + + @Operation(summary = "질문 삭제하기", description = "특정 질문을 삭제합니다.") + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "삭제 성공"), + @ApiResponse(responseCode = "404", description = "해당 questionId가 없을 경우"), + @ApiResponse(responseCode = "500", description = "서버 오류") + }) + @DeleteMapping("/question/{questionId}") + public ResponseEntity deleteQuestion( + @Parameter(description = "질문 ID", example = "1") @PathVariable Long questionId) { + return ResponseEntity.ok(manageService.deleteQuestion(questionId)); + } +} diff --git a/src/main/java/com/powersupply/PES/domain/dto/ManageDTO.java b/src/main/java/com/powersupply/PES/manage/dto/ManageDTO.java similarity index 93% rename from src/main/java/com/powersupply/PES/domain/dto/ManageDTO.java rename to src/main/java/com/powersupply/PES/manage/dto/ManageDTO.java index c7238f6..0142684 100644 --- a/src/main/java/com/powersupply/PES/domain/dto/ManageDTO.java +++ b/src/main/java/com/powersupply/PES/manage/dto/ManageDTO.java @@ -1,15 +1,14 @@ -package com.powersupply.PES.domain.dto; +package com.powersupply.PES.manage.dto; -import com.powersupply.PES.domain.entity.MemberEntity; -import com.powersupply.PES.domain.entity.ProblemEntity; -import com.powersupply.PES.domain.entity.QuestionEntity; -import com.powersupply.PES.exception.ExceptionManger; +import com.powersupply.PES.member.dto.MemberDTO; +import com.powersupply.PES.member.entity.MemberEntity; +import com.powersupply.PES.problem.entity.ProblemEntity; +import com.powersupply.PES.question.entity.QuestionEntity; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; -import java.util.ArrayList; import java.util.List; public class ManageDTO { diff --git a/src/main/java/com/powersupply/PES/service/ManageService.java b/src/main/java/com/powersupply/PES/manage/service/ManageService.java similarity index 93% rename from src/main/java/com/powersupply/PES/service/ManageService.java rename to src/main/java/com/powersupply/PES/manage/service/ManageService.java index a9f83bf..f370938 100644 --- a/src/main/java/com/powersupply/PES/service/ManageService.java +++ b/src/main/java/com/powersupply/PES/manage/service/ManageService.java @@ -1,12 +1,19 @@ -package com.powersupply.PES.service; +package com.powersupply.PES.manage.service; -import com.powersupply.PES.domain.dto.ManageDTO; -import com.powersupply.PES.domain.dto.MemberDTO; -import com.powersupply.PES.domain.entity.*; +import com.powersupply.PES.answer.entity.AnswerEntity; +import com.powersupply.PES.answer.repository.AnswerRepository; +import com.powersupply.PES.comment.repository.CommentRepository; +import com.powersupply.PES.manage.dto.ManageDTO; +import com.powersupply.PES.member.dto.MemberDTO; import com.powersupply.PES.exception.AppException; import com.powersupply.PES.exception.ErrorCode; import com.powersupply.PES.mapper.MapperUtils; -import com.powersupply.PES.repository.*; +import com.powersupply.PES.member.entity.MemberEntity; +import com.powersupply.PES.member.repository.MemberRepository; +import com.powersupply.PES.problem.entity.ProblemEntity; +import com.powersupply.PES.problem.repository.ProblemRepository; +import com.powersupply.PES.question.entity.QuestionEntity; +import com.powersupply.PES.question.repository.QuestionRepository; import com.powersupply.PES.utils.JwtUtil; import lombok.RequiredArgsConstructor; import org.hibernate.Hibernate; @@ -15,9 +22,7 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.lang.reflect.Member; import java.time.LocalDateTime; -import java.util.ArrayList; import java.util.List; import java.util.Optional; import java.util.stream.Collectors; diff --git a/src/main/java/com/powersupply/PES/mapper/MapperUtils.java b/src/main/java/com/powersupply/PES/mapper/MapperUtils.java index 0fabece..c743c70 100644 --- a/src/main/java/com/powersupply/PES/mapper/MapperUtils.java +++ b/src/main/java/com/powersupply/PES/mapper/MapperUtils.java @@ -1,8 +1,8 @@ package com.powersupply.PES.mapper; -import com.powersupply.PES.domain.dto.MemberDTO; -import com.powersupply.PES.domain.entity.AnswerEntity; -import com.powersupply.PES.domain.entity.CommentEntity; +import com.powersupply.PES.member.dto.MemberDTO; +import com.powersupply.PES.answer.entity.AnswerEntity; +import com.powersupply.PES.comment.entity.CommentEntity; public class MapperUtils { diff --git a/src/main/java/com/powersupply/PES/member/controller/MemberController.java b/src/main/java/com/powersupply/PES/member/controller/MemberController.java new file mode 100644 index 0000000..4f05fe6 --- /dev/null +++ b/src/main/java/com/powersupply/PES/member/controller/MemberController.java @@ -0,0 +1,145 @@ +package com.powersupply.PES.member.controller; + +import com.powersupply.PES.member.dto.MemberDTO; +import com.powersupply.PES.member.service.MemberService; +import com.powersupply.PES.utils.ResponseUtil; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletResponse; +import java.time.LocalDate; +import java.util.List; +@RestController +@RequiredArgsConstructor +@Tag(name = "Member", description = "회원 관련 API") +public class MemberController { + + private final MemberService memberService; + + @Operation(summary = "회원가입 정보 저장", description = "회원가입 정보를 저장하는 메서드입니다.") + @ApiResponses({ + @ApiResponse(responseCode = "201", description = "회원가입 성공"), + @ApiResponse(responseCode = "400", description = "서버에 해당 이메일,번호, 아이디가 겹치는 경우"), + @ApiResponse(responseCode = "500", description = "서버 오류") + }) + @PostMapping("/api/signup") + public ResponseEntity postSignUp(@RequestBody MemberDTO.MemberSignUpRequest dto) { + String name = memberService.signUp(dto); + + return ResponseUtil.createResponse(name + "님, 가입에 성공했습니다."); + } + + @Operation(summary = "로그인 진행", description = "로그인 시 jwt 토큰을 반환해 로그인을 승인하는 메서드입니다.") + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "로그인 성공"), + @ApiResponse(responseCode = "401", description = "로그인 실패"), + @ApiResponse(responseCode = "500", description = "서버 오류") + }) + @PostMapping("/api/signin") + public ResponseEntity postSignIn(HttpServletResponse response, @RequestBody MemberDTO.MemberSignInRequest dto) { + String token = memberService.signIn(dto); + + Cookie cookie = new Cookie("Authorization", token); + cookie.setMaxAge(60 * 60); // 쿠키 유효 시간 (1시간) + cookie.setSecure(true); // HTTPS에서만 쿠키 사용 + cookie.setPath("/"); // 도메인 전체에서 사용 가능하도록 설정 + cookie.setHttpOnly(true); // JavaScript에서 쿠키에 접근할 수 없도록 설정 + response.addCookie(cookie); + + return ResponseUtil.successResponse("로그인에 성공했습니다."); + } + + @Operation(summary = "로그아웃 진행", description = "로그아웃을 진행하는 메서드입니다.") + @ApiResponse(responseCode = "200", description = "로그아웃 성공") + @PostMapping("/api/logout") + public ResponseEntity postLogout(HttpServletResponse response) { + Cookie logoutCookie = new Cookie("Authorization", null); + logoutCookie.setMaxAge(0); // 쿠키 즉시 만료 + logoutCookie.setSecure(true); + logoutCookie.setPath("/"); + logoutCookie.setHttpOnly(true); + response.addCookie(logoutCookie); + + return ResponseUtil.successResponse("로그아웃 되었습니다."); + } + + @Operation(summary = "마이페이지 정보 조회", description = "클라이언트가 요청한 마이페이지 정보를 조회하는 메서드입니다.") + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "회원 정보가 존재하여 반환에 성공"), + @ApiResponse(responseCode = "401", description = "회원 정보가 존재하지 않는 경우"), + @ApiResponse(responseCode = "500", description = "서버 오류") + }) + @GetMapping("/api/mypage/information") + public ResponseEntity getMyPageInfo() { + return ResponseEntity.ok().body(memberService.getMyPage()); + } + + @Operation(summary = "마이페이지 내가 푼 문제 조회", description = "클라이언트가 요청한 내가 푼 문제 정보를 조회하는 메서드입니다.") + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "조회 성공"), + @ApiResponse(responseCode = "204", description = "리스트가 비어있는 경우"), + @ApiResponse(responseCode = "500", description = "서버 오류") + }) + @GetMapping("/api/mypage/mysolve") + public ResponseEntity getMySolveInfo() { + return memberService.getMySolve(); + } + + @Operation(summary = "마이페이지 나의 피드백 조회", description = "클라이언트가 요청한 나의 피드백 정보를 조회하는 메서드입니다.") + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "조회 성공"), + @ApiResponse(responseCode = "204", description = "리스트가 비어있는 경우"), + @ApiResponse(responseCode = "500", description = "서버 오류") + }) + @GetMapping("/api/mypage/myfeedback") + public ResponseEntity getMyFeedbackInfo() { + return memberService.getMyFeedback(); + } + + @Operation(summary = "상단 사용자 정보 조회", description = "상단 사용자 정보를 조회하는 메서드입니다.") + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "조회 성공"), + @ApiResponse(responseCode = "204", description = "리스트가 비어있는 경우"), + @ApiResponse(responseCode = "500", description = "서버 오류") + }) + @GetMapping("/api/exp") + public ResponseEntity myUser() { + return ResponseEntity.ok().body(memberService.expVar()); + } + + @Operation(summary = "주니어 랭킹 조회", description = "주니어 회원의 랭킹을 조회하는 메서드입니다.") + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "조회 성공"), + @ApiResponse(responseCode = "204", description = "리스트가 비어있는 경우"), + @ApiResponse(responseCode = "500", description = "서버 오류") + }) + @GetMapping("/api/rank/junior") + public ResponseEntity> getRank(@RequestParam(value = "memberGen", required = false) Integer memberGen) { + if (memberGen == null) { + memberGen = LocalDate.now().getYear() - 1989; + } + + List rank = memberService.getRank(memberGen); + if (rank.isEmpty()) return ResponseEntity.noContent().build(); + return ResponseEntity.ok().body(rank); + } + + @Operation(summary = "시니어 랭킹 조회", description = "시니어 회원의 랭킹을 조회하는 메서드입니다.") + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "조회 성공"), + @ApiResponse(responseCode = "204", description = "리스트가 비어있는 경우"), + @ApiResponse(responseCode = "500", description = "서버 오류") + }) + @GetMapping("/api/rank/senior") + public ResponseEntity> getSeniorRank() { + List rank = memberService.getSeniorRank(); + if (rank.isEmpty()) return ResponseEntity.noContent().build(); + return ResponseEntity.ok().body(rank); + } +} diff --git a/src/main/java/com/powersupply/PES/member/dto/MemberDTO.java b/src/main/java/com/powersupply/PES/member/dto/MemberDTO.java new file mode 100644 index 0000000..5e104a7 --- /dev/null +++ b/src/main/java/com/powersupply/PES/member/dto/MemberDTO.java @@ -0,0 +1,151 @@ +package com.powersupply.PES.member.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; + +public class MemberDTO { + + @Getter + @Schema(description = "회원 가입 요청 데이터") + public static class MemberSignUpRequest { + @Schema(description = "아이디", example = "user123") + private String memberId; // 아이디 + + @Schema(description = "비밀번호", example = "password123") + private String memberPw; // 멤버 비밀번호 + + @Schema(description = "이름", example = "홍길동") + private String memberName; // 이름 + + @Schema(description = "기수", example = "1") + private int memberGen; // 기수 + + @Schema(description = "학과", example = "컴퓨터공학과") + private String memberMajor; // 학과 + + @Schema(description = "전화번호", example = "010-1234-5678") + private String memberPhone; // 전화번호 + + @Schema(description = "이메일", example = "user@example.com") + private String memberEmail; // 이메일 + } + + @Getter + @Schema(description = "회원 로그인 요청 데이터") + public static class MemberSignInRequest { + @Schema(description = "아이디", example = "user123") + private String memberId; // 아이디 + + @Schema(description = "비밀번호", example = "password123") + private String memberPw; // 비밀번호 + } + + @Builder + @Getter + @Schema(description = "마이페이지 응답 데이터") + public static class MemberMyPageResponse { + @Schema(description = "이름", example = "홍길동") + private String memberName; // 이름 + + @Schema(description = "기수", example = "1") + private int memberGen; // 기수 + + @Schema(description = "학과", example = "컴퓨터공학과") + private String memberMajor; // 학과 + + @Schema(description = "전화번호", example = "010-1234-5678") + private String memberPhone; // 전화번호 + + @Schema(description = "상태", example = "재학생") + private String memberStatus; // 상태 + + @Schema(description = "이메일", example = "user@example.com") + private String memberEmail; // 이메일 + + @Schema(description = "점수", example = "100") + private int memberScore; // 점수 + + @Schema(description = "아이디", example = "user123") + private String memberId; // 아이디 + } + + @Getter + @Builder + @Schema(description = "이름과 점수 응답 데이터") + public static class NameScoreResponse { + @Schema(description = "이름", example = "홍길동") + private String memberName; + + @Schema(description = "상태", example = "재학생") + private String memberStatus; + + @Schema(description = "점수", example = "100") + private int memberScore; + + @Schema(description = "기수", example = "1") + private int memberGen; + + @Schema(description = "새로운 공지 여부", example = "true") + private boolean hasNewNotices; + } + + @Getter + @Builder + @Schema(description = "내가 푼 문제 응답 데이터") + public static class MemberMySolveResponse { + @Schema(description = "문제 ID", example = "1") + private Long problemId; + + @Schema(description = "문제 제목", example = "문제 제목 예시") + private String problemTitle; + + @Schema(description = "문제 점수", example = "10") + private int problemScore; + + @Schema(description = "답변 ID", example = "1") + private Long answerId; + + @Schema(description = "답변 상태", example = "완료") + private String answerState; + + @Schema(description = "최종 점수", example = "10") + private int finalScore; + } + + @Getter + @Builder + @Schema(description = "내가 받은 피드백 응답 데이터") + public static class MemberMyFeedbackResponse { + @Schema(description = "답변 ID", example = "1") + private Long answerId; + + @Schema(description = "기수", example = "1") + private int memberGen; + + @Schema(description = "이름", example = "홍길동") + private String memberName; + + @Schema(description = "피드백 통과 여부", example = "1") + private int commentPassFail; + + @Schema(description = "피드백 내용", example = "잘했습니다.") + private String commentContent; + } + + @Getter + @Builder + @Setter + @Schema(description = "랭킹 데이터") + public static class Rank { + @Schema(description = "순위", example = "1") + private int rank; + + @Schema(description = "이름", example = "홍길동") + private String memberName; + + @Schema(description = "점수", example = "100") + private int score; + } +} diff --git a/src/main/java/com/powersupply/PES/domain/entity/MemberEntity.java b/src/main/java/com/powersupply/PES/member/entity/MemberEntity.java similarity index 86% rename from src/main/java/com/powersupply/PES/domain/entity/MemberEntity.java rename to src/main/java/com/powersupply/PES/member/entity/MemberEntity.java index 9e82f87..2a3be24 100644 --- a/src/main/java/com/powersupply/PES/domain/entity/MemberEntity.java +++ b/src/main/java/com/powersupply/PES/member/entity/MemberEntity.java @@ -1,5 +1,8 @@ -package com.powersupply.PES.domain.entity; +package com.powersupply.PES.member.entity; +import com.powersupply.PES.comment.entity.CommentEntity; +import com.powersupply.PES.answer.entity.AnswerEntity; +import com.powersupply.PES.entity.BaseEntity; import lombok.*; import javax.persistence.*; diff --git a/src/main/java/com/powersupply/PES/repository/MemberRepository.java b/src/main/java/com/powersupply/PES/member/repository/MemberRepository.java similarity index 87% rename from src/main/java/com/powersupply/PES/repository/MemberRepository.java rename to src/main/java/com/powersupply/PES/member/repository/MemberRepository.java index 8a08d43..6993f94 100644 --- a/src/main/java/com/powersupply/PES/repository/MemberRepository.java +++ b/src/main/java/com/powersupply/PES/member/repository/MemberRepository.java @@ -1,6 +1,6 @@ -package com.powersupply.PES.repository; +package com.powersupply.PES.member.repository; -import com.powersupply.PES.domain.entity.MemberEntity; +import com.powersupply.PES.member.entity.MemberEntity; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; diff --git a/src/main/java/com/powersupply/PES/service/MemberService.java b/src/main/java/com/powersupply/PES/member/service/MemberService.java similarity index 94% rename from src/main/java/com/powersupply/PES/service/MemberService.java rename to src/main/java/com/powersupply/PES/member/service/MemberService.java index c52164a..5a0f85e 100644 --- a/src/main/java/com/powersupply/PES/service/MemberService.java +++ b/src/main/java/com/powersupply/PES/member/service/MemberService.java @@ -1,13 +1,17 @@ -package com.powersupply.PES.service; - -import com.powersupply.PES.domain.dto.MemberDTO; -import com.powersupply.PES.domain.entity.AnswerEntity; -import com.powersupply.PES.domain.entity.CommentEntity; -import com.powersupply.PES.domain.entity.MemberEntity; +package com.powersupply.PES.member.service; + +import com.powersupply.PES.answer.repository.AnswerRepository; +import com.powersupply.PES.comment.repository.CommentRepository; +import com.powersupply.PES.member.dto.MemberDTO; +import com.powersupply.PES.answer.entity.AnswerEntity; +import com.powersupply.PES.comment.entity.CommentEntity; +import com.powersupply.PES.member.entity.MemberEntity; import com.powersupply.PES.exception.AppException; import com.powersupply.PES.exception.ErrorCode; import com.powersupply.PES.mapper.MapperUtils; -import com.powersupply.PES.repository.*; +import com.powersupply.PES.member.repository.MemberRepository; +import com.powersupply.PES.notice.repository.MemberNoticeRepository; +import com.powersupply.PES.notice.repository.NoticeRepository; import com.powersupply.PES.utils.JwtUtil; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Value; @@ -19,7 +23,6 @@ import java.time.LocalDateTime; import java.time.temporal.ChronoUnit; import java.util.ArrayList; -import java.util.Comparator; import java.util.List; import java.util.stream.Collectors; diff --git a/src/main/java/com/powersupply/PES/notice/controller/NoticeController.java b/src/main/java/com/powersupply/PES/notice/controller/NoticeController.java new file mode 100644 index 0000000..de39a74 --- /dev/null +++ b/src/main/java/com/powersupply/PES/notice/controller/NoticeController.java @@ -0,0 +1,96 @@ +package com.powersupply.PES.notice.controller; + +import com.powersupply.PES.notice.dto.NoticeDTO; +import com.powersupply.PES.notice.service.NoticeService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequiredArgsConstructor +@Tag(name = "Notice", description = "공지사항 관련 API") +public class NoticeController { + + private final NoticeService noticeService; + + @Operation(summary = "새로운 공지사항 존재 확인", description = "비회원 전용 API로 새로운 공지사항이 있는지 확인합니다.") + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "정상적으로 통신 완료"), + @ApiResponse(responseCode = "500", description = "서버 오류") + }) + @GetMapping("/api/notice/new") + public ResponseEntity checkNewNotice() { + return noticeService.checkNewNotice(); + } + + @Operation(summary = "공지사항 등록", description = "새로운 공지사항을 등록합니다.") + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "공지사항 등록 성공"), + @ApiResponse(responseCode = "403", description = "관리자 권한이 없는 경우"), + @ApiResponse(responseCode = "500", description = "서버 오류") + }) + @PostMapping("/api/notice") + public ResponseEntity postNotice(@RequestBody NoticeDTO.BaseNotice dto) { + return noticeService.postNotice(dto); + } + + @Operation(summary = "공지사항 리스트 가져오기", description = "공지사항 리스트를 가져옵니다. state가 true이면 삭제된 공지사항을 포함합니다.") + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "조회 성공"), + @ApiResponse(responseCode = "204", description = "공지사항이 없는 경우"), + @ApiResponse(responseCode = "500", description = "서버 오류") + }) + @GetMapping("/api/notice") + public ResponseEntity getNoticeList(@RequestParam(required = false) boolean state) { + return noticeService.getNoticeList(state); + } + + @Operation(summary = "공지사항 내용 가져오기", description = "특정 공지사항의 내용을 가져옵니다.") + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "조회 성공"), + @ApiResponse(responseCode = "404", description = "해당 noticeId가 없을 경우"), + @ApiResponse(responseCode = "500", description = "서버 오류") + }) + @GetMapping("/api/notice/{noticeId}") + public ResponseEntity getNotice(@PathVariable Long noticeId) { + return noticeService.getNotice(noticeId); + } + + @Operation(summary = "공지사항 수정", description = "특정 공지사항의 내용을 수정합니다.") + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "수정 성공"), + @ApiResponse(responseCode = "403", description = "관리자 권한이 없는 경우"), + @ApiResponse(responseCode = "404", description = "해당 noticeId가 없을 경우"), + @ApiResponse(responseCode = "500", description = "서버 오류") + }) + @PatchMapping("/api/notice/{noticeId}") + public ResponseEntity updateNotice(@PathVariable Long noticeId, @RequestBody NoticeDTO.BaseNotice dto) { + return noticeService.updateNotice(noticeId, dto); + } + + @Operation(summary = "공지사항 복구", description = "삭제된 공지사항을 복구합니다.") + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "복구 성공"), + @ApiResponse(responseCode = "404", description = "해당 noticeId가 없을 경우"), + @ApiResponse(responseCode = "500", description = "서버 오류") + }) + @PatchMapping("/api/notice/restore/{noticeId}") + public ResponseEntity restoreNotice(@PathVariable Long noticeId) { + return noticeService.restoreNotice(noticeId); + } + + @Operation(summary = "공지사항 삭제", description = "특정 공지사항을 삭제합니다.") + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "삭제 성공"), + @ApiResponse(responseCode = "403", description = "관리자 권한이 없는 경우"), + @ApiResponse(responseCode = "500", description = "서버 오류") + }) + @DeleteMapping("/api/notice/{noticeId}") + public ResponseEntity deleteNotice(@PathVariable Long noticeId) { + return noticeService.deleteNotice(noticeId); + } +} diff --git a/src/main/java/com/powersupply/PES/domain/dto/NoticeDTO.java b/src/main/java/com/powersupply/PES/notice/dto/NoticeDTO.java similarity index 97% rename from src/main/java/com/powersupply/PES/domain/dto/NoticeDTO.java rename to src/main/java/com/powersupply/PES/notice/dto/NoticeDTO.java index 3f8ef9d..0e0d677 100644 --- a/src/main/java/com/powersupply/PES/domain/dto/NoticeDTO.java +++ b/src/main/java/com/powersupply/PES/notice/dto/NoticeDTO.java @@ -1,4 +1,4 @@ -package com.powersupply.PES.domain.dto; +package com.powersupply.PES.notice.dto; import com.fasterxml.jackson.annotation.JsonFormat; import lombok.Builder; diff --git a/src/main/java/com/powersupply/PES/domain/entity/MemberNoticeEntity.java b/src/main/java/com/powersupply/PES/notice/entity/MemberNoticeEntity.java similarity index 76% rename from src/main/java/com/powersupply/PES/domain/entity/MemberNoticeEntity.java rename to src/main/java/com/powersupply/PES/notice/entity/MemberNoticeEntity.java index 9607494..e74186e 100644 --- a/src/main/java/com/powersupply/PES/domain/entity/MemberNoticeEntity.java +++ b/src/main/java/com/powersupply/PES/notice/entity/MemberNoticeEntity.java @@ -1,5 +1,7 @@ -package com.powersupply.PES.domain.entity; +package com.powersupply.PES.notice.entity; +import com.powersupply.PES.member.entity.MemberEntity; +import com.powersupply.PES.notice.entity.NoticeEntity; import lombok.*; import javax.persistence.*; diff --git a/src/main/java/com/powersupply/PES/domain/entity/NoticeEntity.java b/src/main/java/com/powersupply/PES/notice/entity/NoticeEntity.java similarity index 89% rename from src/main/java/com/powersupply/PES/domain/entity/NoticeEntity.java rename to src/main/java/com/powersupply/PES/notice/entity/NoticeEntity.java index 23cff42..c11c014 100644 --- a/src/main/java/com/powersupply/PES/domain/entity/NoticeEntity.java +++ b/src/main/java/com/powersupply/PES/notice/entity/NoticeEntity.java @@ -1,5 +1,6 @@ -package com.powersupply.PES.domain.entity; +package com.powersupply.PES.notice.entity; +import com.powersupply.PES.member.entity.MemberEntity; import lombok.*; import org.hibernate.annotations.CreationTimestamp; diff --git a/src/main/java/com/powersupply/PES/repository/MemberNoticeRepository.java b/src/main/java/com/powersupply/PES/notice/repository/MemberNoticeRepository.java similarity index 76% rename from src/main/java/com/powersupply/PES/repository/MemberNoticeRepository.java rename to src/main/java/com/powersupply/PES/notice/repository/MemberNoticeRepository.java index d7aff6e..ce93895 100644 --- a/src/main/java/com/powersupply/PES/repository/MemberNoticeRepository.java +++ b/src/main/java/com/powersupply/PES/notice/repository/MemberNoticeRepository.java @@ -1,6 +1,6 @@ -package com.powersupply.PES.repository; +package com.powersupply.PES.notice.repository; -import com.powersupply.PES.domain.entity.MemberNoticeEntity; +import com.powersupply.PES.notice.entity.MemberNoticeEntity; import org.springframework.data.jpa.repository.JpaRepository; import java.util.List; diff --git a/src/main/java/com/powersupply/PES/repository/NoticeRepository.java b/src/main/java/com/powersupply/PES/notice/repository/NoticeRepository.java similarity index 58% rename from src/main/java/com/powersupply/PES/repository/NoticeRepository.java rename to src/main/java/com/powersupply/PES/notice/repository/NoticeRepository.java index 53bad49..d48e7ef 100644 --- a/src/main/java/com/powersupply/PES/repository/NoticeRepository.java +++ b/src/main/java/com/powersupply/PES/notice/repository/NoticeRepository.java @@ -1,6 +1,6 @@ -package com.powersupply.PES.repository; +package com.powersupply.PES.notice.repository; -import com.powersupply.PES.domain.entity.NoticeEntity; +import com.powersupply.PES.notice.entity.NoticeEntity; import org.springframework.data.jpa.repository.JpaRepository; public interface NoticeRepository extends JpaRepository { diff --git a/src/main/java/com/powersupply/PES/service/NoticeService.java b/src/main/java/com/powersupply/PES/notice/service/NoticeService.java similarity index 94% rename from src/main/java/com/powersupply/PES/service/NoticeService.java rename to src/main/java/com/powersupply/PES/notice/service/NoticeService.java index 0720220..5476833 100644 --- a/src/main/java/com/powersupply/PES/service/NoticeService.java +++ b/src/main/java/com/powersupply/PES/notice/service/NoticeService.java @@ -1,14 +1,14 @@ -package com.powersupply.PES.service; +package com.powersupply.PES.notice.service; -import com.powersupply.PES.domain.dto.NoticeDTO; -import com.powersupply.PES.domain.entity.MemberEntity; -import com.powersupply.PES.domain.entity.MemberNoticeEntity; -import com.powersupply.PES.domain.entity.NoticeEntity; +import com.powersupply.PES.notice.dto.NoticeDTO; +import com.powersupply.PES.member.entity.MemberEntity; +import com.powersupply.PES.notice.entity.MemberNoticeEntity; +import com.powersupply.PES.notice.entity.NoticeEntity; import com.powersupply.PES.exception.AppException; import com.powersupply.PES.exception.ErrorCode; -import com.powersupply.PES.repository.MemberNoticeRepository; -import com.powersupply.PES.repository.MemberRepository; -import com.powersupply.PES.repository.NoticeRepository; +import com.powersupply.PES.notice.repository.MemberNoticeRepository; +import com.powersupply.PES.member.repository.MemberRepository; +import com.powersupply.PES.notice.repository.NoticeRepository; import com.powersupply.PES.utils.JwtUtil; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; diff --git a/src/main/java/com/powersupply/PES/problem/controller/ProblemController.java b/src/main/java/com/powersupply/PES/problem/controller/ProblemController.java new file mode 100644 index 0000000..681c15b --- /dev/null +++ b/src/main/java/com/powersupply/PES/problem/controller/ProblemController.java @@ -0,0 +1,33 @@ +package com.powersupply.PES.problem.controller; + +import com.powersupply.PES.problem.service.ProblemService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequiredArgsConstructor +@Tag(name = "Problem", description = "문제 관련 API") +public class ProblemController { + + private final ProblemService problemService; + + @Operation(summary = "문제 리스트 가져오기", description = "전체 문제 리스트를 가져옵니다.") + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "토큰에 문제가 없고 유저를 DB에서 찾을 수 있음\n" + + "- 로그인 되어있고, 해당 문제를 푼 경우 → answerId 및 answerState를 포함하여 전송\n" + + "- 나머지 경우 → answerId 및 answerState는 null로 전송\n" + + "- answerScore가 없은 경우는 null로 전송 → 타입도 Integer임"), + @ApiResponse(responseCode = "204", description = "리스트가 비어있는 경우"), + @ApiResponse(responseCode = "500", description = "서버 오류") + }) + @GetMapping("/api/problemlist") + public ResponseEntity getProblemList() { + return problemService.getProblemList(); + } +} diff --git a/src/main/java/com/powersupply/PES/domain/dto/ProblemDTO.java b/src/main/java/com/powersupply/PES/problem/dto/ProblemDTO.java similarity index 91% rename from src/main/java/com/powersupply/PES/domain/dto/ProblemDTO.java rename to src/main/java/com/powersupply/PES/problem/dto/ProblemDTO.java index de836d6..51f2ec7 100644 --- a/src/main/java/com/powersupply/PES/domain/dto/ProblemDTO.java +++ b/src/main/java/com/powersupply/PES/problem/dto/ProblemDTO.java @@ -1,4 +1,4 @@ -package com.powersupply.PES.domain.dto; +package com.powersupply.PES.problem.dto; import lombok.Builder; import lombok.Getter; diff --git a/src/main/java/com/powersupply/PES/domain/entity/ProblemEntity.java b/src/main/java/com/powersupply/PES/problem/entity/ProblemEntity.java similarity index 85% rename from src/main/java/com/powersupply/PES/domain/entity/ProblemEntity.java rename to src/main/java/com/powersupply/PES/problem/entity/ProblemEntity.java index 907e10c..8fd013e 100644 --- a/src/main/java/com/powersupply/PES/domain/entity/ProblemEntity.java +++ b/src/main/java/com/powersupply/PES/problem/entity/ProblemEntity.java @@ -1,7 +1,8 @@ -package com.powersupply.PES.domain.entity; +package com.powersupply.PES.problem.entity; +import com.powersupply.PES.answer.entity.AnswerEntity; +import com.powersupply.PES.question.entity.QuestionEntity; import lombok.*; -import org.springframework.lang.Nullable; import javax.persistence.*; import java.util.ArrayList; diff --git a/src/main/java/com/powersupply/PES/repository/ProblemRepository.java b/src/main/java/com/powersupply/PES/problem/repository/ProblemRepository.java similarity index 86% rename from src/main/java/com/powersupply/PES/repository/ProblemRepository.java rename to src/main/java/com/powersupply/PES/problem/repository/ProblemRepository.java index 15693c8..50bbad4 100644 --- a/src/main/java/com/powersupply/PES/repository/ProblemRepository.java +++ b/src/main/java/com/powersupply/PES/problem/repository/ProblemRepository.java @@ -1,12 +1,11 @@ -package com.powersupply.PES.repository; +package com.powersupply.PES.problem.repository; -import com.powersupply.PES.domain.entity.ProblemEntity; +import com.powersupply.PES.problem.entity.ProblemEntity; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; import java.util.List; -import java.util.Optional; public interface ProblemRepository extends JpaRepository { @Query("SELECT p, a FROM ProblemEntity p LEFT JOIN p.answerEntities a ON a.memberEntity.memberId = :id") diff --git a/src/main/java/com/powersupply/PES/service/ProblemService.java b/src/main/java/com/powersupply/PES/problem/service/ProblemService.java similarity index 88% rename from src/main/java/com/powersupply/PES/service/ProblemService.java rename to src/main/java/com/powersupply/PES/problem/service/ProblemService.java index 19a642a..0357cfe 100644 --- a/src/main/java/com/powersupply/PES/service/ProblemService.java +++ b/src/main/java/com/powersupply/PES/problem/service/ProblemService.java @@ -1,9 +1,9 @@ -package com.powersupply.PES.service; +package com.powersupply.PES.problem.service; -import com.powersupply.PES.domain.dto.ProblemDTO; -import com.powersupply.PES.domain.entity.AnswerEntity; -import com.powersupply.PES.domain.entity.ProblemEntity; -import com.powersupply.PES.repository.ProblemRepository; +import com.powersupply.PES.problem.dto.ProblemDTO; +import com.powersupply.PES.answer.entity.AnswerEntity; +import com.powersupply.PES.problem.entity.ProblemEntity; +import com.powersupply.PES.problem.repository.ProblemRepository; import com.powersupply.PES.utils.JwtUtil; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; diff --git a/src/main/java/com/powersupply/PES/question/controller/QuestionController.java b/src/main/java/com/powersupply/PES/question/controller/QuestionController.java new file mode 100644 index 0000000..e2d70a8 --- /dev/null +++ b/src/main/java/com/powersupply/PES/question/controller/QuestionController.java @@ -0,0 +1,34 @@ +package com.powersupply.PES.question.controller; + +import com.powersupply.PES.question.dto.QuestionDTO; +import com.powersupply.PES.question.service.QuestionService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; + +import java.util.List; + +@Controller +@RequiredArgsConstructor +@Tag(name = "Question", description = "질문 관련 API") +public class QuestionController { + + private final QuestionService questionService; + + @Operation(summary = "(재)질문 목록 보기", description = "특정 문제에 대한 (재)질문 목록을 가져옵니다.") + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "조회 성공"), + @ApiResponse(responseCode = "404", description = "해당 problemId가 없을 경우"), + @ApiResponse(responseCode = "500", description = "서버 오류") + }) + @GetMapping("/api/questions/{problemId}") + public ResponseEntity> getQuestionList(@PathVariable Long problemId) { + return ResponseEntity.ok().body(questionService.getQeustionList(problemId)); + } +} diff --git a/src/main/java/com/powersupply/PES/domain/dto/QuestionDTO.java b/src/main/java/com/powersupply/PES/question/dto/QuestionDTO.java similarity index 88% rename from src/main/java/com/powersupply/PES/domain/dto/QuestionDTO.java rename to src/main/java/com/powersupply/PES/question/dto/QuestionDTO.java index a152409..438ad11 100644 --- a/src/main/java/com/powersupply/PES/domain/dto/QuestionDTO.java +++ b/src/main/java/com/powersupply/PES/question/dto/QuestionDTO.java @@ -1,4 +1,4 @@ -package com.powersupply.PES.domain.dto; +package com.powersupply.PES.question.dto; import lombok.Builder; import lombok.Getter; diff --git a/src/main/java/com/powersupply/PES/domain/entity/QuestionEntity.java b/src/main/java/com/powersupply/PES/question/entity/QuestionEntity.java similarity index 83% rename from src/main/java/com/powersupply/PES/domain/entity/QuestionEntity.java rename to src/main/java/com/powersupply/PES/question/entity/QuestionEntity.java index f197d11..e54283d 100644 --- a/src/main/java/com/powersupply/PES/domain/entity/QuestionEntity.java +++ b/src/main/java/com/powersupply/PES/question/entity/QuestionEntity.java @@ -1,5 +1,8 @@ -package com.powersupply.PES.domain.entity; +package com.powersupply.PES.question.entity; +import com.powersupply.PES.answer.entity.AnswerEntity; +import com.powersupply.PES.entity.BaseEntity; +import com.powersupply.PES.problem.entity.ProblemEntity; import lombok.*; import org.hibernate.annotations.CreationTimestamp; diff --git a/src/main/java/com/powersupply/PES/repository/QuestionRepository.java b/src/main/java/com/powersupply/PES/question/repository/QuestionRepository.java similarity index 69% rename from src/main/java/com/powersupply/PES/repository/QuestionRepository.java rename to src/main/java/com/powersupply/PES/question/repository/QuestionRepository.java index 69ab6c7..9dfed56 100644 --- a/src/main/java/com/powersupply/PES/repository/QuestionRepository.java +++ b/src/main/java/com/powersupply/PES/question/repository/QuestionRepository.java @@ -1,6 +1,6 @@ -package com.powersupply.PES.repository; +package com.powersupply.PES.question.repository; -import com.powersupply.PES.domain.entity.QuestionEntity; +import com.powersupply.PES.question.entity.QuestionEntity; import org.springframework.data.jpa.repository.JpaRepository; import java.util.List; diff --git a/src/main/java/com/powersupply/PES/service/QuestionService.java b/src/main/java/com/powersupply/PES/question/service/QuestionService.java similarity index 84% rename from src/main/java/com/powersupply/PES/service/QuestionService.java rename to src/main/java/com/powersupply/PES/question/service/QuestionService.java index e748757..df96a02 100644 --- a/src/main/java/com/powersupply/PES/service/QuestionService.java +++ b/src/main/java/com/powersupply/PES/question/service/QuestionService.java @@ -1,8 +1,8 @@ -package com.powersupply.PES.service; +package com.powersupply.PES.question.service; -import com.powersupply.PES.domain.dto.QuestionDTO; -import com.powersupply.PES.domain.entity.QuestionEntity; -import com.powersupply.PES.repository.QuestionRepository; +import com.powersupply.PES.question.dto.QuestionDTO; +import com.powersupply.PES.question.entity.QuestionEntity; +import com.powersupply.PES.question.repository.QuestionRepository; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; diff --git a/src/main/java/com/powersupply/PES/swagger/SwaggerConfig.java b/src/main/java/com/powersupply/PES/swagger/SwaggerConfig.java new file mode 100644 index 0000000..a6f074f --- /dev/null +++ b/src/main/java/com/powersupply/PES/swagger/SwaggerConfig.java @@ -0,0 +1,35 @@ +package com.powersupply.PES.swagger; + +import io.swagger.v3.oas.models.info.Info; +import io.swagger.v3.oas.models.Components; +import io.swagger.v3.oas.models.security.SecurityRequirement; +import io.swagger.v3.oas.models.security.SecurityScheme; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import io.swagger.v3.oas.models.OpenAPI; + +@Configuration +public class SwaggerConfig { + @Bean + public OpenAPI openAPI() { + String jwt = "JWT"; + SecurityRequirement securityRequirement = new SecurityRequirement().addList(jwt); + Components components = new Components().addSecuritySchemes(jwt, new SecurityScheme() + .name(jwt) + .type(SecurityScheme.Type.HTTP) + .scheme("bearer") + .bearerFormat("JWT") + ); + return new OpenAPI() + .info(apiInfo()) + .addSecurityItem(securityRequirement) + .components(components); + } + + private Info apiInfo() { + return new Info() + .title("PES") + .description("PES API 명세") + .version("1.0.0"); + } +}