Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

야간잔류 신청 기능 구현 #26

Merged
merged 23 commits into from
Nov 24, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
a608539
feat: 야간잔류 신청 기능 구현
ahyeonkong Nov 22, 2024
6d08133
fix: Merge 충돌 해결
ahyeonkong Nov 23, 2024
2cdb954
refactor: ApplicationController 예외 반환 로직 개선
ahyeonkong Nov 23, 2024
85f0e25
ApplicationRequest에서 필요없는 필드 삭제
ahyeonkong Nov 23, 2024
d04b675
refactor: 현재 로그인한 회원 정보 가져오는 로직으로 변경
ahyeonkong Nov 23, 2024
4586e4f
feat: CoParticipantResponse 응답 DTO 생성
ahyeonkong Nov 23, 2024
8f40ecf
feat: CoParticipantRequest 요청 DTO 생성
ahyeonkong Nov 23, 2024
7e6af98
refactor: MemberInfoResponse에 @Builder, @AllArgsConstructor 어노테이션 추가
ahyeonkong Nov 23, 2024
dfaf8ae
fix: 중복된 memberRepository 제거
holyPigeon Nov 23, 2024
d679f52
refactor: ApplicationController 응답 타입을 void로 변경
ahyeonkong Nov 24, 2024
f940741
refactor: Application 엔티티의 날짜 컬럼 변경 및 Member 매핑 컬럼 추가
ahyeonkong Nov 24, 2024
6845361
refactor: ApplicationRequest에서 날짜 변수 수정 및 builder 패턴 제거
ahyeonkong Nov 24, 2024
a1c7807
fix: MyPageService의 잘못된 import 변경
ahyeonkong Nov 24, 2024
3e889da
fix: 공동 참여자 정보 변수 수정 및 리스트 추가
ahyeonkong Nov 24, 2024
0deb19c
fix: builder 패턴 적용하여 로그인 정보와 직접 입력한 정보 호출 구현
ahyeonkong Nov 24, 2024
e3d773b
fix: 코드 충돌 해결
ahyeonkong Nov 24, 2024
705f650
Merge branch 'feat/#21' of https://github.com/TeamPu/TeamPu-Server in…
ahyeonkong Nov 24, 2024
32ab2e2
fix: 코드 충돌 해결
ahyeonkong Nov 24, 2024
691dbae
feat: CoParticipant 엔티티 생성
ahyeonkong Nov 24, 2024
441e3f9
fix: date를 appliedDate로 변경 및 coParticipants 컬럼 로직 수정
ahyeonkong Nov 24, 2024
04cbe5c
fix: ApplicationRequest에 status 변수 추가
ahyeonkong Nov 24, 2024
06b219e
refactor: ApplicationStatus의 enum 값을 json으로 직렬화/역직렬화
ahyeonkong Nov 24, 2024
506cd43
fix: Builder 패턴 로직에 memberId, coParticipants, status 추가
ahyeonkong Nov 24, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ repositories {
dependencies {
implementation 'com.h2database:h2'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-security'
// implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.boot:spring-boot-starter-web'
compileOnly 'org.projectlombok:lombok'
runtimeOnly 'com.mysql:mysql-connector-j'
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package com.kyonggi.teampu.domain.application.controller;

import com.kyonggi.teampu.domain.application.dto.ApplicationRequest;
import com.kyonggi.teampu.domain.application.dto.ApplicationResponse;
import com.kyonggi.teampu.domain.application.service.ApplicationService;
import com.kyonggi.teampu.global.response.ApiResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequiredArgsConstructor
@RequestMapping("/api/apply")
public class ApplicationController {
private final ApplicationService applicationService;

@PostMapping
public ResponseEntity<ApiResponse<ApplicationResponse>> createApplication(@RequestBody ApplicationRequest applicationRequest) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ResponseEntity와 ApiResponse는 거의 같은 기능이어서 한 번 더 감싸지 않아도 될 것 같습니다!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

넵! 좋습니다. ApiResponse에 상태 코드별 메서드를 만들면 더 간단하게 구현할 수 있을 것 같습니다.

// 400 에러 처리
if (applicationRequest.getMemberId() == null || applicationRequest.getStartTime() == null || applicationRequest.getEndTime() == null) {
return ResponseEntity
.status(HttpStatus.BAD_REQUEST)
.body(new ApiResponse<>(
new ApiResponse.Status(HttpStatus.BAD_REQUEST, "필수 값이 누락되었습니다"),
null
));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

따로 메시지를 첨부하려하니 관련 메서드가 없어서 구조가 이상해지는 것 같네요. 이 부분은 추가하겠습니다!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

넵 간단하게 추가해주세요! 더 필요한 부분이 있다면 제가 나중에 추가하겠습니다.

}

try {
ApplicationResponse applicationResponse = applicationService.createApplication(applicationRequest);
return ResponseEntity.ok(new ApiResponse<>(
new ApiResponse.Status(HttpStatus.CREATED, "신청이 완료되었습니다"),
applicationResponse
));
} catch (IllegalArgumentException e) {
// 401 에러 처리
return ResponseEntity
.status(HttpStatus.UNAUTHORIZED)
.body(new ApiResponse<>(
new ApiResponse.Status(HttpStatus.UNAUTHORIZED, "권한이 없습니다"),
null
));
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

try-catch 문을 반복적으로 사용해서 코드가 조금 길어지는 것 같습니다!

이 부분은 예외 상황에서 Http 응답이 아닌 예외만 반환하게 하고, 추후에 전역 예외 핸들러를 통해 통일된 Http 응답 형식으로 반환하는 방식으로 수정할 수 있을 것 같습니다.

@ExceptionHandler(RuntimeException.class)
public ApiResponse<Void> handle(RuntimeException exception, HttpServletRequest request) {
    logInfo(exception, request);

    return ApiResponse.exception(exception);
}

요런 느낌인데 나중에 시간나면 추가해볼게유

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

오 좋습니다! 일단 해당 코드는 주석 처리하고 나중에 예외 throw 하는 방식으로 바꾸겠습니다.

}
}
Original file line number Diff line number Diff line change
@@ -1,17 +1,7 @@
package com.kyonggi.teampu.domain.application.domain;

import com.kyonggi.teampu.domain.member.domain.Member;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;
import jakarta.persistence.*;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
Expand All @@ -20,6 +10,8 @@

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;

@Entity
@Getter
Expand All @@ -46,6 +38,16 @@ public class Application {
@Column(name = "status", nullable = false)
private ApplicationStatus status;

@Column(name = "participant_count")
private Integer participantCount;

@ElementCollection // JPA에서 값 타입 컬렉션을 매핑할 때 사용하는 어노테이션
@Column(name = "co_participant_names")
private List<String> coParticipantNames = new ArrayList<>();

@Column(name = "privacy_agreement")
private Boolean privacyAgreement;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "member_id")
private Member member;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package com.kyonggi.teampu.domain.application.dto;

import com.kyonggi.teampu.domain.application.domain.Application;
import com.kyonggi.teampu.domain.application.domain.ApplicationStatus;
import com.kyonggi.teampu.domain.member.domain.Member;
import com.kyonggi.teampu.domain.member.domain.MemberType;
import lombok.Getter;
import lombok.NoArgsConstructor;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;

@Getter
@NoArgsConstructor
public class ApplicationRequest {
private String name;
private MemberType type;
private Long memberId;
private String loginId;
private String phoneNumber;
private String email;
private Integer participantCount;
private List<String> coParticipantNames;
private Boolean privacyAgreement;
private LocalDateTime startTime;
private LocalDateTime endTime;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

회원이 로그인한 상태로 야간잔류를 신청한다는 걸 감안하면 request dto에서 신청자의 정보를 받을 필요 없이 현재 로그인된 회원의 정보를 그대로 가져올 수 있어서, 관련 필드 변수들을 빼도 될 것 같습니다!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

넵 수정하겠습니다.


public Application toEntity(Member member) {
return Application.builder()
.member(member)
.participantCount(participantCount)
.coParticipantNames(coParticipantNames)
.privacyAgreement(privacyAgreement)
.startTime(startTime)
.endTime(endTime)
.appliedDate(LocalDate.now())
.status(ApplicationStatus.PENDING)
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package com.kyonggi.teampu.domain.application.dto;

import com.kyonggi.teampu.domain.application.domain.Application;
import com.kyonggi.teampu.domain.member.domain.MemberType;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;

import java.time.LocalDateTime;
import java.util.List;

@Getter
@Builder
@AllArgsConstructor
public class ApplicationResponse {
private String loginId;
private String name;
private MemberType type;
private Long memberId;
private String phoneNumber;
private String email;
private Integer participantCount;
private List<String> coParticipantNames;
private Boolean privacyAgreement;
private LocalDateTime startTime;
private LocalDateTime endTime;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

여기서 회원 관련 정보는 히원 정보 DTO로 묶으면 좋을 것 같고, coParticipantNames도 단순 이름이 아닌 회원 정보 DTO 내용을 담으면 좋을 것 같습니다!
(관련 DTO 구현이 아직 해당 PR에는 반영되지 않아서 추후에 변경해도 괜찮을 것 같습니다.)

또한 participantCount의 경우, coParticipantNames.size() + 1로 자체 계산할 수 있을 것 같습니다.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

넵 수정하겠습니다! 감사합니다☺️


public static ApplicationResponse of(Application application) {
return ApplicationResponse.builder()
.loginId(application.getMember().getLoginId())
.name(application.getMember().getName())
.type(application.getMember().getType())
.memberId(application.getMember().getId())
.phoneNumber(application.getMember().getPhoneNumber())
.email(application.getMember().getEmail())
.participantCount(application.getParticipantCount())
.coParticipantNames(application.getCoParticipantNames())
.privacyAgreement(application.getPrivacyAgreement())
.startTime(application.getStartTime())
.endTime(application.getEndTime())
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.kyonggi.teampu.domain.application.repository;

import com.kyonggi.teampu.domain.application.domain.Application;
import org.springframework.data.jpa.repository.JpaRepository;

public interface ApplicationRepository extends JpaRepository<Application, Long> {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.kyonggi.teampu.domain.application.service;

import com.kyonggi.teampu.domain.application.domain.Application;
import com.kyonggi.teampu.domain.application.dto.ApplicationRequest;
import com.kyonggi.teampu.domain.application.dto.ApplicationResponse;
import com.kyonggi.teampu.domain.application.repository.ApplicationRepository;
import com.kyonggi.teampu.domain.member.domain.Member;
import com.kyonggi.teampu.domain.member.repository.MemberRepository;
import jakarta.transaction.Transactional;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
public class ApplicationService {
private final ApplicationRepository applicationRepository;
private final MemberRepository memberRepository;

@Transactional
public ApplicationResponse createApplication(ApplicationRequest request) {
Member member = memberRepository.findById(request.getMemberId())
.orElseThrow(() -> new IllegalArgumentException("회원을 찾을 수 없습니다"));

Application application = request.toEntity(member);
Application savedApplication = applicationRepository.save(application);
ApplicationResponse applicationResponse = ApplicationResponse.of(savedApplication);

return ApplicationResponse.of(applicationRepository.save(application));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.kyonggi.teampu.domain.member.repository;

import com.kyonggi.teampu.domain.member.domain.Member;
import org.springframework.data.jpa.repository.JpaRepository;

public interface MemberRepository extends JpaRepository<Member, Long> {
}
116 changes: 58 additions & 58 deletions src/main/java/com/kyonggi/teampu/global/config/SecurityConfig.java
Original file line number Diff line number Diff line change
@@ -1,58 +1,58 @@
package com.kyonggi.teampu.global.config;

import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.web.cors.CorsConfiguration;

import java.util.Arrays;
import java.util.Collections;
import java.util.List;

@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig {
private static final String[] PUBLIC_URLS = {
"/**"
};

@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration configuration) throws Exception {
return configuration.getAuthenticationManager();
}

@Bean
public BCryptPasswordEncoder bCryptPasswordEncoder() {
return new BCryptPasswordEncoder();
}

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
//CORS 설정
http.cors((cors) -> cors
.configurationSource(request -> {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Arrays.asList("http://localhost:3000", "http://localhost:8080"));
configuration.setAllowedMethods(Collections.singletonList("*"));
configuration.setAllowCredentials(true);
configuration.setAllowedHeaders(Collections.singletonList("*"));
configuration.setMaxAge(3600L);
configuration.setExposedHeaders(List.of("Authorization"));
return configuration;
}));

http.csrf(AbstractHttpConfigurer::disable);
http.formLogin(AbstractHttpConfigurer::disable);
http.httpBasic(AbstractHttpConfigurer::disable);

return http.build();
}
}
//package com.kyonggi.teampu.global.config;
//
//import lombok.RequiredArgsConstructor;
//import org.springframework.context.annotation.Bean;
//import org.springframework.context.annotation.Configuration;
//import org.springframework.security.authentication.AuthenticationManager;
//import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
//import org.springframework.security.config.annotation.web.builders.HttpSecurity;
//import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
//import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
//import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
//import org.springframework.security.web.SecurityFilterChain;
//import org.springframework.web.cors.CorsConfiguration;
//
//import java.util.Arrays;
//import java.util.Collections;
//import java.util.List;
//
//@Configuration
//@EnableWebSecurity
//@RequiredArgsConstructor
//public class SecurityConfig {
// private static final String[] PUBLIC_URLS = {
// "/**"
// };
//
// @Bean
// public AuthenticationManager authenticationManager(AuthenticationConfiguration configuration) throws Exception {
// return configuration.getAuthenticationManager();
// }
//
// @Bean
// public BCryptPasswordEncoder bCryptPasswordEncoder() {
// return new BCryptPasswordEncoder();
// }
//
// @Bean
// public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
// //CORS 설정
// http.cors((cors) -> cors
// .configurationSource(request -> {
// CorsConfiguration configuration = new CorsConfiguration();
// configuration.setAllowedOrigins(Arrays.asList("http://localhost:3000", "http://localhost:8080"));
// configuration.setAllowedMethods(Collections.singletonList("*"));
// configuration.setAllowCredentials(true);
// configuration.setAllowedHeaders(Collections.singletonList("*"));
// configuration.setMaxAge(3600L);
// configuration.setExposedHeaders(List.of("Authorization"));
// return configuration;
// }));
//
// http.csrf(AbstractHttpConfigurer::disable);
// http.formLogin(AbstractHttpConfigurer::disable);
// http.httpBasic(AbstractHttpConfigurer::disable);
//
// return http.build();
// }
//}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public static <T> ApiResponse<T> ok(T body) {

@Getter
@AllArgsConstructor
private static class Status {
public static class Status {
private HttpStatus httpStatus;
private String message;
}
Expand Down
4 changes: 4 additions & 0 deletions src/main/resources/application.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
spring:
h2:
console:
enabled: true
path: /h2-console
datasource:
url: jdbc:h2:mem:test
username: sa
Expand Down
Loading