diff --git a/src/main/java/peer/backend/controller/dnd/DnDController.java b/src/main/java/peer/backend/controller/dnd/DnDController.java new file mode 100644 index 000000000..3d9a2bea3 --- /dev/null +++ b/src/main/java/peer/backend/controller/dnd/DnDController.java @@ -0,0 +1,61 @@ +package peer.backend.controller.dnd; + +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import peer.backend.dto.dnd.RequestDnDDTO; +import peer.backend.mongo.entity.TeamDnD; +import peer.backend.service.dnd.DnDService; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/v1/dnd-main") +public class DnDController { + private final DnDService dndService; + + @PostMapping("/create") + public ResponseEntity create(@RequestBody TeamDnD data) { + TeamDnD ret; + try { + ret = this.dndService.createDnD(data); + } catch (Exception e) { + return new ResponseEntity(e.getMessage(), HttpStatus.BAD_REQUEST); + } + return new ResponseEntity(ret,HttpStatus.CREATED); + } + + @PostMapping("/read") + public ResponseEntity read(@RequestBody peer.backend.dto.dnd.RequestDnDDTO data) { + TeamDnD ret; + try { + ret = this.dndService.getDnD(data); + if (ret == null) + return new ResponseEntity<>("There is no that DnD data", HttpStatus.NOT_FOUND); + } catch (Exception e) { + return new ResponseEntity(e.getMessage(), HttpStatus.BAD_REQUEST); + } + return new ResponseEntity(ret,HttpStatus.OK); + } + + @PostMapping("/update") + public ResponseEntity update(@RequestBody TeamDnD data) { + TeamDnD ret; + try{ + ret = this.dndService.updateDnD(data); + } catch (Exception e) { + return new ResponseEntity(e.getMessage(), HttpStatus.BAD_REQUEST); + } + return new ResponseEntity(ret, HttpStatus.OK); + } + + @DeleteMapping("/delete") + public ResponseEntity delete(@RequestBody RequestDnDDTO data) { + try { + this.dndService.deleteDnD(data); + } catch (Exception e) { + return new ResponseEntity(e.getMessage(), HttpStatus.BAD_REQUEST); + } + return new ResponseEntity<>(HttpStatus.OK); + } +} diff --git a/src/main/java/peer/backend/controller/dnd/DnDSubController.java b/src/main/java/peer/backend/controller/dnd/DnDSubController.java new file mode 100644 index 000000000..3e5d3166c --- /dev/null +++ b/src/main/java/peer/backend/controller/dnd/DnDSubController.java @@ -0,0 +1,145 @@ +package peer.backend.controller.dnd; + +import io.swagger.annotations.ApiOperation; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.Authentication; +import org.springframework.web.bind.annotation.*; +import peer.backend.dto.dndSub.CalendarEventDTO; +import peer.backend.dto.dndSub.DeleteTargetDTO; +import peer.backend.dto.dndSub.MemberDTO; +import peer.backend.entity.team.Team; +import peer.backend.entity.user.User; +import peer.backend.exception.BadRequestException; +import peer.backend.service.dnd.DnDSubService; + + +import java.util.List; +import java.util.NoSuchElementException; + +@RestController +@RequiredArgsConstructor +@RequestMapping(DnDSubController.WIDGET_URL) +public class DnDSubController { + public static final String WIDGET_URL = "api/v1/dnd-sub"; + public static final String CALENDAR_IDENTIFIER = "calendar"; + private final DnDSubService dnDSubService; + + @GetMapping("calendar/team-list") + @ApiOperation(value = "", notes = "달력을 위한 팀 멤버 리스트를 제공합니다.") + public ResponseEntity getTeamMemberList(Authentication auth, @RequestBody long teamId) { + Team target = this.dnDSubService.getTeamByTeamId(teamId); + + // redis 에 자주 쓸 가능성이 있는 team 정보 저장 + this.dnDSubService.saveTeamDataInRedis(Long.toString(teamId), CALENDAR_IDENTIFIER, target); + + // 유효성 검사 + if (this.dnDSubService.validCheckForTeam(target) + || this.dnDSubService.validCheckUserWithTeam(target, User.authenticationToUser(auth))) { + return new ResponseEntity<>(HttpStatus.BAD_REQUEST); + } + + // 멤버 리스트 담기 & 에러 핸들링 + List ret; + try { + ret = this.dnDSubService.getMemberList(User.authenticationToUser(auth), target); + } + catch (NoSuchElementException | BadRequestException e) { + return new ResponseEntity<>(e, HttpStatus.BAD_REQUEST); + } + if(ret == null) { + return new ResponseEntity<>(HttpStatus.FORBIDDEN); + } + return new ResponseEntity<>(ret, HttpStatus.OK); + } + + @PostMapping("calendar/set-alarm") + @ApiOperation(value = "", notes = "알람으로 기록된 이벤트를 설정합니다.") + public ResponseEntity setAlarmEvent(Authentication auth, @RequestBody CalendarEventDTO event) { + // redis 에서 team 정보 찾기 + Team target = (Team)this.dnDSubService.getTeamDataInRedis(Long.toString(event.getTeamId()), CALENDAR_IDENTIFIER); + if(target == null) { + target = this.dnDSubService.getTeamByTeamId(event.getTeamId()); + this.dnDSubService.saveTeamDataInRedis(Long.toString(event.getTeamId()), CALENDAR_IDENTIFIER, target); + } + + // 유효성 검사 + if (this.dnDSubService.validCheckForTeam(target) + || this.dnDSubService.validCheckUserWithTeam(target, User.authenticationToUser(auth))) { + return new ResponseEntity<>(HttpStatus.BAD_REQUEST); + } + + Long eventId; + + try { + eventId = this.dnDSubService.setEventToAlarm(event); + } catch (BadRequestException e) { + return new ResponseEntity<>(e, HttpStatus.BAD_REQUEST); + } + + return new ResponseEntity<>(eventId, HttpStatus.OK); + } + + @DeleteMapping("calendar/delete-alarm") + @ApiOperation(value = "", notes = "알람으로 기록된 이벤트를 삭제합니다.") + public ResponseEntity deleteAlarmEvent(Authentication auth, @RequestBody DeleteTargetDTO event) { + Team target = (Team)this.dnDSubService.getTeamDataInRedis(Long.toString(event.getTeamId()), CALENDAR_IDENTIFIER); + if(target == null) { + target = this.dnDSubService.getTeamByTeamId(event.getTeamId()); + this.dnDSubService.saveTeamDataInRedis(Long.toString(event.getTeamId()), CALENDAR_IDENTIFIER, target); + } + + // 유효성 검사 + if (this.dnDSubService.validCheckForTeam(target) + || this.dnDSubService.validCheckUserWithTeam(target, User.authenticationToUser(auth))) { + return new ResponseEntity<>(HttpStatus.BAD_REQUEST); + } + + //대상 삭제 + try { + this.dnDSubService.deleteEventFromAlarm(event); + }catch (NoSuchElementException e) { + return new ResponseEntity<>(e, HttpStatus.BAD_REQUEST); + } + + return new ResponseEntity<>(HttpStatus.OK); + } + + @PostMapping("calendar/update-alarm") + @ApiOperation(value = "", notes = "알람으로 기록된 이벤트를 갱신합니다.") + public ResponseEntity updateAlarmEvent(Authentication auth, @RequestBody CalendarEventDTO event) { + // redis 에서 team 정보 찾기 + Team target = (Team)this.dnDSubService.getTeamDataInRedis(Long.toString(event.getTeamId()), CALENDAR_IDENTIFIER); + if(target == null) { + target = this.dnDSubService.getTeamByTeamId(event.getTeamId()); + this.dnDSubService.saveTeamDataInRedis(Long.toString(event.getTeamId()), CALENDAR_IDENTIFIER, target); + } + + // 유효성 검사 + if (this.dnDSubService.validCheckForTeam(target) + || this.dnDSubService.validCheckUserWithTeam(target, User.authenticationToUser(auth))) { + return new ResponseEntity<>(HttpStatus.BAD_REQUEST); + } + + Long eventId; + + try { + eventId = this.dnDSubService.updateEventToAlarm(event); + } catch (BadRequestException e) { + return new ResponseEntity<>(e, HttpStatus.BAD_REQUEST); + } + + if (eventId == -1L){ + return new ResponseEntity<>(new NoSuchElementException("존재하지 않는 이벤트입니다."), HttpStatus.BAD_REQUEST); + } + + return new ResponseEntity<>(HttpStatus.NO_CONTENT); + } + + @GetMapping("calendar/test/total-alarm") + @ApiOperation(value="", notes = "기록된 모든 알람을 호출합니다. 테스트용 코드입니다.") + public ResponseEntity getTestTotalAlarmList(Authentication auth) { + return new ResponseEntity<>(this.dnDSubService.getAllEvents(), HttpStatus.OK); + } +} \ No newline at end of file diff --git a/src/main/java/peer/backend/dto/dnd/RequestDnDDTO.java b/src/main/java/peer/backend/dto/dnd/RequestDnDDTO.java new file mode 100644 index 000000000..cb4a0c164 --- /dev/null +++ b/src/main/java/peer/backend/dto/dnd/RequestDnDDTO.java @@ -0,0 +1,13 @@ +package peer.backend.dto.dnd; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +@AllArgsConstructor +public class RequestDnDDTO { + private Long teamId; + private String type; +} diff --git a/src/main/java/peer/backend/dto/dndSub/CalendarEventDTO.java b/src/main/java/peer/backend/dto/dndSub/CalendarEventDTO.java new file mode 100644 index 000000000..dadafe0c5 --- /dev/null +++ b/src/main/java/peer/backend/dto/dndSub/CalendarEventDTO.java @@ -0,0 +1,39 @@ +package peer.backend.dto.dndSub; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Objects; + +@Getter +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class CalendarEventDTO { + private Long teamId; + private Long eventId; + private String title; + private LocalDateTime start; + private LocalDateTime end; + private List member; + + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + CalendarEventDTO target = (CalendarEventDTO) o; + return Objects.equals(eventId, ((CalendarEventDTO) o).eventId) && + Objects.equals(teamId, ((CalendarEventDTO) o).teamId); + } + // HashSet 을 제대로 동작시키기 위한 코드, 같은지 여부를 다각도로 점검한다. + + @Override + public int hashCode() { + return Objects.hash(teamId, eventId); + } +} diff --git a/src/main/java/peer/backend/dto/dndSub/DeleteTargetDTO.java b/src/main/java/peer/backend/dto/dndSub/DeleteTargetDTO.java new file mode 100644 index 000000000..620e724eb --- /dev/null +++ b/src/main/java/peer/backend/dto/dndSub/DeleteTargetDTO.java @@ -0,0 +1,9 @@ +package peer.backend.dto.dndSub; + +import lombok.Getter; + +@Getter +public class DeleteTargetDTO { + private Long teamId; + private Long eventId; +} diff --git a/src/main/java/peer/backend/dto/dndSub/MemberDTO.java b/src/main/java/peer/backend/dto/dndSub/MemberDTO.java new file mode 100644 index 000000000..d04c813ee --- /dev/null +++ b/src/main/java/peer/backend/dto/dndSub/MemberDTO.java @@ -0,0 +1,37 @@ +package peer.backend.dto.dndSub; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import javax.validation.constraints.Min; +import javax.validation.constraints.NotBlank; +import java.util.Objects; + +@Getter +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class MemberDTO { + @NotBlank + @Min(value = 1, message = "정상적인 userId를 넣어 주십시오.") + private Long userId; + @NotBlank(message = "닉네임은 필수 요소 입니다.") + private String nickname; + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + MemberDTO memberDTO = (MemberDTO) o; + return Objects.equals(userId, memberDTO.userId) && + Objects.equals(nickname, memberDTO.nickname); + } + // HashSet 을 제대로 동작시키기 위한 코드, 같은지 여부를 다각도로 점검한다. + + @Override + public int hashCode() { + return Objects.hash(userId, nickname); + } +} diff --git a/src/main/java/peer/backend/service/dnd/DnDService.java b/src/main/java/peer/backend/service/dnd/DnDService.java new file mode 100644 index 000000000..cd56f68a5 --- /dev/null +++ b/src/main/java/peer/backend/service/dnd/DnDService.java @@ -0,0 +1,113 @@ +package peer.backend.service.dnd; + +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Example; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import peer.backend.dto.dnd.RequestDnDDTO; +import peer.backend.entity.team.Team; +import peer.backend.mongo.entity.TeamDnD; +import peer.backend.mongo.repository.PeerLogDnDRepository; +import peer.backend.mongo.repository.TeamDnDRepository; +import peer.backend.repository.team.TeamRepository; + +import java.util.NoSuchElementException; +import java.util.Optional; + +@Service +@RequiredArgsConstructor +public class DnDService { + + private final TeamDnDRepository teamDnDRepository; + private final PeerLogDnDRepository peerLogDnDRepository; + private final TeamRepository teamRepository; + + @Transactional + public TeamDnD createDnD(TeamDnD data) throws RuntimeException { + Optional target = teamRepository.findById(data.getTeamId()); + if (target.isEmpty()) { + throw new NoSuchElementException("Plz, check, There is no team id"); + } + TeamDnD saveData = TeamDnD.builder() + .teamId(data.getTeamId()) + .type(data.getType()) + .widgets(data.getWidgets()) + .build(); + + TeamDnD ret; + try { + if (data.getType().equals("team")) + ret = this.teamDnDRepository.save(saveData); + else + ret = this.peerLogDnDRepository.save(saveData); + } catch (Exception e) { + throw new RuntimeException("DB Server makes an error."); + } + return ret; + } + + @Transactional(readOnly = true) + public TeamDnD getDnD(RequestDnDDTO data) throws NoSuchElementException { + TeamDnD ret; + if (data.getType().equals("team")) { + ret = this.teamDnDRepository.findByTeamId(data.getTeamId()); + } + else { + ret = this.peerLogDnDRepository.findByTeamId(data.getTeamId()); + } + if (ret == null) { + throw new NoSuchElementException("There is no DnD Data"); + } + return ret; + } + + @Transactional + public TeamDnD updateDnD(TeamDnD data) throws Exception { + TeamDnD example = new TeamDnD(); + TeamDnD ret; + example.setTeamId(data.getTeamId()); + try { + if (data.getType().equals("team")) { + if (!this.teamDnDRepository.exists(Example.of(example))){ + throw new NoSuchElementException("There is no that Dnd File."); + } + example = this.teamDnDRepository.findByTeamId(data.getTeamId()); + example.setWidgets(data.getWidgets()); + ret = this.teamDnDRepository.save(example); + } else { + if (!this.peerLogDnDRepository.exists(Example.of(example))){ + throw new NoSuchElementException("There is no that Dnd File."); + } + example = this.peerLogDnDRepository.findByTeamId(data.getTeamId()); + example.setWidgets(data.getWidgets()); + ret = this.peerLogDnDRepository.save(data); + } + } catch (Exception e) { + throw new Exception("DnD file update failed!"); + } + return ret; + } + + @Transactional + public void deleteDnD(RequestDnDDTO data) throws RuntimeException { + TeamDnD example = new TeamDnD(); + example.setTeamId(data.getTeamId()); + try { + if (data.getType().equals("team")) { + if (!this.teamDnDRepository.exists(Example.of(example))){ + throw new NoSuchElementException("There is no that Dnd File."); + } + this.teamDnDRepository.deleteByTeamId(data.getTeamId()); + } + else { + if (!this.peerLogDnDRepository.exists(Example.of(example))){ + throw new NoSuchElementException("There is no that Dnd File."); + } + this.peerLogDnDRepository.deleteByTeamId(data.getTeamId()); + } + } catch (Exception e) { + throw new RuntimeException("DnD file delete is failed!"); + } + + } +} diff --git a/src/main/java/peer/backend/service/dnd/DnDSubService.java b/src/main/java/peer/backend/service/dnd/DnDSubService.java new file mode 100644 index 000000000..d3fc2cda2 --- /dev/null +++ b/src/main/java/peer/backend/service/dnd/DnDSubService.java @@ -0,0 +1,133 @@ +package peer.backend.service.dnd; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Service; +import org.springframework.web.servlet.config.annotation.EnableWebMvc; +import peer.backend.dto.dndSub.CalendarEventDTO; +import peer.backend.dto.dndSub.DeleteTargetDTO; +import peer.backend.dto.dndSub.MemberDTO; +import peer.backend.entity.team.Team; +import peer.backend.entity.team.TeamUser; +import peer.backend.entity.team.enums.TeamStatus; +import peer.backend.entity.user.User; +import peer.backend.exception.BadRequestException; +import peer.backend.repository.team.TeamRepository; +import peer.backend.repository.user.UserRepository; + +import java.util.*; +import java.util.concurrent.atomic.AtomicBoolean; + +@Service +@RequiredArgsConstructor +@EnableWebMvc +@Slf4j +public class DnDSubService { + private final UserRepository userRepository; + private final TeamRepository teamRepository; + + private Long eventCnt = 0L; + + private RedisTemplate redisTemplate; + + private final HashSet tempMemeoryEventList; + + public void saveTeamDataInRedis(String key, String identifier, Object value) { + String newKey = key + "-" + identifier; + redisTemplate.opsForValue().set(newKey, value); + } + + public Object getTeamDataInRedis(String key, String identifier) { + return redisTemplate.opsForValue().get(key + "-" + identifier); + } + + public Team getTeamByTeamId(Long teamId){ + return this.teamRepository.findById(teamId).orElseThrow(() -> new NoSuchElementException("존재하지 않는 팀입니다.")); + } + + public boolean validCheckForTeam(Team target) { + return !target.getStatus().equals(TeamStatus.COMPLETE); + } + + public boolean validCheckUserWithTeam(Team target, User requester) { + return target.getTeamUsers() + .stream() + .anyMatch(member -> member.getUserId().equals(requester.getId())); + } + + public Long makeTemporaryEventId() { + Long id; + id = this.eventCnt++; + + return id; + } + + public List getMemberList(User requesterUser, Team targetTeam) { + + HashSet ret = new HashSet<>(); + for (TeamUser target : targetTeam.getTeamUsers()) { + User targetUser = target.getUser(); + MemberDTO member = MemberDTO.builder() + .nickname(targetUser.getNickname()) + .userId(targetUser.getId()) + .build(); + ret.add(member); + } + MemberDTO requester = MemberDTO.builder() + .nickname(requesterUser.getNickname()) + .userId(requesterUser.getId()) + .build(); + if (!ret.contains(requester)) + throw new BadRequestException("요청자가 팀 멤버가 아닙니다."); + return new ArrayList<>(ret); + } + + public Long setEventToAlarm(CalendarEventDTO data) { + Long id = this.makeTemporaryEventId(); + + if(data.getTeamId() == -1L){ + throw new BadRequestException("비정상적인 요청입니다."); + } + + CalendarEventDTO saveEvent = CalendarEventDTO.builder() + .eventId(id) + .teamId(data.getTeamId()) + .end(data.getEnd()) + .start(data.getStart()) + .member(data.getMember()) + .title(data.getTitle()) + .build(); + + this.tempMemeoryEventList.add(saveEvent); + + return id; + } + + public Long updateEventToAlarm(CalendarEventDTO data){ + if (this.tempMemeoryEventList.stream().noneMatch(event -> event.getEventId().equals(data.getEventId()))) { + return -1L; + } + this.tempMemeoryEventList.remove(data); + this.tempMemeoryEventList.add(data); + return data.getEventId(); + } + + public void deleteEventFromAlarm(DeleteTargetDTO target) { + CalendarEventDTO filtered = this.tempMemeoryEventList.stream() + .filter(event -> event.getEventId().equals(target.getEventId())) + .findFirst() + .orElse(null); + + if (filtered == null) + throw new NoSuchElementException("이벤트가 존재하지 않습니다."); + + this.tempMemeoryEventList.remove(filtered); + return; + } + + public List getAllEvents() { + return new ArrayList<>(this.tempMemeoryEventList); + } +}