diff --git a/backend/.gitlab-ci.yml b/backend/.gitlab-ci.yml new file mode 100644 index 0000000..9761a6c --- /dev/null +++ b/backend/.gitlab-ci.yml @@ -0,0 +1,23 @@ +deploy-to-server: + stage: deploy + only: + - backend + before_script: + - echo 'start deployment' + - whoami + script: + - cd /home/ubuntu/gitlab_runner/ + - git pull origin backend + - cd backend +# - kill $(lsof -t -i:8000) + - ./gradlew build + # - cd /home/ubuntu/s03p12a112/backend/target/ +# - setsid nohup java -jar backend-0.0.1-SNAPSHOT.jar > /dev/null 2>&1 & +# - cd /home/ubuntu/s03p12a112/frontend/ +# - sudo npm install +# - sudo npm run build +# - sudo service nginx restart + after_script: + - echo 'deployment is done' + tags: + - edu \ No newline at end of file diff --git a/backend/build.gradle b/backend/build.gradle index d1068ef..6aad2ae 100644 --- a/backend/build.gradle +++ b/backend/build.gradle @@ -1,38 +1,50 @@ plugins { id 'java' - id 'org.springframework.boot' version '3.3.1' - id 'io.spring.dependency-management' version '1.1.5' + id 'org.springframework.boot' version '3.2.7' + id 'io.spring.dependency-management' version '1.1.0' } -group = 'com.edufocus' +group = 'io.openvidu' version = '0.0.1-SNAPSHOT' - -java { - toolchain { - languageVersion = JavaLanguageVersion.of(17) - } -} - -configurations { - compileOnly { - extendsFrom annotationProcessor - } -} +description = 'Basic server application built for Java with Spring Boot' +sourceCompatibility = '17' +targetCompatibility = '17' repositories { mavenCentral() } dependencies { - implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-web' - compileOnly 'org.projectlombok:lombok' - runtimeOnly 'com.mysql:mysql-connector-j' - annotationProcessor 'org.projectlombok:lombok' + implementation 'org.openpnp:opencv:4.9.0-0' + implementation 'org.springframework.boot:spring-boot-starter-data-jpa' + implementation 'io.livekit:livekit-server:0.5.11' + implementation 'org.springframework.boot:spring-boot-starter-data-jpa' + implementation 'mysql:mysql-connector-java:8.0.33' + implementation 'org.springframework.boot:spring-boot-starter-websocket' + implementation 'org.springframework.boot:spring-boot-starter-amqp' + implementation 'org.springframework.boot:spring-boot-starter-reactor-netty' + testImplementation 'org.springframework.amqp:spring-rabbit-test' testImplementation 'org.springframework.boot:spring-boot-starter-test' - testRuntimeOnly 'org.junit.platform:junit-platform-launcher' + implementation group: 'io.livekit', name: 'livekit-server', version: '0.6.1' + implementation 'org.projectlombok:lombok' + annotationProcessor 'org.projectlombok:lombok' + implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.4.0' + runtimeOnly group: 'io.jsonwebtoken', name: 'jjwt-jackson', version: '0.11.5' + runtimeOnly group: 'io.jsonwebtoken', name: 'jjwt-impl', version: '0.11.5' + implementation group: 'io.jsonwebtoken', name: 'jjwt-api', version: '0.11.5' + implementation group: 'org.springframework.boot', name: 'spring-boot-starter-mail', version: '3.2.5' + implementation 'org.springframework.boot:spring-boot-starter-mail' + implementation 'org.mindrot:jbcrypt:0.4' + implementation 'org.springframework.boot:spring-boot-starter-actuator' + implementation 'org.springframework.boot:spring-boot-starter-data-redis' + + + + implementation group: 'org.glassfish.jaxb', name: 'jaxb-runtime', version: '4.0.5' + } -tasks.named('test') { +test { useJUnitPlatform() -} +} \ No newline at end of file diff --git a/backend/src/main/java/com/edufocus/edufocus/EdufocusApplication.java b/backend/src/main/java/com/edufocus/edufocus/EdufocusApplication.java index 556bff0..41669f4 100644 --- a/backend/src/main/java/com/edufocus/edufocus/EdufocusApplication.java +++ b/backend/src/main/java/com/edufocus/edufocus/EdufocusApplication.java @@ -2,7 +2,11 @@ package com.edufocus.edufocus; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.data.jpa.repository.config.EnableJpaAuditing; +import org.springframework.scheduling.annotation.EnableAsync; +@EnableAsync +@EnableJpaAuditing @SpringBootApplication public class EdufocusApplication { diff --git a/backend/src/main/java/com/edufocus/edufocus/board/controller/BoardController.java b/backend/src/main/java/com/edufocus/edufocus/board/controller/BoardController.java new file mode 100644 index 0000000..da43e88 --- /dev/null +++ b/backend/src/main/java/com/edufocus/edufocus/board/controller/BoardController.java @@ -0,0 +1,139 @@ +package com.edufocus.edufocus.board.controller; + +import com.edufocus.edufocus.board.entity.dto.*; +import com.edufocus.edufocus.board.entity.vo.Board; +import com.edufocus.edufocus.board.entity.vo.Comment; +import com.edufocus.edufocus.board.service.BoardService; +import com.edufocus.edufocus.user.util.JWTUtil; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.validation.constraints.Positive; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + + +/** + * @author Haneol Kim + */ +@RestController +@RequestMapping("/board") +public class BoardController { + + private final JWTUtil jwtUtil; + private final BoardService boardService; + + public BoardController(BoardService boardService, JWTUtil jwtUtil){ + this.boardService = boardService; + this.jwtUtil = jwtUtil; + } + + @GetMapping() + public ResponseEntity> searchBoards( + @RequestParam(value = "category", required = false, defaultValue = "announcement") String category, + @RequestParam(value = "lectureId") long lectureId, + @RequestParam(value = "pageNo", required = false, defaultValue = "0") int pageNo + ){ + List boardSummaries = boardService.findBoards(pageNo, category, lectureId); + + return new ResponseEntity<>(boardSummaries, HttpStatus.OK); + } + + @GetMapping(value = "/{boardId}") + public ResponseEntity getBoardDetail( + @PathVariable int boardId, + HttpServletRequest request + ){ + String token = request.getHeader("Authorization"); + long userId = Long.parseLong(jwtUtil.getUserId(token)); + + ResponseBoardDetailDto responseBoardDetailDto = boardService.findBoardDetail(userId, boardId); + + return new ResponseEntity<>(responseBoardDetailDto, HttpStatus.OK); + } + + @PostMapping + public ResponseEntity addBoard( + @RequestBody RequestBoardDto requestBoardDto, + HttpServletRequest request + ){ + String token = request.getHeader("Authorization"); + long userId = Long.parseLong(jwtUtil.getUserId(token)); + + boardService.createBoard(userId, requestBoardDto); + + return new ResponseEntity<>(HttpStatus.OK); + } + + @PutMapping(value = "/{boardId}") + public ResponseEntity updateBoard( + @PathVariable long boardId, + @RequestBody RequestBoardUpdateDto requestBoardUpdateDto + ){ + boardService.updateBoard(boardId, requestBoardUpdateDto); + + return new ResponseEntity<>(HttpStatus.OK); + } + + @DeleteMapping(value = "/{boardId}") + public ResponseEntity deleteBoard( + @PathVariable int boardId + ){ + boardService.deleteBoard(boardId); + + return new ResponseEntity<>(HttpStatus.OK); + } + + @GetMapping(value = "/comment/{boardId}") + public ResponseEntity> getComments( + @PathVariable int boardId, + HttpServletRequest request + ){ + String token = request.getHeader("Authorization"); + long userId = Long.parseLong(jwtUtil.getUserId(token)); + + List comments = boardService.findComments(userId, boardId); + + return new ResponseEntity<>(comments, HttpStatus.OK); + } + + @PostMapping(value = "/comment/{boardId}") + public ResponseEntity addComment( + @PathVariable int boardId, + @RequestBody RequestCommentDto requestCommentDto, + HttpServletRequest request + ){ + String token = request.getHeader("Authorization"); + long userId = Long.parseLong(jwtUtil.getUserId(token)); + + boardService.createComment(userId, boardId, requestCommentDto); + + return new ResponseEntity<>(HttpStatus.OK); + } + + @PutMapping(value = "/comment/{commentId}") + public ResponseEntity updateComment( + @PathVariable int commentId, + @RequestBody RequestCommentDto requestCommentDto + ){ + boardService.updateComment(commentId, requestCommentDto); + + return new ResponseEntity<>(HttpStatus.OK); + } + + @DeleteMapping(value = "/comment/{commentId}") + public ResponseEntity deleteComment( + @PathVariable int commentId + ){ + boardService.deleteComment(commentId); + + return new ResponseEntity<>(HttpStatus.OK); + } + + + @ExceptionHandler() + public ResponseEntity NoContentException(Exception e){ + return new ResponseEntity<>(HttpStatus.NO_CONTENT); + } +} diff --git a/backend/src/main/java/com/edufocus/edufocus/board/entity/dto/RequestBoardDto.java b/backend/src/main/java/com/edufocus/edufocus/board/entity/dto/RequestBoardDto.java new file mode 100644 index 0000000..478017a --- /dev/null +++ b/backend/src/main/java/com/edufocus/edufocus/board/entity/dto/RequestBoardDto.java @@ -0,0 +1,15 @@ +package com.edufocus.edufocus.board.entity.dto; + + +import com.edufocus.edufocus.board.entity.vo.Board; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class RequestBoardDto { + private long lectureId; + private String title; + private String category; + private String content; +} diff --git a/backend/src/main/java/com/edufocus/edufocus/board/entity/dto/RequestBoardUpdateDto.java b/backend/src/main/java/com/edufocus/edufocus/board/entity/dto/RequestBoardUpdateDto.java new file mode 100644 index 0000000..58ff7a7 --- /dev/null +++ b/backend/src/main/java/com/edufocus/edufocus/board/entity/dto/RequestBoardUpdateDto.java @@ -0,0 +1,12 @@ +package com.edufocus.edufocus.board.entity.dto; + + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class RequestBoardUpdateDto { + private String title; + private String content; +} diff --git a/backend/src/main/java/com/edufocus/edufocus/board/entity/dto/RequestCommentDto.java b/backend/src/main/java/com/edufocus/edufocus/board/entity/dto/RequestCommentDto.java new file mode 100644 index 0000000..4dfbd0b --- /dev/null +++ b/backend/src/main/java/com/edufocus/edufocus/board/entity/dto/RequestCommentDto.java @@ -0,0 +1,10 @@ +package com.edufocus.edufocus.board.entity.dto; + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class RequestCommentDto { + private String content; +} diff --git a/backend/src/main/java/com/edufocus/edufocus/board/entity/dto/ResponseBoardDetailDto.java b/backend/src/main/java/com/edufocus/edufocus/board/entity/dto/ResponseBoardDetailDto.java new file mode 100644 index 0000000..c0e1f28 --- /dev/null +++ b/backend/src/main/java/com/edufocus/edufocus/board/entity/dto/ResponseBoardDetailDto.java @@ -0,0 +1,23 @@ +package com.edufocus.edufocus.board.entity.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; + +import java.time.LocalDateTime; +import java.util.Date; + +@Builder +@Getter +@Setter +@AllArgsConstructor +public class ResponseBoardDetailDto { + private long id; + private String name; + private String title; + private String content; + private boolean isMine; + private Date createdAt; + private Date modifiedAt; +} diff --git a/backend/src/main/java/com/edufocus/edufocus/board/entity/dto/ResponseBoardSummaryDto.java b/backend/src/main/java/com/edufocus/edufocus/board/entity/dto/ResponseBoardSummaryDto.java new file mode 100644 index 0000000..0e50f01 --- /dev/null +++ b/backend/src/main/java/com/edufocus/edufocus/board/entity/dto/ResponseBoardSummaryDto.java @@ -0,0 +1,19 @@ +package com.edufocus.edufocus.board.entity.dto; + + +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; + +import java.time.LocalDateTime; +import java.util.Date; + +@Builder +@Getter +@Setter +public class ResponseBoardSummaryDto { + private long id; + private String name; + private String title; + private Date createdAt; +} diff --git a/backend/src/main/java/com/edufocus/edufocus/board/entity/dto/ResponseCommentDto.java b/backend/src/main/java/com/edufocus/edufocus/board/entity/dto/ResponseCommentDto.java new file mode 100644 index 0000000..ee99823 --- /dev/null +++ b/backend/src/main/java/com/edufocus/edufocus/board/entity/dto/ResponseCommentDto.java @@ -0,0 +1,21 @@ +package com.edufocus.edufocus.board.entity.dto; + +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; + +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.util.Date; + +@Builder +@Getter +@Setter +public class ResponseCommentDto { + private long id; + private String name; + private String content; + private boolean isMine; + private Date createAt; + private Date modifiedAt; +} diff --git a/backend/src/main/java/com/edufocus/edufocus/board/entity/vo/Board.java b/backend/src/main/java/com/edufocus/edufocus/board/entity/vo/Board.java new file mode 100644 index 0000000..17274f4 --- /dev/null +++ b/backend/src/main/java/com/edufocus/edufocus/board/entity/vo/Board.java @@ -0,0 +1,90 @@ +package com.edufocus.edufocus.board.entity.vo; + + +import com.edufocus.edufocus.board.entity.dto.ResponseBoardDetailDto; +import com.edufocus.edufocus.board.entity.dto.ResponseBoardSummaryDto; +import com.edufocus.edufocus.lecture.entity.Lecture; +import com.edufocus.edufocus.user.model.entity.vo.User; +import com.fasterxml.jackson.annotation.JsonAutoDetect; +import jakarta.persistence.*; +import lombok.*; +import org.hibernate.annotations.CreationTimestamp; +import org.hibernate.annotations.OnDelete; +import org.hibernate.annotations.OnDeleteAction; +import org.hibernate.annotations.UpdateTimestamp; +import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.annotation.LastModifiedDate; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; + +import java.time.LocalDateTime; +import java.util.Date; +import java.util.List; + +@Entity +@Builder +@NoArgsConstructor +@AllArgsConstructor +@Setter +@EntityListeners(AuditingEntityListener.class) +@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY) +public class Board { + + @Id + @GeneratedValue + private long id; + + @Column(nullable = false) + private String title; + + @Column(nullable = false) + private String category; + + @Column(columnDefinition = "TEXT", nullable = false) + private String content; + + @Column(nullable = true) + private int viewCount; + + @Column + @CreationTimestamp + @Temporal(TemporalType.TIMESTAMP) + private Date createdAt; + + @Column + @UpdateTimestamp + @Temporal(TemporalType.TIMESTAMP) + private Date modifiedAt; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id") + private User user; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "lecture_id") + @OnDelete(action = OnDeleteAction.CASCADE) + private Lecture lecture; + + @OneToMany(mappedBy = "board") + private List comments; + + public ResponseBoardSummaryDto makeSummaryDto() { + return ResponseBoardSummaryDto.builder() + .id(id) + .title(title) + .name(user.getName()) + .createdAt(createdAt) + .build(); + } + + public ResponseBoardDetailDto makeDetailDto(long userId) { + return ResponseBoardDetailDto.builder() + .id(id) + .name(user.getName()) + .title(title) + .content(content) + .isMine(user.getId() == userId) + .createdAt(createdAt) + .modifiedAt(modifiedAt) + .build(); + } +} diff --git a/backend/src/main/java/com/edufocus/edufocus/board/entity/vo/Comment.java b/backend/src/main/java/com/edufocus/edufocus/board/entity/vo/Comment.java new file mode 100644 index 0000000..3a44c96 --- /dev/null +++ b/backend/src/main/java/com/edufocus/edufocus/board/entity/vo/Comment.java @@ -0,0 +1,65 @@ +package com.edufocus.edufocus.board.entity.vo; + + +import com.edufocus.edufocus.board.entity.dto.ResponseCommentDto; +import com.edufocus.edufocus.user.model.entity.vo.User; +import com.fasterxml.jackson.annotation.JsonAutoDetect; +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.hibernate.annotations.CreationTimestamp; +import org.hibernate.annotations.OnDelete; +import org.hibernate.annotations.OnDeleteAction; +import org.hibernate.annotations.UpdateTimestamp; +import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.annotation.LastModifiedDate; + +import java.time.LocalDateTime; +import java.util.Date; + +@Entity +@Builder +@NoArgsConstructor +@AllArgsConstructor +@Setter +@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY) +public class Comment { + @Id + @GeneratedValue + private long id; + + @Column(columnDefinition = "TEXT", nullable = false) + private String content; + + @Column + @CreationTimestamp + @Temporal(TemporalType.TIMESTAMP) + private Date createdAt; + + @Column + @Temporal(TemporalType.TIMESTAMP) + private Date modifiedAt; + + @ManyToOne + @JoinColumn(name = "user_id") + User user; + + @ManyToOne + @JoinColumn(name = "board_id") + @OnDelete(action = OnDeleteAction.CASCADE) + Board board; + + public ResponseCommentDto makeCommentDto(long userId) { + return ResponseCommentDto.builder() + .id(id) + .name(user.getName()) + .content(content) + .isMine(user.getId() == userId) + .createAt(createdAt) + .modifiedAt(modifiedAt) + .build(); + } + +} diff --git a/backend/src/main/java/com/edufocus/edufocus/board/repository/BoardRepository.java b/backend/src/main/java/com/edufocus/edufocus/board/repository/BoardRepository.java new file mode 100644 index 0000000..beff8b3 --- /dev/null +++ b/backend/src/main/java/com/edufocus/edufocus/board/repository/BoardRepository.java @@ -0,0 +1,15 @@ +package com.edufocus.edufocus.board.repository; + +import com.edufocus.edufocus.board.entity.vo.Board; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; + + +public interface BoardRepository extends JpaRepository { + + @Query("select b from Board b join fetch b.user where b.category =:category and b.lecture.id=:lectureId") + Page findByLectureIdAndCategory(Long lectureId, String category, Pageable pageable); + +} diff --git a/backend/src/main/java/com/edufocus/edufocus/board/repository/CommentRepository.java b/backend/src/main/java/com/edufocus/edufocus/board/repository/CommentRepository.java new file mode 100644 index 0000000..c877fd1 --- /dev/null +++ b/backend/src/main/java/com/edufocus/edufocus/board/repository/CommentRepository.java @@ -0,0 +1,14 @@ +package com.edufocus.edufocus.board.repository; + +import com.edufocus.edufocus.board.entity.vo.Comment; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; + +import java.util.List; + +public interface CommentRepository extends JpaRepository { + + @Query("select c from Comment c join fetch c.user where c.board.id =:boardId") + List findByBoardId(long boardId); + +} diff --git a/backend/src/main/java/com/edufocus/edufocus/board/service/BoardService.java b/backend/src/main/java/com/edufocus/edufocus/board/service/BoardService.java new file mode 100644 index 0000000..d5c646d --- /dev/null +++ b/backend/src/main/java/com/edufocus/edufocus/board/service/BoardService.java @@ -0,0 +1,27 @@ +package com.edufocus.edufocus.board.service; + +import com.edufocus.edufocus.board.entity.dto.*; + +import java.util.List; + +public interface BoardService { + + public void createBoard(long userId, RequestBoardDto requestBoardDto); + + public List findBoards(int pageNo, String category, long lectureId); + + public ResponseBoardDetailDto findBoardDetail(long userId, long boardId); + + public void updateBoard(long boardId, RequestBoardUpdateDto requestBoardUpdateDto); + + public void deleteBoard(long boardId); + + public void createComment(long userId, long boardId, RequestCommentDto requestCommentDto); + + public List findComments(long userId, long boardId); + + public void updateComment(long commentId, RequestCommentDto requestCommentDto); + + public void deleteComment(long commentId); + +} diff --git a/backend/src/main/java/com/edufocus/edufocus/board/service/BoardServiceImpl.java b/backend/src/main/java/com/edufocus/edufocus/board/service/BoardServiceImpl.java new file mode 100644 index 0000000..de97761 --- /dev/null +++ b/backend/src/main/java/com/edufocus/edufocus/board/service/BoardServiceImpl.java @@ -0,0 +1,132 @@ +package com.edufocus.edufocus.board.service; + + +import com.edufocus.edufocus.board.entity.dto.*; +import com.edufocus.edufocus.board.entity.vo.Board; +import com.edufocus.edufocus.board.entity.vo.Comment; +import com.edufocus.edufocus.board.repository.BoardRepository; +import com.edufocus.edufocus.board.repository.CommentRepository; +import com.edufocus.edufocus.lecture.entity.Lecture; +import com.edufocus.edufocus.lecture.repository.LectureRepository; +import com.edufocus.edufocus.user.model.entity.vo.User; +import com.edufocus.edufocus.user.model.repository.UserRepository; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import java.util.NoSuchElementException; +import java.util.stream.Collectors; + +@Service +public class BoardServiceImpl implements BoardService { + + private static final int PAGE_SIZE = 10; + + private final BoardRepository boardRepository; + private final CommentRepository commentRepository; + private final UserRepository userRepository; + private final LectureRepository lectureRepository; + + public BoardServiceImpl(BoardRepository boardRepository, CommentRepository commentRepository, UserRepository userRepository, LectureRepository lectureRepository){ + this.boardRepository = boardRepository; + this.commentRepository = commentRepository; + this.userRepository = userRepository; + this.lectureRepository = lectureRepository; + } + + + @Transactional + public List findBoards(int pageNo, String category, long lectureId) { + Pageable pageable = PageRequest.of(pageNo, PAGE_SIZE, Sort.by("id").descending()); + + List boards = boardRepository.findByLectureIdAndCategory(lectureId, category, pageable).getContent(); + + return boards.stream().map(Board::makeSummaryDto) + .collect(Collectors.toList()); + } + + @Transactional + public ResponseBoardDetailDto findBoardDetail(long userId, long boardId) { + return boardRepository.findById(boardId) + .orElseThrow(NoSuchElementException::new) + .makeDetailDto(userId); + } + + @Transactional + public void createBoard(long userId, RequestBoardDto requestBoardDto) { + User user = userRepository.getReferenceById(userId); + Lecture lecture = lectureRepository.getReferenceById(requestBoardDto.getLectureId()); + + Board board = Board.builder() + .title(requestBoardDto.getTitle()) + .category(requestBoardDto.getCategory()) + .content(requestBoardDto.getContent()) + .user(user) + .lecture(lecture) + .build(); + + boardRepository.save(board); + } + + @Transactional + public void updateBoard(long boardId, RequestBoardUpdateDto requestBoardUpdateDto) { + Board board = boardRepository.getReferenceById(boardId); + + board.setTitle(requestBoardUpdateDto.getTitle()); + board.setContent(requestBoardUpdateDto.getContent()); + + boardRepository.save(board); + } + + + @Transactional + public void deleteBoard(long boardId) { + Board board = boardRepository.getReferenceById(boardId); + + boardRepository.delete(board); + } + + @Transactional + public List findComments(long userId, long boardId) { + return commentRepository.findByBoardId(boardId).stream() + .map((Comment c) -> c.makeCommentDto(userId)) + .collect(Collectors.toList()); + } + + @Transactional + public void createComment(long userId, long boardId, RequestCommentDto requestCommentDto) { + User user = userRepository.getReferenceById(userId); + Board board = boardRepository.getReferenceById(boardId); + + Comment comment = Comment.builder() + .content(requestCommentDto.getContent()) + .board(board) + .user(user) + .build(); + + commentRepository.save(comment); + } + + + + @Transactional + public void updateComment(long commentId, RequestCommentDto requestCommentDto) { + Comment comment = commentRepository.getReferenceById(commentId); + + comment.setContent(requestCommentDto.getContent()); + + commentRepository.save(comment); + } + + @Transactional + public void deleteComment(long commentId) { + Comment comment = commentRepository.getReferenceById(commentId); + + commentRepository.delete(comment); + } + + +} diff --git a/backend/src/main/java/com/edufocus/edufocus/global/config/AsyncConfig.java b/backend/src/main/java/com/edufocus/edufocus/global/config/AsyncConfig.java new file mode 100644 index 0000000..4a2cee9 --- /dev/null +++ b/backend/src/main/java/com/edufocus/edufocus/global/config/AsyncConfig.java @@ -0,0 +1,32 @@ +package com.edufocus.edufocus.global.config; + +import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.annotation.AsyncConfigurer; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; +import org.springframework.web.servlet.config.annotation.EnableWebMvc; + +import java.util.concurrent.Executor; + +@Configuration +@EnableWebMvc +public class AsyncConfig implements AsyncConfigurer { + + @Override + @Bean(name = "mailExecutor") + public Executor getAsyncExecutor() { + ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); + executor.setCorePoolSize(5); + executor.setMaxPoolSize(10); + executor.setQueueCapacity(500); + executor.setThreadNamePrefix("MailExecutor-"); + executor.initialize(); + return executor; + } + + @Override + public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() { + return AsyncConfigurer.super.getAsyncUncaughtExceptionHandler(); + } +} diff --git a/backend/src/main/java/com/edufocus/edufocus/global/config/PropertiesConfig.java b/backend/src/main/java/com/edufocus/edufocus/global/config/PropertiesConfig.java new file mode 100644 index 0000000..f0b476c --- /dev/null +++ b/backend/src/main/java/com/edufocus/edufocus/global/config/PropertiesConfig.java @@ -0,0 +1,17 @@ +package com.edufocus.edufocus.global.config; + + +import com.edufocus.edufocus.global.properties.ImagePathProperties; +import com.edufocus.edufocus.global.properties.MailProperties; +import com.edufocus.edufocus.global.properties.RabbitMQProperties; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +@Configuration +@EnableConfigurationProperties({ + RabbitMQProperties.class, + ImagePathProperties.class, + MailProperties.class +}) +public class PropertiesConfig { +} diff --git a/backend/src/main/java/com/edufocus/edufocus/global/config/RedisConfig.java b/backend/src/main/java/com/edufocus/edufocus/global/config/RedisConfig.java new file mode 100644 index 0000000..eb7a84f --- /dev/null +++ b/backend/src/main/java/com/edufocus/edufocus/global/config/RedisConfig.java @@ -0,0 +1,22 @@ +package com.edufocus.edufocus.global.config; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; + +@Configuration +public class RedisConfig { + + @Value("${spring.data.redis.host}") + private String host; + + @Value("${spring.data.redis.port}") + private int port; + + @Bean + public RedisConnectionFactory redisConnectionFactory() { + return new LettuceConnectionFactory(host, port); + } +} diff --git a/backend/src/main/java/com/edufocus/edufocus/global/constant/RabbitMQConstant.java b/backend/src/main/java/com/edufocus/edufocus/global/constant/RabbitMQConstant.java new file mode 100644 index 0000000..4ccd8fc --- /dev/null +++ b/backend/src/main/java/com/edufocus/edufocus/global/constant/RabbitMQConstant.java @@ -0,0 +1,15 @@ +package com.edufocus.edufocus.global.constant; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public enum RabbitMQConstant { + + CHAT_QUEUE_NAME("chat.queue"), + CHAT_EXCHANGE("chat.exchange"), + ROUTING_KEY("*.room.*"), + ROUTING_KEY_PREFIX("*.room.");; + private final String constant; +} diff --git a/backend/src/main/java/com/edufocus/edufocus/global/properties/ImagePathProperties.java b/backend/src/main/java/com/edufocus/edufocus/global/properties/ImagePathProperties.java new file mode 100644 index 0000000..77d3672 --- /dev/null +++ b/backend/src/main/java/com/edufocus/edufocus/global/properties/ImagePathProperties.java @@ -0,0 +1,12 @@ +package com.edufocus.edufocus.global.properties; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.springframework.boot.context.properties.ConfigurationProperties; + +@Getter +@RequiredArgsConstructor +@ConfigurationProperties(prefix = "image") +public class ImagePathProperties { + private final String path; +} diff --git a/backend/src/main/java/com/edufocus/edufocus/global/properties/MailProperties.java b/backend/src/main/java/com/edufocus/edufocus/global/properties/MailProperties.java new file mode 100644 index 0000000..6240fdd --- /dev/null +++ b/backend/src/main/java/com/edufocus/edufocus/global/properties/MailProperties.java @@ -0,0 +1,15 @@ +package com.edufocus.edufocus.global.properties; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.springframework.boot.context.properties.ConfigurationProperties; + +@Getter +@RequiredArgsConstructor +@ConfigurationProperties(prefix = "spring.mail") +public class MailProperties { + private final String host; + private final Integer port; + private final String name; + private final String password; +} diff --git a/backend/src/main/java/com/edufocus/edufocus/global/properties/RabbitMQProperties.java b/backend/src/main/java/com/edufocus/edufocus/global/properties/RabbitMQProperties.java new file mode 100644 index 0000000..461022c --- /dev/null +++ b/backend/src/main/java/com/edufocus/edufocus/global/properties/RabbitMQProperties.java @@ -0,0 +1,16 @@ +package com.edufocus.edufocus.global.properties; + + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.springframework.boot.context.properties.ConfigurationProperties; + +@Getter +@RequiredArgsConstructor +@ConfigurationProperties(prefix = "spring.rabbitmq") +public class RabbitMQProperties { + private final String host; + private final Integer port; + private final String username; + private final String password; +} diff --git a/backend/src/main/java/com/edufocus/edufocus/lecture/controller/LectureController.java b/backend/src/main/java/com/edufocus/edufocus/lecture/controller/LectureController.java new file mode 100644 index 0000000..3b15967 --- /dev/null +++ b/backend/src/main/java/com/edufocus/edufocus/lecture/controller/LectureController.java @@ -0,0 +1,107 @@ +package com.edufocus.edufocus.lecture.controller; + +import com.edufocus.edufocus.lecture.entity.Lecture; +import com.edufocus.edufocus.lecture.entity.LectureCreateRequest; +import com.edufocus.edufocus.lecture.entity.LectureSearchResponse; +import com.edufocus.edufocus.lecture.entity.LectureDetailResponse; +import com.edufocus.edufocus.lecture.service.LectureService; +import com.edufocus.edufocus.user.util.JWTUtil; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import java.util.ArrayList; +import java.util.List; + +@RestController +@RequestMapping("/lecture") +@Slf4j +@RequiredArgsConstructor +public class LectureController { + + private final LectureService lectureService; + private final JWTUtil jwtUtil; + + @PostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE) + public ResponseEntity createLecture(@RequestHeader("Authorization") String accessToken, @RequestPart LectureCreateRequest lectureCreateRequest + , @RequestPart(value = "image", required = false) MultipartFile image) throws Exception { + Long userId = Long.parseLong(jwtUtil.getUserId(accessToken)); + + Lecture lecture = lectureService.findLectureByTitle(lectureCreateRequest.getTitle()); + if (lecture != null) { + return new ResponseEntity<>(HttpStatus.CONFLICT); + } + + if (image != null && !image.getContentType().startsWith("image")) { + return new ResponseEntity<>(HttpStatus.BAD_REQUEST); + } + + lectureService.createLecture(userId, lectureCreateRequest, image); + + return new ResponseEntity<>(HttpStatus.CREATED); + } + + @PutMapping("/{lectureId}") + public ResponseEntity updateLecture(@RequestHeader("Authorization") String accessToken, @PathVariable Long lectureId, @RequestBody LectureCreateRequest lectureCreateRequest) { + Long userId = Long.parseLong(jwtUtil.getUserId(accessToken)); + + if (!lectureService.updateLecture(userId, lectureId, lectureCreateRequest)) { + return new ResponseEntity<>(HttpStatus.UNAUTHORIZED); + } + return new ResponseEntity<>(HttpStatus.OK); + } + + + @DeleteMapping("/{lectureId}") + public ResponseEntity deleteLecture(@RequestHeader("Authorization") String accessToken, @PathVariable long lectureId) { + Long userId = Long.parseLong(jwtUtil.getUserId(accessToken)); + + if (!lectureService.deleteLecture(userId, lectureId)) { + return new ResponseEntity<>(HttpStatus.UNAUTHORIZED); + } + + return new ResponseEntity<>(HttpStatus.NO_CONTENT); + } + + @GetMapping + public ResponseEntity findAllLecture() { + List lectures = lectureService.findAllLecture(); + + return new ResponseEntity<>(lectures, HttpStatus.OK); + } + + @GetMapping("/{lectureId}") + public ResponseEntity findById(@RequestHeader(value = "Authorization", required = false) String accessToken, @PathVariable long lectureId) { + Long userId = null; + + if (accessToken != null) { + userId = Long.parseLong(jwtUtil.getUserId(accessToken)); + } + LectureDetailResponse lectureDetailResponse = lectureService.findLectureById(userId, lectureId); + + return new ResponseEntity<>(lectureDetailResponse, HttpStatus.OK); + } + + @GetMapping("/mylecture") + public ResponseEntity findMyLecture(@RequestHeader(value = "Authorization", required = false) String accessToken) { + + if (accessToken == null) { + return new ResponseEntity<>(new ArrayList<>(), HttpStatus.OK); + } + + Long userId = Long.parseLong(jwtUtil.getUserId(accessToken)); + + List myLectures = lectureService.findMyLecture(userId); + + return new ResponseEntity<>(myLectures, HttpStatus.OK); + } + + @GetMapping("/isLive/{lectureId}") + public ResponseEntity checkIsLive(@PathVariable long lectureId) { + return new ResponseEntity<>(lectureService.getState(lectureId), HttpStatus.OK); + } +} diff --git a/backend/src/main/java/com/edufocus/edufocus/lecture/entity/Lecture.java b/backend/src/main/java/com/edufocus/edufocus/lecture/entity/Lecture.java new file mode 100644 index 0000000..734354a --- /dev/null +++ b/backend/src/main/java/com/edufocus/edufocus/lecture/entity/Lecture.java @@ -0,0 +1,53 @@ +package com.edufocus.edufocus.lecture.entity; + + +import com.edufocus.edufocus.user.model.entity.vo.User; +import jakarta.persistence.*; +import lombok.*; + +import java.util.Date; + +@Entity +@Getter +@Setter +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class Lecture { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column + private Long id; + + @ManyToOne + @JoinColumn(name = "user_id") + private User user; + + @Column + private String title; + + @Column(columnDefinition = "text") + private String description; + + @Column(columnDefinition = "text") + private String plan; + + @Column + private String image; + + @Column(name = "start_date") + @Temporal(TemporalType.DATE) + private Date startDate; + + @Column(name = "end_date") + @Temporal(TemporalType.DATE) + private Date endDate; + + @Column + private String time; + + @Column(columnDefinition = "boolean default false") + private boolean online; + +} diff --git a/backend/src/main/java/com/edufocus/edufocus/lecture/entity/LectureCreateRequest.java b/backend/src/main/java/com/edufocus/edufocus/lecture/entity/LectureCreateRequest.java new file mode 100644 index 0000000..dfd02fd --- /dev/null +++ b/backend/src/main/java/com/edufocus/edufocus/lecture/entity/LectureCreateRequest.java @@ -0,0 +1,26 @@ +package com.edufocus.edufocus.lecture.entity; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.util.Date; + +@Getter +@NoArgsConstructor +@AllArgsConstructor +public class LectureCreateRequest { + + private String title; + + private String description; + + private String plan; + + private Date startDate; + + private Date endDate; + + private String time; + +} diff --git a/backend/src/main/java/com/edufocus/edufocus/lecture/entity/LectureDetailResponse.java b/backend/src/main/java/com/edufocus/edufocus/lecture/entity/LectureDetailResponse.java new file mode 100644 index 0000000..0a5629a --- /dev/null +++ b/backend/src/main/java/com/edufocus/edufocus/lecture/entity/LectureDetailResponse.java @@ -0,0 +1,37 @@ +package com.edufocus.edufocus.lecture.entity; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.util.Date; + +@Getter +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class LectureDetailResponse { + + private Long id; + + private String teacherName; + + private String title; + + private String description; + + private String plan; + + private String image; + + private Date startDate; + + private Date endDate; + + private String time; + + private boolean online; + + private String status; +} diff --git a/backend/src/main/java/com/edufocus/edufocus/lecture/entity/LectureSearchResponse.java b/backend/src/main/java/com/edufocus/edufocus/lecture/entity/LectureSearchResponse.java new file mode 100644 index 0000000..08bf38d --- /dev/null +++ b/backend/src/main/java/com/edufocus/edufocus/lecture/entity/LectureSearchResponse.java @@ -0,0 +1,19 @@ +package com.edufocus.edufocus.lecture.entity; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class LectureSearchResponse { + + private Long id; + + private String title; + + private String image; +} diff --git a/backend/src/main/java/com/edufocus/edufocus/lecture/entity/UserStatus.java b/backend/src/main/java/com/edufocus/edufocus/lecture/entity/UserStatus.java new file mode 100644 index 0000000..3fbf6ba --- /dev/null +++ b/backend/src/main/java/com/edufocus/edufocus/lecture/entity/UserStatus.java @@ -0,0 +1,9 @@ +package com.edufocus.edufocus.lecture.entity; + +public enum UserStatus { + MANAGED_BY_ME, // 내가 관리하는 강의 - 강사 + MANAGED_BY_OTHERS, // 내가 관리하지 않은 강의 - 강사 + ENROLLED, // 내가 수강 중인 강의 - 학생 + PENDING, // 내가 수강신청하고 승인 대기 중인 강의 - 학생 + NOT_ENROLLED // 내가 수강신청하지 않은 강의 - 학생/비로그인 +} diff --git a/backend/src/main/java/com/edufocus/edufocus/lecture/repository/LectureRepository.java b/backend/src/main/java/com/edufocus/edufocus/lecture/repository/LectureRepository.java new file mode 100644 index 0000000..34edc2a --- /dev/null +++ b/backend/src/main/java/com/edufocus/edufocus/lecture/repository/LectureRepository.java @@ -0,0 +1,20 @@ +package com.edufocus.edufocus.lecture.repository; + +import com.edufocus.edufocus.lecture.entity.Lecture; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import org.springframework.stereotype.Repository; + +import java.util.List; + +@Repository +public interface LectureRepository extends JpaRepository { + Lecture findByTitle(String title); + + List findAllByUserId(Long userId); + + List findLecturesByUserIdOrderById(Long userId); + + Lecture findByIdAndUserId(long id, long userId); +} diff --git a/backend/src/main/java/com/edufocus/edufocus/lecture/service/LectureService.java b/backend/src/main/java/com/edufocus/edufocus/lecture/service/LectureService.java new file mode 100644 index 0000000..fded3dc --- /dev/null +++ b/backend/src/main/java/com/edufocus/edufocus/lecture/service/LectureService.java @@ -0,0 +1,39 @@ +package com.edufocus.edufocus.lecture.service; + +import com.edufocus.edufocus.lecture.entity.Lecture; +import com.edufocus.edufocus.lecture.entity.LectureCreateRequest; +import com.edufocus.edufocus.lecture.entity.LectureSearchResponse; +import com.edufocus.edufocus.lecture.entity.LectureDetailResponse; +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; + +import java.util.List; + + +@Service +public interface LectureService { + + void createLecture(long userId, LectureCreateRequest lectureCreateRequest, MultipartFile image) throws Exception; + + boolean updateLecture(long userId, long lectureId, LectureCreateRequest lectureCreateRequest); + + boolean deleteLecture(long userId, long LectureId); + + List findAllLecture(); + + String getUserStatus(Long userId, Lecture lecture); + + LectureDetailResponse findLectureById(Long userId, long lectureId); + + List findMyLecture(long userId); + + Lecture findLectureByTitle(String title); + + void startClass(Long lectureId); + + void stopClass(Long lectureId); + + boolean getState(Long lectureId); + + boolean checkTeacher(Long userId, Long lectureId); +} diff --git a/backend/src/main/java/com/edufocus/edufocus/lecture/service/LectureServiceImpl.java b/backend/src/main/java/com/edufocus/edufocus/lecture/service/LectureServiceImpl.java new file mode 100644 index 0000000..1a3a572 --- /dev/null +++ b/backend/src/main/java/com/edufocus/edufocus/lecture/service/LectureServiceImpl.java @@ -0,0 +1,302 @@ +package com.edufocus.edufocus.lecture.service; + +import com.edufocus.edufocus.global.properties.ImagePathProperties; +import com.edufocus.edufocus.lecture.entity.*; +import com.edufocus.edufocus.lecture.repository.LectureRepository; +import com.edufocus.edufocus.registration.entity.Registration; +import com.edufocus.edufocus.registration.entity.RegistrationStatus; +import com.edufocus.edufocus.registration.repository.RegistrationRepository; +import com.edufocus.edufocus.user.model.entity.vo.User; +import com.edufocus.edufocus.user.model.entity.vo.UserRole; +import com.edufocus.edufocus.user.model.repository.UserRepository; +import jakarta.transaction.Transactional; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Sort; +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; + +import java.io.File; +import java.io.IOException; +import java.util.*; +import java.util.List; + +@Service +@Transactional +@RequiredArgsConstructor +public class LectureServiceImpl implements LectureService { + + private final ImagePathProperties imagePathProperties; + + private final LectureRepository lectureRepository; + + private final UserRepository userRepository; + + private final RegistrationRepository registrationRepository; + + @Override + public void createLecture(long userId, LectureCreateRequest lectureCreateRequest, MultipartFile image) throws IOException { + User user = userRepository.findById(userId).orElse(null); + + Lecture lecture = Lecture.builder() + .user(user) + .title(lectureCreateRequest.getTitle()) + .description(lectureCreateRequest.getDescription()) + .plan(lectureCreateRequest.getPlan()) + .startDate(lectureCreateRequest.getStartDate()) + .endDate(lectureCreateRequest.getEndDate()) + .time(lectureCreateRequest.getTime()) + .build(); + + if (image != null) { + String uid = UUID.randomUUID().toString(); + + String imagePath = imagePathProperties.getPath(); + + File checkPathFile = new File(imagePath); + if (!checkPathFile.exists()) { + checkPathFile.mkdirs(); + } + + File savingImage = new File(imagePath + "/" + uid + "_" + image.getOriginalFilename()); + image.transferTo(savingImage.toPath()); + String savePath = savingImage.toPath().toString(); + + lecture.setImage(savePath); + } + + lectureRepository.save(lecture); + } + + @Override + public boolean updateLecture(long userId, long lectureId, LectureCreateRequest lectureCreateRequest) { + Optional findLecture = lectureRepository.findById(lectureId); + + if (findLecture.isEmpty() || findLecture.get().getUser().getId() != userId) { + return false; + } + + Lecture lecture = findLecture.get(); + + if (lectureCreateRequest.getTitle() != null) { + lecture.setTitle(lectureCreateRequest.getTitle()); + } + if (lectureCreateRequest.getDescription() != null) { + lecture.setDescription(lectureCreateRequest.getDescription()); + } + if (lectureCreateRequest.getPlan() != null) { + lecture.setPlan(lectureCreateRequest.getPlan()); + } + if (lectureCreateRequest.getStartDate() != null) { + lecture.setStartDate(lectureCreateRequest.getStartDate()); + } + if (lectureCreateRequest.getEndDate() != null) { + lecture.setEndDate(lectureCreateRequest.getEndDate()); + } + if (lectureCreateRequest.getTime() != null) { + lecture.setTime(lectureCreateRequest.getTime()); + } + + lectureRepository.save(lecture); + return true; + } + + @Override + public boolean deleteLecture(long userId, long lectureId) { + Optional findLecture = lectureRepository.findById(lectureId); + + if (findLecture.isEmpty()) { + return false; + } + Lecture lecture = findLecture.get(); + + if (lecture.getUser().getId() != userId) { + return false; + } + + String image = lecture.getImage(); + if (image != null) { + File file = new File(lecture.getImage()); + file.delete(); + } + + lectureRepository.deleteById(lectureId); + return true; + } + + @Override + public List findAllLecture() { + List lectureList = lectureRepository.findAll(Sort.by(Sort.Direction.DESC, "id")); + + List lectureSearchResponseList = new ArrayList<>(); + for (Lecture lecture : lectureList) { + LectureSearchResponse lectureSearchResponse = LectureSearchResponse.builder() + .id(lecture.getId()) + .title(lecture.getTitle()) + .image(lecture.getImage()).build(); + + lectureSearchResponseList.add(lectureSearchResponse); + } + + return lectureSearchResponseList; + } + + @Override + public String getUserStatus(Long userId, Lecture lecture) { + if (userId == null) { + return String.valueOf(UserStatus.NOT_ENROLLED); + } + + User user = userRepository.findById(userId).orElseThrow(NoSuchElementException::new); + UserRole userRole = user.getRole(); + + if (userRole == UserRole.ADMIN) { + if (lecture.getUser() == user) { + return String.valueOf(UserStatus.MANAGED_BY_ME); + } + return String.valueOf(UserStatus.MANAGED_BY_OTHERS); + } + + Registration registration = registrationRepository.findByUserIdAndLectureId(userId, lecture.getId()); + + if (registration == null) { + return String.valueOf(UserStatus.NOT_ENROLLED); + } + + if (registration.getStatus() == RegistrationStatus.ACCEPTED) { + return String.valueOf(UserStatus.ENROLLED); + } + + return String.valueOf(UserStatus.PENDING); + } + + @Override + public LectureDetailResponse findLectureById(Long userId, long lectureId) { + Lecture lecture = lectureRepository.findById(lectureId).orElseThrow(NoSuchElementException::new); + + String userStatus = getUserStatus(userId, lecture); + + return LectureDetailResponse.builder() + .id(lecture.getId()) + .title(lecture.getTitle()) + .description(lecture.getDescription()) + .plan(lecture.getPlan()) + .image(lecture.getImage()) + .startDate(lecture.getStartDate()) + .endDate(lecture.getEndDate()) + .time(lecture.getTime()) + .online(lecture.isOnline()) + .teacherName(lecture.getUser().getName()) + .status(userStatus) + .build(); + + } + + public List findMyLecture(long userId) { + User user = userRepository.findById(userId).orElseThrow(NoSuchElementException::new); + + List myLectureList = new ArrayList<>(); + + if (user.getRole() == UserRole.ADMIN) { + List lectureList = lectureRepository.findLecturesByUserIdOrderById(userId); + for (Lecture lecture : lectureList) { + LectureSearchResponse lectureSearchResponse = LectureSearchResponse.builder() + .id(lecture.getId()) + .title(lecture.getTitle()) + .image(lecture.getImage()).build(); + + myLectureList.add(lectureSearchResponse); + } + } else { + List registrationList = registrationRepository.findAllByUserId(userId); + + for (Registration registration : registrationList) { + Lecture lecture = registration.getLecture(); + + if (registration.getStatus() == RegistrationStatus.ACCEPTED) { + LectureSearchResponse lectureSearchResponse = LectureSearchResponse.builder() + .id(lecture.getId()) + .title(lecture.getTitle()) + .image(lecture.getImage()).build(); + + myLectureList.add(lectureSearchResponse); + } + } + Collections.sort(myLectureList, new Comparator() { + @Override + public int compare(LectureSearchResponse lsr1, LectureSearchResponse lsr2) { + long lsr1Id = lsr1.getId(); + long lsr2Id = lsr2.getId(); + if (lsr2Id > lsr1Id) + return 1; + else if (lsr2Id == lsr1Id) + return 0; + return -1; + } + }); + } + + return myLectureList; + } + + @Override + public Lecture findLectureByTitle(String title) { + return lectureRepository.findByTitle(title); + } + + @Override + public void startClass(Long id) { + + Optional lecture = lectureRepository.findById(id); + + + if (!lecture.isPresent()) { + throw new RuntimeException("Lecture not found with id: " + id); + + } + + Lecture l; + l = lecture.get(); + if (l.isOnline() == false) { + l.setOnline(true); + } + + + lectureRepository.save(l); + } + + @Override + public void stopClass(Long id) { + Optional lecture = lectureRepository.findById(id); + + + if (!lecture.isPresent()) { + throw new RuntimeException("Lecture not found with id: " + id); + + } + + Lecture l; + l = lecture.get(); + if (l.isOnline()) { + l.setOnline(false); + } + + + lectureRepository.save(l); + } + + @Override + public boolean getState(Long lectureId) { + + Lecture lecture = lectureRepository.findById(lectureId).orElse(null); + return lecture.isOnline(); + + } + + @Override + public boolean checkTeacher(Long userId, Long lectureId) { + Optional lecture = lectureRepository.findById(lectureId); + + return lecture.isPresent() && lecture.get().getUser().getId() == userId; + } + +} diff --git a/backend/src/main/java/com/edufocus/edufocus/mail/controller/MailController.java b/backend/src/main/java/com/edufocus/edufocus/mail/controller/MailController.java new file mode 100644 index 0000000..bc6be99 --- /dev/null +++ b/backend/src/main/java/com/edufocus/edufocus/mail/controller/MailController.java @@ -0,0 +1,39 @@ +package com.edufocus.edufocus.mail.controller; + +import com.edufocus.edufocus.mail.service.MailService; +import com.edufocus.edufocus.user.model.service.UserService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +@Slf4j +@RestController +@RequestMapping("/mail") +@RequiredArgsConstructor +public class MailController { + + private final MailService mailService; + + private final UserService userService; + + @PostMapping("/sendcode") + public ResponseEntity sendMail(@RequestParam String email) { + if (!userService.isEmailExist(email)) { + return new ResponseEntity<>(HttpStatus.NOT_FOUND); + } + + mailService.sendMail(email); + return new ResponseEntity<>(HttpStatus.OK); + } + + @GetMapping("/verify") + public ResponseEntity verifyCode(@RequestParam String code, @RequestParam String email) { + if (!mailService.verifyCode(code, email)) { + return new ResponseEntity<>(HttpStatus.NOT_FOUND); + } + return new ResponseEntity<>(HttpStatus.OK); + } + +} diff --git a/backend/src/main/java/com/edufocus/edufocus/mail/service/MailService.java b/backend/src/main/java/com/edufocus/edufocus/mail/service/MailService.java new file mode 100644 index 0000000..1799126 --- /dev/null +++ b/backend/src/main/java/com/edufocus/edufocus/mail/service/MailService.java @@ -0,0 +1,13 @@ +package com.edufocus.edufocus.mail.service; + +import org.springframework.stereotype.Service; + +@Service +public interface MailService { + + void sendMail(String to); + + String createRandomCode(); + + boolean verifyCode(String code, String email); +} diff --git a/backend/src/main/java/com/edufocus/edufocus/mail/service/MailServiceImpl.java b/backend/src/main/java/com/edufocus/edufocus/mail/service/MailServiceImpl.java new file mode 100644 index 0000000..a5c1eb9 --- /dev/null +++ b/backend/src/main/java/com/edufocus/edufocus/mail/service/MailServiceImpl.java @@ -0,0 +1,76 @@ +package com.edufocus.edufocus.mail.service; + +import com.edufocus.edufocus.redis.util.RedisUtil; +import com.edufocus.edufocus.user.model.repository.UserRepository; +import com.edufocus.edufocus.user.model.service.UserService; +import lombok.RequiredArgsConstructor; +import lombok.ToString; +import lombok.extern.slf4j.Slf4j; +import org.springframework.mail.SimpleMailMessage; +import org.springframework.mail.javamail.JavaMailSender; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Service; + +import java.util.Random; + +@Service +@Slf4j +@ToString +@RequiredArgsConstructor +public class MailServiceImpl implements MailService { + + private final JavaMailSender mailSender; + + private final UserRepository userRepository; + + private final UserService userService; + + private final RedisUtil redisUtil; + + @Override + @Async("mailExecutor") + public void sendMail(String email) { + String code = createRandomCode(); + + if (redisUtil.exists(email)) { + redisUtil.deleteData(redisUtil.getData(email)); + } + + redisUtil.setDataExpire(code, email, 60 * 5L); + redisUtil.setDataExpire(email, code, 60 * 5L); + + SimpleMailMessage mail = createEmail(email, "[EDUFOCUS] 비밀번호 찾기 안내", code); + mailSender.send(mail); + } + + @Override + public boolean verifyCode(String code, String email) { + String registedEmail = redisUtil.getData(code); + + return registedEmail != null && registedEmail.equals(email); + } + + private SimpleMailMessage createEmail(String to, String title, String code) { + SimpleMailMessage message = new SimpleMailMessage(); + message.setFrom("EDUFOCUS"); + message.setTo(to); + message.setSubject(title); + message.setText("인증번호 6자리입니다 : " + code); + + return message; + } + + @Override + public String createRandomCode() { + StringBuilder sb = new StringBuilder(); + Random random = new Random(); + for (int i = 0; i < 3; i++) { + sb.append((char) (random.nextInt(26) + 65)); + } + for (int i = 0; i < 3; i++) { + sb.append(random.nextInt(10)); + } + return sb.toString(); + } + +} diff --git a/backend/src/main/java/com/edufocus/edufocus/qna/controller/QnaController.java b/backend/src/main/java/com/edufocus/edufocus/qna/controller/QnaController.java new file mode 100644 index 0000000..7fafb44 --- /dev/null +++ b/backend/src/main/java/com/edufocus/edufocus/qna/controller/QnaController.java @@ -0,0 +1,157 @@ +package com.edufocus.edufocus.qna.controller; + +import com.edufocus.edufocus.qna.entity.Qna; +import com.edufocus.edufocus.qna.entity.QnaRequestDto; +import com.edufocus.edufocus.qna.entity.QnaResponseDto; +import com.edufocus.edufocus.qna.service.QnaService; +import com.edufocus.edufocus.user.model.entity.vo.User; +import com.edufocus.edufocus.user.model.entity.vo.UserRole; +import com.edufocus.edufocus.user.model.repository.UserRepository; +import com.edufocus.edufocus.user.util.JWTUtil; +import jakarta.servlet.http.HttpServletRequest; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import okhttp3.Response; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.sql.SQLException; +import java.util.List; + +@RestController +@RequestMapping("/qna") +@Slf4j +@RequiredArgsConstructor +public class QnaController { + private final QnaService qnaService; + private final JWTUtil jwtUtil; + private static int PAGE_SIZE = 10; + private final UserRepository userRepository; + + @PostMapping("/{lecture_id}") + public ResponseEntity createQna(@PathVariable("lecture_id") Long lecture_id, @RequestBody QnaRequestDto qnaRequestDto, HttpServletRequest request) { + + + try { + String token = request.getHeader("Authorization"); + Long userId = Long.parseLong(jwtUtil.getUserId(token)); + + QnaResponseDto qnaResponseDto = qnaService.createQna(userId, qnaRequestDto, lecture_id); + return new ResponseEntity<>(qnaResponseDto, HttpStatus.CREATED); + + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @PostMapping({"/answer/create/{qna_id}"}) + public ResponseEntity createAnswer(@PathVariable("qna_id") Long qna_id, @RequestBody QnaRequestDto qnaRequestDto, HttpServletRequest request) { + try { + String token = request.getHeader("Authorization"); + System.out.println(token); + Long userId = Long.parseLong(jwtUtil.getUserId(token)); + User findUser = userRepository.findById(userId).orElse(null); + + if (findUser.getRole() != UserRole.ADMIN) { + return new ResponseEntity<>(HttpStatus.NOT_ACCEPTABLE); + + } + + QnaResponseDto responseDto = qnaService.createAnswer(qna_id, qnaRequestDto); + return new ResponseEntity<>(responseDto, HttpStatus.ACCEPTED); + } catch (Exception e) { + return new ResponseEntity<>(HttpStatus.NOT_ACCEPTABLE); + + } + } + + @PutMapping({"/answer/update/{qna_id}"}) + public ResponseEntity updateAnswer(@PathVariable("qna_id") Long qna_id, @RequestBody QnaRequestDto qnaRequestDto, HttpServletRequest request) { + try { + String token = request.getHeader("Authorization"); + Long userId = Long.parseLong(jwtUtil.getUserId(token)); + User findUser = userRepository.findById(userId).orElse(null); + + if (findUser.getRole() != UserRole.ADMIN) { + System.out.println("role 안맞음"); + return new ResponseEntity<>(HttpStatus.NOT_ACCEPTABLE); + } + + QnaResponseDto responseDto = qnaService.updateAnswer(qna_id, qnaRequestDto); + return new ResponseEntity<>(responseDto, HttpStatus.ACCEPTED); + } catch (Exception e) { + return new ResponseEntity<>(HttpStatus.NOT_ACCEPTABLE); + } + } + + @PostMapping("/answer/delete/{qna_id}") + public ResponseEntity deleteAnswer(@PathVariable("qna_id") Long qna_id, HttpServletRequest request) { + try { + String token = request.getHeader("Authorization"); + Long userId = Long.parseLong(jwtUtil.getUserId(token)); + User findUser = userRepository.findById(userId).orElse(null); + + System.out.println("delete answer"); + if (findUser.getRole() != UserRole.ADMIN) { + return new ResponseEntity<>(HttpStatus.NOT_ACCEPTABLE); + } + qnaService.deleteAnswer(qna_id); + return new ResponseEntity<>(HttpStatus.ACCEPTED); + } catch (Exception e) { + return new ResponseEntity<>(HttpStatus.NOT_ACCEPTABLE); + } + } + + + @PutMapping("/{id}") + public ResponseEntity updateQna(@PathVariable Long id, @RequestBody QnaRequestDto qnaRequestDto, HttpServletRequest request) { + String token = request.getHeader("Authorization"); + Long userId = Long.parseLong(jwtUtil.getUserId(token)); + try { + QnaResponseDto qnaResponseDto = qnaService.updateQna(id, qnaRequestDto, userId); + return new ResponseEntity<>(qnaResponseDto, HttpStatus.ACCEPTED); + + } catch (Exception e) { + return new ResponseEntity<>(HttpStatus.NOT_ACCEPTABLE); + } + } + + @DeleteMapping("/{id}") + public ResponseEntity deleteQna(@PathVariable Long id, HttpServletRequest request) { + try { + String token = request.getHeader("Authorization"); + Long userId = Long.parseLong(jwtUtil.getUserId(token)); + qnaService.deleteQna(id, userId); + return new ResponseEntity<>(HttpStatus.ACCEPTED); + + } catch (SQLException e) { + return new ResponseEntity<>(HttpStatus.NOT_ACCEPTABLE); + } + } + + @GetMapping("/{id}") + public ResponseEntity getQna(@PathVariable Long id, HttpServletRequest request) { + try { + String token = request.getHeader("Authorization"); + long userId = Long.parseLong(jwtUtil.getUserId(token)); + QnaResponseDto findQna = qnaService.getQna(id, userId); + return new ResponseEntity<>(findQna, HttpStatus.ACCEPTED); + + } catch (SQLException e) { + return new ResponseEntity<>(HttpStatus.NOT_ACCEPTABLE); + } + } + + @GetMapping("/all/{id}") + public ResponseEntity> getAllQna( + @PathVariable Long id, + @RequestParam(defaultValue = "0") int pageNo) { + try { + List qnaList = qnaService.getAllQnasByLecture(id, pageNo, PAGE_SIZE); + return new ResponseEntity<>(qnaList, HttpStatus.ACCEPTED); + } catch (SQLException e) { + return new ResponseEntity<>(HttpStatus.NOT_ACCEPTABLE); + } + } +} diff --git a/backend/src/main/java/com/edufocus/edufocus/qna/entity/Qna.java b/backend/src/main/java/com/edufocus/edufocus/qna/entity/Qna.java new file mode 100644 index 0000000..6216f88 --- /dev/null +++ b/backend/src/main/java/com/edufocus/edufocus/qna/entity/Qna.java @@ -0,0 +1,63 @@ +package com.edufocus.edufocus.qna.entity; + +import com.edufocus.edufocus.lecture.entity.Lecture; +import com.edufocus.edufocus.user.model.entity.vo.User; +import jakarta.persistence.*; +import lombok.*; +import org.hibernate.annotations.OnDelete; +import org.hibernate.annotations.OnDeleteAction; + +import java.util.Date; + +@Entity +@Getter +@Setter +@Builder +@NoArgsConstructor +@AllArgsConstructor + + +public class Qna { + + // 연관관계 주인 + + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "qna_id") + private Long id; + + + @Column + private String title; + + @Column(columnDefinition = "TEXT") + private String content; + + + @Column(name = "created_at") + @Temporal(TemporalType.TIMESTAMP) + private Date createdAt; + + @Column(name = "modified_at") + @Temporal(TemporalType.DATE) + private Date modifiedAt; + + @Column(columnDefinition = "TEXT") + private String answer; + + + private String name; + @ManyToOne + @JoinColumn(name = "id") + private User user; + + @ManyToOne + @JoinColumn(name = "lecture_id") + @OnDelete(action = OnDeleteAction.CASCADE) + private Lecture lecture; + + private boolean isMine; + + +} diff --git a/backend/src/main/java/com/edufocus/edufocus/qna/entity/QnaRequestDto.java b/backend/src/main/java/com/edufocus/edufocus/qna/entity/QnaRequestDto.java new file mode 100644 index 0000000..f73eeae --- /dev/null +++ b/backend/src/main/java/com/edufocus/edufocus/qna/entity/QnaRequestDto.java @@ -0,0 +1,24 @@ +package com.edufocus.edufocus.qna.entity; + +import jakarta.persistence.Column; +import lombok.*; + +@Getter +public class QnaRequestDto { + + + private String title; + private String content; + private String answer; + + public static Qna toEntity(QnaRequestDto qnaRequestDto) { + { + + return Qna.builder() + .content(qnaRequestDto.getContent()) + .title(qnaRequestDto.getTitle()) + .answer(qnaRequestDto.getAnswer()) + .build(); + } + } +} diff --git a/backend/src/main/java/com/edufocus/edufocus/qna/entity/QnaResponseDto.java b/backend/src/main/java/com/edufocus/edufocus/qna/entity/QnaResponseDto.java new file mode 100644 index 0000000..4a55d77 --- /dev/null +++ b/backend/src/main/java/com/edufocus/edufocus/qna/entity/QnaResponseDto.java @@ -0,0 +1,43 @@ +package com.edufocus.edufocus.qna.entity; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.Date; + +@Getter + +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class QnaResponseDto { + + + private Long id; + private String title; + private String username; + private String content; + private Date createtAt; + private String answer; + private boolean isMine; + + public static QnaResponseDto toEntity(Qna qna) { + return new QnaResponseDto( + qna.getId(), + qna.getTitle(), + qna.getUser().getName(), + qna.getContent(), + qna.getCreatedAt(), + qna.getAnswer(), + qna.isMine() + ); + } + + +} + + + diff --git a/backend/src/main/java/com/edufocus/edufocus/qna/repository/QnaRepository.java b/backend/src/main/java/com/edufocus/edufocus/qna/repository/QnaRepository.java new file mode 100644 index 0000000..52ec462 --- /dev/null +++ b/backend/src/main/java/com/edufocus/edufocus/qna/repository/QnaRepository.java @@ -0,0 +1,20 @@ +package com.edufocus.edufocus.qna.repository; + +import com.edufocus.edufocus.qna.entity.Qna; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import org.springframework.stereotype.Repository; + +import java.util.List; + +@Repository +public interface QnaRepository extends JpaRepository { + + //List findByLectureId(Long lecturerId); + + @Query("SELECT q FROM Qna q WHERE q.lecture.id = :lectureId ORDER BY q.createdAt DESC") + Page findByLectureId(Long lectureId, Pageable pageable); +} diff --git a/backend/src/main/java/com/edufocus/edufocus/qna/service/QnaService.java b/backend/src/main/java/com/edufocus/edufocus/qna/service/QnaService.java new file mode 100644 index 0000000..6308a95 --- /dev/null +++ b/backend/src/main/java/com/edufocus/edufocus/qna/service/QnaService.java @@ -0,0 +1,33 @@ +package com.edufocus.edufocus.qna.service; + +import com.edufocus.edufocus.lecture.entity.Lecture; +import com.edufocus.edufocus.qna.entity.Qna; +import com.edufocus.edufocus.qna.entity.QnaRequestDto; +import com.edufocus.edufocus.qna.entity.QnaResponseDto; +import jakarta.transaction.Transactional; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.sql.SQLException; +import java.util.List; + +@Service +public interface QnaService { + + QnaResponseDto createQna(Long id, QnaRequestDto qnaRequestDto, Long lecture_id) throws SQLException; + + QnaResponseDto updateQna(Long id, QnaRequestDto qnaRequestDto, Long userId) throws SQLException; + + void deleteQna(Long id, Long userId) throws SQLException; + + QnaResponseDto getQna(Long id, Long userId) throws SQLException; + + List getAllQnasByLecture(Long lectureId, int pageNo, int pageNumber) throws SQLException; + + QnaResponseDto createAnswer(Long id, QnaRequestDto qnaRequestDto) throws SQLException; + + QnaResponseDto updateAnswer(Long id, QnaRequestDto qnaRequestDto) throws SQLException; + + void deleteAnswer(Long id) throws SQLException; + +} diff --git a/backend/src/main/java/com/edufocus/edufocus/qna/service/QnaServiceImpl.java b/backend/src/main/java/com/edufocus/edufocus/qna/service/QnaServiceImpl.java new file mode 100644 index 0000000..a60b3d9 --- /dev/null +++ b/backend/src/main/java/com/edufocus/edufocus/qna/service/QnaServiceImpl.java @@ -0,0 +1,169 @@ +package com.edufocus.edufocus.qna.service; + +import com.edufocus.edufocus.lecture.entity.Lecture; +import com.edufocus.edufocus.lecture.repository.LectureRepository; +import com.edufocus.edufocus.qna.entity.Qna; +import com.edufocus.edufocus.qna.entity.QnaRequestDto; +import com.edufocus.edufocus.qna.entity.QnaResponseDto; +import com.edufocus.edufocus.qna.repository.QnaRepository; +import com.edufocus.edufocus.user.model.entity.vo.User; +import com.edufocus.edufocus.user.model.entity.vo.UserRole; +import com.edufocus.edufocus.user.model.repository.UserRepository; +import jakarta.transaction.Transactional; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; + +import java.sql.SQLException; +import java.util.Date; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +@Service +@Transactional +@RequiredArgsConstructor +public class QnaServiceImpl implements QnaService { + + private final QnaRepository qnaRepository; + private final LectureRepository lectureRepository; + private final UserRepository userRepository; + + + @Override + public QnaResponseDto createQna(Long id, QnaRequestDto qnaRequestDto, Long lecture_id) { + + + Lecture lecture = lectureRepository.findById(lecture_id).orElse(null); + + User user = userRepository.findById(id).orElse(null); + + + Qna qna = QnaRequestDto.toEntity(qnaRequestDto); + + qna.setLecture(lecture); + qna.setUser(user); + + qna.setCreatedAt(new Date()); + + qnaRepository.save(qna); + return QnaResponseDto.toEntity(qna); + } + + @Override + public QnaResponseDto updateQna(Long id, QnaRequestDto qnaRequestDto, Long userId) { + + System.out.println("userId:" + userId); + + Qna findQna = qnaRepository.findById(id).orElse(null); + System.out.println("quesiton에 있는거: " + findQna.getUser().getId()); + User user = userRepository.findById(userId).orElse(null); + + if (findQna.getUser().getId() != userId || user.getRole() != UserRole.STUDENT) { + throw new RuntimeException(); + } + + + findQna.setModifiedAt(new Date()); + findQna.setTitle(qnaRequestDto.getTitle()); + findQna.setContent(qnaRequestDto.getContent()); + + qnaRepository.save(findQna); + + return QnaResponseDto.toEntity(findQna); + + + } + + @Override + public void deleteQna(Long id, Long userId) { + + Qna qna = qnaRepository.findById(id).orElse(null); + User user = userRepository.findById(userId).orElse(null); + if (qna.getUser().getId() == userId || user.getRole() == UserRole.ADMIN) { + qnaRepository.delete(qna); + } else { + throw new RuntimeException(); + } + + } + + @Override + public QnaResponseDto getQna(Long id, Long userId) { + + Qna qna; + try { + + qna = qnaRepository.findById(id).orElse(null); + + + } catch (Exception e) { + + throw new RuntimeException("Qna 없음 " + id, e); + } + + QnaResponseDto dto = QnaResponseDto.builder() + .id(qna.getId()) + .title(qna.getTitle()) + .username(qna.getUser().getName()) + .content(qna.getContent()) + .createtAt(qna.getCreatedAt()) + .answer(qna.getAnswer()) + .isMine(userId == qna.getUser().getId()) + .build(); + + + return dto; + + } + + @Override + public List getAllQnasByLecture(Long lectureId, int pageNo, int pageSize) { + Pageable pageable = PageRequest.of(pageNo, pageSize); + Page qnaPage = qnaRepository.findByLectureId(lectureId, pageable); + + return qnaPage.getContent().stream() + .map(QnaResponseDto::toEntity) + .collect(Collectors.toList()); + } + + + @Override + public QnaResponseDto createAnswer(Long id, QnaRequestDto qnaRequestDto) throws SQLException { + + Qna findQna = qnaRepository.findById(id).orElse(null); + if (findQna.getAnswer() != null) { + throw new RuntimeException(); + } + findQna.setAnswer(qnaRequestDto.getAnswer()); + + + qnaRepository.save(findQna); + + return QnaResponseDto.toEntity(findQna); + + } + + @Override + public QnaResponseDto updateAnswer(Long id, QnaRequestDto qnaRequestDto) throws SQLException { + + Qna findQna = qnaRepository.findById(id).orElse(null); + findQna.setAnswer(qnaRequestDto.getAnswer()); + + + qnaRepository.save(findQna); + + return QnaResponseDto.toEntity(findQna); + } + + @Override + public void deleteAnswer(Long id) throws SQLException { + + Qna findQna = qnaRepository.findById(id).orElse(null); + findQna.setAnswer(null); + qnaRepository.save(findQna); + + } +} diff --git a/backend/src/main/java/com/edufocus/edufocus/quiz/controller/QuizController.java b/backend/src/main/java/com/edufocus/edufocus/quiz/controller/QuizController.java new file mode 100644 index 0000000..c9b2dbb --- /dev/null +++ b/backend/src/main/java/com/edufocus/edufocus/quiz/controller/QuizController.java @@ -0,0 +1,145 @@ +package com.edufocus.edufocus.quiz.controller; + +import com.edufocus.edufocus.quiz.entity.*; +import com.edufocus.edufocus.quiz.service.QuizService; +import com.edufocus.edufocus.quiz.service.QuizSetService; +import com.edufocus.edufocus.user.util.JWTUtil; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import java.io.IOException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@RestController +@RequestMapping("/quiz") +@Slf4j +@RequiredArgsConstructor +public class QuizController { + + private final QuizService quizService; + + private final QuizSetService quizSetService; + + private final JWTUtil jwtUtil; + + @PostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE) + public ResponseEntity createQuizSet(@RequestHeader("Authorization") String accessToken, @RequestPart QuizSetCreateRequest quizSetCreateRequest + , @RequestPart(value = "images", required = false) List images) throws IOException { + long userId = Long.parseLong(jwtUtil.getUserId(accessToken)); + + if (quizSetCreateRequest.getQuizzes().isEmpty()) { + return new ResponseEntity<>(HttpStatus.BAD_REQUEST); + } + + for (MultipartFile image : images) { + if (image != null && !image.getContentType().startsWith("image")) { + return new ResponseEntity<>(HttpStatus.BAD_REQUEST); + } + } + + QuizSet quizSet = quizSetService.createQuizSet(userId, quizSetCreateRequest.getTitle()); + + int imageIdx = 0; + for (QuizCreateRequest quizCreateRequest : quizSetCreateRequest.getQuizzes()) { + quizService.createQuiz(quizSet, quizCreateRequest, images.get(imageIdx++)); + } + + return new ResponseEntity<>(HttpStatus.CREATED); + } + + @GetMapping("/student/{quizsetId}") + public ResponseEntity getQuizzes(@PathVariable Long quizsetId) { + QuizSetResponse quizSet = quizSetService.findQuizSetResponse(quizsetId); + + return new ResponseEntity<>(quizSet, HttpStatus.OK); + } + + @GetMapping("/teacher/{quizsetId}") + public ResponseEntity getQuizzesIncludeAnswer(@RequestHeader("Authorization") String accessToken, @PathVariable Long quizsetId) { + long userId = Long.parseLong(jwtUtil.getUserId(accessToken)); + + QuizSet quizSet = quizSetService.findQuizSet(quizsetId); + if (quizSet.getUser().getId() != userId) { + return new ResponseEntity<>(HttpStatus.UNAUTHORIZED); + } + + return new ResponseEntity<>(quizSet, HttpStatus.OK); + } + + @PutMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE) + public ResponseEntity updateQuizSet(@RequestHeader("Authorization") String accessToken, @RequestPart QuizSetUpdateRequest quizSetUpdateRequest + , @RequestPart(value = "images", required = false) List images) throws IOException { + long userId = Long.parseLong(jwtUtil.getUserId(accessToken)); + + QuizSet quizset = quizSetService.findQuizSet(quizSetUpdateRequest.getId()); + if (userId != quizset.getUser().getId()) { + return new ResponseEntity<>(HttpStatus.UNAUTHORIZED); + } + + if (quizset.isTested()) { + return new ResponseEntity<>(HttpStatus.CONFLICT); + } + + for (MultipartFile image : images) { + if (image != null && !image.getContentType().startsWith("image")) { + return new ResponseEntity<>(HttpStatus.BAD_REQUEST); + } + } + + quizSetService.updateQuizSet(quizSetUpdateRequest.getId(), quizSetUpdateRequest.getTitle()); + + Map quizUpdatedCheckMap = new HashMap<>(); + for (Quiz quiz : quizset.getQuizzes()) { + quizUpdatedCheckMap.put(quiz.getId(), false); + } + int imageIdx = 0; + for (QuizUpdateRequest quizUpdateRequest : quizSetUpdateRequest.getQuizzes()) { + if (quizUpdateRequest.getId() == null) { + QuizCreateRequest quizCreateRequest = QuizCreateRequest.builder() + .question(quizUpdateRequest.getQuestion()) + .answer(quizUpdateRequest.getAnswer()) + .choices(quizUpdateRequest.getChoices()) + .build(); + + quizService.createQuiz(quizset, quizCreateRequest, images.get(imageIdx++)); + } else { + quizService.updateQuiz(quizUpdateRequest, images.get(imageIdx++)); + + quizUpdatedCheckMap.put(quizUpdateRequest.getId(), true); + } + } + + for (Long quizId : quizUpdatedCheckMap.keySet()) { + if (!quizUpdatedCheckMap.get(quizId)) { + quizService.removeQuiz(quizId); + } + } + + return new ResponseEntity<>(HttpStatus.OK); + } + + @DeleteMapping("/{quizsetId}") + public ResponseEntity deleteQuizSet(@RequestHeader("Authorization") String accessToken, @PathVariable long quizsetId) { + long userId = Long.parseLong(jwtUtil.getUserId(accessToken)); + + if (!quizSetService.deleteQuizSet(userId, quizsetId)) { + return new ResponseEntity<>(HttpStatus.NOT_FOUND); + } + + return new ResponseEntity<>(HttpStatus.NO_CONTENT); + } + + @GetMapping + public ResponseEntity getQuizSets(@RequestHeader("Authorization") String accessToken) { + long userId = Long.parseLong(jwtUtil.getUserId(accessToken)); + + return new ResponseEntity<>(quizSetService.findMyQuizSetResponses(userId), HttpStatus.OK); + } +} diff --git a/backend/src/main/java/com/edufocus/edufocus/quiz/entity/Choice.java b/backend/src/main/java/com/edufocus/edufocus/quiz/entity/Choice.java new file mode 100644 index 0000000..b8e82c2 --- /dev/null +++ b/backend/src/main/java/com/edufocus/edufocus/quiz/entity/Choice.java @@ -0,0 +1,37 @@ +package com.edufocus.edufocus.quiz.entity; + +import com.edufocus.edufocus.report.entity.dto.ChoiceDto; +import com.fasterxml.jackson.annotation.JsonBackReference; +import jakarta.persistence.*; +import lombok.*; + +@Entity +@Getter +@Setter +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class Choice { + + @Id + @GeneratedValue + private long id; + + @ManyToOne + @JoinColumn(name = "QuizId") + @JsonBackReference + private Quiz quiz; + + @Column + private int num; + + @Column + private String content; + + public ChoiceDto makeChoiceDto(){ + return ChoiceDto.builder() + .num(num) + .content(content) + .build(); + } +} diff --git a/backend/src/main/java/com/edufocus/edufocus/quiz/entity/ChoiceCreateRequest.java b/backend/src/main/java/com/edufocus/edufocus/quiz/entity/ChoiceCreateRequest.java new file mode 100644 index 0000000..80f8fc4 --- /dev/null +++ b/backend/src/main/java/com/edufocus/edufocus/quiz/entity/ChoiceCreateRequest.java @@ -0,0 +1,15 @@ +package com.edufocus.edufocus.quiz.entity; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +@AllArgsConstructor +public class ChoiceCreateRequest { + + private int num; + + private String content; +} diff --git a/backend/src/main/java/com/edufocus/edufocus/quiz/entity/MyQuizSetResponse.java b/backend/src/main/java/com/edufocus/edufocus/quiz/entity/MyQuizSetResponse.java new file mode 100644 index 0000000..9bf177c --- /dev/null +++ b/backend/src/main/java/com/edufocus/edufocus/quiz/entity/MyQuizSetResponse.java @@ -0,0 +1,14 @@ +package com.edufocus.edufocus.quiz.entity; + +import lombok.*; + +@Getter +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class MyQuizSetResponse { + + private long quizSetId; + + private String title; +} diff --git a/backend/src/main/java/com/edufocus/edufocus/quiz/entity/Quiz.java b/backend/src/main/java/com/edufocus/edufocus/quiz/entity/Quiz.java new file mode 100644 index 0000000..d0737b6 --- /dev/null +++ b/backend/src/main/java/com/edufocus/edufocus/quiz/entity/Quiz.java @@ -0,0 +1,55 @@ +package com.edufocus.edufocus.quiz.entity; + +import com.fasterxml.jackson.annotation.JsonBackReference; +import com.fasterxml.jackson.annotation.JsonManagedReference; +import jakarta.persistence.*; +import lombok.*; + +import java.util.List; + +@Entity +@Getter +@Setter +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class Quiz { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private long id; + + @ManyToOne + @JoinColumn(name = "quizset_id") + @JsonBackReference + private QuizSet quizSet; + + @Column + private String question; + + @Column + private String image; + + @Column + private String answer; + + @OneToMany(mappedBy = "quiz", orphanRemoval = true, cascade = CascadeType.ALL) + @JsonManagedReference + private List choices; + + public void setQuizSet(QuizSet quizSet) { + this.quizSet = quizSet; + + if (!quizSet.getQuizzes().contains(this)) { + quizSet.getQuizzes().remove(this); + } + } + + public void addChoice(Choice choice) { + this.choices.add(choice); + + if (choice.getQuiz() != this) { + choice.setQuiz(this); + } + } +} diff --git a/backend/src/main/java/com/edufocus/edufocus/quiz/entity/QuizCreateRequest.java b/backend/src/main/java/com/edufocus/edufocus/quiz/entity/QuizCreateRequest.java new file mode 100644 index 0000000..d2a254d --- /dev/null +++ b/backend/src/main/java/com/edufocus/edufocus/quiz/entity/QuizCreateRequest.java @@ -0,0 +1,21 @@ +package com.edufocus.edufocus.quiz.entity; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.util.List; + +@Builder +@Getter +@NoArgsConstructor +@AllArgsConstructor +public class QuizCreateRequest { + + private String question; + + private String answer; + + private List choices; +} diff --git a/backend/src/main/java/com/edufocus/edufocus/quiz/entity/QuizResponse.java b/backend/src/main/java/com/edufocus/edufocus/quiz/entity/QuizResponse.java new file mode 100644 index 0000000..0b0be9f --- /dev/null +++ b/backend/src/main/java/com/edufocus/edufocus/quiz/entity/QuizResponse.java @@ -0,0 +1,21 @@ +package com.edufocus.edufocus.quiz.entity; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.util.List; + +@Getter +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class QuizResponse { + + private String question; + + private String image; + + private List choices; +} diff --git a/backend/src/main/java/com/edufocus/edufocus/quiz/entity/QuizSet.java b/backend/src/main/java/com/edufocus/edufocus/quiz/entity/QuizSet.java new file mode 100644 index 0000000..19c7e35 --- /dev/null +++ b/backend/src/main/java/com/edufocus/edufocus/quiz/entity/QuizSet.java @@ -0,0 +1,45 @@ +package com.edufocus.edufocus.quiz.entity; + +import com.edufocus.edufocus.user.model.entity.vo.User; +import com.fasterxml.jackson.annotation.JsonBackReference; +import com.fasterxml.jackson.annotation.JsonManagedReference; +import jakarta.persistence.*; +import lombok.*; + +import java.util.List; + +@Entity +@Getter +@Setter +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class QuizSet { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private long id; + + @ManyToOne + @JoinColumn(name = "user_id") + @JsonBackReference + private User user; + + @Column + private String title; + + @Column + private boolean tested; + + @OneToMany(mappedBy = "quizSet", orphanRemoval = true) + @JsonManagedReference + private List quizzes; + + public void addQuiz(Quiz quiz) { + this.quizzes.add(quiz); + + if (quiz.getQuizSet() != this) { + quiz.setQuizSet(this); + } + } +} diff --git a/backend/src/main/java/com/edufocus/edufocus/quiz/entity/QuizSetCreateRequest.java b/backend/src/main/java/com/edufocus/edufocus/quiz/entity/QuizSetCreateRequest.java new file mode 100644 index 0000000..0a137a3 --- /dev/null +++ b/backend/src/main/java/com/edufocus/edufocus/quiz/entity/QuizSetCreateRequest.java @@ -0,0 +1,18 @@ +package com.edufocus.edufocus.quiz.entity; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.util.List; + +@Getter +@NoArgsConstructor +@AllArgsConstructor +public class QuizSetCreateRequest { + + private String title; + + private List quizzes; + +} \ No newline at end of file diff --git a/backend/src/main/java/com/edufocus/edufocus/quiz/entity/QuizSetResponse.java b/backend/src/main/java/com/edufocus/edufocus/quiz/entity/QuizSetResponse.java new file mode 100644 index 0000000..1d4098d --- /dev/null +++ b/backend/src/main/java/com/edufocus/edufocus/quiz/entity/QuizSetResponse.java @@ -0,0 +1,17 @@ +package com.edufocus.edufocus.quiz.entity; + +import lombok.*; + +import java.util.List; + +@Getter +@Setter +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class QuizSetResponse { + + private String title; + + private List quizzes; +} diff --git a/backend/src/main/java/com/edufocus/edufocus/quiz/entity/QuizSetUpdateRequest.java b/backend/src/main/java/com/edufocus/edufocus/quiz/entity/QuizSetUpdateRequest.java new file mode 100644 index 0000000..da4c5f9 --- /dev/null +++ b/backend/src/main/java/com/edufocus/edufocus/quiz/entity/QuizSetUpdateRequest.java @@ -0,0 +1,20 @@ +package com.edufocus.edufocus.quiz.entity; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.util.List; + +@Getter +@NoArgsConstructor +@AllArgsConstructor +public class QuizSetUpdateRequest { + + private long id; + + private String title; + + private List quizzes; + +} diff --git a/backend/src/main/java/com/edufocus/edufocus/quiz/entity/QuizUpdateRequest.java b/backend/src/main/java/com/edufocus/edufocus/quiz/entity/QuizUpdateRequest.java new file mode 100644 index 0000000..7fa153d --- /dev/null +++ b/backend/src/main/java/com/edufocus/edufocus/quiz/entity/QuizUpdateRequest.java @@ -0,0 +1,21 @@ +package com.edufocus.edufocus.quiz.entity; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.util.List; + +@Getter +@NoArgsConstructor +@AllArgsConstructor +public class QuizUpdateRequest { + + private Long id; + + private String question; + + private String answer; + + private List choices; +} diff --git a/backend/src/main/java/com/edufocus/edufocus/quiz/repository/ChoiceRepository.java b/backend/src/main/java/com/edufocus/edufocus/quiz/repository/ChoiceRepository.java new file mode 100644 index 0000000..b32ec5f --- /dev/null +++ b/backend/src/main/java/com/edufocus/edufocus/quiz/repository/ChoiceRepository.java @@ -0,0 +1,7 @@ +package com.edufocus.edufocus.quiz.repository; + +import com.edufocus.edufocus.quiz.entity.Choice; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface ChoiceRepository extends JpaRepository { +} diff --git a/backend/src/main/java/com/edufocus/edufocus/quiz/repository/QuizRepository.java b/backend/src/main/java/com/edufocus/edufocus/quiz/repository/QuizRepository.java new file mode 100644 index 0000000..c271ee2 --- /dev/null +++ b/backend/src/main/java/com/edufocus/edufocus/quiz/repository/QuizRepository.java @@ -0,0 +1,8 @@ +package com.edufocus.edufocus.quiz.repository; + +import com.edufocus.edufocus.quiz.entity.Quiz; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface QuizRepository extends JpaRepository { + +} diff --git a/backend/src/main/java/com/edufocus/edufocus/quiz/repository/QuizSetRepository.java b/backend/src/main/java/com/edufocus/edufocus/quiz/repository/QuizSetRepository.java new file mode 100644 index 0000000..c3282c7 --- /dev/null +++ b/backend/src/main/java/com/edufocus/edufocus/quiz/repository/QuizSetRepository.java @@ -0,0 +1,11 @@ +package com.edufocus.edufocus.quiz.repository; + +import com.edufocus.edufocus.quiz.entity.QuizSet; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +public interface QuizSetRepository extends JpaRepository { + + List findQuizSetsByUserId(long userId); +} diff --git a/backend/src/main/java/com/edufocus/edufocus/quiz/service/QuizService.java b/backend/src/main/java/com/edufocus/edufocus/quiz/service/QuizService.java new file mode 100644 index 0000000..adda12f --- /dev/null +++ b/backend/src/main/java/com/edufocus/edufocus/quiz/service/QuizService.java @@ -0,0 +1,23 @@ +package com.edufocus.edufocus.quiz.service; + +import com.edufocus.edufocus.quiz.entity.Quiz; +import com.edufocus.edufocus.quiz.entity.QuizCreateRequest; +import com.edufocus.edufocus.quiz.entity.QuizSet; +import com.edufocus.edufocus.quiz.entity.QuizUpdateRequest; +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; + +import java.io.IOException; +import java.util.List; + +@Service +public interface QuizService { + + void createQuiz(QuizSet quizSet, QuizCreateRequest quizCreateRequest, MultipartFile quizImage) throws IOException; + + void updateQuiz(QuizUpdateRequest quizUpdateRequest, MultipartFile quizImage) throws IOException; + + Quiz findQuiz(long quizId); + + void removeQuiz(long quizId); +} diff --git a/backend/src/main/java/com/edufocus/edufocus/quiz/service/QuizServiceImpl.java b/backend/src/main/java/com/edufocus/edufocus/quiz/service/QuizServiceImpl.java new file mode 100644 index 0000000..0225c8b --- /dev/null +++ b/backend/src/main/java/com/edufocus/edufocus/quiz/service/QuizServiceImpl.java @@ -0,0 +1,136 @@ +package com.edufocus.edufocus.quiz.service; + +import com.edufocus.edufocus.global.properties.ImagePathProperties; +import com.edufocus.edufocus.quiz.entity.*; +import com.edufocus.edufocus.quiz.repository.ChoiceRepository; +import com.edufocus.edufocus.quiz.repository.QuizRepository; +import jakarta.transaction.Transactional; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.UUID; + + +@Service +@Transactional +@RequiredArgsConstructor +public class QuizServiceImpl implements QuizService { + + private final ImagePathProperties imagePathProperties; + + private final QuizRepository quizRepository; + + private final ChoiceRepository choiceRepository; + + @Override + public void createQuiz(QuizSet quizSet, QuizCreateRequest quizCreateRequest, MultipartFile quizImage) throws IOException { + Quiz quiz = Quiz.builder() + .quizSet(quizSet) + .question(quizCreateRequest.getQuestion()) + .answer(quizCreateRequest.getAnswer()) + .build(); + + List choices = new ArrayList<>(); + + for (ChoiceCreateRequest choiceCreateRequest : quizCreateRequest.getChoices()) { + Choice choice = Choice.builder() + .quiz(quiz) + .num(choiceCreateRequest.getNum()) + .content(choiceCreateRequest.getContent()) + .build(); + choices.add(choice); + } + + quiz.setChoices(choices); + + if (quizImage != null && !quizImage.isEmpty()) { + String uid = UUID.randomUUID().toString(); + + String imagePath = imagePathProperties.getPath(); + + File checkPathFile = new File(imagePath); + if (!checkPathFile.exists()) { + checkPathFile.mkdirs(); + } + + File savingImage = new File(imagePath + "/" + uid + "_" + quizImage.getOriginalFilename()); + quizImage.transferTo(savingImage.toPath()); + String savePath = savingImage.toPath().toString(); + + quiz.setImage(savePath); + } + + quizRepository.save(quiz); + } + + + @Override + public void updateQuiz(QuizUpdateRequest quizUpdateRequest, MultipartFile quizImage) throws IOException { + Quiz quiz = quizRepository.findById(quizUpdateRequest.getId()).orElseThrow(NoSuchElementException::new); + + quiz.setQuestion(quizUpdateRequest.getQuestion()); + quiz.setAnswer(quizUpdateRequest.getAnswer()); + + quiz.getChoices().clear(); + quizRepository.save(quiz); + + for (ChoiceCreateRequest choiceCreateRequest : quizUpdateRequest.getChoices()) { + Choice choice = Choice.builder() + .quiz(quiz) + .num(choiceCreateRequest.getNum()) + .content(choiceCreateRequest.getContent()) + .build(); + + quiz.addChoice(choice); + } + + if (quizImage != null && !quizImage.isEmpty()) { + if (quiz.getImage() != null) { + File file = new File(quiz.getImage()); + file.delete(); + } + + String uid = UUID.randomUUID().toString(); + + String imagePath = imagePathProperties.getPath(); + + File checkPathFile = new File(imagePath); + if (!checkPathFile.exists()) { + checkPathFile.mkdirs(); + } + + File savingImage = new File(imagePath + "/" + uid + "_" + quizImage.getOriginalFilename()); + quizImage.transferTo(savingImage.toPath()); + String savePath = savingImage.toPath().toString(); + + quiz.setImage(savePath); + } + + quizRepository.save(quiz); + } + + @Override + public Quiz findQuiz(long quizId) { + return quizRepository.findById(quizId).orElseThrow(NoSuchElementException::new); + } + + @Override + public void removeQuiz(long quizId) { + Quiz quiz = quizRepository.findById(quizId).orElseThrow(NoSuchElementException::new); + + String image = quiz.getImage(); + if (image != null) { + File file = new File(quiz.getImage()); + file.delete(); + quiz.setImage(null); + } + + quizRepository.delete(quiz); + } +} diff --git a/backend/src/main/java/com/edufocus/edufocus/quiz/service/QuizSetService.java b/backend/src/main/java/com/edufocus/edufocus/quiz/service/QuizSetService.java new file mode 100644 index 0000000..8a610cc --- /dev/null +++ b/backend/src/main/java/com/edufocus/edufocus/quiz/service/QuizSetService.java @@ -0,0 +1,26 @@ +package com.edufocus.edufocus.quiz.service; + +import com.edufocus.edufocus.quiz.entity.MyQuizSetResponse; +import com.edufocus.edufocus.quiz.entity.QuizSet; +import com.edufocus.edufocus.quiz.entity.QuizSetResponse; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Service +public interface QuizSetService { + + QuizSet createQuizSet(long userId, String title); + + void updateQuizSet(long quizSetId, String title); + + boolean deleteQuizSet(long userId, long quizSetId); + + QuizSet findQuizSet(long quizSetId); + + QuizSetResponse findQuizSetResponse(long quizSetId); + + List findMyQuizSetResponses(long userId); + + void updateQuizSetTested(long quizSetId); +} diff --git a/backend/src/main/java/com/edufocus/edufocus/quiz/service/QuizSetServiceImpl.java b/backend/src/main/java/com/edufocus/edufocus/quiz/service/QuizSetServiceImpl.java new file mode 100644 index 0000000..07b68a3 --- /dev/null +++ b/backend/src/main/java/com/edufocus/edufocus/quiz/service/QuizSetServiceImpl.java @@ -0,0 +1,109 @@ +package com.edufocus.edufocus.quiz.service; + +import com.edufocus.edufocus.quiz.entity.*; +import com.edufocus.edufocus.quiz.repository.QuizSetRepository; +import com.edufocus.edufocus.user.model.entity.vo.User; +import com.edufocus.edufocus.user.model.exception.UnAuthorizedException; +import com.edufocus.edufocus.user.model.repository.UserRepository; +import jakarta.transaction.Transactional; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.List; +import java.util.NoSuchElementException; + +@Service +@Transactional +@RequiredArgsConstructor +public class QuizSetServiceImpl implements QuizSetService { + + private final QuizSetRepository quizSetRepository; + + private final UserRepository userRepository; + + @Override + public QuizSet createQuizSet(long userId, String title) { + User user = userRepository.findById(userId).orElseThrow(NoSuchElementException::new); + + QuizSet quizSet = QuizSet.builder() + .user(user) + .title(title) + .build(); + + return quizSetRepository.save(quizSet); + } + + @Override + public void updateQuizSet(long quizSetId, String title) { + QuizSet quizSet = quizSetRepository.findById(quizSetId).orElseThrow(NoSuchElementException::new); + + quizSet.setTitle(title); + quizSetRepository.save(quizSet); + } + + @Override + public boolean deleteQuizSet(long userId, long quizSetId) { + QuizSet quizSet = quizSetRepository.findById(quizSetId).orElseThrow(NoSuchElementException::new); + + if (userId != quizSet.getUser().getId()) { + return false; + } + + quizSetRepository.deleteById(quizSetId); + return true; + } + + @Override + public QuizSet findQuizSet(long quizSetId) { + return quizSetRepository.findById(quizSetId).orElseThrow(NoSuchElementException::new); + } + + @Override + public QuizSetResponse findQuizSetResponse(long quizSetId) { + QuizSet quizSet = quizSetRepository.findById(quizSetId).orElseThrow(NoSuchElementException::new); + + List quizResponses = new ArrayList<>(); + for (Quiz quiz : quizSet.getQuizzes()) { + QuizResponse quizResponse = QuizResponse.builder() + .question(quiz.getQuestion()) + .image(quiz.getImage()) + .choices(quiz.getChoices()) + .build(); + quizResponses.add(quizResponse); + } + + return QuizSetResponse.builder() + .title(quizSet.getTitle()) + .quizzes(quizResponses) + .build(); + + } + + @Override + public List findMyQuizSetResponses(long userId) { + List quizSetList = quizSetRepository.findQuizSetsByUserId(userId); + + List myQuizSetResponses = new ArrayList<>(); + for (QuizSet quizSet : quizSetList) { + MyQuizSetResponse myQuizSetResponse = MyQuizSetResponse.builder() + .quizSetId(quizSet.getId()) + .title(quizSet.getTitle()) + .build(); + + myQuizSetResponses.add(myQuizSetResponse); + } + + return myQuizSetResponses; + + } + + @Override + public void updateQuizSetTested(long quizSetId) { + QuizSet quizSet = quizSetRepository.findById(quizSetId).orElseThrow(NoSuchElementException::new); + + quizSet.setTested(true); + quizSetRepository.save(quizSet); + } + +} diff --git a/backend/src/main/java/com/edufocus/edufocus/redis/util/RedisUtil.java b/backend/src/main/java/com/edufocus/edufocus/redis/util/RedisUtil.java new file mode 100644 index 0000000..d13b40f --- /dev/null +++ b/backend/src/main/java/com/edufocus/edufocus/redis/util/RedisUtil.java @@ -0,0 +1,40 @@ +package com.edufocus.edufocus.redis.util; + +import lombok.RequiredArgsConstructor; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.data.redis.core.ValueOperations; +import org.springframework.stereotype.Component; + +import java.time.Duration; + +@Component +@RequiredArgsConstructor +public class RedisUtil { + + private final StringRedisTemplate stringRedisTemplate; + + public String getData(String key) { + ValueOperations valueOperations = stringRedisTemplate.opsForValue(); + return valueOperations.get(key); + } + + public void setData(String key, String value) { + ValueOperations valueOperations = stringRedisTemplate.opsForValue(); + valueOperations.set(key, value); + } + + public void setDataExpire(String key, String value, long duration) { + ValueOperations valueOperations = stringRedisTemplate.opsForValue(); + Duration expireDuration = Duration.ofSeconds(duration); + valueOperations.set(key, value, expireDuration); + } + + public void deleteData(String key) { + stringRedisTemplate.delete(key); + } + + public boolean exists(String key) { + return stringRedisTemplate.hasKey(key); + } + +} \ No newline at end of file diff --git a/backend/src/main/java/com/edufocus/edufocus/registration/controller/RegistrationController.java b/backend/src/main/java/com/edufocus/edufocus/registration/controller/RegistrationController.java new file mode 100644 index 0000000..2b019c9 --- /dev/null +++ b/backend/src/main/java/com/edufocus/edufocus/registration/controller/RegistrationController.java @@ -0,0 +1,71 @@ +package com.edufocus.edufocus.registration.controller; + +import com.edufocus.edufocus.lecture.service.LectureService; +import com.edufocus.edufocus.registration.service.RegistrationService; +import com.edufocus.edufocus.user.util.JWTUtil; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.Map; + +@RestController +@RequestMapping("/registration") +@Slf4j +@RequiredArgsConstructor +public class RegistrationController { + + private final RegistrationService registrationService; + + private final LectureService lectureService; + + private final JWTUtil jwtUtil; + + @PostMapping + public ResponseEntity register(@RequestHeader("Authorization") String accessToken, @RequestBody Map map) { + Long userId = Long.parseLong(jwtUtil.getUserId(accessToken)); + Long lectureId = map.get("lectureId"); + + if (!registrationService.createRegistration(userId, lectureId)) { + return new ResponseEntity<>(HttpStatus.NOT_FOUND); + } + + return new ResponseEntity<>(HttpStatus.CREATED); + } + + @PutMapping("/{registrationId}") + public ResponseEntity acceptRegistration(@RequestHeader("Authorization") String accessToken, @PathVariable long registrationId) { + Long userId = Long.parseLong(jwtUtil.getUserId(accessToken)); + + if (!registrationService.acceptRegistration(userId, registrationId)) { + return new ResponseEntity<>(HttpStatus.NOT_FOUND); + } + + return new ResponseEntity<>(HttpStatus.OK); + } + + @DeleteMapping("/{registrationId}") + public ResponseEntity deleteRegistration(@RequestHeader("Authorization") String accessToken, @PathVariable long registrationId) { + Long userId = Long.parseLong(jwtUtil.getUserId(accessToken)); + + if (!registrationService.deleteRegistration(userId, registrationId)) { + return new ResponseEntity<>(HttpStatus.NOT_FOUND); + } + + return new ResponseEntity<>(HttpStatus.NO_CONTENT); + } + + @GetMapping("/{lectureId}") + public ResponseEntity getRegistrations(@RequestHeader("Authorization") String accessToken, @PathVariable long lectureId) { + Long userId = Long.parseLong(jwtUtil.getUserId(accessToken)); + + if (!lectureService.checkTeacher(userId, lectureId)) { + return new ResponseEntity<>(HttpStatus.NOT_FOUND); + } + + return new ResponseEntity<>(registrationService.searchRegistrations(lectureId), HttpStatus.OK); + } + +} diff --git a/backend/src/main/java/com/edufocus/edufocus/registration/entity/Registration.java b/backend/src/main/java/com/edufocus/edufocus/registration/entity/Registration.java new file mode 100644 index 0000000..ee0d3e7 --- /dev/null +++ b/backend/src/main/java/com/edufocus/edufocus/registration/entity/Registration.java @@ -0,0 +1,54 @@ +package com.edufocus.edufocus.registration.entity; + +import com.edufocus.edufocus.lecture.entity.Lecture; +import com.edufocus.edufocus.quiz.entity.QuizSet; +import com.edufocus.edufocus.report.entity.vo.Report; +import com.edufocus.edufocus.report.entity.vo.ReportSet; +import com.edufocus.edufocus.user.model.entity.vo.User; +import jakarta.persistence.*; +import lombok.*; +import org.hibernate.annotations.OnDelete; +import org.hibernate.annotations.OnDeleteAction; + +import java.util.UUID; + +@Entity +@Getter +@Setter +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class Registration { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private long id; + + @ManyToOne + @JoinColumn(name = "user_id") + private User user; + + @ManyToOne + @OnDelete(action = OnDeleteAction.CASCADE) + @JoinColumn(name = "lecture_id") + private Lecture lecture; + + @Enumerated(EnumType.STRING) + private RegistrationStatus status; + + public boolean isAccepted() { + return status == RegistrationStatus.ACCEPTED; + } + + public Report makeReport(ReportSet reportSet, QuizSet quizSet, long lectureId) { + return Report.builder() + .allCount(0) + .correctCount(-1) + .reportSet(reportSet) + .quizSet(quizSet) + .lectureId(lectureId) + .user(user) + .build(); + + } +} diff --git a/backend/src/main/java/com/edufocus/edufocus/registration/entity/RegistrationSearchResponse.java b/backend/src/main/java/com/edufocus/edufocus/registration/entity/RegistrationSearchResponse.java new file mode 100644 index 0000000..e9c1b46 --- /dev/null +++ b/backend/src/main/java/com/edufocus/edufocus/registration/entity/RegistrationSearchResponse.java @@ -0,0 +1,18 @@ +package com.edufocus.edufocus.registration.entity; + +import com.edufocus.edufocus.lecture.entity.Lecture; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class RegistrationSearchResponse { + + private Long id; + + private String userName; +} diff --git a/backend/src/main/java/com/edufocus/edufocus/registration/entity/RegistrationStatus.java b/backend/src/main/java/com/edufocus/edufocus/registration/entity/RegistrationStatus.java new file mode 100644 index 0000000..5540eed --- /dev/null +++ b/backend/src/main/java/com/edufocus/edufocus/registration/entity/RegistrationStatus.java @@ -0,0 +1,5 @@ +package com.edufocus.edufocus.registration.entity; + +public enum RegistrationStatus { + WAITING, ACCEPTED +} diff --git a/backend/src/main/java/com/edufocus/edufocus/registration/repository/RegistrationRepository.java b/backend/src/main/java/com/edufocus/edufocus/registration/repository/RegistrationRepository.java new file mode 100644 index 0000000..1ea354b --- /dev/null +++ b/backend/src/main/java/com/edufocus/edufocus/registration/repository/RegistrationRepository.java @@ -0,0 +1,21 @@ +package com.edufocus.edufocus.registration.repository; + +import com.edufocus.edufocus.registration.entity.Registration; +import com.edufocus.edufocus.registration.entity.RegistrationStatus; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import org.springframework.stereotype.Repository; + +import java.util.List; + +@Repository +public interface RegistrationRepository extends JpaRepository { + List findAllByUserId(@Param("userId") Long userId); + + List findAllByLectureId(Long lectureId); + + Registration findByUserIdAndLectureId(Long userId, Long lectureId); + + List findByLectureIdAndStatus(Long lectureId, RegistrationStatus status); +} diff --git a/backend/src/main/java/com/edufocus/edufocus/registration/service/RegistrationService.java b/backend/src/main/java/com/edufocus/edufocus/registration/service/RegistrationService.java new file mode 100644 index 0000000..7fc0ece --- /dev/null +++ b/backend/src/main/java/com/edufocus/edufocus/registration/service/RegistrationService.java @@ -0,0 +1,22 @@ +package com.edufocus.edufocus.registration.service; + +import com.edufocus.edufocus.registration.entity.Registration; +import com.edufocus.edufocus.registration.entity.RegistrationSearchResponse; +import com.edufocus.edufocus.registration.entity.RegistrationStatus; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Service +public interface RegistrationService { + + boolean createRegistration(Long userId, Long registrationId); + + boolean acceptRegistration(Long userId, Long RegistrationId); + + boolean deleteRegistration(Long userId, Long registrationId); + + List[] searchRegistrations(Long LectureId); + + RegistrationStatus getStatus(Long userId, Long lectureId); +} diff --git a/backend/src/main/java/com/edufocus/edufocus/registration/service/RegistrationServiceImpl.java b/backend/src/main/java/com/edufocus/edufocus/registration/service/RegistrationServiceImpl.java new file mode 100644 index 0000000..78a539e --- /dev/null +++ b/backend/src/main/java/com/edufocus/edufocus/registration/service/RegistrationServiceImpl.java @@ -0,0 +1,107 @@ +package com.edufocus.edufocus.registration.service; + +import com.edufocus.edufocus.lecture.repository.LectureRepository; +import com.edufocus.edufocus.registration.entity.Registration; +import com.edufocus.edufocus.registration.entity.RegistrationSearchResponse; +import com.edufocus.edufocus.registration.entity.RegistrationStatus; +import com.edufocus.edufocus.registration.repository.RegistrationRepository; +import com.edufocus.edufocus.user.model.entity.vo.User; +import com.edufocus.edufocus.user.model.entity.vo.UserRole; +import com.edufocus.edufocus.user.model.repository.UserRepository; +import jakarta.transaction.Transactional; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +@Service +@Transactional +@RequiredArgsConstructor +public class RegistrationServiceImpl implements RegistrationService { + + private final RegistrationRepository registrationRepository; + private final UserRepository userRepository; + private final LectureRepository lectureRepository; + + @Override + public boolean createRegistration(Long userId, Long lectureId) { + Optional user = userRepository.findById(userId); + + if (user.isEmpty() || user.get().getRole().equals(UserRole.ADMIN)) { + return false; + } + + if (registrationRepository.findByUserIdAndLectureId(userId, lectureId) != null) { + return false; + } + + Registration registration = Registration.builder() + .user(userRepository.findById(userId).get()) + .lecture(lectureRepository.findById(lectureId).get()) + .status(RegistrationStatus.WAITING) + .build(); + + registrationRepository.save(registration); + return true; + } + + @Override + public boolean acceptRegistration(Long userId, Long registrationId) { + Optional registration = registrationRepository.findById(registrationId); + + if (registration.isEmpty() || registration.get().getLecture().getUser().getId() != userId) { + return false; + } + + registration.get().setStatus(RegistrationStatus.valueOf("ACCEPTED")); + registrationRepository.save(registration.get()); + + return true; + } + + @Override + public boolean deleteRegistration(Long userId, Long registrationId) { + Optional registration = registrationRepository.findById(registrationId); + + if (registration.isEmpty() || registration.get().getLecture().getUser().getId() != userId) { + return false; + } + + registrationRepository.deleteById(registrationId); + return true; + } + + @Override + public List[] searchRegistrations(Long lectureId) { + List registrations = registrationRepository.findAllByLectureId(lectureId); + + List[] responses = new ArrayList[2]; + responses[0] = new ArrayList<>(); + responses[1] = new ArrayList<>(); + for (Registration registration : registrations) { + RegistrationSearchResponse response = RegistrationSearchResponse.builder() + .id(registration.getId()) + .userName(registration.getUser().getName()) + .build(); + + if (registration.getStatus() == RegistrationStatus.ACCEPTED) { + responses[0].add(response); + } else { + responses[1].add(response); + } + } + + + return responses; + } + + @Override + public RegistrationStatus getStatus(Long userId, Long lectureId) { + + Registration registration = registrationRepository.findByUserIdAndLectureId(userId, lectureId); + return registration.getStatus(); + } + +} diff --git a/backend/src/main/java/com/edufocus/edufocus/report/controller/ReportController.java b/backend/src/main/java/com/edufocus/edufocus/report/controller/ReportController.java new file mode 100644 index 0000000..afddfe3 --- /dev/null +++ b/backend/src/main/java/com/edufocus/edufocus/report/controller/ReportController.java @@ -0,0 +1,96 @@ +package com.edufocus.edufocus.report.controller; + +import com.edufocus.edufocus.report.entity.dto.*; +import com.edufocus.edufocus.report.service.ReportService; +import com.edufocus.edufocus.user.model.service.UserService; +import com.edufocus.edufocus.user.util.JWTUtil; +import jakarta.servlet.http.HttpServletRequest; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.sql.SQLException; +import java.util.List; +import java.util.UUID; + +@RestController +@RequestMapping("/report") +@Slf4j +@RequiredArgsConstructor +public class ReportController { + + + private final ReportService reportService; + private final JWTUtil jwtUtil; + private final UserService userService; + + @PostMapping("/submit/quizSet/{reportSetId}") + public ResponseEntity submit(@PathVariable("reportSetId") UUID reportSetId, @RequestBody ReportRequest reportRequest, HttpServletRequest request) { + + String token = request.getHeader("Authorization"); + long userId = Long.parseLong(jwtUtil.getUserId(token)); + + if (userService.isTeacher(userId)) + return new ResponseEntity<>("강사는 퀴즈제출을 할수 없습니다", HttpStatus.FORBIDDEN); + + reportService.grade(userId, reportSetId, reportRequest); + + return new ResponseEntity<>(HttpStatus.OK); + + } + + + @GetMapping("/student/{lectureId}") + public ResponseEntity> searchMyReport(@PathVariable long lectureId, HttpServletRequest request) { + String token = request.getHeader("Authorization"); + long userId = Long.parseLong(jwtUtil.getUserId(token)); + + List reportResponses = reportService.findReports(lectureId, userId); + + if (reportResponses.isEmpty()) + return new ResponseEntity<>(HttpStatus.NO_CONTENT); + + return new ResponseEntity<>(reportResponses, HttpStatus.OK); + } + + + @GetMapping("/reportDetail/{reportId}") + public ResponseEntity searchDetailReport(@PathVariable("reportId") long reportId) { + ReportDetailResponseDto detailReport = reportService.reportDetail(reportId); + + return new ResponseEntity<>(detailReport, HttpStatus.OK); + } + + + @GetMapping("/teacher/reportSet/{lectureId}") + public ResponseEntity> searchReportSets(@PathVariable("lectureId") long lectureId) { + List reportSetResponses = reportService.findReportSets(lectureId); + + if (reportSetResponses.isEmpty()) + return new ResponseEntity<>(HttpStatus.NO_CONTENT); + + return new ResponseEntity<>(reportSetResponses, HttpStatus.OK); + } + + @GetMapping("/teacher/report/{reportSetId}") + public ResponseEntity searchReports(@PathVariable("reportSetId") UUID reportSetId) { + List reportResponses = reportService.findReports(reportSetId); + + if (reportResponses.isEmpty()) + return new ResponseEntity<>(HttpStatus.NO_CONTENT); + + return new ResponseEntity<>(reportResponses, HttpStatus.OK); + } + + @DeleteMapping("/teacher/report/{reportSetId}") + public ResponseEntity deleteReportSet(@PathVariable("reportSetId") UUID reportSetId, HttpServletRequest request) { + String token = request.getHeader("Authorization"); + long userId = Long.parseLong(jwtUtil.getUserId(token)); + + reportService.deleteReportSet(reportSetId, userId); + + return new ResponseEntity<>(HttpStatus.OK); + } +} diff --git a/backend/src/main/java/com/edufocus/edufocus/report/entity/dto/ChoiceDto.java b/backend/src/main/java/com/edufocus/edufocus/report/entity/dto/ChoiceDto.java new file mode 100644 index 0000000..c28bbc2 --- /dev/null +++ b/backend/src/main/java/com/edufocus/edufocus/report/entity/dto/ChoiceDto.java @@ -0,0 +1,12 @@ +package com.edufocus.edufocus.report.entity.dto; + +import lombok.Builder; +import lombok.Data; +import lombok.Setter; + +@Data +@Builder +public class ChoiceDto { + int num; + String content; +} diff --git a/backend/src/main/java/com/edufocus/edufocus/report/entity/dto/QuizDto.java b/backend/src/main/java/com/edufocus/edufocus/report/entity/dto/QuizDto.java new file mode 100644 index 0000000..5dbc56e --- /dev/null +++ b/backend/src/main/java/com/edufocus/edufocus/report/entity/dto/QuizDto.java @@ -0,0 +1,23 @@ +package com.edufocus.edufocus.report.entity.dto; + +import com.edufocus.edufocus.quiz.entity.Choice; +import com.edufocus.edufocus.quiz.entity.QuizSet; +import com.fasterxml.jackson.annotation.JsonBackReference; +import com.fasterxml.jackson.annotation.JsonManagedReference; +import jakarta.persistence.*; +import lombok.Builder; +import lombok.Data; + +import java.util.List; + +@Data +@Builder +public class QuizDto { + private long id; + private String question; + private String image; + private String answer; + private String userAnswer; + private boolean isCorrect; + private List choices; +} diff --git a/backend/src/main/java/com/edufocus/edufocus/report/entity/dto/ReportDetailResponseDto.java b/backend/src/main/java/com/edufocus/edufocus/report/entity/dto/ReportDetailResponseDto.java new file mode 100644 index 0000000..6cd671f --- /dev/null +++ b/backend/src/main/java/com/edufocus/edufocus/report/entity/dto/ReportDetailResponseDto.java @@ -0,0 +1,19 @@ +package com.edufocus.edufocus.report.entity.dto; + +import com.edufocus.edufocus.quiz.entity.Quiz; +import lombok.Builder; +import lombok.Data; + +import java.time.LocalDateTime; +import java.util.Date; +import java.util.List; + +@Data +@Builder +public class ReportDetailResponseDto { + private int allCount; + private String title; + private int correctCount; + private Date testAt; + private List quizzes; +} \ No newline at end of file diff --git a/backend/src/main/java/com/edufocus/edufocus/report/entity/dto/ReportRequest.java b/backend/src/main/java/com/edufocus/edufocus/report/entity/dto/ReportRequest.java new file mode 100644 index 0000000..4f2c0bf --- /dev/null +++ b/backend/src/main/java/com/edufocus/edufocus/report/entity/dto/ReportRequest.java @@ -0,0 +1,23 @@ +package com.edufocus.edufocus.report.entity.dto; + +import lombok.Getter; +import lombok.Setter; + +import java.util.List; + +@Getter +@Setter +public class ReportRequest { + + + List answer; + +// [1,2,,3] +// List a +// userID : +// quizSetId : +// answerList : [ +// { ans1 : 1}, +// {ans2 : 2} +// ] +} diff --git a/backend/src/main/java/com/edufocus/edufocus/report/entity/dto/ReportResponse.java b/backend/src/main/java/com/edufocus/edufocus/report/entity/dto/ReportResponse.java new file mode 100644 index 0000000..9248eef --- /dev/null +++ b/backend/src/main/java/com/edufocus/edufocus/report/entity/dto/ReportResponse.java @@ -0,0 +1,20 @@ +package com.edufocus.edufocus.report.entity.dto; + +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; + +import java.time.LocalDateTime; +import java.util.Date; + +@Getter +@Setter +@Builder +public class ReportResponse { + private long reportId; + private String name; + private String title; + private int allCount; + private int correctCount; + private Date date; +} diff --git a/backend/src/main/java/com/edufocus/edufocus/report/entity/dto/ReportSetResponse.java b/backend/src/main/java/com/edufocus/edufocus/report/entity/dto/ReportSetResponse.java new file mode 100644 index 0000000..119a662 --- /dev/null +++ b/backend/src/main/java/com/edufocus/edufocus/report/entity/dto/ReportSetResponse.java @@ -0,0 +1,19 @@ +package com.edufocus.edufocus.report.entity.dto; + + +import lombok.*; + +import java.time.LocalDateTime; +import java.util.Date; +import java.util.UUID; + +@Builder +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +public class ReportSetResponse { + private UUID reportSetId; + private String quizSetTitle; + private Date testAt; +} diff --git a/backend/src/main/java/com/edufocus/edufocus/report/entity/vo/Answer.java b/backend/src/main/java/com/edufocus/edufocus/report/entity/vo/Answer.java new file mode 100644 index 0000000..969119a --- /dev/null +++ b/backend/src/main/java/com/edufocus/edufocus/report/entity/vo/Answer.java @@ -0,0 +1,39 @@ +package com.edufocus.edufocus.report.entity.vo; + + +import com.edufocus.edufocus.quiz.entity.Quiz; +import jakarta.persistence.*; +import lombok.*; +import org.hibernate.annotations.OnDelete; +import org.hibernate.annotations.OnDeleteAction; + +@Entity +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class Answer { + + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private long id; + + @Column + private String userAnswer; + + @Column + private boolean isCorrect; + + @ManyToOne + @JoinColumn(name = "report_id") + @OnDelete(action = OnDeleteAction.CASCADE) + private Report report; + + @ManyToOne + @JoinColumn(name = "quiz_id") + @OnDelete(action = OnDeleteAction.CASCADE) + private Quiz quiz; + +} diff --git a/backend/src/main/java/com/edufocus/edufocus/report/entity/vo/Report.java b/backend/src/main/java/com/edufocus/edufocus/report/entity/vo/Report.java new file mode 100644 index 0000000..9bb0440 --- /dev/null +++ b/backend/src/main/java/com/edufocus/edufocus/report/entity/vo/Report.java @@ -0,0 +1,70 @@ +package com.edufocus.edufocus.report.entity.vo; + +import com.edufocus.edufocus.quiz.entity.QuizSet; +import com.edufocus.edufocus.report.entity.dto.ReportResponse; +import com.edufocus.edufocus.user.model.entity.vo.User; +import com.fasterxml.jackson.annotation.JsonAutoDetect; +import jakarta.persistence.*; +import lombok.*; +import org.hibernate.annotations.CreationTimestamp; +import org.hibernate.annotations.OnDelete; +import org.hibernate.annotations.OnDeleteAction; +import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; + +import java.time.LocalDateTime; +import java.util.Date; +import java.util.List; + +@Entity +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +@Builder +@EntityListeners(AuditingEntityListener.class) +@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY) +public class Report { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + private int allCount; + + private int correctCount; + + @Column + @CreationTimestamp + @Temporal(TemporalType.TIMESTAMP) + private Date testAt; + + private Long lectureId; + + @ManyToOne + @JoinColumn(name = "user_id") + private User user; + + @ManyToOne + @JoinColumn(name = "quizset_id") + @OnDelete(action = OnDeleteAction.CASCADE) + private QuizSet quizSet; + + @ManyToOne + @JoinColumn(name = "reportset_id") + @OnDelete(action = OnDeleteAction.CASCADE) + private ReportSet reportSet; + + @OneToMany(mappedBy = "report") + private List answers; + + public ReportResponse makeReportResponse() { + return ReportResponse.builder() + .reportId(id) + .name(user.getName()) + .title(quizSet.getTitle()) + .allCount(allCount) + .correctCount(correctCount) + .date(testAt) + .build(); + } +} diff --git a/backend/src/main/java/com/edufocus/edufocus/report/entity/vo/ReportSet.java b/backend/src/main/java/com/edufocus/edufocus/report/entity/vo/ReportSet.java new file mode 100644 index 0000000..f5beca6 --- /dev/null +++ b/backend/src/main/java/com/edufocus/edufocus/report/entity/vo/ReportSet.java @@ -0,0 +1,66 @@ +package com.edufocus.edufocus.report.entity.vo; + +import com.edufocus.edufocus.lecture.entity.Lecture; +import com.edufocus.edufocus.quiz.entity.QuizSet; +import com.edufocus.edufocus.report.entity.dto.ReportSetResponse; +import com.fasterxml.jackson.annotation.JsonAutoDetect; +import jakarta.persistence.*; +import jakarta.persistence.CascadeType; +import lombok.*; +import org.hibernate.annotations.*; +import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; + + +import java.time.LocalDateTime; +import java.util.Date; +import java.util.List; +import java.util.UUID; + + +@Entity +@Getter +@Builder +@NoArgsConstructor +@AllArgsConstructor +@EntityListeners(AuditingEntityListener.class) +@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY) +public class ReportSet { + @Id + @GeneratedValue(generator = "UUID") + @GenericGenerator( + name = "UUID", + strategy = "org.hibernate.id.UUIDGenerator" + ) + private UUID id; + + @Column + @CreationTimestamp + @Temporal(TemporalType.TIMESTAMP) + private Date createAt; + + @OneToMany(mappedBy = "reportSet") + private List reports; + + @ManyToOne + @JoinColumn(name = "lecture_id") + @OnDelete(action = OnDeleteAction.CASCADE) + private Lecture lecture; + + @ManyToOne + @JoinColumn(name = "quizSet_id") + @OnDelete(action = OnDeleteAction.CASCADE) + private QuizSet quizSet; + + public ReportSetResponse makeReportSetResponse() { + return ReportSetResponse.builder() + .reportSetId(id) + .quizSetTitle(quizSet.getTitle()) + .testAt(createAt) + .build(); + } + + public long findUserId() { + return lecture.getUser().getId(); + } +} diff --git a/backend/src/main/java/com/edufocus/edufocus/report/repository/AnswerRepository.java b/backend/src/main/java/com/edufocus/edufocus/report/repository/AnswerRepository.java new file mode 100644 index 0000000..0998a0a --- /dev/null +++ b/backend/src/main/java/com/edufocus/edufocus/report/repository/AnswerRepository.java @@ -0,0 +1,11 @@ +package com.edufocus.edufocus.report.repository; + +import com.edufocus.edufocus.report.entity.vo.Answer; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +public interface AnswerRepository extends JpaRepository { + List findByReportId(Long reportId); + +} diff --git a/backend/src/main/java/com/edufocus/edufocus/report/repository/ReportRepository.java b/backend/src/main/java/com/edufocus/edufocus/report/repository/ReportRepository.java new file mode 100644 index 0000000..2ada83f --- /dev/null +++ b/backend/src/main/java/com/edufocus/edufocus/report/repository/ReportRepository.java @@ -0,0 +1,17 @@ +package com.edufocus.edufocus.report.repository; + +import com.edufocus.edufocus.report.entity.vo.Report; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.List; +import java.util.UUID; + +@Repository +public interface ReportRepository extends JpaRepository { + Report findByReportSetIdAndUserId(UUID reportSetId, long userId); + + List findByReportSetIdOrderById(UUID reportSetId); + + List findByLectureIdAndUserIdOrderById(long lectureId, long userId); +} diff --git a/backend/src/main/java/com/edufocus/edufocus/report/repository/ReportSetRepository.java b/backend/src/main/java/com/edufocus/edufocus/report/repository/ReportSetRepository.java new file mode 100644 index 0000000..fcf2449 --- /dev/null +++ b/backend/src/main/java/com/edufocus/edufocus/report/repository/ReportSetRepository.java @@ -0,0 +1,13 @@ +package com.edufocus.edufocus.report.repository; + +import com.edufocus.edufocus.report.entity.vo.ReportSet; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.List; +import java.util.UUID; + +@Repository +public interface ReportSetRepository extends JpaRepository { + List findByLectureIdOrderById(long userId); +} diff --git a/backend/src/main/java/com/edufocus/edufocus/report/service/AnswerService.java b/backend/src/main/java/com/edufocus/edufocus/report/service/AnswerService.java new file mode 100644 index 0000000..de98121 --- /dev/null +++ b/backend/src/main/java/com/edufocus/edufocus/report/service/AnswerService.java @@ -0,0 +1,8 @@ +package com.edufocus.edufocus.report.service; + +import com.edufocus.edufocus.report.entity.vo.Answer; + +public interface AnswerService { + + void save(Answer answer); +} diff --git a/backend/src/main/java/com/edufocus/edufocus/report/service/AnswerServiceImpl.java b/backend/src/main/java/com/edufocus/edufocus/report/service/AnswerServiceImpl.java new file mode 100644 index 0000000..e19f53e --- /dev/null +++ b/backend/src/main/java/com/edufocus/edufocus/report/service/AnswerServiceImpl.java @@ -0,0 +1,22 @@ +package com.edufocus.edufocus.report.service; + +import com.edufocus.edufocus.report.entity.vo.Answer; +import com.edufocus.edufocus.report.repository.AnswerRepository; +import jakarta.transaction.Transactional; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +@Service +@Transactional +@RequiredArgsConstructor +public class AnswerServiceImpl implements AnswerService{ + + private final AnswerRepository answerRepository; + + @Override + public void save(Answer answer) { + + answerRepository.save(answer); + + } +} diff --git a/backend/src/main/java/com/edufocus/edufocus/report/service/ReportService.java b/backend/src/main/java/com/edufocus/edufocus/report/service/ReportService.java new file mode 100644 index 0000000..5c51ab5 --- /dev/null +++ b/backend/src/main/java/com/edufocus/edufocus/report/service/ReportService.java @@ -0,0 +1,25 @@ +package com.edufocus.edufocus.report.service; + +import com.edufocus.edufocus.report.entity.dto.*; +import org.springframework.stereotype.Service; + +import java.sql.SQLException; +import java.util.List; +import java.util.UUID; + +@Service +public interface ReportService { + void grade(long userId, UUID reportSetId, ReportRequest reportRequest); + + ReportDetailResponseDto reportDetail(long userId); + + List findReportSets(long lectureId); + + List findReports(UUID reportSetId); + + List findReports(long lectureId, long userid); + + UUID initReportSet(long lectureId, long quizSetId); + + void deleteReportSet(UUID reportSetId, long userId); +} diff --git a/backend/src/main/java/com/edufocus/edufocus/report/service/ReportServiceImpl.java b/backend/src/main/java/com/edufocus/edufocus/report/service/ReportServiceImpl.java new file mode 100644 index 0000000..6f27dd6 --- /dev/null +++ b/backend/src/main/java/com/edufocus/edufocus/report/service/ReportServiceImpl.java @@ -0,0 +1,182 @@ +package com.edufocus.edufocus.report.service; + +import com.edufocus.edufocus.lecture.repository.LectureRepository; +import com.edufocus.edufocus.quiz.entity.Choice; +import com.edufocus.edufocus.quiz.entity.Quiz; +import com.edufocus.edufocus.quiz.entity.QuizSet; +import com.edufocus.edufocus.quiz.repository.QuizRepository; +import com.edufocus.edufocus.quiz.repository.QuizSetRepository; +import com.edufocus.edufocus.registration.entity.Registration; +import com.edufocus.edufocus.registration.entity.RegistrationStatus; +import com.edufocus.edufocus.registration.repository.RegistrationRepository; +import com.edufocus.edufocus.report.entity.dto.*; +import com.edufocus.edufocus.report.entity.vo.Answer; +import com.edufocus.edufocus.report.entity.vo.Report; +import com.edufocus.edufocus.report.entity.vo.ReportSet; +import com.edufocus.edufocus.report.repository.AnswerRepository; +import com.edufocus.edufocus.report.repository.ReportRepository; +import com.edufocus.edufocus.report.repository.ReportSetRepository; +import jakarta.transaction.Transactional; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.*; + +@Service +@Transactional +@RequiredArgsConstructor +public class ReportServiceImpl implements ReportService { + + private final ReportRepository reportRepository; + private final QuizRepository quizRepository; + private final RegistrationRepository registrationRepository; + private final AnswerRepository answerRepository; + private final ReportSetRepository reportSetRepository; + private final LectureRepository lectureRepository; + private final QuizSetRepository quizSetRepository; + + + @Override + public void grade(long userId, UUID reportSetId, ReportRequest reportRequest){ + + ReportSet reportSet = reportSetRepository.findById(reportSetId).orElseThrow(NoSuchElementException::new); + + Report report = reportRepository.findByReportSetIdAndUserId(reportSetId, userId); + + List quizList = report.getQuizSet().getQuizzes(); + + List answerInputList = reportRequest.getAnswer(); + + List answerList = new ArrayList<>(); + + int correctCount = 0; + + for (int idx = 0; idx < answerInputList.size(); idx++) { + Quiz quiz = quizList.get(idx); + String inputAnswer = answerInputList.get(idx); + boolean isCorrect; + Answer answer; + if (quiz.getAnswer().equalsIgnoreCase(inputAnswer)) { + correctCount++; + isCorrect = true; + } else { + isCorrect = false; + } + answer = Answer.builder() + .userAnswer(inputAnswer) + .isCorrect(isCorrect) + .report(report) + .quiz(quiz) + .build(); + answerList.add(answer); + } + + + report.setAllCount(quizList.size()); + report.setCorrectCount(correctCount); + + reportRepository.save(report); + + answerRepository.saveAll(answerList); + } + + @Override + public ReportDetailResponseDto reportDetail(long reportId) { + + Report report = reportRepository.findById(reportId).orElseThrow(NoSuchElementException::new); + + QuizSet quizSet = quizSetRepository.findById(report.getQuizSet().getId()).orElseThrow(NoSuchElementException::new); + + List quizDtos = new ArrayList<>(); + + List myAnswer = answerRepository.findByReportId(report.getId()); + + for (Answer answer : myAnswer) { + Quiz quiz = quizRepository.findById(answer.getQuiz().getId()).orElse(null); + + List choiceDtos = quiz.getChoices() + .stream() + .map(Choice::makeChoiceDto) + .toList(); + + QuizDto quizDto = QuizDto.builder() + .id(quiz.getId()) + .question(quiz.getQuestion()) + .image(quiz.getImage()) + .question(quiz.getQuestion()) + .answer(quiz.getAnswer()) + .userAnswer(answer.getUserAnswer()) + .isCorrect(answer.isCorrect()) + .choices(choiceDtos) + .build(); + + quizDtos.add(quizDto); + } + + + return ReportDetailResponseDto.builder() + .title(quizSet.getTitle()) + .testAt(report.getTestAt()) + .allCount(report.getAllCount()) + .correctCount(report.getCorrectCount()) + .quizzes(quizDtos) + .build(); + } + + @Override + public List findReportSets(long lectureId) { + List reportSets = reportSetRepository.findByLectureIdOrderById(lectureId); + + return reportSets.stream() + .map(ReportSet::makeReportSetResponse) + .toList(); + } + + @Override + public List findReports(UUID reportSetId) { + List reports = reportRepository.findByReportSetIdOrderById(reportSetId); + + return reports.stream() + .map(Report::makeReportResponse) + .toList(); + } + + @Override + public List findReports(long lectureId, long userId) { + List reports = reportRepository.findByLectureIdAndUserIdOrderById(lectureId, userId); + + return reports.stream() + .map(Report::makeReportResponse) + .toList(); + } + + @Override + public UUID initReportSet(long lectureId, long quizSetId) { + ReportSet reportSet = ReportSet.builder() + .lecture(lectureRepository.getReferenceById(lectureId)) + .quizSet(quizSetRepository.getReferenceById(quizSetId)) + .build(); + + reportSetRepository.save(reportSet); + + QuizSet quizSet = quizSetRepository.getReferenceById(quizSetId); + + List registrations = registrationRepository.findByLectureIdAndStatus(lectureId, RegistrationStatus.ACCEPTED); + + List reports = registrations.stream() + .map((Registration registration)-> registration.makeReport(reportSet, quizSet, lectureId)) + .toList(); + + reportRepository.saveAll(reports); + + return reportSet.getId(); + + } + + @Override + public void deleteReportSet(UUID reportSetId, long userId) { + ReportSet reportSet = reportSetRepository.findById(reportSetId).orElseThrow(NoSuchElementException::new); + if(reportSet.findUserId() == userId) + reportSetRepository.delete(reportSet); + } +} diff --git a/backend/src/main/java/com/edufocus/edufocus/swagger/SwaggerConfig.java b/backend/src/main/java/com/edufocus/edufocus/swagger/SwaggerConfig.java new file mode 100644 index 0000000..970f7ba --- /dev/null +++ b/backend/src/main/java/com/edufocus/edufocus/swagger/SwaggerConfig.java @@ -0,0 +1,21 @@ +package com.edufocus.edufocus.swagger; + +import io.swagger.v3.oas.models.Components; +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.info.Info; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class SwaggerConfig { + + @Bean + public OpenAPI openAPI() { + return new OpenAPI() + .info(new Info() + .title("에듀포커스 API") + .description("") + + .version("1.0.0")); + } + } \ No newline at end of file diff --git a/backend/src/main/java/com/edufocus/edufocus/user/config/WebConfiguration.java b/backend/src/main/java/com/edufocus/edufocus/user/config/WebConfiguration.java new file mode 100644 index 0000000..5899062 --- /dev/null +++ b/backend/src/main/java/com/edufocus/edufocus/user/config/WebConfiguration.java @@ -0,0 +1,52 @@ +package com.edufocus.edufocus.user.config; + +import com.edufocus.edufocus.user.intercepter.JWTInterceptor; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpMethod; +import org.springframework.web.servlet.config.annotation.CorsRegistry; +import org.springframework.web.servlet.config.annotation.EnableWebMvc; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + + +@Configuration +@EnableWebMvc +public class WebConfiguration implements WebMvcConfigurer { + + private JWTInterceptor jwtInterceptor; + + public WebConfiguration(JWTInterceptor jwtInterceptor) { + super(); + + this.jwtInterceptor = jwtInterceptor; + } + + // + @Override + public void addCorsMappings(CorsRegistry registry) { + registry + .addMapping("/**") + .allowedOriginPatterns("https://i11a701.p.ssafy.io/", "https://localhost:5173", "http://localhost:5173", "http://localhost:4173", "http://localhost:5080", "http://192.168.*.*:5173") + .allowedMethods("GET", "POST", HttpMethod.GET.name(), HttpMethod.POST.name(), HttpMethod.PUT.name(), + HttpMethod.DELETE.name(), HttpMethod.HEAD.name(), HttpMethod.OPTIONS.name(), + HttpMethod.PATCH.name()) + .allowCredentials(true) + .allowedHeaders("*") + .maxAge(1800); // Pre-flight Caching + } + + + @Override + public void addResourceHandlers(ResourceHandlerRegistry registry) { + registry.addResourceHandler("/img/**").addResourceLocations("classpath:/static/assets/img/"); + registry.addResourceHandler("/*.html**").addResourceLocations("classpath:/static/"); + } + + @Override + public void addInterceptors(InterceptorRegistry registry) { + registry.addInterceptor(jwtInterceptor) + .addPathPatterns("/**") // 모든 경로에 대해 인터셉터 적용 + .excludePathPatterns("/v3/api-docs/**", "/swagger-resources/**", "/webjars/**", "/swagger-ui/**", "/auth/**", "/board/**", "/user/**", "/lecture/**", "/qna/**", "/quiz/**", "/video/**", "/registration/**", "/report/**", "/mail/**"); // 인증 없이 접근 가능한 경로 설정 + } +} \ No newline at end of file diff --git a/backend/src/main/java/com/edufocus/edufocus/user/controller/UserController.java b/backend/src/main/java/com/edufocus/edufocus/user/controller/UserController.java new file mode 100644 index 0000000..7fe36d3 --- /dev/null +++ b/backend/src/main/java/com/edufocus/edufocus/user/controller/UserController.java @@ -0,0 +1,203 @@ +package com.edufocus.edufocus.user.controller; + +import com.edufocus.edufocus.user.model.entity.dto.InfoDto; +import com.edufocus.edufocus.user.model.entity.dto.PasswordDto; +import com.edufocus.edufocus.user.model.entity.dto.RequestJoinDto; +import com.edufocus.edufocus.user.model.entity.vo.User; +import com.edufocus.edufocus.user.model.exception.InvalidTokenException; +import com.edufocus.edufocus.user.model.service.UserService; +import com.edufocus.edufocus.user.util.JWTUtil; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import jakarta.servlet.http.Cookie; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.HashMap; +import java.util.Map; + +@RestController +@RequestMapping("/user") +@Slf4j +@RequiredArgsConstructor +@CrossOrigin(origins = "http://localhost:5173", allowedHeaders = "*", allowCredentials = "true") +public class UserController { + + private final UserService userService; + private final JWTUtil jwtUtil; + + @PostMapping("/join") + public ResponseEntity join(@RequestBody RequestJoinDto requestJoinDto) { + + if (userService.isUserIdExist(requestJoinDto.getUserId())) + return new ResponseEntity<>("아이디가 중복 됐습니다.", HttpStatus.CONFLICT); + + if (userService.isEmailExist(requestJoinDto.getEmail())) + return new ResponseEntity<>("이메일이 중복 됐습니다.", HttpStatus.CONFLICT); + + userService.join(requestJoinDto); + + return ResponseEntity.ok("User registered successfully"); + } + + @PutMapping("/updateinfo") + public ResponseEntity updateUserInfo(@RequestBody InfoDto infoDto, HttpServletRequest request) { + + try { + String token = request.getHeader("Authorization"); + Long userId = Long.parseLong(jwtUtil.getUserId(token)); + + userService.changeUserInfo(infoDto, userId); + return ResponseEntity.ok("Password changed successfully"); + } catch (Exception e) { + return ResponseEntity.status(HttpStatus.NOT_ACCEPTABLE).body(e.getMessage()); + } + + } + + // 비밀번호 변경 + @PutMapping("/updatepassword") + public ResponseEntity updatePassword(@RequestBody PasswordDto passwordDto, HttpServletRequest request) { + try { + String token = request.getHeader("Authorization"); + Long userId = Long.parseLong(jwtUtil.getUserId(token)); + + userService.changePassword(passwordDto, userId); + return ResponseEntity.ok("Password changed successfully"); + } catch (Exception e) { + return ResponseEntity.status(HttpStatus.NOT_ACCEPTABLE).body(e.getMessage()); + } + } + + // 비밀번호 찾기를 통한 변경 + @PutMapping("/updateforgottenpassword") + public ResponseEntity updatePassword(@RequestParam String email, + @RequestParam String newPassword) { + userService.changeForgottenPassword(email, newPassword); + return new ResponseEntity<>(HttpStatus.OK); + } + + @Operation(summary = "로그인", description = "아이디와 비밀번호를 이용하여 로그인 처리.") + @PostMapping("/login") + public ResponseEntity> login( + @RequestBody @Parameter(description = "로그인 시 필요한 회원정보(아이디, 비밀번호).", required = true) User user, HttpServletRequest request, HttpServletResponse response) { + + Map resultMap = new HashMap<>(); + HttpStatus status = HttpStatus.ACCEPTED; + + User loginUser = userService.login(user); + if (loginUser == null) + return new ResponseEntity<>(HttpStatus.UNAUTHORIZED); + + String accessToken = jwtUtil.createAccessToken(String.valueOf(loginUser.getId())); + String refreshToken = jwtUtil.createRefreshToken(String.valueOf(loginUser.getId())); + + userService.saveRefreshToken(loginUser.getId(), refreshToken); + + resultMap.put("name", loginUser.getName()); + resultMap.put("role", loginUser.getRole()); + resultMap.put("access-token", accessToken); + + setCookies(response, refreshToken); + + status = HttpStatus.CREATED; + + + return new ResponseEntity<>(resultMap, status); + } + + + @PostMapping("/logout") + public ResponseEntity removeToken(HttpServletRequest request) { + String token = request.getHeader("Authorization"); + Long userId = Long.parseLong(jwtUtil.getUserId(token)); + + userService.deleteRefreshToken(userId); + + return new ResponseEntity>(HttpStatus.ACCEPTED); + } + + @Operation(summary = "Access Token 재발급", description = "만료된 access token 을 재발급 받는다.") + @PostMapping("/refresh") + public ResponseEntity refreshToken(HttpServletRequest request, HttpServletResponse response) { + Cookie[] cookies = request.getCookies(); + String token = null; + if (cookies != null) { + for (Cookie cookie : cookies) { + if (cookie.getName().equals("refresh-token")) { + token = cookie.getValue(); + break; + } + } + } + + try { + jwtUtil.checkToken(token); + } catch (Exception e) { + throw new InvalidTokenException(); + } + + Long userId = Long.parseLong(jwtUtil.getUserId(token)); + + if (!token.equals(userService.getRefreshToken(userId))) { + throw new InvalidTokenException(); + } + + + String accessToken = jwtUtil.createAccessToken(String.valueOf(userId)); + String refreshToken = jwtUtil.createRefreshToken(String.valueOf(userId)); + + + Map resultMap = new HashMap<>(); + resultMap.put("access-token", accessToken); + + userService.saveRefreshToken(userId, refreshToken); + + setCookies(response, refreshToken); + + return new ResponseEntity>(resultMap, HttpStatus.CREATED); + } + + @Operation(summary = "회원 정보 조회", description = "토큰을 이용하여 회원 정보를 조회한다.") + @GetMapping("/userinfo") + public ResponseEntity> getMember(HttpServletRequest request) { + Map resultMap = new HashMap<>(); + HttpStatus status = HttpStatus.ACCEPTED; + String token = request.getHeader("Authorization"); + + if (jwtUtil.checkToken(token)) { + String userId = jwtUtil.getUserId(token); + log.info("사용 가능한 토큰!!! userId: {}", userId); + try { + User user = userService.userInfo(Long.parseLong(userId)); + resultMap.put("userInfo", user); + status = HttpStatus.OK; + + } catch (Exception e) { + log.error("정보조회 실패 : {}", e); + resultMap.put("message", e.getMessage()); + status = HttpStatus.INTERNAL_SERVER_ERROR; + } + } else { + log.error("사용 불가능 토큰!!!"); + status = HttpStatus.UNAUTHORIZED; + } + return new ResponseEntity>(resultMap, status); + } + + + private void setCookies(HttpServletResponse response, String refreshToken) { + Cookie refreshCookie = new Cookie("refresh-token", refreshToken); + refreshCookie.setPath("/"); + refreshCookie.setHttpOnly(true); + refreshCookie.setSecure(true); + response.addCookie(refreshCookie); + } + + +} \ No newline at end of file diff --git a/backend/src/main/java/com/edufocus/edufocus/user/intercepter/JWTInterceptor.java b/backend/src/main/java/com/edufocus/edufocus/user/intercepter/JWTInterceptor.java new file mode 100644 index 0000000..55ec605 --- /dev/null +++ b/backend/src/main/java/com/edufocus/edufocus/user/intercepter/JWTInterceptor.java @@ -0,0 +1,32 @@ +package com.edufocus.edufocus.user.intercepter; + +import com.edufocus.edufocus.user.model.exception.UnAuthorizedException; +import com.edufocus.edufocus.user.util.JWTUtil; +import org.springframework.stereotype.Component; +import org.springframework.web.servlet.HandlerInterceptor; + + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.extern.slf4j.Slf4j; + +@Component +@Slf4j +public class JWTInterceptor implements HandlerInterceptor { + + private final String HEADER_AUTH = "Authorization"; + + private JWTUtil jwtUtil; + + public JWTInterceptor(JWTUtil jwtUtil) { + super(); + this.jwtUtil = jwtUtil; + } + + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler){ + final String token = request.getHeader(HEADER_AUTH); + jwtUtil.checkToken(token); + return true; + } +} \ No newline at end of file diff --git a/backend/src/main/java/com/edufocus/edufocus/user/model/entity/dto/InfoDto.java b/backend/src/main/java/com/edufocus/edufocus/user/model/entity/dto/InfoDto.java new file mode 100644 index 0000000..6c67119 --- /dev/null +++ b/backend/src/main/java/com/edufocus/edufocus/user/model/entity/dto/InfoDto.java @@ -0,0 +1,15 @@ +package com.edufocus.edufocus.user.model.entity.dto; + +import lombok.Getter; +import lombok.Setter; + +@Setter +@Getter + +public class InfoDto { + + String name; + String email; + + +} diff --git a/backend/src/main/java/com/edufocus/edufocus/user/model/entity/dto/MemberChangeDto.java b/backend/src/main/java/com/edufocus/edufocus/user/model/entity/dto/MemberChangeDto.java new file mode 100644 index 0000000..3f965c6 --- /dev/null +++ b/backend/src/main/java/com/edufocus/edufocus/user/model/entity/dto/MemberChangeDto.java @@ -0,0 +1,19 @@ +package com.edufocus.edufocus.user.model.entity.dto; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.ToString; + +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +@ToString +@Data +public class MemberChangeDto { + Long id; + String password; +} \ No newline at end of file diff --git a/backend/src/main/java/com/edufocus/edufocus/user/model/entity/dto/PasswordDto.java b/backend/src/main/java/com/edufocus/edufocus/user/model/entity/dto/PasswordDto.java new file mode 100644 index 0000000..c2f6790 --- /dev/null +++ b/backend/src/main/java/com/edufocus/edufocus/user/model/entity/dto/PasswordDto.java @@ -0,0 +1,12 @@ +package com.edufocus.edufocus.user.model.entity.dto; + +import lombok.Getter; +import lombok.Setter; + +@Setter +@Getter +public class PasswordDto { + String currentPassword; + String newPassword; + String newPasswordCheck; +} diff --git a/backend/src/main/java/com/edufocus/edufocus/user/model/entity/dto/RefreshTokenDto.java b/backend/src/main/java/com/edufocus/edufocus/user/model/entity/dto/RefreshTokenDto.java new file mode 100644 index 0000000..8f22764 --- /dev/null +++ b/backend/src/main/java/com/edufocus/edufocus/user/model/entity/dto/RefreshTokenDto.java @@ -0,0 +1,11 @@ +package com.edufocus.edufocus.user.model.entity.dto; + + +import lombok.AllArgsConstructor; +import lombok.Setter; + +@Setter +@AllArgsConstructor +public class RefreshTokenDto { + private String Token; +} diff --git a/backend/src/main/java/com/edufocus/edufocus/user/model/entity/dto/RequestJoinDto.java b/backend/src/main/java/com/edufocus/edufocus/user/model/entity/dto/RequestJoinDto.java new file mode 100644 index 0000000..6983d90 --- /dev/null +++ b/backend/src/main/java/com/edufocus/edufocus/user/model/entity/dto/RequestJoinDto.java @@ -0,0 +1,17 @@ +package com.edufocus.edufocus.user.model.entity.dto; + +import com.edufocus.edufocus.user.model.entity.vo.UserRole; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.Setter; + +@Setter +@Getter +@AllArgsConstructor +public class RequestJoinDto { + private String userId; + private String email; + private String password; + private UserRole role; + private String name; +} diff --git a/backend/src/main/java/com/edufocus/edufocus/user/model/entity/vo/User.java b/backend/src/main/java/com/edufocus/edufocus/user/model/entity/vo/User.java new file mode 100644 index 0000000..02e409e --- /dev/null +++ b/backend/src/main/java/com/edufocus/edufocus/user/model/entity/vo/User.java @@ -0,0 +1,40 @@ +package com.edufocus.edufocus.user.model.entity.vo; + +import jakarta.persistence.*; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Pattern; +import lombok.*; + +@Entity +@Getter +@Setter +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class User { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) // 자동 증가 설정 + @Column(name = "id") + private Long id; + + + @Column(name = "user_id", unique = true, nullable = false) + @Pattern(regexp = "(?=.*[0-9])(?=.*[a-zA-Z])(?=.*\\W)(?=\\S+$).{8,16}", message = "비밀번호는 8~16자 영문 대 소문자, 숫자, 특수문자를 사용하세요.") + @NotBlank(message = "아이디는 필수 입력 값입니다.") + + private String userId; + + @Pattern(regexp = "^(?:\\w+\\.?)*\\w+@(?:\\w+\\.)+\\w+$", message = "이메일 형식이 올바르지 않습니다.") + private String email; + private String password; + @Enumerated(EnumType.STRING) // 혹은 EnumType.ORDINAL + private UserRole role; + private String refreshToken; + + private String name; + + + + +} diff --git a/backend/src/main/java/com/edufocus/edufocus/user/model/entity/vo/UserRole.java b/backend/src/main/java/com/edufocus/edufocus/user/model/entity/vo/UserRole.java new file mode 100644 index 0000000..41602af --- /dev/null +++ b/backend/src/main/java/com/edufocus/edufocus/user/model/entity/vo/UserRole.java @@ -0,0 +1,5 @@ +package com.edufocus.edufocus.user.model.entity.vo; + +public enum UserRole { + STUDENT,ADMIN +} diff --git a/backend/src/main/java/com/edufocus/edufocus/user/model/exception/ExpriedTokenException.java b/backend/src/main/java/com/edufocus/edufocus/user/model/exception/ExpriedTokenException.java new file mode 100644 index 0000000..e816b32 --- /dev/null +++ b/backend/src/main/java/com/edufocus/edufocus/user/model/exception/ExpriedTokenException.java @@ -0,0 +1,11 @@ +package com.edufocus.edufocus.user.model.exception; + +public class ExpriedTokenException extends RuntimeException { + + private static final long serialVersionUID = 1L; + + public ExpriedTokenException() { + super("계정 권한이 유효하지 않습니다.\n다시 로그인을 하세요."); + } + +} diff --git a/backend/src/main/java/com/edufocus/edufocus/user/model/exception/GlobalExceptionHandler.java b/backend/src/main/java/com/edufocus/edufocus/user/model/exception/GlobalExceptionHandler.java new file mode 100644 index 0000000..fed4131 --- /dev/null +++ b/backend/src/main/java/com/edufocus/edufocus/user/model/exception/GlobalExceptionHandler.java @@ -0,0 +1,26 @@ +package com.edufocus.edufocus.user.model.exception; + +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; + +@ControllerAdvice +public class GlobalExceptionHandler { + + @ExceptionHandler(InvalidTokenException.class) + public ResponseEntity handleInvalidTokenException(InvalidTokenException e) { + return new ResponseEntity<>(e.getMessage(), HttpStatus.FORBIDDEN); + } + + // + @ExceptionHandler(ExpriedTokenException.class) + public ResponseEntity handleExpiredTokenException(ExpriedTokenException e) { + return new ResponseEntity<>(e.getMessage(), HttpStatus.UNAUTHORIZED); + } + + @ExceptionHandler(UserException.class) + public ResponseEntity handleUserException(UserException e) { + return new ResponseEntity<>(e.getMessage(), HttpStatus.FORBIDDEN);//수정해야함 + } +} \ No newline at end of file diff --git a/backend/src/main/java/com/edufocus/edufocus/user/model/exception/InvalidTokenException.java b/backend/src/main/java/com/edufocus/edufocus/user/model/exception/InvalidTokenException.java new file mode 100644 index 0000000..74969f3 --- /dev/null +++ b/backend/src/main/java/com/edufocus/edufocus/user/model/exception/InvalidTokenException.java @@ -0,0 +1,7 @@ +package com.edufocus.edufocus.user.model.exception; + +public class InvalidTokenException extends RuntimeException { + public InvalidTokenException() { + super("Token is invalid"); + } +} \ No newline at end of file diff --git a/backend/src/main/java/com/edufocus/edufocus/user/model/exception/RefreshTokenExpiredException.java b/backend/src/main/java/com/edufocus/edufocus/user/model/exception/RefreshTokenExpiredException.java new file mode 100644 index 0000000..8a1dd14 --- /dev/null +++ b/backend/src/main/java/com/edufocus/edufocus/user/model/exception/RefreshTokenExpiredException.java @@ -0,0 +1,9 @@ +package com.edufocus.edufocus.user.model.exception; + +public class RefreshTokenExpiredException extends RuntimeException { + private static final long serialVersionUID = 1L; + + public RefreshTokenExpiredException() { + super("REFRESH TOKEN 만료\n다시 로그인을 하세요."); + } +} diff --git a/backend/src/main/java/com/edufocus/edufocus/user/model/exception/UnAuthorizedException.java b/backend/src/main/java/com/edufocus/edufocus/user/model/exception/UnAuthorizedException.java new file mode 100644 index 0000000..88bc30e --- /dev/null +++ b/backend/src/main/java/com/edufocus/edufocus/user/model/exception/UnAuthorizedException.java @@ -0,0 +1,11 @@ +package com.edufocus.edufocus.user.model.exception; + +public class UnAuthorizedException extends RuntimeException { + + private static final long serialVersionUID = 1L; + + public UnAuthorizedException() { + super("계정 권한이 유효하지 않습니다.\n다시 로그인을 하세요."); + } + +} \ No newline at end of file diff --git a/backend/src/main/java/com/edufocus/edufocus/user/model/exception/UserException.java b/backend/src/main/java/com/edufocus/edufocus/user/model/exception/UserException.java new file mode 100644 index 0000000..5c73dc1 --- /dev/null +++ b/backend/src/main/java/com/edufocus/edufocus/user/model/exception/UserException.java @@ -0,0 +1,8 @@ +package com.edufocus.edufocus.user.model.exception; + +public class UserException extends RuntimeException{ + + public UserException(String message){ + super(message); + } +} diff --git a/backend/src/main/java/com/edufocus/edufocus/user/model/repository/UserRepository.java b/backend/src/main/java/com/edufocus/edufocus/user/model/repository/UserRepository.java new file mode 100644 index 0000000..c3d42e6 --- /dev/null +++ b/backend/src/main/java/com/edufocus/edufocus/user/model/repository/UserRepository.java @@ -0,0 +1,36 @@ +package com.edufocus.edufocus.user.model.repository; + +import com.edufocus.edufocus.user.model.entity.vo.User; +import jakarta.transaction.Transactional; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import java.util.Optional; + +public interface UserRepository extends JpaRepository { + @Modifying + @Transactional + @Query("UPDATE User u SET u.refreshToken = :refreshToken WHERE u.id = :id") + void saveRefreshToken(@Param("id") Long id, @Param("refreshToken") String refreshToken); + + @Query("SELECT u.refreshToken FROM User u WHERE u.id = :id") + String getRefreshToken(@Param("id") Long id); + + @Modifying + @Transactional + @Query("UPDATE User u SET u.refreshToken = NULL WHERE u.id = :id") + void deleteRefreshToken(@Param("id") Long id); + + @Transactional + @Modifying + @Query("UPDATE User u set u.password = :password where u.id= :id") + void updatePassword(@Param("id") Long id, @Param("password") String password); + + + Optional findByUserId(String userId); + + Optional findByEmail(String email); + +} diff --git a/backend/src/main/java/com/edufocus/edufocus/user/model/service/UserService.java b/backend/src/main/java/com/edufocus/edufocus/user/model/service/UserService.java new file mode 100644 index 0000000..7a42d0d --- /dev/null +++ b/backend/src/main/java/com/edufocus/edufocus/user/model/service/UserService.java @@ -0,0 +1,34 @@ +package com.edufocus.edufocus.user.model.service; + +import com.edufocus.edufocus.user.model.entity.dto.InfoDto; +import com.edufocus.edufocus.user.model.entity.dto.PasswordDto; +import com.edufocus.edufocus.user.model.entity.dto.RequestJoinDto; +import com.edufocus.edufocus.user.model.entity.vo.User; + +public interface UserService { + void join(RequestJoinDto requestJoinDto); + + User login(User user); + + void saveRefreshToken(Long id, String refreshToken); + + String getRefreshToken(Long id); + + void deleteRefreshToken(Long id); + + User userInfo(Long id); + + String getUserName(Long id); + + void changeUserInfo(InfoDto infoDto, Long id); + + void changePassword(PasswordDto passwordDto, Long id); + + boolean isUserIdExist(String userId); + + boolean isEmailExist(String email); + + boolean isTeacher(long userId); + + void changeForgottenPassword(String email, String newPassword); +} diff --git a/backend/src/main/java/com/edufocus/edufocus/user/model/service/UserServiceImpl.java b/backend/src/main/java/com/edufocus/edufocus/user/model/service/UserServiceImpl.java new file mode 100644 index 0000000..516fc16 --- /dev/null +++ b/backend/src/main/java/com/edufocus/edufocus/user/model/service/UserServiceImpl.java @@ -0,0 +1,172 @@ +package com.edufocus.edufocus.user.model.service; + + +import com.edufocus.edufocus.user.model.entity.dto.InfoDto; +import com.edufocus.edufocus.user.model.entity.dto.PasswordDto; +import com.edufocus.edufocus.user.model.entity.dto.RequestJoinDto; +import com.edufocus.edufocus.user.model.entity.vo.User; +import com.edufocus.edufocus.user.model.entity.vo.UserRole; +import com.edufocus.edufocus.user.model.exception.UserException; +import com.edufocus.edufocus.user.model.repository.UserRepository; +import com.edufocus.edufocus.user.util.PasswordUtils; +import jakarta.transaction.Transactional; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.NoSuchElementException; +import java.util.Optional; + +@Service +@Transactional +@RequiredArgsConstructor +public class UserServiceImpl implements UserService { + + + private final UserRepository userRepository; + + + public void join(RequestJoinDto requestJoinDto) { + User user = User.builder() + .userId(requestJoinDto.getUserId()) + .email(requestJoinDto.getEmail()) + .password(PasswordUtils.hashPassword(requestJoinDto.getPassword())) + .role(requestJoinDto.getRole()) + .name(requestJoinDto.getName()) + .build(); + userRepository.save(user); + } + + + public User login(User user) { + Optional findUser = userRepository.findByUserId(user.getUserId()); + + if (findUser.isEmpty()) { + throw new UserException("User does not exist"); + } + + User find = findUser.get(); + if (PasswordUtils.checkPassword(user.getPassword(), find.getPassword())) { + return find; + } else { + throw new UserException("Incorrect password"); + } + + } + + @Override + public User userInfo(Long id) { + try { + return userRepository.findById(id).get(); + } catch (Exception e) { + throw new UserException(e.getMessage()); + } + + } + + + @Override + public String getUserName(Long id) { + return userRepository.findById(id).get().getName(); + } + + + @Override + public void changeUserInfo(InfoDto infoDto, Long id) { + + User user = userRepository.findById(id).orElseThrow(NoSuchElementException::new); + + + if (isEmailExist(infoDto.getEmail()) && !infoDto.getEmail().equals(user.getEmail())) { + throw new RuntimeException("이미 사용 중인 이메일입니다."); + + } + + if (infoDto.getName() != null) + user.setName(infoDto.getName()); + + if (infoDto.getEmail() != null) + user.setEmail(infoDto.getEmail()); + + userRepository.save(user); + } + + + @Override + public void changePassword(PasswordDto passwordDto, Long id) { + User user = userRepository.findById(id).orElse(null); + + if (user == null) { + throw new UserException("User not found"); + } + + if (!PasswordUtils.checkPassword(passwordDto.getCurrentPassword(), user.getPassword())) { + throw new UserException("Current password is incorrect"); + } else if (passwordDto.getCurrentPassword().equals(passwordDto.getNewPassword())) { + throw new UserException("New password cannot be the same as the current password"); + } else { + if (!passwordDto.getNewPassword().equals(passwordDto.getNewPasswordCheck())) { + throw new UserException("New password confirmation does not match"); + } + } + + // Hash the new password before saving + user.setPassword(PasswordUtils.hashPassword(passwordDto.getNewPassword())); + userRepository.save(user); + } + + @Override + public boolean isUserIdExist(String userId) { + return userRepository.findByUserId(userId).isPresent(); + } + + @Override + public boolean isEmailExist(String email) { + return userRepository.findByEmail(email).isPresent(); + } + + public String getTempPassword() { + char[] charSet = new char[]{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', + 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'}; + String str = ""; + + int idx = 0; + for (int i = 0; i < 10; i++) { + idx = (int) (charSet.length * Math.random()); + str += charSet[idx]; + } + return str; + } + + @Override + public void saveRefreshToken(Long id, String refreshToken) { + userRepository.saveRefreshToken(id, refreshToken); + } + + @Override + public String getRefreshToken(Long id) { + return userRepository.getRefreshToken(id); + } + + @Override + public void deleteRefreshToken(Long id) { + userRepository.deleteRefreshToken(id); + } + + @Override + public void changeForgottenPassword(String email, String newPassword) { + User user = userRepository.findByEmail(email).orElse(null); + + if (user == null) { + throw new UserException("User not found"); + } + + // Hash the new password before saving + user.setPassword(PasswordUtils.hashPassword(newPassword)); + userRepository.save(user); + } + + @Override + public boolean isTeacher(long userId) { + return userRepository.findById(userId).orElseThrow(IllegalArgumentException::new).getRole() == UserRole.ADMIN; + } +} diff --git a/backend/src/main/java/com/edufocus/edufocus/user/util/JWTUtil.java b/backend/src/main/java/com/edufocus/edufocus/user/util/JWTUtil.java new file mode 100644 index 0000000..c6630d9 --- /dev/null +++ b/backend/src/main/java/com/edufocus/edufocus/user/util/JWTUtil.java @@ -0,0 +1,93 @@ +package com.edufocus.edufocus.user.util; + +import java.nio.charset.StandardCharsets; +import java.util.Date; +import java.util.Map; + +import com.edufocus.edufocus.user.model.exception.ExpriedTokenException; +import com.edufocus.edufocus.user.model.exception.InvalidTokenException; +import com.edufocus.edufocus.user.model.exception.RefreshTokenExpiredException; +import com.edufocus.edufocus.user.model.exception.UnAuthorizedException; +import io.jsonwebtoken.*; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import lombok.extern.slf4j.Slf4j; + +@Component +@Slf4j +public class JWTUtil { + + @Value("${jwt.salt}") + private String salt; + + @Value("${jwt.access-token.expiretime}") + private long accessTokenExpireTime; + + @Value("${jwt.refresh-token.expiretime}") + private long refreshTokenExpireTime; + + public String createAccessToken(String id) { + return create(id, "access-token", accessTokenExpireTime); + } + + public String createRefreshToken(String id) { + return create(id, "refresh-token", refreshTokenExpireTime); + } + + private String create(String id, String subject, long expireTime) { + Claims claims = Jwts.claims() + .setSubject(subject) + .setIssuedAt(new Date()) + .setExpiration(new Date(System.currentTimeMillis() + expireTime)); + claims.put("id", id); + + return Jwts.builder() + .setHeaderParam("typ", "JWT") + .setClaims(claims) + .signWith(SignatureAlgorithm.HS256, generateKey()) + .compact(); + } + + private byte[] generateKey() { + return salt.getBytes(StandardCharsets.UTF_8); + } + + public boolean checkToken(String token) { + try { + Jws claims = Jwts.parserBuilder() + .setSigningKey(generateKey()) + .build() + .parseClaimsJws(token); + log.debug("claims: {}", claims); + return true; + } + catch (ExpriedTokenException e) { + throw new ExpriedTokenException(); + }catch (Exception e){ + throw new InvalidTokenException(); + } + } + + + public String getUserId(String authorization) { + try { + Jws claims = Jwts.parserBuilder() + .setSigningKey(generateKey()) + .build() + .parseClaimsJws(authorization); + Map value = claims.getBody(); + log.info("value : {}", value); + return (String) value.get("id"); + }catch (ExpiredJwtException e) + { + System.out.println("expired token"); + throw new ExpriedTokenException(); + + } + catch (Exception e) { + log.error("Failed to get user ID from token: {}", e.getMessage()); + throw new InvalidTokenException(); + } + } +} \ No newline at end of file diff --git a/backend/src/main/java/com/edufocus/edufocus/user/util/PasswordUtils.java b/backend/src/main/java/com/edufocus/edufocus/user/util/PasswordUtils.java new file mode 100644 index 0000000..1abbd98 --- /dev/null +++ b/backend/src/main/java/com/edufocus/edufocus/user/util/PasswordUtils.java @@ -0,0 +1,15 @@ +package com.edufocus.edufocus.user.util; +import org.mindrot.jbcrypt.BCrypt; + +public class PasswordUtils { + + // Hash a password + public static String hashPassword(String plainPassword) { + return BCrypt.hashpw(plainPassword, BCrypt.gensalt()); + } + + // Verify a password + public static boolean checkPassword(String plainPassword, String hashedPassword) { + return BCrypt.checkpw(plainPassword, hashedPassword); + } +} \ No newline at end of file diff --git a/backend/src/main/java/com/edufocus/edufocus/video/controller/Controller.java b/backend/src/main/java/com/edufocus/edufocus/video/controller/Controller.java new file mode 100644 index 0000000..88ff332 --- /dev/null +++ b/backend/src/main/java/com/edufocus/edufocus/video/controller/Controller.java @@ -0,0 +1,179 @@ +package com.edufocus.edufocus.video.controller; + +import java.util.Map; +import java.util.Random; + +import com.edufocus.edufocus.lecture.entity.Lecture; +import com.edufocus.edufocus.lecture.repository.LectureRepository; +import com.edufocus.edufocus.lecture.service.LectureService; +import com.edufocus.edufocus.registration.service.RegistrationService; +import com.edufocus.edufocus.user.model.entity.vo.User; +import com.edufocus.edufocus.user.model.entity.vo.UserRole; +import com.edufocus.edufocus.user.model.repository.UserRepository; +import com.edufocus.edufocus.user.model.service.UserService; +import com.edufocus.edufocus.user.util.JWTUtil; +import com.edufocus.edufocus.video.dto.IdentityData; +import com.edufocus.edufocus.video.service.VideoSertvice; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import io.livekit.server.*; +import jakarta.servlet.http.HttpServletRequest; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import livekit.LivekitWebhook.WebhookEvent; + + +@RestController +@RequestMapping("/video") +@Slf4j +@RequiredArgsConstructor + +public class Controller { + private final JWTUtil jwtUtil; + private final UserService userService; + private final LectureService lectureService; + private final VideoSertvice videoSertvice; + private final RegistrationService registrationService; + private final UserRepository userRepository; + private final LectureRepository lectureRepository; + + @Value("${livekit.api.key}") + private String LIVEKIT_API_KEY; + + @Value("${livekit.api.secret}") + private String LIVEKIT_API_SECRET; + private static final ObjectMapper objectMapper = new ObjectMapper(); + + + public static String serializeIdentityData(IdentityData identityData) throws JsonProcessingException, JsonProcessingException { + return objectMapper.writeValueAsString(identityData); + } + + @PostMapping(value = "/joinroom/{lecture_id}") + public ResponseEntity> joinRoom(@PathVariable("lecture_id") Long id, HttpServletRequest request) throws Exception { + + + String userToken = request.getHeader("Authorization"); + + + Long userId = Long.parseLong(jwtUtil.getUserId(userToken)); + User findUser = userRepository.findById(userId).orElse(null); + Lecture lecture = lectureRepository.findById(id).orElse(null); + + + String roomName = lecture.getTitle(); + String participantName = userService.getUserName(userId); + System.out.println(participantName); + + AccessToken token = new AccessToken(LIVEKIT_API_KEY, LIVEKIT_API_SECRET); + + + if (findUser.getRole() == UserRole.STUDENT) { + + if (videoSertvice.isRoomAccessible(id, userId)) { + IdentityData identityData = new IdentityData(participantName, "학생"); + String jsonIdentity = serializeIdentityData(identityData); + + + token.setIdentity(jsonIdentity); + token.setName(participantName); + + token.addGrants(new RoomJoin(true), new RoomName(roomName)); + + + return ResponseEntity.ok(Map.of("token", token.toJwt())); + } else { + return ResponseEntity.status(HttpStatus.NOT_FOUND) + .body(Map.of("error", "방에 들어갈 수 없습니다.")); + } + + + } else if (findUser.getRole() == UserRole.ADMIN) {//&& lecture.isOnline() ) { + + // 자신의 강의인지 확인하기 + if (videoSertvice.checkAdmin(userId, id)) { + IdentityData identityData = new IdentityData(participantName, "강사"); + String jsonIdentity = serializeIdentityData(identityData); + + + token.setIdentity(jsonIdentity); + token.setName(participantName); + + token.addGrants(new RoomJoin(true), new RoomName(roomName), new RoomCreate(true)); + + + return ResponseEntity.ok(Map.of("token", token.toJwt())); + } else { + return ResponseEntity.status(HttpStatus.NOT_FOUND).body(Map.of("error", "자신의 강의가 아닙니다.")); + } + + + } + + return ResponseEntity.ok(Map.of("token", null)); + + } + + + @PostMapping(value = "/makeroom/{lecture_id}") + public ResponseEntity> makeRoom(@PathVariable("lecture_id") Long id, HttpServletRequest request) throws Exception { + String userToken = request.getHeader("Authorization"); + Long userId = Long.parseLong(jwtUtil.getUserId(userToken)); + + User findUser = userRepository.findById(userId).orElse(null); + if (findUser.getRole() == UserRole.ADMIN && videoSertvice.checkAdmin(userId, id)) { + + + videoSertvice.startOnline(userId, id); + + + } else { + return ResponseEntity.status(HttpStatus.NOT_FOUND).body(Map.of("error", "들어갈수없는 계정입니다")); + + } + + return ResponseEntity.ok(Map.of("token", " ")); + + } + + + @PostMapping(value = "/deleteroom/{lecture_id}") + public ResponseEntity> deleteRooom(@PathVariable("lecture_id") Long id, HttpServletRequest request) throws Exception { + String userToken = request.getHeader("Authorization"); + Long userId = Long.parseLong(jwtUtil.getUserId(userToken)); + + User findUser = userRepository.findById(userId).orElse(null); + if (findUser.getRole() == UserRole.ADMIN) { + + + videoSertvice.stopOnline(userId, id); + + + } + + return ResponseEntity.ok(Map.of("token", " ")); + + } + + + @PostMapping(value = "/livekit/webhook", consumes = "application/webhook+json") + public ResponseEntity receiveWebhook(@RequestHeader("Authorization") String authHeader, @RequestBody String body) { + WebhookReceiver webhookReceiver = new WebhookReceiver(LIVEKIT_API_KEY, LIVEKIT_API_SECRET); + try { + WebhookEvent event = webhookReceiver.receive(body, authHeader); + System.out.println("LiveKit Webhook: " + event.toString()); + } catch (Exception e) { + System.err.println("Error validating webhook event: " + e.getMessage()); + } + + + return ResponseEntity.ok("ok"); + } + + +} diff --git a/backend/src/main/java/com/edufocus/edufocus/video/dto/IdentityData.java b/backend/src/main/java/com/edufocus/edufocus/video/dto/IdentityData.java new file mode 100644 index 0000000..e9fd7dd --- /dev/null +++ b/backend/src/main/java/com/edufocus/edufocus/video/dto/IdentityData.java @@ -0,0 +1,17 @@ +package com.edufocus.edufocus.video.dto; + +public class IdentityData { + private String name; + private String role; + + public IdentityData(String name, String role) { + this.name = name; + this.role = role; + } + + // Getters and setters + public String getName() { return name; } + public void setName(String name) { this.name = name; } + public String getRole() { return role; } + public void setRole(String role) { this.role = role; } +} diff --git a/backend/src/main/java/com/edufocus/edufocus/video/service/VideoSertvice.java b/backend/src/main/java/com/edufocus/edufocus/video/service/VideoSertvice.java new file mode 100644 index 0000000..f45cdc4 --- /dev/null +++ b/backend/src/main/java/com/edufocus/edufocus/video/service/VideoSertvice.java @@ -0,0 +1,15 @@ +package com.edufocus.edufocus.video.service; + +import java.sql.SQLException; + +public interface VideoSertvice { + + void startOnline(Long userId, Long lectureId) throws SQLException; + + void stopOnline(Long userId, Long lectureId) throws SQLException; + + boolean isRoomAccessible(Long lectureId, Long userId); + + boolean checkAdmin(Long userId, Long lectureId); + +} diff --git a/backend/src/main/java/com/edufocus/edufocus/video/service/VideoServiceImpl.java b/backend/src/main/java/com/edufocus/edufocus/video/service/VideoServiceImpl.java new file mode 100644 index 0000000..81c604c --- /dev/null +++ b/backend/src/main/java/com/edufocus/edufocus/video/service/VideoServiceImpl.java @@ -0,0 +1,64 @@ +package com.edufocus.edufocus.video.service; + +import com.edufocus.edufocus.lecture.entity.Lecture; +import com.edufocus.edufocus.lecture.entity.LectureDetailResponse; +import com.edufocus.edufocus.lecture.entity.LectureSearchResponse; +import com.edufocus.edufocus.lecture.service.LectureService; +import com.edufocus.edufocus.registration.entity.RegistrationStatus; +import com.edufocus.edufocus.registration.service.RegistrationService; +import jakarta.transaction.Transactional; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.sql.SQLException; +import java.util.List; + +@Service +@Transactional +@RequiredArgsConstructor +public class VideoServiceImpl implements VideoSertvice { + + private final LectureService lectureService; + private final RegistrationService registrationService; + + @Override + public void startOnline(Long userId, Long lectureId) throws SQLException { + + lectureService.startClass(lectureId); + } + + @Override + public void stopOnline(Long userId, Long lectureId) throws SQLException { + lectureService.stopClass(lectureId); + } + + @Override + public boolean isRoomAccessible(Long lectureId, Long userId) { + + + LectureDetailResponse lecture = lectureService.findLectureById(userId, lectureId); + + + RegistrationStatus registrationStatus = registrationService.getStatus(userId, lectureId); + + + if (registrationStatus == RegistrationStatus.ACCEPTED && lecture.isOnline()) { + return true; + } else { + return false; + } + } + + @Override + public boolean checkAdmin(Long userId, Long lectureId) { + // 강의가 내 강의인지 확인 + List lecture = lectureService.findMyLecture(userId); + + for (LectureSearchResponse l : lecture) { + if (l.getId() == lectureId) { + return true; + } + } + return false; + } +} diff --git a/backend/src/main/java/com/edufocus/edufocus/ws/config/RabbitConfig.java b/backend/src/main/java/com/edufocus/edufocus/ws/config/RabbitConfig.java new file mode 100644 index 0000000..349316a --- /dev/null +++ b/backend/src/main/java/com/edufocus/edufocus/ws/config/RabbitConfig.java @@ -0,0 +1,73 @@ +package com.edufocus.edufocus.ws.config; + + +import com.edufocus.edufocus.global.constant.RabbitMQConstant; +import com.edufocus.edufocus.global.properties.RabbitMQProperties; +import org.springframework.amqp.core.*; +import org.springframework.amqp.rabbit.connection.CachingConnectionFactory; +import org.springframework.amqp.rabbit.connection.ConnectionFactory; +import org.springframework.amqp.rabbit.core.RabbitAdmin; +import org.springframework.amqp.rabbit.core.RabbitTemplate; +import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter; +import org.springframework.amqp.support.converter.MessageConverter; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class RabbitConfig { + + private final RabbitMQProperties rabbitMQProperties; + + public RabbitConfig(RabbitMQProperties rabbitMQProperties) { + this.rabbitMQProperties = rabbitMQProperties; + } + + @Bean + public Queue queue() { + + return new Queue(RabbitMQConstant.CHAT_QUEUE_NAME.getConstant(), true); + } + + @Bean + public TopicExchange exchange() { + return new TopicExchange(RabbitMQConstant.CHAT_EXCHANGE.getConstant()); + } + + @Bean + Binding binding(TopicExchange topicExchange, Queue queue) { + return BindingBuilder.bind(queue).to(topicExchange).with(RabbitMQConstant.ROUTING_KEY.getConstant()); + } + + @Bean + ConnectionFactory connectionFactory() { + CachingConnectionFactory connectionFactory = new CachingConnectionFactory(); + connectionFactory.setHost(rabbitMQProperties.getHost()); + connectionFactory.setPort(rabbitMQProperties.getPort()); + connectionFactory.setUsername(rabbitMQProperties.getUsername()); + connectionFactory.setPassword(rabbitMQProperties.getPassword()); + return connectionFactory; + } + + @Bean + public AmqpAdmin amqpAdmin() { + RabbitAdmin rabbitAdmin = new RabbitAdmin(connectionFactory()); + rabbitAdmin.declareQueue(queue()); + return rabbitAdmin; + } + + + @Bean + MessageConverter messageConverter() { + return new Jackson2JsonMessageConverter(); + } + + + + @Bean + RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory, MessageConverter messageConverter) { + RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory); + rabbitTemplate.setMessageConverter(messageConverter); + return rabbitTemplate; + } +} \ No newline at end of file diff --git a/backend/src/main/java/com/edufocus/edufocus/ws/config/WebSocketConfigurer.java b/backend/src/main/java/com/edufocus/edufocus/ws/config/WebSocketConfigurer.java new file mode 100644 index 0000000..30a6441 --- /dev/null +++ b/backend/src/main/java/com/edufocus/edufocus/ws/config/WebSocketConfigurer.java @@ -0,0 +1,40 @@ +package com.edufocus.edufocus.ws.config; + +import com.edufocus.edufocus.global.properties.RabbitMQProperties; +import org.springframework.context.annotation.Configuration; +import org.springframework.messaging.simp.config.MessageBrokerRegistry; +import org.springframework.util.AntPathMatcher; +import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker; +import org.springframework.web.socket.config.annotation.StompEndpointRegistry; +import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer; + +@Configuration +@EnableWebSocketMessageBroker +public class WebSocketConfigurer implements WebSocketMessageBrokerConfigurer { + + private final RabbitMQProperties rabbitMQProperties; + + public WebSocketConfigurer(RabbitMQProperties rabbitMQProperties) { + this.rabbitMQProperties = rabbitMQProperties; + } + + @Override + public void configureMessageBroker(MessageBrokerRegistry registry) { + registry.setPathMatcher(new AntPathMatcher(".")); + + registry.setApplicationDestinationPrefixes("/pub") + .enableStompBrokerRelay("/topic", "/queue", "/exchange") + .setRelayHost(rabbitMQProperties.getHost()) + .setVirtualHost("/") + .setRelayPort(61613) + .setClientLogin(rabbitMQProperties.getUsername()) + .setClientPasscode(rabbitMQProperties.getPassword()); + } + + @Override + public void registerStompEndpoints(StompEndpointRegistry registry) { + registry + .addEndpoint("/ws") + .setAllowedOrigins("*"); + } +} \ No newline at end of file diff --git a/backend/src/main/java/com/edufocus/edufocus/ws/controller/ChatController.java b/backend/src/main/java/com/edufocus/edufocus/ws/controller/ChatController.java new file mode 100644 index 0000000..6f149f0 --- /dev/null +++ b/backend/src/main/java/com/edufocus/edufocus/ws/controller/ChatController.java @@ -0,0 +1,50 @@ +package com.edufocus.edufocus.ws.controller; + + +import com.edufocus.edufocus.global.constant.RabbitMQConstant; +import com.edufocus.edufocus.quiz.service.QuizSetService; +import com.edufocus.edufocus.report.service.ReportService; +import com.edufocus.edufocus.ws.entity.dto.MessageDto; +import com.edufocus.edufocus.ws.entity.dto.QuizDto; +import org.springframework.amqp.rabbit.core.RabbitTemplate; +import org.springframework.messaging.handler.annotation.DestinationVariable; +import org.springframework.messaging.handler.annotation.MessageMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.UUID; + +@RestController +public class ChatController { + + RabbitTemplate rabbitTemplate; + ReportService reportService; + QuizSetService quizSetService; + + public ChatController(RabbitTemplate rabbitTemplate, ReportService reportService, QuizSetService quizSetService) { + this.rabbitTemplate = rabbitTemplate; + this.reportService = reportService; + this.quizSetService = quizSetService; + } + + @MessageMapping("chat.message.{lectureId}") + public void sendMessage(@DestinationVariable long lectureId, MessageDto messageDto) { + rabbitTemplate.convertAndSend(RabbitMQConstant.CHAT_EXCHANGE.getConstant(), + RabbitMQConstant.ROUTING_KEY_PREFIX.getConstant() + lectureId, + messageDto); + } + + @MessageMapping("chat.quiz.{lectureId}") + public void quizStart(@DestinationVariable long lectureId, QuizDto quizDto) { + UUID reportSetId = reportService.initReportSet(lectureId, quizDto.getQuizSetId()); + quizSetService.updateQuizSetTested(quizDto.getQuizSetId()); + + quizDto.setReportSetId(reportSetId); + + rabbitTemplate.convertAndSend(RabbitMQConstant.CHAT_EXCHANGE.getConstant(), + RabbitMQConstant.ROUTING_KEY_PREFIX.getConstant() + lectureId, + quizDto); + } + + +} + diff --git a/backend/src/main/java/com/edufocus/edufocus/ws/entity/dto/ChatUserDto.java b/backend/src/main/java/com/edufocus/edufocus/ws/entity/dto/ChatUserDto.java new file mode 100644 index 0000000..3830c35 --- /dev/null +++ b/backend/src/main/java/com/edufocus/edufocus/ws/entity/dto/ChatUserDto.java @@ -0,0 +1,14 @@ +package com.edufocus.edufocus.ws.entity.dto; + + +import lombok.*; + +@Builder +@Getter +@NoArgsConstructor +@AllArgsConstructor +public class ChatUserDto { + long userId; + long lectureId; + String name; +} diff --git a/backend/src/main/java/com/edufocus/edufocus/ws/entity/dto/HelloDto.java b/backend/src/main/java/com/edufocus/edufocus/ws/entity/dto/HelloDto.java new file mode 100644 index 0000000..6d1eb41 --- /dev/null +++ b/backend/src/main/java/com/edufocus/edufocus/ws/entity/dto/HelloDto.java @@ -0,0 +1,13 @@ +package com.edufocus.edufocus.ws.entity.dto; + + +import lombok.*; + +@Builder +@Getter +@NoArgsConstructor +@AllArgsConstructor +public class HelloDto { + long lectureId; + String name; +} diff --git a/backend/src/main/java/com/edufocus/edufocus/ws/entity/dto/MessageDto.java b/backend/src/main/java/com/edufocus/edufocus/ws/entity/dto/MessageDto.java new file mode 100644 index 0000000..0e12619 --- /dev/null +++ b/backend/src/main/java/com/edufocus/edufocus/ws/entity/dto/MessageDto.java @@ -0,0 +1,13 @@ +package com.edufocus.edufocus.ws.entity.dto; + + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class MessageDto { + long userId; + String name; + String content; +} diff --git a/backend/src/main/java/com/edufocus/edufocus/ws/entity/dto/QuizDto.java b/backend/src/main/java/com/edufocus/edufocus/ws/entity/dto/QuizDto.java new file mode 100644 index 0000000..2d91954 --- /dev/null +++ b/backend/src/main/java/com/edufocus/edufocus/ws/entity/dto/QuizDto.java @@ -0,0 +1,15 @@ +package com.edufocus.edufocus.ws.entity.dto; + + +import lombok.Getter; +import lombok.Setter; + +import java.util.UUID; + +@Getter +@Setter +public class QuizDto { + long userId; + long quizSetId; + UUID ReportSetId; +} diff --git a/backend/src/main/java/com/edufocus/edufocus/ws/entity/vo/ChatUser.java b/backend/src/main/java/com/edufocus/edufocus/ws/entity/vo/ChatUser.java new file mode 100644 index 0000000..1a2a06a --- /dev/null +++ b/backend/src/main/java/com/edufocus/edufocus/ws/entity/vo/ChatUser.java @@ -0,0 +1,33 @@ +package com.edufocus.edufocus.ws.entity.vo; + +import com.edufocus.edufocus.lecture.entity.Lecture; +import com.edufocus.edufocus.user.model.entity.vo.User; +import com.edufocus.edufocus.ws.entity.dto.ChatUserDto; +import jakarta.persistence.*; +import lombok.*; + + +@Entity +@Getter +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class ChatUser { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + int id; + @Column + String sessionId; + @OneToOne + User user; + @ManyToOne + Lecture lecture; + + public ChatUserDto makeChatUserDto(){ + return ChatUserDto.builder() + .userId(user.getId()) + .name(user.getName()) + .lectureId(lecture.getId()) + .build(); + } +} \ No newline at end of file diff --git a/backend/src/main/java/com/edufocus/edufocus/ws/repository/ChatUserRepository.java b/backend/src/main/java/com/edufocus/edufocus/ws/repository/ChatUserRepository.java new file mode 100644 index 0000000..a59fd9e --- /dev/null +++ b/backend/src/main/java/com/edufocus/edufocus/ws/repository/ChatUserRepository.java @@ -0,0 +1,15 @@ +package com.edufocus.edufocus.ws.repository; + +import com.edufocus.edufocus.ws.entity.vo.ChatUser; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; + +import java.util.List; + +public interface ChatUserRepository extends JpaRepository { + public ChatUser findBySessionId(String sessionId); + public List findByLectureId(long lectureId); + + @Query("delete from ChatUser c where c.lecture.id=:lectureId") + public void deleteByLectureId(long lectureId); +} diff --git a/backend/src/main/resources/application.properties b/backend/src/main/resources/application.properties index d5ad940..943e868 100644 --- a/backend/src/main/resources/application.properties +++ b/backend/src/main/resources/application.properties @@ -1 +1,43 @@ spring.application.name=edufocus +server.port=8080 +server.ssl.enabled=false +server.servlet.context-path=${CONTEXT_PATH} +# openvidu +#server.port=${SERVER_PORT:6080} +# LiveKit configuration +livekit.api.key=${LIVEKIT_API_KEY:devkey} +livekit.api.secret=${LIVEKIT_API_SECRET:secret} +# JWT Salt (??? ?? ???? ???) +jwt.salt=${SALT} +# Access Token ?? ?? (??? ??) +#jwt.access-token.expiretime=3600000 +jwt.access-token.expiretime=3000000 +# Refresh Token ?? ?? (??? ??) +jwt.refresh-token.expiretime=504000000 +#jwt.refresh-token.expiretime=4000 +spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver +spring.datasource.url=${DATA_SOURCE_URL} +spring.datasource.username=${USER_NAME} +spring.datasource.password=${USER_PASSWORD} +spring.mvc.pathmatch.matching-strategy=ant_path_matcher +spring.jpa.database=mysql +spring.jpa.hibernate.ddl-auto=update +spring.jpa.show-sql=true +spring.jpa.properties.hibernate.jdbc.time_zone=Asia/Seoul +management.endpoints.web.exposure.include=health,info +management.endpoint.health.show-details=always +spring.rabbitmq.host=${RABBITMQ_HOST} +spring.rabbitmq.port=${RABBITMQ_PORT} +spring.rabbitmq.username=${RABBITMQ_USERNAME} +spring.rabbitmq.password=${RABBITMQ_PASSWORD} +image.path=${IMAGE_PATH} +spring.mail.host=smtp.gmail.com +spring.mail.port=${MAIL_PORT} +spring.mail.username=${MAIL_NAME} +spring.mail.password=${MAIL_PASSWORD} +spring.mail.properties.mail.smtp.auth=true +spring.mail.properties.mail.smtp.timeout=5000 +spring.mail.properties.mail.smtp.starttls.enable=true +spring.data.redis.host=${REDIS_HOST} +spring.data.redis.port=${REDIS_PORT} +spring.data.redis.password=${REDIS_PASSWORD} \ No newline at end of file diff --git a/backend/src/test/java/com/edufocus/edufocus/board/controller/BoardControllerTest.java b/backend/src/test/java/com/edufocus/edufocus/board/controller/BoardControllerTest.java new file mode 100644 index 0000000..e531369 --- /dev/null +++ b/backend/src/test/java/com/edufocus/edufocus/board/controller/BoardControllerTest.java @@ -0,0 +1,2 @@ +package com.edufocus.edufocus.board.controller;public class BoardControllerTest { +} diff --git a/backend/src/test/java/com/edufocus/edufocus/user/model/service/UserServiceImplTest.java b/backend/src/test/java/com/edufocus/edufocus/user/model/service/UserServiceImplTest.java new file mode 100644 index 0000000..a399bd8 --- /dev/null +++ b/backend/src/test/java/com/edufocus/edufocus/user/model/service/UserServiceImplTest.java @@ -0,0 +1,185 @@ +//package com.edufocus.edufocus.user.model.service; +// +//import com.edufocus.edufocus.user.model.entity.dto.PasswordDto; +//import com.edufocus.edufocus.user.model.entity.vo.User; +//import com.edufocus.edufocus.user.model.entity.vo.UserRole; +//import com.edufocus.edufocus.user.model.exception.UserException; +//import com.edufocus.edufocus.user.model.repository.UserRepository; +//import org.junit.jupiter.api.BeforeEach; +//import org.junit.jupiter.api.Test; +//import org.mockito.InjectMocks; +//import org.mockito.Mock; +//import org.mockito.MockitoAnnotations; +//import org.springframework.mail.javamail.JavaMailSender; +// +//import java.util.Optional; +// +//import static org.junit.jupiter.api.Assertions.*; +//import static org.mockito.Mockito.*; +// +//public class UserServiceImplTest { +// +// @InjectMocks +// private UserServiceImpl userService; +// +// @Mock +// private UserRepository userRepository; +// +// +// +// @BeforeEach +// public void setup() { +// MockitoAnnotations.openMocks(this); +// } +// +// @Test +// public void testJoin() { +// User user = new User(); +// user.setUserId("testUser"); +// user.setPassword("password"); +// user.setEmail("test@example.com"); +// user.setRole(UserRole.STUDENT); +// user.setId(1L); +// +// +// userService.join(user); +// +// verify(userRepository, times(1)).save(user); +// } +// +// @Test +// public void testLogin_Success() throws Exception { +// User user = new User(); +// user.setUserId("testUser"); +// user.setPassword("password"); +// +// when(userRepository.findByUserId("testUser")).thenReturn(Optional.of(user)); +// +// User loggedInUser = userService.login(user); +// +// assertNotNull(loggedInUser); +// assertEquals("testUser", loggedInUser.getUserId()); +// } +// +// @Test +// public void testLogin_UserNotFound() { +// User user = new User(); +// user.setUserId("testUser"); +// user.setPassword("password"); +// +// when(userRepository.findByUserId("testUser")).thenReturn(Optional.empty()); +// +// assertThrows(UserException.class, () -> { +// userService.login(user); +// }); +// } +// +// @Test +// public void testLogin_InvalidPassword() { +// User user = new User(); +// user.setUserId("testUser"); +// user.setPassword("password"); +// +// User storedUser = new User(); +// storedUser.setUserId("testUser"); +// storedUser.setPassword("wrongPassword"); +// +// when(userRepository.findByUserId("testUser")).thenReturn(Optional.of(storedUser)); +// +// assertThrows(UserException.class, () -> { +// userService.login(user); +// }); +// } +// +//// @Test +//// public void testUserInfo_Success() { +//// User user = new User(); +//// user.setId(1L); +//// user.setName("testUser"); +//// +//// when(userRepository.findById(1L)).thenReturn(Optional.of(user)); +//// +//// User userInfo = userService.userInfo(1L); +//// +//// assertNotNull(userInfo); +//// assertEquals("testUser", userInfo.getName()); +//// } +//// +//// @Test +//// public void testUserInfo_UserNotFound() { +//// when(userRepository.findById(1L)).thenReturn(Optional.empty()); +//// +//// assertThrows(UserException.class, () -> { +//// userService.userInfo(1L); +//// }); +//// } +//// +//// @Test +//// public void testChangePassword_Success() throws Exception { +//// User user = new User(); +//// user.setId(1L); +//// user.setPassword("currentPassword"); +//// +//// when(userRepository.findById(1L)).thenReturn(Optional.of(user)); +//// +//// PasswordDto passwordDto = new PasswordDto(); +//// passwordDto.setCurrentPassword("currentPassword"); +//// passwordDto.setNewPassword("newPassword"); +//// passwordDto.setNewPasswordCheck("newPassword"); +//// +//// userService.changePassword(passwordDto, 1L); +//// +//// verify(userRepository, times(1)).save(user); +//// assertEquals("newPassword", user.getPassword()); +//// } +//// +//// @Test +//// public void testChangePassword_UserNotFound() { +//// when(userRepository.findById(1L)).thenReturn(Optional.empty()); +//// +//// PasswordDto passwordDto = new PasswordDto(); +//// passwordDto.setCurrentPassword("currentPassword"); +//// passwordDto.setNewPassword("newPassword"); +//// passwordDto.setNewPasswordCheck("newPassword"); +//// +//// assertThrows(Exception.class, () -> { +//// userService.changePassword(passwordDto, 1L); +//// }); +//// } +//// +//// @Test +//// public void testChangePassword_InvalidCurrentPassword() { +//// User user = new User(); +//// user.setId(1L); +//// user.setPassword("currentPassword"); +//// +//// when(userRepository.findById(1L)).thenReturn(Optional.of(user)); +//// +//// PasswordDto passwordDto = new PasswordDto(); +//// passwordDto.setCurrentPassword("wrongPassword"); +//// passwordDto.setNewPassword("newPassword"); +//// passwordDto.setNewPasswordCheck("newPassword"); +//// +//// assertThrows(Exception.class, () -> { +//// userService.changePassword(passwordDto, 1L); +//// }); +//// } +//// +//// @Test +//// public void testChangePassword_NewPasswordMismatch() { +//// User user = new User(); +//// user.setId(1L); +//// user.setPassword("currentPassword"); +//// +//// when(userRepository.findById(1L)).thenReturn(Optional.of(user)); +//// +//// PasswordDto passwordDto = new PasswordDto(); +//// passwordDto.setCurrentPassword("currentPassword"); +//// passwordDto.setNewPassword("newPassword"); +//// passwordDto.setNewPasswordCheck("mismatchNewPassword"); +//// +//// assertThrows(Exception.class, () -> { +//// userService.changePassword(passwordDto, 1L); +//// }); +//// } +//} \ No newline at end of file