Skip to content

Commit

Permalink
Merge pull request #113 from IT-Cotato/develop
Browse files Browse the repository at this point in the history
[Release] V2 2024.08.20.01
  • Loading branch information
Youthhing authored Aug 20, 2024
2 parents 464fe69 + 5810d17 commit 11ba54b
Show file tree
Hide file tree
Showing 32 changed files with 390 additions and 73 deletions.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ public class AttendanceController {
@Operation(summary = "출석 정보 변경 API")
@PatchMapping
public ResponseEntity<Void> updateAttendance(@RequestBody @Valid UpdateAttendanceRequest request) {
attendanceAdminService.updateAttendance(request);
attendanceAdminService.updateAttendanceByAttendanceId(request);
return ResponseEntity.noContent().build();
}

Expand Down Expand Up @@ -117,7 +117,7 @@ public ResponseEntity<AttendResponse> submitOnlineAttendanceRecord(@RequestBody

@Operation(summary = "부원의 기수별 출결 기록 반환 API")
@GetMapping("/records/members")
public ResponseEntity<MemberAttendanceRecordsResponse> findAllRecordsByGeneration(@RequestParam("generation-id") Long generationId ,
public ResponseEntity<MemberAttendanceRecordsResponse> findAllRecordsByGeneration(@RequestParam("generationId") Long generationId ,
@AuthenticationPrincipal Long memberId) {
return ResponseEntity.ok().body(attendanceRecordService.findAllRecordsBy(generationId, memberId));
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
package org.cotato.csquiz.api.attendance.dto;

import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import java.time.LocalTime;
import java.util.Objects;
import lombok.Builder;
import org.cotato.csquiz.domain.attendance.enums.DeadLine;

public record AttendanceDeadLineDto(
@Schema(example = "19:05:00")
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "HH:mm:ss")
LocalTime attendanceDeadLine,
@Schema(example = "19:20:00")
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "HH:mm:ss")
LocalTime lateDeadLine
) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,8 @@ public record UpdateAttendanceRequest(
DEFAULT_LATE_DEADLINE.getTime());
}
}

