diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4f0d27e --- /dev/null +++ b/.gitignore @@ -0,0 +1,36 @@ +HELP.md +target/ +.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ +/.metadata/ + +.class \ No newline at end of file diff --git a/.project b/.project deleted file mode 100644 index e40ff58..0000000 --- a/.project +++ /dev/null @@ -1,11 +0,0 @@ - - - Backend - - - - - - - - diff --git a/README.md b/README.md index 5e97794..bac0e4c 100644 --- a/README.md +++ b/README.md @@ -1 +1,72 @@ -# Backend \ No newline at end of file +# BangBang Codeboy (정보처리기사/SQLD 스피드 퀴즈 플랫폼) + +## 1. 프로젝트 개요 +- 정보처리기사와 sqld를 공부의 범위가 방대하여 공부에 어려움을 겪는 자격증 준비생 혹은 SSAFY생을 위해 제작. +- 각 자격증에서 배우는 내용을 쉽게 복습할 수 있는 시스템을 만든다. +- 반 친구들과 스피드 퀴즈 대결을 하며 재미있게 학습한다. +- 랭킹 기능을 제공하여 학습의 성취감을 얻는다. + +## 2. 핵심 기능 +- 정보처리기사와 sqld 관련 키워드를 활용한 4지선다 퀴즈 제공. +- 퀴즈방 생성 및 최대 20명과 실시간 대결. +- 오답노트 및 해설 제공. +- 유저 제작 문제 생성/수정/삭제. +- 리더보드 랭킹. +- 유저가 직접 문제를 제작 가능. +- 혹은 ChatGPT, Gemini로 문제를 자동으로 생성하고 해설을 제공. + +## 3. Tech Stack & 아키텍처 + image +
+ + - Spring, MySQL, MyBatis, Fast API -> 백엔드 API 개발과 데이터베이스, 외부 LLM 호출 서버 제작을 맡고 있음. + + +## 4. 시스템 설계 문서 +- [ERD] + 관통프로젝트 + +- [유즈케이스 다이어그램] + image + +- [기능 명세서 (구글 스프레드시트)](https://docs.google.com/spreadsheets/d/1pif1EVEQNDvyv444s-4GoqX6n816fivUlyM2uHmzNT8/edit?gid=0#gid=0) +- [WBS & 일정관리(Notion)](https://pointy-harpymimus-0cb.notion.site/2a76fbddf57780169009ed2301511ee1) +- [목업/화면 설계(Notion)](https://pointy-harpymimus-0cb.notion.site/2ab6fbddf57781839d13f9224dfa7a9c) + + +## 5. API 시나리오 테스트 +### 테스트 시나리오 +1. 모든 테이블의 데이터들만 삭제. +2. 회원가입 + 해당 유저의 점수 등록. +3. 로그인(전단계에서 회원가입한 유저의 id와 password를 이용). +4. 문제세트 전체조회. +5. 로그인한 회원이 만든 유저제작문제세트 조회. +6. 로그인한 회원이 유저제작문제 3개 추가. +7. 로그인한 회원이 유저제작문제 하나 수정. +8. 유저제작 문제의 댓글 조회. +9. 로그인한 회원이 댓글 3개 작성. +10. 로그인한 회원이 댓글 하나 수정. +11. 로그인한 회원이 댓글 삭제. +12. 로그인한 회원이 문제 세트 삭제. +13. 모든 회원들의 점수 조회. +14. 로그인한 회원 한명의 점수를 조회. +15. 로그인한 회원 한명의 점수를 수정. +16. 문제테이블(problem테이블) 에서 문제 전체 조회. +17. 문제테이블(problem테이블) 에서 문제 전체 조회. +18. 로그인한 회원의 오답노트에 문제 추가 후 로그인한 회원의 오답노트 안의 전체 문제 조회. +19. 로그인한 회원의 오답노트에 있는 문제 삭제(problem과 user_problem테이블에 있는 문제도 삭제되면 안됨). +20. 모든 퀴즈방 목록 조회. +21. 로그인한 회원이 퀴즈방을 생성하고 방장이 됨(isHost = 1). +22. 또 다른 mcp서버가 localhost:8082에서 가동되며 같은 DB 다른 회원을 가입하고 로그인 전체 퀴즈방들을 조회하여 조회된 퀴즈방에 들어가있는 회원들을 조회하고 isHost=0인 방장이 아닌 상태로 다른 방장이 만든 퀴즈방에 들어가기. +23. 방장인 회원이 퀴즈방을 삭제. + +### 테스트 방법 +1. html에 자바스크립트를 작성 후 axios 통신을 통해 Vite 프록시 서버에 요청을 보낸 후 응답을 console.log로 출력. +2. 테스트 결과를 testResultYYYYMMDDHH형식으로 다운로드. +image + + + +## 6. 향후 개선 계획 +- DeepseekOCR을 활용하여 개념요약 PDF -> 텍스트 -> 문제 제작 Flow 개발 / Fast API로 외부 LLM 호출하여 문제 생성 및 해설 기능 개발. +- API 테스트 파이프라인 제작. diff --git a/codeBoy_backend/.gitignore b/codeBoy_backend/.gitignore index 667aaef..4f0d27e 100644 --- a/codeBoy_backend/.gitignore +++ b/codeBoy_backend/.gitignore @@ -31,3 +31,6 @@ build/ ### VS Code ### .vscode/ +/.metadata/ + +.class \ No newline at end of file diff --git a/codeBoy_backend/mvnw b/codeBoy_backend/mvnw old mode 100644 new mode 100755 diff --git a/codeBoy_backend/pom.xml b/codeBoy_backend/pom.xml index aa498cf..29abeb6 100644 --- a/codeBoy_backend/pom.xml +++ b/codeBoy_backend/pom.xml @@ -29,57 +29,69 @@ 17 - - - org.springframework.boot - spring-boot-starter-web - - - org.mybatis.spring.boot - mybatis-spring-boot-starter - 3.0.5 - - - org.springframework.boot - spring-boot-devtools - runtime - true - - - com.mysql - mysql-connector-j - runtime - - - org.springframework.boot - spring-boot-starter-test - test - - - org.mybatis.spring.boot - mybatis-spring-boot-starter-test - 3.0.5 - test - - - org.springdoc - springdoc-openapi-starter-webmvc-ui - 2.3.0 - - - org.springframework.boot - spring-boot-starter - - - org.projectlombok - lombok - true - - + + com.mysql mysql-connector-j runtime + + org.springframework.boot + spring-boot-starter-web + - + + org.mybatis.spring.boot + mybatis-spring-boot-starter + 3.0.5 + + + + org.springframework.boot + spring-boot-starter-security + + + + io.jsonwebtoken + jjwt-api + 0.12.3 + + + io.jsonwebtoken + jjwt-impl + 0.12.3 + runtime + + + io.jsonwebtoken + jjwt-jackson + 0.12.3 + runtime + + + + org.springdoc + springdoc-openapi-starter-webmvc-ui + 2.3.0 + + + + org.projectlombok + lombok + 1.18.42 + + + + org.flywaydb + flyway-core + + + org.flywaydb + flyway-mysql + + + + + + org.springframework.boot diff --git a/codeBoy_backend/src/main/java/com/codeboy/mvc/config/MyBatisConfig.java b/codeBoy_backend/src/main/java/com/codeboy/mvc/config/MyBatisConfig.java new file mode 100644 index 0000000..f657d4d --- /dev/null +++ b/codeBoy_backend/src/main/java/com/codeboy/mvc/config/MyBatisConfig.java @@ -0,0 +1,10 @@ +package com.codeboy.mvc.config; + +import org.mybatis.spring.annotation.MapperScan; +import org.springframework.context.annotation.Configuration; + +@Configuration +@MapperScan("com.codeboy.mvc.model.dao") +public class MyBatisConfig { + +} diff --git a/codeBoy_backend/src/main/java/com/codeboy/mvc/config/SecurityConfig.java b/codeBoy_backend/src/main/java/com/codeboy/mvc/config/SecurityConfig.java new file mode 100644 index 0000000..300ec84 --- /dev/null +++ b/codeBoy_backend/src/main/java/com/codeboy/mvc/config/SecurityConfig.java @@ -0,0 +1,105 @@ +package com.codeboy.mvc.config; + +import com.codeboy.mvc.jwt.JWTFilter; +import com.codeboy.mvc.jwt.JWTUtil; +import com.codeboy.mvc.jwt.LoginFilter; +import com.codeboy.mvc.model.service.CustomerUserDetailService; +import lombok.RequiredArgsConstructor; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; +import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.cors.CorsConfigurationSource; +import org.springframework.web.cors.UrlBasedCorsConfigurationSource; + +import java.util.List; + +@RequiredArgsConstructor +@EnableWebSecurity +@Configuration +public class SecurityConfig { + + private final CustomerUserDetailService customerUserDetailService; + private final JWTUtil jwtUtil; + private final com.fasterxml.jackson.databind.ObjectMapper objectMapper; // + + + @Bean + public BCryptPasswordEncoder bCryptPasswordEncoder() { + return new BCryptPasswordEncoder(); + } + + @Bean + public AuthenticationManager authenticationManager(HttpSecurity http) throws Exception { + var builder = http.getSharedObject(org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder.class); + + builder + .userDetailsService(customerUserDetailService) + .passwordEncoder(bCryptPasswordEncoder()); + + return builder.build(); + } + + @Bean + public SecurityFilterChain securityFilterChain(HttpSecurity http, + AuthenticationManager authenticationManager) throws Exception { + + http + .formLogin(AbstractHttpConfigurer::disable) + .httpBasic(AbstractHttpConfigurer::disable) + .csrf(AbstractHttpConfigurer::disable) + .cors(cors -> cors.configurationSource(corsConfigurationSource())); + + + http + .authorizeHttpRequests(auth -> auth + .requestMatchers("/", "/login", "/api/join", "/swagger-ui/**", + "/v3/api-docs/**", + "/api-docs/**", + "/swagger-resources/**").permitAll() + .requestMatchers("/admin/**").hasRole("ADMIN") // ✅ 보통 이렇게 씀 + .anyRequest().authenticated() + ); + + http + .sessionManagement(session -> + session.sessionCreationPolicy(SessionCreationPolicy.STATELESS) + ); + + http.addFilterAt( + new LoginFilter(authenticationManager, jwtUtil, objectMapper), + UsernamePasswordAuthenticationFilter.class + ); + + http.addFilterBefore( + new JWTFilter(jwtUtil, customerUserDetailService), + UsernamePasswordAuthenticationFilter.class + ); + + return http.build(); + } + @Bean + public CorsConfigurationSource corsConfigurationSource() { + CorsConfiguration configuration = new CorsConfiguration(); + + // 프론트 주소 허용 + configuration.setAllowedOrigins(List.of("http://localhost:5173")); + // 모든 메서드 허용 + configuration.setAllowedMethods(List.of("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS")); + // 모든 헤더 허용 + configuration.setAllowedHeaders(List.of("*")); + // 쿠키/Authorization 헤더 허용 (JWT 쓸 때 보통 true) + configuration.setAllowCredentials(true); + + UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); + source.registerCorsConfiguration("/**", configuration); + return source; + } +} diff --git a/codeBoy_backend/src/main/java/com/codeboy/mvc/controller/AdminController.java b/codeBoy_backend/src/main/java/com/codeboy/mvc/controller/AdminController.java new file mode 100644 index 0000000..e35b5da --- /dev/null +++ b/codeBoy_backend/src/main/java/com/codeboy/mvc/controller/AdminController.java @@ -0,0 +1,13 @@ +package com.codeboy.mvc.controller; + +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; + +@Controller +public class AdminController { + + @GetMapping("/admin") + public String adminP() { + return "admin Controller"; + } +} diff --git a/codeBoy_backend/src/main/java/com/codeboy/mvc/controller/CommentController.java b/codeBoy_backend/src/main/java/com/codeboy/mvc/controller/CommentController.java new file mode 100644 index 0000000..2d29f62 --- /dev/null +++ b/codeBoy_backend/src/main/java/com/codeboy/mvc/controller/CommentController.java @@ -0,0 +1,169 @@ +package com.codeboy.mvc.controller; + +import java.util.ArrayList; +import java.util.List; + +import com.codeboy.mvc.model.dto.CustomUserDetails; +import com.codeboy.mvc.model.dto.request.CommentUpdateRequest; +import com.codeboy.mvc.model.dto.response.ApiResponse; +import jakarta.servlet.http.HttpSession; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PatchMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.codeboy.mvc.model.dto.Comment; +import com.codeboy.mvc.model.service.CommentService; + +import io.swagger.v3.oas.annotations.tags.Tag; + +@RestController +@RequestMapping("/api/comments") +@Tag(name="Comment RESTful API", description = "Comment CRUD를 할 수 있는 REST API") +public class CommentController { + private final CommentService commentService; + + + @Autowired + public CommentController(CommentService commentService) { + this.commentService = commentService; + } + + + @GetMapping("{userProblemSetId}") + //숫자가 아닌 값이 userProblemSetId에 오면 스프링이 컨트롤러에 도달하기전에 400BAD_REQUEST를 보내줌 + public ResponseEntity>> getAllCommentsById(@PathVariable("userProblemSetId") long userProblemSetId){ + List comments = commentService.getAllCommentsById(userProblemSetId); + + if (comments.isEmpty()) { + //댓글이 달리지 않은 경우 + return ResponseEntity.status(HttpStatus.OK) + .body(ApiResponse.success(HttpStatus.OK, "댓글이 없습니다.", new ArrayList())); + } + //댓글 조회에 성공한 경우 + return ResponseEntity.status(HttpStatus.OK).body(ApiResponse.success(HttpStatus.OK, "댓글 조회 성공", comments)); + + } + + @PostMapping("{userProblemSetId}") + public ResponseEntity> addComment(@PathVariable long userProblemSetId, + @RequestBody Comment comment, @AuthenticationPrincipal CustomUserDetails loginUser) { + if (comment.getContent() == null || comment.getContent().isBlank()) { + return ResponseEntity.status(HttpStatus.BAD_REQUEST) + .body(ApiResponse.failure(HttpStatus.BAD_REQUEST, "댓글 내용은 비어 있을 수 없습니다.")); + } + + Long memberId = loginUser.getMemberId(); + if (memberId == null) { + // 인증 안 됨 + return ResponseEntity.status(HttpStatus.UNAUTHORIZED) + .body(ApiResponse.failure(HttpStatus.UNAUTHORIZED, "로그인이 필요합니다.")); + } + comment.setMemberId(memberId); + int result = commentService.addComment(userProblemSetId, comment); + + if (result == 0) { + return ResponseEntity.status(HttpStatus.BAD_REQUEST) + .body(ApiResponse.failure(HttpStatus.BAD_REQUEST, "잘못된 요청. 댓글 추가 실패")); + } + + return ResponseEntity.status(HttpStatus.CREATED) + .body(ApiResponse.success(HttpStatus.CREATED, "댓글 추가 성공", null)); + } + + + //리소스의 일부(content)만 수정하므로 패치매핑 + @PatchMapping("{commentId}") + public ResponseEntity> updateComment( + @PathVariable long commentId, + @RequestBody CommentUpdateRequest commentUpdateRequest, + @AuthenticationPrincipal CustomUserDetails loginUser) { + + Long memberId = loginUser.getMemberId(); + if (memberId == null) { + // 인증 안 됨 + return ResponseEntity.status(HttpStatus.UNAUTHORIZED) + .body(ApiResponse.failure(HttpStatus.UNAUTHORIZED, "로그인이 필요합니다.")); + } + commentUpdateRequest.setMemberId(memberId); + if (!memberId.equals(commentUpdateRequest.getMemberId())) { + // 인가 실패 + // 로그인 중인 회원이 자신이 작성한 댓글이 아닌 것을 수정하려할 때 + return ResponseEntity.status(HttpStatus.FORBIDDEN) + .body(ApiResponse.failure(HttpStatus.FORBIDDEN, "본인의 댓글만 수정할 수 있습니다.")); + } + + //댓글이 비어있을 때 + if (commentUpdateRequest.getContent() == null || commentUpdateRequest.getContent().isBlank()) { + return ResponseEntity.status(HttpStatus.BAD_REQUEST) + .body(ApiResponse.failure(HttpStatus.BAD_REQUEST, "수정할 댓글 내용을 입력해주세요.")); + } + + Comment comment = new Comment(); + comment.setContent(commentUpdateRequest.getContent()); + + int result = commentService.updateComment(commentId, comment); + + if (result == 0) { + return ResponseEntity.status(HttpStatus.NOT_FOUND) + .body(ApiResponse.failure(HttpStatus.NOT_FOUND, "존재하지 않는 댓글입니다.")); + + } + + return ResponseEntity.status(HttpStatus.OK) + .body(ApiResponse.success(HttpStatus.OK, "댓글 수정 성공", null)); + } + + + @DeleteMapping("{userProblemSetId}/{commentId}") + public ResponseEntity> deleteComment(@PathVariable long userProblemSetId, + @PathVariable long commentId, + @AuthenticationPrincipal CustomUserDetails loginUser) { + + Long memberId = loginUser.getMemberId(); + + // 로그인 안한 상태 + if (memberId == null) { + return ResponseEntity.status(HttpStatus.UNAUTHORIZED) + .body(ApiResponse.failure(HttpStatus.UNAUTHORIZED, "로그인이 필요합니다.")); + } + + // DB에서 댓글 작성자 ID 조회 + Long ownerId = commentService.getCommentOwnerId(commentId); + + // 댓글ID가 존재하지 않는 경우 + if (ownerId == null) { + return ResponseEntity.status(HttpStatus.NOT_FOUND) + .body(ApiResponse.failure(HttpStatus.NOT_FOUND, "존재하지 않는 댓글입니다.")); + } + + // 권한 없음 (본인 댓글 아님) + if (!memberId.equals(ownerId)) { + return ResponseEntity.status(HttpStatus.FORBIDDEN) + .body(ApiResponse.failure(HttpStatus.FORBIDDEN, "본인이 작성한 댓글만 삭제할 수 있습니다.")); + } + + // 삭제 실행 + int result = commentService.deleteComment(commentId); + + if (result == 0) { + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) + .body(ApiResponse.failure(HttpStatus.INTERNAL_SERVER_ERROR, "댓글 삭제에 실패했습니다.")); + } + + // 삭제 실패 (DB 오류 등의 상황) + + return ResponseEntity.status(HttpStatus.OK) + .body(ApiResponse.success(HttpStatus.OK, "댓글 삭제 성공", null)); + } + + +} diff --git a/codeBoy_backend/src/main/java/com/codeboy/mvc/controller/IncorrectNoteController.java b/codeBoy_backend/src/main/java/com/codeboy/mvc/controller/IncorrectNoteController.java new file mode 100644 index 0000000..3bddc25 --- /dev/null +++ b/codeBoy_backend/src/main/java/com/codeboy/mvc/controller/IncorrectNoteController.java @@ -0,0 +1,74 @@ +package com.codeboy.mvc.controller; + +import com.codeboy.mvc.model.dto.CustomUserDetails; +import com.codeboy.mvc.model.dto.request.IncorrectNoteRequest; +import com.codeboy.mvc.model.dto.response.ApiResponse; +import com.codeboy.mvc.model.dto.response.IncorrectNoteResponse; +import com.codeboy.mvc.model.service.IncorrectNoteService; + +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.servlet.http.HttpSession; + +import org.apache.ibatis.javassist.NotFoundException; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@RestController +@RequestMapping("api/incorrect-note") +@Tag(name="Incorrect-note RESTful API", description = "Incorrect-note CRUD를 할 수 있는 REST API") +public class IncorrectNoteController { + + private final IncorrectNoteService incorrectNoteService; + + public IncorrectNoteController(IncorrectNoteService incorrectNoteService) { + this.incorrectNoteService = incorrectNoteService; + } + + //한 회원의 오답노트를 모두 조회 + @GetMapping + public ResponseEntity>> getIncorrectNote( @AuthenticationPrincipal CustomUserDetails loginUser) { + try { + Long memberId = loginUser.getMemberId(); + List incorrectNoteList = incorrectNoteService.getIncorrectNoteList(memberId); + return ResponseEntity.status(HttpStatus.OK).body(ApiResponse.success(HttpStatus.OK, "오답노트가 성공적으로 조회되었습니다", incorrectNoteList)); + } catch (IllegalArgumentException e) { + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(ApiResponse.failure(HttpStatus.BAD_REQUEST, e.getMessage())); + } catch (NotFoundException e) { + return ResponseEntity.status(HttpStatus.NOT_FOUND).body(ApiResponse.failure(HttpStatus.NOT_FOUND, e.getMessage())); + } + } + + //오답노트에 문제 넣기 + @PostMapping + public ResponseEntity> addIncorrectNote(@RequestBody IncorrectNoteRequest incorrectNoteRequest, @AuthenticationPrincipal CustomUserDetails loginUser) { + + Long memberId = loginUser.getMemberId(); + Long problemId = incorrectNoteRequest.getProblemId(); + Long userProblemId = incorrectNoteRequest.getUserProblemId(); + Boolean isUserProblem = incorrectNoteRequest.getIsUserProblem(); + + try { + Long incorrectNoteId = incorrectNoteService.addIncorrectNote(memberId, problemId, userProblemId, isUserProblem); + return ResponseEntity.status(HttpStatus.CREATED).body(ApiResponse.success(HttpStatus.CREATED, "오답노트를 성공적으로 생성하였습니다.", incorrectNoteId)); + + } catch (IllegalArgumentException e) { + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(ApiResponse.failure(HttpStatus.BAD_REQUEST, e.getMessage())); + } + + } + + @DeleteMapping("/{incorrectNoteId}") + public ResponseEntity> deleteIncorrectNote(@PathVariable long incorrectNoteId) { + + try { + incorrectNoteService.deleteIncorrectNote(incorrectNoteId); + return ResponseEntity.ok(ApiResponse.success(HttpStatus.OK, "오답노트가 삭제되었습니다")); + } catch (IllegalArgumentException e) { + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(ApiResponse.failure(HttpStatus.BAD_REQUEST, e.getMessage())); + } + } +} diff --git a/codeBoy_backend/src/main/java/com/codeboy/mvc/controller/JoinController.java b/codeBoy_backend/src/main/java/com/codeboy/mvc/controller/JoinController.java new file mode 100644 index 0000000..c830a4d --- /dev/null +++ b/codeBoy_backend/src/main/java/com/codeboy/mvc/controller/JoinController.java @@ -0,0 +1,28 @@ +package com.codeboy.mvc.controller; + +import com.codeboy.mvc.model.dto.request.JoinRequest; +import com.codeboy.mvc.model.dto.response.ApiResponse; +import com.codeboy.mvc.model.service.JoinService; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/join") +@Tag(name="join RESTful API", description = "회원가입 REST API") +public class JoinController { + private final JoinService joinService; + @PostMapping + public ResponseEntity> adminP(@RequestBody JoinRequest joinRequest) { + try { + Long memberId = joinService.joinProcess(joinRequest); + return ResponseEntity.status(HttpStatus.CREATED).body(ApiResponse.success( HttpStatus.CREATED, "회원가입에 성공했습니다.", memberId)); + } catch (IllegalArgumentException e) { + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(ApiResponse.failure(HttpStatus.BAD_REQUEST, e.getMessage())); + } + } +} \ No newline at end of file diff --git a/codeBoy_backend/src/main/java/com/codeboy/mvc/controller/MainController.java b/codeBoy_backend/src/main/java/com/codeboy/mvc/controller/MainController.java new file mode 100644 index 0000000..ce0a235 --- /dev/null +++ b/codeBoy_backend/src/main/java/com/codeboy/mvc/controller/MainController.java @@ -0,0 +1,13 @@ +package com.codeboy.mvc.controller; + +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; + +@Controller +public class MainController { + + @GetMapping("/") + public String adminP() { + return "admin Controller"; + } +} diff --git a/codeBoy_backend/src/main/java/com/codeboy/mvc/controller/MemberController.java b/codeBoy_backend/src/main/java/com/codeboy/mvc/controller/MemberController.java index 0a18715..11883d8 100644 --- a/codeBoy_backend/src/main/java/com/codeboy/mvc/controller/MemberController.java +++ b/codeBoy_backend/src/main/java/com/codeboy/mvc/controller/MemberController.java @@ -1,32 +1,134 @@ package com.codeboy.mvc.controller; -import org.springframework.web.bind.annotation.ModelAttribute; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import com.codeboy.mvc.model.dto.CustomUserDetails; import com.codeboy.mvc.model.dto.Member; - +import com.codeboy.mvc.model.dto.request.DuplicateCheckRequest; +import com.codeboy.mvc.model.dto.request.LoginRequest; +import com.codeboy.mvc.model.dto.request.MemberUpdateRequest; +import com.codeboy.mvc.model.dto.response.ApiResponse; +import com.codeboy.mvc.model.dto.response.DuplicateCheckResponse; import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.servlet.http.HttpSession; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatusCode; + +import org.springframework.http.HttpStatus; + +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.*; + +import com.codeboy.mvc.model.service.MemberService; + + +import java.net.URI; +import java.util.HashMap; +import java.util.NoSuchElementException; @RestController -@RequestMapping("/api-member") +@RequestMapping("/api/member") @Tag(name="Member RESTful API", description = "Member CRUD를 할 수 있는 REST API") public class MemberController { - -// @Autowired -// private MemberService memberService; - - - @PostMapping("/signup") - public String signup(@ModelAttribute Member member){ - return "회원가입 성공?"; - } - - @GetMapping(){ - - } - - + private final MemberService memberService; + + @Autowired + public MemberController(MemberService memberService) { + this.memberService = memberService; + } + + //회원 조회 + @GetMapping + public ResponseEntity> getMemberInfo( @AuthenticationPrincipal CustomUserDetails loginUser) { + Long memberId = loginUser.getMemberId(); + try { + Member member = memberService.getMemberById(memberId); + return ResponseEntity.status(HttpStatus.OK).body(ApiResponse.success(HttpStatus.OK, "회원정보가 조회되었습니다", member)); + + } catch (NoSuchElementException e) { + return ResponseEntity.status(HttpStatus.NOT_FOUND).body(ApiResponse.failure(HttpStatus.NOT_FOUND, e.getMessage())); + + } catch (IllegalArgumentException e) { + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(ApiResponse.failure(HttpStatus.BAD_REQUEST, e.getMessage())); + } + } +//특정 회원 조회 + @GetMapping("/{memberId}") + public ResponseEntity> getOneMemberInfo( @PathVariable Long memberId) { + try { + Member member = memberService.getMemberById(memberId); + return ResponseEntity.status(HttpStatus.OK).body(ApiResponse.success(HttpStatus.OK, "회원정보가 조회되었습니다", member)); + + } catch (NoSuchElementException e) { + return ResponseEntity.status(HttpStatus.NOT_FOUND).body(ApiResponse.failure(HttpStatus.NOT_FOUND, e.getMessage())); + + } catch (IllegalArgumentException e) { + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(ApiResponse.failure(HttpStatus.BAD_REQUEST, e.getMessage())); + } + } + + //회원 탈퇴 + @DeleteMapping + public ResponseEntity> deleteMember( @AuthenticationPrincipal CustomUserDetails loginUser) { + Long memberId = loginUser.getMemberId(); + try { + memberService.deactivateMember(memberId); + return ResponseEntity.status(HttpStatus.OK).body(ApiResponse.success(HttpStatus.OK, "회원이 성공적으로 삭제되었습니다.")); + + } catch (IllegalArgumentException e) { + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(ApiResponse.failure(HttpStatus.BAD_REQUEST, e.getMessage())); + } catch (NoSuchElementException e) { + return ResponseEntity.status(HttpStatus.NOT_FOUND).body(ApiResponse.failure(HttpStatus.NOT_FOUND, e.getMessage())); + } + } + + //회원 정보 업데이트 + @PatchMapping + public ResponseEntity> updateMember(@RequestBody MemberUpdateRequest request, @AuthenticationPrincipal CustomUserDetails loginUser) { + Long memberId = loginUser.getMemberId(); + try { + memberService.updateMember(memberId, request); + return ResponseEntity.status(HttpStatus.OK).body(ApiResponse.success(HttpStatus.OK, "회원정보 업데이트에 성공했습니다.")); + } catch (IllegalArgumentException e) { + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(ApiResponse.failure(HttpStatus.BAD_REQUEST, e.getMessage())); + } catch (IllegalStateException e) { + return ResponseEntity.status(HttpStatus.NOT_FOUND).body(ApiResponse.failure(HttpStatus.NOT_FOUND, e.getMessage())); + } + } + + //ID, 닉네임, 이메일 중복체크 + @PostMapping("/check-id") + public ResponseEntity> checkId(@RequestBody DuplicateCheckRequest request) { + try { + boolean duplicated = memberService.checkIdDuplicate(request.getValue()); + DuplicateCheckResponse response = new DuplicateCheckResponse(duplicated); + return ResponseEntity.status(HttpStatus.OK).body(ApiResponse.success(HttpStatus.OK, "아이디 중복 확인 완료", response)); + } catch (IllegalArgumentException e) { + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(ApiResponse.failure(HttpStatus.BAD_REQUEST, e.getMessage())); + } + } + + @PostMapping("/check-email") + public ResponseEntity> checkEmail(@RequestBody DuplicateCheckRequest request) { + try { + boolean duplicated = memberService.checkEmailDuplicate(request.getValue()); + DuplicateCheckResponse response = new DuplicateCheckResponse(duplicated); + return ResponseEntity.status(HttpStatus.OK).body(ApiResponse.success(HttpStatus.OK, "이메일 중복 확인 완료", response)); + } catch (IllegalArgumentException e) { + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(ApiResponse.failure(HttpStatus.BAD_REQUEST, e.getMessage())); + } + } + + @PostMapping("/check-nickname") + public ResponseEntity> checkNickname(@RequestBody DuplicateCheckRequest request) { + try { + boolean duplicated = memberService.checkNicknameDuplicate(request.getValue()); + DuplicateCheckResponse response = new DuplicateCheckResponse(duplicated); + return ResponseEntity.status(HttpStatus.OK).body(ApiResponse.success(HttpStatus.OK, "닉네임 중복 확인 완료", response)); + } catch (IllegalArgumentException e) { + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(ApiResponse.failure(HttpStatus.BAD_REQUEST, e.getMessage())); + } + } } diff --git a/codeBoy_backend/src/main/java/com/codeboy/mvc/controller/ProblemController.java b/codeBoy_backend/src/main/java/com/codeboy/mvc/controller/ProblemController.java new file mode 100644 index 0000000..4b1268c --- /dev/null +++ b/codeBoy_backend/src/main/java/com/codeboy/mvc/controller/ProblemController.java @@ -0,0 +1,37 @@ + +package com.codeboy.mvc.controller; + +import com.codeboy.common.Category; +import com.codeboy.mvc.model.dto.response.ApiResponse; +import com.codeboy.mvc.model.dto.Problem; +import com.codeboy.mvc.model.service.ProblemServiceImpl; + +import io.swagger.v3.oas.annotations.tags.Tag; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@RestController +@RequestMapping("/api/problem") +@Tag(name="problem RESTful API", description = "problem을 조회할 수 있게하는 api") +public class ProblemController { + + @Autowired + private ProblemServiceImpl problemService; + + @GetMapping +// @RequestParam int limit, + public ResponseEntity>> getProblems( @RequestParam Category category) { + try { + List problems = problemService.getProblems(category); + return ResponseEntity.status(HttpStatus.OK).body(new ApiResponse<>(HttpStatus.OK, "문제가 성공적으로 반환되었습니다.", problems)); + + } catch (IllegalArgumentException e) { + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(new ApiResponse<>(HttpStatus.BAD_REQUEST, e.getMessage(), null)); + } + } +} diff --git a/codeBoy_backend/src/main/java/com/codeboy/mvc/controller/QuizRoomController.java b/codeBoy_backend/src/main/java/com/codeboy/mvc/controller/QuizRoomController.java new file mode 100644 index 0000000..b101383 --- /dev/null +++ b/codeBoy_backend/src/main/java/com/codeboy/mvc/controller/QuizRoomController.java @@ -0,0 +1,123 @@ +package com.codeboy.mvc.controller; + +import com.codeboy.mvc.model.dto.*; +import com.codeboy.mvc.model.dto.request.JoinQuizRoomRequest; +import com.codeboy.mvc.model.dto.response.ApiResponse; +import com.codeboy.mvc.model.dto.response.GetQuizRoomMembersResponse; +import com.codeboy.mvc.model.service.QuizRoomService; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@RestController +@RequestMapping("/api/quiz-room") +@Tag(name="Quiz-room RESTful API", description = "Quiz-room CRUD를 할 수 있는 REST API") +public class QuizRoomController { + private final QuizRoomService quizRoomService; + + public QuizRoomController(QuizRoomService quizRoomService) { + this.quizRoomService = quizRoomService; + } + + //호스트 - 퀴즈방 만들기 + @PostMapping("/create") + public ResponseEntity> createQuizRoom(@AuthenticationPrincipal CustomUserDetails loginUser) { + try { + Long memberId = loginUser.getMemberId(); + //새로운 채팅방 생성하기 + Long quizRoomId = quizRoomService.createQuizRoom(); + //3. QuizRoomMember 객체 생성 +// Long memberId = 1L; + QuizRoomMember quizRoomMember = new QuizRoomMember(); + //4. setter로 객체 만들기 + quizRoomMember.setMemberId(memberId); + quizRoomMember.setRoomId(quizRoomId); + quizRoomMember.setIsHost(true); + + //5. 채팅방에 넣기 + quizRoomService.joinQuizRoom(quizRoomMember); + return ResponseEntity.status(HttpStatus.CREATED).body(ApiResponse.success(HttpStatus.CREATED, "퀴즈방이 성공적으로 생성되었습니다.", quizRoomId)); + + } catch (IllegalStateException | IllegalArgumentException e) { + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(ApiResponse.failure(HttpStatus.BAD_REQUEST, e.getMessage())); + } + } + + //채팅방 참여하기 + @PostMapping("/join/{roomId}") + public ResponseEntity> joinQuizRoom(@PathVariable Long roomId, @AuthenticationPrincipal CustomUserDetails loginUser) { + Long memberId = loginUser.getMemberId(); + try { + //QuizRoomMember 생성 + QuizRoomMember quizRoomMember = new QuizRoomMember(); + quizRoomMember.setIsHost(false); + quizRoomMember.setMemberId(memberId); + quizRoomMember.setRoomId(roomId); + + //채팅방에 참가 + quizRoomService.joinQuizRoom(quizRoomMember); + return ResponseEntity.status(HttpStatus.CREATED).body(ApiResponse.success(HttpStatus.CREATED, "퀴즈방 입장에 성공하였습니다.")); + + } catch (IllegalArgumentException e) { + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(ApiResponse.failure(HttpStatus.BAD_REQUEST, e.getMessage())); + } + } + + //채팅방 목록 보여주기 + @GetMapping + public ResponseEntity>> getQuizRoomList() { + try { + List quizRooms = quizRoomService.getQuizRoomList(); + return ResponseEntity.status(HttpStatus.OK).body(ApiResponse.success(HttpStatus.OK, "퀴즈방 목록 조회 성공", quizRooms)); + + } catch (IllegalStateException e) { + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(ApiResponse.failure(HttpStatus.BAD_REQUEST, e.getMessage())); + } + } + + + //현재 참가자 목록 보여주기 + @GetMapping("/{roomId}/member") + public ResponseEntity>> getQuizRoomMembers(@PathVariable long roomId) { + try { + List memberList = quizRoomService.getOneQuizRoomMember(roomId); + return ResponseEntity.status(HttpStatus.OK).body(ApiResponse.success(HttpStatus.OK, "멤버 리스트를 반환합니다.", memberList)); + } catch (IllegalStateException | IllegalArgumentException e) { + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(ApiResponse.failure(HttpStatus.BAD_REQUEST, e.getMessage())); + } + } + + //채팅방 삭제하기 + @DeleteMapping("/{roomId}") + public ResponseEntity> deleteQuizRoom(@PathVariable long roomId) { + try { + quizRoomService.deleteQuizRoom(roomId); + return ResponseEntity.status(HttpStatus.OK).body(ApiResponse.success(HttpStatus.OK, "채팅방이 성공적으로 삭제되었습니다.")); + + } catch (IllegalStateException e) { + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(ApiResponse.failure(HttpStatus.BAD_REQUEST, e.getMessage())); + } + } + //퀴즈방에서 나가기 + // 퀴즈방에서 나가기 + @DeleteMapping("/{roomId}/member") + public ResponseEntity> leaveQuizRoom(@PathVariable long roomId, + @AuthenticationPrincipal CustomUserDetails loginUser) { + Long memberId = loginUser.getMemberId(); + try { + quizRoomService.leaveQuizRoom(roomId, memberId); + return ResponseEntity + .status(HttpStatus.OK) + .body(ApiResponse.success(HttpStatus.OK, "퀴즈방에서 성공적으로 나갔습니다.")); + } catch (IllegalArgumentException | IllegalStateException e) { + return ResponseEntity + .status(HttpStatus.BAD_REQUEST) + .body(ApiResponse.failure(HttpStatus.BAD_REQUEST, e.getMessage())); + } + } + +} \ No newline at end of file diff --git a/codeBoy_backend/src/main/java/com/codeboy/mvc/controller/UserProblemController.java b/codeBoy_backend/src/main/java/com/codeboy/mvc/controller/UserProblemController.java new file mode 100644 index 0000000..107eb59 --- /dev/null +++ b/codeBoy_backend/src/main/java/com/codeboy/mvc/controller/UserProblemController.java @@ -0,0 +1,163 @@ +package com.codeboy.mvc.controller; + +import com.codeboy.mvc.model.dto.CustomUserDetails; +import com.codeboy.mvc.model.dto.UserProblem; +import com.codeboy.mvc.model.dto.response.ApiResponse; // 실제 패키지에 맞게 수정 +import com.codeboy.mvc.model.service.UserProblemService; + +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.servlet.http.HttpSession; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.*; + +import javax.crypto.spec.PSource; +import java.util.List; + +@RestController +@RequestMapping("/api/user-problems") +@Tag(name="User-problems RESTful API", description = "User-problems CRUD를 할 수 있는 REST API") +public class UserProblemController { + + + private final UserProblemService userProblemService; + + public UserProblemController(UserProblemService userProblemService) { + this.userProblemService = userProblemService; + } + + // 특정 문제세트 안의 모든 문제 조회 + @GetMapping("/sets/{userProblemSetId}") + public ResponseEntity>> getProblemsByUserProblemSetId( + @PathVariable Long userProblemSetId + ) { + try { + List problems = userProblemService.getProblemsByUserProblemSetId(userProblemSetId); + + if (problems == null || problems.isEmpty()) { + return ResponseEntity.status(HttpStatus.NOT_FOUND) + .body(ApiResponse.failure(HttpStatus.NOT_FOUND, "해당 문제세트에 등록된 문제가 없습니다.")); + } + + return ResponseEntity.status(HttpStatus.OK) + .body(ApiResponse.success(HttpStatus.OK, "문제 목록 조회 성공", problems)); + + } catch (Exception e) { + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) + .body(ApiResponse.failure(HttpStatus.INTERNAL_SERVER_ERROR, "서버 오류로 문제 목록을 조회하지 못했습니다.")); + } + } + + // user_problem테이블에 문제들 일괄 등록 + @PostMapping("/sets/{userProblemSetId}") + public ResponseEntity> createUserProblems( + @PathVariable Long userProblemSetId, + @RequestBody List userProblems, + @AuthenticationPrincipal CustomUserDetails loginUser + ) { + System.out.println("문제세트 번호: "+ userProblemSetId); + Long memberId= loginUser.getMemberId(); + if (memberId == null) { + return ResponseEntity.status(HttpStatus.UNAUTHORIZED) + .body(ApiResponse.failure(HttpStatus.UNAUTHORIZED, "로그인이 필요합니다.")); + } + + try { + // 세트 ID 세팅 + for (UserProblem p : userProblems) { + p.setUserProblemSetId(userProblemSetId); + } + System.out.println("디버깅1"); + + int inserted = userProblemService.addUserProblems(userProblems); + System.out.println("디버깅2"); + + if (inserted == 0) { + return ResponseEntity.status(HttpStatus.BAD_REQUEST) + .body(ApiResponse.failure(HttpStatus.BAD_REQUEST, "문제 등록에 실패했습니다.")); + } + + return ResponseEntity.status(HttpStatus.CREATED) + .body(ApiResponse.success(HttpStatus.CREATED, "문제 등록 성공", null)); + + } catch (IllegalArgumentException e) { + + return ResponseEntity.status(HttpStatus.BAD_REQUEST) + .body(ApiResponse.failure(HttpStatus.BAD_REQUEST, e.getMessage())); + } catch (Exception e) { + e.printStackTrace(); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) + .body(ApiResponse.failure(HttpStatus.INTERNAL_SERVER_ERROR, "서버 오류로 문제를 등록하지 못했습니다.")); + } + } + + // 문제 수정 + @PutMapping("/{userProblemId}") + public ResponseEntity> updateUserProblem( + @PathVariable Long userProblemId, + @RequestBody UserProblem userProblem, + @AuthenticationPrincipal CustomUserDetails loginUser + ) { + Long memberId= loginUser.getMemberId(); + if (memberId == null) { + return ResponseEntity.status(HttpStatus.UNAUTHORIZED) + .body(ApiResponse.failure(HttpStatus.UNAUTHORIZED, "로그인이 필요합니다.")); + } + + try { + userProblem.setUserProblemId(userProblemId); + + System.out.println("수정요청 받은 문제: " + userProblem); + int result = userProblemService.updateUserProblem(userProblem); + System.out.println("디버깅1"); + + if (result == 0) { + return ResponseEntity.status(HttpStatus.NOT_FOUND) + .body(ApiResponse.failure(HttpStatus.NOT_FOUND, "수정할 문제가 존재하지 않습니다.")); + } + + return ResponseEntity.status(HttpStatus.OK) + .body(ApiResponse.success(HttpStatus.OK, "문제 수정 성공", null)); + + } catch (IllegalArgumentException e) { + return ResponseEntity.status(HttpStatus.BAD_REQUEST) + .body(ApiResponse.failure(HttpStatus.BAD_REQUEST, e.getMessage())); + } catch (Exception e) { + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) + .body(ApiResponse.failure(HttpStatus.INTERNAL_SERVER_ERROR, "서버 오류로 문제를 수정하지 못했습니다.")); + } + } + + // 문제 삭제 + @DeleteMapping("/{userProblemId}") + public ResponseEntity> deleteUserProblem( + @PathVariable Long userProblemId, + @AuthenticationPrincipal CustomUserDetails loginUser + ) { + Long memberId= loginUser.getMemberId(); + if (memberId == null) { + return ResponseEntity.status(HttpStatus.UNAUTHORIZED) + .body(ApiResponse.failure(HttpStatus.UNAUTHORIZED, "로그인이 필요합니다.")); + } + + try { + int result = userProblemService.deleteUserProblem(userProblemId); + + if (result == 0) { + return ResponseEntity.status(HttpStatus.NOT_FOUND) + .body(ApiResponse.failure(HttpStatus.NOT_FOUND, "삭제할 문제가 존재하지 않습니다.")); + } + + return ResponseEntity.status(HttpStatus.OK) + .body(ApiResponse.success(HttpStatus.OK, "문제 삭제 성공", null)); + + } catch (IllegalArgumentException e) { + return ResponseEntity.status(HttpStatus.BAD_REQUEST) + .body(ApiResponse.failure(HttpStatus.BAD_REQUEST, e.getMessage())); + } catch (Exception e) { + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) + .body(ApiResponse.failure(HttpStatus.INTERNAL_SERVER_ERROR, "서버 오류로 문제를 삭제하지 못했습니다.")); + } + } +} diff --git a/codeBoy_backend/src/main/java/com/codeboy/mvc/controller/UserProblemSetController.java b/codeBoy_backend/src/main/java/com/codeboy/mvc/controller/UserProblemSetController.java new file mode 100644 index 0000000..b8f3ae5 --- /dev/null +++ b/codeBoy_backend/src/main/java/com/codeboy/mvc/controller/UserProblemSetController.java @@ -0,0 +1,149 @@ +package com.codeboy.mvc.controller; + +import com.codeboy.mvc.model.dto.CustomUserDetails; +import com.codeboy.mvc.model.dto.UserProblemSet; +import com.codeboy.mvc.model.dto.request.ProblemSetRequest; +import com.codeboy.mvc.model.dto.response.ApiResponse; // 실제 패키지에 맞게 수정 +import com.codeboy.mvc.model.service.UserProblemSetService; + +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.servlet.http.HttpSession; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@RestController +@RequestMapping("/api/user-problem-sets") +@Tag(name="User-problems-sets RESTful API", description = "User-problems-sets CRUD를 할 수 있는 REST API") +public class UserProblemSetController { + + + private final UserProblemSetService userProblemSetService; + + public UserProblemSetController(UserProblemSetService userProblemSetService) { + this.userProblemSetService = userProblemSetService; + } + + // 모든 문제 세트 조회 + @GetMapping + public ResponseEntity>> getAllUserProblemSets() { + try { + List sets = userProblemSetService.getAllUserProblemSets(); + return ResponseEntity.status(HttpStatus.OK) + .body(ApiResponse.success(HttpStatus.OK, "유저 문제세트 전체 조회 성공", sets)); + } catch (Exception e) { + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) + .body(ApiResponse.failure(HttpStatus.INTERNAL_SERVER_ERROR, "서버 오류로 문제세트를 조회하지 못했습니다.")); + } + } + + // 마이페이지 - 내가 만든 문제세트 조회 + @GetMapping("/me") + public ResponseEntity>> getMyUserProblemSet( @AuthenticationPrincipal CustomUserDetails loginUser) { + Long memberId = loginUser.getMemberId(); + System.out.println(memberId); + if (memberId == null) { + return ResponseEntity.status(HttpStatus.UNAUTHORIZED) + .body(ApiResponse.failure(HttpStatus.UNAUTHORIZED, "로그인이 필요합니다.")); + } + + try { + List set = userProblemSetService.getUserProblemSetByMemberId(memberId); + + if (set == null) { + return ResponseEntity.status(HttpStatus.NOT_FOUND) + .body(ApiResponse.failure(HttpStatus.NOT_FOUND, "등록된 유저 문제세트가 없습니다.")); + } + + return ResponseEntity.status(HttpStatus.OK) + .body(ApiResponse.success(HttpStatus.OK, "나의 유저 문제세트 조회 성공", set)); + + } catch (Exception e) { + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) + .body(ApiResponse.failure(HttpStatus.INTERNAL_SERVER_ERROR, "서버 오류로 나의 문제세트를 조회하지 못했습니다.")); + } + } + + // 마이페이지 - 문제세트 생성 + @PostMapping + public ResponseEntity> createMyUserProblemSet(@RequestBody ProblemSetRequest problemSetRequest , @AuthenticationPrincipal CustomUserDetails loginUser) { + Long memberId = loginUser.getMemberId(); + if (memberId == null) { + return ResponseEntity.status(HttpStatus.UNAUTHORIZED) + .body(ApiResponse.failure(HttpStatus.UNAUTHORIZED, "로그인이 필요합니다.")); + } + + try { + //문제 세트가 생성되기전에는 PK가 존재하질 않아서 + //문제세트를 등록함과 동시에 이 PK값을 UserProblemController에도 넘겨줘야함. + //따라서 UserProblemSet 객체를 생성하여 memberId를 세팅해서 넘겨줌, + //프론트에서 이를 받아서 다시 문제 하나하나를 user_problem테이블에 저장 할 때 이 userProblemSetId를 같이 넘겨줘야함. + //이 부분은 프론트와 백엔드간의 협의가 필요. + //UserProblemSetMapper에도 generateKey를 추가해서 PK값을 set에 담을 수 있었다. + UserProblemSet set = new UserProblemSet(); + set.setMemberId(memberId); + set.setCategory(problemSetRequest.getCategory()); + set.setCreatedAt(problemSetRequest.getCreatedAt()); + set.setProblemSetTitle(problemSetRequest.getProblemSetTitle()); + + + int result = userProblemSetService.createUserProblemSet(set); + Long userProblemSetId = set.getUserProblemSetId(); + + if (result == 0) { + return ResponseEntity.status(HttpStatus.BAD_REQUEST) + .body(ApiResponse.failure(HttpStatus.BAD_REQUEST, "유저 문제세트 생성에 실패했습니다.")); + } + + return ResponseEntity.status(HttpStatus.CREATED) + .body(ApiResponse.success(HttpStatus.CREATED, "유저 문제세트 생성 성공", userProblemSetId)); + + } catch (IllegalArgumentException e) { + return ResponseEntity.status(HttpStatus.BAD_REQUEST) + .body(ApiResponse.failure(HttpStatus.BAD_REQUEST, e.getMessage())); + } catch (Exception e) { + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) + .body(ApiResponse.failure(HttpStatus.INTERNAL_SERVER_ERROR, "서버 오류로 문제세트를 생성하지 못했습니다.")); + } + } + + // 마이페이지 - 문제세트 삭제 + @DeleteMapping("/{userProblemSetId}") + public ResponseEntity> deleteUserProblemSet( + @PathVariable Long userProblemSetId, + @AuthenticationPrincipal CustomUserDetails loginUser + ) { + Long memberId= loginUser.getMemberId(); + if (memberId == null) { + return ResponseEntity.status(HttpStatus.UNAUTHORIZED) + .body(ApiResponse.failure(HttpStatus.UNAUTHORIZED, "로그인이 필요합니다.")); + } + + System.out.println("삭제할 문제 세트 id: "+ userProblemSetId); + // TODO: userProblemSetId가 memberId가 만든 세트인지 owner 체크 로직을 Service/Dao에 추가하면 더 안전함 + + try { + int result = userProblemSetService.deleteUserProblemSet(userProblemSetId); + + if (result == 0) { + return ResponseEntity.status(HttpStatus.NOT_FOUND) + .body(ApiResponse.failure(HttpStatus.NOT_FOUND, "삭제할 유저 문제세트가 존재하지 않습니다.")); + } + + return ResponseEntity.status(HttpStatus.OK) + .body(ApiResponse.success(HttpStatus.OK, "유저 문제세트 삭제 성공", null)); + + } catch (IllegalArgumentException e) { + return ResponseEntity.status(HttpStatus.BAD_REQUEST) + .body(ApiResponse.failure(HttpStatus.BAD_REQUEST, e.getMessage())); + } catch (Exception e) { + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) + .body(ApiResponse.failure(HttpStatus.INTERNAL_SERVER_ERROR, "서버 오류로 문제세트를 삭제하지 못했습니다.")); + } + } +} diff --git a/codeBoy_backend/src/main/java/com/codeboy/mvc/controller/UserScoreController.java b/codeBoy_backend/src/main/java/com/codeboy/mvc/controller/UserScoreController.java new file mode 100644 index 0000000..78cb89f --- /dev/null +++ b/codeBoy_backend/src/main/java/com/codeboy/mvc/controller/UserScoreController.java @@ -0,0 +1,160 @@ +package com.codeboy.mvc.controller; + +import java.util.List; + +import com.codeboy.mvc.model.dto.CustomUserDetails; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.*; + +import com.codeboy.mvc.model.dto.UserScore; +import com.codeboy.mvc.model.service.UserScoreService; + +import io.swagger.v3.oas.annotations.tags.Tag; + +import com.codeboy.mvc.model.dto.response.ApiResponse; + +import jakarta.servlet.http.HttpSession; + +@RestController +@RequestMapping("api/scores") +@Tag(name="User-score RESTful API", description = "User-score를 CRUD를 할 수 있게하는 api") +public class UserScoreController { + + private final UserScoreService userScoreService; + + public UserScoreController(UserScoreService userScoreService) { + this.userScoreService = userScoreService; + } + + // 점수 등록 + @PostMapping + public ResponseEntity> createUserScore( + @RequestBody UserScore userScore, + @AuthenticationPrincipal CustomUserDetails loginUser + ) { + Long memberId = loginUser.getMemberId(); + if (memberId == null) { + return ResponseEntity.status(HttpStatus.UNAUTHORIZED) + .body(ApiResponse.failure(HttpStatus.UNAUTHORIZED, "로그인이 필요합니다.")); + } + + // 세션의 memberId를 강제로 DTO에 세팅 (신뢰할 수 있는 값) + userScore.setMemberId(memberId); + + try { + int result = userScoreService.registerScore(userScore); + + if (result == 0) { + // SQL은 정상 실행되었지만, 반영된 row가 없음 + return ResponseEntity.status(HttpStatus.BAD_REQUEST) + .body(ApiResponse.failure(HttpStatus.BAD_REQUEST, "SQL이 실행됐으나 row가 반영되지 않았습니다.")); + } + + return ResponseEntity.status(HttpStatus.CREATED) + .body(ApiResponse.success(HttpStatus.CREATED, "점수 등록 성공", null)); + + } catch (IllegalArgumentException e) { + // 파라미터/입력 문제 + return ResponseEntity.status(HttpStatus.BAD_REQUEST) + .body(ApiResponse.failure(HttpStatus.BAD_REQUEST, e.getMessage())); + + } catch (Exception e) { + // 예기치 못한 DB 오류 / 서버 오류 + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) + .body(ApiResponse.failure(HttpStatus.INTERNAL_SERVER_ERROR, "서버 오류로 점수를 등록하지 못했습니다.")); + } + } + + // 전체 유저 점수 조회 + @GetMapping + public ResponseEntity>> getAllUserScores( @AuthenticationPrincipal CustomUserDetails loginUser) { + Long memberId = loginUser.getMemberId(); + if (memberId == null) { + return ResponseEntity.status(HttpStatus.UNAUTHORIZED) + .body(ApiResponse.failure(HttpStatus.UNAUTHORIZED, "로그인이 필요합니다.")); + } + + try { + List scores = userScoreService.getAllUserScores(); + + if (scores == null || scores.isEmpty()) { + return ResponseEntity.status(HttpStatus.NOT_FOUND) + .body(ApiResponse.failure(HttpStatus.NOT_FOUND, "점수 목록이 비어있습니다. UserScore테이블을 확인해주세요.")); + } + + return ResponseEntity.status(HttpStatus.OK) + .body(ApiResponse.success(HttpStatus.OK, "점수 목록 조회 성공", scores)); + + } catch (Exception e) { + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) + .body(ApiResponse.failure(HttpStatus.INTERNAL_SERVER_ERROR, "서버 오류로 점수 목록을 조회하지 못했습니다.")); + } + } + + // 특정 유저 점수 조회 + @GetMapping("/member") + public ResponseEntity> getUserScore( + @AuthenticationPrincipal CustomUserDetails loginUser + ) { + Long memberId = loginUser.getMemberId(); + + try { + UserScore userScore = userScoreService.getUserScoreById(memberId); + + if (userScore == null) { + return ResponseEntity.status(HttpStatus.NOT_FOUND) + .body(ApiResponse.failure(HttpStatus.NOT_FOUND, "해당 회원의 점수가 존재하지 않습니다.")); + } + + return ResponseEntity.status(HttpStatus.OK) + .body(ApiResponse.success(HttpStatus.OK, "점수 조회 성공", userScore)); + + } catch (IllegalArgumentException e) { + return ResponseEntity.status(HttpStatus.BAD_REQUEST) + .body(ApiResponse.failure(HttpStatus.BAD_REQUEST, e.getMessage())); + + } catch (Exception e) { + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) + .body(ApiResponse.failure(HttpStatus.INTERNAL_SERVER_ERROR, "서버 오류로 점수를 조회하지 못했습니다.")); + } + } + + // 점수 수정 HttpSession에서 memberId를 가져와 이 사람의 점수를 갱신하는거라 PathVariable이나 @RequestBody가 없다. + @PutMapping + public ResponseEntity> updateUserScore( + @RequestBody UserScore userScore, + @AuthenticationPrincipal CustomUserDetails loginUser + ) { + Long memberId = loginUser.getMemberId(); + if (memberId == null) { + return ResponseEntity.status(HttpStatus.UNAUTHORIZED) + .body(ApiResponse.failure(HttpStatus.UNAUTHORIZED, "로그인이 필요합니다.")); + } + + // 세션 기준으로 memberId 강제 세팅 + userScore.setMemberId(memberId); + + try { + int result = userScoreService.updateUser(userScore); + + if (result == 0) { + // 수정할 row가 없거나, 업데이트 실패 + return ResponseEntity.status(HttpStatus.BAD_REQUEST) + .body(ApiResponse.failure(HttpStatus.BAD_REQUEST, "점수 수정에 실패했습니다.")); + } + + return ResponseEntity.status(HttpStatus.OK) + .body(ApiResponse.success(HttpStatus.OK, "점수 수정 성공", null)); + + } catch (IllegalArgumentException e) { + return ResponseEntity.status(HttpStatus.BAD_REQUEST) + .body(ApiResponse.failure(HttpStatus.BAD_REQUEST, e.getMessage())); + + } catch (Exception e) { + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) + .body(ApiResponse.failure(HttpStatus.INTERNAL_SERVER_ERROR, "서버 오류로 점수를 수정하지 못했습니다.")); + } + } +} diff --git a/codeBoy_backend/src/main/java/com/codeboy/mvc/jwt/JWTFilter.java b/codeBoy_backend/src/main/java/com/codeboy/mvc/jwt/JWTFilter.java new file mode 100644 index 0000000..ba60793 --- /dev/null +++ b/codeBoy_backend/src/main/java/com/codeboy/mvc/jwt/JWTFilter.java @@ -0,0 +1,97 @@ +package com.codeboy.mvc.jwt; + +import com.codeboy.mvc.model.service.CustomerUserDetailService; +import io.jsonwebtoken.ExpiredJwtException; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.web.filter.OncePerRequestFilter; + +import java.io.IOException; +import java.util.List; + +@RequiredArgsConstructor +public class JWTFilter extends OncePerRequestFilter { + + private final JWTUtil jwtUtil; + private final CustomerUserDetailService userDetailService; + + // ❗ 실제 사용하는 URL에 맞게 수정해야 함 + private static final List EXCLUDED_URLS = List.of( + "/login",// 스프링 시큐리티 로그인 엔드포인트 + "/api/join", + "/member/login", // 커스텀 로그인 엔드포인트가 있다면 + "/auth/login", + "/auth/signup", + "/member/join" + ); + + private boolean isExcluded(HttpServletRequest request) { + String path = request.getRequestURI(); + return EXCLUDED_URLS.stream().anyMatch(path::startsWith); + } + + @Override + protected void doFilterInternal(HttpServletRequest request, + HttpServletResponse response, + FilterChain filterChain) + throws ServletException, IOException { + + // 0. 로그인/회원가입 등 공개 URL이면 토큰 검사 없이 그냥 패스 + if (isExcluded(request)) { + filterChain.doFilter(request, response); + return; + } + + // 1. Authorization 헤더 가져오기 + String authorization = request.getHeader("Authorization"); + + // 2. 토큰이 없거나 Bearer 형식이 아니면 → 그냥 다음 필터로 + if (authorization == null || !authorization.startsWith("Bearer ")) { + System.out.println("JWTFilter: 토큰 없음, 다음 필터로 패스"); + filterChain.doFilter(request, response); + return; + } + + // 3. "Bearer xxxxxx" 에서 실제 토큰 부분만 분리 + String token = authorization.substring(7); // "Bearer " 길이 = 7 + + try { + // 4. 토큰 만료 여부 체크 (이 과정에서 ExpiredJwtException이 날 수 있음) + if (jwtUtil.isExpired(token)) { + throw new ExpiredJwtException(null, null, "Token expired"); + } + + // 5. 여기까지 왔으면 만료 안 된 토큰이므로 username 파싱 + String username = jwtUtil.getUsername(token); + + // 6. UserDetails 조회 + UserDetails userDetails = userDetailService.loadUserByUsername(username); + + // 7. Authentication 객체 생성해서 SecurityContext에 넣기 + Authentication authToken = new UsernamePasswordAuthenticationToken( + userDetails, + null, + userDetails.getAuthorities() + ); + SecurityContextHolder.getContext().setAuthentication(authToken); + + // 8. 다음 필터 진행 + filterChain.doFilter(request, response); + + } catch (ExpiredJwtException e) { + // 9. 만료된 토큰일 때 401로 응답 + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + response.setContentType("application/json;charset=UTF-8"); + response.getWriter().write(""" + {"code": "TOKEN_EXPIRED", "message": "토큰이 만료되었습니다. 다시 로그인 해주세요."} + """); + } + } +} diff --git a/codeBoy_backend/src/main/java/com/codeboy/mvc/jwt/JWTUtil.java b/codeBoy_backend/src/main/java/com/codeboy/mvc/jwt/JWTUtil.java new file mode 100644 index 0000000..26d5e70 --- /dev/null +++ b/codeBoy_backend/src/main/java/com/codeboy/mvc/jwt/JWTUtil.java @@ -0,0 +1,68 @@ +package com.codeboy.mvc.jwt; + +import io.jsonwebtoken.Jwts; +import jakarta.annotation.PostConstruct; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; +import java.nio.charset.StandardCharsets; +import java.util.Date; + +@Component +public class JWTUtil { + + private final SecretKey secretKey; // JWT 서명용 시크릿 키 + + // application.yml / properties 에 정의된 값 주입 + // 예: spring.jwt.secret-key: my-jwt-secret-key... + public JWTUtil(@Value("${spring.jwt.secret-key}") String secret) { + this.secretKey = new SecretKeySpec( + secret.getBytes(StandardCharsets.UTF_8), + Jwts.SIG.HS256.key().build().getAlgorithm() + ); + } + + + public String getUsername(String token) { + return Jwts.parser() + .verifyWith(secretKey) + .build() + .parseSignedClaims(token) + .getPayload() + .get("username", String.class); + } + + public String getRole(String token) { + return Jwts.parser() + .verifyWith(secretKey) + .build() + .parseSignedClaims(token) + .getPayload() + .get("role", String.class); + } + + public boolean isExpired(String token) { + Date expiration = Jwts.parser() + .verifyWith(secretKey) + .build() + .parseSignedClaims(token) + .getPayload() + .getExpiration(); + + return expiration.before(new Date()); + } + + public String createJwt(String username, String role, Long expiredMs) { + long now = System.currentTimeMillis(); + + return Jwts.builder() + .claim("username", username) + .claim("role", role) + .issuedAt(new Date(now)) + .expiration(new Date(now + expiredMs)) + .signWith(secretKey) + .compact(); + } +} diff --git a/codeBoy_backend/src/main/java/com/codeboy/mvc/jwt/LoginFilter.java b/codeBoy_backend/src/main/java/com/codeboy/mvc/jwt/LoginFilter.java new file mode 100644 index 0000000..fb40c5a --- /dev/null +++ b/codeBoy_backend/src/main/java/com/codeboy/mvc/jwt/LoginFilter.java @@ -0,0 +1,126 @@ +package com.codeboy.mvc.jwt; + +import com.codeboy.mvc.model.dto.CustomUserDetails; +import com.codeboy.mvc.model.dto.request.LoginRequest; +import com.codeboy.mvc.model.dto.response.ApiResponse; +import com.codeboy.mvc.model.dto.response.LoginResponse; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; + +import java.io.IOException; + +@RequiredArgsConstructor +public class LoginFilter extends UsernamePasswordAuthenticationFilter { + + private final AuthenticationManager authenticationManager; + private final JWTUtil jwtUtil; + private final com.fasterxml.jackson.databind.ObjectMapper objectMapper; // ✅ 추가 + + { + // 인스턴스 초기화 블록 or 생성자에서 설정 + setFilterProcessesUrl("/login"); // ✅ 이 URL로 오는 요청을 로그인으로 처리 + } + + /** + * 로그인 시도 시 호출되는 메서드 + */ + @Override + public Authentication attemptAuthentication(HttpServletRequest request, + HttpServletResponse response) + throws AuthenticationException { + + LoginRequest loginRequest = + null; + try { + loginRequest = objectMapper.readValue(request.getInputStream(), LoginRequest.class); + } catch (IOException e) { + throw new RuntimeException(e); + } + + String username = loginRequest.getId(); + String password = loginRequest.getPassword(); + + if (username == null) { + username = request.getParameter("username"); + } + if (password == null) { + password = request.getParameter("password"); + } + + + + UsernamePasswordAuthenticationToken authRequest = + new UsernamePasswordAuthenticationToken(username, password); + + return authenticationManager.authenticate(authRequest); + } + + /** + * 로그인 성공 시 호출되는 메서드 + * (JWT 발급 같은 작업을 여기서 하면 됨) + */ + @Override + protected void successfulAuthentication(HttpServletRequest request, + HttpServletResponse response, + FilterChain chain, + Authentication authResult) + throws IOException, ServletException { + + System.out.println("login success: " + authResult.getName()); + + // 1) 인증된 사용자 정보 + CustomUserDetails principal = (CustomUserDetails) authResult.getPrincipal(); + String username = principal.getUsername(); + String role = principal.getAuthorities().iterator().next().getAuthority(); + + // 2) JWT 생성 + String accessToken = jwtUtil.createJwt(username, role, 60 * 60 * 1000L); + String refreshToken = jwtUtil.createJwt(username, role, 7 * 24 * 60 * 60 * 1000L); + + + // 3) LoginResponse 생성 + LoginResponse loginResponse = new LoginResponse( + accessToken, + refreshToken, + principal.getMemberId() + ); + + // 4) ApiResponse 생성 + ApiResponse apiResponse = + ApiResponse.success(HttpStatus.OK, "로그인 성공", loginResponse); + + // 5) JSON으로 내려주기 + response.setStatus(HttpServletResponse.SC_OK); + response.setContentType("application/json;charset=UTF-8"); + objectMapper.writeValue(response.getWriter(), apiResponse); + } + + + /** + * 로그인 실패 시 호출되는 메서드 + */ + @Override + protected void unsuccessfulAuthentication(HttpServletRequest request, + HttpServletResponse response, + AuthenticationException failed) + throws IOException, ServletException { + + System.out.println("login fail: " + failed.getMessage()); + + ApiResponse apiResponse = + ApiResponse.failure(HttpStatus.UNAUTHORIZED, failed.getMessage()); // or failed.getMessage() + + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + response.setContentType("application/json;charset=UTF-8"); + objectMapper.writeValue(response.getWriter(), apiResponse); + } +} diff --git a/codeBoy_backend/src/main/java/com/codeboy/mvc/model/dao/CommentDao.java b/codeBoy_backend/src/main/java/com/codeboy/mvc/model/dao/CommentDao.java index df05e7e..1f75116 100644 --- a/codeBoy_backend/src/main/java/com/codeboy/mvc/model/dao/CommentDao.java +++ b/codeBoy_backend/src/main/java/com/codeboy/mvc/model/dao/CommentDao.java @@ -2,14 +2,24 @@ import java.util.List; +import org.apache.ibatis.annotations.Mapper; + import com.codeboy.mvc.model.dto.Comment; +@Mapper public interface CommentDao { - public List selectAllByProblemId(long problemId); - - public void insertComment(Comment comment); + public List selectCommentsByuserProblemSetId(Long userProblemSetId); + + //유저문제세트 id에다가 댓글을 달아야하므로 long userProblemSetId으로 지정 + public int insertComment(Long userProblemSetId, Comment comment); - public void updateComment(Comment comment); + //조회된 댓글의 id를 가져와서 수정하고 삭제함. + public int updateComment(Long commentId, Comment comment); + + //조회된 댓글의 id를 가져와서 수정하고 삭제함. + public int deleteComment(Long commentId); + + //댓글아이디로 댓글 작성자의 아이디를 가져옴 + public Long selectCommentOwnerId(Long commentId); - public void deleteComment(long commentId); } diff --git a/codeBoy_backend/src/main/java/com/codeboy/mvc/model/dao/IncorrectNoteDao.java b/codeBoy_backend/src/main/java/com/codeboy/mvc/model/dao/IncorrectNoteDao.java index 5a617b2..2782bb9 100644 --- a/codeBoy_backend/src/main/java/com/codeboy/mvc/model/dao/IncorrectNoteDao.java +++ b/codeBoy_backend/src/main/java/com/codeboy/mvc/model/dao/IncorrectNoteDao.java @@ -1,17 +1,32 @@ package com.codeboy.mvc.model.dao; import java.util.List; +import java.util.Map; -import com.codeboy.mvc.model.dto.Problem; +import com.codeboy.mvc.model.dto.response.IncorrectNoteResponse; +import org.apache.ibatis.annotations.Mapper; +@Mapper public interface IncorrectNoteDao { //유저의 아이디로 유저가 틀린 문제들의 모음을 조회 - public List selectIncorrectProblems(long memberId); + //sql의 관점에서 + // + public List selectIncorrectProblems(Long memberId); //유저가 자신의 오답노트 안에서 문제를 삭제 - public void deleteIncorrectProblem(long memberId, long problemId); - + public int deleteIncorrectProblem(Long incorrectNoteId); + //오답노트에 문제 추가 - public void insertIncorrectProblem(long memberId, long problemId); + public void insertIncorrectProblem(Map params); + + //문제 존재 여부 체크 + boolean existsProblemById(Long problemId); + + //유저 문제 존재 여부 체크 + boolean existsUserProblemById(Long userProblemId); + + //incorrectNoteId 존재 여부 체크 + boolean existsIncorrectNoteById(Long incorrectNoteId); + } diff --git a/codeBoy_backend/src/main/java/com/codeboy/mvc/model/dao/MemberDao.java b/codeBoy_backend/src/main/java/com/codeboy/mvc/model/dao/MemberDao.java index 6615b7c..464a799 100644 --- a/codeBoy_backend/src/main/java/com/codeboy/mvc/model/dao/MemberDao.java +++ b/codeBoy_backend/src/main/java/com/codeboy/mvc/model/dao/MemberDao.java @@ -1,18 +1,43 @@ package com.codeboy.mvc.model.dao; import com.codeboy.mvc.model.dto.Member; -import com.codeboy.mvc.model.dto.MemberUpdateRequest; - +import com.codeboy.mvc.model.dto.request.MemberUpdateRequest; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +@Mapper public interface MemberDao { - public void insertMember(Member member); - - public Member selectMember(String id, String password); - - //멤버 삭제 -> db에서는 status 변경 - public void delete(int memberId); - + int insertMember(Member member); + + Member selectMemberById(Long memberId); + + //멤버 삭제 -> db에서는 status 변경 + int deactivateMemberById(Long memberId); + //멤버 업데이트 -> db에서는 patch(nickname, email, id) - public void updateMember(long memberId, MemberUpdateRequest memberUpdateRequest); - + int updateMemberById(@Param("memberId") Long memberId, @Param("update") MemberUpdateRequest memberUpdateRequest); + + //아이디 중복 체크 + Boolean existsId(String id); + + //닉네임 중복 체크 + Boolean existsNickname(String nickname); + + //이메일 중복 체크 + Boolean existsEmail(String email); + + //멤버가 활성화 상태인지 확인 + Boolean isMemberActive(Long memberId); + + // 회원 비활성화(탈퇴) - status, isDeleted 업데이트 + int deleteMember(long memberId); + // 로그인 - ID와 password로 회원 조회 + Member selectMemberByIdAndPassword(@Param("id") String id, @Param("password") String password); + + //spring security + boolean existByUserId(String id); + + Member findByUserId(String id); + + String currentDatabase(); // 🔥 추가 } diff --git a/codeBoy_backend/src/main/java/com/codeboy/mvc/model/dao/ProblemDao.java b/codeBoy_backend/src/main/java/com/codeboy/mvc/model/dao/ProblemDao.java index d98bc01..fe372e7 100644 --- a/codeBoy_backend/src/main/java/com/codeboy/mvc/model/dao/ProblemDao.java +++ b/codeBoy_backend/src/main/java/com/codeboy/mvc/model/dao/ProblemDao.java @@ -4,9 +4,12 @@ import com.codeboy.common.Category; import com.codeboy.mvc.model.dto.Problem; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +@Mapper public interface ProblemDao { //문제 조회 - public List selectProblem(Category category); - +// @Param("limit") int limit + public List selectProblem( @Param("category") Category category); } \ No newline at end of file diff --git a/codeBoy_backend/src/main/java/com/codeboy/mvc/model/dao/QuizRoomDao.java b/codeBoy_backend/src/main/java/com/codeboy/mvc/model/dao/QuizRoomDao.java index 67a7ce4..db92ead 100644 --- a/codeBoy_backend/src/main/java/com/codeboy/mvc/model/dao/QuizRoomDao.java +++ b/codeBoy_backend/src/main/java/com/codeboy/mvc/model/dao/QuizRoomDao.java @@ -4,28 +4,42 @@ import com.codeboy.mvc.model.dto.QuizRoom; import com.codeboy.mvc.model.dto.QuizRoomMember; +import com.codeboy.mvc.model.dto.response.GetQuizRoomMembersResponse; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +@Mapper public interface QuizRoomDao { - //quiz_room 테이블. 퀴즈룸 생성. 생성된 퀴즈룸 id 반환 - public int insertQuizRoom(); - - //quiz_room_member 테이블에 값 넣기 (퀴즈방 입장) - 참가자/호스트 - public void insertMemberToQuizRoom(QuizRoomMember quizRoomMember); + public void insertQuizRoom(QuizRoom room); - + //quiz_room_member 테이블에 값 넣기 (퀴즈방 입장) - 참가자/호스트 + public int insertMemberToQuizRoom(QuizRoomMember quizRoomMember); //모든 퀴즈룸 조회하기 public List selectAllQuizRoom(); - - //하나의 퀴즈룸 조회(참가 멤버확인) - public List selectOneQuizRoom(int roomId); - + public List selectOneQuizRoom(Long roomId); + //퀴즈룸 수정 - - //퀴즈룸 삭제 - public void deleteQuizRoom(long roomId); + //TODO : 지금은 멤버가 전체 퀴즈방 테이블에 중복으로 들어가면 에러 터짐 + //TODO : 채팅방이 끝나면 바로 삭제하는 로직 만들어야 함. + + public boolean deleteQuizRoom(Long roomId); + + //퀴즈룸 존재하는지 확인 + public int existsQuizRoom(Long roomId); + + //퀴즈룸에 특정 멤버가 있는지 확인 + public int isDuplicatedMember(Long memberId); + + QuizRoomMember selectQuizRoomMember(@Param("roomId") long roomId, + @Param("memberId") long memberId); + /** + * 퀴즈방 멤버 테이블에서 특정 멤버를 삭제 (퇴장) + */ + int deleteQuizRoomMember(@Param("roomId") long roomId, + @Param("memberId") long memberId); } diff --git a/codeBoy_backend/src/main/java/com/codeboy/mvc/model/dao/ScoreDao.java b/codeBoy_backend/src/main/java/com/codeboy/mvc/model/dao/ScoreDao.java deleted file mode 100644 index 40a981a..0000000 --- a/codeBoy_backend/src/main/java/com/codeboy/mvc/model/dao/ScoreDao.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.codeboy.mvc.model.dao; - -import java.util.List; - -import com.codeboy.mvc.model.dto.UserScore; - -public interface ScoreDao { - //스코어 등록 - public void insertScore(UserScore userScore); - - //모든 스코어 조회 - public List selectAllUserScore(); - - //한 멤버의 스코어 조회 - public int selectOneUserScore(int memberId); - - //멤버 스코어 업데이트 - public void updateUserScore(UserScore userScore); - - -} diff --git a/codeBoy_backend/src/main/java/com/codeboy/mvc/model/dao/UserIncorrectNoteDao.java b/codeBoy_backend/src/main/java/com/codeboy/mvc/model/dao/UserIncorrectNoteDao.java deleted file mode 100644 index 537c5e1..0000000 --- a/codeBoy_backend/src/main/java/com/codeboy/mvc/model/dao/UserIncorrectNoteDao.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.codeboy.mvc.model.dao; - -import java.util.List; - -import com.codeboy.mvc.model.dto.Problem; -import com.codeboy.mvc.model.dto.UserProblem; - -public interface UserIncorrectNoteDao { - - //유저의 아이디로 유저가 틀린 문제들의 모음을 조회 - public List selectUserIncorrectProblems(long memberId); - - //유저가 자신의 오답노트 안에서 문제를 삭제 - public void deleteUserIncorrectProblem(long memberId, long userProblemId); - - //(중간자 테이블)오답노트에 문제 추가 - public void insertUserIncorrectProblem(long memberId, long userProblemId); -} diff --git a/codeBoy_backend/src/main/java/com/codeboy/mvc/model/dao/UserProblemDao.java b/codeBoy_backend/src/main/java/com/codeboy/mvc/model/dao/UserProblemDao.java index b2ad1ff..61f7b11 100644 --- a/codeBoy_backend/src/main/java/com/codeboy/mvc/model/dao/UserProblemDao.java +++ b/codeBoy_backend/src/main/java/com/codeboy/mvc/model/dao/UserProblemDao.java @@ -3,23 +3,22 @@ import java.util.List; import com.codeboy.common.Category; -import com.codeboy.mvc.model.dto.Problem; import com.codeboy.mvc.model.dto.UserProblem; public interface UserProblemDao extends ProblemDao{ - - //문제세트 조회 - public List selectUserProblem(Category category); - //문제 생성하기 - public void insertUserProblem(UserProblem userProblem); - - //문제 세트 만들기 - public void insertUserProblemSet(List userProblemList); - - //문제 수정 - public void updateUserProblem(UserProblem userProblem); - - //문제 삭제 - public int deleteUserProblem(int id); + + //유저제작 문제풀이 페이지에서 문제세트를 선택했을때, 그 문제세트에 속한문제들 조회 + List selectProblemsByUserProblemSetId(Long userProblemSetId); + + //문제 생성 시 문제세트를 등록함과 동시에 문제들을 전부 user_problem 테이블에 넣기 + int insertUserProblemList(List userProblems); + + //제작자 본인이 자신의 문제들을 수정하는 로직 + int updateUserProblem(UserProblem userProblem); + + //제작자 본인이 자신의 문제를 삭제하는 로직 + int deleteUserProblemById(Long userProblemId); + + } diff --git a/codeBoy_backend/src/main/java/com/codeboy/mvc/model/dao/UserProblemSetDao.java b/codeBoy_backend/src/main/java/com/codeboy/mvc/model/dao/UserProblemSetDao.java new file mode 100644 index 0000000..1a0ffe6 --- /dev/null +++ b/codeBoy_backend/src/main/java/com/codeboy/mvc/model/dao/UserProblemSetDao.java @@ -0,0 +1,25 @@ +package com.codeboy.mvc.model.dao; + +import com.codeboy.mvc.model.dto.Problem; +import com.codeboy.mvc.model.dto.UserProblem; +import com.codeboy.mvc.model.dto.UserProblemSet; + +import java.util.List; + +public interface UserProblemSetDao { + //다른 사용자들이 만든 유저제작 문제세트들을 모두 조회 + List selectUserProblemSets(); + + + //마이페이지에서 자신이 제작한 문제세트 조회 + List selectUserProblemSetByMemberId(long memberId); + + //문제 세트 등록 memberId = 문제 제작자만 넘겨주고 문제들은 problemDao에서 insertUserProblem으로 넣음 (마이페이지에서 생성) + int insertUserProblemSet(UserProblemSet userProblemSet); + + // 문제세트 삭제 (마이페이지에서 삭제) + int deleteUserProblemSetById(Long userProblemSetId); + + + +} diff --git a/codeBoy_backend/src/main/java/com/codeboy/mvc/model/dao/UserScoreDao.java b/codeBoy_backend/src/main/java/com/codeboy/mvc/model/dao/UserScoreDao.java new file mode 100644 index 0000000..4f67e0b --- /dev/null +++ b/codeBoy_backend/src/main/java/com/codeboy/mvc/model/dao/UserScoreDao.java @@ -0,0 +1,21 @@ +package com.codeboy.mvc.model.dao; + +import java.util.List; + +import com.codeboy.mvc.model.dto.UserScore; + +public interface UserScoreDao { + //스코어 등록 + public int insertScore(UserScore userScore); + + //모든 스코어 조회 + public List selectAllUserScores(); + + //한 멤버의 스코어 조회 + public UserScore selectOneUserScore(Long memberId); + + //멤버 스코어 업데이트 + public int updateUserScore(UserScore userScore); + + +} diff --git a/codeBoy_backend/src/main/java/com/codeboy/mvc/model/dto/AIProblemDto.java b/codeBoy_backend/src/main/java/com/codeboy/mvc/model/dto/AIProblemDto.java new file mode 100644 index 0000000..630a143 --- /dev/null +++ b/codeBoy_backend/src/main/java/com/codeboy/mvc/model/dto/AIProblemDto.java @@ -0,0 +1,15 @@ +package com.codeboy.mvc.model.dto; + +import java.util.List; + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class AIProblemDto { + + private String problemDescription; // 문제 본문 + private List choices; // 보기 4개 + private int answer; // 정답 인덱스 (0~3) +} diff --git a/codeBoy_backend/src/main/java/com/codeboy/mvc/model/dto/Comment.java b/codeBoy_backend/src/main/java/com/codeboy/mvc/model/dto/Comment.java index 032ad95..bf7599c 100644 --- a/codeBoy_backend/src/main/java/com/codeboy/mvc/model/dto/Comment.java +++ b/codeBoy_backend/src/main/java/com/codeboy/mvc/model/dto/Comment.java @@ -13,7 +13,8 @@ @NoArgsConstructor @Getter @Setter -public class Comment { +public class +Comment { private long commentId; private long memberId; private String content; diff --git a/codeBoy_backend/src/main/java/com/codeboy/mvc/model/dto/CustomUserDetails.java b/codeBoy_backend/src/main/java/com/codeboy/mvc/model/dto/CustomUserDetails.java new file mode 100644 index 0000000..a353a8b --- /dev/null +++ b/codeBoy_backend/src/main/java/com/codeboy/mvc/model/dto/CustomUserDetails.java @@ -0,0 +1,66 @@ +package com.codeboy.mvc.model.dto; + +import io.micrometer.observation.annotation.Observed; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; + +import java.util.ArrayList; +import java.util.Collection; + +public class CustomUserDetails implements UserDetails { + private final Member member; + + public CustomUserDetails(Member member) { + this.member = member; + } + + @Override + public Collection getAuthorities() { + Collection collection = new ArrayList<>(); + + collection.add(new GrantedAuthority() { + @Override + public String getAuthority() { + return member.getRole(); + } + }); + return collection; + } + public String getNickname() { + return member.getNickname(); + } + + + public Long getMemberId() { + return member.getMemberId(); // member 엔티티에 memberId 필드 있다고 가정 + } + + @Override + public String getPassword() { + return member.getPassword(); + } + + @Override + public String getUsername() { + return member.getId(); + } + @Override + public boolean isAccountNonExpired() { + return true; + } + + @Override + public boolean isAccountNonLocked() { + return true; + } + + @Override + public boolean isCredentialsNonExpired() { + return true; + } + + @Override + public boolean isEnabled() { + return true; + } +} diff --git a/codeBoy_backend/src/main/java/com/codeboy/mvc/model/dto/IncorrectNote.java b/codeBoy_backend/src/main/java/com/codeboy/mvc/model/dto/IncorrectNote.java index f5d7ed0..288ba54 100644 --- a/codeBoy_backend/src/main/java/com/codeboy/mvc/model/dto/IncorrectNote.java +++ b/codeBoy_backend/src/main/java/com/codeboy/mvc/model/dto/IncorrectNote.java @@ -15,4 +15,7 @@ public class IncorrectNote { private long incorrectNoteId; private long memberId; private long problemId; + private long userProblemId; + private Boolean isUserProblem; + } diff --git a/codeBoy_backend/src/main/java/com/codeboy/mvc/model/dto/Member.java b/codeBoy_backend/src/main/java/com/codeboy/mvc/model/dto/Member.java index f657755..d87bfac 100644 --- a/codeBoy_backend/src/main/java/com/codeboy/mvc/model/dto/Member.java +++ b/codeBoy_backend/src/main/java/com/codeboy/mvc/model/dto/Member.java @@ -1,7 +1,9 @@ package com.codeboy.mvc.model.dto; import java.sql.Timestamp; + import com.codeboy.common.Status; + import io.swagger.v3.oas.annotations.media.Schema; import lombok.AllArgsConstructor; import lombok.Getter; @@ -20,7 +22,10 @@ public class Member { private String nickname; private String email; private Timestamp signupDate; - private Status status; + private Boolean isActive; private Timestamp deletedDate; + private String role; + + } diff --git a/codeBoy_backend/src/main/java/com/codeboy/mvc/model/dto/MemberUpdateRequest.java b/codeBoy_backend/src/main/java/com/codeboy/mvc/model/dto/MemberUpdateRequest.java deleted file mode 100644 index eb94e12..0000000 --- a/codeBoy_backend/src/main/java/com/codeboy/mvc/model/dto/MemberUpdateRequest.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.codeboy.mvc.model.dto; - -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; - -@AllArgsConstructor -@NoArgsConstructor -@Getter -@Setter -@Schema(description="유저 정보 수정 DTO") -public class MemberUpdateRequest { - private String nickName; - private String id; - private String email; -} diff --git a/codeBoy_backend/src/main/java/com/codeboy/mvc/model/dto/Problem.java b/codeBoy_backend/src/main/java/com/codeboy/mvc/model/dto/Problem.java index e93caa9..605793f 100644 --- a/codeBoy_backend/src/main/java/com/codeboy/mvc/model/dto/Problem.java +++ b/codeBoy_backend/src/main/java/com/codeboy/mvc/model/dto/Problem.java @@ -20,7 +20,7 @@ public class Problem { private String choice2; private String choice3; private String choice4; - private String answer; + private int answerChoice; private Category category; - + } diff --git a/codeBoy_backend/src/main/java/com/codeboy/mvc/model/dto/QuizRoom.java b/codeBoy_backend/src/main/java/com/codeboy/mvc/model/dto/QuizRoom.java index 059c4aa..01ca516 100644 --- a/codeBoy_backend/src/main/java/com/codeboy/mvc/model/dto/QuizRoom.java +++ b/codeBoy_backend/src/main/java/com/codeboy/mvc/model/dto/QuizRoom.java @@ -13,4 +13,6 @@ @Schema(description="퀴즈방 DTO") public class QuizRoom { private long roomId; + + } diff --git a/codeBoy_backend/src/main/java/com/codeboy/mvc/model/dto/QuizRoomMember.java b/codeBoy_backend/src/main/java/com/codeboy/mvc/model/dto/QuizRoomMember.java index d6cdd6e..8079a50 100644 --- a/codeBoy_backend/src/main/java/com/codeboy/mvc/model/dto/QuizRoomMember.java +++ b/codeBoy_backend/src/main/java/com/codeboy/mvc/model/dto/QuizRoomMember.java @@ -12,8 +12,34 @@ @Setter @Schema(description="회원 퀴즈방 중간 DTO") public class QuizRoomMember { - private long quizRoomMemberId; - private long memberId; - private long roomId; + private Long quizRoomMemberId; + private Long memberId; + private Long roomId; private Boolean isHost; + public Long getQuizRoomMemberId() { + return quizRoomMemberId; + } + public void setQuizRoomMemberId(long quizRoomMemberId) { + this.quizRoomMemberId = quizRoomMemberId; + } + public Long getMemberId() { + return memberId; + } + public void setMemberId(long memberId) { + this.memberId = memberId; + } + public Long getRoomId() { + return roomId; + } + public void setRoomId(long roomId) { + this.roomId = roomId; + } + public Boolean getIsHost() { + return isHost; + } + public void setIsHost(Boolean isHost) { + this.isHost = isHost; + } + + } diff --git a/codeBoy_backend/src/main/java/com/codeboy/mvc/model/dto/UserProblem.java b/codeBoy_backend/src/main/java/com/codeboy/mvc/model/dto/UserProblem.java index fa4f9b3..1a93309 100644 --- a/codeBoy_backend/src/main/java/com/codeboy/mvc/model/dto/UserProblem.java +++ b/codeBoy_backend/src/main/java/com/codeboy/mvc/model/dto/UserProblem.java @@ -14,14 +14,12 @@ @Getter @Setter public class UserProblem { - private long userProblemId; + private Long userProblemId; private String problemDescription; - private Category category; private String choice1; private String choice2; private String choice3; private String choice4; - private String answer; - private int commentCount; - private long userProblemSetId; + private int answerChoice; + private Long userProblemSetId; } diff --git a/codeBoy_backend/src/main/java/com/codeboy/mvc/model/dto/UserProblemSet.java b/codeBoy_backend/src/main/java/com/codeboy/mvc/model/dto/UserProblemSet.java index d460eb4..03f4931 100644 --- a/codeBoy_backend/src/main/java/com/codeboy/mvc/model/dto/UserProblemSet.java +++ b/codeBoy_backend/src/main/java/com/codeboy/mvc/model/dto/UserProblemSet.java @@ -1,17 +1,26 @@ package com.codeboy.mvc.model.dto; +import com.codeboy.common.Category; import io.swagger.v3.oas.annotations.media.Schema; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; +import lombok.ToString; + +import java.sql.Timestamp; @Schema(description = "유저제작문제세트 DTO") @NoArgsConstructor @AllArgsConstructor @Getter @Setter +@ToString public class UserProblemSet { - private long userProblemSetId; - private long memberId; + private Long userProblemSetId; + private String problemSetTitle; + private Timestamp createdAt; + private Long memberId; + private Category category; + private int commentCount; } diff --git a/codeBoy_backend/src/main/java/com/codeboy/mvc/model/dto/UserScore.java b/codeBoy_backend/src/main/java/com/codeboy/mvc/model/dto/UserScore.java index e65a711..6a323ad 100644 --- a/codeBoy_backend/src/main/java/com/codeboy/mvc/model/dto/UserScore.java +++ b/codeBoy_backend/src/main/java/com/codeboy/mvc/model/dto/UserScore.java @@ -15,6 +15,4 @@ public class UserScore { private long memberId; private int score; - - } diff --git a/codeBoy_backend/src/main/java/com/codeboy/mvc/model/dto/request/AIProblemRequest.java b/codeBoy_backend/src/main/java/com/codeboy/mvc/model/dto/request/AIProblemRequest.java new file mode 100644 index 0000000..9c69b63 --- /dev/null +++ b/codeBoy_backend/src/main/java/com/codeboy/mvc/model/dto/request/AIProblemRequest.java @@ -0,0 +1,10 @@ +package com.codeboy.mvc.model.dto.request; + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class AIProblemRequest { + private String category; // 정보처리기사 or SQLD +} diff --git a/codeBoy_backend/src/main/java/com/codeboy/mvc/model/dto/request/CommentUpdateRequest.java b/codeBoy_backend/src/main/java/com/codeboy/mvc/model/dto/request/CommentUpdateRequest.java new file mode 100644 index 0000000..5614319 --- /dev/null +++ b/codeBoy_backend/src/main/java/com/codeboy/mvc/model/dto/request/CommentUpdateRequest.java @@ -0,0 +1,17 @@ +package com.codeboy.mvc.model.dto.request; + + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@AllArgsConstructor +@NoArgsConstructor +@Getter +@Setter +public class CommentUpdateRequest { + //댓글을 업데이트할 때 content만 수정하니 수정할 content만 전달하는 DTO + private String content; + private long memberId; +} diff --git a/codeBoy_backend/src/main/java/com/codeboy/mvc/model/dto/request/DuplicateCheckRequest.java b/codeBoy_backend/src/main/java/com/codeboy/mvc/model/dto/request/DuplicateCheckRequest.java new file mode 100644 index 0000000..8a366f4 --- /dev/null +++ b/codeBoy_backend/src/main/java/com/codeboy/mvc/model/dto/request/DuplicateCheckRequest.java @@ -0,0 +1,14 @@ +package com.codeboy.mvc.model.dto.request; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@NoArgsConstructor +@AllArgsConstructor +@Getter +@Setter +public class DuplicateCheckRequest { + private String value; +} diff --git a/codeBoy_backend/src/main/java/com/codeboy/mvc/model/dto/request/IncorrectNoteRequest.java b/codeBoy_backend/src/main/java/com/codeboy/mvc/model/dto/request/IncorrectNoteRequest.java new file mode 100644 index 0000000..ffc92f4 --- /dev/null +++ b/codeBoy_backend/src/main/java/com/codeboy/mvc/model/dto/request/IncorrectNoteRequest.java @@ -0,0 +1,14 @@ +package com.codeboy.mvc.model.dto.request; + +import lombok.*; + +@NoArgsConstructor +@AllArgsConstructor +@Getter +@Setter +@ToString +public class IncorrectNoteRequest { + private Long problemId; + private Long userProblemId; + private Boolean isUserProblem; +} diff --git a/codeBoy_backend/src/main/java/com/codeboy/mvc/model/dto/request/JoinQuizRoomRequest.java b/codeBoy_backend/src/main/java/com/codeboy/mvc/model/dto/request/JoinQuizRoomRequest.java new file mode 100644 index 0000000..4e0d70d --- /dev/null +++ b/codeBoy_backend/src/main/java/com/codeboy/mvc/model/dto/request/JoinQuizRoomRequest.java @@ -0,0 +1,13 @@ +package com.codeboy.mvc.model.dto.request; + +import lombok.*; + +@NoArgsConstructor +@AllArgsConstructor +@Getter +@Setter +@ToString +public class JoinQuizRoomRequest { + private long roomId; + +} diff --git a/codeBoy_backend/src/main/java/com/codeboy/mvc/model/dto/request/JoinRequest.java b/codeBoy_backend/src/main/java/com/codeboy/mvc/model/dto/request/JoinRequest.java new file mode 100644 index 0000000..e38606d --- /dev/null +++ b/codeBoy_backend/src/main/java/com/codeboy/mvc/model/dto/request/JoinRequest.java @@ -0,0 +1,15 @@ +package com.codeboy.mvc.model.dto.request; + +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Getter +@Setter +@NoArgsConstructor +public class JoinRequest { + private String id; + private String password; + private String nickname; + private String email; +} diff --git a/codeBoy_backend/src/main/java/com/codeboy/mvc/model/dto/request/LoginRequest.java b/codeBoy_backend/src/main/java/com/codeboy/mvc/model/dto/request/LoginRequest.java new file mode 100644 index 0000000..3ddd898 --- /dev/null +++ b/codeBoy_backend/src/main/java/com/codeboy/mvc/model/dto/request/LoginRequest.java @@ -0,0 +1,16 @@ +package com.codeboy.mvc.model.dto.request; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +@AllArgsConstructor +@NoArgsConstructor +@Setter +@Getter +public class LoginRequest { + //로그인을 할 때에는 id와 password만 사용하니 이를 간편하게 전달하기 위한 DTO + private String id; + private String password; + +} diff --git a/codeBoy_backend/src/main/java/com/codeboy/mvc/model/dto/request/MemberUpdateRequest.java b/codeBoy_backend/src/main/java/com/codeboy/mvc/model/dto/request/MemberUpdateRequest.java new file mode 100644 index 0000000..86afc67 --- /dev/null +++ b/codeBoy_backend/src/main/java/com/codeboy/mvc/model/dto/request/MemberUpdateRequest.java @@ -0,0 +1,17 @@ +package com.codeboy.mvc.model.dto.request; + + import io.swagger.v3.oas.annotations.media.Schema; + import lombok.AllArgsConstructor; + import lombok.Getter; + import lombok.NoArgsConstructor; + import lombok.Setter; + +@AllArgsConstructor +@NoArgsConstructor +@Getter +@Setter +@Schema(description="유저 정보 수정 DTO") +public class MemberUpdateRequest { + private String nickname; + private String email; +} diff --git a/codeBoy_backend/src/main/java/com/codeboy/mvc/model/dto/request/ProblemSetRequest.java b/codeBoy_backend/src/main/java/com/codeboy/mvc/model/dto/request/ProblemSetRequest.java new file mode 100644 index 0000000..f3b6347 --- /dev/null +++ b/codeBoy_backend/src/main/java/com/codeboy/mvc/model/dto/request/ProblemSetRequest.java @@ -0,0 +1,21 @@ +package com.codeboy.mvc.model.dto.request; + +import com.codeboy.common.Category; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.sql.Timestamp; + +@AllArgsConstructor +@NoArgsConstructor +@Getter +@Setter +@Schema(description="유저 문제 세트 등록 DTO") +public class ProblemSetRequest { + private String problemSetTitle; + private Timestamp createdAt; + private Category category; +} diff --git a/codeBoy_backend/src/main/java/com/codeboy/mvc/model/dto/response/ApiResponse.java b/codeBoy_backend/src/main/java/com/codeboy/mvc/model/dto/response/ApiResponse.java new file mode 100644 index 0000000..c06d007 --- /dev/null +++ b/codeBoy_backend/src/main/java/com/codeboy/mvc/model/dto/response/ApiResponse.java @@ -0,0 +1,36 @@ +package com.codeboy.mvc.model.dto.response; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.springframework.http.HttpStatus; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class ApiResponse { + private HttpStatus status; + private String message; + private T data; + + + +// public ApiResponse(HttpStatus status, String message, T data) { +// super(); +// this.status = status; +// this.message = message; +// this.data = data; +// } +// + public static ApiResponse success(HttpStatus status, T data) { + return new ApiResponse<>(status, null, data); + } + + public static ApiResponse success(HttpStatus status, String message, T data) { + return new ApiResponse<>(status, message, data); + } + + public static ApiResponse failure(HttpStatus status, String message) { + return new ApiResponse<>(status, message, null); + } +} diff --git a/codeBoy_backend/src/main/java/com/codeboy/mvc/model/dto/response/DuplicateCheckResponse.java b/codeBoy_backend/src/main/java/com/codeboy/mvc/model/dto/response/DuplicateCheckResponse.java new file mode 100644 index 0000000..17a6543 --- /dev/null +++ b/codeBoy_backend/src/main/java/com/codeboy/mvc/model/dto/response/DuplicateCheckResponse.java @@ -0,0 +1,15 @@ +package com.codeboy.mvc.model.dto.response; + + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@AllArgsConstructor +@NoArgsConstructor +@Getter +@Setter +public class DuplicateCheckResponse { + private boolean duplicated; +} diff --git a/codeBoy_backend/src/main/java/com/codeboy/mvc/model/dto/response/GetQuizRoomMembersResponse.java b/codeBoy_backend/src/main/java/com/codeboy/mvc/model/dto/response/GetQuizRoomMembersResponse.java new file mode 100644 index 0000000..9ec59ed --- /dev/null +++ b/codeBoy_backend/src/main/java/com/codeboy/mvc/model/dto/response/GetQuizRoomMembersResponse.java @@ -0,0 +1,13 @@ +package com.codeboy.mvc.model.dto.response; + +import lombok.*; + +@Data +@AllArgsConstructor +@NoArgsConstructor +@Getter +@Setter +public class GetQuizRoomMembersResponse { + private Long memberId; + private String nickname; +} diff --git a/codeBoy_backend/src/main/java/com/codeboy/mvc/model/dto/response/IncorrectNoteResponse.java b/codeBoy_backend/src/main/java/com/codeboy/mvc/model/dto/response/IncorrectNoteResponse.java new file mode 100644 index 0000000..f86a4c2 --- /dev/null +++ b/codeBoy_backend/src/main/java/com/codeboy/mvc/model/dto/response/IncorrectNoteResponse.java @@ -0,0 +1,20 @@ +package com.codeboy.mvc.model.dto.response; + +import com.codeboy.common.Category; +import lombok.*; + +@Data +@AllArgsConstructor +@NoArgsConstructor +@Getter +@Setter +public class IncorrectNoteResponse { + private Long incorrectNoteId; + private String problemDescription; + private String choice1; + private String choice2; + private String choice3; + private String choice4; + private String answer; + private Category category; +} diff --git a/codeBoy_backend/src/main/java/com/codeboy/mvc/model/dto/response/LoginResponse.java b/codeBoy_backend/src/main/java/com/codeboy/mvc/model/dto/response/LoginResponse.java new file mode 100644 index 0000000..4b076ae --- /dev/null +++ b/codeBoy_backend/src/main/java/com/codeboy/mvc/model/dto/response/LoginResponse.java @@ -0,0 +1,14 @@ +package com.codeboy.mvc.model.dto.response; + +// 예: com.codeboy.mvc.model.dto.response 패키지에 둔다 +import com.codeboy.mvc.model.dto.Member; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public class LoginResponse { + private String accessToken; + private String refreshToken; // 추가 + private Long memberId; +} diff --git a/codeBoy_backend/src/main/java/com/codeboy/mvc/model/service/AIProblemService.java b/codeBoy_backend/src/main/java/com/codeboy/mvc/model/service/AIProblemService.java new file mode 100644 index 0000000..06b9a64 --- /dev/null +++ b/codeBoy_backend/src/main/java/com/codeboy/mvc/model/service/AIProblemService.java @@ -0,0 +1,11 @@ +package com.codeboy.mvc.model.service; + + +import com.codeboy.mvc.model.dto.AIProblemDto; +import com.codeboy.mvc.model.dto.request.AIProblemRequest; + +import java.util.List; + +public interface AIProblemService { + List generateProblems(AIProblemRequest request); +} diff --git a/codeBoy_backend/src/main/java/com/codeboy/mvc/model/service/CommentService.java b/codeBoy_backend/src/main/java/com/codeboy/mvc/model/service/CommentService.java index 962d5ec..d1b02dd 100644 --- a/codeBoy_backend/src/main/java/com/codeboy/mvc/model/service/CommentService.java +++ b/codeBoy_backend/src/main/java/com/codeboy/mvc/model/service/CommentService.java @@ -1,5 +1,24 @@ package com.codeboy.mvc.model.service; -public class CommentService { +import java.util.List; + +import com.codeboy.mvc.model.dto.Comment; + +public interface CommentService { + /** + * 유저제작 문제 세트Id로 조회 + * @param userProblemSetId + * @return + */ + public List getAllCommentsById(Long userProblemSetId); + + //유저제작 문제 세트Id로 조회후 해당 세트에 댓글 작성 + public int addComment(Long userProblemSetId, Comment comment); +// + public int updateComment(Long commentId, Comment comment); +// + public int deleteComment(Long commentId); + + public Long getCommentOwnerId(Long commentId); } diff --git a/codeBoy_backend/src/main/java/com/codeboy/mvc/model/service/CommentServiceImpl.java b/codeBoy_backend/src/main/java/com/codeboy/mvc/model/service/CommentServiceImpl.java new file mode 100644 index 0000000..2d1dedd --- /dev/null +++ b/codeBoy_backend/src/main/java/com/codeboy/mvc/model/service/CommentServiceImpl.java @@ -0,0 +1,58 @@ +package com.codeboy.mvc.model.service; + +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import com.codeboy.mvc.model.dao.CommentDao; +import com.codeboy.mvc.model.dto.Comment; + +@Service +public class CommentServiceImpl implements CommentService { + + private final CommentDao commentDao; + + @Autowired + public CommentServiceImpl(CommentDao commentDao) { + this.commentDao = commentDao; + } + + + + // 유저제작 문제 세트Id로 조회 + @Override + public List getAllCommentsById(Long userProblemSetId) { + return commentDao.selectCommentsByuserProblemSetId(userProblemSetId); + } + + @Override + public int addComment(Long userProblemSetId, Comment comment ) { + + return commentDao.insertComment(userProblemSetId, comment); + } + + + + @Override + public int updateComment(Long commentId, Comment comment) { + return commentDao.updateComment(commentId, comment); + + } + + + + @Override + public int deleteComment(Long commentId) { + return commentDao.deleteComment(commentId); + + } + + + @Override + public Long getCommentOwnerId(Long commentId){ + return commentDao.selectCommentOwnerId(commentId); + } + + +} diff --git a/codeBoy_backend/src/main/java/com/codeboy/mvc/model/service/CustomerUserDetailService.java b/codeBoy_backend/src/main/java/com/codeboy/mvc/model/service/CustomerUserDetailService.java new file mode 100644 index 0000000..96b2b63 --- /dev/null +++ b/codeBoy_backend/src/main/java/com/codeboy/mvc/model/service/CustomerUserDetailService.java @@ -0,0 +1,28 @@ +package com.codeboy.mvc.model.service; + +import com.codeboy.mvc.model.dao.MemberDao; +import com.codeboy.mvc.model.dto.CustomUserDetails; +import com.codeboy.mvc.model.dto.Member; +import lombok.RequiredArgsConstructor; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class CustomerUserDetailService implements UserDetailsService { + + private final MemberDao memberDao; + + @Override + public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { + + Member userData = memberDao.findByUserId(username); + + if (userData == null) { + throw new UsernameNotFoundException("User not found: " + username); + } + return new CustomUserDetails(userData); + } +} diff --git a/codeBoy_backend/src/main/java/com/codeboy/mvc/model/service/IncorrectNoteService.java b/codeBoy_backend/src/main/java/com/codeboy/mvc/model/service/IncorrectNoteService.java index e8d653a..a3a64b2 100644 --- a/codeBoy_backend/src/main/java/com/codeboy/mvc/model/service/IncorrectNoteService.java +++ b/codeBoy_backend/src/main/java/com/codeboy/mvc/model/service/IncorrectNoteService.java @@ -1,5 +1,78 @@ package com.codeboy.mvc.model.service; +import com.codeboy.mvc.model.dao.IncorrectNoteDao; +import com.codeboy.mvc.model.dto.response.IncorrectNoteResponse; +import org.apache.ibatis.javassist.NotFoundException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import java.math.BigInteger; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@Service public class IncorrectNoteService { + @Autowired + private IncorrectNoteDao incorrectNoteDao; + + public Long addIncorrectNote(Long memberId, Long problemId, Long userProblemId, Boolean isUserProblem) { + if (!isUserProblem) { + boolean exists = incorrectNoteDao.existsProblemById(problemId); + if (!exists) { + throw new IllegalArgumentException("DB에 존재하지 않는 문제 ID입니다.:" + problemId); + } + } else { + boolean exists = incorrectNoteDao.existsUserProblemById(userProblemId); + if (!exists) { + throw new IllegalArgumentException("DB에 존재하지 않는 유저문제 ID입니다.:" + userProblemId); + } + } + + //TODO : DB에 존재하는지 검증하는 로직 필요 + if (memberId == null) { + throw new IllegalArgumentException("유효하지 않은 회원 ID입니다.: " + memberId); + } + if ((!isUserProblem && (problemId == null || userProblemId != null)) || + (isUserProblem && (problemId != null || userProblemId == null))) { + throw new IllegalArgumentException("유효하지 않은 문제 ID 입니다.:" ); + } + + Map params = new HashMap<>(); + params.put("memberId", memberId); + params.put("problemId", problemId); + params.put("userProblemId", userProblemId); + params.put("isUserProblem", isUserProblem); + + params.put("incorrectNoteId", null); + + incorrectNoteDao.insertIncorrectProblem(params); + + BigInteger id = (BigInteger) params.get("incorrectNoteId"); + return id != null ? id.longValue() : null; + + } + + public List getIncorrectNoteList(Long memberId) throws NotFoundException { + //TODO : DB에 존재하는지 검증하는 로직 +// if (memberId == null || !memberDao.existsById(memberId)) { +// throw new IllegalArgumentException("유효하지 않은 회원 ID입니다."); +// } + if (memberId == null) { + throw new IllegalArgumentException("유효하지 않은 회원 ID입니다."); + } + List list = incorrectNoteDao.selectIncorrectProblems(memberId); + + if (list == null || list.isEmpty()) { + throw new NotFoundException("오답노트를 조회할 수 없습니다."); + } + return list; + } + public void deleteIncorrectNote(long incorrectNoteId) { + boolean exists = incorrectNoteDao.existsIncorrectNoteById(incorrectNoteId); + if (!exists) { + throw new IllegalArgumentException("DB에 존재하지 않는 오답노트ID 입니다.: " + incorrectNoteId); + } + incorrectNoteDao.deleteIncorrectProblem(incorrectNoteId); + } } diff --git a/codeBoy_backend/src/main/java/com/codeboy/mvc/model/service/JoinService.java b/codeBoy_backend/src/main/java/com/codeboy/mvc/model/service/JoinService.java new file mode 100644 index 0000000..cdd1dda --- /dev/null +++ b/codeBoy_backend/src/main/java/com/codeboy/mvc/model/service/JoinService.java @@ -0,0 +1,55 @@ +package com.codeboy.mvc.model.service; + +import com.codeboy.mvc.model.dao.MemberDao; +import com.codeboy.mvc.model.dao.UserScoreDao; +import com.codeboy.mvc.model.dto.Member; +import com.codeboy.mvc.model.dto.UserScore; +import com.codeboy.mvc.model.dto.request.JoinRequest; +import com.codeboy.mvc.model.dto.request.LoginRequest; +import lombok.RequiredArgsConstructor; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class JoinService { + + private final MemberDao memberDao; + private final UserScoreDao userScoreDao; + private final BCryptPasswordEncoder bCryptPasswordEncoder; + + public Long joinProcess(JoinRequest req) { + + // 1) 중복 아이디 체크 + Boolean exists = memberDao.existByUserId(req.getId()); + + if (Boolean.TRUE.equals(exists)) { + throw new IllegalArgumentException("이미 존재하는 아이디입니다."); + } + + // 2) Member 매핑 + Member member = new Member(); + member.setId(req.getId()); + member.setPassword(bCryptPasswordEncoder.encode(req.getPassword())); + member.setNickname(req.getNickname()); + member.setEmail(req.getEmail()); + member.setIsActive(true); + member.setRole("USER"); + + // 3) DB Insert + int rows = memberDao.insertMember(member); + if (rows != 1) { + throw new IllegalStateException("회원 가입에 실패했습니다. (insert rows = " + rows + ")"); + } + //회원가입에 성공했다면 score에 0점 넣기 + UserScore userScore = new UserScore(); + userScore.setMemberId(member.getMemberId()); + userScore.setScore(0); + + int scoreRows = userScoreDao.insertScore(userScore); + if (scoreRows != 1) { + throw new IllegalStateException("초기 점수 등록에 실패했습니다"); + } + return member.getMemberId(); + } +} diff --git a/codeBoy_backend/src/main/java/com/codeboy/mvc/model/service/MemberService.java b/codeBoy_backend/src/main/java/com/codeboy/mvc/model/service/MemberService.java index 90316ff..697bd31 100644 --- a/codeBoy_backend/src/main/java/com/codeboy/mvc/model/service/MemberService.java +++ b/codeBoy_backend/src/main/java/com/codeboy/mvc/model/service/MemberService.java @@ -1,19 +1,31 @@ package com.codeboy.mvc.model.service; import com.codeboy.mvc.model.dto.Member; -import com.codeboy.mvc.model.dto.MemberUpdateRequest; -import com.codeboy.mvc.model.dto.MemberUpdateRequest; +import com.codeboy.mvc.model.dto.request.MemberUpdateRequest; public interface MemberService { - //회원가입 - public void signupMember(); - //회원 정보 가져오기기 - public Member readMember(String id, String password); + // 회원정보 조회 + Member getMemberById(Long memberId); - //회원 탈퇴 - public void withdrawal(int memberId); + // 회원 탈퇴 + void deactivateMember(Long memberId); + + // 회원 정보 수정 + void updateMember(Long memberId, MemberUpdateRequest memberUpdateRequest); + + // 중복검사 (ID) + boolean checkIdDuplicate(String id); + + // 중복검사 (Nickname) + boolean checkNicknameDuplicate(String nickname); + + // 중복검사 (Email) + boolean checkEmailDuplicate(String email); + + int signUp(Member member); + - //회원정보 수정-> db에서는 patch(nickname, email, id) - public void updateMember(long memberId, MemberUpdateRequest memberUpdateRequest); + // 로그인 + Member login(String id, String password); } diff --git a/codeBoy_backend/src/main/java/com/codeboy/mvc/model/service/MemberServiceImpl.java b/codeBoy_backend/src/main/java/com/codeboy/mvc/model/service/MemberServiceImpl.java new file mode 100644 index 0000000..7ff5bb0 --- /dev/null +++ b/codeBoy_backend/src/main/java/com/codeboy/mvc/model/service/MemberServiceImpl.java @@ -0,0 +1,138 @@ +package com.codeboy.mvc.model.service; + +import com.codeboy.mvc.model.dao.MemberDao; +import com.codeboy.mvc.model.dao.UserScoreDao; +import com.codeboy.mvc.model.dto.Member; +import com.codeboy.mvc.model.dto.UserScore; +import com.codeboy.mvc.model.dto.request.MemberUpdateRequest; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.NoSuchElementException; + +@Service +public class MemberServiceImpl implements MemberService { + @Autowired + MemberDao memberDao; + + @Autowired + UserScoreDao userScoreDao; + + //회원 정보 가져오기기 + public Member getMemberById(Long memberId) { + isMemberExist(memberId); + + Member member = memberDao.selectMemberById(memberId); + if (member == null) { + throw new NoSuchElementException("Id에 해당하는 회원 정보를 찾을 수 없습니다. memberId:" + memberId); + } + return member; + }; + + //회원 탈퇴 + public void deactivateMember(Long memberId){ + isMemberExist(memberId); + + int updatedRows = memberDao.deactivateMemberById(memberId); + if (updatedRows == 0) { + throw new IllegalStateException("회원 탈퇴 실패 : memberId: " + memberId); + } + } + + + public void updateMember(Long memberId, MemberUpdateRequest memberUpdateRequest) { + isMemberExist(memberId); + + // 현재 DB에 저장된 회원 정보 + Member current = memberDao.selectMemberById(memberId); + if (current == null) { + throw new IllegalStateException("회원 정보 수정 실패: 존재하지 않는 회원입니다. memberId: " + memberId); + } + + String nickname = memberUpdateRequest.getNickname(); + String email = memberUpdateRequest.getEmail(); + + // ✅ 내 현재 값과 비교해서, 실제로 바뀌는 필드만 중복체크 + validateMemberUpdate(current, nickname, email); + + int affectedRows = memberDao.updateMemberById(memberId, memberUpdateRequest); + if (affectedRows == 0) { + throw new IllegalStateException("회원 정보 수정에 실패하였습니다. memberId: " + memberId); + } + } + + public boolean checkIdDuplicate(String id) { + if (id == null) { + throw new IllegalArgumentException("유효하지 않은 Id 입니다."); + } + return memberDao.existsId(id); + } + + public boolean checkNicknameDuplicate(String nickname ) { + if (nickname == null) { + throw new IllegalArgumentException("유효하지 않은 Id 입니다."); + } + return memberDao.existsNickname(nickname); + } + + public boolean checkEmailDuplicate(String email) { + if (email == null) { + throw new IllegalArgumentException("유효하지 않은 Id 입니다."); + } + return memberDao.existsEmail(email); + } + + @Override + public int signUp(Member member) { + UserScore userScore = new UserScore(member.getMemberId(), 0); + userScoreDao.insertScore(userScore); + return memberDao.insertMember(member); + } + + public void isMemberExist(Long memberId) { + if (memberId == null) { + throw new IllegalArgumentException("유효하지 않은 memberId입니다. memberId: " ); + } + + boolean isActive = memberDao.isMemberActive(memberId); + if (!isActive) { + throw new IllegalArgumentException("비활성화된 멤버 id 입니다. memberId: " + memberId); + } + } + + // 최종 제출 시 전체 검증 +// 현재 회원 정보 + 변경 요청값 기준 검증 + public void validateMemberUpdate(Member current, String nickname, String email) { + + // 닉네임 + if (nickname != null && !nickname.equals(current.getNickname())) { + if (memberDao.existsNickname(nickname)) { + throw new IllegalArgumentException("중복된 닉네임입니다."); + } + } + + // 이메일 + if (email != null && !email.equals(current.getEmail())) { + if (memberDao.existsEmail(email)) { + throw new IllegalArgumentException("중복된 이메일입니다."); + } + } + } + + + // 로그인 + public Member login(String id, String password) { + if (id == null || password == null) { + throw new IllegalArgumentException("ID와 비밀번호를 입력해주세요."); + } + + Member member = memberDao.selectMemberByIdAndPassword(id, password); + if (member == null) { + throw new IllegalArgumentException("ID 또는 비밀번호가 올바르지 않습니다."); + } + + return member; + } + + +}; \ No newline at end of file diff --git a/codeBoy_backend/src/main/java/com/codeboy/mvc/model/service/ProblemService.java b/codeBoy_backend/src/main/java/com/codeboy/mvc/model/service/ProblemService.java index 959188b..1986fc6 100644 --- a/codeBoy_backend/src/main/java/com/codeboy/mvc/model/service/ProblemService.java +++ b/codeBoy_backend/src/main/java/com/codeboy/mvc/model/service/ProblemService.java @@ -1,5 +1,14 @@ package com.codeboy.mvc.model.service; -public class ProblemService { +import com.codeboy.common.Category; +import com.codeboy.mvc.model.dto.Problem; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Service +public interface ProblemService { + + public List getProblems(Category category); } diff --git a/codeBoy_backend/src/main/java/com/codeboy/mvc/model/service/ProblemServiceImpl.java b/codeBoy_backend/src/main/java/com/codeboy/mvc/model/service/ProblemServiceImpl.java new file mode 100644 index 0000000..ad0265c --- /dev/null +++ b/codeBoy_backend/src/main/java/com/codeboy/mvc/model/service/ProblemServiceImpl.java @@ -0,0 +1,34 @@ +package com.codeboy.mvc.model.service; + +import com.codeboy.common.Category; +import com.codeboy.mvc.model.dao.ProblemDao; +import com.codeboy.mvc.model.dto.Problem; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Service +public class ProblemServiceImpl implements ProblemService{ + @Autowired + private ProblemDao problemDao; + + @Override + public List getProblems(Category category) { +// if (limit <= 0) { +// throw new IllegalArgumentException("limit의 수는 0이상이어야 합니다. limit: " + limit); +// } + if (category == null) { + throw new IllegalArgumentException("category의 값은 null이 될 수 없습니다. : category: "); + } + List problems = problemDao.selectProblem(category); + + //만약 요청 수보다 존재하는 문제 수가 적다면 +// if (limit > problems.size()) { +// throw new IllegalArgumentException("limit의 값은 문제 수보다 클 수 없습니다. problems: " + problems.size()); +// } + + return problems; + } + +} diff --git a/codeBoy_backend/src/main/java/com/codeboy/mvc/model/service/QuizRoomService.java b/codeBoy_backend/src/main/java/com/codeboy/mvc/model/service/QuizRoomService.java index cd3f65e..c66dde8 100644 --- a/codeBoy_backend/src/main/java/com/codeboy/mvc/model/service/QuizRoomService.java +++ b/codeBoy_backend/src/main/java/com/codeboy/mvc/model/service/QuizRoomService.java @@ -2,22 +2,108 @@ import java.util.List; +import com.codeboy.mvc.model.dto.response.GetQuizRoomMembersResponse; +import org.springframework.beans.factory.annotation.Autowired; + +import com.codeboy.mvc.model.dao.QuizRoomDao; import com.codeboy.mvc.model.dto.QuizRoom; +import com.codeboy.mvc.model.dto.QuizRoomMember; +import org.springframework.stereotype.Service; + +@Service +public class QuizRoomService{ + @Autowired + private QuizRoomDao quizRoomDao; + + public List getQuizRoomList() { + List quizRooms = quizRoomDao.selectAllQuizRoom(); + if (quizRooms == null) { + throw new IllegalStateException("퀴즈방 정보를 가져오지 못했습니다"); + } + return quizRooms; + } + + public List getOneQuizRoomMember(long roomId) { + validateRoomId(roomId); + + List memberList = quizRoomDao.selectOneQuizRoom(roomId); + if (memberList.isEmpty()) { + throw new IllegalStateException("퀴즈방에 참가자가 없습니다."); + } + return memberList; + } + + public long createQuizRoom() { + QuizRoom room = new QuizRoom(); + quizRoomDao.insertQuizRoom(room); + + Long roomId = room.getRoomId(); + + validateRoomId(roomId); + return roomId; + } + + public void joinQuizRoom(QuizRoomMember quizRoomMember) { + Long memberId = quizRoomMember.getMemberId(); + Long roomId = quizRoomMember.getRoomId(); + validateRoomId(roomId); + memberDuplicateCheck(memberId); + + //멤버 id가 실제로 존재하는지 확인 + if (memberId == null) { + throw new IllegalArgumentException("멤버 ID가 유효하지 않습니다. :" ); + } + //TODO : 채팅방 참여 인원이 초과되었는지 검증하는 로직 추가 + int rowAffected = quizRoomDao.insertMemberToQuizRoom(quizRoomMember); + if (rowAffected <= 0) { + throw new IllegalStateException("퀴즈방 입장에 실패하였습니다."); + } + } + + public void deleteQuizRoom(long roomId) { + validateRoomId(roomId); + boolean deleted = quizRoomDao.deleteQuizRoom(roomId); + if (!deleted) { + throw new IllegalStateException("퀴즈방 삭제에 실패했습니다."); + } + } + + private void validateRoomId(Long roomId) { + boolean existRoom = quizRoomDao.existsQuizRoom(roomId) > 0; + if (roomId == null || roomId <= 0 || !existRoom) { + throw new IllegalArgumentException("퀴즈방 ID가 유효하지 않습니다. : " + roomId); + } + } + + private void memberDuplicateCheck(Long memberId) { + boolean duplicated = quizRoomDao.isDuplicatedMember(memberId) > 0; + if (duplicated) { + throw new IllegalArgumentException("한 멤버는 하나의 채팅방에만 들어갈 수 있습니다."); + } + } + + public void leaveQuizRoom(long roomId, long memberId) { + + // 1) 현재 방에 이 멤버가 실제로 있는지 확인 + QuizRoomMember quizRoomMember = quizRoomDao.selectQuizRoomMember(roomId, memberId); + + if (quizRoomMember == null) { + throw new IllegalArgumentException("해당 멤버는 이 퀴즈방에 참가 중이 아닙니다."); + } + + // TODO: 호스트가 나갈 때의 정책이 따로 있다면 여기서 처리 + // if (quizRoomMember.getIsHost()) { ... } + + // 2) 퀴즈방 멤버 목록에서 삭제 + int deleted = quizRoomDao.deleteQuizRoomMember(roomId, memberId); + + if (deleted != 1) { + throw new IllegalStateException("퀴즈방 퇴장 처리에 실패했습니다."); + } + + // TODO: 마지막 사람이 나가면 방 자체를 삭제할지 여부는 여기서 추가로 정책 결정 가능 + // int remains = quizRoomDao.countMembersInRoom(roomId); + // if (remains == 0) { quizRoomDao.deleteQuizRoom(roomId); } + } -public interface QuizRoomService { - - //퀴즈방 전체 조회 - public List getQuizRoomList(); - - //퀴즈 방 만들기 -> 생성된 퀴즈방 id를 return - //퀴즈방 생성하고 생성된 퀴즈방에 방장 id 넣기 - public int createQuizRoom(long memberId); - - //참가자 채팅방 입장 - public void joinQuizRoom(long memberId, long quizRoomId); - - //퀴즈룸 삭제 - public boolean deleteQuizRoom() - - } diff --git a/codeBoy_backend/src/main/java/com/codeboy/mvc/model/service/ScoreService.java b/codeBoy_backend/src/main/java/com/codeboy/mvc/model/service/ScoreService.java deleted file mode 100644 index 2b2aa4c..0000000 --- a/codeBoy_backend/src/main/java/com/codeboy/mvc/model/service/ScoreService.java +++ /dev/null @@ -1,5 +0,0 @@ -package com.codeboy.mvc.model.service; - -public class ScoreService { - -} diff --git a/codeBoy_backend/src/main/java/com/codeboy/mvc/model/service/UserProblemService.java b/codeBoy_backend/src/main/java/com/codeboy/mvc/model/service/UserProblemService.java index c315c3b..2a25a51 100644 --- a/codeBoy_backend/src/main/java/com/codeboy/mvc/model/service/UserProblemService.java +++ b/codeBoy_backend/src/main/java/com/codeboy/mvc/model/service/UserProblemService.java @@ -1,5 +1,16 @@ package com.codeboy.mvc.model.service; -public class UserProblemService { +import com.codeboy.mvc.model.dto.UserProblem; +import java.util.List; + +public interface UserProblemService { + + List getProblemsByUserProblemSetId(Long userProblemSetId); + + int addUserProblems(List userProblems); + + int updateUserProblem(UserProblem userProblem); + + int deleteUserProblem(Long userProblemId); } diff --git a/codeBoy_backend/src/main/java/com/codeboy/mvc/model/service/UserProblemServiceImpl.java b/codeBoy_backend/src/main/java/com/codeboy/mvc/model/service/UserProblemServiceImpl.java new file mode 100644 index 0000000..e426773 --- /dev/null +++ b/codeBoy_backend/src/main/java/com/codeboy/mvc/model/service/UserProblemServiceImpl.java @@ -0,0 +1,47 @@ +package com.codeboy.mvc.model.service; + +import com.codeboy.mvc.model.dao.UserProblemDao; +import com.codeboy.mvc.model.dto.UserProblem; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Service +public class UserProblemServiceImpl implements UserProblemService { + + private final UserProblemDao userProblemDao; + + public UserProblemServiceImpl(UserProblemDao userProblemDao) { + this.userProblemDao = userProblemDao; + } + + @Override + public List getProblemsByUserProblemSetId(Long userProblemSetId) { + return userProblemDao.selectProblemsByUserProblemSetId(userProblemSetId); + } + + @Override + public int addUserProblems(List userProblems) { + if (userProblems == null || userProblems.isEmpty()) { + throw new IllegalArgumentException("등록할 문제가 하나 이상 있어야 합니다."); + } + return userProblemDao.insertUserProblemList(userProblems); + } + + @Override + public int updateUserProblem(UserProblem userProblem) { + if (userProblem == null || userProblem.getUserProblemId() == null) { + throw new IllegalArgumentException("수정할 문제를 찾을 수 없습니다. 문제Id를 확인하세요."); + } + System.out.println("디버깅2"); + return userProblemDao.updateUserProblem(userProblem); + } + + @Override + public int deleteUserProblem(Long userProblemId) { + if (userProblemId == null) { + throw new IllegalArgumentException("삭제할 문제를 찾을 수 없습니다. 삭제할 문제 ID를 확인하세요."); + } + return userProblemDao.deleteUserProblemById(userProblemId); + } +} diff --git a/codeBoy_backend/src/main/java/com/codeboy/mvc/model/service/UserProblemSetService.java b/codeBoy_backend/src/main/java/com/codeboy/mvc/model/service/UserProblemSetService.java new file mode 100644 index 0000000..ec15d55 --- /dev/null +++ b/codeBoy_backend/src/main/java/com/codeboy/mvc/model/service/UserProblemSetService.java @@ -0,0 +1,15 @@ +package com.codeboy.mvc.model.service; + +import com.codeboy.mvc.model.dto.UserProblemSet; + +import java.util.List; + +public interface UserProblemSetService { + List getAllUserProblemSets(); + + List getUserProblemSetByMemberId(Long memberId); + + int createUserProblemSet(UserProblemSet set); + + int deleteUserProblemSet(Long userProblemSetId); +} diff --git a/codeBoy_backend/src/main/java/com/codeboy/mvc/model/service/UserProblemSetServiceImpl.java b/codeBoy_backend/src/main/java/com/codeboy/mvc/model/service/UserProblemSetServiceImpl.java new file mode 100644 index 0000000..4135072 --- /dev/null +++ b/codeBoy_backend/src/main/java/com/codeboy/mvc/model/service/UserProblemSetServiceImpl.java @@ -0,0 +1,49 @@ +package com.codeboy.mvc.model.service; + +import com.codeboy.mvc.model.dao.UserProblemSetDao; +import com.codeboy.mvc.model.dto.UserProblemSet; +import org.springframework.stereotype.Service; + + +import java.util.List; + +@Service +public class UserProblemSetServiceImpl implements UserProblemSetService { + + private final UserProblemSetDao userProblemSetDao; + + public UserProblemSetServiceImpl(UserProblemSetDao userProblemSetDao) { + this.userProblemSetDao = userProblemSetDao; + } + + @Override + public List getAllUserProblemSets() { + return userProblemSetDao.selectUserProblemSets(); + } + + @Override + public List getUserProblemSetByMemberId(Long memberId) { + if (memberId == null) { + throw new IllegalArgumentException("회원 ID가 필요합니다."); + } + long memberIdl = memberId; + return userProblemSetDao.selectUserProblemSetByMemberId(memberIdl); + } + + @Override + public int createUserProblemSet(UserProblemSet set) { + if (set.getMemberId() == null) { + throw new IllegalArgumentException("회원 ID가 필요합니다."); + } +// long memberId = set.getMemberId(); + return userProblemSetDao.insertUserProblemSet(set); + } + + @Override + public int deleteUserProblemSet(Long userProblemSetId) { + if (userProblemSetId == null) { + throw new IllegalArgumentException("삭제할 문제세트 ID가 필요합니다."); + } + return userProblemSetDao.deleteUserProblemSetById(userProblemSetId); + } +} diff --git a/codeBoy_backend/src/main/java/com/codeboy/mvc/model/service/UserScoreService.java b/codeBoy_backend/src/main/java/com/codeboy/mvc/model/service/UserScoreService.java new file mode 100644 index 0000000..2f07894 --- /dev/null +++ b/codeBoy_backend/src/main/java/com/codeboy/mvc/model/service/UserScoreService.java @@ -0,0 +1,23 @@ +package com.codeboy.mvc.model.service; + +import java.util.List; + +import com.codeboy.mvc.model.dto.UserScore; + +public interface UserScoreService { + + + //회원가입 시 자동으로 리더보드에 보여줄 점수를 등록 디폴트는 0 + int registerScore(UserScore userScore); + + //리더보드에 모든 유저들의 점수정보들을 보여줌 + List getAllUserScores(); + + //한 멤버의 스코어 조회 + UserScore getUserScoreById(Long memberId); + + //멤버 스코어 업데이트 + //업데이트된 행 수 반환 + int updateUser(UserScore userScore); + +} diff --git a/codeBoy_backend/src/main/java/com/codeboy/mvc/model/service/UserScoreServiceImpl.java b/codeBoy_backend/src/main/java/com/codeboy/mvc/model/service/UserScoreServiceImpl.java new file mode 100644 index 0000000..1733b62 --- /dev/null +++ b/codeBoy_backend/src/main/java/com/codeboy/mvc/model/service/UserScoreServiceImpl.java @@ -0,0 +1,71 @@ +package com.codeboy.mvc.model.service; + +import java.util.List; + +import org.springframework.stereotype.Service; + +import com.codeboy.mvc.model.dao.UserScoreDao; +import com.codeboy.mvc.model.dto.UserScore; + + +@Service +public class UserScoreServiceImpl implements UserScoreService { + + private final UserScoreDao userScoreDao; + + public UserScoreServiceImpl(UserScoreDao userScoreDao) { + this.userScoreDao = userScoreDao; + } + + @Override + public int registerScore(UserScore userScore) { + // 1. 파라미터 유효성 검증 → IllegalArgumentException + if (userScore == null) { + throw new IllegalArgumentException("점수 정보가 비어 있습니다."); + } + if (userScore.getMemberId() <= 0) { + throw new IllegalArgumentException("유효하지 않은 회원 ID입니다."); + } + if (userScore.getScore() < 0) { + throw new IllegalArgumentException("점수는 0 이상이어야 합니다."); + } + + // 2. DAO 호출 → 삽입된 row 수 반환 + int result = userScoreDao.insertScore(userScore); + // result가 0인지 아닌지는 컨트롤러에서 판단 + return result; + } + + @Override + public List getAllUserScores() { + // 여기서는 그냥 DAO 결과를 그대로 반환하고 + // 목록이 비었는지 여부는 컨트롤러에서 판단함. + return userScoreDao.selectAllUserScores(); + } + + @Override + public UserScore getUserScoreById(Long memberId) { + if (memberId == null || memberId <= 0) { + throw new IllegalArgumentException("유효하지 않은 회원 ID입니다."); + } + + // 없으면 null 반환 → 컨트롤러에서 404 처리 + return userScoreDao.selectOneUserScore(memberId); + } + + @Override + public int updateUser(UserScore userScore) { + if (userScore == null) { + throw new IllegalArgumentException("점수 정보가 비어 있습니다."); + } + if (userScore.getMemberId() <= 0) { + throw new IllegalArgumentException("유효하지 않은 회원 ID입니다."); + } + if (userScore.getScore() < 0) { + throw new IllegalArgumentException("점수는 0 이상이어야 합니다."); + } + + int result = userScoreDao.updateUserScore(userScore); + return result; + } +} \ No newline at end of file diff --git a/codeBoy_backend/src/main/resources/application.properties b/codeBoy_backend/src/main/resources/application.properties index 603f194..598f3d5 100644 --- a/codeBoy_backend/src/main/resources/application.properties +++ b/codeBoy_backend/src/main/resources/application.properties @@ -9,9 +9,9 @@ spring.application.name=codeBoy_backend # # spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver -spring.datasource.url=jdbc:mysql://localhost:3306/ssafy_board?serverTimezone=UTC -spring.datasource.username=ssafy -spring.datasource.password=ssafy +spring.datasource.url=jdbc:mysql://localhost:3306/board_test?serverTimezone=UTC +spring.datasource.username=root +spring.datasource.password=088alstmd! #mybatis #mapper @@ -21,4 +21,12 @@ mybatis.mapper-locations=classpath:mappers/*.xml mybatis.type-aliases-package=com.codeboy.mvc.model.dto mybatis.configuration.map-underscore-to-camel-case=true -debug=true \ No newline at end of file +debug=true + +#spring security +spring.jwt.secret-key=_1234567890abcdefghijkmnkopqrstu + +spring.flyway.enabled=false + +logging.level.org.mybatis=DEBUG +logging.level.org.springframework.jdbc.core=DEBUG diff --git a/codeBoy_backend/src/main/resources/db/migration/DDL_schema.sql b/codeBoy_backend/src/main/resources/db/migration/DDL_schema.sql new file mode 100644 index 0000000..1508fbf --- /dev/null +++ b/codeBoy_backend/src/main/resources/db/migration/DDL_schema.sql @@ -0,0 +1,221 @@ +-- MySQL Script generated by MySQL Workbench +-- Mon Nov 24 16:21:44 2025 +-- Model: New Model Version: 1.0 +-- MySQL Workbench Forward Engineering + DROP DATABASE IF EXISTS board_test; + CREATE DATABASE board_test; + USE board_test; + +SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0; +SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0; +SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION'; + + +CREATE SCHEMA IF NOT EXISTS `board_test` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci ; +USE board_test; + + +-- ----------------------------------------------------- +-- Table `board_test`.`member` +-- ----------------------------------------------------- +CREATE TABLE IF NOT EXISTS `board_test`.`member` ( + `member_id` BIGINT AUTO_INCREMENT, + `ID` VARCHAR(30) NOT NULL, + `password` CHAR(64) NOT NULL, + `nickname` VARCHAR(30) NOT NULL, + `email` VARCHAR(30) NOT NULL, + `signup_date` TIMESTAMP NOT NULL, + `is_active` TINYINT NOT NULL, + `deleted_date` TIMESTAMP NULL DEFAULT NULL, + `role` VARCHAR(20) NOT NULL DEFAULT 'USER', + PRIMARY KEY (`member_id`)) +ENGINE = InnoDB +DEFAULT CHARACTER SET = utf8mb4 +COLLATE = utf8mb4_0900_ai_ci; + + +-- ----------------------------------------------------- +-- Table `board_test`.`user_problem_set` +-- ----------------------------------------------------- +CREATE TABLE IF NOT EXISTS `board_test`.`user_problem_set` ( + `user_problem_set_id` BIGINT NOT NULL AUTO_INCREMENT, + `member_id` BIGINT NOT NULL, + `category` VARCHAR(50) NOT NULL, + `comment_count` INT NOT NULL DEFAULT 0, + `problem_set_title` VARCHAR(50) NOT NULL, + `created_at` TIMESTAMP NOT NULL DEFAULT NOW(), + PRIMARY KEY (`user_problem_set_id`), + INDEX `FK_member_TO_user_problem_set_1` (`member_id` ASC) VISIBLE, + CONSTRAINT `FK_member_TO_user_problem_set_1` + FOREIGN KEY (`member_id`) + REFERENCES `board_test`.`member` (`member_id`)) + +ENGINE = InnoDB +DEFAULT CHARACTER SET = utf8mb4 +COLLATE = utf8mb4_0900_ai_ci; + + +-- ----------------------------------------------------- +-- Table `board_test`.`comment` +-- ----------------------------------------------------- +CREATE TABLE IF NOT EXISTS `board_test`.`comment` ( + `comment_id` BIGINT NOT NULL AUTO_INCREMENT, + `content` VARCHAR(500) NOT NULL, + `comment_date` TIMESTAMP NOT NULL, + `member_id` BIGINT NOT NULL, + `user_problem_set_id` BIGINT NOT NULL, + PRIMARY KEY (`comment_id`), + INDEX `FK_member_TO_comment_1` (`member_id` ASC) VISIBLE, + INDEX `FK_user_problem_set_TO_comment_1` (`user_problem_set_id` ASC) VISIBLE, + CONSTRAINT `FK_member_TO_comment_1` + FOREIGN KEY (`member_id`) + REFERENCES `board_test`.`member` (`member_id`), + CONSTRAINT `FK_user_problem_set_TO_comment_1` + FOREIGN KEY (`user_problem_set_id`) + REFERENCES `board_test`.`user_problem_set` (`user_problem_set_id`)) +ENGINE = InnoDB +DEFAULT CHARACTER SET = utf8mb4 +COLLATE = utf8mb4_0900_ai_ci; + + + +-- ----------------------------------------------------- +-- Table `board_test`.`problem` +-- ----------------------------------------------------- +CREATE TABLE IF NOT EXISTS `board_test`.`problem` ( + `problem_id` BIGINT NOT NULL AUTO_INCREMENT, + `problem_description` VARCHAR(300) NOT NULL, + `choice_1` VARCHAR(255) NOT NULL, + `choice_2` VARCHAR(255) NOT NULL, + `choice_3` VARCHAR(255) NOT NULL, + `choice_4` VARCHAR(255) NOT NULL, + `answer_choice` TINYINT NOT NULL, + `category` VARCHAR(50) NOT NULL, + PRIMARY KEY (`problem_id`)) +ENGINE = InnoDB +DEFAULT CHARACTER SET = utf8mb4 +COLLATE = utf8mb4_0900_ai_ci; + +-- ----------------------------------------------------- +-- Table `board_test`.`incorrect_note` +-- ----------------------------------------------------- +-- DROP TABLE IF EXISTS incorrect_note; + +CREATE TABLE IF NOT EXISTS `board_test`.`incorrect_note` ( + `incorrect_note_id` BIGINT NOT NULL AUTO_INCREMENT, + `member_id` BIGINT NOT NULL, + `problem_id` BIGINT, + `user_problem_id` BIGINT, + `is_user_problem` TINYINT NOT NULL, + + PRIMARY KEY (`incorrect_note_id`), + INDEX `FK_member_TO_incorrect_note_1` (`member_id` ASC) VISIBLE, + INDEX `FK_problem_TO_incorrect_note_1` (`problem_id` ASC) VISIBLE, + INDEX `FK_user_problem_TO_incorrect_note_1` (`user_problem_id` ASC) VISIBLE, + + CONSTRAINT `FK_member_TO_incorrect_note_1` + FOREIGN KEY (`member_id`) + REFERENCES `board_test`.`member` (`member_id`), + + CONSTRAINT `FK_problem_TO_incorrect_note_1` + FOREIGN KEY (`problem_id`) + REFERENCES `board_test`.`problem` (`problem_id`), + + CONSTRAINT `FK_user_problem_TO_incorrect_note_1` + FOREIGN KEY (`user_problem_id`) + REFERENCES `board_test`.`user_problem` (`user_problem_id`), + + CHECK ( + (is_user_problem= 0 AND problem_id IS NOT NULL AND user_problem_id IS NULL) OR + (is_user_problem= 1 AND user_problem_id IS NOT NULL AND problem_id IS NULL) + ) +) ENGINE = InnoDB + DEFAULT CHARACTER SET = utf8mb4 + COLLATE = utf8mb4_0900_ai_ci; + + +-- ----------------------------------------------------- +-- Table `board_test`.`quiz_room` +-- ----------------------------------------------------- +CREATE TABLE IF NOT EXISTS `board_test`.`quiz_room` ( + `room_id` BIGINT NOT NULL AUTO_INCREMENT, + PRIMARY KEY (`room_id`)) +ENGINE = InnoDB +DEFAULT CHARACTER SET = utf8mb4 +COLLATE = utf8mb4_0900_ai_ci; + +-- ----------------------------------------------------- +-- Table `board_test`.`quiz_room_member` +-- ----------------------------------------------------- +CREATE TABLE IF NOT EXISTS `board_test`.`quiz_room_member` ( + `quiz_room_member_id` BIGINT NOT NULL AUTO_INCREMENT, + `member_id` BIGINT NOT NULL, + `room_id` BIGINT NOT NULL, + `is_host` TINYINT(1) NULL DEFAULT NULL, + PRIMARY KEY (`quiz_room_member_id`), + INDEX `FK_member_TO_quiz_room_member_1` (`member_id` ASC) VISIBLE, + INDEX `FK_quiz_room_TO_quiz_room_member_1` (`room_id` ASC) VISIBLE, + UNIQUE INDEX `member_id_UNIQUE` (`member_id` ASC) VISIBLE, + CONSTRAINT `FK_member_TO_quiz_room_member_1` + FOREIGN KEY (`member_id`) + REFERENCES `board_test`.`member` (`member_id`), + CONSTRAINT `FK_quiz_room_TO_quiz_room_member_1` + FOREIGN KEY (`room_id`) + REFERENCES `board_test`.`quiz_room` (`room_id`)) +ENGINE = InnoDB +DEFAULT CHARACTER SET = utf8mb4 +COLLATE = utf8mb4_0900_ai_ci; + +-- 퀴즈룸이 삭제되면 퀴즈룸멤버테이블에서 퀴즈룸아이디를 가지고있는 row가 연쇄적으로 삭제 +ALTER TABLE quiz_room_member +DROP FOREIGN KEY FK_quiz_room_TO_quiz_room_member_1; + +ALTER TABLE quiz_room_member +ADD CONSTRAINT FK_quiz_room_TO_quiz_room_member_1 + FOREIGN KEY (room_id) + REFERENCES quiz_room (room_id) + ON DELETE CASCADE; + +-- ----------------------------------------------------- +-- Table `board_test`.`user_problem` +-- ----------------------------------------------------- +CREATE TABLE IF NOT EXISTS `board_test`.`user_problem` ( + `user_problem_id` BIGINT NOT NULL AUTO_INCREMENT, + `problem_description` VARCHAR(300) NOT NULL, + `choice_1` VARCHAR(255) NOT NULL, + `choice_2` VARCHAR(255) NOT NULL, + `choice_3` VARCHAR(255) NOT NULL, + `choice_4` VARCHAR(255) NOT NULL, + `answer_choice` TINYINT NOT NULL, + `user_problem_set_id` BIGINT NOT NULL, + PRIMARY KEY (`user_problem_id`), + INDEX `FK_user_problem_set_TO_user_problem_1` (`user_problem_set_id` ASC) VISIBLE, + CONSTRAINT `FK_user_problem_set_TO_user_problem_1` + FOREIGN KEY (`user_problem_set_id`) + REFERENCES `board_test`.`user_problem_set` (`user_problem_set_id`) + ON DELETE CASCADE + ) + +ENGINE = InnoDB +DEFAULT CHARACTER SET = utf8mb4 +COLLATE = utf8mb4_0900_ai_ci; + +-- user_score테이블고유의 아이디를 가지고있도록 변경. member_id가 이전에는 pk역할을했엇음 +-- 로직에 맞게 member_id를 member테이블에서 외래키로 가져오도록 변경 +CREATE TABLE IF NOT EXISTS user_score ( + user_score_id BIGINT NOT NULL AUTO_INCREMENT, -- 👉 점수 레코드 자체의 PK + member_id BIGINT NOT NULL, -- 👉 member 테이블 PK를 참조하는 FK + score INT NOT NULL, + PRIMARY KEY (user_score_id), + CONSTRAINT FK_member_TO_user_score_1 + FOREIGN KEY (member_id) + REFERENCES member (member_id) + ON DELETE CASCADE +); + +ALTER TABLE board_test.user_score +ADD CONSTRAINT user_score_unique_member_id UNIQUE (member_id); + +SET SQL_MODE=@OLD_SQL_MODE; +SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS; +SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS; diff --git a/codeBoy_backend/src/main/resources/db/migration/DML_schema.sql b/codeBoy_backend/src/main/resources/db/migration/DML_schema.sql new file mode 100644 index 0000000..bfd1092 --- /dev/null +++ b/codeBoy_backend/src/main/resources/db/migration/DML_schema.sql @@ -0,0 +1,3 @@ + USE board_test; + + SELECT * FROM member; \ No newline at end of file diff --git a/codeBoy_backend/src/main/resources/mappers/CommentMapper.xml b/codeBoy_backend/src/main/resources/mappers/CommentMapper.xml new file mode 100644 index 0000000..21c1d0f --- /dev/null +++ b/codeBoy_backend/src/main/resources/mappers/CommentMapper.xml @@ -0,0 +1,38 @@ + + + + + + + + + + INSERT INTO comment (member_id, content, comment_date, user_problem_set_id) + VALUES (#{comment.memberId}, #{comment.content}, NOW(), #{userProblemSetId}) + + + + UPDATE comment + SET content = #{comment.content} + WHERE comment_id = #{commentId} + + + + DELETE + FROM comment + WHERE comment_id = #{commentId} + + + + + + diff --git a/codeBoy_backend/src/main/resources/mappers/IncorrectNoteMapper.xml b/codeBoy_backend/src/main/resources/mappers/IncorrectNoteMapper.xml new file mode 100644 index 0000000..cb57765 --- /dev/null +++ b/codeBoy_backend/src/main/resources/mappers/IncorrectNoteMapper.xml @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + DELETE FROM incorrect_note + WHERE incorrect_note_id = #{incorrectNoteId} + + + + + INSERT INTO incorrect_note (member_id, problem_id, user_problem_id, is_user_problem) + VALUES (#{memberId}, #{problemId}, #{userProblemId}, #{isUserProblem}) + + + \ No newline at end of file diff --git a/codeBoy_backend/src/main/resources/mappers/MemberMapper.xml b/codeBoy_backend/src/main/resources/mappers/MemberMapper.xml new file mode 100644 index 0000000..b4437ae --- /dev/null +++ b/codeBoy_backend/src/main/resources/mappers/MemberMapper.xml @@ -0,0 +1,95 @@ + + + + + + + UPDATE member + SET is_active = 0 + WHERE member_id = #{memberId} + + + + UPDATE member + + + nickname = #{update.nickname}, + + + + email = #{update.email} + + + WHERE member_id = #{memberId} + + + + + + + + + + + + + INSERT INTO member (ID, password, nickname, email, is_active, signup_date, role) + VALUES (#{id}, #{password}, #{nickname}, #{email}, 1, NOW(), #{role}); + + + + + + + + + + diff --git a/codeBoy_backend/src/main/resources/mappers/ProblemMapper.xml b/codeBoy_backend/src/main/resources/mappers/ProblemMapper.xml index b994f53..ff28a26 100644 --- a/codeBoy_backend/src/main/resources/mappers/ProblemMapper.xml +++ b/codeBoy_backend/src/main/resources/mappers/ProblemMapper.xml @@ -1 +1,14 @@ - \ No newline at end of file + + + + + + + \ No newline at end of file diff --git a/codeBoy_backend/src/main/resources/mappers/QuizRoomMapper.xml b/codeBoy_backend/src/main/resources/mappers/QuizRoomMapper.xml index d5660c4..a89ba62 100644 --- a/codeBoy_backend/src/main/resources/mappers/QuizRoomMapper.xml +++ b/codeBoy_backend/src/main/resources/mappers/QuizRoomMapper.xml @@ -3,23 +3,24 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "https://mybatis.org/dtd/mybatis-3-mapper.dtd"> - + INSERT INTO quiz_room() VALUES(); - + parameterType="QuizRoomMember" + > INSERT INTO quiz_room_member(member_id, room_id, is_host) - VALUES(#{memberId, roomId, isHost}); + VALUES(#{memberId}, #{roomId}, #{isHost}); @@ -31,21 +32,57 @@ - - - DELETE FROM quiz_room_member - WHERE room_id = #{roomId} - + DELETE FROM quiz_room WHERE room_id = #{roomId}; + + + + + + + + + + + + DELETE FROM quiz_room_member + WHERE room_id = #{roomId} + AND member_id = #{memberId} + + + - \ No newline at end of file + \ No newline at end of file diff --git a/codeBoy_backend/src/main/resources/mappers/ScoreMapper.xml b/codeBoy_backend/src/main/resources/mappers/ScoreMapper.xml deleted file mode 100644 index b994f53..0000000 --- a/codeBoy_backend/src/main/resources/mappers/ScoreMapper.xml +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/codeBoy_backend/src/main/resources/mappers/UserMapper.xml b/codeBoy_backend/src/main/resources/mappers/UserMapper.xml deleted file mode 100644 index b994f53..0000000 --- a/codeBoy_backend/src/main/resources/mappers/UserMapper.xml +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/codeBoy_backend/src/main/resources/mappers/UserProblemMapper.xml b/codeBoy_backend/src/main/resources/mappers/UserProblemMapper.xml new file mode 100644 index 0000000..fecd538 --- /dev/null +++ b/codeBoy_backend/src/main/resources/mappers/UserProblemMapper.xml @@ -0,0 +1,56 @@ + + + + + + + + + + + + + INSERT INTO user_problem + (problem_description, choice_1, choice_2, choice_3, choice_4, answer_choice, user_problem_set_id) + VALUES + + (#{item.problemDescription}, + #{item.choice1}, + #{item.choice2}, + #{item.choice3}, + #{item.choice4}, + #{item.answerChoice}, + #{item.userProblemSetId}) + + + + + + + + UPDATE user_problem + SET problem_description = #{problemDescription}, + choice_1 = #{choice1}, + choice_2 = #{choice2}, + choice_3 = #{choice3}, + choice_4 = #{choice4}, + answer_choice = #{answerChoice} + WHERE user_problem_id = #{userProblemId} + + + + + DELETE FROM user_problem + WHERE user_problem_id = #{userProblemId} + + + + + + + diff --git a/codeBoy_backend/src/main/resources/mappers/UserProblemSetMapper.xml b/codeBoy_backend/src/main/resources/mappers/UserProblemSetMapper.xml new file mode 100644 index 0000000..eb7115f --- /dev/null +++ b/codeBoy_backend/src/main/resources/mappers/UserProblemSetMapper.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + INSERT INTO user_problem_set (member_id, category, problem_set_title, created_at) + VALUES (#{memberId}, #{category}, #{problemSetTitle}, #{createdAt}) + + + + + DELETE FROM user_problem_set + WHERE user_problem_set_id = #{userProblemSetId} + + + + + diff --git a/codeBoy_backend/src/main/resources/mappers/UserScoreMapper.xml b/codeBoy_backend/src/main/resources/mappers/UserScoreMapper.xml new file mode 100644 index 0000000..3909667 --- /dev/null +++ b/codeBoy_backend/src/main/resources/mappers/UserScoreMapper.xml @@ -0,0 +1,29 @@ + + + + + + + INSERT INTO user_score (member_id, score) + VALUES(#{memberId}, #{score}) + + + + + + + + UPDATE user_score + SET score = #{score} + WHERE member_id = #{memberId} + + +