Skip to content

Commit

Permalink
Merge pull request #101 from HowMuchPay/feature104
Browse files Browse the repository at this point in the history
  • Loading branch information
seongHyun-Min committed Oct 10, 2023
2 parents 0373272 + 4d28a4b commit eab5b21
Show file tree
Hide file tree
Showing 12 changed files with 159 additions and 36 deletions.
27 changes: 25 additions & 2 deletions .github/workflows/CI.yml → .github/workflows/CI&CD.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: CI
name: CI & CD using Github Actions

on:
push:
Expand All @@ -11,7 +11,8 @@ permissions:

jobs:
build:
name: build
## CI
name: CI
runs-on: ubuntu-latest

steps:
Expand Down Expand Up @@ -66,5 +67,27 @@ jobs:
push: true
tags: ${{ secrets.DOCKER_USERNAME }}/howmuchpay_image:latest

deploy:
## CD
name: CD
runs-on: ubuntu-latest
needs: build
steps:
- name: Deploy to EC2
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.HOST_IP }}
username: ${{ secrets.USERNAME }}
port: 22
key: ${{ secrets.PRIVATE_KEY }}
script: |
docker ps
docker stop ${{ secrets.CONTAINER_NAME }}
docker rm $(docker ps -a -q)
docker image rm $(docker images -q)
docker pull ${{ secrets.DOCKER_USERNAME }}/howmuchpay_image:latest
docker run -d -p 8080:8080 --name ${{ secrets.CONTAINER_NAME }} --env-file .env ${{ secrets.DOCKER_USERNAME }}/howmuchpay_image:latest
docker ps
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package com.example.howmuch.config.security;

import com.example.howmuch.exception.ErrorMessage;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.OutputStream;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;