public static UpdateAttendanceRequest of(Long attendanceId, Location location, AttendanceDeadLineDto attendTime) {
return new UpdateAttendanceRequest(attendanceId, location, attendTime);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package org.cotato.csquiz.api.event.controller;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.cotato.csquiz.common.sse.SseService;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;

@Tag(name = "서버에서 발생할 이벤트 구독 요청 API")
@RestController
@RequestMapping("/v2/api/events")
@RequiredArgsConstructor
public class EventController {

private final SseService sseService;

@Operation(summary = "최초 로그인 시 출결 알림 구독 API")
@GetMapping(value = "/attendances", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public ResponseEntity<SseEmitter> subscribeAttendance(@AuthenticationPrincipal Long memberId) {
return ResponseEntity.ok().body(sseService.subscribeAttendance(memberId));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package org.cotato.csquiz.api.event.dto;

import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Builder;
import org.cotato.csquiz.domain.attendance.enums.AttendanceOpenStatus;

@Builder
public record AttendanceStatusInfo(
@Schema(description = "출결 PK", nullable = true)
Long attendanceId,
@Schema(description = "오픈 상태: 존재하면 OPEN, 없으면 CLOSED")
AttendanceOpenStatus openStatus
) {
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.cotato.csquiz.api.session.controller;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import java.util.List;
import lombok.RequiredArgsConstructor;
Expand Down Expand Up @@ -31,6 +32,7 @@
import org.springframework.web.bind.annotation.RestController;

@RestController
@Tag(name = "세션 정보", description = "세션 관련 API 입니다.")
@RequestMapping("/v1/api/session")
@RequiredArgsConstructor
@Slf4j
Expand All @@ -39,56 +41,60 @@ public class SessionController {
private final SessionService sessionService;
private final SessionImageService sessionImageService;

@Operation(summary = "Session 리스트 정보 얻기", description = "Get Session Infos")
@Operation(summary = "세션 목록 반환 API")
@GetMapping("")
public ResponseEntity<List<SessionListResponse>> findSessionsByGenerationId(@RequestParam Long generationId) {
return ResponseEntity.status(HttpStatus.OK).body(sessionService.findSessionsByGenerationId(generationId));
}

@Operation(summary = "Session 추가하기", description = "세션 추가하기")

@Operation(summary = "CS ON인 세션 목록 반환 API")
@GetMapping("/cs-on")
public ResponseEntity<List<CsEducationOnSessionNumberResponse>> findAllCsOnSessionsByGenerationId(
@RequestParam Long generationId) {
return ResponseEntity.status(HttpStatus.OK)
.body(sessionService.findAllNotLinkedCsOnSessionsByGenerationId(generationId));
}

@Operation(summary = "Session 추가 API")
@PostMapping(value = "/add", consumes = "multipart/form-data")
public ResponseEntity<AddSessionResponse> addSession(@ModelAttribute @Valid AddSessionRequest request)
throws ImageException {
return ResponseEntity.status(HttpStatus.CREATED).body(sessionService.addSession(request));
}

@Operation(summary = "세션 수정 API")
@PatchMapping(value = "/update")
public ResponseEntity<Void> updateSession(@RequestBody @Valid UpdateSessionRequest request) {
sessionService.updateSession(request);
return ResponseEntity.noContent().build();
}

@Operation(summary = "세션 숫자 변경 API")
@PatchMapping("/number")
public ResponseEntity<Void> updateSessionNumber(@RequestBody @Valid UpdateSessionNumberRequest request) {
sessionService.updateSessionNumber(request);
return ResponseEntity.noContent().build();
}

@Operation(summary = "Session 수정 - 사진 순서", description = "세션 사진 순서 바꾸기")
@Operation(summary = "세션 사진 순서 변경 API")
@PatchMapping("/image/order")
public ResponseEntity<Void> updateSessionImageOrder(@RequestBody UpdateSessionImageOrderRequest request) {
sessionImageService.updateSessionImageOrder(request);
return ResponseEntity.noContent().build();
}

@Operation(summary = "Session 수정 - 사진 추가하기", description = "세션 수정 시 사진 추가하기, photoId 반환")
@Operation(summary = "세션 사진 추가 API")
@PostMapping(value = "/image", consumes = "multipart/form-data")
public ResponseEntity<AddSessionImageResponse> additionalSessionImage(@ModelAttribute @Valid AddSessionImageRequest request)
throws ImageException {
return ResponseEntity.status(HttpStatus.CREATED).body(sessionImageService.additionalSessionImage(request));
}

@Operation(summary = "Session 수정 - 사진 삭제하기", description = "사진 삭제하기")
@Operation(summary = "세션 사진 삭제 API")
@DeleteMapping(value = "/image")
public ResponseEntity<Void> deleteSessionImage(@RequestBody DeleteSessionImageRequest request) {
sessionImageService.deleteSessionImage(request);
return ResponseEntity.noContent().build();
}

@GetMapping("/cs-on")
public ResponseEntity<List<CsEducationOnSessionNumberResponse>> findAllCsOnSessionsByGenerationId(
@RequestParam Long generationId) {
return ResponseEntity.status(HttpStatus.OK)
.body(sessionService.findAllNotLinkedCsOnSessionsByGenerationId(generationId));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,11 @@ public record AddSessionRequest(
@NotNull
LocalDate sessionDate,

@Schema(example = "17:00:00")
@Schema(example = "19:05:00")
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "HH:mm:ss")
LocalTime attendanceDeadLine,

@Schema(example = "17:00:00")
@Schema(example = "19:20:00")
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "HH:mm:ss")
LocalTime lateDeadLine,

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package org.cotato.csquiz.api.session.dto;

import java.time.LocalDate;
import org.cotato.csquiz.api.attendance.dto.AttendanceDeadLineDto;
import org.cotato.csquiz.domain.attendance.embedded.Location;
import org.cotato.csquiz.domain.generation.enums.CSEducation;
import org.cotato.csquiz.domain.generation.enums.DevTalk;
import org.cotato.csquiz.domain.generation.enums.ItIssue;
Expand All @@ -12,6 +15,11 @@ public record UpdateSessionRequest(
String title,
String description,
@NotNull
LocalDate sessionDate,
String placeName,
Location location,
AttendanceDeadLineDto attendTime,
@NotNull
ItIssue itIssue,
@NotNull
Networking networking,
Expand Down
19 changes: 19 additions & 0 deletions src/main/java/org/cotato/csquiz/common/config/SchedulerConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package org.cotato.csquiz.common.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;

@Configuration
public class SchedulerConfig {

@Bean
public TaskScheduler taskScheduler() {
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
scheduler.setPoolSize(10);
scheduler.setThreadNamePrefix("scheduled-task-");
scheduler.initialize();
return scheduler;
}
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
package org.cotato.csquiz.common.config;

import lombok.RequiredArgsConstructor;
import org.cotato.csquiz.common.config.filter.JwtAuthenticationFilter;
import org.cotato.csquiz.common.config.filter.JwtAuthorizationFilter;
import org.cotato.csquiz.common.config.filter.JwtExceptionFilter;
import org.cotato.csquiz.common.config.jwt.JwtTokenProvider;
import org.cotato.csquiz.common.config.jwt.RefreshTokenRepository;
import lombok.RequiredArgsConstructor;
import org.cotato.csquiz.common.error.handler.CustomAccessDeniedHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
Expand Down Expand Up @@ -38,6 +39,7 @@ public class SecurityConfig {
private final RefreshTokenRepository refreshTokenRepository;
private final CorsFilter corsFilter;
private final JwtAuthorizationFilter jwtAuthorizationFilter;
private final CustomAccessDeniedHandler customAccessDeniedHandler;

@Bean
public AuthenticationManager authenticationManager(HttpSecurity httpSecurity) throws Exception {
Expand All @@ -51,9 +53,11 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
AuthenticationManagerBuilder sharedObject = http.getSharedObject(AuthenticationManagerBuilder.class);
AuthenticationManager authenticationManager = sharedObject.build();
http.authenticationManager(authenticationManager);

http.cors();
http.exceptionHandling(exception ->
exception.accessDeniedHandler(customAccessDeniedHandler));
http.csrf().disable()
.cors().disable()
.formLogin().disable()
.addFilter(new JwtAuthenticationFilter(authenticationManager, jwtTokenProvider, refreshTokenRepository))
.addFilterBefore(jwtAuthorizationFilter, UsernamePasswordAuthenticationFilter.class)
Expand Down Expand Up @@ -83,6 +87,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
.requestMatchers("/v2/api/attendance").hasAnyRole("ADMIN")
.requestMatchers(new AntPathRequestMatcher("/v1/api/socket/token", "POST"))
.hasAnyRole("MEMBER", "EDUCATION", "ADMIN")
.requestMatchers("/v2/api/events/attendances").hasAnyRole("MEMBER", "ADMIN", "EDUCATION")
.requestMatchers("/v1/api/socket/**").hasAnyRole("EDUCATION", "ADMIN")
.anyRequest().authenticated()
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ public enum ErrorCode {
ENUM_NOT_RESOLVED(HttpStatus.BAD_REQUEST, "S-005", "입력한 Enum이 존재하지 않습니다."),
SCORER_LOCK_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "S-006", "득점자 락 획득 과정에서 에러 발생"),
IMAGE_CONVERT_FAIL(HttpStatus.INTERNAL_SERVER_ERROR, "S-007", "로컬 이미지 변환에 실패했습니다"),
SSE_SEND_FAIL(HttpStatus.INTERNAL_SERVER_ERROR, "S-008", "서버 이벤트 전송간 오류 발생"),
;

private final HttpStatus httpStatus;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package org.cotato.csquiz.common.error.handler;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;

@Slf4j
@Component
public class CustomAccessDeniedHandler implements AccessDeniedHandler {

@Override
public void handle(HttpServletRequest request, HttpServletResponse response,
AccessDeniedException accessDeniedException) throws IOException {
log.error("Access Denied Exception 발생");
response.sendError(HttpServletResponse.SC_FORBIDDEN, accessDeniedException.getMessage());
}
}
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
package org.cotato.csquiz.common.error.handler;

import com.amazonaws.services.s3.model.AmazonS3Exception;
import org.cotato.csquiz.common.error.exception.AppException;
import org.cotato.csquiz.common.error.exception.ImageException;
import org.cotato.csquiz.common.error.response.ErrorResponse;
import org.cotato.csquiz.common.error.response.MethodArgumentErrorResponse;
import org.cotato.csquiz.common.error.response.MethodArgumentErrorResponse.FieldErrorResponse;
import jakarta.persistence.EntityNotFoundException;
import jakarta.servlet.http.HttpServletRequest;
import java.sql.SQLException;
import java.util.List;
import lombok.extern.slf4j.Slf4j;
import org.cotato.csquiz.common.error.ErrorCode;
import org.cotato.csquiz.common.error.exception.AppException;
import org.cotato.csquiz.common.error.exception.ImageException;
import org.cotato.csquiz.common.error.response.ErrorResponse;
import org.cotato.csquiz.common.error.response.MethodArgumentErrorResponse;
import org.cotato.csquiz.common.error.response.MethodArgumentErrorResponse.FieldErrorResponse;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.HttpStatusCode;
Expand Down Expand Up @@ -94,4 +94,3 @@ public ResponseEntity<ErrorResponse> handleAmazonS3Exception(AmazonS3Exception e
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(errorResponse);
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@

@Component
public class CustomServletWrappingFilter extends OncePerRequestFilter {

private static final String EVENT_PATH = "/v2/api/events";

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
Expand All @@ -20,4 +23,9 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse

responseWrapper.copyBodyToResponse();
}

@Override
protected boolean shouldNotFilter(HttpServletRequest request) {
return request.getRequestURI().startsWith(EVENT_PATH);
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package org.cotato.csquiz.common.S3;
package org.cotato.csquiz.common.s3;

import com.amazonaws.auth.AWSStaticCredentialsProvider;
import com.amazonaws.auth.BasicAWSCredentials;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package org.cotato.csquiz.common.S3;
package org.cotato.csquiz.common.s3;

import static org.cotato.csquiz.common.util.FileUtil.extractFileExtension;
import static org.cotato.csquiz.common.util.FileUtil.isImageFileExtension;
Expand Down
Loading

0 comments on commit 11ba54b

Please sign in to comment.