diff --git a/build.gradle b/build.gradle
index 43232a4..8617aea 100644
--- a/build.gradle
+++ b/build.gradle
@@ -40,9 +40,6 @@ dependencies {
runtimeOnly 'com.h2database:h2'
- // Jakarta Validation API 의존성
-// implementation("jakarta.validation:jakarta.validation-api") // 최신 버전 사용 권장
-
// Spring Security 사용 시 필요한 의존성
implementation("org.springframework.boot:spring-boot-starter-security")
@@ -58,6 +55,11 @@ dependencies {
// enable production
implementation ("org.springframework.boot:spring-boot-starter-actuator")
+ // modelmapper
+ implementation 'org.modelmapper:modelmapper:2.4.4'
+
+ // swagger3
+ implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.2.0'
}
diff --git a/screenshots/Oauth2.png b/screenshots/Oauth2.png
new file mode 100644
index 0000000..a825fc6
Binary files /dev/null and b/screenshots/Oauth2.png differ
diff --git a/src/main/java/com/chukapoka/server/common/authority/AppConfig.java b/src/main/java/com/chukapoka/server/common/authority/AppConfig.java
new file mode 100644
index 0000000..46f442c
--- /dev/null
+++ b/src/main/java/com/chukapoka/server/common/authority/AppConfig.java
@@ -0,0 +1,22 @@
+package com.chukapoka.server.common.authority;
+
+
+import lombok.RequiredArgsConstructor;
+import org.modelmapper.ModelMapper;
+import org.modelmapper.convention.MatchingStrategies;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+@RequiredArgsConstructor
+public class AppConfig {
+ @Bean
+ public ModelMapper modelMapper() {
+ ModelMapper modelMapper = new ModelMapper();
+ /** 연결 전략 : 같은 타입의 필드명이 같은 경우만 동작 */
+ modelMapper.getConfiguration().setMatchingStrategy(MatchingStrategies.LOOSE).setSkipNullEnabled(true).setFieldMatchingEnabled(true)
+ .setAmbiguityIgnored(true) // id속성을 매핑에서 제외
+ .setFieldAccessLevel(org.modelmapper.config.Configuration.AccessLevel.PRIVATE);
+ return modelMapper;
+ }
+}
diff --git a/src/main/java/com/chukapoka/server/common/authority/SecurityConfig.java b/src/main/java/com/chukapoka/server/common/authority/SecurityConfig.java
index bfb6556..713b09a 100644
--- a/src/main/java/com/chukapoka/server/common/authority/SecurityConfig.java
+++ b/src/main/java/com/chukapoka/server/common/authority/SecurityConfig.java
@@ -1,9 +1,11 @@
package com.chukapoka.server.common.authority;
+import com.chukapoka.server.common.authority.jwt.JwtAuthenticationFilter;
+import com.chukapoka.server.common.authority.jwt.JwtTokenProvider;
import com.chukapoka.server.common.enums.Authority;
import com.chukapoka.server.common.repository.TokenRepository;
-import org.springframework.beans.factory.annotation.Autowired;
+import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@@ -11,6 +13,8 @@
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
+import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer;
+import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
@@ -18,33 +22,37 @@
@Configuration
@EnableWebSecurity
+@RequiredArgsConstructor
public class SecurityConfig {
/**
* Spring Security 6.1.0부터는 메서드 체이닝의 사용을 지양하고 람다식을 통해 함수형으로 설정하게 지향함
*/
- @Autowired
- private JwtTokenProvider jwtTokenProvider;
-
- @Autowired
- private TokenRepository tokenRepository;
+ private final JwtTokenProvider jwtTokenProvider;
+ private final TokenRepository tokenRepository;
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
+ /** rest api 설정 */
http
- .httpBasic(AbstractHttpConfigurer::disable)
- .csrf(AbstractHttpConfigurer::disable)
- .addFilterBefore(new JwtAuthenticationFilter(jwtTokenProvider, tokenRepository), UsernamePasswordAuthenticationFilter.class)
- .authorizeHttpRequests((authorizeRequests) -> {
- authorizeRequests
- .requestMatchers("/api/user/emailCheck", "/api/user", "/api/user/authNumber", "/api/health").anonymous()
-
- .requestMatchers("/api/user/logout", "api/user/reissue").hasRole(Authority.USER.getAuthority());// hasAnyRole은 "ROLE_" 접두사를 자동으로 추가해줌 하지만 Authority는 "ROLE_USER"로 설정해야했음 이것떄문에 회원가입할떄 권한이 안넘어갔음
- }
-
+ .headers(headers -> headers.frameOptions(HeadersConfigurer.FrameOptionsConfig::sameOrigin)) // h2 요소를 사용 비활성화
+ .httpBasic(AbstractHttpConfigurer::disable) // 기본 인증 로그인 비활성화
+ .logout(AbstractHttpConfigurer::disable) // 기본 로그아웃 비활성화
+ .formLogin(AbstractHttpConfigurer::disable) // 기본 로그인 비활성화
+ .csrf(AbstractHttpConfigurer::disable) // csrf 비활성화 -> cookie를 사용하지 않으면 꺼도 된다. (cookie를 사용할 경우 httpOnly(XSS 방어), sameSite(CSRF 방어)로 방어해야 한다.)
+ .sessionManagement(session ->
+ session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) // 세션관리 정책을 STATELESS(세션이 있으면 쓰지도 않고, 없으면 만들지도 않는다)
+ .addFilterAfter(new JwtAuthenticationFilter(jwtTokenProvider, tokenRepository), UsernamePasswordAuthenticationFilter.class);
+ /** request 인증, 인가 설정 */
+ http
+ .authorizeHttpRequests((authorizeRequests) -> {
+ authorizeRequests
+ .requestMatchers("/swagger-ui/**", "/v3/api-docs/**","/h2-console/**").permitAll()
+ .requestMatchers("/api/user/emailCheck", "/api/user", "/api/user/authNumber").anonymous()
+ .requestMatchers("/api/user/logout", "api/user/reissue", "api/tree/**","api/treeItem/**").hasRole(Authority.USER.getAuthority()// hasAnyRole은 "ROLE_" 접두사를 자동으로 추가해줌 하지만 Authority는 "ROLE_USER"로 설정해야했음 이것떄문에 회원가입할떄 권한이 안넘어갔음
- );
-
+ );
+ });
return http.build();
}
diff --git a/src/main/java/com/chukapoka/server/common/authority/SwaggerConfig.java b/src/main/java/com/chukapoka/server/common/authority/SwaggerConfig.java
new file mode 100644
index 0000000..d96a6ff
--- /dev/null
+++ b/src/main/java/com/chukapoka/server/common/authority/SwaggerConfig.java
@@ -0,0 +1,32 @@
+package com.chukapoka.server.common.authority;
+
+import io.swagger.v3.oas.models.Components;
+import io.swagger.v3.oas.models.OpenAPI;
+import io.swagger.v3.oas.models.info.Info;
+import io.swagger.v3.oas.models.security.SecurityRequirement;
+import io.swagger.v3.oas.models.security.SecurityScheme;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+/** swagger 의존성만 설정해도 자동 적용되지만, jwt 토큰값을 확인하기 위한 설정 */
+@Configuration
+public class SwaggerConfig {
+
+
+ /** SwaggerConfig의 openAPI 함수에 security schemes를 추가
+ * addList 부분과 addSecuritySchemes의 이름 부분은 변경이 가능하지만 둘 다 같은 이름이어야 함 */
+ @Bean
+ public OpenAPI openAPI(){
+ return new OpenAPI().addSecurityItem(new SecurityRequirement().addList("JWT"))
+ .components(new Components().addSecuritySchemes("JWT", createAPIKeyScheme()))
+ .info(new Info().title("Chukapoka API")
+ .description("This is Chukapoka API")
+ .version("v2.2.0"));
+ }
+ /** JWT를 적용하려면 confugure에 JWT SecurityScheme이 필요 */
+ private SecurityScheme createAPIKeyScheme() {
+ return new SecurityScheme().type(SecurityScheme.Type.HTTP)
+ .bearerFormat("JWT")
+ .scheme("bearer");
+ }
+}
diff --git a/src/main/java/com/chukapoka/server/common/authority/JwtAuthenticationFilter.java b/src/main/java/com/chukapoka/server/common/authority/jwt/JwtAuthenticationFilter.java
similarity index 95%
rename from src/main/java/com/chukapoka/server/common/authority/JwtAuthenticationFilter.java
rename to src/main/java/com/chukapoka/server/common/authority/jwt/JwtAuthenticationFilter.java
index 2668701..ab4c7be 100644
--- a/src/main/java/com/chukapoka/server/common/authority/JwtAuthenticationFilter.java
+++ b/src/main/java/com/chukapoka/server/common/authority/jwt/JwtAuthenticationFilter.java
@@ -1,4 +1,4 @@
-package com.chukapoka.server.common.authority;
+package com.chukapoka.server.common.authority.jwt;
import com.chukapoka.server.common.entity.Token;
import com.chukapoka.server.common.repository.TokenRepository;
@@ -25,7 +25,6 @@ public class JwtAuthenticationFilter extends GenericFilterBean {
public static final String AUTHORIZATION_HEADER = "Authorization";
public static final String BEARER_PREFIX = "Bearer";
-
private final JwtTokenProvider jwtTokenProvider;
private final TokenRepository tokenRepository;
@@ -36,8 +35,6 @@ public class JwtAuthenticationFilter extends GenericFilterBean {
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
// 1. Request Header 에서 토큰을 꺼냄
String accessToken = resolveToken((HttpServletRequest) request);
-// String data = tokenRepository.getAccessToken(token);
-// System.out.println("data = " + data);
// 2. validateToken 으로 토큰 유효성 검사
// 정상 토큰이면 해당 토큰으로 Authentication 을 가져와서 SecurityContext 에 저장
if (StringUtils.hasText(accessToken)) {
diff --git a/src/main/java/com/chukapoka/server/common/authority/JwtTokenProvider.java b/src/main/java/com/chukapoka/server/common/authority/jwt/JwtTokenProvider.java
similarity index 81%
rename from src/main/java/com/chukapoka/server/common/authority/JwtTokenProvider.java
rename to src/main/java/com/chukapoka/server/common/authority/jwt/JwtTokenProvider.java
index d1f83d6..df5383f 100644
--- a/src/main/java/com/chukapoka/server/common/authority/JwtTokenProvider.java
+++ b/src/main/java/com/chukapoka/server/common/authority/jwt/JwtTokenProvider.java
@@ -1,9 +1,10 @@
-package com.chukapoka.server.common.authority;
+package com.chukapoka.server.common.authority.jwt;
-
-import com.chukapoka.server.common.dto.CustomUser;
+import com.chukapoka.server.common.dto.CustomUserDetails;
import com.chukapoka.server.common.dto.TokenDto;
+import com.chukapoka.server.user.entity.User;
+import com.chukapoka.server.user.repository.UserRepository;
import io.jsonwebtoken.*;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.security.Keys;
@@ -19,8 +20,10 @@
import java.security.Key;
+import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
+import java.util.Optional;
import java.util.stream.Collectors;
@@ -35,12 +38,14 @@ public class JwtTokenProvider {
private static final long ACCESS_EXPIRATION_MILLISECONDS = 1000 * 60 * 30;
// Refresh Token 만료 시간 상수 (7일)
private static final long REFRESH_EXPIRATION_MILLISECONDS = 1000L * 60 * 60 * 24 * 7;
+ private UserRepository userRepository;
private final Key key;
// 비밀 키를 Base64 디코딩한 값으로 초기화
@Autowired
- public JwtTokenProvider(@Value("${jwt.secret}") String secretKey) {
+ public JwtTokenProvider(@Value("${jwt.secret}") String secretKey, UserRepository userRepository) {
byte[] keyBytes = Decoders.BASE64.decode(secretKey);
this.key = Keys.hmacShaKeyFor(keyBytes);
+ this.userRepository = userRepository;
}
/**
@@ -63,7 +68,7 @@ public TokenDto createToken(Authentication authentication) {
String accessToken = Jwts.builder()
.setSubject(authentication.getName())
.claim(AUTHORITIES_KEY, authorities) // 권한
- .claim(USER_KEY, ((CustomUser) authentication.getPrincipal()).getUserId())
+ .claim(USER_KEY, ((CustomUserDetails) authentication.getPrincipal()).getUserId())
.setIssuedAt(now)
.setExpiration(accessTokenExpiresIn) // 토큰이 만료될시간
.signWith(key, SignatureAlgorithm.HS256) // 비밀키, 암호화 알고리즘이름
@@ -74,7 +79,7 @@ public TokenDto createToken(Authentication authentication) {
String refreshToken = Jwts.builder()
.setSubject(authentication.getName())
.claim(AUTHORITIES_KEY, authorities) // 권한
- .claim(USER_KEY, ((CustomUser) authentication.getPrincipal()).getUserId()) // user id
+ .claim(USER_KEY, ((CustomUserDetails) authentication.getPrincipal()).getUserId()) // user id
.setIssuedAt(now)
.setExpiration(refreshExpiration)
.signWith(key, SignatureAlgorithm.HS256)
@@ -83,8 +88,9 @@ public TokenDto createToken(Authentication authentication) {
return TokenDto.builder()
.grantType(BEARER_TYPE)
.accessToken(accessToken)
- .accessTokenExpiresIn(accessTokenExpiresIn.getTime())
.refreshToken(refreshToken)
+ .atExpiration(formatDate(accessTokenExpiresIn))
+ .rtExpiration(formatDate(refreshExpiration))
.build();
}
@@ -94,7 +100,6 @@ public TokenDto createToken(Authentication authentication) {
/**
* JWT 토큰에서 사용자 정보를 추출하여 인증 객체를 반환하는 메서드
*/
-
public Authentication getAuthentication(String token) {
Claims claims = parseClaims(token);
@@ -112,8 +117,14 @@ public Authentication getAuthentication(String token) {
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toList());
+ // 데이터베이스에서 사용자 정보 조회
+ Optional userOptional = userRepository.findById(userId);
+ if (userOptional.isEmpty()) {
+ throw new RuntimeException("User not found for id: " + userId);
+ }
+ User user = userOptional.get();
// UserDetails 객체 생성
- UserDetails principal = new CustomUser(userId, claims.getSubject(), authorities);
+ UserDetails principal = new CustomUserDetails(user);
// UsernamePasswordAuthenticationToken을 사용하여 Authentication 객체 반환
return new UsernamePasswordAuthenticationToken(principal, "", authorities);
@@ -156,6 +167,11 @@ public boolean isTokenExpired(String token) {
Date expirationDate = claims.getExpiration();
return expirationDate != null && expirationDate.before(new Date());
}
+ /** 토큰 만료기한 날짜 포맷메서드 */
+ private String formatDate(Date date) {
+ SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMddHHmmss");
+ return dateFormat.format(date);
+ }
}
\ No newline at end of file
diff --git a/src/main/java/com/chukapoka/server/common/dto/CustomUser.java b/src/main/java/com/chukapoka/server/common/dto/CustomUser.java
deleted file mode 100644
index e4832d5..0000000
--- a/src/main/java/com/chukapoka/server/common/dto/CustomUser.java
+++ /dev/null
@@ -1,22 +0,0 @@
-package com.chukapoka.server.common.dto;
-
-import lombok.Getter;
-import org.springframework.security.core.GrantedAuthority;
-import org.springframework.security.core.userdetails.User;
-
-import java.util.Collection;
-
-/**
- * CustomUser 클래스는 Spring Security에서 제공하는 User 클래스를 확장하여 추가적인 사용자 정보를 저장하기 위한 클래스
- * 주로 사용자의 고유한 식별자(ID)를 추가로 저장하고자 할 때 사용
- */
-@Getter
-public class CustomUser extends User {
- private final Long userId;
-
- public CustomUser(Long userId, String password, Collection extends GrantedAuthority> authorities) {
- super(String.valueOf(userId), password, authorities);
- this.userId = userId;
- }
-
-}
diff --git a/src/main/java/com/chukapoka/server/common/dto/CustomUserDetails.java b/src/main/java/com/chukapoka/server/common/dto/CustomUserDetails.java
new file mode 100644
index 0000000..da48094
--- /dev/null
+++ b/src/main/java/com/chukapoka/server/common/dto/CustomUserDetails.java
@@ -0,0 +1,78 @@
+package com.chukapoka.server.common.dto;
+
+import com.chukapoka.server.user.entity.User;
+import lombok.Getter;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
+import org.springframework.security.core.userdetails.UserDetails;
+
+import java.util.Collection;
+import java.util.Collections;
+
+/**
+ * CustomUserDetails 클래스는 Spring Security에서 제공하는 User 클래스를 확장하여 추가적인 사용자 정보를 저장하기 위한 클래스
+ * 주로 사용자의 고유한 식별자(ID)를 추가로 저장하고자 할 때 사용
+ */
+@Getter
+public class CustomUserDetails implements UserDetails {
+
+ private final User user;
+
+ /** 일반 로그인 */
+ public CustomUserDetails(User user) {
+ this.user = user;
+ }
+
+ @Override
+ public Collection extends GrantedAuthority> getAuthorities() {
+ if (user == null) {
+ return Collections.emptyList(); // user가 null인 경우 빈 권한 목록 반환
+ }
+ return Collections.singleton(new SimpleGrantedAuthority(user.getAuthorities()));
+ }
+
+ public String getEmail() {
+ return user.getEmail();
+ }
+
+ public Long getUserId() {
+ return user.getId();
+ }
+
+ @Override
+ public String getPassword() {
+ return user.getPassword();
+ }
+
+ @Override
+ public String getUsername() {
+ if (user != null) {
+ return user.getId().toString();
+ }
+ return null; // 사용자 객체가 null인 경우 null 반환
+ }
+
+
+ /** 계정의 만료 여부 반환 (기한이 없으므로 항상 true 반환) */
+ @Override
+ public boolean isAccountNonExpired() {
+ return true;
+ };
+ /** 계정의 잠금 여부 반환 (잠금되지 않았으므로 항상 true 반환)*/
+ @Override
+ public boolean isAccountNonLocked() {
+ return true;
+ }
+ /** 자격 증명의 만료 여부 반환 (기한이 없으므로 항상 true 반환)*/
+ @Override
+ public boolean isCredentialsNonExpired() {
+ return true;
+ }
+ /** 계정의 활성화 여부 반환 (활성화된 계정이므로 항상 true 반환)*/
+ @Override
+ public boolean isEnabled() {
+ return true;
+ }
+
+
+}
diff --git a/src/main/java/com/chukapoka/server/common/dto/TokenDto.java b/src/main/java/com/chukapoka/server/common/dto/TokenDto.java
index 32d2b5e..d3f150d 100644
--- a/src/main/java/com/chukapoka/server/common/dto/TokenDto.java
+++ b/src/main/java/com/chukapoka/server/common/dto/TokenDto.java
@@ -13,6 +13,7 @@ public class TokenDto {
private String grantType; // JWT에 대한 인증 타입. 여기서는 Bearer를 사용. 이후 HTTP 헤더에 prefix로 붙여주는 타입
private String accessToken;
private String refreshToken;
- private Long accessTokenExpiresIn;
+ private String atExpiration;
+ private String rtExpiration;
}
diff --git a/src/main/java/com/chukapoka/server/common/entity/Token.java b/src/main/java/com/chukapoka/server/common/entity/Token.java
index f819e77..406e3eb 100644
--- a/src/main/java/com/chukapoka/server/common/entity/Token.java
+++ b/src/main/java/com/chukapoka/server/common/entity/Token.java
@@ -10,6 +10,8 @@
import lombok.Getter;
import lombok.NoArgsConstructor;
+import javax.crypto.KEM;
+
@Getter
@NoArgsConstructor
@Data
@@ -26,23 +28,25 @@ public class Token {
@Column(name = "rt_value")
private String rtValue; // refresh token
- // TODO: 현진 access token, refresh token 만료시간 컬럼 추가
+
+ // 만료 시간을 나타내는 컬럼 추가
+ @Column(name = "at_expiration")
+ private String atExpiration; // access token 만료 시간
+
+ @Column(name = "rt_expiration")
+ private String rtExpiration; // refresh token 만료 시간
+
@Builder
- public Token(String key, String atValue, String rtValue) {
+ public Token(String key, String atValue, String rtValue, String atExpiration, String rtExpiration) {
this.key = key;
this.atValue = atValue;
this.rtValue = rtValue;
+ this.atExpiration = atExpiration;
+ this.rtExpiration = rtExpiration;
}
-
- public Token updateValues(String accessToken, String refreshToken) {
- this.atValue = accessToken;
- this.rtValue = refreshToken;
- return this;
- }
-
+
public TokenResponseDto toResponseDto(){
return new TokenResponseDto(this.atValue);
}
-
}
diff --git a/src/main/java/com/chukapoka/server/common/enums/TreeType.java b/src/main/java/com/chukapoka/server/common/enums/TreeType.java
new file mode 100644
index 0000000..f95fa85
--- /dev/null
+++ b/src/main/java/com/chukapoka/server/common/enums/TreeType.java
@@ -0,0 +1,30 @@
+package com.chukapoka.server.common.enums;
+
+import lombok.Getter;
+
+import java.util.HashMap;
+import java.util.Map;
+
+@Getter
+public enum TreeType {
+ MINE("내트리"),
+ NOT_YET_SEND("미부여 트리");
+
+ private final String description;
+ private static final Map lookup = new HashMap<>();
+
+ static {
+ for (TreeType treeType : TreeType.values()) {
+ lookup.put(treeType.getDescription(), treeType);
+ }
+ }
+
+ TreeType(String description) {
+ this.description = description;
+ }
+
+ public static TreeType getByDescription(String description) {
+ return lookup.get(description);
+ }
+
+}
diff --git a/src/main/java/com/chukapoka/server/common/repository/TokenRepository.java b/src/main/java/com/chukapoka/server/common/repository/TokenRepository.java
index 0d58a39..52d3b3c 100644
--- a/src/main/java/com/chukapoka/server/common/repository/TokenRepository.java
+++ b/src/main/java/com/chukapoka/server/common/repository/TokenRepository.java
@@ -10,5 +10,4 @@ public interface TokenRepository extends JpaRepository {
Optional findByKey(String key);
Optional findByAtValue(String atValue);
-// String getAccessToken(String token);
}
diff --git a/src/main/java/com/chukapoka/server/common/service/CustomUserDetailsService.java b/src/main/java/com/chukapoka/server/common/service/CustomUserDetailsService.java
index 47ba13c..c979180 100644
--- a/src/main/java/com/chukapoka/server/common/service/CustomUserDetailsService.java
+++ b/src/main/java/com/chukapoka/server/common/service/CustomUserDetailsService.java
@@ -1,22 +1,15 @@
package com.chukapoka.server.common.service;
-import com.chukapoka.server.common.dto.CustomUser;
-import com.chukapoka.server.common.enums.Authority;
+import com.chukapoka.server.common.dto.CustomUserDetails;
import com.chukapoka.server.user.entity.User;
import com.chukapoka.server.user.repository.UserRepository;
import jakarta.transaction.Transactional;
import lombok.RequiredArgsConstructor;
-import org.springframework.security.core.GrantedAuthority;
-import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
-import java.util.Collection;
-import java.util.Collections;
-
-
@Service
@RequiredArgsConstructor
public class CustomUserDetailsService implements UserDetailsService {
@@ -32,10 +25,7 @@ public UserDetails loadUserByUsername(String email) throws UsernameNotFoundExcep
}
private UserDetails createUserDetails(User user) {
- return new CustomUser(user.getId(), user.getPassword(), getAuthorities());
+ return new CustomUserDetails(user);
}
- private Collection extends GrantedAuthority> getAuthorities() {
- return Collections.singletonList(new SimpleGrantedAuthority("ROLE_" + Authority.USER.getAuthority()));
- }
}
diff --git a/src/main/java/com/chukapoka/server/tree/controller/TreeController.java b/src/main/java/com/chukapoka/server/tree/controller/TreeController.java
new file mode 100644
index 0000000..e319445
--- /dev/null
+++ b/src/main/java/com/chukapoka/server/tree/controller/TreeController.java
@@ -0,0 +1,71 @@
+package com.chukapoka.server.tree.controller;
+
+import com.chukapoka.server.common.dto.BaseResponse;
+import com.chukapoka.server.common.dto.CustomUserDetails;
+import com.chukapoka.server.common.enums.ResultType;
+import com.chukapoka.server.tree.dto.TreeDetailResponseDto;
+import com.chukapoka.server.tree.dto.TreeListResponseDto;
+import com.chukapoka.server.tree.dto.TreeCreateRequestDto;
+import com.chukapoka.server.tree.dto.TreeModifyRequestDto;
+import com.chukapoka.server.tree.service.TreeService;
+import jakarta.validation.Valid;
+import lombok.AllArgsConstructor;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.web.bind.annotation.*;
+
+
+
+@RestController
+@AllArgsConstructor
+@RequestMapping("/api/tree")
+public class TreeController {
+
+ @Autowired
+ private final TreeService treeService ;
+
+ /**트리 생성 */
+ @PostMapping
+ public BaseResponsecreateTree(@Valid @RequestBody TreeCreateRequestDto treeRequestDto) {
+ long userId = ((CustomUserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal()).getUserId();
+ TreeDetailResponseDto responseDto = treeService.createTree(treeRequestDto, userId);
+ return new BaseResponse<>(ResultType.SUCCESS, responseDto);
+ }
+
+ /** 트리리스트 목록 */
+ @GetMapping
+ public BaseResponse treeList() {
+ long userId = ((CustomUserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal()).getUserId();
+ TreeListResponseDto responseDto = treeService.treeList(userId);
+ return new BaseResponse<>(ResultType.SUCCESS, responseDto);
+ }
+
+ /** 트리상세 정보 */
+ @GetMapping("/{treeId}")
+ private BaseResponse treeDetail(@PathVariable("treeId") String treeId) {
+ long userId = ((CustomUserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal()).getUserId();
+ TreeDetailResponseDto responseDto = treeService.treeDetail(treeId, userId);
+ return new BaseResponse<>(ResultType.SUCCESS, responseDto);
+ }
+
+ /** 트리 수정 */
+ @PutMapping("/{treeId}")
+ public BaseResponse treeModify(@PathVariable("treeId") String treeId,
+ @Valid @RequestBody TreeModifyRequestDto treeModifyDto) {
+ long userId = ((CustomUserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal()).getUserId();
+ TreeDetailResponseDto responseDto = treeService.treeModify(treeId,userId ,treeModifyDto);
+ return new BaseResponse<>(ResultType.SUCCESS, responseDto);
+ }
+
+
+ /** 트리 삭제 */
+ @DeleteMapping("/{treeId}")
+ public BaseResponse treeDelete(@PathVariable("treeId") String treeId) {
+ long userId = ((CustomUserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal()).getUserId();
+ treeService.treeDelete(treeId, userId);
+ return new BaseResponse<>(ResultType.SUCCESS, null);
+ }
+
+
+}
+
diff --git a/src/main/java/com/chukapoka/server/tree/dto/TreeCreateRequestDto.java b/src/main/java/com/chukapoka/server/tree/dto/TreeCreateRequestDto.java
new file mode 100644
index 0000000..ffea5be
--- /dev/null
+++ b/src/main/java/com/chukapoka/server/tree/dto/TreeCreateRequestDto.java
@@ -0,0 +1,43 @@
+package com.chukapoka.server.tree.dto;
+
+import com.chukapoka.server.common.annotation.ValidEnum;
+import com.chukapoka.server.common.enums.TreeType;
+import com.chukapoka.server.tree.entity.Tree;
+import jakarta.validation.constraints.NotBlank;
+import lombok.Data;
+
+import java.util.UUID;
+
+@Data
+public class TreeCreateRequestDto {
+ @NotBlank(message = "title is null")
+ private String title;
+ @NotBlank(message = "treeType is null")
+ @ValidEnum(enumClass = TreeType.class, message = "TreeType must be MINE or NOT_YET_SEND")
+ private String type;
+ private String treeBgColor;
+ private String groundColor;
+ private String treeTopColor;
+ private String treeItemColor;
+ private String treeBottomColor;
+ // 클라이언트에서는 입력받을 필요없음 ( TreeServicelmpl.createTree 에서 처리 )
+
+ /** Create Tree Build*/
+ public Tree toEntity(TreeCreateRequestDto treeRequestDto, long userId) {
+ UUID linkId = UUID.randomUUID();
+ UUID sendId = UUID.randomUUID();
+ return Tree.builder()
+ .title(treeRequestDto.title)
+ .type(treeRequestDto.type)
+ .linkId(linkId + "-link-"+ userId )
+ .sendId(sendId + "-send-"+ userId)
+ .treeBgColor(treeRequestDto.treeBgColor)
+ .groundColor(treeRequestDto.groundColor)
+ .treeTopColor(treeRequestDto.treeTopColor)
+ .treeItemColor(treeRequestDto.treeItemColor)
+ .treeBottomColor(treeRequestDto.treeBottomColor)
+ .updatedBy(userId)
+ .build();
+
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/chukapoka/server/tree/dto/TreeDetailResponseDto.java b/src/main/java/com/chukapoka/server/tree/dto/TreeDetailResponseDto.java
new file mode 100644
index 0000000..14248ca
--- /dev/null
+++ b/src/main/java/com/chukapoka/server/tree/dto/TreeDetailResponseDto.java
@@ -0,0 +1,67 @@
+package com.chukapoka.server.tree.dto;
+
+import com.chukapoka.server.tree.entity.Tree;
+import com.chukapoka.server.treeItem.entity.TreeItem;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class TreeDetailResponseDto {
+
+ /** 트리상세 정보 */
+ private String treeId;
+ private String title;
+ private String type; // MINE or NOT_YEN_SEND
+ private String linkId;
+ private String sendId;
+ private String treeBgColor;
+ private String groundColor;
+ private String treeTopColor;
+ private String treeItemColor;
+ private String treeBottomColor;
+ private Long updatedBy;
+ private LocalDateTime updatedAt;
+
+ /** treeItem 목록 */
+ private List treeItem;
+
+
+ /** 트리 생성, 수정 constructor */
+ public TreeDetailResponseDto(Tree tree) {
+ this.treeId = tree.getTreeId();
+ this.title = tree.getTitle();
+ this.type = tree.getType();
+ this.linkId = tree.getLinkId();
+ this.sendId = tree.getSendId();
+ this.treeBgColor = tree.getTreeBgColor();
+ this.groundColor = tree.getGroundColor();
+ this.treeTopColor = tree.getTreeTopColor();
+ this.treeItemColor = tree.getTreeItemColor();
+ this.treeBottomColor = tree.getTreeBottomColor();
+ this.updatedBy = tree.getUpdatedBy();
+ this.updatedAt = tree.getUpdatedAt();
+ }
+
+ /** 트리 상세정보 constructor */
+ public TreeDetailResponseDto(Tree tree, List treeItem) {
+ this.treeId = tree.getTreeId();
+ this.title = tree.getTitle();
+ this.type = tree.getType();
+ this.linkId = tree.getLinkId();
+ this.sendId = tree.getSendId();
+ this.treeBgColor = tree.getTreeBgColor();
+ this.groundColor = tree.getGroundColor();
+ this.treeTopColor = tree.getTreeTopColor();
+ this.treeItemColor = tree.getTreeItemColor();
+ this.treeBottomColor = tree.getTreeBottomColor();
+ this.updatedBy = tree.getUpdatedBy();
+ this.updatedAt = tree.getUpdatedAt();
+ this.treeItem = treeItem;
+ }
+}
diff --git a/src/main/java/com/chukapoka/server/tree/dto/TreeList.java b/src/main/java/com/chukapoka/server/tree/dto/TreeList.java
new file mode 100644
index 0000000..defffd3
--- /dev/null
+++ b/src/main/java/com/chukapoka/server/tree/dto/TreeList.java
@@ -0,0 +1,24 @@
+package com.chukapoka.server.tree.dto;
+
+import lombok.Data;
+import java.time.LocalDateTime;
+
+@Data
+public class TreeList {
+
+ /** 트리 리스트정보 */
+ private String treeId;
+ private String title;
+ private String type; // MINE or NOT_YEN_SEND
+ private String linkId;
+ private String sendId;
+ private Long updatedBy;
+ private LocalDateTime updatedAt;
+ /** 트리색상 부분 추가 가능 */
+ private String treeBgColor;
+ private String groundColor;
+ private String treeTopColor;
+ private String treeItemColor;
+ private String treeBottomColor;
+
+}
diff --git a/src/main/java/com/chukapoka/server/tree/dto/TreeListResponseDto.java b/src/main/java/com/chukapoka/server/tree/dto/TreeListResponseDto.java
new file mode 100644
index 0000000..8947a13
--- /dev/null
+++ b/src/main/java/com/chukapoka/server/tree/dto/TreeListResponseDto.java
@@ -0,0 +1,17 @@
+package com.chukapoka.server.tree.dto;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.List;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class TreeListResponseDto {
+
+ /** 트리 리스트 목록 */
+ private List treeList;
+}
+
+
diff --git a/src/main/java/com/chukapoka/server/tree/dto/TreeModifyRequestDto.java b/src/main/java/com/chukapoka/server/tree/dto/TreeModifyRequestDto.java
new file mode 100644
index 0000000..9e9cc19
--- /dev/null
+++ b/src/main/java/com/chukapoka/server/tree/dto/TreeModifyRequestDto.java
@@ -0,0 +1,24 @@
+package com.chukapoka.server.tree.dto;
+
+import com.chukapoka.server.common.annotation.ValidEnum;
+import com.chukapoka.server.common.enums.TreeType;
+import jakarta.validation.constraints.NotBlank;
+
+import lombok.Data;
+
+@Data
+public class TreeModifyRequestDto {
+
+ /** 트리에 관련된 것만 수정할것인지 ?*/
+ private String title;
+ @NotBlank(message = "treeType is null")
+ @ValidEnum(enumClass = TreeType.class, message = "TreeType must be MINE or NOT_YET_SEND")
+ private String type;
+ private String treeBgColor;
+ private String groundColor;
+ private String treeTopColor;
+ private String treeItemColor;
+ private String treeBottomColor;
+
+
+}
diff --git a/src/main/java/com/chukapoka/server/tree/entity/Tree.java b/src/main/java/com/chukapoka/server/tree/entity/Tree.java
new file mode 100644
index 0000000..96d5599
--- /dev/null
+++ b/src/main/java/com/chukapoka/server/tree/entity/Tree.java
@@ -0,0 +1,82 @@
+package com.chukapoka.server.tree.entity;
+
+import jakarta.persistence.*;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.hibernate.annotations.DynamicUpdate;
+import org.springframework.data.annotation.LastModifiedDate;
+
+import java.time.LocalDateTime;
+import java.util.concurrent.atomic.AtomicInteger;
+
+@Entity
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+@DynamicUpdate // 데이터의 변경사항이 있는 것만 수정
+@Builder
+@Table(name = "tb_tree")
+public class Tree {
+ @Id
+ @Column(name = "treeId", unique = true, nullable = false)
+ private String treeId;
+
+ /** 트리제목 */
+ @Column(name = "title")
+ private String title;
+
+ /** 내트리 || 미부여 트리 */
+ @Column(name = "type")
+ private String type;
+
+ /** 트리 링크를 특정하기 위한 id*/
+ @Column(name = "linkId", nullable = false, unique = true, length = 200)
+ private String linkId;
+
+ /** 타인에게 트리를 전달할 때 트리를 특정하기 위한 id */
+ @Column(name = "sendId", unique = true, length = 200)
+ private String sendId;
+
+ /** 트라 관련 색상은 String -> enum type으로 상수로 바꿔야 관리가 더 편할것같음 */
+ @Column(name = "treeBgColor", nullable = true)
+ private String treeBgColor;
+
+ @Column(name = "groundColor", nullable = true)
+ private String groundColor;
+
+ @Column(name = "treeTopColor", nullable = true)
+ private String treeTopColor;
+
+ @Column(name = "treeItemColor", nullable = true)
+ private String treeItemColor;
+
+ @Column(name = "treeBottomColor", nullable = true)
+ private String treeBottomColor;
+
+ /** userId가 값임 */
+ @Column(name = "updatedBy")
+ private Long updatedBy;
+
+ /** 생성 시간 */
+ @Column(name = "updatedAt", nullable = false)
+ @LastModifiedDate
+ private LocalDateTime updatedAt;
+
+ @PrePersist
+ public void prePersist() {
+ this.updatedAt = LocalDateTime.now();
+ if(this.treeId == null) {
+ this.treeId = TreeId();
+ }
+
+ }
+
+ private static final AtomicInteger counter = new AtomicInteger(0);
+ private static String TreeId() {
+ return "treeId" + counter.incrementAndGet();
+ }
+
+}
diff --git a/src/main/java/com/chukapoka/server/tree/repository/TreeRepository.java b/src/main/java/com/chukapoka/server/tree/repository/TreeRepository.java
new file mode 100644
index 0000000..47124b0
--- /dev/null
+++ b/src/main/java/com/chukapoka/server/tree/repository/TreeRepository.java
@@ -0,0 +1,23 @@
+package com.chukapoka.server.tree.repository;
+
+import com.chukapoka.server.tree.entity.Tree;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.stereotype.Repository;
+
+import java.util.List;
+import java.util.Optional;
+
+@Repository
+public interface TreeRepository extends JpaRepository {
+ Optional findByTreeIdAndUpdatedBy(String treeId, long userId);
+
+ List findAllByUpdatedBy(Long updatedBy);
+ /** treeList 조회 어떤 방법으로 할지 1. jpa @Query로 직접 찾기 2. jpa로 모두 찾은후 modelmapper로 맵핑할지
+ * @Query("SELECT new com.chukapoka.server.tree.dto.TreeList(tree.treeId, tree.title, tree.type, tree.linkId, tree.sendId, tree.updatedBy, tree.updatedAt) FROM Tree tree")
+ * List findAllTrees();
+ */
+
+
+
+}
diff --git a/src/main/java/com/chukapoka/server/tree/service/TreeService.java b/src/main/java/com/chukapoka/server/tree/service/TreeService.java
new file mode 100644
index 0000000..d71f568
--- /dev/null
+++ b/src/main/java/com/chukapoka/server/tree/service/TreeService.java
@@ -0,0 +1,25 @@
+package com.chukapoka.server.tree.service;
+
+import com.chukapoka.server.tree.dto.TreeDetailResponseDto;
+import com.chukapoka.server.tree.dto.TreeListResponseDto;
+import com.chukapoka.server.tree.dto.TreeCreateRequestDto;
+import com.chukapoka.server.tree.dto.TreeModifyRequestDto;
+
+public interface TreeService {
+
+ /** 트리 저장 */
+ TreeDetailResponseDto createTree(TreeCreateRequestDto treeRequestDto, long userId);
+
+ /** 트리리스트 조회(리스트용 모델) */
+ TreeListResponseDto treeList(long userId);
+
+ /** 트리 상세 정보 조회 (상세정보 모델) */
+ TreeDetailResponseDto treeDetail(String treeId, long userId);
+
+ /** 트리 수정 */
+ TreeDetailResponseDto treeModify(String treeId,long userId, TreeModifyRequestDto treeModifyDto);
+
+ /** 트리 삭제 */
+ void treeDelete(String treeId,long userId);
+
+}
diff --git a/src/main/java/com/chukapoka/server/tree/service/TreeServiceImpl.java b/src/main/java/com/chukapoka/server/tree/service/TreeServiceImpl.java
new file mode 100644
index 0000000..706d013
--- /dev/null
+++ b/src/main/java/com/chukapoka/server/tree/service/TreeServiceImpl.java
@@ -0,0 +1,88 @@
+package com.chukapoka.server.tree.service;
+
+
+import com.chukapoka.server.tree.dto.*;
+import com.chukapoka.server.tree.entity.Tree;
+import com.chukapoka.server.tree.repository.TreeRepository;
+import com.chukapoka.server.treeItem.entity.TreeItem;
+import com.chukapoka.server.treeItem.repository.TreeItemRepository;
+import jakarta.persistence.EntityNotFoundException;
+import lombok.AllArgsConstructor;
+import org.modelmapper.ModelMapper;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.time.LocalDateTime;
+import java.util.List;
+import java.util.stream.Collectors;
+
+
+@Service
+@AllArgsConstructor
+public class TreeServiceImpl implements TreeService{
+
+ private final TreeRepository treeRepository;
+ private final TreeItemRepository treeItemRepository;
+ private final ModelMapper modelMapper;
+
+ /** 트리생성 */
+ @Override
+ @Transactional
+ public TreeDetailResponseDto createTree(TreeCreateRequestDto treeRequestDto, long userId) {
+ // 클라이언트에서 입력 받을 필요없이 토큰으로 접속후 권한id로 셋팅
+ Tree tree = treeRequestDto.toEntity(treeRequestDto, userId);
+ treeRepository.save(tree);
+ return new TreeDetailResponseDto(tree);
+ }
+
+ /** 사용자 트리 리스트 조회(리스트용 모델) */
+ @Override
+ public TreeListResponseDto treeList(long userId) {
+ // modelMapper로 리스트 조회후 맵핑하는방법
+ List trees = treeRepository.findAllByUpdatedBy(userId);
+ List treeLists = trees.stream()
+ .map(tree -> modelMapper.map(tree, TreeList.class))
+ .collect(Collectors.toList());
+ return new TreeListResponseDto(treeLists);
+ }
+
+ /** 트리 상세 정보 조회 (상세정보 모델) */
+ @Override
+ public TreeDetailResponseDto treeDetail(String treeId, long userId) {
+ Tree tree = findTreeByIdOrThrow(treeId, userId);
+ // 트리에 속한 모든 TreeItem을 가져오기
+ List treeItems = treeItemRepository.findByTreeId(tree.getTreeId());
+ // 트리와 트리아이템 전체목록 반환
+ return new TreeDetailResponseDto(tree, treeItems);
+ }
+
+ /** 트리수정 */
+ @Override
+ @Transactional
+ public TreeDetailResponseDto treeModify(String treeId, long userId, TreeModifyRequestDto treeModifyDto) {
+ // 트리 아이디로 트리를 찾음
+ Tree tree = findTreeByIdOrThrow(treeId, userId);
+ // TreeModifyRequestDto를 Tree 엔티티로 변환하여 엔티티에 적용
+ modelMapper.map(treeModifyDto, tree);
+ tree.setUpdatedAt(LocalDateTime.now());
+ // 변경된 트리 저장
+ treeRepository.save(tree);
+ // 변경된 트리 상세 정보 반환
+ return modelMapper.map(tree, TreeDetailResponseDto.class);
+ }
+
+ /** 트리 삭제 */
+ @Override
+ @Transactional
+ public void treeDelete(String treeId, long userId) {
+ Tree tree = findTreeByIdOrThrow(treeId, userId);
+ treeRepository.delete(tree);
+ }
+
+
+ /** treeId Exception 처리 메서드 */
+ private Tree findTreeByIdOrThrow(String treeId, long userId) {
+ return treeRepository.findByTreeIdAndUpdatedBy(treeId, userId)
+ .orElseThrow(() -> new EntityNotFoundException("등록되지 않은 " + treeId + "입니다."));
+ }
+}
diff --git a/src/main/java/com/chukapoka/server/treeItem/controller/TreeItemController.java b/src/main/java/com/chukapoka/server/treeItem/controller/TreeItemController.java
new file mode 100644
index 0000000..353e0d9
--- /dev/null
+++ b/src/main/java/com/chukapoka/server/treeItem/controller/TreeItemController.java
@@ -0,0 +1,63 @@
+package com.chukapoka.server.treeItem.controller;
+
+import com.chukapoka.server.common.dto.BaseResponse;
+import com.chukapoka.server.common.dto.CustomUserDetails;
+import com.chukapoka.server.common.enums.ResultType;
+import com.chukapoka.server.treeItem.dto.TreeItemCreateRequestDto;
+import com.chukapoka.server.treeItem.dto.TreeItemDetailResponseDto;
+import com.chukapoka.server.treeItem.dto.TreeItemListResponseDto;
+import com.chukapoka.server.treeItem.dto.TreeItemModifyRequestDto;
+import com.chukapoka.server.treeItem.service.TreeItemService;
+import jakarta.validation.Valid;
+import lombok.AllArgsConstructor;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.web.bind.annotation.*;
+
+@RestController
+@AllArgsConstructor
+@RequestMapping("api/treeItem")
+public class TreeItemController {
+
+ private final TreeItemService treeItemService;
+ /** 트리아이템 생성 */
+ @PostMapping
+ public BaseResponse createTreeItem(@Valid @RequestBody TreeItemCreateRequestDto treeItemCreateRequestDto) {
+ long userId = ((CustomUserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal()).getUserId();
+ TreeItemDetailResponseDto responseDto = treeItemService.createTreeItem(treeItemCreateRequestDto, userId);
+ return new BaseResponse<>(ResultType.SUCCESS, responseDto);
+ }
+
+ /** 트리리스트 목록 */
+ @GetMapping
+ public BaseResponse treeItemList() {
+ long userId = ((CustomUserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal()).getUserId();
+ TreeItemListResponseDto responseDto = treeItemService.treeList(userId);
+ return new BaseResponse<>(ResultType.SUCCESS, responseDto);
+ }
+
+ /** 트리상세 정보 */
+ @GetMapping("/{treeItemId}")
+ private BaseResponse treeItemDetail(@PathVariable("treeItemId") String treeItemId) {
+ long userId = ((CustomUserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal()).getUserId();
+ TreeItemDetailResponseDto responseDto = treeItemService.treeDetail(treeItemId, userId);
+ return new BaseResponse<>(ResultType.SUCCESS, responseDto);
+ }
+
+ /** 트리 수정 */
+ @PutMapping("/{treeItemId}")
+ public BaseResponse treeItemModify(@PathVariable("treeItemId") String treeItemId,
+ @Valid @RequestBody TreeItemModifyRequestDto treeItemModifyDto) {
+ long userId = ((CustomUserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal()).getUserId();
+ TreeItemDetailResponseDto responseDto = treeItemService.treeModify(treeItemId, treeItemModifyDto, userId);
+ return new BaseResponse<>(ResultType.SUCCESS, responseDto);
+ }
+
+
+ /** 트리 삭제 */
+ @DeleteMapping("/{treeItemId}")
+ public BaseResponse treeItemDelete(@PathVariable("treeItemId") String treeItemId) {
+ long userId = ((CustomUserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal()).getUserId();
+ treeItemService.treeItemDelete(treeItemId, userId);
+ return new BaseResponse<>(ResultType.SUCCESS, null);
+ }
+}
diff --git a/src/main/java/com/chukapoka/server/treeItem/dto/TreeItemCreateRequestDto.java b/src/main/java/com/chukapoka/server/treeItem/dto/TreeItemCreateRequestDto.java
new file mode 100644
index 0000000..95b0268
--- /dev/null
+++ b/src/main/java/com/chukapoka/server/treeItem/dto/TreeItemCreateRequestDto.java
@@ -0,0 +1,37 @@
+package com.chukapoka.server.treeItem.dto;
+
+import com.chukapoka.server.tree.entity.Tree;
+import com.chukapoka.server.treeItem.entity.TreeItem;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+@Data
+public class TreeItemCreateRequestDto {
+
+ @NotNull(message = "treeId is null")
+ private String treeId;
+ @NotBlank(message = "title is null")
+ private String title;
+ @NotBlank(message = "content is null")
+ private String content;
+ @NotBlank(message = "treeItemColor is null")
+ private String treeItemColor;
+
+
+ public TreeItem toEntity(Tree tree, TreeItemCreateRequestDto treeItemCreateRequestDto, long userId) {
+ return TreeItem.builder()
+ .treeId(tree.getTreeId())
+ .title(treeItemCreateRequestDto.getTitle())
+ .content(treeItemCreateRequestDto.getContent())
+ .treeItemColor(treeItemCreateRequestDto.getTreeItemColor())
+ .updatedBy(userId)
+ .updatedAt(LocalDateTime.now())
+ .build();
+ }
+
+}
+
+
diff --git a/src/main/java/com/chukapoka/server/treeItem/dto/TreeItemDetailResponseDto.java b/src/main/java/com/chukapoka/server/treeItem/dto/TreeItemDetailResponseDto.java
new file mode 100644
index 0000000..1c8028d
--- /dev/null
+++ b/src/main/java/com/chukapoka/server/treeItem/dto/TreeItemDetailResponseDto.java
@@ -0,0 +1,33 @@
+package com.chukapoka.server.treeItem.dto;
+
+import com.chukapoka.server.treeItem.entity.TreeItem;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.time.LocalDateTime;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class TreeItemDetailResponseDto {
+
+ /** 트리 아이템 상세정보 */
+ private String id;
+ private String treeId;
+ private String title;
+ private String content;
+ private String treeItemColor;
+ private Long updatedBy;
+ private LocalDateTime updatedAt;
+
+ public TreeItemDetailResponseDto(TreeItem treeItem) {
+ this.id = treeItem.getId();
+ this.treeId = treeItem.getTreeId();
+ this.title = treeItem.getTitle();
+ this.content = treeItem.getContent();
+ this.treeItemColor = treeItem.getTreeItemColor();
+ this.updatedBy = treeItem.getUpdatedBy();
+ this.updatedAt = treeItem.getUpdatedAt();
+ }
+}
diff --git a/src/main/java/com/chukapoka/server/treeItem/dto/TreeItemListResponseDto.java b/src/main/java/com/chukapoka/server/treeItem/dto/TreeItemListResponseDto.java
new file mode 100644
index 0000000..5f5068f
--- /dev/null
+++ b/src/main/java/com/chukapoka/server/treeItem/dto/TreeItemListResponseDto.java
@@ -0,0 +1,14 @@
+package com.chukapoka.server.treeItem.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.List;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+public class TreeItemListResponseDto {
+ private List treeItem;
+}
diff --git a/src/main/java/com/chukapoka/server/treeItem/dto/TreeItemModifyRequestDto.java b/src/main/java/com/chukapoka/server/treeItem/dto/TreeItemModifyRequestDto.java
new file mode 100644
index 0000000..68e965b
--- /dev/null
+++ b/src/main/java/com/chukapoka/server/treeItem/dto/TreeItemModifyRequestDto.java
@@ -0,0 +1,11 @@
+package com.chukapoka.server.treeItem.dto;
+
+import lombok.Data;
+
+@Data
+public class TreeItemModifyRequestDto {
+
+ private String title;
+ private String content;
+ private String treeItemColor;
+}
diff --git a/src/main/java/com/chukapoka/server/treeItem/entity/TreeItem.java b/src/main/java/com/chukapoka/server/treeItem/entity/TreeItem.java
new file mode 100644
index 0000000..50784e0
--- /dev/null
+++ b/src/main/java/com/chukapoka/server/treeItem/entity/TreeItem.java
@@ -0,0 +1,62 @@
+package com.chukapoka.server.treeItem.entity;
+
+
+import jakarta.persistence.*;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.hibernate.annotations.DynamicUpdate;
+import org.springframework.data.annotation.LastModifiedDate;
+
+import java.time.LocalDateTime;
+import java.util.concurrent.atomic.AtomicInteger;
+
+@Entity
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+@Builder
+@DynamicUpdate // 데이터의 변경사항이 있는 것만 수정
+@Table(name = "tb_treeItem")
+public class TreeItem {
+ @Id
+ @Column(name = "treeItemId", unique = true, nullable = false)
+ private String id;
+
+ @Column(name = "treeId")
+ private String treeId;
+
+ /** 편지 제목 */
+ @Column(name = "title")
+ private String title;
+
+ /** 편지 내용*/
+ @Column(name = "content")
+ private String content;
+
+ /** 트리아이템 색상 */
+ @Column(name = "treeItemColor", nullable = true)
+ private String treeItemColor;
+
+ /** userId가 값임 */
+ @Column(name = "updatedBy")
+ private Long updatedBy;
+
+ /** 생성 시간 */
+ @Column(name = "updatedAt", nullable = false)
+ @LastModifiedDate
+ private LocalDateTime updatedAt;
+
+ @PrePersist // JPA에서는 엔티티의 생명주기 중 하나의 이벤트에 대해 하나의 @PrePersist 메서드만을 허용
+ public void prePersist() {
+ if (this.id == null) {
+ this.id = TreeItemId();
+ }
+ }
+ private static final AtomicInteger counter = new AtomicInteger(0);
+ private static String TreeItemId() {
+ return "treeItem" + counter.incrementAndGet();
+ }
+
+}
diff --git a/src/main/java/com/chukapoka/server/treeItem/repository/TreeItemRepository.java b/src/main/java/com/chukapoka/server/treeItem/repository/TreeItemRepository.java
new file mode 100644
index 0000000..364846f
--- /dev/null
+++ b/src/main/java/com/chukapoka/server/treeItem/repository/TreeItemRepository.java
@@ -0,0 +1,12 @@
+package com.chukapoka.server.treeItem.repository;
+
+import com.chukapoka.server.treeItem.entity.TreeItem;
+import org.springframework.data.jpa.repository.JpaRepository;
+
+import java.util.List;
+import java.util.Optional;
+
+public interface TreeItemRepository extends JpaRepository {
+ List findByTreeId(String treeId);
+ Optional findByIdAndUpdatedBy(String treeItemId, long userId);
+}
diff --git a/src/main/java/com/chukapoka/server/treeItem/service/TreeItemService.java b/src/main/java/com/chukapoka/server/treeItem/service/TreeItemService.java
new file mode 100644
index 0000000..5811bbe
--- /dev/null
+++ b/src/main/java/com/chukapoka/server/treeItem/service/TreeItemService.java
@@ -0,0 +1,23 @@
+package com.chukapoka.server.treeItem.service;
+import com.chukapoka.server.treeItem.dto.TreeItemCreateRequestDto;
+import com.chukapoka.server.treeItem.dto.TreeItemDetailResponseDto;
+import com.chukapoka.server.treeItem.dto.TreeItemListResponseDto;
+import com.chukapoka.server.treeItem.dto.TreeItemModifyRequestDto;
+
+public interface TreeItemService {
+
+ /** 트리 아이템 생성 */
+ TreeItemDetailResponseDto createTreeItem(TreeItemCreateRequestDto treeItemCreateRequestDto, long userId);
+
+ /** 트리아이템리스트 조회(리스트용 모델) */
+ TreeItemListResponseDto treeList(long userId);
+
+ /** 트리아이템 상세 정보 조회 (상세정보 모델) */
+ TreeItemDetailResponseDto treeDetail(String treeItemId, long userId);
+
+ /** 트리아이템 수정 */
+ TreeItemDetailResponseDto treeModify(String treeItemId, TreeItemModifyRequestDto treeItemModifyDto, long userId);
+
+ /** 트리아이템 삭제 */
+ void treeItemDelete(String treeItemId, long userId);
+}
diff --git a/src/main/java/com/chukapoka/server/treeItem/service/TreeItemServiceImpl.java b/src/main/java/com/chukapoka/server/treeItem/service/TreeItemServiceImpl.java
new file mode 100644
index 0000000..9dd36ce
--- /dev/null
+++ b/src/main/java/com/chukapoka/server/treeItem/service/TreeItemServiceImpl.java
@@ -0,0 +1,98 @@
+package com.chukapoka.server.treeItem.service;
+
+import com.chukapoka.server.tree.entity.Tree;
+import com.chukapoka.server.tree.repository.TreeRepository;
+import com.chukapoka.server.treeItem.dto.TreeItemCreateRequestDto;
+import com.chukapoka.server.treeItem.dto.TreeItemDetailResponseDto;
+import com.chukapoka.server.treeItem.dto.TreeItemListResponseDto;
+import com.chukapoka.server.treeItem.dto.TreeItemModifyRequestDto;
+import com.chukapoka.server.treeItem.entity.TreeItem;
+import com.chukapoka.server.treeItem.repository.TreeItemRepository;
+import jakarta.persistence.EntityNotFoundException;
+import lombok.AllArgsConstructor;
+import org.modelmapper.ModelMapper;
+
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.time.LocalDateTime;
+import java.util.List;
+import java.util.stream.Collectors;
+
+@Service
+@AllArgsConstructor
+public class TreeItemServiceImpl implements TreeItemService{
+
+ private final TreeItemRepository treeItemRepository;
+ private final TreeRepository treeRepository;
+ private final ModelMapper modelMapper;
+
+ /** 트리 아이템 생성 */
+ @Override
+ @Transactional
+ public TreeItemDetailResponseDto createTreeItem(TreeItemCreateRequestDto treeItemCreateRequestDto, long userId) {
+ String treeId = treeItemCreateRequestDto.getTreeId();
+ return saveTreeItem(treeId, userId, treeItemCreateRequestDto);
+ }
+
+
+ /** 트리아이템 (리스트) */
+ @Override
+ public TreeItemListResponseDto treeList(long userId) {
+ List treeItems = treeItemRepository.findAll();
+ List treeItemDetailResponseDtos = treeItems.stream()
+ .map(treeItem -> modelMapper.map(treeItem, TreeItemDetailResponseDto.class))
+ .collect(Collectors.toList());
+ return new TreeItemListResponseDto(treeItemDetailResponseDtos);
+
+ }
+
+ /** 트라이이템 (상세정보) */
+ @Override
+ public TreeItemDetailResponseDto treeDetail(String treeItemId, long userId) {
+ TreeItem treeItem = findTreeItemIdOrThrow(treeItemId, userId);
+ return new TreeItemDetailResponseDto(treeItem);
+ }
+
+ /** 트리아이템 수정 */
+ @Override
+ @Transactional
+ public TreeItemDetailResponseDto treeModify(String treeItemId, TreeItemModifyRequestDto treeItemModifyDto, long userId) {
+ TreeItem treeItem = findTreeItemIdOrThrow(treeItemId, userId);
+ modelMapper.map(treeItemModifyDto, treeItem);
+ treeItem.setUpdatedAt(LocalDateTime.now());
+ // 변경된 트리아이템 저장
+ treeItemRepository.save(treeItem);
+ // 변경된 트리아이템 상세 정보 반환
+ return modelMapper.map(treeItem, TreeItemDetailResponseDto.class);
+ }
+
+ /** 트리아이템 삭제 */
+ @Override
+ @Transactional
+ public void treeItemDelete(String treeItemId, long userId) {
+ TreeItem treeItem = findTreeItemIdOrThrow(treeItemId, userId);
+ treeItemRepository.delete(treeItem);
+ }
+
+ /** 트라이이템 저장 메서드 */
+ private TreeItemDetailResponseDto saveTreeItem(String treeId, long userId, TreeItemCreateRequestDto treeItemCreateRequestDto) {
+ // 트리 객체 조회
+ Tree tree = treeRepository.findById(treeId).orElseThrow(() -> new EntityNotFoundException("등록되지 않은 " + treeId + "입니다."));
+ // 트리 아이템 생성 및 저장
+ TreeItem treeItem = treeItemCreateRequestDto.toEntity(tree , treeItemCreateRequestDto, userId);
+ treeItemRepository.save(treeItem);
+ return new TreeItemDetailResponseDto(treeItem);
+ }
+
+ /** treeItemId Exception 처리 메서드 */
+ private TreeItem findTreeItemIdOrThrow(String treeItemId, long userId) {
+ return treeItemRepository.findByIdAndUpdatedBy(treeItemId, userId)
+ .orElseThrow(() -> new EntityNotFoundException("등록되지 않은 " + treeItemId + "입니다."));
+ }
+}
+
+
+
+
+
diff --git a/src/main/java/com/chukapoka/server/user/controller/HealthController.java b/src/main/java/com/chukapoka/server/user/controller/HealthController.java
deleted file mode 100644
index bed8451..0000000
--- a/src/main/java/com/chukapoka/server/user/controller/HealthController.java
+++ /dev/null
@@ -1,34 +0,0 @@
-package com.chukapoka.server.user.controller;
-
-
-import com.chukapoka.server.common.dto.BaseResponse;
-import com.chukapoka.server.common.dto.CustomUser;
-import com.chukapoka.server.common.dto.TokenResponseDto;
-import com.chukapoka.server.common.enums.NextActionType;
-import com.chukapoka.server.common.enums.ResultType;
-import com.chukapoka.server.user.dto.*;
-import com.chukapoka.server.user.sevice.AuthNumberService;
-import com.chukapoka.server.user.sevice.UserService;
-import jakarta.mail.MessagingException;
-import jakarta.validation.Valid;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.security.core.context.SecurityContextHolder;
-import org.springframework.web.bind.annotation.*;
-
-import java.io.UnsupportedEncodingException;
-
-@RestController
-@RequestMapping("/api")
-public class HealthController {
-
-
- /** 인증번호 요청 API */
- @GetMapping("/health")
- public BaseResponse authNumber() {
- return new BaseResponse<>(ResultType.SUCCESS, "health");
- }
-
-
-
-}
-
diff --git a/src/main/java/com/chukapoka/server/user/controller/UserController.java b/src/main/java/com/chukapoka/server/user/controller/UserController.java
index 6cdc92b..e20c78d 100644
--- a/src/main/java/com/chukapoka/server/user/controller/UserController.java
+++ b/src/main/java/com/chukapoka/server/user/controller/UserController.java
@@ -59,7 +59,7 @@ public BaseResponse authNumber(@RequestParam("email") Str
/** 토큰 재발급(refresh token 유효한 상태) */
@PostMapping("/reissue")
public BaseResponse reissue() {
- long userId = ((CustomUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal()).getUserId();
+ long userId = ((CustomUserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal()).getUserId();
TokenResponseDto tokenDto = userService.reissue(userId);
return new BaseResponse<>(ResultType.SUCCESS, tokenDto);
}
@@ -68,7 +68,7 @@ public BaseResponse reissue() {
@PostMapping("/logout")
public BaseResponse logout() {
// 인증된 사용자 Id
- long userId = ((CustomUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal()).getUserId();
+ long userId = ((CustomUserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal()).getUserId();
// 사용자의 ID를 기반으로 로그아웃 수행
ResultType logout = userService.logout(userId);
return new BaseResponse<>(ResultType.SUCCESS, logout);
diff --git a/src/main/java/com/chukapoka/server/user/dto/UserResponseDto.java b/src/main/java/com/chukapoka/server/user/dto/UserResponseDto.java
index 0d63ba0..edeed49 100644
--- a/src/main/java/com/chukapoka/server/user/dto/UserResponseDto.java
+++ b/src/main/java/com/chukapoka/server/user/dto/UserResponseDto.java
@@ -21,9 +21,4 @@ public class UserResponseDto {
private Long userId; // unique_userid
private TokenResponseDto token; // JWT 토큰
- public UserResponseDto(ResultType result, String email, Long userId) {
- this.result = result;
- this.email = email;
- this.userId = userId;
- }
}
diff --git a/src/main/java/com/chukapoka/server/user/entity/User.java b/src/main/java/com/chukapoka/server/user/entity/User.java
index b3317af..3cfc437 100644
--- a/src/main/java/com/chukapoka/server/user/entity/User.java
+++ b/src/main/java/com/chukapoka/server/user/entity/User.java
@@ -24,19 +24,19 @@ public class User {
@Column(name = "userId")
private Long id;
- @Column(nullable = false, unique = true)
+ @Column(nullable = false, name = "email", unique = true)
private String email;
- @Column(nullable = false)
+ @Column(nullable = false, name = "emailType")
private String emailType;
- @Column(nullable = false)
+ @Column(nullable = false, name = "password")
private String password;
- @Column(nullable = false)
+ @Column(nullable = false, name = "updateAt")
private LocalDateTime updatedAt;
- @Transient
+ @Column
private String authorities; // 권한 ROLE_USER || ROLE_ADMIN
@Builder
diff --git a/src/main/java/com/chukapoka/server/user/repository/UserRepository.java b/src/main/java/com/chukapoka/server/user/repository/UserRepository.java
index 9853b04..6c8bedb 100644
--- a/src/main/java/com/chukapoka/server/user/repository/UserRepository.java
+++ b/src/main/java/com/chukapoka/server/user/repository/UserRepository.java
@@ -20,10 +20,10 @@ public interface UserRepository extends JpaRepository {
Optional findByEmailAndEmailType(String email, String emailType);
- @Override
- Optional findById(Long aLong);
+ Optional findById(Long id);
// 이메일이 등록되어있는지 이메일과 이메일타입 확인
boolean existsByEmailAndEmailType(String email, String emailType);
+
}
diff --git a/src/main/java/com/chukapoka/server/user/sevice/UserService.java b/src/main/java/com/chukapoka/server/user/sevice/UserService.java
index 964f307..d4515f7 100644
--- a/src/main/java/com/chukapoka/server/user/sevice/UserService.java
+++ b/src/main/java/com/chukapoka/server/user/sevice/UserService.java
@@ -2,9 +2,8 @@
-import com.chukapoka.server.common.authority.JwtTokenProvider;
-
-import com.chukapoka.server.common.dto.CustomUser;
+import com.chukapoka.server.common.authority.jwt.JwtTokenProvider;
+import com.chukapoka.server.common.dto.CustomUserDetails;
import com.chukapoka.server.common.dto.TokenDto;
import com.chukapoka.server.common.dto.TokenResponseDto;
import com.chukapoka.server.common.entity.Token;
@@ -152,12 +151,7 @@ public User findUser(UserRequestDto userRequestDto) {
/** 유저정보에 따른 Authentication 생성 */
public Authentication getAuthentication(User user) {
return new UsernamePasswordAuthenticationToken(
- new CustomUser(
- user.getId(),
- user.getPassword(),
- List.of(
- new SimpleGrantedAuthority("ROLE" + Authority.USER.getAuthority()))
- ),
+ new CustomUserDetails(user),
null,
List.of(
new SimpleGrantedAuthority("ROLE_" + Authority.USER.getAuthority())
@@ -173,6 +167,8 @@ public TokenResponseDto saveToken(Authentication authentication){
.key(authentication.getName())
.atValue(jwtToken.getAccessToken())
.rtValue(jwtToken.getRefreshToken())
+ .atExpiration(jwtToken.getAtExpiration())
+ .rtExpiration(jwtToken.getRtExpiration())
.build();
return tokenRepository.save(token).toResponseDto();
diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml
index e401720..462daff 100644
--- a/src/main/resources/application.yaml
+++ b/src/main/resources/application.yaml
@@ -3,4 +3,4 @@ spring:
# active: prod
active: dev
# active: dev-db
-# active: local
+# active: local
\ No newline at end of file
diff --git a/swagger.yaml b/swagger.yaml
deleted file mode 100644
index c1c77f6..0000000
--- a/swagger.yaml
+++ /dev/null
@@ -1,90 +0,0 @@
-openapi: 3.0.3
-info:
- title: Vue-Springboot-Memo Api
- description: |-
- Vue Springboot client와 server 테스트
- https://github.com/doyou1/vue-spring-sampling
- version: 1.0.0
-servers:
- - url: http://jh-memo-env.eba-khreh2xt.ap-northeast-1.elasticbeanstalk.com/api
-tags:
- - name: /api/memo
- description: about memo
-paths:
- /api/memo:
- get:
- tags:
- - /api/memo
- summary: get memo list
- description: get memo list
- responses:
- '200':
- description: Successful operation
- content:
- application/json:
- schema:
- type: array
- items:
- $ref: '#/components/schemas/Memo'
- post:
- tags:
- - /api/memo
- summary: add a Memo item
- description: add a Memo item
- responses:
- '200':
- description: Successful operation
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/ApiResponse'
- /api/memo/{id}:
- put:
- tags:
- - /api/memo
- summary: update a Memo item
- description: update a Memo item
- parameters:
- - name: id
- in: path
- required: true
- description: id to update the Memo item
- schema:
- type : integer
- format: int64
- example: 10
- requestBody:
- required: true
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/Memo'
- responses:
- '200':
- description: Successful operation
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/ApiResponse'
-components:
- schemas:
- Memo:
- properties:
- id:
- type: integer
- format: int64
- example: 10
- content:
- type: string
- example: memo content
- isDone:
- type: boolean
- ApiResponse:
- type: object
- properties:
- id:
- type: integer
- format: int32
- example: 10
- isSuccess:
- type: boolean
\ No newline at end of file