@Component
@Slf4j
public class JwtAccessDeniedHandler implements AccessDeniedHandler {

private static void makeResultResponse(
HttpServletResponse response,
Exception exception
) throws IOException {
try (OutputStream os = response.getOutputStream()) {
JavaTimeModule javaTimeModule = new JavaTimeModule();
LocalDateTimeSerializer localDateTimeSerializer = new LocalDateTimeSerializer(
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").withZone(ZoneId.of("Asia/Seoul")));
javaTimeModule.addSerializer(LocalDateTime.class, localDateTimeSerializer);
ObjectMapper objectMapper = new ObjectMapper().registerModule(javaTimeModule);
objectMapper.writeValue(os, ErrorMessage.of(exception, HttpStatus.FORBIDDEN));
os.flush();
}
}

@Override
public void handle(HttpServletRequest request,
HttpServletResponse response,
AccessDeniedException accessDeniedException) throws IOException, ServletException {
log.error("AccessDenied Exception Occurs!");
sendErrorAccessDenied(response);

}

// 권한 없는 경우
private void sendErrorAccessDenied(HttpServletResponse response) throws IOException {
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
response.setContentType("application/json,charset=utf-8");
makeResultResponse(
response,
new AccessDeniedException("접근 권한이 없습니다.")
);
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package com.example.howmuch.config.security;

import com.example.howmuch.exception.ErrorMessage;
import com.example.howmuch.exception.user.ForbiddenUserException;
import com.example.howmuch.exception.user.UnauthorizedUserException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
Expand Down Expand Up @@ -47,13 +46,8 @@ private static void makeResultResponse(
@Override
public void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException e) throws IOException {
Object errorObject = request.getAttribute("UnauthorizedUserException");
if (errorObject != null) {
log.info("errorObject Not null");
sendErrorUnauthorized(response);
} else {
sendErrorDenied(response);
}
log.error("Authentication Exception Occurs!");
sendErrorUnauthorized(response);
}

// 인증 안된 경우
Expand All @@ -66,15 +60,4 @@ private void sendErrorUnauthorized(HttpServletResponse response) throws IOExcept
HttpStatus.UNAUTHORIZED
);
}

// 권한 없는 경우
private void sendErrorDenied(HttpServletResponse response) throws IOException {
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
response.setContentType("application/json,charset=utf-8");
makeResultResponse(
response,
new ForbiddenUserException("권한이 없는 요청입니다."),
HttpStatus.FORBIDDEN
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,6 @@ protected void doFilterInternal(HttpServletRequest request,
// access token 이 만료된 경우 or access token 정보로 식별 안되는 경우
} catch (IllegalArgumentException | JwtException e) {
log.info("JwtAuthentication UnauthorizedUserException!");
request.setAttribute("UnauthorizedUserException", e);
}
filterChain.doFilter(request, response);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ public class SecurityConfig {

private final JwtAuthenticationFilter jwtAuthenticationFilter;
private final JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
private final JwtAccessDeniedHandler jwtAccessDeniedHandler;

@Bean
public WebSecurityCustomizer webSecurityCustomizer() {
Expand Down Expand Up @@ -55,6 +56,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
.and()
.exceptionHandling()
.authenticationEntryPoint(jwtAuthenticationEntryPoint)
.accessDeniedHandler(jwtAccessDeniedHandler)
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
Expand Down
10 changes: 10 additions & 0 deletions src/main/java/com/example/howmuch/controller/EventController.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.example.howmuch.dto.event.*;
import com.example.howmuch.dto.event.GetAllMyEventDetailResponseDto.GetAllMyEventDetails;
import com.example.howmuch.dto.notice.GetAllNoticeResponseDto;
import com.example.howmuch.dto.notice.GetNoticeResponseDto;
import com.example.howmuch.service.event.EventService;
import com.example.howmuch.service.notice.NoticeService;
import lombok.RequiredArgsConstructor;
Expand Down Expand Up @@ -203,4 +204,13 @@ public ResponseEntity<GetAllAcEventsResponseDto> getAcEventsByNickname(
public ResponseEntity<List<GetAllNoticeResponseDto>> getAllNoticeResponseDto() {
return new ResponseEntity<>(this.noticeService.getAllNotices(), HttpStatus.OK);
}


// 사용자 공지 사항 상세 조회
@GetMapping("/notice/{id}")
public ResponseEntity<GetNoticeResponseDto> getNoticeResponseDto(
@PathVariable Long id
) {
return new ResponseEntity<>(this.noticeService.getNoticeById(id), HttpStatus.OK);
}
}
11 changes: 10 additions & 1 deletion src/main/java/com/example/howmuch/domain/entity/Notice.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.example.howmuch.domain.BaseTimeEntity;
import com.example.howmuch.dto.notice.GetAllNoticeResponseDto;
import com.example.howmuch.dto.notice.GetNoticeResponseDto;
import com.example.howmuch.dto.notice.UpdateNoticeRequestDto;
import lombok.*;
import lombok.extern.slf4j.Slf4j;
Expand All @@ -23,7 +24,7 @@ public class Notice extends BaseTimeEntity {
private String content;


public GetAllNoticeResponseDto from() {
public GetAllNoticeResponseDto toGetAllNoticeResponseDto() {
return GetAllNoticeResponseDto.builder()
.id(id)
.title(title)
Expand All @@ -32,6 +33,14 @@ public GetAllNoticeResponseDto from() {
.build();
}

public GetNoticeResponseDto toGetNoticeResponseDto() {
return GetNoticeResponseDto.builder()
.title(title)
.content(content)
.updatedAt(getUpdatedAt().toLocalDate())
.build();
}

public void updateNotice(UpdateNoticeRequestDto request) {
this.title = request.getTitle();
this.content = request.getContent();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
public class GetStatisticsResponseDto {
private long totalPayment; // 받은 총 금액(냈어요)
private long totalReceiveAmount; // 낸 총금액(받았어요)
private String mostEventCategory; // 가장 많은 비용을 지출한 경조사
private List<String> mostEventCategory; // 가장 많은 비용을 지출한 경조사
private Long mostEventPayAmount; // 가장 많은 비용을 지출한 경조사 비용
private Map<String, List<StatisticsListResponse>> statisticsListResponse;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.example.howmuch.dto.notice;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;

import java.time.LocalDate;

@Getter
@Setter
@Builder
@AllArgsConstructor
public class GetNoticeResponseDto {
private String title;
private String content;
private LocalDate updatedAt;
}
Original file line number Diff line number Diff line change
@@ -1,23 +1,23 @@
package com.example.howmuch.exception.notice;

import com.example.howmuch.controller.AdminController;
import com.example.howmuch.controller.EventController;
import com.example.howmuch.exception.ErrorMessage;
import com.example.howmuch.exception.event.NotFoundEventException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

@Slf4j
@RestControllerAdvice(basePackageClasses = AdminController.class)
@RestControllerAdvice(basePackageClasses = {AdminController.class, EventController.class})
public class NoticeExceptionHandler {

private static final HttpStatus errorStatus = HttpStatus.BAD_REQUEST;

@ExceptionHandler(NotFoundNoticeException.class)
public ResponseEntity<ErrorMessage> notFoundNoticeException(
NotFoundEventException exception
NotFoundNoticeException exception
) {
return ResponseEntity.badRequest()
.body(ErrorMessage.of(exception, errorStatus));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,30 +71,38 @@ public GetStatisticsResponseDto getStatistics(String time) {
.map(AcEvent::toStatisticsListResponseDto)
.forEach(responseDtoList::add);

if (responseDtoList.isEmpty()) {
return null;
}

// 2. EventAt 최신순 정렬(내림차순)
responseDtoList.sort(Comparator.comparing(StatisticsListResponse::getEventAt).reversed());

// 3. Map 으로 날짜별 그룹화
Map<String, List<StatisticsListResponse>> resultList = responseDtoList.stream()
.collect(Collectors.groupingBy(dto -> dto.getEventAt().toString()));


// 4 - 1. acEvents 에서 가장 많은 비용을 지출한 eventCategory
Map<EventCategory, Long> totalPayByCategory = acEvents.stream()
.collect(Collectors.groupingBy(AcEvent::getEventCategory,
Collectors.summingLong(AcEvent::getPayAmount)));

// 4 - 2. 가장 많은 비용을 지출한 EventCategory를 찾습니다.
Optional<Map.Entry<EventCategory, Long>> maxEntry = totalPayByCategory.entrySet().stream()
.max(Map.Entry.comparingByValue());
OptionalLong maxPayAmount = totalPayByCategory.values().stream().mapToLong(Long::longValue).max();

List<String> maxPayCategories = totalPayByCategory.entrySet().stream()
.filter(entry -> entry.getValue().equals(maxPayAmount.orElseThrow()))
.map(entry -> entry.getKey().getCategoryName())
.collect(Collectors.toList());

// 5. GetStatisticsResponseDto 반환
return GetStatisticsResponseDto.builder()
.totalPayment(myEvents.stream().mapToLong(MyEvent::getTotalReceiveAmount).sum())
.totalReceiveAmount(myEvents.stream().mapToLong(MyEvent::getTotalReceiveAmount).sum())
.totalPayment(acEvents.stream().mapToLong(AcEvent::getPayAmount).sum())
.statisticsListResponse(resultList)
.mostEventCategory(maxEntry.orElse(null).getKey().getCategoryName())
.mostEventPayAmount(maxEntry.orElse(null).getValue())
.mostEventCategory(maxPayCategories)
.mostEventPayAmount(maxPayAmount.orElse(0L))
.build();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.example.howmuch.domain.entity.Notice;
import com.example.howmuch.domain.repository.NoticeRepository;
import com.example.howmuch.dto.notice.GetAllNoticeResponseDto;
import com.example.howmuch.dto.notice.GetNoticeResponseDto;
import com.example.howmuch.dto.notice.UpdateNoticeRequestDto;
import com.example.howmuch.dto.notice.createNoticeRequestDto;
import com.example.howmuch.exception.notice.NotFoundNoticeException;
Expand All @@ -11,42 +12,53 @@
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;

@Slf4j
@RequiredArgsConstructor
@Service
public class NoticeService {

private final NoticeRepository noticeRepository;

/* 공지사항 등록 */
@Transactional
public Long createNotice(createNoticeRequestDto request) {
return this.noticeRepository.save(request.toEntity())
.getId();
}

/* 공지사항 전체 조회 */
@Transactional(readOnly = true)
public List<GetAllNoticeResponseDto> getAllNotices() {
return this.noticeRepository.findAllNotices()
.stream()
.map(Notice::from)
.sorted(Comparator.comparing(Notice::getUpdatedAt).reversed()) // 수정 날짜 기준 내림차순
.map(Notice::toGetAllNoticeResponseDto)
.collect(Collectors.toList());
}

/* 공지사항 수정 */
@Transactional
public void updateNotice(Long id, UpdateNoticeRequestDto request) {
Notice notice = getNotice(id);
Notice notice = this.getNotice(id);
notice.updateNotice(request);
}

/* 공지사항 삭제 */
@Transactional
public void deleteNotice(Long id) {
Notice notice = getNotice(id);
Notice notice = this.getNotice(id);
this.noticeRepository.delete(notice);
}

/* 공지사항 상세 조회 */
@Transactional(readOnly = true)
public GetNoticeResponseDto getNoticeById(Long id) {
return this.getNotice(id).toGetNoticeResponseDto();
}

private Notice getNotice(Long id) {
return this.noticeRepository.findById(id)
.orElseThrow(() -> new NotFoundNoticeException("일치하는 공지사항 정보가 존재하지 않습니다."));
Expand Down

0 comments on commit eab5b21

Please sign in to comment.